gdb调试知识
gdb bomb:使用gdb调试可执行文件bomb
break 函数名/行号:打断点
r(run):运行至断点
c(continue):继续执行到下一个断点
q(quit):退出gdb
si:单指令执行
n(next):单步跟踪程序,遇到调用函数不会进入函数内部
s(step):单步调试,遇到调用函数会进入函数内部
until:运行程序直到退出循环体
layout asm:显示反汇编窗口
layout regs:显示反汇编和寄存器窗口
disassemble:查看当前执行指令的汇编代码
i r 寄存器名:显示当前寄存器值
x/[count][format] [address]:打印指定格式指定数量的内存值
phase_1
反汇编代码:
0x00005555555555e7 <+0>: endbr64
0x00005555555555eb <+4>: sub $0x8,%rsp
0x00005555555555ef <+8>: lea 0x1b56(%rip),%rsi # 0x55555555714c
0x00005555555555f6 <+15>: call 0x555555555af3 <strings_not_equal>
0x00005555555555fb <+20>: test %eax,%eax
0x00005555555555fd <+22>: jne 0x555555555604 <phase_1+29>
0x00005555555555ff <+24>: add $0x8,%rsp
0x0000555555555603 <+28>: ret
0x0000555555555604 <+29>: call 0x555555555c07 <explode_bomb>
0x0000555555555609 <+34>: jmp 0x5555555555ff <phase_1+24>
<+0>指令前缀,目的是增强安全性
<+4>减少栈指针的八个字节,分配空间
前两行基本是固定的,对拆弹没什么作用。
<+8>lea这个指令在实际应用中是用来计算地址,而非访问内存。这里是将%rip内的地址加上0x1b56的偏移量加载到%rsi中,i r rip查看此处rip的值为0x5555555555e7,加上偏移量刚好等于注释中的0x55555555714c。
<+15>这里调用函数,看名字就能知道这是一个判断字符串是否相等的函数,我们先接着往下看。
<+20>这里是将eax的值自身按位与,eax是返回值,也就是判断<+15>处调用的函数的返回值是否为零。
<+22>:如果上一行返回值不等于0,则跳转到<phase_1+29>处,即爆炸。
<+24>:如果<+20>处返回值等于0,则增加栈指针八个字节,释放分配的空间。
<+28>:从函数返回,结束phase_1。
也就是说我们本题输入应为一个字符串,存在%rdi(第一个参数)中,与%rsi(第二个参数)内地址对应的字符串相等。
x/s 0x55555555714c查看内容得到"Public speaking is very easy."即为答案。
phase_2
反汇编代码:
0x000055555555560b <+0>: endbr64
0x000055555555560f <+4>: push %rbp
0x0000555555555610 <+5>: push %rbx
0x0000555555555611 <+6>: sub $0x28,%rsp
0x0000555555555615 <+10>: mov %fs:0x28,%rax
0x000055555555561e <+19>: mov %rax,0x18(%rsp)
0x0000555555555623 <+24>: xor %eax,%eax
0x0000555555555625 <+26>: mov %rsp,%rsi
0x0000555555555628 <+29>: call 0x555555555c33 <read_six_numbers>
0x000055555555562d <+34>: cmpl $0x0,(%rsp)
0x0000555555555631 <+38>: js 0x55555555563d <phase_2+50>
0x0000555555555633 <+40>: mov %rsp,%rbp
0x0000555555555636 <+43>: mov $0x1,%ebx
0x000055555555563b <+48>: jmp 0x555555555650 <phase_2+69>
0x000055555555563d <+50>: call 0x555555555c07 <explode_bomb>
0x0000555555555642 <+55>: jmp 0x555555555633 <phase_2+40>
0x0000555555555644 <+57>: add $0x1,%ebx
0x0000555555555647 <+60>: add $0x4,%rbp
0x000055555555564b <+64>: cmp $0x6,%ebx
0x000055555555564e <+67>: je 0x555555555661 <phase_2+86>
0x0000555555555650 <+69>: mov %ebx,%eax
0x0000555555555652 <+71>: add 0x0(%rbp),%eax
0x0000555555555655 <+74>: cmp %eax,0x4(%rbp)
0x0000555555555658 <+77>: je 0x555555555644 <phase_2+57>
0x000055555555565a <+79>: call 0x555555555c07 <explode_bomb>
0x000055555555565f <+84>: jmp 0x555555555644 <phase_2+57>
0x0000555555555661 <+86>: mov 0x18(%rsp),%rax
0x0000555555555666 <+91>: sub %fs:0x28,%rax
0x000055555555566f <+100>: jne 0x555555555678 <phase_2+109>
0x0000555555555671 <+102>: add $0x28,%rsp
0x0000555555555675 <+106>: pop %rbx
0x0000555555555676 <+107>: pop %rbp
0x0000555555555677 <+108>: ret
0x0000555555555678 <+109>: call 0x555555555250 <__stack_chk_fail@plt>
有了上边的经验,我们后边直接看关键点,调用了函数<read_six_numbers>可以推知我们本题需要输入六个数字,设为x1-x6。
<+34>:比较栈指针指向的内存中的数与0的大小,即判断正负,若为负数,跳转到<phase_2+50>爆炸。
此时栈指针指向的内存的数即x1。
然后将栈指针的值赋给%rbp,将立即数1赋给%ebx。
无条件跳转至<phase_2+69>,将%ebx的值(也就是1)赋给%eax,将%rbp指向的内存的值(即x1)加到%eax中,比较%rbp的值+0x4的偏移量所指向的内存的值(即x2)与%eax的值(1+x1)的大小。如果相等则跳转<phase_2+57>,不相等则爆炸。
所以有x2=x1+1。
跳转到<+57>:将%ebx的值加1,将%rbp的值加4,此时%rbp指向的内存的数变为x2。再判断6和%ebx的大小,不相等则继续进行<+69>的操作:将%ebx的值(此时为2)赋给%eax,将%rbp指向的内存的值(x2)加到%eax中,比较%rbp的值+0x4的偏移量所指向的内存的值(即x3)与%eax的值(2+x2)的大小。如果相等则跳转<phase_2+57>,不相等则爆炸。
即x3=x2+2。
只有当%ebx=6时才跳出这个循环,那么%ebx在这里我们可以理解为数字的索引n,必须保证Xn=X(n-1)+n-1。
又因为输入的数必须不能为负,我们设x1=1,则答案为1 2 4