Zhen Zhang's Blog

CSAPP 拆弹之旅

作为一个著名的反汇编语言实验,他的享(作)受(死)程度令我心向往很久。终于,我也悄悄的从CMU官网上下载了这个实验,here we go!

本文将首先讨论一些gdb的基础操作,然后就来逐个拆炸弹。

GDB 基本知识

首先,我们需要将目录切换到准备调试的文件所在目录下,然后打开gdb调试工具。我们这里假设文件名称叫做bomb,那么我们应该在gdb中输入

1
(gdb)file bomb

下面是几个最常用的命令:

运行至断点(如果没有设置断点就运行到程序结束):run (r)

继续至下一个断点(如果没有则运行到程序结束):continue (c)

设置断点:break (b)

我们有如下几种方式设置断点:

b + line number : 在该行设置断点

b + function name : 在该函数处设置断点

b + *code address : 在该地址处设置断点

删除断点 : delete (d) + number

注意如果没有写number,则删除所有断点

下一步 : s/n/si/ni

他们之间的区别在于是step into (s) 还是 step over (n) 还是针对对应的汇编语言 (si/ni)

这里有一个小窍门:如果我们在单步调试的过程中一直输入si之后输入回车是很麻烦的,因而聪明的gdb给了我们一个简单的方法,直接输入回车就可以默认执行上一个命令。

打印对应变量 : print (p) + variable

这里可以有多种print的格式,他们可以用作p/?,其中?可以分别是:

x: print as hex format

d: print as decimal format

t: print as binary format

s: print as string format

p $eax 可以直接查看eax register中的内容

查看寄存器 : i r

检查连续变量 : x/nfu

当我们需要连续检查一个内存区域中的变量的时候,我们可能需要用到examine命令,他的nfu分别表示:

n代表从起始地址开始显示几个地址

f代表格式,同上文的print

u代表显示几个字节,一般默认代表4字节

举个栗子:x/3xw从起始地址处每次显示3个地址,每个以16进制显示,每个4字节

结束当前调试过程 : kill (k)

退出gdb : quit (q)

基本知识大概就是这么多,现在,炸弹我来也~

拆弹过程

首先我们把bomb文件通过objdump反汇编成汇编语言,之后在每个炸弹的讨论过程中我会分别把相应的汇编代码贴出来。

Phase_1

首先是第一关的汇编代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
08048b20 <phase_1>:
8048b20: 55 push %ebp
8048b21: 89 e5 mov %esp,%ebp
8048b23: 83 ec 08 sub $0x8,%esp
8048b26: 8b 45 08 mov 0x8(%ebp),%eax
8048b29: 83 c4 f8 add $0xfffffff8,%esp
8048b2c: 68 c0 97 04 08 push $0x80497c0
8048b31: 50 push %eax
8048b32: e8 f9 04 00 00 call 8049030 <strings_not_equal>
8048b37: 83 c4 10 add $0x10,%esp
8048b3a: 85 c0 test %eax,%eax
8048b3c: 74 05 je 8048b43 <phase_1+0x23>
8048b3e: e8 b9 09 00 00 call 80494fc <explode_bomb>
8048b43: 89 ec mov %ebp,%esp
8048b45: 5d pop %ebp
8048b46: c3 ret
8048b47: 90 nop

这应该是一个热身关卡,蛮简单的,主要是让我们学会gdb的基本调试命令,以及熟悉出题人的一些思路。根据0x8048b32那里的string_not_equal函数,我们可以猜测到,应该是让输入的值(%eax)和指定的值(0x80497c0)进行比较,如果相等就进入下一关,如果不相等就调用explode_bomb函数,也就是炸弹爆炸。为此,我们只需要知道0x80497c0中的string到底是什么就可以了:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
(gdb) file bomb
Reading symbols from bomb...done.
(gdb) b phase_1
Breakpoint 1 at 0x8048b26
(gdb) r
Starting program: /home/elliot/Desktop/bomb
Welcome to my fiendish little bomb. You have 6 phases with
which to blow yourself up. Have a nice day!
qqq
Breakpoint 1, 0x08048b26 in phase_1 ()
(gdb) p/s (char*) 0x80497c0
$1 = 0x80497c0 "Public speaking is very easy."
(gdb)

