CSAPP Lab2: Binary Bomb

著名的CSAPP实验:二进制炸弹

就是通过gdb和反汇编猜测程序意图,共有6关和一个隐藏关卡

只有输入正确的字符串才能过关,否则会程序会bomb终止运行

隐藏关卡需要输入特定字符串方会开启

实验材料下载地址:http://csapp.cs.cmu.edu/2e/labs.html

下面通关解法:

反汇编:

objdump -d bomb > bomb_assembly_32.S

Phase 1:

打开bomb_assembly_32.S,定位到<phase_1>函数,可以看到以下代码:

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

可以看出,用户输入字串指针保存在0x8(%ebp), <phase_1>把此指针放入eax,

然后把$0x80497c0压栈,再把eax也就是用户字串指针压栈,

然后调用<strings_not_equal>

待<strings_not_equal>返回后,测试返回值,

若equal则进入下一phase,否则<explode_bomb>

从<strings_not_equal>可知该函数用于比较两函数的值,因此需要两个字串作为输入,

上面代码中,push %eax用于传递用户字串指针,

则push $0x80497c0自然是传递比较字串的指针了.

打开gdb,x/s 0x80497c0, 可以直接查看到该指针指向的子符串:

Public speaking is very easy.

Phase 2:

打开bomb_assembly_32.S,定位到<phase_2>函数,留意以下几行:

 1     8048b50:    8b 55 08                 mov    0x8(%ebp),%edx
 2
 3     8048b53:    83 c4 f8                 add    $0xfffffff8,%esp
 4
 5     8048b56:    8d 45 e8                 lea    -0x18(%ebp),%eax
 6
 7     8048b59:    50                       push   %eax
 8
 9     8048b5a:    52                       push   %edx
10
11     8048b5b:    e8 78 04 00 00           call   8048fd8 <read_six_numbers>

mov 0x8(%ebp),%edx 将用户字串指针存入edx,

lea -0x18(%ebp),%eax 把ebp-0x18这个地址存入eax,

则最后三句

push %eax

push %edx

call 8048fd8 <read_six_numbers>

相当于read_six_numbers( 用户字串指针地址, ebp-0x18 )

现在我们切换到<read_six_numbers>,看看这个函数是干什么的:

先来看下面2行:

1     8048fde:    8b 4d 08                 mov    0x8(%ebp),%ecx
2
3     8048fe1:    8b 55 0c                 mov    0xc(%ebp),%edx

把用户字串指针存入ecx, ebp-0x18存入edx

往下看:

1     8048fe4:    8d 42 14                 lea    0x14(%edx),%eax

eax存入了 edx+0x14 这个值

再往下:

 1     8048fe7:    50                       push   %eax
 2
 3     8048fe8:    8d 42 10                 lea    0x10(%edx),%eax
 4
 5     8048feb:    50                       push   %eax
 6
 7     8048fec:    8d 42 0c                 lea    0xc(%edx),%eax
 8
 9     8048fef:    50                       push   %eax
10
11     8048ff0:    8d 42 08                 lea    0x8(%edx),%eax
12
13     8048ff3:    50                       push   %eax
14
15     8048ff4:    8d 42 04                 lea    0x4(%edx),%eax
16
17     8048ff7:    50                       push   %eax
18
19     8048ff8:    52                       push   %edx

上面几行依次把 edx+0x14, edx+0x10, edx+0xc, edx+0x8, edx+4, edx 这6个地址值压栈

注意edx是<phase_2>的stack frame的 ebp-0x18 这个地址值

1     8048ff9:    68 1b 9b 04 08           push   $0x8049b1b
2
3     8048ffe:    51                       push   %ecx
4
5     8048fff:    e8 5c f8 ff ff           call   8048860 <[email protected]>

前2行把 $0x8049b1b 和 ecx(用户字串指针) 压栈, 然后调用sscanf

sscanf的原型是int sscanf(const char *str, const char *format, ...);

按format的格式解释str,然后把得到的值放入后面省略号所代表的变量中

因此, 按刚才压栈的顺序, str是用户输入字串, $0x8049b1b 是format的地址,

edx, edx+4,...,edx+0x14是对应的变量.

先用gdb查看format, x/s $0x8049b1b, "%d %d %d %d %d %d".

可知,需要从用户字串中提取6个整数,存入(edx)--(edx+0x14)中.

综上, <read_six_numbers> 作用就是从用户字串中提取6个数字, 存入<phase_2>stack frame中的(ebp-0x18)中

回到<phase_2>接着看:

1     8048b63:    83 7d e8 01              cmpl   $0x1,-0x18(%ebp)
2
3     8048b67:    74 05                    je     8048b6e <phase_2+0x26>
4
5     8048b69:    e8 8e 09 00 00           call   80494fc <explode_bomb>

测试(ebp-0x18)是否等于1, 不等则bomb, 因此用户输入的第一个数字应为1.

