反转 ELF 64 位 LSB 可执行文件,x86-64,gdb

逆向工程 数据库 小精灵 x64
2021-06-20 01:24:29

我是新手,刚刚进入 RE。我得到了一个 ELF 64 位 LSB 可执行文件,x86-64。我正在努力扭转它。首先,我尝试使用在第 1 行设置断点

gdb ./filename
break 1

gdb 说

No symbol table is loaded.  Use the "file" command.

OKie 所以发出文件命令

(gdb) file filename
Reading symbols from /media/Disk/filename...(no debugging symbols found)...done.

如何设置断点以查看执行情况..?

1个回答

获取入口点

如果没有有用的符号,首先需要找到可执行文件的入口点。有几种方法可以做到(取决于您拥有的工具或您最喜欢的工具):

  1. 使用 readelf

    $> readelf -h /bin/ls
    ELF Header:
    Magic:   7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00 
    Class:                             ELF64
    Data:                              2's complement, little endian
    Version:                           1 (current)
    OS/ABI:                            UNIX - System V
    ABI Version:                       0
    Type:                              EXEC (Executable file)
    Machine:                           Advanced Micro Devices X86-64
    Version:                           0x1
    Entry point address:               0x40489c
    Start of program headers:          64 (bytes into file)
    Start of section headers:          108264 (bytes into file)
    Flags:                             0x0
    Size of this header:               64 (bytes)
    Size of program headers:           56 (bytes)
    Number of program headers:         9
    Size of section headers:           64 (bytes)
    Number of section headers:         27
    Section header string table index: 26
    

    所以,入口点地址是0x40489c.

  2. 使用 objdump

    $> objdump -f /bin/ls
    
    /bin/ls:     file format elf64-x86-64
    architecture: i386:x86-64, flags 0x00000112:
    EXEC_P, HAS_SYMS, D_PAGED
    start address 0x000000000040489c
    

    同样,入口点是0x000000000040489c.

  3. 使用 gdb

    $> gdb /bin/ls
    GNU gdb (GDB) 7.6.2 (Debian 7.6.2-1)
    ...
    Reading symbols from /bin/ls...(no debugging symbols found)...done.
    (gdb) info files
    Symbols from "/bin/ls".
    Local exec file:
        `/bin/ls', file type elf64-x86-64.
        Entry point: 0x40489c
        0x0000000000400238 - 0x0000000000400254 is .interp
        0x0000000000400254 - 0x0000000000400274 is .note.ABI-tag
        0x0000000000400274 - 0x0000000000400298 is .note.gnu.build-id
        0x0000000000400298 - 0x0000000000400300 is .gnu.hash
        0x0000000000400300 - 0x0000000000400f18 is .dynsym
        0x0000000000400f18 - 0x00000000004014ab is .dynstr
        0x00000000004014ac - 0x00000000004015ae is .gnu.version
        0x00000000004015b0 - 0x0000000000401640 is .gnu.version_r
        0x0000000000401640 - 0x00000000004016e8 is .rela.dyn
        0x00000000004016e8 - 0x0000000000402168 is .rela.plt
        0x0000000000402168 - 0x0000000000402182 is .init
        0x0000000000402190 - 0x00000000004028a0 is .plt
        0x00000000004028a0 - 0x0000000000411f0a is .text
        0x0000000000411f0c - 0x0000000000411f15 is .fini
        0x0000000000411f20 - 0x000000000041701c is .rodata
        0x000000000041701c - 0x0000000000417748 is .eh_frame_hdr
        ...
    

    入口点仍然是0x40489c

定位main程序

知道入口点后,您可以在其上设置断点并开始查找main过程。因为,你必须知道所有的程序都会由一个_start()负责初始化进程内存和加载动态库的过程开始。事实上,这第一个过程是 Unix 世界的惯例。

这个初始化过程到底是做什么的,遵循起来非常乏味,而且在大多数情况下,根本没有兴趣理解你的程序。main()只有在所有内存设置完毕并准备就绪后程序才会启动。

让我们看看如何做到这一点(我假设可执行文件已经用 编译gcc):

(gdb) break *0x40489c
Breakpoint 1 at 0x40489c
(gdb) run
Starting program: /bin/ls 
warning: Could not load shared library symbols for linux-vdso.so.1.

Breakpoint 1, 0x000000000040489c in ?? ()

好的,所以我们在可执行文件的最开始处停了下来。此时,什么都没有准备好,一切都需要设置。让我们看看可执行文件的第一步是什么:

(gdb) disas 0x40489c,+50
Dump of assembler code from 0x40489c to 0x4048ce:
=> 0x000000000040489c:  xor    %ebp,%ebp
   0x000000000040489e:  mov    %rdx,%r9
   0x00000000004048a1:  pop    %rsi
   0x00000000004048a2:  mov    %rsp,%rdx
   0x00000000004048a5:  and    $0xfffffffffffffff0,%rsp
   0x00000000004048a9:  push   %rax
   0x00000000004048aa:  push   %rsp
   0x00000000004048ab:  mov    $0x411ee0,%r8
   0x00000000004048b2:  mov    $0x411e50,%rcx
   0x00000000004048b9:  mov    $0x4028c0,%rdi
   0x00000000004048c0:  callq  0x4024f0 <__libc_start_main@plt>
   0x00000000004048c5:  hlt    
   0x00000000004048c6:  nopw   %cs:0x0(%rax,%rax,1)
End of assembler dump.

后面的hlt只是由于 执行的线性扫描而获得的垃圾gdb所以,忽略它。相关的是我们正在调用的事实__libc_start_main()(我不会对此发表评论,@plt因为它会将我们拖出问题的范围)。

实际上,该过程__libc_start_main()为使用libc动态库运行的进程初始化内存并且,一旦完成,跳转到位于%rdi(通常是main()程序)中程序。看下图有一个全局视图是什么__libc_start_main()程序[ 1 ]

ELF 完整调用图

因此,确实,该main()过程的地址位于0x4028c0让我们在这个地址反汇编一些指令:

(gdb) x /10i 0x4028c0
   0x4028c0:    push   %r15
   0x4028c2:    push   %r14
   0x4028c4:    push   %r13
   0x4028c6:    push   %r12
   0x4028c8:    push   %rbp
   0x4028c9:    mov    %rsi,%rbp
   0x4028cc:    push   %rbx
   0x4028cd:    mov    %edi,%ebx
   0x4028cf:    sub    $0x388,%rsp
   0x4028d6:    mov    (%rsi),%rdi
   ...

而且,如果你看看它,这确实是main()程序。所以,这是真正开始分析的地方。

警告的话

即使这种查找main()程序的方式在大多数情况下都有效。您必须知道我们强烈依赖以下假设:

  1. 用纯汇编语言编写并用gcc -nostdlib(或直接用gasnasm编译的程序不会有第一次调用,而是__libc_start_main()直接从入口点开始。因此,对于这些程序,_start()程序就是main()程序。事实上,重要的是要理解,main()过程只是 C 语言引入的约定,作为程序中要运行的第一个函数(由程序员编写)。当然,您可以发现该约定在许多其他语言中也得到了复制,例如 Java、C++ 和其他语言。但是,所有这些语言都源自 C。

  2. 我们还强烈依赖对__libc_start_main()工作方式的了解并且,这个程序是如何由gcc团队设计的因此,如果您正在分析的程序是用另一个编译器编译的,您可能需要进一步研究这个编译器以及它如何在运行该main()过程之前执行内存设置

无论如何,如果您仔细阅读此答案,您现在应该能够找到一个完全没有符号的程序。

最后,您可以通过阅读Patrick Horgan 的Linux x86 程序启动或 - 我们如何到达main()找到关于启动可执行文件的出色总结