我有一个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
(或直接用gas
或nasm
编译)的程序不会有第一个调用到__libc_start_main()
,并将直接从入口点开始。因此,对于这些程序,_start()
过程就是main()
过程。实际上,重要的是要了解main()
过程只是C语言引入的约定,它是要在程序中运行的第一个函数(由程序员编写)。当然,您可以找到以许多其他语言(例如Java,C ++等)复制的约定。但是,所有这些语言都源于C。我们还非常依赖
__libc_start_main()
的工作方式方面的知识。并且,gcc
团队如何设计此过程。因此,如果要分析的程序已使用其他编译器编译,则可能需要进一步研究此编译器以及它如何执行内存设置,然后再运行main()
过程。无论如何,如果您仔细阅读此答案,现在应该可以完全没有任何符号地查找程序。
最后,您可以阅读“ Linux x86程序启动或-如何深入了解
main()
?”,找到有关可执行文件启动的出色摘要。作者:帕特里克·赫根(Patrick Horgan)。