1     8048b6e:    bb 01 00 00 00           mov    $0x1,%ebx
2
3     8048b73:    8d 75 e8                 lea    -0x18(%ebp),%esi

令ebx=1, esi = ebp-18

 1     8048b76:    8d 43 01                 lea    0x1(%ebx),%eax
 2
 3     8048b79:    0f af 44 9e fc           imul   -0x4(%esi,%ebx,4),%eax
 4
 5     8048b7e:    39 04 9e                 cmp    %eax,(%esi,%ebx,4)
 6
 7     8048b81:    74 05                    je     8048b88 <phase_2+0x40>
 8
 9     8048b83:    e8 74 09 00 00           call   80494fc <explode_bomb>
10
11     8048b88:    43                       inc    %ebx
12
13     8048b89:    83 fb 05                 cmp    $0x5,%ebx
14
15     8048b8c:    7e e8                    jle    8048b76 <phase_2+0x2e>

注意, esi是存放6个数字中第1数字的地址,

因此 -0x4(%esi,%ebx,4) 表示第ebx个数字,

(%esi,ebx,4)表示第ebx+1个数字

因此上面第3-6行代码检查 a[ebx]*(ebx+1) == a[ebx+1], 其中a[n]表示第n个数字

若不等则bomb,否则ebx增1并循环

因此<phase_2>需要输入一个数列, a[1]=1, a[n+1] = a[n]*(n+1), n<=6

1, 2, 6, 24, 120, 720

Phase 3:

打开bomb_assembly_32.S,定位到<phase_3>函数,可以看到以下代码:

 1     ;; edx stores pointer of user input
 2
 3     8048b9f:    8b 55 08                 mov    0x8(%ebp),%edx
 4
 5     8048ba2:    83 c4 f4                 add    $0xfffffff4,%esp
 6
 7     ;; push ebp-4 onto stack
 8
 9     8048ba5:    8d 45 fc                 lea    -0x4(%ebp),%eax
10
11     8048ba8:    50                       push   %eax
12
13     ;; push ebp-5 onto stack
14
15     8048ba9:    8d 45 fb                 lea    -0x5(%ebp),%eax
16
17     8048bac:    50                       push   %eax
18
19     ;; push ebp-12 onto stack
20
21     8048bad:    8d 45 f4                 lea    -0xc(%ebp),%eax
22
23     8048bb0:    50                       push   %eax
24
25     ;; push $0x80497de onto stack
26
27     ;; gdb x/s 0x80497de: "%d %c %d"
28
29     8048bb1:    68 de 97 04 08           push   $0x80497de
30
31     ;; push pointer of user input onto stack
32
33     8048bb6:    52                       push   %edx
34
35     8048bb7:    e8 a4 fc ff ff           call   8048860 <[email protected]>

具体代码请看注释,一开始主要是sscanf(用户字串指针, "%d %c %d", ebp-12, ebp-5, ebp-4)

继续看下去:

 1     ;; (ebp-12) stores the first int, compare to 7
 2
 3     ;; cmpl takes (ebp-12) as unsigned int
 4
 5     8048bc9:    83 7d f4 07              cmpl   $0x7,-0xc(%ebp)
 6
 7     ;; (unsigned)(ebp-12) > 7, jump to 0x8048c88, which will bomb
 8
 9     8048bcd:    0f 87 b5 00 00 00        ja     8048c88 <phase_3+0xf0>
10
11     ;; jump to *( 0x80497e8 + 4*(the first int) )
12
13     8048bd3:    8b 45 f4                 mov    -0xc(%ebp),%eax
14
15     8048bd6:    ff 24 85 e8 97 04 08     jmp    *0x80497e8(,%eax,4)

关键在于最后的跳转,根据输入的第一个整数确定跳转地址,

地址存储在(0x80497e8 + 4*(the first int)).

容易联想到(0x80497e8)存储着一个跳转表,用gdb查看之,x/10wx 0x80497e8:

    0x80497e8:    0x08048be0    0x08048c00    0x08048c16    0x08048c28

    0x80497f8:    0x08048c40    0x08048c52    0x08048c64    0x08048c76

    0x8049808:    0x67006425    0x746e6169

可以看到表中有很多个地址,先来看第一个地址指向的语句(对应的输入整数为0):

 1     ;; bl = 0x71
 2
 3     8048be0:    b3 71                    mov    $0x71,%bl
 4
 5     ;; if 0x309==777==the last int,
 6
 7     ;; jump to 0x8048c8f, which will compare the char
 8
 9     8048be2:    81 7d fc 09 03 00 00     cmpl   $0x309,-0x4(%ebp)
10
11     8048be9:    0f 84 a0 00 00 00        je     8048c8f <phase_3+0xf7>
12
13     8048bef:    e8 08 09 00 00           call   80494fc <explode_bomb>

可以看出,先把0x71存入bl,

然后若输入的最后一个整数==777的话,则跳转到0x8048c8f

 1     ;; after compare the last int, jump here
 2
 3     ;; bl = 0x71 = ‘q‘, compare to the char
 4
 5     ;; if ==, jump to 0x8048c99, and leave this function
 6
 7     8048c8f:    3a 5d fb                 cmp    -0x5(%ebp),%bl
 8
 9     8048c92:    74 05                    je     8048c99 <phase_3+0x101>
10
11     8048c94:    e8 63 08 00 00           call   80494fc <explode_bomb>

比较输入的字符是否等于‘q‘,若等于则defuse成功

因此,输入应为: "0 q 777"

当然此题应该有不止一个答案,选择跳转表中不同的地址会导致不同的输入.

Phase 4:

打开bomb_assembly_32.S,定位到<phase_4>函数,可以看到以下代码:

 1     ;; edx = pointer of input string
 2
 3     8048ce6:    8b 55 08                 mov    0x8(%ebp),%edx
 4
 5     8048ce9:    83 c4 fc                 add    $0xfffffffc,%esp
 6
 7     ;; eax = ebp-4
 8
 9     8048cec:    8d 45 fc                 lea    -0x4(%ebp),%eax
10
11     ;; push ebp-4
12
13     8048cef:    50                       push   %eax
14
15     ;; push $0x8049808
16
17     ;; x/s 0x804980: "%d"
18
19     8048cf0:    68 08 98 04 08           push   $0x8049808
20
21     ;; push pointer of input string
22
23     8048cf5:    52                       push   %edx
24
25     8048cf6:    e8 65 fb ff ff           call   8048860 <[email protected]>

就是读入一个整数,存入ebp-4

 1  ;; func4( input_number )
 2
 3     8048d11:    8b 45 fc                 mov    -0x4(%ebp),%eax
 4
 5     8048d14:    50                       push   %eax
 6
 7     8048d15:    e8 86 ff ff ff           call   8048ca0 <func4>
 8
 9
10
11     8048d1a:    83 c4 10                 add    $0x10,%esp
12
13     ;; eax should contain the return value of <func4>
14
15     ;; if eax == 0x37 == 55, defused
16
17     8048d1d:    83 f8 37                 cmp    $0x37,%eax
18
19     8048d20:    74 05                    je     8048d27 <phase_4+0x47>
20
21     8048d22:    e8 d5 07 00 00           call   80494fc <explode_bomb>

然后比较 func4( input_number )==55, 若等于则成功defuse.

接下来看看<func4>:

 1     ;; ebx = input_number
 2
 3     8048ca8:    8b 5d 08                 mov    0x8(%ebp),%ebx
 4
 5     ;; if input_number<=1, <func4> return 1
 6
 7     8048cab:    83 fb 01                 cmp    $0x1,%ebx
 8
 9     8048cae:    7e 20                    jle    8048cd0 <func4+0x30>
10
11
12
13     8048cb0:    83 c4 f4                 add    $0xfffffff4,%esp
14
15     ;; esi == func4( input_number-1 )
16
17     8048cb3:    8d 43 ff                 lea    -0x1(%ebx),%eax
18
19     8048cb6:    50                       push   %eax
20
21     8048cb7:    e8 e4 ff ff ff           call   8048ca0 <func4>
22
23     8048cbc:    89 c6                    mov    %eax,%esi
24
25
26
27     8048cbe:    83 c4 f4                 add    $0xfffffff4,%esp
28
29
30
31     ;; esi += func4( input_number-2 )
32
33     8048cc1:    8d 43 fe                 lea    -0x2(%ebx),%eax
34
35     8048cc4:    50                       push   %eax
36
37     8048cc5:    e8 d6 ff ff ff           call   8048ca0 <func4>
38
39     8048cca:    01 f0                    add    %esi,%eax

很明显是Fibonacci数列,  func4(n) = func4(n-1) + func4(n-2)

注意f(0)=f(1)=1, 通过简单计算知f(9)=55

因此输入应为55

Phase 5:

打开bomb_assembly_32.S,定位到<phase_5>函数,可以看到以下代码:

 1     ;; ebx = pointer of input
 2
 3     ;; push ebx onto stack
 4
 5     ;; call string_length
 6
 7     8048d34:    8b 5d 08                 mov    0x8(%ebp),%ebx
 8
 9     8048d37:    83 c4 f4                 add    $0xfffffff4,%esp
10
11     8048d3a:    53                       push   %ebx
12
13     8048d3b:    e8 d8 02 00 00           call   8049018 <string_length>
14
15
16
17     8048d40:    83 c4 10                 add    $0x10,%esp
18
19     ;; eax stores the return value of string_length
20
21     ;; if eax == 6, jump to 0x8048d4d
22
23     8048d43:    83 f8 06                 cmp    $0x6,%eax
24
25     8048d46:    74 05                    je     8048d4d <phase_5+0x21>
26
27     8048d48:    e8 af 07 00 00           call   80494fc <explode_bomb>

