使用 C++ 写壳

  • A+
所属分类:安全开发
使用 C++ 写壳

本文为看雪论精华文章

看雪论坛作者ID:三一米田



使用 C++ 写壳

一、框架

使用 C++ 写壳

编写一个加壳器项目、一个动态链接库项目,将动态链接库的.text、.data、.rdata区段合并为一个区段。
//1.  将 DLL 设置为 Release 版本进行编译、小、内联了一些函数//2.  CC++ -> 代码生成 -> GS安全检查 -> 禁用 (取消一些库函数的调用)//3.  CC++ -> 所有选项 -> 运行库 -> MT (取消一些库函数的调用)//4.  合并区段,并设置属性为可读可写可执行(0xE00000E0)#pragma comment(linker, "/merge:.data=.text")#pragma comment(linker, "/merge:.rdata=.text")#pragma comment(linker, "/section:.text,RWE")

1.1 为了方便调试,进行如下设置:
  • 设置 .exe(加壳器) 和 .dll(stub) 文件的输出路径为同一路径
  • 项目属性 -> 常规 -> 输出目录
  • 设置工作目录为刚才的输出路径
  • 项目属性 -> 调试 -> 工作目录

使用 C++ 写壳


1.2 STUB 区域代码的重定位问题
 
起初代码是放在了 dll 中,需要重定位的内容,是以 dll 实际加载基址设置的,现在我们将dll中的三个区段合并之后放到原程序后面了,那么涉及到重定位的问题:首先了解一下一个虚拟地址的组成:
 
VA = ImageBase + 区段首地址 + 区段偏移
使用 C++ 写壳



使用 C++ 写壳

二、加壳器

使用 C++ 写壳


2.1 打开被加壳程序


在加壳器中,首先打开需要被加壳的程序,读取需要加壳的程序的PE文件到内存中。
//打开、读取、判断文件VOID Pack::OpenPeFile(LPCSTR Path) {    HANDLE hFile = CreateFile(Path, GENERIC_READ, FILE_SHARE_READ, NULL,        OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);    if (hFile == INVALID_HANDLE_VALUE)        print("打开文件失败");//自己封装的判断函数    PeSize = GetFileSize(hFile, NULL);    PeBase = (DWORD)malloc(PeSize);    DWORD ReadSize = 0;    ReadFile(hFile, (LPVOID)PeBase, PeSize, &ReadSize, NULL);    if (pDosHead(PeBase)->e_magic!=0x5A4D)        print("不是有效的PE文件");       else if (pNtHead(PeBase)->Signature != 0x4550)        print("不是有效的PE文件");    CloseHandle(hFile);}

新建一个动态链接库项目,作为壳代码使用。
#include "../pack/Share.h"                  // 壳代码与加壳器之间共用的结构体类型头文件#include"../aplib/aplib.h"                  // 压缩库头文件#include <Windows.h>#pragma comment(lib, "../aplib/aplib.lib")    // 压缩库// 对 dll 的区段合并为.text区段,并设置区段属性为[读写执行]#pragma comment(linker, "/merge:.data=.text")#pragma comment(linker, "/merge:.rdata=.text")#pragma comment(linker, "/section:.text,RWE")// 去除所有导出函数和导出变量的名称粉碎extern "C"{    __declspec(dllexport) SHARE ShareData;                    //导出变量,用于壳代码与加壳器之间的通讯    __declspec(dllexport) __declspec(naked) void start()     //导出函数地址,将会作为新的OEP{               _asm{            mov eax, fs:[0x30]            mov eax, [eax + 0x8]        //获取当前进程gImageBase            mov gImageBase, eax        }        GetFunAdd();                    //获取GetProcAddress        GetApi();                        //获取API        CallShllFun();                    //密码弹窗        UnCompression(ShareData);         //解压        DecSection();                    //解密代码段        DecIat(ShareData);                //加密IAT        FixReloca(ShareData);            //修复重定位        CallTls(ShareData);                //处理TLS               Debugging();                    //反调试        __asm        {            mov eax, gImageBase                //获取当前进程gImageBase            mov ebx, ShareData.DestOEP         //获取原程序的OEP            add eax,ebx            jmp eax        }    }}

2.2 打开dll文件


在加壳器中,打开动态链接库生成的dll文件,以便后面获取合并区段中的壳代码。
//打开 壳代码的DLL 文件,获取基址VOID  Pack::OpenDllFile(LPCSTR Path) {    //加载模块到内存中,并且不调用DllMain    DllBase = (DWORD)LoadLibraryEx(Path, NULL, DONT_RESOLVE_DLL_REFERENCES);    //获取导出函数StartOfsset地址    StartOfsset = (DWORD)GetProcAddress((HMODULE)DllBase, "start");    if (!StartOfsset)        print("获取DLLMain的导出函数失败");    StartOfsset = StartOfsset - DllBase - (DWORD)GetSecHeadAddr(DllBase, DllSecName)->VirtualAddress;    //获取导出变量通讯结构体,加壳器需要使用到    pShareData = (SHARE*)GetProcAddress((HMODULE)DllBase, "ShareData");}

2.3 新增并拷贝区段


打开完dll和需要加壳的exe后,对exe的PE结构操作:新增一个区段,将dll中合并后的.text区段拷贝到新区段中。
//添加并拷贝一个新区段VOID Pack::CopySection(LPCSTR DestName) {    if (strlen(DestName) > 7)        print("新区段名称太长");    //获取需要添加的区段    auto pLastSection = IMAGE_FIRST_SECTION(pNtHead(PeBase)) + (pFileHead(PeBase)->NumberOfSections - 1);    auto pNewSection = pLastSection + 1;    //获取区段头的首地址    auto SrcAddr = GetSecHeadAddr(DllBase, DllSecName);    //拷贝区段头    memcpy(pNewSection, SrcAddr, sizeof(IMAGE_SECTION_HEADER));    //修改属性    pNewSection->VirtualAddress = pLastSection->VirtualAddress +        AlignmentToSize(pLastSection->Misc.VirtualSize, pOptionHead(PeBase)->SectionAlignment);    pNewSection->PointerToRawData = pLastSection->PointerToRawData +        AlignmentToSize(pLastSection->SizeOfRawData, pOptionHead(PeBase)->FileAlignment);    // Name    memcpy(pNewSection->Name, DestName, strlen(DestName));    // NumberOfSections    pFileHead(PeBase)->NumberOfSections += 1;    // SizeOfImage大小    pOptionHead(PeBase)->SizeOfImage +=        AlignmentToSize(pNewSection->Misc.VirtualSize, pOptionHead(PeBase)->SectionAlignment);    // 从新分配空间,将新区段添加到堆空间中    PeSize = pNewSection->SizeOfRawData + pNewSection->PointerToRawData;    PeBase = (DWORD)realloc((VOID*)PeBase, PeSize);       return;}

