操作系统:linux
调试工具:gdb
Preparation before the lab
实验由phase_1 ~ phase_6
组成,共6个阶段,
我们需要分别输入6个字符串密码,以便解除Dr. Evil的炸弹,
但只要有1个字符串输错,炸弹就会爆炸。
那么如何知道6个正确的字符串呢?
我们需要分别查看phase_1 ~ phase_6
的汇编代码,
而炸弹就存放在其中,我们要根据汇编代码小心地避开炸弹,分析出正确的字符串即可。
而分析需要用到学习的汇编知识、常用gdb指令以及linux终端指令。
需要用到的linux终端指令:
别忘了先进入文件地址中
-
objdump -d bomb > assembly.s
反编译出bomb.c
的汇编代码,并存放在assembly.s
中(assembly.s是自己命名的) -
gdb 文件名
用gdb调试工具打开某个文件
eg.gdb bomb
用gdb调试工具打开bomb.c文件
而我们就是要用gdb调试工具打开bomb.c文件后做实验
常用gdb指令:
gdb指令必须用gdb调试工具打开文件后才能调用
-
run
(可简写为:r)
运行文件。 -
quit
(可简写为:q)
停止运行,跳出gdb调试工具 -
break 函数名
(可简写为:b 函数名)
在某个函数处打断点,以便之后用disas展示这个函数的汇编代码。
eg.b phase_1
在phase_1函数打断点 -
disas 函数名
展示这个函数的汇编代码(但必须先打断点)
eg.disas phase_1
展示phase_1的汇编代码 -
x/s 明码或者寄存器
用以访问明码或者寄存器对应内容
eg1.x/s 0x40139b
访问0x40139b对应内容
eg2.x/s $rsi
访问%rsi对应内容
在正式分析之前需要注意到一点,我们输入的字符串都是放入到%rdi
当中的。
(其实一般情况下,输入也是放到%rdi
的)
也就是说,在每个phase
调用时,默认%rdi
这个寄存器存放着输入的字符串。
如何得到这点的呢?是通过分析main
函数的汇编代码得出的。
# main part
0x0000000000400e32 <+146>: callq 0x40149e <read_line>
0x0000000000400e37 <+151>: mov %rax,%rdi # input store %rdi
0x0000000000400e3a <+154>: callq 0x400ee0 <phase_1>
0x0000000000400e3f <+159>: callq 0x4015c4 <phase_defused>
0x0000000000400e44 <+164>: mov $0x4023a8,%edi
0x0000000000400e49 <+169>: callq 0x400b10 <puts@plt>
0x0000000000400e4e <+174>: callq 0x40149e <read_line>
0x0000000000400e53 <+179>: mov %rax,%rdi # input store %rdi
0x0000000000400e56 <+182>: callq 0x400efc <phase_2>
0x0000000000400e5b <+187>: callq 0x4015c4 <phase_defused>
0x0000000000400e60 <+192>: mov $0x4022ed,%edi
0x0000000000400e65 <+197>: callq 0x400b10 <puts@plt>
0x0000000000400e6a <+202>: callq 0x40149e <read_line>
0x0000000000400e6f <+207>: mov %rax,%rdi # input store %rdi
0x0000000000400e72 <+210>: callq 0x400f43 <phase_3>
0x0000000000400e77 <+215>: callq 0x4015c4 <phase_defused>
0x0000000000400e7c <+220>: mov $0x40230b,%edi
0x0000000000400e81 <+225>: callq 0x400b10 <puts@plt>
0x0000000000400e86 <+230>: callq 0x40149e <read_line>
0x0000000000400e8b <+235>: mov %rax,%rdi # input store %rdi
0x0000000000400e8e <+238>: callq 0x40100c <phase_4>
0x0000000000400e93 <+243>: callq 0x4015c4 <phase_defused>
0x0000000000400e98 <+248>: mov $0x4023d8,%edi
0x0000000000400e9d <+253>: callq 0x400b10 <puts@plt>
0x0000000000400ea2 <+258>: callq 0x40149e <read_line>
0x0000000000400ea7 <+263>: mov %rax,%rdi # input store %rdi
0x0000000000400eaa <+266>: callq 0x401062 <phase_5>
0x0000000000400eaf <+271>: callq 0x4015c4 <phase_defused>
0x0000000000400eb4 <+276>: mov $0x40231a,%edi
0x0000000000400eb9 <+281>: callq 0x400b10 <puts@plt>
0x0000000000400ebe <+286>: callq 0x40149e <read_line>
0x0000000000400ec3 <+291>: mov %rax,%rdi # input store %rdi
0x0000000000400ec6 <+294>: callq 0x4010f4 <phase_6>
0x0000000000400ecb <+299>: callq 0x4015c4 <phase_defused>
0x0000000000400ed0 <+304>: mov $0x0,%eax
0x0000000000400ed5 <+309>: pop %rbx
0x0000000000400ed6 <+310>: retq
分析:我们可以看到,在每个phase
调用前,都callq 0x40149e <read_line>
,然后mov %rax,%rdi
,这样子会将我们输入的字符串最终存到%rdi
中。(我们是把字符串输入到read_line
的,而read_line
会返回到%rax
中,mov
操作将%rax
存到%rdi
。注意,%rax
往往用来放返回值)
至于read_line
的汇编代码我就不分析了,通过名字也可以看出:它是用来返回我们的输入值的
下面开始分析phase_1 ~ phase_6
的汇编代码,
得出认为正确的字符串,并逐个解除炸弹
phase_1
查看phase_1
的汇编:
0x0000000000400ee0 <+0>: sub $0x8,%rsp
0x0000000000400ee4 <+4>: mov $0x402400,%esi
0x0000000000400ee9 <+9>: callq 0x401338 <strings_not_equal>
0x0000000000400eee <+14>: test %eax,%eax
0x0000000000400ef0 <+16>: je 0x400ef7 <phase_1+23>
0x0000000000400ef2 <+18>: callq 0x40143a <explode_bomb>
0x0000000000400ef7 <+23>: add $0x8,%rsp
0x0000000000400efb <+27>: retq
查看string_not_equal
的汇编:
#string_not_equal
0x0000000000401338 <+0>: push %r12 # 栈帧相关
0x000000000040133a <+2>: push %rbp # 栈帧相关
0x000000000040133b <+3>: push %rbx # 栈帧相关
0x000000000040133c <+4>: mov %rdi,%rbx # 第一个参数保存到%rbx
0x000000000040133f <+7>: mov %rsi,%rbp # 第二个参数保存到%rbp
0x0000000000401342 <+10>: callq 0x40131b <string_length> # 以第一个参数作为入参,调用函数
0x0000000000401347 <+15>: mov %eax,%r12d # 返回值保存到%r12d
0x000000000040134a <+18>: mov %rbp,%rdi # 把第二个参数作为入参
0x000000000040134d <+21>: callq 0x40131b <string_length> # 调用函数
0x0000000000401352 <+26>: mov $0x1,%edx # 将1赋值给%edx,作为strings_not_equal的返回结果预备值
0x0000000000401357 <+31>: cmp %eax,%r12d # 比较两个参数分别作为入参的调用结果
0x000000000040135a <+34>: jne 0x40139b <strings_not_equal+99> # 不相等时跳转
0x000000000040135c <+36>: movzbl (%rbx),%eax # 长度相等时,对(%rbx)内存的值付给%eax
0x000000000040135f <+39>: test %al,%al
0x0000000000401361 <+41>: je 0x401388 <strings_not_equal+80> # 如果这个值为0,即字符串的结束字符,则跳转
0x0000000000401363 <+43>: cmp 0x0(%rbp),%al # 对(%rbp)取值,和%al做比较;此时(%rbp)是内存的值,%al是我们输入的字符串的第一个字符
0x0000000000401366 <+46>: je 0x401372 <strings_not_equal+58> # 相等跳转
0x0000000000401368 <+48>: jmp 0x40138f <strings_not_equal+87> # 不相等跳转
0x000000000040136a <+50>: cmp 0x0(%rbp),%al # 比较%rbp和%rbx
0x000000000040136d <+53>: nopl (%rax)
0x0000000000401370 <+56>: jne 0x401396 <strings_not_equal+94>
0x0000000000401372 <+58>: add $0x1,%rbx # 第一个参数指针+1
0x0000000000401376 <+62>: add $0x1,%rbp # 第二个参数指针+1
0x000000000040137a <+66>: movzbl (%rbx),%eax
0x000000000040137d <+69>: test %al,%al # 若%al为空即0,结束循环
0x000000000040137f <+71>: jne 0x40136a <strings_not_equal+50> # 重新进行字符比对
0x0000000000401381 <+73>: mov $0x0,%edx
0x0000000000401386 <+78>: jmp 0x40139b <strings_not_equal+99>
0x0000000000401388 <+80>: mov $0x0,%edx
0x000000000040138d <+85>: jmp 0x40139b <strings_not_equal+99>
0x000000000040138f <+87>: mov $0x1,%edx
0x0000000000401394 <+92>: jmp 0x40139b <strings_not_equal+99>
0x0000000000401396 <+94>: mov $0x1,%edx
0x000000000040139b <+99>: mov %edx,%eax # 将返回结果预备值赋值(0或1)给%eax,准备返回
0x000000000040139d <+101>: pop %rbx
0x000000000040139e <+102>: pop %rbp
0x000000000040139f <+103>: pop %r12
0x00000000004013a1 <+105>: retq
未完续待~