从上面代码可知,输入需要6个字符.

 1     ;; edx = 0
 2
 3     8048d4d:    31 d2                    xor    %edx,%edx
 4
 5     ;; ecx = ebp-8
 6
 7     8048d4f:    8d 4d f8                 lea    -0x8(%ebp),%ecx
 8
 9     ;; esi = 0x804b220
10
11     8048d52:    be 20 b2 04 08           mov    $0x804b220,%esi
12
13     ;; edx is a counter from 0 to 5
14
15     ;; al = (edx + ebx), then al reads a char each time
16
17     8048d57:    8a 04 1a                 mov    (%edx,%ebx,1),%al
18
19     ;; extract the low 4 bit of al
20
21     8048d5a:    24 0f                    and    $0xf,%al
22
23     ;; sign-extend al to eax
24
25     8048d5c:    0f be c0                 movsbl %al,%eax
26
27     ;; al = ( eax + 0x804b220 )
28
29     ;; x/16c 0x804b220:
30
31     ;; 0x804b220:    105 ‘i‘    115 ‘s‘    114 ‘r‘    118 ‘v‘    101 ‘e‘    97 ‘a‘    119 ‘w‘    104 ‘h‘
32
33     ;; 0x804b228:    111 ‘o‘    98 ‘b‘    112 ‘p‘    110 ‘n‘    117 ‘u‘    116 ‘t‘    102 ‘f‘    103 ‘g‘
34
35     8048d5f:    8a 04 30                 mov    (%eax,%esi,1),%al
36
37     ;; edx + ecx = al,
38
39     ;; notice that, ecx = ebp-8
40
41     ;; and edx is a counter from 0 to 5
42
43     8048d62:    88 04 0a                 mov    %al,(%edx,%ecx,1)
44
45     8048d65:    42                       inc    %edx
46
47     ;; loop
48
49     8048d66:    83 fa 05                 cmp    $0x5,%edx
50
51     8048d69:    7e ec                    jle    8048d57 <phase_5+0x2b>
52
53
54
55     ;; ebp-2 = 0, a terminal of string started from ebp-8
56
57     8048d6b:    c6 45 fe 00              movb   $0x0,-0x2(%ebp)
58
59     8048d6f:    83 c4 f8                 add    $0xfffffff8,%esp

上面代码的作用是循环读取6个输入字符中的每一字符input[k],

提取input[k]的低四位,把这四位构成的整数index当作索引,

查找0x804b220开始16个字节中存储的字符.

用gdb查看, x/16c 0x804b220:

1     0x804b220:    105 ‘i‘    115 ‘s‘    114 ‘r‘    118 ‘v‘    101 ‘e‘    97 ‘a‘    119 ‘w‘    104 ‘h‘
2
3     0x804b228:    111 ‘o‘    98 ‘b‘    112 ‘p‘    110 ‘n‘    117 ‘u‘    116 ‘t‘    102 ‘f‘    103 ‘g‘

获取0x804b220[ input[k] & 0xf ]后,将之copy至 (ebp-8)[k]

继续看:

 1     ;; x/s 0x804980b: "giants"
 2
 3     ;; push "giants"
 4
 5     8048d72:    68 0b 98 04 08           push   $0x804980b
 6
 7     ;; push ebp-8
 8
 9     8048d77:    8d 45 f8                 lea    -0x8(%ebp),%eax
10
11     8048d7a:    50                       push   %eax
12
13     ;; compare "giants" and the string started from ebp-8
14
15     8048d7b:    e8 b0 02 00 00           call   8049030 <strings_not_equal>
16
17     8048d80:    83 c4 10                 add    $0x10,%esp
18
19     8048d83:    85 c0                    test   %eax,%eax
20
21     ;; if two strings equal to each other, defused
22
23     8048d85:    74 05                    je     8048d8c <phase_5+0x60>
24
25     8048d87:    e8 70 07 00 00           call   80494fc <explode_bomb>

上面代码便是将ebp-18开始的字串和"giants"比较,若相等,则defused.

注意到 (ebp-18)[k] = 0x804b220[ input[k] & 0xf ]

1     0x804b220:    105 ‘i‘    115 ‘s‘    114 ‘r‘    118 ‘v‘    101 ‘e‘    97 ‘a‘    119 ‘w‘    104 ‘h‘
2
3     0x804b228:    111 ‘o‘    98 ‘b‘    112 ‘p‘    110 ‘n‘    117 ‘u‘    116 ‘t‘    102 ‘f‘    103 ‘g‘

因此,

1     input[0]&0xf = 0xf, input[1]&0xf = 0x0,
2
3     input[2]&0xf = 0x5, input[3]&0xf = 0xb,
4
5     input[4]&0xf = 0xd, input[5]&0xf = 0x1,

