2. 處理stub.dll
配置stub工程
將工程設(shè)置release版本,如果不想代碼被優(yōu)化,可以禁止優(yōu)化。
大概流程如下:
① 將數(shù)據(jù)段,只讀數(shù)據(jù)段和代碼段進(jìn)行合并
② 編寫代碼獲取API的地址
③ 加入混淆指令,反調(diào)試
④ 解密/解壓縮
⑤ 加密IAT等等
之后會(huì)把存根文件stub.dll的.data,.rdata這2個(gè)區(qū)段合并到.text段并設(shè)置為可讀可寫可執(zhí)行屬性,需要前置代碼
//把數(shù)據(jù)段融入代碼段
#pragma comment(linker,"/merge:.data=.text")
//把只讀數(shù)據(jù)段融入代碼段
#pragma comment(linker,"/merge:.rdata=.text")
//設(shè)置代碼段為可讀可寫可執(zhí)行
#pragma comment(linker,"/section:.text,RWE")
根據(jù)之前說(shuō)的已經(jīng)知道殼區(qū)段就是新添加的區(qū)段了,里面將保存移植過(guò)來(lái)的stub的.text段里的所有內(nèi)容,稱之為殼代碼。
而使用殼代碼的時(shí)候要注意,因?yàn)榧油隁ず?,在殼代碼中無(wú)法使用導(dǎo)入表,因此,需要自己動(dòng)態(tài)獲取需要使用的API函數(shù)的地址。
只要獲取到LoadLibraryExA和GetProcAddress兩個(gè)函數(shù)的地址,我們就可以根據(jù)LoadLibraryExA來(lái)獲取任意模塊dll的基地址,再使用GetProcAddress函數(shù)獲取到任意API函數(shù)的地址了。
根據(jù)kernel32基址可獲取到GetProcAddress地址。
下面是我獲取kernel32基址的內(nèi)聯(lián)匯編代碼。
__asm
{
push esi;
mov esi, fs:[0x30]; //得到PEB地址
mov esi, [esi + 0xc]; //指向PEB_LDR_DATA結(jié)構(gòu)的首地址
mov esi, [esi + 0x1c];//一個(gè)雙向鏈表的地址
mov esi, [esi]; //得到第2個(gè)條目kernelBase的鏈表
mov esi, [esi]; //得到第3個(gè)條目kernel32的鏈表(win10系統(tǒng))
mov esi, [esi + 0x8]; //kernel32.dll地址
mov g_hKernel32, esi;
pop esi;
}
然后是獲取GetProcAddress函數(shù)的匯編代碼,可以使用C語(yǔ)言方式獲取,但我覺得用匯編寫,它就這樣赤裸裸呈現(xiàn),能更加清晰的了解找到一個(gè)函數(shù)地址的過(guò)程。
//獲取GetProcAddress函數(shù)地址
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進(jìn)制是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]; //得到序號(hào)
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函數(shù),彈出一個(gè)對(duì)話框,測(cè)試是否成功。
//運(yùn)行函數(shù)
void RunFun()
{
MyLoadLibraryExA = (FuLoadLibraryExA)MyGetProcAddress(g_hKernel32, "LoadLibraryExA");
g_hUser32 = MyLoadLibraryExA("user32.dll", 0, 0);
MyMessageBoxW = (FuMessageBoxW)MyGetProcAddress(g_hUser32, "MessageBoxW");
MyMessageBoxW(0, L"大家好我是一個(gè)殼", L"提示", 0);
}
它在運(yùn)行原代碼之前先運(yùn)行了殼代碼,測(cè)試成功。
四、代碼段加密
我們?cè)谀嫦蚱平獾臅r(shí)候通常第一方法是找到關(guān)鍵字符串,關(guān)鍵代碼等,他們都是存在于代碼段的,那么只要把代碼段進(jìn)行加密,這種方式就不可行了。
先在加殼器中加密,這使用簡(jiǎn)單的亦或加密。
//加密代碼段
//1.獲取代碼段首地址
char* pTarText = GetSecHeader(pTarBuff, ".text")->PointerToRawData + pTarBuff;
//2.獲取代碼段實(shí)際大小
int nSize = GetSecHeader(pTarBuff, ".text")->Misc.VirtualSize;
for (int i = 0; i < nSize; ++i)
{
pTarText[i] ^= 0x15;
}
再到殼代碼里解密,自己寫了一個(gè)對(duì)比字符串的函數(shù)。
//自寫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的區(qū)段頭
auto pNt = GetNtHeader((char*)g_hModule);
DWORD dwSecNum = pNt->FileHeader.NumberOfSections;
auto pSec = IMAGE_FIRST_SECTION(pNt);
//找到代碼區(qū)段
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);
}
五、壓縮
壓縮是一個(gè)比較復(fù)雜的過(guò)程,對(duì)于一個(gè)主要功能的加密的殼來(lái)說(shuō),壓縮也有一定的加密效果,如果使用了一些加密庫(kù)加密,即使你壓縮了,會(huì)發(fā)現(xiàn)加殼后的文件比沒加殼之前還要大!
這說(shuō)一下壓縮大概思路,首先不能壓縮頭部,考慮到后面要處理TLS,還有一個(gè)程序的圖標(biāo)在資源段,所以不壓縮這兩個(gè)段。
在加殼器中把原文件的中除了.tls和.rsrc段的其他段的數(shù)據(jù)一個(gè)一個(gè)的按順序取出來(lái),然后拼接在一起,然后對(duì)這份拼接后數(shù)據(jù)進(jìn)行一個(gè)整體的壓縮,之后需要再添加一個(gè)區(qū)段專門用于存放壓縮后的數(shù)據(jù),這個(gè)過(guò)程中,需要把壓縮后的區(qū)段的文件偏移和文件大小都清零,如下圖所示,把.tsl段和.rsrc段移動(dòng)到頭部的后面。
值得注意的是沒有處理TLS時(shí)要把TLS表的RVA和大小清零,TLS在數(shù)據(jù)目錄表的第九項(xiàng)。
auto pData = GetOptHeader(pTarBuff)->DataDirectory;
pData[9].Size = 0;
pData[9].VirtualAddress = 0;
運(yùn)行時(shí),先在殼代碼中進(jìn)行解壓縮,再解密,然后程序就能正常運(yùn)行了。
到此一個(gè)簡(jiǎn)單的加密壓縮殼就完成了,在這個(gè)過(guò)程中實(shí)際出現(xiàn)了很多bug,因?yàn)樯婕暗紻LL文件無(wú)法用VS調(diào)試, 所以使用OD或者x64dbg進(jìn)行調(diào)試,推薦使用x64dbg(x32dbg),這個(gè)軟件一直在更新,而且字符串提示更友好,更方便快捷。OD主要用于脫殼破解,逆向還是x64dbg更方便。
最后再說(shuō)一下VS2017使用配置:
有2個(gè)工程文件 一個(gè)是加殼器,一個(gè)是sutb。
加殼器使用x32debug編譯
sutb使用x32Release編譯
找到工程所在文件夾,新建一個(gè)bin目錄,把這兩個(gè)工程屬性中的輸出目錄改為bin,這樣操作起來(lái)方便一些,不改也行,但是加載stub時(shí)路徑就要填寫正確才行。
一個(gè)殼的基本框架就搭建完成了,而加殼主要是為了防止被別人破解,所以接下來(lái)就可以執(zhí)行加密操作了,下一次再說(shuō)說(shuō)IAT加密,Hash加密,動(dòng)態(tài)解密,反調(diào)試等技術(shù)吧。
-
WINDOWS
+關(guān)注
關(guān)注
4文章
3564瀏覽量
89104 -
C++
+關(guān)注
關(guān)注
22文章
2114瀏覽量
73784 -
PE文件
+關(guān)注
關(guān)注
0文章
4瀏覽量
5458
發(fā)布評(píng)論請(qǐng)先 登錄
相關(guān)推薦
評(píng)論