Today we finish our AlexProtector unpacker whose creation was started last week with file format analysis. When the analysis was conducted we were intending to create a dynamic unpacker for this protection but since it is just as "easy" to create a static one we went for that option. That exact idea is the reason why we are a day late with our blog and we are glad we are since we noticed some bugs in the Importer module that were resolved. But we did more then just bug fixing we did some tweaks to existing function for even better import elimination protection support.
Since we already did the analysis part we will go straight to coding an unpacker and describe everything that needs to be done to complete it. We are going to start by reserving enough space for section data which needs to be decompressed. This action can be a complex task if we try to manually re-size the file and move all data to their appropriate location. However the TitanEngine comes with two functions which can do the hard work for us even though they are not meant to do this exact task. But before we go into that lets explain what is really needed. The problem lays in the fact that some sections are compressed by aPLib which means that the protected file size is most likely smaller then the original one. To accommodate the need for extra space in which this decompressed data will be written we have to re-size the sections so they can hold this physical data. How much space is needed? That part is simple only because AlexProtector stores compressed section data inside their original sections. Which means that if the first PE section is compressed its compressed data will still be in the first section. After decompression that data will still need to be written there, but there its going to be a lack of physical space because the size of compressed data is up to ~70% smaller then the original data size. Because the original data was still in the section which we need to decompress we know that the maximum physical size of that data is equal to the virtual size of that section. And that means that we need to re-size all section so that their physical size is equal to rounded up virtual size. Doing this is equal to dumping the memory of the running process with the TitanEngine's DumpProcess function but since we don't want to start the process while making a static unpacker we will do this differently. We will use StaticFileLoad to simulate the file load which is equal to running the process after which we will dump that memory to disk with DumpMemory. But that doesn't complete our task since dumped image needs to have its PE section values corrected so that the file is a valid PE image. This is done by setting the raw size of each section to rounded up virtual one and by setting the raw offset of each section to its virtual offset. Once that is done all virtual locations in the file are equal to their physical counterparts, and so the virtual address 0x1000 is on the 0x1000 byte from the start of the file.
Once we have the file re-sized to near to original size we can start decompressing data. However we don't want to be guessing which section is compressed and which isn't so we will directly read the protector data to reverse the compression. To find out where this data is stored we monitor calls to aPLib decompression code and we find a call to it from the second protection layer which is exactly what we need:
;Layer base: 0x00220000
;--------------------------------------------------------------------
/*2202D1*/ LEA ESI,DWORD PTR SS:[EBP+4023C1] ;Internal section data
/*2202D7*/ MOV EAX,DWORD PTR DS:[ESI+4]
/*2202DA*/ PUSH 4
/*2202DC*/ PUSH 1000
/*2202E1*/ PUSH EAX
... Junk removed with DeJunk ...
/*220387*/ PUSH 0
/*220389*/ CALL NEAR DWORD PTR SS:[EBP+402411] ;VirtualAlloc
/*22038F*/ MOV DWORD PTR SS:[EBP+40239D],EAX
/*220395*/ PUSH ESI
/*220396*/ MOV EBX,DWORD PTR DS:[ESI]
/*220398*/ ADD EBX,DWORD PTR SS:[EBP+402399]
/*22039E*/ PUSH EAX
... Junk removed with DeJunk ...
/*220444*/ PUSH EBX
/*220445*/ LEA ECX,DWORD PTR SS:[EBP+401108]
/*22044B*/ CALL NEAR ECX ;aPLib decompress
/*22044D*/ ADD ESP,8
/*220450*/ MOV ECX,EAX
/*220452*/ MOV EDI,DWORD PTR DS:[ESI]
... Junk removed with DeJunk ...
/*2204F9*/ ADD EDI,DWORD PTR SS:[EBP+402399]
/*2204FF*/ MOV ESI,DWORD PTR SS:[EBP+40239D]
/*220505*/ REP MOVS BYTE PTR ES:[EDI],BYTE PTR DS:[ESI]
/*220507*/ POP ESI
/*220508*/ MOV EAX,DWORD PTR SS:[EBP+40239D]
/*22050E*/ PUSH 8000
/*220513*/ PUSH 0
/*220515*/ PUSH EAX
/*220516*/ CALL NEAR DWORD PTR SS:[EBP+402415] ;VirtualFree
... Junk removed with DeJunk ...
/*2205C1*/ ADD ESI,8
/*2205C4*/ CMP DWORD PTR DS:[ESI],0
/*2205C7*/ JNZ 002202D7
As we can see from this code snippet protector is reading data located at EBP+0x004023c1. Here both numbers are constants with EBP being calculated at the start of the protector code and referred to as "protector delta". It is determined here:
PUSHAD
CALL L002
L002:
POP EBP
SUB EBP,00401006
Most protections use this kind of coding which is popularly called offset independent coding. By using this coding method protection authors don't need to know the exact offset on which their protection will be written on. Now since we are only coding an unpacker for this one protection version we will use the same internal constants that the protector uses. So, at the location Delta+0x004023c1 we will find and read the internal AlexProtector data about compressed sections. That data is a two dimensional array with the following structure:
typedef struct AlexProt_SectionData{
DWORD SectionVirtualOffset;
DWORD SectionVirtualSize;
}AlexProt_SectionData, *PAlexProt_SectionData;
After we decompress that data all the sections are restored to their original state. With a small exception of the code section which still need original entry point and import elimination protection removing. First thing out of that two that we will fix is the import protection because removing that protection will correct the code section data.
When we were analyzing the file format we have determined the internal format that AlexProtector uses for storing import data. Now that we are creating an unpacker we need that data so we need to determine where it is stored and if it is encrypted at any point. With some tracing we determine that this code does the decryption of the compressed import data.
;Layer base: 0x00220000
;--------------------------------------------------------------------
/*22080F*/ LEA EDI,DWORD PTR SS:[EBP+402531] ;Pointer to import data
/*220815*/ ADD EDI,DWORD PTR SS:[EBP+4023B5]
/*22081B*/ XOR EDX,EDX
/*22081D*/ MOV ECX,100
/*220822*/ DIV ECX
/*220824*/ MOV EBX,EDX
/*220826*/ DIV ECX
/*220828*/ ADD EBX,EDX
/*22082A*/ DIV ECX
/*22082C*/ ADD EBX,EDX
/*22082E*/ DIV ECX
/*220830*/ ADD EBX,EDX
/*220832*/ POP ECX
/*220833*/ MOV EAX,EBX
/*220835*/ MOV ECX,DWORD PTR SS:[EBP+4023B9] ;Size of the import data
/*22083B*/ XOR BYTE PTR DS:[EDI],AL ;Decryption loop
/*22083D*/ INC EDI
/*22083E*/ DEC ECX
/*22083F*/ JNZ 0022083B
Internal import data is compressed and then encrypted. To be able to use it we must reverse this process by decrypting the memory content and then decompressing it. For decryption part we need the decryption key and the decryption algorithm. Since the algorithm is known, and it is a simple XOR one, we must determine the key for decryption before continuing. Here is where the protection author made a mistake because he used a decryption key which is CRC hash of the selected memory part. Now this is a conditional "mistake" since it does prevent us from using software breakpoints in some areas but it makes the decryption key calculation unnecessary because that key can't change for this particular protector version. Since the EAX value at the start of this code snippet is 0xD0340178 we can calculate that decryption key is 0x7D.
Once this memory is decrypted it can be decompressed and then processed in order to correct both the import table and the code section. If we remember our analysis from the last time, here is how internal import data is packed:
typedef struct ALEX_IAT_DLL{
BYTE DLLSignature; //0xC3
BYTE DLLNameLength;
// DLLName[DLLNameLength] followed by 0x00
}ALEX_IAT_DLL, *PALEX_IAT_DLL;
typedef struct ALEX_IAT_APIENTRY{
// 0xC4 indicates Ordinal import
BYTE APINameLength;
// APIName[APINameLength] followed by 0x00
BYTE RedirectionNumber; //Number of redirections
DWORD RedirectionAddress[RedirectionNumber];
}ALEX_IAT_APIENTRY, *PALEX_IAT_APIENTRY;
Above is the pseudo C code that describes the internal import data structure. Here, redirection data refers to addresses in code section which need to be corrected so that they point to correct API pointers. That is why in a way we can observe this table as a sort of a relocation table because the data which needs to be written at that location corresponds to random location in memory where import redirection is allocated on. Since that memory allocation is outside the PE image file memory we call that kind of import protection import eliminations. To repair it we need to estimate the location where we will reconstruct the import table and create a table aligned to that location on the fly. There are two ways to do this and we decided to go with the more complex one. Its logic goes like this:
- Initialize importer and say that import table will be moved
- Create a relative virtual import table with no point of reference
- Relocate virtual import table to reserved spot
- Write the new import table
Now the relative virtual import table must be a confusing term but it can be simply described as an import table whose references start with zero and increment by four. That would look something like this:
- New DLL: Kernel32.dll
- TrunkValue: 0x00000000; GetProcAddress (e.g.)
- TrunkValue: 0x00000004; LoadLibraryA (e.g.)
- TrunkValue: 0x00000008; FreeLibrary (e.g.); Remember its +8 for the last item
- New DLL: User32.dll
- TrunkValue: 0x00000010; MessageBoxA (e.g.)
- TrunkValue: 0x00000014; MessageBoxW (e.g.)
Once we compile this kind of table we simply relocate it by adding a correct value to their trunk relative addresses. That value is virtual address of the section we will add to the file in order to store the import table there. At the time of unpacking that address will be a SectionAlignment aligned value of NtSizeOfImage. We could have build our table like this from the start with no need for relocating it but we wanted to show a more general approach. Since that is done we just need to write correct pointers to redirection addresses found in AlexProtector internal data so that the code section and the new import table are connected.
With imports sorted we move on the last item on our list, the entry point protection. It is processed by this code here:
;Layer base: 0x00220000
;--------------------------------------------------------------------
/*220D0B*/ LEA EBX,DWORD PTR SS:[EBP+402531] ;Pointer to EP data
... Junk removed with DeJunk ...
/*220DB6*/ ADD EBX,DWORD PTR SS:[EBP+4023B5]
/*220DBC*/ ADD EBX,DWORD PTR SS:[EBP+4023B9] ;Pointer correction
... Junk removed with DeJunk ...
/*220E67*/ MOV ECX,DWORD PTR SS:[EBP+402395]
... Junk removed with DeJunk ...
/*220F12*/ MOV EAX,DWORD PTR SS:[EBP+4023AD]
/*220F18*/ PUSH EAX
/*220F19*/ PUSH EBX
/*220F1A*/ LEA EDX,DWORD PTR SS:[EBP+401108]
/*220F20*/ CALL NEAR EDX ;aPLib
... Junk removed with DeJunk ...
/*220FC7*/ ADD ESP,8
/*220FCA*/ MOV EAX,DWORD PTR SS:[EBP+4023AD]
/*220FD0*/ MOV EBX,DWORD PTR SS:[EBP+402391]
... Junk removed with DeJunk ...
/*22107B*/ MOV ESI,DWORD PTR SS:[EBP+402385] ;EP resumes here
/*221081*/ ADD EAX,EBX
/*221083*/ MOV BYTE PTR DS:[EAX],0E9 ;Write OEP jump
/*221086*/ INC EAX
/*221087*/ MOV ECX,ESI
... Junk removed with DeJunk ...
/*22112E*/ SUB ECX,EAX
/*221130*/ SUB ECX,4
/*221133*/ MOV DWORD PTR DS:[EAX],ECX
... Junk removed with DeJunk ...
/*2211DA*/ MOV EAX,DWORD PTR SS:[EBP+4023AD]
/*2211E0*/ MOV DWORD PTR SS:[ESP+1C],EAX
/*2211E4*/ POPAD
/*2211E5*/ JMP NEAR EAX ;Jump to stolen EP
As we can see the stolen entry point data is decompressed and a new jump to the first non stolen instruction is written. To reverse this we must decompress the buffer with EP jump correction after which we need to write it to a new section. It is possible to extract the correct the entry point to its original state but that would mean that we would have to disassemble and analyze the decompressed buffer which contains those original functions riddled with junk code. There is a high margin of error if this is performed and therefore keeping the junk is the simplest option.
Writing an unpacker for AlexProtector should be a nice exercise for any reverser. We have shown in detail how it can be done statically. If you have any questions about any of the steps in writing this unpacker feel free to contact us. Until next week....
Samples and Protector / RL!deAlexProtector
(package contains protector binary, unpacker with source and the samples used)
VN:F [1.8.4_1055]
Rating: +2 (from 2 votes)