Developing PE file packer step-by-step. Step 4. Running

Previous step is here.

So, from previous steps we have working packer and basic unpacker, which does nothing yet. At this step we will make run simple packed programs (which have nothing, except import table and possibly relocations). First thing to do in addition to data uncompressing is to fix original file import table. Usually the loader does that, but for now we play its role for compressed file.

Let's add several fields to our packed_file_info structure:

We added 4 fields which will be useful for the unpacker. Now we have to fill them in packer code:

Everything is simple here. At the second step, if you remember, I calculated manually the total size of all original file sections and explained, that it is equivalent to the value, returned by get_size_of_image function. Here we used it. That's all with packer for now. Now we turn to the unpacker (unpacker project). We need to compile LZO1Z algorithm into it, I did it in a simple and stupid way - I moved all the files required for lzo1z_decompress function compilation (in particular: lzo1z_d1.c, lzo1x_d.ch, config1z.h, config1x.h, lzo_conf.h, lzo_ptr.h, lzo1_d.ch, miniacc.h)to unpacker project. Besides that, I added include directory to the project: ../../lzo-2.06/include. Further I had to dig into project settings. When using memset, memcpy and similar functions (and we are going to use them more then once) Visual C++ can insert whole CRT into resulting exe file at its own discretion, which is absolutely unnecessary for us. I had to turn off intrinsic (internal) functions (C/C++ - Optimization - Enable Intrinsic Functions - No) and full optimization (C/C++ - Optimization - Whole Program Optimization - No), add libcmt.lib to ignored libraries list (Linker - Input - Ignore Specific Default Libraries - libcmt.lib) just in case and to turn off code generation at the linking stage (Linker - Optimization - Link Time Code Generation - Default). As we turned off all internal functions (with memset and memcpy among them) we need our own implementations now. We add two files to the project: memcpy.c and memset.c. I copied source code of functions with same names from CRT to these files:

Another issue is awaiting us here. There are four modules in our code now (four files with source code, .c and .cpp), and after compilation we will have four object (obj) files. The linker will have to assembly this to one exe file somehow, and it will do this. However, it will place these modules in exe file in an arbitrary order. But we need unpacker_main function to be placed at the beginning of unpacker code. We patch it in packer, do you remember? This problem can be solved easily. We create text file containing the following:

We call it link_order.txt and put it to the folder with unpacker project sources. This file will provide the resulting file functions order to linker. Let's set this file in project settings Linker - Optimization - Function Order - link_order.txt. That's all, the setup is completed, let's develop the unpacker!

Firstly, I increased the amount of data allocated on stack up to 256 bytes (sub esp, 256). There are a lot of local variables, so let's get reinsured, if suddenly 128 will be insufficient.

Let's add unpacker function prototype to the beginning of unpacker.cpp file:

We can use it in code now. Further we will need VirtualAlloc function (to allocate memory), VirtualProtect (to change memory pages attributes) and VirtualFree (to release allocated memory). Let's import them from kernel32.dll:

This piece of code is similar to code at step 3, where we loaded user32.dll and got MessageBoxA function address in it, so I will not explain it again here. Then we should move necessary variables to local scope, which were stored by the packer for us:

We did this because packed_file_info structure in the beginning of first packed file section will be overwritten with real unpacked data soon. Now we allocate memory and unpack compressed data block to it:

We don't need to initialize LZO algorithm before unpacking, it is enough to call one function to unpack, and we did that. Further we have to calculate first section header virtual address.

Now we have virtual address of section headers. We need to overwrite them, to make them similar to original file headers. Before doing this it is necessary to manage some more things:

We begin to restore section headers:

Section headers have been restored, let's restore their data now:

So, everything is almost ready. To launch the unpacked file successfully, we just have to fix its import table, playing a role of PE loader again. At first let's fix import table virtual address and size in PE header:

Fill import table:

That's all, we, as PE loader, filled PE file import table. There are couple of things remaining:

Now you understand, why we need to write our own function prologue and epilogue in assembler. Instead of ret instruction, which was placed at the end of unpacker code, we put jmp eax instruction, which performs jump to original file code.

So, the packer can process simple PE file now, which has import table only. Any file with resources, TLS, exports will not work, and we will manage this at the next steps. But we can pack ourselves and run the packed file!

As you can see, we packed ourselves, got the binary file packed_simple_pe_packer.exe, and it works!

Complete solution with all projects for this step: Own PE Packer Step 4

Leave a Reply

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