最近看了计算机系统基础实验这门课程,做了Bomb Lab的实验,然后做做相应的总结。
二进制炸弹实验
介绍
一个二进制炸弹是一个 Linux 可执行程序,包含了多个阶段(又称为层次、关卡)。在炸弹程序运行的每个阶段要求输入一个特定字符串,如果该输入字符串符合程序的要求,该阶段的炸弹就被拆除了,否则炸弹“爆炸——即打印输出“BOOM!!!”的提示。
每个炸弹阶段考察了程序与数据的机器级表示的不同方面,难度逐级递增:
- 阶段 0:字符串比较
- 阶段 1:浮点数表示
- 阶段 2:循环
- 阶段 3:条件/分支
- 阶段 4:递归调用和栈
- 阶段 5:指针
- 阶段 6:链表/指针/结构
- 另外还有一个隐藏阶段作为阶段 7,只有在阶段 4 的拆解字符串后附加一特定字符串后,才能在实验最后进入隐藏阶段。
实验的目标是拆除尽可能多的炸弹关卡——分析获得尽可能多的正确拆解字符串。
实验数据
可在 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
该命令显示二进制程序中的所有可打印字符串
反汇编代码
-
在命令行执行以下命令,将汇编代码重定向到bomb.s文件中
.s结尾代表汇编代码,未进行预处理
objdump -d bomb > bomb.s
-
可以使用任何文本查看工具打开bomb.s文件,内容就是反汇编的结果
less bomb.s
-
在bomb.s中搜索phase_0函数
/phase_0
使用n(next)和p(pervious)查找上一个和下一个
使用q退出查看模式
反汇编代码的理解
过程栈帧及入口参数的位置
阶段 0:字符串比较
1.对二进制炸弹程序进行反汇编
objdump -d bomb > bomb.s
2.对函数的汇编指令代码进行分析
定位到phase_0函数上,分析test,je,call
代码发现
为避免执行0x8049471
处对引爆炸弹函数的调用指令,je
指令的测试条件应被满足,即0x804946d
处test
指令执行之前,寄存器EAX的值应为0,同时可以判断出pushl
处是用户输入的字符串地址,push
处是内部保存的字符串地址,所以需要满足pushl
处输入的和push
处的字符串相同。
功能说明:如果输入的两个字符串参数的内容相同,strings_not_equal函数将返回0,则在示例汇编代码中,test指令测试时,将使得je指令跳转到程序的正常结束位置。
否则引爆炸弹
分析strings_not_equal函数的控制逻辑
分析汇编代码可知:该函数在输入的两个字符串参数的长度和内容均相同时将返回0,否则返回1,并且返回值保存于EAX寄存器中
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
中存储的内置字符串的内容:
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.测试
随意输入的结果
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.
输入获取到的字符串
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表示
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.分析程序的输入要求和比较逻辑
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查看存放于该处的值-
(gdb) x/1s 0x804a20f 0x804a20f: "%d %d"
-
-
可见,输入的应是空格分隔的两个整数
- (参考sscanf调用时参数压栈顺序)分别存储于-0x1c(%ebp)(第二个压栈的参数)、-0x20(%ebp)(第一个压栈的参数)
-
变量及其存储地址
- -0x18(%ebp):浮点数
- -0x1c(%ebp) :第1个输入整数
- -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是我们输入的结果
通过断点调试
(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
指令,我们可以查看其中的内容
(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
表示有符号大于
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
赋一个地址值,接着执行
80495ca: mov 0x804a218(,%eax,4),%eax //eax = 134521368
执行下面的jmp
指令之后跳转到0x08049624
处,这里对-0xc(%ebp)
进行了赋值,3e5 = 997
8049624: movl $0x3e5,-0xc(%ebp)//ebp = 3e5 = 997
804962b: jmp 8049639 <phase_3+0xc0>
接着jmp
跳转到0x8049639
处,这里首先mov
指令将我们输入的第二个数赋给了eax
,cmp
指令对%eax和-0xc(%ebp)
的值进行比较,je表示Jump if Equals
所以如果这两个值相等就满足条件,所以eax
的值就应该是997
8049639: mov -0x18(%ebp),%eax
804963c: cmp %eax,-0xc(%ebp)
804963f: je 804964d <phase_3+0xd4>
IDA反汇编的结果是
阶段 4:递归调用和栈
没有理解!!!
首先定位到下面这行,发现这里有一个地址,可以看看值是什么
80496f9: push $0x804a20f
发现内容是两个整形的值,这就表示需要输入两个值
(gdb) b *0x80496f9
(gdb) r res.txt
(gdb) x/1s 0x804a20f
0x804a20f: "%d %d"
接着往下,输入参数之后就会去调用func4
,func4
就是一个递归函数,看了半天也没搞命名func4
的逻辑,只好用IDA来进行反汇编,可以看到phase_4的反汇编结果
这里的v5的含义就是我们输入参数的个数,需要判断它是否输入的是两个数,然后是三个递归条件,这里就需要明白v2,v3,v4的含义了,接着看看func4的反汇编结果
然后再看phase_4的前面的这两行
80496d9: mov $0x804a240,%ebx
80496de: mov $0x24,%edx //edx = 0x24(36)
这两行的意思是说,先开辟了一段空间,然后放一个数组进去,长度是0x24也就是36.
然后执行到下面这一行发现,ecx
的值是从36一直递减到0的
80496e9: rep movsl %ds:(%esi),%es:(%edi)
再往后会发现有两个比较,0和34,同时还有判断是否不等于456
8049784: cmp $0x1c8,%eax//eax != 456 ?
8049789: jne 8049797 <phase_4+0xd0>//不等于则跳转
这个先搁置吧,在网上也没找到和我这个一样的, 其他很多都是有比较的指令,感觉比我碰到的这个都要简单些。
阶段 5:指针
首先发现push指令处有一个地址
80497f3: push $0x804a20f
查看这个位置的内容,发现需要输入的是两个数
(gdb) x/1s 0x804a20f
0x804a20f: "%d %d"
再往下看,发现这是一个循环结构
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的操作
8049831: addl $0x1,-0xc(%ebp) //
然后下面这几句的就是对v3赋值的过程
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==6
和v5==v2
,从循环处可以发现v6每一次进入循环都会有自增的操作,而v5也是在每一次的循环过程中去进行加操作的,所以拿到v5的值就相当于知道了v2的值了,下面有两个cmp的操作,就是在进行比较
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
(gdb) x $ebp-16
0xbffff5a8: 0x00000030
当然,我这种方式是运气好一开始就使用0作为第一个参数来测试的,如果使用的其他值就需要一步一步推出来其他的值,因为其中有一个数组,那就可以输入小于15的值来确定每一个位置的值是多少,我这里得到了数组中所有的值
索引 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
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函数,就是对字符串进行分解
下面看看第一个循环
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,同时各个值不相等。
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();
}
下面又是循环结构,不过有点复杂,懒得一行一行去分析了,直接到最后一个循环结构,发现前面这段代码
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编译后发现是下面两句,应该是为了后面的循环中作为判断条件的
v7[2] = 0;
v7 = v4;
接着看循环体内部,发现有一个cmp
指令,下面的jle
的作用是小于或等于,这里比较的内容其实就是上面两句中的参数。
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
处,查看其中的内容如下:
(gdb) x/4w $eax
0x804c130 <node1>: 0x00000009 0x00000001 0x00000000 0x000003e9
发现有个node1的标识,其实这就是一个链表,同样可以推断出其他node的信息
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。
所以这个结构体为:
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