很明显,这里的字符串是”Public speaking is very easy”(真的吗,出题人你出来我保证不打死你)。为此,我们进行测试,发现进入了第二关~

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
28
29
30
31
32
33
08048b48 <phase_2>:
8048b48: 55 push %ebp
8048b49: 89 e5 mov %esp,%ebp
8048b4b: 83 ec 20 sub $0x20,%esp
8048b4e: 56 push %esi
8048b4f: 53 push %ebx
8048b50: 8b 55 08 mov 0x8(%ebp),%edx
8048b53: 83 c4 f8 add $0xfffffff8,%esp
8048b56: 8d 45 e8 lea -0x18(%ebp),%eax
8048b59: 50 push %eax
8048b5a: 52 push %edx
8048b5b: e8 78 04 00 00 call 8048fd8 <read_six_numbers>
8048b60: 83 c4 10 add $0x10,%esp
8048b63: 83 7d e8 01 cmpl $0x1,-0x18(%ebp)
8048b67: 74 05 je 8048b6e <phase_2+0x26>
8048b69: e8 8e 09 00 00 call 80494fc <explode_bomb>
8048b6e: bb 01 00 00 00 mov $0x1,%ebx
8048b73: 8d 75 e8 lea -0x18(%ebp),%esi
8048b76: 8d 43 01 lea 0x1(%ebx),%eax
8048b79: 0f af 44 9e fc imul -0x4(%esi,%ebx,4),%eax
8048b7e: 39 04 9e cmp %eax,(%esi,%ebx,4)
8048b81: 74 05 je 8048b88 <phase_2+0x40>
8048b83: e8 74 09 00 00 call 80494fc <explode_bomb>
8048b88: 43 inc %ebx
8048b89: 83 fb 05 cmp $0x5,%ebx
8048b8c: 7e e8 jle 8048b76 <phase_2+0x2e>
8048b8e: 8d 65 d8 lea -0x28(%ebp),%esp
8048b91: 5b pop %ebx
8048b92: 5e pop %esi
8048b93: 89 ec mov %ebp,%esp
8048b95: 5d pop %ebp
8048b96: c3 ret
8048b97: 90 nop

