Understanding
Import Tables #2 - Manually add imports
-
by Sunshine
|
What
we need: |
PEditor or LordPE |
Download whole package here! (includes original & reversed file and this tut)
Introduction
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.
+0
|
DWORD |
OriginalFirstThunk |
+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
X0..D0..........
00009A20 D230 0000 6030 0000 64D0
0000 0000 0000 .0..`0..`.......
00009A30 0000 0000 8830 0000 64D0 0000
0000 0000 .....0..`.......
00009A40 0000 0000
0000 0000 0000
0000 0000 0000
................
00009A50 0000 0000
0000 0000 0000
0000 0000 0000
................
00009A60 0000 0000
80D0 0000 97D0 0000
0000 0000
................
00009A70 0000 0000
0000 0000 0000
0000 0000 0000
................
00009A80 0000 4765 7443 7572 7265 6E74 4469 7265
..GetCurrentDire
00009A90 6374 6F72 7941 0000
0047 6574 5769 6E64 ctoryA...GetWind
00009AA0 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...
00009A40 0000 0000 0000 0000 D230 0000 70D0 0000
.........0..p...
00009A50 0000 0000 0000 0000 0000 0000 0000 0000
................
00009A60 0000 0000
80D0 0000 97D0
0000 0000 0000
................
00009A70 B0D0 0000 0000 0000
0000 0000 0000
0000 ................
00009A80 0000 4765
7443 7572 7265
6E74 4469 7265
..GetCurrentDire
00009A90 6374 6F72
7941 0000 0047
6574 5769 6E64
ctoryA...GetWind
00009AA0 6F77 7344
6972 6563 746F
7279 4100 0000
owsDirectoryA...
00009AB0 0000 5365 7444 6C67 4974 656D 5465 7874
..SetDlgItemText
00009AC0 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...
BONUS PART
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