深入理解计算机系统——LAB-3_BombLab

深入理解计算机系统——LAB-3_Bomb_Lab

简介

二进制炸弹实验。

二进制炸弹是一个作为可执行目标代码文件提供给学生们的程序。

运行时,它提示用户输入6个不同的字符串

如果其中的任何一个不正确,炸弹就会“爆炸”,打印一条错误信息,并且在一个分级服务器记录事件日志。

学生们必须通过对程序反汇编和逆向工程来测定应该是哪6个字符串,从而拆除他们各自炸弹的雷管。

该实验教会学生理解汇编语言,并且强制他们学习怎样使用调试器

实验环境和使用工具

  • 主机为Windows10

  • 运行炸弹的虚拟机Ubuntu12.04.5

  • gdb7.4版本

  • objdump2.22版本

  • VS code编辑器,下载了x86 and x86_64 Assembly插件,提供汇编代码高亮功能

实验过程

准备工作,研究bomb.c源代码

一共有两个文件:bomb.cbomb

bomb.c编译得到bomb文件,所以我先研究一下bomb.c的代码

  • 代码如下(随便看看):
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
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
/***************************************************************************
* 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.
***************************************************************************/

#include <stdio.h>
#include <stdlib.h>
#include "support.h"
#include "phases.h"

/*
* Note to self: Remember to erase this file so my victims will have no
* idea what is going on, and so they will all blow up in a
* spectaculary fiendish explosion. -- Dr. Evil
*/

FILE *infile;

int main(int argc, char *argv[])
{
char *input;

/* Note to self: remember to port this bomb to Windows and put a
* fantastic GUI on it. */

/* When run with no arguments, the bomb reads its input lines
* from standard input. */
if (argc == 1) {
infile = stdin;
}

/* When run with one argument <file>, the bomb reads from <file>
* until EOF, and then switches to standard input. Thus, as you
* defuse each phase, you can add its defusing string to <file> and
* avoid having to retype it. */
else if (argc == 2) {
if (!(infile = fopen(argv[1], "r"))) {
printf("%s: Error: Couldn't open %s\n", argv[0], argv[1]);
exit(8);
}
}

/* You can't call the bomb with more than 1 command line argument. */
else {
printf("Usage: %s [<input_file>]\n", argv[0]);
exit(8);
}

/* Do all sorts of secret stuff that makes the bomb harder to defuse. */
initialize_bomb();

printf("Welcome to my fiendish little bomb. You have 6 phases with\n");
printf("which to blow yourself up. Have a nice day!\n");

/* Hmm... Six phases must be more secure than one phase! */
input = read_line(); /* Get input */
phase_1(input); /* Run the phase */
phase_defused(); /* Drat! They figured it out!
* Let me know how they did it. */
printf("Phase 1 defused. How about the next one?\n");

/* The second phase is harder. No one will ever figure out
* how to defuse this... */
input = read_line();
phase_2(input);
phase_defused();
printf("That's number 2. Keep going!\n");

/* I guess this is too easy so far. Some more complex code will
* confuse people. */
input = read_line();
phase_3(input);
phase_defused();
printf("Halfway there!\n");

/* Oh yeah? Well, how good is your math? Try on this saucy problem! */
input = read_line();
phase_4(input);
phase_defused();
printf("So you got that one. Try this one.\n");

/* Round and 'round in memory we go, where we stop, the bomb blows! */
input = read_line();
phase_5(input);
phase_defused();
printf("Good work! On to the next...\n");

/* This phase will never be used, since no one will get past the
* earlier ones. But just in case, make this one extra hard. */
input = read_line();
phase_6(input);
phase_defused();

/* Wow, they got it! But isn't something... missing? Perhaps
* something they overlooked? Mua ha ha ha ha! */

return 0;
}

  • 首先,我们看到一个有趣的license,(Google翻译)意思是

    Evil Incorporated博士(PERPETRATOR)特此授予您(VICTIM)使用该炸弹(BOMB)的明确许可。

    这是一个有时间限制的许可证,在VICTIM死亡时到期。

    PERPETRATOR对损坏,挫败,精神错乱,虫眼,腕隧道综合症,睡眠不足或对VICTIM造成的其他伤害不承担任何责任。 除非PERPETRATOR想要获得信誉,否则就是这样。

    VICTIM不得将此炸弹源代码分发给PERPETRATOR的任何敌人。 //重点,问问别人

    VICTIM不得调试,反向工程,在其上运行“字符串”,反编译,解密或使用任何其他技术来了解和拆除BOMB。 处理此程序时,不能穿防弹衣。 //重点,使用上述所提到的技术拆除炸弹,穿防弹衣

    PERPETRATOR不会因PERPETRATOR糟糕的幽默感而道歉。 //哈哈(强颜欢笑)

    在法律禁止BOMB的情况下,此许可无效。

  • ,四个头文件,波浪线的两个我们没有,很好,过!
    image-20210428143806306

  • 注释意思是

    提醒自己:记得把这个文件删掉,这样我的受害者就不会知道发生了什么事,这样他们就会在一场可怕的恐怖爆炸中全部引爆。——邪恶博士 //重点,看这个文件,我们就知道炸弹怎么做的了

  • FILE *infile;全局变量,文件指针

  • int main(int argc, char *argv[])主函数,参数argc是提供给主函数的参数个数,参数argv[]是参数的字符串数组的指针。

    举例:./bomb answer.txt 此时,argc=2,argv[0]=”./bomb”,argv[1]=”answer.txt”

  • char *input;输入字符串的地址

  • 注释环节

    提醒自己:记得将炸弹移植到Windows并在上面放上精美的GUI。 //老师拿来考试的

  • 后面的太多了,又看注释又看代码太累,我直接写意思,用法:

    1. ./bomb,然后自己一个一个输入
    2. ./bomb xxx.txt,不用输入,直接看炸没炸

    显然我们选第二种,一个一个答案存起来。

  • initialize_bomb();做炸弹函数,我们显然没有这个函数所需要的头文件。

  • 输出文件开始执行的提示信息

  • 接下来是6个phasephase流程:

    • input=read_line();没有函数所需的头文件,功能就是输入字符串或者数字,也就是phase的答案
    • phase_x(input);将输入作为参数运行第x个phase函数
    • phase_defused();看答案是否匹配。
    • 输出成功的提示信息
  • 至此,bomb.c已经看完了,我们已经知道炸弹是怎么做的了!

  • 接下来,我们需要反汇编bomb文件,看看这些phase函数都是干什么的!

反汇编bomb文件

  • Ubuntu虚拟机中,实验文件夹下,我们执行命令
1
objdump -d bomb > bomb.asm
  • 然后将bomb.asm移动到本机,用vscode打开,有1716行汇编代码
    image-20210428154937224
  • objdump反汇编得到的文件,方便我们全局查找,gdb用来调试

分析汇编代码

我们将代码拆成一个函数一个函数进行分析。

  • 在文件夹下输入命令行使用gdb反汇编函数

    1
    2
    gdb bomb
    gdb> disassemble <函数名>

