我是新手,刚接触RE。
我有一个ELF 64位LSB可执行文件x86-64。我试图将其反转。
首先,我尝试使用

gdb ./filename
break 1


gdb说

在第1行上设置断点。 />
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 楼

获取入口点

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



使用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


使用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


使用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 ?? ()


好,所以我们停在了可执行文件的开始。目前,一切还没有准备就绪,需要进行所有设置。让我们看看可执行文件的第一步是什么:由于hlt进行线性扫描,gdb之后的只是垃圾。因此,只需忽略它。与此相关的是,我们正在呼叫__libc_start_main()(我不会在@plt上发表评论,因为这会使我们脱离问题的范围)。

实际上,过程__libc_start_main()会为使用libc动态库运行的进程初始化内存。并且,一旦完成,请跳至%rdi中的过程(通常是main()过程)。请参见下图以全面了解__libc_start_main()过程的作用[1]



因此,实际上,main()过程的地址位于0x4028c0。让我们在此地址处分解一些说明:

(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    
(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    q4312078qx388,%rsp
   0x4028d6:    mov    (%rsi),%rdi
   ...
xfffffffffffffff0,%rsp 0x00000000004048a9: push %rax 0x00000000004048aa: push %rsp 0x00000000004048ab: mov q4312078qx411ee0,%r8 0x00000000004048b2: mov q4312078qx411e50,%rcx 0x00000000004048b9: mov q4312078qx4028c0,%rdi 0x00000000004048c0: callq 0x4024f0 <__libc_start_main@plt> 0x00000000004048c5: hlt 0x00000000004048c6: nopw %cs:0x0(%rax,%rax,1) End of assembler dump.


,如果您查看它,的确是main()过程。因此,这是真正开始分析的地方。

警告语

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


用纯汇编语言编写并用gcc -nostdlib(或直接用gasnasm编译)的程序不会有第一个调用到__libc_start_main(),并将直接从入口点开始。因此,对于这些程序,_start()过程就是main()过程。实际上,重要的是要了解main()过程只是C语言引入的约定,它是要在程序中运行的第一个函数(由程序员编写)。当然,您可以找到以许多其他语言(例如Java,C ++等)复制的约定。但是,所有这些语言都源于C。
我们还非常依赖__libc_start_main()的工作方式方面的知识。并且,gcc团队如何设计此过程。因此,如果要分析的程序已使用其他编译器编译,则可能需要进一步研究此编译器以及它如何执行内存设置,然后再运行main()过程。

无论如何,如果您仔细阅读此答案,现在应该可以完全没有任何符号地查找程序。

最后,您可以阅读“ Linux x86程序启动或-如何深入了解main()?”,找到有关可执行文件启动的出色摘要。作者:帕特里克·赫根(Patrick Horgan)。