網(wǎng)絡(luò)安全編程:PE編程實例之添加節(jié)區(qū)
添加節(jié)區(qū)在很多場合都會用到,比如在加殼中、在免殺中都會經(jīng)常用到對PE文件添加一個節(jié)區(qū)。添加一個節(jié)區(qū)的方法有4步,第1步是在節(jié)表的最后面添加一個IMAGE_SECTI ON_HEADER,第2步是更新IMAGE_FILE_HEADER中的NumberOfSections字段,第3步是更新IMAGE_OPTIONAL_HEADER中的SizeOfImage字段,最后一步則是添加文件的數(shù)據(jù)。當(dāng)然,前3步是沒有先后順序的,但是最后一步一定要明確如何改變。
某些情況下,在添加新的節(jié)區(qū)項以后會向新節(jié)區(qū)項的數(shù)據(jù)部分添加一些代碼,而這些代碼可能要求在程序執(zhí)行之前就被執(zhí)行,那么這時還需要更新IMAGE_OPTIONAL_HEADER中的AddressOfEntryPoint字段。
1. 手動添加一個節(jié)區(qū)
先來進行一次手動添加節(jié)區(qū)的操作,這個過程是個熟悉上述步驟的過程。網(wǎng)上有很多現(xiàn)成的添加節(jié)區(qū)的工具。這里自己編寫工具的目的是掌握和了解其實現(xiàn)方法,鍛煉編程能力;手動添加節(jié)區(qū)是為了鞏固所學(xué)的知識,熟悉添加節(jié)區(qū)的步驟。
使用C32Asm用十六進制編輯方式打開測試程序,并定位到其節(jié)表處,如圖1所示。
圖1 節(jié)表位置信息
從圖1中可以看到,該PE文件有3個節(jié)表。直接看十六進制信息可能很不方便,為了直觀方便地查看節(jié)表中IMAGE_SECTION_HEADER的信息,那么使用LordPE進行查看,如圖2所示。
圖2 使用LordPE查看該節(jié)表信息
用LordPE工具查看的確直觀多了。對照LordPE顯示的節(jié)表信息來添加一個節(jié)區(qū)。IMAGE_SECTION_HEADER結(jié)構(gòu)體定義如下:
- typedef struct _IMAGE_SECTION_HEADER {
- BYTE Name[IMAGE_SIZEOF_SHORT_NAME];
- union {
- DWORD PhysicalAddress;
- DWORD VirtualSize;
- } Misc;
- DWORD VirtualAddress;
- DWORD SizeOfRawData;
- DWORD PointerToRawData;
- DWORD PointerToRelocations;
- DWORD PointerToLinenumbers;
- WORD NumberOfRelocations;
- WORD NumberOfLinenumbers;
- DWORD Characteristics;
- } IMAGE_SECTION_HEADER, *PIMAGE_SECTION_HEADER;
IMAGE_SECTION_HEADER 結(jié)構(gòu)體的成員很多,但是真正要使用的只有 6 個,分別是Name、VirtualSize、VritualAddress、SizeOfRawData、PointerToRawData 和 Characteristics。這 6 項剛好與 LordPE 顯示的 6 項相同。其實 IMAGE_SECTION_HEADER 結(jié)構(gòu)體中其余的成員幾乎不被使用。下面介紹如何添加這些內(nèi)容。
IMAGE_SECTION_HEADER 的長度為 40 字節(jié),是十六進制的 0x28,在 C32Asm 中占用 2 行半的內(nèi)容,這里一次把這兩行半的內(nèi)容手動添加進去?;氐?C32Asm 中,在最后一個節(jié)表的位置處開始添加內(nèi)容,首先把光標放到右邊的 ASCII 字符中,輸入“.test”,如圖3所示。
圖3 添加“.test”節(jié)名
接下來在00000240位置處添加節(jié)的大小,該大小直接是對齊后的大小即可。由于文件對齊是0x1000字節(jié),也就是4096字節(jié),那么采用最小值即可,使該值為0x1000。在C32Asm中添加時,正確的添加應(yīng)當(dāng)是“00 10 00 00”,以后添加時也要注意字節(jié)順序。在添加后面幾個成員時,不再提示注意字節(jié)順序,應(yīng)時刻清楚這點。在添加該值時,應(yīng)當(dāng)將光標定位在十六進制編輯處,而不是剛才所在的ASCII字符處。順便要把VirutalAddress也添加上,VirtualAddress的值是前一個節(jié)區(qū)的起始位置加上上一個節(jié)對齊后的長度的值,上一個節(jié)區(qū)的起始位置為0x6000,上一個節(jié)區(qū)對齊后的長度為0x3000,因此新節(jié)區(qū)的起始位置為0x9000。添加VirtualSize和VirtualAddress后如圖4所示。
圖4 添加VirtualSize和VirtualAddress的值
接下來的兩個字段分別是SizeOfRawData和PointerToRawData,其添加方法類似前面兩個字段的添加方法,這里就不細說了。分別添加“0x9000”和“0x1000”兩個值,如圖5所示。
圖5 添加SizeOfRawData和PointerToRawData
PointerToRawData后面的12字節(jié)都可以為0,只要修改最后4字節(jié)的內(nèi)容,也就是Characteristics的值即可。這個值直接使用上一個節(jié)區(qū)的值即可,實際添加時應(yīng)根據(jù)所要節(jié)的屬性給值。這里為了省事而直接使用上一個節(jié)區(qū)的屬性,如圖6所示。
圖6 添加Characteristics屬性
整個節(jié)表需要添加的地方就添加完成了,接下來需要修改該PE文件的節(jié)區(qū)數(shù)量。當(dāng)前節(jié)區(qū)數(shù)量是3,這里要修改為4。雖然可以通過LordPE等修改工具完成,但是這里仍然使用手動修改。對于修改的位置,請大家自行定位找到,修改如圖7所示。
圖7 修改節(jié)區(qū)個數(shù)為4
除了節(jié)區(qū)數(shù)量以外,還要修改文件映像的大小,也就是SizeOfImage的值。由于新添加了節(jié)區(qū),那么應(yīng)該把該節(jié)區(qū)的大小加上SizeOfImage的大小,即為新的SizeOfImage的大小?,F(xiàn)在的SizeOfImage的大小為0x9000,加上新添加節(jié)區(qū)的大小為0xa000。SizeOfImage的位置請大家自行查找,修改如圖8所示。
圖8 修改SizeOfImage的值為0xa000
修改PE結(jié)構(gòu)字段的內(nèi)容都已經(jīng)做完了,最后一步就是添加真實的數(shù)據(jù)。由于這個節(jié)區(qū)不使用,因此填充0值就可以了,文件的起始位置為0x9000,長度為0x1000。把光標移到文件的末尾,單擊“編輯”→“插入數(shù)據(jù)”命令,在“插入數(shù)據(jù)大小”文本框中輸入十進制的4096,也就是十六進制的0x1000,如圖9所示。
圖9 “插入數(shù)據(jù)”對話框的設(shè)置
單擊“確定”按鈕,可以看到在剛才的光標處插入了很多0值,這樣工作也完成了。單擊“保存”按鈕進行保存,提示是否備份,選擇“是”。然后用LordPE查看添加節(jié)區(qū)的情況,如圖10所示。
圖10 添加新的節(jié)區(qū)信息
對比前后兩個文件的大小,如圖11所示。
圖11 添加節(jié)區(qū)前后文件的大小
從圖11中可以看出,添加節(jié)區(qū)后的文件比原來的文件大了4KB,這是由于添加了4096字節(jié)的0值。也許大家最關(guān)心的不是大小問題,而是軟件添加了大小后是否真的可以運行。其實試運行一下,是可以運行的。
上面的整個過程就是手動添加一個新節(jié)區(qū)的全部過程,除了特有的幾個步驟以外,要注意新節(jié)區(qū)的內(nèi)存起始位置和文件起始位置的值。相信通過上面手動添加節(jié)區(qū),大家對此已經(jīng)非常熟悉了。下面就開始通過編程來完成添加節(jié)區(qū)的任務(wù)。
在C32Asm軟件中可以快速定位PE結(jié)構(gòu)的各個結(jié)構(gòu)體和字段的位置,在菜單欄單擊“查看(V)”->“PE信息(P)”即可在C32Asm工作區(qū)的左側(cè)打開一個PE結(jié)構(gòu)字段的解析面板,在面板上雙擊PE結(jié)構(gòu)的每個字段則可在C32Asm工作區(qū)中定位到十六進制形式的PE結(jié)構(gòu)字段的數(shù)據(jù)。
2. 通過編程添加節(jié)區(qū)
通過編程添加一個新的節(jié)區(qū)無非就是文件相關(guān)的操作,只是多了一個對PE文件的解析和操作而已。添加節(jié)區(qū)的步驟和手動添加節(jié)區(qū)的步驟是一樣的,只要一步一步按照上面的步驟寫代碼就可以了。在開始寫代碼前,首先修改FileCreate()函數(shù)中的部分代碼,具體如下:
- m_hMap = CreateFileMapping(m_hFile, NULL,
- PAGE_READWRITE /*| SEC_IMAGE*/,0, 0, 0);
- if ( m_hMap == NULL )
- {
- CloseHandle(m_hFile);
- return bRet;
- }
這里要把SEC_IMAGE宏注釋掉。因為要修改內(nèi)存文件映射,有這個值會使添加節(jié)區(qū)失敗,因此要將其注釋掉或者直接刪除掉。
程序的界面如圖12所示。
圖12 添加節(jié)區(qū)界面
首先編寫“添加”按鈕響應(yīng)事件,代碼如下:
- void CPeParseDlg::OnBtnAddSection()
- {
- // 在這里添加驅(qū)動程序
- // 節(jié)名
- char szSecName[8] = { 0 };
- // 節(jié)大小
- int nSecSize = 0;
- GetDlgItemText(IDC_EDIT_SECNAME, szSecName, 8);
- nSecSize = GetDlgItemInt(IDC_EDIT_SEC_SIZE, FALSE, TRUE);
- AddSec(szSecName, nSecSize);
- }
按鈕事件中最關(guān)鍵的地方是AddSec()函數(shù)。該函數(shù)有兩個參數(shù),分別是添加節(jié)的名稱與添加節(jié)的大小。這個大小無論輸入多大,最后都會按照對齊方式進行向上對齊。看一下AddSec()函數(shù)的代碼,具體如下:
- VOID CPeParseDlg::AddSec(char *szSecName, int nSecSize)
- {
- int nSecNum = m_pNtHdr->FileHeader.NumberOfSections;
- DWORD dwFileAlignment = m_pNtHdr->OptionalHeader.FileAlignment;
- DWORD dwSecAlignment = m_pNtHdr->OptionalHeader.SectionAlignment;
- PIMAGE_SECTION_HEADER pTmpSec = m_pSecHdr + nSecNum;
- // 復(fù)制節(jié)名
- strncpy((char *)pTmpSec->Name, szSecName, 7);
- // 節(jié)的內(nèi)存大小
- pTmpSec->Misc.VirtualSize = AlignSize(nSecSize, dwSecAlignment);
- // 節(jié)的內(nèi)存起始位置
- pTmpSec->VirtualAddress=m_pSecHdr[nSecNum-1].VirtualAddress+AlignSize(m_pSecHdr [nSecNum - 1].Misc.VirtualSize, dwSecAlignment);
- // 節(jié)的文件大小
- pTmpSec->SizeOfRawData = AlignSize(nSecSize, dwFileAlignment);
- // 節(jié)的文件起始位置
- pTmpSec->PointerToRawData=m_pSecHdr[nSecNum-1].PointerToRawData+AlignSize(m_pSecHdr[nSecNum - 1].SizeOfRawData, dwSecAlignment);
- // 修正節(jié)數(shù)量
- m_pNtHdr->FileHeader.NumberOfSections ++;
- // 修正映像大小
- m_pNtHdr->OptionalHeader.SizeOfImage += pTmpSec->Misc.VirtualSize;
- FlushViewOfFile(m_lpBase, 0);
- // 添加節(jié)數(shù)據(jù)
- AddSecData(pTmpSec->SizeOfRawData);
- EnumSections();
- }
代碼中每一步都按照相應(yīng)的步驟來完成,其中用到的兩個函數(shù)分別是 AlignSize()和AddSecData()。前者是用來進行對齊的,后者是用來在文件中添加實際的數(shù)據(jù)內(nèi)容的。這兩個函數(shù)非常簡單,代碼如下:
- DWORD CPeParseDlg::AlignSize(int nSecSize, DWORD Alignment)
- {
- int nSize = nSecSize;
- if ( nSize % Alignment != 0 )
- {
- nSecSize = (nSize / Alignment + 1) * Alignment;
- }
- return nSecSize;
- }
- VOID CPeParseDlg::AddSecData(int nSecSize)
- {
- PBYTE pByte = NULL;
- pByte = (PBYTE)malloc(nSecSize);
- ZeroMemory(pByte, nSecSize);
- DWORD dwNum = 0;
- SetFilePointer(m_hFile, 0, 0, FILE_END);
- WriteFile(m_hFile, pByte, nSecSize, &dwNum, NULL);
- FlushFileBuffers(m_hFile);
- free(pByte);
- }
整個添加節(jié)區(qū)的代碼就完成了,仍然使用最開始的那個簡單程序進行測試,看是否可以添加一個節(jié)區(qū),如圖13所示。
圖13 添加節(jié)區(qū)
從圖13中可以看出,添加節(jié)區(qū)是成功的。試著運行一下添加節(jié)區(qū)后的文件,可以正常運行,而且添加節(jié)區(qū)的文件比原文件大了4KB,和前面手動添加的效果是一樣的。