Bomb Lab: Defusing a Binary Bomb
我经过漫长的分析和调试最终完成了本实验,完成后撰写本文用时约两天,若参考或转载请注明出处 。
Dr. Evil’s Insidious Bomb, Version 1.1 Copyright 2011, Dr. Evil Incorporated. All rights reserved.
LICENSE:
Dr. Evil Incorporated (the PERPETRATOR) hereby grants you (the VICTIM) explicit permission to use this bomb (the BOMB). This is a time limited license, which expires on the death of the VICTIM. The PERPETRATOR takes no responsibility for damage, frustration, insanity, bug-eyes, carpal-tunnel syndrome, loss of sleep, or other harm to the VICTIM. Unless the PERPETRATOR wants to take credit, that is. The VICTIM may not distribute this bomb source code to any enemies of the PERPETRATOR. No VICTIM may debug, reverse-engineer, run “strings” on, decompile, decrypt, or use any other technique to gain knowledge of and defuse the BOMB. BOMB proof clothing may not be worn when handling this program. The PERPETRATOR will not apologize for the PERPETRATOR’s poor sense of humor. This license is null and void where the BOMB is prohibited by law.
大意为
Dr.Evil的阴险炸弹,版本1.1 版权声明:2011,Dr.Evil公司版权所有。
许可声明:
Dr.Evil公司(犯罪者)特此授予您(受害者)使用该炸弹(BOMB)的明确许可。这是一个有时间限制的许可证,在受害者死亡时到期。破坏者对损坏,沮丧,精神错乱,虫眼,腕管综合症,睡眠不足或对受害者造成的其他伤害概不负责,除非犯罪者想要获得荣誉。受害者不得将此炸弹源代码分发给犯罪者的任何敌人。受害者不得调试,逆向工程,在其上运行“字符串”,反编译,解密或使用任何其他技术来了解和拆除BOMB。处理此程序时,可能不能穿防弹衣。犯罪者不会因犯罪者的幽默感而道歉。在法律禁止BOMB的情况下,此许可无效。
本实验考察了对汇编语言的分析,对程序控制、过程调用的汇编级实现的理解。故做本实验之前,应先掌握CS:APP第三章《程序的机器级表示》部分。即使你未掌握汇编语言相关的知识,阅读本文后你也会有很大收获。
本文所有操作均基于以下环境:
OS: Ubuntu 18.04.4 LTS (Linux ubuntu 5.3.0-46-generic x86_64)
Debugger: GNU gdb (Ubuntu 8.1-0ubuntu3.2) 8.1.0.20180409-git
有关实验的基本介绍参见实验说明。
准备 阅读bomb.c
,可以发现:
bomb
支持两种输入方式。
为避免重复输入,建议将分析出的password预先存入文本文件passwd.txt
中。
bomb
总共设置了6个phase
,每个phase
包括:
read_line()
:读取一行作为输入的password;
phase_X()
:第X
个phase
调用对应此函数以检验password是否正确;
phase_defused()
:每个phase
在检验密码正确后都会运行此函数。
只有password全部正确,拆弹才成功,否则将爆炸:
1 2 BOOM!!! The bomb has blown up.
拆弹的过程,就是通过分析objdump
反汇编生成的代码,并利用gdb
调试得到每一个phase
的password,完成总共6个phase
即拆弹成功。
下面是关于objdump
和gdb
的具体用法:
gdb
中使用x
命令来打印内存的值,格式为“x/<nfu> <addr>
”。含义为以<f>
格式打印从<addr>
开始的<n>
个长度单元为<u>
的内存值。参数具体含义如下:
<n>
:输出单元的个数。
<f>
:输出格式。
s
输出字符串;
x
按十六进制格式显示变量;
d
按十进制格式显示变量;
u
按十六进制格式显示无符号整型;
o
按八进制格式显示变量;
t
按二进制格式显示变量;
a
按十六进制格式显示变量;
c
按字符格式显示变量;
f
按浮点数格式显示变量。
<u>
:标明一个单元的长度。
b
是1个byte
;
h
是2个byte
(halfword);
w
是4个byte
(word);
g
是8个byte
(giant word)。
拆弹开始 首先利用objdump
工具反汇编,并将结果重定向存入bomb.s
(或bomb.asm
、bomb.txt
)文件以便分析。(注意此处操作务必在Linux环境下进行,若在Windows或macOS环境下可能会无法生成注释,并可能无法将数值显示为十六进制)
1 objdump -d bomb > bomb.s
生成的汇编文件包括标号地址 、标号名字 、指令地址 、指令机器码 以及指令机器码反汇编得到的指令 等部分。
运行
拆弹开始。
main 首先分析main
函数,找到phase_1
对应的汇编语句部分:
1 2 3 4 5 6 400e32: e8 67 06 00 00 callq 40149e <read_line> 400e37: 48 89 c7 mov %rax,%rdi 400e3a: e8 a1 00 00 00 callq 400ee0 <phase_1> 400e3f: e8 80 07 00 00 callq 4015c4 <phase_defused> 400e44: bf a8 23 40 00 mov $0x4023a8,%edi 400e49: e8 c2 fc ff ff callq 400b10 <puts@plt>
结合bomb.c
可知,程序首先调用read_line
函数读取一行字符串(400e32),并将其返回值input
从%rax
寄存器传入%rdi
寄存器(400e37)。(由于read_line
函数的作用易知,故本文不再对其进行详细分析)
分析剩余main
函数可知,其调用每个phase
函数时,input
字符串地址都存储于%rdi
寄存器中,即**%rdi
指向input
字符串**。
后续直接分析各phase
函数即可。
Phase 1: string phase_1
对应的汇编语句如下:
1 2 3 4 5 6 7 8 9 0000000000400ee0 <phase_1>: 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 <strings_not_equal> 400eee: 85 c0 test %eax,%eax 400ef0: 74 05 je 400ef7 <phase_1+0x17> 400ef2: e8 43 05 00 00 callq 40143a <explode_bomb> 400ef7: 48 83 c4 08 add $0x8,%rsp 400efb: c3 retq
分段分析:
1 400ee0: 48 83 ec 08 sub $0x8,%rsp
400ee0在%rsp
中分配8字节空间压栈
1 400ee4: be 00 24 40 00 mov $0x402400,%esi
400ee4将地址0x402400
传入%esi
寄存器,直接gdb
打断点查看此处内容:
1 2 (gdb) b explode_bomb (gdb) x/s 0x402400
得到输出如下:
1 2 Breakpoint 1 at 0x40143a 0x402400: "Border relations with Canada have never been better."
可以看到地址0x402400
指向的是字符串Border relations with Canada have never been better.
,推测phase_1
的密码就是此字符串(后续称之为“密码串”,对应input
字符串为“输入串”)。
1 400ee9: e8 4a 04 00 00 callq 401338 <strings_not_equal>
400ee9调用strings_not_equal
函数 (推测 可能是判断两字符串是否相等,点此分析strings_not_equal
函数 )
1 2 3 4 5 400eee: 85 c0 test %eax,%eax 400ef0: 74 05 je 400ef7 <phase_1+0x17> 400ef2: e8 43 05 00 00 callq 40143a <explode_bomb> 400ef7: 48 83 c4 08 add $0x8,%rsp 400efb: c3 retq
400eee和400ef0判断%eax
(strings_not_equal
函数的返回值)是否为0:
若为0(输入串和密码串相等)则跳到400ef7(400ee0+0x17),弹出栈并返回 ;
若不为0(输入串和密码串不等)则运行400ef2执行explode_bomb
函数使炸弹爆炸
至此,得知密码串就是password。即phase_1
的password为
1 Border relations with Canada have never been better.
Phase2: loop phase_2
对应的汇编语句如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 0000000000400efc <phase_2>: 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 <read_six_numbers> 400f0a: 83 3c 24 01 cmpl $0x1,(%rsp) 400f0e: 74 20 je 400f30 <phase_2+0x34> 400f10: e8 25 05 00 00 callq 40143a <explode_bomb> 400f15: eb 19 jmp 400f30 <phase_2+0x34> 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 <phase_2+0x29> 400f20: e8 15 05 00 00 callq 40143a <explode_bomb> 400f25: 48 83 c3 04 add $0x4,%rbx 400f29: 48 39 eb cmp %rbp,%rbx 400f2c: 75 e9 jne 400f17 <phase_2+0x1b> 400f2e: eb 0c jmp 400f3c <phase_2+0x40> 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 <phase_2+0x1b> 400f3c: 48 83 c4 28 add $0x28,%rsp 400f40: 5b pop %rbx 400f41: 5d pop %rbp 400f42: c3 retq
分段分析:
1 2 3 4 400efc: 55 push %rbp 400efd: 53 push %rbx 400efe: 48 83 ec 28 sub $0x28,%rsp 400f02: 48 89 e6 mov %rsp,%rsi
400efe在%rsp
中分配40字节空间压栈,400f02将%rsp
地址给%rsi
1 400f05: e8 52 05 00 00 callq 40145c <read_six_numbers>
400f05调用read_six_numbers
函数 (推测 可能是读取6个数字,点此分析read_six_numbers
函数 )。
分析完read_six_numbers
函数后,可以明确:输入的6个整数依次存放于0(%rsp)
、4(%rsp)
、8(%rsp)
、12(%rsp)
、16(%rsp)
、20(%rsp)
中
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 400f0a: 83 3c 24 01 cmpl $0x1,(%rsp) 400f0e: 74 20 je 400f30 <phase_2+0x34> 400f10: e8 25 05 00 00 callq 40143a <explode_bomb> 400f15: eb 19 jmp 400f30 <phase_2+0x34> 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 <phase_2+0x29> 400f20: e8 15 05 00 00 callq 40143a <explode_bomb> 400f25: 48 83 c3 04 add $0x4,%rbx 400f29: 48 39 eb cmp %rbp,%rbx 400f2c: 75 e9 jne 400f17 <phase_2+0x1b> 400f2e: eb 0c jmp 400f3c <phase_2+0x40> 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 <phase_2+0x1b> 400f3c: 48 83 c4 28 add $0x28,%rsp 400f40: 5b pop %rbx 400f41: 5d pop %rbp 400f42: c3 retq
400f0a和400f0e判断%rsp
指向的值(输入的第1个整数)是否为1:
若不为1则继续执行400f10使炸弹爆炸;
若为1则跳到400f30(400efc+0x34)把%rsp+4
的地址(指向输入的下一个整数)给%rbx
,400f35把%rsp+24
的地址(指向6个整数结束的位置)给%rbp
,再跳回400f17(400efc+0x1b)把%rbx-4
的地址(指向当前整数的上一个整数)给%eax
,400f1a将%eax
的值(上一个整数)乘2,400f1c和400f1e将%eax
(上一个整数的2倍)和%rbx
指向的值(当前整数)对比:
若不等则继续执行400f20使炸弹爆炸;
若相等则跳到400f25(400efc+0x29)将%rbx
加4(使%rbx
指向下一个整数),400f29和400f2c判断%rbx
(当前整数位置)是否等于%rbp
(6整数结束的位置):
若相等则说明6整数判断完毕,继续执行400f2e跳到400f3c弹出栈并返回;
若不等则跳到400f17循环。
可以看出此部分的循环等价于:
1 2 3 4 5 6 7 8 9 if (passwd[0 ] != 1 ) explode_bomb(); p_tail = passwd + 6 ; for (p_loc = passwd + 1 ; p_loc != p_tail; p_loc++) { temp = *(p_loc - 1 ) * 2 ; if (*p_loc != temp) explode_bomb(); } return temp;
即判断是否满足“第一个整数为1
,后面5个整数依次为前一个的2倍 ”;不满足则使炸弹爆炸
至此,得到phase_2
的password为
Phase 3: switch phase_3
对应的汇编语句如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 0000000000400f43 <phase_3>: 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 <phase_3+0x27> 400f65: e8 d0 04 00 00 callq 40143a <explode_bomb> 400f6a: 83 7c 24 08 07 cmpl $0x7,0x8(%rsp) 400f6f: 77 3c ja 400fad <phase_3+0x6a> 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 <phase_3+0x7b> 400f83: b8 c3 02 00 00 mov $0x2c3,%eax 400f88: eb 34 jmp 400fbe <phase_3+0x7b> 400f8a: b8 00 01 00 00 mov $0x100,%eax 400f8f: eb 2d jmp 400fbe <phase_3+0x7b> 400f91: b8 85 01 00 00 mov $0x185,%eax 400f96: eb 26 jmp 400fbe <phase_3+0x7b> 400f98: b8 ce 00 00 00 mov $0xce,%eax 400f9d: eb 1f jmp 400fbe <phase_3+0x7b> 400f9f: b8 aa 02 00 00 mov $0x2aa,%eax 400fa4: eb 18 jmp 400fbe <phase_3+0x7b> 400fa6: b8 47 01 00 00 mov $0x147,%eax 400fab: eb 11 jmp 400fbe <phase_3+0x7b> 400fad: e8 88 04 00 00 callq 40143a <explode_bomb> 400fb2: b8 00 00 00 00 mov $0x0,%eax 400fb7: eb 05 jmp 400fbe <phase_3+0x7b> 400fb9: b8 37 01 00 00 mov $0x137,%eax 400fbe: 3b 44 24 0c cmp 0xc(%rsp),%eax 400fc2: 74 05 je 400fc9 <phase_3+0x86> 400fc4: e8 71 04 00 00 callq 40143a <explode_bomb> 400fc9: 48 83 c4 18 add $0x18,%rsp 400fcd: c3 retq
分段分析:
1 2 3 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
400f43在%rsp
中分配24字节空间压栈,400f47把%rsp+12
的地址给%rcx
,400f4c把%rsp+8
的地址给%rdx
1 2 %rdx = %rsp + 8 %rcx = %rsp + 12
1 2 3 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>
400f51将地址0x4025cf
传入%esi
寄存器,直接gdb
查看此处内容:
得到输出如下:
根据phase_2
的经验,结合后续400f5b再次调用了sscanf
函数,可以推测 phase_3
的password可能是两个整数,中间以1个空格隔开。而且输入的两个整数存储于上述的两个地址中。
1 2 3 4 5 6 7 400f60: 83 f8 01 cmp $0x1,%eax 400f63: 7f 05 jg 400f6a <phase_3+0x27> 400f65: e8 d0 04 00 00 callq 40143a <explode_bomb> 400f6a: 83 7c 24 08 07 cmpl $0x7,0x8(%rsp) 400f6f: 77 3c ja 400fad <phase_3+0x6a> ... 400fad: e8 88 04 00 00 callq 40143a <explode_bomb>
400f60和400f63判断%eax
是否为1:
若不为1则继续执行400f65使炸弹爆炸;
若为1则跳到400f6a(400f43+0x27),和400f6f判断%rsp+8
指向的值(输入的第1个整数)是否超过7:
若超过7则跳到400fad(400f43+0x6a)使炸弹爆炸(由ja
针对无符号数可以得知第一个整数在0~7之间 );
若不超过7则继续执行
1 400f71: 8b 44 24 08 mov 0x8(%rsp),%eax
400f71将%rsp+8
指向的值(输入的第1个整数)赋给%eax
1 400f75: ff 24 c5 70 24 40 00 jmpq *0x402470(,%rax,8)
400f75根据%rax
的值(输入的第一个整数)跳转到相应地址存放的地址,由于第一个整数在0~7之间,gdb
以单字节十六进制查看8*8=64个对应地址:
得到输出如下:
1 2 3 4 5 6 7 8 0x402470: 0x7c 0x0f 0x40 0x00 0x00 0x00 0x00 0x00 0x402478: 0xb9 0x0f 0x40 0x00 0x00 0x00 0x00 0x00 0x402480: 0x83 0x0f 0x40 0x00 0x00 0x00 0x00 0x00 0x402488: 0x8a 0x0f 0x40 0x00 0x00 0x00 0x00 0x00 0x402490: 0x91 0x0f 0x40 0x00 0x00 0x00 0x00 0x00 0x402498: 0x98 0x0f 0x40 0x00 0x00 0x00 0x00 0x00 0x4024a0: 0x9f 0x0f 0x40 0x00 0x00 0x00 0x00 0x00 0x4024a8: 0xa6 0x0f 0x40 0x00 0x00 0x00 0x00 0x00
由于机器为小端法(little endian),整理后得到输入的第一个整数为0~7时依次对应跳转到的地址依次为:0x400f7c
、0x400fb9
、0x400f83
、0x400f8a
、0x400f91
、0x400f98
、0x400f9f
和0x400fa6
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 400f7c: b8 cf 00 00 00 mov $0xcf,%eax 400f81: eb 3b jmp 400fbe <phase_3+0x7b> 400f83: b8 c3 02 00 00 mov $0x2c3,%eax 400f88: eb 34 jmp 400fbe <phase_3+0x7b> 400f8a: b8 00 01 00 00 mov $0x100,%eax 400f8f: eb 2d jmp 400fbe <phase_3+0x7b> 400f91: b8 85 01 00 00 mov $0x185,%eax 400f96: eb 26 jmp 400fbe <phase_3+0x7b> 400f98: b8 ce 00 00 00 mov $0xce,%eax 400f9d: eb 1f jmp 400fbe <phase_3+0x7b> 400f9f: b8 aa 02 00 00 mov $0x2aa,%eax 400fa4: eb 18 jmp 400fbe <phase_3+0x7b> 400fa6: b8 47 01 00 00 mov $0x147,%eax 400fab: eb 11 jmp 400fbe <phase_3+0x7b> ... 400fb2: b8 00 00 00 00 mov $0x0,%eax 400fb7: eb 05 jmp 400fbe <phase_3+0x7b> 400fb9: b8 37 01 00 00 mov $0x137,%eax 400fbe: 3b 44 24 0c cmp 0xc(%rsp),%eax 400fc2: 74 05 je 400fc9 <phase_3+0x86> 400fc4: e8 71 04 00 00 callq 40143a <explode_bomb> 400fc9: 48 83 c4 18 add $0x18,%rsp 400fcd: c3 retq
类似switch
语句,结合上述得到的8个地址,此部分等价于:
1 2 3 4 5 6 7 8 9 10 switch (passwd[0 ]) { case 0 : result = 207 ; break ; case 1 : result = 311 ; break ; case 2 : result = 707 ; break ; case 3 : result = 256 ; break ; case 4 : result = 389 ; break ; case 5 : result = 206 ; break ; case 6 : result = 682 ; break ; case 7 : result = 327 ; break ; }
每个case
赋值后,都跳到400fbe(400f43+0x7b)和400fc2将%rsp+12
指向的值(输入的第2个整数)和%eax
的值(result
值)对比:
若二者相等则跳到400fc9弹出栈并返回 ;
若二者不等则继续执行400fc4使炸弹爆炸
综上得知输入的第1个整数可以有8种情况,而输入的第2个整数因第1个整数而异
至此,得到phase_3
的password为
或
或
或
或
或
或
或
Phase 4: recursion phase_4
对应的汇编语句如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 000000000040100c <phase_4>: 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 <phase_4+0x29> 40102e: 83 7c 24 08 0e cmpl $0xe,0x8(%rsp) 401033: 76 05 jbe 40103a <phase_4+0x2e> 401035: e8 00 04 00 00 callq 40143a <explode_bomb> 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 <func4> 40104d: 85 c0 test %eax,%eax 40104f: 75 07 jne 401058 <phase_4+0x4c> 401051: 83 7c 24 0c 00 cmpl $0x0,0xc(%rsp) 401056: 74 05 je 40105d <phase_4+0x51> 401058: e8 dd 03 00 00 callq 40143a <explode_bomb> 40105d: 48 83 c4 18 add $0x18,%rsp 401061: c3 retq
分段分析:
40100c~401024与phase_3
的400f43~400f5b相同,即phase_4
的password同样可能是两个整数,中间以1个空格隔开,而且输入的两个整数分别存储于%rsp+8
和%rsp+12
两个地址中
1 2 3 4 401029: 83 f8 02 cmp $0x2,%eax 40102c: 75 07 jne 401035 <phase_4+0x29> ... 401035: e8 00 04 00 00 callq 40143a <explode_bomb>
401029和40102c判断%eax
(sscanf
函数的返回值)是否为2,即判断输入的是否为2个整数:
不为2则跳到401035(40100c+0x29)使炸弹爆炸;
为2则继续执行
1 2 3 4 40102e: 83 7c 24 08 0e cmpl $0xe,0x8(%rsp) 401033: 76 05 jbe 40103a <phase_4+0x2e> 401035: e8 00 04 00 00 callq 40143a <explode_bomb> 40103a: ba 0e 00 00 00 mov $0xe,%edx
40102e和401033判断%rsp+8
指向的值(输入的第1个整数)是否不超过14:
若超过14则继续执行401035使炸弹爆炸(由此得知输入的第1个整数范围是0~14 );
若不超过14则跳到40103a(40100c+0x2e)把%edx
赋为14
1 2 3 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 <func4>
40103f把%esi
赋为0,401044将%rsp+8
指向的值(输入的第1个整数)给%edi
,401048调用了func4
函数 (无法推测func4函数的功能,点此分析func4
函数 )
1 2 3 4 5 6 7 40104d: 85 c0 test %eax,%eax 40104f: 75 07 jne 401058 <phase_4+0x4c> 401051: 83 7c 24 0c 00 cmpl $0x0,0xc(%rsp) 401056: 74 05 je 40105d <phase_4+0x51> 401058: e8 dd 03 00 00 callq 40143a <explode_bomb> 40105d: 48 83 c4 18 add $0x18,%rsp 401061: c3 retq
40104d和40104f判断%eax
的值(func4
函数的返回值)是否为0:
若不为0则跳到401058(40100c+0x4c)使炸弹爆炸(由此得知**func4
函数的返回值必须为0**);
若为0则继续执行401051和401056判断%rsp+12
指向的值(输入的第2个整数)是否为0:
若为0则跳到40105d(40100c+0x51)弹出栈并返回 ;
若不为0则继续执行401058使炸弹爆炸(由此得知输入的第2个整数必须为0 )
分析完毕,已经得知phase_4
的password由两个整数构成,而第2个整数必为0,第一个整数是使func4
函数返回值为0且在0~14之间的数。
编写C程序求第一个整数:
1 2 3 4 5 6 7 8 9 10 11 12 13 #include <stdio.h> int func4 (int , int , int ) ;int main () { int min = 0 ; int max = 14 ; for (int i = 0 ; i < 15 ; i++) { if (!func4(i, min, max)) printf ("%d\n" , i); } return 0 ; }
得到输出如下:
即第1个整数可以是0
、1
、3
或7
。
至此,得到phase_4
的password为
或
或
或
Phase 5: pointer phase_5
对应的汇编语句如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 0000000000401062 <phase_5>: 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 <string_length> 40107f: 83 f8 06 cmp $0x6,%eax 401082: 74 4e je 4010d2 <phase_5+0x70> 401084: e8 b1 03 00 00 callq 40143a <explode_bomb> 401089: eb 47 jmp 4010d2 <phase_5+0x70> 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 <phase_5+0x29> 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 <strings_not_equal> 4010c2: 85 c0 test %eax,%eax 4010c4: 74 13 je 4010d9 <phase_5+0x77> 4010c6: e8 6f 03 00 00 callq 40143a <explode_bomb> 4010cb: 0f 1f 44 00 00 nopl 0x0(%rax,%rax,1) 4010d0: eb 07 jmp 4010d9 <phase_5+0x77> 4010d2: b8 00 00 00 00 mov $0x0,%eax 4010d7: eb b2 jmp 40108b <phase_5+0x29> 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 <phase_5+0x8c> 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
分段分析:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 401062: 53 push %rbx 401063: 48 83 ec 20 sub $0x20,%rsp ... 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) ... 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 <phase_5+0x8c> 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
%fs:40
是FS段寄存器上偏移地址0x28
上的数据,这是一个随机量 ,起到stack canary 的作用。此部分利用stack canary确保24(%rsp)
的数值(栈底8字节,401063)在函数调用前后不改变,若改变则执行4010e9调用__stack_chk_fail
函数跳出,以防栈溢出(stack overflow)。
此部分与拆弹联系较弱,可以跳过。
1 2 3 4 401067: 48 89 fb mov %rdi,%rbx ... 401078: 31 c0 xor %eax,%eax 40107a: e8 9c 02 00 00 callq 40131b <string_length>
401067将%rdi
的地址(指向输入串)赋给%rbx
,401078将%eax
设为0,40107a调用string_length
函数,返回输入串的长度%eax
1 2 3 4 5 6 40107f: 83 f8 06 cmp $0x6,%eax 401082: 74 4e je 4010d2 <phase_5+0x70> 401084: e8 b1 03 00 00 callq 40143a <explode_bomb> ... 4010d2: b8 00 00 00 00 mov $0x0,%eax 4010d7: eb b2 jmp 40108b <phase_5+0x29>
40107f和401082判断%eax
的值(输入串的长度)是否为6:
若不为6则继续执行401084使炸弹爆炸(由此得知输入串长度必须为6 );
若为6则跳到4010d2(401062+0x70)将%eax
赋为0,4010d7跳到40108b(401062+0x29)
1 2 3 4 5 6 7 8 9 10 401089: eb 47 jmp 4010d2 <phase_5+0x70> 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 <phase_5+0x29>
此时%eax
为0,40108b将%rbx
指向的值加上%rax
的值(输入串的第%rax+1
个字符)赋给%ecx
,40108f将%cl
(输入串的第%rax+1
个字符低8位)赋给%rsp
指向的值,401092再把%rsp
指向的值(输入串的第%rax+1
个字符低8位)赋给%rdx
,401096将%edx
(输入串的第%rax+1
个字符低8位)和0xf
按位取与(取低8位的低4位,结果存放于%edx
中),401099将0x4024b0
作为基地址、%rdx
指向的值(取与后的值)作为偏移量,并将偏移后的值赋给%edx
,4010a0将%dl
(%edx
低8位)赋给%rsp+%rax+16
指向的值,4010a4%rax
作为索引加1,4010a8判断%rax
是否为6:
不为6则跳到40108b(401062+0x29)循环;
为6则继续执行4010ae
可以看出此处的循环等价于:
1 2 3 4 5 6 7 8 int i = 0 ;for (i = 0 ; i != 6 ; i++) { ch = passwd[i]; index = ch; index &= 0xf ; ch = func[index]; str[i] = ch; }
由输入串的6个字符,每个字符的低4位作为偏移量,0x4024b0
作为基地址,最终映射生成新的str
串
1 2 3 4 5 6 7 8 9 10 11 12 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 <strings_not_equal> 4010c2: 85 c0 test %eax,%eax 4010c4: 74 13 je 4010d9 <phase_5+0x77> 4010c6: e8 6f 03 00 00 callq 40143a <explode_bomb> 4010cb: 0f 1f 44 00 00 nopl 0x0(%rax,%rax,1) 4010d0: eb 07 jmp 4010d9 <phase_5+0x77> 4010d2: b8 00 00 00 00 mov $0x0,%eax 4010d7: eb b2 jmp 40108b <phase_5+0x29> 4010d9: 48 8b 44 24 18 mov 0x18(%rsp),%rax
4010ae将%rsp+22
指向的值(str[6]
)赋为'\0'
,4010b3将地址0x40245e
赋给%esi
,4010b8将%rsp+16
的地址(str
)赋给%rdi
,4010bd调用strings_not_equal
函数(相等返回0,不等返回1),4010c2和4010c4判断返回值%eax
是否为0:
若不为0则继续执行4010c6使炸弹爆炸(由此得知返回值须为0 ,即生成的str
串须等于0x40245e
处的字符串 );
若为0则跳到4010d9(401062+0x77)继续执行则弹出栈并返回
直接gdb
查看0x40245e
处的字符串:
得到输出如下:
即我们新生成的str
串须为“flyers
”
gdb
查看基地址0x4024b0
处的字符串:
得到输出如下:
1 0x4024b0 <array.3449>: "maduiersnfotvbylSo you think you can stop the bomb with ctrl-c, do you?"
即基地址指向的字符串为“maduiersnfotvbyl
”,从中找到“flyers
”对应的下标为9
、15
、14
、5
、6
和7
,转换为十六进制为0x9
、0xf
、0xe
、0x5
、0x6
和0x7
——这就是输入串6个字符的低4位的值。
借助ASCII表,可以发现phase_5
的密码并不唯一。
低4位为0x9
的字符有:)
、9
、I
、Y
、i
、y
低4位为0xf
的字符有:/
、?
、O
、_
、o
低4位为0xe
的字符有:.
、>
、N
、^
、n
、~
低4位为0x5
的字符有:%
、5
、E
、U
、e
、u
低4位为0x6
的字符有:&
、6
、F
、V
、f
、v
低4位为0x7
的字符有:'
、7
、G
、W
、g
、w
至此,得到phase_5
的password为(不唯一)
Phase 6: link list phase_6
对应的汇编语句如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 00000000004010f4 <phase_6>: 4010f4: 41 56 push %r14 4010f6: 41 55 push %r13 4010f8: 41 54 push %r12 4010fa: 55 push %rbp 4010fb: 53 push %rbx 4010fc: 48 83 ec 50 sub $0x50,%rsp 401100: 49 89 e5 mov %rsp,%r13 401103: 48 89 e6 mov %rsp,%rsi 401106: e8 51 03 00 00 callq 40145c <read_six_numbers> 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 <phase_6+0x34> 401123: e8 12 03 00 00 callq 40143a <explode_bomb> 401128: 41 83 c4 01 add $0x1,%r12d 40112c: 41 83 fc 06 cmp $0x6,%r12d 401130: 74 21 je 401153 <phase_6+0x5f> 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 <phase_6+0x51> 401140: e8 f5 02 00 00 callq 40143a <explode_bomb> 401145: 83 c3 01 add $0x1,%ebx 401148: 83 fb 05 cmp $0x5,%ebx 40114b: 7e e8 jle 401135 <phase_6+0x41> 40114d: 49 83 c5 04 add $0x4,%r13 401151: eb c1 jmp 401114 <phase_6+0x20> 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 <phase_6+0x6c> 40116f: be 00 00 00 00 mov $0x0,%esi 401174: eb 21 jmp 401197 <phase_6+0xa3> 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 <phase_6+0x82> 401181: eb 05 jmp 401188 <phase_6+0x94> 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 <phase_6+0xb7> 401197: 8b 0c 34 mov (%rsp,%rsi,1),%ecx 40119a: 83 f9 01 cmp $0x1,%ecx 40119d: 7e e4 jle 401183 <phase_6+0x8f> 40119f: b8 01 00 00 00 mov $0x1,%eax 4011a4: ba d0 32 60 00 mov $0x6032d0,%edx 4011a9: eb cb jmp 401176 <phase_6+0x82> 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 <phase_6+0xde> 4011cd: 48 89 d1 mov %rdx,%rcx 4011d0: eb eb jmp 4011bd <phase_6+0xc9> 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 <phase_6+0xfa> 4011e9: e8 4c 02 00 00 callq 40143a <explode_bomb> 4011ee: 48 8b 5b 08 mov 0x8(%rbx),%rbx 4011f2: 83 ed 01 sub $0x1,%ebp 4011f5: 75 e8 jne 4011df <phase_6+0xeb> 4011f7: 48 83 c4 50 add $0x50,%rsp 4011fb: 5b pop %rbx 4011fc: 5d pop %rbp 4011fd: 41 5c pop %r12 4011ff: 41 5d pop %r13 401201: 41 5e pop %r14 401203: c3 retq
分段分析:
1 2 3 4 5 6 7 8 9 10 4010f4: 41 56 push %r14 4010f6: 41 55 push %r13 4010f8: 41 54 push %r12 4010fa: 55 push %rbp 4010fb: 53 push %rbx 4010fc: 48 83 ec 50 sub $0x50,%rsp 401100: 49 89 e5 mov %rsp,%r13 401103: 48 89 e6 mov %rsp,%rsi 401106: e8 51 03 00 00 callq 40145c <read_six_numbers> 40110b: 49 89 e6 mov %rsp,%r14
401100将%rsp
赋给%r13
,401103将%rsp
赋给%rsi
,40110b将%rsp
赋给%r14
,401106调用read_six_numbers
函数读取6个数字,输入的6个整数依次存放于0(%rsp)
、4(%rsp)
、8(%rsp)
、12(%rsp)
、16(%rsp)
、20(%rsp)
中
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 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 <phase_6+0x34> 401123: e8 12 03 00 00 callq 40143a <explode_bomb> 401128: 41 83 c4 01 add $0x1,%r12d 40112c: 41 83 fc 06 cmp $0x6,%r12d 401130: 74 21 je 401153 <phase_6+0x5f> 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 <phase_6+0x51> 401140: e8 f5 02 00 00 callq 40143a <explode_bomb> 401145: 83 c3 01 add $0x1,%ebx 401148: 83 fb 05 cmp $0x5,%ebx 40114b: 7e e8 jle 401135 <phase_6+0x41> 40114d: 49 83 c5 04 add $0x4,%r13 401151: eb c1 jmp 401114 <phase_6+0x20>
40110e将%r12d
赋为0,401114将%r13
赋给%rbp
,401117将%r13
指向的值赋给%eax
,40111b将%eax
减1,40111e和401121判断%eax
是否不超过5:
若%eax
超过5,则继续执行401123使炸弹爆炸;
若%eax
不超过5,则跳到401128(4010f4+0x34)将%r12d
加1,40112c和401130判断%r12d
是否为6:
若为6则跳到401153(4010f4+0x5f);
若不为6则继续执行401132将%r12d
赋给%ebx
,401135再将%ebx
赋给%rax
,401138再将%rsp+%rax+4
指向的值(下一个整数)赋给%eax
,40113b和40113e判断%eax
的值是否等于%rbp
指向的值:
若相等则继续运行401140使炸弹爆炸;
若不等则跳到401145(4010f4+0x51)将%ebx
加1,401148和40114b判断%ebx
是否不超过5:
若不超过5则跳到401135(4010f4+0x41)循环;
若超过5则继续执行40114d将%r13
加4,401151跳到401114(4010f4+0x20)循环
可以看出此处的循环等价于:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 next == 0 ; while (1 ) { num = *p_loc; if (--num > 5 ) explode_bomb(); next++; if (next == 6 ) break ; for (i = next; i <= 5 ; i++) { num_next = input[i]; if (num_next == *p_loc) explode_bomb(); } p_loc++; }
可以看出,这6个无符号整数减1后不能超过5(第4、5行),即在1~6之间;且这6个数互不相同(第9~13行),即这6个整数的可能序列是1~6的全排列 。
1 2 3 4 5 6 7 8 9 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 <phase_6+0x6c>
上一循环跳出后即执行400153将%rsp+24
的地址(6个整数末尾的位置)给%rsi
,401158将%r14
(6个整数开头的位置)赋给%rax
,40115b将%ecx
赋为7,401160将%ecx
(7)赋给%edx
,401162将%edx
(7)减去%rax
指向的值(第“%rax+1
”个整数),401164将%edx
(7)赋给%rax
指向的值(第“%rax+1
”个整数),401166将%rax
加4(指向下一个整数),40116a和40116d判断%rsi
(6个整数末尾的位置)和%rax
(逐渐向后移动的指针)是否相等:
若不等则跳到401160(4010f4+0x6c)循环;
若相等则继续执行下一部分;
可以看出此处的循环等价于:
1 2 3 4 5 6 p_end = rsp + 6 ; ecx = 7 ; for (i = p_begin; i != p_end; i++) { num_new = ecx - *i; *i = num_new; }
可以看出,原来输入到栈中的第i
个整数被重写为7-i
,将重写后的6个整数视为数组nums[6]
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 40116f: be 00 00 00 00 mov $0x0,%esi 401174: eb 21 jmp 401197 <phase_6+0xa3> 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 <phase_6+0x82> 401181: eb 05 jmp 401188 <phase_6+0x94> 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 <phase_6+0xb7> 401197: 8b 0c 34 mov (%rsp,%rsi,1),%ecx 40119a: 83 f9 01 cmp $0x1,%ecx 40119d: 7e e4 jle 401183 <phase_6+0x8f> 40119f: b8 01 00 00 00 mov $0x1,%eax 4011a4: ba d0 32 60 00 mov $0x6032d0,%edx 4011a9: eb cb jmp 401176 <phase_6+0x82>
40116f将%esi
(外层循环的索引)赋为0,401174跳到401197(4010f4+0xa3)将%rsp
指向的值(“数组基地址”)和%rsi
指向的值(“偏移量”)相加赋给%ecx
(依次取6个整数),40119a和40119d判断%ecx
(当前整数)是否不超过1:
若不超过1则跳到401183(4010f4+0x8f)将地址0x6032d0
赋给%edx
(某指针),401188将%rdx
(某指针,存放地址0x6032d0
)赋给32+%rsp+%rsi*2
指向的值,40118d将%rsi
加4(外层循环索引向后移动),401191和401195判断%rsi
是否等于24(是否走到第6个整数):
若等于24则跳到4011ab(4010f4+0xb7)继续执行下一部分;
若不等于24则继续执行401197(外层)循环
若超过1则继续执行40119f将%eax
(内层循环的索引)赋为1,4011a4将地址0x6032d0
赋给%edx
(某指针),4011a9跳到401176(4010f4+0x82)将%rdx+8
指向的值(下一个指针的值)赋给%rdx
,40117a将%eax
(内层循环的索引)加1,40117d和40117f判断%ecx
(当前整数)和%eax
(内层循环的索引)是否相等:
若不等则跳到401176(4010f4+0x82)(内层)循环;
若相等则继续执行401181跳到401188(4010f4+0x94)继续外层循环
可以看出此处的两层循环等价于:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 for (i = 0 ; i != 6 ; i++) { num = nums[i]; if (num <= 1 ) { p = 0x6032d0 ; new_nums[i] = p; goto 0x401191 ; } else { for (j = 1 ; j != num; j++) { p = 0x6032d0 ; p = *(++p); new_nums[i] = p; goto 0x401191 ; } } }
在意义不改变的基础上再次优化:
1 2 3 4 5 6 7 8 9 for (i = 0 ; i != 6 ; i++) { num = nums[i]; p = 0x6032d0 ; if (num > 1 ) { for (j = 1 ; j != num; j++) p = *(++p); } new_nums[i] = p; }
可以看出,此处再次生成了一个新的地址 数组new_nums[6]
(存放的是链表各个结点的地址 ,新数组起始地址是%rsp+32
,依次加8),它是以nums[6]
数组(上面重写后的)为索引,将(以0x6032d0
为头指针的)链表 的第nums[i]
个数的地址 赋给new_nums[i]
。构造这个地址数组的目的就是排序。
由于输入的6个数是1~6的全排列,7-i
后依然是1~6的全排列,所以新生成的数组的6个元素可以通过0x6032d0+i
(i=1,2,3,4,5,6)来获得(顺序未知),直接gdb
:
得到输出如下:
1 2 3 4 5 6 0x6032d0 <node1>: 332 1 6304480 0 0x6032e0 <node2>: 168 2 6304496 0 0x6032f0 <node3>: 924 3 6304512 0 0x603300 <node4>: 691 4 6304528 0 0x603310 <node5>: 477 5 6304544 0 0x603320 <node6>: 443 6 0 0
左侧为链表6个结点的地址(新数组中的元素),右侧第1列则是6个结点的数据域,第3列是6个结点的指针域(十进制)
1 2 3 4 5 6 7 8 9 10 11 12 13 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 <phase_6+0xde> 4011cd: 48 89 d1 mov %rdx,%rcx 4011d0: eb eb jmp 4011bd <phase_6+0xc9> 4011d2: 48 c7 42 08 00 00 00 movq $0x0,0x8(%rdx) 4011d9: 00
上一部分的循环跳出之后即执行4011ab将%rsp+32
指向的值(new_nums[0]
)赋给%rbx
,4011b0将%rsp+40
的地址(新数组第2个元素的地址)赋给%rax
(索引),4011b5将%rsp+80
的地址(新数组末尾的地址)赋给%rsi
,4011ba将%rbx
(新数组第1个元素)赋给%rcx
,4011bd将%rax
指向的值(新数组第%rax
个元素)赋给%rdx
,4011c0将%rdx
(新数组第%rax
个元素)赋给%rcx+8
指向的值,4011c4将%rax
加8,即移动到下一个元素的地址,4011c8和4011cb比较%rax
和%rsi
:
若不等则继续执行4011cd将%rdx
赋给%rcx
,4011d0跳到4011bd(4010f4+0xc9)循环;
若相等则跳到4011d2(4010f4+0xde)将%rdx+8
指向的值赋为0
可以看出此处的循环等价于:
1 2 3 4 5 6 7 8 9 num_begin = *p_new_nums; p_next = p_new_nums + 1 ; p_end = p_new_nums + 6 ; for (num = num_begin; p_next != p_end; num = num_next) { num_next = *p_next; *(num + 1 ) = num_next; p_next++; } *(num_next + 1 ) = 0 ;
可以看出,此处的循环是为新数组new_nums[6]
的各个元素添加了指针域,即第i
个元素(i=0,1,2,3,4)的指针域用*(new_nums[i] + 1)
表示,指向第i+1
个元素new_nums[i+1]
,第5
个元素(第6个地址)的指针域赋为0。这样做的好处是可以直接用元素的值表示下一个元素的值,便于重复赋值。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 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 <phase_6+0xfa> 4011e9: e8 4c 02 00 00 callq 40143a <explode_bomb> 4011ee: 48 8b 5b 08 mov 0x8(%rbx),%rbx 4011f2: 83 ed 01 sub $0x1,%ebp 4011f5: 75 e8 jne 4011df <phase_6+0xeb> 4011f7: 48 83 c4 50 add $0x50,%rsp 4011fb: 5b pop %rbx 4011fc: 5d pop %rbp 4011fd: 41 5c pop %r12 4011ff: 41 5d pop %r13 401201: 41 5e pop %r14 401203: c3 retq
上一部分加完指针域后继续执行4011da将%ebp
(索引)赋为5,4011df将%rbx+8
指向的值(开始时%rbx
是new_nums[0]
,此时%rbx+8
即为新数组第0
个元素的指针域,指向的值就是下一个元素)赋给%rax
(即当前元素的下一个元素),4011e3将%rax
指向的值(新数组下一个元素作为地址指向的值)赋给%eax
,4011e5和4011e7将%eax
和%rbx
指向的值(新数组当前元素作为地址指向的值)比较:
若%rbx
指向的值(当前元素指向值)小于%eax
(下一元素指向值),则继续执行4011e9使炸弹爆炸(由此得知当前元素指向值必须大于等于下一元素指向值 );
若%rbx
指向的值(当前元素指向值)不小于%eax
(下一元素指向值),则跳到4011ee(4010f4+0xfa)将%rbx+8
指向的值(下一个元素)赋给%rbx
(好处体现),4011f2将%ebp
(索引)减1,4011f5判断减1后是否为0:
若不为0则跳到4011df(4010f4+0xeb)循环;
若为0则继续执行并弹栈返回
可以看出此处的循环等价于:
1 2 3 4 5 6 7 8 for (i = 5 ; i != 0 ; i--) { num_next = *(num + 1 ); number_next = *num_next; if (*num < number_next) explode_bomb(); num = *(num + 1 ); } return num_next;
可以看出,需要满足*num >= **(num+1)
即对于新数组存放的6个地址值,它们对应指向的数据应该按顺序递减。
整理一下,我们需要输入6个整数,是1~6的排列,每个数再对7求补,得到新的序列。新的序列一一对应了一个链表的6个结点,即:
1 2 3 4 5 6 1: 332 2: 168 3: 924 4: 691 5: 477 6: 443
而这6个结点的数据域(上面右侧一列)需要递减排列:
1 2 3 4 5 6 3: 924 4: 691 5: 477 6: 443 1: 332 2: 168
此时便得到了新的序列为3
,4
,5
,6
,1
,2
。
输入序列为新的序列关于7的补,即为4
,3
,2
,1
,6
,5
。
至此,得到phase_6
的password为
Secret phase: binary search tree 虽然已经解决了6个phase,但在bomb.c
的最后却有这样一句话:
行百里者,半于九十。游戏尚未结束。
在objdump
反汇编生成的文件中可以看到,6个phase
函数的后面存在secret_phase
函数,此函数应该是拆除隐藏phase
的线索。
但是当6个phase
的password输入完成后,程序自动终止。如何进入此secret_phase
呢?
可以在objdump
反汇编生成的文件中查找secret_phase
,看看在哪里调用了此函数,沿此线索可能找到入口。
1 2 3 4 00000000004015c4 <phase_defused>: ... 401630: e8 0d fc ff ff callq 401242 <secret_phase> ...
不出所料,在phase_defused
函数的401630处调用了secret_phase
函数,而根据本文开头对main
函数的分析可知在每个phase
通过后都会运行phase_defused
函数。
下面开始分析phase_defused
函数,它很可能就是开启secret_phase
大门的入口 。
Enter phase_defused
对应的汇编语句如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 00000000004015c4 <phase_defused>: 4015c4: 48 83 ec 78 sub $0x78,%rsp 4015c8: 64 48 8b 04 25 28 00 mov %fs:0x28,%rax 4015cf: 00 00 4015d1: 48 89 44 24 68 mov %rax,0x68(%rsp) 4015d6: 31 c0 xor %eax,%eax 4015d8: 83 3d 81 21 20 00 06 cmpl $0x6,0x202181(%rip) # 603760 <num_input_strings> 4015df: 75 5e jne 40163f <phase_defused+0x7b> 4015e1: 4c 8d 44 24 10 lea 0x10(%rsp),%r8 4015e6: 48 8d 4c 24 0c lea 0xc(%rsp),%rcx 4015eb: 48 8d 54 24 08 lea 0x8(%rsp),%rdx 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 <phase_defused+0x71> 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 <strings_not_equal> 401613: 85 c0 test %eax,%eax 401615: 75 1e jne 401635 <phase_defused+0x71> 401617: bf f8 24 40 00 mov $0x4024f8,%edi 40161c: e8 ef f4 ff ff callq 400b10 <puts@plt> 401621: bf 20 25 40 00 mov $0x402520,%edi 401626: e8 e5 f4 ff ff callq 400b10 <puts@plt> 40162b: b8 00 00 00 00 mov $0x0,%eax 401630: e8 0d fc ff ff callq 401242 <secret_phase> 401635: bf 58 25 40 00 mov $0x402558,%edi 40163a: e8 d1 f4 ff ff callq 400b10 <puts@plt> 40163f: 48 8b 44 24 68 mov 0x68(%rsp),%rax 401644: 64 48 33 04 25 28 00 xor %fs:0x28,%rax 40164b: 00 00 40164d: 74 05 je 401654 <phase_defused+0x90> 40164f: e8 dc f4 ff ff callq 400b30 <__stack_chk_fail@plt> 401654: 48 83 c4 78 add $0x78,%rsp 401658: c3 retq 401659: 90 nop 40165a: 90 nop 40165b: 90 nop 40165c: 90 nop 40165d: 90 nop 40165e: 90 nop 40165f: 90 nop
分段分析:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 4015c4: 48 83 ec 78 sub $0x78,%rsp 4015c8: 64 48 8b 04 25 28 00 mov %fs:0x28,%rax 4015cf: 00 00 4015d1: 48 89 44 24 68 mov %rax,0x68(%rsp) 4015d6: 31 c0 xor %eax,%eax 4015d8: 83 3d 81 21 20 00 06 cmpl $0x6,0x202181(%rip) # 603760 <num_input_strings> 4015df: 75 5e jne 40163f <phase_defused+0x7b> ... 40163f: 48 8b 44 24 68 mov 0x68(%rsp),%rax 401644: 64 48 33 04 25 28 00 xor %fs:0x28,%rax 40164b: 00 00 40164d: 74 05 je 401654 <phase_defused+0x90> 40164f: e8 dc f4 ff ff callq 400b30 <__stack_chk_fail@plt> 401654: 48 83 c4 78 add $0x78,%rsp 401658: c3 retq ...
4015c4~4015d1和40163f~401658的含义参见phase_5
起始部分 。
4015d6将%eax
赋为0,4015d8和4015df判断%rip+0x202181
指向的值是否为6:
若不为6则跳到40163f(4015c4+0x7b)继续执行并返回 ;
若为6则继续执行
可以看出,只有当%rip+0x202181
指向的值为6时才会继续执行。
结合4015d8右侧的注释可以发现,%rip+0x202181
的地址为0x603760
,再次gdb
:
得到输出如下:
1 0x603760 <num_input_strings>: 0
发现0x603760
指向的值为0,而结合<num_input_strings>
名称可以推测 此地址指向的值是一个计数器 ,用于记录我们通过了几个phase
。
下面进行验证,利用gdb
打断点,查看每次通过phase
后0x603760
指向的值的变化(输入和输出一同列出,省略了文件路径):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 (gdb) x/d 0x603760 0x603760 <num_input_strings>: 0 (gdb) b explode_bomb Breakpoint 1 at 0x40143a (gdb) b phase_1 Breakpoint 2 at 0x400ee0 (gdb) b phase_2 Breakpoint 3 at 0x400efc (gdb) b phase_3 Breakpoint 4 at 0x400f43 (gdb) b phase_4 Breakpoint 5 at 0x40100c (gdb) b phase_5 Breakpoint 6 at 0x401062 (gdb) b phase_6 Breakpoint 7 at 0x4010f4 (gdb) r Starting program: /.../bomb 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. Breakpoint 1, 0x0000000000400ee0 in phase_1 () (gdb) x/d 0x603760 0x603760 <num_input_strings>: 1 (gdb) c Continuing. Phase 1 defused. How about the next one? 1 2 4 8 16 32 Breakpoint 2, 0x0000000000400efc in phase_2 () (gdb) x/d 0x603760 0x603760 <num_input_strings>: 2 (gdb) c Continuing. That's number 2. Keep going! 0 207 Breakpoint 3, 0x0000000000400f43 in phase_3 () (gdb) x/d 0x603760 0x603760 <num_input_strings>: 3 (gdb) c Continuing. Halfway there! 0 0 Breakpoint 4, 0x000000000040100c in phase_4 () (gdb) x/d 0x603760 0x603760 <num_input_strings>: 4 (gdb) c Continuing. So you got that one. Try this one. 9/N567 Breakpoint 5, 0x0000000000401062 in phase_5 () (gdb) x/d 0x603760 0x603760 <num_input_strings>: 5 (gdb) c Continuing. Good work! On to the next... 4 3 2 1 6 5 Breakpoint 6, 0x00000000004010f4 in phase_6 () (gdb) x/d 0x603760 0x603760 <num_input_strings>: 6 (gdb) c Continuing. Congratulations! You've defused the bomb! [Inferior 1 (process 8462) exited normally]
可以发现,每次成功通过一个phase
后,0x603760
指向的值都会加1。而此处判断当0x603760
指向的值为6时才会进行,即只有成功通过6个phase
后,phase_defused
函数才会继续执行4015e1及后续部分
1 2 3 4 5 6 4015e1: 4c 8d 44 24 10 lea 0x10(%rsp),%r8 4015e6: 48 8d 4c 24 0c lea 0xc(%rsp),%rcx 4015eb: 48 8d 54 24 08 lea 0x8(%rsp),%rdx 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>
0x603760
指向的值为6时,继续执行4015e1将%rsp+16
的地址给%r8
,4015e6将%rsp+12
的地址给%rcx
,4015eb将%rsp+8
的地址给%rdx
,4015f0将地址0x402619
给%esi
,4015f5将地址0x603870
给%edi
,4015fa又调用了sscanf
函数,推测 地址0x402619
指向的可能是一个格式化字符串,gdb
:
得到输出如下:
不出所料,我们应该在某处输入两个整数(存放于%rsp+8
和%rsp+12
中),再输入一个字符串(存放于%rsp+16
中)。
回忆6个phase
,发现没有一个phase
的密码符合这种格式,但是phase_3
和phase_4
的密码均为两个整数。推测 可能在phase_3
或phase_4
输入两个整数后,再输入一个字符串,可能就会开启secret_phase
。
注意此时还有一个地址0x603870
被传给了%edi
,推测 可能指向一个字符串用于和输入的字符串进行比对。
直接gdb
查看0x603870
指向的值:
得到输出如下:
1 0x603870 <input_strings+240>: ""
发现是空字符串,说明推测错误。再次推测,0x603870
指向的值不可能一直是空字符串,推测 它可能会随着我们输入密码而发生变化。使用gdb
,并在phase_defused
入口处打一个断点(输入和输出一同列出,省略了文件路径):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 (gdb) b phase_defused Breakpoint 8 at 0x4015c4 (gdb) r Starting program: /.../bomb 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. Breakpoint 1, 0x0000000000400ee0 in phase_1 () (gdb) x/s 0x603870 0x603870 <input_strings+240>: "" (gdb) c Continuing. Breakpoint 8, 0x00000000004015c4 in phase_defused () (gdb) c Continuing. Phase 1 defused. How about the next one? 1 2 4 8 16 32 Breakpoint 2, 0x0000000000400efc in phase_2 () (gdb) x/s 0x603870 0x603870 <input_strings+240>: "" (gdb) c Continuing. Breakpoint 8, 0x00000000004015c4 in phase_defused () (gdb) c Continuing. That's number 2. Keep going! 0 207 Breakpoint 3, 0x0000000000400f43 in phase_3 () (gdb) x/s 0x603870 0x603870 <input_strings+240>: "" (gdb) c Continuing. Breakpoint 8, 0x00000000004015c4 in phase_defused () (gdb) c Continuing. Halfway there! 0 0 Breakpoint 4, 0x000000000040100c in phase_4 () (gdb) x/s 0x603870 0x603870 <input_strings+240>: "0 0" (gdb) c Continuing. Breakpoint 8, 0x00000000004015c4 in phase_defused () (gdb) c Continuing. So you got that one. Try this one. 9/N567 Breakpoint 5, 0x0000000000401062 in phase_5 () (gdb) x/s 0x603870 0x603870 <input_strings+240>: "0 0" (gdb) c Continuing. Breakpoint 8, 0x00000000004015c4 in phase_defused () (gdb) c Continuing. Good work! On to the next... 4 3 2 1 6 5 Breakpoint 6, 0x00000000004010f4 in phase_6 () (gdb) x/s 0x603870 0x603870 <input_strings+240>: "0 0" (gdb) c Continuing. Breakpoint 8, 0x00000000004015c4 in phase_defused () (gdb) c Continuing. Congratulations! You've defused the bomb! [Inferior 1 (process 13412) exited normally]
可以发现,在通过phase_4
后,0x603870
指向的值发生了变化——变成了我们输入的"0 0"
(不应认为此时0x603870
指向的值就是"0 0"
,而是会因我们输入的phase_4
密码的不同而不同,此处可以自行验证当输入不同的的phase_4
密码时0x603870
指向的值,同时也可以理解phase_4
密码存在多种可能性的原因)
1 2 3 4 5 4015ff: 83 f8 03 cmp $0x3,%eax 401602: 75 31 jne 401635 <phase_defused+0x71> ... 401630: e8 0d fc ff ff callq 401242 <secret_phase> 401635: bf 58 25 40 00 mov $0x402558,%edi
继续执行4015ff和401602判断%eax
(sscanf
函数的返回值)是否为3:
若为3则继续执行;
若不为3则跳到401635(4015c4+0x71)继续执行输出提示信息
结合后续语句可以看出,secret_phase
在401630处被调用,若跳到401635则跳过了secret_phase
的调用,且不会再跳回来。即要想进入secret_phase
,则sscanf
函数的返回值必须为3,也就是说0x603870
指向的值(输入的phase_4
的密码 )除了两个整数,必须还要有一个字符串
这个额外输入的字符串是什么?继续分析~
1 2 3 4 5 6 7 8 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 <strings_not_equal> 401613: 85 c0 test %eax,%eax 401615: 75 1e jne 401635 <phase_defused+0x71> ... 401630: e8 0d fc ff ff callq 401242 <secret_phase> 401635: bf 58 25 40 00 mov $0x402558,%edi
401604将地址0x402622
给%esi
,401609把%rsp+16
的地址(指向在两个整数后输入的字符串)给%rdi
,40160e调用strings_not_equal
函数判断两个字符串是否相等(相等返回0,不等返回1),401613和401615判断%eax
(strings_not_equal
函数的返回值)是否为0:
若为0则继续执行;
若不为0则跳到401635(4015c4+0x71)继续执行输出提示信息
和上述分析相同,地址0x402622
指向的应该是一个字符串,且要想进入secret_phase
,在两个整数后输入的字符串必须和此字符串相等。利用gdb
查看地址0x402622
:
得到输出如下:
即bomb.c
文件开头注释部分提到的“犯罪者”的名字
至此,我们已经找到了开启secret_phase
大门的入口——**在phase_4
输入密码时额外输入字符串"DrEvil"
**。
Defuse secret_phase
对应的汇编语句如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 0000000000401242 <secret_phase>: 401242: 53 push %rbx 401243: e8 56 02 00 00 callq 40149e <read_line> 401248: ba 0a 00 00 00 mov $0xa,%edx 40124d: be 00 00 00 00 mov $0x0,%esi 401252: 48 89 c7 mov %rax,%rdi 401255: e8 76 f9 ff ff callq 400bd0 <strtol@plt> 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 <secret_phase+0x2a> 401267: e8 ce 01 00 00 callq 40143a <explode_bomb> 40126c: 89 de mov %ebx,%esi 40126e: bf f0 30 60 00 mov $0x6030f0,%edi 401273: e8 8c ff ff ff callq 401204 <fun7> 401278: 83 f8 02 cmp $0x2,%eax 40127b: 74 05 je 401282 <secret_phase+0x40> 40127d: e8 b8 01 00 00 callq 40143a <explode_bomb> 401282: bf 38 24 40 00 mov $0x402438,%edi 401287: e8 84 f8 ff ff callq 400b10 <puts@plt> 40128c: e8 33 03 00 00 callq 4015c4 <phase_defused> 401291: 5b pop %rbx 401292: c3 retq 401293: 90 nop 401294: 90 nop 401295: 90 nop 401296: 90 nop 401297: 90 nop 401298: 90 nop 401299: 90 nop 40129a: 90 nop 40129b: 90 nop 40129c: 90 nop 40129d: 90 nop 40129e: 90 nop 40129f: 90 nop
分段分析:
1 2 3 4 5 6 401242: 53 push %rbx 401243: e8 56 02 00 00 callq 40149e <read_line> 401248: ba 0a 00 00 00 mov $0xa,%edx 40124d: be 00 00 00 00 mov $0x0,%esi 401252: 48 89 c7 mov %rax,%rdi 401255: e8 76 f9 ff ff callq 400bd0 <strtol@plt>
401243调用read_line
函数读取一行字符串(返回值存放于%rax
),401248将%edx
赋为10,40124d将%esi
赋为0,401252将%rax
(read_line
函数返回值)赋给%rdi
,401255调用strtol
函数(什么是strtol
函数? )。
strtol
函数将字符串转化为long
型整数:%rdi
作为传入的第1个参数,作为用来解析的字符串(输入串);%esi
作为传入的第2个参数,作为需要解析部分的结束地址(NULL
);%edx
作为整数的进制(10
代表十进制)。函数的返回值存放于%rax
中
1 2 3 4 5 6 7 8 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 <secret_phase+0x2a> 401267: e8 ce 01 00 00 callq 40143a <explode_bomb> 40126c: 89 de mov %ebx,%esi 40126e: bf f0 30 60 00 mov $0x6030f0,%edi 401273: e8 8c ff ff ff callq 401204 <fun7>
40125a将%rax
(strtol
函数的返回值)赋给%rbx
,40125d将%rax-1
的地址给%eax
,401260和401265判断%eax
是否不超过1000:
若超过1000则继续执行401267使炸弹爆炸(由此得知应使strtol
函数的返回值不超过1001(无符号数) );
若不超过1000则跳到40126c(401242+0x2a)将%ebx
(strtol
函数的返回值)赋给%esi
,40126e将地址0x6030f0
赋给%edi
,401273调用函数fun7
,点此分析fun7
函数
gdb
查看地址0x6030f0
(fun7
的第一个参数)(多次查看可知,只有前60个连续内存是相关的,后面均为无关数据):
得到输出如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 0x6030f0 <n1>: 0x24 0x603110 <n21> 0x603100 <n1+16>: 0x603130 <n22> 0x0 0x603110 <n21>: 0x8 0x603190 <n31> 0x603120 <n21+16>: 0x603150 <n32> 0x0 0x603130 <n22>: 0x32 0x603170 <n33> 0x603140 <n22+16>: 0x6031b0 <n34> 0x0 0x603150 <n32>: 0x16 0x603270 <n43> 0x603160 <n32+16>: 0x603230 <n44> 0x0 0x603170 <n33>: 0x2d 0x6031d0 <n45> 0x603180 <n33+16>: 0x603290 <n46> 0x0 0x603190 <n31>: 0x6 0x6031f0 <n41> 0x6031a0 <n31+16>: 0x603250 <n42> 0x0 0x6031b0 <n34>: 0x6b 0x603210 <n47> 0x6031c0 <n34+16>: 0x6032b0 <n48> 0x0 0x6031d0 <n45>: 0x28 0x0 0x6031e0 <n45+16>: 0x0 0x0 0x6031f0 <n41>: 0x1 0x0 0x603200 <n41+16>: 0x0 0x0 0x603210 <n47>: 0x63 0x0 0x603220 <n47+16>: 0x0 0x0 0x603230 <n44>: 0x23 0x0 0x603240 <n44+16>: 0x0 0x0 0x603250 <n42>: 0x7 0x0 0x603260 <n42+16>: 0x0 0x0 0x603270 <n43>: 0x14 0x0 0x603280 <n43+16>: 0x0 0x0 0x603290 <n46>: 0x2f 0x0 0x6032a0 <n46+16>: 0x0 0x0 0x6032b0 <n48>: 0x3e9 0x0 0x6032c0 <n48+16>: 0x0 0x0
可以发现这是一棵二叉树,其中<nab>
表示第a
层从左向右数的第b
个结点,且数值依次为结点数据、左子树地址、右子树地址。画出二叉树(十六进制):
转换成十进制:
即传入fun7
函数的第一个参数就是此二叉树根结点的地址。结合fun7
函数改写的C语句:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 int fun7 (int * p_node, int target) { if (p_node == 0 ) return 0xffffffff ; num = *p_node; if (num <= target) { result = 0 ; if (num == target) return result; else { p_node = *(p_node + 2 ); result = fun7(p_node, target); return result * 2 + 1 ; } } else { p_node = *(p_node + 1 ); result = fun7(p_node, target); return result * 2 ; } }
可以发现fun7
函数的功能是:
若当前结点为空,则返回0xffffffff
;
若当前结点数据为target
,则返回0
;
若当前结点数据小于target
,则继续搜索右子树,返回右子树搜索返回值的2倍+1;
若当前结点数据大于target
,则继续搜索左子树,返回左子树搜索返回值的2倍
1 2 3 4 5 6 7 8 401278: 83 f8 02 cmp $0x2,%eax 40127b: 74 05 je 401282 <secret_phase+0x40> 40127d: e8 b8 01 00 00 callq 40143a <explode_bomb> 401282: bf 38 24 40 00 mov $0x402438,%edi 401287: e8 84 f8 ff ff callq 400b10 <puts@plt> 40128c: e8 33 03 00 00 callq 4015c4 <phase_defused> 401291: 5b pop %rbx 401292: c3 retq
401278判断%eax
(fun7
函数的返回值)是否为2:
若不为2则继续执行40127d使炸弹爆炸(由此得知**fun7
函数的返回值须为2**);
若为2则跳到401282(401242+0x40)将地址0x402438
赋给%edi
,401287调用puts
函数,40128c调用phase_defused
函数,401291和401292弹栈返回
gdb
查看地址0x402438
指向的值:
得到输出如下:
1 0x402438: "Wow! You've defused the secret stage!"
可以看出之后调用函数即输出文本,至此拆弹结束。
综合以上分析,secret_phase
的密码就是使fun7
函数的返回值为2的target
值 。
什么时候fun7
函数的返回值为2?再次观察二叉树的值:
发现左子树各结点数值均小于根结点、右子树各结点数值均大于根结点——这是一棵二叉排序树(Binary Search Tree,BST) 。
观察fun7
函数:
若当前结点为空,则返回0xffffffff
;
若当前结点数据为target
,则返回0
;
若当前结点数据小于target
,则继续搜索右子树,返回右子树搜索返回值的2倍+1;
若当前结点数据大于target
,则继续搜索左子树,返回左子树搜索返回值的2倍。
分析:
得出结论:
若逆推开始沿右路返回0次,则为22
;
若逆推开始沿右路返回1次,则为20
。
至此,得到secret_phase
的password为
或
Functions 下面是拆弹过程中遇到的一些函数:
explode_bomb explode_bomb
函数对应的汇编语句如下:
1 2 3 4 5 6 7 8 000000000040143a <explode_bomb>: 40143a: 48 83 ec 08 sub $0x8,%rsp 40143e: bf a3 25 40 00 mov $0x4025a3,%edi 401443: e8 c8 f6 ff ff callq 400b10 <puts@plt> 401448: bf ac 25 40 00 mov $0x4025ac,%edi 40144d: e8 be f6 ff ff callq 400b10 <puts@plt> 401452: bf 08 00 00 00 mov $0x8,%edi 401457: e8 c4 f7 ff ff callq 400c20 <exit@plt>
gdb
查看0x4025a3
和0x4025ac
指向的值:
1 2 (gdb) x/s 0x4025a3 (gdb) x/s 0x4025ac
可以得到输出如下:
1 2 0x4025a3: "\nBOOM!!!" 0x4025ac: "The bomb has blown up."
可以看出explode_bomb
函数的功能就是引爆炸弹 。
strings_not_equal strings_not_equal
函数 对应的汇编语句如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 0000000000401338 <strings_not_equal>: 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 401342: e8 d4 ff ff ff callq 40131b <string_length> 401347: 41 89 c4 mov %eax,%r12d 40134a: 48 89 ef mov %rbp,%rdi 40134d: e8 c9 ff ff ff callq 40131b <string_length> 401352: ba 01 00 00 00 mov $0x1,%edx 401357: 41 39 c4 cmp %eax,%r12d 40135a: 75 3f jne 40139b <strings_not_equal+0x63> 40135c: 0f b6 03 movzbl (%rbx),%eax 40135f: 84 c0 test %al,%al 401361: 74 25 je 401388 <strings_not_equal+0x50> 401363: 3a 45 00 cmp 0x0(%rbp),%al 401366: 74 0a je 401372 <strings_not_equal+0x3a> 401368: eb 25 jmp 40138f <strings_not_equal+0x57> 40136a: 3a 45 00 cmp 0x0(%rbp),%al 40136d: 0f 1f 00 nopl (%rax) 401370: 75 24 jne 401396 <strings_not_equal+0x5e> 401372: 48 83 c3 01 add $0x1,%rbx 401376: 48 83 c5 01 add $0x1,%rbp 40137a: 0f b6 03 movzbl (%rbx),%eax 40137d: 84 c0 test %al,%al 40137f: 75 e9 jne 40136a <strings_not_equal+0x32> 401381: ba 00 00 00 00 mov $0x0,%edx 401386: eb 13 jmp 40139b <strings_not_equal+0x63> 401388: ba 00 00 00 00 mov $0x0,%edx 40138d: eb 0c jmp 40139b <strings_not_equal+0x63> 40138f: ba 01 00 00 00 mov $0x1,%edx 401394: eb 05 jmp 40139b <strings_not_equal+0x63> 401396: ba 01 00 00 00 mov $0x1,%edx 40139b: 89 d0 mov %edx,%eax 40139d: 5b pop %rbx 40139e: 5d pop %rbp 40139f: 41 5c pop %r12 4013a1: c3 retq
分段分析:
1 2 3 4 5 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
40133c和40133f将%rdi
和%rsi
寄存器的值分别给了%rbx
和%rbp
(注意%rdi
存的是input
字符串;%rsi
存的是疑似密码的字符串)
1 401342: e8 d4 ff ff ff callq 40131b <string_length>
401342调用string_length
函数,推测可能返回某字符串的长度,点此分析string_length
函数
1 401347: 41 89 c4 mov %eax,%r12d
401347将%eax
的值(即string_length
函数返回值,input
字符串的长度)给%r12d
1 40134a: 48 89 ef mov %rbp,%rdi
40134a将%rbp
的值(疑似密码的字符串)给了%rdi
1 40134d: e8 c9 ff ff ff callq 40131b <string_length>
40134d再次调用string_length
函数,由于%rdi
在上一步被重新赋值为疑似密码的字符串,此处求的就是密码串的长度,存储在%eax
中
1 401352: ba 01 00 00 00 mov $0x1,%edx
401352将%edx
的值赋为1
1 2 3 4 401357: 41 39 c4 cmp %eax,%r12d 40135a: 75 3f jne 40139b <strings_not_equal+0x63> ... 40139b: 89 d0 mov %edx,%eax
401357和40135a比较%eax
和%r12d
处的值,即密码串和输入串的长度:
若二者不相等则跳到40139b(401338+0x63)将%eax
的值赋为1(见401352)(可以看出**密码串和输入串的长度不相等时函数返回1
**);
若二者相等则继续执行
1 40135c: 0f b6 03 movzbl (%rbx),%eax
40135c将%rbx
指向的内容(input
字符串第1个字符)传给%eax
寄存器(零扩展,见CS:APP中文第三版123页)
1 2 3 4 5 6 7 40135f: 84 c0 test %al,%al 401361: 74 25 je 401388 <strings_not_equal+0x50> ... 401388: ba 00 00 00 00 mov $0x0,%edx 40138d: eb 0c jmp 40139b <strings_not_equal+0x63> ... 40139b: 89 d0 mov %edx,%eax
40135f和401361判断%al
寄存器的值(input
字符串第1个字符)是否为'\0'
:
若为'\0'
则跳到401388(401338+0x50)将%edx
赋为0(继续执行40138d和40139b(401338+0x63)将%edx
的0给%eax
并返回 );
若不为'\0'
则继续执行
1 2 3 4 5 6 7 8 9 10 401363: 3a 45 00 cmp 0x0(%rbp),%al 401366: 74 0a je 401372 <strings_not_equal+0x3a> 401368: eb 25 jmp 40138f <strings_not_equal+0x57> ... 401372: 48 83 c3 01 add $0x1,%rbx ... 40138f: ba 01 00 00 00 mov $0x1,%edx 401394: eb 05 jmp 40139b <strings_not_equal+0x63> ... 40139b: 89 d0 mov %edx,%eax
401363、401366和401368比较%rbp
指向的值(密码串第1个字符)和%al
(输入串第1个字符):
若二者相等则跳到401372(401338+0x3a);
若不等则继续执行跳到40138f(401338+0x57)将%edx
赋为1(继续执行401394和40139b(401338+0x63)将%edx
的1给%eax
并返回 )
1 2 3 4 5 6 7 8 9 10 11 12 13 40136a: 3a 45 00 cmp 0x0(%rbp),%al 40136d: 0f 1f 00 nopl (%rax) 401370: 75 24 jne 401396 <strings_not_equal+0x5e> 401372: 48 83 c3 01 add $0x1,%rbx 401376: 48 83 c5 01 add $0x1,%rbp 40137a: 0f b6 03 movzbl (%rbx),%eax 40137d: 84 c0 test %al,%al 40137f: 75 e9 jne 40136a <strings_not_equal+0x32> 401381: ba 00 00 00 00 mov $0x0,%edx 401386: eb 13 jmp 40139b <strings_not_equal+0x63> ... 401396: ba 01 00 00 00 mov $0x1,%edx 40139b: 89 d0 mov %edx,%eax
40136a和401370比较%rbp
指向的值(密码串第1个字符)和%al
(输入串第1个字符):
若二者不等则跳到401396(401338+0x5e)把1给%edx
再给%eax
(即返回1 );
若相等则继续执行401372将%rbx
(输入串指针,见40133c)加1、401376将%rbp
(密码串指针)加1。40137a将%rbx
指向的内容(input
字符串第2个字符)传给%eax
寄存器,40137d和40137f判断%al
的值是否为'\0'
:
若不为'\0'
则跳到40136a(401338+0x32)循环;
若为'\0'
则继续执行401381和401386将%edx
赋为0并跳到40139b把%edx
的0再给%eax
并返回(即返回0 )
可以看出此部分的循环等价于:
1 2 3 4 5 6 7 8 do { if (*p_passwd != locChar) return 1 ; p_input++; p_passwd++; locChar = *p_input; } while (locChar != '\0' ); return 0 ;
可以看出此部分用于判断输入串和密码串是否相等,相等则返回0;不等则返回1。
综合以上分析可以看出strings_not_equal
函数的功能就是**比较%rdi
和%rsi
指向的字符串(即输入串和密码串),相等返回0、不等返回1,返回值存入%eax
**。
点此返回到phase_1
调用此函数处 。
string_length string_length
函数 对应的汇编语句如下:
1 2 3 4 5 6 7 8 9 10 11 12 000000000040131b <string_length>: 40131b: 80 3f 00 cmpb $0x0,(%rdi) 40131e: 74 12 je 401332 <string_length+0x17> 401320: 48 89 fa mov %rdi,%rdx 401323: 48 83 c2 01 add $0x1,%rdx 401327: 89 d0 mov %edx,%eax 401329: 29 f8 sub %edi,%eax 40132b: 80 3a 00 cmpb $0x0,(%rdx) 40132e: 75 f3 jne 401323 <string_length+0x8> 401330: f3 c3 repz retq 401332: b8 00 00 00 00 mov $0x0,%eax 401337: c3 retq
分段分析:
1 2 3 4 40131b: 80 3f 00 cmpb $0x0,(%rdi) 40131e: 74 12 je 401332 <string_length+0x17> ... 401332: b8 00 00 00 00 mov $0x0,%eax
40131b和40131e判断%rdi
寄存器指向的值(记住%rdi
指向的是input
)是否为'\0'
:
若为'\0'
则跳到401332(40131b+0x17)将%eax
寄存器的值赋为0
后返回(可以看出**输入串为空串时函数返回0
**)
若不为'\0'
则继续执行
1 2 3 4 5 6 7 401320: 48 89 fa mov %rdi,%rdx 401323: 48 83 c2 01 add $0x1,%rdx 401327: 89 d0 mov %edx,%eax 401329: 29 f8 sub %edi,%eax 40132b: 80 3a 00 cmpb $0x0,(%rdx) 40132e: 75 f3 jne 401323 <string_length+0x8> 401330: f3 c3 repz retq
401320先将%rdi
寄存器的值给%rdx
(记住%rdi
指向的是input
),401323再将%rdx
的值加1,401327再把%edx
的值给%eax
,401329%eax
再减去%edi
的值,40132b和40132e判断此时%rdx
指向的值是否为'\0'
:
若不为'\0'
则跳到401323(40131b+0x8)循环;
若为'\0'
则返回,此时%eax
存储的便是%rdi
中字符串的长度
可以看出此部分的循环等价于:
1 2 3 4 5 6 7 p = head; do { p++; result = p; result -= head; } while (*p != '\0' ); return result;
p
指针从字符串头head
开始向后移动,一直移动到末尾'\0'
处,此时p
与head
之差即字符串的长度
综合以上分析可以看出string_length
函数的功能就是**返回%rdi
处字符串的长度,并存入%eax
**。
点此返回到未分析完的strings_not_equal
函数 。
read_six_numbers read_six_numbers
函数 对应的汇编语句如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 000000000040145c <read_six_numbers>: 40145c: 48 83 ec 18 sub $0x18,%rsp 401460: 48 89 f2 mov %rsi,%rdx 401463: 48 8d 4e 04 lea 0x4(%rsi),%rcx 401467: 48 8d 46 14 lea 0x14(%rsi),%rax 40146b: 48 89 44 24 08 mov %rax,0x8(%rsp) 401470: 48 8d 46 10 lea 0x10(%rsi),%rax 401474: 48 89 04 24 mov %rax,(%rsp) 401478: 4c 8d 4e 0c lea 0xc(%rsi),%r9 40147c: 4c 8d 46 08 lea 0x8(%rsi),%r8 401480: be c3 25 40 00 mov $0x4025c3,%esi 401485: b8 00 00 00 00 mov $0x0,%eax 40148a: e8 61 f7 ff ff callq 400bf0 <__isoc99_sscanf@plt> 40148f: 83 f8 05 cmp $0x5,%eax 401492: 7f 05 jg 401499 <read_six_numbers+0x3d> 401494: e8 a1 ff ff ff callq 40143a <explode_bomb> 401499: 48 83 c4 18 add $0x18,%rsp 40149d: c3 retq
分段分析:
1 2 3 4 5 6 7 8 9 40145c: 48 83 ec 18 sub $0x18,%rsp 401460: 48 89 f2 mov %rsi,%rdx 401463: 48 8d 4e 04 lea 0x4(%rsi),%rcx 401467: 48 8d 46 14 lea 0x14(%rsi),%rax 40146b: 48 89 44 24 08 mov %rax,0x8(%rsp) 401470: 48 8d 46 10 lea 0x10(%rsi),%rax 401474: 48 89 04 24 mov %rax,(%rsp) 401478: 4c 8d 4e 0c lea 0xc(%rsi),%r9 40147c: 4c 8d 46 08 lea 0x8(%rsi),%r8
40145c在%rsp
中分配24字节空间压栈,401460把%rsi
的地址给%rdx
,401463把%rsi+4
的地址给%rcx
,401467把%rsi+20
的地址给%rax
,40146b把%rax
的地址给%rsp+8
,401470把%rsi+16
的地址给%rax
,401474把%rax
的地址给%rsp+0
,401478把%rsi+12
的地址给%r9
,40147c把%rsi+8
的地址给%r8
。
这一连串的赋值可以写成:
1 2 3 4 5 6 %rdx = %rsi %rcx = %rsi + 4 0(%rsp) = %rsi + 16 8(%rsp) = %rsi + 20 %r9 = %rsi + 12 %r8 = %rsi + 8
按顺序排好:
1 2 3 4 5 6 %rdx = %rsi %rcx = %rsi + 4 %r8 = %rsi + 8 %r9 = %rsi + 12 0x0(%rsp) = %rsi + 16 0x8(%rsp) = %rsi + 20
由前面的分析可知,%rsi
寄存器存放的是%rsp
的内容,可知此6个地址对应了%rsp
栈中6个int
的地址
1 2 3 401480: be c3 25 40 00 mov $0x4025c3,%esi 401485: b8 00 00 00 00 mov $0x0,%eax 40148a: e8 61 f7 ff ff callq 400bf0 <__isoc99_sscanf@plt>
401480将地址0x4025c3
传入%esi
寄存器,直接gdb
查看此处内容:
得到输出如下:
1 0x4025c3: "%d %d %d %d %d %d"
可以看出这就是C格式化字符串,之后的401485将%eax
赋为0,40148a调用sscanf
函数(什么是sscanf
函数? ),推测 此处读取了6个整数并对应存放在上述6个地址中。另外从此格式化字符串可以推测 ,输入6个整数时中间须以1个空格隔开
1 2 3 4 5 40148f: 83 f8 05 cmp $0x5,%eax 401492: 7f 05 jg 401499 <read_six_numbers+0x3d> 401494: e8 a1 ff ff ff callq 40143a <explode_bomb> 401499: 48 83 c4 18 add $0x18,%rsp 40149d: c3 retq
40148f和401492判断%eax
的值是否大于5:
若大于5则跳到401499(40145c+0x3d)弹出栈并返回;
若不大于5则继续执行401494使炸弹爆炸
综合以上分析可以看出read_six_numbers
函数的功能就是判断是否输入了5个以上整数,若输入整数未超过5个则引爆炸弹 。
点此返回到phase_2
调用此函数处 。
func4 func4
函数 对应的汇编语句如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 0000000000400fce <func4>: 400fce: 48 83 ec 08 sub $0x8,%rsp 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 <func4+0x24> 400fe6: 8d 51 ff lea -0x1(%rcx),%edx 400fe9: e8 e0 ff ff ff callq 400fce <func4> 400fee: 01 c0 add %eax,%eax 400ff0: eb 15 jmp 401007 <func4+0x39> 400ff2: b8 00 00 00 00 mov $0x0,%eax 400ff7: 39 f9 cmp %edi,%ecx 400ff9: 7d 0c jge 401007 <func4+0x39> 400ffb: 8d 71 01 lea 0x1(%rcx),%esi 400ffe: e8 cb ff ff ff callq 400fce <func4> 401003: 8d 44 00 01 lea 0x1(%rax,%rax,1),%eax 401007: 48 83 c4 08 add $0x8,%rsp 40100b: c3 retq
分段分析:
1 2 3 4 5 6 7 400fce: 48 83 ec 08 sub $0x8,%rsp 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
400fce分配8字节空间压栈,400fd2将%edx
的值(14)赋给%eax
,400fd4将%eax
减去%esi
(0),400fd6将%eax
的值(差值)赋给%ecx
,400fd8再将%ecx
(差值)逻辑右移31位,400fdb再将%eax
的值(差值)加上%ecx
(右移后的值),400fdd将二者之和算术右移1位,结果存入%eax
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 400fdf: 8d 0c 30 lea (%rax,%rsi,1),%ecx 400fe2: 39 f9 cmp %edi,%ecx 400fe4: 7e 0c jle 400ff2 <func4+0x24> 400fe6: 8d 51 ff lea -0x1(%rcx),%edx 400fe9: e8 e0 ff ff ff callq 400fce <func4> 400fee: 01 c0 add %eax,%eax 400ff0: eb 15 jmp 401007 <func4+0x39> 400ff2: b8 00 00 00 00 mov $0x0,%eax 400ff7: 39 f9 cmp %edi,%ecx 400ff9: 7d 0c jge 401007 <func4+0x39> 400ffb: 8d 71 01 lea 0x1(%rcx),%esi 400ffe: e8 cb ff ff ff callq 400fce <func4> 401003: 8d 44 00 01 lea 0x1(%rax,%rax,1),%eax 401007: 48 83 c4 08 add $0x8,%rsp 40100b: c3 retq
400fdf将%rax
的值(右移1位后的值)和%rsi
的值(0)加到%ecx
中,400fe2和400fe4将%ecx
(二者之和)和%edi
(输入的第一个整数)对比:
若二者之和不超过输入的第1个整数,则跳到400ff2(400fce+0x24)将%eax
(先前存放右移1位后的值)赋为0,400ff7和400ff9比较%edi
(输入的第1个整数)和%ecx
(二者之和):
若二者之和不小于输入的第1个整数(即二者之和等于输入的第1个整数),则跳到401007(400fce+0x39)弹出栈并返回(此时返回值%eax
为0 )
若二者之和小于输入的第1个整数,则继续执行400ffb将%rcx
(二者之和)加1赋给%esi
(先前存放的是0),400ffe递归调用func4
函数,401003将递归调用的返回值%rax
乘2再加1,401007返回
若二者之和超过了输入的第1个整数,则继续执行400fe6将%rcx
(二者之和)减1赋给%edx
(先前存放的是14),400fe9递归调用func4
函数,400fee将递归调用的返回值%eax
乘2,400ff0跳到401007(400fce+0x39)弹出栈并返回
结合两部分的分析,可以看出此部分的递归调用等价于:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 int func4 (int passwd, int min, int max) { int len = max - min; unsigned sign = len >> 31 ; int half_len = (len + sign) >> 1 ; int mid = half_len + min; if (mid <= passwd) { half_len = 0 ; if (mid >= passwd) return half_len; else { min = mid + 1 ; half_len = func4(passwd, min, max) * 2 + 1 ; return half_len; } } else { max = mid - 1 ; half_len = func4(passwd, min, max) * 2 ; return half_len; } }
奇怪的判断与二分法使我们暂不能看出此函数的功能,需要继续分析原phase
函数。
点此返回到phase_4
调用此函数处 。
fun7 fun7
函数 对应的汇编语句如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 0000000000401204 <fun7>: 401204: 48 83 ec 08 sub $0x8,%rsp 401208: 48 85 ff test %rdi,%rdi 40120b: 74 2b je 401238 <fun7+0x34> 40120d: 8b 17 mov (%rdi),%edx 40120f: 39 f2 cmp %esi,%edx 401211: 7e 0d jle 401220 <fun7+0x1c> 401213: 48 8b 7f 08 mov 0x8(%rdi),%rdi 401217: e8 e8 ff ff ff callq 401204 <fun7> 40121c: 01 c0 add %eax,%eax 40121e: eb 1d jmp 40123d <fun7+0x39> 401220: b8 00 00 00 00 mov $0x0,%eax 401225: 39 f2 cmp %esi,%edx 401227: 74 14 je 40123d <fun7+0x39> 401229: 48 8b 7f 10 mov 0x10(%rdi),%rdi 40122d: e8 d2 ff ff ff callq 401204 <fun7> 401232: 8d 44 00 01 lea 0x1(%rax,%rax,1),%eax 401236: eb 05 jmp 40123d <fun7+0x39> 401238: b8 ff ff ff ff mov $0xffffffff,%eax 40123d: 48 83 c4 08 add $0x8,%rsp 401241: c3 retq
401208和40120b判断%rdi
(地址0x6030F0
)是否为0:
若为0则跳到401238(401204+0x34)将值0xffffffff
赋给%eax
并返回
若不为0则继续执行40120d将%rdi
指向的值(地址0x6030F0
指向的值)赋给%edx
,40120f和401211比较%edx
(地址0x6030F0
指向的值)是否不超过%esi
(strtol
函数的返回值):
若%edx
不超过%esi
,则跳到401220(401204+0x1c)将%eax
赋为0,401225和401227比较%edx
和%esi
是否相等:
若相等则跳到40123d(401204+0x39)弹栈返回 ;
若不等则继续执行401229将%rdi+16
指向的值赋给%rdi
,40122d递归调用fun7
函数,401232将%rax
的值乘2加1赋给%eax
,401236跳到40123d(401204+0x39)弹栈返回
若%edx
超过%esi
,则继续执行401213将%rdi+8
指向的值赋给%rdi
,401217递归调用fun7
函数,40121c将返回值乘2,40121e跳到40123d(401204+0x39)弹栈返回
可以看出此部分的递归调用等价于:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 int fun7 (int * p_node, int target) { if (p_node == 0 ) return 0xffffffff ; num = *p_node; if (num <= target) { result = 0 ; if (num == target) return result; else { p_node = *(p_node + 2 ); result = fun7(p_node, target); return result * 2 + 1 ; } } else { p_node = *(p_node + 1 ); result = fun7(p_node, target); return result * 2 ; } }
奇怪的条件判断与递归调用使我们暂不能看出此函数的功能,需要继续分析原phase
函数。
点此返回到secret_phase
调用此函数处 。
拆弹结束 将以上得到的各个phase
的password存入passed.txt
中:
1 2 3 4 5 6 7 8 Border relations with Canada have never been better. 1 2 4 8 16 32 0 207 0 0 DrEvil 9/N567 4 3 2 1 6 5 22
注意:
phase_3
、phase_4
、phase_5
和secret_phase
的password均有多种情况,请只选择一种输入;
若想进入并拆除secret_phase
,请在第4行后添加空格和DrEvil
,并在第6行后添加一行,输入secret_phase
的password;
文件最后一定要另起一空行,以代表输入结束。
若password全部正确,则会看到输出为:
1 2 3 4 5 6 7 8 9 10 11 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... Wow! You've defused the secret stage! Congratulations! You've defused the bomb!
特别鸣谢:本实验部分思路受Hakula 启发,在此表示感谢。