Bomb lab

175 阅读20分钟

Bomb lab主要涉及2个文件

  • bomb:实验提供的可执行程序,需要反汇编和调试
  • bomb.c:bomb的主函数源码

根据readme.pdf文档,就是有6段代码,需要自己反汇编bomb可执行文件,根据反汇编的文件,实现源文件里面的函数。

首先反汇编这个bomb文件

objdump -d ./bomb > bomb.dis

注:如果你忘记了这些寄存器都是干嘛的,请查看CSAPP 3.4章节的寄存器表,里面有写用途

phase1

首先反汇编中看到的phase_1代码如下

0000000000400ee0 <phase_1>:
  400ee0:	48 83 ec 08          	sub    $0x8,%rsp # 移动栈指针
  400ee4:	be 00 24 40 00       	mov    $0x402400,%esi # 将立即数传递到了rsi寄存器的低位esi
  400ee9:	e8 4a 04 00 00       	callq  401338 <strings_not_equal> # 调用字符串函数
  400eee:	85 c0                	test   %eax,%eax # 判断是否为0
  400ef0:	74 05                	je     400ef7 <phase_1+0x17> #  跳转到f7也就是爆炸下面的add指令
  400ef2:	e8 43 05 00 00       	callq  40143a <explode_bomb> # 调用炸弹函数
  400ef7:	48 83 c4 08          	add    $0x8,%rsp # 恢复栈
  400efb:	c3                   	retq   

这里拷贝了一个立即数到esi,但是不知道这个是什么值,看到寄存器esi,那就是函数第一个参数的存储嘛,再看到第二个参数(phase_1里面没有,那就全局搜索phase_1),找到调用phase_1调用前有一个mov操作存放参数,这样就很清楚了。

  400e37:	48 89 c7             	mov    %rax,%rdi #将第二个参数放到rdi寄存器
  400e3a:	e8 a1 00 00 00       	callq  400ee0 <phase_1>

接下来就是查看这个二进制值是多少,因为是字符串比较,将其以字符串形式打印出来,可以使用gdb查看下二进制值。

gdb bomb

x/s 0x402400

image.png

那就很明显了,最终phase_1代码如下。

void phase_1(char *input)
{
    if(string_not_equal(input,"Border relations with Canada have never been better.")==0)
    {
        explode_bomb();
    }
    return ;
}

字符串为

Border relations with Canada have never been better.

那么对于bomb输入的字符串如下,phase1通过

phase_2

再看到phase2,这次比较复杂了,好像是各种的控制语句(一堆jump, cmp)。

0000000000400da0 <main>:
  ...		...
  400e49:	e8 c2 fc ff ff       	callq  400b10 <puts@plt>
  400e4e:	e8 4b 06 00 00       	callq  40149e <read_line>
  400e53:	48 89 c7             	mov    %rax,%rdi
  400e56:	e8 a1 00 00 00       	callq  400efc <phase_2>

0000000000400efc <phase_2>:
# 保存现场
  400efc:	55                   	push   %rbp # callee saved 当前函数指针栈帧基地址, 等下跳转到其他函数
  400efd:	53                   	push   %rbx # callee saved
# 保存现场
  400efe:	48 83 ec 28          	sub    $0x28,%rsp # 移动栈指针, 这里分配内存空间
  400f02:	48 89 e6             	mov    %rsp,%rsi # 
  400f05:	e8 52 05 00 00       	callq  40145c <read_six_numbers>  #读取字符串到数组里面, 从这里就知道前面是分配了6个元素大小空间
  400f0a:	83 3c 24 01          	cmpl   $0x1,(%rsp) #将数组第一个元素和1比较, 这里是double word,就是4个字节, 那就是一个int[6]数组
  400f0e:	74 20                	je     400f30 <phase_2+0x34> # 如果相等就跳转
  400f10:	e8 25 05 00 00       	callq  40143a <explode_bomb> # 不相等爆炸
  400f15:	eb 19                	jmp    400f30 <phase_2+0x34>
phase_2+0x1b:
  400f17:	8b 43 fc             	mov    -0x4(%rbx),%eax # array[i-1]
  400f1a:	01 c0                	add    %eax,%eax # 自加
  400f1c:	39 03                	cmp    %eax,(%rbx) #比较array[i]和array[i-1]
  400f1e:	74 05                	je     400f25 <phase_2+0x29>
  400f20:	e8 15 05 00 00       	callq  40143a <explode_bomb> 
phase_2+0x29: # if跳转
  400f25:	48 83 c3 04          	add    $0x4,%rbx # array[i]
  400f29:	48 39 eb             	cmp    %rbp,%rbx #比较是否超过了数组范围
  400f2c:	75 e9                	jne    400f17 <phase_2+0x1b> #循环跳转
  400f2e:	eb 0c                	jmp    400f3c <phase_2+0x40> #结束
phase_2+0x34:
  400f30:	48 8d 5c 24 04       	lea    0x4(%rsp),%rbx #保存了数组的第一个元素的地址
  400f35:	48 8d 6c 24 18       	lea    0x18(%rsp),%rbp #偏移24字节,也就是数组的末尾
  400f3a:	eb db                	jmp    400f17 <phase_2+0x1b>
phase_2+0x40: #结束函数,恢复现场
  400f3c:	48 83 c4 28          	add    $0x28,%rsp 
  400f40:	5b                   	pop    %rbx
  400f41:	5d                   	pop    %rbp
  400f42:	c3                   	retq   

