Bomb Lab

二进制炸弹实验

最近看了计算机系统基础实验这门课程,做了Bomb Lab的实验,然后做做相应的总结。

介绍

一个二进制炸弹是一个 Linux 可执行程序,包含了多个阶段(又称为层次、关卡)。在炸弹程序运行的每个阶段要求输入一个特定字符串,如果该输入字符串符合程序的要求,该阶段的炸弹就被拆除了,否则炸弹“爆炸——即打印输出“BOOM!!!”的提示。

每个炸弹阶段考察了程序与数据的机器级表示的不同方面,难度逐级递增:

  • 阶段 0:字符串比较
  • 阶段 1:浮点数表示
  • 阶段 2:循环
  • 阶段 3:条件/分支
  • 阶段 4:递归调用和栈
  • 阶段 5:指针
  • 阶段 6:链表/指针/结构
  • 另外还有一个隐藏阶段作为阶段 7,只有在阶段 4 的拆解字符串后附加一特定字符串后,才能在实验最后进入隐藏阶段。

实验的目标是拆除尽可能多的炸弹关卡——分析获得尽可能多的正确拆解字符串。

实验数据

http://cs.nju.edu.cn/sufeng/course/mooc/0809NJU064_bomblab.tar

可在 Linux 实验环境中使用命令“tar xvf 0809NJU064_bomblab.tar”将其中包含的文件提取到当前目录中。该 tar 文件中包含如下实验所需文件:

  • bomb:二进制炸弹可执行程序
  • bomb.c:包含 bomb 程序中 main 函数的 C 语言程序框架

实验工具

完成二进制炸弹拆除任务,可使用 objdump 工具程序反汇编可执行炸弹程序,并使用gdb 工具程序单步跟踪每一阶段的机器指令,从中理解每一指令的行为和作用,进而推断拆除炸弹所需的目标字符串的内容组成。例如,可在每一阶段的起始指令前和引爆炸弹的函数调用指令前设置断点。

GDB

为从二进制炸弹可执行程序”bomb“中找出触发炸弹爆炸的条件,可使用 GDB 程序帮助对程序的分析。GDB 是 GNU 开源组织发布的一个强大的交互式程序调试工具。一般来说,GDB 可帮助完成以下几方面的调试工作(更详细描述可参看 GDB 文档和相关资料):

  • 装载、启动被调试的程序
  • 使被调试程序在指定的调试断点处中断执行,以方便查看程序变量、寄存器、栈内容等程序运行的现场数据
  • 动态改变程序的执行环境,如修改变量的值

objdump

反汇编二进制炸弹程序,获得其中汇编指令供分析

  • objdump -d bomb 输出bomb程序的反汇编结果

  • objdump -d bomb > bomb.s 获得bomb程序的反汇编结果并保存于文本文件bomb.s中供分析

    • –d 选项:对二进制程序中的机器指令代码进行反汇编。通过分析汇编源代码可以发现bomb 程序是如何运行的
  • objdump -t bomb 打印bomb程序的符号表,其中包含bomb中所有函数、全局变量的名称和存储地址

    • –t 选项:打印指定二进制程序的符号表,其中包含了程序中的函数、全局变量的名称和存储地址

strings

该命令显示二进制程序中的所有可打印字符串

反汇编代码

  1. 在命令行执行以下命令,将汇编代码重定向到bomb.s文件中

    .s结尾代表汇编代码,未进行预处理

    1
    objdump -d bomb > bomb.s
  2. 可以使用任何文本查看工具打开bomb.s文件,内容就是反汇编的结果

    1
    less bomb.s
  3. 在bomb.s中搜索phase_0函数

    1
    /phase_0

    使用n(next)和p(pervious)查找上一个和下一个

    使用q退出查看模式

反汇编代码的理解

过程栈帧及入口参数的位置

阶段 0:字符串比较

1.对二进制炸弹程序进行反汇编

1
objdump -d bomb > bomb.s

2.对函数的汇编指令代码进行分析

定位到phase_0函数上,分析test,je,call代码发现

