Bomb Lab

BOMB LAB

文件来源于《深入理解计算机系统》一书,
BOMB LAB 网址:http://csapp.cs.cmu.edu/3e/labs.html

运行坏境:Ubuntu 22.04.1
使用 GDB 调试 bomb,逐一破译 phrase

先使用反汇编命令

objdump -d bomb > bomb.s

bomb进行反汇编,通过输出重定向符号>保存反汇编内容到bomb.s文件中。

在这里插入图片描述

通过查看bomb.s文件中的汇编指令,以及对应bomb.c,可知,依次输入六个 phase,分别检验其准确性,只有输入都正确,才会成功拆解炸弹。

每个 phase 的读取、检验的汇编代码均为同一格式,例如:

  • phrase 的读取、检验 C 代码:
    在这里插入图片描述

  • phrase 的读取、检验 汇编代码:
    在这里插入图片描述

下面是函数传参时用到的寄存器

%rdi%rsi%rdx%rcx%r8%r9
作用第一个参数第二个参数第三个参数第四个参数第五个参数第六个参数

接下来开始分析汇编代码和破译 phase

1. 破译 phase1

通过分析 main 函数和 phase_1 函数的汇编代码,可以注意到两个寄存器

寄存器作用
%rdi保存用户输入的 phase
%rsi保存 bomb 预先设置的 phase

查看phase_1汇编代码段
在这里插入图片描述

可以知道函数phase_1输入参数(用户输入的 phase 字符串)的首地址保存在寄存器%rdi中,预先设置的 phase1 字符串首地址保存在%rsi中,通过调用函数strings_not_equal判断两者是否相同,根据结果判断是否破译 phrase1 成功。

接下来我们使用GDB对 bomb 进行调试,以获取 phase1 的字符串形式。
在这里插入图片描述

从 phase_1 的汇编代码可以知道其存储的地址为0x402400,下面通过两种方式获取 phase1.

  1. 在 GDB 中运行 print,可知 phase1 为: Border relations with Canada have never been better.

    在这里插入图片描述

  2. 在函数 phase_1 处设置断点,运行到断点处,通过 x/ls 命令 获取 phase1,结果与第一种方法一样

    在这里插入图片描述

将结果输入到 bomb 运行程序中,phase1 破解成功。

在这里插入图片描述

2. 破译 phase2

phase2 的汇编代码偏长,接下来分片段进行分析

  • 片段一(读取 6 位数字)

    0000000000400efc <phase_2>:
    400efc:	55                   	push   %rbp
    400efd:	53                   	push   %rbx
    400efe:	48 83 ec 28          	sub    $0x28,%rsp
    400f02:	48 89 e6             	mov    %rsp,%rsi
    400f05:	e8 52 05 00 00       	call   40145c <read_six_numbers>
    

    该片段调用了函数read_six_numbers,并根据上下文推断出调用read_six_numbers函数时传递了两个参数,其一为%rdi:用户输入的字符串首地址;其二为%rsi: phase_2函数分配的数组首元素地址。

    接下来分析函数read_six_numbers

    000000000040145c <read_six_numbers>:
    40145c:	48 83 ec 18          	sub    $0x18,%rsp
    401460:	48 89 f2             	mov    %rsi,%rdx          # y
    401463:	48 8d 4e 04          	lea    0x4(%rsi),%rcx     # y+4
    401467:	48 8d 46 14          	lea    0x14(%rsi),%rax    # y+20
    40146b:	48 89 44 24 08       	mov    %rax,0x8(%rsp)     # (%rsp+8)=y+20
    401470:	48 8d 46 10          	lea    0x10(%rsi),%rax    # y+16
    401474:	48 89 04 24          	mov    %rax,(%rsp)        # (%rsp)=y+16
    401478:	4c 8d 4e 0c          	lea    0xc(%rsi),%r9      # y+12
    40147c:	4c 8d 46 08          	lea    0x8(%rsi),%r8      # y+8
    401480:	be c3 25 40 00       	mov    $0x4025c3,%esi
    401485:	b8 00 00 00 00       	mov    $0x0,%eax
    40148a:	e8 61 f7 ff ff       	call   400bf0 <__isoc99_sscanf@plt>
    40148f:	83 f8 05             	cmp    $0x5,%eax
    401492:	7f 05                	jg     401499 <read_six_numbers+0x3d>
    401494:	e8 a1 ff ff ff       	call   40143a <explode_bomb>
    401499:	48 83 c4 18          	add    $0x18,%rsp
    40149d:	c3                   	ret
    

    该函数调用了 sscanf,作用为:input 字符串按照 format 模式串的形式,将字符输出给指定的变量地址,返回成功输入的参数个数

    int sscanf(char *input,char *format,arg1,..)
    {
      ...
    }
    

    根据上下文我们可以得出,在phase_2中创建了一个长度为 6 的整型数组,暂命名为 y ,而在read_six_numbers中使用sscanf将输入中的 6 个整数存储到 y 中,sscanf 各参数所用寄存器或栈地址如下表

    参数寄存器或栈地址内容
    input%rdi用户输入字符串
    format%rsi“%d %d %d %d %d %d”
    arg1%rdxy
    arg2%rcxy+4
    arg3%r8y+8
    arg4%r9y+12
    arg5(%rsp)y+16
    arg6(%rsp+8)y+20

    对 format 格式内容可以通过 gdb 查看地址为 0x4025c3 的值,结果为"%d %d %d %d %d %d",说明输入应为 以单个空格相隔的六个整数

    在这里插入图片描述

  • 片段二(判别 6 位数字)

    400f0a:	83 3c 24 01          	cmpl   $0x1,(%rsp)
    400f0e:	74 20                	je     400f30 <phase_2+0x34>
    400f10:	e8 25 05 00 00       	call   40143a <explode_bomb>
    400f15:	eb 19                	jmp    400f30 <phase_2+0x34>
    400f17:	8b 43 fc             	mov    -0x4(%rbx),%eax
    400f1a:	01 c0                	add    %eax,%eax
    400f1c:	39 03                	cmp    %eax,(%rbx)
    400f1e:	74 05                	je     400f25 <phase_2+0x29>
    400f20:	e8 15 05 00 00       	call   40143a <explode_bomb>
    400f25:	48 83 c3 04          	add    $0x4,%rbx
    400f29:	48 39 eb             	cmp    %rbp,%rbx
    400f2c:	75 e9                	jne    400f17 <phase_2+0x1b>
    400f2e:	eb 0c                	jmp    400f3c <phase_2+0x40>
    400f30:	48 8d 5c 24 04       	lea    0x4(%rsp),%rbx
    400f35:	48 8d 6c 24 18       	lea    0x18(%rsp),%rbp
    400f3a:	eb db                	jmp    400f17 <phase_2+0x1b>
    400f3c:	48 83 c4 28          	add    $0x28,%rsp
    400f40:	5b                   	pop    %rbx
    400f41:	5d                   	pop    %rbp
    400f42:	c3                   	ret
    

    对数组 y 中的六个数字进行判断,先判断 y[0]是否为 1,之后判断是否
    y[n]=y[n-1]*2 (0<n<6)

    所以 phase2 的正确破解结果为

    "1 2 4 8 16 32"
    

    输入后程序显示如下
    在这里插入图片描述