AlignmentToSize函数:根据大小以及对齐粒度,获取对齐后的大小。
//根据大小以及对齐粒度获取对齐大小DWORD Pack::AlignmentToSize(DWORD Size, DWORD Algmt) {    return (Size / Algmt)?(((Size / Algmt) + 1)*Algmt):Algmt;}

2.4 设置OEP


拷贝完成之后,将程序的OEP设置为壳代码的OEP(也就是刚刚拷贝过来的区段),并且备份原程序的OEP。
//设置OEPVOID Pack::SetOep() {       //在重定位之前,保存原程序OEP    pShareData->DestOEP = pOptionHead(PeBase)->AddressOfEntryPoint;    //设置OEP为壳代码OEP    pOptionHead(PeBase)->AddressOfEntryPoint = StartOfsset + GetSecHeadAddr(PeBase,".pack")->VirtualAddress;   }

2.5 加密区段


设置完OEP,开始对区段加密,加密的实现很简单,就是获取需要加密的区段首地址和大小,循环加密。加密算法这里就使用简单的异或。
//加密区段VOID Pack::Encryption(CHAR* SectionName) {    //找到需要加密的区段    auto SectionHead = GetSecHeadAddr(PeBase, SectionName);    //加密的大小和加密起始地址    DWORD Size = SectionHead->Misc.VirtualSize;    CHAR* Address =(CHAR*)(SectionHead->PointerToRawData + PeBase);    for (size_t i = 0; i < Size; i++){        *Address ^= 0x15;        Address++;    }}

2.6 压缩


加密完之后,对原程序的.text区段压缩,压缩的原理如下图,此处仅压缩了.text区段。 
使用 C++ 写壳
//压缩VOID Pack::Compression(){       int SecTextSize = GetSecHeadAddr(PeBase, ".text")->SizeOfRawData;    pShareData->SizeOfRawData = SecTextSize;    char* TextSecData = (char*)(GetSecHeadAddr(PeBase, ".text")->PointerToRawData + PeBase);    //被压缩的数据,Packed保存压缩数据的空间,WorkMem为完成压缩需要使用的空间    char* Packed = (char*)malloc(aP_max_packed_size(SecTextSize));    char* WorkMem = (char*)malloc(aP_workmem_size(SecTextSize));    //压缩后的大小    int OutSize = aPsafe_pack(TextSecData, Packed, SecTextSize, WorkMem, NULL, NULL);    //对齐后的大小    int AlignSize = OutSize % 0x200 == 0 ? OutSize : (OutSize / 0x200 + 1) * 0x200;    //新空间大小    DWORD NewFileSize = PeSize - GetSecHeadAddr(PeBase, ".text")->SizeOfRawData + AlignSize;    //申请新的空间大小 文件大小 - 区段在文件中的大小 + 压缩后的大小(不对齐)    DWORD NewFileBase = (DWORD)malloc(NewFileSize);    //TextSecData之前的数据    DWORD PreText = GetSecHeadAddr(PeBase, ".text")->PointerToRawData - 1;    //拷贝TextSecData段之前的数据    memcpy((LPVOID)NewFileBase, (LPVOID)PeBase, PreText);    //拷贝压缩部分的数据    memcpy((LPVOID)(NewFileBase + PreText + 1), Packed, OutSize);    //拷贝TextSecData段后面的数据    LPVOID DestAddr = (LPVOID)(NewFileBase + PreText + AlignSize + 1);    DWORD TextSecSize = GetSecHeadAddr(PeBase, ".text")->SizeOfRawData;    DWORD TextSecPointRaw = GetSecHeadAddr(PeBase, ".text")->PointerToRawData;    LPVOID SrcAddr = (LPVOID)(PeBase + TextSecSize + TextSecPointRaw);    DWORD LastSize = NewFileSize - PreText - AlignSize;    memcpy(DestAddr, SrcAddr, LastSize);    //free(&FileBase);    PeBase = NewFileBase;    //free((void*)NewFileBase);    NewFileBase = NULL;    // 1. 获取到目标模块的区段表       auto Sections = IMAGE_FIRST_SECTION(pNtHead(PeBase));    // 2. 使用文件头中的区段数量遍历区段表       WORD Count = pFileHead(PeBase)->NumberOfSections;    BOOL bChangeFoa = FALSE;    for (WORD i = 0; i < Count; ++i)    {        if (bChangeFoa) {            Sections[i].PointerToRawData = Sections[i].PointerToRawData - GetSecHeadAddr(PeBase, ".text")->SizeOfRawData + AlignSize;        }        // 3. .text区段之前的区段不改变,操作.text区段之后的区段        if (!memcmp(Sections[i].Name, ".text", 7)) {            bChangeFoa = TRUE;        }    }       pShareData->TextCompressSize = OutSize;    GetSecHeadAddr(PeBase, ".text")->SizeOfRawData = AlignSize;    pShareData->TextRVA = GetSecHeadAddr(PeBase, ".text")->VirtualAddress;    PeSize = NewFileSize;}

压缩实现之后的大小变化:因为只是压缩了一个.text区段所以压缩率并不高,只是少了600KB左右。
使用 C++ 写壳


2.7 IAT加密


我们先看一下程序从编译到运行之间的流程:
 
编译:将单个的 .c 或 .cpp编译成中间文件 (.obj),在VS 下,这个过程由 cl.exe程序完成。
 
链接:将编译出的.obj 中间文件、系统的启动文件和用到的库文件链接成一个可执行文件 (.exe)。VS下由 link.exe程序完成
 
