没有动态符号表,但共享库中的方法解析有效

逆向工程 小精灵
2021-06-13 09:48:48

我想找到如何仅在 GDB 中识别对共享库的调用。在剥离的二进制文件上,我找不到动态符号表:

$> objdump -tT crackme-01

crackme-01:     file format elf32-i386

objdump: crackme-01: not a dynamic object
SYMBOL TABLE:
no symbols

DYNAMIC SYMBOL TABLE:
no symbols

但仍然存在动态库解析,例如在调用之前strcmp

0x08048330 in ?? ()
...
0xb7ff2420 in _dl_runtime_resolve () from /lib/ld-linux.so.2
0xb7fec020 in _dl_fixup () from /lib/ld-linux.so.2
0xb7ff6678 in __x86.get_pc_thunk.bx () from /lib/ld-linux.so.2
0xb7fec033 in _dl_fixup () from /lib/ld-linux.so.2
0xb7fe7600 in _dl_lookup_symbol_x () from /lib/ld-linux.so.2
0xb7fe6df9 in do_lookup_x () from /lib/ld-linux.so.2
0xb7fedb70 in _dl_name_match_p () from /lib/ld-linux.so.2
...
0xb7ff5d74 in strcmp () from /lib/ld-linux.so.2

我的问题是符号表是如何隐藏readelf但在执行过程中仍然使用的?

3个回答

事实上,他们可能使用了ElfKickersstrip包中软件根据文件:sstrip README

sstrip 是一个小型实用程序,用于删除 ELF 文件末尾不属于程序内存映像的内容。

大多数 ELF 可执行文件都使用程序头表和节头表构建。然而,只有前者是操作系统加载、链接和执行程序所必需的。sstrip 尝试提取 ELF 头、程序头表及其内容,将其他所有内容留在位桶中。它只能删除文件最后出现的部分,在要保存的部分之后。然而,这几乎总是包括节头表,以及一些不涉及程序加载和执行的其他节。

应该注意的是,大多数使用 ELF 文件的程序都依赖于节头表作为文件内容的索引。因此,当使用没有节头表的可执行文件时,诸如 gdb 和 objdump 之类的实用程序通常具有有限的功能。其他一些公用事业公司可能根本拒绝与他们合作。

事实上,sstrip从可执行文件中删除所有节信息并保持可执行文件仍然可用。

但是让我们看看我们可以达到的不同级别是条带。

无剥离

让我们考虑一个没有剥离全部的程序(类似于问题中看到的那个程序)。

$> objdump -tT ./crackme

./crackme:     file format elf32-i386

SYMBOL TABLE:
08048134 l    d  .interp            00000000              .interp
08048148 l    d  .note.ABI-tag      00000000              .note.ABI-tag
08048168 l    d  .note.gnu.build-id 00000000              .note.gnu.build-id
0804818c l    d  .gnu.hash          00000000              .gnu.hash
080481ac l    d  .dynsym            00000000              .dynsym
0804822c l    d  .dynstr            00000000              .dynstr
...
080497dc g       .bss               00000000              _end
08048390 g     F .text              00000000              _start
080485f8 g     O .rodata            00000004              _fp_hw
080497d8 g       .bss               00000000              __bss_start
08048490 g     F .text              00000000              main
00000000  w      *UND*              00000000              _Jv_RegisterClasses
080497d8 g     O .data              00000000              .hidden __TMC_END__
00000000  w      *UND*              00000000              _ITM_registerTMCloneTable
080482f4 g     F .init              00000000              _init

DYNAMIC SYMBOL TABLE:
00000000      DF *UND*              00000000  GLIBC_2.0   strcmp
00000000      DF *UND*              00000000  GLIBC_2.0   read
00000000      DF *UND*              00000000  GLIBC_2.0   printf
00000000      DF *UND*              00000000  GLIBC_2.0   system
00000000  w   D  *UND*              00000000              __gmon_start__
00000000      DF *UND*              00000000  GLIBC_2.0   __libc_start_main
080485fc g    DO .rodata            00000004  Base        _IO_stdin_used

