二进制加壳原理与实现

Home / Article 百晓生 2019-1-22 2873


软件加壳的目的是为了防止外部程序对软件进行静态反汇编分析或者动态分析达到对软件保护的目的。壳子的种类很多,大体种类分2种二进制壳和指令壳,二进制壳不改变硬编码的含义,指令壳则改变了原有硬编码的含义。 本篇文章介绍二进制壳的加壳的一种思路及实现。

1,加壳过程

加壳流程如下图所示:


(1)将待加壳程序全部进行加密,加密可以采用任何方式;(2)在壳子程序中新增一个节,大小为待加壳程序的大小+解壳程序的大小;(3)将解壳程序拷贝到1位置,加密后的待加壳程序拷贝到2位置;(4)修改壳子程序的OEP到新增节的位置。(5)存盘壳子程序。

这里的壳子程序可以是任意程序,只提供了外壳。壳子程序是任意的,新增节后的位置也是变化的,所以解壳程序一定是shell程序。

2,解壳程序

打开壳子程序的时候,程序会从OEP开始启动,执行的是解壳程序:(1)读取加密后的待加壳程序2;(2)解密得到原来的程序;(3)以挂起的方式创建进程,创建的进程是壳子程序进程,这里要注意的是以挂起的方式创建的进程,没有加载任何模块。(4)获取新创建的进程的Contex环境,(5)卸载外壳程序,卸载掉原来壳子程序imageBase开始到程序内存镜像这部分空间的数据,此时只有空间,而内部的内存处于没有使用状态。(6)在创建的进程内部在待加壳程序的imageBase处申请内存空间,大小是待加壳程序的imageBase。(7)将待加壳程序拉伸,复制到刚刚申请到的imageBase处,此时,在壳子的空间内部,已经偷天换日编程了待加壳的程序了。(8)修改外壳Contex的imagebase=待加壳的imagebase,OEP=待加壳程序的OEP了。(9)恢复外壳程序的主线程。(10)主线程启动后,解壳结束,壳子内部运行的是待加壳程序。好一个偷天换日。

整个解壳程序都需要使用shellCode的方式来写,编译后,抠出硬编码存储成字节数组,所以拷贝的解壳程序就是这个字节数组。

3,加壳代码

int _tmain(int argc, _TCHAR* argv[])
{
    char* szShell = "../Debug/shell.exe";
    char* szObj = "../Debug/xxx.exe";
 
    HPE shell = loadPE(szShell);
    HPE object = loadPE(szObj);
 
    int length = getFileLength(object);
    BYTE* src = getFileBuffer(object);
    HPE newObj = addSection(shell, ".src", length + 0x2000, 0x60000020);
    IMAGE_OPTIONAL_HEADER32* pOptionalHeader = getOptionalHeader(newObj);
     
    IMAGE_SECTION_HEADER* pShellSection = getLastSection(newObj);
    pOptionalHeader->AddressOfEntryPoint = pShellSection->VirtualAddress;
    memcpy((void*)(pShellSection->PointerToRawData + (int)getFileBuffer(newObj)), myshellcode, sizeof(myshellcode));
    memcpy((void*)(pShellSection->PointerToRawData + (int)getFileBuffer(newObj) + 0x2000), src, length);
     
    writeFile("../Debug/test.exe", newObj);
    releasePE(newObj);
    return 0;
}

4,解壳代码