装载:将一个可执行文件映射到虚拟地址空间并执行,由操作系统完成。在装载的过程中,完了让程序正常被执行,会有下列几个步骤:
  1. 判断是否开启重定位,如果开启了,将 PE 文件加载到指定位置,并且修复目标PE 文件的重定位。

  2. 遍历导入表,加载使用到的所有模块到内存,修复模块相关的信息,并根据导入表中的函数名称,填充所有的IAT地址项。

  3. 查看当前是否存在TLS回调函数,如果存在,则传入进程创建事件,依次调用 所有的TLS回调函数。

  4. 以PE文件中的AddressOfEntryPoint为起始位置,创建线程并运行。


我们的IAT加密是在壳代码中完成的,但是为了能让壳代码顺利接管到IAT表,需要在令程序在装载的时候不填充IAT表
//令壳代码接管IAT表VOID Pack::SetIatTo0() {    // 在重定位之前设置    pShareData->ImpTableVA = pOptionHead(PeBase)->DataDirectory[1].VirtualAddress;//备份原导入表RVA    pOptionHead(PeBase)->DataDirectory[1].VirtualAddress = 0;    //清零    pOptionHead(PeBase)->DataDirectory[12].VirtualAddress = 0;    //第[12]项也是导入表,也需要清零}

2.8 处理TLS


一个程序如果有TLS会在程序运行之前就调用TLS里面的回调函数,由于我们运行的是加壳后的程序,原程序的代码段已经被加密,直接运行TLS肯定不行,我们需要手动处理TLS,在原程序中备份TLS以及TLS的回调函数数组,然后在壳代码中,程序解密后运行前,自己手动遍历TLS回调函数,手动循环调用一次,然后在恢复TLS。
//备份TLS后清空TLSVOID Pack::SetTls() {    pShareData->TlsVirtualAddress = pOptionHead(PeBase)->DataDirectory[9].VirtualAddress;    //如果存在TLS表    if (pShareData->TlsVirtualAddress) {        pOptionHead(PeBase)->DataDirectory[9].VirtualAddress = 0;        DWORD TlsFoa = RvaToOffset(PeBase, pShareData->TlsVirtualAddress);        auto TlsTable = (PIMAGE_TLS_DIRECTORY)(TlsFoa + PeBase);        //备份TLS的回调函数        pShareData->TlsCallBackTableVa = TlsTable->AddressOfCallBacks;    }}

2.9 修复重定位


重定位是一个比较麻烦的问题,还需要考虑到随机基址的问题。
 
首先,dll合并后的.text区段由于ImageBase和区段RVA改变了,需要先修复dll的.text区段的重定位项。
使用 C++ 写壳
// 对壳代码进行重定位VOID Pack::FixReloc(){    DWORD OldImageBase = pOptionHead(DllBase)->ImageBase;    DWORD NewImageBase = pOptionHead(PeBase)->ImageBase;    DWORD OldSectionBase = GetSecHeadAddr(DllBase, ".text")->VirtualAddress;    DWORD NewSectionBase = GetSecHeadAddr(PeBase, ".pack")->VirtualAddress;    auto Relocs = (PIMAGE_BASE_RELOCATION)(DllBase + pOptionHead(DllBase)->DataDirectory[5].VirtualAddress);    // 遍历重定位表    while (Relocs->SizeOfBlock)    {        DWORD OldProtect = 0;        VirtualProtect((LPVOID)(DllBase + Relocs->VirtualAddress), 0x1000, PAGE_READWRITE, &OldProtect);        TypeOffset* items = (TypeOffset*)(Relocs + 1);        //重定位项数量        int count = (Relocs->SizeOfBlock - 8) / 2;        //遍历并修复重定位项        for (int i = 0; i < count; ++i)    {                       if (items[i].Type == 3)    {                DWORD* item = (DWORD*)(pOptionHead(DllBase)->ImageBase + Relocs->VirtualAddress + items[i].Offset);                *item = *item - OldImageBase - OldSectionBase + NewImageBase + NewSectionBase;            }        }        VirtualProtect((LPVOID)(DllBase + Relocs->VirtualAddress), 0x1000, OldProtect, &OldProtect);        Relocs = (PIMAGE_BASE_RELOCATION)(Relocs->SizeOfBlock + (DWORD)Relocs);    }    // 关闭源程序的随机基址    //pOptionHead(PeBase)->DllCharacteristics &= ~IMAGE_DLLCHARACTERISTICS_DYNAMIC_BASE;}

随机基址的问题:由于有了随机基址,即使我们上面修复了重定位项,程序也不能正常运行,我们可以替换原程序的重定位表,让原程序帮我们修复壳代码的重定位项,当程序装载之后,壳代码的重定位项已经修复为随机基址之后的地址了,我们就可以在壳代码中修复原程序的重定位表,壳代码修复原程序的重定位项需要使用到原程序的重定位表,因此,还需要备份一下原程序的重定位表地址。
 