剥离与 strip

$> strip ./crackme-striped
$> objdump -tT ./crackme-striped 

./crackme-striped:     file format elf32-i386

SYMBOL TABLE:
no symbols

DYNAMIC SYMBOL TABLE:
00000000     DF *UND*   00000000  GLIBC_2.0   strcmp
00000000     DF *UND*   00000000  GLIBC_2.0   read
00000000     DF *UND*   00000000  GLIBC_2.0   printf
00000000     DF *UND*   00000000  GLIBC_2.0   system
00000000  w  D  *UND*   00000000              __gmon_start__
00000000     DF *UND*   00000000  GLIBC_2.0   __libc_start_main
080485fc g   DO .rodata 00000004  Base        _IO_stdin_used

如您所见,strip应用时动态符号仍然存在其余的只是干净地移除。

剥离与 sstrip

最后,让我们看看使用sstrip.

$> sstrip ./crackme-sstriped
$> objdump -tT ./crackme-sstriped 

./crackme-sstriped:     file format elf32-i386

objdump: ./crackme-sstriped: not a dynamic object
SYMBOL TABLE:
no symbols

DYNAMIC SYMBOL TABLE:
no symbols

如您所见,所有符号,包括动态符号都已被删除。事实上,所有指向 PLT 的符号都被移除,地址被保留为静态地址。这是_start程序序言的示例,首先是所有符号:

 0x8048390 <_start>:    xor    %ebp,%ebp
 0x8048392 <_start+2>:  pop    %esi
 0x8048393 <_start+3>:  mov    %esp,%ecx
 0x8048395 <_start+5>:  and    $0xfffffff0,%esp
 0x8048398 <_start+8>:  push   %eax
 0x8048399 <_start+9>:  push   %esp
 0x804839a <_start+10>: push   %edx
 0x804839b <_start+11>: push   $0x80485e0
 0x80483a0 <_start+16>: push   $0x8048570
 0x80483a5 <_start+21>: push   %ecx
 0x80483a6 <_start+22>: push   %esi
 0x80483a7 <_start+23>: push   $0x8048490
 0x80483ac <_start+28>: call   0x8048380 <__libc_start_main@plt>
 0x80483b1 <_start+33>: hlt    

然后,stripep:

0x8048390:  xor    %ebp,%ebp
0x8048392:  pop    %esi
0x8048393:  mov    %esp,%ecx
0x8048395:  and    $0xfffffff0,%esp
0x8048398:  push   %eax
0x8048399:  push   %esp
0x804839a:  push   %edx
0x804839b:  push   $0x80485e0
0x80483a0:  push   $0x8048570
0x80483a5:  push   %ecx
0x80483a6:  push   %esi
0x80483a7:  push   $0x8048490
0x80483ac:  call   0x8048380 <__libc_start_main@plt>
0x80483b1:  hlt    

最后,sstrip版本:

0x8048390:  xor    %ebp,%ebp
0x8048392:  pop    %esi
0x8048393:  mov    %esp,%ecx
0x8048395:  and    $0xfffffff0,%esp
0x8048398:  push   %eax
0x8048399:  push   %esp
0x804839a:  push   %edx
0x804839b:  push   $0x80485e0
0x80483a0:  push   $0x8048570
0x80483a5:  push   %ecx
0x80483a6:  push   %esi
0x80483a7:  push   $0x8048490
0x80483ac:  call   0x8048380
0x80483b1:  hlt    

令人惊讶的是,可执行文件仍然可以运行。让我们比较一下strip之后留下的 ELF 标头sstrip(如 Igor 建议的那样)。首先,经过一个strip

$> readelf -l crackme-striped 

Elf file type is EXEC (Executable file)
Entry point 0x8048390
There are 8 program headers, starting at offset 52

