前言
这学期做完了计组实验后,我终于有时间挑战一下一直心心念念的CMU CS15-213的拆炸弹实验了。
看了几眼题目,发现需要读懂汇编代码,于是又屁颠屁颠跑回去学CSAPP第三章……
学完之后,我大致描述一下这个Lab:这个Lab重点考察x86-64汇编代码阅读能力,但考察的难度不深,主要是读懂条件分支、循环和数组链表等代码的实现。
而入门x86-64汇编代码的最好教程就是CSAPP第三章!
而入门x86-64汇编代码的最好教程就是CSAPP第三章!
而入门x86-64汇编代码的最好教程就是CSAPP第三章!
(此发言综合了找资源等时间精力上的消耗的考量,含有较强的个人主观成分;当然若您有更好的资料,您对,您也可分享)
在学CSAPP第三章的时候,首先要简单掌握各个指令分别是什么意思,而重点在于后面的“控制”“过程”两节,讲解了分支、循环、函数调用过程中的返回值
、被调用者保存
、局部变量
和参数
等概念的汇编实现,学会了对看懂汇编程序大有帮助!
Bomb Lab 提供两个文件,可执行文件bomb
和程序主函数bomb.c
,我们目前只有程序的主函数,需要使用objdump反编译bomb得到炸弹程序完整的汇编代码,再用所学知识反编译为C代码。
因此实际上呀,是有6个阶段,要拆6个炸弹!
但是这个Lab并没有想象中的难,最好是尽量自己做,不会再查相关资料,而不是直接看别人的总结。
准备工作
- 在VM安装Ubuntu(bomb程序在Linux下才能运行)
- 下载lab文件夹(CASPP实验官网、官方学生资源界面)
- 使用objdump反汇译得到汇编代码程序(objdump安装方式上网搜索即可,windows也可以反汇编)
- 学习使用GDB调试bomb程序,用Linux的gdb才能运行bomb(GDB指令手册)
- 学习CSAPP第三章
正式开始吧
- 下载并解压程序文件,得到
bomb
可执行文件与bomb.c
- 在该文件夹下终端输入指令
objdump -S -d main > main.txt
,从bomb
中反汇编出汇编文件bomb.txt
- 再用指令
gbd bomb
进入到gdb功能
那现在,我们就有汇编代码与gdb啦
main函数
(main的源码我就不贴了)
我们从bomb.c
文件中得知,我们的目标就是解决6个phase
函数,每次一个输入,当6次输入都不会引爆各自phase
函数中的<explode_bomb>
时,就能够正常通过phase
函数啦!
phase_1
那就开始看<phase_1>
函数吧!
0000000000400ee0 <phase_1>:
phase_1():
400ee0: 48 83 ec 08 sub $0x8,%rsp # 开8字节的栈
400ee4: be 00 24 40 00 mov $0x402400,%esi # 第二个参数(第一个是你刚输进入的数)
400ee9: e8 4a 04 00 00 call 401338 <strings_not_equal> #查一下
400eee: 85 c0 test %eax,%eax
400ef0: 74 05 je 400ef7 <phase_1+0x17> # BOMB if %eax == 0
400ef2: e8 43 05 00 00 call 40143a <explode_bomb>
400ef7: 48 83 c4 08 add $0x8,%rsp # 关栈
400efb: c3 ret
(p.s. 里面的两个函数<strings_not_equal>
和<explode_bomb>
,先姑且按字面意思理解,一个是比较两字符串是否相等并返回布尔值,另一个是炸弹爆炸。lab做完后再去看源码相信对你来说已是小菜一碟)
书上说,x86-64给函数传参使用的寄存器是(依次列出):%rdi
%rsi
%dx
rcx
r8
r9
。因此我们在调用函数<strings_not_equal>
前设置的%rsi
就是它的第二个参数,那我们的第一个参数则是我们的%rdi
。为了验证想法,我们可以用GDB查看在0x400ee9
位置时这两个参数的值(此时0x400ee9
位置的指令尚未执行):
忽略中间执行信息,我们直接看头尾,我先是在0x400ee9
设置了一个断点,然后输入r
执行调试,输入hhhhhhhWhat?
。随后达到断点,查看两寄存器指向位置的字符串,证实了我们的想法。
继续看<phase_1>
的代码:
400ee9: e8 4a 04 00 00 call 401338 <strings_not_equal> #查一下
400eee: 85 c0 test %eax,%eax
400ef0: 74 05 je 400ef7 <phase_1+0x17> # BOMB if %eax == 0
400ef2: e8 43 05 00 00 call 40143a <explode_bomb>
这一段是非常经典的分支代码了,我们知道调用函数<strings_not_equal>
会返回一个布尔值,而这个返回值是放在%rax
中传出的。test %eax,%eax
使%eax = %eax & %eax
,与je
指令一起使用则是比较%rax == 0
,满足则跳转,不满足则不跳转然后炸弹爆炸。
那就简单了嘛,只要我的输入和%rsi
指向的字符串相同,那我就成了嘻嘻~
成功通过<phase_1>
:
phase_2
这是<phase_2>
开头部分,信息量比较小:
0000000000400efc <phase_2>:
400efc: 55 push %rbp
400efd: 53 push %rbx # 保存被调用者保存寄存器,可以不理会
400efe: 48 83 ec 28 sub $0x28,%rsp # 开栈(开栈的用途有多种,看后面才能知道这里的目的)
400f02: 48 89 e6 mov %rsp,%rsi # 把栈交给%rsi
这时候也还看不出%rsi
的作用:
接下来是一个函数调用与一个分支判断,“read_six_numbers”是吧,那我就随便输入个1 1 1 1 1 1
吧。暂时不管函数<read_six_numbers>
内部进行了什么工作,直接看调用后的结果。
400f05: e8 52 05 00 00 call 40145c <read_six_numbers> # 获取6个int数
400f0a: 83 3c 24 01 cmpl $0x1,(%rsp)
400f0e: 74 20 je 400f30 <phase_2+0x34> # 若nums[0]==1
400f10: e8 25 05 00 00 call 40143a <explode_bomb>
这不就来了嘛,看来这个栈,相当于一个int数组呀!(姑且命名为nums数组)
然后的分支判断比较1 == (%rsp)
相当于1 == nums[0]
,不满足就爆炸,还好我满足了哇。
下面又直接跳到了位置0x400f30
,不好,书上说,这是循环的征兆!
# 跳到循环初始化
400f15: eb 19 jmp 400f30 <phase_2+0x34>
# 循环体begin
400f17: 8b 43 fc mov -0x4(%rbx),%eax # %eax = nums[i-1]
400f1a: 01 c0 add %eax,%eax
# if(2*nums[i-1] != nums[i]) BOMB();
400f1c: 39 03 cmp %eax,(%rbx) # if(2*nums[i-1] != nums[i]) BOMB()
400f1e: 74 05 je 400f25 <phase_2+0x29>
400f20: e8 15 05 00 00 call 40143a <explode_bomb>
# 即总要有nums[i] == 2 * nums[i-1]!!
400f25: 48 83 c3 04 add $0x4,%rbx # i++
# 循环终止判断
400f29: 48 39 eb cmp %rbp,%rbx
400f2c: 75 e9 jne 400f17 <phase_2+0x1b>
400f2e: eb 0c jmp 400f3c <phase_2+0x40>
# 循环初始化
400f30: 48 8d 5c 24 04 lea 0x4(%rsp),%rbx # %rbx加4,rbx即for循环中的i
400f35: 48 8d 6c 24 18 lea 0x18(%rsp),%rbp # 终止条件
400f3a: eb db jmp 400f17 <phase_2+0x1b>
# 循环体end
循环的流程已经给出来了,直接看注释。目前得到的最重要的信息有: