This is a packer some botnet authors really love to use. With some anti-emulator and anti-debug ticks, this can be tough to unpack by the anti-virus engine.

The classic flowchart looks like this:

The decryption routine is highlighted in red. Sometimes it won’t be recognized by IDE, because the address of this sub routine is going to be calculated in the entry sub routine.

Decryption, simple XOR

Then it goes into dynamic memory (2nd layer) to do further decryption.

To unpack, put a breakpoint on VirtualFreeEx…

 

Have seen a lot of this trick, just want to point it out…

  1. call a junk API sets the last error code to 0×57 or 0x7B
  2. get the error code from fs:[0x18]->+0×34, which is actually fs:[34] and compare
  3. if the last error code is not as expected, terminate

Here is the flow chart:

Obfuscated code to check error code:

fs:[0x18]+0×34

 

 

Packer Analysis-VMProtect

General Information:

Sample MD5: 53bc30d3adae3c963e260cf595902046

Technical Details:

  • Here are some identification of this VMProtect packer:

Illustration 1: MZ Header followed by numbers, this characteristic does not belong to VMProtect


Illustration 2: Section names

  • How it works:
    • some important functions are turned into p-code

    • loop to VM dispatcher and to handlers translates the p-codes and executes them

there is one unique handler that will restore the general registers from VM context, people call it VM_Retn, find it and put breakpoint on it, interesting stuff will happen:

Illustration 3: 564d5868 = VMXh, my guess is, next, somewhere, there will be in eax, dx

    • in some version of VMProtect, it may have pre-calculated hash of some blocks and store the values, later on it will use VM_CRC handler to match the CRC prevent hooking/patching

  • Obfuscation:
    • The NAND operation:

.vmp1:00468A54 8B 45 00 mov eax, [ebp+0]

.vmp1:00468A57 8B 55 04 mov edx, [ebp+4]

.vmp1:00468A5A F7 D0 not eax

.vmp1:00468A5C F7 D2 not edx

.vmp1:00468A5E 21 D0 and eax, edx

.vmp1:00468A60 89 45 04 mov [ebp+4], eax

.vmp1:00468A63 9C pushf

.vmp1:00468A64 8F 45 00 pop dword ptr [ebp+0]

What is so special of NAND gate? It can produce the other basic logical operation: not, and, or and xor:

Let P(a,b) = ~a & ~b we have:

not(a) = ~a = ~a & ~a = P(a,a)
and(a,b) = a & b = ~(~a) & ~(~b) = P(not(a),not(b)) = P(P(a,a),P(b,b))
or(a,b) = a | b = ~(~(a|b)) = ~(~a & ~b) = ~P(a,b) = P(P(a,b),P(a,b))
xor(a,b) = ~a & b | a & ~b = ~(~(~a & b | a & ~b)) = ~(~(~a & b) & ~(a & ~b)) = ~((a | ~b) & (~a | b)) = ~(1 | 1 | a & b | ~a & ~b) = ~(a & b) & ~(~a & ~b) = P(and(a,b),P(a,b)) = P(P(P(a,a),P(b,b)),P(a,b))

    • directly push/pop into stacks:

Illustration 4: Restore(pop) the value from stack to general purpose registers(and btw, this is the VM_Retn, to find this in IDA with findOPMask plugin, ctrl+s search 8b 00:00 24)

  • to unpack (for this kind of sample only):

Before that, in OllyDBG, PhantOm Plugin Enable → Protect Drx

StrongOD Plugin Enable → HidePEB | KernelMode| Normal

Look for the OEP:

    • Ctrl+G|VirtualProctect|F2 on VirtualProctectEx
    • F6 until you see Address = .text twice, and the NewProtect= mode probaly will be something like PAGE_EXECUTE_READ, which means .text section is read to execute now
    • F2(or right-click| set memory break on access) on ALT-M window at .text, and remove the other breakpoints
    • F6 | you will land at OEP or somewhere near it or for the professional VMProtect, inside VM_CRC handler, in which the VM is going to check the integrity of this PE file and then go to OEP.(usually, on the line: XOR AL, Byte Ptr DS:[edx]) In that case, make use of the stack balance theory, put write breakpoint on one stack address just before RETURN to kernel32.xxxxxx from the bottom.


Illustration 5: OEP

 

The name is self-explaining, one is Base64 the other is RC4:

