在 Windows x86_64 DLL 中静态恢复 thunk

逆向工程 视窗 dll 我在
2021-07-07 04:44:10

我刚开始用我的方式来反转 Windows 二进制文件,我偶然发现了导入地址表。在反转特定的 DLL 时,我遇到了许多 thunk 函数,它们都被认为都引用了 IAT。根据我在 Linux 上的经验,我猜这有点类似于过程链接表(或者更确切地说是我认为的全局偏移表)。基于这一点,我认为链接过程是相似的,但我似乎无法找到相关的详细信息。任何帮助,将不胜感激。

此外,我想知道您是否可以在不运行二进制文件的情况下解决这些 thunk。特别是因为它实际上是一个 DLL,我正在分析解决这些问题的信息应该已经可用?虽然我无法从可用的信息中真正理解。

为了确保我没有完全离开,这里是我正在谈论的一个例子:

void THUNK_FUN_18002d97a(void)

{
  (*_DAT_18005ba68)();
  return;
}

地址处的内存(在节中.data):

0x18005ba68: 94 00 00 06 00 00 00 00

编辑:感谢您的投入。我现在觉得我误解了 IAT 的目的。所以考虑以下场景:我们有一个 PE 可执行文件 A,它从 DLL B 中导入符号。

  1. 导入目录表是在A中使用,而在B中的对应的条目具有在被发现出口目录表那是对的吗?
  2. 在 DLL (B) 中,我正在调查提到的 thunk 既不是导入也不是导出符号。那么我可以见证什么?
  3. 整个过程必须看起来像这样:
    1. A 被执行。搜索并链接所有需要的 DLL(这在此上下文中称为绑定?)
    2. 这会导致 B 实际加载到某个地址。现在可以解析 A 中 B 中的符号(使用导入目录表)。B 一定是位置无关的吗?如果无法匹配,我阅读了整个二进制文件的首选基地址和条件重定位。这仍然正确吗?
    3. 我仍然没有看到为什么需要我遇到的第二层跳转表。
1个回答

第1部分

PE 导入 thunk 的工作方式与 ELF PLT 不同。第一次调用时没有调用动态解析器​​,但所有导入指针都会在进程启动时提前解析(类似于LD_BIND_NOW)。指针被分组在一个类似 GOT 的导入地址表 (IAT) 中,包含有关 DLL 和导入符号的详细信息的元数据存储在由 PE 标头引用的导入目录中。

要恢复符号,您需要解析导入目录。详细信息可以在官方PE 格式规范中找到。

第2部分

编辑后,您似乎正在处理本机到托管代码 thunk

我已经完成了以下实验以生成混合可执行文件

m.cpp (托管代码):

using namespace System;

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

n.cpp (本机代码):

void hello();

void main()
{
  hello();
}

编译并链接:

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

拆卸本地部分(并通过 PDB 获得符号)后,我可以观察到以下内容:

.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

通话后:

.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

最后,遵循jmp

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

值 6000001 是一个CLR 令牌高字节字节是令牌种类,或元数据表索引,在本例中为 0x6 表示Method在 .NET 查看器(如 ILDASM 或 dnSpy)中查找它,我们可以看到它引用了 RVA 的托管方法“hello” 000010a0转到那个地址,我们看到:

.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

它作为 x64 代码没有任何意义,因此这显然是 CLI 字节码,应该使用 .net 反编译器进行检查。奇怪的是,我尝试的那些似乎都没有显示该功能,但我设法从 ILDASM 获得了 IL 反汇编:

.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

您可能可以遵循类似的方法并在元数据表或 IL 反汇编中查找令牌 0x06000094 以找出托管代码中跳转的目的地。

随机观察:

该段.nep似乎意味着“本机入口点”

__mep令牌名称中的前缀可能意味着“托管入口点”。