read_six_number

phase2调用了read_six_number,简单看一下。就是将rsi一直做偏移,把地址存放到了寄存器,然后把他存到了分配的连续数组里面,一直在lea和mov。

000000000040145c <read_six_numbers>:
  40145c:	48 83 ec 18          	sub    $0x18,%rsp
  401460:	48 89 f2             	mov    %rsi,%rdx
  401463:	48 8d 4e 04          	lea    0x4(%rsi),%rcx
  401467:	48 8d 46 14          	lea    0x14(%rsi),%rax
  40146b:	48 89 44 24 08       	mov    %rax,0x8(%rsp)
  401470:	48 8d 46 10          	lea    0x10(%rsi),%rax
  401474:	48 89 04 24          	mov    %rax,(%rsp)
  401478:	4c 8d 4e 0c          	lea    0xc(%rsi),%r9
  40147c:	4c 8d 46 08          	lea    0x8(%rsi),%r8
  401480:	be c3 25 40 00       	mov    $0x4025c3,%esi
  401485:	b8 00 00 00 00       	mov    $0x0,%eax
  40148a:	e8 61 f7 ff ff       	callq  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       	callq  40143a <explode_bomb>
  401499:	48 83 c4 18          	add    $0x18,%rsp
  40149d:	c3                   	retq   

转换为C

phase2汇编转换为C代码如下

void phase_2(char *input)
{
	int array[6];
    read_six_number(input, array);
    if(array[0]!=1)
    {
        explode_bomb();
    }
    for(int i=1;i<6;i++)
    {
        array[i-1]+=array[i-1];
        if(array[i]!=array[i-1])
        {
            explode_bomb();
        }
    }
    return ;
}

测试

这里为了方便测试,将输入写到txt文件里面,再输入到bomb程序。

Border relations with Canada have never been better.
1 2 4 8 16 32

已经通过phase2了,没问题。

phase3

phase3也是很多跳转,而且都是跳转到了同一个地址?

main:
  400e65:	e8 a6 fc ff ff       	callq  400b10 <puts@plt>
  400e6a:	e8 2f 06 00 00       	callq  40149e <read_line>
  400e6f:	48 89 c7             	mov    %rax,%rdi
  400e72:	e8 cc 00 00 00       	callq  400f43 <phase_3>

0000000000400f43 <phase_3>:
  400f43:	48 83 ec 18          	sub    $0x18,%rsp
  400f47:	48 8d 4c 24 0c       	lea    0xc(%rsp),%rcx #用于scanf输入
  400f4c:	48 8d 54 24 08       	lea    0x8(%rsp),%rdx #用于scanf输入
  400f51:	be cf 25 40 00       	mov    $0x4025cf,%esi
  400f56:	b8 00 00 00 00       	mov    $0x0,%eax
  400f5b:	e8 90 fc ff ff       	callq  400bf0 <__isoc99_sscanf@plt> 
  400f60:	83 f8 01             	cmp    $0x1,%eax
  400f63:	7f 05                	jg     400f6a <phase_3+0x27> 
  400f65:	e8 d0 04 00 00       	callq  40143a <explode_bomb>
phase_3+0x27:
  400f6a:	83 7c 24 08 07       	cmpl   $0x7,0x8(%rsp) #前面是scanf的输入判断,这里开始是输入后比较
  400f6f:	77 3c                	ja     400fad <phase_3+0x6a>  #0x8(%rsp)比7大就跳转到explode
  400f71:	8b 44 24 08          	mov    0x8(%rsp),%eax #拷贝变量
  400f75:	ff 24 c5 70 24 40 00 	jmpq   *0x402470(,%rax,8) # 跳转到某个地方去了 
  
  # 下面是赋值然后又跳转到400fad,多个分支而且没有判断,应该是switch语句
phase_3+57:
  400f7c:	b8 cf 00 00 00       	mov    $0xcf,%eax
  400f81:	eb 3b                	jmp    400fbe <phase_3+0x7b>
phase_3+64:
  400f83:	b8 c3 02 00 00       	mov    $0x2c3,%eax
  400f88:	eb 34                	jmp    400fbe <phase_3+0x7b>
phase_3+71:
  400f8a:	b8 00 01 00 00       	mov    $0x100,%eax
  400f8f:	eb 2d                	jmp    400fbe <phase_3+0x7b>
phase_3+78:
  400f91:	b8 85 01 00 00       	mov    $0x185,%eax
  400f96:	eb 26                	jmp    400fbe <phase_3+0x7b>
phase_3+85:
  400f98:	b8 ce 00 00 00       	mov    $0xce,%eax
  400f9d:	eb 1f                	jmp    400fbe <phase_3+0x7b>
phase_3+92:
  400f9f:	b8 aa 02 00 00       	mov    $0x2aa,%eax
  400fa4:	eb 18                	jmp    400fbe <phase_3+0x7b>
phase_3+99:
  400fa6:	b8 47 01 00 00       	mov    $0x147,%eax
  400fab:	eb 11                	jmp    400fbe <phase_3+0x7b>
phase_3+0x6a:
  400fad:	e8 88 04 00 00       	callq  40143a <explode_bomb>
  400fb2:	b8 00 00 00 00       	mov    $0x0,%eax
  400fb7:	eb 05                	jmp    400fbe <phase_3+0x7b>