那如何替换原程序的重定位表呢?新建一个区段,这个区段用于存放DLL的重定位表,然后备份原程序的重定位表后,将原程序的重定位表指向新建的区段。完成重定位表的替换。
使用 C++ 写壳
//备份并替换重定位表VOID Pack::SetReloc() {    //1.备份原程序的重定位表地址,将原程序的重定位写入到通讯结构体中,将dll的重定位表替换到原程序的重定位表    //2.在壳代码中,系统会帮我们修复壳代码的重定位,待原程序解密之后,在壳代码中获取通讯结构体,获取原程序重定位表,修复原程序重定位    DWORD DllImageBase = pOptionHead(DllBase)->ImageBase;    //备份原重定位表    pShareData->dwRelocRva = pOptionHead(PeBase)->DataDirectory[5].VirtualAddress;    pShareData->dwRelocSize = pOptionHead(PeBase)->DataDirectory[5].Size;    pShareData->OldImageBase = pOptionHead(PeBase)->ImageBase;    //Dll重定位表    auto DllBaseReloc = (PIMAGE_BASE_RELOCATION)(pOptionHead(DllBase)->DataDirectory[5].VirtualAddress + DllImageBase);    DWORD DllRelocaSize = pOptionHead(DllBase)->DataDirectory[5].Size;       //新增区段    AddSection(".mysec", DllRelocaSize);    auto NewSecHed = GetSecHeadAddr(PeBase, ".mysec");    auto OldSecHed = GetSecHeadAddr(DllBase, ".text");    auto PackSecHed = GetSecHeadAddr(PeBase, ".pack");    auto NewRelocaSection = (PIMAGE_BASE_RELOCATION)(NewSecHed->PointerToRawData + PeBase);    DWORD OldSectionAddr = (DWORD)(OldSecHed->VirtualAddress + DllBase);     memcpy((DWORD*)NewRelocaSection, (DWORD*)(DllBaseReloc), DllRelocaSize);    while (NewRelocaSection->VirtualAddress){        //新的内存页起始RVA = 原RVA - 原段基址 +.pack段基址        NewRelocaSection->VirtualAddress = NewRelocaSection->VirtualAddress - (OldSectionAddr -DllBase) + PackSecHed->VirtualAddress;        NewRelocaSection = (PIMAGE_BASE_RELOCATION)(NewRelocaSection->SizeOfBlock + (DWORD)NewRelocaSection);    }       //替换原程序重定位表    pOptionHead(PeBase)->DataDirectory[5].VirtualAddress = NewSecHed->VirtualAddress;    pOptionHead(PeBase)->DataDirectory[5].Size = DllRelocaSize;   }
添加区段的函数:
//添加一个新区段VOID Pack::AddSection(LPCSTR Name, DWORD Size) {    PIMAGE_DOS_HEADER pDos = pDosHead(PeBase);    PIMAGE_NT_HEADERS pNt = pNtHead(PeBase);    //获取需要添加的区段    PIMAGE_SECTION_HEADER pLastSection = (IMAGE_FIRST_SECTION(pNt)) + (pNt->FileHeader.NumberOfSections - 1);    PIMAGE_SECTION_HEADER pNewSection = pLastSection + 1;    //Name    memcpy(pNewSection->Name, Name, 7);    //VirtualSize    pNewSection->Misc.VirtualSize = AlignmentToSize(Size, pNt->OptionalHeader.SectionAlignment);    //VirtualAddress = 最后一个区段的 VirtualAddress +最后一个区段内存大小    pNewSection->VirtualAddress = pLastSection->VirtualAddress +        AlignmentToSize(pLastSection->Misc.VirtualSize, pNt->OptionalHeader.SectionAlignment);    //SizeOfRawData    pNewSection->SizeOfRawData = AlignmentToSize(Size, pNt->OptionalHeader.FileAlignment);    //PointerToRawData = 最后一个区段的 PointerToRawData + 最后一个区段文件大小    pNewSection->PointerToRawData = pLastSection->PointerToRawData +        AlignmentToSize(pLastSection->SizeOfRawData, pNt->OptionalHeader.FileAlignment);    //Characteristics    pNewSection->Characteristics = 0xE00000E0;    //NumberOfSections    pNt->FileHeader.NumberOfSections += 1;    //SizeOfImage大小    pNt->OptionalHeader.SizeOfImage += AlignmentToSize(pNewSection->Misc.VirtualSize, pNt->OptionalHeader.SectionAlignment);    //从新分配空间,将新区段添加到堆空间中    PeSize = pNewSection->SizeOfRawData + pNewSection->PointerToRawData;    PeBase = (DWORD)realloc((VOID*)PeBase, PeSize);    pNewSection = GetSecHeadAddr(PeBase, Name);    memset((DWORD*)(pNewSection->PointerToRawData + PeBase), 0, pNewSection->SizeOfRawData);    return;}

2.10 拷贝数据


拷贝dll的.text区段到被加壳程序的.pack区段
//拷贝区段数据VOID Pack::CopySecData(CHAR* DestName) {       //.text的地址    CHAR* Src = (CHAR*)(GetSecHeadAddr(DllBase, ".text")->VirtualAddress+DllBase);    //.pack的地址    CHAR* Dest = (CHAR*)(GetSecHeadAddr(PeBase, DestName)->PointerToRawData+PeBase);    memcpy(Dest, Src, GetSecHeadAddr(DllBase,  ".text")->Misc.VirtualSize);    return;}

2.11 写入磁盘

//将新文件写入磁盘VOID Pack::WriteToFile(LPCSTR Path) {    HANDLE hFile = CreateFile(Path, GENERIC_WRITE, 0, NULL,        CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);    DWORD WriteSize = 0;    DWORD Ret = WriteFile(hFile, (LPVOID)PeBase, PeSize, &WriteSize, NULL);    if (!Ret)        print("写入文件失败");    CloseHandle(hFile);    return;}



使用 C++ 写壳

三、壳代码

使用 C++ 写壳


壳代码其实就是动态链接库中合并后的.text区段,在2.1中有提到过。
#include "../pack/Share.h"                  // 壳代码与加壳器之间共用的结构体类型头文件#include"../aplib/aplib.h"                  // 压缩库头文件#include <Windows.h>#pragma comment(lib, "../aplib/aplib.lib")    // 压缩库// 对 dll 的区段合并为.text区段,并设置区段属性为[读写执行]#pragma comment(linker, "/merge:.data=.text")#pragma comment(linker, "/merge:.rdata=.text")#pragma comment(linker, "/section:.text,RWE")// 去除所有导出函数和导出变量的名称粉碎extern "C"{    __declspec(dllexport) SHARE ShareData;                    //导出变量,用于壳代码与加壳器之间的通讯    __declspec(dllexport) __declspec(naked) void start()     //导出函数地址,将会作为新的OEP{               _asm{            mov eax, fs:[0x30]            mov eax, [eax + 0x8]        //获取当前进程gImageBase            mov gImageBase, eax        }        GetFunAdd();                    //获取GetProcAddress        GetApi();                        //获取API        CallShllFun();                    //密码弹窗        UnCompression(ShareData);         //解压        DecSection();                    //解密代码段        DecIat(ShareData);                //加密IAT        FixReloca(ShareData);            //修复重定位        CallTls(ShareData);                //处理TLS               Debugging();                    //反调试        __asm        {            mov eax, gImageBase                //获取当前进程gImageBase            mov ebx, ShareData.DestOEP         //获取原程序的OEP            add eax,ebx            jmp eax        }    }}

