hnu bomblab拆炸弹实验

几乎是大学上到现在最难的一次实验了吧,发出来供大家参考。

注意每个人拿到的bomb文件都不一样,只能参考一下思路哦...相信我写的很详细了,大家应该都能在其中获得一些新思路吧

BOMBLAB

计科23xx xxx 2023xxxxxxxx

【实验目的】

通过反汇编和调试可执行程序来理解程序的机器级表示。

【实验环境】

远程连接到linux服务器下进行试验:已安装gcc,gdb等基础工具

内核版本和系统架构:

Linux slave2 3.10.0-1160.102.1.el7.x86_64

CPU信息:Intel(R) Xeon(R) Bronze 3206R CPU @ 1.90GHz

 

【实验过程】

ssh连接到服务器,进入linux环境:

拿到文件夹后,里面的内容:

Bomb是可执行文件,我们先看一下bomb.c

发现关键的函数都被隐藏了,主函数大致的意思是:有六个关卡,每个关卡要输入一个特定的东西才能解除bomb,否则就会爆炸。

       最后,提示了something missing?可能存在隐藏关卡!

执行一下bomb文件:

确实是这样,我们要输入六个phases以拆除炸弹。

反汇编bomb文件得到汇编代码,把它重定向到1.txt,易于分析汇编代码。

开始分析汇编代码:

 

Phase_1:函数调用,简单的字符串比较

8048b33:    83 ec 14                  sub    $0x14,%esp

 8048b36:    68 bc 9f 04 08              push   $0x8049fbc

 8048b3b:    ff 74 24 1c          pushl  0x1c(%esp)

 8048b3f:     e8 84 04 00 00             call   8048fc8 <strings_not_equal>

 string1是输入的字符串,string2是内存中的字符串,gdb查看内存中的字符串内容即可。

答案为I am not part of the problem. I am a Republican.

*调用stringnotequal

08048fc8 <strings_not_equal>:

 8048fc8:       57                          push   %edi

 8048fc9:       56                          push   %esi

 8048fca:       53                          push   %ebx

 8048fcb:       8b 5c 24 10          mov    0x10(%esp),%ebx

 8048fcf: 8b 74 24 14          mov    0x14(%esp),%esi

mov 0x10(%esp),%ebx 和 mov 0x14(%esp),%esi 分别将第一个和第二个字符串指针加载到ebx和esi寄存器中。

调用string_length函数两次,分别计算两个字符串的长度,结果存入edi(第一个字符串长度)和eax(第二个字符串长度)。比较两长度,若不等则直接返回1(字符串不等)。后面再逐字比较。

Res返回1:两字符串长度不同,或长度相同但内容不同。

返回0:两字符串完全一致(长度和内容均相同)。

 

 8048b44:    83 c4 10                  add    $0x10,%esp

 8048b47:    85 c0                    test   %eax,%eax

//检测eax中(函数的返回值)是否为0,若为0则设置ZF=1

 8048b49:    74 05                    je     8048b50 <phase_1+0x1d>

//ZF=1,跳转,不执行explode_bomb,否则引爆。

 8048b4b:    e8 6f 05 00 00              call   80490bf <explode_bomb>

 8048b50:    83 c4 0c                  add    $0xc,%esp

 8048b53:    c3                       ret

 

 

 

 

 

 

 

08048b54 <phase_2>:数组+循环结构

 8048b54:    56                      push   %esi

 8048b55:    53                      push   %ebx

 8048b56:    83 ec 2c                  sub    $0x2c,%esp

Esi和ebx入栈,esp-2c 下移,为存储临时变量腾出空间。

 

 8048b59:        65 a1 14 00 00 00          mov    %gs:0x14,%eax

 8048b5f: 89 44 24 24               mov    %eax,0x24(%esp)

 8048b63:        31 c0                     xor    %eax,%eax 清零eax,防止对后面有影响。

一种保护机制,从线程局部存储(TLS)中读取一个随机值(称为 "Canary" 或 "金丝雀值")。

函数返回前:检查栈中的 Canary 值是否被篡改,若被篡改则触发崩溃(防止攻击者利用栈溢出)。

8048b65:      8d 44 24 0c               lea    0xc(%esp),%eax  //eax=esp-28

 8048b69:    50                      push   %eax //esp-28=0xbffff1a4入栈。

 8048b6a:    ff 74 24 3c          pushl  0x3c(%esp)//esp+4中的值入栈

 8048b6e:    e8 71 05 00 00             call   80490e4 <read_six_numbers>

//函数调用过程见后面两页

//函数返回,esp恢复为esp-3c=0xffff190 函数中的操作让esp-14到esp-28存入了数组的六个int。

add:esp-28    esp-24    esp-20      esp-1c       esp-18        esp-14

a[0]

a[1]

a[2]

a[3]

a[4]

a[5]

 

 8048b73:    83 c4 10                  add    $0x10,%esp

//esp-3c+10=esp-2c 更新esp的值,为后面访问数组值准备。

 8048b76:    83 7c 24 04 00             cmpl   $0x0,0x4(%esp)

//比较esp-2c+4=esp-28即a[0]与0的大小

 8048b7b:    75 07                    jne    8048b84 <phase_2+0x30>

//不一样则跳转到爆炸函数,所以a[0]=0

 8048b7d:    83 7c 24 08 01             cmpl   $0x1,0x8(%esp)

8048b82:      74 05                    je     8048b89 <phase_2+0x35>

//比较esp-24即a[1]和1,一样则跳过爆炸函数,所以a[1]=1

 8048b84:    e8 36 05 00 00             call   80490bf <explode_bomb>

 8048b89:    8d 5c 24 04               lea    0x4(%esp),%ebx

 8048b8d:    8d 74 24 14               lea    0x14(%esp),%esi