只要输入的各个字符的低四位符合上面就好,我个人选取了"opekma"

Phase 6:

写得太复杂了,各种内外循环,各种跳转,看得头晕,日后有闲再看.

现在先把能看懂的部份写出来:

 1     ;; edx = pointer of input
 2
 3     8048da1:    8b 55 08                 mov    0x8(%ebp),%edx
 4
 5     ;; (ebp-0x34) = $0x804b26c
 6
 7     8048da4:    c7 45 cc 6c b2 04 08     movl   $0x804b26c,-0x34(%ebp)
 8
 9     8048dab:    83 c4 f8                 add    $0xfffffff8,%esp
10
11     ;; read six numbers from input,
12
13     ;; and storse in the area started from ebp-18
14
15     8048dae:    8d 45 e8                 lea    -0x18(%ebp),%eax
16
17     8048db1:    50                       push   %eax
18
19     8048db2:    52                       push   %edx
20
21     8048db3:    e8 20 02 00 00           call   8048fd8 <read_six_numbers>

上面代码就是从输入读入6个整数,存入ebp-0x18,

初步怀疑0x804b26c地址存放着一个链表.

 1     ;; edi = 0
 2
 3     8048db8:    31 ff                    xor    %edi,%edi
 4
 5     8048dba:    83 c4 10                 add    $0x10,%esp
 6
 7     8048dbd:    8d 76 00                 lea    0x0(%esi),%esi
 8
 9     ;; eax = (ebp-0x18 + 4*edi) = six-number[edi]
10
11     ;; ebp-0x18 = the beginning address of the six numbers
12
13     ;; edi is a counter from 0 to 5
14
15     8048dc0:    8d 45 e8                 lea    -0x18(%ebp),%eax
16
17     8048dc3:    8b 04 b8                 mov    (%eax,%edi,4),%eax
18
19     ;; eax = six-number[edi]-1
20
21     8048dc6:    48                       dec    %eax
22
23     ;; if eax <= 5 , continue
24
25     8048dc7:    83 f8 05                 cmp    $0x5,%eax
26
27     8048dca:    76 05                    jbe    8048dd1 <phase_6+0x39>
28
29     8048dcc:    e8 2b 07 00 00           call   80494fc <explode_bomb>
30
31
32
33     ;; if edi+1 > 5, finish edi loop
34
35     8048dd1:    8d 5f 01                 lea    0x1(%edi),%ebx
36
37     8048dd4:    83 fb 05                 cmp    $0x5,%ebx
38
39     8048dd7:    7f 23                    jg     8048dfc <phase_6+0x64>
40
41
42
43     ;; (ebp-0x38) = edi*4
44
45     8048dd9:    8d 04 bd 00 00 00 00     lea    0x0(,%edi,4),%eax
46
47     8048de0:    89 45 c8                 mov    %eax,-0x38(%ebp)
48
49
50
51     ;; esi = ebp-18 = the beginning address of the six numbers
52
53     8048de3:    8d 75 e8                 lea    -0x18(%ebp),%esi
54
55     ;; edx = (ebp-0x38) = edi*4
56
57     ;; inner loops,
58
59     ;; ebx is the counter from edi+1 to 5
60
61     8048de6:    8b 55 c8                 mov    -0x38(%ebp),%edx
62
63     ;; eax = edx + esi = six-number[edi]
64
65     8048de9:    8b 04 32                 mov    (%edx,%esi,1),%eax
66
67     ;; compare six-number[edi] and six-number[edi+ebx]
68
69     8048dec:    3b 04 9e                 cmp    (%esi,%ebx,4),%eax
70
71     ;; if six-number[edi] != six-number[edi+1], continue
72
73     8048def:    75 05                    jne    8048df6 <phase_6+0x5e>
74
75     8048df1:    e8 06 07 00 00           call   80494fc <explode_bomb>
76
77     ;; ebx++
78
79     ;; if ebx<=5, jump to 0x8048de6, ebx loops
80
81     ;; else , finish ebx loop
82
83     8048df6:    43                       inc    %ebx
84
85     8048df7:    83 fb 05                 cmp    $0x5,%ebx
86
87     8048dfa:    7e ea                    jle    8048de6 <phase_6+0x4e>

内外两层循环,外层用edi计数,确保输入的6个整数不大于6,

内层用ebx计数,保证所有数字两两不相等.

再往后的代码异常混乱,各种链表离历,没空看....

先从网上获得答案:4 2 6 3 1 5

Secret Phase:

首先要找到<secret_phase>的入口,经搜索发现入口是在<phase_defused>里面.

先来看看<phase_defused>:

1     ;; every time call read_line, ( 0x804b480 )++
2
3     ;; only with 6 correct answer given ,will the secret phase appear
4
5     8049533:    83 3d 80 b4 04 08 06     cmpl   $0x6,0x804b480
6
7     804953a:    75 63                    jne    804959f <phase_defused+0x73>