其实也是和加壳器是一一对应的,理解了加壳器的代码,壳代码也就好理解了。

3.1 手动获取函数地址


由于壳代码在运行的时候,原程序还没有运行起来,并且我们对IAT进行了加密,因此,在壳代码中不能直接调用函数,需要手动获取函数地址。
使用 C++ 写壳
//定义GetProcAddress函数指针typedef FARPROC(WINAPI* MYGetProcAddress)(HMODULE hModule, LPCSTR  lpProcName);MYGetProcAddress MyGetProcAddress =0;void GetFunAdd() {    DWORD Ent;    DWORD Eot;    DWORD Eat;    CHAR Buff[] = { 'G','e','t','P','r','o','c','A','d','d','r' ,'e','s','s','' };    __asm    {        pushad        mov eax,fs:[0x30]        mov eax,[eax+0xc]        mov eax,[eax+0x1c]        //第一个模块        mov eax,[eax]            //kernel模块        mov eax,[eax+0x8]        //kernel基址        mov gKernelBase,eax               mov edx,[eax+0x3c]        add edx,eax        mov edx,[edx+0x78]        add edx,eax                //导出表        mov ebx,[edx+0x1c]        add ebx,eax        mov Eat,ebx                //地址表        mov ebx,[edx+0x20]        add ebx,eax        mov Ent,ebx                //名称表        mov ebx,[edx+0x24]        add ebx,eax        mov Eot,ebx                //序号表         xor ebx, ebx        jmp tag_FristCmp    tag_CmpFunNameLoop:        inc ebx    tag_FristCmp:        mov esi, Ent        mov esi, [ebx * 0x4 + esi]                    //遍历名称表                   lea esi, [esi + eax]                        //函数名称VA        lea edi, dword ptr Buff                        //GetProAddress字符串地址        mov ecx, 0xE                               //GetProAddress长度        cld                                          //清除方向位        repe cmpsb                                   //循环比较字符        jne tag_cmpFunNameLoop         mov esi,Eot                                           xor edi, edi        mov di, [ebx * 2 + esi]                        //序号        //使用序号在地址表中找到函数地址        mov edx, Eat        mov esi, [edx + edi * 4]                    //得到函数地址RVA        lea eax, [esi + eax]                        //函数地址        mov MyGetProcAddress,eax                    //保存函数地址到函数指针        popad    }}

一旦有了GetProcAddress这个函数和Kerler32基址,我们就可以调用所有函数了,获取到LoadLibraryExA之后,就可以加载其他模块,获取其他模块的函数了。(因为不管是Kernel32还是KernelBase都存在这个导出函数,所以不使用LoadLibraryA)

void GetApi() {    MyLoadLibraryExA = (MYLoadLibraryExA)MyGetProcAddress(gKernelBase, "LoadLibraryExA");    MyVirtualProtect = (MYVirtualProtect)MyGetProcAddress(gKernelBase, "VirtualProtect");     gUser32Base = MyLoadLibraryExA("User32.dll", NULL, NULL);    MyMessageBoxA = (MYMessageBoxA) MyGetProcAddress(gUser32Base, "MessageBoxA");     MyVirtualAlloc = (MYVirtualAlloc)MyGetProcAddress(gKernelBase, "VirtualAlloc");     HMODULE hVcruntime = MyLoadLibraryExA("vcruntime140.dll", NULL, NULL);    Myatexit = (MYatexit)MyGetProcAddress(hVcruntime, "atexit");    Mymemcpy = (MYmemcpy)MyGetProcAddress(hVcruntime, "memcpy");    MyRegisterClassA = (MYRegisterClassA)MyGetProcAddress(gUser32Base, "RegisterClassA");    MyCreateWindowExA = (MYCreateWindowExA)MyGetProcAddress(gUser32Base, "CreateWindowExA");    MyGetModuleHandleA = (MYGetModuleHandleA)MyGetProcAddress(gKernelBase, "GetModuleHandleA");    MyShowWindow = (MYShowWindow)MyGetProcAddress(gUser32Base, "ShowWindow");    MyUpdateWindow = (MYUpdateWindow)MyGetProcAddress(gUser32Base, "UpdateWindow");    MyGetMessageA = (MYGetMessageA)MyGetProcAddress(gUser32Base, "GetMessageA");    MyTranslateMessage = (MYTranslateMessage)MyGetProcAddress(gUser32Base, "TranslateMessage");    MyDispatchMessageA = (MYDispatchMessageA)MyGetProcAddress(gUser32Base, "DispatchMessageA");    MyDefWindowProcA = (MYDefWindowProcA)MyGetProcAddress(gUser32Base, "DefWindowProcA");    MyPostQuitMessage = (MYPostQuitMessage)MyGetProcAddress(gUser32Base, "PostQuitMessage");    MyGetClientRect  = (MYGetClientRect)MyGetProcAddress(gUser32Base, "GetClientRect");    MyGetWindowTextA = (MYGetWindowTextA)MyGetProcAddress(gUser32Base, "GetWindowTextA");    MyGetDlgItem = (MYGetDlgItem)MyGetProcAddress(gUser32Base, "GetDlgItem");    Mymemcmp = (MYmemcmp)MyGetProcAddress(hVcruntime, "memcmp");    MyExitProcess = (MYExitProcess)MyGetProcAddress(gKernelBase, "ExitProcess");    MySendMessageA = (MYSendMessageA)MyGetProcAddress(gUser32Base, "SendMessageA");    MyCreateThread = (MYCreateThread)MyGetProcAddress(gKernelBase, "CreateThread");    MyGetThreadContext = (MYGetThreadContext)MyGetProcAddress(gKernelBase, "GetThreadContext");    MyGetCurrentThread = (MYGetCurrentThread)MyGetProcAddress(gKernelBase, "GetCurrentThread");    MyTerminateThread = (MYTerminateThread)MyGetProcAddress(gKernelBase, "TerminateThread");     HMODULE hKernel32 = MyLoadLibraryExA("Kernel32.dll", NULL, NULL);    HMODULE hNtdll = MyLoadLibraryExA("ntdll.dll", NULL, NULL);    MYCreateToolhelp32Snapshot MyCreateToolhelp32Snapshot = (MYCreateToolhelp32Snapshot)MyGetProcAddress(hKernel32, "CreateToolhelp32Snapshot");    MyThread32First = (MYThread32First)MyGetProcAddress(hKernel32, "Thread32First");    MyThread32Next = (MYThread32Next)MyGetProcAddress(hKernel32, "Thread32Next");    MyOpenThread = (MYOpenThread)MyGetProcAddress(hKernel32, "OpenThread");    MyNtQueryInformationThread = (MYNtQueryInformationThread)MyGetProcAddress(hNtdll, "NtQueryInformationThread");    MyIsDebuggerPresent = (MYIsDebuggerPresent)MyGetProcAddress(hKernel32, "IsDebuggerPresent");}