3. 破译 phase3

0000000000400f43 <phase_3>:
  400f43:	48 83 ec 18          	sub    $0x18,%rsp
  400f47:	48 8d 4c 24 0c       	lea    0xc(%rsp),%rcx
  400f4c:	48 8d 54 24 08       	lea    0x8(%rsp),%rdx
  400f51:	be cf 25 40 00       	mov    $0x4025cf,%esi
  400f56:	b8 00 00 00 00       	mov    $0x0,%eax
  400f5b:	e8 90 fc ff ff       	call   400bf0 <__isoc99_sscanf@plt>

观察 phase_3 前半段汇编代码,其也调用了sscanf函数,接下来查看参数 format 的内容,使用 gdb 的 print 命令打印地址为 0x4025cf 的内容,结果为 "%d %d",说明需要用户输入两个整数。

  400f6a:	83 7c 24 08 07       	cmpl   $0x7,0x8(%rsp)
  400f6f:	77 3c                	ja     400fad <phase_3+0x6a>
  400f71:	8b 44 24 08          	mov    0x8(%rsp),%eax
  ...
  400fad:	e8 88 04 00 00       	call   40143a <explode_bomb>

说明用户输入的第一个整数需要满足 0<=d[0]<=7

  400f71:	8b 44 24 08          	mov    0x8(%rsp),%eax
  400f75:	ff 24 c5 70 24 40 00 	jmp    *0x402470(,%rax,8)

使用 jmp 命令的间接跳转格式,说明 0x402470 是一个跳转表的首地址, %rax是索引, 最后跳转地址为 *(0x402470 + %rax*8),接下来我们使用 gdb 执行命令 x/8gx 0x402470 查看跳转表的内容。

x/8gx:
  第一个x表示查看
  8表示查看指定地址开始的8个地址单元的内容(索引为0~7)
  g表示一个地址单元为8个字节
  x表示以十六进制显示

其执行结果为
在这里插入图片描述

通过跳转表可以看出,phase3 有八种输入情况,分别为

第一个整数(索引)第二个整数
00xcf
10x137
20x2c3
30x100
40x185
50xce
60x2aa
70x147

输入7 327可以看出 phase3 破解成功。
在这里插入图片描述

4. 破译 phase4

000000000040100c <phase_4>:
  40100c:	48 83 ec 18          	sub    $0x18,%rsp
  401010:	48 8d 4c 24 0c       	lea    0xc(%rsp),%rcx     //第二个
  401015:	48 8d 54 24 08       	lea    0x8(%rsp),%rdx     //第一个
  40101a:	be cf 25 40 00       	mov    $0x4025cf,%esi
  40101f:	b8 00 00 00 00       	mov    $0x0,%eax
  401024:	e8 c7 fb ff ff       	call   400bf0 <__isoc99_sscanf@plt>
  401029:	83 f8 02             	cmp    $0x2,%eax
  40102c:	75 07                	jne    401035 <phase_4+0x29>
  40102e:	83 7c 24 08 0e       	cmpl   $0xe,0x8(%rsp)
  401033:	76 05                	jbe    40103a <phase_4+0x2e>
  401035:	e8 00 04 00 00       	call   40143a <explode_bomb>

还是一样的开始,一样的调用 sscanf, 而且 format 参数的地址也一样,所以 format 内容为 %d %d,其用户输入也为两个整数,且第一个整数应小于等于 14。

  40103a:	ba 0e 00 00 00       	mov    $0xe,%edx
  40103f:	be 00 00 00 00       	mov    $0x0,%esi
  401044:	8b 7c 24 08          	mov    0x8(%rsp),%edi
  401048:	e8 81 ff ff ff       	call   400fce <func4>
  40104d:	85 c0                	test   %eax,%eax
  40104f:	75 07                	jne    401058 <phase_4+0x4c>
  ...
  401058:	e8 dd 03 00 00       	call   40143a <explode_bomb>