phase_3+118:
  400fb9:	b8 37 01 00 00       	mov    $0x137,%eax
phase_3+0x7b:
  400fbe:	3b 44 24 0c          	cmp    0xc(%rsp),%eax #比较变量和eax值是否相等
  400fc2:	74 05                	je     400fc9 <phase_3+0x86> #如果相等就explode
phase_3+0x86:
  400fc4:	e8 71 04 00 00       	callq  40143a <explode_bomb>
  400fc9:	48 83 c4 18          	add    $0x18,%rsp
  400fcd:	c3                   	retq   

地址分析

里面的立即数地址拷贝到了寄存器中,下面分析一下

# %d %d字符串常量地址
(gdb) x/s 0x4025cf
0x4025cf:       "%d %d"

# 0x402470地址存放的是一个跳表, 这些地址就是eax赋值的指令地址
(gdb) x/16a 0x402470
0x402470:       0x400f7c <phase_3+57>   0x400fb9 <phase_3+118>
0x402480:       0x400f83 <phase_3+64>   0x400f8a <phase_3+71>
0x402490:       0x400f91 <phase_3+78>   0x400f98 <phase_3+85>
0x4024a0:       0x400f9f <phase_3+92>   0x400fa6 <phase_3+99>
0x4024b0 <array.3449>:  0x737265697564616d      0x6c796276746f666e
0x4024c0:       0x7420756f79206f53      0x756f79206b6e6968
0x4024d0:       0x6f7473206e616320      0x6f62206568742070
0x4024e0:       0x206874697720626d      0x202c632d6c727463

转换为C

上面分析后实际上就是scanf输入后,通过swtich语句将变量跳转,最后再进行一次比较看是否相等。实际上两个输入变量满足小于等于7和case标签里面的值即可。

void phase_3(char *input)
{
    int x,y,z; 
    if(scanf("%d %d",&x,&y)<=1)
    {
        explode_bomb();
    }
    if(x>7)
    {
        explode_bomb();
    }
    swtich(x)
    {
        case 0:
            z = 0xcf; // 207
            break;
        case 1:
            z = 0x137; // 311
            break;
        case 2:
            z = 0x2c3; // 707
            break;
        case 3: 
            z = 0x100; // 256
            break;
        case 4:
            z = 0x185; // 389
            break;
        case 5:
            z = 0xce; // 206
            break;
        case 6:
            z = 0x2aa; // 682
            break;
        case 7:
            z = 0x147; // 327
            break;
    }
    if(z!=y)
    {
        explode_bomb();
    }
    return ;
}

测试

多组数据都可以满足,只要x<=7,并且x对应的标签和输入y相同即可。

3 256

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       	callq  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       	callq  40143a <explode_bomb>
  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       	callq  400fce <func4>
  40104d:	85 c0                	test   %eax,%eax
  40104f:	75 07                	jne    401058 <phase_4+0x4c>
  401051:	83 7c 24 0c 00       	cmpl   $0x0,0xc(%rsp)
  401056:	74 05                	je     40105d <phase_4+0x51>
  401058:	e8 dd 03 00 00       	callq  40143a <explode_bomb>
  40105d:	48 83 c4 18          	add    $0x18,%rsp
  401061:	c3                   	retq 

转换为C

phase_4函数整体流程如下

void phase(char *input)
{
    int x,y;    // 0x8(%rsp) 0xc(%rsp)
    int ret = sscanf("%d %d",&x,&y); //实际上ret是寄存器, 不占用内存, 优化后就是放在寄存器
    if(ret!=2 || x>14)
    {
        explode_bomb();
    }
    ret = func4(x,0,14);
    if(ret!=0 || y!=0) // 返回值和栈变量比较
    {
        explode_bomb();
    }
    return ;
}

func4

可以看到func4中递归调用了自己

0000000000400fce <func4>:
  400fce:	48 83 ec 08          	sub    $0x8,%rsp  # 分配8个字节
  400fd2:	89 d0                	mov    %edx,%eax  # 将第三个参数移动到eax
  400fd4:	29 f0                	sub    %esi,%eax  # edx - esi, 结果存放到eax
  400fd6:	89 c1                	mov    %eax,%ecx  # eax存到ecx
  400fd8:	c1 e9 1f             	shr    $0x1f,%ecx # ecx右移31位
  400fdb:	01 c8                	add    %ecx,%eax  # eax+ecx -> eax
  400fdd:	d1 f8                	sar    %eax       # eax算术右移1位
  400fdf:	8d 0c 30             	lea    (%rax,%rsi,1),%ecx #ecx存放rax+rsi内存地址的值
  400fe2:	39 f9                	cmp    %edi,%ecx  # 比较ecx和edi
  400fe4:	7e 0c                	jle    400ff2 <func4+0x24>
  400fe6:	8d 51 ff             	lea    -0x1(%rcx),%edx # edx = rcx - 1
  400fe9:	e8 e0 ff ff ff       	callq  400fce <func4> # 递归调用func4
  400fee:	01 c0                	add    %eax,%eax # eax = 2*eax
  400ff0:	eb 15                	jmp    401007 <func4+0x39> 
