Advertisement
BaSs_HaXoR

Injective Code inside Import Table

Feb 4th, 2019
450
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 40.68 KB | None | 0 0
  1. https://www.codeproject.com/Articles/14360/Injective-Code-inside-Import-Table
  2.  
  3.  
  4. Injective Code inside Import Table
  5. Ashkbiz Danehkar, 29 Mar 2007
  6. 4.95 (118 votes)
  7.  
  8. Rate this:
  9. vote 1vote 2vote 3vote 4vote 5
  10.  
  11. An introduction to injection the code into Import Table of Portable Executable file format, which is called API redirection technique.
  12.  
  13. Import Table viewer - 87.1 KB
  14. PE Maker to redirect API by JMP - 96.6 KB
  15. PE Maker to redirect ShellAbout() - 193 KB
  16. Import Table runtime redirector - 130 KB
  17.  
  18. Contents
  19.  
  20. Into Import Table
  21. Import Descriptor at a glance
  22. API redirection technique
  23. Protection again reversion
  24. Runtime Import Table Injection
  25. Trojan horse
  26. Consequences
  27.  
  28. Let's imagine we could redirect the thoroughfare of the imported function's entrances into our especial routines by manipulating the import table thunks, it could be possible to filter the demands of the importations through our routines. Furthermore, we could settle our appropriate routine by this performance, which is done by the professional Portable Executable (PE) Protectors, additionally some sort of rootkits employ this approach to embed its malicious code inside the victim by a Trojan horse.
  29.  
  30. In the reverse engineering world, we describe it as "API redirection technique". Nevertheless I am not going to accompany all viewpoints in this area by source code, this article merely represents a brief aspect of this technique by a simple code. I will describe other issues in the absence of the source code; I could not release code which is related to commercial projects or intended for malicious motivation, however, I think this article could be used as an introduction to this topic.
  31. 1. Into Import Table
  32.  
  33. The portable executable file structure consists of the MS-DOS header, the NT headers, the Sections headers and the Section images, as you observe in Figure 1. The MS-DOS header is common in all Microsoft executable file formats from the DOS days until the Windows days. The NT headers idea was abstracted form the Executable and Linkable Format (ELF) of UNIX System, indeed the Portable Executable (PE) format is Sister to the Linux Executable and Linkable Format (ELF). The PE format headers consists of the "PE" Signature, the Common Object File Format (COFF) header, the Portable Executable Optimal header and the Section headers.
  34. Figure 1 - Portable Executable file format structure
  35.  
  36. The definition of the NT headers can be found in <winnt.h> header file of Virtual C++ included directory. This information can be retrieved very easy by using ImageNtHeader() from DbgHelp.dll. You can also employ the DOS header in order to fetch the NT headers, so the last position of the DOS header, e_lfanew, represents the offset of the NT headers.
  37. Hide Copy Code
  38.  
  39. typedef struct _IMAGE_NT_HEADERS {
  40. DWORD Signature;
  41. IMAGE_FILE_HEADER FileHeader;
  42. IMAGE_OPTIONAL_HEADER OptionalHeader;
  43. } IMAGE_NT_HEADERS, *PIMAGE_NT_HEADERS;
  44.  
  45. In the Portable Executable Optional header, there are some data directories which delineate the relative location and the size of the principal information tables inside the virtual memory of the current process. These tables can hold the information of resource, import, export, relocation, debug, thread local storage, and COM runtime. It is impossible to find a PE executable file without the import table; this table contains the DLL names and the Functions names which are essential when the program tend to request them by their virtual addresses. The resource table is not found in the Console executable files; nevertheless it is vital part of the Windows executable files with Graphic User Interface (GUI). The export table is necessary when a dynamic link library inclines to export its function outside and also in OLE Active-X container. The .NET virtual machine could not be executed without being escorted by the COM+ runtime header. As you discerned, each table has especial commission in PE format, Figure 2.
  46. Figure 2 - Data Directories
  47.  
  48. Data
  49. Directories
  50. 0 Export Table
  51. 1 Import Table
  52. 2 Resource Table
  53. 3 Exception Table
  54. 4 Certificate File
  55. 5 Relocation Table
  56. 6 Debug Data
  57. 7 Architecture Data
  58. 8 Global Ptr
  59. 9 Thread Local Storage Table
  60. 10 Load Config Table
  61. 11 Bound Import Table
  62. 12 Import Address Table
  63. 13 Delay Import Descriptor
  64. 14 COM+ Runtime Header
  65. 15 Reserved
  66. Hide Copy Code
  67.  
  68. // <winnt.h>
  69.  
  70. #define IMAGE_NUMBEROF_DIRECTORY_ENTRIES 16
  71.  
  72. // Optional header format.
  73.  
  74. typedef struct _IMAGE_OPTIONAL_HEADER
  75. {
  76. ...
  77.  
  78. IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES];
  79. } IMAGE_OPTIONAL_HEADER32, *PIMAGE_OPTIONAL_HEADER32;
  80.  
  81.  
  82. // Directory Entries
  83. #define IMAGE_DIRECTORY_ENTRY_EXPORT 0 // Export Directory
  84. #define IMAGE_DIRECTORY_ENTRY_IMPORT 1 // Import Directory
  85. #define IMAGE_DIRECTORY_ENTRY_RESOURCE 2 // Resource Directory
  86. #define IMAGE_DIRECTORY_ENTRY_BASERELOC 5 // Base Relocation Table
  87. #define IMAGE_DIRECTORY_ENTRY_DEBUG 6 // Debug Directory
  88. #define IMAGE_DIRECTORY_ENTRY_TLS 9 // TLS Directory
  89.  
  90. We can obtain the position and size of the import table with only two or three lines. By knowing the position of the import table, we move to the next step to retrieve the DLL names and the Function names, it will be discussed in the succeeding section.
  91. Hide Copy Code
  92.  
  93. PIMAGE_NT_HEADERS pimage_nt_headers = ImageNtHeader(pImageBase);
  94. DWORD it_voffset = pimage_nt_headers->OptionalHeader.
  95. DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress;
  96.  
  97. PIMAGE_DOS_HEADER pimage_dos_header = PIMAGE_DOS_HEADER(pImageBase);
  98. PIMAGE_NT_HEADERS pimage_nt_headers = (PIMAGE_NT_HEADERS)
  99. (pImageBase + pimage_dos_header->e_lfanew);
  100. DWORD it_voffset = pimage_nt_headers->OptionalHeader.
  101. DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress;
  102.  
  103. 2. Import Descriptor at a glance
  104.  
  105. The import directory entry of the import table leads us to the position of the import table inside the file image. There is a container for each imported DLL, import descriptor, which embraces the address of first thunk and the address of original first thunk, the pointer to DLL name. The First Thunk refers to the location of the first thunk; the thunks will be initialized by PE loader of Windows during running the program, Figure 5. The Original First Thunk points to the first storage of the thunks, where provide the address of the Hint data and the Function Name data for each functions, Figure 4. In the case, the First Original Thunk is not present, the First Thunks refers to where the Hint data and the Function Name data are located, Figure 3.
  106.  
  107. The import descriptor is represented with IMAGE_IMPORT_DESCRIPTOR structures as the following definition:
  108. Hide Copy Code
  109.  
  110. ypedef struct _IMAGE_IMPORT_DESCRIPTOR {
  111. DWORD OriginalFirstThunk;
  112. DWORD TimeDateStamp;
  113. DWORD ForwarderChain;
  114. DWORD Name;
  115. DWORD FirstThunk;
  116. } IMAGE_IMPORT_DESCRIPTOR, *PIMAGE_IMPORT_DESCRIPTOR;
  117.  
  118. Members
  119.  
  120. OriginalFirstThunk
  121. It points to the first thunk, IMAGE_THUNK_DATA, the thunk holds the address of the Hint and the Function name.
  122. TimeDateStamp
  123. It contains the time/data stamp if there is the binding. If it is 0, no bound in imported DLL has happened. In new days, it sets to 0xFFFFFFFF to describe the binding occurred.
  124. ForwarderChain
  125. In old version of binding, it acts as referee to the first forwarder chain of API. It can be set 0xFFFFFFFF to describe no forwarder.
  126. Name
  127. It shows the relative virtual address of DLL name.
  128. FirstThunk
  129. It contains the virtual address of the first thunk arrays that is defined by IMAGE_THUNK_DATA, the thunk is initialized by loader with function virtual address. In the absence view of the Original First Thunk, it points to the first thunk, the thunks of the Hints and The Function names.
  130.  
  131. Hide Copy Code
  132.  
  133. typedef struct _IMAGE_IMPORT_BY_NAME {
  134. WORD Hint;
  135. BYTE Name[1];
  136. } IMAGE_IMPORT_BY_NAME, *PIMAGE_IMPORT_BY_NAME;
  137.  
  138. typedef struct _IMAGE_THUNK_DATA {
  139. union {
  140. PDWORD Function;
  141. PIMAGE_IMPORT_BY_NAME AddressOfData;
  142. } u1;
  143. } IMAGE_THUNK_DATA, *PIMAGE_THUNK_DATA;
  144.  
  145. Figure 3 - Import Table View
  146.  
  147. Figure 4 - Import Table View with Orignal First Thunk
  148.  
  149. These two import tables (Figure 3 and Figure 4) illustrate the different between import table with and without the original first thunk.
  150. Figure 5 - Import Table after overwritten by PE loader
  151.  
  152. We can use Dependency Walker, Figure 6, to observe the whole information of the import table. By the way, I have provided another tool, Import Table viewer, Figure 7, with simple and similar operation. I am sure its source will help you to understand better the main representation that is done by this kind of equipments.
  153. Figure 6 - Dependency Walker, Steve P. Miller
  154.  
  155. Here we observe a simple source which could be used to display the import DLLs and the import Functions with a console mode program. However, I think my Import Table viewer, Figure 7, has more motivation to follow the topic because of its graphic user interface.
  156. Hide Shrink Copy Code
  157.  
  158. PCHAR pThunk;
  159. PCHAR pHintName;
  160. DWORD dwAPIaddress;
  161. PCHAR pDllName;
  162. PCHAR pAPIName;
  163. //----------------------------------------
  164. DWORD dwImportDirectory= RVA2Offset(pImageBase, pimage_nt_headers->
  165. OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].
  166. VirtualAddress);
  167. //----------------------------------------
  168. PIMAGE_IMPORT_DESCRIPTOR pimage_import_descriptor= (PIMAGE_IMPORT_DESCRIPTOR)
  169. (pImageBase+
  170. dwImportDirectory);
  171. //----------------------------------------
  172. while(pimage_import_descriptor->Name!=0)
  173. {
  174. pThunk= pImageBase+pimage_import_descriptor->FirstThunk;
  175. pHintName= pImageBase;
  176. if(pimage_import_descriptor->OriginalFirstThunk!=0)
  177. {
  178. pHintName+= RVA2Offset(pImageBase, pimage_import_descriptor->
  179. OriginalFirstThunk);
  180. }
  181. else
  182. {
  183. pHintName+= RVA2Offset(pImageBase, pimage_import_descriptor->
  184. FirstThunk);
  185. }
  186. pDllName= pImageBase + RVA2Offset(pImageBase, pimage_import_descriptor->
  187. Name);
  188. printf(" DLL Name: %s First Thunk: 0x%x", pDllName,
  189. pimage_import_descriptor->FirstThunk);
  190. PIMAGE_THUNK_DATA pimage_thunk_data= (PIMAGE_THUNK_DATA) pHintName;
  191. while(pimage_thunk_data->u1.AddressOfData!=0)
  192. {
  193. dwAPIaddress= pimage_thunk_data->u1.AddressOfData;
  194. if((dwAPIaddress&0x80000000)==0x80000000)
  195. {
  196. dwAPIaddress&= 0x7FFFFFFF;
  197. printf("Proccess: 0x%x", dwAPIaddress);
  198. }
  199. else
  200. {
  201. pAPIName= pImageBase+RVA2Offset(pImageBase, dwAPIaddress)+2;
  202. printf("Proccess: %s", pAPIName);
  203. }
  204. pThunk+= 4;
  205. pHintName+= 4;
  206. pimage_thunk_data++;
  207. }
  208. pimage_import_descriptor++;
  209. }
  210.  
  211. Figure 7 - Import Table viewer
  212.  
  213. 3. API redirection technique
  214.  
  215. We perceive all essential knowledge regarding the import table, so it is the time to establish our redirection method. The algorithm is so simple, creating an extra virtual space inside the virtual memory of the current process, and generate instructions to redirect with JMP to original function location. We can perform it by absolute jump or relative jump. You should take care in the case of the absolute jump, you can not perform it simply as in Figure 8, you should first move the virtual address to EAX and then a jump by JMP EAX. In pemaker6.zip, I have done a redirection by relative jump.
  216. Figure 8 - Overview of a simple API redirection by the absolute jump instruction
  217.  
  218. This PE maker was created in the consequence of my previous article [1], I suggest you to read it if you are interested to know how it works. In this version, I have modified the Import table fix up routine, as you see in the following lines, I wrote some lines to generate relative JMP instruction to the real position of the function. It is important to know, you could not perform the API redirection for all DLL modules. For instance, in CALC.EXE, some thunks of MSVCRT.DLL will be accessed from inside of CALC.EXE code section during the runtime initialization. Therefore, it will not work in the case of the redirection.
  219. Hide Shrink Copy Code
  220.  
  221. _it_fixup_1:
  222. push ebp
  223. mov ebp,esp
  224. add esp,-14h
  225. push PAGE_READWRITE
  226. push MEM_COMMIT
  227. push 01D000h
  228. push 0
  229. call _jmp_VirtualAlloc
  230. //NewITaddress=VirtualAlloc(NULL, 0x01D000, MEM_COMMIT, PAGE_READWRITE);
  231. mov [ebp-04h],eax
  232. mov ebx,[ebp+0ch]
  233. test ebx,ebx
  234. jz _it_fixup_1_end
  235. mov esi,[ebp+08h]
  236. add ebx,esi // dwImageBase + dwImportVirtualAddress
  237. _it_fixup_1_get_lib_address_loop:
  238. mov eax,[ebx+0ch] // image_import_descriptor.Name
  239. test eax,eax
  240. jz _it_fixup_1_end
  241.  
  242. mov ecx,[ebx+10h] // image_import_descriptor.FirstThunk
  243. add ecx,esi
  244. mov [ebp-08h],ecx // dwThunk
  245. mov ecx,[ebx] // image_import_descriptor.Characteristics
  246. test ecx,ecx
  247. jnz _it_fixup_1_table
  248. mov ecx,[ebx+10h]
  249. _it_fixup_1_table:
  250. add ecx,esi
  251. mov [ebp-0ch],ecx // dwHintName
  252. add eax,esi // image_import_descriptor.Name +
  253. // dwImageBase = ModuleName
  254. push eax // lpLibFileName
  255. mov [ebp-10h],eax
  256. call _jmp_LoadLibrary // LoadLibrary(lpLibFileName);
  257.  
  258. test eax,eax
  259. jz _it_fixup_1_end
  260. mov edi,eax
  261. _it_fixup_1_get_proc_address_loop:
  262. mov ecx,[ebp-0ch] // dwHintName
  263. mov edx,[ecx] // image_thunk_data.Ordinal
  264. test edx,edx
  265. jz _it_fixup_1_next_module
  266. test edx,080000000h // .IF( import by ordinal )
  267. jz _it_fixup_1_by_name
  268. and edx,07FFFFFFFh// get ordinal
  269. jmp _it_fixup_1_get_addr
  270. _it_fixup_1_by_name:
  271. add edx,esi // image_thunk_data.Ordinal +
  272. // dwImageBase = OrdinalName
  273. inc edx
  274. inc edx // OrdinalName.Name
  275. _it_fixup_1_get_addr:
  276. push edx // lpProcName
  277. push edi // hModule
  278. call _jmp_GetProcAddress // GetProcAddress(hModule,lpProcName);
  279. mov [ebp-14h],eax //_p_dwAPIaddress
  280. //================================================================
  281. // Redirection Engine
  282. push edi
  283. push esi
  284. push ebx
  285.  
  286. mov ebx,[ebp-10h]
  287. push ebx
  288. push ebx
  289. call _char_upper
  290.  
  291. mov esi,[ebp-10h]
  292. mov edi,[ebp+010h]
  293. _it_fixup_1_check_dll_redirected:
  294. push edi
  295. call __strlen
  296. add esp, 4
  297.  
  298. mov ebx,eax
  299. mov ecx,eax
  300. push edi
  301. push esi
  302. repe cmps
  303. jz _it_fixup_1_do_normal_it_0
  304. pop esi
  305. pop edi
  306. add edi,ebx
  307. cmp byte ptr [edi],0
  308. jnz _it_fixup_1_check_dll_redirected
  309. mov ecx,[ebp-08h]
  310. mov eax,[ebp-014h]
  311. mov [ecx],eax
  312. jmp _it_fixup_1_do_normal_it_1
  313. _it_fixup_1_do_normal_it_0:
  314. pop esi
  315. pop edi
  316. mov edi,[ebp-04h]
  317. mov byte ptr [edi], 0e9h // JMP Instruction
  318. mov eax,[ebp-14h]
  319. sub eax, edi
  320. sub eax, 05h
  321. mov [edi+1],eax // Relative JMP value
  322. mov word ptr [edi+05], 0c08bh
  323. mov ecx,[ebp-08h]
  324. mov [ecx],edi // -> Thunk
  325. add dword ptr [ebp-04h],07h
  326. _it_fixup_1_do_normal_it_1:
  327. pop ebx
  328. pop esi
  329. pop edi
  330. //==============================================================
  331. add dword ptr [ebp-08h],004h // dwThunk => next dwThunk
  332. add dword ptr [ebp-0ch],004h // dwHintName => next dwHintName
  333. jmp _it_fixup_1_get_proc_address_loop
  334. _it_fixup_1_next_module:
  335. add ebx,014h // sizeof(IMAGE_IMPORT_DESCRIPTOR)
  336. jmp _it_fixup_1_get_lib_address_loop
  337. _it_fixup_1_end:
  338. mov esp,ebp
  339. pop ebp
  340. ret 0ch
  341.  
  342. Do not think the API redirection is discharged with this simple method in professional EXE protectors; they have an x86 instruction generator engine which is used to create the code for redirection purpose. Some time this engine is accompanied with metamorphism engine, that makes them extremely complicated to analyze.
  343. How does it work?
  344.  
  345. The preceding code works according to the succeeding algorithm:
  346.  
  347. Create a separated space to store the generated instructions by VirtualAlloc().
  348.  
  349. Find the function virtual address by LoadLibrary() and GerProcAddress().
  350.  
  351. Check if DLL name is match with valid DLL list. In this example, we recognize KERNEL32.DLL, USER32.DLL, GDI32.DLL, ADVAPI32.DLL,and SHELL32.DLL as valid DLL name to be redirect.
  352.  
  353. If DLL name is valid, go to redirect routine, otherwise initialize the thunk with the original function virtual address.
  354.  
  355. To redirect API, generate the JMP (0xE9) instruction , calculate the relative position of the function position in order to establish a relative jump.
  356.  
  357. Store the generated instructions inside the separated space, and refer the thunk to the first position of these instructions.
  358.  
  359. Continue this routine for other the Functions and the DLLs.
  360.  
  361. If you implement this performance on CALC.EXE, and trace it by OllyDbg or a similar user mode debugger, you will perceive this code generated a view as similar as the following view:
  362. Hide Copy Code
  363.  
  364. 008E0000 - E9 E6F8177C JMP SHELL32.ShellAboutW
  365. 008E0005 8BC0 MOV EAX,EAX
  366. 008E0007 - E9 0F764F77 JMP ADVAPI32.RegOpenKeyExA
  367. 008E000C 8BC0 MOV EAX,EAX
  368. 008E000E - E9 70784F77 JMP ADVAPI32.RegQueryValueExA
  369. 008E0013 8BC0 MOV EAX,EAX
  370. 008E0015 - E9 D66B4F77 JMP ADVAPI32.RegCloseKey
  371. 008E001A 8BC0 MOV EAX,EAX
  372. 008E001C - E9 08B5F27B JMP kernel32.GetModuleHandleA
  373. 008E0021 8BC0 MOV EAX,EAX
  374. 008E0023 - E9 4F1DF27B JMP kernel32.LoadLibraryA
  375. 008E0028 8BC0 MOV EAX,EAX
  376. 008E002A - E9 F9ABF27B JMP kernel32.GetProcAddress
  377. 008E002F 8BC0 MOV EAX,EAX
  378. 008E0031 - E9 1AE4F77B JMP kernel32.LocalCompact
  379. 008E0036 8BC0 MOV EAX,EAX
  380. 008E0038 - E9 F0FEF27B JMP kernel32.GlobalAlloc
  381. 008E003D 8BC0 MOV EAX,EAX
  382. 008E003F - E9 EBFDF27B JMP kernel32.GlobalFree
  383. 008E0044 8BC0 MOV EAX,EAX
  384. 008E0046 - E9 7E25F37B JMP kernel32.GlobalReAlloc
  385. 008E004B 8BC0 MOV EAX,EAX
  386. 008E004D - E9 07A8F27B JMP kernel32.lstrcmpW
  387. 008E0052 8BC0 MOV EAX,EAX
  388.  
  389. For your homework, you can practice changing the PE Maker source with the absolute jump instruction by this code:
  390. Hide Copy Code
  391.  
  392. 008E0000 - B8 EBF8A57C MOV EAX,7CA5F8EBh // address of SHELL32.ShellAboutW
  393. 008E0005 FFE0 JMP EAX
  394.  
  395. What do you call this?
  396.  
  397. This time, I want to change the function of an API by this technique. I am not sure if we can call it "API redirection" again. In this sample, I redirect the ShellAbout() dialog of CALC.EXE to my "Hello World!" message box in pemaker7.zip. You will see how easy it is implemented by a few changes in the following code:
  398. Hide Shrink Copy Code
  399.  
  400. ...
  401. //==============================================================
  402. push edi
  403. push esi
  404. push ebx
  405.  
  406. mov ebx,[ebp-10h]
  407. push ebx
  408. push ebx
  409. call _char_upper
  410.  
  411. mov esi,[ebp-10h]
  412. mov edi,[ebp+010h] // [ebp+_p_szShell32]
  413. _it_fixup_1_check_dll_redirected:
  414. push edi
  415. call __strlen
  416. add esp, 4
  417.  
  418. mov ebx,eax
  419. mov ecx,eax
  420. push edi
  421. push esi
  422. repe cmps //byte ptr [edi], byte ptr [esi]
  423. jz _it_fixup_1_check_func_name
  424. jmp _it_fixup_1_no_check_func_name
  425. _it_fixup_1_check_func_name:
  426. mov edi,[ebp+014h] // [ebp+_p_szShellAbout]
  427. push edi
  428. call __strlen
  429. add esp, 4
  430. mov ecx,eax
  431. mov esi,[ebp-18h]
  432. mov edi,[ebp+014h] // [ebp+_p_szShellAbout]
  433. repe cmps //byte ptr [edi], byte ptr [esi]
  434. jz _it_fixup_1_do_normal_it_0
  435. _it_fixup_1_no_check_func_name:
  436. pop esi
  437. pop edi
  438. add edi,ebx
  439. cmp byte ptr [edi],0
  440. jnz _it_fixup_1_check_dll_redirected
  441. mov ecx,[ebp-08h]
  442. mov eax,[ebp-014h]
  443. mov [ecx],eax
  444. jmp _it_fixup_1_do_normal_it_1
  445. _it_fixup_1_do_normal_it_0:
  446. pop esi
  447. pop edi
  448. mov ecx,[ebp-08h]
  449. mov edi,[ebp+18h]
  450. mov [ecx],edi // move address of new function to the thunk
  451. _it_fixup_1_do_normal_it_1:
  452. pop ebx
  453. pop esi
  454. pop edi
  455. //==============================================================
  456. ...
  457.  
  458. I summarize this routine successively:
  459.  
  460. Check if DLL name is "Shell32.DLL".
  461.  
  462. Check if Function name is "ShellAboutW".
  463.  
  464. If condition 1 and 2 are true, redirect the thunk of ShellAbout() to new function.
  465.  
  466. This new function is a simple message box:
  467. Hide Copy Code
  468.  
  469. _ShellAbout_NewCode:
  470. _local_0:
  471. pushad // save the registers context in stack
  472. call _local_1
  473. _local_1:
  474. pop ebp
  475. sub ebp,offset _local_1 // get base ebp
  476. push MB_OK | MB_ICONINFORMATION
  477. lea eax,[ebp+_p_szCaption]
  478. push eax
  479. lea eax,[ebp+_p_szText]
  480. push eax
  481. push NULL
  482. call _jmp_MessageBox
  483. // MessageBox(NULL, szText, szCaption, MB_OK | MB_ICONINFORMATION) ;
  484. popad // restore the first registers context from stack
  485. ret 10h
  486.  
  487. When you plan to replace an API with a new function, you should consider some important notes:
  488.  
  489. Do not corrupt the Stack memory by missing the stack point. Therefore, it is necessary to restore finally the original stack point by ADD ESP,xxx or RET xxx.
  490. Try to keep safe the most of the thread registers except EAX by capturing and restoring them with PUSHAD and POPAD.
  491.  
  492. As you see, I have employed the PUSHAD and POPAD to reclaim the thread registers. For this case, ShellAbout(), it has 4 DWORD memebers so the stack point is increased 0x10 while returning.
  493.  
  494. After redirecting ShellAbout(), you can try About Calculator menu item form Help menu, you will see what it has done on target CALC.EXE.
  495. Figure 9 - The redirection of About Calculator to a dialog message box
  496.  
  497. The EXE protectors manipulate the target in this way; they establish the redirection to their extra memory space, the next section will discuss.
  498. 4. Protection again reversion
  499.  
  500. It is extremely difficult to reconstruct an import table with complex API redirection technique. Sometimes the tools like Import REConstructor, Figure 10, will be confused to rebuild the import table, especially if the redirection is accomplished with polymorphism code image. Import REConstructor is a famous tool in the reverse world; it will suspend the target process in order to capture the import information. If you make a redirection like a simile JMP, it certainly will be reconstructed with this tool. Nevertheless, if we encrypt the Function name and bundle it with polymorphism code inside the memory, it will be befogged to retrieve the correct import table. We present our EXE protector according to this technique, "Native Security Engine", [6] is a packer which follow this way. It has an x86 code generator plus a metamorphism engine, both of them help to establish a complex redirection structure.
  501. Figure 10 - Import REConstructor, MackT/uCF2000
  502.  
  503. The Figure 11 illustrates the main strategy of the import protection in EXE protectors. Some of them employ the redirection to virtual Win32 libraries. For instance, they have the virtual libraries for Kernel32, User32, and AdvApi32. They use their own libraries to prevent from hacking or to install their Virtual Machine.
  504. Figure 11 - Import Table Protection
  505.  
  506. It is achievable to cut off the access to outside by this technique. As you see, MoleBox behaves the same, it filters FindFirstFile() and FindNextFile() in order to merge TEXT files and JPEG files inside the packed file. When the program tends to find a file form hard disk, it will be redirected to memory.
  507. 5. Runtime Import Table Injection
  508.  
  509. Now I want to discuss once more. This topic is certainly interesting for the people who intend to understand the maneuver of the user level (ring-3) rootkits [7] on Windows System. First and final question: "How it is obtainable to inject to import table of a runtime process?" This section will answer to this question.
  510.  
  511. We want to inject to a runtime process and modify it. If you remember, in one of my previous articles [2], I established a Windows Spy to capture Windows Class properties and modify them runtime. This time, I will move near to rewrite the memory and redirect import table from outside.
  512.  
  513. By using WindowFromPoint() we can obtain the window handle of a special point, GetWindowThreadProcessId() aids us to know the process ID and the thread ID of this window handle.
  514. Hide Copy Code
  515.  
  516. POINT point;
  517. HWND hWindowUnderTheMouse = WindowFromPoint(point);
  518.  
  519. DWORD dwProcessId;
  520. DWORD dwThreadId;
  521. dwThreadId=GetWindowThreadProcessId(hSeekedWindow, &dwProcessId);
  522.  
  523. The process handle and the thread are acquired by OpenProcess() and OpenThread(). But there is no OpenThread() in Windows 98! Do not worry, try to find RT library by EliCZ', a library to emulate OpenThread(), CreateRemoteThread(), VirtualAllocEX(), and VirtualFreeEx() inside Windows 98.
  524. Hide Copy Code
  525.  
  526. HANDLE hProcess = OpenProcess( PROCESS_ALL_ACCESS, FALSE, dwProcessId );
  527. HANDLE hThread = OpenThread( THREAD_ALL_ACCESS, FALSE, dwThreadId);
  528.  
  529. To start to manipulate the process memory, we should first freeze the process by suspending the main thread.
  530. Hide Copy Code
  531.  
  532. SuspendThread(hThread);
  533.  
  534. The Thread Environment Block (TEB) location can be obtained by FS:[18] which we do not have access to it! so GetThreadContext() and GetThreadSelectorEntry() help us to know the base value of FS segment.
  535. Hide Copy Code
  536.  
  537. CONTEXT Context;
  538. LDT_ENTRY SelEntry;
  539.  
  540. Context.ContextFlags = CONTEXT_FULL | CONTEXT_DEBUG_REGISTERS;
  541. GetThreadContext(hThread,&Context);
  542.  
  543. // Calculate the base address of FS
  544. GetThreadSelectorEntry(hThread, Context.SegFs, &SelEntry);
  545. DWORD dwFSBase = ( SelEntry.HighWord.Bits.BaseHi << 24) |
  546. (SelEntry.HighWord.Bits.BaseMid << 16) |
  547. SelEntry.BaseLow;
  548.  
  549. The Thread Environment Block (TEB) is obtained by reading from its position inside the virtual memory of the target process. The thread and process environment blocks, Figure 12, has been explained enough in "Undocumented Windows 2000 secrets" [4]. Moreover, the NTInternals team [5] presents the complete definition of TEB and FEB. As I guessed, the Microsoft team has forgotten to offer information about them or do not intend to make them public! This is the reason I like the Linux team.
  550. Hide Copy Code
  551.  
  552. PTEB pteb = new TEB;
  553. PPEB ppeb = new PEB;
  554. DWORD dwBytes;
  555.  
  556. ReadProcessMemory( hProcess, (LPCVOID)dwFSBase, pteb, sizeof(TEB),
  557. &dwBytes);
  558. ReadProcessMemory( hProcess, (LPCVOID)pteb->Peb, ppeb, sizeof(PEB),
  559. &dwBytes);
  560.  
  561. Figure 12 - The Thread Environment Blocks and the Process Environment Block
  562.  
  563. The image base of portable executable image inside the current process memory is found from the process environment block information.
  564. Hide Copy Code
  565.  
  566. DWORD dwImageBase = (DWORD)ppeb->ImageBaseAddress;
  567.  
  568. ReadProcessMemory() helps us to read the entire image of the portable executable file.
  569. Hide Copy Code
  570.  
  571. PIMAGE_DOS_HEADER pimage_dos_header = new IMAGE_DOS_HEADER;
  572. PIMAGE_NT_HEADERS pimage_nt_headers = new IMAGE_NT_HEADERS;
  573.  
  574. ReadProcessMemory( hProcess,
  575. (LPCVOID)dwImageBase,
  576. pimage_dos_header,
  577. sizeof(IMAGE_DOS_HEADER),
  578. &dwBytes);
  579. ReadProcessMemory( hProcess,
  580. (LPCVOID)(dwImageBase+pimage_dos_header->e_lfanew),
  581. pimage_nt_headers, sizeof(IMAGE_NT_HEADERS),
  582. &dwBytes);
  583.  
  584. PCHAR pMem = (PCHAR)GlobalAlloc(
  585. GMEM_FIXED | GMEM_ZEROINIT,
  586. pimage_nt_headers->OptionalHeader.SizeOfImage);
  587.  
  588. ReadProcessMemory( hProcess,
  589. (LPCVOID)(dwImageBase),
  590. pMem,
  591. pimage_nt_headers->OptionalHeader.SizeOfImage,
  592. &dwBytes);
  593.  
  594. We watch the DLL names and the thunk values find our target and to redirect it. In this example, the DLL name is Shell32.dll and the thunk is the virtual address of ShellAbout().
  595. Hide Shrink Copy Code
  596.  
  597. HMODULE hModule = LoadLibrary("Shell32.dll");
  598. DWORD dwShellAbout= (DWORD)GetProcAddress(hModule, "ShellAboutW");
  599.  
  600. DWORD dwRedirectMem = (DWORD)VirtualAllocEx(
  601. hProcess,
  602. NULL,
  603. 0x01D000,
  604. MEM_COMMIT,
  605. PAGE_EXECUTE_READWRITE);
  606.  
  607. RedirectAPI(pMem, dwShellAbout, dwRedirectMem);
  608.  
  609. ...
  610.  
  611. int RedirectAPI(PCHAR pMem, DWORD API_voffset, DWORD NEW_voffset)
  612. {
  613. PCHAR pThunk;
  614. PCHAR pHintName;
  615. DWORD dwAPIaddress;
  616. PCHAR pDllName;
  617. DWORD dwImportDirectory;
  618.  
  619. DWORD dwAPI;
  620.  
  621. PCHAR pImageBase = pMem;
  622. //----------------------------------------
  623. PIMAGE_IMPORT_DESCRIPTOR pimage_import_descriptor;
  624. PIMAGE_THUNK_DATA pimage_thunk_data;
  625. //----------------------------------------
  626. PIMAGE_DOS_HEADER pimage_dos_header;
  627. PIMAGE_NT_HEADERS pimage_nt_headers;
  628. pimage_dos_header = PIMAGE_DOS_HEADER(pImageBase);
  629. pimage_nt_headers = (PIMAGE_NT_HEADERS)(
  630. pImageBase+pimage_dos_header->e_lfanew);
  631. //----------------------------------------
  632. dwImportDirectory=pimage_nt_headers->OptionalHeader
  633. .DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress;
  634. if(dwImportDirectory==0)
  635. {
  636. return -1;
  637. }
  638. //----------------------------------------
  639. pimage_import_descriptor=(PIMAGE_IMPORT_DESCRIPTOR)(
  640. pImageBase+dwImportDirectory);
  641. //----------------------------------------
  642. while(pimage_import_descriptor->Name!=0)
  643. {
  644. pThunk=pImageBase+pimage_import_descriptor->FirstThunk;
  645. pHintName=pImageBase;
  646. if(pimage_import_descriptor->OriginalFirstThunk!=0)
  647. {
  648. pHintName+=pimage_import_descriptor->OriginalFirstThunk;
  649. }
  650. else
  651. {
  652. pHintName+=pimage_import_descriptor->FirstThunk;
  653. }
  654. pDllName=pImageBase+pimage_import_descriptor->Name;
  655.  
  656. StrUpper(pDllName);
  657. if(strcmp(pDllName,"SHELL32.DLL")==0)
  658. {
  659. pimage_thunk_data=PIMAGE_THUNK_DATA(pHintName);
  660. while(pimage_thunk_data->u1.AddressOfData!=0)
  661. {
  662. //----------------------------------------
  663. memcpy(&dwAPI, pThunk, 4);
  664. if(dwAPI==API_voffset)
  665. {
  666. memcpy(pThunk, &NEW_voffset, 4);
  667. return 0;
  668. }
  669. //----------------------------------------
  670. pThunk+=4;
  671. pHintName+=4;
  672. pimage_thunk_data++;
  673. }
  674. }
  675. pimage_import_descriptor++;
  676. }
  677. //----------------------------------------
  678. return -1;
  679. }
  680.  
  681. Extra memory for the redirection purpose is created by VirtualProtectEx(). We will generate the code and write it inside the new spare space.
  682. Hide Copy Code
  683.  
  684. DWORD dwRedirectMem = (DWORD)VirtualAllocEx(
  685. hProcess,
  686. NULL,
  687. 0x01D000,
  688. MEM_COMMIT,
  689. PAGE_EXECUTE_READWRITE);
  690.  
  691. ...
  692.  
  693. PCHAR pLdr;
  694. DWORD Ldr_rsize;
  695. GetLdrCode(pLdr, Ldr_rsize);
  696.  
  697. WriteProcessMemory( hProcess,
  698. (LPVOID)(dwRedirectMem),
  699. pLdr,
  700. Ldr_rsize,
  701. &dwBytes);
  702.  
  703. The loader is written on the extra memory. It holds the code to show a sample message box.
  704. Hide Shrink Copy Code
  705.  
  706. void GetLdrCode(PCHAR &pLdr, DWORD &rsize)
  707. {
  708. HMODULE hModule;
  709. DWORD dwMessageBox;
  710.  
  711. PCHAR ch_temp;
  712. DWORD dwCodeSize;
  713. ch_temp=(PCHAR)DWORD(ReturnToBytePtr(DynLoader,
  714. DYN_LOADER_START_MAGIC))+4;
  715. dwCodeSize=DWORD(ReturnToBytePtr(DynLoader,
  716. DYN_LOADER_END_MAGIC))-DWORD(ch_temp);
  717. rsize= dwCodeSize;
  718. pLdr = (PCHAR)GlobalAlloc(GMEM_FIXED | GMEM_ZEROINIT, dwCodeSize);
  719. memcpy(pLdr, ch_temp, dwCodeSize);
  720.  
  721. ch_temp=(PCHAR)ReturnToBytePtr(pLdr, DYN_LOADER_START_DATA1);
  722.  
  723. hModule = LoadLibrary("User32.dll");
  724. dwMessageBox= (DWORD)GetProcAddress(hModule, "MessageBoxA");
  725. memcpy(ch_temp+4, &dwMessageBox, 4);
  726. }
  727.  
  728. ...
  729. _ShellAbout_NewCode:
  730. _local_0:
  731. pushad // save the registers context in stack
  732. call _local_1
  733. _local_1:
  734. pop ebp
  735. sub ebp,offset _local_1// get base ebp
  736. push MB_OK | MB_ICONINFORMATION
  737. lea eax,[ebp+_p_szCaption]
  738. push eax
  739. lea eax,[ebp+_p_szText]
  740. push eax
  741. push NULL
  742. mov eax, [ebp+_p_MessageBox]
  743. call eax
  744. // MessageBox(NULL, szText, szCaption, MB_OK | MB_ICONINFORMATION) ;
  745. popad // restore the first registers context from stack
  746. ret 10h
  747. ...
  748.  
  749. The executable image is written on memory after modification. Do not forget to set full access on memory in front of writing.
  750. Hide Copy Code
  751.  
  752. VirtualProtectEx( hProcess,
  753. (LPVOID)(dwImageBase),
  754. pimage_nt_headers->OptionalHeader.SizeOfImage,
  755. PAGE_EXECUTE_READWRITE,
  756. &OldProtect);
  757.  
  758. WriteProcessMemory( hProcess,
  759. (LPVOID)(dwImageBase),
  760. pMem,
  761. pimage_nt_headers->OptionalHeader.SizeOfImage,
  762. &dwBytes);
  763.  
  764. VirtualProtectEx() sets the page access to PAGE_EXECUTE_READWRITE protection type. It is necessary to have PAGE_READWRITE access when WriteProcessMemory is used and PAGE_EXECUTE in the case of executable page.
  765.  
  766. Now the process is ready to unfreeze and the life will start again, but what happens? Try the about menu item you will see, Figure 13, this is the first aspect of the injection life!
  767. Hide Copy Code
  768.  
  769. ResumeThread(hThread);
  770.  
  771. Figure 13 - Runtime Injection into ShellAbout() Thunk
  772.  
  773. I am thinking about injection to other API thunks, we can also upload other dynamic link libraries in the target process to redirect the victim thunk to it, but that has been explained completely in another article [3]. The next section discusses a bit about one of the disasters which comes as a consequence of this performance. You can imagine other possible tsunamis by yourself.
  774. 6. Trojan horse
  775.  
  776. Always block the Pop-Up on your web browser and turn off the automatic installing of Active-X controls and plug-ins on your Internet Explorer. It will come to your computer inside an OLE component or small DLL plug-ins and come to life inside a process. Some time, this life is inside a import table of a special process (for instance Yahoo Messenger or MSN Messenger). It can hook all Windows control and filter the API (oh my God!) Where did the password of my e-mail go? This is one possibility of a user level rootkit [7]. It can make a root to your computer and steal your important information. The Antivirus only can scan the file image; they lost their control over the runtime process injection. Therefore, when you survey on the web be careful and always use a strong firewall filter.
  777. How does a Yahoo Messenger hooker work?
  778.  
  779. I explain the practicable steps of how to write a Yahoo Messenger hooker:
  780.  
  781. Obtain the Yahoo Messenger handle with its class name by using FindWindow().
  782. Hide Copy Code
  783.  
  784. HWND hWnd = FindWindow("YahooBuddyMain", NULL);
  785.  
  786. Implement an injection to its process as similar as the previous section.
  787. Perform this injection on the import thunk of GetDlgItemText() to filter its members.
  788. Hide Copy Code
  789.  
  790. UINT GetDlgItemText( HWND hDlg,
  791. int nIDDlgItem,
  792. LPTSTR lpString,
  793. int nMaxCount);
  794.  
  795. Compare the dialog item ID, nIDDlgItem, with the specific ID to detect which item currently is in use. If the ID is found, hook the string with original GetDlgItemText().
  796. Hide Copy Code
  797.  
  798. CHAR pYahooID[127];
  799. CHAR pPassword[127];
  800.  
  801. switch(nIDDlgItem)
  802. {
  803. case 211: // Yahoo ID
  804. GetDlgItemText(hDlg, nIDDlgItem, pYahooID, 127); // for stealing
  805. // ...
  806. GetDlgItemText(hDlg, nIDDlgItem, lpString, nMaxCount);// Emulate
  807. //the original
  808. break;
  809.  
  810. case 212: // Password
  811. GetDlgItemText(hDlg, nIDDlgItem, pPassword, 127); // for stealing
  812. // ...
  813. GetDlgItemText(hDlg, nIDDlgItem, lpString, nMaxCount);// Emulate
  814. //the original
  815. break;
  816.  
  817. default:
  818. GetDlgItemText(hDlg, nIDDlgItem, lpString, nMaxCount);// Emulate
  819. //the original
  820. }
  821.  
  822. Figure 14 - Hooking Yahoo Messenger
  823.  
  824. Now I believe there is no safety. Someone can steal my Yahoo ID and its password with a few piece of code. We live in an insecure world!
  825. 7. Consequences
  826.  
  827. The Import Table is essentially part of a Windows executable file. The knowledge of the import table performance helps us to realize how API is requested during runtime. You can redirect the import table to another executable memory inside the current process memory to prevent reverse activity with your own PE loader and also to hook the API functions. It is possible to modify the import table of a process in runtime by freezing and unfreezing the process from outside; this disaster forces us to think more concerning security equipment (like antivirus, firewall, etc.). Nevertheless, they do not have any lasting benefits with the new methods which every day appear. Moreover, this conception aids us to establish our virtual machine monitor to run the Windows executable file inside a separated environment inside Windows or Linux. Consequently, I do not need a Windows System anymore to run my Windows EXE files!
  828.  
  829. Read more:
  830. Inject your code to a Portable Executable file, The Code Project, December 2005.
  831. Capturing Window Controls and Modifying their properties, The Code Project, February 2005.
  832. Three Ways to Inject Your Code into Another Process, Robert Kuster , The Code Project, July 2003.
  833. Documents:
  834. Undocumented Windows® 2000 Secrets: A Programmer's Cookbook, Sven B. Schreiber, Addison-Wesley, July 2001, ISBN 0-201-72187-2.
  835. Undocumented Functions for Microsoft® Windows® NT®/ 2000, Tomasz Nowak and others, NTInternals team, 1999-2005.
  836. Links:
  837. NTCore, System and Security team.
  838. Rootkit, The Online Rootkit Magazine.
  839.  
  840. License
  841.  
  842. This article, along with any associated source code and files, is licensed under The GNU General Public License (GPLv3)
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement