WinDbg : Trying To Find The Import Address Table (IAT) Of A Binary
So we see that we are getting the same output which ever method we prefer to use.
In the last article we learnt how to use the basic WinDbg commands we had learnt, to find out useful information within the loaded binary images. We will extend that study here, to see how other relevant fields output by the !dh command can be used. The focus of this article would be to use the WinDbg commands and to find the Import Address Table of the loaded image and to see the list of functions that it has imported.
Commands used in this article are:
For this example I picked the DLL called KernelBase. you can use any other DLL or executable of your choice.I also used the -f switch to trim the output of !dh to the relevant sections we need here.
The IAT is an array of pointers which are loaded by the PE Image Loader. The IAT is used primarily as form of a lookup table, which is used to call function present in other library modules (.DLLs). Since the executable module will not the the know memory addresses of the libraries and it's stored functions, it brings the in the purpose of the IAT. The IAT slots will be written with memory addresses by the linker.
The IAT is part of a larger data structure called the _IMAGE_IMPORT_DESCRIPTOR, which also contains another lookup table called the INT (Import Name Table), which is identical to the IAT.
Unfortunately, this structure is not exported in WinDbg, so we cannot use the dt command to see it. However, it is available in WinNt.H, so I will copy paste it for our convenience.
typedef struct _IMAGE_IMPORT_DESCRIPTOR {
union {
DWORD Characteristics; // 0 for terminating null import descriptor
DWORD OriginalFirstThunk; // RVA to original unbound IAT (PIMAGE_THUNK_DATA)
} DUMMYUNIONNAME;
DWORD TimeDateStamp; // 0 if not bound,
// -1 if bound, and real date\time stamp
// in IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT (new BIND)
// O.W. date/time stamp of DLL bound to (Old BIND)
DWORD ForwarderChain; // -1 if no forwarders
DWORD Name;
DWORD FirstThunk; // RVA to IAT (if bound this IAT has actual addresses)
} IMAGE_IMPORT_DESCRIPTOR;
The OriginalFirstThunk points to the Import Name Table and the FirstThunk points to the Import Address Table. We can find these in WinDbg, but we would need to use the !dh extension to do so.
kd> !dh kernelbase.dll -f
File Type: DLL
FILE HEADER VALUES
14C machine (i386)
4 number of sections
4CE7B8F0 time date stamp Sat Nov 20 17:32:56 2010
0 file pointer to symbol table
0 number of symbols
E0 size of optional header
2102 characteristics
Executable
32 bit word machine
DLL
OPTIONAL HEADER VALUES
10B magic #
9.00 linker version
42400 size of code
3E00 size of initialized data
0 size of uninitialized data
7DE0 address of entry point
1000 base of code
----- new -----
75d70000 image base
1000 section alignment
200 file alignment
3 subsystem (Windows CUI)
6.01 operating system version
6.01 image version
6.01 subsystem version
4A000 size of image
400 size of headers
4984C checksum
00040000 size of stack reserve
00001000 size of stack commit
00100000 size of heap reserve
00001000 size of heap commit
140 DLL characteristics
Dynamic base
NX compatible
18D4 [ 4E67] address [size] of Export Directory
406A8 [ 28] address [size] of Import Directory
46000 [ 530] address [size] of Resource Directory
0 [ 0] address [size] of Exception Directory
0 [ 0] address [size] of Security Directory
47000 [ 25D0] address [size] of Base Relocation Directory
43294 [ 38] address [size] of Debug Directory
0 [ 0] address [size] of Description Directory
0 [ 0] address [size] of Special Directory
0 [ 0] address [size] of Thread Storage Directory
1E110 [ 40] address [size] of Load Configuration Directory
278 [ 1C] address [size] of Bound Import Directory
1000 [ 65C] address [size] of Import Address Table Directory
0 [ 0] address [size] of Delay Import Directory
0 [ 0] address [size] of COR20 Header Directory
0 [ 0] address [size] of Reserved Directory
Using the dd command to dump the memory addresses of the Import Directory, we can see the relevant members mentioned earlier.
kd> dd 406A8+kernelbase
75db06a8 000406dc ffffffff ffffffff 000406d0
75db06b8 00001000 00000000 00000000 00000000
75db06c8 00000000 00000000 6c64746e 6c642e6c
75db06d8 9090006c 00040d38 00040d58 00040d78
75db06e8 00040d86 00040d90 00040da4 00040db2
75db06f8 00040dce 00040dee 00040e00 00040e12
75db0708 00040e2e 00040e42 00040e5e 00040e78
75db0718 00040e82 00040e9a 00040eb2 00040ec6
000406dc is the address of the OriginalFirstThunk and the 00001000 is the address of the FirstThunk.
Lets use the dd command to dump the OriginalFirstThunk.
kd> dd 000406dc+kernelbase
75db06dc 00040d38 00040d58 00040d78 00040d86
75db06ec 00040d90 00040da4 00040db2 00040dce
75db06fc 00040dee 00040e00 00040e12 00040e2e
75db070c 00040e42 00040e5e 00040e78 00040e82
75db071c 00040e9a 00040eb2 00040ec6 00040ede
75db072c 00040ef2 00040f0a 00040f36 00040f50
75db073c 00040f7a 00040f92 00040fa0 00040fb8
75db074c 00040fca 00040fda 00040ff4 00040ffe
The addresses shown are for the INT, and by using the dc command on one of the selected offsets with the base address, you will be able to view the name of the function.
kd> dc 00040d38+kernelbase
75db0d38 745204c2 696e556c 65646f63 69727453 ..RtlUnicodeStri
75db0d48 6f54676e 69736e41 69727453 0000676e ngToAnsiString..
75db0d58 74520268 736e416c 72745369 54676e69 h.RtlAnsiStringT
75db0d68 696e556f 65646f63 69727453 0000676e oUnicodeString..
75db0d78 765f074c 70776e73 746e6972 077e0066 L._vsnwprintf.~.
75db0d88 736d656d 00007465 74520346 6572466c memset..F.RtlFre
75db0d98 736e4165 72745369 00676e69 74520348 eAnsiString.H.Rt
75db0da8 6572466c 61654865 02da0070 446c7452 lFreeHeap...RtlD
We can display similar information with the FirstThunk, using FirstThunk can be a bit cleaner, since we can use the ln command with the address to find the exact function, which will be displayed cleanly with WinDbg.
kd> dd 00001000+kernelbase
75d71000 77bf9e8e 77bf572f 77c0caaa 77bd5340
75d71010 77bf3306 77bf2c6a 77bf9ac5 77bfa149
75d71020 77bf2dd6 77c3ea50 77c08b1b 77c07dfd
75d71030 77be6048 77be6678 77be54c8 77be6638
75d71040 77be55d8 77be66f8 77bf30fb 77bf7e74
75d71050 77be6398 77bf3063 77be63a8 77bf2fed
75d71060 77be5658 77be5cd8 77be6a18 77be5a08
75d71070 77be55a8 77be6018 77bd4500 77c0314e
kd> ln 77bf9e8e
(77bf9e8e) ntdll!RtlUnicodeStringToAnsiString | (77bf9f5b) ntdll!RtlpScanEnvironment
Exact matches:
ntdll!RtlUnicodeStringToAnsiString (<no parameter info>)
Now, going back to the original output of the !dh, we see that the offset 0x1000 is also shown as:
1000 [ 65C] address [size] of Import Address Table Directory
So this extension does give us the FirstThunk directly.
We can use the dds command to dump address range from the virtual address of the beginning of the IAT array. dds is equivalent to dd except that 's' stands for resolving symbols (if available). This means that if any of the addresses dumped in word format resolves to a known symbol, then the corresponding name would also be displayed. (Also read up on the dps command to see how it can also be used in this case).
kd> dd 406A8+kernelbase
75db06a8 000406dc ffffffff ffffffff 000406d0
75db06b8 00001000 00000000 00000000 00000000
75db06c8 00000000 00000000 6c64746e 6c642e6c
75db06d8 9090006c 00040d38 00040d58 00040d78
75db06e8 00040d86 00040d90 00040da4 00040db2
75db06f8 00040dce 00040dee 00040e00 00040e12
75db0708 00040e2e 00040e42 00040e5e 00040e78
75db0718 00040e82 00040e9a 00040eb2 00040ec6
000406dc is the address of the OriginalFirstThunk and the 00001000 is the address of the FirstThunk.
Lets use the dd command to dump the OriginalFirstThunk.
kd> dd 000406dc+kernelbase
75db06dc 00040d38 00040d58 00040d78 00040d86
75db06ec 00040d90 00040da4 00040db2 00040dce
75db06fc 00040dee 00040e00 00040e12 00040e2e
75db070c 00040e42 00040e5e 00040e78 00040e82
75db071c 00040e9a 00040eb2 00040ec6 00040ede
75db072c 00040ef2 00040f0a 00040f36 00040f50
75db073c 00040f7a 00040f92 00040fa0 00040fb8
75db074c 00040fca 00040fda 00040ff4 00040ffe
The addresses shown are for the INT, and by using the dc command on one of the selected offsets with the base address, you will be able to view the name of the function.
kd> dc 00040d38+kernelbase
75db0d38 745204c2 696e556c 65646f63 69727453 ..RtlUnicodeStri
75db0d48 6f54676e 69736e41 69727453 0000676e ngToAnsiString..
75db0d58 74520268 736e416c 72745369 54676e69 h.RtlAnsiStringT
75db0d68 696e556f 65646f63 69727453 0000676e oUnicodeString..
75db0d78 765f074c 70776e73 746e6972 077e0066 L._vsnwprintf.~.
75db0d88 736d656d 00007465 74520346 6572466c memset..F.RtlFre
75db0d98 736e4165 72745369 00676e69 74520348 eAnsiString.H.Rt
75db0da8 6572466c 61654865 02da0070 446c7452 lFreeHeap...RtlD
We can display similar information with the FirstThunk, using FirstThunk can be a bit cleaner, since we can use the ln command with the address to find the exact function, which will be displayed cleanly with WinDbg.
kd> dd 00001000+kernelbase
75d71000 77bf9e8e 77bf572f 77c0caaa 77bd5340
75d71010 77bf3306 77bf2c6a 77bf9ac5 77bfa149
75d71020 77bf2dd6 77c3ea50 77c08b1b 77c07dfd
75d71030 77be6048 77be6678 77be54c8 77be6638
75d71040 77be55d8 77be66f8 77bf30fb 77bf7e74
75d71050 77be6398 77bf3063 77be63a8 77bf2fed
75d71060 77be5658 77be5cd8 77be6a18 77be5a08
75d71070 77be55a8 77be6018 77bd4500 77c0314e
kd> ln 77bf9e8e
(77bf9e8e) ntdll!RtlUnicodeStringToAnsiString | (77bf9f5b) ntdll!RtlpScanEnvironment
Exact matches:
ntdll!RtlUnicodeStringToAnsiString (<no parameter info>)
1000 [ 65C] address [size] of Import Address Table Directory
So this extension does give us the FirstThunk directly.
We can use the dds command to dump address range from the virtual address of the beginning of the IAT array. dds is equivalent to dd except that 's' stands for resolving symbols (if available). This means that if any of the addresses dumped in word format resolves to a known symbol, then the corresponding name would also be displayed. (Also read up on the dps command to see how it can also be used in this case).
kd> dds kernelbase + 1000
75d71000 77bf9e8e ntdll!RtlUnicodeStringToAnsiString
75d71004 77bf572f ntdll!RtlAnsiStringToUnicodeString
75d71008 77c0caaa ntdll!_vsnwprintf
75d7100c 77bd5340 ntdll!memset
75d71010 77bf3306 ntdll!RtlFreeAnsiString
75d71014 77bf2c6a ntdll!RtlFreeHeap
75d71018 77bf9ac5 ntdll!RtlDeleteCriticalSection
75d7101c 77bfa149 ntdll!RtlInitializeCriticalSection
75d71020 77bf2dd6 ntdll!RtlAllocateHeap
75d71024 77c3ea50 ntdll!CsrVerifyRegion
75d71028 77c08b1b ntdll!CsrClientConnectToServer
75d7102c 77c07dfd ntdll!RtlCreateTagHeap
75d71030 77be6048 ntdll!ZwQueryInformationProcess
75d71034 77be6678 ntdll!NtSetInformationProcess
75d71038 77be54c8 ntdll!NtClose
75d7103c 77be6638 ntdll!ZwSetInformationFile
75d71040 77be55d8 ntdll!NtCreateIoCompletion
75d71044 77be66f8 ntdll!NtSetIoCompletion
75d71048 77bf30fb ntdll!RtlSetLastWin32Error
75d7104c 77bf7e74 ntdll!SbSelectProcedure
75d71050 77be6398 ntdll!NtRemoveIoCompletion
75d71054 77bf3063 ntdll!RtlDeactivateActivationContextUnsafeFast
75d71058 77be63a8 ntdll!NtRemoveIoCompletionEx
75d7105c 77bf2fed ntdll!RtlActivateActivationContextUnsafeFast
75d71060 77be5658 ntdll!ZwCreateNamedPipeFile
75d71064 77be5cd8 ntdll!NtOpenFile
75d71068 77be6a18 ntdll!ZwWaitForSingleObject
75d7106c 77be5a08 ntdll!ZwFsControlFile
75d71070 77be55a8 ntdll!ZwCreateEvent
75d71074 77be6018 ntdll!ZwQueryInformationFile
75d71078 77bd4500 ntdll!_allmul
75d7107c 77c0314e ntdll!RtlSetDaclSecurityDescriptor
So we see that we are getting the same output which ever method we prefer to use.
Try out these command sequences for other binary images to see the outputs. Beware, not all binaries import, though that would be an extremely rare occurrence. Also please do note, that in the above example I ignored the size of the import table 0x65C. The right way to dump would be to ask dds to only dump till the end of the import address table, else it might show garbage.
No comments:
Post a Comment