该段汇编调用函数 fun4,且应使返回值为 0, 传入参数分别为 a[0](用户输入的第一个整数)、0x0、0xe

0000000000400fce <func4>:
  400fce:	48 83 ec 08          	sub    $0x8,%rsp
  400fd2:	89 d0                	mov    %edx,%eax
  400fd4:	29 f0                	sub    %esi,%eax
  400fd6:	89 c1                	mov    %eax,%ecx
  400fd8:	c1 e9 1f             	shr    $0x1f,%ecx
  400fdb:	01 c8                	add    %ecx,%eax
  400fdd:	d1 f8                	sar    %eax
  400fdf:	8d 0c 30             	lea    (%rax,%rsi,1),%ecx
  400fe2:	39 f9                	cmp    %edi,%ecx
  400fe4:	7e 0c                	jle    400ff2 <func4+0x24>
  400fe6:	8d 51 ff             	lea    -0x1(%rcx),%edx
  400fe9:	e8 e0 ff ff ff       	call   400fce <func4>
  400fee:	01 c0                	add    %eax,%eax
  400ff0:	eb 15                	jmp    401007 <func4+0x39>
  400ff2:	b8 00 00 00 00       	mov    $0x0,%eax
  400ff7:	39 f9                	cmp    %edi,%ecx
  400ff9:	7d 0c                	jge    401007 <func4+0x39>
  400ffb:	8d 71 01             	lea    0x1(%rcx),%esi
  400ffe:	e8 cb ff ff ff       	call   400fce <func4>
  401003:	8d 44 00 01          	lea    0x1(%rax,%rax,1),%eax
  401007:	48 83 c4 08          	add    $0x8,%rsp
  40100b:	c3                   	ret

由于函数 fun4调用了自己,所以其为递归函数,对于递归函数,逐行分析较为困难,我们将其逆向为 c 代码,逆向结果如下:

//汇编代码fun4的逆向c代码

// a = ?, b = 0, c = 14
// a in %rdi, b in %rsi, c in %rdx
// 调用函数 fun4(a[0], 0, 14);
// a < 14
int func4(int a, int b, int c)
{
    // temp1 in %rax
    int temp1 = c - b;
    // temp2 in %rcx
    int temp2 = (unsigned)temp1 >> 31;
    temp1 = (temp1 + temp2) >> 1;
    temp2 = temp1 + b;
    if (temp2 <= a)
    {
        temp1 = 0;
        if (temp2 >= a)
            return temp1;
        else
        {
            b = temp2 + 1;
            int r = func4(a, b, c);
            return 2 * r + 1;
        }
    }
    else
    {
        c = temp2 - 1;
        int r = func4(a, b, c);
        return 2 * r;
    }
}

// 简化版的func4
int func4_simple(int a, int b, int c)
{
    int temp = c - b;
    temp = ((((unsigned)temp >> 31) + temp) >> 1) + b;

    if (temp == a)
        return 0;
    else if (temp < a)
        return 2 * func4_simple(a, temp + 1, c) + 1;
    else if (temp > a)
        return 2 * func4_simple(a, b, temp - 1);
}

由于已知 a[0]在[0,14]区间内,所以对 a[0]进行群举,如下:

int main()
{
    int i = 0;
    while (i <= 14)
    {
        if (!func4_simple(i, 0, 14))
            printf("%d ", i);
        ++i;
    }
}

得出结果为 0 1 3 7

  401051:	83 7c 24 0c 00       	cmpl   $0x0,0xc(%rsp)
  401056:	74 05                	je     40105d <phase_4+0x51>
  401058:	e8 dd 03 00 00       	call   40143a <explode_bomb>
  40105d:	48 83 c4 18          	add    $0x18,%rsp
  401061:	c3                   	ret

对于 a[1]来说,其值应为 0。至此,可得 phase4 有 种情况,分别为:0 01 03 07 0。输入结果如下,phase4 破解成功。
在这里插入图片描述

5. 破译 phase5

0000000000401062 <phase_5>:
  401062:	53                   	push   %rbx
  401063:	48 83 ec 20          	sub    $0x20,%rsp
  401067:	48 89 fb             	mov    %rdi,%rbx
  40106a:	64 48 8b 04 25 28 00 	mov    %fs:0x28,%rax  //段寻址
  401071:	00 00
  401073:	48 89 44 24 18       	mov    %rax,0x18(%rsp)
  401078:	31 c0                	xor    %eax,%eax      //置零
  ...
  4010d9:	48 8b 44 24 18       	mov    0x18(%rsp),%rax
  4010de:	64 48 33 04 25 28 00 	xor    %fs:0x28,%rax
  4010e5:	00 00
  4010e7:	74 05                	je     4010ee <phase_5+0x8c>
  4010e9:	e8 42 fa ff ff       	call   400b30 <__stack_chk_fail@plt>  //运行时栈被破坏
  4010ee:	48 83 c4 20          	add    $0x20,%rsp
  4010f2:	5b                   	pop    %rbx
  4010f3:	c3                   	ret

这一段汇编代码起到了保护运行时栈的作用(对抗缓存区溢出攻击)。采用段寻址的方式从只读内存区读取一个值作为金丝雀值,并保存到当前函数运行时栈帧的0x18(%rsp)处。在函数结束运行时,判断金丝雀值是否改变,从而判断函数是否正常运行。

  40107a:	e8 9c 02 00 00       	call   40131b <string_length>
  40107f:	83 f8 06             	cmp    $0x6,%eax
  401082:	74 4e                	je     4010d2 <phase_5+0x70>
  401084:	e8 b1 03 00 00       	call   40143a <explode_bomb>