(0x804b480)是一个计数器,每当调用一次<read_line>每自增1,因此只有6关全通才能打开隐藏关卡.

 1     ;; push ebp-0x50
 2
 3     804953c:    8d 5d b0                 lea    -0x50(%ebp),%ebx
 4
 5     804953f:    53                       push   %ebx
 6
 7     ;; push ebp-0x54
 8
 9     8049540:    8d 45 ac                 lea    -0x54(%ebp),%eax
10
11     8049543:    50                       push   %eax
12
13     ;; (gdb) x/s 0x8049d03
14
15     ;; 0x8049d03:    "%d %s"
16
17     8049544:    68 03 9d 04 08           push   $0x8049d03
18
19     ;; push the string stores in 0x804b770
20
21     ;; the address of input of phase 4
22
23     8049549:    68 70 b7 04 08           push   $0x804b770
24
25     804954e:    e8 0d f3 ff ff           call   8048860 <[email protected]>
26
27
28
29     ....
30
31
32
33     ;; (gdb) x/s 0x8049d09
34
35     ;; 0x8049d09:    "austinpowers"
36
37     804955e:    68 09 9d 04 08           push   $0x8049d09
38
39     ;; push the %s
40
41     8049563:    53                       push   %ebx
42
43     8049564:    e8 c7 fa ff ff           call   8049030 <strings_not_equal>

省略号上方的代码调用sscanf( (char *)0x804b770, "%d %s", (int *)(ebp-0x54), (char *)ebp-0x50 )

即从0x804b770读入一个整数和字串.

再看省略号下方的代码,比较读入的字串和"austinpowers", 若相等,则打开<secret_phase>

好了,现在问题是,如何把一个整数和"austinpowers"写入地址0x804b770?

回想前几关,写入字串都是通过read_line,所以猜想可能是在某一关的输入中多输入些内容以写入地址0x804b770.

用gdb查看前几关输入字串的指针,发现第4关的输入刚好是在地址0x804b770,而Phase 4只需输入一个数字,因此只需

在第4关的输入中多输入一个"austinpowers"即可进入<secret_phase>.

现在看看<secret_phase>:

 1     8048eef:    e8 08 03 00 00           call   80491fc <read_line>
 2
 3
 4
 5     8048ef4:    6a 00                    push   $0x0
 6
 7
 8
 9     ;; strtol( user input string, 0, 10)
10
11     ;; long int strtol(const char *nptr, char **endptr, int base);
12
13     ;; converts the initial part of the string in nptr to a long integer value according to the given base
14
15     8048ef6:    6a 0a                    push   $0xa
16
17     8048ef8:    6a 00                    push   $0x0
18
19     8048efa:    50                       push   %eax
20
21     8048efb:    e8 f0 f8 ff ff           call   80487f0 <[email protected]>

首先,读入一个字串,并用strtol将之转换为long int.

 1     ;; if fun7( 0x804b320, the input long int )
 2
 3     ;; x/d 0x804b320: (0x804b320) = 36
 4
 5     8048f17:    53                       push   %ebx
 6
 7     8048f18:    68 20 b3 04 08           push   $0x804b320
 8
 9     8048f1d:    e8 72 ff ff ff           call   8048e94 <fun7>
10
11
12
13     8048f22:    83 c4 10                 add    $0x10,%esp
14
15     ;; if fun7(0x804b320, the input long int) == 7, defused
16
17     8048f25:    83 f8 07                 cmp    $0x7,%eax
18
19     8048f28:    74 05                    je     8048f2f <secret_phase+0x47>
20
21     8048f2a:    e8 cd 05 00 00           call   80494fc <explode_bomb>

代码很简单,调用fun7( (void *)0x804b320, 输入的整数 ),若返回值==7, 则成功defused.

