本次实验主要考察汇编代码以及GDB调试的应用。实验总共要求6次输入,每次输入都正确才算完成实验,每次输入的值隐藏在汇编代码中。实验通过反汇编二进制文件得到汇编代码,通过GDB查看寄存器值和内存器值辅助阅读代码,理解代码之间的逻辑及联系,找到解题的关键。
预备知识
首先介绍一下要用到的命令:
反汇编bomb文件,并将得到的汇编代码保存在code.txt中方便查看。(只需要汇编代码就行,符号表不需要)
1 |
|
gdb命令:
gdb bomb 用gdb对bomb进行调试
run 从头开始运行程序
kill 结束运行程序
break *0x80483c3 在地址0x80483c3处设置断点
delete 删除所有断点
stepi 运行一条指令
continue 继续执行直到遇到下一个断点
until 3 据徐执行直到遇到断点3(gdb会自动给每个断点编号)
print /x $rax 以16进制打印寄存器中rax的值
print /x ($rsp+8) 以16进制打印rsp的内容+8的值。注意这里是rsp寄存器的内容,而不是所对应的栈地址的值。简单来说,是得到$rsp+8的值,而不是($rsp+8)的值。
x/w 0xbffff890 检索以地址0xbffff890为首连续四个字节的数据。
x/s 0xbffff890 检索以地址0xbffff890为首的字符串。
x/w ($rsp+8) 检索以rsp+8为首地址的四个字节,简单来说,是获得(rsp+8)而不是rsp+8。
phase_1
第一个炸弹非常简单,方便我们熟悉汇编代码和gdb的操作。
首先在code.txt中找到main函数的代码,因为程序总是从main开始开始的,然后找到第一个炸弹phase_1的程序入口,查看代码。
分析phase_1中的代码,当执行嵌套函数<strings_not_equal>后,寄存器eax的值为0时可以解除第一个炸弹。所以我们查看strings_not_equal的代码,观察什么条件下才能使得eax=0。
1 |
|
仔细阅读代码,发现就是比较rsi和rdi寄存器的值,若相同则能使eax为0。运行程序,输入数据,发现rdi存放的就是我们输入的数据,那么rsi就是正确的数据。回到phase_1中,发现rsi的值是0x402400。ok,直接使用x/s 0x402400即可获得正确的字符串。
phase_2
第二个实验比较直观,看汇编代码就可以。
1 |
|
phase_3
第三个炸弹比较巧妙。根据汇编代码以及gdb查看寄存器值,我们可以判断出eax存放的是输入的数据个数,当输入个数大于等于2时,eax=2,输入一个数时eax为1,而这里要求eax大于1,再结合上下代码,我们可以判断出一个只需要输入两个数字,第一个数字存放在rsp+8中,第二个存放于rsp+0xc中,即便你输入更多的数字,也只有前两个是有效的,其余忽略。
仔细查看代码,可以发现最重要的一行跳转指令jmpq 0x402470(,%rax,8),程序将跳转到(0x402470+8第一个数字)处,并赋值eax,然后eax和输入的第二个数字进行比较,若相等,则解除炸弹。查看地址0x402488,发现其内容是0x400f8a,恰好是跳转到后面的指令。
这很好理解,0x402488是一张表table的起始点,第一个数字是索引下标index,第二个数字等于table[index]的值即可解除炸弹。
1 |
|
phase_4
简单分析一下代码,发现eax代表的是读入的数字,当读入数字大于等于2时,eax=2,否则eax为1。而rsp+8存放的是第一个读入的数字,rsp+c存放的是第二个读入的数字,如果你输入多余2个数字,那么其余数字将会舍弃。
仔细看一下代码,发现只有一处cmpl $0x0, 0xc(%rsp)用到了第二个输入数字,结合判定条件,显然第二个数字是0。
再分析一下func4这个代码,当且仅当ecx等于第一个输入数字时,程序才会返回eax=0这个我们想要的结果。而第一次进入func4时,ecx的值是7,所以第一个输入的数字是7。
Func4其他的代码构成了一个递归序列,属于烟雾弹坑人的,一开始我掉进去了很久才醒悟过来。根本没必要弄清楚函数递归时各个寄存器的关系,只需要弄明白什么条件下能获得想要的结果就行。
1 |
|
phase_5
这里一开始看到字符串比较,我就以为是之前的套路,查找正确字符串地址,发现是flyers,输入flyers,结果却是boom!!!爆炸了。
仔细查看代码,发现并不是直接拿输入的字符串和”flyers”比较,而是对输入字符串低4位做一定操作后得到的结果和flyers比较。
那么是什么操作呢?注意到程序中截取输入字符串中每个字母的低四位进行操作,结合movzbl 0x4024b0(%rdx), %edx,看一看0x4024b0处,发现是一堆字母。
并且最后进行字符串比较的,是flyers和从0x4024b0偏移得到的字符串。
明白了,0x4024b0就是一个含有flyers的表格,输入字符串低四位就是flyers在表格中的索引位置。
所以只要数一下flyers在表格里是第几位,就可以分别得到输入字符串的低四位。
又因为输入是字符串,所以查一下ascii表就行。最后的结果是ionefg。(不唯一,用大写字母应该也是可以的,只要低四位符合要求就行)。
1 | 0000000000401062 <phase_5>: |
phase_6
第六个炸弹是最难的,变量赋值运算繁多,地址映射关系复杂,理不断剪还乱,需要将程序划分为几个模块来理解。
读数
第一个阶段是读取六个数字,然后进入两个循环,第一个循环检查读入的数字是否大于0,小于等于6,第二个循环将前面的数字与后面的每个数字进行比较,保证相互之间不相等。
综上输入的六个数字必须是1~6并且互不相等,所以必然是1,2,3,4,5,6六个数字,输入顺序需要我们进一步确定。
1 | 00000000004010f4 <phase_6>: |
变数
用7减去输入的数字,并按照原来的顺序保存差的结果,进入下一个阶段。
1 | section2 |
获取地址
每一个数字都对应一个地址,这个阶段是将数字对应的地址,按照数字输入的顺序存放到rsp+32中。
1 | #=========================================================================================== |
变址
上一阶段存放到rsp+32中的地址只是内存地址,类似于指针,其本身还指向一个数值。
这里要求将后一个地址存放到前一个地址的后面。
1 | #=========================================================================================== |
验证
这里要求前一个地址对应的值要大于后一个地址对应的值。 所以现在的任务就是找到输入数字、地址、地址值之间的关系。如下表所示:
原始输入数字 | 7-数字 | 地址 | 地址对应值 |
---|---|---|---|
1 | 6 | 0x603320 | 0x1bb |
2 | 5 | 0x603310 | 0x1dd |
3 | 4 | 0x603300 | 0x2b3 |
4 | 3 | 0x6032f0 | 0x39c |
5 | 2 | 0x60032e0 | 0xa8 |
6 | 1 | 0x60032d0 | 14c |
从表格以及要求我们可以知道,第一个输入数字对应的地址对应的值最大,最后一个输入数字对应的地址对应的值最小。
不难得到正确的输入序列:4,3,2,1,6,5
1 | #=========================================================================================== |
总结
通过这次试验,大致了解了gdb的使用,虽然我觉得以后不太可能从事汇编相关的工作(看的脑疼),不过还是收获蛮多的。