为什么 GOT 和 PLT 仍然存在于 Linux 静态剥离二进制文件中?

逆向工程 linux 二元分析 小精灵
2021-06-16 03:00:18

我正在查看静态链接的 linux x86 剥离二进制文件。我注意到有.got.plt部分。

我想知道静态链接的二进制文件需要什么gotplt部分?任何人 ?

2个回答

关于ELF二进制文件在内部是如何工作的,程序员有很多不知道的事情而且,不幸的是,除了广泛涵盖该主题的两三个之外,几乎没有可靠的参考资料。许多工具(链接器、加载器、汇编器、调试器等)对你们大多数人来说仍然是个谜。当涉及到连接体和装载机,主要参考是链接器和加载由John R.莱文(http://linker.iecc.com/)。另一个可靠的信息来源是官方的ELF二进制格式文档。但这些只是对某些或大多数技术如何工作的介绍。

现在,这是您问题的答案(为什么GOTPLT部分仍包含在静态ELF二进制文件中?):PERFORMANCE

更多解释...假设你有这个 C 代码:

    #include <stdio.h>
    #include <string.h>

    int main(int argc, char **argv)
    {
        char str[1024];

        strcpy(str, argv[1]);
        printf("%s\n", str);

        return 0;
    }

无需成为天才就可以弄清楚它所做的只是将命令行参数复制到字符串中并打印出来。这是main汇编中函数:

     000000000040105e <main>:
     40105e:   55                      push   rbp
     40105f:   48 89 e5                mov    rbp,rsp
     401062:   48 81 ec 10 04 00 00    sub    rsp,0x410
     401069:   89 bd fc fb ff ff       mov    DWORD PTR [rbp-0x404],edi
     40106f:   48 89 b5 f0 fb ff ff    mov    QWORD PTR [rbp-0x410],rsi
     401076:   48 8b 85 f0 fb ff ff    mov    rax,QWORD PTR [rbp-0x410]
     40107d    48 83 c0 08             add    rax,0x8
     401081:   48 8b 10                mov    rdx,QWORD PTR [rax]
     401084:   48 8d 85 00 fc ff ff    lea    rax,[rbp-0x400]
     40108b:   48 89 d6                mov    rsi,rdx
     40108e:   48 89 c7                mov    rdi,rax
     401091:   e8 3a f2 ff ff          call   4002d0 <__rela_iplt_end+0x38>
     401096:   48 8d 85 00 fc ff ff    lea    rax,[rbp-0x400]
     40109d:   48 89 c7                mov    rdi,rax
     4010a0:   e8 fb 09 00 00          call   401aa0 <_IO_puts>
     4010a5:   b8 00 00 00 00          mov    eax,0x0
     4010aa:   c9                      leave
     4010ab:   c3                      ret
     4010ac:   0f 1f 40 00             nop    DWORD PTR [rax+0x0]

请注意,在地址处,401091您调用了存储在 中的函数PLT(标签更具表现力)。令人惊讶的是,在这个地址4002d0你会发现一个跳转到存储在GOT(见下文)中的内容。

     4002d0:   ff 25 f2 2f 2c 00       jmp QWORD PTR [rip+0x2c2ff2] # 6c32c8 <_GLOBAL_OFFSET_TABLE_+0x20>

在 中的那个确切位置GOT,您会发现对存储在以下部分中的函数的调用:

    00000000004187d0 <handle_amd>:
    4187d0:    53                      push   rbx
    4187d1:    b8 00 00 00 80          mov    eax,0x80000000
    4187d6:    0f a2                   cpuid
    4187d8:    81 ff c4 00 00 00       cmp    edi,0xc4
    4187de:    7f 40                   jg     418820 <handle_amd+0x50>
    4187e0:    31 d2                   xor    edx,edx
    4187e2:    81 ff bf 00 00 00       cmp    edi,0xbf
    4187e8:    0f 9d c2                setge  dl
    4187eb:    81 ea fb ff ff 7f       sub    edx,0x7ffffffb
    4187f1:    39 c2                   cmp    edx,eax
    4187f3:    77 2b                   ja     418820 <handle_amd+0x50>
    4187f5:    89 d0                   mov    eax,edx
    4187f7:    0f a2                   cpuid
    4187f9:    81 ff bb 00 00 00       cmp    edi,0xbb
    4187ff:    7e 27                   jle    418828 <handle_amd+0x58>
    418801:    81 ef bc 00 00 00       sub    edi,0xbc
    418807:    83 ff 08                cmp    edi,0x8
    41880a:    0f 87 48 01 00 00       ja     418958 <handle_amd+0x188>
    418810:    48 8d 35 c9 0b 08 00    lea    rsi,[rip+0x80bc9]        # 4993e0 <__PRETTY_FUNCTION__.4767+0x20>
    418817:    48 63 04 be             movsxd rax,DWORD PTR [rsi+rdi*4]
    41881b:    48 01 c6                add    rsi,rax
    41881e:    ff e6                   jmp    rsi
    418820:    31 c0                   xor    eax,eax
    418822:    5b                      pop    rbx
    418823:    c3                      ret

首先,查看分区的名称。其次,如果您仔细查看代码,您会注意到该函数识别 CPU - 通过剖析cpuid指令 ( 4187d6and 4187f7)的返回值- (更准确地说是微架构和其他功能,例如缓存大小,...)您正在运行ELF二进制文件,然后决定哪种实现套件最适合该配置。这样strcpy,无论您使用何种架构(英特尔:Nehalem、Sandy Bridge、Ivy Bridge、Haswell、...;AMD:Phenom、Opteron、... ; ...) 请记住,这些快速实现已经针对每个可能的目标架构进行了手动优化和微调。

这就是静态二进制文件中使用PLTGOT部分的目的ELF

现在,如果您想自己调查这个问题,您应该C使用-static-g3(调试符号)标志使用 GCC 4.9 版(这是我使用的版本)编译上面代码然后,使用objdump-D开关反汇编二进制文件以获得所有ELF部分。然后,您可以浏览所有部分并探索汇编代码。您还可以使用运行二进制文件gdb并在关键位置设置断点并逐步运行程序。

@yaspr 的回答很好,因为这个问题得到了一些“从可信和/或官方来源寻找答案”的赏金,让我在这里尝试提供一些参考。

一般来说,在我的理解,.PLT.GOT表要求在这里由于性能问题。

BinCFI发表于去年排名前 2 的计算机安全会议。

由于 PLT 存根的目的是调度跨模块调用,因此目标似乎只能从其他模块导出符号。然而,最近版本的 gcc 支持一种新的函数类型,称为 gnu 间接函数,它允许一个函数有许多不同的实现,在运行时根据 CPU 类型等因素选择最合适的一个目前,许多 glibc 低级函数如 memcpystrcmpstrlen 都使用此功能。为了支持此功能,库导出了一个选择器函数,该函数在运行时选择将使用众多实现中的哪一个。这些实现函数可能根本不导出。

此处列出了有关如何利用此功能的其他一些参考资料。