现在看看<fun7>:

  1     ;; edx = the first parameter, an address
  2
  3     8048e9a:    8b 55 08                 mov    0x8(%ebp),%edx
  4
  5     ;; eax = the input long int
  6
  7     8048e9d:    8b 45 0c                 mov    0xc(%ebp),%eax
  8
  9
 10
 11     ;; if edx != 0
 12
 13     8048ea0:    85 d2                    test   %edx,%edx
 14
 15     8048ea2:    75 0c                    jne    8048eb0 <fun7+0x1c>
 16
 17
 18
 19     8048ea4:    b8 ff ff ff ff           mov    $0xffffffff,%eax
 20
 21     8048ea9:    eb 37                    jmp    8048ee2 <fun7+0x4e>
 22
 23     8048eab:    90                       nop
 24
 25     8048eac:    8d 74 26 00              lea    0x0(%esi,%eiz,1),%esi
 26
 27
 28
 29     ;; if (edx) >= the input long int, jump to 0x8048ec5
 30
 31     8048eb0:    3b 02                    cmp    (%edx),%eax
 32
 33     8048eb2:    7d 11                    jge    8048ec5 <fun7+0x31>
 34
 35
 36
 37     ;; (0x804b320) < eax
 38
 39     8048eb4:    83 c4 f8                 add    $0xfffffff8,%esp
 40
 41     ;; <func7>( (edx+4) ,the input long int )
 42
 43     8048eb7:    50                       push   %eax
 44
 45     8048eb8:    8b 42 04                 mov    0x4(%edx),%eax
 46
 47     8048ebb:    50                       push   %eax
 48
 49     8048ebc:    e8 d3 ff ff ff           call   8048e94 <fun7>
 50
 51
 52
 53     ;; return eax *= 2, exit
 54
 55     8048ec1:    01 c0                    add    %eax,%eax
 56
 57     8048ec3:    eb 1d                    jmp    8048ee2 <fun7+0x4e>
 58
 59
 60
 61     ;; (edx) >= the input long int
 62
 63     ;; if (edx) == eax, return eax=0
 64
 65     8048ec5:    3b 02                    cmp    (%edx),%eax
 66
 67     8048ec7:    74 17                    je     8048ee0 <fun7+0x4c>
 68
 69
 70
 71     ;; (edx) > the input long int
 72
 73     8048ec9:    83 c4 f8                 add    $0xfffffff8,%esp
 74
 75     ;; <fun7>( (edx+8) ,the input long int )
 76
 77     8048ecc:    50                       push   %eax
 78
 79     8048ecd:    8b 42 08                 mov    0x8(%edx),%eax
 80
 81     8048ed0:    50                       push   %eax
 82
 83     8048ed1:    e8 be ff ff ff           call   8048e94 <fun7>
 84
 85
 86
 87     ;; fun7 return 2*eax + 1
 88
 89     8048ed6:    01 c0                    add    %eax,%eax
 90
 91     8048ed8:    40                       inc    %eax
 92
 93     8048ed9:    eb 07                    jmp    8048ee2 <fun7+0x4e>
 94
 95
 96
 97     8048edb:    90                       nop
 98
 99     8048edc:    8d 74 26 00              lea    0x0(%esi,%eiz,1),%esi
100
101
102
103     8048ee0:    31 c0                    xor    %eax,%eax

从上面代码可看出函数原型是:fun7( void *address, long int number ).

    当 number == *(int*)address, fun7( address, number) = 0

    当 number > *(int*)address, fun7( address, number) = 2*fun7( address+8, number ) + 1

    当 number < *(int*)address, fun7( address, number) = 2*fun7( address+4, number )

从上面可以看出, 上面的address表示的是棵二叉树(左子树的值<父节点的值, 右子树的值>父节点的值):

 1     struct BST
 2
 3     {
 4
 5         int num;
 6
 7         struct BST *left;
 8
 9         struct BST *right;
10
11     } *bst;

则上面的递推式可表示为:

    当 number == bst->num, fun7( bst, number ) = 0;

    当 number > bst->num, fun7( bst, number ) = 2*fun7( bst->right, number ) + 1;

    当 number < bst->num, fun7( bst, number ) = 2*fun7( bst->left, number );

鉴于<secret_phase>需要fun7( (struct BST *)0x804b320, number )返回7,一个奇数,所以第一步应该执行第二钟情况,

又经观察发现以下递推规律:

        fun7( (struct BST *)0x804b320, number )

    =   2 * fun7( (struct BST *)0x804b320->right, number ) + 1

    =   2 * (2 * fun7( (struct BST *)0x804b320->right->right, number ) + 1) + 1

    =   4 * fun7( (struct BST *)0x804b320->right->right, number ) + 3

    =   4 * (2 * fun7( (struct BST *)0x804b320->right->right->right, number ) + 1) + 3

    =   8 * fun7( (struct BST *)0x804b320->right->right->right, number ) + 7

因此当 number == (struct BST *)0x804b320->right->right->right->num, fun7便可返回7

用gdb查看,

    x/wx 0x804b320+8  ==>  0x0804b308 

    x/wx 0x804b308+8  ==>  0x0804b2d8

    x/wx 0x804b2d8+8  ==>  0x0804b278

    x/d  0x0804b278   ==>  1001

因此应输入1001

时间: 2024-10-02 00:08:14

CSAPP Lab2: Binary Bomb的相关文章

CSAPP lab2 二进制拆弹 binary bombs phase_5 施工中

phase_5 phase_5要求输入一个包含6个字符的字符串.phase_5函数从中读取这些信息,并判断其正确性,如果不正确,则炸弹爆炸. phase_5主要考察学生对指针(数组)机器级表示的掌握程度. 观察框架源文件bomb.c: 从上可以看出: 1.首先调用了read_line()函数,用于输入炸弹秘钥,输入放置在char* input中. 2.调用phase_5函数,输入参数即为input,可以初步判断,phase_5函数将输入的input字符串作为参数. 因此下一步的主要任务是从asm

