4

I just started working my way to reversing Windows binaries and I stumbled upon the Import Address Table. When reversing a particular DLL I encountered many thunk-functions which all supposedly referenced the IAT. From my experience on Linux I guessed that this is somewhat similar to the procedure linkage table (or rather the global offset table I suppose). Based on that I would assume that the linking process is similar, though I cannot seem to find detailed information on that. Any help would be appreciated.

Furthermore I was wondering whether you could resolve these thunks without ever running the binary. In particular because it is in fact a DLL that I am analyzing the information to resolve these should be available already? Though I cannot really make sense out of the information available.

Just to be sure I am not completely off, here is an example of what I am talking about:

void THUNK_FUN_18002d97a(void)

{
  (*_DAT_18005ba68)();
  return;
}

Memory at the address (in section .data):

0x18005ba68: 94 00 00 06 00 00 00 00

Edit: Thanks for the input. I now feel like I misunderstood the purpose of the IAT. So consider the following scenario: We have a PE executable A which imports symbols from a DLL B.

  1. The import directory table is used in A, whereas in B a corresponding entry has to be found in the export directory table. Is that correct?
  2. In the DLL (B) I am investigating the thunks mentioned are neither imported nor exported symbols. So what may I be witnessing?
  3. The overall process must be looking something like this:
    1. A is executed. All needed DLLs are searched and linked (this is called binding in this context?)
    2. This causes B to be actually loaded at some address. Now symbols from B in A can be resolved (using the import directory table). Is B necessarily position independent? I read about preferred base addresses and conditional re-location of the whole binary if it cannot be matched. Is this still correct?
    3. I still do not see a point why the second layer jump table I encountered is needed.
3
  • is the binary available anywhere? is it x86 or x64 code?
    – Igor Skochinsky
    Dec 17 '20 at 23:03
  • it is not publicly available. It is x64. I tried re-creating similar patterns but I failed. Apparently it is some kind of C++/.NET stuff, though I am not sure how it could have been created. The DLL implements a GUI which is definitely C#/.NET though it also seems to contain some "normal" C++ code. I just discovered that the C# part is perfectly decompilable using dotPeek
    – milck
    Dec 18 '20 at 13:23
  • oh if it's C++/CLI that explains some things. I'll investigate and update
    – Igor Skochinsky
    Dec 18 '20 at 20:58
2

PART 1

The PE import thunks do not work the same way as ELF PLT. There is no dynamic resolver invoked on the first call but all import pointers are resolved at the process startup ahead of time (similar to LD_BIND_NOW). The pointers are grouped in a GOT-like Import Address Table (IAT) and the metadata with the details about the DLLs and symbols imported is stored in the Import Directory which is referred to by the PE header.

To recover the symbols you need to parse the import directory. The details can be found in the official PE format specification.

PART2

After your edit, it seems you're dealing with a native to managed code thunk.

I've done the following experiment to produce a mixed executable:

m.cpp (Managed code):

using namespace System;

void hello()
{
    String^ str = "Hello World";
    Console::WriteLine(str);
}

n.cpp (Native code):

void hello();

void main()
{
  hello();
}

Compile and link:

cl /c /clr /Zi m.cpp
cl /c /Zi n.cpp
link /debug /out:mixed.exe m.obj n.obj

After disassembling the native part (and getting symbols thanks to the PDB), I could observe the following:

.text:00007FF798E81090 main proc near     
.text:00007FF798E81090 sub     rsp, 28h
.text:00007FF798E81094 call    ?hello@@YAXXZ ; hello(void)
.text:00007FF798E81099 xor     eax, eax
.text:00007FF798E8109B add     rsp, 28h
.text:00007FF798E8109F retn
.text:00007FF798E8109F main endp

Following the call:

.nep:00007FF798ECC000 ?hello@@YAXXZ proc near
.nep:00007FF798ECC000 jmp     short loc_7FF798ECC00A
.nep:00007FF798ECC002 ud2
.nep:00007FF798ECC004 jmp     cs:__m2mep@?hello@@$$FYAXXZ
.nep:00007FF798ECC00A loc_7FF798ECC00A:
.nep:00007FF798ECC00A jmp     cs:__mep@?hello@@$$FYAXXZ
.nep:00007FF798ECC00A ?hello@@YAXXZ endp

And finally, following the jmp:

.data:00007FF798EE7000 __m2mep@?hello@@$$FYAXXZ dq 6000001h
.data:00007FF798EE7008 __mep@?hello@@$$FYAXXZ dq 6000001h

The value 6000001 is a CLR Token. The high byte byte is the token kind, or the metadata table index, in this case 0x6 meaning Method. Looking it up in a .NET viewer such as ILDASM or dnSpy we can see that it refers to the managed method "hello" with the RVA 000010a0. Going to that address we see:

.text:00007FF798E810A0 ?hello@@$$FYAXXZ:
.text:00007FF798E810A0 add     esi, [rax]
.text:00007FF798E810A2 add     [rax], eax
.text:00007FF798E810A4 sldt    word ptr [rax]
.text:00007FF798E810A7 add     [rdx], al
.text:00007FF798E810A9 db 2 dup(0), 11h
.text:00007FF798E810AC hello:
.text:00007FF798E810AC adc     al, 0Ah
.text:00007FF798E810AE jb      short loc_7FF798E810B1
.text:00007FF798E810B0 db 0
.text:00007FF798E810B1 loc_7FF798E810B1:
.text:00007FF798E810B1 add     [rax+0Ah], dh
.text:00007FF798E810B4 dd 22806h
.text:00007FF798E810B8 db 0, 0Ah, 2Ah, 0CCh

It does not make any sense as x64 code so this is obviously CLI bytecode and should be checked with a .net decompiler. Strangely, none of those I tried seems to show the function but I managed to get the IL disassembly from ILDASM:

.method /*06000001*/ assembly static void modopt([mscorlib/*23000001*/]System.Runtime.CompilerServices.CallConvCdecl/*01000001*/) 
        hello() cil managed
{
  .vtentry 1 : 1
  // Code size       15 (0xf)
  .maxstack  1
  .locals /*11000002*/ ([0] string str)
  IL_0000:  ldnull
  IL_0001:  stloc.0
  IL_0002:  ldstr      "Hello World" /* 70000001 */
  IL_0007:  stloc.0
  IL_0008:  ldloc.0
  IL_0009:  call       void [mscorlib/*23000001*/]System.Console/*01000003*/::WriteLine(string) /* 0A000002 */
  IL_000e:  ret
} // end of global method hello

You can probably follow a similar approach and look up the token 0x06000094 in the metadata tables or IL disassembly to figure out the destination of the jump in managed code.

Random observations:

The segment .nep seems to mean "native entrypoint"

The prefix __mep in the token name probably means "managed entrypoint".

2
  • thanks, though I feel a little more confused now. Take a look at my edits if you would
    – milck
    Dec 17 '20 at 20:12
  • 1
    @milck see part2
    – Igor Skochinsky
    Dec 19 '20 at 1:02

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.