//--------------------------------------
// Decrypt base64 encoded array
// arg1: uint8_t array of encoded code, arg2: size of encoded code, arg3: uint8_t array of decoded code
// return: size of decoded data
// Author:Neo Tan
// Comment:
//--------------------------------------
uint32_t base64_decryption(uint8_t *enCode, uint32_t size, uint8_t *deCode)
{
 // read data, 6 bits per input byte
 uint32_t data = 0;
 uint32_t bits = 0;
 uint32_t out = 0;
 uint32_t adr = 0;
 uint8_t b;
 //int32_t siz = sizeof(enCode); this gets the size of the pointer which is 4 bytes
 uint32_t siz = size;
 #ifdef DEBUG
 debug("!!!!!!!!!!!!!!!!!!!\nsiz=%d\n", siz);
 #endif
 while (siz > 0)
 {
 b = enCode[adr]; adr++; siz--;
 #ifdef DEBUG
 debug("b=%X|siz=%d\n",b, siz);
 #endif
 if ((b >= 'A') && (b {
 #ifdef DEBUG
 debug("(data << 6) = %X | %X - 'A' = %X", (data << 6),b, (b - 'A'));
 #endif
 data = (data << 6) | (b - 'A'); bits += 6; #ifdef DEBUG debug("data = %X\n bits = %d", data, bits); #endif } else if ((b >= 'a') && (b {
 data = (data << 6) | (b - 'a' + 26); bits += 6; } else if ((b >= '0') && (b {
 data = (data << 6) | (b - '0' + 52); bits += 6;
 }
 else if ((b == '+') || (b == '-'))
 {
 data = (data << 6) | 62; bits += 6;
 }
 else if ((b == '/') || (b == '_'))
 {
 data = (data << 6) | 63; bits += 6; } else if (b > ' ')
 {
 siz = 0;
 break;
 }
 // write data
 if (bits >= 8)
 {
 bits -= 8;
 deCode[out] = data >> bits; out++;
 }
 }
 return out;
}

 

//--------------------------------------
// Decrypt RC4 encoded array
// arg1: uint8_t array of encoded code and will hold decoded code, arg2: size of encoded code, arg3: uint8_t array of key, arg4: size of key
// return: size of decoded data
// Author:Neo Tan
// Comment:
//--------------------------------------
uint32_t rc4_decryption(uint8_t *enCode, uint32_t size, uint8_t *key, uint32_t keySize)
{
	int i = 0, j = 0, a = 0, b = 0, out = 0;
	uint8_t skey[256];
	uint8_t temp, theKey;

	for (i = 0; i 	{
		skey[i] = i;
	}

	for (i =0; i 	{
		j = (j + skey[i] + key[(i)%keySize])%256;
		#ifdef DEBUG
		debug("%X,%d   ",key[(i)%keySize], (i)%keySize);
		#endif
		temp = skey[j];
		skey[j] = skey[i];
		skey[i] = temp;
	}
	#ifdef DEBUG
	for (i =0; i 	{
		debug("skey[%x] %X,   ", i, skey[i]);
	}
	#endif
	b = j;//this is a custom RC4
	#ifdef DEBUG
	debug("b = %X", b);
	#endif
	while(out < size)
	{
		#ifdef DEBUG
		debug("out = %d", out);
		#endif
		a = (a+1)%256;//watch out some may be customed here eg (a+3)
		b = (b+skey[a])%256;

		//swap
		#ifdef DEBUG
		debug("skey[a] = %X, skey[a] = %X\n\n", skey[a], skey[b]);
		#endif
		temp = skey[a];
		skey[a] = skey[b];
		skey[b] = temp;
		theKey = skey[(skey[a]+skey[b])%256];

		//output file
		#ifdef DEBUG
		debug("skey[%X] = %X, enCode[out]=%X\n\n ", (skey[a]+skey[b])%256, theKey,enCode[out] );
		#endif
		enCode[out] = enCode[out]^theKey; out++;
	}
	#ifdef DEBUG
	for (int i = 0; i < out; i++)
	{
		debug("+++++++++++%X",enCode[i]);
	}
	#endif
	return out;
}
Dec 132011
 

Recently I revisited the fundamental structure. This is the C/C++ compatible code, header codes are borrowed from winnt.h:
pe_struct.h:

typedef unsigned __int8 BYTE;
typedef unsigned __int16 WORD;
typedef unsigned __int32 DWORD;
typedef long LONG;

#define IMAGE_DOS_SIGNATURE 0x5A4D
#define IMAGE_OS2_SIGNATURE 0x454E
#define IMAGE_OS2_SIGNATURE_LE 0x454C
#define IMAGE_VXD_SIGNATURE 0x454C
#define IMAGE_NT_SIGNATURE 0x00004550
#define IMAGE_NT_OPTIONAL_HDR32_MAGIC 0x10b
#define IMAGE_NT_OPTIONAL_HDR64_MAGIC 0x20b

#define IMAGE_NUMBEROF_DIRECTORY_ENTRIES 16
#define IMAGE_SIZEOF_SHORT_NAME 8

#define CHARARRAYMAXSIZE 256

typedef struct _IMAGE_TLS_DIRECTORY {
DWORD StartAddressOfRawData;
DWORD EndAddressOfRawData;
DWORD AddressOfIndex;
DWORD AddressOfCallBacks;
DWORD SizeOfZeroFill;
DWORD Characteristics;
} IMAGE_TLS_DIRECTORY,*PIMAGE_TLS_DIRECTORY;

// typedef struct _IMAGE_THUNK_DATA {
// union {
// ULONG ForwarderString;
// ULONG Function;
// DWORD Ordinal;
// ULONG AddressOfData;
// } u1;
// } IMAGE_THUNK_DATA,*PIMAGE_THUNK_DATA;

typedef struct _IMAGE_IMPORT_DESCRIPTOR {
DWORD OriginalFirstThunk;//Import Lookup(Name) Table RVA
DWORD TimeDateStamp;
DWORD ForwarderChain;
DWORD Name;//DLL name RVA
DWORD FirstThunk;//Import Address Table RVA
} IMAGE_IMPORT_DESCRIPTOR,*PIMAGE_IMPORT_DESCRIPTOR;

typedef struct _IMAGE_EXPORT_DIRECTORY {
DWORD Characteristics;
DWORD TimeDateStamp;
WORD MajorVersion;
WORD MinorVersion;
DWORD Name;//DLL name RVA
DWORD Base;
DWORD NumberOfFunctions;
DWORD NumberOfNames;
DWORD AddressOfFunctions;
DWORD AddressOfNames;//Export Name Table RVA
DWORD AddressOfNameOrdinals;
} IMAGE_EXPORT_DIRECTORY,*PIMAGE_EXPORT_DIRECTORY;

typedef struct _IMAGE_SECTION_HEADER {
BYTE Name[IMAGE_SIZEOF_SHORT_NAME];
union {
DWORD PhysicalAddress;
DWORD VirtualSize;
} Misc;
DWORD VirtualAddress;
DWORD SizeOfRawData;
DWORD PointerToRawData;
DWORD PointerToRelocations;
DWORD PointerToLinenumbers;
WORD NumberOfRelocations;
WORD NumberOfLinenumbers;
DWORD Characteristics;
} IMAGE_SECTION_HEADER,*PIMAGE_SECTION_HEADER;

typedef struct _IMAGE_FILE_HEADER {
WORD Machine;
WORD NumberOfSections;
DWORD TimeDateStamp;
DWORD PointerToSymbolTable;
DWORD NumberOfSymbols;
WORD SizeOfOptionalHeader;
WORD Characteristics;
} IMAGE_FILE_HEADER, *PIMAGE_FILE_HEADER;

typedef struct _IMAGE_DATA_DIRECTORY {
DWORD VirtualAddress;
DWORD Size;
} IMAGE_DATA_DIRECTORY,*PIMAGE_DATA_DIRECTORY;

typedef struct _IMAGE_OPTIONAL_HEADER32 {
WORD Magic;
BYTE MajorLinkerVersion;
BYTE MinorLinkerVersion;
DWORD SizeOfCode;
DWORD SizeOfInitializedData;
DWORD SizeOfUninitializedData;
DWORD AddressOfEntryPoint;
DWORD BaseOfCode;
DWORD BaseOfData;
DWORD ImageBase;
DWORD SectionAlignment;
DWORD FileAlignment;
WORD MajorOperatingSystemVersion;
WORD MinorOperatingSystemVersion;
WORD MajorImageVersion;
WORD MinorImageVersion;
WORD MajorSubsystemVersion;
WORD MinorSubsystemVersion;
DWORD Win32VersionValue;
DWORD SizeOfImage;
DWORD SizeOfHeaders;
DWORD CheckSum;
WORD Subsystem;
WORD DllCharacteristics;
DWORD SizeOfStackReserve;
DWORD SizeOfStackCommit;
DWORD SizeOfHeapReserve;
DWORD SizeOfHeapCommit;
DWORD LoaderFlags;
DWORD NumberOfRvaAndSizes;
IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES];
} IMAGE_OPTIONAL_HEADER32,*PIMAGE_OPTIONAL_HEADER32;

typedef struct _IMAGE_NT_HEADERS32 {
DWORD Signature;
IMAGE_FILE_HEADER FileHeader;
IMAGE_OPTIONAL_HEADER32 OptionalHeader;
} IMAGE_NT_HEADERS32,*PIMAGE_NT_HEADERS32;

typedef struct _IMAGE_DOS_HEADER {
WORD e_magic;
WORD e_cblp;
WORD e_cp;
WORD e_crlc;
WORD e_cparhdr;
WORD e_minalloc;
WORD e_maxalloc;
WORD e_ss;
WORD e_sp;
WORD e_csum;
WORD e_ip;
WORD e_cs;
WORD e_lfarlc;
WORD e_ovno;
WORD e_res[4];
WORD e_oemid;
WORD e_oeminfo;
WORD e_res2[10];
LONG e_lfanew;
} IMAGE_DOS_HEADER,*PIMAGE_DOS_HEADER;

typedef struct _PE_LAYOUT {
IMAGE_DOS_HEADER *pDosHeader;
IMAGE_NT_HEADERS32 *pPEHeader;//these 2 above = MS COFF Header
IMAGE_SECTION_HEADER *pSecHeader;//Section Header
IMAGE_EXPORT_DIRECTORY *pExportDir;//Export Table
IMAGE_IMPORT_DESCRIPTOR *pImportDir;//Import Table
IMAGE_TLS_DIRECTORY *pTLSDir;//TLS Table
} PE_LAYOUT,*PPE_LAYOUT;

The parser:

#include "pe_struct.h"

//--------------------------------------
// Helper function to get byte and increment the offset by sizeof byte
// Author:Neo Tan
// Comment:
//--------------------------------------
DWORD getbyte(DWORD *offset)
{
	BYTE res = get_byte(*offset);
	*offset += sizeof(BYTE);
	return res;
}

//--------------------------------------
// Helper function to get word and increment the offset by sizeof WORD
// Author:Neo Tan
// Comment:
//--------------------------------------
WORD getword(DWORD *offset)
{
	WORD res = get_word(*offset);
	*offset += sizeof(WORD);
	return res;
}

//--------------------------------------
// Helper function to get dword and increment the offset by sizeof DWORD
// Author:Neo Tan
// Comment:
//--------------------------------------
DWORD getdword(DWORD *offset)
{
	DWORD res = (DWORD)get_dword(*offset);
	*offset += sizeof(DWORD);
	return res;
}

//--------------------------------------
// Helper function to get bytes until hits 0x0, max size 256
// Author:Neo Tan
// Comment:
//--------------------------------------
char *getstring(DWORD *offset)
{
	// #ifdef DEBUG
	// debug("\n++++++++++++++\n 1offset = %X\n",*offset);
	// debug("\n++++++++++++++\n file length = %X\n",get_file_length());
	// debug("\n++++++++++++++\n get_byte(*offset) = %X\n",get_byte(*offset));
	// #endif
	char res[CHARARRAYMAXSIZE];
	//reset to zero, not working, bug?
	for(int a = 0; a < 15; a++)//was going to use CHARARRAYMAXSIZE, but can't set to  larger than 15 otherwise code will stop here
	{
		res[a] = 0;
	}

	int i = 0;
	while (get_byte(*offset) != 0 && i < CHARARRAYMAXSIZE)
	{
		res[i] = get_byte(*offset);
		// #ifdef DEBUG
		// debug("\n++++++++++++++\n 2res = %s\n",res);
		// #endif
		*offset += sizeof(char);
		i ++;
	}
	return res;
}

//--------------------------------------
// Helper function as memset
// Author:Neo Tan
// Comment:
//--------------------------------------
void * neomemset(void * ptr, char value, size_t num )
{
	char* m = ptr;

	for(int i = 0; i < num; i++)
	{
		*(m+i) = 0;
	}

	return ptr;
}

//--------------------------------------
// Helper function to convert RVA to Raw offset
// args: should be self-explaining
// returns: 0 if fails
// Author:Neo Tan
// Comment:
//--------------------------------------
DWORD rva_to_raw(IMAGE_NT_HEADERS32 *pPEHeader, IMAGE_SECTION_HEADER *pSecHeader, DWORD rva)
{
	DWORD rawOffset = 0;
	int secIndex = -1;//the section the rva at
	for (int i = 0; i < pPEHeader->FileHeader.NumberOfSections; i++)
	{
		if (pSecHeader[i].VirtualAddress FileHeader.NumberOfSections) |
			( pSecHeader[i + 1].VirtualAddress > rva))//the last section or less than the next section VA
			{
				secIndex = i;
			}
		}
	}

	if (secIndex != -1)
	{
		rawOffset = rva - pSecHeader[secIndex].VirtualAddress + pSecHeader[secIndex].PointerToRawData;
	}

	#ifdef DEBUG
	debug("\n++++++++++++++\n rva = %X\n",rva);
	debug("\n++++++++++++++\n secIndex = %X\n",secIndex);
	debug("\n++++++++++++++\n rawOffset = %X\n",rawOffset);
	#endif
	return rawOffset;
}

//--------------------------------------
// PE Parser
// arg1: parse offset, arg2: output pointer
// return: void
// Author:Neo Tan
// Comment:
//--------------------------------------
void pe_parse(DWORD parseOffset, PE_LAYOUT *out)
{
//parse IMAGE_DOS_HEADER
	IMAGE_DOS_HEADER *imagehdr = allocate(sizeof(IMAGE_DOS_HEADER));

	imagehdr->e_magic = getword(&parseOffset);

	#ifdef DEBUG
	debug("\n++++++++++++++\n parseOffset = %X\n",parseOffset);
	debug("\n++++++++++++++\n imagehdr->e_magic = %X\n",imagehdr->e_magic);
	#endif

	imagehdr->e_cblp = getword(&parseOffset);
	imagehdr->e_cp = getword(&parseOffset);
	imagehdr->e_crlc = getword(&parseOffset);
	imagehdr->e_cparhdr = getword(&parseOffset);
	imagehdr->e_minalloc = getword(&parseOffset);
	imagehdr->e_maxalloc = getword(&parseOffset);
	imagehdr->e_ss = getword(&parseOffset);
	imagehdr->e_sp = getword(&parseOffset);
	imagehdr->e_csum = getword(&parseOffset);
	imagehdr->e_ip = getword(&parseOffset);
	imagehdr->e_cs = getword(&parseOffset);
	imagehdr->e_lfarlc = getword(&parseOffset);
	imagehdr->e_ovno = getword(&parseOffset);
	imagehdr->e_res[0] = getword(&parseOffset);
	imagehdr->e_res[1] = getword(&parseOffset);
	imagehdr->e_res[2] = getword(&parseOffset);
	imagehdr->e_res[3] = getword(&parseOffset);
	imagehdr->e_oemid = getword(&parseOffset);
	imagehdr->e_oeminfo = getword(&parseOffset);
	for (int i =0; i < 10; i ++) 	{ 		imagehdr->e_res2[i] = getword(&parseOffset);
	}

	imagehdr->e_lfanew =  getword(&parseOffset);// File address of new exe header
	#ifdef DEBUG
	debug("\n++++++++++++++\n imagehdr->e_lfanew = %X\n",imagehdr->e_lfanew);
	#endif

//parse IMAGE_NT_HEADERS32
	parseOffset = imagehdr->e_lfanew;
	IMAGE_NT_HEADERS32 * pehdr32 = allocate(sizeof(IMAGE_NT_HEADERS32));
	pehdr32->Signature = getdword(&parseOffset);
	#ifdef DEBUG
	debug("\n++++++++++++++\n pehdr32->Signature = %X\n",pehdr32->Signature);
	#endif

	//parse IMAGE_FILE_HEADER
	IMAGE_FILE_HEADER *pfileHeader = (IMAGE_FILE_HEADER *)&pehdr32->FileHeader;
	pfileHeader->Machine =  getword(&parseOffset);
	pfileHeader->NumberOfSections =  getword(&parseOffset);
	pfileHeader->TimeDateStamp =  getdword(&parseOffset);
	pfileHeader->PointerToSymbolTable =  getdword(&parseOffset);
	pfileHeader->NumberOfSymbols =  getdword(&parseOffset);
	pfileHeader->SizeOfOptionalHeader =  getword(&parseOffset);
	pfileHeader->Characteristics =  getword(&parseOffset);
	#ifdef DEBUG
	debug("\n++++++++++++++\n pehdr32->FileHeader.Machine = %X\n", pehdr32->FileHeader.Machine);
	debug("\n++++++++++++++\n pehdr32->FileHeader.NumberOfSections = %X\n", pehdr32->FileHeader.NumberOfSections);
	debug("\n++++++++++++++\n pehdr32->FileHeader.Characteristics = %X\n", pehdr32->FileHeader.Characteristics);
	#endif
	//parse IMAGE_OPTIONAL_HEADER32
	IMAGE_OPTIONAL_HEADER32 *poptHeader = (IMAGE_OPTIONAL_HEADER32 *)&pehdr32->OptionalHeader;
	poptHeader->Magic = getword(&parseOffset);
	poptHeader->MajorLinkerVersion = getbyte(&parseOffset);
	poptHeader->MinorLinkerVersion = getbyte(&parseOffset);
	poptHeader->SizeOfCode = getdword(&parseOffset);
	poptHeader->SizeOfInitializedData = getdword(&parseOffset);
	poptHeader->SizeOfUninitializedData = getdword(&parseOffset);
	poptHeader->AddressOfEntryPoint = getdword(&parseOffset);
	poptHeader->BaseOfCode = getdword(&parseOffset);
	poptHeader->BaseOfData = getdword(&parseOffset);
	poptHeader->ImageBase = getdword(&parseOffset);
	poptHeader->SectionAlignment = getdword(&parseOffset);
	poptHeader->FileAlignment = getdword(&parseOffset);
	poptHeader->MajorOperatingSystemVersion = getword(&parseOffset);
	poptHeader->MinorOperatingSystemVersion = getword(&parseOffset);
	poptHeader->MajorImageVersion = getword(&parseOffset);
	poptHeader->MinorImageVersion = getword(&parseOffset);
	poptHeader->MajorSubsystemVersion = getword(&parseOffset);
	poptHeader->MinorSubsystemVersion = getword(&parseOffset);
	poptHeader->Win32VersionValue = getdword(&parseOffset);
	poptHeader->SizeOfImage = getdword(&parseOffset);
	poptHeader->SizeOfHeaders = getdword(&parseOffset);
	poptHeader->CheckSum = getdword(&parseOffset);
	poptHeader->Subsystem = getword(&parseOffset);
	poptHeader->DllCharacteristics = getword(&parseOffset);
	poptHeader->SizeOfStackReserve = getdword(&parseOffset);
	poptHeader->SizeOfStackCommit = getdword(&parseOffset);
	poptHeader->SizeOfHeapReserve = getdword(&parseOffset);
	poptHeader->SizeOfHeapCommit = getdword(&parseOffset);
	poptHeader->LoaderFlags = getdword(&parseOffset);
	poptHeader->NumberOfRvaAndSizes = getdword(&parseOffset);
	#ifdef DEBUG
	debug("\n++++++++++++++\n pehdr32->OptionalHeader.Magic = %X\n", pehdr32->OptionalHeader.Magic);//usually == IMAGE_NT_OPTIONAL_HDR32_MAGIC
	debug("\n++++++++++++++\n pehdr32->OptionalHeader.NumberOfRvaAndSizes = %X\n", pehdr32->OptionalHeader.NumberOfRvaAndSizes);//usually == IMAGE_NUMBEROF_DIRECTORY_ENTRIES
	#endif
	//some samples use extremely large NumberOfRvaAndSizes, need to do some fix here, can use this characteristic as a filter
	if ((poptHeader->NumberOfRvaAndSizes >  IMAGE_NUMBEROF_DIRECTORY_ENTRIES) |
	(poptHeader->NumberOfRvaAndSizes < IMAGE_NUMBEROF_DIRECTORY_ENTRIES - 3)) 	{ 		debug("!!!!!!!!!!!!!!!!!!!!!!!!abnormal NumberOfRvaAndSizes = %X!!!!!!!!!!!!!!!!!!!!!!!!\n\n", poptHeader->NumberOfRvaAndSizes);
		//poptHeader->NumberOfRvaAndSizes = IMAGE_NUMBEROF_DIRECTORY_ENTRIES;
	}

	//parse IMAGE_DATA_DIRECTORY
	IMAGE_DATA_DIRECTORY *pdd = (IMAGE_DATA_DIRECTORY*)&poptHeader->DataDirectory;
	for(int i = 0; i < IMAGE_NUMBEROF_DIRECTORY_ENTRIES; i++) 	{ 		pdd[i].VirtualAddress = getdword(&parseOffset); 		pdd[i].Size = getdword(&parseOffset); 	} 	#ifdef DEBUG 	debug("\n++++++++++++++\n IAT RVA = %X\n", pehdr32->OptionalHeader.DataDirectory[0xC].VirtualAddress);
	#endif

//parse IMAGE_SECTION_HEADER
	IMAGE_SECTION_HEADER *ish = allocate(pehdr32->FileHeader.NumberOfSections*sizeof(IMAGE_SECTION_HEADER));
	#ifdef DEBUG
	debug("\n++++++++++++++\nsizeof(IMAGE_SECTION_HEADER)= %X\n", sizeof(IMAGE_SECTION_HEADER));
	#endif
	for(int i = 0; i < pehdr32->FileHeader.NumberOfSections; i++)
	{
		for(int j = 0; j < IMAGE_SIZEOF_SHORT_NAME; j++) 		{ 			ish[i].Name[j] = getbyte(&parseOffset); 		} 		//ish[i].PhysicalAddress = getdword(&parseOffset); 		ish[i].Misc.VirtualSize = getdword(&parseOffset); 		ish[i].VirtualAddress = getdword(&parseOffset); 		ish[i].SizeOfRawData = getdword(&parseOffset); 		ish[i].PointerToRawData = getdword(&parseOffset); 		ish[i].PointerToRelocations = getdword(&parseOffset); 		ish[i].PointerToLinenumbers = getdword(&parseOffset); 		ish[i].NumberOfRelocations = getword(&parseOffset); 		ish[i].NumberOfLinenumbers = getword(&parseOffset); 		ish[i].Characteristics = getdword(&parseOffset); 	} 	#ifdef DEBUG 	debug("\n++++++++++++++\n ish[4].Name[1]= %c\n", ish[4].Name[1]); 	debug("\n++++++++++++++\n ish[4].Misc= %X\n", ish[4].Misc); 	#endif 	 //parse IMAGE_EXPORT_DIRECTORY 	IMAGE_EXPORT_DIRECTORY *ied = allocate(sizeof(IMAGE_EXPORT_DIRECTORY)); 	//neomemset(ied, 0, sizeof(IMAGE_EXPORT_DIRECTORY));//will cause error because maybe the 196k bufferlimit bug? 	if (pehdr32->OptionalHeader.DataDirectory[0].VirtualAddress != 0)//there is a export table
	{
		parseOffset = rva_to_raw(pehdr32, ish, pehdr32->OptionalHeader.DataDirectory[0].VirtualAddress);//get the rawoffset of Export Table

		ied->Characteristics = getdword(&parseOffset);
		ied->TimeDateStamp = getdword(&parseOffset);
		ied->MajorVersion = getword(&parseOffset);
		ied->MinorVersion = getword(&parseOffset);
		ied->Name = getdword(&parseOffset);
		ied->Base = getdword(&parseOffset);
		ied->NumberOfFunctions = getdword(&parseOffset);
		ied->NumberOfNames = getdword(&parseOffset);
		ied->AddressOfFunctions = getdword(&parseOffset);
		ied->AddressOfNames = getdword(&parseOffset);
		ied->AddressOfNameOrdinals = getdword(&parseOffset);
		#ifdef DEBUG
		uint32_t fileLength = get_file_length();
		debug("\n++++++++++++++\n parseOffset = %X\n", parseOffset);
		debug("\n++++++++++++++\n get_file_length() = %X\n", fileLength);
		debug("\n++++++++++++++\n ied->AddressOfNameOrdinals  = %X\n", ied->AddressOfNameOrdinals );
		#endif
	}

//parse IMAGE_IMPORT_DESCRIPTOR
	IMAGE_IMPORT_DESCRIPTOR *iid = allocate(pehdr32->OptionalHeader.DataDirectory[1].Size);//structsize*(tablesize/structsize)
	//neomemset(iid, 0, pehdr32->OptionalHeader.DataDirectory[1].Size);

	if (pehdr32->OptionalHeader.DataDirectory[1].VirtualAddress != 0)//there is a import table
	{
		parseOffset = rva_to_raw(pehdr32, ish, pehdr32->OptionalHeader.DataDirectory[1].VirtualAddress);//get the rawoffset of Export Table
		#ifdef DEBUG
		debug("\n++++++++++++++\n rva_to_raw(pehdr32, ish, pehdr32->OptionalHeader.DataDirectory[1].VirtualAddress) = %X\n", rva_to_raw(pehdr32, ish, pehdr32->OptionalHeader.DataDirectory[1].VirtualAddress));
		#endif
		for(int i = 0; i < pehdr32->OptionalHeader.DataDirectory[1].Size/sizeof(IMAGE_IMPORT_DESCRIPTOR); i++)
		{
			iid[i].OriginalFirstThunk = getdword(&parseOffset);
			iid[i].TimeDateStamp = getdword(&parseOffset);
			iid[i].ForwarderChain = getdword(&parseOffset);
			iid[i].Name = getdword(&parseOffset);
			iid[i].FirstThunk = getdword(&parseOffset);
		}
		#ifdef DEBUG
		debug("\n++++++++++++++\n iid[0].FirstThunk = %X\n", iid[0].FirstThunk);
		#endif
	}

//parse IMAGE_TLS_DIRECTORY
	IMAGE_TLS_DIRECTORY *itd = allocate(pehdr32->OptionalHeader.DataDirectory[9].Size);
	//neomemset(itd, 0, pehdr32->OptionalHeader.DataDirectory[9].Size);
	#ifdef DEBUG
	debug("\n++++++++++++++\n pehdr32->OptionalHeader.DataDirectory[9].Size = %X\n", pehdr32->OptionalHeader.DataDirectory[9].Size);
	#endif
	if (pehdr32->OptionalHeader.DataDirectory[9].Size != 0)//there is a TLS table
	{
		parseOffset = rva_to_raw(pehdr32, ish, pehdr32->OptionalHeader.DataDirectory[9].VirtualAddress);//get the rawoffset of Export Table
		#ifdef DEBUG
		debug("\n++++++++++++++\n rva_to_raw(pehdr32, ish, pehdr32->OptionalHeader.DataDirectory[9].VirtualAddress) = %X\n", rva_to_raw(pehdr32, ish, pehdr32->OptionalHeader.DataDirectory[9].VirtualAddress));
		#endif
		for(int i = 0; i < pehdr32->OptionalHeader.DataDirectory[9].Size/sizeof(IMAGE_IMPORT_DESCRIPTOR); i++)
		{
			itd[i].StartAddressOfRawData = getdword(&parseOffset);
			itd[i].EndAddressOfRawData = getdword(&parseOffset);
			itd[i].AddressOfIndex = getdword(&parseOffset);
			itd[i].AddressOfCallBacks = getdword(&parseOffset);
			itd[i].SizeOfZeroFill = getdword(&parseOffset);
			itd[i].Characteristics = getdword(&parseOffset);
		}
		#ifdef DEBUG
		debug("\n++++++++++++++\n itd[0].AddressOfCallBacks = %X\n", itd[0].AddressOfCallBacks );
		#endif
	}

	out->pDosHeader = imagehdr;
	out->pPEHeader = pehdr32;
	out->pSecHeader = ish;
	out->pExportDir = ied;
	out->pImportDir = iid;
	out->pTLSDir = itd;
	return;
}
 

Pasted my earlier study documents, just to test how this wordpress post functions. It turned out it worked GREAT!

Plus, this sample is quite interesting, it uses a relatively new injecting method. So here we go…


Virus Description-1.vxe(I call it MESSAGE)

General Information:

Sample MD5: 19f6d8f565d465f3ee9c03881cbc3893

Malware Classification: Trojan

Packer Details:

  • 2 layers

  • 1st layer:

  • decryption algorithm xor 0xa0:

  • jump into dynamic memory to further decrypt, uses multiple dynamic memory, bp on VirtualFree we can get to somewhere near the 2nd layer

           

  • 2nd layer is UPX

Other Details:

  • Uses SendMessageCallbackW to call a function

  • When parsing the ntdll APIs, it intentionally decrement the address by 1 to confuse reverser. The 3 APIs it tries to hide are ZwCreateSection, ZwUnmapViewOfSection and ZwMapViewOfsection

            

    • injection method, target: svchost.exe

      • CreateFileA svchost.exe with GENERIC_READ, then ReadFile. It only reads 0×1000 size of the file because it only wants to get the IMAGESIZE [PE+0x50]

      • Use the IMAGESIZE to do a VirtualAlloc

      • Reset file pointer of svchost.exe back to 0

      • Read svchost.exe again, this time, with size = IMAGESIZE.

      • Copy the svchost.exe image to another newly allocated memory

      • dword_401838 contains the encrypted code size, CreateSection with MaximumSize = [401838]+0×367, 0×367 is the size of the decryption routine

      • ZwMapViewOfSection with ProcessHandle = -1(CurrentProcess) and InheritDisposition = ViewShare, get the BaseAddress = baseaddress1

      • Copy opcode to the [baseaddress1] with size 0×367, then copy the encrypted data to it just after the opcode

      • ZwUnmapViewOfSection with handle=-1 and BaseAddress= baseaddress1

      • Create suspended process svchost.exe

      • ZwMapViewOfSection with ProcessHandle = svchost.exe handle, get the BaseAddress = baseaddress2, Close Section Handle. This maps the decryption routine and encrypted code to svchost.exe process memory space.

      • Create another section

      • ZwMapViewOfSection with ProcessHandle = -1, get the BaseAddress = baseaddress1

      • Get ThreadContext and then get the EAX value(+0Bh in CONTEXT), it contains the entry point VA. Then, use EP_VA – EP_RVA to get the ImageBase(1000000) of the svchost.exe process

      • Copy svchost.exe image to [baseaddress1]

      • in [baseaddress1], locate EntryPoint, modify it to 68 |baseaddress2|c3

      • ZwUnmapViewOfSection with process handle= svchost.exe handle and BaseAddress = ImageBase(0×1000000 in my test machine)

      • ZwUnmapViewOfSection with process handle= -1 and BaseAddress = baseaddress1

      • ZwMapViewOfSection with ProcessHandle = svchost.exe, BaseAddress = ImageBase(0×1000000). This maps the svchost.exe with EP patched to point to the newly mapped decryption routine(at baseaddress2).

                  

                         Illustration 1: loc_401090: the beginning of the decryption procedure

  • ResumeThread to run svchost.exe


  • after all it is a dll sends message to and waiting for command from a C&C server

    • server name: heppishopdrm.ru and infoodstuffshop.com, it uses fast flux to switch IPs.

    • message example hashed: “id:2887351144|bid:1|bv:516|sv:1281|la:3232247168″—— VersionInformation|some counter|version?|SocketInformation|VersionInformation

    • data is encrypted twice 1000187f 1000188c, first encryption is xor using a modified key from string:”blablablaandromeda”, second encryption is interesting and short:

.text:100017FD loc_100017FD:

.text:100017FD xor ax, ax

.text:10001800 lodsb

.text:10001801 shl ax, 4

.text:10001805 shr al, 4

.text:10001808 add ax, 616B

e.g. [A6]=>[0A] [06]=>[0A]+[61] [06]+[6B]=>[6B][71]=>’qk’

This makes sure that the encrypted will not have any symbol because the max it can get is [6B]+[0F]=[7A]

    • recv data example decrypted: [AA]=…#http://www.familytindoor.net/dbs/0090_2.exe.N…#http://www.familytindoor.net/dbs/0090_4.exe.[ZZ]

    • Depending on the byte value right before the “http://”, it can do 3 kinds of job: (1)download, execute and modify registry key (2)redirect to another C&C server (3)download and execute. Then it will send log to the C&C server after whatever job is done.

  • Currently, the downloaded file is packed by 3 layers. 1st layer is UPX, 2nd layer contains a lot of jumps to decrypt, findOPMask ff d0:f8, then because this packer writes back to itself from dynamic memory space, we can just F9 run it and go to its PE header in dump view and look for the entry point RVA. And then run again with a bp at the EP. 3rd layer is UPX again.

  • The download files are:

    • 0090_4.exe: a password stealer and detected by NOD32 with name “a variant of Win32/PSW.LdPinch.NCB trojan “

      • drops files %system32%\dbs.dat, %Application Data%\firewall\system.exe

      • one of the threads contacts a remote server, in my test machine: duffiduffii.ru/dbs/logo90.php

      • the remote server sends back encrypted data, and use it to modify dbs.dat file. (probably a config file, contains update server list)

      • one of the threads can send spam emails according to the latest config

      • has ability to steal user passwords for various applications

      • config data encryption is first RC4 xor with pre-scheduled key at:0041C6D9 and then byte xor side by side starting from bottom of the data do it [size of encrypted data] time, and the first byte xor with 0xff.

    • 0090_3.exe: a sys file

 

This is Neo Tan

from Fortinet

Vancouver BC, Canada

 

123 321 wow

© 2012 Neo Tan's Blog Suffusion theme by Sayontan Sinha