Dr.Evil在你的机器上埋了大量的“”二进制炸弹“,你的任务是输入字符串来拆除炸弹。如果输入了正确的字符串,炸弹将被拆除,否则 ”BOOM!!!“
加油吧,拆弹英雄们!!!
bomb lab共有6个关卡:
1-4关卡 10分一个5-6关卡 比较难,15分一个,总共70分
GNU专用debugger工具,简单好用。如果你从来没有debugger过,建议看看gdb Tutorial,下面列出一些该实验中需要的指令
指令 | 效果 |
---|---|
run | 开始运行程序 |
kill | 结束运行 |
finish | 执行完当前函数 |
nexti | 执行一条指令(跳过子程序) |
nexti 4 | 执行4条指令 |
stepi | 执行一条指令 |
stepi 4 | 执行4条指令 |
print/format | 打印表达式 |
break | 设置断点 |
x/format | 查看内存里的值 |
disas function | 反汇编函数 |
其中format是:
n 打印几个单位
o(八进制) …x(十六进制)…d(十进制)…u(unsigned)…t(二进制)…f(浮点),s(字符串)
b(bytes)…h(halfword)…w(word)
例:
x/20b mem 打印地址mem的20个字节
x/s mem 打印地址mem的字符串
print $esp (是的,你没看错,在gdb里寄存器是用$前缀)
objdump -t 反汇编程序
为什么我要把最后的实验感受放在前面呢?因为这个实验深刻教育了我。
当我面对满屏黑漆漆的汇编代码的时候,内心有些小崩溃的,当我开始开始做实验的时候,根本不知道从哪里入手,手忙脚乱的输入指令,却没有一丝思路,内心崩溃程度50%。当终于找到路子,开始拆弹时,看到和cs:app里完全不同的汇编代码,他们混乱,复杂,你会有种盲人摸象的感觉,分析了好长时间的代码,发现根本不是重点,崩溃100%
但是,还是坚强QAQ地拆完了炸弹,写下了这篇blog。经过这一次拆弹体验,我才真正学会了调试,千言万语,尽在不言中。QwQ
所有如果你遇到困难,坚持做下去o( ̄▽ ̄)o
我总结了几个调试经验
①不要试图分析所有代码,分析关键,不然会很浪费时间。
②仔细分析跳转指令,是拆弹的关键
③不要放弃(ಥ _ ಥ)
输入 objdump -t |bomb less
印入你眼前的,是恶心的代码。不要急,慢慢寻找线索。你会慢慢看到
phase_1
phase_2
......
explode_bomb
phase_1应该是关卡1函数的名字,explode_bomb应该是炸弹爆炸函数的名字。
好啦,得到这些信息后就可以开始调试了。
首先设置断点,并运行程序
break phase_1
break explode_bomb
run
然后根据提示,输入第一关的密码。随便输入一个(我输入1234)
然后在反汇编phase_!函数
disas
第四行 callq string_not_equaled
第五行:test %eax,%eax
第六行 je 0x4003f
第七行: call explode_bomb
大概就明白了,首先调用函数string_not_equaled,如果返回值是0,拆弹成功,否则BOMB!!!
string_not_equaled,从名字上看,大概是判断字符串是否相等。那我们再运行到string_not_equaled函数里,看看该函数的参数
x/s $edi
x/s esi
第一个参数显然就是我们的输入
第二个参数是Border relations with Canada have never been better.。
呢,答案出来了,判断输入的字符串是否为Border relations with Canada have never been better.,赶快保存起来吧。
还是一样
break phase_2
run
输入密码 (我的输入是1234)
disas
第5行 callq read_six_number
第6行 cmpl $0x1,(%rsp)
第7行 je phase_2+52
第8行 callq 0x400f30
从中可以看到,调用了read_six_number,比较(%rsp)是否为1,否则爆炸。我们可以得到一些模糊的信息。但不确定。只能看看read_six_number的代码了。
第12行 call _isoc99_sscanf
第13行 cmp $0x5,%eax
第14行 jg read_six_number+61
第15行 call explode_bomb
同样,我们还是看看_isoc99_sscanf的参数
第一个参数是 ”1234“即是我们的输入
第二个参数 “%d %d %d %d %d %d”
再加上比较返回值是否大于5,否则爆炸。我们可以推测密码是6个数字
重新来过
run
输入密码 (我的输入是1 2 3 4 5 6)
disas
nexti 5
现在,直接研究执行完read_six_number后的情况
程序需要检查(%rsp)的值,那我们就看看现在(%rsp)里的值。可以看到 ,是对应了我们的输入。哦,程序把我们输入的6个值放在了(%rsp)里,而且第一个数字必须为1(第6行 cmpl $0x1,(%rsp))
沿着程序,我们发现这是的循环(即下图)
这个循环干什么呢,①你可以分析每条语句,判断该循环在干什么。 ②你可以单步调试,看看每一步产生的影响是什么(比如寄存器值的改变,栈的改变)来推测 循环在干什么 (笔者用的第二种方法)。
可以分析得到,该循环是每次数字翻倍.答案即是 1 2 4 8 16 32
第6行 callq __isoc99_sscanf
第7行 cmp $0x1,%eax
第8行 jg
第9行 callq explode_bomb
可知,输入的数 数量要大于1,否则Bomb!!
在看看_isoc99_sscanf的参数:
%edi ”1 2 3 4 5 6“
%esi ”%d %d“
(⊙o⊙)?原来只用输入2个数。
第10行 cmpl $0x7,0x8(%rsp)
第11行 ja phase_3+106
第12行 mov 0x8(%rsp),%rax
第13行 jmpq *0x402470(,%rax,8)
首先,看看0x8(%rsp)里的值,是1,对应了我第一个输入。可知,第一个输入必须小于7,否则bomb,第13行,明显的跳转表,我们可以估计是用我们第一个输入当索引跳转。
此时,你有2个选择①逐行分析跳转表②单步调试,看看跳转表产生的效果。
最后,我们明白跳转表会给%eax赋值,而我们第二个输入必须等于该值,否则Bomb!!!
答案有很多,我测试的答案是 1 311
第6行 callq __isoc99_sscanf
第7行 cmp $0x2,%eax
第8行 jne
第9行 cmpl $0xe,0x8(%rsp)
第10行 jbe
很明显,输入数的数量必须等于2,否则Bomb!!!
且第一个输入小于14
在分析%rsp,发现输入的第一个数在 0x8(%rsp),第二个数在0xc(%rsp)
callq func4
test %eax,%eax
jne
看来这个func4是关键了。
首先看看参数
这个时候,你会很摸不着头脑,感觉参数都很莫名其妙,为什么是三个参数,为什么只有第一个参数和我们的输入对应(ಥ _ ಥ) (为什么是三个参数,可以从func4里得知)
此时,先就试试者三个参数的值,把func4的返回值摸清楚。
这里我也老老实实的把程序分析了一便(*  ̄︿ ̄)。还不知道什么取巧的方法。
func4(x,y,z)
{
%ecx = ((((z-y)>>3) +z-y)>>1)+y //偷懒了
if(%ecx==x)
return 0;
else if(%ecx > x)
return func(x,y,%ecx-1);
else
return func(x,y,%ecx+1)
}
说实话,被这个代码恶心到了(ง •_•)ง .就姑且认为y =0 ,z=14.
func4(x,0,14)
{
%ecx = 7 //偷懒了
if(%ecx==x)
return 0;
else if(%ecx > x)
return func(x,y,%ecx-1);
else
return func(x,y,%ecx+1)
}
in phase_4
callq func4
test %eax,%eax
jne
既然要求func4的返回值是0,那我们就不管后面的递归,让第一个参数x=7,也就是输入第一个数字是7
cmpl $0x0,0xc(%rsp)
je
这里看出,第二个输入必须是0,否则Bomb
答案便是 7 0
(这关真的挑战人的心态,感觉答案都很迷(#°Д°)
这关非常有意思( ̄︶ ̄)↗
(这个关卡有金丝雀哦)
话不多说 输入密码 (abcdef)
callq string_length
cmp $0x6,$eax
je
看来是我们输入的字符串长度要等于6,否则Bomb!!! (那就重来,输入abcdef)
callq string_not_equal
test %eax,%eax
je
和第一关一样的套路啊,运行到string_not_equal,看看参数"★,°:.☆( ̄▽ ̄)/ : ∗ . ° ★ ∗ 。 " ! [ 这 里 写 图 片 描 述 ] ( h t t p s : / / i m g − b l o g . c s d n . n e t / 20180526105516878 ? w a t e r m a r k / 2 / t e x t / a H R 0 c H M 6 L y 9 i b G 9 n L m N z Z G 4 u b m V 0 L 3 d l a X h p b l 80 M T I 1 N j Q x M w = = / f o n t / 5 a 6 L 5 L 2 T / f o n t s i z e / 400 / f i l l / I 0 J B Q k F C M A = = / d i s s o l v e / 70 ) 我 们 发 现 , :*.°★* 。" ![这里写图片描述](https://img-blog.csdn.net/20180526105516878?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80MTI1NjQxMw==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70) 我们发现, :∗.°★∗。"![这里写图片描述](https://img−blog.csdn.net/20180526105516878?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80MTI1NjQxMw==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70)我们发现,edi是空地址,而第二个参数是flyers, 而在rsp里也找不到我们输入的abcdef(;´༎ຶД༎ຶ`)
没办法,只能分析在执行string_not_equal前,程序做了什么事。
此时你有两个选择①老老实实的分析循环②单步调试,看看每一步的作用是什么(我选择的是第一二结合(✿◡‿◡))
movzbl (%rbx,%rax,1),%ecx #取输入的字符(我看了%ecx的值,发现是我们的输入,就能推测出)
mov %cl,(%rsp) #取字符的低8位
mov (%rsp),%rdx
and $0xf,%edx #取低4位
movvbl 0x4024b0(%rdx),%edx #big discover
mov %dl,0x10(%rsp,%rax,1)
add $0x1,%rax #循环6次,正好对应我们输入6个字母
cmp $0x6,%rax
jne 0x40102b
big discover : 我们发现,这是一个映射表,取你输入字母的低4位当索引,转换成上面字符串中的字母。
这样就能解释为什么我们输入abcdef,会出现aduier
|input|index|output
|–|--|
|a|0001|a
|b|0010|d
|c|0011|u
|d|0100|i
|e|0101|e
|f|0110|r
答案也就出来了,输入6个字母,其低4位,经过映射表的转化,是flyers。 ψ(`∇´)ψ
ionefg
第6关,是个硬骨头(ง •_•)ง加油。建议腾出超过2小时。
而且这题,我没有取巧,老老实实的把代码都分析了一编(这个代码很良心)(下面具体代码就不分析了)
第一段:执行read_six_numbers判断是否读了6个数
首先判断你输入的数是否小于等于6,否则Bomb,再判断你输入的6个是是否各不相等,否则Bomb
把你输入的六个数num, num = 7-num
是一个结构体
typedef struct node{
int lval;
int rval;
node *next;
};
后面的代码我没有分析,因为我估计是输入的数字是链表的顺序,要么是从大到小,要么是从小到大。
从大到小 3 4 5 6 1 2 从小到大 2 1 6 5 4 3
但你会发现,输入的上面两个密码都是错的,这时,你会想去之前有把六个数被减7,那答案应该是:
从大到小4 3 2 1 6 5 从小到大 5 6 2 1 3 4。经过测试。正确答案是从大到小,即4 3 2 1 6 5
???有空在研究