func4+0x24:
  400ff2:	b8 00 00 00 00       	mov    $0x0,%eax # eax = 0 返回值为0
  400ff7:	39 f9                	cmp    %edi,%ecx  
  400ff9:	7d 0c                	jge    401007 <func4+0x39> # ecx > edi? 跳转返回函数值
  400ffb:	8d 71 01             	lea    0x1(%rcx),%esi # 设置参数, 准备继续调用func4
  400ffe:	e8 cb ff ff ff       	callq  400fce <func4>
  401003:	8d 44 00 01          	lea    0x1(%rax,%rax,1),%eax #eax = rax+rax+1
func4+0x39:
  401007:	48 83 c4 08          	add    $0x8,%rsp
  40100b:	c3                   	retq   

根据func4的汇编代码翻译为C代码如下,实际上就是使用二分法找到x。

int func4(int x,int y,int z)
{
    
    int tmp = z - y; // 400fd2 ~ 400fd4
    tmp = (tmp + tmp >>31)>>1; // 400fd6 ~ 400fdd
    int cx = tmp + y; // 
    if(cx<=x)
    {
        if(cx>=x) return 0; // 400ff2~400ff9 -> 401007~40100b
        return 2*func4(x,tmp+1,z)+1; // 400ffb~40100b
    }
    return 2*func4(x,y,tmp-1)+1; // 400fe6~400ff0 -> 401007~40100b
}

测试

根据上面转换好的C代码,现在来输入测试。首先第一个输入值需要小于14的整数,第二个输入值从phase_4的汇编代码来看,它必须是为0的。所以输入值可以为

0 0
1 0
3 0
7 0

phase5

前面硬啃汇编代码问题不大,但是这里开始啃不下去了(前面尝试硬啃熟悉下汇编指令和常用寄存器),必须GDB单步调试看寄存器和内存才知道其中几段指令在干嘛。首先看到整个phase5的汇编代码。

Dump of assembler code for function phase_5:
   0x0000000000401062 <+0>:     push   %rbx
   0x0000000000401063 <+1>:     sub    $0x20,%rsp
   0x0000000000401067 <+5>:     mov    %rdi,%rbx
   0x000000000040106a <+8>:     mov    %fs:0x28,%rax
   0x0000000000401073 <+17>:    mov    %rax,0x18(%rsp)
   0x0000000000401078 <+22>:    xor    %eax,%eax
   0x000000000040107a <+24>:    callq  0x40131b <string_length>
   0x000000000040107f <+29>:    cmp    $0x6,%eax
   0x0000000000401082 <+32>:    je     0x4010d2 <phase_5+112>
   0x0000000000401084 <+34>:    callq  0x40143a <explode_bomb>
   0x0000000000401089 <+39>:    jmp    0x4010d2 <phase_5+112>
   0x000000000040108b <+41>:    movzbl (%rbx,%rax,1),%ecx
   0x000000000040108f <+45>:    mov    %cl,(%rsp)
   0x0000000000401092 <+48>:    mov    (%rsp),%rdx
   0x0000000000401096 <+52>:    and    $0xf,%edx
   0x0000000000401099 <+55>:    movzbl 0x4024b0(%rdx),%edx
   0x00000000004010a0 <+62>:    mov    %dl,0x10(%rsp,%rax,1)
   0x00000000004010a4 <+66>:    add    $0x1,%rax
   0x00000000004010a8 <+70>:    cmp    $0x6,%rax
   0x00000000004010ac <+74>:    jne    0x40108b <phase_5+41>
   0x00000000004010ae <+76>:    movb   $0x0,0x16(%rsp)
   0x00000000004010b3 <+81>:    mov    $0x40245e,%esi
   0x00000000004010b8 <+86>:    lea    0x10(%rsp),%rdi
   0x00000000004010bd <+91>:    callq  0x401338 <strings_not_equal>
   0x00000000004010c2 <+96>:    test   %eax,%eax
   0x00000000004010c4 <+98>:    je     0x4010d9 <phase_5+119>
   0x00000000004010c6 <+100>:   callq  0x40143a <explode_bomb>
   0x00000000004010cb <+105>:   nopl   0x0(%rax,%rax,1)
   0x00000000004010d0 <+110>:   jmp    0x4010d9 <phase_5+119>
   0x00000000004010d2 <+112>:   mov    $0x0,%eax
   0x00000000004010d7 <+117>:   jmp    0x40108b <phase_5+41>
   0x00000000004010d9 <+119>:   mov    0x18(%rsp),%rax
   0x00000000004010de <+124>:   xor    %fs:0x28,%rax
   0x00000000004010e7 <+133>:   je     0x4010ee <phase_5+140>
   0x00000000004010e9 <+135>:   callq  0x400b30 <__stack_chk_fail@plt>
   0x00000000004010ee <+140>:   add    $0x20,%rsp
   0x00000000004010f2 <+144>:   pop    %rbx
   0x00000000004010f3 <+145>:   retq

首先是输入部分,这里如果读取的字符串长度不够6,会直接bomb结束,所以input要满足6个字符才能继续。

Dump of assembler code for function phase_5:
   0x0000000000401062 <+0>:     push   %rbx
   0x0000000000401063 <+1>:     sub    $0x20,%rsp
   0x0000000000401067 <+5>:     mov    %rdi,%rbx
   0x000000000040106a <+8>:     mov    %fs:0x28,%rax
   0x0000000000401073 <+17>:    mov    %rax,0x18(%rsp)
   0x0000000000401078 <+22>:    xor    %eax,%eax
   0x000000000040107a <+24>:    callq  0x40131b <string_length>
   0x000000000040107f <+29>:    cmp    $0x6,%eax
   0x0000000000401082 <+32>:    je     0x4010d2 <phase_5+112>
   0x0000000000401084 <+34>:    callq  0x40143a <explode_bomb>
   0x0000000000401089 <+39>:    jmp    0x4010d2 <phase_5+112>