这个比刚才的长了好多(然而我想多了,后面的一个比一个长)。下面我会分条写出我的思考过程:

  1. 注意到0x8048b5b那里调用了read_six_numbers这个函数,所以我们的Input一定要是六个数。

  2. 通过0x8048b56中leal加载的有效地址,可以知道,register %eax points to the first number, which is at address -0x18(%ebp)。

  3. 之后的任务就是分析对应的汇编代码 (0x8048b60-0x8048b8c),可以发现这是一个循环,依次来检测下面的数字是否符合条件,如果不符合就引爆炸弹。

  4. 首先可以看出,0x8048b63的cmpl指令告诉我们第一个数必须是1。

  5. 下面的过程我用java代码写了一下:

    1
    2
    3
    4
    5
    6
    7
    public void testNumber (int[] nums) {
    if (nums[0] != 1) explode();
    for (int i = 1; i <= 5; i++) {
    temp = (i + 1) * nums[i - 1];
    if (nums[i] != temp) explode();
    }
    }
  6. 因此我们发现,最终要求输入的数字是1 2 6 24 120 720.

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
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
08048b98 <phase_3>:
8048b98: 55 push %ebp
8048b99: 89 e5 mov %esp,%ebp
8048b9b: 83 ec 14 sub $0x14,%esp
8048b9e: 53 push %ebx
8048b9f: 8b 55 08 mov 0x8(%ebp),%edx
8048ba2: 83 c4 f4 add $0xfffffff4,%esp
8048ba5: 8d 45 fc lea -0x4(%ebp),%eax
8048ba8: 50 push %eax
8048ba9: 8d 45 fb lea -0x5(%ebp),%eax
8048bac: 50 push %eax
8048bad: 8d 45 f4 lea -0xc(%ebp),%eax
8048bb0: 50 push %eax
8048bb1: 68 de 97 04 08 push $0x80497de
8048bb6: 52 push %edx
8048bb7: e8 a4 fc ff ff call 8048860 <sscanf@plt>
8048bbc: 83 c4 20 add $0x20,%esp
8048bbf: 83 f8 02 cmp $0x2,%eax
8048bc2: 7f 05 jg 8048bc9 <phase_3+0x31>
8048bc4: e8 33 09 00 00 call 80494fc <explode_bomb>
8048bc9: 83 7d f4 07 cmpl $0x7,-0xc(%ebp)
8048bcd: 0f 87 b5 00 00 00 ja 8048c88 <phase_3+0xf0>
8048bd3: 8b 45 f4 mov -0xc(%ebp),%eax
8048bd6: ff 24 85 e8 97 04 08 jmp *0x80497e8(,%eax,4)
8048bdd: 8d 76 00 lea 0x0(%esi),%esi
8048be0: b3 71 mov $0x71,%bl
8048be2: 81 7d fc 09 03 00 00 cmpl $0x309,-0x4(%ebp)
8048be9: 0f 84 a0 00 00 00 je 8048c8f <phase_3+0xf7>
8048bef: e8 08 09 00 00 call 80494fc <explode_bomb>
8048bf4: e9 96 00 00 00 jmp 8048c8f <phase_3+0xf7>
8048bf9: 8d b4 26 00 00 00 00 lea 0x0(%esi,%eiz,1),%esi
8048c00: b3 62 mov $0x62,%bl
8048c02: 81 7d fc d6 00 00 00 cmpl $0xd6,-0x4(%ebp)
8048c09: 0f 84 80 00 00 00 je 8048c8f <phase_3+0xf7>
8048c0f: e8 e8 08 00 00 call 80494fc <explode_bomb>
8048c14: eb 79 jmp 8048c8f <phase_3+0xf7>
8048c16: b3 62 mov $0x62,%bl
8048c18: 81 7d fc f3 02 00 00 cmpl $0x2f3,-0x4(%ebp)
8048c1f: 74 6e je 8048c8f <phase_3+0xf7>
8048c21: e8 d6 08 00 00 call 80494fc <explode_bomb>
8048c26: eb 67 jmp 8048c8f <phase_3+0xf7>
8048c28: b3 6b mov $0x6b,%bl
8048c2a: 81 7d fc fb 00 00 00 cmpl $0xfb,-0x4(%ebp)
8048c31: 74 5c je 8048c8f <phase_3+0xf7>
8048c33: e8 c4 08 00 00 call 80494fc <explode_bomb>
8048c38: eb 55 jmp 8048c8f <phase_3+0xf7>
8048c3a: 8d b6 00 00 00 00 lea 0x0(%esi),%esi
8048c40: b3 6f mov $0x6f,%bl
8048c42: 81 7d fc a0 00 00 00 cmpl $0xa0,-0x4(%ebp)
8048c49: 74 44 je 8048c8f <phase_3+0xf7>
8048c4b: e8 ac 08 00 00 call 80494fc <explode_bomb>
8048c50: eb 3d jmp 8048c8f <phase_3+0xf7>
8048c52: b3 74 mov $0x74,%bl
8048c54: 81 7d fc ca 01 00 00 cmpl $0x1ca,-0x4(%ebp)
8048c5b: 74 32 je 8048c8f <phase_3+0xf7>
8048c5d: e8 9a 08 00 00 call 80494fc <explode_bomb>
8048c62: eb 2b jmp 8048c8f <phase_3+0xf7>
8048c64: b3 76 mov $0x76,%bl
8048c66: 81 7d fc 0c 03 00 00 cmpl $0x30c,-0x4(%ebp)
8048c6d: 74 20 je 8048c8f <phase_3+0xf7>
8048c6f: e8 88 08 00 00 call 80494fc <explode_bomb>
8048c74: eb 19 jmp 8048c8f <phase_3+0xf7>
8048c76: b3 62 mov $0x62,%bl
8048c78: 81 7d fc 0c 02 00 00 cmpl $0x20c,-0x4(%ebp)
8048c7f: 74 0e je 8048c8f <phase_3+0xf7>
8048c81: e8 76 08 00 00 call 80494fc <explode_bomb>
8048c86: eb 07 jmp 8048c8f <phase_3+0xf7>
8048c88: b3 78 mov $0x78,%bl
8048c8a: e8 6d 08 00 00 call 80494fc <explode_bomb>
8048c8f: 3a 5d fb cmp -0x5(%ebp),%bl
8048c92: 74 05 je 8048c99 <phase_3+0x101>
8048c94: e8 63 08 00 00 call 80494fc <explode_bomb>
8048c99: 8b 5d e8 mov -0x18(%ebp),%ebx
8048c9c: 89 ec mov %ebp,%esp
8048c9e: 5d pop %ebp
8048c9f: c3 ret

我把代码在一些位置断了行,方便阅读。

  1. 我们首先看part1, 从三个leal发现,加载了三个有效地址,因而我们猜测是三个输入输入。通过0x8048bb7我们发现三个input的格式是要通过检测的,因而我们打印0x80497de来看看究竟让我们输入什么样的格式:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    (gdb) b phase_3
    Breakpoint 3 at 0x8048b9f
    (gdb) r
    Starting program: /home/elliot/Desktop/bomb
    Welcome to my fiendish little bomb. You have 6 phases with
    which to blow yourself up. Have a nice day!
    Public speaking is very easy.
    Phase 1 defused. How about the next one?
    1 2 6 24 120 720
    That\'s number 2. Keep going!
    1 w 3
    Breakpoint 3, 0x08048b9f in phase_3 ()
    (gdb) p/s (char*) 0x80497de
    $2 = 0x80497de "%d %c %d"

    清楚的我们发现,需要输入数字,字符,数字一组。这里需要注意的是,我们在调试的时候不能随意输入,因为如果随意输入就不能进入之后的调试环节,因为不能通过sscanf检测。

  2. part2告诉我们,需要让我们的第一个输入大于2并且小于等于7,也就是在3–7之间。

  3. part3中0x8048bd6的 jmp *0x80497e8(,%eax,4) 是这个题目中最关键的一条指令,它告诉了我们这是一个switch函数,并且这个就是跳转表的地址。因此,如果我们第一个输入的是3的话,我们的跳转地址应该是(0x80497e8+0xc)

    1
    2
    3
    4
    5
    6
    (gdb) x/4 0x80497e8
    0x80497e8: 0x08048be0 0x08048c00 0x08048c16 0x08048c28
    (gdb) p/s (char) 0x6b
    $8 = 107 'k'
    (gdb) p/d 0xfb
    $10 = 251
  4. 通过gdb的输出,我们可以发现需要跳转到地址0x08048c28,进而我们发现了0x6b和0xfb,分别用他们适当的格式打印他们,就能得到他们对应的输出。因为这是一个switch语句,所以答案并不唯一,这里我们找到的答案是3 k 251

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
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
08048ca0 <func4>:
8048ca0: 55 push %ebp
8048ca1: 89 e5 mov %esp,%ebp
8048ca3: 83 ec 10 sub $0x10,%esp
8048ca6: 56 push %esi
8048ca7: 53 push %ebx
8048ca8: 8b 5d 08 mov 0x8(%ebp),%ebx
8048cab: 83 fb 01 cmp $0x1,%ebx
8048cae: 7e 20 jle 8048cd0 <func4+0x30>
8048cb0: 83 c4 f4 add $0xfffffff4,%esp
8048cb3: 8d 43 ff lea -0x1(%ebx),%eax
8048cb6: 50 push %eax
8048cb7: e8 e4 ff ff ff call 8048ca0 <func4>
8048cbc: 89 c6 mov %eax,%esi
8048cbe: 83 c4 f4 add $0xfffffff4,%esp
8048cc1: 8d 43 fe lea -0x2(%ebx),%eax
8048cc4: 50 push %eax
8048cc5: e8 d6 ff ff ff call 8048ca0 <func4>
8048cca: 01 f0 add %esi,%eax
8048ccc: eb 07 jmp 8048cd5 <func4+0x35>
8048cce: 89 f6 mov %esi,%esi
8048cd0: b8 01 00 00 00 mov $0x1,%eax
8048cd5: 8d 65 e8 lea -0x18(%ebp),%esp
8048cd8: 5b pop %ebx
8048cd9: 5e pop %esi
8048cda: 89 ec mov %ebp,%esp
8048cdc: 5d pop %ebp
8048cdd: c3 ret
8048cde: 89 f6 mov %esi,%esi
08048ce0 <phase_4>:
8048ce0: 55 push %ebp
8048ce1: 89 e5 mov %esp,%ebp
8048ce3: 83 ec 18 sub $0x18,%esp
8048ce6: 8b 55 08 mov 0x8(%ebp),%edx
8048ce9: 83 c4 fc add $0xfffffffc,%esp
8048cec: 8d 45 fc lea -0x4(%ebp),%eax
8048cef: 50 push %eax
8048cf0: 68 08 98 04 08 push $0x8049808
8048cf5: 52 push %edx
8048cf6: e8 65 fb ff ff call 8048860 <sscanf@plt>
8048cfb: 83 c4 10 add $0x10,%esp
8048cfe: 83 f8 01 cmp $0x1,%eax
8048d01: 75 06 jne 8048d09 <phase_4+0x29>
8048d03: 83 7d fc 00 cmpl $0x0,-0x4(%ebp)
8048d07: 7f 05 jg 8048d0e <phase_4+0x2e>
8048d09: e8 ee 07 00 00 call 80494fc <explode_bomb>
8048d0e: 83 c4 f4 add $0xfffffff4,%esp
8048d11: 8b 45 fc mov -0x4(%ebp),%eax
8048d14: 50 push %eax
8048d15: e8 86 ff ff ff call 8048ca0 <func4>
8048d1a: 83 c4 10 add $0x10,%esp
8048d1d: 83 f8 37 cmp $0x37,%eax
8048d20: 74 05 je 8048d27 <phase_4+0x47>
8048d22: e8 d5 07 00 00 call 80494fc <explode_bomb>
8048d27: 89 ec mov %ebp,%esp
8048d29: 5d pop %ebp
8048d2a: c3 ret
8048d2b: 90 nop
  1. 这个很明显的是 phase_4 调用了 func4 ,因而我们先研究phase_4:

  2. phase_4的0x8048cf6同样调用了sscanf函数,因而通过对前面0x8049808的分析我们发现需要输入的是一个数字,它经过func4的输出结果必须为0x37(55).

  3. func4是一个递归函数,当初始值为1的时候直接输出结果为1,当不为1的时候他会进行递归:

    1
    2
    3
    4
    public void func4 (int x) {
    if (x == 1) return 1;
    return func4(x-1) + func4(x-2);
    }

    通过带入计算,发现 func4(9) = 55。所以此处的答案应该为9。

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
42
43
44
45
08048d2c <phase_5>:
8048d2c: 55 push %ebp
8048d2d: 89 e5 mov %esp,%ebp
8048d2f: 83 ec 10 sub $0x10,%esp
8048d32: 56 push %esi
8048d33: 53 push %ebx
8048d34: 8b 5d 08 mov 0x8(%ebp),%ebx
8048d37: 83 c4 f4 add $0xfffffff4,%esp
8048d3a: 53 push %ebx
8048d3b: e8 d8 02 00 00 call 8049018 <string_length>
8048d40: 83 c4 10 add $0x10,%esp
8048d43: 83 f8 06 cmp $0x6,%eax
8048d46: 74 05 je 8048d4d <phase_5+0x21>
8048d48: e8 af 07 00 00 call 80494fc <explode_bomb>
8048d4d: 31 d2 xor %edx,%edx
8048d4f: 8d 4d f8 lea -0x8(%ebp),%ecx
8048d52: be 20 b2 04 08 mov $0x804b220,%esi
8048d57: 8a 04 1a mov (%edx,%ebx,1),%al
8048d5a: 24 0f and $0xf,%al
8048d5c: 0f be c0 movsbl %al,%eax
8048d5f: 8a 04 30 mov (%eax,%esi,1),%al
8048d62: 88 04 0a mov %al,(%edx,%ecx,1)
8048d65: 42 inc %edx
8048d66: 83 fa 05 cmp $0x5,%edx
8048d69: 7e ec jle 8048d57 <phase_5+0x2b>
8048d6b: c6 45 fe 00 movb $0x0,-0x2(%ebp)
8048d6f: 83 c4 f8 add $0xfffffff8,%esp
8048d72: 68 0b 98 04 08 push $0x804980b
8048d77: 8d 45 f8 lea -0x8(%ebp),%eax
8048d7a: 50 push %eax
8048d7b: e8 b0 02 00 00 call 8049030 <strings_not_equal>
8048d80: 83 c4 10 add $0x10,%esp
8048d83: 85 c0 test %eax,%eax
8048d85: 74 05 je 8048d8c <phase_5+0x60>
8048d87: e8 70 07 00 00 call 80494fc <explode_bomb>
8048d8c: 8d 65 e8 lea -0x18(%ebp),%esp
8048d8f: 5b pop %ebx
8048d90: 5e pop %esi
8048d91: 89 ec mov %ebp,%esp
8048d93: 5d pop %ebp
8048d94: c3 ret
8048d95: 8d 76 00 lea 0x0(%esi),%esi
  1. part1有一个function是string_length,后面有一个比较,如果不为6就会引爆炸弹。这意味着,我们的input需要是一个包含6个字节的string。

  2. part2中有一个0x804b220被移入了esi register,因而我们打印这个地址:

    1
    2
    (gdb) p/s (char*) 0x804b220
    $12 = 0x804b220 "isrveawhobpnutfg\260\001"

    发现这是一串莫名其妙的数字,后面还有\260\001也不知道是什么鬼。前面的“isrveawhobpnutfg”一共是16个字符,我们姑且叫他string1吧。

  3. part3是这个题目的核心部分,它表示,我们的输入字符作为偏移量,对刚才的string1进行一个变换。具体的变换方法是,通过把我们的字符转化为ASCII码,取出其中的最后一位(&0xF),假设为5,那么我们就像对应的取出string1[5]。具体的java代码是这样的:

    1
    2
    3
    4
    5
    6
    7
    public void phase5 (string inputString) {
    String[] strings = new String[6];
    for (int i = 0; i < 6; i++) {
    strings[i] = string1[(inputString[i] & 0xF)];
    return strings;
    }
    }
  4. 我们在part3中得到的字符串需要与part4中题目要求的字符串相同。题目要求的字符串在0x0x804980b中,通过print我们发现他是”giants”,因而通过查询ASCII table我们可以得到一组字符串作为偏移量:opekmq。注意,这里的答案并不唯一,甚至于他们不是字母而是其他的之类的符号都是可以的,但是他们的ASCII码的最后一位必须符合题意。