调用了函数string_length, 可以猜测 phase5 是一个字符串且长度为 6。

  401082:	74 4e                	je     4010d2 <phase_5+0x70>
  401084:	e8 b1 03 00 00       	call   40143a <explode_bomb>
  ...
  40108b:	0f b6 0c 03          	movzbl (%rbx,%rax,1),%ecx     //
  40108f:	88 0c 24             	mov    %cl,(%rsp)
  401092:	48 8b 14 24          	mov    (%rsp),%rdx
  401096:	83 e2 0f             	and    $0xf,%edx              //保留低四位
  401099:	0f b6 92 b0 24 40 00 	movzbl 0x4024b0(%rdx),%edx    //从该地址+offset处读值
  4010a0:	88 54 04 10          	mov    %dl,0x10(%rsp,%rax,1)  //保存到%rsp+0x10 开始的数组
  4010a4:	48 83 c0 01          	add    $0x1,%rax
  4010a8:	48 83 f8 06          	cmp    $0x6,%rax
  4010ac:	75 dd                	jne    40108b <phase_5+0x29>
  ...
  4010d2:	b8 00 00 00 00       	mov    $0x0,%eax
  4010d7:	eb b2                	jmp    40108b <phase_5+0x29>

显而易见,%rax 中存储的是一个对输入字符串的索引,其值为[0, 5],在这个范围内对省略号之间的汇编代码循坏执行,每循环以此%rax+1。每次循坏,都是以输入字符的低四位为索引,去首地址为0x4024b0的数组中取值存到首地址为%rsp+0x10的数组 a[6]中。
接下来查看首地址为0x4024b0的数组中的内容:
在这里插入图片描述

内容为:maduiersnfotvbylSo you think you can stop the bomb with ctrl-c, do you?,因为四位二进制表示的索引值范围为[0,15],所以有效信息为前十六位字符。

现在我们只知道以输入字符的低四位为索引,从maduiersnfotvbylSo中读取了六位字符顺序存储到 a[6]中,还缺乏一些条件,我们继续看下一段汇编代码。

  4010ae:	c6 44 24 16 00       	movb   $0x0,0x16(%rsp)      //字符串数组末尾停止位
  4010b3:	be 5e 24 40 00       	mov    $0x40245e,%esi
  4010b8:	48 8d 7c 24 10       	lea    0x10(%rsp),%rdi
  4010bd:	e8 76 02 00 00       	call   401338 <strings_not_equal>
  4010c2:	85 c0                	test   %eax,%eax
  4010c4:	74 13                	je     4010d9 <phase_5+0x77>
  4010c6:	e8 6f 03 00 00       	call   40143a <explode_bomb>
  ...
  4010d9:	48 8b 44 24 18       	mov    0x18(%rsp),%rax
  4010de:	64 48 33 04 25 28 00 	xor    %fs:0x28,%rax
  4010e5:	00 00
  4010e7:	74 05                	je     4010ee <phase_5+0x8c>
  4010e9:	e8 42 fa ff ff       	call   400b30 <__stack_chk_fail@plt>
  4010ee:	48 83 c4 20          	add    $0x20,%rsp
  4010f2:	5b                   	pop    %rbx
  4010f3:	c3                   	ret

注意到调用了函数strings_not_equal来判断字符串是否相等。其输入的两个参数分别为:

  1. %rsp+0x10 处的字符串。
  2. 0x40245e 处的字符串。

接下来查看0x40245e处的内容,为flyers

这样的话,结合前面的分析,我们可以得出下表

字符索引索引二进制可配对的字符输入顺序
f91001)、9、I、Y、i、y1
l151111/、?、O、_、o2
y141110.、>、N、^、n、~3
e50101%、5、E、U、e、u4
r60110&、6、F、V、f、v5
s70111'、7、G、W、g、w6

选取其中一种输入IONEFG,phase5 破译成功。
在这里插入图片描述

6. 破译 phase6

  4010f4:	41 56                	push   %r14
  4010f6:	41 55                	push   %r13
  4010f8:	41 54                	push   %r12
  4010fa:	55                   	push   %rbp
  4010fb:	53                   	push   %rbx
  4010fc:	48 83 ec 50          	sub    $0x50,%rsp   //分配栈帧
  401100:	49 89 e5             	mov    %rsp,%r13
  401103:	48 89 e6             	mov    %rsp,%rsi
  401106:	e8 51 03 00 00       	call   40145c <read_six_numbers>

调用了函数read_six_numbers,说明输入格式为"%d %d %d %d %d %d"的六个整数,假定存储到数组 y 中(首地址位于函数phase_6的%rsp 中)。

  40110b:	49 89 e6             	mov    %rsp,%r14
  40110e:	41 bc 00 00 00 00    	mov    $0x0,%r12d
  401114:	4c 89 ed             	mov    %r13,%rbp      //外层循坏开始处
  401117:	41 8b 45 00          	mov    0x0(%r13),%eax
  40111b:	83 e8 01             	sub    $0x1,%eax
  40111e:	83 f8 05             	cmp    $0x5,%eax
  401121:	76 05                	jbe    401128 <phase_6+0x34>      //y[i]>0
  401123:	e8 12 03 00 00       	call   40143a <explode_bomb>
  401128:	41 83 c4 01          	add    $0x1,%r12d
  40112c:	41 83 fc 06          	cmp    $0x6,%r12d   //循坏六次
  401130:	74 21                	je     401153 <phase_6+0x5f>
  401132:	44 89 e3             	mov    %r12d,%ebx
  401135:	48 63 c3             	movslq %ebx,%rax                //内存循坏开始处
  401138:	8b 04 84             	mov    (%rsp,%rax,4),%eax
  40113b:	39 45 00             	cmp    %eax,0x0(%rbp)
  40113e:	75 05                	jne    401145 <phase_6+0x51>
  401140:	e8 f5 02 00 00       	call   40143a <explode_bomb>
  401145:	83 c3 01             	add    $0x1,%ebx
  401148:	83 fb 05             	cmp    $0x5,%ebx
  40114b:	7e e8                	jle    401135 <phase_6+0x41>
  40114d:	49 83 c5 04          	add    $0x4,%r13
  401151:	eb c1                	jmp    401114 <phase_6+0x20>

