这次的实验真的挺有意思的,但是枯燥的汇编代码,实在是让我非常捉急,晦涩难懂,真的还好有汇编器这种东西的出现,否则真的贼要命啊去编写汇编。这次的实验让我增长了见识,还有熟悉地练习了一下gdb,使自己对逆向工程加深了一定见解。
刚开始下载完程序之后,我们并不知道自己该干些什么事情,看看文档writeup,不过反正看起来人家说得挺清楚,就是让你是拆炸弹,又怎么拆?我们先看一下目录。
这里只提供了3个文件,而且README也没说什么。我们直接看看
bomb.c
吧。好像每一行都有一个这样的东西,我们先记住。
尝试运行下
./bomb
,然后随便输入点东西?确实炸了哦,又怎么回事?这给我第一个感觉就是输入某些东西,如果不正确就错误,其实我第一个实验刚开始也不特别会,因为不会gdb,所以觉得很懵逼,我也是稍微参考了一下别人,才开始做了第一个实验(gdb先稍微自学一下,网上教程很多)。
好,从
gdb bomb
进入。
l
进入以后输入此命令,查看一下源码,然后break 37
,将源码定位在37行,然后run
,然后disas
,看看main函数的反编译,密密麻麻的好像没什么提示,我们想找爆的源在哪。再试试disas phase_1
。
稍微看看汇编码,完完全全好像就是这么感觉的回事,我们要输入某些东西,使其避开explode_bomb(顾名思义)。
一,phase_1
好吧我们一步步来解开这些谜团吧。
disas phase_1
稍微看看二进制码,很晕哦,但我们隐约记得
%esi
是第一个参数入口,然后还调用了strings_not_equal函数比较,难道第一个拆炸弹行为就是对比一下字符串想不想等,不相等就爆炸,那这个0x402400
,是个什么东西。我们使用x
命令访问一下,x 0x402400
果然出现了一串字符串,再看看这么短的汇编码,毫无疑问,这就是答案了。
答案:Border relations with Canada have never been better.
二,phase_2
上面做通了下面这道题也会做得很快,其实道理一样,无非就看看寄存器,和内存给你的信息罢了,这是最简单的方式,好吧,我们继续用最笨的方法来解决这次的爆炸。
break phase_2
同样的,我们先设置断点在第二个阶段函数。
disas phase_2
前4段特别好理解其实就是先申请一堆空间,我们暂时不管他,我们可以看到read_six_numbers,再次顾名思义,他要我们输入6个数字,其实看到这里我们已经可以不管了,
run
一下,随便输入6个数字即可,不管我们还是看看里面的究竟disas read_six_numbers
。简略的看一看,里面就是有个sscanf读入,而且不正确读入的话也会引发炸弹,我们继续对照函数的调用,看到又有一个地方特别神奇,完完整整是一个地址。然后我查了一查sscanf的声明:
int sscanf( const char *buffer, const char *format, [ argument ] ... );
,第2个参数是输入格式,那么刚好对照我们的[ $0x4025c3,%esi ],怎么验证我们是正确的呢?x/s $esi
。真的要我们设置一个读入六个参数的设置。剩下的就不用看了,我们就正常
run
,输入六个参数。disas phase_2
再次回到函数phase_2我们先从( 1 )看起,这句话给了我们一个提示,我们第一个参数要是< 1 >,然后成功的话跳转到[ lea 0x4(%rsp),%rbx ]第( 3 )段中,这里的[ 0x18(%rsp),%rbp ],这里的rbp一看就知道是个终结位置,因为6个整形大小就是0x18 = 24B,然后我们跳到第( 2 )段去,着段码也很好理解,不就是获取前一个位置的数存入寄存%eax,然后%eax *=2,这不就是就告诉我们这个阶段要的答案是以1开头的,后一个数比前一个数大2倍的序列吗?,显而易见。
答案:1 2 4 8 16 32
三,phase_3
disas phase_3
不必多说,继续看反汇编码。
x/s 0x4025cf
看到又有一个常数放在第2参数读入了吧?想都不想,直接输出看看。这次要读入两个整数。这两个整数又是什么呢?
第( 1 )就告诉我们第一个数字必须是小于7,然后接着是一个多个选择,我就不解释了,这很容易看懂,然后第( 3 )我们看到一个比较[ $0x137,%eax ],这个数字必须是等于的否则就会引爆炸弹,所以
print 0x137
。所以答案就很容易推算出来。好像这道题有多种答案,但最后一个是确定的,因为我就输入这两个就过了,分支我也没看下去。
答案:1 311
四,phase_4
disas phase_4
估计也知道我想说什么了,没错先看看输入的是什么。
x/s 0x4025cf
第( 2 )个阶段已经告诉我们了,我们的第< 1 >个数必须是小于14。
从第( 1 )个阶段我们可以看到它将我们第< 1 >个数值输入到函数当中,并且要返回一个0才能跳过炸弹,从第( 2 )个阶段中自然解出第< 2 >个数值必须是0。那么我们现在看看fun是个什么东西。
disas func4
反正我看着就觉得烦,所以有研究的同学可以自己去观察一下这是什么意思。因为这个函数里面根本没有炸弹,我们可以把它直接当作黑盒测试。
我们尝试着输入8,0 5,0 4,0... 你会发通过func函数返回,好像就是log2(< 1 >)。怎么看呢我们可以看看上面那张图的( 1 )阶段通过
print $eax
测试call完函数之后的返回值,只要是0就是答案。所以很容易推出来答案是什么。
答案:1 0
五,phase_5
disas phase_5
首先,从着两段看出,我们得随便先输入一个字符串。这个字符串只有6个字母大小。
我们首先看看第( 2 )段,这里要比较一下两个字符串是否相等那么,同时又有新的发现[ $0x40245e,%esi ]想都不用想,先输出来看一下先,x 0x40245e
我们要输出的是一个和这个字符串相同的字符串,假设我刚刚在
run
时输入了123456,我看一看我的字符串变成什么样子了。x ($rsp+0x10)
明显这两个字符串不是一回事情。这时候我们就看看( 1 )究竟做了什么操作,其实这上面也很简单,看看就懂,我们主要关注的是这句话[ and $0xf,%edx ]它把我们的字符相与了0xf。然后好像从字符串找找出对应字符来。这个字符串又是什么?
x (0x4024b0)
瞬间恍然大悟,炸弹的拆除方法,就是从将每个字符的低4位作为索引,从中找到的字符串组合起来,使其能够成为flyers。那么我们该输入怎么样的字符串,才是对应的索引呢?别着急。在linux下输入
man ascii
。我们从第64的字符编码@开始,依次输入16个编码,通过
x ($rsp+0x10)
,就可以找到映射关系,然后就可以找到答案了。这个答案有很多个。但原理就是取每个字符的低4位作为索引,这也就为什么我说只要依次输入16个编码即可。
答案:9o>567
六,phase_6
这段代码实在有点长,而且比较难读懂,只能自己一点点手推代码。稍微不细心就会很心乱。我们还是一步步来吧,这个还是得自己手写一下,否则的话也是看得半懵状态,因为这个代码实在太难解释了,我就说说大概定位的每段的意思,也方便你来自己定位断点。
disas phase_6
还是原来很老的处理,先读入6个数字,这个上边已经叙述过,直接输入6个数字即可。
然后我们继续往下看
这段代码其实是这样的:在( 1 )中,输入的每一个数字-1必须小于等于5,( 2 )并且每一个数字都不能相等。所以我们输入的数字都是如何1,2,3,4,5,6这样才合法。
继续往下看
这段代码的意思就是:就是7-每一个数字,例如刚刚的1,2,4,3,5,6得到的就是6,5,3,4,2,1。
前面这两段都很好理解,接下来这段如果不知道某些东西就gg了。
这段代码就是,将某些东西进行排序?没错又有一段很神奇的代码[ $0x6032d0,%edx ],我一开始我也不会看这是什么回事我以为是字符串或者什么的,这要输入下面的命令x/24xw 0x6032d0
,你如果一开始使用x 0x6032d0
你就会发现有一个node1的出来,要多加几个读入才能输出完整的链,所以x
命令后面加了个24。
这一看就知道就是个结构体,那么上面的代码的意思其实是:按照相减少之后的排序每个节点重新排列一遍,如果按照1,2,4,3,5,6得到的输出6,5,3,4,2,1,那么排序就是第6个节点排第1,第5个节点排第2,第3个节点排第3...
struct
{
int value;
int order;
tag* next;
} tag;
接着再往下看
第( 1 )段很好理解,只是判断是否已经到了链的尾部,最重要的是第( 2 )段,它表示前一个节点的值必须比后一个节点的值大。这答案就出来了啊。
答案:4 3 2 1 6 5
我可能不一定完全能让你全懂,代码还是得自己试读,我只能提供一个启发,你自己去试试。突然发现实验居然能这样子做,真的有意思。