read_line

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
Dump of assembler code for function read_line:
0x0804911d <+0>: sub $0x2c,%esp //栈帧预留0x2c字节的空间
0x08049120 <+3>: mov %ebx,0x20(%esp) //M(0x20+esp)=ebx
0x08049124 <+7>: mov %esi,0x24(%esp) //M(0x24+esp)=esi
0x08049128 <+11>: mov %edi,0x28(%esp) //M(0x28+esp)=edi
0x0804912c <+15>: call 0x80490b0 <skip> //调用skip()
0x08049131 <+20>: test %eax,%eax //eax & eax
0x08049133 <+22>: jne 0x80491a1 <read_line+132> //if zf=0, jump 0x80491a1 <read_line+132>
0x08049135 <+24>: mov 0x804c3a4,%eax //eax=M(0x804c3a4)
//0x804c3a4 <stdin@@GLIBC_2.0>: ""
0x0804913a <+29>: cmp %eax,0x804c3d0 //compare M(0x804c3d0) with eax
//0x804c3d0 <infile>: ""
0x08049140 <+35>: jne 0x804915a <read_line+61> //if zf=0, jump 0x804915a <read_line+61>
0x08049142 <+37>: movl $0x804a383,(%esp) //M(esp)=0x804a383
//0x804a383: "Error: Premature EOF on stdin"
0x08049149 <+44>: call 0x8048800 <puts@plt> //puts(0x804a383)
0x0804914e <+49>: movl $0x8,(%esp) //M(esp)=0x8
0x08049155 <+56>: call 0x8048840 <exit@plt> //exit(0x8)
0x0804915a <+61>: movl $0x804a3a1,(%esp) //M(esp)=0x804a3a1
//0x804a3a1: "GRADE_BOMB"
0x08049161 <+68>: call 0x80487f0 <getenv@plt> //getenv@plt(0x804a3a1)
0x08049166 <+73>: test %eax,%eax //eax&eax
0x08049168 <+75>: je 0x8049176 <read_line+89> //if zf=1, jump 0x8049176 <read_line+89>
0x0804916a <+77>: movl $0x0,(%esp) //M(esp)=0x0
0x08049171 <+84>: call 0x8048840 <exit@plt> //exit(0x0)
0x08049176 <+89>: mov 0x804c3a4,%eax //eax=M(0x804c3a4)
//0x804c3a4 <stdin@@GLIBC_2.0>: ""
0x0804917b <+94>: mov %eax,0x804c3d0 //M(0x804c3d0)=eax
//0x804c3d0 <infile>: ""
0x08049180 <+99>: call 0x80490b0 <skip> //调用skip()
0x08049185 <+104>: test %eax,%eax //eax & eax
0x08049187 <+106>: jne 0x80491a1 <read_line+132> //if zf=0, jump 0x80491a1 <read_line+132>
0x08049189 <+108>: movl $0x804a383,(%esp) //M(esp)=0x804a383
//0x804a383: "Error: Premature EOF on stdin"
0x08049190 <+115>: call 0x8048800 <puts@plt> //puts(0x804a383)
0x08049195 <+120>: movl $0x0,(%esp) //M(esp)=0x0
0x0804919c <+127>: call 0x8048840 <exit@plt> //exit(0x0)
0x080491a1 <+132>: mov 0x804c3cc,%edx //edx=M(0x804c3cc)
//0x804c3cc <num_input_strings>: 0x00 记录关卡数
0x080491a7 <+138>: lea (%edx,%edx,4),%ebx //ebx=edx+edx*4=5*edx
0x080491aa <+141>: shl $0x4,%ebx //ebx<<4
0x080491ad <+144>: add $0x804c3e0,%ebx //ebx=ebx+0x804c3e0
0x080491b3 <+150>: mov %ebx,%edi //edi=ebx
0x080491b5 <+152>: mov $0x0,%eax //eax=0x0
0x080491ba <+157>: mov $0xffffffff,%ecx //ecx=-1
0x080491bf <+162>: repnz scas %es:(%edi),%al //?
0x080491c1 <+164>: not %ecx //ecx=-ecx
0x080491c3 <+166>: sub $0x1,%ecx //ecx=ecx-1
0x080491c6 <+169>: cmp $0x4e,%ecx //compare ecx with 0x4e
0x080491c9 <+172>: jle 0x8049202 <read_line+229> //if ecx<=0x4e, jump 0x8049202 <read_line+229>
0x080491cb <+174>: movl $0x804a3ac,(%esp) //M(esp)=0x804a3ac
//0x804a3ac: "Error: Input line too long"
0x080491d2 <+181>: call 0x8048800 <puts@plt> //puts(0x804a3ac)
0x080491d7 <+186>: mov 0x804c3cc,%eax //eax=M(0x804c3cc)
//0x804c3cc <num_input_strings>: 0x00 记录关卡数
0x080491dc <+191>: lea 0x1(%eax),%edx //edx=0x1+eax
0x080491df <+194>: mov %edx,0x804c3cc //M(0x804c3cc)=edx
0x080491e5 <+200>: imul $0x50,%eax,%eax //eax=eax*0x50
0x080491e8 <+203>: add $0x804c3e0,%eax //eax=eax+0x804c3e0
0x080491ed <+208>: mov $0x804a3c7,%edx //edx=0x804a3c7
//0x804a3c7: "***truncated***"
0x080491f2 <+213>: mov $0x4,%ecx //ecx=0x4
0x080491f7 <+218>: mov %eax,%edi //edi=eax
0x080491f9 <+220>: mov %edx,%esi //esi=edx
0x080491fb <+222>: rep movsl %ds:(%esi),%es:(%edi) //?
0x080491fd <+224>: call 0x80490f6 <explode_bomb> //explode_bomb()
0x08049202 <+229>: lea (%edx,%edx,4),%eax //eax=5*edx
0x08049205 <+232>: shl $0x4,%eax //eax=eax<<4
0x08049208 <+235>: movb $0x0,0x804c3df(%ecx,%eax,1) //M(0x804c3df+ecx+eax)=0x0
0x08049210 <+243>: add $0x1,%edx //edx=edx+1
0x08049213 <+246>: mov %edx,0x804c3cc //M(0x804c3cc)=edx
0x08049219 <+252>: mov %ebx,%eax //eax=ebx
0x0804921b <+254>: mov 0x20(%esp),%ebx //ebx=M(0x20+esp)
0x0804921f <+258>: mov 0x24(%esp),%esi //esi=M(0x24+esp)
0x08049223 <+262>: mov 0x28(%esp),%edi //edi=M(0x28+esp)
0x08049227 <+266>: add $0x2c,%esp //栈帧释放空间
0x0804922a <+269>: ret //返回地址出栈,并跳转到返回地址
End of assembler dump.
  • 主函数中多次调用的函数,我们可以得知
    • 0x804c3cc地址存的是关卡数
    • 返回值是字符串地址,并且赋给了input
    • 0x804c4d0存的是第四关的字符串

phase_1

