0

我试图弄清楚跳转表(将子程序名称与其地址配对的数据表)在可执行文件中的位置,以及它是否基于语言、编译器,或者是否有标准放置可能在 PE 的标头中/ELF 二进制文件。它是哪一个?我怎样才能找到这些表格或找到有关它们放置位置的文档?


到目前为止我尝试了什么:

首先,我阅读了 PE / ELF 标头的每个部分,并不确定哪一个是跳转表。

因为我发现学习编译器如何工作的前景非常令人生畏,所以我想到的最直接的解决方法是使用子例程反汇编二进制文件并找到引用该跳转目标和其他的二进制文件的一部分。在编译为 ELF 格式的C 程序开始时,我发现了以下部分:

0000000000001020 <.plt>:
    1020:   ff 35 1a 2f 00 00       pushq  0x2f1a(%rip)        # 3f40 <_GLOBAL_OFFSET_TABLE_+0x8>
    1026:   f2 ff 25 1b 2f 00 00    bnd jmpq *0x2f1b(%rip)        # 3f48 <_GLOBAL_OFFSET_TABLE_+0x10>
    102d:   0f 1f 00                nopl   (%rax)
    1030:   f3 0f 1e fa             endbr64 
    1034:   68 00 00 00 00          pushq  $0x0
    1039:   f2 e9 e1 ff ff ff       bnd jmpq 1020 <.plt>
    103f:   90                      nop
    1040:   f3 0f 1e fa             endbr64 
    1044:   68 01 00 00 00          pushq  $0x1
    1049:   f2 e9 d1 ff ff ff       bnd jmpq 1020 <.plt>
    ...

我认为这可能是跳转表的样子,这些地址是各种动态链接库的偏移量。我之前曾看到一个带有 .plt 的 ELF 标头部分的引用,但最初并不清楚它是否是一个跳转表。进一步研究表明:

PLT代表Procedure Linkage Table,简单地说,用于调用在链接时地址未知的外部过程/函数,并在运行时由动态链接器解析。

GOT 代表 Global Offsets Table,同样用于解析地址。PLT 和 GOT 以及其他重定位信息都在本文中详细解释

我仍在努力寻找本节中的哪个跳转(如果有)指向我程序中的子例程。也许那个 GOT 是我接下来需要看的地方。


如果需要更多上下文,这就是我要问的原因:

我一直在研究二进制补丁,尤其是用于跟踪恶意软件行为的挂钩技术,以及恶意软件如何阻止这种跟踪。钩子(只是将控制流重定向到中间函数,然后重定向到最初预期的目标的指令)可以去很多地方,例如修补到内存中的共享二进制文件(libs)中,甚至修补到内核子例程中,但如果我理解正确地,它们有时也被直接注入到可执行二进制文件中的子例程中。

我正在研究的是攻击者阻止这些钩子放置在二进制文件中的可能性。假设攻击者从恶意软件的执行开始就使用了一个不确定的(从受害者的角度来看)跳转目的地。现在假设分析师或自动启发式分析工具试图在沙盒环境中反汇编程序以确定程序的行为,但程​​序为该跳转目标地址访问的 Web 服务器将仅返回一个入口点对程序在未来某个日期执行时的恶意控制流。在此之前,它会返回一个地址,使程序以良性方式运行。这是教科书式的规避,由于 x86/-64 架构的可变长度特性而成为可能。我最近发表了一篇根据我的理解将问题集可视化的图表。

但是如果编译器在程序中建立了跳转表,分析人员或威胁检测系统仍然可以知道入口点的位置以跳转到并分析这些子程序。一旦这些子例程在目标条件下在运行时执行,还可以分析寄存器以找到执行例程的地址(x86 调用约定包括此信息,以便子例程知道返回到哪里),并从分析人员还可以知道其他有效的指令边界以开始反汇编。

我对编译器的工作原理几乎一无所知,并且已经阅读了 PE / ELF 文件头的规范,但也许我错过了一些东西。我真的很感激一个指向正确方向的指针。

4

1 回答 1

0

正如@MSalters 在评论中指出的那样,跳转表不一定是将子例程名称与其地址配对的数据表。通常它是单个子程序中的控制流,特别是switch/case语句。考虑 Duff 的设备,一个可以预期跳转表的经典示例(如果不是没有表的计算跳转):

void send(int *to, int *from, size_t count)
{
    size_t n = (count + 7) / 8;
    switch (count % 8) {
    case 0: do { *to = *from++;
    case 7:      *to = *from++;
    case 6:      *to = *from++;
    case 5:      *to = *from++;
    case 4:      *to = *from++;
    case 3:      *to = *from++;
    case 2:      *to = *from++;
    case 1:      *to = *from++;
            } while (--n > 0);
    }
}

MSVC编译如下,跳转目标为case标签:

       mov     eax, DWORD PTR $LN17@send[r10+r8*4]
        add     rax, r10
        jmp     rax
$LN10@send:
        mov     eax, DWORD PTR [rdx]
        add     rdx, 4
        mov     DWORD PTR [rcx], eax
$LN11@send:
        mov     eax, DWORD PTR [rdx]
        add     rdx, 4
        mov     DWORD PTR [rcx], eax

https://godbolt.org/z/e64vKzq4d


PE 文件格式没有定义任何与跳转表相关的内容。有一些指针表,这种指针表称为导入函数/数据的导入地址表,或 TLS 回调表,但没有跳转表。

MSVC 碰巧将跳转表放在代码部分,靠近 using 函数。它使跳转表成为只读且更难覆盖。

尽管跳转表没有必需的部分,但它们仍可能以某种方式进行注释。32 位 x86 将使用绝对地址(而不是 RIP 相对地址),因此如果重定位表完全存在,这些跳转表将作为连续指针范围生成重定位表。不确定 SEH 或控制流保护数据,它也可能包含跳转表注释。

于 2021-12-12T17:52:14.970 回答