本篇內(nèi)容介紹了“如何從零開始寫一個加殼器”的有關(guān)知識,在實際案例的操作過程中,不少人都會遇到這樣的困境,接下來就讓小編帶領(lǐng)大家學習一下如何處理這些情況吧!希望大家仔細閱讀,能夠?qū)W有所成!
讓客戶滿意是我們工作的目標,不斷超越客戶的期望值來自于我們對這個行業(yè)的熱愛。我們立志把好的技術(shù)通過有效、簡單的方式提供給客戶,將通過不懈努力成為客戶在信息化領(lǐng)域值得信任、有價值的長期合作伙伴,公司提供的服務(wù)項目有:主機域名、網(wǎng)頁空間、營銷軟件、網(wǎng)站建設(shè)、永定網(wǎng)站維護、網(wǎng)站推廣。
即我們向PE文件添加一個區(qū)段并將其設(shè)置為入口點,這樣PE文件最開始執(zhí)行的命令就是我們添加的區(qū)段也就是殼的指令,殼對加密區(qū)進行解密,對壓縮區(qū)進行解壓,將原本的EXE文件還原出來,然后跳轉(zhuǎn)至原程序入口,程序照常運行。
首先生成一個打印hello的exe文件。
#include <stdio.h> int main() { printf("hello"); }
我們目前要干的事情是:以手動的形式向PE文件添加一個殼部分并設(shè)為程序入口,并使其能跳轉(zhuǎn)回原入口。 那就來吧
用010editor打開我們的exe文件,啟用exe模板分析。 我們首先修改其文件頭numverofsection屬性,這個屬性用來定義當前PE文件存在多少個區(qū)段,因為我們要添加一個殼區(qū)段,所以我們將其加1變成6
在我們重載模板后我們就會在區(qū)段表發(fā)現(xiàn)多出來一個空的區(qū)段表
從上到下各個比較重要字段的意思是 \1. Name 表示該區(qū)段的名字 2.VirtualSize 表示在內(nèi)存中的大小(一般內(nèi)存對齊為0x1000) 3.virtualaddress 虛擬地址 即上一個區(qū)段的VirtualAddress + 上一個區(qū)段經(jīng)內(nèi)存對齊粒度對齊后的大小 4.sizeofdata 表示在文件中的大?。ㄒ话阄募R為0x200) 5.pointertorawdata 文件的偏移 即 上一個區(qū)段的PointerToRawData + 上一個區(qū)段的SizeOfRawData
然后我們通過修改以上各值來定義一個新區(qū)段(殼區(qū)段)的屬性
這里的virtualsize看著填一個就行了。 此時我們只是定義了區(qū)段表,但文件中并沒有該區(qū)段存在,所以我們得創(chuàng)建該區(qū)段。 然后還要讓區(qū)段可編輯,把下列值改為1即可
ctrl+shift+i 向目標文件偏移處插入0x200大小的空間。 這樣一來,殼區(qū)段就創(chuàng)建好了。 然后我們還要修改 擴展頭的SizeofImage 。將他改為最后一個區(qū)段的內(nèi)存地址+內(nèi)存大小
然后去掉隨機基址選項。
找到擴展頭的DLL屬性字段,去掉隨機基址,把40 81改為 00 81
接下來我們把程序入口點設(shè)置給殼區(qū)段。 使用LORDPE把入口點設(shè)為殼區(qū)塊的虛擬地址
然后我們用OD打開這個文件
剛剛提到的手工加殼,不過是最最基礎(chǔ)的加殼原型而已,真正的加殼還涉及了代碼加解密等操作.
真正寫殼時一般寫兩個東西,加殼器和stub 所謂加殼器,就是給被加殼文件創(chuàng)造出一個新的區(qū)段, 在此同時將程序以某種方式加密,然后把stub放入新區(qū)段,并將程序入口點設(shè)為新區(qū)段的地址,然后在新區(qū)段結(jié)束后跳轉(zhuǎn)回原程序入口。這個新區(qū)段我們叫做殼區(qū)段. 那么這個stub就是加殼后程序最先執(zhí)行的命令了,它執(zhí)行解密算法,將原程序釋放出來。
https://github.com/ConsT27/PackingEXE/tree/master項目地址
stub是被植入到PE文件中的代碼,它一般會干下面這些事情。
流程如下
0.合并data,rdata到text 1.PEB動態(tài)尋址,遍歷導(dǎo)出表找到GetProcAddress函數(shù) 2.解密 3.修改入口點到原入口點
同時stub一般以dll的形式存在。原因是DLL通常自帶重定位表,這在我們的移植過程中的重定位操作中提供了巨大的便利。
我們要移植stub過去,肯定需要移植代碼段,也需要移植數(shù)據(jù)段。不如我們干脆把數(shù)據(jù)段合并到代碼段,一塊移植過去。
為什么會用到這個技術(shù)編寫stub? 因為我們的stub.dll植入到宿主程序時,只有.text植入過去,沒有對應(yīng)的導(dǎo)入表,所以我們的stub無法直接調(diào)用一些API。所以我們需要動態(tài)獲取各種API。 其中我采用的是PEB動態(tài)查詢得到GetProcAddress函數(shù),然后用GetProcAddress函數(shù)去獲取各個API。
那么,什么是PEB? PEB是一個微軟還未完全公開作用的一個結(jié)構(gòu),它叫做 進程環(huán)境信息塊 ,包含了進程的信息。其結(jié)構(gòu)如下
typedef struct _PEB { BYTE Reserved1[2]; BYTE BeingDebugged; //被調(diào)試狀態(tài) BYTE Reserved2[1]; PVOID Reserved3[2]; PPEB_LDR_DATA Ldr; PRTL_USER_PROCESS_PARAMETERS ProcessParameters; BYTE Reserved4[104]; PVOID Reserved5[52]; PPS_POST_PROCESS_INIT_ROUTINE PostProcessInitRoutine; BYTE Reserved6[128]; PVOID Reserved7[1]; ULONG SessionId; } PEB, *PPEB; 復(fù)制代碼
我們關(guān)心的是PEB偏移0c得到的 PPEB_LDR_DATA Ldr; 它是一個指針,指向一個 PPEB_LDR_DATA 結(jié)構(gòu), 存放著已經(jīng)被進程裝在的動態(tài)鏈接庫的信息
typedef struct _PEB_LDR_DATA { ULONG Length; // +0x00 BOOLEAN Initialized; // +0x04 PVOID SsHandle; // +0x08 LIST_ENTRY InLoadOrderModuleList; // +0x0c LIST_ENTRY InMemoryOrderModuleList; // +0x14 LIST_ENTRY InInitializationOrderModuleList;// +0x1c } PEB_LDR_DATA,*PPEB_LDR_DATA; // +0x24
PPEB_LDR_DATA 偏移1c是一個指向LIST_ENTRY InInitializationOrderModuleList結(jié)構(gòu)的指針,這個結(jié)構(gòu) 存放著指向模塊初始化鏈表的頭 , 按順序存放著PE裝入運行時初始化模塊信息,一般來說第一個鏈表結(jié)點是ntdll.dll,第二個鏈表結(jié)點就是kernel32.dll 。我們就在其中找到kernel32.dll的信息,獲取其PE信息,得到導(dǎo)出表,循環(huán)遍歷得到GetProcAddress函數(shù)。 另外,PEB地址再TEB偏移0x30處。用匯編語言表示就是 fs:[0x30]。
以上是PEB尋址的大致流程,另外還有一個比較關(guān)鍵的點是遍歷kernel32.dll導(dǎo)出表獲得GetProcAddress函數(shù)信息。 關(guān)于導(dǎo)出表可以看看這個文章https://blog.csdn.net/evileagle/article/details/12176797
首先一個導(dǎo)出表結(jié)構(gòu)體如下
typedef struct _IMAGE_EXPORT_DIRECTORY { DWORD Characteristics; //一般為0,沒啥用 DWORD TimeDateStamp; //導(dǎo)出表生成的時間 WORD MajorVersion; //版本,也是0沒啥用 WORD MinorVersion; //也是沒啥用的版本信息一般為0 DWORD Name; //當前導(dǎo)出表的模塊名字 DWORD Base; //序號表中序號的基數(shù) DWORD NumberOfFunctions; //導(dǎo)出函數(shù)數(shù)量 DWORD NumberOfNames; //按名字導(dǎo)出函數(shù)的數(shù)量 DWORD AddressOfFunctions; // 序號表 DWORD AddressOfNames; // 名稱表 DWORD AddressOfNameOrdinals; // 地址表 } IMAGE_EXPORT_DIRECTORY, *PIMAGE_EXPORT_DIRECTORY;
其中序號表的起始序號是Base屬性定義的值。以下是導(dǎo)出表的序號名稱地址表的關(guān)系
我們的遍歷流程是,先遍歷名稱表找到GetProcAddress在名稱數(shù)組中的下標,然后根據(jù)這個下標去序號數(shù)組中找相同下標的序號值,然后以這個序號值為下標去找地址數(shù)組中的對應(yīng)值。我們找到的地址表中的值就是函數(shù)入口
下面我把這段程序的匯編代碼放出來。我是用內(nèi)聯(lián)匯編把這段代碼塞進C++的
void GetApis() { HMODULE hKernel32; _asm { pushad; ; //獲取kernel32.dll的加載基址; mov eax, fs: [0x30] ; //得到PEB地址 mov eax, [eax + 0ch]; //獲得LDR_PEB_DATA地址 mov eax, [eax + 0ch]; //獲得LIST_ENTRY InLoadOrderModuleList;地址 mov eax, [eax]; //獲得LIST_ENTRY InLoadOrderModuleList下一項的地址 mov eax, [eax]; /獲得LIST_ENTRY InLoadOrderModuleList下下項即我們需要的LIST_ENTRY InInitializationOrderModuleList的地址 mov eax, [eax + 018h]; //獲得kernel32.dll地址 mov hKernel32, eax; mov ebx, [eax + 03ch];//獲得kernel32.dll NT頭RVA add ebx, eax; //NT頭的VA add ebx, 078h; //獲得區(qū)段表 mov ebx, [ebx]; //獲得導(dǎo)出表RVA add ebx, eax; //導(dǎo)出表VA lea ecx, [ebx + 020h]; mov ecx, [ecx]; // ecx => 名稱表的首地址(rva); add ecx, eax; // ecx => 名稱表的首地址(va); xor edx, edx; // 作為索引(index)來使用. _WHILE:; mov esi, [ecx + edx * 4];//名稱數(shù)組入口點rva,名稱數(shù)組單位大小4字節(jié) lea esi, [esi + eax]; //入口點VA cmp dword ptr[esi], 050746547h; //進行名稱匹配,050746547h即小端存儲的GetP jne _LOOP;//不相等就跳入_LOOP段 cmp dword ptr[esi + 4], 041636f72h; //名陳匹配,rocA,以下依次為ddre,ss jne _LOOP; cmp dword ptr[esi + 8], 065726464h; jne _LOOP; cmp word ptr[esi + 0ch], 07373h; jne _LOOP; mov edi, [ebx + 024h]; add edi, eax; //獲得序號表VA mov di, [edi + edx * 2]; //獲得序號數(shù)組中對應(yīng)下標的地址,序號數(shù)組單位大小2字節(jié) and edi, 0FFFFh; //給di提位到32位,即給予edi 序號表中對應(yīng)下標的地址 mov edx, [ebx + 01ch]; add edx, eax; //獲得地址表 mov edi, [edx + edi * 4]; //獲得地址數(shù)組中,序號對應(yīng)的值,地址數(shù)組單位大小4字節(jié) add edi, eax; //獲得GetProcAddress的入口地址 mov MyGetProcAddress, edi; //賦值 jmp _ENDWHILE; //END _LOOP:; inc edx; // ++index; jmp _WHILE; _ENDWHILE:; popad; }
解密代碼段。這段好寫。
void Decrypt() { unsigned char* pText = (unsigned char*)g_conf.textScnRVA + 0x400000;//鎖定到PE文件的text段(因為加殼時去掉了基址隨機化,所以自信的把基址填成0x400000 DWORD old = 0; MyVirtualProtect(pText, g_conf.textScnSize, PAGE_READWRITE, &old);//修改代碼段的屬性,注意我們這里使用了動態(tài)獲得的 //解密代碼段 for (DWORD i = 0; i < g_conf.textScnSize; i++) { pText[i] ^= g_conf.key; } //把屬性修改回去 MyVirtualProtect(pText, g_conf.textScnSize, old, &old); }
_asm { mov eax, g_conf.srcOep; //入口點是g_conf.srcOep add eax, 0x400000 jmp eax }
加殼器流程如下
1.打開需要被加殼的PE文件 2.加載stub 3.加密代碼段 4.添加新區(qū)段 5.stub重定位修復(fù) 6.stub移植 7.PE文件入口點修改 8.去隨機基址 9.保存文件
以下的各個流程描述中會用到諸多自定義函數(shù),我先貼上來吧。
//**************** //對齊處理 //time:2020/11/5 //**************** int AlignMent(_In_ int size, _In_ int alignment) { return (size) % (alignment)==0 ? (size) : ((size) / alignment+1) * (alignment); } //*********************** //PE信息獲取函數(shù)簇 //time:2020/11/2 //*********************** PIMAGE_DOS_HEADER GetDosHeader(_In_ char* pBase) { return PIMAGE_DOS_HEADER(pBase); } PIMAGE_NT_HEADERS GetNtHeader(_In_ char* pBase) { return PIMAGE_NT_HEADERS(GetDosHeader(pBase)->e_lfanew+(SIZE_T)pBase); } PIMAGE_FILE_HEADER GetFileHeader(_In_ char* pBase) { return &(GetNtHeader(pBase)->FileHeader); } PIMAGE_OPTIONAL_HEADER32 GetOptHeader(_In_ char* pBase) { return &(GetNtHeader(pBase)->OptionalHeader); } PIMAGE_SECTION_HEADER GetLastSec(_In_ char* pBase) { DWORD SecNum = GetFileHeader(pBase)->NumberOfSections; PIMAGE_SECTION_HEADER FirstSec = IMAGE_FIRST_SECTION(GetNtHeader(pBase)); PIMAGE_SECTION_HEADER LastSec = FirstSec + SecNum - 1; return LastSec; } PIMAGE_SECTION_HEADER GetSecByName(_In_ char* pBase,_In_ const char* name) { DWORD Secnum = GetFileHeader(pBase)->NumberOfSections; PIMAGE_SECTION_HEADER Section = IMAGE_FIRST_SECTION(GetNtHeader(pBase)); char buf[10] = { 0 }; for (DWORD i = 0; i < Secnum; i++) { memcpy_s(buf, 8, (char*)Section[i].Name, 8); if (!strcmp(buf, name)) { return Section + i; } } return nullptr; } typedef struct _StubConf { DWORD srcOep; //入口點 DWORD textScnRVA; //代碼段RVA DWORD textScnSize; //代碼段的大小 DWORD key; //解密密鑰 }StubConf; struct StubInfo { char* dllbase; //stub.dll的加載基址 DWORD pfnStart; //stub.dll(start)導(dǎo)出函數(shù)的地址 StubConf* pStubConf; //stub.dll(g_conf)導(dǎo)出全局變量的地址 };
打開PE文件
這里采用的方法是利用CreateFileA函數(shù)。同時這個函數(shù)還拋出了一個指向PE文件大小的指針
char* GetFileHmoudle(_In_ const char* path,_Out_opt_ DWORD* nFileSize) { //打開一個文件并獲得文件句柄 HANDLE hFile = CreateFileA(path, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); //獲得文件大小 DWORD FileSize = GetFileSize(hFile, NULL); //返回文件大小到變量nFileSize if(nFileSize) *nFileSize = FileSize; //申請一片大小為FileSize的內(nèi)存并將指針置于首位 char* pFileBuf = new CHAR[FileSize]{ 0 }; //給剛剛申請的內(nèi)存讀入數(shù)據(jù) DWORD dwRead; ReadFile(hFile, pFileBuf, FileSize, &dwRead, NULL); CloseHandle(hFile); return pFileBuf; }
加載STUB
void LoadStub(_In_ StubInfo* pstub) { pstub->dllbase = (char*)LoadLibraryEx(L"F:\\stubdll.dll", NULL, DONT_RESOLVE_DLL_REFERENCES); pstub->pfnStart = (DWORD)GetProcAddress((HMODULE)pstub->dllbase, "Start"); //獲得stub的入口函數(shù)Start(自己定義在stub中的一個函數(shù) pstub->pStubConf = (StubConf*)GetProcAddress((HMODULE)pstub->dllbase, "g_conf"); } //不僅加載了stub,還獲得了stub拋出的用于收集信息的全局結(jié)構(gòu)體(g_conf,是一個stub拋出的結(jié)構(gòu)體,用于獲取信息,結(jié)構(gòu)如下) typedef struct _StubConf { DWORD srcOep; //入口點 DWORD textScnRVA; //代碼段RVA DWORD textScnSize; //代碼段的大小 DWORD key; //解密密鑰 }StubConf;
加密代碼段
DWORD textRVA = GetSecByName(PeHmoudle, ".text")->VirtualAddress; DWORD textSize = GetSecByName(PeHmoudle, ".text")->Misc.VirtualSize; Encry(PeHmoudle,pstub); void Encry(_In_ char* hpe,_In_ StubInfo pstub) { //獲取代碼段首地址 BYTE* TargetText = GetSecByName(hpe, ".text")->PointerToRawData + (BYTE*)hpe; //獲取代碼段大小 DWORD TargetTextSize = GetSecByName(hpe, ".text")->Misc.VirtualSize; //加密代碼段 for (int i = 0; i < TargetTextSize; i++) { TargetText[i] ^= 0x15; } pstub.pStubConf->textScnRVA = GetSecByName(hpe, ".text")->VirtualAddress; pstub.pStubConf->textScnSize = TargetTextSize; pstub.pStubConf->key = 0x15; } //加密代碼段,并給予了stub一些信息
char* AddSec(_In_ char*& hpe, _In_ DWORD& filesize, _In_ const char* secname, _In_ const int secsize) { GetFileHeader(hpe)->NumberOfSections++; PIMAGE_SECTION_HEADER pesec = GetLastSec(hpe); //設(shè)置區(qū)段表屬性 memcpy(pesec->Name, secname, 8); pesec->Misc.VirtualSize = secsize; pesec->VirtualAddress = (pesec - 1)->VirtualAddress + AlignMent((pesec - 1)->SizeOfRawData,GetOptHeader(hpe)->SectionAlignment); pesec->SizeOfRawData = AlignMent(secsize, GetOptHeader(hpe)->FileAlignment); pesec->PointerToRawData = AlignMent(filesize,GetOptHeader(hpe)->FileAlignment); pesec->Characteristics = 0xE00000E0; //設(shè)置OPT頭映像大小 GetOptHeader(hpe)->SizeOfImage = pesec->VirtualAddress + pesec->SizeOfRawData; //擴充文件數(shù)據(jù) int newSize = pesec->PointerToRawData + pesec->SizeOfRawData; char* nhpe = new char [newSize] {0}; //向新緩沖區(qū)錄入數(shù)據(jù) memcpy(nhpe, hpe, filesize); //緩存區(qū)更替 delete hpe; filesize = newSize; return nhpe; }
stub重定位
好家伙,這個東西稍有不慎就會讓整個程序拉跨掉(過來人的忠告 為什么需要stub重定位呢?因為我們的stub最開始是加載在內(nèi)存中的,它的許多指令如跳轉(zhuǎn)到的地址是按內(nèi)存為基準確定的,但是我們需要把他移植進文件,所以它的代碼里許多地址就是錯誤的,我們需要對這些地址進行處理,即重定位,使其以宿主程序為標準進行地址修復(fù)。 可能我表述的不是很清楚
舉個例子吧,比如stub在加載進內(nèi)存時,有一條跳轉(zhuǎn)指令時jmp 12345678, 如果我們不處理就把這條指令移植進PE文件,那么PE文件執(zhí)行到此處時就會跳轉(zhuǎn)到12345678,此時的12345678地址可能就已經(jīng)不是PE文件加載的內(nèi)存區(qū)間了,從而程序會崩潰。所以要修復(fù)。根據(jù)stub的重定位表進行修復(fù)。 重定位表就是記錄哪些地址的數(shù)據(jù)需要被修復(fù)的,我們遍歷這些地址進行修復(fù)即可。 如果以下代碼看起來吃力,可以先去了解一下重定位表
void FixStub(DWORD targetDllbase, DWORD stubDllbase,DWORD targetNewScnRva,DWORD stubTextRva ) { //找到stub.dll的重定位表 DWORD dwRelRva = GetOptHeader((char*)stubDllbase)->DataDirectory[5].VirtualAddress; IMAGE_BASE_RELOCATION* pRel = (IMAGE_BASE_RELOCATION*)(dwRelRva + stubDllbase); //遍歷重定位表 while (pRel->SizeOfBlock) { struct TypeOffset { WORD offset : 12; WORD type : 4; }; TypeOffset* pTypeOffset = (TypeOffset*)(pRel + 1); DWORD dwCount = (pRel->SizeOfBlock - 8) / 2; //需要重定位的數(shù)量 for (int i = 0; i < dwCount; i++) { if (pTypeOffset[i].type != 3) { continue; } //需要重定位的地址 DWORD* pFixAddr = (DWORD*)(pRel->VirtualAddress + pTypeOffset[i].offset + stubDllbase); DWORD dwOld; //修改屬性為可寫 VirtualProtect(pFixAddr, 4, PAGE_READWRITE, &dwOld); //去掉dll當前加載基址 *pFixAddr -= stubDllbase; //去掉默認的段首RVA *pFixAddr -= stubTextRva; //換上目標文件的加載基址 *pFixAddr += targetDllbase; //加上新區(qū)段的段首RVA *pFixAddr += targetNewScnRva; //把屬性修改回去 VirtualProtect(pFixAddr, 4, dwOld, &dwOld); } //切換到下一個重定位塊 pRel = (IMAGE_BASE_RELOCATION*)((DWORD)pRel + pRel->SizeOfBlock); }
stub移植
這個簡單,沒啥說的
memcpy(GetLastSec(PeNewHmoudle)->PointerToRawData+ PeNewHmoudle, GetSecByName(pstub.dllbase, ".text")->VirtualAddress+pstub.dllbase, GetSecByName(pstub.dllbase,".text")->Misc.VirtualSize);
GetOptHeader(PeNewHmoudle)->AddressOfEntryPoint = pstub.pfnStart-(DWORD)pstub.dllbase-GetSecByName(pstub.dllbase,".text")->VirtualAddress+GetLastSec(PeNewHmoudle)->VirtualAddress;
去隨機基址
不去掉隨機基址,加載基址就是不固定的,不方便操作
GetOptHeader(PeNewHmoudle)->DllCharacteristics &= (~0x40);
void SaveFile(_In_ const char* path, _In_ const char* data, _In_ int FileSize) { HANDLE hFile = CreateFileA( path, GENERIC_WRITE, FILE_SHARE_READ, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL ); DWORD Buf = 0; WriteFile(hFile, data, FileSize, &Buf,NULL); CloseHandle(hFile); }
“如何從零開始寫一個加殼器”的內(nèi)容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業(yè)相關(guān)的知識可以關(guān)注創(chuàng)新互聯(lián)網(wǎng)站,小編將為大家輸出更多高質(zhì)量的實用文章!
新聞標題:如何從零開始寫一個加殼器
當前地址:http://www.rwnh.cn/article0/jgpsoo.html
成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供搜索引擎優(yōu)化、微信公眾號、網(wǎng)站維護、網(wǎng)站策劃、自適應(yīng)網(wǎng)站、靜態(tài)網(wǎng)站
聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請盡快告知,我們將會在第一時間刪除。文章觀點不代表本網(wǎng)站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時需注明來源: 創(chuàng)新互聯(lián)