Bomb Lab (计算机系统基础实验)

最近看了计算机系统基础实验这门课程,做了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

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

反汇编代码

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

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

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

    less bomb.s
    
  3. 在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指令的测试条件应被满足,即0x804946dtest指令执行之前,寄存器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)(第一个压栈的参数)

在这里插入图片描述

  • 变量及其存储地址

    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是我们输入的结果

通过断点调试

(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指令将我们输入的第二个数赋给了eaxcmp指令对%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"

接着往下,输入参数之后就会去调用func4func4就是一个递归函数,看了半天也没搞命名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==6v5==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

  • 2
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值