3.2 密码弹窗


此处为壳子添加了一个密码弹框,需要输入正确的密码后才能运行壳代码,其实也并没什么作用,就是手动获取API调用了SDK的函数,创建窗口以及消息处理的函数。
//密码弹框void CallShllFun() {    WNDCLASS wc = { 0 };    HMODULE hInstance = MyGetModuleHandleA(NULL);    wc.style = CS_VREDRAW | CS_HREDRAW | CS_DBLCLKS;//类风格    wc.lpfnWndProc = WndProc;    wc.cbClsExtra = 0;    wc.cbWndExtra = 0;    wc.hInstance = hInstance;//实例句柄,代表此程序    wc.hIcon = 0;    wc.hCursor = 0;    wc.hbrBackground = 0;//BRUSH)GetStockObject(WHITE_BRUSH);    wc.lpszMenuName = 0;//菜单    wc.lpszClassName = ("加密壳");    MyRegisterClassA(&wc);    gParentWnd = MyCreateWindowExA(        0,        "加密壳",//类名        "C++写的壳",//窗口名        WS_OVERLAPPEDWINDOW,//重叠窗口的风格   重要的参数----窗口风格        100, 200, 300, 130, //位置和大小        NULL,               //父窗口句柄        NULL,               //菜单句柄,没有菜单        hInstance,          //实例句柄        NULL                //附加信息    );    //显示、更新窗口    MyShowWindow(gParentWnd, SW_SHOW);    MyUpdateWindow(gParentWnd);    MSG msg = { 0 };    while (MyGetMessageA(&msg, 0, 0, 0)){        MyTranslateMessage(&msg);        MyDispatchMessageA(&msg);    }    return ;}
//回调函数LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam){    CHAR Buff[0x11] = {0};    HWND h1000 = 0;    DWORD Len = 0;    switch (message)    {    case WM_CREATE:        MyCreateWindowExA(WS_EX_CLIENTEDGE, TEXT("EDIT"), NULL, WS_CHILD | WS_VISIBLE | WS_BORDER, 10, 10, 200, 30, hWnd, (HMENU)0x1000, NULL, NULL);        MyCreateWindowExA(WS_EX_CLIENTEDGE, TEXT("BUTTON"), TEXT("验证密码"), WS_CHILD | WS_VISIBLE, 10, 40, 80, 30, hWnd, (HMENU)0x1001, NULL, NULL);        MyCreateWindowExA(WS_EX_CLIENTEDGE, TEXT("BUTTON"), TEXT("取消启动"), WS_CHILD | WS_VISIBLE, 100, 40, 80, 30, hWnd, (HMENU)0x1002, NULL, NULL);    }    if (hWnd == gParentWnd)    {        switch (message)        {            case WM_CLOSE:                MyPostQuitMessage(0);                if (lParam != 100)                {                    MyExitProcess(0);                }                break;            case WM_COMMAND:            {                int nId = LOWORD(wParam);                switch (nId)                {                    case 0x1001:                        h1000 = MyGetDlgItem(gParentWnd, 0x1000);                        Len = MyGetWindowTextA(h1000, Buff, 0x10);                        if (Len ==0)                        {                            MyMessageBoxA(0, TEXT("密码不能为空"), TEXT("提示"), 0);                        }                        else if (!Mymemcmp("1234", Buff, Len)) {                            MyMessageBoxA(0, TEXT("密码正确"), TEXT("提示"), 0);                                                       MySendMessageA(gParentWnd, WM_CLOSE, 0, 100);                        }                        else {                            MyMessageBoxA(0, TEXT("密码错误"), TEXT("错误"), 0);                        }                        break;                    case 0x1002:                        MyExitProcess(0);                                               break;                    default:                        break;                }            }        }    }    return  MyDefWindowProcA(hWnd, message, wParam, lParam);    };

运行加壳后的程序会弹出对话框:
使用 C++ 写壳

3.3 解压


在加壳器中我们对原程序进行了压缩,那么在壳代码中就要对原程序解压,解压很简单,就是将压缩后的数据解压后放回该数据的原来的位置(参考2.6的图)
//解压缩函数void UnCompression(SHARE ShareData){    // 1.获取节区头首地址       auto SecTextHead = GetSecHeadAddr(gImageBase, ".text");     // 2.解压压缩区段     // 内存地址    PCHAR lpPacked = ((PCHAR)gImageBase + ShareData.TextRVA);    // 获取解压后的大小    DWORD dwPackedSize = aPsafe_get_orig_size(lpPacked);    // 申请内存    PCHAR lpBuffer = (PCHAR)MyVirtualAlloc(NULL, dwPackedSize, MEM_COMMIT, PAGE_EXECUTE_READWRITE);    // 解压    aPsafe_depack(lpPacked, ShareData.TextCompressSize, lpBuffer, dwPackedSize);    //3. 还原区段,将.text区段的内容写入原地址    DWORD OldProtect = 0;    MyVirtualProtect(lpPacked, dwPackedSize, PAGE_EXECUTE_READWRITE, &OldProtect);    Mymemcpy(lpPacked, lpBuffer, SecTextHead->Misc.VirtualSize);    MyVirtualProtect(lpPacked, dwPackedSize, OldProtect, &OldProtect);}

3.4 解密代码


