花了一天多时间, 做完了csapp很有名的bomb实验,感到有趣的同时受益匪浅。这个lab一共有6个阶段,越往后难度越大。个人觉得最后一个lab是最有趣的,涉及到了链表以及排序。
上csapp官网下载bomb实验的相关资料,解压后利用
objdump -d bomb > bomb.asm
进行反汇编得到bomb.asm,再进行下面的六个阶段的拆炸弹过程。
phase_1
b20 <phase_1>:
b20: push %ebp
b21: e5 mov %esp,%ebp
b23: ec sub $0x8,%esp
b26: b mov (%ebp),%eax
b29: c4 f8 add $0xfffffff8,%esp
b2c: c0 push $0x80497c0
b31: push %eax
b32: e8 f9 call <strings_not_equal>
b37: c4 add $0x10,%esp
b3a: c0 test %eax,%eax
b3c: je b43 <phase_1+>
b3e: e8 b9 call fc <explode_bomb>
b43: ec mov %ebp,%esp
b45: d pop %ebp
b46: c3 ret
b47: nop
查看此函数和函数
strings_not_equal
很简单就可以看出是比较输入的字符串和位于
0x80497c0
的字符串是否相等,所以直接
gdb
后打断点查看该位置的字符串即可。
phase_2
b48 <phase_2>:
b48: push %ebp
b49: e5 mov %esp,%ebp
b4b: ec sub $0x20,%esp
b4e: push %esi
b4f: push %ebx
b50: b mov (%ebp),%edx
b53: c4 f8 add $0xfffffff8,%esp
b56: d e8 lea -(%ebp),%eax
b59: push %eax
b5a: push %edx
b5b: e8 call fd8 <read_six_numbers>
b60: c4 add $0x10,%esp
b63: d e8 cmpl $0x1,-(%ebp)
b67: je b6e <phase_2+>
b69: e8 e call fc <explode_bomb>
b6e: bb mov $0x1,%ebx
b73: d e8 lea -(%ebp),%esi
b76: d lea (%ebx),%eax
b79: f af e fc imul -(%esi,%ebx,),%eax
b7e: e cmp %eax,(%esi,%ebx,)
b81: je b88 <phase_2+>
b83: e8 call fc <explode_bomb>
b88: inc %ebx
b89: fb cmp $0x5,%ebx
b8c: e e8 jle b76 <phase_2+>
b8e: d d8 lea -(%ebp),%esp
b91: b pop %ebx
b92: e pop %esi
b93: ec mov %ebp,%esp
b95: d pop %ebp
b96: c3 ret
b97: nop
第二个阶段是要输入6个数字作为答案满足后面的检查,先读入输入的6个数字
read_six_numbers
,再对这6个数字进行循环检查,第一个数字为1,从
8048b63
这一行可以看出来,后面的数字是根据前一个数字和索引加一相乘获得。
phase_3
08048b98 <phase_3>:
b98: push %ebp
b99: e5 mov %esp,%ebp
b9b: ec sub $0x14,%esp
8048b9e: 53 push %ebx
8048b9f: 8b 55 08 mov 0x8(%ebp),%edx
8048ba2: 83 c4 f4 add $0xfffffff4,%esp
8048ba5: 8d 45 fc lea -0x4(%ebp),%eax ;; int
ba8: push %eax
ba9: d fb lea -(%ebp),%eax ;; char
bac: push %eax
bad: d f4 lea -(%ebp),%eax ;; int
bb: push %eax
bb1: de 08 push $0x80497de
bb6: push %edx
bb7: e8 a4 fc ff ff call <sscanf@plt>
bbc: c4 add $0x2,%esp
bbf: f8 cmp $0x2,%eax ;; 参数个数必须大于
bc2: f jg bc9 <phase_3+>
bc4: e8 09 call fc <explode_bomb>
bc9: d f4 cmpl $0x7,-(%ebp) ;; 第一个数字必须不大于,注意这里使用的是无符号数字的比较
bcd: 0f b5 ja c88 <phase_3+>
bd3: b f4 mov -(%ebp),%eax
bd6: ff e8 08 jmp *0x80497e8(,%eax,) ;; 根据第一个数字n3的值决定跳转地址,取值范围-
bdd: d lea (%esi),%esi
be: b3 mov $0x71,%bl ;; n1 =
be2: d fc 09 cmpl $0x309,-(%ebp)
be9: 0f a je c8f <phase_3+>
bef: e8 08 09 call fc <explode_bomb>
bf4: e9 jmp c8f <phase_3+>
bf9: d b4 lea (%esi,%eiz,),%esi
c0: b3 mov $0x62,%bl ;; n1 =
c02: d fc d6 cmpl $0xd6,-(%ebp)
c09: 0f je c8f <phase_3+>
c0f: e8 e8 08 call fc <explode_bomb>
c14: eb jmp c8f <phase_3+>
c16: b3 mov $0x62,%bl ;; n1 =
c18: d fc f3 cmpl $0x2f3,-(%ebp)
c1f: e je c8f <phase_3+>
c21: e8 d6 08 call fc <explode_bomb>
c26: eb jmp c8f <phase_3+>
c28: b3 b mov $0x6b,%bl ;; n1 =
c2a: d fc fb cmpl $0xfb,-(%ebp)
c31: c je c8f <phase_3+>
c33: e8 c4 08 call fc <explode_bomb>
c38: eb jmp c8f <phase_3+>
c3a: d b6 lea (%esi),%esi
c4: b3 f mov $0x6f,%bl ;; n1 =
c42: d fc a cmpl $0xa,-(%ebp)
c49: je c8f <phase_3+>
c4b: e8 ac 08 call fc <explode_bomb>
c5: eb d jmp c8f <phase_3+>
c52: b3 mov $0x74,%bl ;; n1 =
c54: d fc ca cmpl $0x1ca,-(%ebp)
c5b: je c8f <phase_3+>
c5d: e8 a 08 call fc <explode_bomb>
c62: eb b jmp c8f <phase_3+>
c64: b3 mov $0x76,%bl ;; n1 =
c66: d fc 0c cmpl $0x30c,-(%ebp)
c6d: je c8f <phase_3+>
c6f: e8 08 call fc <explode_bomb>
c74: eb jmp c8f <phase_3+>
c76: b3 mov $0x62,%bl ;; n1 =
c78: d fc 0c cmpl $0x20c,-(%ebp)
c7f: 0e je c8f <phase_3+>
c81: e8 08 call fc <explode_bomb>
c86: eb jmp c8f <phase_3+>
c88: b3 mov $0x78,%bl
c8a: e8 d 08 call fc <explode_bomb>
c8f: a d fb cmp -(%ebp),%bl
c92: je c99 <phase_3+>
c94: e8 08 call fc <explode_bomb>
c99: b d e8 mov -(%ebp),%ebx
c9c: ec mov %ebp,%esp
c9e: d pop %ebp
c9f: c3 ret
先使用
print (char*)
查看
0x80497de
代表的字符串格式,得到
%d %c %d
,再往下看,得到输入的三个值以后,需要知道三个值的取值,避免炸弹爆炸。查看
0x80497e8+4*n3(n1<=7)
地址的16进制值,代表的是8个跳转地址
0x8048be0, 0x8048c00 ...
。选择其中一个跳转,我选择的是0,从
$0x309,-0x4(%ebp)
可以得到第三个参数的值,再看
0x8048c8f
处的代码,可以得到第二个字符类型的值是
0x71
,查ascii表即可得到该字符。至此,第三阶段拆弹完毕。
phase_4
08048ca <func4>:
ca: push %ebp
ca1: e5 mov %esp,%ebp
ca3: ec sub $0x10,%esp
8048ca6: 56 push %esi
8048ca7: 53 push %ebx
8048ca8: 8b 5d 08 mov 0x8(%ebp),%ebx ;; (%ebx) = v1
cab: fb cmp $0x1,%ebx
cae: e jle cd <func4+> ;; 有符号小于等于,则v1 =
cb: c4 f4 add $0xfffffff4,%esp
cb3: d ff lea -(%ebx),%eax
cb6: push %eax
cb7: e8 e4 ff ff ff call ca <func4>
cbc: c6 mov %eax,%esi
cbe: c4 f4 add $0xfffffff4,%esp
cc1: d fe lea -(%ebx),%eax
cc4: push %eax
cc5: e8 d6 ff ff ff call ca <func4>
cca: f add %esi,%eax
ccc: eb jmp cd5 <func4+>
cce: f6 mov %esi,%esi
cd: b8 mov $0x1,%eax ;; 如果v1 = ,(%eax)= ,返回
cd5: d e8 lea -(%ebp),%esp
cd8: b pop %ebx
cd9: e pop %esi
cda: ec mov %ebp,%esp
cdc: d pop %ebp
cdd: c3 ret
cde: f6 mov %esi,%esi
08048ce <phase_4>:
ce: push %ebp
ce1: e5 mov %esp,%ebp
ce3: ec sub $0x18,%esp
8048ce6: 8b 55 08 mov 0x8(%ebp),%edx
8048ce9: 83 c4 fc add $0xfffffffc,%esp
8048cec: 8d 45 fc lea -0x4(%ebp),%eax ;; 假设值为v1
cef: push %eax
cf: 08 08 push $0x8049808 ;; () = "%d"
cf5: push %edx ;; 输入值的地址
cf6: e8 fb ff ff call <sscanf@plt>
cfb: c4 add $0x1,%esp
cfe: f8 cmp $0x1,%eax
d01: jne d09 <phase_4+> ;; 参数个数为,否则爆炸
d03: d fc cmpl $0x,-(%ebp) ;; 将v1和比较,带符号数比较,需要大于
d07: f jg d0e <phase_4+>
d09: e8 ee call fc <explode_bomb>
d0e: c4 f4 add $0xfffffff4,%esp
d11: b fc mov -(%ebp),%eax
d14: push %eax
d15: e8 ff ff ff call ca <func4>
d1a: c4 add $0x1,%esp
d1d: f8 cmp $0x37,%eax ;; 不等于就爆炸
d2: je d27 <phase_4+>
d22: e8 d5 call fc <explode_bomb>
d27: ec mov %ebp,%esp
d29: d pop %ebp
d2a: c3 ret
d2b: nop
这里的
phase_4
函数调用了
func_4
函数,而
func_4
是fibinacci函数,所以这个炸弹还是很简单的,只需要求到
func_4(x) = 0x37 = 55
的数字x就可以了。
phase_5
08048d2c <phase_5>:
d2c: push %ebp
d2d: e5 mov %esp,%ebp
d2f: ec sub $0x10,%esp
8048d32: 56 push %esi
8048d33: 53 push %ebx
8048d34: 8b 5d 08 mov 0x8(%ebp),%ebx
8048d37: 83 c4 f4 add $0xfffffff4,%esp
8048d3a: 53 push %ebx
8048d3b: e8 d8 02 00 00 call 8049018 <string_length> ;; 获取输入的字符串长度,放在%eax中
d4: c4 add $0x1,%esp
d43: f8 cmp $0x6,%eax
d46: je d4d <phase_5+> ;; 字符串长度为
d48: e8 af call fc <explode_bomb>
d4d: d2 xor %edx,%edx
d4f: d d f8 lea -(%ebp),%ecx
d52: be b2 08 mov $0x804b22,%esi
d57: a a mov (%edx,%ebx,),%al
d5a: 0f and $0xf,%al
d5c: 0f be c movsbl %al,%eax
d5f: a mov (%eax,%esi,),%al
d62: 0a mov %al,(%edx,%ecx,)
d65: inc %edx
d66: fa cmp $0x5,%edx
d69: e ec jle d57 <phase_5+>
d6b: c6 fe movb $0x,-(%ebp)
d6f: c4 f8 add $0xfffffff8,%esp
d72: 0b 08 push $0x804980b
d77: d f8 lea -(%ebp),%eax
d7a: push %eax
d7b: e8 b call <strings_not_equal>
d8: c4 add $0x1,%esp
d83: c test %eax,%eax
d85: je d8c <phase_5+>
d87: e8 call fc <explode_bomb>
d8c: d e8 lea -(%ebp),%esp
d8f: b pop %ebx
d9: e pop %esi
d91: ec mov %ebp,%esp
d93: d pop %ebp
d94: c3 ret
d95: d lea (%esi),%esi
push $0x804980b
和后面的
call 8049030 <strings_not_equal>
是为了比较两个字符串是否相等,查看地址
0x804980b
的字节码,会得到6个字节码
c0 c1 c2 c3 c4 c5
。而和这6个字节相比较的字符串是通过
mov %al,(%edx,%ecx,1)
循环写入的。而这里的
%al
则是来自
mov (%eax,%esi,1),%al
,这里的
%esi
的值是
0x804b220
,该地址后面的字节可以通过
x/2w 0x804b220
获取,其实就是根据我们输入的字符的低字节作为
mov (%eax,%esi,1),%al
的
%eax
,即索引值,获取我们想要的字符,理解之后就可以根据上面的分析很容易得到想要的字符低位字节,再在ascii表中选择字符就可以了,答案不是唯一的。
phase_6
d98 <phase_6>:
d98: push %ebp
d99: e5 mov %esp,%ebp
d9b: ec c sub $0x4c,%esp
d9e: push %edi
d9f: push %esi
da0: push %ebx
da1: b mov (%ebp),%edx
da4: c7 cc c b2 movl $0x804b26c,-(%ebp)
dab: c4 f8 add $0xfffffff8,%esp
dae: d e8 lea -(%ebp),%eax
db1: push %eax
db2: push %edx
db3: e8 call fd8 <read_six_numbers>
db8: ff xor %edi,%edi
dba: c4 add $0x10,%esp
dbd: d lea (%esi),%esi
;; 下面这段证明个数字为-,顺序不知,不过个数字不能重复
dc0: d e8 lea -(%ebp),%eax
dc3: b b8 mov (%eax,%edi,),%eax
dc6: dec %eax ;; 证明不能为,否则无符号数比较后不跳转
dc7: f8 cmp $0x5,%eax
dca: jbe dd1 <phase_6+> ;; 无符号低于或等于
dcc: e8 b call fc <explode_bomb>
dd1: d f lea (%edi),%ebx
dd4: fb cmp $0x5,%ebx
dd7: f jg dfc <phase_6+>
dd9: d bd lea (,%edi,),%eax
de0: c8 mov %eax,-(%ebp)
de3: d e8 lea -(%ebp),%esi
de6: b c8 mov -(%ebp),%edx
de9: b mov (%edx,%esi,),%eax
dec: b e cmp (%esi,%ebx,),%eax
def: jne df6 <phase_6+>
df1: e8 call fc <explode_bomb>
df6: inc %ebx
df7: fb cmp $0x5,%ebx
dfa: e ea jle de6 <phase_6+>
dfc: inc %edi
dfd: ff cmp $0x5,%edi
: e be jle dc0 <phase_6+>
: ff xor %edi,%edi
: d d e8 lea -(%ebp),%ecx
: d d0 lea -(%ebp),%eax
a: c4 mov %eax,-(%ebp)
d: d lea (%esi),%esi
: b cc mov -(%ebp),%esi
: bb mov $0x1,%ebx
: d bd lea (,%edi,),%eax
f: c2 mov %eax,%edx
: b c cmp (%eax,%ecx,),%ebx
: d jge <phase_6+>
: b a mov (%edx,%ecx,),%eax
: d b4 lea (%esi,%eiz,),%esi
: b mov (%esi),%esi
: inc %ebx
: c3 cmp %eax,%ebx
: c f8 jl <phase_6+>
: b c4 mov -(%ebp),%edx
b: ba mov %esi,(%edx,%edi,)
e: inc %edi
f: ff cmp $0x5,%edi
: e cc jle <phase_6+>
: b d0 mov -(%ebp),%esi
: cc mov %esi,-(%ebp)
a: bf mov $0x1,%edi
f: d d0 lea -(%ebp),%edx
: b ba mov (%edx,%edi,),%eax
: mov %eax,(%esi)
: c6 mov %eax,%esi
a: inc %edi
b: ff cmp $0x5,%edi
e: e f2 jle <phase_6+>
: c7 movl $0x0,(%esi)
: b cc mov -(%ebp),%esi
a: ff xor %edi,%edi
c: d lea (%esi,%eiz,),%esi
: b mov (%esi),%edx
: b mov (%esi),%eax
: b cmp (%edx),%eax
: d jge e <phase_6+>
: e8 e call fc <explode_bomb>
e: b mov (%esi),%esi
: inc %edi
: ff cmp $0x4,%edi
: e e9 jle <phase_6+>
: d a8 lea -(%ebp),%esp
a: b pop %ebx
b: e pop %esi
c: f pop %edi
d: ec mov %ebp,%esp
f: d pop %ebp
: c3 ret
: d lea (%esi),%esi
- 先利用函数
读入6个数字,根据bomb的条件可以得到6个数字为不重复的1-6,因为如果数字为0,read_six_numbers
得到的二进制位0xffffffff, 从dec %eax
和cmp $0x5,%eax
可以得出是无符号比较,所以不会跳转引起bomb;jbe 8048dd1 <phase_6+0x39>
- 从
到0x8048e10
的汇编语句必须联系地址为0x8048e42
的情况才能得出结论,使用gdb获取该地址存储的值,可以得到该处是一个链表的node节点,再假设一组1-6的随机分布代入该段汇编,可以得出实际上有2个数组,分别命名为input[]和addr[]。input数组存储的是我们输入的6个数字,而addr数组则是存储input数组相应位置所对应的链表索引。举个例子,如果0x804b26c
,那么b[0]所对应的就是链表的第5个node,该node的第二个字存储的其实就是0x5;input[] = {5, 2, 1, 4, 3, 6}
- 最后将addr[]数组元素即链表地址所对应的链表进行重新连接排序,根据最后避免bomb的条件很容易可以看出链表数据是倒序排列,根据
处的链表信息可以得出最后的索引顺序。至此,第六阶段也结束了。0x804b26c