通读一遍该段汇编代码,推测为双层嵌套的循坏,将其逆向为 c 代码如下:

// i in %r12d, j in %rbx, array(y) in %rsp
for(int i=0; i<6; ++i)
{
  if(y[i]-1 > 5) explode_bomb();
  for(int j=i+1; i<=5; ++j)
    if(y[i] == y[j]) explode_bomb();
}

逆向为 c 代码可以很容易看出,输入得到六个数字必须在[1, 6]之间且各不相等。

  401153:	48 8d 74 24 18       	lea    0x18(%rsp),%rsi
  401158:	4c 89 f0             	mov    %r14,%rax
  40115b:	b9 07 00 00 00       	mov    $0x7,%ecx
  401160:	89 ca                	mov    %ecx,%edx
  401162:	2b 10                	sub    (%rax),%edx
  401164:	89 10                	mov    %edx,(%rax)
  401166:	48 83 c0 04          	add    $0x4,%rax
  40116a:	48 39 f0             	cmp    %rsi,%rax
  40116d:	75 f1                	jne    401160 <phase_6+0x6c>

该段执行 y[i]=7-y[i],(0<=i<=5)的操作。

  40116f:	be 00 00 00 00       	mov    $0x0,%esi
  401174:	eb 21                	jmp    401197 <phase_6+0xa3>
  401176:	48 8b 52 08          	mov    0x8(%rdx),%rdx
  40117a:	83 c0 01             	add    $0x1,%eax
  40117d:	39 c8                	cmp    %ecx,%eax
  40117f:	75 f5                	jne    401176 <phase_6+0x82>
  401181:	eb 05                	jmp    401188 <phase_6+0x94>
  401183:	ba d0 32 60 00       	mov    $0x6032d0,%edx
  401188:	48 89 54 74 20       	mov    %rdx,0x20(%rsp,%rsi,2)
  40118d:	48 83 c6 04          	add    $0x4,%rsi
  401191:	48 83 fe 18          	cmp    $0x18,%rsi
  401195:	74 14                	je     4011ab <phase_6+0xb7>      //退出点
  401197:	8b 0c 34             	mov    (%rsp,%rsi,1),%ecx
  40119a:	83 f9 01             	cmp    $0x1,%ecx
  40119d:	7e e4                	jle    401183 <phase_6+0x8f>
  40119f:	b8 01 00 00 00       	mov    $0x1,%eax
  4011a4:	ba d0 32 60 00       	mov    $0x6032d0,%edx
  4011a9:	eb cb                	jmp    401176 <phase_6+0x82>

先通过 gdb x 命令查看地址$0x6032d0的内容

(gdb) x 0x6032d0
0x6032d0 <node1>:	0x0000014c

node1代表其可能有多个类似结构,下面查看更多的内容

(gdb) x/24wx 0x6032d0
0x6032d0 <node1>:	0x0000014c	0x00000001	0x006032e0	0x00000000
0x6032e0 <node2>:	0x000000a8	0x00000002	0x006032f0	0x00000000
0x6032f0 <node3>:	0x0000039c	0x00000003	0x00603300	0x00000000
0x603300 <node4>:	0x000002b3	0x00000004	0x00603310	0x00000000
0x603310 <node5>:	0x000001dd	0x00000005	0x00603320	0x00000000
0x603320 <node6>:	0x000001bb	0x00000006	0x00000000	0x00000000

可以观察到每个 node 的高 8 字节位存储着下一个 node 的地址,所以推测 node 类型为链式结构体数组。node 的低四字节位分别存储着 0~6,猜测其类型为 int 且为应输入的数字,同时考虑到字节对齐、机器为小端模式,所以假设该结构体定义如下

typedef struct node{
    int something;
    int input;
    struct node *next;
}node;

回头再看上面的汇编代码,其实就是将每个 node 的地址按照 node[i].input 与 y[i]的对应关系存储到一个新的结构体数组中 z[i]中,其首地址为 %rsp+0x20;

假设 y[i]={1,2,3,4,5,6},那么 z[i]={0x6032d0,0x6032e0,0x6032f0,0x603300,0x603310,0x603320}。

继续看下面的汇编处理

  4011ab:	48 8b 5c 24 20       	mov    0x20(%rsp),%rbx    //z[0]
  4011b0:	48 8d 44 24 28       	lea    0x28(%rsp),%rax
  4011b5:	48 8d 74 24 50       	lea    0x50(%rsp),%rsi
  4011ba:	48 89 d9             	mov    %rbx,%rcx
  4011bd:	48 8b 10             	mov    (%rax),%rdx
  4011c0:	48 89 51 08          	mov    %rdx,0x8(%rcx)
  4011c4:	48 83 c0 08          	add    $0x8,%rax
  4011c8:	48 39 f0             	cmp    %rsi,%rax
  4011cb:	74 05                	je     4011d2 <phase_6+0xde>
  4011cd:	48 89 d1             	mov    %rdx,%rcx
  4011d0:	eb eb                	jmp    4011bd <phase_6+0xc9>
  4011d2:	48 c7 42 08 00 00 00 	movq   $0x0,0x8(%rdx)

