網(wǎng)絡(luò)安全編程:PE編程實例之查殼工具
PE文件結(jié)構(gòu)中大多用的是偏移地址,因此,只要偏移地址和實際的數(shù)據(jù)相符,那么PE文件格式有可能是嵌套的。也就是說,PE文件是可以變形的,只要保證其偏移地址和PE文件格式的結(jié)構(gòu)基本就沒多大問題。
對于PE可執(zhí)行文件來說,為了保護(hù)可執(zhí)行文件或者是壓縮可執(zhí)行文件,通常會對該文件進(jìn)行加殼。接觸過軟件破解的人應(yīng)該都清楚殼的概念。下面來寫一個查殼的工具。
首先,用ASPack給前面寫的程序加個殼。打開ASPack加殼工具,如圖1所示。
圖1 ASPack加殼工具界面
對測試用的軟件進(jìn)行一次加殼,不過在加殼前先用PEiD查看一下,如圖2所示。
圖2 PEiD查殼
從圖2可以看出,該程序是Visual C++ 5.0 Debug版的程序。其實該程序是用Visual C++ 6.0寫的,這里是PEiD識別有誤。不過只要用Visual C++ 6.0進(jìn)行編譯選擇Release版時,PEiD是可以正確進(jìn)行識別的。使用ASPack對該程序進(jìn)行加殼,然后用PEiD查殼,如圖3所示。
圖3 用PEiD查看加殼后的文件
從圖3中可以看出,PEiD識別出文件被加過殼,且是用ASPack進(jìn)行加殼的。PEiD如何識別程序被加殼,以及加了哪種殼呢?在PEiD的目錄下有一個特征碼文件,名為“userdb.txt”。打開這個文件,看大概內(nèi)容就能知道里邊保存了殼的特征碼。程序員的任務(wù)就是自己實現(xiàn)一個這個殼的識別工具。
殼的識別是通過特征碼進(jìn)行的,特征碼的提取通常是選擇文件的入口處。殼會修改程序的入口處,因此對于殼的特征碼來說,選擇入口處比較合適。這里的工具主要是用來學(xué)習(xí)和演示用的,因此寫的查殼工具要能識別兩種類型,第一種類型是可以識別用Visual C++ 6.0編譯出來的文件,第二種類型是可以識別ASPack加殼后的程序。當(dāng)然,ASPack加殼工具的版本眾多,這里只要能識別上面所演示版本的ASPack就可以了。
如何提取特征碼呢?程序無論是在磁盤上還是在內(nèi)存中,都是以二進(jìn)制的形式存在的。特征碼是從程序的入口處進(jìn)行提取的,那么可以使用C32Asm以十六進(jìn)制的形式打開這些文件,在入口處提取特征碼,也可以用OD將程序載入內(nèi)存后提取特征碼。這里選擇使用OD提取特征碼。用OD載入未加殼的程序,如圖4所示。
圖4 OD載入為加殼文件的入口處
可以看到,這就是未加殼程序的入口處代碼。在圖4中,“HEX數(shù)據(jù)”列中就是代碼對應(yīng)的十六進(jìn)制編碼,這里要做的就是提取這些十六進(jìn)制編碼。提取結(jié)果如下:
- "\x55\x8B\xEC\x6A\xFF\x68\x00\x65\x41\x00" \
- "\x68\xE8\x2D\x40\x00\x64\xA1\x00\x00\x00" \
- "\x00\x50\x64\x89\x25\x00\x00\x00\x00\x83" \
- "\xC4\x94"
根據(jù)這個步驟,把ASPack的特征碼也提取出來,提取結(jié)果如下:
- "\x60\xE8\x03\x00\x00\x00\xE9\xEB\x04\x5D" \
- "\x45\x55\xC3\xE8\x01\x00\x00\x00\xEB\x5D" \
- "\xBB\xED\xFF\xFF\xFF\x03\xDD\x81\xEB\x00"
- "\xC0\x01"
有了這些特征碼,就可以開始編程了。先來定義一個數(shù)據(jù)結(jié)構(gòu),用來保存特征碼,該結(jié)構(gòu)如下:
- #define NAMELEN 20
- #define SIGNLEN 32
- typedef struct _SIGN
- {
- char szName[NAMELEN];
- BYTE bSign[SIGNLEN + 1];
- }SIGN, *PSIGN;
利用該數(shù)據(jù)結(jié)構(gòu)定義2個保存特征碼的全局變量,具體如下:
- SIGN Sign[2] =
- {
- {
- // VC6
- "VC6",
- "\x55\x8B\xEC\x6A\xFF\x68\x00\x65\x41\x00" \
- "\x68\xE8\x2D\x40\x00\x64\xA1\x00\x00\x00" \
- "\x00\x50\x64\x89\x25\x00\x00\x00\x00\x83" \
- "\xC4\x94"
- },
- {
- // ASPACK
- "ASPACK",
- "\x60\xE8\x03\x00\x00\x00\xE9\xEB\x04\x5D" \
- "\x45\x55\xC3\xE8\x01\x00\x00\x00\xEB\x5D" \
- "\xBB\xED\xFF\xFF\xFF\x03\xDD\x81\xEB\x00"
- "\xC0\x01"
- }};
程序界面是在PE查看器的基礎(chǔ)上完成的,如圖5所示。
圖5 查殼程序結(jié)果
提取特征碼后,查殼工作只剩特征碼匹配了。這非常簡單,只要用文件的入口處代碼和特征碼進(jìn)行匹配,匹配相同就會給出相應(yīng)的信息。查殼的代碼如下:
- VOID CPeParseDlg::GetPeInfo()
- {
- PBYTE pSign = NULL;
- // 定位文件入口位置
- pSign = (PBYTE)((DWORD)m_lpBase
- + m_pNtHdr->OptionalHeader.AddressOfEntryPoint);
- // 比較入口特征碼
- if ( memcmp(Sign[0].bSign, pSign, SIGNLEN) == 0 )
- {
- SetDlgItemText(IDC_EDIT_PEINFO, Sign[0].szName);
- }
- else if ( memcmp(Sign[1].bSign, pSign, SIGNLEN) == 0 )
- {
- SetDlgItemText(IDC_EDIT_PEINFO, Sign[1].szName);
- }
- else
- {
- SetDlgItemText(IDC_EDIT_PEINFO, "未知");
- }
- }
這樣,查殼程序的功能就完成了。在程序中提取的特征碼的長度為32字節(jié),由于這里只是一個簡單的例子,大家在提取特征碼的時候,為了提高準(zhǔn)確率,需要多進(jìn)行一些測試。