//设置ebx的值为esp-28,esi的值为esp-18

 8048b91:    8b 43 04                 mov    0x4(%ebx),%eax

 8048b94:    03 03                    add    (%ebx),%eax

//将ebx的值加4移到eax,并将原来的ebx内存中对应的数和现在内存中对应的数相加。相当于a[0]+a[1]

 8048b96:    39 43 08                  cmp    %eax,0x8(%ebx)

//比较a[0]+a[1]的值和a[2]的值,不相等则爆炸。

 8048b99:    74 05                    je     8048ba0 <phase_2+0x4c>

 8048b9b:    e8 1f 05 00 00              call   80490bf <explode_bomb>

 8048ba0:    83 c3 04                  add    $0x4,%ebx

 8048ba3:    39 f3                     cmp    %esi,%ebx

8048ba5:      75 ea                    jne    8048b91 <phase_2+0x3d>

//直到ebx=esi才停止循环,

相当于对整个数组进行a[i]+a[i+1]=a[i+2]的操作(i=0-3)

对应的c伪代码大致如下:

最终的结果为0 1 1 2 3 5,(其实更多的数也可以,只需要前面六个是011235即可)

 8048ba7:        8b 44 24 1c               mov    0x1c(%esp),%eax

 8048bab:        65 33 05 14 00 00 00       xor    %gs:0x14,%eax

 8048bb2:        74 05                    je     8048bb9 <phase_2+0x65>

 8048bb4:        e8 d7 fb ff ff       call   8048790 <__stack_chk_fail@plt>

Canary值的校验,如果存储的值与gs+14中的值不等,说明发生了缓冲区溢出,程序终止。

 8048bb9:    83 c4 24                  add    $0x24,%esp

 8048bbc:    5b                      pop    %ebx

 8048bbd:    5e                      pop    %esi

 8048bbe:    c3                       ret   

上面四句是函数返回操作。

 

 

 

调用的函数

080490e4 <read_six_numbers>:

 80490e4:     83 ec 0c                  sub    $0xc,%esp

 80490e7:     8b 44 24 14               mov    0x14(%esp),%eax

 80490eb:    8d 50 14                 lea    0x14(%eax),%edx

//此时eax的值为esp-38中放的值,也就是esp-28,加上14变为esp-14,移到edx内,再压栈

 80490ee:     52                      push   %edx

 80490ef:      8d 50 10                 lea    0x10(%eax),%edx

 80490f2:     52                      push   %edx

 80490f3:     8d 50 0c                  lea    0xc(%eax),%edx

 80490f6:     52                      push   %edx

 80490f7:     8d 50 08                 lea    0x8(%eax),%edx

 80490fa:      52                      push   %edx

 80490fb:     8d 50 04                 lea    0x4(%eax),%edx

 80490fe:      52                      push   %edx

 80490ff:      50                      push   %eax

在栈空间内,分配数组中六个数字的地址。随后用scanf将六个数读入相对应的内存(较高地址的栈)中。

 8049100:    68 53 a1 04 08             push   $0x804a153

 8049105:    ff 74 24 2c          pushl  0x2c(%esp)

而esp-64+2c=esp+38 其中存储了esp-28的值,也就是数组的首地址

 8049109:    e8 02 f7 ff ff          call   8048810 <__isoc99_sscanf@plt>

//调用sscanf 两个参数分别为数组首地址和 %d,%d,%d,%d,%d,%d

 804910e:     83 c4 20                  add    $0x20,%esp

 8049111:    83 f8 05                   cmp    $0x5,%eax

//如果输入的数字个数小于等于5 则引爆炸弹。

 8049114:    7f 05                     jg     804911b <read_six_numbers+0x37>

 8049116:    e8 a4 ff ff ff           call   80490bf <explode_bomb>

 804911b:    83 c4 0c                  add    $0xc,%esp

 804911e:     c3                       ret       

08048bbf <phase_3>:条件分支,跳转表

 8048bbf:     83 ec 1c             sub    $0x1c,%esp //esp-1c腾出空间给临时变量

 8048bc2:     65 a1 14 00 00 00           mov    %gs:0x14,%eax

 8048bc8:     89 44 24 0c                mov    %eax,0xc(%esp)

 8048bcc:     31 c0                    xor    %eax,%eax

//Canary 值初始化

 8048bce:     8d 44 24 08               lea    0x8(%esp),%eax

 8048bd2:    50                      push   %eax

 8048bd3:    8d 44 24 08               lea    0x8(%esp),%eax

 8048bd7:    50                      push   %eax

 8048bd8:    68 5f a1 04 08              push   $0x804a15f

 8048bdd:    ff 74 24 2c          pushl  0x2c(%esp)

 8048be1:    e8 2a fc ff ff           call   8048810 <__isoc99_sscanf@plt>

//读取两个数。栈空间如图所示:

esp+8中存储的是0x0804c480,即为这两个输入数字存储的地方,我们可以进行gdb调试,断点设置在*8048be6处,输入2,3后查看对应的内存空间可以发现

在内存中,2,3以int存储在esp-18和esp-14的地址空间中。如图:

 

 8048be6:    83 c4 10                  add    $0x10,%esp

 8048be9:    83 f8 01                   cmp    $0x1,%eax

 8048bec:     7f 05                     jg     8048bf3 <phase_3+0x34>