这段很绕,看了很久才弄个明白。
结合前面那一段的 node 地址数组 z 来对 node 链表进行重排。即按照用户输入的 1~6 数字,在进行7-y[i]后的顺序,比对 node.input ,对 node 链表进行重排。

可能没说明白,接下来使用 gdb 断点功能查看函数运行完此段汇编代码的 node 链表内容。

Good work!  On to the next...
3 2 1 6 5 4

Breakpoint 1, 0x00000000004011da in phase_6 ()
(gdb) x/24wx 0x6032d0
0x6032d0 <node1>:	0x0000014c	0x00000001	0x006032e0	0x00000000
0x6032e0 <node2>:	0x000000a8	0x00000002	0x006032f0	0x00000000
0x6032f0 <node3>:	0x0000039c	0x00000003	0x00000000	0x00000000
0x603300 <node4>:	0x000002b3	0x00000004	0x00603310	0x00000000
0x603310 <node5>:	0x000001dd	0x00000005	0x00603320	0x00000000
0x603320 <node6>:	0x000001bb	0x00000006	0x006032d0	0x00000000

当输入为 3 2 1 6 5 4时,7-y[i]后为4 5 6 1 2 3,如上结果,node 链表依照4 5 6 1 2 3进行排序,即

node4->node5->node6->node1->node2->node3

再来看最后一段汇编代码。

  4011d9:	00
  4011da:	bd 05 00 00 00       	mov    $0x5,%ebp
  4011df:	48 8b 43 08          	mov    0x8(%rbx),%rax    //z[0].next
  4011e3:	8b 00                	mov    (%rax),%eax        //取z[1].something
  4011e5:	39 03                	cmp    %eax,(%rbx)        //z[1]与z[2] something比较
  4011e7:	7d 05                	jge    4011ee <phase_6+0xfa>
  4011e9:	e8 4c 02 00 00       	call   40143a <explode_bomb>
  4011ee:	48 8b 5b 08          	mov    0x8(%rbx),%rbx
  4011f2:	83 ed 01             	sub    $0x1,%ebp
  4011f5:	75 e8                	jne    4011df <phase_6+0xeb>
  4011f7:	48 83 c4 50          	add    $0x50,%rsp
  4011fb:	5b                   	pop    %rbx
  4011fc:	5d                   	pop    %rbp
  4011fd:	41 5c                	pop    %r12
  4011ff:	41 5d                	pop    %r13
  401201:	41 5e                	pop    %r14
  401203:	c3                   	ret

此段为以此比较 node 链表中相邻的两个 node.sometging,要求前者大于等于后者,即 y[i]>=y[i+1],i 在[0,4]中,因此比较原始链表中 node.something ,以降序排列可以得出顺序为3 4 5 6 1 2,被 7 减去得出输入应为4 3 2 1 6 5.

在程序中输入4 3 2 1 6 5,成功破解 phase_6。
在这里插入图片描述

7. 破译隐藏关卡

在查看bomb.c时,发现在 main 函数结尾有这样一句话:

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

说明可能存在隐藏关卡,通览 main 函数,发现只有函数phase_defused()中可能隐藏些东西,查看该函数,发现其确实执行了一些操作并且调用了函数secret_phase,接下来以此分析。

先看函数 phase_defused, 发现有很多处直接使用了地址传值,以此打印这些地址内容

  4015f0:	be 19 26 40 00       	mov    $0x402619,%esi
  # 0x402619:	"%d %d %s"
  4015f5:	bf 70 38 60 00       	mov    $0x603870,%edi
  # 0x603870 <input_strings+240>:	""
  401604:	be 22 26 40 00       	mov    $0x402622,%esi
  # 0x402622:	"DrEvil"
  401617:	bf f8 24 40 00       	mov    $0x4024f8,%edi
  # 0x4024f8:	"Curses, you've found the secret phase!"
  401621:	bf 20 25 40 00       	mov    $0x402520,%edi
  # 0x402520:	"But finding it and solving it are quite different..."
  401635:	bf 58 25 40 00       	mov    $0x402558,%edi
  # 0x402558:	"Congratulations! You've defused the bomb!"

根据打印信息,函数phase_defused应该是隐藏关卡的看门员,只有当输入合理时,才会进入隐藏关卡。

  4015c4:	48 83 ec 78          	sub    $0x78,%rsp
  4015c8:	64 48 8b 04 25 28 00 	mov    %fs:0x28,%rax
  4015cf:	00 00
  4015d1:	48 89 44 24 68       	mov    %rax,0x68(%rsp)
  4015d6:	31 c0                	xor    %eax,%eax
  4015d8:	83 3d 81 21 20 00 06 	cmpl   $0x6,0x202181(%rip)        # 603760 <num_input_strings>
  4015df:	75 5e                	jne    40163f <phase_defused+0x7b>
  ...
  40163f:	48 8b 44 24 68       	mov    0x68(%rsp),%rax
  401644:	64 48 33 04 25 28 00 	xor    %fs:0x28,%rax
  40164b:	00 00
  40164d:	74 05                	je     401654 <phase_defused+0x90>
  40164f:	e8 dc f4 ff ff       	call   400b30 <__stack_chk_fail@plt>
  401654:	48 83 c4 78          	add    $0x78,%rsp

