翻譯作者:天魔降臨
原始鏈接:http://www.ph4nt0m.org/bbs/showthread.php?s=&threadid=34318
PS:翻譯作者話:
事先聲明這片文章并非我的原創,你可以在這個地址
http://www.codeproject.com/system/inject2exe.asp
察看到原文,由于本人能力有限,可能有一些翻譯錯誤請見諒(PS:感謝ROOTKIT上的朋友告訴我這片文章)
Download
PE Viewer
PE Maker - Step 1
PE Maker - Step 2 - Travel towards OEP
PE Maker - Step 3 - Support Import Table.
PE Maker - Step 4 - Support DLL and OCX
PE Maker - Step 5- Final work.
CALC.EXE - test file
0序文
也許你想要了解一個病毒,注入到程序內部并且感染他的方法,或者你對保護你特殊的PE文件的數據感興趣。你能使用這篇文章的源代碼構建你自定義的EXE BUILDER。如果用在好的方面,他能教你怎樣保護或封裝加密你的PE文件,但是同樣如果你用在邪惡的方面,他能產生一個病毒。然而,我寫這篇文章的目的是前者,所以,我不會為不道德的使用負責。
1預備知識
按照主題這篇文章不需要特殊的預備知識,如果你已經了解了DEBUGGER和文件結構,那么我建議你跳過2,3部分,這兩部分是為毫無基礎的人準備的。
2.PE文件的結構
規定PE文件的結構為WINDOWS OS提供了最好的方式去執行代碼,并且儲存一個程序運行所需要的基本數據。例如常量,變量等等。如圖:





2.1MS-DOS的數據
MS-DOS數據由你的可執行文件調用DOS內部的一個函數完成。并且 the MS-DOS Stub program讓他顯示:
This program can not be run in MS-DOS mode" 或"This program can be run only in Windows mode的字樣,或者當你試圖在 MS-DOS 6.0中運行一個windows文件時也回顯示類似的字樣。MS-DOS數據最有意思的部分是"MZ"!
對于我,在 MS-DOS 數據中僅僅PE署名的偏移是重要的,借助于他我能找到WINDOWS NT數據的位置。
我建議你在看看上圖。然后看看 文件中IMAGE_DOS_HEADER 的結構。

e_lfanew是一個與WINDOWS NT數據有關的偏移。在這我提供你一個顯示EXE文件頭信息的工具。
PE Viewer
Download source files - 132 Kb
2.2 The Windows NT data
正如前一節提及的e_lfanew儲存者有關WINDOWS NT數據位置的信息,假設 pMem 指針與指向一個PE文件內存空間的起始點時,借助于下面的代碼你既能找會DOS 頭,也能找到WINDOWS NT 頭
Code:
IMAGE_DOS_HEADER????????image_dos_header
;
IMAGE_NT_HEADERS????????image_nt_headers
;
PCHAR pMem
;
… memcpy
(&
image_dos_header
,
pMem
, ???????
sizeof
(
IMAGE_DOS_HEADER
));
memcpy
(&
image_nt_headers
, ???????
pMem
+
image_dos_header
.
e_lfanew
, ???????
sizeof
(
IMAGE_NT_HEADERS
));
|
似乎找回頭信息是很容易的事,我建議去看MSDN中關于IMAGE_NT_HEADERS 的說明。現在你應該很了解WINDOW NT結構了,他包含PE署名,the File Header,和the Optional Header。
下面是大多數環境中IMAGE_NT_HEADERS的結構:
PRE lang
=
c
++>
FileHeader
->
NumberOfSections OptionalHeader
->
AddressOfEntryPoint OptionalHeader
->
ImageBase OptionalHeader
->
SectionAlignment OptionalHeader
->
FileAlignment OptionalHeader
->
SizeOfImage OptionalHeader
-> ??
DataDirectory
[
IMAGE_DIRECTORY_ENTRY_IMPORT
]->
VirtualAddress OptionalHeader
->
DataDirectory
[
IMAGE_DIRECTORY_ENTRY_IMPORT
]->
Size
PRE
>
|
你能清晰的觀察到,這些值的目的,以及當為一個EXE文件分配虛擬內存時,他們的腳色。我要對PE數據目錄進行一個簡單的說明。當你通過Windows NT 信息對Optional header 進行觀察時,你會發現在Optional Header的結尾處有十六個目錄,再那你能找到連續的目錄,包括一些與虛擬內存和其大小有關的信息。
Code:
#define IMAGE_DIRECTORY_ENTRY_EXPORT??????????0???// Export Directory #define IMAGE_DIRECTORY_ENTRY_IMPORT??????????1???// Import Directory #define IMAGE_DIRECTORY_ENTRY_RESOURCE????????2???// Resource Directory #define IMAGE_DIRECTORY_ENTRY_EXCEPTION???????3???// Exception Directory #define IMAGE_DIRECTORY_ENTRY_SECURITY????????4???// Security Directory #define IMAGE_DIRECTORY_ENTRY_BASERELOC???????5???// Base Relocation Table #define IMAGE_DIRECTORY_ENTRY_DEBUG???????????6???// Debug Directory #define IMAGE_DIRECTORY_ENTRY_ARCHITECTURE????7???// Architecture Specific Data #define IMAGE_DIRECTORY_ENTRY_GLOBALPTR???????8???// RVA of GP #define IMAGE_DIRECTORY_ENTRY_TLS?????????????9???// TLS Directory #define IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG????10???// Load Configuration Directory #define IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT???11???// Bound Import Directory in headers #define IMAGE_DIRECTORY_ENTRY_IAT????????????12???// Import Address Table #define IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT???13???// Delay Load Import Descriptors #define IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR 14???// COM Runtime descriptor
|
對于我們,如果你想觀察相對虛擬內存地址和數據尺寸,下面的代碼是足夠的:
Code:
DWORD dwRVA
=
image_nt_headers
.
OptionalHeader
-> ??
DataDirectory
[
IMAGE_DIRECTORY_ENTRY_RESOURCE
]->
VirtualAddress
;
DWORD dwSize
=
image_nt_headers
.
OptionalHeader
-> ??
DataDirectory
[
IMAGE_DIRECTORY_ENTRY_RESOURCE
]->
Size
;
|
2.3 The Section Headers and Sections(節頭和節)
現在我們觀察PE文件怎樣聲明節的位置和尺寸。為了更好的理解節頭(Section header)和節區的特征,我建議你去看看IMAGE_SECTION_HEADER在MSDN定義的結構,對于一個EXE packer的開發者VirtualSize, VirtualAddress, SizeOfRawData, PointerToRawData和Characteristics單元有著嚴格的規則。當開發一個exe packer,你應該非常了解他們。當你修改他們時,有許多你要注意,你要按OptionalHeader->SectionAlignment的順序小心排列VirtualSize和VirtualAddress同樣SizeOfRawData和PointerToRawData要按OptionalHeader->FileAlignment的順序排列。否則你將損壞目標EXE文件,并且不能運行他們。至于Characteristics,更要格外小心,你要按IMAGE_SCN_MEM_READ | IMAGE_SCN_MEM_WRITE | IMAGE_SCN_CNT_INITIALIZED_DATA的順序建立一個節區。我更喜歡我的新節區在進程運行時有初始化這些數據的能力。當然引入表也要如此。此外,我還需要他能利用loader修改他自己。同樣你要考慮好新的節區名,這樣你能僅通過名字就知道他們的作用。如表二
表二

