With the latest TitanEngine release, we introduced new functions which enable decompression of content packed with aPLib and LZMA. Today we will use those functions to make a static decompressor for AHPack. But before we do that we must answer a simple question: "What is the difference between regular static unpackers and static decompressors?"
Simply put, regular static unpackers are only used to unpack "simple" crypters which don't compress data in order to decrease the encrypted file size. In contrast, in the case where some data is compressed, unpacking must decompress that data, therefore we call such unpackers static decompressors. Static decompression can be used to unpack both PE packers and installer formats since similar unpacking logic is used for both.
The Unpacker we are making today will be a static decompressor, since AHPack uses aPLib compressionto decrease the file size. Furthermore we are "killing two birds with one stone" since both AHPack and !EPPack are based on the same source code base and can be unpacked the same way. If you open any of the provided samples in OllyDBG you'll see the packed file entry point:
PUSHAD PUSH 00407054 ;String: kernel32.dll MOV EAX,[KERNEL32.GetModuleHandleA] CALL NEAR DWORD PTR DS:[EAX] PUSH 004070B3 ;String: GlobalAlloc PUSH EAX MOV EAX,[KERNEL32.GetProcAddress] CALL NEAR DWORD PTR DS:[EAX] PUSH 3000 ;Virtual size of first section PUSH 40 CALL NEAR EAX MOV DWORD PTR DS:[4070CA],EAX MOV EDI,EAX MOV ESI,00401000 ;Virtual offset of first section PUSHAD ;aPLib decompression CLD MOV DL,80 XOR EBX,EBX MOVS BYTE PTR ES:[EDI],BYTE PTR DS:[ESI] ... POPAD ;aPLib decompression end MOV ECX,2FFC ;copy decompressed data to first section L002: MOV EBX,DWORD PTR DS:[EAX+ECX] MOV DWORD PTR DS:[ECX+401000],EBX LOOPD L002
This first part of the packer code quite clearly shows what the packer does. First it allocates a temporary memory buffer to store decompressed data, then decompresses the content of the first section to it. After the content is decompressed it is written to its original location, which, in this case, is first section's memory. The packer only compresses the first section since all compilers create PE files with a code section as the first file section. Resources, imports, relocations and TLS data isn't compressed, it is just realigned to new physical location after the size of first section decreases. In order to decompress the file we must do the following:
- Decompress the content of the first section
- Move the content of all other sections (including overlay) by the size needed to write decompressed content
- Write decompressed data to first section and correct its physical size
- Fix section data pointers to correctly point to the new section location for the remaining sections
After this we have to fix imports, correct the entry point address, and optionally delete the last section. We have already said the imports are not compressed, but that doesn't mean that this packer doesn't process imports. This code here does exactly that:
MOV EDX,00400000 MOV ESI,445C ;Address of first IID ADD ESI,EDX MOV EAX,DWORD PTR DS:[ESI+C] TEST EAX,EAX JE 00407277 ADD EAX,EDX MOV EBX,EAX PUSH EAX MOV EAX,[KERNEL32.GetModuleHandleA] CALL NEAR DWORD PTR DS:[EAX] ... AND EBX,0FFFFFFF PUSH EBX PUSH DWORD PTR DS:[4070CE] MOV EAX,[KERNEL32.GetProcAddress] CALL NEAR DWORD PTR DS:[EAX] MOV DWORD PTR DS:[EDI],EAX ADD DWORD PTR DS:[4070D2],4 JMP SHORT 00407218 ADD ESI,14 MOV EDX,00400000 JMP 004071E5
You can see that it's very simple code that just goes through the normal import table and fills its content. The data we need from here is address of the first IID, which will be used to find out the size of the import table or the number of IIDs present in the import table. Keep in mind that last IID will be empty, since that is the way import table is described in PECOFF. Since this table is valid we can use these two values to fix it. Simply by setting ImportTableAddress and ImportTableSize values in the PE header, we fix the import table in the unpacked file. Last thing we need to do is read the address of the entry point which can be found here:
PUSH EDX CALL NEAR EAX POPAD MOV EAX,004012C0 ;Address of entry point ... PUSH ECX RET
Writing an unpacker for AHPack is fairly complex, since there are a few details to worry about. It provides an interesting challenge for any reverser and it shows the potential of TitanEngine's new static unpacking function. As always unpacker, source code and the samples are included with the blog. Until next week...
(package contains unpacker binary, source and samples used)