如何使用 ELF 文件中的偏移量设置断点,而不是虚拟地址?

逆向工程 数据库
2021-06-15 04:07:06

首先,生成一个简单的可执行文件。(忽略警告,可执行文件无论如何都会运行)

echo 'main(){puts("123");}'|gcc -x c - -o a

加载它gdb a,然后:

(gdb) info file
Symbols from "/home/user202729/PINCE/a".
Local exec file:
        `/home/user202729/PINCE/a', file type elf64-x86-64.
        Entry point: 0x520
        [...]

设置一些断点:

(gdb) b _start
Breakpoint 1 at 0x520
(gdb) b *0x520
Note: breakpoint 1 also set at pc 0x520.
Breakpoint 2 at 0x520
(gdb) info b
Num     Type           Disp Enb Address            What
1       breakpoint     keep y   0x0000000000000520 <_start>
2       breakpoint     keep y   0x0000000000000520 <_start>

(使用 gdb 的break命令)断点 1 使用语法设置,断点 2 使用.break functionbreak *address

运行程序:

(gdb) r
Starting program: /home/user202729/PINCE/a 
Warning:
Cannot insert breakpoint 2.
Cannot access memory at address 0x520

(gdb) info b
Num     Type           Disp Enb Address            What
1       breakpoint     keep y   0x0000555555554520 <_start>
2       breakpoint     keep y   0x0000000000000520 

问题:

  • 什么是0x00000000000005200x0000555555554520以上的叫什么?
  • 如何设置断点,只给定0x0000000000000520,不给定0x0000555555554520或符号名称?(以防万一可执行文件被stripped)
  • 或者,有没有办法获得_start(即,0x0000555555554520的加载地址以便在那里中断?
4个回答

基本问题是加载地址在操作系统实际映射文件之前未知的,如果程序已经在运行,到那时可能为时已晚。可能有几种解决方法:

  • 如果文件有符号,则使用符号断点。GDB 会自动将断点重新映射到实际运行时地址
  • 如果操作系统允许,请禁用 ASLR,以便加载地址与文件地址匹配,并且您无需移动断点
  • 修补输入文件以0xCC在所需位置插入断点操作码(例如对于 x86/x64)。由于意外的调试事件,GDB 将停止。
  • 在不存在的地址处添加断点(由 Zach Riggle 建议)。GDB 在无法设置请求的断点时会停止程序,此时您可以检查加载地址并调整您的断点。

  • 在最近的 GDB 版本中,该starti命令将在程序开始运行后立即停止执行。文档

'start' 命令的作用相当于在主程序开始时设置一个临时断点,然后调用 'run' 命令。

一些程序包含一个细化阶段,其中一些启动代码在调用主过程之前执行。这取决于用于编写程序的语言。例如,在 C++ 中,静态和全局对象的构造函数在调用 main 之前执行。因此,调试器有可能在到达主程序之前停止。但是,临时断点将保留以停止执行。

指定要提供给程序的参数作为“开始”命令的参数。这些参数将逐字提供给底层的“运行”命令。请注意,如果在后续调用 'start' 或 'run' 期间未提供任何参数,则将重用相同的参数。

有时需要在细化过程中调试程序。在这些情况下,使用 start 命令会过晚停止程序的执行,因为程序已经完成了细化阶段。在这些情况下,要么在运行程序之前在细化代码中插入断点,要么使用该starti命令。

不,事实上你误解了一些东西。:-)

该地址0x0000000000000520是从 ELF 文件开头.text_start过程所在部分的偏移量并且,地址0x0000555555554520对应于其中的部分的地址.text已经由操作系统映射加程序的偏移量:0x0000555555554000 + 0x0000000000000520.text段地址+主过程偏置)。

gdb(没有 a run)将只有该.text部分的偏移量,就好像它从零开始一样。然后,在运行该_start程序后,该.text部分将被操作系统重新映射到虚拟内存中。这种重新映射在调用加载器时发生。

而且,如果您观察到两个断点之间的差异,主要是因为第一个断点已设置在符号上_start,而另一个断点已设置在地址上。gdb但是,符号的重新映射将被考虑在内,地址是一个地址,它不会再改变。

例如,在gdb(no run) 中加载可执行文件后

(gdb) info files
Symbols from "/tmp/a".
Local exec file:
    `/tmp/a', file type elf64-x86-64.
    Entry point: 0x1050
    0x00000000000002a8 - 0x00000000000002c4 is .interp
    0x00000000000002c4 - 0x00000000000002e4 is .note.ABI-tag
    0x00000000000002e4 - 0x0000000000000308 is .note.gnu.build-id
    0x0000000000000308 - 0x000000000000032c is .gnu.hash
    0x0000000000000330 - 0x00000000000003d8 is .dynsym
    0x00000000000003d8 - 0x000000000000045a is .dynstr
    0x000000000000045a - 0x0000000000000468 is .gnu.version
    0x0000000000000468 - 0x0000000000000488 is .gnu.version_r
    0x0000000000000488 - 0x0000000000000548 is .rela.dyn
    0x0000000000000548 - 0x0000000000000560 is .rela.plt
    0x0000000000001000 - 0x0000000000001017 is .init
    0x0000000000001020 - 0x0000000000001040 is .plt
    0x0000000000001040 - 0x0000000000001048 is .plt.got
    0x0000000000001050 - 0x00000000000011c2 is .text
    0x00000000000011c4 - 0x00000000000011cd is .fini
    0x0000000000002000 - 0x0000000000002008 is .rodata
    0x0000000000002008 - 0x0000000000002044 is .eh_frame_hdr
    0x0000000000002048 - 0x0000000000002150 is .eh_frame
    0x0000000000003de8 - 0x0000000000003df0 is .init_array
    0x0000000000003df0 - 0x0000000000003df8 is .fini_array
    0x0000000000003df8 - 0x0000000000003fd8 is .dynamic
    0x0000000000003fd8 - 0x0000000000004000 is .got
    0x0000000000004000 - 0x0000000000004020 is .got.plt
    0x0000000000004020 - 0x0000000000004030 is .data
    0x0000000000004030 - 0x0000000000004038 is .bss

