原文链接
任务目标
“拆除炸弹”:通过反汇编,找到要输入的信息,输入到程序中达到“拆弹”的目的。
文件说明:
- bomb :二进制文件,要拆除的“炸弹”
- bomb.c :程序主函数源代码
可能使用到的工具:
- gdb :用于程序调试
- objdump :用于反汇编
- 流程图软件(帮助简化思考
笔者未在文章给出流程图,请读者自行绘制)
可能使用到的命令(详细用法未给出):
-
objdump -d
:返回二进制文件反汇编码 -
(gdb)break ...
:设置断点 -
(gdb) next
:单步调试,不进入 -
(gdb) step
:单步调试,进入函数 -
(gdb)info r
:查看寄存器 -
(gdb)info r …
:查看某寄存器 -
(gdb)p ...
:输出变量 -
(gdb)x ...
:查看内容 -
(gdb)set args …
:设置函数运行参数
第一关
阅读bomb.c
,了解炸弹工作流程。
initialize_bomb(); // 初始化
input = read_line(); // 读取输入
phase_1(input); // “拆弹”
phase_defused();
phase_1
函数便是要拆除的“炸弹”了,需要了解“炸弹”内部是如何工作的,我们来反汇编它。
使用objdump -d bomb > bomb.as
将汇编代码保存到bomb.as
文件当中。
查找到phase_1
反汇编后的代码。
0000000000400ee0 :
400ee0: 48 83 ec 08 sub $0x8,%rsp
400ee4: be 00 24 40 00 mov $0x402400,%esi
400ee9: e8 4a 04 00 00 callq 401338
400eee: 85 c0 test %eax,%eax
400ef0: 74 05 je 400ef7
400ef2: e8 43 05 00 00 callq 40143a
400ef7: 48 83 c4 08 add $0x8,%rsp
400efb: c3 retq
首先看到explode_bomb
函数,猜测调用这个函数会引爆炸弹,所以需要执行之前的je
指令跳过它。
je
指令能跳转所满足的条件为%eax = 0
,猜测strings_not_equal
函数的功能用于比较字符串是否相同。
0000000000401338 :
401338: 41 54 push %r12
40133a: 55 push %rbp
40133b: 53 push %rbx
40133c: 48 89 fb mov %rdi,%rbx
40133f: 48 89 f5 mov %rsi,%rbp
----------------------------------------------> 参数为%rdi和%rsi
...............................................
----------------------------------------------> 返回寄存器%eax
40139d: 5b pop %rbx
40139e: 5d pop %rbp
40139f: 41 5c pop %r12
4013a1: c3 retq
从strings_not_equal
函数可以初略看出,若字符串不相等会返回1
,反之。
该函数所需要的参数为两个字符串首地址,用%rsi
和%rdi
寄存器存放。
使用gdb
调试,查看这两个字符串存放的数据是什么。
$gdb bomb
(gdb) break strings_not_equal
(gdb) r
(gdb) p/s (char*)$rdi
$5 = 0x603780 "111111"
(gdb) p/s (char*)$rsi
$6 = 0x402400 "Border relations with Canada have never been better."
第一关密码便得到了Border relations with Canada have never been better.
。
Welcome to my fiendish little bomb. You have 6 phases with
which to blow yourself up. Have a nice day!
Border relations with Canada have never been better.
Phase 1 defused. How about the next one?
第二关
0000000000400efc :
400efc: 55 push %rbp
400efd: 53 push %rbx
400efe: 48 83 ec 28 sub $0x28,%rsp
400f02: 48 89 e6 mov %rsp,%rsi
400f05: e8 52 05 00 00 callq 40145c
400f0a: 83 3c 24 01 cmpl $0x1,(%rsp)
400f0e: 74 20 je 400f30
400f10: e8 25 05 00 00 callq 40143a
400f15: eb 19 jmp 400f30
400f17: 8b 43 fc mov -0x4(%rbx),%eax
400f1a: 01 c0 add %eax,%eax
400f1c: 39 03 cmp %eax,(%rbx)
400f1e: 74 05 je 400f25
400f20: e8 15 05 00 00 callq 40143a
400f25: 48 83 c3 04 add $0x4,%rbx
400f29: 48 39 eb cmp %rbp,%rbx
400f2c: 75 e9 jne 400f17
400f2e: eb 0c jmp 400f3c
400f30: 48 8d 5c 24 04 lea 0x4(%rsp),%rbx
400f35: 48 8d 6c 24 18 lea 0x18(%rsp),%rbp
400f3a: eb db jmp 400f17
400f3c: 48 83 c4 28 add $0x28,%rsp
400f40: 5b pop %rbx
400f41: 5d pop %rbp
400f42: c3 retq
猜测read_six_numbers
会读入六个数字,我们将断点设置在0x400f0a
处,在文件psol.txt
中写入测试数据。
Border relations with Canada have never been better.
1 2 3 4 5 6
(gdb) set args psol.txt
(gdb) break *0x400f0a
(gdb) r
(gdb) x $rsp
0x7fffffffea30: 0x00000001
推测输入的六个数存放在首地址为rsp
的数组中。去除无用汇编代码,修改整理下逻辑,转化为伪代码。
cmpl $0x1,(%rsp) -> arr[0] == 1
jne explode_bomb
L1:
lea 0x4(%rsp),%rbx -> rbx = arr[1]
lea 0x18(%rsp),%rbp -> rbp = arr[6]
L2:
mov -0x4(%rbx),%eax -> eax = arr[?-1]
add %eax,%eax -> eax += eax
cmp %eax,(%rbx) -> cmp arr[?],arr[?-1]
jne explode_bomb
add $0x4,%rbx -> arr[?+1]
cmp %rbp,%rbx
jne L2
----------------------------------------------
流程简化如下
if(arr[0]!=0) bomb()
for(int i=1;i<6;i++){
if(arr[i-1]*2 != arr[i]) bomb()
}
----------------------------------------------
推导出序列为1 2 4 8 16 32
,成功拆除。
Welcome to my fiendish little bomb. You have 6 phases with
which to blow yourself up. Have a nice day!
Phase 1 defused. How about the next one?
That's number 2. Keep going!
第三关
0000000000400f43 :
400f43: 48 83 ec 18 sub $0x18,%rsp
400f47: 48 8d 4c 24 0c lea 0xc(%rsp),%rcx
400f4c: 48 8d 54 24 08 lea 0x8(%rsp),%rdx
400f51: be cf 25 40 00 mov $0x4025cf,%esi
400f56: b8 00 00 00 00 mov $0x0,%eax
400f5b: e8 90 fc ff ff callq 400bf0 <__isoc99_sscanf@plt>
400f60: 83 f8 01 cmp $0x1,%eax
400f63: 7f 05 jg 400f6a
400f65: e8 d0 04 00 00 callq 40143a
400f6a: 83 7c 24 08 07 cmpl $0x7,0x8(%rsp)
400f6f: 77 3c ja 400fad
400f71: 8b 44 24 08 mov 0x8(%rsp),%eax
400f75: ff 24 c5 70 24 40 00 jmpq *0x402470(,%rax,8)
400f7c: b8 cf 00 00 00 mov $0xcf,%eax
400f81: eb 3b jmp 400fbe
400f83: b8 c3 02 00 00 mov $0x2c3,%eax
400f88: eb 34 jmp 400fbe
400f8a: b8 00 01 00 00 mov $0x100,%eax
400f8f: eb 2d jmp 400fbe
400f91: b8 85 01 00 00 mov $0x185,%eax
400f96: eb 26 jmp 400fbe
400f98: b8 ce 00 00 00 mov $0xce,%eax
400f9d: eb 1f jmp 400fbe
400f9f: b8 aa 02 00 00 mov $0x2aa,%eax
400fa4: eb 18 jmp 400fbe
400fa6: b8 47 01 00 00 mov $0x147,%eax
400fab: eb 11 jmp 400fbe
400fad: e8 88 04 00 00 callq 40143a
400fb2: b8 00 00 00 00 mov $0x0,%eax
400fb7: eb 05 jmp 400fbe
400fb9: b8 37 01 00 00 mov $0x137,%eax
400fbe: 3b 44 24 0c cmp 0xc(%rsp),%eax
400fc2: 74 05 je 400fc9
400fc4: e8 71 04 00 00 callq 40143a
400fc9: 48 83 c4 18 add $0x18,%rsp
400fcd: c3 retq
代码变长了,我们分段来分析。
400f5b: e8 90 fc ff ff callq 400bf0 <__isoc99_sscanf@plt>
400f60: 83 f8 01 cmp $0x1,%eax
400f63: 7f 05 jg 400f6a
400f65: e8 d0 04 00 00 callq 40143a
sscanf
函数是库函数,返回值eax
表示正确格式化的数据个数,在测试文件中随便写入几个数0 1 2 3 4 5
,断点在0x400f60
处查看下eax
中的值。
(gdb) set args psol.txt
(gdb) break *0x400f60
(gdb) run
(gdb) info r eax
eax 0x2 2
eax=0x2
,而我输入参数是多于两个的,所有第三关要求输入两个参数。并且通过调试(第二关)我们可以知道输入的两个参数存放的位置:
0x0000000000400f6a <+39>: cmpl $0x7,0x8(%rsp)
0x0000000000400f6f <+44>: ja 0x400fad
-----------------------------------------------------------
读出0x8(%rsp)
(gdb) x $rsp+0x8
0x7fffffffea58: 0x00000000
(gdb) x $rsp+0x8+0x4
0x7fffffffea5c: 0x00000001
0x8(%rsp)为输入的第一个参数a
0xc(%rsp)为输入的第二个参数b
通过上面的代码我们可以获取到第一个条件a<=7
。
0x0000000000400f71 <+46>: mov 0x8(%rsp),%eax
0x0000000000400f75 <+50>: jmpq *0x402470(,%rax,8)
--------------------------------------------------------------
*0x402470(,%rax,8) = *(0x402470+8*a) = 0x402470[8*a]
读出从0x402470[0..7]的所有数据
(gdb) x/8xg 0x402470
0x402470: 0x0000000000400f7c 0x0000000000400fb9
0x402480: 0x0000000000400f83 0x0000000000400f8a
0x402490: 0x0000000000400f91 0x0000000000400f98
0x4024a0: 0x0000000000400f9f 0x0000000000400fa6
以上便是跳转的地址范围
分析下面关键代码,来决定跳转的地址。
0x0000000000400f7c <+57>: mov $0xcf,%eax
0x0000000000400f81 <+62>: jmp 0x400fbe
0x0000000000400f83 <+64>: mov $0x2c3,%eax
0x0000000000400f88 <+69>: jmp 0x400fbe
0x0000000000400f8a <+71>: mov $0x100,%eax
0x0000000000400f8f <+76>: jmp 0x400fbe
0x0000000000400f91 <+78>: mov $0x185,%eax
0x0000000000400f96 <+83>: jmp 0x400fbe
0x0000000000400f98 <+85>: mov $0xce,%eax
0x0000000000400f9d <+90>: jmp 0x400fbe
0x0000000000400f9f <+92>: mov $0x2aa,%eax
0x0000000000400fa4 <+97>: jmp 0x400fbe
0x0000000000400fa6 <+99>: mov $0x147,%eax
0x0000000000400fab <+104>: jmp 0x400fbe
--------------------------------------------------------- 都会跳转到
0x0000000000400fad <+106>: callq 0x40143a
---------------------------------------------------------
0x0000000000400fb2 <+111>: mov $0x0,%eax
0x0000000000400fb7 <+116>: jmp 0x400fbe
0x0000000000400fb9 <+118>: mov $0x137,%eax
0x0000000000400fbe <+123>: cmp 0xc(%rsp),%eax
---------------------------------------------------------
0x0000000000400fc2 <+127>: je 0x400fc9
0x0000000000400fc4 <+129>: callq 0x40143a
他们都会跳转到0x400fbe
,最后会比较0xc(%rsp)
和eax
的大小。
通过前面的分析知道0xc(%rsp)
便是输入的参数b
,我们再讨论输入不同的a
,最终得到的eax
值是多少。
a | 跳转地址 | 最终eax/b的值 |
---|---|---|
0 | 0x0000000000400f7c | 0xcf |
1 | 0x0000000000400fb9 | 0x137 |
2 | 0x0000000000400f83 | 0x2c3 |
3 | 0x0000000000400f8a | 0x100 |
4 | 0x0000000000400f91 | 0x185 |
5 | 0x0000000000400f98 | 0xce |
6 | 0x0000000000400f9f | 0x2aa |
7 | 0x0000000000400fa6 | 0x147 |
随意选择一组a,b
的值填入即可。
Welcome to my fiendish little bomb. You have 6 phases with
which to blow yourself up. Have a nice day!
Phase 1 defused. How about the next one?
That's number 2. Keep going!
Halfway there!
第四关
000000000040100c :
40100c: 48 83 ec 18 sub $0x18,%rsp
401010: 48 8d 4c 24 0c lea 0xc(%rsp),%rcx
401015: 48 8d 54 24 08 lea 0x8(%rsp),%rdx
40101a: be cf 25 40 00 mov $0x4025cf,%esi
40101f: b8 00 00 00 00 mov $0x0,%eax
401024: e8 c7 fb ff ff callq 400bf0 <__isoc99_sscanf@plt>
401029: 83 f8 02 cmp $0x2,%eax
40102c: 75 07 jne 401035
40102e: 83 7c 24 08 0e cmpl $0xe,0x8(%rsp)
401033: 76 05 jbe 40103a
401035: e8 00 04 00 00 callq 40143a
40103a: ba 0e 00 00 00 mov $0xe,%edx
40103f: be 00 00 00 00 mov $0x0,%esi
401044: 8b 7c 24 08 mov 0x8(%rsp),%edi
401048: e8 81 ff ff ff callq 400fce
40104d: 85 c0 test %eax,%eax
40104f: 75 07 jne 401058
401051: 83 7c 24 0c 00 cmpl $0x0,0xc(%rsp)
401056: 74 05 je 40105d
401058: e8 dd 03 00 00 callq 40143a
40105d: 48 83 c4 18 add $0x18,%rsp
401061: c3 retq
和第三关同理,库函数sscanf
需要输入的参数个数为2
个,代码中已有,所有这次不用断点测试。
40102e: 83 7c 24 08 0e cmpl $0xe,0x8(%rsp)
401033: 76 05 jbe 40103a
获取第一个情报a<=0xe
,继续分析代码。
0x000000000040103a <+46>: mov $0xe,%edx
0x000000000040103f <+51>: mov $0x0,%esi
0x0000000000401044 <+56>: mov 0x8(%rsp),%edi
0x0000000000401048 <+60>: callq 0x400fce
0x000000000040104d <+65>: test %eax,%eax
0x000000000040104f <+67>: jne 0x401058
0x0000000000401051 <+69>: cmpl $0x0,0xc(%rsp)
0x0000000000401056 <+74>: je 0x40105d
0x0000000000401058 <+76>: callq 0x40143a
需要再访问函数func4
之后,eax=0
才不会触发“爆炸”函数。
获取第二个情报b=0x0
,在func4
函数中要让eax=0
,我们继续看func4
函数。
400fd2: 89 d0 mov %edx,%eax
400fd4: 29 f0 sub %esi,%eax
400fd6: 89 c1 mov %eax,%ecx
400fd8: c1 e9 1f shr $0x1f,%ecx
400fdb: 01 c8 add %ecx,%eax
400fdd: d1 f8 sar %eax
400fdf: 8d 0c 30 lea (%rax,%rsi,1),%ecx
400fe2: 39 f9 cmp %edi,%ecx
400fe4: 7e 0c jle 400ff2
400fe6: 8d 51 ff lea -0x1(%rcx),%edx
400fe9: e8 e0 ff ff ff callq 400fce
400fee: 01 c0 add %eax,%eax
400ff0: eb 15 jmp 401007
400ff2: b8 00 00 00 00 mov $0x0,%eax
400ff7: 39 f9 cmp %edi,%ecx
400ff9: 7d 0c jge 401007
400ffb: 8d 71 01 lea 0x1(%rcx),%esi
400ffe: e8 cb ff ff ff callq 400fce
401003: 8d 44 00 01 lea 0x1(%rax,%rax,1),%eax
现在获得的情报有:
a<=0xe
,b=0
,这两个参数范围确定。func4
函数需要三个参数,%edx=0xe
,%esi=0x0
,%edi=a
。func4
函数反汇编的倒数第二行,可能是一个递归程序,最终发现其实没有调用。func4
函数并没有改变%edi
寄存器的值。
整理一下代码,可以明显看到这里有一条指令mov $0x0,%eax
。
400fe2: 39 f9 cmp %edi,%ecx
400fe4: 7e 0c jle 400ff2
400fe6: 8d 51 ff lea -0x1(%rcx),%edx
400fe9: e8 e0 ff ff ff callq 400fce
400fee: 01 c0 add %eax,%eax
400ff0: eb 15 jmp 401007
400ff2: b8 00 00 00 00 mov $0x0,%eax
400ff7: 39 f9 cmp %edi,%ecx
400ff9: 7d 0c jge 401007
能够跳转到这里的条件是%ecx <= %edi
(第一行),能够结束函数的条件是%ecx >= %edi
(倒数第二行)。所以使%ecx = %edi
即可,而%edi
便是输入的参数a
。计算或者断点调试一下得到%ecx=7
。填入参数7 0
即可过关。
Welcome to my fiendish little bomb. You have 6 phases with
which to blow yourself up. Have a nice day!
Phase 1 defused. How about the next one?
That's number 2. Keep going!
Halfway there!
So you got that one. Try this one.
第五关
0000000000401062 :
401062: 53 push %rbx
401063: 48 83 ec 20 sub $0x20,%rsp
401067: 48 89 fb mov %rdi,%rbx
40106a: 64 48 8b 04 25 28 00 mov %fs:0x28,%rax
401071: 00 00
401073: 48 89 44 24 18 mov %rax,0x18(%rsp)
401078: 31 c0 xor %eax,%eax
40107a: e8 9c 02 00 00 callq 40131b
40107f: 83 f8 06 cmp $0x6,%eax
401082: 74 4e je 4010d2
401084: e8 b1 03 00 00 callq 40143a
401089: eb 47 jmp 4010d2
40108b: 0f b6 0c 03 movzbl (%rbx,%rax,1),%ecx
40108f: 88 0c 24 mov %cl,(%rsp)
401092: 48 8b 14 24 mov (%rsp),%rdx
401096: 83 e2 0f and $0xf,%edx
401099: 0f b6 92 b0 24 40 00 movzbl 0x4024b0(%rdx),%edx
4010a0: 88 54 04 10 mov %dl,0x10(%rsp,%rax,1)
4010a4: 48 83 c0 01 add $0x1,%rax
4010a8: 48 83 f8 06 cmp $0x6,%rax
4010ac: 75 dd jne 40108b
4010ae: c6 44 24 16 00 movb $0x0,0x16(%rsp)
4010b3: be 5e 24 40 00 mov $0x40245e,%esi
4010b8: 48 8d 7c 24 10 lea 0x10(%rsp),%rdi
4010bd: e8 76 02 00 00 callq 401338
4010c2: 85 c0 test %eax,%eax
4010c4: 74 13 je 4010d9
4010c6: e8 6f 03 00 00 callq 40143a
4010cb: 0f 1f 44 00 00 nopl 0x0(%rax,%rax,1)
4010d0: eb 07 jmp 4010d9
4010d2: b8 00 00 00 00 mov $0x0,%eax
4010d7: eb b2 jmp 40108b
4010d9: 48 8b 44 24 18 mov 0x18(%rsp),%rax
4010de: 64 48 33 04 25 28 00 xor %fs:0x28,%rax
4010e5: 00 00
4010e7: 74 05 je 4010ee
4010e9: e8 42 fa ff ff callq 400b30 <__stack_chk_fail@plt>
4010ee: 48 83 c4 20 add $0x20,%rsp
4010f2: 5b pop %rbx
4010f3: c3 retq
40107a: e8 9c 02 00 00 callq 40131b
40107f: 83 f8 06 cmp $0x6,%eax
401082: 74 4e je 4010d2
又相同的string_length
函数,通关第一关的经验,我们知道肯定要输入一个字符串并且长度为6
,在测试文件中写入6
个字符,调试程序。
0x0000000000401089 <+39>: jmp 0x4010d2
0x000000000040108b <+41>: movzbl (%rbx,%rax,1),%ecx
0x000000000040108f <+45>: mov %cl,(%rsp)
0x0000000000401092 <+48>: mov (%rsp),%rdx
0x0000000000401096 <+52>: and $0xf,%edx
0x0000000000401099 <+55>: movzbl 0x4024b0(%rdx),%edx
0x00000000004010a0 <+62>: mov %dl,0x10(%rsp,%rax,1)
0x00000000004010a4 <+66>: add $0x1,%rax
0x00000000004010a8 <+70>: cmp $0x6,%rax
0x00000000004010ac <+74>: jne 0x40108b
0x00000000004010ae <+76>: movb $0x0,0x16(%rsp)
0x00000000004010b3 <+81>: mov $0x40245e,%esi
0x00000000004010b8 <+86>: lea 0x10(%rsp),%rdi
0x00000000004010bd <+91>: callq 0x401338
0x00000000004010c2 <+96>: test %eax,%eax
0x00000000004010c4 <+98>: je 0x4010d9
0x00000000004010c6 <+100>: callq 0x40143a
0x00000000004010cb <+105>: nopl 0x0(%rax,%rax,1)
0x00000000004010d0 <+110>: jmp 0x4010d9
0x00000000004010d2 <+112>: mov $0x0,%eax
0x00000000004010d7 <+117>: jmp 0x40108b
设置断点到0x40108b<+41>
处,得到%rbx
寄存器中是我们输入的字符串首地址。
(gdb) x/6c $rbx
0x6038c0 : 97 'a' 98 'b' 99 'c' 100 'd' 101 'e' 102 'f'
看一下这一段代码。
0x000000000040108b <+41>: movzbl (%rbx,%rax,1),%ecx
0x000000000040108f <+45>: mov %cl,(%rsp)
0x0000000000401092 <+48>: mov (%rsp),%rdx
0x0000000000401096 <+52>: and $0xf,%edx
0x0000000000401099 <+55>: movzbl 0x4024b0(%rdx),%edx
0x00000000004010a0 <+62>: mov %dl,0x10(%rsp,%rax,1)
0x00000000004010a4 <+66>: add $0x1,%rax
0x00000000004010a8 <+70>: cmp $0x6,%rax
0x00000000004010ac <+74>: jne 0x40108b
-------------------------------------------------------------
narr首地址为rsp
array首地址为0x4024b0
for(int i=0;i<6;i++){
narr[i] = array[input[i]&0xf]
}
-------------------------------------------------------------
因为条件input[i]&0xf <= 0xf
读取出0x4024b0开始的16个数据
(gdb) x/16c 0x4024b0
0x4024b0 : 109 'm' 97 'a' 100 'd' 117 'u' 105 'i' 101 'e' 114 'r'115 's'
0x4024b8 : 110 'n' 102 'f' 111 'o' 116 't' 118 'v' 98 'b' 121 'y' 108 'l'
继续阅读接下来的代码。
0x00000000004010ae <+76>: movb $0x0,0x16(%rsp)
0x00000000004010b3 <+81>: mov $0x40245e,%esi
0x00000000004010b8 <+86>: lea 0x10(%rsp),%rdi
0x00000000004010bd <+91>: callq 0x401338
0x00000000004010c2 <+96>: test %eax,%eax
0x00000000004010c4 <+98>: je 0x4010d9
通过第一关知道strings_not_equal
函数的功能:比较首地址为esi
和rdi
的字符串是否相等。查看下比较的字符串。
(gdb) x/s 0x40245e
0x40245e: "flyers"
获得对比的字符串为flyers
。通过上面的array
数组得到下表。
array[i] | char | input[c]&0xf == i | array[i] | char | input[c]&0xf == i |
---|---|---|---|---|---|
array[0] | m | 0,@,P,` | array[8] | n | 8,H,X,h |
array[1] | a | 1,A,Q,a | array[9] | f | 9,I,Y,i |
array[2] | d | 2,B,R,b | array[0xa] | o | :,J,Z,j |
array[3] | u | 3,C,S,c | array[0xb] | t | ;,K,[,k |
array[4] | i | 4,D,T,d | array[0xc] | v | <,L,'\',l |
array[5] | e | 5,E,U,e | array[0xd] | b | =,M,],m |
array[6] | r | 6,F,V,f | array[0xe] | y | >,N,^,n |
array[7] | s | 7,G,W,g | array[0xf] | l | ?,O,_,o |
随意选取上表"flyers"
对应的input[]&0xf
,ionefg
,YONUFG
,任意组合都能过关。
Welcome to my fiendish little bomb. You have 6 phases with
which to blow yourself up. Have a nice day!
Phase 1 defused. How about the next one?
That's number 2. Keep going!
Halfway there!
So you got that one. Try this one.
Good work! On to the next...
第六关
401106: e8 51 03 00 00 callq 40145c
40110b: 49 89 e6 mov %rsp,%r14
40110e: 41 bc 00 00 00 00 mov $0x0,%r12d
401114: 4c 89 ed mov %r13,%rbp
401117: 41 8b 45 00 mov 0x0(%r13),%eax
40111b: 83 e8 01 sub $0x1,%eax
40111e: 83 f8 05 cmp $0x5,%eax
401121: 76 05 jbe 401128
需要读入六个数字,随意写1 2 3 4 5 6
测试,断点在0x401117
处调试。
(gdb) x/6u $rbp
0x7fffffffe9f0: 1 2 3 4
0x7fffffffea00: 5 6
rbp
和r13
寄存器是存放输入数字的数组首地址。
上面的代码可以得到情报arr[0]-1 <= 5
,然后看代码跳转到的位置。
这段代码需要看到0x401153
,因为0x401130
处的指令会跳转到这里。
40110e: 41 bc 00 00 00 00 mov $0x0,%r12d
401114: 4c 89 ed mov %r13,%rbp
401117: 41 8b 45 00 mov 0x0(%r13),%eax
40111b: 83 e8 01 sub $0x1,%eax
40111e: 83 f8 05 cmp $0x5,%eax
401121: 76 05 jbe 401128
401123: e8 12 03 00 00 callq 40143a
401128: 41 83 c4 01 add $0x1,%r12d
40112c: 41 83 fc 06 cmp $0x6,%r12d
401130: 74 21 je 401153
401132: 44 89 e3 mov %r12d,%ebx
401135: 48 63 c3 movslq %ebx,%rax
401138: 8b 04 84 mov (%rsp,%rax,4),%eax
40113b: 39 45 00 cmp %eax,0x0(%rbp)
40113e: 75 05 jne 401145
401140: e8 f5 02 00 00 callq 40143a
401145: 83 c3 01 add $0x1,%ebx
401148: 83 fb 05 cmp $0x5,%ebx
40114b: 7e e8 jle 401135
40114d: 49 83 c5 04 add $0x4,%r13
401151: eb c1 jmp 401114
401153: 48 8d 74 24 18 lea 0x18(%rsp),%rsi
---------------------------------------------------------------------
循环分析比较麻烦,401151会跳转到401114这个是最外层,完成了%r13 + 0x4。即arr++
%13初始的时候是数组首地址。
按照思路逻辑如下
for i=0;i<6;i++{
if(arr[i] - 1 > 5) bomb()
for j=i+1;j<=5;j++{
if(arr[j] == arr[i]) bomb()
}
}
可以得到情报:六个数不相同,且需要<=6
,接着看下一段代码。
401153: 48 8d 74 24 18 lea 0x18(%rsp),%rsi
401158: 4c 89 f0 mov %r14,%rax
40115b: b9 07 00 00 00 mov $0x7,%ecx
401160: 89 ca mov %ecx,%edx
401162: 2b 10 sub (%rax),%edx
401164: 89 10 mov %edx,(%rax)
401166: 48 83 c0 04 add $0x4,%rax
40116a: 48 39 f0 cmp %rsi,%rax
40116d: 75 f1 jne 401160
rsi
的值为rsp+0x18 = rsp+0x4*6
即数组的结束位置。
%rax = %r14
往前看,是输入的数组首地址。
这段代码一个循环很简单看出,其逻辑如下:
for(int i=0;i<6;i++){
arr[i] = 7 - arr[i];
}
继续看下一段代码。
40116f: be 00 00 00 00 mov $0x0,%esi
401174: eb 21 jmp 401197
401176: 48 8b 52 08 mov 0x8(%rdx),%rdx
40117a: 83 c0 01 add $0x1,%eax
40117d: 39 c8 cmp %ecx,%eax
40117f: 75 f5 jne 401176
401181: eb 05 jmp 401188
401183: ba d0 32 60 00 mov $0x6032d0,%edx
401188: 48 89 54 74 20 mov %rdx,0x20(%rsp,%rsi,2)
40118d: 48 83 c6 04 add $0x4,%rsi
401191: 48 83 fe 18 cmp $0x18,%rsi
401195: 74 14 je 4011ab
401197: 8b 0c 34 mov (%rsp,%rsi,1),%ecx
40119a: 83 f9 01 cmp $0x1,%ecx
40119d: 7e e4 jle 401183
40119f: b8 01 00 00 00 mov $0x1,%eax
4011a4: ba d0 32 60 00 mov $0x6032d0,%edx
4011a9: eb cb jmp 401176
4011ab: 48 8b 5c 24 20 mov 0x20(%rsp),%rbx
--------------------------------------------------------------------------
借助流程图分析我们得到如下逻辑伪代码
for(int i=0;i<6;i++){
int cnt = arr[i];
int add = 0x6032d0;
for(int j=1;j: 0x006032e0
(gdb) x 0x6032e0+0x8
0x6032e8 : 0x006032f0
(gdb) x 0x6032f0+0x8
0x6032f8 : 0x00603300
(gdb) x 0x603300+0x8
0x603308 : 0x00603310
(gdb) x 0x603310+0x8
0x603318 : 0x00603320
通过上述处理后得到的新数组narry
其首地址为rsp+0x20
,结束地址为rsp+0x50
,其中的数据可选范围为0x6032d0 - 0x603320
,继续往下看。
4011ab: 48 8b 5c 24 20 mov 0x20(%rsp),%rbx
4011b0: 48 8d 44 24 28 lea 0x28(%rsp),%rax
4011b5: 48 8d 74 24 50 lea 0x50(%rsp),%rsi
4011ba: 48 89 d9 mov %rbx,%rcx
4011bd: 48 8b 10 mov (%rax),%rdx
4011c0: 48 89 51 08 mov %rdx,0x8(%rcx)
4011c4: 48 83 c0 08 add $0x8,%rax
4011c8: 48 39 f0 cmp %rsi,%rax
4011cb: 74 05 je 4011d2
4011cd: 48 89 d1 mov %rdx,%rcx
4011d0: eb eb jmp 4011bd
4011d2: 48 c7 42 08 00 00 00 movq $0x0,0x8(%rdx)
---------------------------------------------------------------
narr 数组存放数据范围上面分析为 0x6032d0 - 0x603320
int *narr = {};
for(int i=1;i<6;i++){
*(narr[i-1]+0x8) = narr[i]
}
将narr[i-1]+0x8内存地址数据存放为narr[i]
再来看最后一段代码的功能:
4011d2: 48 c7 42 08 00 00 00 movq $0x0,0x8(%rdx)
4011d9: 00
4011da: bd 05 00 00 00 mov $0x5,%ebp
4011df: 48 8b 43 08 mov 0x8(%rbx),%rax
4011e3: 8b 00 mov (%rax),%eax
4011e5: 39 03 cmp %eax,(%rbx)
4011e7: 7d 05 jge 4011ee
4011e9: e8 4c 02 00 00 callq 40143a
4011ee: 48 8b 5b 08 mov 0x8(%rbx),%rbx
4011f2: 83 ed 01 sub $0x1,%ebp
4011f5: 75 e8 jne 4011df
%rbx
寄存器的值没有变化,仍然为narr
数组的首元素即narr[0]
。
结合上一段代码,这段代码的逻辑也能够知道了。
eax : *(narr[0]+0x8) => narr[1] => *(narr[1])
(%rbx) : *(narr[0])
for(int i=1;i<=5;i++){
if( *(narr[i-1]) < *(narr[i]) ) bomb()
}
获取narr
,以及*(narr[i])
的取值如下:
(gdb) x/d 0x6032d0+0x10*0
0x6032d0 : 332
(gdb) x/d 0x6032d0+0x10*1
0x6032e0 : 168
(gdb) x/d 0x6032d0+0x10*2
0x6032f0 : 924
(gdb) x/d 0x6032d0+0x10*3
0x603300 : 691
(gdb) x/d 0x6032d0+0x10*4
0x603310 : 477
(gdb) x/d 0x6032d0+0x10*5
0x603320 : 443
---------------------------------
int narr[1..6] = {
0x6032d0,0x6032e0,0x6032f0,
0x603300,0x603310,0x603320
};
int m_narr[1..6] = {
332,168,924,691,477,443
};
*(narr[i])需要从大到小排序,其位置对应如下:
924 691 477 443 332 168
3 4 5 6 1 2
因为原数据被7
减之后才得到该序列,逆运算得到4 3 2 1 6 5
,拆弹成功!
Welcome to my fiendish little bomb. You have 6 phases with
which to blow yourself up. Have a nice day!
Phase 1 defused. How about the next one?
That's number 2. Keep going!
Halfway there!
So you got that one. Try this one.
Good work! On to the next...
Congratulations! You've defused the bomb!
彩蛋关
在bomb.c
注释说明了存在彩蛋关,查看汇编代码,发现phase_defused
函数会调用函数secret_phase
。
4015fa: e8 f1 f5 ff ff callq 400bf0 <__isoc99_sscanf@plt>
4015ff: 83 f8 03 cmp $0x3,%eax
401602: 75 31 jne 401635
4015f0: be 19 26 40 00 mov $0x402619,%esi
4015f5: bf 70 38 60 00 mov $0x603870,%edi
4015fa: e8 f1 f5 ff ff callq 400bf0 <__isoc99_sscanf@plt>
4015ff: 83 f8 03 cmp $0x3,%eax
401602: 75 31 jne 401635
401604: be 22 26 40 00 mov $0x402622,%esi
401609: 48 8d 7c 24 10 lea 0x10(%rsp),%rdi
40160e: e8 25 fd ff ff callq 401338
401613: 85 c0 test %eax,%eax
401615: 75 1e jne 401635
(gdb) x/s 0x402619
0x402619: "%d %d %s"
(gdb) x/s 0x402622
0x402622: "DrEvil"
这两段代码可以看到,只有输入的参数前两个为整数,第三个为字符串DrEvil
时才不会跳过隐藏关。我们再看看运行结束时0x603870
里面的字符串是什么。
(gdb) x/s 0x603870
0x603870 : "7 0"
是我们第四关选择的答案。那么在第四关后面加上字符串DrEvil
开启隐藏关卡,查看secret_phase
函数代码。
401255: e8 76 f9 ff ff callq 400bd0
40125a: 48 89 c3 mov %rax,%rbx
40125d: 8d 40 ff lea -0x1(%rax),%eax
401260: 3d e8 03 00 00 cmp $0x3e8,%eax
401265: 76 05 jbe 40126c
401267: e8 ce 01 00 00 callq 40143a
断点在0x40125d
看看寄存器里面的值是什么,猜测是我们输入的数字,因为之前有一个函数strtol
。
(gdb) set args psol.txt
(gdb) break *0x40125d
Breakpoint 1 at 0x40125d
(gdb) r
Starting program: /home/pipapa/bomb/bomb psol.txt
Welcome to my fiendish little bomb. You have 6 phases with
which to blow yourself up. Have a nice day!
Phase 1 defused. How about the next one?
That's number 2. Keep going!
Halfway there!
So you got that one. Try this one.
Good work! On to the next...
Curses, you've found the secret phase!
But finding it and solving it are quite different...
1
Breakpoint 1, 0x000000000040125d in secret_phase ()
(gdb) info r eax
eax 0x1 1
果然是我们输入的数字,结合上面跳转代码知道a - 1 <= 0x3e8
,继续往下看。
0x000000000040126c <+42>: mov %ebx,%esi
0x000000000040126e <+44>: mov $0x6030f0,%edi
0x0000000000401273 <+49>: callq 0x401204
0x0000000000401278 <+54>: cmp $0x2,%eax
0x000000000040127b <+57>: je 0x401282
0x000000000040127d <+59>: callq 0x40143a
这时候%esi
是我们输入的数字,%edi
是0x6030f0
,然后调用fun7
函数之后,要让返回值为2
,我们搞明白fun7
函数所完成的功能。
0x0000000000401204 <+0>: sub $0x8,%rsp
0x0000000000401208 <+4>: test %rdi,%rdi
0x000000000040120b <+7>: je 0x401238
0x000000000040120d <+9>: mov (%rdi),%edx
0x000000000040120f <+11>: cmp %esi,%edx
0x0000000000401211 <+13>: jle 0x401220
0x0000000000401213 <+15>: mov 0x8(%rdi),%rdi
0x0000000000401217 <+19>: callq 0x401204
0x000000000040121c <+24>: add %eax,%eax
0x000000000040121e <+26>: jmp 0x40123d
0x0000000000401220 <+28>: mov $0x0,%eax
0x0000000000401225 <+33>: cmp %esi,%edx
0x0000000000401227 <+35>: je 0x40123d
0x0000000000401229 <+37>: mov 0x10(%rdi),%rdi
0x000000000040122d <+41>: callq 0x401204
0x0000000000401232 <+46>: lea 0x1(%rax,%rax,1),%eax
0x0000000000401236 <+50>: jmp 0x40123d
0x0000000000401238 <+52>: mov $0xffffffff,%eax
0x000000000040123d <+57>: add $0x8,%rsp
0x0000000000401241 <+61>: retq
%esi
和%edi
是fun7
函数的两个参数,%eax
是fun7
函数的返回值,可以看到可能是一个递归函数。
int fun7(di,si){
if(di == 0) return 0xffffffff;
if(*di <= si){
a = 0;
if(si == *di) return;
di = *(di+0x10)
a = fun7(di,si)
return 2*a + 1;
}else{
di = *(di+0x8)
a = fun7(di,si)
return 2*a
}
}
分析这个函数,fun7
要么让di=*(di+0x10)
要么让di=*(di+0x8)
,那么如果把它看成一个节点,则含有指向两个节点的指针,和一个值*di
,它的结构可能如下(可能是颗树):
struct node{
long val; // 8个字节?
struct node *l,*r; // l和r都是4个字节
}
那么它的第一个节点的地址是0x6030f0
,我们来画一下这颗树:
[图片上传失败...(image-47b81-1578127466578)]
若访问到空节点,则返回值会出现0xffffffff
,所以我们只能在树上(软件原因树的最右分枝未补全)的节点选择。通过代码我们知道,若从节点x
出发a
初始值为0
,若改节点为右儿子则a=2*a+1
、为左儿子则a=a*2
。所以20
、22
节点都可以在到达根节点时返回a=2
,输入任意一值即可通关。
Welcome to my fiendish little bomb. You have 6 phases with
which to blow yourself up. Have a nice day!
Phase 1 defused. How about the next one?
That's number 2. Keep going!
Halfway there!
So you got that one. Try this one.
Good work! On to the next...
Curses, you've found the secret phase!
But finding it and solving it are quite different...
20
Wow! You've defused the secret stage!
Congratulations! You've defused the bomb!