循环

往下面看,看到里面的jump结构,可以看到41是关键的循环部分,这里就比较复杂了。

   0x000000000040108b <+41>:    movzbl (%rbx,%rax,1),%ecx
   0x000000000040108f <+45>:    mov    %cl,(%rsp)
   0x0000000000401092 <+48>:    mov    (%rsp),%rdx
   0x0000000000401096 <+52>:    and    $0xf,%edx
   0x0000000000401099 <+55>:    movzbl 0x4024b0(%rdx),%edx
   0x00000000004010a0 <+62>:    mov    %dl,0x10(%rsp,%rax,1)
   0x00000000004010a4 <+66>:    add    $0x1,%rax
   0x00000000004010a8 <+70>:    cmp    $0x6,%rax
   0x00000000004010ac <+74>:    jne    0x40108b <phase_5+41>

进入到循环位置(+41)

image.png 首先查看下rbx存放了什么字符串,好像是输入的字符串

image.png

再打印下内存,确定了是自己输入的字符串。

image.png

走几次循环后发现,rax是计数器,然后将输入字符串和循环变量的偏移值得到的字符存到rcx,rcx数据又存放到edx,edx和0xf进行与运算,得到低4位放到edx。最后从[0x4024b0+rdx]偏移得到的字节拷贝到edx。最后把edx的值又放到内存里面**M[rsp+rax+0x10]**里面。

查看下这**M[rsp+rax+0x10]**里面存放了什么内容,有点奇怪,输入的abcdef,一顿操作后变成了aduier。

image.png

再看一下$0x4024b0里面的地址存放的内容,这里的字符串内容如下,后面存放的So you think...应该是ctrl-c接收信号中断后输出的字符串。

image.png

那么就是这maduiersnfotvbyl这16个字符,因为前面是通过取得edx低4位的数值然后作为偏移又去取出这个字符串的某个字符。

字符串比较

再看到循环结束后的指令

 0x00000000004010ae <+76>:    movb   $0x0,0x16(%rsp) # '\0'赋值到字符串末尾
   0x00000000004010b3 <+81>:    mov    $0x40245e,%esi # 拷贝地址值到esi
   0x00000000004010b8 <+86>:    lea    0x10(%rsp),%rdi #将偏移地址放到rdi作为参数传递
   0x00000000004010bd <+91>:    callq  0x401338 <strings_not_equal> #字符串比较
   0x00000000004010c2 <+96>:    test   %eax,%eax # test 返回值
   0x00000000004010c4 <+98>:    je     0x4010d9 <phase_5+119>
   0x00000000004010c6 <+100>:   callq  0x40143a <explode_bomb> # 不相等就爆炸
   0x00000000004010cb <+105>:   nopl   0x0(%rax,%rax,1)
   0x00000000004010d0 <+110>:   jmp    0x4010d9 <phase_5+119>
   0x00000000004010d2 <+112>:   mov    $0x0,%eax
   0x00000000004010d7 <+117>:   jmp    0x40108b <phase_5+41>
   0x00000000004010d9 <+119>:   mov    0x18(%rsp),%rax
   0x00000000004010de <+124>:   xor    %fs:0x28,%rax
   0x00000000004010e7 <+133>:   je     0x4010ee <phase_5+140>
   0x00000000004010e9 <+135>:   callq  0x400b30 <__stack_chk_fail@plt>
   0x00000000004010ee <+140>:   add    $0x20,%rsp
   0x00000000004010f2 <+144>:   pop    %rbx
   0x00000000004010f3 <+145>:   retq

查看地址0x40245e的内容,是一个字符串。后面就比较清楚了,在前面操作的字符串保存在数组里面了,现在比较这6个字符是否等于"flyers",如果是,那么这个phase_5就通过了。

image.png

现在的问题就是如何输入值从字符串maduiersnfotvbyl取出flyters

  • f对应的offset是9
  • l对应的offset是15
  • y对应的offset是14
  • e对应的offset是5
  • r对应的offset是6
  • s对应的offset是7

**那么只要输入的ASCII字符的低4位(模16)对应的是这么几个offset即可,比如

IONEFG

测试

输入上面的字符串后,没问题通过。

image.png

phase6

phase6接收了6个数字,尝试输入6个数字后,直接一直单步,发现里面有非常多的循环,而且有嵌套循环。

0x00000000004010f4 <+0>:     push   %r14
0x00000000004010f6 <+2>:     push   %r13
0x00000000004010f8 <+4>:     push   %r12
0x00000000004010fa <+6>:     push   %rbp
0x00000000004010fb <+7>:     push   %rbx
0x00000000004010fc <+8>:     sub    $0x50,%rsp
0x0000000000401100 <+12>:    mov    %rsp,%r13 # r13 = rsp
0x0000000000401103 <+15>:    mov    %rsp,%rsi
0x0000000000401106 <+18>:    callq  0x40145c <read_six_numbers>
0x000000000040110b <+23>:    mov    %rsp,%r14 # r14 = rsp
0x000000000040110e <+26>:    mov    $0x0,%r12d # r12 = 0
0x0000000000401114 <+32>:    mov    %r13,%rbp # rbp = r13

