東坡下載:內(nèi)容最豐富最安全的下載站!

首頁IT技術(shù)軟件教程 → 利用pe添加節(jié)的方法添加代碼 實(shí)現(xiàn)簡單的加殼

利用pe添加節(jié)的方法添加代碼 實(shí)現(xiàn)簡單的加殼

相關(guān)文章發(fā)表評論 來源:本站原創(chuàng)時(shí)間:2014/2/20 19:08:20字體大。A-A+

更多

作者:不詳點(diǎn)擊:589次評論:0次標(biāo)簽: Speccy

利用pe添加節(jié)的方法添加代碼 實(shí)現(xiàn)簡單的加殼

大致流程如下

要達(dá)到的目的是 添加一個(gè)新節(jié) 在新節(jié)中添加自己的代碼 讓程序運(yùn)行時(shí) 先運(yùn)行自己的代碼

而自己的代碼就是為了解開之前對程序進(jìn)行的加密執(zhí)行完后 再繼續(xù)運(yùn)行原程序的路線。




詳細(xì)步驟

第一步

先寫程序?qū)?采用文件映射的方法將待修改的exe加載進(jìn)來 獲取所有我們需要的信息
CreateFile    CreateFileMapping  MapViewOfFile(返回 文件基地址 Pimage)
GetFileSize(用于修改文件大小)

第二步

初始化pe頭信息 DOS=PIMAGE_DOS_HEADER(Pimage); 其他初始化略
驗(yàn)證pe的有效性 MZ 和 PE

第三步

開始加節(jié) 因?yàn)楹筮呉薷膃op所在的節(jié)的內(nèi)容
所以先將此節(jié)的屬性設(shè)置為 可讀可寫  IMAGE_SCN_MEM_READ|IMAGE_SCN_MEM_WRITE
(此處很關(guān)鍵 自己修改的事飛秋 結(jié)果修改完后一直不能運(yùn)行 卡了很長時(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)容開始 有沒有40個(gè)字節(jié)
(一般都有  沒有考慮不夠的情況)
驗(yàn)證方法(主要明白SizeOfHeaders的真實(shí)含義)

NEWSIZE 計(jì)算的是 (dos + dos stub ) + nt +  新加節(jié)后的節(jié)表頭總大小(純粹大小 未對齊)
DWORD NEWSIZE = (DOS->e_lfanew)+sizeof(IMAGE_OPTIONAL_HEADER32)+sizeof(IMAGE_SECTION_HEADER)*NumberOfSections;
(不要忘記 dos stub  直接用(sizeof(IMAGE_DOS_HEADER))盡管沒有神馬影響 但是事實(shí) 必須這樣計(jì)算)

讓它跟原SizeOfHeaders比較
SizeOfHeaders是  dos+dos stub+nt+所有節(jié)表頭總大小(對齊后的)       可以用作第一個(gè)節(jié)內(nèi)容的開始位置

2
大小滿足之后  對新加節(jié)的所有屬性進(jìn)行初始化(UP函數(shù)是 用來對齊的 參數(shù) 1 大小 2 對齊粒度)

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按文件對齊得到的
文件對齊粒度512 內(nèi)存對齊粒度4096 所以肯定相等
不過為了準(zhǔn)確 采用后者
其實(shí)這個(gè)last 也就是 pe文件 載入內(nèi)存后 經(jīng)過SectionAlignment對齊后 的 總大小 SizeOfImage

newsec->PointerToRawData=lastsec->PointerToRawData+lastsec->SizeOfRawData;  同理
newsec->Characteristics=IMAGE_SCN_MEM_READ|IMAGE_SCN_MEM_WRITE; 可讀可寫

其他屬性沒有什么影響 故均設(shè)置為0  到此 初始化完畢

3

需要修改的其他地方的參數(shù)
a (必須)文件在磁盤上的總大小 即第一步中的GetFileSize返回的值 再加上一頁的大小4096
  用于CreateFileMapping創(chuàng)建文件映射對象函數(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)槭菍?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é)后  是要對在磁盤上此節(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í) 磁盤上的 exe文件已經(jīng)被 破壞 接下來是 解密

第五步

解密 比較復(fù)雜

首先說一下思路
解密其實(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)存地址才行(重要)
并不是說將磁盤上的文件再改回來 這樣想是錯(cuò)的 再怎么說文件執(zhí)行了 再對它進(jìn)行修改 肯定是不行的

