2. 處理stub.dll
配置stub工程
將工程設置release版本,如果不想代碼被優化,可以禁止優化。
大概流程如下:
① 將數據段,只讀數據段和代碼段進行合并
② 編寫代碼獲取API的地址
③ 加入混淆指令,反調試
④ 解密/解壓縮
⑤ 加密IAT等等
之后會把存根文件stub.dll的.data,.rdata這2個區段合并到.text段并設置為可讀可寫可執行屬性,需要前置代碼
//把數據段融入代碼段
#pragma comment(linker,"/merge:.data=.text")
//把只讀數據段融入代碼段
#pragma comment(linker,"/merge:.rdata=.text")
//設置代碼段為可讀可寫可執行
#pragma comment(linker,"/section:.text,RWE")
根據之前說的已經知道殼區段就是新添加的區段了,里面將保存移植過來的stub的.text段里的所有內容,稱之為殼代碼。
而使用殼代碼的時候要注意,因為加完殼后,在殼代碼中無法使用導入表,因此,需要自己動態獲取需要使用的API函數的地址。
只要獲取到LoadLibraryExA和GetProcAddress兩個函數的地址,我們就可以根據LoadLibraryExA來獲取任意模塊dll的基地址,再使用GetProcAddress函數獲取到任意API函數的地址了。
根據kernel32基址可獲取到GetProcAddress地址。
下面是我獲取kernel32基址的內聯匯編代碼。
__asm
{
push esi;
mov esi, fs:[0x30]; //得到PEB地址
mov esi, [esi + 0xc]; //指向PEB_LDR_DATA結構的首地址
mov esi, [esi + 0x1c];//一個雙向鏈表的地址
mov esi, [esi]; //得到第2個條目kernelBase的鏈表
mov esi, [esi]; //得到第3個條目kernel32的鏈表(win10系統)
mov esi, [esi + 0x8]; //kernel32.dll地址
mov g_hKernel32, esi;
pop esi;
}
然后是獲取GetProcAddress函數的匯編代碼,可以使用C語言方式獲取,但我覺得用匯編寫,它就這樣赤裸裸呈現,能更加清晰的了解找到一個函數地址的過程。
//獲取GetProcAddress函數地址
void MyGetFunAddress()
{
__asm
{
pushad;
mov ebp, esp;
sub esp, 0xc;
mov edx, g_hKernel32;
mov esi, [edx + 0x3c]; //NT頭的RVA
lea esi, [esi + edx]; //NT頭的VA
mov esi, [esi + 0x78]; //Export的Rva
lea edi, [esi + edx]; //Export的Va
mov esi, [edi + 0x1c]; //Eat的Rva
lea esi, [esi + edx]; //Eat的Va
mov[ebp - 0x4], esi; //保存Eat
mov esi, [edi + 0x20]; //Ent的Rva
lea esi, [esi + edx]; //Ent的Va
mov[ebp - 0x8], esi; //保存Ent
mov esi, [edi + 0x24]; //Eot的Rva
lea esi, [esi + edx]; //Eot的Va
mov[ebp - 0xc], esi; //保存Eot
xor ecx, ecx;
jmp _First;
_Zero:
inc ecx;
_First:
mov esi, [ebp - 0x8]; //Ent的Va
mov esi, [esi + ecx * 4]; //FunName的Rva
lea esi, [esi + edx]; //FunName的Va
cmp dword ptr[esi], 050746547h;// 47657450 726F6341 64647265 7373;
jne _Zero; // 上面的16進制是GetProcAddress的ASCII
cmp dword ptr[esi + 4], 041636f72h;
jne _Zero;
cmp dword ptr[esi + 8], 065726464h;
jne _Zero;
cmp word ptr[esi + 0ch], 07373h;
jne _Zero;
xor ebx,ebx
mov esi, [ebp - 0xc]; //Eot的Va
mov bx, [esi + ecx * 2]; //得到序號
mov esi, [ebp - 0x4]; //Eat的Va
mov esi, [esi + ebx * 4]; //FunAddr的Rva
lea eax, [esi + edx]; //FunAddr
mov MyGetProcAddress, eax;
add esp, 0xc;
popad;
}
}
然后再獲取下MessageBoxW函數,彈出一個對話框,測試是否成功。
//運行函數
void RunFun()
{
MyLoadLibraryExA = (FuLoadLibraryExA)MyGetProcAddress(g_hKernel32, "LoadLibraryExA");
g_hUser32 = MyLoadLibraryExA("user32.dll", 0, 0);
MyMessageBoxW = (FuMessageBoxW)MyGetProcAddress(g_hUser32, "MessageBoxW");
MyMessageBoxW(0, L"大家好我是一個殼", L"提示", 0);
}
它在運行原代碼之前先運行了殼代碼,測試成功。
四、代碼段加密
我們在逆向破解的時候通常第一方法是找到關鍵字符串,關鍵代碼等,他們都是存在于代碼段的,那么只要把代碼段進行加密,這種方式就不可行了。
先在加殼器中加密,這使用簡單的亦或加密。
//加密代碼段
//1.獲取代碼段首地址
char* pTarText = GetSecHeader(pTarBuff, ".text")->PointerToRawData + pTarBuff;
//2.獲取代碼段實際大小
int nSize = GetSecHeader(pTarBuff, ".text")->Misc.VirtualSize;
for (int i = 0; i < nSize; ++i)
{
pTarText[i] ^= 0x15;
}
再到殼代碼里解密,自己寫了一個對比字符串的函數。
//自寫strcmp
int StrCmpText(const char* pStr, char* pBuff)
{
int nFlag = 1;
__asm
{
mov esi, pStr;
mov edi, pBuff;
mov ecx, 0x6;
cld;
repe cmpsb;
je _end;
mov nFlag, 0;
_end:
}
return nFlag;
}
//解密
void Decryption()
{
//獲取.text的區段頭
auto pNt = GetNtHeader((char*)g_hModule);
DWORD dwSecNum = pNt->FileHeader.NumberOfSections;
auto pSec = IMAGE_FIRST_SECTION(pNt);
//找到代碼區段
for (size_t i = 0; i < dwSecNum; i++)
{
if (StrCmpText(".text", (char*)pSec[i].Name))
{
pSec += i;
break;
}
}
//獲取代碼段首地址
char* pTarText = pSec->VirtualAddress + (char*)g_hModule;
int nSize = pSec->Misc.VirtualSize;
DWORD old = 0;
//解密代碼段
MyVirtualProtect(pTarText, nSize, PAGE_READWRITE, &old);
for (int i = 0; i < nSize; ++i) {
pTarText[i] ^= 0x15;
}
MyVirtualProtect(pTarText, nSize, old, &old);
}
五、壓縮
壓縮是一個比較復雜的過程,對于一個主要功能的加密的殼來說,壓縮也有一定的加密效果,如果使用了一些加密庫加密,即使你壓縮了,會發現加殼后的文件比沒加殼之前還要大!
這說一下壓縮大概思路,首先不能壓縮頭部,考慮到后面要處理TLS,還有一個程序的圖標在資源段,所以不壓縮這兩個段。
在加殼器中把原文件的中除了.tls和.rsrc段的其他段的數據一個一個的按順序取出來,然后拼接在一起,然后對這份拼接后數據進行一個整體的壓縮,之后需要再添加一個區段專門用于存放壓縮后的數據,這個過程中,需要把壓縮后的區段的文件偏移和文件大小都清零,如下圖所示,把.tsl段和.rsrc段移動到頭部的后面。
值得注意的是沒有處理TLS時要把TLS表的RVA和大小清零,TLS在數據目錄表的第九項。
auto pData = GetOptHeader(pTarBuff)->DataDirectory;
pData[9].Size = 0;
pData[9].VirtualAddress = 0;
運行時,先在殼代碼中進行解壓縮,再解密,然后程序就能正常運行了。
到此一個簡單的加密壓縮殼就完成了,在這個過程中實際出現了很多bug,因為涉及到DLL文件無法用VS調試, 所以使用OD或者x64dbg進行調試,推薦使用x64dbg(x32dbg),這個軟件一直在更新,而且字符串提示更友好,更方便快捷。OD主要用于脫殼破解,逆向還是x64dbg更方便。
最后再說一下VS2017使用配置:
有2個工程文件 一個是加殼器,一個是sutb。
加殼器使用x32debug編譯
sutb使用x32Release編譯
找到工程所在文件夾,新建一個bin目錄,把這兩個工程屬性中的輸出目錄改為bin,這樣操作起來方便一些,不改也行,但是加載stub時路徑就要填寫正確才行。
一個殼的基本框架就搭建完成了,而加殼主要是為了防止被別人破解,所以接下來就可以執行加密操作了,下一次再說說IAT加密,Hash加密,動態解密,反調試等技術吧。
-
WINDOWS
+關注
關注
4文章
3551瀏覽量
88841 -
C++
+關注
關注
22文章
2110瀏覽量
73697 -
PE文件
+關注
關注
0文章
4瀏覽量
5457
發布評論請先 登錄
相關推薦
評論