1
2
3
4
5
6
7
8
9
10
11
12
Dump of assembler code for function phase_1:          //函数phase_1(inpput)
0x08048b50 <+0>: sub $0x1c,%esp //在栈中预留0x1c字节的空间
0x08048b53 <+3>: movl $0x804a1c4,0x4(%esp) //M(0x4+esp)=0x804a1c4
0x08048b5b <+11>: mov 0x20(%esp),%eax //eax=M(0x20+esp)=input
0x08048b5f <+15>: mov %eax,(%esp) //M(esp)=eax=input
0x08048b62 <+18>: call 0x8048fe4 <strings_not_equal> //调用strings_not_equal(input,0x804a1c4)函数
0x08048b67 <+23>: test %eax,%eax //eax保存函数返回值,执行eax&eax
0x08048b69 <+25>: je 0x8048b70 <phase_1+32> //if zf=1, jump 0x8048b70 <phase_1+32>
0x08048b6b <+27>: call 0x80490f6 <explode_bomb> //调用 explode_bomb函数
0x08048b70 <+32>: add $0x1c,%esp //释放空间
0x08048b73 <+35>: ret //返回地址出栈,并跳转到返回地址
End of assembler dump.
  • 为了搞清楚这个过程,我们需要进行gdb调试,下面是一些gdb命令(列出来的我都用了)

    1
    2
    3
    4
    5
    6
    7
    gdb> break *<<函数名>+<偏移值>>                             //设置断点
    gdb> run //运行
    gdb> step //下一步
    gdb> info registers <寄存器> //列出寄存器的值,寄存器可选
    gdb> set var <寄存器或者变量>=<0x111> //改变寄存器的值
    gdb> set *<内存地址>=<0x11> //改变内存的值
    gdb> x/nfu <内存地址> //查看内存地址的值,examine简写为x,n长度,f显示格式,u一个地址单元的长度
  • 所以phase_1的过程就是:将input0x804a1c4的字符串比较,如果想

  • 执行命令,查看0x804a1c4地址的字符串
    image-20210428220435495

  • 运行,输入与0x804a1c4地址的字符串相等的input字符串,提示炸弹已经被拆,下一关!
    image-20210428220713564

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
27
Dump of assembler code for function phase_2:      //函数phase_2(input)
0x08048b74 <+0>: push %esi //将程序的入口地址压入栈中
0x08048b75 <+1>: push %ebx //将被调用者保存寄存器中的值压入栈中,以便在返回前可以恢复它们
0x08048b76 <+2>: sub $0x34,%esp //栈帧预留0x34字节空间
0x08048b79 <+5>: lea 0x18(%esp),%eax //eax=0x18+esp=a[]
0x08048b7d <+9>: mov %eax,0x4(%esp) //M(0x4+esp)=eax=0x18+esp=a[]
0x08048b81 <+13>: mov 0x40(%esp),%eax //eax=M(0x40+esp)=input
0x08048b85 <+17>: mov %eax,(%esp) //M(esp)=eax=input
0x08048b88 <+20>: call 0x804922b <read_six_numbers> //调用read_six_numbers(input,a[]=0x18+esp)
0x08048b8d <+25>: cmpl $0x1,0x18(%esp) //compare M(0x18+esp) with 0x1
0x08048b92 <+30>: je 0x8048b99 <phase_2+37> //if zf=1, jump 0x8048b99 <phase_2+37>
0x08048b94 <+32>: call 0x80490f6 <explode_bomb> //调用explode_bomb()
0x08048b99 <+37>: lea 0x1c(%esp),%ebx //ebx=0x1c+esp=&a[1]
0x08048b9d <+41>: lea 0x30(%esp),%esi //esi=0x30+esp=&a[5]
0x08048ba1 <+45>: mov -0x4(%ebx),%eax //eax=M(-0x4+ebx)
0x08048ba4 <+48>: add %eax,%eax //eax=eax+eax=2*M(-0x4+ebx)
0x08048ba6 <+50>: cmp %eax,(%ebx) //compare M(ebx) with eax=2*M(-0x4+ebx)
0x08048ba8 <+52>: je 0x8048baf <phase_2+59> //if zf=1, jump 0x8048baf <phase_2+59>
0x08048baa <+54>: call 0x80490f6 <explode_bomb> //调用<explode_bomb>函数
0x08048baf <+59>: add $0x4,%ebx //ebx=ebx+4
0x08048bb2 <+62>: cmp %esi,%ebx //compare ebx with esi=0x30+esp=&a[5]
0x08048bb4 <+64>: jne 0x8048ba1 <phase_2+45> //if zf=0, jump 0x8048ba1 <phase_2+45>
0x08048bb6 <+66>: add $0x34,%esp //释放栈帧空间
0x08048bb9 <+69>: pop %ebx //恢复ebx原先的数据
0x08048bba <+70>: pop %esi //弹出程序的入口地址
0x08048bbb <+71>: ret //返回地址出栈,并跳转到返回地址
End of assembler dump.
  • 分析可得phase_2的过程是:输入6个数并将其地址保存在栈中,第一个数a[0]要等于1,否则爆炸;从第二个数a[1]开始,每个数都必须等于它前一个数的2倍,否则爆炸。
  • 所以我们得到的六个数字是:1 2 4 8 16 32 64,测试,成功!
    image-20210430085749377

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
38
39
40
41
Dump of assembler code for function phase_3:         //函数phase_3(input)
0x08048bbc <+0>: sub $0x2c,%esp //栈帧预留0x2c字节的空间
0x08048bbf <+3>: lea 0x1c(%esp),%eax //eax=0x1c+esp
0x08048bc3 <+7>: mov %eax,0xc(%esp) //M(0xc+esp)=eax
0x08048bc7 <+11>: lea 0x18(%esp),%eax //eax=0x18+esp
0x08048bcb <+15>: mov %eax,0x8(%esp) //M(0x8+esp)=eax
0x08048bcf <+19>: movl $0x804a3e3,0x4(%esp) //M(0x4+esp)=0x804a3e3
0x08048bd7 <+27>: mov 0x30(%esp),%eax //eax=M(0x30+esp)=input
0x08048bdb <+31>: mov %eax,(%esp) //M(esp)=M(0x30+esp)=input
0x08048bde <+34>: call 0x8048870 <__isoc99_sscanf@plt> //调用__isoc99_sscanf@plt(input,0x804a3e3,0x18+esp,0x1c+esp)
0x08048be3 <+39>: cmp $0x1,%eax //compare eax with 0x1,eax返回值是参数个数
0x08048be6 <+42>: jg 0x8048bed <phase_3+49> //if eax>0x1, jump 0x8048bed <phase_3+49>
0x08048be8 <+44>: call 0x80490f6 <explode_bomb> //调用explode_bomb()
0x08048bed <+49>: cmpl $0x7,0x18(%esp) //compare M(0x18+esp) with 0x7
0x08048bf2 <+54>: ja 0x8048c30 <phase_3+116> //if M(0x18+esp)>0x7, jump 0x8048c30<phase_3+116>
0x08048bf4 <+56>: mov 0x18(%esp),%eax //eax=M(0x18+esp)
0x08048bf8 <+60>: jmp *0x804a220(,%eax,4) //jump M(0x804a220+4*eax)
0x08048bff <+67>: mov $0x32d,%eax //eax=0x32d
0x08048c04 <+72>: jmp 0x8048c41 <phase_3+133> //jump 0x8048c41 <phase_3+133>
0x08048c06 <+74>: mov $0x36c,%eax //eax=0x36c
0x08048c0b <+79>: jmp 0x8048c41 <phase_3+133> //jump 0x8048c41 <phase_3+133>
0x08048c0d <+81>: mov $0x3db,%eax //eax=0x3db
0x08048c12 <+86>: jmp 0x8048c41 <phase_3+133> //jump 0x8048c41 <phase_3+133>
0x08048c14 <+88>: mov $0x16d,%eax //eax=0x16d
0x08048c19 <+93>: jmp 0x8048c41 <phase_3+133> //jump 0x8048c41 <phase_3+133>
0x08048c1b <+95>: mov $0x3d1,%eax //eax=0x3d1
0x08048c20 <+100>: jmp 0x8048c41 <phase_3+133> //jump 0x8048c41 <phase_3+133>
0x08048c22 <+102>: mov $0x3cb,%eax //eax=0x3cb
0x08048c27 <+107>: jmp 0x8048c41 <phase_3+133> //jump 0x8048c41 <phase_3+133>
0x08048c29 <+109>: mov $0x21c,%eax //eax=0x21c
0x08048c2e <+114>: jmp 0x8048c41 <phase_3+133> //jump 0x8048c41 <phase_3+133>
0x08048c30 <+116>: call 0x80490f6 <explode_bomb> //调用explode_bomb()
0x08048c35 <+121>: mov $0x0,%eax //eax=0x0
0x08048c3a <+126>: jmp 0x8048c41 <phase_3+133> //jump 0x8048c41 <phase_3+133>
0x08048c3c <+128>: mov $0x283,%eax //eax=0x283
0x08048c41 <+133>: cmp 0x1c(%esp),%eax //compare eax with M(0x1c+esp)
0x08048c45 <+137>: je 0x8048c4c <phase_3+144> //if zf=1, jump 0x8048c4c <phase_3+144>
0x08048c47 <+139>: call 0x80490f6 <explode_bomb> //调用explode_bomb()
0x08048c4c <+144>: add $0x2c,%esp //释放栈帧空间
0x08048c4f <+147>: ret //返回地址出栈,并跳转到返回地址
End of assembler dump.
  • __isoc99_sscanf@plt的第二个参数是0x804a3e3,以字符串的形式查看内存,所以我们知道后两个参数地址存的都是整数。
    image-20210430191833050
  • 0x08048bed <+49>: cmpl $0x7,0x18(%esp)可知,第一个整数范围是0-7,不能是负数因为ja是无符号比较,出范围就爆炸
  • 0x08048bf8 <+60>: jmp *0x804a220(,%eax,4)可知,根据第一个整数,决定跳转到什么位置
    • 查看这8个不同跳转位置
      image-20210430200908533
    • 因此我们得到了八个不同的输入组合:
      0 8131 6432 8763 9874 3655 9776 9717 540
    • 经验证,这几个都是符合的答案
  • 我们选0 813输入,输出成功拆除的提示语,第三关结束。
    image-20210430204301058

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
24
25
26
27
28
29
30
31
Dump of assembler code for function phase_4:               //函数phase(input)
0x08048cb9 <+0>: sub $0x2c,%esp //栈帧预留0x2c字节的空间
0x08048cbc <+3>: lea 0x1c(%esp),%eax //eax=0x1c+esp
0x08048cc0 <+7>: mov %eax,0xc(%esp) //M(0xc+esp)=eax
0x08048cc4 <+11>: lea 0x18(%esp),%eax //eax=0x18+esp
0x08048cc8 <+15>: mov %eax,0x8(%esp) //M(0x8+esp)=eax
0x08048ccc <+19>: movl $0x804a3e3,0x4(%esp) //M(0x4+esp)=0x804a3e3
0x08048cd4 <+27>: mov 0x30(%esp),%eax //eax=M(0x30+esp)=input
0x08048cd8 <+31>: mov %eax,(%esp) //M(esp)=input
0x08048cdb <+34>: call 0x8048870 <__isoc99_sscanf@plt> //调用__isoc99_sscanf@plt(input,0x804a3e3,0x18+esp,0x1c+esp)
0x08048ce0 <+39>: cmp $0x2,%eax //compare eax with 0x2
0x08048ce3 <+42>: jne 0x8048cf2 <phase_4+57> //if zf=0, jump 0x8048cf2 <phase_4+57>
0x08048ce5 <+44>: mov 0x18(%esp),%eax //eax=M(0x18+esp)
0x08048ce9 <+48>: test %eax,%eax //eax & eax
0x08048ceb <+50>: js 0x8048cf2 <phase_4+57> //if sf=1, jump 0x8048cf2 <phase_4+57>
0x08048ced <+52>: cmp $0xe,%eax //compare eax with 0xe
0x08048cf0 <+55>: jle 0x8048cf7 <phase_4+62> //if eax<=0xe, jump 0x8048cf7 <phase_4+62>
0x08048cf2 <+57>: call 0x80490f6 <explode_bomb> //调用explode_bomb()
0x08048cf7 <+62>: movl $0xe,0x8(%esp) //M(0x8+esp)=0xe
0x08048cff <+70>: movl $0x0,0x4(%esp) //M(0x4+esp)=0x0
0x08048d07 <+78>: mov 0x18(%esp),%eax //eax=M(0x18+esp)
0x08048d0b <+82>: mov %eax,(%esp) //M(esp)=eax
0x08048d0e <+85>: call 0x8048c50 <func4> //调用fun4(M(0x18+esp), 0x0, 0xe)
0x08048d13 <+90>: cmp $0x15,%eax //compare eax with 0x15
0x08048d16 <+93>: jne 0x8048d1f <phase_4+102> //if zf=0, jump 0x8048d1f <phase_4+102>
0x08048d18 <+95>: cmpl $0x15,0x1c(%esp) //compare M(0x1c+esp) with 0x15
0x08048d1d <+100>: je 0x8048d24 <phase_4+107> //if zf=1, jump 0x8048d24 <phase_4+107>
0x08048d1f <+102>: call 0x80490f6 <explode_bomb> //调用explode_bomb()
0x08048d24 <+107>: add $0x2c,%esp //栈帧释放空间
0x08048d27 <+110>: ret //返回地址出栈,并跳转到返回地址
End of assembler dump.
  • 查看scanf的参数,在0x804a3e3以字符串的形式输出,我们看到是两个整数

    image-20210430230148066

  • 0x08048d18 <+95>: cmpl $0x15,0x1c(%esp)
    我们分析M(0x1c+%esp)要等于0x15

  • 0x08048d13 <+90>: cmp $0x15,%eax
    我们需要知道M(0x18+%esp)到底等于多少,才能作为参数让fun4(M(0x18+esp), 0x0, 0xe)的返回值等于0x15