//对scanf的返回值和1作比较,若输入的个数小于等于1,则爆炸。

 8048bee:     e8 cc 04 00 00              call   80490bf <explode_bomb>

 8048bf3:     83 7c 24 04 07             cmpl   $0x7,0x4(%esp)

 8048bf8:     77 3c                    ja     8048c36 <phase_3+0x77>

 8048bfa:     8b 44 24 04               mov    0x4(%esp),%eax

//将7与a[0]作比较,(无符号数)若a[0]>7则爆炸,在有符号数中

:>7或<0则爆炸。

 8048bfe: ff 24 85 20 a0 04 08      jmp    *0x804a020(,%eax,4)

初始化跳转表,跳到0x804a020+4*a[0]的位置 通过gdb查看对应的内存可得:

跳转表如下:

a[0]的值

跳转的地址

跳转执行的操作

0

0x08048c42

比较a[1]与0x329(809)

1

0x08048c50

比较a[1]与0x27f(639)

2

0x08048c0c

比较a[1]与0x336(822)

3

0x08048c13

比较a[1]与0x173(371)

4

0x08048c1a

比较a[1]与0x2f2(754)

5

0x08048c21

比较a[1]与0x275(629)

6

0x08048c28

比较a[1]与0x7a(122)

7

0x08048c2f

比较a[1]与0x370(880)

>7或<0

0x08048c36

Bomb!

以下均为跳转表的具体实现部分:

 8048c05:     b8 7f 02 00 00              mov    $0x27f,%eax

 8048c0a:     eb 3b                    jmp    8048c47 <phase_3+0x88>

 8048c0c:     b8 36 03 00 00             mov    $0x336,%eax

 8048c11:     eb 34                    jmp    8048c47 <phase_3+0x88>

 8048c13:     b8 73 01 00 00             mov    $0x173,%eax

 8048c18:     eb 2d                    jmp    8048c47 <phase_3+0x88>

 8048c1a:     b8 f2 02 00 00              mov    $0x2f2,%eax

 8048c1f:      eb 26                    jmp    8048c47 <phase_3+0x88>

 8048c21:     b8 75 02 00 00             mov    $0x275,%eax

 8048c26:     eb 1f                     jmp    8048c47 <phase_3+0x88>

 8048c28:     b8 7a 00 00 00             mov    $0x7a,%eax

 8048c2d:     eb 18                    jmp    8048c47 <phase_3+0x88>

 8048c2f:      b8 70 03 00 00             mov    $0x370,%eax

 8048c34:     eb 11                    jmp    8048c47 <phase_3+0x88>

 8048c36:     e8 84 04 00 00             call   80490bf <explode_bomb>

 8048c3b:     b8 00 00 00 00             mov    $0x0,%eax

 8048c40:     eb 05                    jmp    8048c47 <phase_3+0x88>

 8048c42:     b8 29 03 00 00             mov    $0x329,%eax

 8048c47:     3b 44 24 08               cmp    0x8(%esp),%eax

 8048c4b:     74 05                    je     8048c52 <phase_3+0x93>

 8048c4d:     e8 6d 04 00 00             call   80490bf <explode_bomb>

由跳转表,可知答案如下:(不唯一)

 8048c52:     8b 44 24 0c               mov    0xc(%esp),%eax

 8048c56:     65 33 05 14 00 00 00 xor    %gs:0x14,%eax

 8048c5d:     74 05                    je     8048c64 <phase_3+0xa5>

 8048c5f:      e8 2c fb ff ff          call   8048790 <__stack_chk_fail@plt>

//Canary 值校验,不满足则退出函数。

 8048c64:     83 c4 1c                  add    $0x1c,%esp

 8048c67:     c3                       ret   

//函数返回

 

 

08048cab <phase_4>:递归

 8048cab:     83 ec 1c                  sub    $0x1c,%esp

 8048cae:     65 a1 14 00 00 00           mov    %gs:0x14,%eax

 8048cb4:     89 44 24 0c                mov    %eax,0xc(%esp)

 8048cb8:     31 c0                    xor    %eax,%eax

 8048cba:     8d 44 24 04               lea    0x4(%esp),%eax

 8048cbe:     50                      push   %eax

 8048cbf:      8d 44 24 0c               lea    0xc(%esp),%eax

 8048cc3:     50                      push   %eax

 8048cc4:     68 5f a1 04 08              push   $0x804a15f

 8048cc9:     ff 74 24 2c          pushl  0x2c(%esp)

 8048ccd:     e8 3e fb ff ff          call   8048810 <__isoc99_sscanf@plt>

前面和上一问一致,都是输入两个数。(顺序交换)

 8048cd2:     83 c4 10                  add    $0x10,%esp

 8048cd5:     83 f8 02                   cmp    $0x2,%eax

 8048cd8:     75 0c                    jne    8048ce6 <phase_4+0x3b>

//输入的不是两个数 则爆炸

 8048cda:     8b 44 24 04               mov    0x4(%esp),%eax

 8048cde:     83 e8 02                  sub    $0x2,%eax

 8048ce1:     83 f8 02                   cmp    $0x2,%eax

//2<=a[1]<=4 否则爆炸。

 8048ce4:     76 05                    jbe    8048ceb <phase_4+0x40>

 8048ce6:     e8 d4 03 00 00             call   80490bf <explode_bomb>

 8048ceb:     83 ec 08                  sub    $0x8,%esp

 8048cee:     ff 74 24 0c          pushl  0xc(%esp)

 8048cf2:      6a 08                    push   $0x8

//原来放scanf参数的地方压入a[1]和立即数8,作为func4的两个参数。调用func4.

 8048cf4:      e8 6f ff ff ff            call   8048c68 <func4>

