11.16
After a few weeks we return to building unpackers with an interesting packer called Packman. Even though this is a pretty straight forward packer there are a few details that make us learn a trick or two while working on this unpacker. Most interesting detail about how one could find a loaded base for the module with just some simple math waits for us in the first few instructions, here:
PUSHAD CALL L002 L002: POP EBX LEA EBX,DWORD PTR DS:[EBX-3A] ADD DWORD PTR DS:[EBX],EBX
That's is? This code can find on which base loaded module was loaded? It can and here is why. Once the first call and POP EBX execute EBX will be equal to address on which EBX is located. That doesn't get us closer to our loaded base address but next two instructions do. Once LEA executes EBX will be pointing to data inside the same section where the entry point resides. Data at that pointer is 0xFFFF8A30 which is equal to 0x004075D0 - 0x00400000 which is the EBX data minus the default image base. Since math for this is EBX - ImageBase = Delta reversing this would be EBX + Delta = ImageBase. And from this it is simple to figure out that as long as the EBX changes and delta remains the same with this simple math formula we can always calculate the loaded base of the file. Quite a neat trick.
MOV BYTE PTR DS:[ESI],0E9 MOV EAX,DWORD PTR DS:[EBX+C] ;0xFFFF9CB7 MOV DWORD PTR DS:[ESI+1],EAX
What comes next is also interesting and very simple. Code above is a dynamic entry point jump generation. Since at that point ESI always point to the address of the packed entry point data that will be written to that address changes its code content. New instruction to be written instead of fist PUSHAD is a jump to the entry point. Since the relative distance between packed and original entry point will never change there is no need to recalculate this jump and we can always write the same data for that jump regardless of the file loaded base. This works of course because jumps are relative to their location. However in orderĀ to get this location we can do several things:
- Set a breakpoint on the JUMP to packed entry point right after POPAD and single step twice to get to the entry point
- Place a breakpoint at the packed entry point at some point and wait for it to hit and then single step to get to entry point
- Read newly created entry point jump (or data used to write it) and recalculate the entry point jump placing a hardware breakpoint there
Any of the solutions above is a good choice and you can use any of those to place the final entry point breakpoint. But we are getting ahead of ourselves since we still need to work out relocations and imports before we even get to the entry point.
First thing is first, imports. And so this code blob does everything we need to know about import handling in PackMan.
L000: ADD EAX,DWORD PTR DS:[EBX] PUSH EAX CALL NEAR DWORD PTR SS:[EBP] ;GetModuleHandleA MOV EDI,DWORD PTR DS:[ESI] ADD EDI,DWORD PTR DS:[EBX] JMP L017 L006: BTR ECX,1F JB L011 ADD ECX,DWORD PTR DS:[EBX] INC ECX INC ECX L011: PUSH EAX PUSH ECX PUSH EAX CALL NEAR DWORD PTR SS:[EBP+4] ;GetProcAddress STOS DWORD PTR ES:[EDI] POP EAX L017: MOV ECX,DWORD PTR DS:[EDI] TEST ECX,ECX JNZ L006 ADD ESI,10 LODS DWORD PTR DS:[ESI] TEST EAX,EAX JNZ L000
One thing to notice is that PackMan uses GetModuleHandleA to get the base of the loaded DLL file. This is because it doesn't load libraries by itself, instead it lets Windows do the loading part and it just fills in the import address table with the correct API pointers. Its easy to place two breakpoints on these function calls and grab the data we need to fill in the imports correctly. Moving on to relocations.
MOV ECX,DWORD PTR DS:[EBX+2C] ;First snapshot OR ECX,ECX JE SHORT L015 MOV EDI,DWORD PTR DS:[EBX+24] JMP L014 L005: XOR EAX,EAX LODS WORD PTR DS:[ESI] OR EAX,EAX JE L012 AND AH,0F ADD EAX,DWORD PTR DS:[EBX] ADD DWORD PTR DS:[EDX+EAX],ECX L012: CMP ESI,EDI JNZ L005 L014: MOV EDX,DWORD PTR DS:[EDI] LEA ESI,DWORD PTR DS:[EDI+8] ADD EDI,DWORD PTR DS:[EDI+4] TEST EDX,EDX JNZ L012 L015: POPAD ;Second snapshot
This code blob relocates the file to newly loaded base. As always we can make two snapshots that fix relocations automatically. Question is which memory segment do we snapshot? Since Packman has two memory forms we apply a solution that works for both. Packman can have two or more PE sections with the packer code in the last section. So the memory to snapshot is always the entire memory minus the packer section, which is in all cases from virtual address of the first section to virtual address of the last one. Comparing those two snapshots fixes the relocation table with ease.
Writing an unpacker for PackMan should be an easy task since there are just a few things to look out for. If you had no trouble writing an unpacker for UPX you shouldn't have a problem with this one. As always unpacker, source code and the samples are included with the blog. Until next week...
TitanEngine![]() ReversingLabs Corporation |
RL!dePackMan 1.x |