為了更好的理解節頭和節區,請運行PE viewer。借助于他,你能了解一個文件映像中節頭的應用。要想觀察到虛擬內存,你應該用調試器載入一個PE文件。請記住無論什么時候在PE文件中刪除或添加節區,注意調整PE文件的NumberOfSections( IMAGE_NT_HEADERS-> FileHeader->NumberOfSections)他說明了節區的數目。
3 DEBUGER、DISASSEMBLER和一些有用的工具
3.1 DEBUGER
要想變成一個PE工具的開發者,對BUG跟蹤程序的使用經驗是一個必備條件。此外,你還不得知道一些必備的匯編指令,對于我們INTEL公司的文檔是最好的參考書,你可以到INTEL的網站獲得他們。
IA-32 Intel Architecture Software Developer’s Manuals.
Intel Itanium Architecture Assembly Language Reference Guide.
The Intel Itanium Processor Developer Resource Guide.
對于PE文件的追蹤,我認為SOFTICE是最好的調試工具。借助與內核模式我們可以進行進程追蹤而無須應用API函數另外,我還要介紹在一個調試器,他在USER模式下使用有很好的效果,他利用API函數跟蹤PE文件,并把自身進程依附在一個活躍的進程中。在WINDOWS內核的庫中,微軟提供了這些API函數,為了跟蹤特定的進程,你要使用調試工具。一些重要的API函數包括 CreateThread(), CreateProcess(), OpenProcess(), DebugActiveProcess(), GetThreadContext(), SetThreadContext(), ContinueDebugEvent(), DebugBreak(), ReadProcessMemory(), WriteProcessMemory(), SuspendThread(), and ResumeThread().
3.1.2調試器的那些部分是重要的 (偶省掉了一些沒用的部分)
前面我介紹了兩個調試器,但是我并沒有說明怎樣使用他們。我建議你去看他們的HELP文檔,但我還是想簡要的說明一些重要的部分。
1。Registers viewer.
Code:
EAX ECX EDX EBX ESP EBP ESI EDI EIP o d t s z a p c
|
2。Disassembler or Code viewer
Code:
010119E0 PUSH EBP 010119E1 MOV EBP
,
ESP 010119E3 PUSH
-
1 010119E5 PUSH 01001570 010119EA PUSH 01011D60 010119EF MOV EAX
,
DWORD PTR FS
:[
0
]
010119F5 PUSH EAX 010119F6 MOV DWORD PTR FS
:[
0
],
ESP 010119FD ADD ESP
,-
68 01011A00 PUSH EBX 01011A01 PUSH ESI 01011A02 PUSH EDI 01011A03 MOV DWORD PTR SS
:[
EBP
-
18
],
ESP 01011A06 MOV DWORD PTR SS
:[
EBP
-
4
],
0
|
3.Memory watcher.
Code:
0023
:
01013000 00 00 00 00 00 00 00 00
-
00 00 00 00 00 00 00 00
................
0023
:
01013010 01 00 00 00 20 00 00 00
-
0A 00 00 00 0A 00 00 00
................
0023
:
01013020 20 00 00 00 00 00 00 00
-
53 63 69 43 61 6C 63 00
........
SciCalc
.
0023
:
01013030 00 00 00 00 00 00 00 00
-
62 61 63 6B 67 72 6F 75
........
backgrou 0023
:
01013040 6E 64 00 00 00 00 00 00
-
2E 00 00 00 00 00 00 00 nd
..............
|
4.Stack viewer.
Code:
0010
:
0007FFC4 4F 6D 81 7C 38 07 91 7C
-
FF FF FF FF 00 90 FD 7F Om
|
8 ‘
| .
0010
:
0007FFD4 ED A6 54 80 C8 FF 07 00
-
E8 B4 F5 81 FF FF FF FF T
.
0010
:
0007FFE4 F3 99 83 7C 58 6D 81 7C
-
00 00 00 00 00 00 00 00 Xm
|........
0010
:
0007FFF4 00 00 00 00 E0 19 01 01
-
00 00 00 00 00 00 00 00
.... ....
|
5.Command line, command buttons, or shortcut keys to follow the debugging process.
Code:
Command SoftICE OllyDbg Run F5 F9 Step Into F11 F7 Step Over F10 F8 Set
Break
Point F8 F2
|
3.2一些有用的工具
一個好的PE開發者要熟悉一些工具來節省他們的時間,所以我替大家選擇了一些有用的工具,來分析可執行文件。
3.2.1 LordPE
LordPE仍然是獲得PE文件信息并修改他們的首選工具。

3.3.2 PEiD
PEiD對于分析編譯器,殼種類,入口點來說是一款很好的工具。到現在為止,他能偵測500種以上的PE文
件類型。
3.3.3 Resource Hacker
他能用來修改一些資源數據信息,如圖標,菜單,版本等等。

3.3.4 WinHex

3.3.5 CFF Explorer
他支持PE32/64,PE重建包括CIL文件,換句話說就是.NET文件。他使用更方便,功能更強大。

4添加新的節區并改變EPO
我們已經完成了工作的第一步。所以我添加了一個新的節區,并重建了PE文件。在開始前我想讓你借助OD熟悉PE頭。用OD載入文件,選擇View->Executable file,Special->PE header,你將看到類似圖3的畫面。現在在主菜單選擇View->Memory,試著在內存映射窗口區別各個節區。