我们可以看到该_start过程正好位于本.text的开头

(gdb) disas 0x0000000000001050, 0x00000000000011c2
Dump of assembler code from 0x1050 to 0x11c2:
   0x0000000000001050 <_start+0>:   xor    %ebp,%ebp
   0x0000000000001052 <_start+2>:   mov    %rdx,%r9
   0x0000000000001055 <_start+5>:   pop    %rsi
   0x0000000000001056 <_start+6>:   mov    %rsp,%rdx
   0x0000000000001059 <_start+9>:   and    $0xfffffffffffffff0,%rsp
   0x000000000000105d <_start+13>:  push   %rax
   0x000000000000105e <_start+14>:  push   %rsp
   0x000000000000105f <_start+15>:  lea    0x15a(%rip),%r8  # 0x11c0 <__libc_csu_fini>
   0x0000000000001066 <_start+22>:  lea    0xe3(%rip),%rcx  # 0x1150 <__libc_csu_init>
   0x000000000000106d <_start+29>:  lea    0xc1(%rip),%rdi  # 0x1135 <main>
   0x0000000000001074 <_start+36>:  callq  *0x2f66(%rip)    # 0x3fe0
   0x000000000000107a <_start+42>:  hlt    
   0x000000000000107b:  nopl   0x0(%rax,%rax,1)
   0x0000000000001080 <deregister_tm_clones+0>: lea    0x2fa9(%rip),%rdi       
   0x0000000000001087 <deregister_tm_clones+7>: lea    0x2fa2(%rip),%rax 
   ...

而且,一旦我们点击“开始”命令(对应于tbreak main+ run):

(gdb) start
Temporary breakpoint 1 at 0x1139
Starting program: /tmp/a 

Temporary breakpoint 1, 0x0000555555555139 in main ()
(gdb) info files
Symbols from "/tmp/a".
Native process:
    Using the running image of child process 22585.
    While running this, GDB does not access memory from...
