In continuation to reverse engineering malware series, this is the fifth post. I will recommend that you read my first, second, third and fourth posts to be in sync with whole exercise.
In previous posts, we performed behavioral and code analysis of the malware specimen - slackbot. We identified that the bot executable was packed with UPX packer. Since UPX has native unpacking capabilities as well, we had unpacked the specimen exe and learnt more about its code & operations during code analysis. Subsequently we were able to gain control over the bot.
What if, the malware exe was packed with a packer which has no native unpacking capabilities. In such a case, the exe will have to be extracted manually. In this post, I will cover how Packing works and take you through manually unpacking a UPX-packed exe.
Original program ---> passes through ---> Packer ---> New program [ Unpacking routine + packed original program ]
Packing is simply compressing and / or encrypting the executable. The actual code in a packed executable is obfuscated as well as the overall file size is reduced. This creates two prime benefits to a malware creator / attacker / user:
A packed executable has 2 components:
- Low probability to get identified by AV / malware scanners.
- More difficult to analyze since the actual code is now obfuscated.
- Easy distribution and faster loading into memory becomes possible due to low file size, for example, in drive-by downloads, trojans etc.
- Unpacker routine
- Packed original code
The packer compresses and / or encrypts the original program and creates a new executable. The new executable carries the unpacking routine and the packed original program. The unpacking routine is responsible to unpack the original program when the new, packed exe is run. The original packed exe is unpacked into the memory of the system when unpacking stub is run. Prior to this, the original program can not be fully read in clear.
How does a [ UPX ] packed executable run?
Generally speaking, once a packed exe is run, the packed code [ both the unpacking routine and the packed original program code ] is loaded up in the memory. The program run starts at Original Entry Point [ OEP ]. In simple terms, OEP is like the main() function, in C, C++, C# etc programming languages. That is, OEP is the starting point of the program - the first instruction from where the execution will begin.
In case of a packed exe, the OEP points to the start of unpacking routine. This is because unless the unpacking routine executes, the original code can't be unpacked. When the unpacking routine has finished its run, the execution pointer jumps to the first instruction of the original program. This jump can be a simple JMP or may be tricky utilizing SEH / CALL / RET. Post jump, the actual unpacked program runs.
When UPX packs an executable, it consolidates all exe sections - .text, .data, .idata, etc - into one section called UPX1. This section UPX1 also contains the unpacking / decompression program stub. There are 2 other sections called UPX0 - has nothing - and UPX2 - has data & imports table.
This is the reason that when will try to open a packed exe in a disassembler such as IDA, it will throw an error, basically cos right now, it can't differentiate between code and data.
IDA will still load the packed exe. If we look at the program in text view, we will find IDA automatically identifies & marks UPX sections appropriately.
UPX0 is uninitalized space in the start. At runtime, the stub in UPX1 will decompress the packed code to the UPX0. After this, a Jump is made to the start of UPX0, where now the original program resides. At this point, the EIP [ Instruction pointer ] points to the first instruction of the unpacked program.
PUSHAD pushes contents of all the registers on to the stack.
POPAD pops out the contents from the stack back into the registers.
JMP takes the instruction pointer to the start of unpacked code.
JMP takes the instruction pointer to the start of unpacked code.
Import Address Table (IAT) -> Simply put, a program has dependencies on dynamic link libraries [ DLLs ] and loads the required DLLs at runtime. The memory locations of these DLLs are dynamic. Therefore, it is not feasible to hard-code memory address of functions in these DLLs, into the program. Import Address Table comes to the rescue. IAT is a table of pointers to functions in the required DLLs. So, whenever a compiled program has to access a specific function, it can do so by making a CALL to the appropriate IAT record. Packers generally damage / modify the original IAT of the packed program so the incorrectly dumped program code fails to run. This basically means, a packer modifies the IAT by defining afresh which dlls and functions need to be loaded and how & where to put the pointers in order to ensure normal run of the original program, post unpacking.
The IAT is accessed by a CALL[<address_to_pointer>] instruction.
Okay, let's roll up our sleeves and start with dumping the unpacked code directly from the memory.
Fire up OllyDbg and load the packed exe. Do not press the 'Play' button. Observe that when we loaded the packed exe into Olly, it has halted at the address 00408760 - PUSHAD instruction. This is program entry point [ look at the bottom, message bar in Olly ]. We already know that UPX1 constitutes of the unpacking routine and the packed program code. So, this address 00408760 seems to be the start address of unpacking routine.
Note this address down somewhere.
Now Check the Memory Map by clicking on the 'M' button in the menu bar. Memory Map is simply a mapping between a loaded executable / library and the memory regions. You will find section UPX0 starts at address 00401000, and UPX1 from address 00407000.
This is how it flows. Starting from PUSHAD at 00408760, the unpacking routine runs and unpacks the program code. In addition to this, the Import Address Table is fixed so the original unpacked program code may run good. CALL DWORD PTR DS:[ESI+8094] in the figure below is referring to the Import Address Table.
When the unpacking code run is finished, and IAT is fixed, POPAD instruction pushes out the contents of registers present on stack, which was initially put by PUSHAD, back into the registers. Finally a JMP is made to the freshly, unpacked code. In our case, as per screen above, JMP happens at address 004088AF ---> JMP fid.004011CB.
OllyDump identifies the start address, entry point and show UPX section info automatically. Now we need to modify the entry point. Remember, you noted the Program Entry Point 00408760 earlier. The actual unpacked code starts at 004011CB. So, we will modify the entry point to 004011CB. Dump the code now, and we save it as fid_unpacked.exe.
004011CB must be the address where the unpacked code starts.
Go to this address using Ctl+G.Once at this address, we will use a plugin for OllyDbg - OllyDump - to dump the code. Go to Plugins menu -> OllyDump -> Dump debugged process.
Cool! We were able to manually unpack the specimen. Now that we have the unpacked specimen, we can perform code analysis using techniques shared in Code Analysis post.
Let's confirm if the exe we've just dumped is indeed unpacked.
If you have any questions or feedback, do post a comment below.