与加壳器中的加密同理
//解密代码void DecSection() {       DWORD OldProtect;    //获取.text区段    auto SectionHead = GetSecHeadAddr(gImageBase, ".text");    DWORD Size = SectionHead->Misc.VirtualSize;    CHAR* Address = (CHAR*)(SectionHead->VirtualAddress + gImageBase);    MyVirtualProtect(Address, Size, PAGE_EXECUTE_READWRITE, &OldProtect);    //循环解密    while (Size) {        *Address = *Address ^ 0x15;        Address++;        Size--;    }    MyVirtualProtect(Address, Size, OldProtect, &OldProtect);}


3.5 加密IAT


在加壳器中,我们对导出表进行了清零操作,目的就是为了不让系统去修复导入表,而令我们的壳代码来修复导入表并加密IAT。而加密IAT的原理如下:
 
就是为每一个IAT都申请一个堆空间,然后在堆空间中填入解密IAT的代码以及加密后的IAT地址,将原来的IAT替换为堆空间的地址,这样,只要程序使用到IAT,就会执行堆空间的代码,动态解密IAT。下面的图片是为了便于理解画的简化版,并没有加密IAT。
使用 C++ 写壳
//修复并加密IATvoid DecIat(SHARE ShareData) {    auto ImportTable = (PIMAGE_IMPORT_DESCRIPTOR)(gImageBase + ShareData.ImpTableVA);    while (ImportTable->Name)    {        //获取DLL名称        CHAR* Name = (CHAR*)(ImportTable->Name+gImageBase);        HMODULE Module = MyLoadLibraryExA(Name, NULL, NULL);        auto Iat = (int*)(ImportTable->FirstThunk + gImageBase);        for (size_t i = 0; Iat[i]!=0; i++)        {            DWORD FunAddr = 0;            DWORD OldProtect;            BYTE* DecCode = (BYTE*)MyVirtualAlloc(0, 0x50, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);            //解密代码            BYTE OpCode[] = {            0xE8, 0x01, 0x00, 0x00, 0x00, 0xE8, 0x58, 0x90, 0xB8,                   0xA4, 0xA3, 0xA2, 0xA1,            //加密                                       0xEB, 0x04, 0xE8, 0xBF, 0x78, 0xCC, 0x35, 0x59, 0x10, 0x40,            0x00, 0xEB, 0x01, 0xE9, 0x50, 0xEB, 0x02, 0x61, 0xE8, 0x90,               0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0xEB, 0x05, 0xE8, 0x5B,            0xC3, 0xE8, 0x88, 0x90, 0xC3, 0xE8, 0x72, 0x6B, 0x77, 0x88               };                       MyVirtualProtect(&Iat[i], 0x50, PAGE_EXECUTE_READWRITE, &OldProtect);            //最高位为1,序号导入            if ((DWORD)Iat[i] & 0x80000000)                FunAddr = (DWORD)MyGetProcAddress(Module, (LPCSTR)(Iat[i] & 0xffff));                       else{                auto AddressOfData = (PIMAGE_IMPORT_BY_NAME)(Iat[i] + gImageBase);                FunAddr = (DWORD)MyGetProcAddress(Module, AddressOfData->Name);//异或前77216210            }            //将加密后的函数地址写入堆空间的解密代码            FunAddr ^= 0x00401059;            OpCode[9] = FunAddr;            OpCode[10] = FunAddr >> 0x8;            OpCode[11] = FunAddr >> 0x10;            OpCode[12] = FunAddr >> 0x18;            Mymemcpy(DecCode, OpCode, 0x50);            Iat[i] = (int)DecCode;            MyVirtualProtect(&Iat[i], 0x50, OldProtect, &OldProtect);        }        ImportTable++;    } }


3.6 修复重定位


由于加壳器中的设置,程序装载的时候,系统帮我们修复了壳代码的重定位,但是原程序的重定位并没有修复,需要我们自己手动修复。
//修复重定位void FixReloca(SHARE ShareData) {    auto Relocs = (PIMAGE_BASE_RELOCATION)(ShareData.dwRelocRva + gImageBase);    DWORD OldProtect = 0;    while (Relocs->VirtualAddress)    {        DWORD OldProtect = 0;        MyVirtualProtect((LPVOID)(gImageBase + Relocs->VirtualAddress), 0x1000, PAGE_READWRITE, &OldProtect);         TypeOffset* items = (TypeOffset*)(Relocs + 1);        //遍历重定位项        int count = (Relocs->SizeOfBlock - 8) / 2;        for (int i = 0; i < count; ++i)        {            if (items[i].Type == 3)            {                DWORD Temp = 0;                // 计算出每一个需要重定位的数据所在的地址                DWORD* item = (DWORD*)(gImageBase + Relocs->VirtualAddress + items[i].Offset);                // 这里操作的是需要重定位的数据,通常是代码段(不可写)                *item = *item + gImageBase - ShareData.OldImageBase;            }        }        MyVirtualProtect((LPVOID)(gImageBase + Relocs->VirtualAddress), 0x1000, OldProtect, &OldProtect);        // 下一个重定位块        Relocs = (PIMAGE_BASE_RELOCATION)(Relocs->SizeOfBlock + (DWORD)Relocs);    }}

3.7 处理TLS


这里处理的TLS并不是完美处理的。但是也能实现TLS的处理,就是循环调用TLS的回调函数,将数据目录表的TLS恢复。
//手动调用Tlsvoid CallTls(SHARE ShareData) {    //如果存在TLS表    if (ShareData.TlsVirtualAddress)    {        DWORD OldProtect = 0;        MyVirtualProtect(&(pOptionHead(gImageBase)->DataDirectory[9].VirtualAddress), 0x1000, PAGE_EXECUTE_READWRITE, &OldProtect);        //恢复Tls数据        pOptionHead(gImageBase)->DataDirectory[9].VirtualAddress = ShareData.TlsVirtualAddress;        MyVirtualProtect(&(pOptionHead(gImageBase)->DataDirectory[9].VirtualAddress), 0x1000, OldProtect, &OldProtect);           auto TlsTable = (PIMAGE_TLS_DIRECTORY)(pOptionHead(gImageBase)->DataDirectory[9].VirtualAddress + gImageBase);         //手动调用TLS回调函数        auto CallBackTable = (PIMAGE_TLS_CALLBACK*)(ShareData.TlsCallBackTableVa - ShareData.OldImageBase + gImageBase);        while (*CallBackTable)        {            (*CallBackTable)((PVOID)gImageBase, DLL_PROCESS_ATTACH, NULL);            CallBackTable++;        }    }}