为避免执行0x8049471处对引爆炸弹函数的调用指令,je指令的测试条件应被满足,即0x804946dtest指令执行之前,寄存器EAX的值应为0,同时可以判断出pushl处是用户输入的字符串地址,push处是内部保存的字符串地址,所以需要满足pushl处输入的和push处的字符串相同。

功能说明:如果输入的两个字符串参数的内容相同,strings_not_equal函数将返回0,则在示例汇编代码中,test指令测试时,将使得je指令跳转到程序的正常结束位置。

否则引爆炸弹

分析strings_not_equal函数的控制逻辑

分析汇编代码可知:该函数在输入的两个字符串参数的长度和内容均相同时将返回0,否则返回1,并且返回值保存于EAX寄存器中

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
08049c61 <strings_not_equal>:
8049c61: 55 push %ebp
8049c62: 89 e5 mov %esp,%ebp
8049c64: 53 push %ebx
8049c65: 83 ec 10 sub $0x10,%esp
8049c68: ff 75 08 pushl 0x8(%ebp)
8049c6b: e8 c5 ff ff ff call 8049c35 <string_length>
8049c70: 83 c4 04 add $0x4,%esp
8049c73: 89 c3 mov %eax,%ebx
8049c75: ff 75 0c pushl 0xc(%ebp)
8049c78: e8 b8 ff ff ff call 8049c35 <string_length>
8049c7d: 83 c4 04 add $0x4,%esp
8049c80: 39 c3 cmp %eax,%ebx
8049c82: 74 07 je 8049c8b <strings_not_equal+0x2a>
8049c84: b8 01 00 00 00 mov $0x1,%eax
8049c89: eb 3c jmp 8049cc7 <strings_not_equal+0x66>
8049c8b: 8b 45 08 mov 0x8(%ebp),%eax
8049c8e: 89 45 f8 mov %eax,-0x8(%ebp)
8049c91: 8b 45 0c mov 0xc(%ebp),%eax
8049c94: 89 45 f4 mov %eax,-0xc(%ebp)
8049c97: eb 1f jmp 8049cb8 <strings_not_equal+0x57>
8049c99: 8b 45 f8 mov -0x8(%ebp),%eax
8049c9c: 0f b6 10 movzbl (%eax),%edx
8049c9f: 8b 45 f4 mov -0xc(%ebp),%eax
8049ca2: 0f b6 00 movzbl (%eax),%eax
8049ca5: 38 c2 cmp %al,%dl
8049ca7: 74 07 je 8049cb0 <strings_not_equal+0x4f>
8049ca9: b8 01 00 00 00 mov $0x1,%eax
8049cae: eb 17 jmp 8049cc7 <strings_not_equal+0x66>
8049cb0: 83 45 f8 01 addl $0x1,-0x8(%ebp)
8049cb4: 83 45 f4 01 addl $0x1,-0xc(%ebp)
8049cb8: 8b 45 f8 mov -0x8(%ebp),%eax
8049cbb: 0f b6 00 movzbl (%eax),%eax
8049cbe: 84 c0 test %al,%al
8049cc0: 75 d7 jne 8049c99 <strings_not_equal+0x38>
8049cc2: b8 00 00 00 00 mov $0x0,%eax
8049cc7: 8b 5d fc mov -0x4(%ebp),%ebx
8049cca: c9 leave
8049ccb: c3 ret

3.定位并获得内置字符串的值,并相应构造输入字符串

前面已推断出:和用户输入字符串相比较的程序内置字符串的存储地址为0x804a1e0,并且两个字符串的内容应相同,因此可使用gdb查看地址0x804a1e0中存储的内置字符串的内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
ics@debian:~/test/bomlab$ gdb bomb
#断点设置到phase_0函数中调用strings_not_equal函数的call指令的位置
(gdb) b *0x8049465
Breakpoint 1 at 0x8049465
(gdb) r
Starting program: /home/ics/test/bomlab/bomb
Welcome to my fiendish little bomb. You have 7 phases with
which to blow yourself up. Have a nice day!
fdafada(这里先随意输入一些字符串)

