利用pe添加節(jié)的方法添加代碼 實(shí)現(xiàn)簡(jiǎn)單的加殼
大致流程如下
要達(dá)到的目的是 添加一個(gè)新節(jié) 在新節(jié)中添加自己的代碼 讓程序運(yùn)行時(shí) 先運(yùn)行自己的代碼
而自己的代碼就是為了解開(kāi)之前對(duì)程序進(jìn)行的加密執(zhí)行完后 再繼續(xù)運(yùn)行原程序的路線。
詳細(xì)步驟
第一步
先寫(xiě)程序?qū)?采用文件映射的方法將待修改的exe加載進(jìn)來(lái) 獲取所有我們需要的信息
CreateFile CreateFileMapping MapViewOfFile(返回 文件基地址 Pimage)
GetFileSize(用于修改文件大小)
第二步
初始化pe頭信息 DOS=PIMAGE_DOS_HEADER(Pimage); 其他初始化略
驗(yàn)證pe的有效性 MZ 和 PE
第三步
開(kāi)始加節(jié) 因?yàn)楹筮呉薷膃op所在的節(jié)的內(nèi)容
所以先將此節(jié)的屬性設(shè)置為 可讀可寫(xiě) IMAGE_SCN_MEM_READ|IMAGE_SCN_MEM_WRITE
(此處很關(guān)鍵 自己修改的事飛秋 結(jié)果修改完后一直不能運(yùn)行 卡了很長(zhǎng)時(shí)間 切記)
加節(jié)
1
找到最后一節(jié)的地址
PIMAGE_SECTION_HEADER lastsec = SECTION+(FILE->NumberOfSections-1);
確定新加節(jié)的地址
PIMAGE_SECTION_HEADER newsec = lastsec+1;
節(jié)表數(shù)目加1
FILE->NumberOfSections++;
驗(yàn)證一下 在節(jié)表頭最后 到 第一個(gè)節(jié)內(nèi)容開(kāi)始 有沒(méi)有40個(gè)字節(jié)
(一般都有 沒(méi)有考慮不夠的情況)
驗(yàn)證方法(主要明白SizeOfHeaders的真實(shí)含義)
NEWSIZE 計(jì)算的是 (dos + dos stub ) + nt + 新加節(jié)后的節(jié)表頭總大小(純粹大小 未對(duì)齊)
DWORD NEWSIZE = (DOS->e_lfanew)+sizeof(IMAGE_OPTIONAL_HEADER32)+sizeof(IMAGE_SECTION_HEADER)*NumberOfSections;
(不要忘記 dos stub 直接用(sizeof(IMAGE_DOS_HEADER))盡管沒(méi)有神馬影響 但是事實(shí) 必須這樣計(jì)算)
讓它跟原SizeOfHeaders比較
SizeOfHeaders是 dos+dos stub+nt+所有節(jié)表頭總大小(對(duì)齊后的) 可以用作第一個(gè)節(jié)內(nèi)容的開(kāi)始位置
2
大小滿足之后 對(duì)新加節(jié)的所有屬性進(jìn)行初始化(UP函數(shù)是 用來(lái)對(duì)齊的 參數(shù) 1 大小 2 對(duì)齊粒度)
memcpy(newsec->Name,".NewSec",8);
newsec->Misc.VirtualSize=實(shí)際大小(size); 節(jié)內(nèi)容的實(shí)際大小 SizeOfRawData 用到
newsec->SizeOfRawData=Up(size,pe.OPTION->FileAlignment); 文件中的大小
newsec->VirtualAddress 在內(nèi)存中的RVA 重要 可以利用上一個(gè)節(jié)的數(shù)據(jù)得到
DWORD last =Up((lastsec->SizeOfRawData),pe.OPTION->SectionAlignment)+lastsec->VirtualAddress;
DWORD last =Up((LastSec->Misc.VirtualSize),pe.OPTION->SectionAlignment)+lastsec->VirtualAddress;
兩個(gè)last 相等 因?yàn)?SizeOfRawData 是Misc.VirtualSize按文件對(duì)齊得到的
文件對(duì)齊粒度512 內(nèi)存對(duì)齊粒度4096 所以肯定相等
不過(guò)為了準(zhǔn)確 采用后者
其實(shí)這個(gè)last 也就是 pe文件 載入內(nèi)存后 經(jīng)過(guò)SectionAlignment對(duì)齊后 的 總大小 SizeOfImage
newsec->PointerToRawData=lastsec->PointerToRawData+lastsec->SizeOfRawData; 同理
newsec->Characteristics=IMAGE_SCN_MEM_READ|IMAGE_SCN_MEM_WRITE; 可讀可寫(xiě)
其他屬性沒(méi)有什么影響 故均設(shè)置為0 到此 初始化完畢
3
需要修改的其他地方的參數(shù)
a (必須)文件在磁盤(pán)上的總大小 即第一步中的GetFileSize返回的值 再加上一頁(yè)的大小4096
用于CreateFileMapping創(chuàng)建文件映射對(duì)象函數(shù) 把大小設(shè)置為修改后的大小 即 加上4096后的值
b (必須)文件映射內(nèi)存后的總大小
OPTION->SizeOfImage+=Up(size,pe.OPTION->SectionAlignment);
c (不必須的)
pe.DATA[IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT].Size=0;
pe.DATA[IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT].VirtualAddress=0;
pe.OPTION->SizeOfCode+=Up(size,pe.OPTION->SectionAlignment);
pe.OPTION->SizeOfInitializedData+=Up(size,pe.OPTION->SectionAlignment);
4 至此加節(jié)成功
第四步
取反
因?yàn)槭菍?duì) eop所在節(jié)的內(nèi)容取反 所以要找到 eop 所在節(jié)
方法 獲取eop= OPTION->AddressOfEntryPoint 然后循環(huán)遍歷各個(gè)節(jié)表頭的起始地址 進(jìn)行比較
for (int i=0; i<FILE->NumberOfSections; i++)
{
PIMAGE_SECTION_HEADER sec = PIMAGE_SECTION_HEADER(SECTION+i);
if (eop>=sec->VirtualAddress && eop<=(sec->VirtualAddress+sec->SizeOfRawData))
{return sec;}
}
找到所在節(jié)后 是要對(duì)在磁盤(pán)上此節(jié)的內(nèi)容 進(jìn)行修改 所以獲取
PVOID address = sec->PointerToRawData+image; //文件的地址
DWORD lenth = sec->SizeOfRawData; //文件的大小
取反函數(shù)
void _stdcall QF(PVOID address,DWORD len)
{
DWORD i=0;
PBYTE buf=(PBYTE)address;
for ( i=0; i<len; i++)
{buf[i]=~buf[i];}
}
函數(shù)中有一個(gè)關(guān)鍵字 _stdcall 作用是 函數(shù)執(zhí)行完后 自己跳轉(zhuǎn)到之前壓棧的地址 用于返回eop使用
好了 取反結(jié)束了 此時(shí) 磁盤(pán)上的 exe文件已經(jīng)被 破壞 接下來(lái)是 解密
第五步
解密 比較復(fù)雜
首先說(shuō)一下思路
解密其實(shí)是 exe在執(zhí)行后 先執(zhí)行自己的解密算法 在跳轉(zhuǎn)回去 繼續(xù)自己的程序
所以解密 就是當(dāng)exe文件執(zhí)行時(shí) 先執(zhí)行自己的代碼 即將已取反加密的節(jié)再取反 即可正常運(yùn)行
記住是當(dāng)exe文件執(zhí)行的時(shí)候所以 需要獲得當(dāng) exe文件加載進(jìn)去后的 內(nèi)存地址才行(重要)
并不是說(shuō)將磁盤(pán)上的文件再改回來(lái) 這樣想是錯(cuò)的 再怎么說(shuō)文件執(zhí)行了 再對(duì)它進(jìn)行修改 肯定是不行的
獲得了待解密的節(jié)首地址 和 大小 此時(shí)解密函數(shù)參數(shù)已經(jīng)解決了 接下來(lái)要做的 是讓程序運(yùn)行的時(shí)候 先執(zhí)行自己的代碼
也就是先執(zhí)行自己的這個(gè)解密算法函數(shù) 一個(gè)函數(shù)運(yùn)行的步驟是 先將參數(shù)從右到左依次入棧 再將代碼入棧 而我們還需要
執(zhí)行完后 再跳轉(zhuǎn)回原eop入口地址 所以再將原eop入口地址入棧 待函數(shù)執(zhí)行完后 返回到這個(gè)地址 怎么返回的? 這就用到
關(guān)鍵字 _stdcall 它的作用是 由被調(diào)的函數(shù)清除堆棧 它的反匯編是 ret 8 ;兩個(gè)字節(jié)的大小
(在這個(gè)程序中不用它也可以 用是最安全 省心的 至于原因 強(qiáng)哥解釋了 我沒(méi)太搞明白)
接下來(lái)就開(kāi)始copy應(yīng)該copy的內(nèi)容到新加節(jié)的內(nèi)容中
參數(shù)入棧 需要自己寫(xiě)機(jī)器語(yǔ)言(因?yàn)槭菆?zhí)行自己的函數(shù)所以自己調(diào)) 利用結(jié)構(gòu)體 形成語(yǔ)句 然后再copy到新加節(jié)的開(kāi)始
當(dāng)執(zhí)行新加節(jié)的內(nèi)容時(shí) 先執(zhí)行自己做的這個(gè)解密函數(shù)(參數(shù)入棧 原eop入棧 代碼入棧 ret 8)
1 獲得copy的首地址 即 新加節(jié)的文件地址
PBYTE begin=(RVATORAW(newsec->VirtualAddress)+image);
再將 參數(shù)入棧的機(jī)器碼構(gòu)成結(jié)構(gòu)體 直接copy機(jī)器碼到新加節(jié)中
2 從右到左 copy參數(shù) 即先copy 節(jié)的長(zhǎng)度length
先對(duì)之前自己做的結(jié)構(gòu)體進(jìn)行初始化
MOV_EBX.address=secofeop->SizeOfRawData; //所在節(jié)的length
MOV_EAX.address=secofeop->VirtualAddress+pe.OPTION->ImageBase; //所在節(jié)的起始地址 va+400000
PUSH_OLD_EOP.address=pe.OPTION->AddressOfEntryPoint+pe.OPTION->ImageBase; //原eop+400000
再copy到新加節(jié)的內(nèi)容中
memcpy(begin,&MOV_EBX,sizeof(mov_eax)); //所在節(jié)的length
memcpy(begin+sizeof(mov_eax),&MOV_EAX,sizeof(mov_eax)); //所在節(jié)的起始地址 va+400000
memcpy(begin+sizeof(mov_eax)*2,&PUSH_OLD_EOP,sizeof(push_old_eop)); //原eop+400000
DWORD lenofcode=(DWORD)end_qf-(DWORD)start_qf;
//其中 start_qf 和 end_qf 是取反函數(shù)的首地址和末地址 用來(lái)計(jì)算代碼的長(zhǎng)度 注意最后還得加上末地址后邊的幾個(gè)字節(jié)(重要)
//兩個(gè)數(shù)值是自己寫(xiě)好程序后 調(diào)試程序時(shí) 手動(dòng)找出來(lái)的 不知道怎么動(dòng)態(tài)獲得 待大?春 指點(diǎn)迷津
//計(jì)算一下代碼需要的內(nèi)存長(zhǎng)度 再加上至少有返回的指令的長(zhǎng)度 此時(shí)即為2
//多了也沒(méi)事 自動(dòng)填充為 CC
memcpy(begin+sizeof(mov_eax)*2+sizeof(push_old_eop),(PVOID)start_qf,lenofcode+2); //
重要一步 修改eop入口點(diǎn)地址為新加節(jié)的rva
OPTION->AddressOfEntryPoint=newsec->VirtualAddress; //修改入口點(diǎn)地址到新加節(jié)的rva
此時(shí)解密也結(jié)束了
第六步
收尾工作
首先了解一點(diǎn)知識(shí) 摘在網(wǎng)絡(luò)
為了提高速度,系統(tǒng)將文件的數(shù)據(jù)頁(yè)面進(jìn)行高速緩存,并且在對(duì)文件的映射視圖進(jìn)行操作時(shí)不立即更新文件的磁盤(pán)映像。如果需要確保你的更新被寫(xiě)入磁盤(pán),可以強(qiáng)制系統(tǒng)將修改過(guò)的數(shù)據(jù)的一部分或全部重新寫(xiě)入磁盤(pán)映像中,方法是調(diào)用F l u s h Vi e w O f F i l e函數(shù):
BOOL FlushViewOfFile(
PVOID pvAddress,
SIZE_T dwNumberOfBytesToFlush);
第一個(gè)參數(shù)是包含在內(nèi)存映射文件中的視圖的一個(gè)字節(jié)的地址。該函數(shù)將你在這里傳遞的地址圓整為一個(gè)頁(yè)面邊界值。第二個(gè)參數(shù)用于指明你想要刷新的字節(jié)數(shù)。系統(tǒng)將把這個(gè)數(shù)字向上圓整,使得字節(jié)總數(shù)是頁(yè)面的整數(shù)。
1 "對(duì)文件的映射視圖進(jìn)行操作時(shí)不立即更新文件的磁盤(pán)映像" 那么何時(shí)更新?我程序正常退出前一定會(huì)更新吧?如果程序意外結(jié)束(比如電腦死機(jī))那是不是就可能無(wú)法將更改寫(xiě)入磁盤(pán)?
1、在UnmapViewOfFile、CloseHandle和系統(tǒng)回收物理內(nèi)存的時(shí)候?qū)懭氪疟P(pán)。
當(dāng)進(jìn)程結(jié)束時(shí)(包括正常和異常),系統(tǒng)會(huì)自動(dòng)關(guān)閉該進(jìn)程打開(kāi)的所有Handle,所以會(huì)寫(xiě)入磁盤(pán)。除非是內(nèi)核代碼異常,導(dǎo)致死機(jī),這時(shí)才可能沒(méi)有寫(xiě)入。
2 是不是只要程序不意外結(jié)束,我們就沒(méi)使用FlushViewOfFile的必要?否則請(qǐng)問(wèn)在什么情況下有必要使用它?
2、FlushViewOfFile是為了實(shí)現(xiàn)程序自己控制寫(xiě)入磁盤(pán)而提供的,當(dāng)你真正遇到這種需求的時(shí)候才能體會(huì)到它的價(jià)值。
本程序?qū)崿F(xiàn)的代碼如下
BOOL success = FlushViewOfFile(Pimage,FileSize); //將寫(xiě)入文件映射緩沖區(qū)的所有數(shù)據(jù)都刷新到磁盤(pán)
if (!success)
{return false;}
success = UnmapViewOfFile(Pimage); //在當(dāng)前應(yīng)用程序的內(nèi)存地址空間解除對(duì)一個(gè)文件映射對(duì)象的映射
//lpBaseAddress Long,指定要解除映射的一個(gè)文件映射的基準(zhǔn)地址。這個(gè)地址是早先用MapViewOfFile函數(shù)獲得的
CloseHandle(hMap);
return true;