Phase_6

这是CMU老师声称 “The last phase will even challenge the best students”。 我觉得像我这种学霸(渣)那是一定会被虐的体无完肤的,不过没有困难也要创造困难,何况现在就有困难呢。

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
08048d98 <phase_6>:
8048d98: 55 push %ebp
8048d99: 89 e5 mov %esp,%ebp
8048d9b: 83 ec 4c sub $0x4c,%esp
8048d9e: 57 push %edi
8048d9f: 56 push %esi
8048da0: 53 push %ebx
8048da1: 8b 55 08 mov 0x8(%ebp),%edx
8048da4: c7 45 cc 6c b2 04 08 movl $0x804b26c,-0x34(%ebp)
8048dab: 83 c4 f8 add $0xfffffff8,%esp
8048dae: 8d 45 e8 lea -0x18(%ebp),%eax
8048db1: 50 push %eax
8048db2: 52 push %edx
8048db3: e8 20 02 00 00 call 8048fd8 <read_six_numbers>
8048db8: 31 ff xor %edi,%edi
8048dba: 83 c4 10 add $0x10,%esp
8048dbd: 8d 76 00 lea 0x0(%esi),%esi
8048dc0: 8d 45 e8 lea -0x18(%ebp),%eax
8048dc3: 8b 04 b8 mov (%eax,%edi,4),%eax
8048dc6: 48 dec %eax
8048dc7: 83 f8 05 cmp $0x5,%eax
8048dca: 76 05 jbe 8048dd1 <phase_6+0x39>
8048dcc: e8 2b 07 00 00 call 80494fc <explode_bomb>
8048dd1: 8d 5f 01 lea 0x1(%edi),%ebx
8048dd4: 83 fb 05 cmp $0x5,%ebx
8048dd7: 7f 23 jg 8048dfc <phase_6+0x64>
8048dd9: 8d 04 bd 00 00 00 00 lea 0x0(,%edi,4),%eax
8048de0: 89 45 c8 mov %eax,-0x38(%ebp)
8048de3: 8d 75 e8 lea -0x18(%ebp),%esi
8048de6: 8b 55 c8 mov -0x38(%ebp),%edx
8048de9: 8b 04 32 mov (%edx,%esi,1),%eax
8048dec: 3b 04 9e cmp (%esi,%ebx,4),%eax
8048def: 75 05 jne 8048df6 <phase_6+0x5e>
8048df1: e8 06 07 00 00 call 80494fc <explode_bomb>
8048df6: 43 inc %ebx
8048df7: 83 fb 05 cmp $0x5,%ebx
8048dfa: 7e ea jle 8048de6 <phase_6+0x4e>
8048dfc: 47 inc %edi
8048dfd: 83 ff 05 cmp $0x5,%edi
8048e00: 7e be jle 8048dc0 <phase_6+0x28>
8048e02: 31 ff xor %edi,%edi
8048e04: 8d 4d e8 lea -0x18(%ebp),%ecx
8048e07: 8d 45 d0 lea -0x30(%ebp),%eax
8048e0a: 89 45 c4 mov %eax,-0x3c(%ebp)
8048e0d: 8d 76 00 lea 0x0(%esi),%esi
8048e10: 8b 75 cc mov -0x34(%ebp),%esi
8048e13: bb 01 00 00 00 mov $0x1,%ebx
8048e18: 8d 04 bd 00 00 00 00 lea 0x0(,%edi,4),%eax
8048e1f: 89 c2 mov %eax,%edx
8048e21: 3b 1c 08 cmp (%eax,%ecx,1),%ebx
8048e24: 7d 12 jge 8048e38 <phase_6+0xa0>
8048e26: 8b 04 0a mov (%edx,%ecx,1),%eax
8048e29: 8d b4 26 00 00 00 00 lea 0x0(%esi,%eiz,1),%esi
8048e30: 8b 76 08 mov 0x8(%esi),%esi
8048e33: 43 inc %ebx
8048e34: 39 c3 cmp %eax,%ebx
8048e36: 7c f8 jl 8048e30 <phase_6+0x98>
8048e38: 8b 55 c4 mov -0x3c(%ebp),%edx
8048e3b: 89 34 ba mov %esi,(%edx,%edi,4)
8048e3e: 47 inc %edi
8048e3f: 83 ff 05 cmp $0x5,%edi
8048e42: 7e cc jle 8048e10 <phase_6+0x78>
8048e44: 8b 75 d0 mov -0x30(%ebp),%esi
8048e47: 89 75 cc mov %esi,-0x34(%ebp)
8048e4a: bf 01 00 00 00 mov $0x1,%edi
8048e4f: 8d 55 d0 lea -0x30(%ebp),%edx
8048e52: 8b 04 ba mov (%edx,%edi,4),%eax
8048e55: 89 46 08 mov %eax,0x8(%esi)
8048e58: 89 c6 mov %eax,%esi
8048e5a: 47 inc %edi
8048e5b: 83 ff 05 cmp $0x5,%edi
8048e5e: 7e f2 jle 8048e52 <phase_6+0xba>
8048e60: c7 46 08 00 00 00 00 movl $0x0,0x8(%esi)
8048e67: 8b 75 cc mov -0x34(%ebp),%esi
8048e6a: 31 ff xor %edi,%edi
8048e6c: 8d 74 26 00 lea 0x0(%esi,%eiz,1),%esi
8048e70: 8b 56 08 mov 0x8(%esi),%edx
8048e73: 8b 06 mov (%esi),%eax
8048e75: 3b 02 cmp (%edx),%eax
8048e77: 7d 05 jge 8048e7e <phase_6+0xe6>
8048e79: e8 7e 06 00 00 call 80494fc <explode_bomb>
8048e7e: 8b 76 08 mov 0x8(%esi),%esi
8048e81: 47 inc %edi
8048e82: 83 ff 04 cmp $0x4,%edi
8048e85: 7e e9 jle 8048e70 <phase_6+0xd8>
8048e87: 8d 65 a8 lea -0x58(%ebp),%esp
8048e8a: 5b pop %ebx
8048e8b: 5e pop %esi
8048e8c: 5f pop %edi
8048e8d: 89 ec mov %ebp,%esp
8048e8f: 5d pop %ebp
8048e90: c3 ret
8048e91: 8d 76 00 lea 0x0(%esi),%esi