Breakpoint 1, 0x08049465 in phase_0 ()
#查看50个字节的内容,每个字节内容作为字符输出
(gdb)x/50c 0x804a1e0
0x804a1e0: 65 'A' 32 ' ' 116 't' 101 'e' 120 'x' 116 't' 32 ' ' 108 'l'
0x804a1e8: 105 'i' 110 'n' 101 'e' 32 ' ' 105 'i' 115 's' 32 ' ' 97 'a'
0x804a1f0: 32 ' ' 115 's' 101 'e' 113 'q' 117 'u' 101 'e' 110 'n' 99 'c'
0x804a1f8: 101 'e' 32 ' ' 111 'o' 102 'f' 32 ' ' 65 'A' 83 'S' 67 'C'
0x804a200: 73 'I' 73 'I' 32 ' ' 99 'c' 104 'h' 97 'a' 114 'r' 97 'a'
0x804a208: 99 'c' 116 't' 101 'e' 114 'r' 115 's' 46 '.' 0 '\000' 37 '%'
#直接输出的方式
(gdb)x/1s 0x804a1e0
0x804a1e0: "A text line is a sequence of ASCII characters."

4.测试

随意输入的结果

1
2
3
4
5
6
7
ics@debian:~/test/bomlab$ ./bomb
Welcome to my fiendish little bomb. You have 7 phases with
which to blow yourself up. Have a nice day!
asdfa

BOOM!!!
The bomb has blown up.

输入获取到的字符串

1
2
3
4
5
ics@debian:~/test/bomlab$ ./bomb
Welcome to my fiendish little bomb. You have 7 phases with
which to blow yourself up. Have a nice day!
A text line is a sequence of ASCII characters.
Well done! You seem to have warmed up!

阶段 1:浮点数表示

阶段说明:该阶段要求输入对应某浮点(float或double)数值表示的一对整数(short或int)

IEEE 754标准

X87浮点指令(MMX和SSE指令)

X87 FPU指令

实验步骤:

1.对本阶段函数的汇编指令代码进行分析

定位到phase_1函数上

2.找到浮点数常量的值,获得其IEEE-754表示

1
2
3
4
5
6
7
8
08049484 <phase_1>:
8049484: 55 push %ebp
8049485: 89 e5 mov %esp,%ebp
8049487: 83 ec 28 sub $0x28,%esp
804948a: c7 45 f4 a1 84 76 09 movl $0x97684a1,-0xc(%ebp)
8049491: db 45 f4 fildl -0xc(%ebp)
8049494: dd 5d e8 fstpl -0x18(%ebp)
...
  • 从中可看出,整型值0x97684a1通过浮点栈被转为双精度浮点表示,并传送到本阶段函数栈帧中地址为**-0x18(%ebp)**处开始连续存放
  • 从课程内容可知,该常量值的双精度(double)IEEE 754表示为(十六进制字节串):41 A2 ED 09 42 0 0 0

3.分析程序的输入要求和比较逻辑

1
2
3
4
5
6
7
8049497:       8d 45 e0                lea    -0x20(%ebp),%eax
804949a: 50 push %eax
804949b: 8d 45 e4 lea -0x1c(%ebp),%eax
804949e: 50 push %eax
804949f: 68 0f a2 04 08 push $0x804a20f
80494a4: ff 75 08 pushl 0x8(%ebp)
80494a7: e8 24 fc ff ff call 80490d0 <__isoc99_sscanf@plt>
  • 输入拆解字符串的解析调用了sscanf函数,

    其原型如下:

    • int sscanf ( const char * s, const char * format, …);
    • 返回从第一个输入字符串中成功扫描,读入的数据项的个数
  • 输入格式字符串起始地址位于0x804a20f,可使用gdb查看存放于该处的值

    1
    2
    (gdb) x/1s 0x804a20f
    0x804a20f: "%d %d"
  • 可见,输入的应是空格分隔的两个整数

    • (参考sscanf调用时参数压栈顺序)分别存储于-0x1c(%ebp)(第二个压栈的参数)、-0x20(%ebp)(第一个压栈的参数)

  • 变量及其存储地址

    1. -0x18(%ebp):浮点数
    2. -0x1c(%ebp) :第1个输入整数
    3. -0x20(%ebp) :第2个输入整数
  • 0x80494c0-c1处比较了第1个输入整数与浮点数起始存储地址处(小端表示中是其低32位)整数是否相等

  • 80494cc-d9处比较了第2个输入整数与浮点数高4个字节存储地址处(小端表示中是其高32位)整数是否相等

  • 因此,输入字符串中应包含分别对应该浮点数表示低32位的整数和高32位的整数