注意到0x202181(%rip),其地址应为(%rip=0x4015d9) + 0x202181 = 0x603760。 只有当该地址值为 6 时才会继续执行…中的函数。

  4015df:	75 5e                	jne    40163f <phase_defused+0x7b>
  4015e1:	4c 8d 44 24 10       	lea    0x10(%rsp),%r8
  4015e6:	48 8d 4c 24 0c       	lea    0xc(%rsp),%rcx
  4015eb:	48 8d 54 24 08       	lea    0x8(%rsp),%rdx
  4015f0:	be 19 26 40 00       	mov    $0x402619,%esi //"%d %d %s"
  4015f5:	bf 70 38 60 00       	mov    $0x603870,%edi //""
  4015fa:	e8 f1 f5 ff ff       	call   400bf0 <__isoc99_sscanf@plt>
  4015ff:	83 f8 03             	cmp    $0x3,%eax
  401602:	75 31                	jne    401635 <phase_defused+0x71>
  ...
  401635:	bf 58 25 40 00       	mov    $0x402558,%edi  //"Congratulations! You've defused the bomb!"
  40163a:	e8 d1 f4 ff ff       	call   400b10 <puts@plt>
  ...

这段说明当存储在地址0x603870%d %d %s时才会继续执行下面的函数。

  401604:	be 22 26 40 00       	mov    $0x402622,%esi  // "DrEvil"
  401609:	48 8d 7c 24 10       	lea    0x10(%rsp),%rdi
  40160e:	e8 25 fd ff ff       	call   401338 <strings_not_equal>
  401613:	85 c0                	test   %eax,%eax
  401615:	75 1e                	jne    401635 <phase_defused+0x71>
  401617:	bf f8 24 40 00       	mov    $0x4024f8,%edi    // "Curses, you've found the secret phase!"
  40161c:	e8 ef f4 ff ff       	call   400b10 <puts@plt>
  401621:	bf 20 25 40 00       	mov    $0x402520,%edi   //"But finding it and solving it are quite different..."
  401626:	e8 e5 f4 ff ff       	call   400b10 <puts@plt>
  40162b:	b8 00 00 00 00       	mov    $0x0,%eax
  401630:	e8 0d fc ff ff       	call   401242 <secret_phase>

由此可见,只有当地址处内容符合"%d %d %s"且第三个字符串为DrEvil时会调用函数secret_phase进入隐藏关卡。此外,由于时连续地址存储且两位数字,最符合的为 phase3 或 phase4

接下来,使用 gdb 打断点调试 bomb,查看何时符合进入隐藏关卡条件。

在该处打上断点

  4015fa:	e8 f1 f5 ff ff       	call   400bf0 <__isoc99_sscanf@plt>

当所有 phase 输入完之后,发现断点被执行,查看0x603870处为输入的 phase4,说明在输入 phase4 时加上DrEvil且所有 phase 输入正确后进入隐藏关卡。

Welcome to my fiendish little bomb. You have 6 phases with
which to blow yourself up. Have a nice day!
Border relations with Canada have never been better.
Phase 1 defused. How about the next one?
1 2 4 8 16 32
That's number 2.  Keep going!
7 327
Halfway there!
0 0 DrEvil
So you got that one.  Try this one.
IONEFG
Good work!  On to the next...
4 3 2 1 6 5
Curses, you've found the secret phase!
But finding it and solving it are quite different...

当 phase4 输入为0 0 DrEvil时,成功进入隐藏关卡。

继续看函数secret_phase

0000000000401242 <secret_phase>:
  401242:	53                   	push   %rbx
  401243:	e8 56 02 00 00       	call   40149e <read_line>
  401248:	ba 0a 00 00 00       	mov    $0xa,%edx
  40124d:	be 00 00 00 00       	mov    $0x0,%esi
  401252:	48 89 c7             	mov    %rax,%rdi
  401255:	e8 76 f9 ff ff       	call   400bd0 <strtol@plt>
  40125a:	48 89 c3             	mov    %rax,%rbx
  40125d:	8d 40 ff             	lea    -0x1(%rax),%eax  # %eax-1
  401260:	3d e8 03 00 00       	cmp    $0x3e8,%eax
  401265:	76 05                	jbe    40126c <secret_phase+0x2a>
  401267:	e8 ce 01 00 00       	call   40143a <explode_bomb>
  40126c:	89 de                	mov    %ebx,%esi
  40126e:	bf f0 30 60 00       	mov    $0x6030f0,%edi
  401273:	e8 8c ff ff ff       	call   401204 <fun7>
  401278:	83 f8 02             	cmp    $0x2,%eax
  40127b:	74 05                	je     401282 <secret_phase+0x40>
  40127d:	e8 b8 01 00 00       	call   40143a <explode_bomb>
  401282:	bf 38 24 40 00       	mov    $0x402438,%edi
  401287:	e8 84 f8 ff ff       	call   400b10 <puts@plt>
  40128c:	e8 33 03 00 00       	call   4015c4 <phase_defused>
  401291:	5b                   	pop    %rbx

调用了函数strtol,调用格式为strtol(intput, 0, 10);,查看其定义如下:

strtol是一个C语言函数,作用就是将一个字符串转换为长整型long
long int strtol (const char* str, char** endptr, int base);
其中:
  str是要转换的字符
  enptr是指向第一个不可转换的字符位置的指针
  base的基数,表示转换成为几进制的数