//调用func4过程见后面

 8048cf9:      83 c4 10                  add    $0x10,%esp

 8048cfc:      3b 44 24 08               cmp    0x8(%esp),%eax

//func4(8,a[1]),return的值和a[0]相等即结束,否则爆炸。

//这边我们先用取巧的方法:用gdb调试 输入a[1]=2,a[0]随便输入一个。在函数前后分别打两个断点,我们观察一下func4运行结束后eax中的值。

用c指令跳过函数func4内部。

查看寄存器:

Eax=108,说明返回值为108,有一组正确答案为108 2.

a[1]=3时,类似方法 eax=162

a[1]=4时,类似方法 eax=216

所有的答案:

2 108

3 162

4 216

8048d00:      74 05                    je     8048d07 <phase_4+0x5c>

 8048d02:    e8 b8 03 00 00             call   80490bf <explode_bomb>、

 

 8048d07:    8b 44 24 0c               mov    0xc(%esp),%eax

 8048d0b:    65 33 05 14 00 00 00 xor    %gs:0x14,%eax

 8048d12:    74 05                    je     8048d19 <phase_4+0x6e>

 8048d14:    e8 77 fa ff ff          call   8048790 <__stack_chk_fail@plt>

 8048d19:    83 c4 1c                  add    $0x1c,%esp

 8048d1c:     c3                       ret   

校验canary值和返回

Func4分析

08048c68 <func4>:

原先的栈结构,调用的参数分别为8,a[1]

函数的流程图                           递归调用的大致过程

 

函数递归调用,栈不断向下增长。函数返回,依次弹栈,直到返回入口处的函数。

 

当val<=0 返回0;val=1,返回n

Else:return func(val-1,n)+func(val-2,n)+n

 8048c68:     57                      push   %edi

 8048c69:     56                      push   %esi

 8048c6a:     53                      push   %ebx

 8048c6b:     8b 5c 24 10               mov    0x10(%esp),%ebx

 8048c6f:      8b 7c 24 14               mov    0x14(%esp),%edi

 8048c73:     85 db                    test   %ebx,%ebx

 8048c75:     7e 2b                    jle    8048ca2 <func4+0x3a>

 8048c77:     89 f8                     mov    %edi,%eax

 8048c79:     83 fb 01                  cmp    $0x1,%ebx

 8048c7c:     74 29                    je     8048ca7 <func4+0x3f>

 8048c7e:     83 ec 08                  sub    $0x8,%esp

 8048c81:     57                      push   %edi

 8048c82:     8d 43 ff                   lea    -0x1(%ebx),%eax

 8048c85:     50                      push   %eax

 8048c86:     e8 dd ff ff ff           call   8048c68 <func4>

 8048c8b:     83 c4 08                  add    $0x8,%esp

 8048c8e:     8d 34 07                 lea    (%edi,%eax,1),%esi

 8048c91:     57                      push   %edi

 8048c92:     83 eb 02                  sub    $0x2,%ebx

 8048c95:     53                      push   %ebx

 8048c96:     e8 cd ff ff ff           call   8048c68 <func4>

 8048c9b:     83 c4 10                  add    $0x10,%esp

 8048c9e:     01 f0                     add    %esi,%eax

 8048ca0:     eb 05                    jmp    8048ca7 <func4+0x3f>

 8048ca2:     b8 00 00 00 00             mov    $0x0,%eax

 8048ca7:     5b                      pop    %ebx

 8048ca8:     5e                      pop    %esi

 8048ca9:     5f                       pop    %edi

 8048caa:     c3                       ret   

相当于

 

 

 

 

 

 

 

 

 

 

 

 

 

 

08048d1d <phase_5>:ASCII密码

 8048d1d:    53                      push   %ebx

 8048d1e:    83 ec 24                  sub    $0x24,%esp

 8048d21:    8b 5c 24 2c                mov    0x2c(%esp),%ebx

//将输入字符串的地址移到ebx

 8048d25:    65 a1 14 00 00 00           mov    %gs:0x14,%eax

 8048d2b:    89 44 24 18               mov    %eax,0x18(%esp)

8048d2f: 31 c0                    xor    %eax,%eax

//保存ebx canary值初始化

 8048d31:    53                      push   %ebx

用gdb输入为abcdef

查看此时ebx中内存对应的string 为“abcdef” 对此调用string_length,返回eax=6。

 8048d32:    e8 72 02 00 00             call   8048fa9 <string_length>

 8048d37:    83 c4 10                  add    $0x10,%esp

 8048d3a:    83 f8 06                   cmp    $0x6,%eax

 8048d3d:    74 05                    je     8048d44 <phase_5+0x27>

 8048d3f:     e8 7b 03 00 00             call   80490bf <explode_bomb>

//字符串的长度不等于6,则爆炸

 8048d44:    b8 00 00 00 00             mov    $0x0,%eax

 8048d49:    0f b6 14 03                movzbl (%ebx,%eax,1),%edx

//Ebx对应的内存中存储abcdef的ascii码,movzbl指令获取第一个字符的ascii码值,前面补满0,填到edx中。

 8048d4d:    83 e2 0f                   and    $0xf,%edx

//edx和0xf按位与,0xf是00000000000000000000000000001111,也就是获取edx最后4bit的值,也就是第一个字符ascii码的后面一位。

 8048d50:    0f b6 92 40 a0 04 08 movzbl 0x804a040(%edx),%edx

以上是0x804a040附近的内存。加上edx中的偏移量,获取内存移到edx。

 8048d57:    88 54 04 05               mov    %dl,0x5(%esp,%eax,1)