4.基于上述结果,构造输入字符串

整型值0x97684a1对应的双精度浮点数的IEEE 754表示为(十六进制字节串,从高位到低位):

41 A2 ED 09 42 00 00 00

  • 低32位(从高位到低位)并转为十进制有符号整数:

    42 00 00 00 = 1107296256

  • 高32位(从高位到低位)并转为十进制有符号整数:

    41 A2 ED 09 = 1101196553

  • 因此拆解字节串应为“1107296256 1101196553”

IDA反汇编的结果是

阶段 2:循环

输入满足程序所期望的顺序和取值的一个数字序列

分析结果如上

发现push $0x9表示数组中有9个数

通过cmp $0x86,%eax判断出第一个数是134

0x804951c开始进入循环,0x8049555处进行比较,其中eax是期望的结果,edx是我们输入的结果

通过断点调试

1
2
3
4
5
6
7
(gdb) b *0x8049555
(gdb) r
(gdb) i r
eax 0x85 133
ecx 0x86 134
edx 0x85 133
ebx 0x1 1

可以通过逐步调试得到结果,也可以发现汇编指令的计算方式满足下面形式

系统生成的eax中的结果满足:Array[i - 1] - 2 * i + 1

最终结果可以通过这个网站在线编程计算最终结果。

134 133 130 125 118 109 98 85 70

IDA反汇编的结果是

阶段 3:条件/分支

发现push $0x804a20f指令,我们可以查看其中的内容

1
2
3
4
5
6
7
(gdb) b phase_3
(gdb) r
...
(gdb) si
0x0804959a in phase_3 ()
(gdb) x/1s 0x804a20f
0x804a20f: "%d %d"

发现是两个参数,所以可以确定输入的是两个数

演示就先输入1 2然后进行调试,当执行到sub $0xfe,%eax处时,发现eax的值需要做减操作,而0xfe的值是254,eax的值是1,所以sub之后eax值为-253,往下进行,是cmp指令,判断9和eax的大小,ja指令表示无符号大于就跳转,jb表示小于就跳转,所以,如果eax > 9 ,就会执行ja跳转到804962d,发生爆炸。jg表示有符号大于

1
2
3
80495c0:       sub    $0xfe,%eax   //eax = eax - 0xfe(254) = -253
80495c5: cmp $0x9,%eax //9 > eax 爆炸 ja是大于
80495c8: ja 804962d <phase_3+0xb4> //爆炸

所以为了继续执行,需要满足eax <= 9,这里先拿9来测试,所以得出在sub之前的eax = 263也就是输入的第一个参数的值,接着继续执行,到达下面这行,也就是说第一个参数满足条件的情况下会eax赋一个地址值,接着执行

1
80495ca:    mov    0x804a218(,%eax,4),%eax //eax = 134521368

执行下面的jmp指令之后跳转到0x08049624处,这里对-0xc(%ebp)进行了赋值,3e5 = 997

1
2
8049624:    movl   $0x3e5,-0xc(%ebp)//ebp = 3e5 = 997
804962b: jmp 8049639 <phase_3+0xc0>

接着jmp跳转到0x8049639处,这里首先mov指令将我们输入的第二个数赋给了eaxcmp指令对%eax和-0xc(%ebp)的值进行比较,je表示Jump if Equals所以如果这两个值相等就满足条件,所以eax的值就应该是997

1
2
3
8049639:    mov    -0x18(%ebp),%eax
804963c: cmp %eax,-0xc(%ebp)
804963f: je 804964d <phase_3+0xd4>