且:
  1.当 base 的值为 0 时,默认采用 10 进制转换,但如果遇到 '0x'/'0X' 前置字符则会使用 16 进制转换,遇到 '0' 前置字符则会使用 8 进制转换。

  2.若endptr 不为NULL,则会将遇到的不符合条件而终止的字符指针由 endptr 传回;若 endptr 为 NULL,则表示该参数无效,或不使用该参数。

因此调用该函数后就将输入的有数字的字符串转为数字,且输入应满足 in - 1 <= 1000.

注意到

 40126e:	bf f0 30 60 00       	mov    $0x6030f0,%edi

使用 gdb x命令查看此处内容,可能为一片连续空间,查看更多的内容如下

(gdb) x/60gx 0x6030f0
0x6030f0 <n1>:	0x0000000000000024	0x0000000000603110
0x603100 <n1+16>:	0x0000000000603130	0x0000000000000000
0x603110 <n21>:	0x0000000000000008	0x0000000000603190
0x603120 <n21+16>:	0x0000000000603150	0x0000000000000000
0x603130 <n22>:	0x0000000000000032	0x0000000000603170
0x603140 <n22+16>:	0x00000000006031b0	0x0000000000000000
0x603150 <n32>:	0x0000000000000016	0x0000000000603270
0x603160 <n32+16>:	0x0000000000603230	0x0000000000000000
0x603170 <n33>:	0x000000000000002d	0x00000000006031d0
0x603180 <n33+16>:	0x0000000000603290	0x0000000000000000
0x603190 <n31>:	0x0000000000000006	0x00000000006031f0
0x6031a0 <n31+16>:	0x0000000000603250	0x0000000000000000
0x6031b0 <n34>:	0x000000000000006b	0x0000000000603210
0x6031c0 <n34+16>:	0x00000000006032b0	0x0000000000000000
0x6031d0 <n45>:	0x0000000000000028	0x0000000000000000
0x6031e0 <n45+16>:	0x0000000000000000	0x0000000000000000
0x6031f0 <n41>:	0x0000000000000001	0x0000000000000000
0x603200 <n41+16>:	0x0000000000000000	0x0000000000000000
0x603210 <n47>:	0x0000000000000063	0x0000000000000000
0x603220 <n47+16>:	0x0000000000000000	0x0000000000000000
0x603230 <n44>:	0x0000000000000023	0x0000000000000000
0x603240 <n44+16>:	0x0000000000000000	0x0000000000000000
0x603250 <n42>:	0x0000000000000007	0x0000000000000000
0x603260 <n42+16>:	0x0000000000000000	0x0000000000000000
0x603270 <n43>:	0x0000000000000014	0x0000000000000000
0x603280 <n43+16>:	0x0000000000000000	0x0000000000000000
0x603290 <n46>:	0x000000000000002f	0x0000000000000000
0x6032a0 <n46+16>:	0x0000000000000000	0x0000000000000000
0x6032b0 <n48>:	0x00000000000003e9	0x0000000000000000
0x6032c0 <n48+16>:	0x0000000000000000	0x0000000000000000

观察该地址空间的内容,推测此处为链表结构,且应为二叉树

typedef struct tree{
   int val;
   struct tree *left;
   struct tree *right;
}tree;

此二叉树如下:

└─ 36
   ├─ 8
   │  ├─ 6
   │  │  ├─ left: 1
   │  │  └─ right: 7
   │  └─ 22
   │     ├─ left: 20
   │     └─ right: 35
   └─ 50
      ├─ 45
      │  ├─ left: 40
      │  └─ right: 47
      └─ 107
         ├─ left: 99
         └─ right: 1001

接下来则是调用函数fun7,调用格式为fun7(node, input_num);

再看函数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>
  40120d:	8b 17                	mov    (%rdi),%edx
  40120f:	39 f2                	cmp    %esi,%edx
  401211:	7e 0d                	jle    401220 <fun7+0x1c>
  401213:	48 8b 7f 08          	mov    0x8(%rdi),%rdi
  401217:	e8 e8 ff ff ff       	call   401204 <fun7>
  40121c:	01 c0                	add    %eax,%eax
  40121e:	eb 1d                	jmp    40123d <fun7+0x39>
  401220:	b8 00 00 00 00       	mov    $0x0,%eax
  401225:	39 f2                	cmp    %esi,%edx
  401227:	74 14                	je     40123d <fun7+0x39>
  401229:	48 8b 7f 10          	mov    0x10(%rdi),%rdi
  40122d:	e8 d2 ff ff ff       	call   401204 <fun7>
  401232:	8d 44 00 01          	lea    0x1(%rax,%rax,1),%eax
  401236:	eb 05                	jmp    40123d <fun7+0x39>
  401238:	b8 ff ff ff ff       	mov    $0xffffffff,%eax
  40123d:	48 83 c4 08          	add    $0x8,%rsp
  401241:	c3                   	ret

递归掉用自己,为了方便查看,将其逆向为 c 代码如下:

int fun7(tree *node, int input_num)
{
  if(!node) return -1;
  int temp = node->val;
  if(temp > input_num)        return 2*fun7(node->left, input_num);
  else if(temp < input_num)   return 2*fun7(node->right, input_num) + 1;
  else                        return 0;
}

寻找使 fun7 返回为 2 的 input_num 值。分析该递归函数,得出如下结论:

  • 如果输入不为该二叉树上节点的值,最后返回值一定为负数。

当输入值为2220的时候,fun4 返回结果为 2.
在这里插入图片描述

结束

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值