Edx中的低16位移动到esp+5(0xffffd270+5)

 8048d5b:    83 c0 01                  add    $0x1,%eax

 8048d5e:    83 f8 06                   cmp    $0x6,%eax

 8048d61:    75 e6                    jne    8048d49 <phase_5+0x2c>

//循环结构,结果:把六个字符的ascii码的后面一位作为偏移量,加上0x804a040,得到的内存中的值放在esp+5(0xffffd270+5)到esp+10(0xffffd270+10)

 8048d63:    c6 44 24 0b 00             movb   $0x0,0xb(%esp)

 8048d68:    83 ec 08                  sub    $0x8,%esp

 8048d6b:    68 16 a0 04 08             push   $0x804a016

 8048d70:    8d 44 24 11               lea    0x11(%esp),%eax

 8048d74:    50                      push   %eax

 8048d75:    e8 4e 02 00 00             call   8048fc8 <strings_not_equal>

 8048d7a:    83 c4 10                  add    $0x10,%esp

 8048d7d:    85 c0                    test   %eax,%eax

 8048d7f:     74 05                    je     8048d86 <phase_5+0x69>

 8048d81:    e8 39 03 00 00             call   80490bf <explode_bomb>

观察以上汇编,与第一题类似,调用了strings_not_equal,不一样则爆炸。两个参数:一个是前面存储在esp+5(0xffffd270+5)到esp+10(0xffffd270+10)中的值,一个是存储在0x804a016中的字符串,用gdb查看。

每个数对应的ascii码如下:

每个ASCII码对应一个0x804a040+偏移量处的内存,这个偏移量是输入字符串的ASCII码值的后面一位。

偏移量对应:

也就是说,输入的六个字符的ASCII码值应该为:

0x*A   0x*4   0x*f   0x*5   0x*6   0x*7(*可以为任何值)

答案是六个任意组合的字符:

例如

jdoefg                        JDOEFG                          jdo567

*$/%&'                        J4o567                          *$/567

 8048d86:    8b 44 24 0c               mov    0xc(%esp),%eax

 8048d8a:    65 33 05 14 00 00 00 xor    %gs:0x14,%eax

 8048d91:    74 05                    je     8048d98 <phase_5+0x7b>

 8048d93:    e8 f8 f9 ff ff           call   8048790 <__stack_chk_fail@plt>

 8048d98:    83 c4 18                  add    $0x18,%esp

 8048d9b:    5b                      pop    %ebx

 8048d9c:     c3                       ret   

校验canary值和返回

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

08048d9d <phase_6>:链表

 8048d9d:    56                      push   %esi

 8048d9e:    53                      push   %ebx

 8048d9f:     83 ec 4c                  sub    $0x4c,%esp

 8048da2:    65 a1 14 00 00 00           mov    %gs:0x14,%eax

 8048da8:    89 44 24 44               mov    %eax,0x44(%esp)

 8048dac:     31 c0                    xor    %eax,%eax

//函数入口,canary值初始化

 8048dae:     8d 44 24 14               lea    0x14(%esp),%eax

 8048db2:    50                      push   %eax

 8048db3:    ff 74 24 5c          pushl  0x5c(%esp)

 8048db7:    e8 28 03 00 00             call   80490e4 <read_six_numbers>

将六个数读取到相应内存,栈结构如图所示。

8048dbc:      83 c4 10                  add    $0x10,%esp

8048dbf: mov    $0x0,%esi              esi = 0(循环计数器)

8048dc4:      mov    0xc(%esp,%esi,4),%eax  eax = 第esi个数(esp+0xc + esi*4)

8048dc8:      sub    $0x1,%eax           eax = num-1

8048dcb:      cmp    $0x5,%eax          检查 num-1 ∈ [0,5](即 num ∈ [1,6])

8048dce:       76 05                    jbe    8048dd5 <phase_6+0x38>

不∈ [1,6]就爆炸

8048dd0:      e8 ea 02 00 00             call   80490bf <explode_bomb>

8048dd5:      83 c6 01                  add    $0x1,%esi 循环。

8048dd8:      83 fe 06                   cmp    $0x6,%esi

8048ddb:      74 33                    je     8048e10 <phase_6+0x73>

上面汇编是判断输入的6个数字都要在1到6之间。循环结束,跳转到地址8048e10处继续执行

8048ddd:      mov    %esi,%ebx              ebx = esi(内部循环起始索引)

8048ddf: mov    0xc(%esp,%ebx,4),%eax   eax = 当前数(第ebx个,ebx >= esi+1)

8048de3:      cmp    %eax,0x8(%esp,%esi,4)   比较第esi个数是否与第ebx个数相等

8048de7:      75 05                    jne    8048dee <phase_6+0x51>

8048de9:      e8 d1 02 00 00             call   80490bf <explode_bomb> 相等就爆炸

上面是验证数值是否唯一的过程,

比如先验证完a[0]是不是∈ [1,6],接着访问a[1]~a[5],如果与a[0]相等就爆炸

