Understanding Import Tables #2 - Manually add imports - by Sunshine

What we need:

PEditor or LordPE
My SecMaker V2 or ZeroAdd by SantMat (or add section yourself)
Hex Editor (I use Hex Workshop)
Win32 API reference,
a bit knowledge about Win API / PE files and assembly...
I also suggest you to read my first tut about Import Tables (self-propaganda :-)

Download whole package here! (includes original & reversed file and this tut)


Hello! Welcome to my second article about Import Tables. This tutorial should explain how to add imports to a PE file. I hope I can explain it understandable without forgetting important details.
The tutorial structure is following :
  Part one: some theoretical words about adding imports manually
  Part two: we'll have a look at our simple file I've coded for this tut, then we'll add imports to it.
  Bonus Part: we'll inject some code which uses our new imports :-)

And as always: Sorry for my bad English, it's not my mother tongue! :-)

Part One - Theory

Ok, so what we have to do is to add an IMPORT_DESCRIPTOR array, for each dll we want to import one or more functions from.

+04 DWORD TimeDateStamp
+08 DWORD ForwarderChain
+0C DWORD Name
+10 DWORD FirstThunk

OriginalFirstThunk and FirstThunkValue can be the same DWORD which is a pointer to an array of so-called IMAGE_THUNK_DATAs. In fact it's an array of DWORDs, each one pointing to the hint and the name of an imported function. So we have also to create this array for each new dll. The DWORD called Name points to the name of our dll so we just have to store its name somewhere in file and put the rva of this position as DWORD Name. The values TimeDateStamp and ForwarderChain are not important; we set them to 0.
Well that is in fact all you have to know! Now you surely think: "bah I didn't get it!". But it will be clear when doing these steps in the example file, trust me!.
One thing I want to mention: it's nearly always better to add a new section, copy the old Import Table to this new position and add the new values there. You don't have to change anything in the already existing Import Table cause all pointer and values there will be still valid! (The names and the arrays are still in the same place in the file, they don't move!) Why this you ask? Cause then you have enough room for your changes... In most files there is no much space left after the Import Table and maybe you'll get into trouble so let's do it the easy way.

Part Two - Let's go...

- Get information and thinking about what is needed

Ok, first let's get an overview of our target file. Run AddImpExample.exe and you see a simple window (with a nice bitmap on top :-) containing some labels. Our job is to put the current directory of this file and your current windows directory into these labels with the "???". Ok, close it and run PEditor/LordPE (or any other PE tool, but I prefer them) and have a look at the Import Table: Only 5 api functions imported, from 2 different dlls, none is for us really interesting... But we remember for later: Import Table RVA is 0x3000 (= file offset 0x800) and size is 0x3C bytes. ImageBase is 0x00400000.

Well we should first think about what we really have to do. Obviously we need some functions for solving our task, so we'll add the following three functions: (info taken from Win32 Programmer's Reference)

Function Name DLL Name  
GetCurrentDirectory KERNEL32.DLL for retrieving Windows Directory
GetWindowsDirectory KERNEL32.DLL for retrieving current directory
SetDlgItemText USER32.DLL for setting text into these labels

As said before, we'll add a new section where we'll store our new import table..

- Add section and move existing Import Table there