Program Headers:
  Type           Offset   VirtAddr   PhysAddr   FileSiz MemSiz  Flg Align
  PHDR           0x000034 0x08048034 0x08048034 0x00100 0x00100 RWE 0x4
  INTERP         0x000134 0x08048134 0x08048134 0x00013 0x00013 RWE 0x1
      [Requesting program interpreter: /lib/ld-linux.so.2]
  LOAD           0x000000 0x08048000 0x08048000 0x006b4 0x006b4 RWE 0x1000
  LOAD           0x0006b4 0x080496b4 0x080496b4 0x00124 0x00128 RWE 0x1000
  DYNAMIC        0x0006c0 0x080496c0 0x080496c0 0x000e8 0x000e8 RWE 0x4
  NOTE           0x000148 0x08048148 0x08048148 0x00044 0x00044 RWE 0x4
  GNU_EH_FRAME   0x000600 0x08048600 0x08048600 0x00024 0x00024 RWE 0x4
  GNU_STACK      0x000000 0x00000000 0x00000000 0x00000 0x00000 RWE 0x10

 Section to Segment mapping:
   Segment Sections...
   00     
   01     .interp 
   02     .interp .note.ABI-tag .note.gnu.build-id .gnu.hash .dynsym .dynstr .gnu.version .gnu.version_r .rel.dyn .rel.plt .init .plt .text .fini .rodata .eh_frame_hdr .eh_frame 
   03     .init_array .fini_array .jcr .dynamic .got .got.plt .data .bss 
   04     .dynamic 
   05     .note.ABI-tag .note.gnu.build-id 
   06     .eh_frame_hdr 
   07     

然后是通过的版本sstrip

$> readelf -l ./crackme-sstriped 

Elf file type is EXEC (Executable file)
Entry point 0x8048390
There are 8 program headers, starting at offset 52

Program Headers:
  Type           Offset   VirtAddr   PhysAddr   FileSiz MemSiz  Flg Align
  PHDR           0x000034 0x08048034 0x08048034 0x00100 0x00100 RWE 0x4
  INTERP         0x000134 0x08048134 0x08048134 0x00013 0x00013 RWE 0x1
      [Requesting program interpreter: /lib/ld-linux.so.2]
  LOAD           0x000000 0x08048000 0x08048000 0x006b4 0x006b4 RWE 0x1000
  LOAD           0x0006b4 0x080496b4 0x080496b4 0x00124 0x00128 RWE 0x1000
  DYNAMIC        0x0006c0 0x080496c0 0x080496c0 0x000e8 0x000e8 RWE 0x4
  NOTE           0x000148 0x08048148 0x08048148 0x00044 0x00044 RWE 0x4
  GNU_EH_FRAME   0x000600 0x08048600 0x08048600 0x00024 0x00024 RWE 0x4
  GNU_STACK      0x000000 0x00000000 0x00000000 0x00000 0x00000 RWE 0x10

如您所见,部分的名称也已被删除(如 README 文件中所述)。

请注意,应用sstrip经过upx渲染的可执行文件使最终的可执行文件无法使用(我尝试过)。

所以看起来你的可执行文件毕竟使用了共享对象。我将使用我的精神力量并冒险猜测它已被压缩为类似UPX 的东西

UPX 接受一个可执行文件(静态或动态),压缩它的头和段,并添加一个小的解包存根。生成的可执行文件对于操作系统来说看起来像是静态的。

但是,当它运行时,解包器存根将段和头解包到内存中,并在原始程序需要时加载动态解释器所以在运行时,文件确实使用了动态符号(通过解释器)。

编辑:如 perror 所示,该文件可能实际上并未打包,而只是将其节表剥离了。虽然这不会影响它的可运行性,但它确实破坏了许多工具,包括,显然,objdump readelf. 您可能想尝试我们的扩展文件转储程序(EFD)工具,即使节表已被剥离,它也可以打印动态符号表。

EDIT2readelf毕竟似乎可以处理此类文件。尝试运行readelf -D -s <file.elf>(我第一次尝试,--dyn-syms但没有奏效。)

区别如下:

符号表包含其代码在二进制本身中的函数的符号。

您上面列出的第二个代码片段显示了其代码位于共享库中的二进制文件之外的函数的名称。