假设我编写了下面的应用程序并删除了它的符号。
#include <stdio.h>
int main()
{
printf("Hello\n");
}
构建程序:
gcc -o hello hello.c
strip --strip-unneeded hello
如果应用程序未被剥离,则拆卸主要功能将很容易。但是,我不知道如何反汇编被剥离的应用程序的 main 函数。
(gdb) disas main
No symbol table is loaded. Use the "file" command.
(gdb) info line main
Function "main" not defined.
我怎么能这样做?它甚至可能吗?
备注:这必须仅使用GDB。忘记 objdump 。假设我无法访问代码。
非常感谢一步一步的例子。
答案 0 :(得分:42)
好的,这是我以前的答案的大版本。我想我现在找到了办法。
你(仍然:)有这个特定的问题:
(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
。它是代码汇编开始的通用名称,从我们对主要波纹管的解释,从它的大小,你可以看到它包括main。如果你反汇编它,你会看到对__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;
}
但是!
如果这不起作用,那么你仍然有一些提示:
您应该从现在开始在所有函数的开头设置断点。它们就在ret
或leave
之前。第一个入口点是.text
本身。这是装配开始,但不是主要的。
问题是并不总是断点会让你的程序运行。像.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 definition以了解代码中应显示的部分数量并尝试计算地址。不过,您需要有关代码中各部分的信息!
这是一项艰苦的工作,我的朋友!祝你好运!
贝乔
答案 1 :(得分:8)
如何使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
。尽管如此,优化可能会受到影响。
如果您的二进制文件被剥离并优化,main
可能不会作为二进制文件中的“实体”存在;你不可能比这种程序好得多。
答案 2 :(得分:1)
有一个很棒的新免费工具,从paradyn项目中称为unstrip(完全披露:我在这个项目上工作),它将重写你的程序二进制文件,为它添加符号信息,并恢复所有(或几乎所有)函数精确地剥离了精灵二进制文件。它不会将主要功能识别为“主要”,但它会找到它,并且您可以应用上面已经提到的启发式来确定哪个功能是主要功能。
http://www.paradyn.org/html/tools/unstrip.html
对不起,这不是一个只有gdb的解决方案。
答案 3 :(得分:-1)
x/i <location>
是你的朋友。当然,你必须弄清楚你想要自己拆卸哪个位置。