func4
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
Dump of assembler code for function func4:                  //函数fun4(x,second,third)
0x08048c50 <+0>: sub $0x1c,%esp //栈帧预留0x1c字节的空间
0x08048c53 <+3>: mov %ebx,0x14(%esp) //M(0x14+esp)=ebx
0x08048c57 <+7>: mov %esi,0x18(%esp) //M(0x18+esp)=esi
0x08048c5b <+11>: mov 0x20(%esp),%eax //eax=M(0x20+esp)=x
0x08048c5f <+15>: mov 0x24(%esp),%edx //edx=M(0x24+esp)=second
0x08048c63 <+19>: mov 0x28(%esp),%esi //esi=M(0x28+esp)=third
0x08048c67 <+23>: mov %esi,%ecx //ecx=esi=third
0x08048c69 <+25>: sub %edx,%ecx //ecx=ecx-edx=third-second
0x08048c6b <+27>: mov %ecx,%ebx //ebx=ecx=third-second
0x08048c6d <+29>: shr $0x1f,%ebx //ebx逻辑右移31位,ebx=(third-second)<0 ? 1:0
0x08048c70 <+32>: add %ebx,%ecx //ecx=ebx+ecx=third-second+(third-second)<0 ? 1:0
0x08048c72 <+34>: sar %ecx //ecx默认算术右移1位,ecx=ecx/2
0x08048c74 <+36>: lea (%ecx,%edx,1),%ebx //ebx=ecx+edx=ecx+second=average
0x08048c77 <+39>: cmp %eax,%ebx //compare ebx=average with eax=x
0x08048c79 <+41>: jle 0x8048c92 <func4+66> //if ebx=average<=eax=x, jump 0x8048c92 <func4+66>
0x08048c7b <+43>: lea -0x1(%ebx),%ecx //ecx=-0x1+ebx=average-1
0x08048c7e <+46>: mov %ecx,0x8(%esp) //M(0x8+esp)=ecx=average-1
0x08048c82 <+50>: mov %edx,0x4(%esp) //M(0x4+esp)=edx=second
0x08048c86 <+54>: mov %eax,(%esp) //M(esp)=eax=x
0x08048c89 <+57>: call 0x8048c50 <func4> //调用fun4(x,second,average-1)
0x08048c8e <+62>: add %eax,%ebx //ebx=ebx+eax=average+fun4(x,second,average-1)
0x08048c90 <+64>: jmp 0x8048cab <func4+91> //jump 0x8048cab <func4+91>
0x08048c92 <+66>: cmp %eax,%ebx //compare ebx=average with eax=x
0x08048c94 <+68>: jge 0x8048cab <func4+91> //if ebx=average>=eax=x, jump 0x8048cab <func4+91>
0x08048c96 <+70>: mov %esi,0x8(%esp) //M(0x8+esp)=esi=third
0x08048c9a <+74>: lea 0x1(%ebx),%edx //edx=0x1+ebx=average+1
0x08048c9d <+77>: mov %edx,0x4(%esp) //M(0x4+esp)=edx=average+1
0x08048ca1 <+81>: mov %eax,(%esp) //M(esp)=eax=x
0x08048ca4 <+84>: call 0x8048c50 <func4> //调用fun4(x,average+1,third)
0x08048ca9 <+89>: add %eax,%ebx //ebx=ebx+eax=average+fun4(x,average+1,third)
0x08048cab <+91>: mov %ebx,%eax //eax=ebx
0x08048cad <+93>: mov 0x14(%esp),%ebx //ebx=M(0x14+esp)
0x08048cb1 <+97>: mov 0x18(%esp),%esi //esi=M(0x18+esp)
0x08048cb5 <+101>: add $0x1c,%esp //栈帧释放空间
0x08048cb8 <+104>: ret //返回地址出栈,并跳转到返回地址
End of assembler dump.
  • 我们将fun4函数翻译成C语言代码,主函数遍历func4第一个参数x,如果返回值等于0x15也就是21,那就是正确答案