以上的代码要求输入 ​6 个 ​1~6 之间的不重复整数。

 8048dee:     83 c3 01                  add    $0x1,%ebx

 8048df1:     83 fb 05                  cmp    $0x5,%ebx

 8048df4:     7e e9                    jle    8048ddf <phase_6+0x42>

 8048df6:     eb cc                    jmp    8048dc4 <phase_6+0x27>

 8048df8:     8b 52 08                 mov    0x8(%edx),%edx

 8048dfb:     83 c0 01                  add    $0x1,%eax

 8048dfe:     39 c8                    cmp    %ecx,%eax

 8048e00:     75 f6                     jne    8048df8 <phase_6+0x5b>

 8048e02:     89 54 b4 24               mov    %edx,0x24(%esp,%esi,4)

 8048e06:     83 c3 01                  add    $0x1,%ebx

 8048e09:     83 fb 06                  cmp    $0x6,%ebx

 8048e0c:     75 07                    jne    8048e15 <phase_6+0x78>

 8048e0e:     eb 1c                    jmp    8048e2c <phase_6+0x8f>

 8048e10:     bb 00 00 00 00             mov    $0x0,%ebx

 8048e15:     89 de                    mov    %ebx,%esi

 8048e17:     8b 4c 9c 0c                mov    0xc(%esp,%ebx,4),%ecx

 8048e1b:    b8 01 00 00 00             mov    $0x1,%eax

 8048e20:     ba 3c c1 04 08             mov    $0x804c13c,%edx

 8048e25:     83 f9 01                   cmp    $0x1,%ecx

 8048e28:     7f ce                     jg     8048df8 <phase_6+0x5b>

 8048e2a:     eb d6                    jmp    8048e02 <phase_6+0x65>

//以上代码,将链表节点的地址存入栈中。

分析时发现一个立即数0x804c13c,存入edx中后有这样的寻址:8048df8:mov    0x8(%edx),%edx。我们查看此处附近的内存:

自然联想到node是链表的节点,进一步观察可得:每个节点占12个字节,前四个字节是节点的值,中间四个是节点的编号,最后四个存储下一个节点的地址。

代码做了些什么呢?我们逐行分析,8048e10 – 8048e25

这段取出了a[0],若<1则跳转到8048df8,>=1跳转到8048e02

而8048df8~8048e02中的内容是通过a[0]的值来确定需要访问的链表节点地址。

mov  %edx,0x24(%esp,%esi,4)这句将节点地址移到以esp-20开头的栈空间中(位于存储数组的上方)

然后,通过跳转实现循环,对数组中的每个元素实行一样的操作。

分析完成后,我们用gdb查看内存验证一下:(输入的是2 3 1 5 6 4)

可见24c~260中存储了2 3 1 5 6 4。而264开始,存储了6个链表节点的地址,分别是2,3,1,5,6,4号节点的地址!我们的分析是正确的。

8048e2c:       mov    0x24(%esp),%ebx   ebx = 第一个节点地址(新链表头)

8048e30:      lea    0x24(%esp),%eax   eax = 节点地址数组起始地址

8048e34:      lea    0x38(%esp),%esi   esi = 节点地址数组结束地址

8048e38:      mov    %ebx,%ecx         ecx = 当前节点

8048e3a:       mov    0x4(%eax),%edx   edx = 下一个节点地址(eax+4)

8048e3d:      mov    %edx,0x8(%ecx)   当前节点->next = edx

8048e40:      add    $0x4,%eax        eax +=4(移动到下个节点地址)

8048e43:      mov    %edx,%ecx         ecx = edx(更新当前节点)

8048e45:      cmp    %esi,%eax         检查是否处理完所有节点

8048e47:      jne    8048e3a            未完成则继续循环

8048e49:      movl   $0x0,0x8(%edx)    最后一个节点->next = NULL

以上,将节点地址刚才存储的数组顺序连接成新链表。

用gdb查看内存

改变链表顺序实则就是在改变最后4个字节中的地址值!

 8048e50:     be 05 00 00 00             mov    $0x5,%esi          初始化循环的变量

 8048e55:     8b 43 08                 mov    0x8(%ebx),%eax

//eax中存储第二个节点的地址

 8048e58:     8b 00                    mov    (%eax),%eax

//eax中存储第二个地址的数值

 8048e5a:     39 03                    cmp    %eax,(%ebx)

//比较第一个节点的值和第二个节点的值,node1.val<=node2.val则不爆炸

然后循环访问每个元素,其实就是对原链表值做个降序排序。

 8048e5c:     7e 05                    jle    8048e63 <phase_6+0xc6>

 8048e5e:     e8 5c 02 00 00             call   80490bf <explode_bomb>

 8048e63:     8b 5b 08                 mov    0x8(%ebx),%ebx

 8048e66:     83 ee 01                  sub    $0x1,%esi

 8048e69:     75 ea                    jne    8048e55 <phase_6+0xb8>

以上代码要求我们输入的值满足链表值降序的要求。

降序 输入3 1 6 4 2 5

 8048e6b:    8b 44 24 3c               mov    0x3c(%esp),%eax

 8048e6f:      65 33 05 14 00 00 00 xor    %gs:0x14,%eax

 8048e76:     74 05                    je     8048e7d <phase_6+0xe0>

 8048e78:     e8 13 f9 ff ff          call   8048790 <__stack_chk_fail@plt>

 8048e7d:    83 c4 44                  add    $0x44,%esp

 8048e80:     5b                      pop    %ebx

 8048e81:     5e                      pop    %esi

 8048e82:     c3                       ret    

校验canary值并返回

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

隐藏关卡:二叉树

如何进入?

只有phase_defused函数调用了secret_phase,我们来分析下phase_defused函数

08049218 <phase_defused>:

 8049218:    83 ec 6c                  sub    $0x6c,%esp

 804921b:    65 a1 14 00 00 00           mov    %gs:0x14,%eax

 8049221:    89 44 24 5c                mov    %eax,0x5c(%esp)

 8049225:    31 c0                    xor    %eax,%eax

//函数入口,设置canary值

 8049227:    83 3d cc c3 04 08 06 cmpl   $0x6,0x804c3cc

在phase_defused处打断点 查看0x804c3cc处内存,解开一关后:

解开两关后:

可知0x804c3cc处存储的是解开的关卡数,与6比较,直接跳到结束处,开启不了隐藏关。

 804922e:     75 73                    jne    80492a3 <phase_defused+0x8b>

 8049230:    83 ec 0c                  sub    $0xc,%esp

 8049233:    8d 44 24 18               lea    0x18(%esp),%eax

 8049237:    50                      push   %eax

 8049238:    8d 44 24 18               lea    0x18(%esp),%eax

 804923c:     50                      push   %eax

 804923d:    8d 44 24 18               lea    0x18(%esp),%eax

 8049241:    50                      push   %eax          

//在栈上分配三个连续的空间

 8049242:    68 b9 a1 04 08             push   $0x804a1b9

 8049247:    68 d0 c4 04 08             push   $0x804c4d0

 804924c:     e8 bf f5 ff ff           call   8048810 <__isoc99_sscanf@plt>

//查看传入的两个参数,一个是d d s,说明开启隐藏关需要在某一关输出两个整数和一个字符串,答案是两个整数的有关卡3,4,而第二个参数存储的是108 2(调试中我们输入的),是4的一个答案,说明开启隐藏关的条件是在第四关的答案后面加上一个字符串,这个字符串是什么呢?我们继续往下看。

 8049251:    83 c4 20                  add    $0x20,%esp

 8049254:    83 f8 03                   cmp    $0x3,%eax

 8049257:    75 3a                    jne    8049293 <phase_defused+0x7b>

//输入的参数必须为3个

 8049259:    83 ec 08                  sub    $0x8,%esp

 804925c:     68 c2 a1 04 08             push   $0x804a1c2

 8049261:    8d 44 24 18               lea    0x18(%esp),%eax

 8049265:    50                      push   %eax

 8049266:    e8 5d fd ff ff          call   8048fc8 <strings_not_equal>

 804926b:    83 c4 10                  add    $0x10,%esp

 804926e:     85 c0                    test   %eax,%eax

 8049270:    75 21                    jne    8049293 <phase_defused+0x7b>

比较局部变量中的字符串(栈地址 0x18(%esp))与预设字符串 0x804a1c2(反编译后内容如 "DrEvil")。

第三个参数要是DrEvil!

 8049272:    83 ec 0c                  sub    $0xc,%esp

 8049275:    68 88 a0 04 08             push   $0x804a088

 804927a:     e8 41 f5 ff ff          call   80487c0 <puts@plt>

 804927f:     c7 04 24 b0 a0 04 08 movl   $0x804a0b0,(%esp)

 8049286:    e8 35 f5 ff ff          call   80487c0 <puts@plt>

 804928b:    e8 44 fc ff ff          call   8048ed4 <secret_phase>

 8049290:    83 c4 10                  add    $0x10,%esp

 8049293:    83 ec 0c                  sub    $0xc,%esp

 8049296:    68 e8 a0 04 08             push   $0x804a0e8

 804929b:    e8 20 f5 ff ff          call   80487c0 <puts@plt>

一些提示信息,对解题无帮助……

 80492a0:     83 c4 10                  add    $0x10,%esp

 80492a3:     8b 44 24 5c               mov    0x5c(%esp),%eax

 80492a7:     65 33 05 14 00 00 00 xor    %gs:0x14,%eax

 80492ae:     74 05                    je     80492b5 <phase_defused+0x9d>

 80492b0:    e8 db f4 ff ff          call   8048790 <__stack_chk_fail@plt>

 80492b5:    83 c4 6c                  add    $0x6c,%esp

 80492b8:    c3                       ret   

//函数返回,校验canary值。

 

于是,我们到了隐藏关…

隐藏关的代码位于

08048ed4 <secret_phase>:

 8048ed4:    53                      push   %ebx

 8048ed5:    83 ec 08                  sub    $0x8,%esp

 8048ed8:    e8 42 02 00 00             call   804911f <read_line>

//读取输入值 存的地址返回到eax 这边输入123456用于调试。

 8048edd:    83 ec 04                  sub    $0x4,%esp

 8048ee0:     6a 0a                    push   $0xa

 8048ee2:     6a 00                    push   $0x0

 8048ee4:     50                      push   %eax

 8048ee5:     e8 96 f9 ff ff          call   8048880 <strtol@plt>

使用strtol将字符串转换为长整数(n),返回值在eax中

 8048eea:     89 c3                    mov    %eax,%ebx

 8048eec:     8d 40 ff                   lea    -0x1(%eax),%eax

 8048eef:      83 c4 10                  add    $0x10,%esp

 8048ef2:      3d e8 03 00 00             cmp    $0x3e8,%eax

 8048ef7:      76 05                    jbe    8048efe <secret_phase+0x2a>

 8048ef9:      e8 c1 01 00 00             call   80490bf <explode_bomb>

//验证n-1是否小于等于0x3e8(1000)(即1 ≤ n ≤ 1001),否则引爆炸弹。

 8048efe:      83 ec 08                  sub    $0x8,%esp

 8048f01:     53                      push   %ebx

 8048f02:     68 88 c0 04 08             push $0x804c088

 8048f07:     e8 77 ff ff ff           call   8048e83 <fun7>

//Fun7的两个参数分别是:n和内存地址0x804c088

查看0x804c088附近的内存

//这是什么数据结构?根据之前链表的经验,关键是看每个结构的数据域和地址域。

一个节点12个字节 前四个没什么规律 其它8个字节似乎存储了其它两个节点的内存……?这不是树结构吗?节点值和左右子节点啊!画出示意图看看:

一个标准的完全二叉树(一共四层)。Fun7函数接收的参数也就是根节点!