Local exec file:
    `/tmp/a', file type elf64-x86-64.
    Entry point: 0x555555555050
    0x00005555555542a8 - 0x00005555555542c4 is .interp
    0x00005555555542c4 - 0x00005555555542e4 is .note.ABI-tag
    0x00005555555542e4 - 0x0000555555554308 is .note.gnu.build-id
    0x0000555555554308 - 0x000055555555432c is .gnu.hash
    0x0000555555554330 - 0x00005555555543d8 is .dynsym
    0x00005555555543d8 - 0x000055555555445a is .dynstr
    0x000055555555445a - 0x0000555555554468 is .gnu.version
    0x0000555555554468 - 0x0000555555554488 is .gnu.version_r
    0x0000555555554488 - 0x0000555555554548 is .rela.dyn
    0x0000555555554548 - 0x0000555555554560 is .rela.plt
    0x0000555555555000 - 0x0000555555555017 is .init
    0x0000555555555020 - 0x0000555555555040 is .plt
    0x0000555555555040 - 0x0000555555555048 is .plt.got
    0x0000555555555050 - 0x00005555555551c2 is .text
    0x00005555555551c4 - 0x00005555555551cd is .fini
    0x0000555555556000 - 0x0000555555556008 is .rodata
    0x0000555555556008 - 0x0000555555556044 is .eh_frame_hdr
    0x0000555555556048 - 0x0000555555556150 is .eh_frame
    0x0000555555557de8 - 0x0000555555557df0 is .init_array
    0x0000555555557df0 - 0x0000555555557df8 is .fini_array
    0x0000555555557df8 - 0x0000555555557fd8 is .dynamic
    0x0000555555557fd8 - 0x0000555555558000 is .got
    0x0000555555558000 - 0x0000555555558020 is .got.plt
    0x0000555555558020 - 0x0000555555558030 is .data
    ....

您可以看到加载器重新映射了所有部分(并且添加了一些部分来处理动态库)。

如果你想了解更多关于 Linux 下可执行文件的加载过程,我强烈建议你看看Patrick Horgan 的这篇优秀文章我认为它将涵盖您可能对这个过程提出的大部分问题。

希望这有帮助。

您不能在 vanilla gdb 中执行此操作,但如果您正在使用pwndbg,则可以。

该命令被称为breakrvabrva简称。你可以这样使用它:

brva 0x520

但程序必须运行。

令人惊讶的是没有人提到这一点,如果您有符号,您可以使用它break * _start+9来实现相同的结果。例子

pwndbg> b * main+29
Breakpoint 1 at 0x9f7
pwndbg> r
Starting program: /tmp/a.out 

Breakpoint 1, 0x00005555555549f7 in main ()

但是在绝对地址的情况下,由于 PIE 的存在,加载二进制文件时会失败,并且 .text 的加载地址现在受 ASLR 约束。

pwndbg> disass main
Dump of assembler code for function main:
   0x00000000000009da <+0>: push   rbp
   0x00000000000009db <+1>: mov    rbp,rsp
   0x00000000000009de <+4>: push   rbx
   0x00000000000009df <+5>: sub    rsp,0x18
   0x00000000000009e3 <+9>: mov    edi,0x8
   0x00000000000009e8 <+14>:    call   0x890 <_Znwm@plt>
   0x00000000000009ed <+19>:    mov    rbx,rax
   0x00000000000009f0 <+22>:    mov    QWORD PTR [rbx],0x0
   0x00000000000009f7 <+29>:    mov    rdi,rbx
   0x00000000000009fa <+32>:    call   0xb08 <_ZN7VehicleC2Ev>
   0x00000000000009ff <+37>:    mov    QWORD PTR [rbp-0x18],rbx
   0x0000000000000a03 <+41>:    mov    rax,QWORD PTR [rbp-0x18]
   0x0000000000000a07 <+45>:    mov    rax,QWORD PTR [rax]
   0x0000000000000a0a <+48>:    mov    rax,QWORD PTR [rax]
   0x0000000000000a0d <+51>:    mov    rdx,QWORD PTR [rbp-0x18]
   0x0000000000000a11 <+55>:    mov    rdi,rdx
   0x0000000000000a14 <+58>:    call   rax
   0x0000000000000a16 <+60>:    mov    rax,QWORD PTR [rbp-0x18]
   0x0000000000000a1a <+64>:    mov    rax,QWORD PTR [rax]
   0x0000000000000a1d <+67>:    add    rax,0x8
   0x0000000000000a21 <+71>:    mov    rax,QWORD PTR [rax]
   0x0000000000000a24 <+74>:    mov    rdx,QWORD PTR [rbp-0x18]
   0x0000000000000a28 <+78>:    mov    rdi,rdx
   0x0000000000000a2b <+81>:    call   rax
   0x0000000000000a2d <+83>:    mov    eax,0x0
   0x0000000000000a32 <+88>:    add    rsp,0x18
   0x0000000000000a36 <+92>:    pop    rbx
   0x0000000000000a37 <+93>:    pop    rbp
   0x0000000000000a38 <+94>:    ret    
End of assembler dump.
pwndbg> b * main+29
Breakpoint 1 at 0x9f7
pwndbg> info breakpoints 
Num     Type           Disp Enb Address            What
1       breakpoint     keep y   0x00000000000009f7 <main+29>
pwndbg> b * 0x0000000000000a11
Breakpoint 2 at 0xa11
pwndbg> info breakpoints 
Num     Type           Disp Enb Address            What
1       breakpoint     keep y   0x00000000000009f7 <main+29>
2       breakpoint     keep y   0x0000000000000a11 <main+55>
pwndbg> r
Starting program: /tmp/a.out 
Warning:
Cannot insert breakpoint 2.
Cannot access memory at address 0xa11