中文字幕av专区_日韩电影在线播放_精品国产精品久久一区免费式_av在线免费观看网站

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

如何從零開始寫一個加殼器

發布時間:2021-10-15 09:37:27 來源:億速云 閱讀:231 作者:iii 欄目:編程語言

本篇內容介紹了“如何從零開始寫一個加殼器”的有關知識,在實際案例的操作過程中,不少人都會遇到這樣的困境,接下來就讓小編帶領大家學習一下如何處理這些情況吧!希望大家仔細閱讀,能夠學有所成!

加殼原理

手工加殼

即我們向PE文件添加一個區段并將其設置為入口點,這樣PE文件最開始執行的命令就是我們添加的區段也就是殼的指令,殼對加密區進行解密,對壓縮區進行解壓,將原本的EXE文件還原出來,然后跳轉至原程序入口,程序照常運行。

首先生成一個打印hello的exe文件。

#include <stdio.h>

int main() {
printf("hello");
}

我們目前要干的事情是:以手動的形式向PE文件添加一個殼部分并設為程序入口,并使其能跳轉回原入口。 那就來吧

用010editor打開我們的exe文件,啟用exe模板分析。 我們首先修改其文件頭numverofsection屬性,這個屬性用來定義當前PE文件存在多少個區段,因為我們要添加一個殼區段,所以我們將其加1變成6

如何從零開始寫一個加殼器

在我們重載模板后我們就會在區段表發現多出來一個空的區段表

如何從零開始寫一個加殼器

從上到下各個比較重要字段的意思是 \1. Name 表示該區段的名字 2.VirtualSize 表示在內存中的大小(一般內存對齊為0x1000) 3.virtualaddress 虛擬地址 即上一個區段的VirtualAddress + 上一個區段經內存對齊粒度對齊后的大小 4.sizeofdata 表示在文件中的大小(一般文件對齊為0x200) 5.pointertorawdata 文件的偏移 即 上一個區段的PointerToRawData + 上一個區段的SizeOfRawData

然后我們通過修改以上各值來定義一個新區段(殼區段)的屬性

如何從零開始寫一個加殼器

這里的virtualsize看著填一個就行了。 此時我們只是定義了區段表,但文件中并沒有該區段存在,所以我們得創建該區段。 然后還要讓區段可編輯,把下列值改為1即可

如何從零開始寫一個加殼器

如何從零開始寫一個加殼器

ctrl+shift+i 向目標文件偏移處插入0x200大小的空間。 這樣一來,殼區段就創建好了。 然后我們還要修改 擴展頭的SizeofImage 。將他改為最后一個區段的內存地址+內存大小

如何從零開始寫一個加殼器

然后去掉隨機基址選項。

找到擴展頭的DLL屬性字段,去掉隨機基址,把40 81改為 00 81

如何從零開始寫一個加殼器

接下來我們把程序入口點設置給殼區段。 使用LORDPE把入口點設為殼區塊的虛擬地址

然后我們用OD打開這個文件

如何從零開始寫一個加殼器

如何從零開始寫一個加殼器

真正的加殼流程

剛剛提到的手工加殼,不過是最最基礎的加殼原型而已,真正的加殼還涉及了代碼加解密等操作.

真正寫殼時一般寫兩個東西,加殼器和stub 所謂加殼器,就是給被加殼文件創造出一個新的區段, 在此同時將程序以某種方式加密,然后把stub放入新區段,并將程序入口點設為新區段的地址,然后在新區段結束后跳轉回原程序入口。這個新區段我們叫做殼區段. 那么這個stub就是加殼后程序最先執行的命令了,它執行解密算法,將原程序釋放出來。

基于c++的殼編寫

https://github.com/ConsT27/PackingEXE/tree/master項目地址

Stub

stub是被植入到PE文件中的代碼,它一般會干下面這些事情。

流程如下

0.合并data,rdata到text 1.PEB動態尋址,遍歷導出表找到GetProcAddress函數 2.解密 3.修改入口點到原入口點

同時stub一般以dll的形式存在。原因是DLL通常自帶重定位表,這在我們的移植過程中的重定位操作中提供了巨大的便利。

合并數據段

我們要移植stub過去,肯定需要移植代碼段,也需要移植數據段。不如我們干脆把數據段合并到代碼段,一塊移植過去。

PEB動態尋址&導出表遍歷找函數

為什么會用到這個技術編寫stub? 因為我們的stub.dll植入到宿主程序時,只有.text植入過去,沒有對應的導入表,所以我們的stub無法直接調用一些API。所以我們需要動態獲取各種API。 其中我采用的是PEB動態查詢得到GetProcAddress函數,然后用GetProcAddress函數去獲取各個API。

那么,什么是PEB? PEB是一個微軟還未完全公開作用的一個結構,它叫做 進程環境信息塊 ,包含了進程的信息。其結構如下