func4转换成C语言:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include <iostream>
using namespace std;

int func4(int x, int second, int third) {
int average = second + (third - second + ((third - second) < 0 ? 1 : 0)) / 2;
if (x < average) {
return average + func4(x, second, average - 1);
} else if (x > average) {
return average + func4(x, average + 1, third);
} else
return average;
}

int main() {
for (int i = 0; i <= 15; i++) {
if (func4(i, 0, 15) == 21)
cout << i << " ";
}
return 0;
}
  • 输出结果如下:
    image-20210501104004856
  • 所以第四关答案为:6 21,输入验证,输出过关提示语
    image-20210501104406772

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
Dump of assembler code for function phase_5:                     //函数phase_5(input)
0x08048d28 <+0>: sub $0x2c,%esp //栈帧预留0x2c字节的空间
0x08048d2b <+3>: lea 0x1c(%esp),%eax //eax=0x1c+esp
0x08048d2f <+7>: mov %eax,0xc(%esp) //M(0xc+esp)=eax=0x1c+esp
0x08048d33 <+11>: lea 0x18(%esp),%eax //eax=0x18+esp
0x08048d37 <+15>: mov %eax,0x8(%esp) //M(0x8+esp)=eax=0x18+esp
0x08048d3b <+19>: movl $0x804a3e3,0x4(%esp) //M(0x4+esp)=0x804a3e3
0x08048d43 <+27>: mov 0x30(%esp),%eax //eax=M(0x30+esp)=input
0x08048d47 <+31>: mov %eax,(%esp) //M(esp)=eax=input
0x08048d4a <+34>: call 0x8048870 <__isoc99_sscanf@plt> //调用__isoc99_sscanf@plt(input,0x804a3e3,0x18+esp,0x1c+esp)
0x08048d4f <+39>: cmp $0x1,%eax //compare eax with 0x1
0x08048d52 <+42>: jg 0x8048d59 <phase_5+49> //if eax>0x1, jump 0x8048d59 <phase_5+49>
0x08048d54 <+44>: call 0x80490f6 <explode_bomb> //调用explode_bomb()
0x08048d59 <+49>: mov 0x18(%esp),%eax //eax=M(0x18+esp)
0x08048d5d <+53>: and $0xf,%eax //eax=eax & 0xf
0x08048d60 <+56>: mov %eax,0x18(%esp) //M(0x18+esp)=eax
0x08048d64 <+60>: cmp $0xf,%eax //compare eax with 0xf
0x08048d67 <+63>: je 0x8048d93 <phase_5+107> //if zf=1, jump 0x8048d93 <phase_5+107>
0x08048d69 <+65>: mov $0x0,%ecx //ecx=0x0
0x08048d6e <+70>: mov $0x0,%edx //edx=0x0
0x08048d73 <+75>: add $0x1,%edx //edx=edx+1
0x08048d76 <+78>: mov 0x804a240(,%eax,4),%eax //eax=M(0x804a240+4*eax)=array[eax]
0x08048d7d <+85>: add %eax,%ecx //ecx=ecx+eax
0x08048d7f <+87>: cmp $0xf,%eax //compare eax with 0xf
0x08048d82 <+90>: jne 0x8048d73 <phase_5+75> //if zf=0, jump 0x8048d73 <phase_5+75>
0x08048d84 <+92>: mov %eax,0x18(%esp) //M(0x18+esp)=eax
0x08048d88 <+96>: cmp $0xf,%edx //compare edx with 0xf
0x08048d8b <+99>: jne 0x8048d93 <phase_5+107> //if zf=0, jump 0x8048d93 <phase_5+107>
0x08048d8d <+101>: cmp 0x1c(%esp),%ecx //compare ecx with M(0x1c+esp)
0x08048d91 <+105>: je 0x8048d98 <phase_5+112> //if zf=1, jump 0x8048d98 <phase_5+112>
0x08048d93 <+107>: call 0x80490f6 <explode_bomb> //调用explode_bomb()
0x08048d98 <+112>: add $0x2c,%esp //栈帧释放空间
0x08048d9b <+115>: ret //返回地址出栈,且跳转到返回地址
End of assembler dump.
  • 还是先看输入,还是两个整数
    image-20210501104814076

  • 过程:保存求和结果的ecx和发挥计数作用的edx都初始化为0
    edx加1,输入的M(0x18+esp)作为下标将array[M(0x18+esp)]存到eaxecx加上eax也就是这个元素值array[M(0x18+esp)]
    edx加1,将eax作为数组元素的下标将元素array[eax]存到eaxecx加上这个元素值eax
    。。。这个过程持续到eax等于0xf就是当下标等于0xf时结束,同时edx等于0xf也就是这个过程刚好进行15次。

    • 我们查看这个数组
      image-20210501113814480
    • 发现0xf元素的下标为6=0x6
      0x6元素的下标为14=0xe
      0xe元素的下标为2=0x2
      0x2元素的下表为1=0x1。。。。继续往下。。。。
    • 我们得到的序列为:f 6 e 2 1 a 0 8 4 9 d b 7 3 c 5
    • 所以第一个输入是5
  • 第二个输入等于%ecx,因为只进行15次加法,所以最后和少了array[15]=5,所以是结果是115

  • 所以答案是5 115,验证,成功。
    image-20210501120859183

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
Dump of assembler code for function phase_6:               //phase_6(input)
0x08048d9c <+0>: push %esi //esi入栈
0x08048d9d <+1>: push %ebx //ebx入栈
0x08048d9e <+2>: sub $0x44,%esp //栈帧预留0x44字节的空间
0x08048da1 <+5>: lea 0x10(%esp),%eax //eax=0x10+esp
0x08048da5 <+9>: mov %eax,0x4(%esp) //M(0x4+esp)=eax=0x10+esp
0x08048da9 <+13>: mov 0x50(%esp),%eax //eax=M(0x50+esp)=input
0x08048dad <+17>: mov %eax,(%esp) //M(esp)=eax=input
0x08048db0 <+20>: call 0x804922b <read_six_numbers>//调用read_six_numbers(input,0x10+esp)
0x08048db5 <+25>: mov $0x0,%esi //esi=0x0
0x08048dba <+30>: mov 0x10(%esp,%esi,4),%eax //eax=M(0x10+esp+4*esi)=array[esi]
0x08048dbe <+34>: sub $0x1,%eax //eax=eax-1=array[esi]-1
0x08048dc1 <+37>: cmp $0x5,%eax //compare eax=array[esi]-1 with 0x5
0x08048dc4 <+40>: jbe 0x8048dcb <phase_6+47> //if 0<=eax<=5, jump 0x8048dcb <phase_6+47>
0x08048dc6 <+42>: call 0x80490f6 <explode_bomb> //调用explode_bomb()
0x08048dcb <+47>: add $0x1,%esi //esi=esi+1
0x08048dce <+50>: cmp $0x6,%esi //compare esi with 0x6
0x08048dd1 <+53>: je 0x8048dee <phase_6+82> //if zf=1, jump 0x8048dee <phase_6+82>
0x08048dd3 <+55>: mov %esi,%ebx //ebx=esi
0x08048dd5 <+57>: mov 0x10(%esp,%ebx,4),%eax //eax=M(0x10+esp+ebx*4)=array[ebx]
0x08048dd9 <+61>: cmp %eax,0xc(%esp,%esi,4) //compare M(0xc+esp+4*esi)=array[esi-1] with eax
0x08048ddd <+65>: jne 0x8048de4 <phase_6+72> //if zf=0, jump 0x8048de4 <phase_6+72>
0x08048ddf <+67>: call 0x80490f6 <explode_bomb> //调用explode_bomb()
0x08048de4 <+72>: add $0x1,%ebx //ebx=ebx+1
0x08048de7 <+75>: cmp $0x5,%ebx //compare ebx with 0x5
0x08048dea <+78>: jle 0x8048dd5 <phase_6+57> //if ebx<=5,jump 0x8048dd5 <phase_6+57>
0x08048dec <+80>: jmp 0x8048dba <phase_6+30> //jump 0x8048dba <phase_6+30>
/*输入六个数到array[x],每一个数都进行限制,必须与它之后数字不同,而且必须处于范围1-6 */
0x08048dee <+82>: lea 0x10(%esp),%eax //eax=0x10+esp
0x08048df2 <+86>: lea 0x28(%esp),%ebx //ebx=0x28+esp=0x10+esp+0x4*6
0x08048df6 <+90>: mov $0x7,%ecx //ecx=0x7
0x08048dfb <+95>: mov %ecx,%edx //edx=ecx=0x7
0x08048dfd <+97>: sub (%eax),%edx //edx=edx-M(eax)=0x7-array[eax-(0x10+esp)]
0x08048dff <+99>: mov %edx,(%eax) //array[eax-(0x10+esp)]=M(eax)=edx
0x08048e01 <+101>: add $0x4,%eax //eax=eax+0x4
0x08048e04 <+104>: cmp %ebx,%eax //compare eax with ebx=0x10+esp+0x4*6
0x08048e06 <+106>: jne 0x8048dfb <phase_6+95> //if zf=0, jump 0x8048dfb <phase_6+95>
/*将这六个数组元素都分别被7减,所得结果还是存到他们原来的位置,也就是array[x]=7-array[x] */
0x08048e08 <+108>: mov $0x0,%ebx //ebx=0x0
0x08048e0d <+113>: jmp 0x8048e25 <phase_6+137> //jump 0x8048e25 <phase_6+137>
0x08048e0f <+115>: mov 0x8(%edx),%edx //edx=M(0x8+edx)
0x08048e12 <+118>: add $0x1,%eax //eax=eax+0x1
0x08048e15 <+121>: cmp %ecx,%eax //compare eax with ecx=array[ebx]
0x08048e17 <+123>: jne 0x8048e0f <phase_6+115> //if zf=0, jump 0x8048e0f <phase_6+115>
0x08048e19 <+125>: mov %edx,0x28(%esp,%esi,4) //array[6+esi]=M(0x28+esp+4*esi)=edx
0x08048e1d <+129>: add $0x1,%ebx //ebx=ebx+0x1
0x08048e20 <+132>: cmp $0x6,%ebx //compare ebx with 0x6
0x08048e23 <+135>: je 0x8048e3c <phase_6+160> //if zf=1, jump 0x8048e3c <phase_6+160>
0x08048e25 <+137>: mov %ebx,%esi //esi=ebx
0x08048e27 <+139>: mov 0x10(%esp,%ebx,4),%ecx //ecx=M(0x10+esp+4*ebx)=array[ebx]
0x08048e2b <+143>: mov $0x1,%eax //eax=0x1
0x08048e30 <+148>: mov $0x804c13c,%edx //edx=0x804c13c
0x08048e35 <+153>: cmp $0x1,%ecx //compare ecx=array[ebx] with 0x1
0x08048e38 <+156>: jg 0x8048e0f <phase_6+115> //if ecx>0x1, jump 0x8048e0f <phase_6+115>
0x08048e3a <+158>: jmp 0x8048e19 <phase_6+125> //jump 0x8048e19 <phase_6+125>
/* 0<=x<=5,初始化edx=0x804c13c,循环array[x]-1次edx=M(edx+0x8),最后edx存到array[x+6]中 */
0x08048e3c <+160>: mov 0x28(%esp),%ebx //ebx=M(0x28+esp)=array[6]
0x08048e40 <+164>: mov 0x2c(%esp),%eax //eax=M(0x2c+esp)=array[7]
0x08048e44 <+168>: mov %eax,0x8(%ebx) //M(0x8+array[6])=M(0x8+ebx)=eax=array[7]
0x08048e47 <+171>: mov 0x30(%esp),%edx //edx=M(0x30+esp)=array[8]
0x08048e4b <+175>: mov %edx,0x8(%eax) //M(0x8+array[7])=M(0x8+eax)=edx=array[8]
0x08048e4e <+178>: mov 0x34(%esp),%eax //eax=M(0x34+esp)=array[9]
0x08048e52 <+182>: mov %eax,0x8(%edx) //M(0x8+array[8])=M(0x8+edx)=eax=array[9]
0x08048e55 <+185>: mov 0x38(%esp),%edx //edx=M(0x38+esp)=array[10]
0x08048e59 <+189>: mov %edx,0x8(%eax) //M(0x8+array[9])=M(0x8+eax)=edx=array[10]
0x08048e5c <+192>: mov 0x3c(%esp),%eax //eax=M(0x3c+esp)=array[11]
0x08048e60 <+196>: mov %eax,0x8(%edx) //M(0x8+array[10])=M(0x8+edx)=eax=array[11]
0x08048e63 <+199>: movl $0x0,0x8(%eax) //M(0x8+array[11])=M(0x8+eax)=0x0
/* M(0x8+array[6+x])=array[6+x+1],也就是对链表重新进行排序*/
0x08048e6a <+206>: mov $0x5,%esi //esi=0x5
0x08048e6f <+211>: mov 0x8(%ebx),%eax //eax=M(0x8+ebx)
0x08048e72 <+214>: mov (%eax),%edx //edx=M(eax)
0x08048e74 <+216>: cmp %edx,(%ebx) //compare M(ebx) with edx
0x08048e76 <+218>: jge 0x8048e7d <phase_6+225> //if M(ebx)>=edx, jump 0x8048e7d <phase_6+225>
0x08048e78 <+220>: call 0x80490f6 <explode_bomb> //调用explode_bomb()
0x08048e7d <+225>: mov 0x8(%ebx),%ebx //ebx=M(0x8+ebx)
0x08048e80 <+228>: sub $0x1,%esi //esi=esi-1
0x08048e83 <+231>: jne 0x8048e6f <phase_6+211> //if zf=0, jump 0x8048e6f <phase_6+211>
/* 排序后的链表,前一个节点的值大于等于后一节点的值,*/
0x08048e85 <+233>: add $0x44,%esp //栈帧释放空间
0x08048e88 <+236>: pop %ebx //出栈ebx
0x08048e89 <+237>: pop %esi //出栈esi
0x08048e8a <+238>: ret //返回地址出栈,跳转到返回地址
End of assembler dump.
  • 打印查看0x804c13c地址所存,发现它是一个结构体,三个成员是:valueidnext
    依次查看各个节点。
    image-20210501183605731

  • 所以我们知道array[x+6]存的是id=array[x]的节点的地址;
    M(array[x+6])存的是id=array[x]的节点的value
    M(array[x+6]+8)存的是id=array[x]的节点的next;

  • 函数执行过程是:先输入array[x]并判断是否在[1,6]且各不相同;

    对这六个元素都执行array[x]=7-array[x]

    id=array[x]的节点地址存到array[x+6]

    id=array[x]的节点的地址的next成员改写成id=array[x+1]的节点的地址,也就是array[x+6]->next=array[x+6+1]

    判断排序后的链表节点的value成员大小是否是递减的。

  • 所以我们输入的数,其实是6个节点的id成员,函数按照输入id的顺序,将这六个节点排序,排序后的链表是递减的。

  • 将这六个节点的value转化为10进制的数

    id 1 2 3 4 5 6
    value 495 476 197 612 890 249
  • 正确的id序列:5 4 1 2 6 3

  • 但是因为执行过array[x]=7-array[x],所以正确的输入是:2 3 6 5 1 4

  • 验证,输出提示语
    image-20210502160922650

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
Dump of assembler code for function phase_defused:    //phase_defused()
0x0804927b <+0>: sub $0x8c,%esp //栈帧预留0x8c字节的空间
0x08049281 <+6>: mov %gs:0x14,%eax //eax=gs:0x14
0x08049287 <+12>: mov %eax,0x7c(%esp) //M(0x7c+esp)=eax
0x0804928b <+16>: xor %eax,%eax //eax=eax^eax
0x0804928d <+18>: cmpl $0x6,0x804c3cc //compare M(0x804c3cc) with 0x6
//0x804c3cc <num_input_strings>: 0x00 记录关卡数
0x08049294 <+25>: jne 0x8049308 <phase_defused+141>//if zf=0,jump 0x8049308 <phase_defused+141>
0x08049296 <+27>: lea 0x2c(%esp),%eax //eax=0x2c+esp
0x0804929a <+31>: mov %eax,0x10(%esp) //M(0x10+esp)=eax
0x0804929e <+35>: lea 0x28(%esp),%eax //eax=0x28+esp
0x080492a2 <+39>: mov %eax,0xc(%esp) //M(0xc+esp)=eax
0x080492a6 <+43>: lea 0x24(%esp),%eax //eax=0x24+esp
0x080492aa <+47>: mov %eax,0x8(%esp) //M(0x8+esp)=eax
0x080492ae <+51>: movl $0x804a3e9,0x4(%esp) //M(0x4+esp)=0x804a3e9
// 0x804a3e9: "%d %d %s"
0x080492b6 <+59>: movl $0x804c4d0,(%esp) //M(esp)=0x804c4d0
// 0x804c4d0: "" 这个地址在某一关被赋值
0x080492bd <+66>: call 0x8048870 <__isoc99_sscanf@plt> //__isoc99_sscanf@plt(0x804c4d0,0x804a3e9,0x24+esp,0x28+esp,0x2c+esp)
0x080492c2 <+71>: cmp $0x3,%eax //compare 返回值eax with 0x3
0x080492c5 <+74>: jne 0x80492fc <phase_defused+129>//if zf=0,jump 0x80492fc <phase_defused+129>
0x080492c7 <+76>: movl $0x804a3f2,0x4(%esp) //M(0x4+esp)=0x804a3f2
// 0x804a3f2: "DrEvil"
0x080492cf <+84>: lea 0x2c(%esp),%eax //eax=0x2c+esp
0x080492d3 <+88>: mov %eax,(%esp) //M(esp)=eax
0x080492d6 <+91>: call 0x8048fe4 <strings_not_equal> //调用strings_not_equal(0x2c+esp,0x804a3f2)
0x080492db <+96>: test %eax,%eax //eax&eax
0x080492dd <+98>: jne 0x80492fc <phase_defused+129>//if zf=0,jump 0x80492fc <phase_defused+129>
0x080492df <+100>: movl $0x804a2b8,(%esp) //M(esp)=0x804a2b8
//0x804a2b8: "Curses, you've found the secret phase!"
0x080492e6 <+107>: call 0x8048800 <puts@plt> //调用puts@plt(0x804a2b8)
0x080492eb <+112>: movl $0x804a2e0,(%esp) //M(esp)=0x804a2e0
//0x804a2e0: "But finding it and solving it are quite different..."
0x080492f2 <+119>: call 0x8048800 <puts@plt> //调用puts@plt(0x804a2e0)
0x080492f7 <+124>: call 0x8048edc <secret_phase> //进入隐藏关卡
0x080492fc <+129>: movl $0x804a318,(%esp) //M(esp)=0x804a318
//0x804a318: "Congratulations! You've defused the bomb!"
0x08049303 <+136>: call 0x8048800 <puts@plt> //调用puts@plt(0x804a318)
0x08049308 <+141>: mov 0x7c(%esp),%eax //eax=M(0x7c+esp)
0x0804930c <+145>: xor %gs:0x14,%eax //eax ^ %gs:0x14
0x08049313 <+152>: je 0x804931a <phase_defused+159>//if zf=1,jump 0x804931a <phase_defused+159>
0x08049315 <+154>: call 0x80487d0 <__stack_chk_fail@plt> //调用__stack_chk_fail@plt()
0x0804931a <+159>: add $0x8c,%esp //栈帧释放空间
0x08049320 <+165>: ret //返回地址出栈,并跳转到返回地址
End of assembler dump.
  • 查看一下炸弹拆除函数汇编代码,发现0x080492f7 <+124>: call 0x8048edc <secret_phase>隐藏关卡
  • 只有在第六关之后,而且0x2c+esp地址的字符串等于内置的DrEvil,我们才能进入隐藏关卡
    • 但是0x804c4d0什么时候被赋值的呢,我们进行调试,调试发现它是在第四关被赋值的,所以我们要将DrEvil放在第四关
      image-20210503173426765
    • 放进之后我们成功触发了隐藏关
      image-20210503173643070
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
Dump of assembler code for function secret_phase:     //secret_phase()
0x08048edc <+0>: push %ebx //push ebx
0x08048edd <+1>: sub $0x18,%esp //栈帧预留0x18字节
0x08048ee0 <+4>: call 0x804911d <read_line> //调用read_line()
0x08048ee5 <+9>: movl $0xa,0x8(%esp) //M(0x8+esp)=0xa
0x08048eed <+17>: movl $0x0,0x4(%esp) //M(0x4+esp)=0x0
0x08048ef5 <+25>: mov %eax,(%esp) //M(esp)=eax=input
0x08048ef8 <+28>: call 0x80488e0 <strtol@plt>//调用strtol@plt(input,0x0,0xa)返回值为字符串转化的整数
0x08048efd <+33>: mov %eax,%ebx //ebx=eax=input
0x08048eff <+35>: lea -0x1(%eax),%eax //eax=-0x1+eax
0x08048f02 <+38>: cmp $0x3e8,%eax //compare eax with 0x3e8=1000
0x08048f07 <+43>: jbe 0x8048f0e <secret_phase+50>//if 0<=eax<=1000,jump 0x8048f0e <secret_phase+50>
0x08048f09 <+45>: call 0x80490f6 <explode_bomb>//调用explode_bomb()
0x08048f0e <+50>: mov %ebx,0x4(%esp) //M(0x4+esp)=ebx
0x08048f12 <+54>: movl $0x804c088,(%esp) //M(esp)=0x804c088
// 0x804c088: 0x24
0x08048f19 <+61>: call 0x8048e8b <fun7> //调用fun7(0x804c088,ebx)
0x08048f1e <+66>: cmp $0x5,%eax //compare eax with 0x5
0x08048f21 <+69>: je 0x8048f28 <secret_phase+76> //if zf=1, jump 0x8048f28 <secret_phase+76>
0x08048f23 <+71>: call 0x80490f6 <explode_bomb> //调用explode_bomb()
0x08048f28 <+76>: movl $0x804a1e8,(%esp) //M(esp)=0x804a1e8
// 0x804a1e8: "Wow! You've defused the secret stage!"
0x08048f2f <+83>: call 0x8048800 <puts@plt> //调用puts@plt(0x804a1e8)
0x08048f34 <+88>: call 0x804927b <phase_defused> //调用phase_defused()
0x08048f39 <+93>: add $0x18,%esp //栈帧释放空间
0x08048f3c <+96>: pop %ebx //出栈ebx
0x08048f3d <+97>: ret //返回地址出栈,并跳转到返回地址
End of assembler dump.
  • 如果<fun7>的返回值%eax如果等于5我们就能成功拆除炸弹
  • 查看fun7函数