0x0000000000401117 <+35>:    mov    0x0(%r13),%eax
0x000000000040111b <+39>:    sub    $0x1,%eax # eax = eax - 1
0x000000000040111e <+42>:    cmp    $0x5,%eax # 大于5则结束
0x0000000000401121 <+45>:    jbe    0x401128 <phase_6+52>
0x0000000000401123 <+47>:    callq  0x40143a <explode_bomb>


0x0000000000401128 <+52>:    add    $0x1,%r12d # r12 += 1
0x000000000040112c <+56>:    cmp    $0x6,%r12d # r12==6?
0x0000000000401130 <+60>:    je     0x401153 <phase_6+95>
0x0000000000401132 <+62>:    mov    %r12d,%ebx # rbx = r12

0x0000000000401135 <+65>:    movslq %ebx,%rax 
0x0000000000401138 <+68>:    mov    (%rsp,%rax,4),%eax # rax = 4*rax + rsp 将每个数字都拷贝到rax
0x000000000040113b <+71>:    cmp    %eax,0x0(%rbp) # rax 和 rbp是否相等? if(num[i]==num[j])
0x000000000040113e <+74>:    jne    0x401145 <phase_6+81>
0x0000000000401140 <+76>:    callq  0x40143a <explode_bomb> # 相等则bomb

0x0000000000401145 <+81>:    add    $0x1,%ebx # rbx += 1, j++
0x0000000000401148 <+84>:    cmp    $0x5,%ebx # rbx < 5 ?
0x000000000040114b <+87>:    jle    0x401135 <phase_6+65>

0x000000000040114d <+89>:    add    $0x4,%r13 # r13 += 4, 就是rsp移动了4字节
0x0000000000401151 <+93>:    jmp    0x401114 <phase_6+32>  # 又回到了32,是双重循环
0x0000000000401153 <+95>:    lea    0x18(%rsp),%rsi # rsi = 0
0x0000000000401158 <+100>:   mov    %r14,%rax # rax = r14
0x000000000040115b <+103>:   mov    $0x7,%ecx # rcx = 7

# 单层循环将数组的每个元素赋值为 7 - nums[i]
0x0000000000401160 <+108>:   mov    %ecx,%edx # rdx = rcx
0x0000000000401162 <+110>:   sub    (%rax),%edx # rdx -= *rax
0x0000000000401164 <+112>:   mov    %edx,(%rax) *rax = edx # 
0x0000000000401166 <+114>:   add    $0x4,%rax # rax += 4
0x000000000040116a <+118>:   cmp    %rsi,%rax  # rax是否为0
0x000000000040116d <+121>:   jne    0x401160 <phase_6+108> #这里是一个单层循环
0x000000000040116f <+123>:   mov    $0x0,%esi # rsi = 0
0x0000000000401174 <+128>:   jmp    0x401197 <phase_6+163> 

0x0000000000401176 <+130>:   mov    0x8(%rdx),%rdx
0x000000000040117a <+134>:   add    $0x1,%eax
0x000000000040117d <+137>:   cmp    %ecx,%eax
0x000000000040117f <+139>:   jne    0x401176 <phase_6+130>
0x0000000000401181 <+141>:   jmp    0x401188 <phase_6+148>

0x0000000000401183 <+143>:   mov    $0x6032d0,%edx
0x0000000000401188 <+148>:   mov    %rdx,0x20(%rsp,%rsi,2)
0x000000000040118d <+153>:   add    $0x4,%rsi
0x0000000000401191 <+157>:   cmp    $0x18,%rsi
0x0000000000401195 <+161>:   je     0x4011ab <phase_6+183>

0x0000000000401197 <+163>:   mov    (%rsp,%rsi,1),%ecx
0x000000000040119a <+166>:   cmp    $0x1,%ecx
0x000000000040119d <+169>:   jle    0x401183 <phase_6+143>
0x000000000040119f <+171>:   mov    $0x1,%eax
0x00000000004011a4 <+176>:   mov    $0x6032d0,%edx
0x00000000004011a9 <+181>:   jmp    0x401176 <phase_6+130>

0x00000000004011ab <+183>:   mov    0x20(%rsp),%rbx
0x00000000004011b0 <+188>:   lea    0x28(%rsp),%rax
0x00000000004011b5 <+193>:   lea    0x50(%rsp),%rsi
0x00000000004011ba <+198>:   mov    %rbx,%rcx
0x00000000004011bd <+201>:   mov    (%rax),%rdx
0x00000000004011c0 <+204>:   mov    %rdx,0x8(%rcx)
0x00000000004011c4 <+208>:   add    $0x8,%rax
0x00000000004011c8 <+212>:   cmp    %rsi,%rax
0x00000000004011cb <+215>:   je     0x4011d2 <phase_6+222>
0x00000000004011cd <+217>:   mov    %rdx,%rcx
0x00000000004011d0 <+220>:   jmp    0x4011bd <phase_6+201>

0x00000000004011d2 <+222>:   movq   $0x0,0x8(%rdx)
0x00000000004011da <+230>:   mov    $0x5,%ebp

0x00000000004011df <+235>:   mov    0x8(%rbx),%rax
0x00000000004011e3 <+239>:   mov    (%rax),%eax
0x00000000004011e5 <+241>:   cmp    %eax,(%rbx)
0x00000000004011e7 <+243>:   jge    0x4011ee <phase_6+250>
0x00000000004011e9 <+245>:   callq  0x40143a <explode_bomb>