typedef struct _PEB {
BYTE                          Reserved1[2];
BYTE                          BeingDebugged; //被調試狀態
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;
復制代碼

我們關心的是PEB偏移0c得到的 PPEB_LDR_DATA Ldr; 它是一個指針,指向一個 PPEB_LDR_DATA 結構, 存放著已經被進程裝在的動態鏈接庫的信息

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結構的指針,這個結構 存放著指向模塊初始化鏈表的頭 , 按順序存放著PE裝入運行時初始化模塊信息,一般來說第一個鏈表結點是ntdll.dll,第二個鏈表結點就是kernel32.dll 。我們就在其中找到kernel32.dll的信息,獲取其PE信息,得到導出表,循環遍歷得到GetProcAddress函數。 另外,PEB地址再TEB偏移0x30處。用匯編語言表示就是 fs:[0x30]。

以上是PEB尋址的大致流程,另外還有一個比較關鍵的點是遍歷kernel32.dll導出表獲得GetProcAddress函數信息。 關于導出表可以看看這個文章https://blog.csdn.net/evileagle/article/details/12176797

首先一個導出表結構體如下

typedef struct _IMAGE_EXPORT_DIRECTORY {
DWORD   Characteristics;  //一般為0,沒啥用
DWORD   TimeDateStamp;  //導出表生成的時間
WORD    MajorVersion;  //版本,也是0沒啥用
WORD    MinorVersion;  //也是沒啥用的版本信息一般為0
DWORD   Name;  //當前導出表的模塊名字
DWORD   Base;  //序號表中序號的基數
DWORD   NumberOfFunctions;  //導出函數數量
DWORD   NumberOfNames;  //按名字導出函數的數量
DWORD   AddressOfFunctions;     // 序號表
DWORD   AddressOfNames;         // 名稱表
DWORD   AddressOfNameOrdinals;  // 地址表
} IMAGE_EXPORT_DIRECTORY, *PIMAGE_EXPORT_DIRECTORY;

其中序號表的起始序號是Base屬性定義的值。以下是導出表的序號名稱地址表的關系

如何從零開始寫一個加殼器

我們的遍歷流程是,先遍歷名稱表找到GetProcAddress在名稱數組中的下標,然后根據這個下標去序號數組中找相同下標的序號值,然后以這個序號值為下標去找地址數組中的對應值。我們找到的地址表中的值就是函數入口

下面我把這段程序的匯編代碼放出來。我是用內聯匯編把這段代碼塞進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; //獲得區段表
mov ebx, [ebx]; //獲得導出表RVA
add ebx, eax;  //導出表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];//名稱數組入口點rva,名稱數組單位大小4字節
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];  //獲得序號數組中對應下標的地址,序號數組單位大小2字節
and edi, 0FFFFh;  //給di提位到32位,即給予edi 序號表中對應下標的地址
mov edx, [ebx + 01ch];  
add edx, eax;  //獲得地址表
mov edi, [edx + edi * 4];  //獲得地址數組中,序號對應的值,地址數組單位大小4字節
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);//修改代碼段的屬性,注意我們這里使用了動態獲得的
//解密代碼段
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.添加新區段 5.stub重定位修復 6.stub移植 7.PE文件入口點修改 8.去隨機基址 9.保存文件

以下的各個流程描述中會用到諸多自定義函數,我先貼上來吧。

諸多自定函數&結構體
//****************
//對齊處理
//time:2020/11/5
//****************
int AlignMent(_In_ int size, _In_ int alignment) {
return (size) % (alignment)==0 ? (size) : ((size) / alignment+1) * (alignment);
}

//***********************
//PE信息獲取函數簇
//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)導出函數的地址
StubConf* pStubConf;    //stub.dll(g_conf)導出全局變量的地址
};

打開PE文件

這里采用的方法是利用CreateFileA函數。同時這個函數還拋出了一個指向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的內存并將指針置于首位
char* pFileBuf = new CHAR[FileSize]{ 0 };
//給剛剛申請的內存讀入數據
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的入口函數Start(自己定義在stub中的一個函數
pstub->pStubConf = (StubConf*)GetProcAddress((HMODULE)pstub->dllbase, "g_conf");
}
//不僅加載了stub,還獲得了stub拋出的用于收集信息的全局結構體(g_conf,是一個stub拋出的結構體,用于獲取信息,結構如下)
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);
//設置區段表屬性
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;
//設置OPT頭映像大小
GetOptHeader(hpe)->SizeOfImage = pesec->VirtualAddress + pesec->SizeOfRawData;
//擴充文件數據
int newSize = pesec->PointerToRawData + pesec->SizeOfRawData;
char* nhpe = new char [newSize] {0};
//向新緩沖區錄入數據
memcpy(nhpe, hpe, filesize);
//緩存區更替
delete hpe;
filesize = newSize;
return nhpe;
}

stub重定位

好家伙,這個東西稍有不慎就會讓整個程序拉跨掉(過來人的忠告 為什么需要stub重定位呢?因為我們的stub最開始是加載在內存中的,它的許多指令如跳轉到的地址是按內存為基準確定的,但是我們需要把他移植進文件,所以它的代碼里許多地址就是錯誤的,我們需要對這些地址進行處理,即重定位,使其以宿主程序為標準進行地址修復。 可能我表述的不是很清楚

舉個例子吧,比如stub在加載進內存時,有一條跳轉指令時jmp 12345678, 如果我們不處理就把這條指令移植進PE文件,那么PE文件執行到此處時就會跳轉到12345678,此時的12345678地址可能就已經不是PE文件加載的內存區間了,從而程序會崩潰。所以要修復。根據stub的重定位表進行修復。 重定位表就是記錄哪些地址的數據需要被修復的,我們遍歷這些地址進行修復即可。 如果以下代碼看起來吃力,可以先去了解一下重定位表

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;    //需要重定位的數量
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;
//加上新區段的段首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);
}

“如何從零開始寫一個加殼器”的內容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業相關的知識可以關注億速云網站,小編將為大家輸出更多高質量的實用文章!

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

南靖县| 武强县| 青浦区| 周至县| 五原县| 邢台县| 弥渡县| 雅安市| 阳泉市| 冕宁县| 吴川市| 峡江县| 卓尼县| 上高县| 饶平县| 铜川市| 久治县| 桦甸市| 阜南县| 兖州市| 商洛市| 麻栗坡县| 汕头市| 永济市| 鄂伦春自治旗| 南城县| 临漳县| 太白县| 丹江口市| 莱西市| 峨边| 博罗县| 龙江县| 芦山县| 大余县| 永年县| 陇川县| 和林格尔县| 嘉祥县| 迭部县| 通辽市|