3.8 反调试


反调试有很多很多种方法,这里就是简单的意思一下。遍历所有线程的TEB+0xF24的反调试方法目前还有BUG,暂时没有完成实现。若有大佬能指点一二,感激不尽~
//反调试void Debugging() {    gThread = MyCreateThread(NULL, NULL, ThreadProc1,NULL, 0, NULL);    //gThread = MyCreateThread(NULL, NULL, ThreadProc2, NULL, 0, NULL);//遍历所有线程的TEB+0xF24    if (MyIsDebuggerPresent())        MyMessageBoxA(0,"当前处于[被]调试状态n",0,0);    CheckNtGlobalFlag();}
//反调试回调函数----硬件断点DWORD WINAPI ThreadProc1(_In_ LPVOID lpParameter) {    while (true){        CONTEXT ConText;        DWORD  Temp = CONTEXT_DEBUG_REGISTERS;        Mymemcpy(&ConText, &Temp, sizeof(CONTEXT));        //获取调试寄存器        MyGetThreadContext(MyGetCurrentThread(), &ConText);           //判断调试寄存器        if (ConText.Dr0 || ConText.Dr1 || ConText.Dr2 || ConText.Dr3)        {            MyMessageBoxA(0, "检测到硬件断点", 0, 0);        }    }}
//反调试之 PEB+0x68void CheckNtGlobalFlag() {    int NtGlobalFlag = 0;    __asm {               mov eax, dword ptr fs : [0x30]// 通过 TEB 偏移为 0x30 找到 PEB 结构               mov eax, dword ptr[eax + 0x68]// 通过 PEB 偏移为 0x68 的地方找到 NtGlobalFlag               mov NtGlobalFlag, eax          // 如果当前的进程被调试,保存的就是 0x70    }    if(NtGlobalFlag==0x70)        MyMessageBoxA(0,"PEB+0x68==0x70,当前被调试n",0,0);}

//反调试回调函数---遍历所有线程的TEB+0xF24使用到的结构体typedef struct tagTHREADENTRY32{    DWORD   dwSize;    DWORD   cntUsage;    DWORD   th32ThreadID;       // this thread    DWORD   th32OwnerProcessID; // Process this thread is associated with    LONG    tpBasePri;    LONG    tpDeltaPri;    DWORD   dwFlags;} THREADENTRY32;typedef THREADENTRY32 *  PTHREADENTRY32;typedef THREADENTRY32 *  LPTHREADENTRY32;typedef struct _CLIENT_ID {    DWORD   UniqueProcess;    DWORD   UniqueThread;} CLIENT_ID;typedef   CLIENT_ID   *PCLIENT_ID;typedef enum MY_THREAD_INFORMATION_CLASS {    ThreadBasicInformation,    ThreadTimes,    ThreadPriority,    ThreadBasePriority,    ThreadAffinityMask,    ThreadImpersonationToken,    ThreadDescriptorTableEntry,    ThreadEnableAlignmentFaultFixup,    ThreadEventPair,    ThreadQuerySetWin32StartAddress,    ThreadZeroTlsCell,    ThreadPerformanceCount,    ThreadAmILastThread,    ThreadIdealProcessor,    ThreadPriorityBoost,    ThreadSetTlsArrayAddress,    ThreadIsIoPending,    ThreadHideFromDebugger} MYTHREAD_INFORMATION_CLASS, *PMYTHREAD_INFORMATION_CLASS;typedef ULONG KPRIORITY;typedef struct _THREAD_BASIC_INFORMATION {    NTSTATUS                ExitStatus;    PVOID                   TebBaseAddress;    CLIENT_ID               ClientId;    KAFFINITY               AffinityMask;    KPRIORITY               Priority;    KPRIORITY               BasePriority; } THREAD_BASIC_INFORMATION, *PTHREAD_BASIC_INFORMATION; //遍历所有线程的TEB+0xF24的回调函数DWORD WINAPI ThreadProc2(_In_ LPVOID lpParameter) {    while (true)    {        THREADENTRY32 entryThread ;        DWORD  Temp = 28;        Mymemcpy(&entryThread, &Temp, sizeof(THREADENTRY32));        //线程快照        HANDLE hSnapshot = MyCreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0);        if (hSnapshot == INVALID_HANDLE_VALUE)            return 0;        if (!MyThread32First(hSnapshot, &entryThread))            return 0;        //遍历线程快照        do{            DWORD ThreadId = entryThread.th32OwnerProcessID;            HANDLE hThread = MyOpenThread(THREAD_ALL_ACCESS, NULL, ThreadId);            THREAD_BASIC_INFORMATION threadBasicInfo;            //查询TEB地址            LONG status = MyNtQueryInformationThread(hThread, ThreadBasicInformation, &threadBasicInfo, sizeof(threadBasicInfo), NULL);            DWORD* DbgSsReserved = (DWORD*)((DWORD)(threadBasicInfo.TebBaseAddress) + 0xF20);            if (*(DbgSsReserved + 1))                MyMessageBoxA(0, "线程TEB+0xF24不为空,该线程正在调试程序", 0, 0);         } while (MyThread32Next(hSnapshot, &entryThread));    }}


3.9 跳回原OEP


原程序的解压、解密、IAT加密都已经操作完成了,跳回原程序执行。
__asm    {        mov eax, gImageBase                //获取当前进程gImageBase        mov ebx, ShareData.DestOEP               add eax,ebx        jmp eax    }

关于混淆和花指令在这里并没有做太深入的探究。


使用 C++ 写壳

- End -


使用 C++ 写壳


看雪ID:三一米田

https://bbs.pediy.com/user-home-881392.htm

 

 *本文由看雪论坛 三一米田 原创,转载请注明来自看雪社区。




# 往期推荐






使用 C++ 写壳
公众号ID:ikanxue
官方微博:看雪安全
商务合作:[email protected]



使用 C++ 写壳

球分享

使用 C++ 写壳

球点赞

使用 C++ 写壳

球在看



使用 C++ 写壳

点击“阅读原文”,了解更多!

本文始发于微信公众号(看雪学院):使用 C++ 写壳

发表评论

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: