如何反汇编应用程序的主要function?

比方说,我编译了下面的应用程序,并剥离它的符号。

#include <stdio.h> int main() { printf("Hello\n"); } 

构build过程:

 gcc -o hello hello.c strip --strip-unneeded hello 

如果应用程序没有被剥离,则拆卸主要function将非常简单。 但是,我不知道如何反汇编剥离的应用程序的主要function。

 (gdb) disas main No symbol table is loaded. Use the "file" command. (gdb) info line main Function "main" not defined. 

我怎么能这样做? 这甚至有可能吗?

注意 :这只能用GDB来完成。 忘记objdump 。 假设我没有访问代码的权限。

一个一步一步的例子将不胜感激。

好的,这里是我以前答案的一个大版本。 我想我现在找到了一条路。

你(仍然:)有这个具体问题:

 (gdb) disas main No symbol table is loaded. Use the "file" command. 

现在,如果你编译代码(我在最后加了一个return 0 ),你将会得到gcc -S

  pushq %rbp movq %rsp, %rbp movl $.LC0, %edi call puts movl $0, %eax leave ret 

现在,你可以看到你的二进制给你一些信息:

条纹:

 (gdb) info files Symbols from "/home/beco/Documents/fontes/cpp/teste/stackoverflow/distrip". Local exec file: `/home/beco/Documents/fontes/cpp/teste/stackoverflow/distrip', file type elf64-x86-64. Entry point: 0x400440 0x0000000000400238 - 0x0000000000400254 is .interp ... 0x00000000004003a8 - 0x00000000004003c0 is .rela.dyn 0x00000000004003c0 - 0x00000000004003f0 is .rela.plt 0x00000000004003f0 - 0x0000000000400408 is .init 0x0000000000400408 - 0x0000000000400438 is .plt 0x0000000000400440 - 0x0000000000400618 is .text ... 0x0000000000601010 - 0x0000000000601020 is .data 0x0000000000601020 - 0x0000000000601030 is .bss 

这里最重要的条目是.text 。 这是一个代码组装开始的通用名称,从我们对主波纹管的解释,从它的大小,你可以看到,它包括主要。 如果你反汇编,你会看到__libc_start_main的调用。 最重要的是,你正在分解一个真正的代码的好的切入点(你不会误导人们把DATA变成CODE)。

 disas 0x0000000000400440,0x0000000000400618 Dump of assembler code from 0x400440 to 0x400618: 0x0000000000400440: xor %ebp,%ebp 0x0000000000400442: mov %rdx,%r9 0x0000000000400445: pop %rsi 0x0000000000400446: mov %rsp,%rdx 0x0000000000400449: and $0xfffffffffffffff0,%rsp 0x000000000040044d: push %rax 0x000000000040044e: push %rsp 0x000000000040044f: mov $0x400540,%r8 0x0000000000400456: mov $0x400550,%rcx 0x000000000040045d: mov $0x400524,%rdi 0x0000000000400464: callq 0x400428 <__libc_start_main@plt> 0x0000000000400469: hlt ... 0x000000000040046c: sub $0x8,%rsp ... 0x0000000000400482: retq 0x0000000000400483: nop ... 0x0000000000400490: push %rbp .. 0x00000000004004f2: leaveq 0x00000000004004f3: retq 0x00000000004004f4: data32 data32 nopw %cs:0x0(%rax,%rax,1) ... 0x000000000040051d: leaveq 0x000000000040051e: jmpq *%rax ... 0x0000000000400520: leaveq 0x0000000000400521: retq 0x0000000000400522: nop 0x0000000000400523: nop 0x0000000000400524: push %rbp 0x0000000000400525: mov %rsp,%rbp 0x0000000000400528: mov $0x40062c,%edi 0x000000000040052d: callq 0x400418 <puts@plt> 0x0000000000400532: mov $0x0,%eax 0x0000000000400537: leaveq 0x0000000000400538: retq 

对__libc_start_main的调用作为第一个参数获得一个指向main()的指针。 所以,紧接在调用之前堆栈中的最后一个参数是main()地址。

  0x000000000040045d: mov $0x400524,%rdi 0x0000000000400464: callq 0x400428 <__libc_start_main@plt> 

这里是0x400524(正如我们已经知道的)。 现在你设置一个断点,试试这个:

 (gdb) break *0x400524 Breakpoint 1 at 0x400524 (gdb) run Starting program: /home/beco/Documents/fontes/cpp/teste/stackoverflow/disassembly/d2 Breakpoint 1, 0x0000000000400524 in main () (gdb) n Single stepping until exit from function main, which has no line number information. hello 1 __libc_start_main (main=<value optimized out>, argc=<value optimized out>, ubp_av=<value optimized out>, init=<value optimized out>, fini=<value optimized out>, rtld_fini=<value optimized out>, stack_end=0x7fffffffdc38) at libc-start.c:258 258 libc-start.c: No such file or directory. in libc-start.c (gdb) n Program exited normally. (gdb) 

现在你可以使用下面的方法进行反汇编:

 (gdb) disas 0x0000000000400524,0x0000000000400600 Dump of assembler code from 0x400524 to 0x400600: 0x0000000000400524: push %rbp 0x0000000000400525: mov %rsp,%rbp 0x0000000000400528: sub $0x10,%rsp 0x000000000040052c: movl $0x1,-0x4(%rbp) 0x0000000000400533: mov $0x40064c,%eax 0x0000000000400538: mov -0x4(%rbp),%edx 0x000000000040053b: mov %edx,%esi 0x000000000040053d: mov %rax,%rdi 0x0000000000400540: mov $0x0,%eax 0x0000000000400545: callq 0x400418 <printf@plt> 0x000000000040054a: mov $0x0,%eax 0x000000000040054f: leaveq 0x0000000000400550: retq 0x0000000000400551: nop 0x0000000000400552: nop 0x0000000000400553: nop 0x0000000000400554: nop 0x0000000000400555: nop ... 

这主要是解决方案。

顺便说一句,这是一个不同的代码,看看它是否工作。 这就是为什么上面的程序集有点不同。 上面的代码是从这个c文件:

 #include <stdio.h> int main(void) { int i=1; printf("hello %d\n", i); return 0; } 

但!


如果这不起作用,那么你仍然有一些提示:

你应该从现在开始在所有的函数开始设置断点。 他们只是在retleave之前。 第一个入口点是.text本身。 这是组装的开始,但不是主要的。

问题是,并不总是一个断点将让你的程序运行。 就像这篇文章中的那样:

 (gdb) break *0x0000000000400440 Breakpoint 2 at 0x400440 (gdb) run Starting program: /home/beco/Documents/fontes/cpp/teste/stackoverflow/disassembly/d2 Breakpoint 2, 0x0000000000400440 in _start () (gdb) n Single stepping until exit from function _start, which has no line number information. 0x0000000000400428 in __libc_start_main@plt () (gdb) n Single stepping until exit from function __libc_start_main@plt, which has no line number information. 0x0000000000400408 in ?? () (gdb) n Cannot find bounds of current function 

所以你需要继续努力,直到你找到自己的方式,在下面设置断点:

 0x400440 0x40046c 0x400490 0x4004f4 0x40051e 0x400524 

从另一个答案,我们应该保持这个信息:

在文件的非条带化版本中,我们看到:

 (gdb) disas main Dump of assembler code for function main: 0x0000000000400524 <+0>: push %rbp 0x0000000000400525 <+1>: mov %rsp,%rbp 0x0000000000400528 <+4>: mov $0x40062c,%edi 0x000000000040052d <+9>: callq 0x400418 <puts@plt> 0x0000000000400532 <+14>: mov $0x0,%eax 0x0000000000400537 <+19>: leaveq 0x0000000000400538 <+20>: retq End of assembler dump. 

现在我们知道main是0x0000000000400524,0x0000000000400539 。 如果我们使用相同的偏移量来查看条带化二进制数,我们得到相同的结果:

 (gdb) disas 0x0000000000400524,0x0000000000400539 Dump of assembler code from 0x400524 to 0x400539: 0x0000000000400524: push %rbp 0x0000000000400525: mov %rsp,%rbp 0x0000000000400528: mov $0x40062c,%edi 0x000000000040052d: callq 0x400418 <puts@plt> 0x0000000000400532: mov $0x0,%eax 0x0000000000400537: leaveq 0x0000000000400538: retq End of assembler dump. 

所以,除非你可以得到一些提示,比如使用另一个带有符号的代码,另一种方法是如果你可以获得关于第一个汇编指令的信息,那么你可以在特定的地方进行反汇编,看看它是否匹配。 如果您根本没有访问代码的权限,您仍然可以阅读ELF定义,以了解代码中应该显示多少部分,并尝试计算出的地址。 不过,你需要在代码中的部分信息!

我的朋友,这是艰苦的工作! 祝你好运!

贝乔

如何做info files以获得部分列表(与地址),并从那里?

例:

 gdb) info files Symbols from "/home/bob/tmp/t". Local exec file: `/home/bob/tmp/t', file type elf64-x86-64. Entry point: 0x400490 0x0000000000400270 - 0x000000000040028c is .interp 0x000000000040028c - 0x00000000004002ac is .note.ABI-tag .... 0x0000000000400448 - 0x0000000000400460 is .init .... 

反汇编.init

 (gdb) disas 0x0000000000400448,0x0000000000400460 Dump of assembler code from 0x400448 to 0x400460: 0x0000000000400448: sub $0x8,%rsp 0x000000000040044c: callq 0x4004bc 0x0000000000400451: callq 0x400550 0x0000000000400456: callq 0x400650 0x000000000040045b: add $0x8,%rsp 0x000000000040045f: retq 

然后继续拆卸其余部分。

如果我是你,而且我的GCC版本和你的可执行文件一样,我会检查一个虚拟的非剥离可执行文件上调用的函数序列。 在大多数情况下,调用的顺序可能是相似的,所以通过比较,可以帮助您调试启动顺序。 尽管优化可能会出现。

如果您的二进制文件被剥离和优化, main可能不会作为二进制文件中的“实体”存在; 很有可能你不能比这种程序好得多。

有一个伟大的新的免费工具称为unstrip项目(完全公开:我在这个项目上工作),将重写您的程序二进制文件,添加符号信息,并恢复剥离精灵二进制文件中的所有(或几乎所有)功能为你,非常准确。 它不会将主要功能标识为“主要”,而是会找到它,并且可以应用上面已经提到的启发式来确定哪个功能是主要的。

http://www.paradyn.org/html/tools/unstrip.html

对不起,这不是一个gdb专用的解决方案。

IIRC, x/i <location>是你的朋友。 当然,你必须弄清楚你想拆卸自己的位置。