我们往下看fun7函数。

 8048f0c:      83 c4 10                  add    $0x10,%esp

 8048f0f:      83 f8 02                   cmp    $0x2,%eax

 8048f12:     74 05                    je     8048f19 <secret_phase+0x45>

 8048f14:     e8 a6 01 00 00             call   80490bf <explode_bomb>

//检查fun7返回值(eax)是否为2,否则引爆炸弹。

8048f19: 83 ec 0c                  sub    $0xc,%esp

 8048f1c:      68 f0 9f 04 08       push   $0x8049ff0

 8048f21:     e8 9a f8 ff ff          call   80487c0 <puts@plt>

 8048f26:     e8 ed 02 00 00             call   8049218 <phase_defused>

 8048f2b:     83 c4 18                  add    $0x18,%esp

 8048f2e:      5b                      pop    %ebx

 8048f2f:      c3                       ret   

 

 

 

Fun7函数,接收根节点地址和输入字符串的强转长整数。

08048e83 <fun7>:

 8048e83:     53                      push   %ebx

 8048e84:     83 ec 08                  sub    $0x8,%esp

 8048e87:     8b 54 24 10               mov    0x10(%esp),%edx

 8048e8b:    8b 4c 24 14               mov    0x14(%esp),%ecx

//edx=node地址(第一次是根节点)

ecx=n

 8048e8f:      85 d2                    test   %edx,%edx

 8048e91:     74 37                    je     8048eca <fun7+0x47>

//若node=NULL,返回-1(递归终止条件!)

if (node=NULL) return -1;

 8048e93:     8b 1a                    mov    (%edx),%ebx

//ebx=node->value

 8048e95:     39 cb                    cmp    %ecx,%ebx

 8048e97:     7e 13                    jle    8048eac <fun7+0x29>

//比较 node->value 和n

若 node->value <=n,跳转8048eac。if (node->value <=n) return…;

 8048e99:     83 ec 08                  sub    $0x8,%esp

 8048e9c:     51                      push   %ecx

 8048e9d:    ff 72 04                   pushl  0x4(%edx)

 8048ea0:     e8 de ff ff ff           call   8048e83 <fun7>

//调用左子树,两个参数分别为ecx(n), 0x4(%edx)(node->left)

 8048ea5:     83 c4 10                  add    $0x10,%esp

 8048ea8:     01 c0                    add    %eax,%eax

 8048eaa:     eb 23                    jmp    8048ecf <fun7+0x4c>

//将返回值翻倍后直接跳转8048ecf(返回eax,即为2倍fun(node->left,n))

if (node->value >n) return 2fun(node->left,n);

 (若 node->value <=n跳转到这边

8048eac:   b8 00 00 00 00             mov    $0x0,%eax

 8048eb1:    39 cb                    cmp    %ecx,%ebx

 8048eb3:    74 1a                    je     8048ecf <fun7+0x4c>

//eax=0 ecx=n ebx=node->val 若两者相等则返回0(eax)

if (node->value ==n) return 0;

 8048eb5:    83 ec 08                  sub    $0x8,%esp

 8048eb8:    51                      push   %ecx

 8048eb9:    ff 72 08                   pushl  0x8(%edx)

 8048ebc:     e8 c2 ff ff ff           call   8048e83 <fun7>、

//ecx=n, 0x8(%edx)是右子节点,调用func(node->right,n)

 8048ec1:     83 c4 10                  add    $0x10,%esp

 8048ec4:     8d 44 00 01               lea    0x1(%eax,%eax,1),%eax

 8048ec8:     eb 05                    jmp    8048ecf <fun7+0x4c>

//返回的值*2再加1

if (node->value <n) return 2func(node->right,n)+1;

 8048eca:     b8 ff ff ff ff             mov    $0xffffffff,%eax

 8048ecf:      83 c4 08                  add    $0x8,%esp

 8048ed2:    5b                      pop    %ebx

 8048ed3:    c3                       ret   

有伪代码:

我们的树结构(附上节点值

func(root,n)=2,确定n?

最终希望返回值是 2,所以我们输入的数一定在树中,否则就返回负值。

经过这个节点先要0*2+1=1,也就是其父节点的右子节点,然后他的父节点是祖父节点的子节点(1*2 = 2),此时返回值恰好是 2,故祖父节点就是根节点。所以目标节点(36)是根节点的左子节点(8)的右子节点(22),所以最终期待用户输入值是 22,即 0x16.

什么字符串经过strtol后是0x16?其实也就是字符串”22”即可。

 

 

Ans:an example

I am not part of the problem. I am a Republican.

0 1 1 2 3 5

0 809

108 2 DrEvil

*$/%&'

3 1 6 4 2 5

22

Bomb has been defused!

 

 

 

 

 

 

【实验总结】

这次试验个人觉得难度非常大,简单易懂的c代码想从汇编级读懂,难度不知道翻了多少倍。但是,从困难的解题过程中,我知道了计算机到底是怎么执行程序的,寄存器,内存,指令……到底他们之间怎么配合才能让一个程序得到应有的结果,分支,循环这种东西在我脑中不再是简单的ifelse,while这样的英文单词,而是汇编级的“跳转”。数组,链表,二叉树这些数据结构也不是晦涩难懂的东西,只是内存中的一些按规律排列的字节罢了。程序的运行不再是轻点一下“开始运行”,而是ebp,esp不断变化,栈帧不断上下增长的过程。从上学期电子电路学的制作cpu,到现在对汇编代码有了充分的研究,可以说,计算机这个神奇的东西在我看来已经不那么神秘了。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值