IDA反汇编的结果是

阶段 4:递归调用和栈

没有理解!!!

首先定位到下面这行,发现这里有一个地址,可以看看值是什么

1
80496f9:      push   $0x804a20f

发现内容是两个整形的值,这就表示需要输入两个值

1
2
3
4
(gdb) b *0x80496f9
(gdb) r res.txt
(gdb) x/1s 0x804a20f
0x804a20f: "%d %d"

接着往下,输入参数之后就会去调用func4func4就是一个递归函数,看了半天也没搞命名func4的逻辑,只好用IDA来进行反汇编,可以看到phase_4的反汇编结果

这里的v5的含义就是我们输入参数的个数,需要判断它是否输入的是两个数,然后是三个递归条件,这里就需要明白v2,v3,v4的含义了,接着看看func4的反汇编结果

然后再看phase_4的前面的这两行

1
2
80496d9:   mov    $0x804a240,%ebx
80496de: mov $0x24,%edx //edx = 0x24(36)

这两行的意思是说,先开辟了一段空间,然后放一个数组进去,长度是0x24也就是36.

然后执行到下面这一行发现,ecx的值是从36一直递减到0的

1
80496e9:  rep movsl %ds:(%esi),%es:(%edi)

再往后会发现有两个比较,0和34,同时还有判断是否不等于456

1
2
8049784:      cmp    $0x1c8,%eax//eax != 456 ?
8049789: jne 8049797 <phase_4+0xd0>//不等于则跳转

这个先搁置吧,在网上也没找到和我这个一样的, 其他很多都是有比较的指令,感觉比我碰到的这个都要简单些。

阶段 5:指针

首先发现push指令处有一个地址

1
80497f3:   push   $0x804a20f

查看这个位置的内容,发现需要输入的是两个数

1
2
(gdb) x/1s 0x804a20f
0x804a20f: "%d %d"

再往下看,发现这是一个循环结构

1
2
3
4
5
6
7
8
9
8049831:     addl   $0x1,-0xc(%ebp) //
8049835: mov -0x18(%ebp),%eax
8049838: mov 0x804c200(,%eax,4),%eax //eax = eax*4+0x804c200
804983f: mov %eax,-0x18(%ebp)
8049842: mov -0x18(%ebp),%eax
8049845: add %eax,-0x10(%ebp)
8049848: mov -0x18(%ebp),%eax
804984b: cmp $0xf,%eax
804984e: jne 8049831 <phase_5+0x4c>

看了半天没理清楚😶,还是使用IDA进行反编译看看结果吧

这样就清楚了,可以发现,这个循环的条件是v3 != 15,然后后面还有一个数组的赋值操作v3 = array_2751[v3],这里的数组长度是16,但是我们还是不知道这个数组里面的值是多少,但是可以看出来这个循环中v3的结果是根据数组的值进行变化的,所以我就想到会不会是这种情况,索引0处值为1,索引1处值为2…这种形式,当然,可以输入结果进行测试,因为条件是v3不等于15,而v3就是我们输入的第一个值,v2暂时可以随意输入,所以输入0 1进行测试

到达这个循环里,下面这一句就对应了++v6的操作

1
8049831:  addl   $0x1,-0xc(%ebp) //

然后下面这几句的就是对v3赋值的过程

1
2
3
4
8049835:   mov    -0x18(%ebp),%eax
8049838: mov 0x804c200(,%eax,4),%eax //eax = eax*4+0x804c200
804983f: mov %eax,-0x18(%ebp)
8049842: mov -0x18(%ebp),%eax

结束后发现eax的值变成了10,那意思就是说数组中的索引0处的内容就是10,如果要进行下一次循环的话,就该读取索引10处的内容了,然后就是这样一个逻辑,就可以推出后面的值了,当v3==15的时候就会跳出循环

接着会去判断v6==6v5==v2,从循环处可以发现v6每一次进入循环都会有自增的操作,而v5也是在每一次的循环过程中去进行加操作的,所以拿到v5的值就相当于知道了v2的值了,下面有两个cmp的操作,就是在进行比较

