Writing static decompressors, aPLib story

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...

RL!deAHPack
(package contains unpacker binary, source and samples used)

More News