csapp lab2

PHASE_1 使用到的命令: objdump -t bomb | less 我们得到的bomb文件是一个二进制文件,使用 objdump 可以得到反汇编的代码: -t 表示生成符号表,不必关注 . 开头的内容,可以看到phase_1/phase_2/.../phase_6,显然应该是对应不同的关卡: | less 表示使用一种方便浏览的分页方式,可以使用对应的快捷键辅助浏览. objdump -d bomb > bomb.txt -d将需要执行的内容生成反汇编代码. chmod 777 bom

CSAPP Lab:Bomb Lab(从拆弹到爆炸。。。

这个实验的要做的是用gdb逆向一段code,通过查看汇编代码以及单步调试找出这段code需要你填入的字符串,好像每个人都不一样,所以每个人都需要找到自己的拆弹密码,很有意思. 实验一共有6关,我们一关关来看一下: phase_1 打开bomb.c看些c源码(这里的核心方法已经被删除了,只能看到最外层的代码,但能得到一些线索). 很容易就发现这个phase_1方法是第一题的核心方法,直接逆向它,看下它的汇编. 第一句话是在为函数栈开辟空间,第二句话是关键,讲一个立即数赋值给%esi,然后就调用st

CSAPP 六个重要实验 lab2

CSAPP  &&  lab2 哈哈~ 不愧是"美国进口的六级炸弹"!爽歪歪的"升级打怪" 我把实验材料都上传到下面这个link了,0分下载(良心啊~) http://download.csdn.net/detail/u011368821/7892649 再一个实验指导说明供大家下载: http://download.csdn.net/detail/u011368821/7892677 对于Phase_1的分析: 0000000000400ef0 &

CSAPP第二個實驗bomblab

  文件和完整的word見github 3.1 阶段1的破解与分析 密码如下:I am not part of the problem. I am a Republican. 破解过程: 1.首先读主函数的汇编代码 发现这里是用了以一个函数<phase_1>(后面每一个炸弹都是对应的一个函数,在主函数中调用的表现形式一致,后面就不赘述了) 然后找到地址0x400e8d对应的函数<phase_1> 发现这里里面是把将立即数0x402470复制到%esi,然后调用一个<判断字符串

Bomblab

这个实验曾经做过 这次又做了一遍,对之前的过程作了补充 binary bomb 一.实验目的: 增强对程序机器级表示.汇编语言.调试器和逆向工程等理解. 二.实验要求: 1熟练使用gdb调试器和objdump: 2单步跟踪调试每一阶段的机器代码: 3理解汇编语言代码的行为或作用: 4"推断"拆除炸弹所需的目标字符串. 5在各阶段的开始代码前和引爆炸弹函数前设置断点,便于调试. 三.实验内容: 准备: 使用objdump –d bomb > asm.txt命令,将bomb程序逆成汇

CSAPP Bomb Lab记录

记录关于CSAPP 二进制炸弹实验过程 (CSAPP配套教学网站Bomb Lab自学版本,实验地址:http://csapp.cs.cmu.edu/2e/labs.html) (个人体验:对x86汇编寻址模式要有清晰的了解,如mov指令涉及的是计算出的地址所指向的存储单元的值,而lea指令保留的是计算出来的地址,数字是否加$表示常数的问题等: 实验中涉及的跳表的存储方式.链表的处理等是C语言的汇编语言实现方式,处理起来较为复杂,但可对这些方式的对象底层实现方式有一个较为清晰的了解: 涉及指针操作

深入理解计算机系统 (CS:APP) Lab2 - Bomb Lab 解析

原文地址:https://billc.io/2019/04/csapp-bomblab/ 写在前面 CS:APP是这学期的一门硬核课程,应该是目前接触到最底层的课程了.学校的教学也是尝试着尽量和CMU同步,课件和习题都直接照搬原版.包括现在着手的第二个实验室Bomb Lab.这个lab很有意思,没有提供全部c语言代码,需要手动根据反汇编语言推测在每一个阶段需要输入的内容,输入正确就可以进入下一个阶段. 理论上每个人获取到的lab都是不一样的,但对于自学学生而言在官网http://csapp.cs

CSAPP 3e: Bomb lab (secret_phase)

这是秘密关卡,需要通过主动调用secret_phase函数才能触发,可以通过call secret 或者jump *0x地址来调用. 贴出函数:(fun7函数部分没有注释,后边续上了手写的图来解析这个函数了) 0000000000401204 <fun7>: 401204: 48 83 ec 08 sub $0x8,%rsp 401208: 48 85 ff test %rdi,%rdi 40120b: 74 2b je 401238 <fun7+0x34>;如果%rdi==0,r