1
2
3
4
5
6
8049850:    cmpl   $0x6,-0xc(%ebp) //-0xc(%ebp) == 6 ?
8049854: jne 804985e <phase_5+0x79>
8049856: mov -0x1c(%ebp),%eax
8049859: cmp %eax,-0x10(%ebp)
804985c: je 804986a <phase_5+0x85>
804985e: call 8049ec9 <explode_bomb>

可以在执行完8049859: cmp %eax,-0x10(%ebp)之后查看一下-0x10(%ebp)中的值,这里拿到的0x00000030换算成十进制就是48,所以第二个参数就是48

1
2
(gdb) x $ebp-16
0xbffff5a8: 0x00000030

当然,我这种方式是运气好一开始就使用0作为第一个参数来测试的,如果使用的其他值就需要一步一步推出来其他的值,因为其中有一个数组,那就可以输入小于15的值来确定每一个位置的值是多少,我这里得到了数组中所有的值

1
2
索引 0	1   2   3  4   5   6   7  8  9   10  11   12  13 14    
值 10 2 14 7 8 12 15 11 0 4 1 13 3 9 6

然后就可以得到所有的循环结果,但是因为最终跳出循环的结果是v3=15,所以满足条件的就只有第一个也就是0 48

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
0: 10  1   2   14  6   15 sum=48
1: 2 14 6 15 x
2: 14 6 15 x
3: 7 11 13 9 4 8
4: 8 0 10 1 2 14
5: 12 3 7 11 13 9
6: 15 x
7: 11 13 9 4 8 0
8: 0 10 1 2 14 6
9: 4 8 0 10 1 2
10:1 2 14 6 15 x
11:13 9 4 8 0 10
12:3 7 11 13 9 4
13:9 4 8 0 10 1
14:6 15 x

阶段 6:链表/指针/结构

这个部分有一大堆的代码,很长…

要求应该是输入多个数,因为在里面调用了另外一个函数read_n_numbers,这里会读取输入的数,使用IDA进行反汇编之后就能知道函数的内容了,里面调用了strtok函数,就是对字符串进行分解

下面看看第一个循环

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
80498a0:       c7 45 f0 00 00 00 00    movl   $0x0,-0x10(%ebp)
80498a7: eb 60 jmp 8049909 <phase_6+0x98>
80498a9: 8b 45 f0 mov -0x10(%ebp),%eax
80498ac: 8b 44 85 cc mov -0x34(%ebp,%eax,4),%eax
80498b0: 85 c0 test %eax,%eax
80498b2: 7e 0c jle 80498c0 <phase_6+0x4f>
80498b4: 8b 45 f0 mov -0x10(%ebp),%eax
80498b7: 8b 44 85 cc mov -0x34(%ebp,%eax,4),%eax
80498bb: 83 f8 07 cmp $0x7,%eax
80498be: 7e 0f jle 80498cf <phase_6+0x5e>
80498c0: e8 04 06 00 00 call 8049ec9 <explode_bomb>
80498c5: b8 00 00 00 00 mov $0x0,%eax
80498ca: e9 08 01 00 00 jmp 80499d7 <phase_6+0x166>
80498cf: 8b 45 f0 mov -0x10(%ebp),%eax
80498d2: 83 c0 01 add $0x1,%eax
80498d5: 89 45 ec mov %eax,-0x14(%ebp)
80498d8: eb 25 jmp 80498ff <phase_6+0x8e>
----------------
80498da: 8b 45 f0 mov -0x10(%ebp),%eax
80498dd: 8b 54 85 cc mov -0x34(%ebp,%eax,4),%edx
80498e1: 8b 45 ec mov -0x14(%ebp),%eax
80498e4: 8b 44 85 cc mov -0x34(%ebp,%eax,4),%eax
80498e8: 39 c2 cmp %eax,%edx
80498ea: 75 0f jne 80498fb <phase_6+0x8a>
80498ec: e8 d8 05 00 00 call 8049ec9 <explode_bomb>
80498f1: b8 00 00 00 00 mov $0x0,%eax
80498f6: e9 dc 00 00 00 jmp 80499d7 <phase_6+0x166>
80498fb: 83 45 ec 01 addl $0x1,-0x14(%ebp)
80498ff: 83 7d ec 06 cmpl $0x6,-0x14(%ebp)
8049903: 7e d5 jle 80498da <phase_6+0x69>
------------------
8049905: 83 45 f0 01 addl $0x1,-0x10(%ebp)
8049909: 83 7d f0 06 cmpl $0x6,-0x10(%ebp)
804990d: 7e 9a jle 80498a9 <phase_6+0x38>

