• CSAPP实验记录(2)--------- Bomb


    实验简介

    本实验需要拆除一个“二进制炸弹”,“二进制炸弹”是一个可执行目标程序。运行时,它会提示用户键入6个不同的字符串。如果其中任何一个错误,炸弹就会“爆炸”。必须通过逆向工程和汇编语言知识,推导出六个字符串分别是什么。从而拆除炸弹。

    开始实验

    下载实验包后,会解压得到一个bomb.c源码和bomb.o可执行目标文件。bomb.c源码如下:

    int main(int argc, char *argv[])
    {
        char *input;
    
        /* Note to self: remember to port this bomb to Windows and put a 
         * fantastic GUI on it. */
    
        /* When run with no arguments, the bomb reads its input lines 
         * from standard input. */
        if (argc == 1) {  
    	infile = stdin;
        } 
    
        /* When run with one argument , the bomb reads from  
         * until EOF, and then switches to standard input. Thus, as you 
         * defuse each phase, you can add its defusing string to  and
         * avoid having to retype it. */
        else if (argc == 2) {
    	if (!(infile = fopen(argv[1], "r"))) {
    	    printf("%s: Error: Couldn't open %s\n", argv[0], argv[1]);
    	    exit(8);
    	}
        }
    
        /* You can't call the bomb with more than 1 command line argument. */
        else {
    	printf("Usage: %s []\n", argv[0]);
    	exit(8);
        }
    
        /* Do all sorts of secret stuff that makes the bomb harder to defuse. */
        initialize_bomb();
    
        printf("Welcome to my fiendish little bomb. You have 6 phases with\n");
        printf("which to blow yourself up. Have a nice day!\n");
    
        /* Hmm...  Six phases must be more secure than one phase! */
        input = read_line();             /* Get input                   */
        phase_1(input);                  /* Run the phase               */
        phase_defused();                 /* Drat!  They figured it out!
    				      * Let me know how they did it. */
        printf("Phase 1 defused. How about the next one?\n");
    
        /* The second phase is harder.  No one will ever figure out
         * how to defuse this... */
        input = read_line();
        phase_2(input);
        phase_defused();
        printf("That's number 2.  Keep going!\n");
    
        /* I guess this is too easy so far.  Some more complex code will
         * confuse people. */
        input = read_line();
        phase_3(input);
        phase_defused();
        printf("Halfway there!\n");
    
        /* Oh yeah?  Well, how good is your math?  Try on this saucy problem! */
        input = read_line();
        phase_4(input);
        phase_defused();
        printf("So you got that one.  Try this one.\n");
        
        /* Round and 'round in memory we go, where we stop, the bomb blows! */
        input = read_line();
        phase_5(input);
        phase_defused();
        printf("Good work!  On to the next...\n");
    
        /* This phase will never be used, since no one will get past the
         * earlier ones.  But just in case, make this one extra hard. */
        input = read_line();
        phase_6(input);
        phase_defused();
    
        /* Wow, they got it!  But isn't something... missing?  Perhaps
         * something they overlooked?  Mua ha ha ha ha! */
        
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80

    可以看到代码中有许多没有给出定义(其实是定义在spport.h头文件中,这个头文件没有给出)的函数,也就是说我们仅仅通过bomb.c代码是不能得知我们要输入的六个字符串的,不过好在题目给出了代码编译出的可执行目标文件bomb.o,这样我们就有机会通过逆向工程确定bomb.c的源码了。
    接下来运行可执行目标文件bomb.o,先随便输入个abc试一试效果:

    在这里插入图片描述
    果然会爆炸。。
    看来现在可以对bomb.o的汇编代码进行分析了,使用objdump工具,将bomb.o的反汇编代码导入到txt文件中,这里我用
    下面是主函数的反汇编代码:

    0000000000400da0 
    : 400da0: 53 push %rbx 400da1: 83 ff 01 cmp $0x1,%edi 400da4: 75 10 jne 400db6 400da6: 48 8b 05 9b 29 20 00 mov 0x20299b(%rip),%rax # 603748 400dad: 48 89 05 b4 29 20 00 mov %rax,0x2029b4(%rip) # 603768 400db4: eb 63 jmp 400e19 400db6: 48 89 f3 mov %rsi,%rbx 400db9: 83 ff 02 cmp $0x2,%edi 400dbc: 75 3a jne 400df8 400dbe: 48 8b 7e 08 mov 0x8(%rsi),%rdi 400dc2: be b4 22 40 00 mov $0x4022b4,%esi 400dc7: e8 44 fe ff ff callq 400c10 400dcc: 48 89 05 95 29 20 00 mov %rax,0x202995(%rip) # 603768 400dd3: 48 85 c0 test %rax,%rax 400dd6: 75 41 jne 400e19 400dd8: 48 8b 4b 08 mov 0x8(%rbx),%rcx 400ddc: 48 8b 13 mov (%rbx),%rdx 400ddf: be b6 22 40 00 mov $0x4022b6,%esi 400de4: bf 01 00 00 00 mov $0x1,%edi 400de9: e8 12 fe ff ff callq 400c00 <__printf_chk@plt> 400dee: bf 08 00 00 00 mov $0x8,%edi 400df3: e8 28 fe ff ff callq 400c20 400df8: 48 8b 16 mov (%rsi),%rdx 400dfb: be d3 22 40 00 mov $0x4022d3,%esi 400e00: bf 01 00 00 00 mov $0x1,%edi 400e05: b8 00 00 00 00 mov $0x0,%eax 400e0a: e8 f1 fd ff ff callq 400c00 <__printf_chk@plt> 400e0f: bf 08 00 00 00 mov $0x8,%edi 400e14: e8 07 fe ff ff callq 400c20 400e19: e8 84 05 00 00 callq 4013a2 400e1e: bf 38 23 40 00 mov $0x402338,%edi 400e23: e8 e8 fc ff ff callq 400b10 400e28: bf 78 23 40 00 mov $0x402378,%edi 400e2d: e8 de fc ff ff callq 400b10 400e32: e8 67 06 00 00 callq 40149e 400e37: 48 89 c7 mov %rax,%rdi 400e3a: e8 a1 00 00 00 callq 400ee0 400e3f: e8 80 07 00 00 callq 4015c4 400e44: bf a8 23 40 00 mov $0x4023a8,%edi 400e49: e8 c2 fc ff ff callq 400b10 400e4e: e8 4b 06 00 00 callq 40149e 400e53: 48 89 c7 mov %rax,%rdi 400e56: e8 a1 00 00 00 callq 400efc 400e5b: e8 64 07 00 00 callq 4015c4 400e60: bf ed 22 40 00 mov $0x4022ed,%edi 400e65: e8 a6 fc ff ff callq 400b10 400e6a: e8 2f 06 00 00 callq 40149e 400e6f: 48 89 c7 mov %rax,%rdi 400e72: e8 cc 00 00 00 callq 400f43 400e77: e8 48 07 00 00 callq 4015c4 400e7c: bf 0b 23 40 00 mov $0x40230b,%edi 400e81: e8 8a fc ff ff callq 400b10 400e86: e8 13 06 00 00 callq 40149e 400e8b: 48 89 c7 mov %rax,%rdi 400e8e: e8 79 01 00 00 callq 40100c 400e93: e8 2c 07 00 00 callq 4015c4 400e98: bf d8 23 40 00 mov $0x4023d8,%edi 400e9d: e8 6e fc ff ff callq 400b10 400ea2: e8 f7 05 00 00 callq 40149e 400ea7: 48 89 c7 mov %rax,%rdi 400eaa: e8 b3 01 00 00 callq 401062 400eaf: e8 10 07 00 00 callq 4015c4 400eb4: bf 1a 23 40 00 mov $0x40231a,%edi 400eb9: e8 52 fc ff ff callq 400b10 400ebe: e8 db 05 00 00 callq 40149e 400ec3: 48 89 c7 mov %rax,%rdi 400ec6: e8 29 02 00 00 callq 4010f4 400ecb: e8 f4 06 00 00 callq 4015c4 400ed0: b8 00 00 00 00 mov $0x0,%eax 400ed5: 5b pop %rbx 400ed6: c3 retq 400ed7: 90 nop 400ed8: 90 nop 400ed9: 90 nop 400eda: 90 nop 400edb: 90 nop 400edc: 90 nop 400edd: 90 nop 400ede: 90 nop 400edf: 90 nop
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81

    第一个炸弹

    在这里插入图片描述

    从bomb.c的代码片段可以看出,每道题目都是由一个函数开始的,我们在反汇编代码中找到phase_1的代码:

    0000000000400ee0 :
      400ee0:	48 83 ec 08          	sub    $0x8,%rsp
      400ee4:	be 00 24 40 00       	mov    $0x402400,%esi
      400ee9:	e8 4a 04 00 00       	callq  401338 
      400eee:	85 c0                	test   %eax,%eax
      400ef0:	74 05                	je     400ef7 
      400ef2:	e8 43 05 00 00       	callq  40143a 
      400ef7:	48 83 c4 08          	add    $0x8,%rsp
      400efb:	c3                   	retq   
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    可以看到在第二行,程序将一个好像是地址的变量赋给了%esi,然后调用了函数,从字面上可以看出这个函数好像是判断字符串是否相等。
    容易想到,mov $0x402400,%esi这句指令是在为调用构造参数,而此时寄存器%rdi的值并没有变,所以可以想到函数有两个参数,一个是位于 0x402400 位置的数据,另一个就是 本身的参数,也就是我们输入的字符串。
    返回后,执行test指令,如果返回值不为0,那么就会执行这个显然是让炸弹爆炸的函数,我们不能让程序执行到这里。
    然后找到 0x402400:发现其位于可执行目标文件的.rodata节:

    在这里插入图片描述
    在这里插入图片描述
    也就是说,第一个炸弹的逻辑是,输入的字符串与程序中内置的格式串比对,相同就通过测试。观察0x402400处的字节序列,可以发现答案就Border relations with Canada have never been better.,这句话最后的句号坑了我很长时间,因为.rodata节里的不可见字符全都会显示成"."。总的来说第一个炸弹十分简单。

    第二个炸弹

    找到phase_2的反汇编代码部分:

    0000000000400efc :
      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       	callq  40145c 
      400f0a:	83 3c 24 01          	cmpl   $0x1,(%rsp)
      400f0e:	74 20                	je     400f30 
      400f10:	e8 25 05 00 00       	callq  40143a 
      400f15:	eb 19                	jmp    400f30 
      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 
      400f20:	e8 15 05 00 00       	callq  40143a 
      400f25:	48 83 c3 04          	add    $0x4,%rbx
      400f29:	48 39 eb             	cmp    %rbp,%rbx
      400f2c:	75 e9                	jne    400f17 
      400f2e:	eb 0c                	jmp    400f3c 
      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 
      400f3c:	48 83 c4 28          	add    $0x28,%rsp
      400f40:	5b                   	pop    %rbx
      400f41:	5d                   	pop    %rbp
      400f42:	c3                   	retq  
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    可以看到有一个叫的函数,字面意思是要读取6个数字的输入。而且注意到400f02: 48 89 e6 mov %rsp,%rsi,它似乎需要栈指针作为第二个参数,和第一个炸弹一样,此时%rdi同样没有改变,说明函数的第一个参数也是我们输入的字符串。

    通过观察400f0e一行,可以得知调用后,如果栈指针处的值为1,那么将跳转到400f30(核心代码所在处),否则直接执行炸弹。那么我们可以猜想如果我们以正确的格式输入六个数字的话,栈指针此时一定是等于0x1的。我们可以找到的位置,看看这个函数到底做了什么。

    000000000040145c :
      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 
      401494:	e8 a1 ff ff ff       	callq  40143a 
      401499:	48 83 c4 18          	add    $0x18,%rsp
      40149d:	c3                   	retq   
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    可以看到整个函数的前半部分都在进行参数的构造,只是为了能够调用__isoc99_sscanf@plt,而之前我们推导过,这个函数的第二个参数%rsi其实是栈指针,也就是说这些操作%rsi的指令其实都是对栈指针的操作。这里的<__isoc99_sscanf>函数是动态链接进来的C标准库函数,在可执行目标文件里是看不到它的信息的,所以我们只能上网查这个函数的功能了。经过查资料,我大概可以知道这个函数在里的作用是,将输入的六个整数保存在 (%rsp),(%rsp+0x4),(%rsp+0x8),(%rsp+0xc),(%rsp+0x10),(%rsp+0x14),这六个位置。也就是栈上的一片地址连续的空间中。

    然后就是从位置400f30处开始的核心代码了,可以看到指令定义了两个指针,%rbx和%rbp,一个指向 0x4(%rsp),一个指向 0x18(%rsp),这中间就是刚才我们输入的六个整数保存的位置。从:

      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 
      400f20:	e8 15 05 00 00       	callq  40143a 
    
    • 1
    • 2
    • 3
    • 4
    • 5

    这几句可以看出,程序不断将 %rbx 与 -0x4(%rbx) 对比,400f1a 一句表明当且仅当 (%rbx) 是 -0x4(%rbx)的二倍时,炸弹才不会爆炸。

      400f25:	48 83 c3 04          	add    $0x4,%rbx
      400f29:	48 39 eb             	cmp    %rbp,%rbx
      400f2c:	75 e9                	jne    400f17 
    
    • 1
    • 2
    • 3

    这三句又说明,%rbx只有和 %rbp 相等时,才能结束程序。否则将%rbx的值加4(一个int的大小),之后跳回400f17。这明显是一个循环语句。刚才我们了解到 %rbp = 0x18(rbx),也就是说当%rbx = %rbp时,程序刚好扫描到我们输入的六个数字,而且每个数字都应该是前一个的二倍,第一个数字等于 (%rsp) = 0x1,那么这个炸弹就被成功解开了。答案是:
    1 2 4 8 16 32

    第三个炸弹

    截至最终更新时,作者已经解开了五个炸弹,但这个实验总归还是比较繁琐,还剩下两个没有解开,而且像这样详细地复盘也比较费时间,没有写出来的部分之后再补 😃 。

    第五个炸弹

    phase_5的汇编代码:

    0000000000401062 :
      401062:	53                   	push   %rbx                          //保存被调者保存寄存器
      401063:	48 83 ec 20          	sub    $0x20,%rsp                    //申请 0x20 个栈空间 32B, 8个int数据
      401067:	48 89 fb             	mov    %rdi,%rbx                     //将用户输入的串(地址)保存至 %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)               //金丝雀值保存在 (%rsp)+0x18
      401078:	31 c0                	xor    %eax,%eax                     //返回值清零
      40107a:	e8 9c 02 00 00       	callq  40131b         //调用函数 返回字符串长度
      40107f:	83 f8 06             	cmp    $0x6,%eax                     //将返回值与0x6比较
      401082:	74 4e                	je     4010d2 
      401084:	e8 b1 03 00 00       	callq  40143a          //返回值不为6 则引爆 (输入的字符串长度必须是6)
      401089:	eb 47                	jmp    4010d2 
      40108b:	0f b6 0c 03          	movzbl (%rbx,%rax,1),%ecx            //%ecx = (%rbx + 1*%rax), %ecx保存输入串第i个字符的地址
      40108f:	88 0c 24             	mov    %cl,(%rsp)
      401092:	48 8b 14 24          	mov    (%rsp),%rdx
      401096:	83 e2 0f             	and    $0xf,%edx                     //掩码吸取低4位
      401099:	0f b6 92 b0 24 40 00 	movzbl 0x4024b0(%rdx),%edx           //将用户输入字符的低4位, 作为寻址的偏移量
      4010a0:	88 54 04 10          	mov    %dl,0x10(%rsp,%rax,1)         //将处理好的字符保存进栈中
      4010a4:	48 83 c0 01          	add    $0x1,%rax
      4010a8:	48 83 f8 06          	cmp    $0x6,%rax
      4010ac:	75 dd                	jne    40108b 
      4010ae:	c6 44 24 16 00       	movb   $0x0,0x16(%rsp)               //'\0', 字符串结束标志
      4010b3:	be 5e 24 40 00       	mov    $0x40245e,%esi
      4010b8:	48 8d 7c 24 10       	lea    0x10(%rsp),%rdi               //0x10(%rsp) 到 0x15(%rsp) 之间保存了经过处理的六个字符
      4010bd:	e8 76 02 00 00       	callq  401338 
      4010c2:	85 c0                	test   %eax,%eax
      4010c4:	74 13                	je     4010d9 
      4010c6:	e8 6f 03 00 00       	callq  40143a 
      4010cb:	0f 1f 44 00 00       	nopl   0x0(%rax,%rax,1)
      4010d0:	eb 07                	jmp    4010d9 
      4010d2:	b8 00 00 00 00       	mov    $0x0,%eax                           //初始化返回值
      4010d7:	eb b2                	jmp    40108b 
      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                //栈破坏检测
      4010e9:	e8 42 fa ff ff       	callq  400b30 <__stack_chk_fail@plt>
      4010ee:	48 83 c4 20          	add    $0x20,%rsp                          //释放栈空间
      4010f2:	5b                   	pop    %rbx                                //恢复被调保存寄存器
      4010f3:	c3                   	retq   
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41

    后面的题目都比较复杂,所以我将关键的部分都做了注释。
    这道题有一个值得注意的地方,就是下面几句:

      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)               //金丝雀值保存在 (%rsp)+0x18
      (中间省略)
      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                //栈破坏检测
      4010e9:	e8 42 fa ff ff       	callq  400b30 <__stack_chk_fail@plt>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    这段代码是x86的堆栈保护机制,产生金丝雀值保存在栈中,函数返回前检查金丝雀值是否发生变化,如果有变化,说明栈已经受到破坏,需要执行<__stack_chk_fail>函数,随后进程会收到SIGABRT信号并异常退出。其中 %fs:0x28 处的值是每次执行程序时随机产生的,%fs代表附加段。

    然后开始分析代码,由40107a: e8 9c 02 00 00 callq 40131b 及其附近的代码可知,这次程序需要输入一个长度为6的字符串,的功能就是字面意思,返回字符串的长度。其参数就是 %rdi,即保存的用户输入字符串的基地址,这个操作之前已经见过多次了。

    40108b开始,就是这个炸弹的核心逻辑。首先注意到 401067: 48 89 fb mov %rdi,%rbx一句,将的参数,即用户输入的字符串 (的基地址),保存进%rbx。也就是说此后所有对 %rbx进行的操作,都是对用户输入进行的操作。

    然后我们观察一下,什么情况下会引发爆炸。注意到如下代码:

      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       	callq  401338 
      4010c2:	85 c0                	test   %eax,%eax
      4010c4:	74 13                	je     4010d9 
      4010c6:	e8 6f 03 00 00       	callq  40143a 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    这段代码以 0x10(%rsp) 作为第一个参数,以 0x40245e——看上去像是一个地址,作为第二个参数,执行,字面意思是判断两个字符串是否相等,然后如果不等则会引发爆炸。我们可以想到,0x10(%rsp)处保存的值很有可能与用户输入有关。我们先去看看 0x40245e 保存了什么内容:

    在这里插入图片描述
    从0x40245e 开始,截取6个字符,恰好是字符串“flyers”。但是经验证,答案并不是flyers,所以可以猜到,用户的字符串应该经过某种处理,变成“flyers”。

    然后再看核心代码部分:

      40108b:	0f b6 0c 03          	movzbl (%rbx,%rax,1),%ecx            //%ecx = (%rbx + 1*%rax), %ecx保存输入串第i个字符的地址
      40108f:	88 0c 24             	mov    %cl,(%rsp)
      401092:	48 8b 14 24          	mov    (%rsp),%rdx
      401096:	83 e2 0f             	and    $0xf,%edx                     //掩码吸取低4位
      401099:	0f b6 92 b0 24 40 00 	movzbl 0x4024b0(%rdx),%edx           //将用户输入字符的低4位, 作为寻址的偏移量
      4010a0:	88 54 04 10          	mov    %dl,0x10(%rsp,%rax,1)         //将处理好的字符保存进栈中
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    前面提到过,现在对于%rbx的操作,就等于对输入的字符串的操作,因为%rbx里保存的是输入串的地址。那么40108b一句中,%ecx = (%rbx + 1*%rax) 的操作就很可能是对字符数组的引用,其中%rax保存了数组的下标。随后程序将得到的引用结果的低八位(char一共就8位),放进了%rdx中。然后关键的地方是401096401099两句:

      401096:	83 e2 0f             	and    $0xf,%edx                     //掩码吸取低4位
      401099:	0f b6 92 b0 24 40 00 	movzbl 0x4024b0(%rdx),%edx           //将用户输入字符的低4位, 作为寻址的偏移量
    
    • 1
    • 2

    虽然程序输入的是char类型的数组,但程序实际只用到了每个字符的低四位(0xf & %edx),高四位实际上是没有用的。而这低四位被用作一个变址寻址指令的偏移量:movzbl 0x4024b0(%rdx),%edx。我们找到 0x4024b0 这个位置:

    在这里插入图片描述
    看到一串意义不明的字符串,这条指令引用的数据都在这里。我们可以试试这些字符能不能拼凑出“flyers”:可以看到,当偏移量 %rdx 的值为:9,f,e,5,6,7时,刚好可以组成单词“flyers”。也就是说我们只需要让偏移量的值等于这些数字就可以了。但是还有一个问题:那就是我输入的字符串都是ASCII码值,我没有办法输入一个ASCII码为"0x9"的字符,因为那是一个控制字符。

    这时候就要想到,这个偏移量不是只取低四位么,那么高四位根本不会对结果产生影响,即使他们不为零。那么我就可以利用高四位,来输入可见字符。我只需要让0x9,0xf,0xe,0x5,0x6,0x7这六个十六进制数,加上一个高四位变成可见字符就可以了,这里我取:
    0x39 = '9',0x3f = '?',0x3e = '>',0x35 = '5',0x36 = '6',0x37 = '7'
    那么第五个炸弹的一个可行解就是:9?>567

    第六个炸弹

    截至最终更新时,作者已经解开了五个炸弹,但这个实验总归还是比较繁琐,还剩下两个没有解开,而且像这样详细地复盘也比较费时间,没有写出来的部分之后再补 😃 。

  • 相关阅读:
    再探Kotlin 跨平台——迁移Paging分页库至KMM
    关键的服务器硬件组件及其基本功能
    zemax---Ray Aberration(光线光扇图)
    实现Spring的Ordered接口,控制Bean的初始化优先级最高
    shell-数组和关联数组
    数字人解决方案——ER-NeRF实时对话数字人模型训练与项目部署
    并发编程二 JMM&volatile详解
    PyQt5快速开发与实战 6.1 好软件的三个维度 && 6.2 PyQt5中的布局管理 && 6.3 PyQt5的绝对位置布局
    画画水族馆的应用特色及功能
    从IDEA开始,迈进GO语言之门
  • 原文地址:https://blog.csdn.net/Berserker____/article/details/127760225