fun7
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
Dump of assembler code for function fun7:             //fun7(address,number)
0x08048e8b <+0>: push %ebx //ebx入栈
0x08048e8c <+1>: sub $0x18,%esp //栈帧预留0x18字节的空间
0x08048e8f <+4>: mov 0x20(%esp),%edx //edx=M(0x20+esp)=address
0x08048e93 <+8>: mov 0x24(%esp),%ecx //ecx=M(0x24+esp)=number
0x08048e97 <+12>: test %edx,%edx //edx & edx
0x08048e99 <+14>: je 0x8048ed2 <fun7+71> //if zf=1, jump 0x8048ed2 <fun7+71>
0x08048e9b <+16>: mov (%edx),%ebx //ebx=M(edx)=*address
0x08048e9d <+18>: cmp %ecx,%ebx //compare ebx with ecx=number
0x08048e9f <+20>: jle 0x8048eb4 <fun7+41> //if ebx<=ecx=number, jump 0x8048eb4 <fun7+41>
0x08048ea1 <+22>: mov %ecx,0x4(%esp) //M(0x4+esp)=ecx=number
0x08048ea5 <+26>: mov 0x4(%edx),%eax //eax=M(0x4+edx)=*(address+4)
0x08048ea8 <+29>: mov %eax,(%esp) //M(esp)=eax=*(address+4)
0x08048eab <+32>: call 0x8048e8b <fun7> //fun7(*(address+4),number)
0x08048eb0 <+37>: add %eax,%eax //eax=eax+eax
0x08048eb2 <+39>: jmp 0x8048ed7 <fun7+76> //jump 0x8048ed7 <fun7+76>
0x08048eb4 <+41>: mov $0x0,%eax //eax=0x0
0x08048eb9 <+46>: cmp %ecx,%ebx //compare ebx=*address with ecx=number
0x08048ebb <+48>: je 0x8048ed7 <fun7+76> //if zf=1, jump 0x8048ed7 <fun7+76>
0x08048ebd <+50>: mov %ecx,0x4(%esp) //M(0x4+esp)=ecx=number
0x08048ec1 <+54>: mov 0x8(%edx),%eax //eax=M(0x8+edx)=*(address+8)
0x08048ec4 <+57>: mov %eax,(%esp) //M(esp)=eax=*(address+8)
0x08048ec7 <+60>: call 0x8048e8b <fun7> //调用fun7(*(address+8),number)
0x08048ecc <+65>: lea 0x1(%eax,%eax,1),%eax //eax=0x1+eax+eax
0x08048ed0 <+69>: jmp 0x8048ed7 <fun7+76> //jump 0x8048ed7 <fun7+76>
0x08048ed2 <+71>: mov $0xffffffff,%eax //返回值eax=-1
0x08048ed7 <+76>: add $0x18,%esp //栈帧释放空间
0x08048eda <+79>: pop %ebx //出栈ebx
0x08048edb <+80>: ret //返回地址出栈,并跳转到返回地址
End of assembler dump.
  • 我们将fun7翻译成c语言函数

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    int fun7(int *address, int number) {
    if (address == null)
    return -1;
    if (*address <= number) {
    if (*address == number )
    return 0;
    else
    2 * fun7(*(address + 8), number) + 1;
    } else
    2 * fun7(*(address + 4), number);
    }
  • 因为返回值要为5,推导:0->1->2->5,所以地址变化:加8、加4、加8,依次查看,答案是0x2f=47
    image-20210503163503472

  • 这个过程其实是:36<=47地址加8,50>47小地址加4,45<=47地址加8,47=47返回值为0
    image-20210503181401052

  • 完成验证
    image-20210503160027467

第一次总结

bomb lab这个实验让我对gdb调试更加熟悉,对于汇编代码不至于每一个都要去查,这实验还是很有用处的。

问题总是出现在函数的参数、各种跳转上,其实还是对寄存器、内存地址以及栈帧的认识不够,还需继续努力。

第一次做的时候连栈帧图都不会画,所以不仅做得效率低下而且也不太明白。

为了更加好看,正在对过程的重写中。。。。

第二次总结

即将进行实验验收,今天终于完成了对炸弹实验过程的重写。

这个过程之所以写的这么详细,不只是过关,其实也是为了加强自己对汇编代码的阅读和理解能力。

总之,这个实验花了很久的时间,同样也收获了很多。

完结撒花!

  • Copyright: Copyright is owned by the author. For commercial reprints, please contact the author for authorization. For non-commercial reprints, please indicate the source.
  • Copyrights © 2021 Sung
  • Visitors: | Views:

请我喝杯咖啡吧~

支付宝
微信