Unpacking UPX manually
I presented this tutorial at cybersec meetup 2600 Kazakhstan.
For example, take any file and pack it with UPX.
Make sure the file is packed. This can be done in several ways.
1) Using Detect it easy!
As we can see, it shows that the file is packed with UPX (3.94)
2) With PEid
Here we also see UPX.
3) Using PEView. PEView gives information about PE Header, from which we can see that the binary contains UPX sections.
Another way to understand that something is wrong with the file is the difference between the values of SizeOfRawData (the size of the data on the disk) and VirtualSize (the amount of memory you need to allocate for the section). For example, in the UPX0 section SizeOfRawData is 0. It is already abnormal. VirtualSize = 67000, which tells us that the section is filled with data at run time.
4) If you open the file in IDA, then we get the following message:
IDA tells us that it could not identify the standard sections in the file (.text, .data, …)
5) Looking at the import table, using the Dependency Walker, we see only the functions LoadLibrary, GetProcAddress. Such a set of functions indicates that the import table is restored during execution. Each library is loaded manually (LoadLibrary) and the addresses of each function are retrieved (GetProcAddress)
We determined that the file is packed, now we will try to unpack it manually, using IDA. To unpack the file we need to do 3 things: 1) Debug exe until the data is unpacked 2) Resolve the import table 3) Change the OEP (Original Entry Point) value in the file to the original
Open the file in IDA by ticking the “Manual load” and removing the “Create imports segment” (if checked, IDA will convert .idata section definitions to “extrn” directives and truncate it).
Next, we will be asked to specify the Image Base (you can see it, using PEView, in IMAGE_OPTIONAL_HEADER). Then we load all sections. Now our task is to find when the PUSHA/PUSHAD instruction is executed (Push the values of all 32-bit general-purpose registers to the stack). Start execution and put breakpoint on PUSHAD.
With this instruction, the process of unpacking the UPX begins, so all register values are saved. Accordingly, after unpacking, the POPA instruction must be executed, which will restore the previous values. We have to put a breakpoint on this instruction. This can be done in two ways:
1) Text search for the word POPA, set breakpoint, continue execution until the trigger.
2) We execute the PUSHA instruction (that is, the EIP is on the line after the PUSHA). Go to the ESP address, by pressing the arrow opposite the ESP in the list of registers.
We put Hardware on write breakpoint in the size of 4 bytes. As an alternative, you can also put a breakpoint to the ESP + 4 address. Do not forget to remove the breakpoint after it has been triggered. The point is that you put a breakpoint on ESP before unpacking. When everything is unpacked, ESP will return to the previous value and POPA will be executed and the breakpoint will work.
After triggering a breakpoint on POPA, somewhere nearby we should see TAIL JMP.
TAIL JMP is an instruction that redirects execution to OEP. A unique pattern of jump address is its remoteness from the current address. This happens because the data is unpacked in the UPX1 section and placed in the UPX0. It was possible to understand that unpacking occurs in the UPX0 section by its properties SizeOfRawData = 0 and VirtualSize. Various plug-ins that search for OEP rely on this transition property and determine when execution in one section goes to execution in another.
Now, we can execute the jmp instruction.
At this moment, we are on OEP, so it is necessary to make a dump of the program unpacked in memory. We do this with the help of Lord PE. We start LordPE, then find our process and dump it.
Open the resulted dump file in the PE Editor (LordPE) and set OEP address (we have it equal to 57703 (RVA)). This is the address we see after following Tail JMP. Save changes.
Open the dump file in Rebuild PE (Lord PE) to correct alignments, etc.
Run the dump, open ImpRec to correct the import table. In ImpRec, select our process, click “IAT AutoSearch”, “Fix Dump”. Finally, you can compare the import table of the original file and the resulting one.Вверх