Hello,

the following is taken from a book page describing DLL imported functions and how their calls are resolved:

So how does the loader tell your application where to find an imported function? The solution is fiendishly clever. If you think about where the calls to XXXXX go, you'll soon realize that each call must go to the same address: the address where XXXXX is loaded into memory. Of course, your application can't know this address ahead of time, so instead, all your XXXXX calls get routed through a single, indirect address. When the program loader loads your executable and its dependent DLLs, the loader fixes up this one indirect address so that it corresponds to the final load address of XXXXX. The compiler makes this indirect addressing work by generating a jump to the indirect address any time your code calls the imported function. This indirect address is stored in the .idata (or import) section of the executable. If you import through __declspec(dllimport), instead of being an indirect jump, the code is an indirect call, thus saving a couple of instructions per function call.


I have looked at a few applications using a debugger, and some (notepad, regedit, explorer) not have a .idata section, and in fact all calls to imported functions "point" directly to the imported function's code.

Other applications have their imported function calls point to an area such as the following:

004088A0 JMP DWORD PTR DS:[<&KERNEL32.HeapAlloc>]
004088A6 JMP DWORD PTR DS:[<&KERNEL32.HeapFree>]
004088AC JMP DWORD PTR DS:[<&KERNEL32.LoadLibraryA>]
004088B2 JMP DWORD PTR DS:[<&KERNEL32.RaiseException>]
004088B8 JMP DWORD PTR DS:[<&KERNEL32.RtlUnwind>]
004088BE JMP DWORD PTR DS:[<&KERNEL32.SetConsoleCtrlHandler>]
004088C4 JMP DWORD PTR DS:[<&KERNEL32.SetFilePointer>]
004088CA JMP DWORD PTR DS:[<&KERNEL32.SetHandleCount>]
004088D0 JMP DWORD PTR DS:[<&KERNEL32.TlsAlloc>]

Is the first an example of the use of __declspec(dllimport)?
Is the second an example of a import address table?


yaa
Posted on 2004-01-01 08:31:42 by yaa
No that's not an IAT. That's a jump table. Some assembler/compiler call the IAT directly while some assembler/compile call a jmp table which in turn jmp to the IAT.
Posted on 2004-01-01 08:59:37 by roticv
I'm not quite sure what you are asking, but I can tell you that once an application is loaded by a debugger, the imports have been loaded and the import table jumps have been filled. What most debuggers do is allow the os to load the application as if it was going to be executed, and break the execution at the entrypoint. Take another look at one of these applications in a hex editor and you will see that the import table jumps are not defined until the app is loaded.
Posted on 2004-01-01 09:01:15 by Homer
EvilHomer2k,

the debugger I talk about is just a means for me to try to give a more tangible meaning to what I read, but it is not the object of my question.

What I was asking about is a clearer exaplaination and maybe an example of what is described in the last part of the fragment take from my book and exactly:

This indirect address is stored in the .idata (or import) section of the executable. If you import through __declspec(dllimport), instead of being an indirect jump, the code is an indirect call, thus saving a couple of instructions per function call.

I see some applications don't have an .idata section ... how do these applications (those missing a .idata section) resolve imported functions???

I also noticed that applications without a .idata section are missing what roticv calls a jump table, I was wondering if there is a relationship between the two things.

And finally, is the import take located in the .idata section???


yaa
Posted on 2004-01-01 09:25:54 by yaa
I see some applications don't have an .idata section ... how do these applications (those missing a .idata section) resolve imported functions???

There is no rule saying that the section containing the IAT has to be called .idata. Using .idata is for import table is probably compiler/assembler specific. For example the following code (fasm) has the import table in .import



format PE GUI 4.0
entry start

include 'C:\fasm\include\win32a.inc'

;section '.data' data readable writeable

section '.roticv' data readable writeable executable
start:
push _handler
xor eax, eax
push dword [fs:eax]
mov [fs:eax], esp
int3
@@:
jmp @B
int 1
@@:
jmp @B
nop
nop
nop
nop
invoke MessageBox,0,0,0,0
invoke ExitProcess,0

_handler:
mov ecx, [esp+08h]
mov eax,[esp+0Ch]
cmp ecx, 80000003h
jz _int3
add dword[eax+0b8h], 4
xor eax, eax
retn
_int3:
add dword[eax+0B8h], 3
xor eax, eax
retn

section '.import' import data readable writeable

library kernel,'KERNEL32.DLL',\
user,'USER32.DLL'

import kernel,\
ExitProcess, 'ExitProcess'

import user,\
MessageBox, 'MessageBoxA'

Posted on 2004-01-01 09:40:40 by roticv
Ok, supose that you have a proc/function in your code, you will call:

invoke myCode, uno, dos, tres
and you use invoke MessageBox, NULL, "hi", "more hi", MB_OK

for the first a expansion is:
push tres
push dos
push uno
call myCode ;this is a direct address call