0x00000000004011ee <+250>:   mov    0x8(%rbx),%rbx
0x00000000004011f2 <+254>:   sub    $0x1,%ebp
0x00000000004011f5 <+257>:   jne    0x4011df <phase_6+235>
0x00000000004011f7 <+259>:   add    $0x50,%rsp
0x00000000004011fb <+263>:   pop    %rbx
0x00000000004011fc <+264>:   pop    %rbp
0x00000000004011fd <+265>:   pop    %r12
0x00000000004011ff <+267>:   pop    %r13
0x0000000000401201 <+269>:   pop    %r14
0x0000000000401203 <+271>:   retq

循环拆分

检测输入

首先是第一个循环,检查6个输入值是否唯一并且在[1,6]范围内。

0x000000000040110e <+26>:    mov    $0x0,%r12d # r12 = 0
0x0000000000401114 <+32>:    mov    %r13,%rbp # rbp = r13
0x0000000000401117 <+35>:    mov    0x0(%r13),%eax #  rax = M[r13]
0x000000000040111b <+39>:    sub    $0x1,%eax # eax = eax - 1
0x000000000040111e <+42>:    cmp    $0x5,%eax # 大于5则结束
0x0000000000401121 <+45>:    jbe    0x401128 <phase_6+52>
0x0000000000401123 <+47>:    callq  0x40143a <explode_bomb>]

0x0000000000401128 <+52>:    add    $0x1,%r12d # r12 += 1, 元素计数
0x000000000040112c <+56>:    cmp    $0x6,%r12d # r12==6?
0x0000000000401130 <+60>:    je     0x401153 <phase_6+95>
0x0000000000401132 <+62>:    mov    %r12d,%ebx # rbx = r12

0x0000000000401135 <+65>:    movslq %ebx,%rax 
0x0000000000401138 <+68>:    mov    (%rsp,%rax,4),%eax # rax = 4*rax + rsp 将每个数字都拷贝到rax
0x000000000040113b <+71>:    cmp    %eax,0x0(%rbp) # rax 和 M[rbp]是否相等?
0x000000000040113e <+74>:    jne    0x401145 <phase_6+81>
0x0000000000401140 <+76>:    callq  0x40143a <explode_bomb> # 相等则bomb

0x0000000000401145 <+81>:    add    $0x1,%ebx # rbx += 1, j++
0x0000000000401148 <+84>:    cmp    $0x5,%ebx # rbx <= 5 ?
0x000000000040114b <+87>:    jle    0x401135 <phase_6+65>

0x000000000040114d <+89>:    add    $0x4,%r13 # r13 += 4, 就是rsp移动了4字节
0x0000000000401151 <+93>:    jmp    0x401114 <phase_6+32>  # 又回到了+32,是双重循环, 此时是下一个元素

修改输入值

第二循环是一个单层循环,这个循环主要就是修改每个数组元素array[i] = 7 - array[i]

0x0000000000401153 <+95>:    lea    0x18(%rsp),%rsi # rsi = 0
0x0000000000401158 <+100>:   mov    %r14,%rax # rax = r14
0x000000000040115b <+103>:   mov    $0x7,%ecx # rcx = 7

# 单层循环将数组的每个元素赋值为 7 - nums[i]
0x0000000000401160 <+108>:   mov    %ecx,%edx # rdx = rcx
0x0000000000401162 <+110>:   sub    (%rax),%edx # rdx -= *rax
0x0000000000401164 <+112>:   mov    %edx,(%rax) *rax = edx # 
0x0000000000401166 <+114>:   add    $0x4,%rax # rax += 4
0x000000000040116a <+118>:   cmp    %rsi,%rax  # rax是否为0
0x000000000040116d <+121>:   jne    0x401160 <phase_6+108> #这里是一个单层循环

首先是将rsp的偏移0x18位置赋值到rsi,此时的r14的地址就是rsp中数组元素的首地址(下图是输入了1,2,3,4,5,6)。

image.png

循环后结束发现,数组元素确实变成了7-x形式,在这里就是刚好顺序反过来了。

image.png

构造节点数组

第三个循环,这是一个双重循环,这里一个地址值很重要,0x6032d0

0x000000000040116f <+123>:   mov    $0x0,%esi # rsi = 0 
0x0000000000401174 <+128>:   jmp    0x401197 <phase_6+163> 

0x0000000000401176 <+130>:   mov    0x8(%rdx),%rdx
0x000000000040117a <+134>:   add    $0x1,%eax  
0x000000000040117d <+137>:   cmp    %ecx,%eax
0x000000000040117f <+139>:   jne    0x401176 <phase_6+130>
0x0000000000401181 <+141>:   jmp    0x401188 <phase_6+148>

0x0000000000401183 <+143>:   mov    $0x6032d0,%edx #将地址放到edx
0x0000000000401188 <+148>:   mov    %rdx,0x20(%rsp,%rsi,2) # 将地址放到栈内存中
0x000000000040118d <+153>:   add    $0x4,%rsi # 偏移量+4
0x0000000000401191 <+157>:   cmp    $0x18,%rsi # 是否超过元素个数范围
0x0000000000401195 <+161>:   je     0x4011ab <phase_6+183>  # 是就跳出

0x0000000000401197 <+163>:   mov    (%rsp,%rsi,1),%ecx # 将数组元素值取出来放到rcx
0x000000000040119a <+166>:   cmp    $0x1,%ecx # 判断元素值是否为1
0x000000000040119d <+169>:   jle    0x401183 <phase_6+143> #如果小于等于1

0x000000000040119f <+171>:   mov    $0x1,%eax
0x00000000004011a4 <+176>:   mov    $0x6032d0,%edx # 把另一段内存地址放到了rdx中
0x00000000004011a9 <+181>:   jmp    0x401176 <phase_6+130>

这里面涉及到了链表节点,而且不止一个,是一个数组,每个节点元素占了16个字节,包括指针域和整型值。

image.png

调试过程中,130内部是链表节点的操作,不断将next域的地址赋给当前节点变量变量,可以看到不断stepi执行,rdx指向的节点是在变化的。

image.png

再看到这个内部循环的控制变量,eax肯定是for循环中的循环控制变量,因为进入130循环前都是将它赋值为1。内部循环结束后是跳转到了148偏移位置,这里开始将rdx的地址放到M[rsp+rsi*2+0x20]的位置,这个地方应该是分配的节点数组。

image.png

在后面的代码实际上就是外部循环的开头,更新rsi指针

0x0000000000401188 <+148>:   mov    %rdx,0x20(%rsp,%rsi,2) # 将地址放到栈内存中
0x000000000040118d <+153>:   add    $0x4,%rsi # 偏移量+4
0x0000000000401191 <+157>:   cmp    $0x18,%rsi # 是否超过元素个数范围
0x0000000000401195 <+161>:   je     0x4011ab <phase_6+183>  # 是就跳出
0x0000000000401197 <+163>:   mov    (%rsp,%rsi,1),%ecx # 将数组元素值取出来放到rcx
0x000000000040119a <+166>:   cmp    $0x1,%ecx # 判断元素值是否为1
0x000000000040119d <+169>:   jle    0x401183 <phase_6+143> #如果小于等于1

构造完的链表结构如下,构造出来的内容就是指针域的对应,但是会发现还没有碰这个内存里面的指针节点时,他前面的一些内容已经初始化好了,刚好有123456(跟后续的操作有关)。

image.png

搬运全局内存到栈内存中

下面的部分就是将链表节点从bss段搬运到栈内存中。

0x00000000004011ab <+183>:   mov    0x20(%rsp),%rbx  #node3 地址存放到rbx
0x00000000004011b0 <+188>:   lea    0x28(%rsp),%rax  #node3 的指针域地址存放到rax,也就是node4的地址
0x00000000004011b5 <+193>:   lea    0x50(%rsp),%rsi # rsi指向保存的链表节点地址末尾, 0x50是前面分配的栈大小
0x00000000004011ba <+198>:   mov    %rbx,%rcx 
0x00000000004011bd <+201>:   mov    (%rax),%rdx 
0x00000000004011c0 <+204>:   mov    %rdx,0x8(%rcx) # 栈指向的最后一个节点地址复制到前一个节点的地址位置
0x00000000004011c4 <+208>:   add    $0x8,%rax  # 移动到下一个节点
0x00000000004011c8 <+212>:   cmp    %rsi,%rax # 是否到达链表末尾,是否遍历完成
0x00000000004011cb <+215>:   je     0x4011d2 <phase_6+222> 
0x00000000004011cd <+217>:   mov    %rdx,%rcx # 更新rcx指向下一个节点
0x00000000004011d0 <+220>:   jmp    0x4011bd <phase_6+201>
0x00000000004011d2 <+222>:   movq   $0x0,0x8(%rdx)

单步调试到222后,查看栈的内存,将节点指针移动到了栈中。而且顺序不太一样,实际上就是根据之前的节点内存图片里面的数值(39c, 263, 1dd, 1bb, 14c, 0a8)重新排序链接扔到了栈里面。

image.png

检查链表值大小

最后就是检查链表中每一个节点是否比下一个节点值要大。

0x00000000004011da <+230>:   mov    $0x5,%ebp
0x00000000004011df <+235>:   mov    0x8(%rbx),%rax  # rax指向头指针
0x00000000004011e3 <+239>:   mov    (%rax),%eax  
0x00000000004011e5 <+241>:   cmp    %eax,(%rbx) #比较节点值
0x00000000004011e7 <+243>:   jge    0x4011ee <phase_6+250> 
0x00000000004011e9 <+245>:   callq  0x40143a <explode_bomb>

0x00000000004011ee <+250>:   mov    0x8(%rbx),%rbx # 访问next指针
0x00000000004011f2 <+254>:   sub    $0x1,%ebp
0x00000000004011f5 <+257>:   jne    0x4011df <phase_6+235>
0x00000000004011f7 <+259>:   add    $0x50,%rsp
0x00000000004011fb <+263>:   pop    %rbx
0x00000000004011fc <+264>:   pop    %rbp
0x00000000004011fd <+265>:   pop    %r12
0x00000000004011ff <+267>:   pop    %r13
0x0000000000401201 <+269>:   pop    %r14
0x0000000000401203 <+271>:   retq

总结

  • bomb lab把x86_64常用特殊寄存器在课本那一页多翻,多写课本上的几个练习题还是没太大问题的,如果一些指令忘记了回去翻ppt或书,最好了个相应的练习题
  • 给自己多点耐心,最后两题我写了差不多一个周日
  • 最后,phase6题目花的时间是前面5道题时间的几倍(麻了)