獲得了待解密的節(jié)首地址 和 大小 此時(shí)解密函數(shù)參數(shù)已經(jīng)解決了 接下來要做的 是讓程序運(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)哥解釋了 我沒太搞明白)

接下來就開始copy應(yīng)該copy的內(nèi)容到新加節(jié)的內(nèi)容中

參數(shù)入棧 需要自己寫機(jī)器語言(因?yàn)槭菆?zhí)行自己的函數(shù)所以自己調(diào)) 利用結(jié)構(gòu)體 形成語句 然后再copy到新加節(jié)的開始
當(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é)的長度length

先對之前自己做的結(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ù)的首地址和末地址 用來計(jì)算代碼的長度 注意最后還得加上末地址后邊的幾個(gè)字節(jié)(重要)
//兩個(gè)數(shù)值是自己寫好程序后 調(diào)試程序時(shí) 手動(dòng)找出來的 不知道怎么動(dòng)態(tài)獲得 待大牛看后 指點(diǎn)迷津
//計(jì)算一下代碼需要的內(nèi)存長度 再加上至少有返回的指令的長度 此時(shí)即為2
//多了也沒事 自動(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)知識 摘在網(wǎng)絡(luò)

為了提高速度,系統(tǒng)將文件的數(shù)據(jù)頁面進(jìn)行高速緩存,并且在對文件的映射視圖進(jìn)行操作時(shí)不立即更新文件的磁盤映像。如果需要確保你的更新被寫入磁盤,可以強(qiáng)制系統(tǒng)將修改過的數(shù)據(jù)的一部分或全部重新寫入磁盤映像中,方法是調(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è)頁面邊界值。第二個(gè)參數(shù)用于指明你想要刷新的字節(jié)數(shù)。系統(tǒng)將把這個(gè)數(shù)字向上圓整,使得字節(jié)總數(shù)是頁面的整數(shù)。

1 "對文件的映射視圖進(jìn)行操作時(shí)不立即更新文件的磁盤映像" 那么何時(shí)更新?我程序正常退出前一定會更新吧?如果程序意外結(jié)束(比如電腦死機(jī))那是不是就可能無法將更改寫入磁盤?
1、在UnmapViewOfFile、CloseHandle和系統(tǒng)回收物理內(nèi)存的時(shí)候?qū)懭氪疟P。
當(dāng)進(jìn)程結(jié)束時(shí)(包括正常和異常),系統(tǒng)會自動(dòng)關(guān)閉該進(jìn)程打開的所有Handle,所以會寫入磁盤。除非是內(nèi)核代碼異常,導(dǎo)致死機(jī),這時(shí)才可能沒有寫入。

2 是不是只要程序不意外結(jié)束,我們就沒使用FlushViewOfFile的必要?否則請問在什么情況下有必要使用它?
2、FlushViewOfFile是為了實(shí)現(xiàn)程序自己控制寫入磁盤而提供的,當(dāng)你真正遇到這種需求的時(shí)候才能體會到它的價(jià)值。


本程序?qū)崿F(xiàn)的代碼如下
BOOL success = FlushViewOfFile(Pimage,FileSize);  //將寫入文件映射緩沖區(qū)的所有數(shù)據(jù)都刷新到磁盤
if (!success)
{return false;}
success = UnmapViewOfFile(Pimage); //在當(dāng)前應(yīng)用程序的內(nèi)存地址空間解除對一個(gè)文件映射對象的映射
      //lpBaseAddress Long,指定要解除映射的一個(gè)文件映射的基準(zhǔn)地址。這個(gè)地址是早先用MapViewOfFile函數(shù)獲得的
CloseHandle(hMap);
return true;

擴(kuò)展知識

相關(guān)評論

閱讀本文后您有什么感想? 已有 人給出評價(jià)!

  • 2791 喜歡喜歡
  • 2101 頂
  • 800 難過難過
  • 1219 囧
  • 4049 圍觀圍觀
  • 5602 無聊無聊
熱門評論
最新評論
發(fā)表評論 查看所有評論(0)
昵稱:
表情: 高興 可 汗 我不要 害羞 好 下下下 送花 屎 親親
字?jǐn)?shù): 0/500 (您的評論需要經(jīng)過審核才能顯示)