Boomlab
(感觉这个实验也可以直接丢进IDA当逆向做,相对简单;本次以汇编语言下手;所用工具为VS2022社区版)
先在Linux里用如下命令把文件反汇编出来。
1 | objdump -d bomb > bomb.asm |
查找到main函数方式多样,此处直接搜索main即可找到位置。
大致浏览一遍main函数,有几个phase,就是需要拆除的炸弹了。
本文着重描述过程,运算只涉及最后的结果。
本文着重描述过程,运算只涉及最后的结果。
本文着重描述过程,运算只涉及最后的结果。(水平有限)
Phase_1
根据函数对应的地址跳转
可以看到该函数中,先将一段地址赋给esi,然后调用strings_not_equal;然后测试eax值,为0就跳过了引爆炸弹的一步。由此可见关键在于strings_not_equal这个函数。
可以看到还是比较长的,不过容易读懂。首先是传参,将rbx,rbp作为string_length的参数,然后调用。可以根据最开始的两个mov猜测,rdi,rsi就是string_not_equal的两个参数。
再看本函数结尾,会有一个给eax赋值的操作,然后返回。这时候就需要知道edx被赋了什么值。
浏览代码可以看出大致逻辑(也可以根据函数名直接猜)两参数的字符串相等则返回0,不相等则返回1
查看esi里的地址所对应的字符串如下:(用gdb调起来才能看到,不熟悉gdb也可以拖进IDA,看汇编还是伪代码就自己选择了)
“Border relations with Canada have never been better.”
Phase_2
首先是代码
可以看到调用read_six_numbers函数
代码大致逻辑为读取6个数,没有六个就引爆炸弹。
接着看phase_2代码,将rsp的值与1作比较,不相等就爆;然后将rsp+0x4和rsp+0x18的值给了rbx,rbp然后是把rbx-0x4地址处的内容给了eax,然后eax+eax,再做比较,相等就不爆;再继续,将rbx值加4字节(一个int的大小),到了下一个数,再进行比较。依照逻辑,输入的六个数分别为:1 2 4 8 16 32.即可拆弹
Phase_3
代码
先查看一下0x4025cf处是啥,结合下面的输入函数,这是两个输入的参数。这里可以假设一下rcx和rdx就是这两个参数。
将0给eax,然后调用参数后将eax的值与1作比较,大于就不爆。然后将rsp+0x8的值,也就是rdx的值与7作比较,大于就跳转,小于就爆,回到上一步向下看,会将rdx的值给eax,然后跳到402470这个地址
不知道这是个啥
用IDA发现是到了switch语句。(其实是汇编语言的switch实现)
看后面的代码,都会跳到400fbe的位置,将eax的值与rsp+0xc的值作比较,相等则不爆。
这题答案不固定,根据第一个数不同,第二个数也会不同随机选取一组符合条件的即可。
Phase_4
代码
前面几行跟上一个阶段一样,就是输入两个值作为参数。
再将eax的值与2作比较,不相等就爆。然后将0xe和rsp+0x8的值作比较,大于0xe就不跳,故此处需小于。
然后是三个赋值操作,再接着调用func4函数,然后验证eax的值,不为0就爆,接着rsp+0xc的值等于0,炸弹拆除。
func4:
由上面的分析可知,此处要让eax的值为0。这玩意儿还挺复杂,自己调用自己,递归了。
大致逻辑:上面phase_4的edx,esi,edi作为func4的三个参数。将edx的值给eax,用eax的值减esi,然后保存在ecx,再将ecx的值逻辑右移31位(0x1f),再将eax的值与ecx的值相加,保存在eax,然后算数右移1位(需要注意与逻辑右移的区别。)然后是ecx=rax+rsi*1,然后是ecx和edi比较,ecx较大就跳。400ff2处,edi的值小于ecx则跳转并结束该函数调用,否则将rcx+0x1的值给esi。从400fe6继续看,将rcx-0x1的值给edx,然后调用func4,然后eax+eax,然后结束调用。
上代码,帮助理解:
1 | int fun(int a1, int a2, int a3){ |
此处a1,a2,a3分别为0xe,0x0,rsp+0x8(输入的第一个值)
最后,什么时候返回0。我们将值代入发现,7是能返回0的一个界限,那么7,0可以作为一种答案。
看大佬们的解法才恍然大悟。
1 | int main(void){ |
允许的答案由0 1 3 7,只需要简单调用一下函数就行,没想到。
Phase_5
这1-7行刚开始是没看懂,后来看伪代码是v4 = __readfsqword(0x28u),这玩意儿。
eax存储了字符串长度,必须为6,否则就炸。然后再把0给eax,将输入保存在ecx,因为rax刚开始为0.
将cl的值(ecx的一个子寄存器)给rdx,然后获取edx的低四位。然后所得到的edx的值作为指针访问0x4024b0地址下的某一位(有点绕),一直到4010ac处的指令,可用代码表示为:
1 | for ( i = 0LL; i != 6; ++i ) |
0x4024b0处
将rsp+0x16的值化为0,也就是代表该字符串的末尾;0x40245e处:
然后传入两个参数esi,rdi到strings_not_equal函数不相等就炸。
所以根据后续代码逻辑,我们需要在0x4024b0处的字符串中找到0x40245e处的下标。
分别是9 15 14 5 6 7,但是,这些都是答案的低四位
因为有取低四位这个步骤,这里给出C语言代码
1 | array[arry_4024b0[i]&0xf] = arry_0x40245e[i]; |
借用一张图片,可以对着查询,任意选一组即可。
Phase_6
代码有点长,我们分部来看。
首先是将rsi作为参数传入read_six_numbers函数,然后又赋值,将eax的值减1后,与5作比较,小于等于5则不爆。然后将r12d的值加一(刚开始为0),与6比较,等于才跳,接着又有赋值操作,然后将eax与rbp+0作比较,相等就炸。
接着来到401145处,ebx加一,再与5作比较,大于5就不跳。很明显一个循环。我们向下继续。
将r13的值加4,跳到刚刚的401114处,如此看来,r12d是循环次数,6次。从开始截至401151,是一个二重循环。
1 | for (int i=0; i<6; i++){ |
我们从401153处继续,向下还是一堆赋值操作,然后比较,rax和rsi不等就跳,向下看,0x0给esi,然后跳到401197.
来到401197处
首先是赋值,然后与1作比较,小于等于跳到401183否则跳到401176进行循环,然后通过401176的循环再步入401188的循环。
我们从4011ab向下看
依然是一堆赋值,然后接上比较,相等就跳转。将rbx给rcx后,跳到4011bd处,进行循环,要循环8次,然后到4011d2处,接着又是一堆赋值,需要eax小于rbx的值,才跳转,否则炸。最后还有一个从4011df开始的循环(ebp-1不为0则跳转。)。其实是一个值的置换操作,不过是执行6次,按降序排列
需要注意一下4011a4处的命令
看样子是个结构体(用IDA看就很明确)
所以以上逻辑大致为我们输入的序列被7减去后得到的序列,是一个向量,向量每个数字是node的序号,向量的顺序是node的链接顺序,也就是说向量的第一个序号对应的node会变成头节点
最后放上IDA的代码
故,依照上述代码逻辑将6个node的值按降序排列,所得序号为3 4 5 6 1 2,但因为在这之前涉及x=7-x这一操作
故正确答案应为4 3 2 1 6 5,至此扫雷。
secret_phase
wairi,受不了了,直接放反编译出的代码
func7
和汇编代码对照看,第二个if是右移,第三个是左移
发现0x6030f0处的往后是个二叉树,
再往后就是node了。
└─ 36
├─ 8
│ ├─ 6
│ │ ├─ left: 1
│ │ └─ right: 7
│ └─ 22
│ ├─ left: 20
│ └─ right: 35
└─ 50
├─ 45
│ ├─ left: 40
│ └─ right: 47
└─ 107
├─ left: 99
└─ right: 1001
我们需要找的数,是最后返回2的,那么,最后一次就返回0,再上一次返回2 * rax + 1,第一次返回2 * rax
那么两个值可以,20或22.
至此,扫雷完毕。
不算小结的小结
汇编看起来确实麻烦,做最后两个的时候都是结合C语言代码来搞得,对汇编还不是很熟悉。
这个实验如果用IDA当逆向题做的话,难度会降低很多。