这些就是第一个循环的逻辑,根据逻辑变成c语言代码就是,可以发现其作用是判断输入的数是否大于0小于等于7,同时各个值不相等。

1
2
3
4
5
6
7
8
9
int n_numbers[7] = { /* 我们输入的7个数 */ };
for (int i = 0; i <= 6; ++i)
{
if (six_numbers[i] <= 0 || six_numbers[i] > 7)
explode_bomb();
for (int j = i + 1; j <= 6; ++j)
if (six_numbers[i] == six_numbers[j])
explode_bomb();
}

下面又是循环结构,不过有点复杂,懒得一行一行去分析了,直接到最后一个循环结构,发现前面这段代码

1
2
3
4
8049989:     mov    -0xc(%ebp),%eax
804998c: movl $0x0,0x8(%eax) //v7[2]=0x8(%eax)=0
8049993: mov -0x18(%ebp),%eax //v7=%eax=0x804c130
8049996: mov %eax,-0xc(%ebp)

这段代码通过IDA编译后发现是下面两句,应该是为了后面的循环中作为判断条件的

1
2
v7[2] = 0;
v7 = v4;

接着看循环体内部,发现有一个cmp指令,下面的jle的作用是小于或等于,这里比较的内容其实就是上面两句中的参数。

1
2
3
4
5
6
7
8
80499a2:    mov    -0xc(%ebp),%eax
80499a5: mov (%eax),%edx
80499a7: mov -0xc(%ebp),%eax
80499aa: mov 0x8(%eax),%eax
80499ad: mov (%eax),%eax
80499af: cmp %eax,%edx //比较
80499b1: jle 80499bf <phase_6+0x14e>
80499b3: call 8049ec9 <explode_bomb>

这里就使用GDB进行调试,查看参数的内容是什么,将断点设置到0x8049993处,查看其中的内容如下:

1
2
(gdb) x/4w $eax
0x804c130 <node1>: 0x00000009 0x00000001 0x00000000 0x000003e9

发现有个node1的标识,其实这就是一个链表,同样可以推断出其他node的信息

1
2
3
4
5
6
7
0x804c130 <node1>:	0x00000009	0x00000001	0x0804c124	0x000003e9
0x804c124 <node2>: 0x00000003 0x00000002 0x0804c118 0x00000009
0x804c118 <node3>: 0x00000006 0x00000003 0x0804c10c 0x00000003
0x804c10c <node4>: 0x00000007 0x00000004 0x0804c100 0x00000006
0x804c100 <node5>: 0x00000000 0x00000005 0x0804c0f4 0x00000007
0x804c0f4 <node6>: 0x00000002 0x00000006 0x0804c0e8 0x00000000
0x804c0e8 <node7>: 0x00000005 0x00000007 0x00000000 0x00000002

0x804c130结构体中的指针为0x0804c124,这个指针指向node2,同样node2中的指针指向node3 …最后一个node7中的指针指向地址0。

所以这个结构体为:

1
2
3
4
5
struct node{
int val;//这个暂不知道是什么数据
int id;//id是编号
node *next; //next是指向的node的地址
}

再回到cmp指令,相当于排序的功能了,按照node上数值大小从小到大进行排列,也就是对9(node1) 3(node2) 6(node3) 7(node4) 0(node5) 2(node6) 5(node7)进行排序,结果是node5<node6<node2<node7<node3<node4<node1

所以输入的结果就是5 6 2 7 3 4 1

- - - - - - - - - - - - - 文 章 结 束 感 谢 阅 读 - - - - - - - - - - - - -