void Decrypt()
{
    BOOL bResult = TRUE;
    HANDLE hProcess = NULL;
    TERMINATEPROCESS TerminateProcess = NULL;
    try
    {
 
        HINSTANCE hKernel32 = NULL;
        __asm
        {
            push eax
                mov eax, fs:[0x30]        // PEB
                mov eax, [eax + 0x0c]       // LDR
                mov eax, [eax + 0x0c]       // 本进程模块, LDA_DATA_TABLE_ENTRY 结构
                mov eax, [eax]            // ntdll 模块
                mov eax, [eax]            // kernel32 模块
                mov eax, [eax + 0x18]       // dllBase
                mov hKernel32, eax
                pop eax
        }
 
        char szGetProcAddress[] = { 'G', 'e', 't', 'P', 'r', 'o', 'c', 'A', 'd', 'd', 'r', 'e', 's', 's', '\0' };
        GETPROCADDRESS GetProcAddress = NULL;
        // 在 kernel32中寻找 GetProcAddress 函数
        IMAGE_DOS_HEADER* pDosHeader = (IMAGE_DOS_HEADER*)hKernel32;
        IMAGE_NT_HEADERS* pNtHeader = (IMAGE_NT_HEADERS*)((DWORD)hKernel32 + pDosHeader->e_lfanew);
        IMAGE_EXPORT_DIRECTORY* pExportDirectory = (IMAGE_EXPORT_DIRECTORY*)((DWORD)hKernel32 + pNtHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress);
        short* functionOrdinals = (short*)((DWORD)hKernel32 + pExportDirectory->AddressOfNameOrdinals);
        int* adddressOfNames = (int*)((DWORD)hKernel32 + pExportDirectory->AddressOfNames);
        int* addressOfFunctions = (int*)((DWORD)hKernel32 + pExportDirectory->AddressOfFunctions);
        for (int i = 0; i < pExportDirectory->NumberOfNames; ++i)
        {
            // 第 i 个函数名地址
            char* name = (char*)((DWORD)hKernel32 + adddressOfNames[i]);
            char* left = name;
            char* right = szGetProcAddress;
 
            bool res = true;
            while (*left || *right)
            {
                if (*left != *right)
                {
                    res = false;
                    break;
                }
                ++left;
                ++right;
            }
            if (res)
            {
                GetProcAddress = (GETPROCADDRESS)((DWORD)hKernel32 + addressOfFunctions[functionOrdinals[i]]);
                break;
            }
        }
 
        pDosHeader = NULL;
        pNtHeader = NULL;
 
        char szLoadLibrary[] = { 'L', 'o', 'a', 'd', 'L', 'i', 'b', 'r', 'a', 'r', 'y', 'A', '\0' };
        char szGetModuleHandle[] = { 'G', 'e', 't', 'M', 'o', 'd', 'u', 'l', 'e', 'H', 'a', 'n', 'd', 'l', 'e', 'A', '\0' };
        char szCreateProcess[] = { 'C', 'r', 'e', 'a', 't', 'e', 'P', 'r', 'o', 'c', 'e', 's', 's', 'A', '\0' };
        char szGetModuleFileName[] = { 'G', 'e', 't', 'M', 'o', 'd', 'u', 'l', 'e', 'F', 'i', 'l', 'e', 'N', 'a', 'm', 'e', 'A', '\0' };
        char szGetThreadContext[] = { 'G', 'e', 't', 'T', 'h', 'r', 'e', 'a', 'd', 'C', 'o', 'n', 't', 'e', 'x', 't', '\0'};
        char szReadProcessMemory[] = { 'R', 'e', 'a', 'd', 'P', 'r', 'o', 'c', 'e', 's', 's', 'M', 'e', 'm', 'o', 'r', 'y', '\0' };
        char szZwUnmapViewOfSection[] = { 'Z', 'w', 'U', 'n', 'm', 'a', 'p', 'V', 'i', 'e', 'w', 'O', 'f', 'S', 'e', 'c', 't', 'i', 'o', 'n', '\0' };
        char szNtDll[] = { 'n', 't', 'd', 'l', 'l', '.', 'd', 'l', 'l', '\0' };
        char szMemcpy[] = { 'm', 'e', 'm', 'c', 'p', 'y', '\0' };
        char szMemset[] = { 'm', 'e', 'm', 's', 'e', 't', '\0' };
        char szMSVCRT[] = { 'm', 's', 'v', 'c', 'r', 't', '.', 'd', 'l', 'l', '\0' };
        char szMalloc[] = { 'm', 'a', 'l', 'l', 'o', 'c', '\0' };
        char szFree[] = { 'f', 'r', 'e', 'e', '\0' };
        char szVirtualAllocEx[] = { 'V', 'i', 'r', 't', 'u', 'a', 'l', 'A', 'l', 'l', 'o', 'c', 'E', 'x', '\0' };
        char szWriteProcessMemory[] = { 'W', 'r', 'i', 't', 'e', 'P', 'r', 'o', 'c', 'e', 's', 's', 'M', 'e', 'm', 'o', 'r', 'y', '\0' };
        char szSetThreadContext[] = { 'S', 'e', 't', 'T', 'h', 'r', 'e', 'a', 'd', 'C', 'o', 'n', 't', 'e', 'x', 't', '\0' };
        char szResumeThread[] = { 'R', 'e', 's', 'u', 'm', 'e', 'T', 'h', 'r', 'e', 'a', 'd', '\0' };
        char szTerminateProcess[] = { 'T', 'e', 'r', 'm', 'i', 'n', 'a', 't', 'e', 'P', 'r', 'o', 'c', 'e', 's', 's', '\0' };
        char szExitProcess[] = { 'E', 'x', 'i', 't', 'P', 'r', 'o', 'c', 'e', 's', 's', '\0' };
 
        LOADLIBRARY LoadLibraryA = (LOADLIBRARY)GetProcAddress(hKernel32, szLoadLibrary);
        GETMODULEHANDLE GetModuleHandleA = (GETMODULEHANDLE)GetProcAddress(hKernel32, szGetModuleHandle);
        CREATEPROCESS CreateProcessA = (CREATEPROCESS)GetProcAddress(hKernel32, szCreateProcess);
        GETMODULEFILENAME GetModuleFileNameA = (GETMODULEFILENAME)GetProcAddress(hKernel32, szGetModuleFileName);
        GETTHREADCONTEXT GetThreadContext = (GETTHREADCONTEXT)GetProcAddress(hKernel32, szGetThreadContext);
        READPROCESSMEMORY ReadProcessMemory = (READPROCESSMEMORY)GetProcAddress(hKernel32, szReadProcessMemory);
        WRITEPROCESSMEMORY WriteProcessMemory = (WRITEPROCESSMEMORY)GetProcAddress(hKernel32, szWriteProcessMemory);
        VIRTUALALLOCEX VirtualAllocEx = (VIRTUALALLOCEX)GetProcAddress(hKernel32, szVirtualAllocEx);
        SETTHREADCONTEXT SetThreadContext = (SETTHREADCONTEXT)GetProcAddress(hKernel32, szSetThreadContext);
        RESUMETHREAD ResumeThread = (RESUMETHREAD)GetProcAddress(hKernel32, szResumeThread);
        TerminateProcess = (TERMINATEPROCESS)GetProcAddress(hKernel32, szTerminateProcess);
        EXITPROCESS ExitProcess = (EXITPROCESS)GetProcAddress(hKernel32, szExitProcess);
 
        HMODULE hNtDll = GetModuleHandleA(szNtDll);
        ZWUNMAPVIEWOFSECTION ZwUnmapViewOfSection = (ZWUNMAPVIEWOFSECTION)GetProcAddress(hNtDll, szZwUnmapViewOfSection);
        MEMCPY memcpy = (MEMCPY)GetProcAddress(hNtDll, szMemcpy);
        MEMSET memset = (MEMSET)GetProcAddress(hNtDll, szMemset);
 
        HMODULE hMSVCRT = LoadLibraryA(szMSVCRT);
        MALLOC malloc = (MALLOC)GetProcAddress(hMSVCRT, szMalloc);
        FREE free = (FREE)GetProcAddress(hMSVCRT, szFree);
 
 
        HANDLE hModule = GetModuleHandleA(NULL);
        pDosHeader = (IMAGE_DOS_HEADER*)hModule;
        pNtHeader = (IMAGE_NT_HEADERS*)((DWORD)hModule + pDosHeader->e_lfanew);
        IMAGE_SECTION_HEADER* pSectionHeader = (IMAGE_SECTION_HEADER*)((DWORD)&pNtHeader->OptionalHeader + pNtHeader->FileHeader.SizeOfOptionalHeader);
        pSectionHeader = pSectionHeader + pNtHeader->FileHeader.NumberOfSections - 1;
 
        // 获取程序数据
        char* src = (char*)((DWORD)hModule + pSectionHeader->VirtualAddress + 0x2000);
        int srcSize = pSectionHeader->SizeOfRawData - 0x2000;
 
        // 拉伸
        pDosHeader = (IMAGE_DOS_HEADER*)src;
        pNtHeader = (IMAGE_NT_HEADERS*)((DWORD)src + pDosHeader->e_lfanew);
        IMAGE_OPTIONAL_HEADER32* pOptionalHeader = &pNtHeader->OptionalHeader;
        pSectionHeader = (IMAGE_SECTION_HEADER*)((DWORD)pOptionalHeader + pNtHeader->FileHeader.SizeOfOptionalHeader);
        int length = pSectionHeader[pNtHeader->FileHeader.NumberOfSections - 1].VirtualAddress + pSectionHeader[pNtHeader->FileHeader.NumberOfSections - 1].SizeOfRawData;
 
        //有时候会出现length比Image还要大
        length = length > pOptionalHeader->SizeOfImage ? length : pOptionalHeader->SizeOfImage;
 
        char* imageBuffer = (char*)malloc(length);
        memset(imageBuffer, 0, length);
        memcpy(imageBuffer, src, pOptionalHeader->SizeOfHeaders);
        for (int i = 0; i < pNtHeader->FileHeader.NumberOfSections; ++i)
        {
            memcpy(imageBuffer + pSectionHeader->VirtualAddress, src + pSectionHeader->PointerToRawData, pSectionHeader->SizeOfRawData);
            ++pSectionHeader;
        }
 
 
        char szModuleName[MAX_PATH];
        GetModuleFileNameA(NULL, szModuleName, MAX_PATH);
 
        STARTUPINFO si;
        memset(&si, 0, sizeof(si));
        PROCESS_INFORMATION pi;
        memset(&pi, 0, sizeof(pi));
        si.cb = sizeof(si);
        CreateProcessA(NULL, szModuleName, NULL, NULL, FALSE, CREATE_SUSPENDED, NULL, NULL, &si, &pi);
        hProcess = pi.hProcess;
        CONTEXT context;
        context.ContextFlags = CONTEXT_FULL;
        GetThreadContext(pi.hThread, &context);
        DWORD dwEntryPoint = context.Eax;
        DWORD baseAddress = context.Ebx + 8;
        DWORD shellImageBase = 0;
        ReadProcessMemory(pi.hProcess, (LPVOID)baseAddress, &shellImageBase, 4, NULL);
        ZwUnmapViewOfSection(pi.hProcess, (PVOID)shellImageBase);
        LPVOID buffer = VirtualAllocEx(pi.hProcess, (LPVOID)pOptionalHeader->ImageBase, pOptionalHeader->SizeOfImage, MEM_RESERVE | MEM_COMMIT, PAGE_EXECUTE_READWRITE);
        if (!buffer)
        {
            VirtualAllocEx(pi.hProcess, NULL, pOptionalHeader->SizeOfImage, MEM_RESERVE | MEM_COMMIT, PAGE_EXECUTE_READWRITE);
            pDosHeader = (IMAGE_DOS_HEADER*)imageBuffer;
            pNtHeader = (IMAGE_NT_HEADERS*)((DWORD)imageBuffer + pDosHeader->e_lfanew);
            IMAGE_OPTIONAL_HEADER32* pOptionalHeader = &pNtHeader->OptionalHeader;
            IMAGE_BASE_RELOCATION* pBaseRelocation = (IMAGE_BASE_RELOCATION*)((DWORD)imageBuffer + pOptionalHeader->DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].VirtualAddress);
 
            while (pBaseRelocation->SizeOfBlock || pBaseRelocation->VirtualAddress)
            {
                short* pTable = (short*)((int)pBaseRelocation + 8);
                int len = (pBaseRelocation->SizeOfBlock - 8) / 2;
                for (int i = 0; i < len; ++i)
                {
                    DWORD RVA = pBaseRelocation->VirtualAddress + (pTable[i] & 0x0FFF);
                    if (((pTable[i] & 0xF000) >> 12) == 3)
                    {
                        *(DWORD*)(RVA + (DWORD)imageBuffer) += ((DWORD)buffer - pOptionalHeader->ImageBase);
                    }
                }
                pBaseRelocation = (IMAGE_BASE_RELOCATION*)((DWORD)pBaseRelocation + pBaseRelocation->SizeOfBlock);
            }
            pOptionalHeader->ImageBase = (DWORD)buffer;
        }
        WriteProcessMemory(pi.hProcess, buffer, imageBuffer, pOptionalHeader->SizeOfImage, NULL);
        WriteProcessMemory(pi.hProcess, (LPVOID)(context.Ebx + 8), &buffer, 4, NULL);
        context.Eax = pOptionalHeader->ImageBase + pOptionalHeader->AddressOfEntryPoint;
        SetThreadContext(pi.hThread, &context);
        ResumeThread(pi.hThread);
        free(imageBuffer);
        ExitProcess(0);
    }
    catch (...)
    {
        if (hProcess) TerminateProcess(hProcess, 0);
        return;
    }
}

5,如何脱壳

这种壳子解密完后,内存中的程序就是原来拉伸后的文件,只需要一步一步F8跟进,就会跟到OEP,然后把内存dump出来就可以了。

转自看雪论坛

本文链接:https://www.it72.com/12474.htm

推荐阅读
最新回复 (0)
返回