使用 gdb 向后反汇编核心文件
Disassemble core file backwards with gdb
在模糊测试期间,被测试的应用程序崩溃了,我得到了一个核心文件。用gdb打开core文件后,提示应用程序没有调试符号。
backtrace 命令显示以下内容:
Program terminated with signal 11, Segmentation fault.
#0 0x0000000000000000 in ?? ()
(gdb) bt
#0 0x0000000000000000 in ?? ()
#1 0x00007f8749f065d3 in ?? ()
#2 0x00007f874c6a9000 in ?? ()
#3 0x00007f8749f06568 in ?? ()
#4 0x00007f874c6ab9a0 in ?? ()
#5 0x00007f874c6aaa00 in ?? ()
#6 0x0000000000000001 in ?? ()
#7 0x00007f8749f06664 in ?? ()
#8 0x0000000000000000 in ?? ()
所以,当我输入 (gdb) disass 0x00007f8749f065d3, +1 时,我得到以下输出:
Dump of assembler code from 0x7f8749f065d3 to 0x7f8749f065d4:
0x00007f8749f065d3: mov QWORD PTR [rbx+0x70],0x0
End of assembler dump.
现在我的问题:
例如,当我在上一行 (0x00007f8749f065d3) 并想分析,比方说,在 before 0x00007f8749f065d3 之前执行的两个行,那么我必须输入什么?
注意:我凭直觉输入的 (gdb) disass 0x00007f8749f065d3, -2 这样的命令没有帮助。
此致,
您需要了解如何将源代码行与汇编程序指令相关联吗?或者你的意思是如何反汇编当前指令之前的指令?
以下是两个问题的答案。
回溯命令中显示的十六进制数是函数入口点的程序计数器地址。地址处的汇编程序命令将是单个指令,如调用或 jmp。
像 gcc 这样的编译器会将源代码转换为汇编指令。编译器将在可执行映像中包含 'DWARF' 信息。 DWARF 信息将用于将一组汇编程序指令关联到特定的源代码行。
考虑这个 C 片段:
int main(int argc, char *argv[])
{
int s, tnum, opt, num_threads;
struct thread_info *tinfo;
pthread_attr_t attr;
int stack_size;
void *res;
/* The "-s" option specifies a stack size for our threads */
stack_size = -1;
while ((opt = getopt(argc, argv, "s:")) != -1) {
switch (opt) {
case 's':
stack_size = strtoul(optarg, NULL, 0);
break;
使用 disass /m
演示 gdb 如何将源代码行与汇编器相关联。
48 {
0x0000000000400bdf <+0>: push %rbp
0x0000000000400be0 <+1>: mov %rsp,%rbp
0x0000000000400be3 <+4>: add [=11=]xffffffffffffff80,%rsp
0x0000000000400be7 <+8>: mov %edi,-0x74(%rbp)
0x0000000000400bea <+11>: mov %rsi,-0x80(%rbp)
0x0000000000400bee <+15>: mov %fs:0x28,%rax
0x0000000000400bf7 <+24>: mov %rax,-0x8(%rbp)
0x0000000000400bfb <+28>: xor %eax,%eax
49 int s, tnum, opt, num_threads;
50 struct thread_info *tinfo;
51 pthread_attr_t attr;
52 int stack_size;
53 void *res;
54
55 /* The "-s" option specifies a stack size for our threads */
56
57 stack_size = -1;
0x0000000000400bfd <+30>: movl [=11=]xffffffff,-0x60(%rbp)
58 while ((opt = getopt(argc, argv, "s:")) != -1) {
0x0000000000400c04 <+37>: jmp 0x400c56 <main+119>
0x0000000000400c56 <+119>: mov -0x80(%rbp),%rcx
0x0000000000400c5a <+123>: mov -0x74(%rbp),%eax
0x0000000000400c5d <+126>: mov [=11=]x401002,%edx
0x0000000000400c62 <+131>: mov %rcx,%rsi
0x0000000000400c65 <+134>: mov %eax,%edi
0x0000000000400c67 <+136>: callq 0x400a00 <getopt@plt>
0x0000000000400c6c <+141>: mov %eax,-0x5c(%rbp)
0x0000000000400c6f <+144>: cmpl [=11=]xffffffff,-0x5c(%rbp)
0x0000000000400c73 <+148>: jne 0x400c06 <main+39>
59 switch (opt) {
0x0000000000400c06 <+39>: mov -0x5c(%rbp),%eax
0x0000000000400c09 <+42>: cmp [=11=]x73,%eax
0x0000000000400c0c <+45>: jne 0x400c2c <main+77>
60 case 's':
61 stack_size = strtoul(optarg, NULL, 0);
0x0000000000400c0e <+47>: mov 0x2014cb(%rip),%rax # 0x6020e0 <optarg@@GLIBC_2.2.5>
0x0000000000400c15 <+54>: mov [=11=]x0,%edx
0x0000000000400c1a <+59>: mov [=11=]x0,%esi
0x0000000000400c1f <+64>: mov %rax,%rdi
0x0000000000400c22 <+67>: callq 0x400a10 <strtoul@plt>
0x0000000000400c27 <+72>: mov %eax,-0x60(%rbp)
62 break;
0x0000000000400c2a <+75>: jmp 0x400c56 <main+119>
一些源代码行是一条指令,而另一些则由多条指令组成。请注意,某些行的地址也是乱序的
如您所见,如果可执行文件中没有调试信息,则无法自动将汇编指令与源代码行相关联。调试信息的目的就是将两者关联起来。
对于像 x86 这样的可变长度指令体系结构,这不是向后查找上一条指令的简单方法。在调试器中,最好的策略就是减去某个值,比如 16,然后进行反汇编。如果当前指令看起来是正确的,那么大部分反汇编都是正确的。在我的示例中,让我们考虑 0x0000000000400c06。
所以减去 16 (0x10),我们得到 0x400bf6。
(gdb) disass 0x0000000000400bf6, +20
Dump of assembler code from 0x400bf6 to 0x400c0a:
0x0000000000400bf6 <main+23>: add %cl,-0x77(%rax)
0x0000000000400bf9 <main+26>: rex.RB clc
0x0000000000400bfb <main+28>: xor %eax,%eax
0x0000000000400bfd <main+30>: movl [=12=]xffffffff,-0x60(%rbp)
0x0000000000400c04 <main+37>: jmp 0x400c56 <main+119>
0x0000000000400c06 <main+39>: mov -0x5c(%rbp),%eax
0x0000000000400c09 <main+42>: cmp [=12=]x73,%eax
对比上面的反汇编,从'xor'指令开始的反汇编是正确的。如果您熟悉汇编程序,那么您会注意到一些奇怪的指令,例如 'rex'。
问gdb是怎么反汇编代码的,应该是有道理的。答案是它总是从函数的第一条指令开始并向下运行。
问得好!
在模糊测试期间,被测试的应用程序崩溃了,我得到了一个核心文件。用gdb打开core文件后,提示应用程序没有调试符号。
backtrace 命令显示以下内容:
Program terminated with signal 11, Segmentation fault.
#0 0x0000000000000000 in ?? ()
(gdb) bt
#0 0x0000000000000000 in ?? ()
#1 0x00007f8749f065d3 in ?? ()
#2 0x00007f874c6a9000 in ?? ()
#3 0x00007f8749f06568 in ?? ()
#4 0x00007f874c6ab9a0 in ?? ()
#5 0x00007f874c6aaa00 in ?? ()
#6 0x0000000000000001 in ?? ()
#7 0x00007f8749f06664 in ?? ()
#8 0x0000000000000000 in ?? ()
所以,当我输入 (gdb) disass 0x00007f8749f065d3, +1 时,我得到以下输出:
Dump of assembler code from 0x7f8749f065d3 to 0x7f8749f065d4:
0x00007f8749f065d3: mov QWORD PTR [rbx+0x70],0x0
End of assembler dump.
现在我的问题:
例如,当我在上一行 (0x00007f8749f065d3) 并想分析,比方说,在 before 0x00007f8749f065d3 之前执行的两个行,那么我必须输入什么?
注意:我凭直觉输入的 (gdb) disass 0x00007f8749f065d3, -2 这样的命令没有帮助。
此致,
您需要了解如何将源代码行与汇编程序指令相关联吗?或者你的意思是如何反汇编当前指令之前的指令?
以下是两个问题的答案。
回溯命令中显示的十六进制数是函数入口点的程序计数器地址。地址处的汇编程序命令将是单个指令,如调用或 jmp。
像 gcc 这样的编译器会将源代码转换为汇编指令。编译器将在可执行映像中包含 'DWARF' 信息。 DWARF 信息将用于将一组汇编程序指令关联到特定的源代码行。
考虑这个 C 片段:
int main(int argc, char *argv[])
{
int s, tnum, opt, num_threads;
struct thread_info *tinfo;
pthread_attr_t attr;
int stack_size;
void *res;
/* The "-s" option specifies a stack size for our threads */
stack_size = -1;
while ((opt = getopt(argc, argv, "s:")) != -1) {
switch (opt) {
case 's':
stack_size = strtoul(optarg, NULL, 0);
break;
使用 disass /m
演示 gdb 如何将源代码行与汇编器相关联。
48 {
0x0000000000400bdf <+0>: push %rbp
0x0000000000400be0 <+1>: mov %rsp,%rbp
0x0000000000400be3 <+4>: add [=11=]xffffffffffffff80,%rsp
0x0000000000400be7 <+8>: mov %edi,-0x74(%rbp)
0x0000000000400bea <+11>: mov %rsi,-0x80(%rbp)
0x0000000000400bee <+15>: mov %fs:0x28,%rax
0x0000000000400bf7 <+24>: mov %rax,-0x8(%rbp)
0x0000000000400bfb <+28>: xor %eax,%eax
49 int s, tnum, opt, num_threads;
50 struct thread_info *tinfo;
51 pthread_attr_t attr;
52 int stack_size;
53 void *res;
54
55 /* The "-s" option specifies a stack size for our threads */
56
57 stack_size = -1;
0x0000000000400bfd <+30>: movl [=11=]xffffffff,-0x60(%rbp)
58 while ((opt = getopt(argc, argv, "s:")) != -1) {
0x0000000000400c04 <+37>: jmp 0x400c56 <main+119>
0x0000000000400c56 <+119>: mov -0x80(%rbp),%rcx
0x0000000000400c5a <+123>: mov -0x74(%rbp),%eax
0x0000000000400c5d <+126>: mov [=11=]x401002,%edx
0x0000000000400c62 <+131>: mov %rcx,%rsi
0x0000000000400c65 <+134>: mov %eax,%edi
0x0000000000400c67 <+136>: callq 0x400a00 <getopt@plt>
0x0000000000400c6c <+141>: mov %eax,-0x5c(%rbp)
0x0000000000400c6f <+144>: cmpl [=11=]xffffffff,-0x5c(%rbp)
0x0000000000400c73 <+148>: jne 0x400c06 <main+39>
59 switch (opt) {
0x0000000000400c06 <+39>: mov -0x5c(%rbp),%eax
0x0000000000400c09 <+42>: cmp [=11=]x73,%eax
0x0000000000400c0c <+45>: jne 0x400c2c <main+77>
60 case 's':
61 stack_size = strtoul(optarg, NULL, 0);
0x0000000000400c0e <+47>: mov 0x2014cb(%rip),%rax # 0x6020e0 <optarg@@GLIBC_2.2.5>
0x0000000000400c15 <+54>: mov [=11=]x0,%edx
0x0000000000400c1a <+59>: mov [=11=]x0,%esi
0x0000000000400c1f <+64>: mov %rax,%rdi
0x0000000000400c22 <+67>: callq 0x400a10 <strtoul@plt>
0x0000000000400c27 <+72>: mov %eax,-0x60(%rbp)
62 break;
0x0000000000400c2a <+75>: jmp 0x400c56 <main+119>
一些源代码行是一条指令,而另一些则由多条指令组成。请注意,某些行的地址也是乱序的
如您所见,如果可执行文件中没有调试信息,则无法自动将汇编指令与源代码行相关联。调试信息的目的就是将两者关联起来。
对于像 x86 这样的可变长度指令体系结构,这不是向后查找上一条指令的简单方法。在调试器中,最好的策略就是减去某个值,比如 16,然后进行反汇编。如果当前指令看起来是正确的,那么大部分反汇编都是正确的。在我的示例中,让我们考虑 0x0000000000400c06。
所以减去 16 (0x10),我们得到 0x400bf6。
(gdb) disass 0x0000000000400bf6, +20
Dump of assembler code from 0x400bf6 to 0x400c0a:
0x0000000000400bf6 <main+23>: add %cl,-0x77(%rax)
0x0000000000400bf9 <main+26>: rex.RB clc
0x0000000000400bfb <main+28>: xor %eax,%eax
0x0000000000400bfd <main+30>: movl [=12=]xffffffff,-0x60(%rbp)
0x0000000000400c04 <main+37>: jmp 0x400c56 <main+119>
0x0000000000400c06 <main+39>: mov -0x5c(%rbp),%eax
0x0000000000400c09 <main+42>: cmp [=12=]x73,%eax
对比上面的反汇编,从'xor'指令开始的反汇编是正确的。如果您熟悉汇编程序,那么您会注意到一些奇怪的指令,例如 'rex'。
问gdb是怎么反汇编代码的,应该是有道理的。答案是它总是从函数的第一条指令开始并向下运行。
问得好!