for the second, because you dont know where to jump:
push MB_OK
push xAddress
push xAddress2
push NULL
extern MessageBox
call

Also in a obj file, whe have import xFunction X.dll

That is a expansion of a nasm macro (normally used, called invoke too), like you see, you mark that a function is extern (you dont have it in your code), but when you call it you dont call directly a address like in example one, you call a unknow address by the direction of the symbol.

Now the full call is resolved to some like: KERNEL32.HeapAlloc (in a debuger) now this 'name' or data is provided by the loader, it say that call dword[&KERNEL32.HeapAlloc], is a call to the direction of that symbol (provided by the loader) and say that is 100, see that the debuger say & before the name (or symbol), then following what C say "& mean the direction of ____", hope you get the idea.

Nice day or night.
Posted on 2004-01-01 10:50:05 by rea
If you import through __declspec(dllimport), instead of being an indirect jump, the code is an indirect call, thus saving a couple of instructions per function call.???

Anyone?
Posted on 2004-01-01 12:27:27 by yaa
Yaa, never depend on section names - follow the PE headers.

Indirect jump vs call... "normal" code would be like...

MessageBox(hwnd, "blah", "bluh", MB_OK); - would generate


push MB_OK
push szBluh
push szBlah
push
call j_MessageBoxA@16
...
j_MessageBoxA@16: jmp


the __dllimport method would generate code like this:


push MB_OK
push szBluh
push szBlah
push [hwnd]
call [imp__MessageBoxA@16]


...

Which version is most optimal depends on the amount of calls you have for each imported function.
Posted on 2004-01-01 15:20:18 by f0dder
f0dder,

when coding I use __declspec(dllimport) to import custom functions. Never heard/though that one could use it with system functions. I even wonder how it can be done.

yaa
Posted on 2004-01-01 15:36:28 by yaa
It should be doable - it really only depends on how the IAT is used, and the linker should discard the jmp-indiret table if only call-indirect is used at the call sites.
Posted on 2004-01-02 14:12:42 by f0dder
mmmmhhh, things aren't exactly clear.
How do an indirect jump and an indirect call look like???

yaa
Posted on 2004-01-03 05:51:11 by yaa
indirect call/jmp are FF15<imm32> and FF25<imm32> - where the imm32 is the absolute value to call/jmp indirectly. Ie, if the opcode is FF1504404000 you get 'call d.[000404004]'.

Normal call/jmp has a *relative* address in the opcode - thus the opcode generated for "call/jmp <addr>" will be different depending on where the call/jmp is made, even for the same address. Addr can be encoded as either 8bit or 32bit immediate, depending of course on the distance to the target - and it's a signed value.

When you do "call MessageBox", you get the EIP-relative form. It's possible to handle imports this way too, iirc the LE format did it this way? (or perhaps I'm confusing it with a exe format I did back when LE was in fashion), but it requires a fixup record for each and every call in your exe - wasteful.

The IAT method in windows is smarter - you only fill the pointers in the IAT.

It also makes it easier to redirect imports, which is a nice thing.

Indirect call/jmp look like the following, depending on assembler syntax...

"call dword ptr [_imp__MessageBoxA@16]" or "call dword [_imp__MessageBoxA@16]" (the name in the brackets can differ too, it will depend on which kind of import libraries you are using - this example is standard MS style. It will always be a dword, though, and it's in your importa table, filled out by the windows EXE loader).
Posted on 2004-01-03 13:23:34 by f0dder
f0dder,

you introduced "normal" call/jmp and got me a little lost.
What are "normal" call/jmp??? I though "indirect" call/jmp are called this way simply because they call or jump to an address that contains the real address thus the "indirect" but they are by all means "normal" call/jmp. You talk instead of "normal" call/jmp as something different .... and in fact they use relative addresses as you say instead of absolute ones. Why do they use relative addresses?

Thx.


yaa
Posted on 2004-01-04 05:49:04 by yaa
Well, of course all the call/jmp are "normal", I chose to call the non-indirect versions "normal" because I'd say they're used more often than the indirect versions - sorry for any confusion :)

I guess the EIP-relative encoding was chosen because it allows the short 8-bit signed offset when the distance from call site to destination is short enough. Furthermore, it allows blocks of code without absolute references to be relocated without any code fixups.
Posted on 2004-01-04 07:24:08 by f0dder
btw, how is a relative address distinguished from an absolute one as the argument to a jmp or call???

yaa
Posted on 2004-01-04 13:32:59 by yaa
Well, a relative offset is always used in a direct near jump or call, in all other cases an absolute offset is used.
Posted on 2004-01-04 13:56:49 by Sephiroth3
Sephiroth3, I can use an absolute address for a near call or jump too.

yaa
Posted on 2004-01-04 14:39:05 by yaa
no you can't.

Well, your assembler might support it, but it will be encoded as a relative call/jmp, since there's no direct call/jmp on x86 taking an absolute offset.
Posted on 2004-01-04 14:41:42 by f0dder