这个题目的代码应该也是这个lab中最长的了,不过不要着急,我们一步步来看。

  1. 这里的part1就很复杂,首先,0x8048da4中将一个0x804b26c移动到-0x34(%ebp)中,不过这个家伙和我们现在还没什么关系,等会儿part2和part3的时候才会用到它。

  2. 继续往下看part1,可以发现它需要我们输入六个数字,这六个数字都必须位于<6的范围内,否则就会爆炸。

  3. 同时,这六个数字每一个都不能相等。

    1
    2
    3
    4
    5
    6
    7
    8
    public void phase6_1 (int[] nums) {
    for (int i = 0; i < 6; i++) {
    if (nums[i] > 6) explode();
    for (int j = i + 1; j < 6; j++) {
    if (nums[i] == nums[j]) explode();
    }
    }
    }

    至此,part1分析完毕,我们进入part2。

  4. part2揭露了这个问题的本质:刚才我们放过的0x804b26c我们重新回头来看他,发现这个货很奇怪:

    1
    2
    (gdb) x/d 0x804b26c
    0x804b26c <node1>: 253

    奇怪就在,它的前面有一个tag:node1,这是什么,这不是传说中的链表吗!因而我们使用x/3d打印他的连续三个相关字节的内容:

    1
    2
    (gdb) x/3d 0x804b26c
    0x804b26c <node1>: 253 1 134525536

    结果表明,这确实是一个链表。那么part2的代码就好理解了:他的主要目的是根据我们所提供的六个数字,并以我们的数字为索引进行链表的重新排序,组成一个全新的链表:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    public void phase6_sort (int[] nums) {
    \\ "nodes" is the nodes that we already have in our memory
    \\ We assume we already have the node data structure
    Node[] nodesSorted = new Node[6];
    for (int i = 0; i < 6; i++) {
    Node node = nodes[0];
    for (int j = 1; j < nums[i]; j++) {
    node = node.next;
    }
    nodesSorted[i] = node;
    }
    }
  5. 在part3中,我们则对我们刚才已经重排序的链表进行重新组织和测试,他们必须满足前一个node的值都比后面的要大:

    1
    2
    3
    4
    5
    public void phase6_test (node[] nodes) {
    for (int i = 0; i < 5; i++) {
    if (nodes[i] < nodes[i+1]) explode();
    }
    }
  6. 现在我们需要做什么?我们需要做的就是把这6个node的值全部找出来(通过p node1/node2/…),然后把他们按照从大到小排序,提取他们的索引,就是我们最后需要的答案。这六个数字分别是:253,725,301,997,212,432, 所以我们最终的次序应该是4 2 6 3 1 5。

至此,6个炸弹全部排解完毕。

关于隐藏关的炸弹,我会在后续的博文中讨论。当然,也有可能直接更新在这里。附上一个通关截图~

bomb complete