Developing PE file packer step-by-step. Step 7. Relocations

Previous step is here. By the way, there was a bug in the code, I fixed it. It appeared when PE file had more than one callback.

Let's turn to the next important part of many PE files - relocations. They are used, when it is impossible to load an image to the base address indicated in its header. Mainly this is a typical behavior for DLL files (basically they can't work properly without relocations). Imagine that EXE file is being loaded to 0x400000 address. This EXE file loads the DLL, which is also loaded to this address. The addresses are the same, and the loader will look for DLL file relocations, because the DLL is loaded after the EXE. If there will be no relocations, loading will fail.

The relocations themselves are just the set of the tables with pointers to DWORDs, which should be calculated by the loader, if the image is loaded by an address other than base. There are many types of relocations, but actually only two are used in x86 (PE): IMAGE_REL_BASED_HIGHLOW = 3 and IMAGE_REL_BASED_ABSOLUTE = 0, and the second one does nothing, it is required only for relocation tables alignment.

I will just say, that loader loads EXE files to base address almost always, without using relocation. Our packer is unable to pack DLL yet, so to test relocation packing we should create an EXE file with incorrect base address, and then the loader will have to change it and apply relocations. I will not provide the test project source code here, you will find it in solution at the end of the article. I set 0x7F000000 as a base address (Linker - Advanced - Base Address).

We have to process relocations manually, like everything else, after unpacking a file. We should notify the loader in advance, that the file has relocations. Also, we need to know a new address, to which the loader moved the file.

We don't have to do anything to notify the loader, that our file has relocations - we still have necessary flags set in PE file headers left from original file. However, we need to know at which address the file was loaded.

Let's start with the unpacker code (unpacker project). To see, at which address the file should be loaded, and where the file was actually loaded, we can do the following:

We added a variable, which meaning is completely similar to original_image_base variable, which was introduced at one of previous steps. The difference is that we will apply relocations to original_image_base variable and thus understand at which real address the image was loaded. So we will not have to edit all the following operations in the unpacker, which we perform using this variable. And contents of original_image_base_no_fixup variable will not be modified, thereby we keep the address the image should be loaded to. This variable will be written by the packer for the unpacker, as other two.

Modify parameters.h file in the unpacker and update offsets to these three variables:

Now, as always, modify packer packed_file_info structure (simple_pe_packer project) by adding two fields to it:

Further, as we did with imports and resources:

After the line:

we add the following one:

which stores the image loading base address value to the variable recently added to the unpacker. At this stage after any file packing original_image_base and original_image_base_no_fixup variables will have the same values. We need to make the loader fix this variable if the image is relocated in memory. We will write the code after these lines:

So,

Everything is simple here - we just make relocation table of single element and add it to PE file.

Besides that we have to replace the lines:

to:

to prevent the last data bytes used to initialize thread local memory from overwriting relocations, which we place right after these bytes. Also we have to replace lines

with

to make the section be stripped only in case we are not going to put relocation data to it. We have to remove the line we added before:

to prevent relocation directory from being removed from file (image.rebuild_relocations call fill it in such way, that it points to a new relocation directory).

All we have to do is to process original file relocations in the unpacker (unpacker project):

I placed this code right before TLS processing code in the unpacker. We act as a loader. After making ourselves sure that a file was moved and that it has a relocation directory, we enumerate all relocation tables (or, in other words, fixups) and all relocations in each table. We calculate values at each address pointed by fixups. If, for example, DWORD at address, which has to be recalculated, contained the value 0x800000, PE file base loading address was 0x400000, and actually it was loaded to the address 0x500000, then we calculate new value using formula [0x800000 - 0x400000 + 0x500000] = 0x900000.

It's funny, by the way, I mentioned earlier, that MSVC++ does not allow to declare and initialize variables at the same time in naked function body. It turned out, that it is correct only for function scope. If we make a new nested scope, everything works. Thus, the code

will fail to build, but

works fine.

At this point work with relocations is completed, and any file, which has relocations and even wrong base loading address, like example in solution, will run. But there is also something: if a file has TLS in addition to relocations, we are going to fail. Absolute, not relative, addresses are used in TLS directory (IMAGE_TLS_DIRECTORY32 structure), thus we have to move them, if the loader placed an image at the address different from base loading address, set in PE file header. Besides that, TLS callback addresses, if they exist, are also absolute and they have to be fixed.

Before working on TLS relocations I wondered how to test this. I was not willing to build binaries with TLS and relocations manually. Thus I modified relocation testing example (reloc_test), which I mentioned above, and linked it using free linker UniLink. This is, probably, the only linker, which is able to build TLS with callbacks. The source code of the example is now as follows:

I will explain what this example does. Two callbacks will be called when process starts - tls_callback and tls_callback2. Two MessageBox'es will open with texts: "Process Callback!" and "Process Callback 2!". After that the following will be displayed in console:

Relocation test 123, 456
x

At last, after 2 seconds a new thread will be created, and TLS callbacks will be called again, but they will show MessageBox'es with texts: "Thread Callback!" and "Thread Callback 2!", and after 2 seconds the program finishes. Here we test relocations and TLS processing of our packer in full. To build this program, at first let's compile this source (right mouse button on main.cpp file - Compile). We will get main.obj file and pass it to UniLink linker by typing such command in console:

This command tells ulink.exe linker that main.exe file should be made from main.obj file. Its base loading address should be set to 0x7F000000 (to certainly make the loader apply the relocations). -B- option is used to add the relocations. After executing the command we will get a file with invalid base loading address, relocations and TLS with callbacks. Perfect for testing!

Let's turn to the packer project (simple_pe_packer). We move first_callback_offset variable to wider scope by replacing the lines:

to

and adding the lines

before

Further, after the lines

write TLS relocation code:

We added fixups for all non-zero fields of IMAGE_TLS_DIRECTORY32 structure, which contain absolute addresses. If we have TLS callbacks, we add a relocation also for our empty TLS callback absolute address. The most interesting thing is that we have nothing to edit in the unpacker, because it will process original file relocations, thus recalculating original TLS callback addresses, and it will call them only after that. The only thing I had to do is to increase the amount of memory reserved by the unpacker on stack, because it is not enough already. (I replaced sub esp, 256 command to sub esp, 4096, to be sure).

We test our packer on our hardcore example main.exe and make sure that everything works fine.

By this moment I have already tested current version of the packer on main EXE files of following applications: IrfanView, HM NIS Edit, Firefox, Notepad++, NSIS, Opera (it should be renamed to opera.exe after packing), Winamp, WinDjView, ResEd, Quake3, CatMario, Media Player Classic, Windows Media Player. They still work after packing!

Finally, I will note, that there is a remark in UPX source code comments, stating that if relocations and TLS are located in same section, the loader will not fix the addresses in TLS. Obviously, our main.exe sample is written this way, and curiously enough everything works on Windows XP and 7 (I didn't test it on other systems).

Full solution for this step: Own PE Packer Step 7

Leave a Reply

Your email address will not be published. Required fields are marked *