我想向你解釋,我們怎樣改變舉例文件中的入口點偏移量(CALC.EXE)。首先,使用PE工具,找到入口點 0x00012475,并且映像基地址為 0x01000000,這個OEP是虛擬地址的相對地址,所以映像基地址來轉變把他為虛擬地址。
Code:
Virtual_Address = Image_Base + Relative_Virtual_Address
DWORD OEP_RVA = image_nt_headers->OptionalHeader.AddressOfEntryPoint ; // OEP_RVA = 0x00012475 DWORD OEP_VA = image_nt_headers->OptionalHeader.ImageBase + OEP_RVA ; // OEP_VA = 0x01000000 + 0x00012475 = 0x01012475
|
PE Maker - 1
Download test PE file - 49.5 Kb
in loader.cpp文件中的DynLoader()函數用來保存新的節的數據
Code:
__stdcall void DynLoader() { _asm { //---------------------------------- ????DWORD_TYPE(DYN_LOADER_START_MAGIC) //---------------------------------- ????MOV EAX,01012475h // << Original OEP ????JMP EAX //---------------------------------- ????DWORD_TYPE(DYN_LOADER_END_MAGIC) //---------------------------------- } }
|
不幸的是這個源代碼只能在sample test file. 中應用,我們應該借助于在新的節區保存前面的OEP的值,來完善他,并使用他到達真正的OEP。我會在在 Step 2 完善他
4.1找回并重建PE文件
為了使用新的PE文件,我寫了一個簡單的類庫文件,來修復PE信息。
Code:
CPELibrary Class Step 1 ---------------------------------------------------------------- class CPELibrary { private: ????//----------------------------------------- ????PCHAR???????????????????pMem; ????DWORD???????????????????dwFileSize; ????//----------------------------------------- protected: ????//----------------------------------------- ????PIMAGE_DOS_HEADER???????image_dos_header; ????PCHAR???????????????????pDosStub; ????DWORD???????????????????dwDosStubSize, dwDosStubOffset; ????PIMAGE_NT_HEADERS???????image_nt_headers; ????PIMAGE_SECTION_HEADER???image_section_header[MAX_SECTION_NUM]; ????PCHAR???????????????????image_section[MAX_SECTION_NUM]; ????//----------------------------------------- protected: ????//----------------------------------------- ????DWORD PEAlign(DWORD dwTarNum,DWORD dwAlignTo); ????void AlignmentSections(); ????//----------------------------------------- ????DWORD Offset2RVA(DWORD dwRO); ????DWORD RVA2Offset(DWORD dwRVA); ????//----------------------------------------- ????PIMAGE_SECTION_HEADER ImageRVA2Section(DWORD dwRVA); ????PIMAGE_SECTION_HEADER ImageOffset2Section(DWORD dwRO); ????//----------------------------------------- ????DWORD ImageOffset2SectionNum(DWORD dwRVA); ????PIMAGE_SECTION_HEADER AddNewSection(char* szName,DWORD dwSize); ????//----------------------------------------- public: ????//----------------------------------------- ????CPELibrary(); ????~CPELibrary(); ????//----------------------------------------- ????void OpenFile(char* FileName); ????void SaveFile(char* FileName);???? ????//----------------------------------------- };
|
借助于表1,image_dos_header, pDosStub, image_nt_headers, image_section_header [MAX_SECTION_NUM], 和image_section[MAX_SECTION_NUM] 的用法是很清晰的。我們使用OpenFile()和SaveFile()來重新得到并重建PE文件此外,一個重要的階段是使用AddNewSection()來創造新的節區。
4.2為新的節區添加數據
在pecrypt.cpp中,我構建了另一個類,CPECryptor來包含新節區的數據。新節區的數據由loader.cpp文件中的DynLoader()函數構建。DynLoader Step 1.我們也使用CPECryptor類來填充其他資料。
Code:
CPECryptor Class Step 1 ---------------------------------------------------------------- class CPECryptor: public CPELibrary { private: ????//---------------------------------------- ????PCHAR pNewSection; ????//---------------------------------------- ????DWORD GetFunctionVA(void* FuncName); ????void* ReturnToBytePtr(void* FuncName, DWORD findstr); ????//---------------------------------------- protected: ????//---------------------------------------- public:???? ????//---------------------------------------- ????void CryptFile(int(__cdecl *callback) (unsigned int, unsigned int)); ????//---------------------------------------- }; //---------------------------------------------------------------- |
4.3一些關于構建新的PE文件的說明
用一下片斷來排列虛擬地址和每個節區的虛擬大小
Code:
image_section_header[i]->VirtualAddress= ????PEAlign(image_section_header[i]->VirtualAddress, ????image_nt_headers->OptionalHeader.SectionAlignment);
image_section_header[i]->Misc.VirtualSize= ????PEAlign(image_section_header[i]->Misc.VirtualSize, ????image_nt_headers->OptionalHeader.SectionAlignment);
|
Align the PointerToRawData and the SizeOfRawData of each section by FileAlignment
Code:
image_section_header[i]->PointerToRawData = ????PEAlign(image_section_header[i]->PointerToRawData, ????????????image_nt_headers->OptionalHeader.FileAlignment);
image_section_header[i]->SizeOfRawData = ????PEAlign(image_section_header[i]->SizeOfRawData, ????????????image_nt_headers->OptionalHeader.FileAlignment);
|
Correct the SizeofImage by the virtual size and the virtual address of the last section
Code:
image_nt_headers->OptionalHeader.SizeOfImage = ??????????image_section_header[LastSection]->VirtualAddress + ??????????image_section_header[LastSection]->Misc.VirtualSize;
|
Set the Bound Import Directory header to zero, as this directory is not very important to execute a PE file:
Code:
image_nt_headers-> ??OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT]. ??VirtualAddress = 0; image_nt_headers-> ??OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT].Size = 0;
|
4.4一些關于連接這個VC程序的解釋
Set Linker->General->Enable Incremental Linking to No (/INCREMENTAL:NO).

你能了解增加連接和不增加連接之間的不同

為了獲得DynLoader(), 的虛擬地址,再增加LINK中我們獲得JMP pemaker.DynLoader 的虛擬地址。但是不使用LINK時用以下代碼獲得地址
Code:
DWORD dwVA= (DWORD) DynLoader;
|
This setting is more critical in the incremental link when you try to find the beginning and ending of the Loader,
DynLoader(), by CPECryptor::ReturnToBytePtr():(抱歉,這句話偶不知道如何才能翻譯通順

)
Code:
void* CPECryptor::ReturnToBytePtr(void* FuncName, DWORD findstr) { ????void* tmpd; ????__asm ???{ ????????mov eax, FuncName ????????jmp df hjg:????inc eax df:?????mov ebx, [eax] ????????cmp ebx, findstr ????????jnz hjg ????????mov tmpd, eax ????} ????return tmpd; }
|
5.保存重要的數據和延伸原入口點現在,我們已經保存了原入口點,并映射了基地址以便于到達虛擬地址入口點。在DynLoader()結尾處我已經保存一個空白區來儲存這些重要的數據。DynLoader Step 2.
PE Maker - Step 2Download source files - 58.3 Kb Code:
DynLoaderStep2>DynLoader Step 2
__stdcall void DynLoader() { _asm { //---------------------------------- ????DWORD_TYPE(DYN_LOADER_START_MAGIC) //---------------------------------- Main_0: ????PUSHAD ????// get base ebp ????CALL Main_1 Main_1:???? ????POP EBP ????SUB EBP,OFFSET Main_1 ????MOV EAX,DWORD PTR [EBP+_RO_dwImageBase] ????ADD EAX,DWORD PTR [EBP+_RO_dwOrgEntryPoint] ????PUSH EAX ????RETN // >> JMP to Original OEP //---------------------------------- ????DWORD_TYPE(DYN_LOADER_START_DATA1) //----------------------------------<FONT color=red> _RO_dwImageBase:????????????????DWORD_TYPE(0xCCCCCCCC) _RO_dwOrgEntryPoint:????????????DWORD_TYPE(0xCCCCCCCC)</FONT> //---------------------------------- ????DWORD_TYPE(DYN_LOADER_END_MAGIC) //---------------------------------- } }
|
5.1恢復開始時寄存器間的關系
恢復他們間的關系是重要的,但在DynLoader Step 2的源代碼中我們還沒有做這件事。我們可以修改DynLoader() 函數來重建開始的關系
Code:
__stdcall void DynLoader() { _asm { //---------------------------------- ????DWORD_TYPE(DYN_LOADER_START_MAGIC) //---------------------------------- Main_0: ????<FONT color=red>PUSHAD// Save the registers context in stack</FONT> ????CALL Main_1 Main_1:???? ????POP EBP// Get Base EBP ????SUB EBP,OFFSET Main_1 ????MOV EAX,DWORD PTR [EBP+_RO_dwImageBase] ????ADD EAX,DWORD PTR [EBP+_RO_dwOrgEntryPoint] ????MOV DWORD PTR [ESP+1Ch],EAX // pStack.Eax <- EAX ????<FONT color=red>POPAD // Restore the first registers context from stack</FONT> ????PUSH EAX ????XOR??EAX, EAX ????RETN // >> JMP to Original OEP //---------------------------------- ????DWORD_TYPE(DYN_LOADER_START_DATA1) //---------------------------------- _RO_dwImageBase:????????????????DWORD_TYPE(0xCCCCCCCC) _RO_dwOrgEntryPoint:????????????DWORD_TYPE(0xCCCCCCCC) //---------------------------------- ????DWORD_TYPE(DYN_LOADER_END_MAGIC) //---------------------------------- } }
|
5.2恢復最初的堆棧
我們能利用設置在堆棧開始處的值加0x34到原入口點,來恢復原始的堆棧,但這不是很重要。然而在下面的代碼中我用了一個簡單的技巧來到達OEP以便于修改堆棧,你能利用OD來跟蹤執行過程。
Code:
__stdcall void DynLoader() { _asm { //---------------------------------- ????DWORD_TYPE(DYN_LOADER_START_MAGIC) //---------------------------------- Main_0: ????PUSHAD // Save the registers context in stack ????CALL Main_1 Main_1:???? ????POP EBP ????SUB EBP,OFFSET Main_1 ????MOV EAX,DWORD PTR [EBP+_RO_dwImageBase] ????ADD EAX,DWORD PTR [EBP+_RO_dwOrgEntryPoint] ????MOV DWORD PTR [ESP+54h],EAX // pStack.Eip <- EAX ????POPAD // Restore the first registers context from stack ????CALL _OEP_Jump ????DWORD_TYPE(0xCCCCCCCC) _OEP_Jump: ????PUSH EBP ????MOV EBP,ESP ????MOV EAX,DWORD PTR [ESP+3Ch] // EAX <- pStack.Eip ????MOV DWORD PTR [ESP+4h],EAX??// _OEP_Jump RETURN pointer <- EAX ????XOR EAX,EAX ????LEAVE ????RETN //---------------------------------- ????DWORD_TYPE(DYN_LOADER_START_DATA1) //---------------------------------- _RO_dwImageBase:????????????????DWORD_TYPE(0xCCCCCCCC) _RO_dwOrgEntryPoint:????????????DWORD_TYPE(0xCCCCCCCC) //---------------------------------- ????DWORD_TYPE(DYN_LOADER_END_MAGIC) //---------------------------------- } }
|
5.3Approach OEP by Structured Exception Handling(借助于構造異常處理來接近OEP)
當程序執行了錯誤的代碼就會拋出一個異常,在這種條件下,程序迅速跳轉到一個叫做異常處理的功能中。
下面的文件包含了異常處理的調用,異常的拋出,以及其功能。
Code:
#include "stdafx.h" #include "windows.h"
void RAISE_AN_EXCEPTION() {???? _asm { ????INT 3 ????INT 3 ????INT 3 ????INT 3 } }
int _tmain(int argc, _TCHAR* argv[]) { ????__try ????{ ????????__try{ ????????????printf("1: Raise an Exception\n"); ????????????RAISE_AN_EXCEPTION(); ????????} ????????__finally ????????{ ????????????printf("2: In Finally\n"); ????????} ????} ????__except( printf("3: In Filter\n"), EXCEPTION_EXECUTE_HANDLER ) ????{ ????????printf("4: In Exception Handler\n"); ????} ????return 0; }
|
;Code:
main() 00401000: PUSH EBP 00401001: MOV EBP,ESP 00401003: PUSH -1 00401005: PUSH 00407160 ; __try { ; the structured exception handler (SEH) installation 0040100A: PUSH _except_handler3?? 0040100F: MOV EAX,DWORD PTR FS:[0] 00401015: PUSH EAX 00401016: MOV DWORD PTR FS:[0],ESP 0040101D: SUB ESP,8 00401020: PUSH EBX 00401021: PUSH ESI 00401022: PUSH EDI 00401023: MOV DWORD PTR SS:[EBP-18],ESP ;?????__try { 00401026: XOR ESI,ESI 00401028: MOV DWORD PTR SS:[EBP-4],ESI 0040102B: MOV DWORD PTR SS:[EBP-4],1 00401032: PUSH OFFSET "1: Raise an Exception" 00401037: CALL printf 0040103C: ADD ESP,4 ; the raise a exception, INT 3 exception ; RAISE_AN_EXCEPTION() 0040103F: INT3?????? 00401040: INT3 00401041: INT3 00401042: INT3 ;?????} __finally { 00401043: MOV DWORD PTR SS:[EBP-4],ESI 00401046: CALL 0040104D 0040104B: JMP 00401080 0040104D: PUSH OFFSET "2: In Finally" 00401052: CALL printf 00401057: ADD ESP,4 0040105A: RETN <FONT color=black>;?????} <FONT color=black>; } <FONT color=black>; __except( 0040105B: JMP 00401080 0040105D: PUSH OFFSET "3: In Filter" 00401062: CALL printf 00401067: ADD ESP,4 0040106A: MOV EAX,1 ; EXCEPTION_EXECUTE_HANDLER = 1 0040106F: RETN ;?????, EXCEPTION_EXECUTE_HANDLER ) ; } ; the exception handler funtion 00401070: MOV ESP,DWORD PTR SS:[EBP-18] 00401073: PUSH OFFSET "4: In Exception Handler" 00401078: CALL printf 0040107D: ADD ESP,4 ; } 00401080: MOV DWORD PTR SS:[EBP-4],-1 0040108C: XOR EAX,EAX ; restore previous SEH 0040108E: MOV ECX,DWORD PTR SS:[EBP-10] 00401091: MOV DWORD PTR FS:[0],ECX 00401098: POP EDI 00401099: POP ESI 0040109A: POP EBX 0040109B: MOV ESP,EBP 0040109D: POP EBP 0040109E: RETN
|
建立一個控制臺工程,連接并執行先前的文件,觀察結果。
Code:
1: Raise an Exception 3: In Filter 2: In Finally 4: In Exception Handler _
|
程序執行了一個異常的表達式printf("3: In Filter\n");,當異常發生,(在這個例子INT 3 異常,你也能用另外的異常),在OD中Debugging options->Exceptions你能看到不同類型異常的清單

5.3.1 Implement Exception Handler
我們希望建立一個異常處理來到達程序的OEP,借助前滿的代碼,你現在已經能區別SEH安裝,異常拋出,異常表達式過濾,為了建立我們的異常接近(OEP),我們需要下面的代碼
SEH installation
Code:
LEA EAX,[EBP+_except_handler1_OEP_Jump] ????PUSH EAX ????PUSH DWORD PTR FS:[0] ????MOV DWORD PTR FS:[0],ESP
|
異常拋出
Code:
異常表達式過濾
Code:
except_handler1_OEP_Jump: ????PUSH EBP ????MOV EBP,ESP ????... ????MOV EAX, EXCEPTION_CONTINUE_SEARCH // EXCEPTION_CONTINUE_SEARCH = 0 ????LEAVE ????RETN</
|
所以我們利用ASM嵌套來到達OEP
Code:
__try // SEH installation { ????__asm ????{ ????????INT 3 // An Exception Raise ????} } __except( ..., EXCEPTION_CONTINUE_SEARCH ){} // Exception handler expression filter
|
ASM代碼
Code:
;---------------------------------------------------- ????; the structured exception handler (SEH) installation ????; __try { ????LEA EAX,[EBP+_except_handler1_OEP_Jump] ????PUSH EAX ????PUSH DWORD PTR FS:[0] ????MOV DWORD PTR FS:[0],ESP ????<FONT color=green>; ---------------------------------------------------- ????; the raise a INT 3 exception ????INT 3 ????INT 3 ????INT 3 ????INT 3 ????; } ????; __except( ... ????; ---------------------------------------------------- ????; exception handler expression filter _except_handler1_OEP_Jump: ????PUSH EBP ????MOV EBP,ESP ????... ????MOV EAX, EXCEPTION_CONTINUE_SEARCH ; EXCEPTION_CONTINUE_SEARCH = 0 ????LEAVE ????RETN ???;, EXCEPTION_CONTINUE_SEARCH ) { }
|
異常值__except(..., Value), 定義了這個異常如何被處理,他能由三個值分別是1,0,-1。為了理解他們請在MSDN Library中尋找有關
try-except 聲明的描述。當我們把他設置為0時,就不會執行異常處理的功能,因此,借助于這個值,異常處理就會被忽略,并且線程繼續執行其他的代碼。
如何安裝SEH就像你從前面的代碼中看到的,SEH是借助于FS段寄存器還完成安裝的。WIN32使用FS段寄存器作為主線程中數據塊的指針。開始的0X1C字節包含Thread Information Block (TIB)的信息,此外,FS:[00h] 在與主線程的ExceptionList 有關(表三),在我們的代碼中,我們已經將_except_handler1_OEP_Jump指針壓入堆棧,并改變ExceptionList, FS:[00h]的值為堆棧起始的值,ESP線程信息塊(TIB)
Code:
typedef struct _NT_TIB32 { ????DWORD ExceptionList; ????DWORD StackBase; ????DWORD StackLimit; ????DWORD SubSystemTib; ????union { ????????DWORD FiberData; ????????DWORD Version; ????}; ????DWORD ArbitraryUserPointer; ????DWORD Self; } NT_TIB32, *PNT_TIB32;
|
表三 FS段寄存器和線程信息塊
5.3.2借助于調整線程間的關系獲得OEP在這部分,我們將完成OEP的接近,并改變線程間的關系,忽略每個簡單的異常處理。讓線程在先前的OEP中繼續執行代碼。當產生異常時,進程間的關系就被保存在堆棧中,借助EXCEPTION_POINTERS,我們能訪問ContextRecord的指針。ContextRecord擁有CONTEXT的數據結構(表四),這便是在執行時間中的線程間的關系。當我們使用 EXCEPTION_CONTINUE_SEARCH (0)來忽略異常時,指令指針間的關系也被設置到ContextRecord,以便程序可以返回先前的環境中去。因此,如果我們改變 Win32 Thread Context中的EIP,使其為開始的OEP,這便可以進入OEP。
表四


Code:
MOV EAX, ContextRecord MOV EDI, dwOEP???????????????????; EAX <- dwOEP MOV DWORD PTR DS:[EAX+0B8h], EDI ; pContext.Eip <- EAX
|
WIN32線程間關系的數據結構Code:
#define MAXIMUM_SUPPORTED_EXTENSION?????512
typedef struct _CONTEXT { ????//----------------------------------------- ????DWORD ContextFlags; ????//----------------------------------------- ????DWORD???Dr0; ????DWORD???Dr1; ????DWORD???Dr2; ????DWORD???Dr3; ????DWORD???Dr6; ????DWORD???Dr7; ????//----------------------------------------- ????FLOATING_SAVE_AREA FloatSave; ????//----------------------------------------- ????DWORD???SegGs; ????DWORD???SegFs; ????DWORD???SegEs; ????DWORD???SegDs; ????//----------------------------------------- ????DWORD???Edi; ????DWORD???Esi; ????DWORD???Ebx; ????DWORD???Edx; ????DWORD???Ecx; ????DWORD???Eax; ????//----------------------------------------- ????DWORD???Ebp; ????DWORD???Eip; ????DWORD???SegCs; ????DWORD???EFlags; ????DWORD???Esp; ????DWORD???SegSs; ????//----------------------------------------- ????BYTE????ExtendedRegisters[MAXIMUM_SUPPORTED_EXTENSION]; ????//---------------------------------------- } CONTEXT, *LPCONTEXT;
|
用下面的代碼,我們就能使用異常處理程序來到達OEP
Code:
__stdcall void DynLoader() { _asm { //---------------------------------- ????DWORD_TYPE(DYN_LOADER_START_MAGIC) //---------------------------------- Main_0: ????PUSHAD??// Save the registers context in stack ????CALL Main_1 Main_1:???? ????POP EBP ????SUB EBP,OFFSET Main_1 // Get Base EBP ????MOV EAX,DWORD PTR [EBP+_RO_dwImageBase] ????ADD EAX,DWORD PTR [EBP+_RO_dwOrgEntryPoint] ????MOV DWORD PTR [ESP+10h],EAX????// pStack.Ebx <- EAX ????LEA EAX,[EBP+_except_handler1_OEP_Jump] ????MOV DWORD PTR [ESP+1Ch],EAX????// pStack.Eax <- EAX ????POPAD??// Restore the first registers context from stack ????//---------------------------------------------------- ????// the structured exception handler (SEH) installation ????PUSH EAX ????XOR??EAX, EAX ????PUSH DWORD PTR FS:[0]????????// NT_TIB32.ExceptionList ????MOV DWORD PTR FS:[0],ESP????// NT_TIB32.ExceptionList <-ESP ????//---------------------------------------------------- ????// the raise a INT 3 exception ????DWORD_TYPE(0xCCCCCCCC) ????//-------------------------------------------------------- // -------- exception handler expression filter ---------- _except_handler1_OEP_Jump: ????PUSH EBP ????MOV EBP,ESP ????//------------------------------ ????MOV EAX,DWORD PTR SS:[EBP+010h]????// PCONTEXT: pContext <- EAX ????//============================== ????PUSH EDI ????// restore original SEH ????MOV EDI,DWORD PTR DS:[EAX+0C4h]????// pContext.Esp ????PUSH DWORD PTR DS:[EDI] ????POP DWORD PTR FS:[0] ????ADD DWORD PTR DS:[EAX+0C4h],8????// pContext.Esp ????//------------------------------ ????// set the Eip to the OEP ????MOV EDI,DWORD PTR DS:[EAX+0A4h] // EAX <- pContext.Ebx ????MOV DWORD PTR DS:[EAX+0B8h],EDI // pContext.Eip <- EAX ????//------------------------------ ????POP EDI ????//============================== ????MOV EAX, EXCEPTION_CONTINUE_SEARCH ????LEAVE ????RETN //---------------------------------- ????DWORD_TYPE(DYN_LOADER_START_DATA1) //---------------------------------- _RO_dwImageBase:????????????????DWORD_TYPE(0xCCCCCCCC) _RO_dwOrgEntryPoint:????????????DWORD_TYPE(0xCCCCCCCC) //---------------------------------- ????DWORD_TYPE(DYN_LOADER_END_MAGIC) //---------------------------------- } }
|
6.構建一個引入表,并重建開始的引入表我們有兩種方法在應用程序中使用DLL文件:
.通過額外的支持使用庫文件
.在運行時間中使用DLL文件Code:
// DLL function signature typedef HGLOBAL (*importFunction_GlobalAlloc)(UINT, SIZE_T); ... importFunction_GlobalAlloc __GlobalAlloc;
// Load DLL file HINSTANCE hinstLib = LoadLibrary("Kernel32.dll"); if (hinstLib == NULL) { ????// Error - unable to load DLL }
// Get function pointer __GlobalAlloc = ????(importFunction_GlobalAlloc)GetProcAddress(hinstLib,?? ?????????????????????????????????????????"GlobalAlloc"); if (addNumbers == NULL) { ?????// Error - unable to find DLL function }
FreeLibrary(hinstLib);
|
當你建立一個WINDOWS應用程序的工程,連接器至少包括kernel32.dll,沒有kernel32.dll文件中的LoadLibrary()和GetProcAddress()函數我們就不能在運行時間中裝載DLL文件,所依賴的信息被儲存在引入表的節區內。如果利用Dependency Walker,我們將很容易的觀察DLL模塊,以及那些被引入PE文件的重要功能。

我打算建立我們自定義的引入表來管理我們的工程,我們必須修改原來的引入表,以便于運行程序中真正的代碼。
PE Maker - Step 3Download source files - 65.4 Kb
6.1構件客戶端引入表我建議你讀一下6.4節
Microsoft Portable Executable and the Common Object File Format Specification 文擋,這節包含了重要的知識來讓你了解引入表的性能。利用optional header 中的第二頁數據,便可得到引入表數據。因此,你能用下面的代碼獲得他
Code:
DWORD dwVirtualAddress = image_nt_headers-> ??OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress; DWORD dwSize = image_nt_headers-> ??OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].Size;
|
The VirtualAddress refers to structures by IMAGE_IMPORT_DESCRIPTOR. This structure contains the pointer to the imported DLL name and the relative virtual address of the first thunk(抱歉,THUNK我不知道該如何翻譯,所以我無法理解這段的含義

)
Code:
typedef struct _IMAGE_IMPORT_DESCRIPTOR { ????union { ????????DWORD???Characteristics; ????????DWORD???OriginalFirstThunk; ????}; ????DWORD???TimeDateStamp; ????DWORD???ForwarderChain; ????DWORD???<FONT color=red>Name</FONT>;?????????// the imported DLL name ????DWORD???<FONT color=red>FirstThunk</FONT>;???// the relative virtual address of the first thunk } IMAGE_IMPORT_DESCRIPTOR, *PIMAGE_IMPORT_DESCRIPTOR;
|
When a program is running, the Windows task manager sets the thunks by the virtual address of the function. The virtual address is found by the name of the function. At first, the thunks hold the relative virtual address of the function name, Table 5; during execution, they are fixed up by the virtual address of the functions, Table 6.
表五--在文件映像中的引入表
表六--在虛擬內存中的引入表
我想制作一個簡單的引入表來引入Kernel32.dll中的LoadLibrary(),和 GetProcAddress() 函數,在運行時間中我要用這兩個基本的API函數來覆蓋其他的API函數。下面的ASM代碼將展示實現他是多么的容易。
運行后.....
6我已經準備了一個類庫來制作每個引入表,這將借助于字符串。CITMaker類庫在itmaker.h中,他將使用sz_IT_EXE_strings 來建立一個引入表,以及一個相對虛擬地址引入表。
Code:
static const char *sz_IT_EXE_strings[]= { ????"Kernel32.dll", ????"LoadLibraryA", ????"GetProcAddress", ????0,, ????0, };
|
下面我使用這個類庫來建立引入表以支持DLL和OCX,所以這是一個常規庫來表現所有可能的引入表。下個一步驟,我用下面的代碼來完成。

6.2在運行時間內使用其他的API函數
在這次,我們使用LoadLibrary() 和GetProcAddress()來裝載其他的DLL文件,找出其他的API函數的進程地址。

我想有一個完善的被引入函數表,類似于一個真正執行中的EXE文件,如果你看一下一個PE文件的內部,你將發現API的調用是API函數在虛擬地址間的間接跳轉完成的。
JMP DWORD PTR [XXXXXXXX]

借助于這個性能我們可以很容易的擴展我們工程的其他部分 ,因此我們建立兩個數據表,一個是API函數,另一個是JMP [XXXXXXXX]
Code:
#define __jmp_api???????????????byte_type(0xFF) byte_type(0x25) __asm { ... //---------------------------------------------------------------- _p_GetModuleHandle:?????????????dword_type(0xCCCCCCCC) _p_VirtualProtect:??????????????dword_type(0xCCCCCCCC) _p_GetModuleFileName:???????????dword_type(0xCCCCCCCC) _p_CreateFile:??????????????????dword_type(0xCCCCCCCC) _p_GlobalAlloc:?????????????????dword_type(0xCCCCCCCC) //---------------------------------------------------------------- _jmp_GetModuleHandle:???????????__jmp_api???dword_type(0xCCCCCCCC) _jmp_VirtualProtect:????????????__jmp_api???dword_type(0xCCCCCCCC) _jmp_GetModuleFileName:?????????__jmp_api???dword_type(0xCCCCCCCC) _jmp_CreateFile:????????????????__jmp_api???dword_type(0xCCCCCCCC) _jmp_GlobalAlloc:???????????????__jmp_api???dword_type(0xCCCCCCCC) //---------------------------------------------------------------- ... }
|
下面的代碼我們來安裝自定義的引入表

6.3修復最初的引入表
In order to run the program again, we should fix up the thunks of the actual import table, otherwise we have a corrupted target PE file. Our code must correct all of the thunks the same as Table 5 to Table 6. Once more, LoadLibrary() and GetProcAddress() aid us in our effort to reach our intention.(8會翻譯,抱歉)


7.支持DLL和OCX下面我將把DLL和OCX加入我們的PE工程中,如果我們注意到兩次到達入口點的偏移地址,客戶引入表,重新調整后的表的話,要支持他們是很容易的事。
PE Maker - Step 4Download source files - 68.6 Kb7.1 Twice OEP approachDLL或著OCX文件的入口點偏移最少要被主程序接觸兩次:
.構造[B]
當一個DLL文件被LoadLibrary()函數裝載,或者通過調用 DllRegisterServer()函數使用LoadLibaray()和GetProcAddress() 函數對一個OCX文件注冊,第一次到達OEP就完成了。
Code:
hinstDLL = LoadLibrary( "test1.dll" );
|
Code:
hinstOCX = LoadLibrary( "test1.ocx" ); _DllRegisterServer = GetProcAddress( hinstOCX, "DllRegisterServer" ); _DllRegisterServer(); // ocx register
|
[B].Destructor(破壞)當主程序使用FreeLibrary()函數釋放庫文件,就會第二次到達OEP
Code:
Code:
To perform this, I have employed a trick, that causes in the second time again, the instruction pointer (EIP) traveling owards the original OEP by the structured exception handler.(抱歉8會翻譯)

我希望在前面的代碼中你已經掌握了那個技巧,但是那還不夠,我們還要考慮映像基址(ImageBase)的問題,當庫文件被主程序載入不同映像基址我們應該寫一些代碼來找到真正的映像基址,記住他并在將來利用他。

借助于觀察堆棧信息,這個代碼找到真正的映像基址。通過使用真正的和形式上的映像基址映像基址,我們要修正程序內部所有的內存調用,不要對此擔心,通過重新調整表的信息就可以輕松的完成。
7.2 對表進行重新調整為了更好的了解重置表(relocation table ),你應該看一看6.6節中
Microsoft Portable Executable and Common Object File Format Specification 文檔的內容。重置表包含許多package來包含在虛擬內存映像中虛擬地址的相關信息。每一個package包含一個8bit的報頭以顯示虛擬地址基址和數據的數量。下面我用IMAGE_BASE_RELOCATION的數據結構來說明。
Code:
typedef struct _IMAGE_BASE_RELOCATION { ????DWORD???VirtualAddress; ????DWORD???SizeOfBlock; } IMAGE_BASE_RELOCATION, *PIMAGE_BASE_RELOCATION;
|
表七
表七 表現了重置表的主要概念。此外你能在OD中裝載一個DLL或OCX文件來觀察重置表,在內存映射窗口中便可觀察到".reloc" 節區。使用我們工程中的下面代碼,你能找到重置表的位置。
Code:
DWORD dwVirtualAddress = image_nt_headers-> ??OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC]. ??VirtualAddress; DWORD dwSize = image_nt_headers-> ??OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].Size;
|
借助于OD的Long Hex viewer模式,我們可以看到,在這個例子中虛擬內存基址是0x1000,塊大小是0x184。
Code:
008E1000 : 00001000??00000184??30163000??30403028 008E1010 : 30683054??308C3080??30AC309C??30D830CC 008E1020 : 30E030DC??30E830E4??30F030EC??310030F4 008E1030 : 3120310D??315F3150??31A431A0??31C031A8 008E1040 : 31D031CC??31F431EC??31FC31F8??32043200 008E1050 : 320C3208??32143210??324C322C??32583254 008E1060 : 3260325C??32683264??3270326C??32B03274
|
他重新安排了后面的虛擬地址:
Code:
0x1000 + 0x0000 = 0x1000 0x1000 + 0x0016 = 0x1016 0x1000 + 0x0028 = 0x1028 0x1000 + 0x0040 = 0x1040 0x1000 + 0x0054 = 0x1054
|
每個package使用其內部連續的4字節表格來完成表的重置.第一個字節與重置表的類型有關,下面的連續三個字節是那些必須用來糾正映像信息的虛擬地址基址和映像基址的偏移.
我所指的類型是什么?我所說的類型是以下一系列值的一個:
.IMAGE_REL_BASED_ABSOLUTE (0):不操作(NO effect)
.IMAGE_REL_BASED_HIGH (1):重新部署虛擬地址和偏移地址的高十六字節。
.IMAGE_REL_BASED_LOW (2):重新部署虛擬地址和偏移地址的低十六字節。
.IMAGE_REL_BASED_HIGHLOW (3): 重新部署虛擬地址和偏移地址。
在重新部署是都做了什么?通過重新部署,借助".reloc"節區packageS按照現在的映像基址,虛擬內存中的一些值被糾正。
Code:
delta_ImageBase = current_ImageBase - image_nt_headers->OptionalHeader.ImageBase
|
Code:
mem[ current_ImageBase + 0x1000 ] = ???mem[ current_ImageBase + 0x1000 ] + delta_ImageBase ; mem[ current_ImageBase + 0x1016 ] = ???mem[ current_ImageBase + 0x1016 ] + delta_ImageBase ; mem[ current_ImageBase + 0x1028 ] = ???mem[ current_ImageBase + 0x1028 ] + delta_ImageBase ; mem[ current_ImageBase + 0x1040 ] = ???mem[ current_ImageBase + 0x1040 ] + delta_ImageBase ; mem[ current_ImageBase + 0x1054 ] = ??mem[ current_ImageBase + 0x1054 ] + delta_ImageBase ; ...
|
I have employed the following code from Morphine packer to implement the relocation.(抱歉實在翻譯不通順)

7.3建立一個特殊的引入表為了支持 OLE-ActiveX控件注冊,我們應該給予目標OCX和DLL文件一個特殊的入口表。因此,我用如下的字符串建立了一個引入表:
Code:
const char *sz_IT_OCX_strings[]= { ????"Kernel32.dll", ????"LoadLibraryA", ????"GetProcAddress", ????"GetModuleHandleA", ????0, ????"User32.dll", ????"GetKeyboardType", ????"WindowFromPoint", ????0, ????"AdvApi32.dll", ????"RegQueryValueExA", ????"RegSetValueExA", ????"StartServiceA", ????0, ????"Oleaut32.dll", ????"SysFreeString", ????"CreateErrorInfo", ????"SafeArrayPtrOfIndex", ????0, ????"Gdi32.dll", ????"UnrealizeObject", ????0, ????"Ole32.dll", ????"CreateStreamOnHGlobal", ????"IsEqualGUID", ????0, ????"ComCtl32.dll", ????"ImageList_SetIconSize", ????0, ????0, };
|
缺少這些API函數,庫文件就不能裝載,此外 DllregisterServer() and DllUregisterServer()也不會起作用。在CPECryptor::CryptFile中,在新的目標引入表創造時我對DLL和EXE文件做了區別。
Code:
if(( image_nt_headers->FileHeader.Characteristics ?????????????& IMAGE_FILE_DLL ) == IMAGE_FILE_DLL ) { ????ImportTableMaker = new CITMaker( IMPORT_TABLE_OCX ); } else { ????ImportTableMaker = new CITMaker( IMPORT_TABLE_EXE );
}
|
8.保護線程局部存儲使用線程局部存儲(TLS),程序能執行一個線狀的進程,這個性能通常被Borland使用:Delphi和C++ Builder。當你封裝一個PE文件,你應該注意保持TLS清空(you should take care to keep clean the TLS),否則你的packer將不支持Borland Delphi and C++連接成EXE文件。請查閱6.7節的
Microsoft Portable Executable and Common Object File Format Specification 文檔
Code:
typedef struct _IMAGE_TLS_DIRECTORY32 { ????DWORD???StartAddressOfRawData; ????DWORD???EndAddressOfRawData; ????DWORD???AddressOfIndex; ????DWORD???AddressOfCallBacks; ????DWORD???SizeOfZeroFill; ????DWORD???Characteristics; } IMAGE_TLS_DIRECTORY32, * PIMAGE_TLS_DIRECTORY32;
|
為了保證TLS目錄的安全,我把他拷貝到裝載者(loader)內部一個特殊的地方
Code:
... _tls_dwStartAddressOfRawData:???dword_type(0xCCCCCCCC) _tls_dwEndAddressOfRawData:?????dword_type(0xCCCCCCCC) _tls_dwAddressOfIndex:??????????dword_type(0xCCCCCCCC) _tls_dwAddressOfCallBacks:??????dword_type(0xCCCCCCCC) _tls_dwSizeOfZeroFill:??????????dword_type(0xCCCCCCCC) _tls_dwCharacteristics:?????????dword_type(0xCCCCCCCC) ...
|
It is necessary to correct the TLS directory entry in the Optional Header:(entry怕翻譯錯,這句就看英文把

)
Code:
if(image_nt_headers-> ??OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_TLS]. ??VirtualAddress!=0) { ????memcpy(&pDataTable->image_tls_directory, ???????????image_tls_directory, ???????????sizeof(IMAGE_TLS_DIRECTORY32));???? ????dwOffset=DWORD(pData1)-DWORD(pNewSection); ????dwOffset+=sizeof(t_DATA_1)-sizeof(IMAGE_TLS_DIRECTORY32); ????image_nt_headers-> ??????OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_TLS]. ??????VirtualAddress=dwVirtualAddress + dwOffset; }
|
9.注入你的代碼(PS:我已等了很久~~~~~~~~~~)我已經放置了一個"Hello World!" 的代碼
Code:
push MB_OK | MB_ICONINFORMATION lea eax,[ebp+_p_szCaption] push eax lea eax,[ebp+_p_szText] push eax push NULL call _jmp_MessageBox // MessageBox(NULL, szText, szCaption, MB_OK | MB_ICONINFORMATION) ; ???...
|
PE Maker - Step 5Download source files - 71.7 Kb 