· 2015/09/28 9:43

http://int0xcc.svbtle.com/a-guide-to-malware-binary-reconstruction

When analyzing or unpacking malware, we often encounter the need to rebuild PE files. Most of today’s automated PE reconstruction tools, while great, are not for every situation and sometimes require us to manually rebuild PE files ourselves. In this blog post, we will cover some methods for rebuilding PE files.

0x00 Rebuild IAT table for “Stolen API Code”


Stolen API Code is often used by malware to rebuild IAT tables after preventing the reverse engineer from unshucking. Stolen API code (IAT) ¶ Stolen API code (IAT) ¶ stolen API code (IAT) ¶

0x01 IAT Basics


Import Address Table (IAT) is a structure in the PE file, which contains information about Windows Loader loading dynamic link libraries and importing API function addresses. When you look at the PE file you should notice that there are two Pointers in the IMAGE_OPTIONAL_HEADER structure: one to the IMAGE_IMPORT_DESCRIPTOR and the other to an array of import function addresses.

Functions can be imported by function name or ordinal number (API number).

The FirstThunk member points to an array of imported API functions (also known as the imported address table).

The figure above shows an example of the kernel32.dll import function GetProcAddress().

Shell programs usually break the original form of the IAT table and let the shell code solve the function import instead of relying on the Windows Loader. Therefore, the IAT table of the program needs to be rebuilt after unhulling. Next, we will use the tool Scylla V0.9.6B to rebuild the IAT.

0x02 Stolen API code


Some shell programs use “Stolen code” technology to prevent reverse engineers from rebuilding IAT tables. Stolen Code re-emulates instructions to API functions in a memory area. So when a scanner scans these import functions, it gets some invalid API Pointers.

Scylla was unable to automatically rebuild IAT tables using “Stolen API Code” technology. We need to write a Scylla plugin to get the correct offset and rebuild the IAT table.

0x03 Write a Scylla plug-in


The Scylla plug-in is basically injected into the target process as a DLL file. To do this, it provides an API to build plug-ins, as well as an interface to make it easy for the Scylla plug-in to be embedded into the target program. In addition, Scylla provides a named memory map file for the Scylla plug-in to retrieve information from the target program.

Scylla provides named file mappings for target DLLS pointing to specific memory regions.

This memory-mapped file is called “ScyllaPluginExchange”.

ScyllaPluginExchange provides the following information:

UNRESOLVED_IMPORT structure through SCYLLA_EXCHANGE. OffsetUnresolvedImportsArray members.

The first step in writing the plug-in is to get the base address of the SCYLLA_EXCHANGE structure using the named memory mapping file provided by Scylla:

#! c++ BOOL getMappedView() { hMapFile = OpenFileMappingA(FILE_MAP_ALL_ACCESS, 0, FILE_MAPPING_NAME); //open named file mapping object if (hMapFile == 0) { writeToLogFile("OpenFileMappingA failed\r\n"); return FALSE; } lpViewOfFile = MapViewOfFile(hMapFile, FILE_MAP_ALL_ACCESS, 0, 0); //map the view with full access if (lpViewOfFile == 0) { CloseHandle(hMapFile); //close mapping handle hMapFile = 0; writeToLogFile("MapViewOfFile failed\r\n"); return FALSE; } return TRUE; }Copy the code

UNRESOLVED_IMPORT contains a list of unresolved import functions.

#! c++ typedef struct _UNRESOLVED_IMPORT { // Scylla Plugin exchange format DWORD_PTR ImportTableAddressPointer; //in VA, address in IAT which points to an invalid api address DWORD_PTR InvalidApiAddress; //in VA, invalid api address that needs to be resolved } UNRESOLVED_IMPORT, *PUNRESOLVED_IMPORT;Copy the code

ImportTableAddressPointer pointer to valid API address. The InvalidApiAddress pointer points to the address of an undecided API function. In this example, this is the dynamically allocated memory area where stolen Code was emulated.

You can see we need to calculate each ImportTableAddressPointer JMP instruction into the number of bytes, then remove the JMP instruction have the jump target address minus the number of bytes to get the original API base address:

#! c++ while (unresolvedImport->ImportTableAddressPointer ! = 0) //last element is a nulled struct { insDelta = 0; invalidApiAddress = unresolvedImport->InvalidApiAddress; sprintf(buffer, "API Address = 0x%p\t IAT Address = 0x%p\n", invalidApiAddress, unresolvedImport->ImportTableAddressPointer); writeToLogFile(buffer); IATbase = unresolvedImport->InvalidApiAddress; for (j = 0; j < COUNT_INS; j++) { memset(&inst, 0x00, sizeof(INSTRUCTION)); i = get_instruction(&inst, IATbase, MODE_32); memset(buffer, 0x00, sizeof(buffer)); get_instruction_string(&inst, FORMAT_ATT, 0, buffer, sizeof(buffer)); if (strstr(buffer, "jmp")) { printf(" JUMP Dest = %d" , ( (unsigned int)strtol(strstr(buffer, "jmp") + 4 + 2, NULL, 16))); *(DWORD*)(unresolvedImport->ImportTableAddressPointer) = ( (unsigned int)strtol(strstr(buffer, "jmp") + 4 + 2, NULL, 16) + IATbase ) - insDelta; unresolvedImport->InvalidApiAddress = ( (unsigned int)strtol(strstr(buffer, "jmp") + 4 + 2, NULL, 16) + IATbase ) - insDelta; break; } else { insDelta = insDelta + i; } IATbase = IATbase + i; } unresolvedImport++; //next pointer to struct }Copy the code

This code iterates through all the undecided import functions and tries to locate the correct API address.

Subtracting insDelta from the target address of the JMP directive gives the final InvalidApiAddress:

#! C++ unresolvedImport->InvalidApiAddress = ((unsigned int)strtol(buffer, "JMP") + 4 + 2, NULL, 16) + IATbase) - insDelta;Copy the code

After repairing the entire IAT table, there may be some invalid import addresses that need to be manually removed.

There are still some invalid addresses left after running the plugin written above, now remove them manually:

0x04 Export RunPE shell program


RunPE works by creating a dummy process in a suspended state that is then hollowed out and injected with malicious code. This technique is often used to hide malicious code. RunPE injected code can be exported as a valid PE file. The PE+ header needs to be modified because 64-bit PE files use QWORD for some fields.

Align the Windows Loader to image_section_header. VirtualSize after loading it into memory. However, the RawSize of the Section table may be smaller than the VirtualSize, and the operating system may need to fill this Section.

The PE files on the disk are aligned with the IMAGe_OptionAL_header64.filealignment. After the PE files are exported from the memory, the PE files must be aligned with the Image_Optional_header64.filealignment.

Failed to find the correct virtual address when loading the exported PE file with IDA because the PE file is not aligned.

This problem is easy to solve, let’s first take out PE+ file structure:

#! c++ IMAGE_DOS_HEADER DosHdr = {0}; IMAGE_FILE_HEADER FileHdr = {0}; IMAGE_OPTIONAL_HEADER64 OptHdr = {0}; // Read All Structure as per offset fread(&DosHdr, sizeof(IMAGE_DOS_HEADER), 0x01, fp); fseek(fp, (unsigned int)DosHdr.e_lfanew + 4,SEEK_SET); fread(&FileHdr, sizeof(IMAGE_FILE_HEADER), 1, fp); fread(&OptHdr, sizeof(IMAGE_OPTIONAL_HEADER64), 1, fp);Copy the code

Read all section headers iteratively:

#! c++ while (iNumSec < FileHdr.NumberOfSections) { fread(&pTail[iNumSec], sizeof( IMAGE_SECTION_HEADER), 1, fp); iNumSec++; }Copy the code

Then read the PointerToRawData of the first section:

#! c++ i = ftell(fp); buffer = (unsigned char*) malloc(sizeof(char) * pTail[0].PointerToRawData + 1); fseek(fp, 0, SEEK_SET); fread(buffer, pTail[0].PointerToRawData, 1, fp); // Read/Write Everything Till the beginning of first section fwrite(buffer, pTail[0].PointerToRawData, 1, out);Copy the code

Finally, rewrite the data in an aligned form:

#! c++ while ( i < iNumSec) { buffer = (unsigned char*) malloc(sizeof(char) * pTail[i].SizeOfRawData + 1); fseek(fp, pTail[i].VirtualAddress, SEEK_SET); fread(buffer, pTail[i].SizeOfRawData, 1, fp); fwrite(buffer, pTail[i].SizeOfRawData, 1, out); i++; }Copy the code

After all the repairs are complete, we can get a correct PE file. Here is IDA loading the repaired PE file:

Add: sample_plugin. Rar