Ok start SecMaker V2, load AddImpExample.exe and add 0x200 bytes (choose E0000060 as characteristics; name doesn't matter). Ok now we have enough space.
Next step is to copy the existing Import Table to our new section. Open our file in your favorite hex editor, go to offset 0x800 and copy the next 0x3C bytes. Now scroll down, you see the file is 0x9C00 bytes long, so our added section begins at offset 0x9C00 - 0x200 = 0x9A00. So insert there the Import Table; it should look like this now:

00009A00 3C30 0000 0000 0000 0000 0000 8830 0000 <0...........0..
00009A10 5830 0000 4430 0000 0000 0000 0000 0000 X0..D0..........
00009A20 D230 0000 6030 0000 0000 0000 0000 0000 .0..`0..........
00009A30 0000 0000 0000 0000 0000 0000 0000 0000 ................
(Note: cause you've inserted 0x3C bytes, you have to delete the last 0x3C bytes to keep filesize 0x9C00 bytes).
The 0x14 green bytes are the first IMAGE_IMPORT_DESCRIPTOR, the next 0x14 bytes are the seond IMAGE_IMPORT_DESCRIPTOR, the next 0x14 bytes (all zeros!) end the Import Table.

Now we do a little test: To see if it really works, we delete the old Import Table and adjust the Import Table values in the PE header to our new Import Table. So go back to offset 0x800 and overwrite the next 0x3C bytes with zeros. Then go to offset 0x100 (why? PE signature is at 0x80; Import Table RVA is stored 0x80 bytes after this signature), there you'll see 0030 0000 (= 3000). That's the RVA pointing to old Import Table. We have added our new one at file offset 0x9A00 which is RVA 0xD000. So change this 0030 0000 to 00D0 0000. Save it and run the file... yeah it works. Now we've already moved successfully the Import Table to our new section.

- Modify Import Table:
Ok our three functions we want to add are from 2 different dlls so we have to add 2 IMAGE_IMPORT_DESCRIPTORS.
Let's add everything for the first one; that means GetWindowsDirectory and GetCurrentDirectory from kernel32.dll:

00009A00 3C30 0000 0000 0000 0000 0000 8830 0000 <0...........0..
00009A10 5830 0000 4430 0000 0000 0000 0000 0000
00009A20 D230 0000 6030 0000
64D0 0000 0000 0000 .0..`0..`.......
0000 0000 8830 0000 64D0 0000 0000 0000 .....0..`.......
0000 0000 0000 0000 0000 0000 0000 0000 ................
0000 0000 0000 0000 0000 0000 0000 0000 ................
0000 0000 80D0 0000 97D0 0000 0000 0000 ................
0000 0000 0000 0000 0000 0000 0000 0000 ................
0000 4765 7443 7572 7265 6E74 4469 7265 ..GetCurrentDire
6374 6F72 7941 0000 0047 6574 5769 6E64 ctoryA...GetWind
6F77 7344 6972 6563 746F 7279 4100 0000 owsDirectoryA...

The new IMPORT_DESCRIPTOR array: First value (OrginalFirstThunk) is 0000D064. As said before, it's a pointer to another array of DWORDs. 0x0000D064 is file offset 0x9A64. There you see two DWORDs followed by a DWORD filled with zero which ends this array. First pointer is 0x0000D080 (= file offset 0x9A80). As you see it points to a WORD (two bytes long) which can be zero (that value is called 'hint'), then follows the name of the first function you want to import. The second pointer is 0x0000D097 (= file offset 0x9A97). Again two zero bytes, then the name of the seond function.
Next two DWORDs in IMPORT_DESCRIPTOR array are TimeDateStamp and ForwarderChain; the can be zero. Then follows the RVA to the name of the DLL which is kernel32.dll. But this string 'kernel32.dll' is already stored in the file at file offset 0x888 (RVA = 0x3088) so we use it.
Last DWORD in IMPORT_DESCRIPTOR array is FirstThunk which can be the same as OriginalFirstThunk.

Ok that was for kernel32.dll, but we need also SetDlgItemTextA from user32.dll... so let's do it again then it looks like:

00009A00 3C30 0000 0000 0000 0000 0000 8830 0000 <0...........0..
00009A10 5830 0000 4430 0000 0000 0000 0000 0000 X0..D0..........
00009A20 D230 0000 6030 0000 64D0 0000 0000 0000 .0..`0..d.......
00009A30 0000 0000 8830 0000 64D0 0000
70D0 0000 .....0..d...p...
0000 0000 0000 0000 D230 0000 70D0 0000 .........0..p...
0000 0000 0000 0000 0000 0000 0000 0000 ................
0000 0000 80D0 0000 97D0 0000 0000 0000 ................
B0D0 0000 0000 0000 0000 0000 0000 0000 ................
0000 4765 7443 7572 7265 6E74 4469 7265 ..GetCurrentDire
6374 6F72 7941 0000 0047 6574 5769 6E64 ctoryA...GetWind
6F77 7344 6972 6563 746F 7279 4100 0000 owsDirectoryA...
0000 5365 7444 6C67 4974 656D 5465 7874 ..SetDlgItemText
4100 0000 0000 0000 0000 0000 0000 0000 A..............

Our second new IMPORT_DESCRIPTOR value, now for user32.dll. Here our OriginalFirstThunk is 0x0000D070 (= file offset 0x00009A70). Here we have just one pointer in our array cause we only add one function. As always don't forget this empty DWORD value (4 zero bytes) to end this array. This pointer is 0x0000D0B0 (= file offset 0x00009AB0) and as before we have our two bytes long hint value (again zero) and after this follows our function name, zero-terminated.
TimeDateStamp and ForwarderChain are 0 again; RVA to name of dll is 0x000030D2 (= file offset 0x000008D2) cause at this offset is the string 'user32.dll' already stored. If this would not be the case, you could easily place it somewhere after SetDlgItemTextA and adjust the Name RVA.
FirstThunk is again the same as OriginalFirstThunk.
Note: The Import Table is ended with an completely empty IMPORT_DESCRIPTOR array. Don't forget that!

Finally you have to adjust the values in the PE Header. The RVA we have already set, but we have to correct the size. So go to offset 0x104 (0x84 bytes after the PE signature). As you see (and perhaps still know from the beginning) the old size is 0x3C. Why? One IMPORT_DESCRIPTOR array is 0x14 bytes long -> there were two for two dlls and one empty one for showing the end of the Import Table : 3 * 0x14 = 0x3C.
We have added two arrays, so now we have 4 dlls = 4 IMPORT_DESCRIPTOR arrays and one empty one : 5 * 0x14 bytes = 0x64. So change 0x0000003C to 0x00000064.

Ok everything done! File works and you can now use your new functions for your really cool reversing stuff. But how to call it? Easy :-) I'm sure you know it from debugging files in Ollydbg.
It is: FF15???????? : call dword ptr[????????]
But how to get this ???????? value, this opcode for your api. It's just ImageBase + the address of the pointer to the function name. In this file, opcode for GetCurrentDirectory would be 0x00400000 + 0x0000D064 = 0040D064, for GetWindowsDirectory 0x0040D068 and for SetDlgItemText 0x0040D070.

Short summary
1. [not a must!] Add a new zeropadded section, copy Import Table there and change Import Table Rva in the PE Header (0x80 bytes after "PE" signature).
2. For every dll you want to import an api function from : add a new IMPORT_DESCRIPTOR, let OriginalFirstThunk (=FirstThunk) point to an array of Rvas. They point to the hint of the function (can always be 0) followed by the name (you have also to store in file). TimeDateStamp and ForwarderChain can be 0; Name must contain Rva to dll name (you have also to store in file).
3. Append a zero-filled IMPORT_DESCRIPTOR array to end Import Table.
4. Adjust Import Table Size in PE Header (0x84 bytes after the "PE" signature); it's calculated : size = (number of dlls + 1) * 0x14.

Some important notes:
- As you may have noticed, every api function has a capital "A" at the end of its name. It's always like this and it's important!
- Moreover these names are case-sensitive: "messagebox" would be wrong, "MESSAGEBOX" would be wrong, "MessageBox" would be wrong.... "MessageBoxA" would be right! Get it? Good!
- These function name strings are zero-terminated so there must be always a 0x00 after each name.
- Look at these arrays with pointers to hint/name of a function: they must be terminated with a zero-filled DWORD.
- Import Table must be terminated with a completely zero-filled IMPORT_DESCRIPTOR array.
- Don't forget to adjust Import Table Rva and Size values in the PE Header.

Well, that was everything I wanted to say :-) Hope you are now able to add imports yourself. So go, download some nice reversemes and practice...


As some kind of bonus to this tut, I will inject some code to our AddImpExample.exe file to make it work, so let's start...
Opening our file in Ollydbg we see this at entry point:

00401060 >/$ 6A 00        PUSH 0 ; /pModule = NULL
00401062 |. FF15 58304000 CALL DWORD PTR DS:[<&KERNEL32.GetModuleH>; \GetModuleHandleA
00401068 |. 6A 00         PUSH 0 ; /lParam = NULL
0040106A |. 68 00104000   PUSH AddImpEx.00401000 ; |DlgProc = AddImpEx.00401000
0040106F |. 6A 00         PUSH 0 ; |hOwner = NULL
00401071 |. 6A 65         PUSH 65 ; |pTemplate = 65
00401073 |. 50            PUSH EAX ; |hInst
00401074 |. FF15 68304000 CALL DWORD PTR DS:[<&USER32.DialogBoxPar>; \DialogBoxParamA
0040107A |. 33C0          XOR EAX,EAX
0040107C \. C2 1000       RETN 10

The dialogbox is loaded from resource using DialogBoxParamA and as we know the second parameter pushed to this function is the address to the main dialog procedure; here it's 401000.

00401000 . 8B4424 08      MOV EAX,DWORD PTR SS:[ESP+8]
00401004 . 83F8 10        CMP EAX,10
00401007 . 74 45          JE SHORT AddImpEx.0040104E

Eax always contains the message sent to our window. Cause we want the labels be filled when the application starts, we have to check if eax is WM_INITDIALOG (= 110). So I overwrite the cmp and the je with a jump to down to some free space...

00401080 > 3D 10010000    CMP EAX,110 is message = WM_INITDIALOG?
00401085 . 74 0C          JE SHORT AddImpEx.00401093
yes then jump to our code
00401087 . 83F8 10        CMP EAX,10
cmp we had overwritten
0040108A .^E9 78FFFFFF    JMP AddImpEx.00401007
jump back
0040108F   00 DB 00
00401090   00 DB 00
00401091   00 DB 00
00401092   00 DB 00

00401093 > FF7424 04      PUSH DWORD PTR SS:[ESP+4]
save window handle
00401097 . 8D05 40214000  LEA EAX,DWORD PTR DS:[402140] load address of buffer
0040109D . 50             PUSH EAX ; /Buffer => AddImpEx.00402140
0040109E . 68 04010000    PUSH 104 ; |BufSize = 104 (260.)
004010A3 . FF15 64D04000  CALL DWORD PTR DS:[<&KERNEL32.GetCurrent>; \GetCurrentDirectoryA
004010A9 . 5B             POP EBX
load window handle in ebx
004010AA . 68 40214000    PUSH AddImpEx.00402140 ; /Text = ""
004010AF . 6A 0A          PUSH 0A ; |ControlID = A (10.)
004010B1 . 53             PUSH EBX ; |hWnd
004010B2 . FF15 70D04000  CALL DWORD PTR DS:[<&USER32.SetDlgItemTe>; \SetDlgItemTextA
004010B8 . 53             PUSH EBX
004010B9 . 68 04010000    PUSH 104 ; /BufSize = 104 (260.)
004010BE . 8D05 40214000  LEA EAX,DWORD PTR DS:[402140] ; |
004010C4 . 50             PUSH EAX ; |Buffer => AddImpEx.00402140
004010C5 . FF15 68D04000  CALL DWORD PTR DS:[<&KERNEL32.GetWindows>; \GetWindowsDirectoryA
004010CB . 5B             POP EBX load window handle in ebx
004010CC . 68 40214000    PUSH AddImpEx.00402140 ; /Text = ""
004010D1 . 6A 0B          PUSH 0B ; |ControlID = B (11.)
004010D3 . 53             PUSH EBX ; |hWnd
004010D4 . FF15 70D04000  CALL DWORD PTR DS:[<&USER32.SetDlgItemTe>; \SetDlgItemTextA
004010DA .^E9 7CFFFFFF    JMP AddImpEx.0040105B jump to end of dialog proc

First I've saved the main window handle which is stored at [esp+4]. I found that out by looking at the other code in the file. Then we load the address where we wanna store the returned string by GetCurrentDirectoryA. Why 402140 you wanna know? Well have a look at the section table of this file: .data section begins at 0x400000 (ImageBase)+ 0x2000 (Virtual Address) = 0x402000 and is 0x135 bytes long. So at 0x402140 we have some free room to store our directory string. 260 bytes (dec) should be enough for the buffer. The rest should be self-explainatory. The IDs of the labels I got with The Customizer.

Till my next tut (if there will be one....)
Questions, criticism? Mail me!
Sunshine, October 2003

This site is part of Sunshine's Homepage