哈工大计算机系统Lab2.二进制炸弹

说明

给定一个可执行目标文件bomb,共有6个阶段,需要用gdb等程序分析反汇编,以获取每个阶段对应的密码。若密码输错则炸弹爆炸。其中一个炸弹可以从这里获取(若提示无法安全下载可以把链接复制到浏览器试试)。如果想要答案可以直接翻到最后一部分。

准备工作

实验环境:Ubuntu 20.04

获取反汇编

objdump -d bomb > asm.txt

在这里插入图片描述
就可以把反汇编以文件形式存到当前文件夹下。

炸弹程序框架

和bomb同时下发了一个bomb.c文件,为炸弹程序框架:

/***************************************************************************
 * Dr. Evil's Insidious Bomb, Version 1.1
 * Copyright 2011, Dr. Evil Incorporated. All rights reserved.
 *
 * LICENSE:
 *
 * Dr. Evil Incorporated (the PERPETRATOR) hereby grants you (the
 * VICTIM) explicit permission to use this bomb (the BOMB).  This is a
 * time limited license, which expires on the death of the VICTIM.
 * The PERPETRATOR takes no responsibility for damage, frustration,
 * insanity, bug-eyes, carpal-tunnel syndrome, loss of sleep, or other
 * harm to the VICTIM.  Unless the PERPETRATOR wants to take credit,
 * that is.  The VICTIM may not distribute this bomb source code to
 * any enemies of the PERPETRATOR.  No VICTIM may debug,
 * reverse-engineer, run "strings" on, decompile, decrypt, or use any
 * other technique to gain knowledge of and defuse the BOMB.  BOMB
 * proof clothing may not be worn when handling this program.  The
 * PERPETRATOR will not apologize for the PERPETRATOR's poor sense of
 * humor.  This license is null and void where the BOMB is prohibited
 * by law.
 ***************************************************************************/

#include <stdio.h>
#include <stdlib.h>
#include "support.h"
#include "phases.h"

/* 
 * Note to self: Remember to erase this file so my victims will have no
 * idea what is going on, and so they will all blow up in a
 * spectaculary fiendish explosion. -- Dr. Evil 
 */

FILE *infile;

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 <file>, the bomb reads from <file> 
     * until EOF, and then switches to standard input. Thus, as you 
     * defuse each phase, you can add its defusing string to <file> 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 [<input_file>]\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;
}

可以看到程序在每个阶段之前调用read_line函数读取一行,然后进入各个phase。

输入方式

程序可以直接从键盘获取输入;或者以文件给出头一部分输入,随后用键盘输入,用以避免破解后面阶段时对前面阶段答案的重复输入。以文件给出输入时,需要在bomb的目录下新建文本文件文件(如ans.txt),输入若干个阶段的答案,在运行时传入另一个参数:

./bomb ans.txt

或者在gdb内:

gdb bomb
run ans.txt

注:当运行时出现permission denied,
右键文件->Properties(属性)->Permissions(许可)->把execute的Allow executing file as program属性勾上即可。
在这里插入图片描述
在这里插入图片描述

要用到的GDB指令

disas

用于反汇编某一函数(有了objdump反汇编出的就可以不用了)。

disas phase_1

break

break指令用于设置断点。可以设置于反汇编的某地址处:

break * 0x4013ec

或者设置于某一函数开始处偏移一定位置处:

break * phase_1+9

在这里插入图片描述

print

用于打印某处内存/寄存器的值,一般读内存加上类型强制转换:

(注意不是%rax而是$rax)
print $rax

以16进制输出
print /x $rax

即0x8(%rsp),以四字节整型解释,不加强制转换会被视为void*
print *(int*)($rsp+8)

以字符串方式解释0x403150处的数据
print (char*)0x403150

x

以一定格式输出字符串的值。
格式: x /nfu <addr>
其中:
n为要显示的单元数;
f为显示格式,本实验中有用的有d:十进制,x:十六进制
u为单元大小,b/h/w/g分别表示1/2/4/8字节。

以十六进制打印0x4050f0开始的80个8字节值
x /80xg 0x4050f0

阶段一

考察点:字符串比较;函数传参
反汇编如下(汇编中前面不空两格的是注释):

00000000004013ec <phase_1>:
  4013ec:	48 83 ec 08          	sub    $0x8,%rsp
  4013f0:	be 50 31 40 00       	mov    $0x403150,%esi
  4013f5:	e8 b7 04 00 00       	callq  4018b1 <strings_not_equal>
  4013fa:	85 c0                	test   %eax,%eax
  4013fc:	75 05                	jne    401403 <phase_1+0x17>
  4013fe:	48 83 c4 08          	add    $0x8,%rsp
  401402:	c3                   	retq   
  401403:	e8 8b 05 00 00       	callq  401993 <explode_bomb>
  401408:	eb f4                	jmp    4013fe <phase_1+0x12>

可以看到,在程序调用strings_not_equal之前,将地址0x403150传入参数。猜测内存该处存放了答案字符串。用gdb设置断点,在此处查看该地址处的字符串:

print (char*)0x403150

在这里插入图片描述

即为phase_1的答案I was trying to give Tina Fey more material.。至于strings_not_equal函数具体如何实现,在这一阶段并不是重点,只要功能和函数名对上就不用费时间去考察了。

阶段二

考察点:循环
反汇编:

000000000040140a <phase_2>:
let ebx = i
  40140a:	53                   	push   %rbx
  40140b:	48 83 ec 20          	sub    $0x20,%rsp
  40140f:	48 89 e6             	mov    %rsp,%rsi
  401412:	e8 a0 05 00 00       	callq  4019b7 <read_six_numbers>
  401417:	83 3c 24 01          	cmpl   $0x1,(%rsp)
  40141b:	75 07                	jne    401424 <phase_2+0x1a>
  40141d:	bb 01 00 00 00       	mov    $0x1,%ebx
  401422:	eb 0f                	jmp    401433 <phase_2+0x29>
  401424:	e8 6a 05 00 00       	callq  401993 <explode_bomb>
  401429:	eb f2                	jmp    40141d <phase_2+0x13>
  40142b:	e8 63 05 00 00       	callq  401993 <explode_bomb>
  401430:	83 c3 01             	add    $0x1,%ebx
  401433:	83 fb 05             	cmp    $0x5,%ebx
  401436:	7f 14                	jg     40144c <phase_2+0x42>
  401438:	48 63 d3             	movslq %ebx,%rdx
  40143b:	8d 43 ff             	lea    -0x1(%rbx),%eax
  40143e:	48 98                	cltq   
  401440:	8b 04 84             	mov    (%rsp,%rax,4),%eax
  401443:	01 c0                	add    %eax,%eax
  401445:	39 04 94             	cmp    %eax,(%rsp,%rdx,4)
  401448:	74 e6                	je     401430 <phase_2+0x26>
  40144a:	eb df                	jmp    40142b <phase_2+0x21>
  40144c:	48 83 c4 20          	add    $0x20,%rsp
  401450:	5b                   	pop    %rbx
  401451:	c3                   	retq   

在401412地址处调用read_six_numbers,推测要输入六个参数。随后比较(%rsp)与1,若(%rsp)!=1,则引爆炸弹,于是可知第一个参数(存放于栈指针处)为1.接下来程序初始化%ebx为1,并且在%ebx>5时跳转到40144c,通过phase_2.因此只需让程序顺利通过该循环即可。
设%ebx代表循环变量i,那么下面的循环相当于如下代码(省去了一些中间过程):

arg[0] = 1;//第1个参数
int i;//%ebx
for(i=1;i<=5;i++) {
    
    
	int prev_arg = arg[i-1];//%eax
	int now_arg = arg[i];//(%rsp,%rdx,4)
	if(prev_arg * 2 != now_arg)
		explode_bomb();
}
return;

该程序检查6个参数是否为首项为1,公比为2的等比数列。因此答案即为等比数列前6项1 2 4 8 16 32

阶段三

考察点:switch(跳转表)

0000000000401452 <phase_3>:
  401452:	48 83 ec 18          	sub    $0x18,%rsp
  401456:	4c 8d 44 24 08       	lea    0x8(%rsp),%r8
  40145b:	48 8d 4c 24 07       	lea    0x7(%rsp),%rcx
  401460:	48 8d 54 24 0c       	lea    0xc(%rsp),%rdx
  401465:	be a6 31 40 00       	mov    $0x4031a6,%esi
  40146a:	b8 00 00 00 00       	mov    $0x0,%eax
  40146f:	e8 9c fc ff ff       	callq  401110 <__isoc99_sscanf@plt>
  401474:	83 f8 02             	cmp    $0x2,%eax
  401477:	7e 16                	jle    40148f <phase_3+0x3d>
  401479:	8b 44 24 0c          	mov    0xc(%rsp),%eax
  40147d:	83 f8 07             	cmp    $0x7,%eax
  401480:	0f 87 0d 01 00 00    	ja     401593 <phase_3+0x141>
  401486:	89 c0                	mov    %eax,%eax
  401488:	ff 24 c5 c0 31 40 00 	jmpq   *0x4031c0(,%rax,8)
  40148f:	e8 ff 04 00 00       	callq  401993 <explode_bomb>
  401494:	eb e3                	jmp    401479 <phase_3+0x27>
  401496:	81 7c 24 08 b2 01 00 	cmpl   $0x1b2,0x8(%rsp)
  40149d:	00 
  40149e:	75 0a                	jne    4014aa <phase_3+0x58>
  4014a0:	b8 65 00 00 00       	mov    $0x65,%eax
  4014a5:	e9 f3 00 00 00       	jmpq   40159d <phase_3+0x14b>
  4014aa:	e8 e4 04 00 00       	callq  401993 <explode_bomb>
  4014af:	b8 65 00 00 00       	mov    $0x65,%eax
  4014b4:	e9 e4 00 00 00       	jmpq   40159d <phase_3+0x14b>
  4014b9:	81 7c 24 08 46 02 00 	cmpl   $0x246,0x8(%rsp)
  4014c0:	00 
  4014c1:	75 0a                	jne    4014cd <phase_3+0x7b>
  4014c3:	b8 74 00 00 00       	mov    $0x74,%eax
  4014c8:	e9 d0 00 00 00       	jmpq   40159d <phase_3+0x14b>
  4014cd:	e8 c1 04 00 00       	callq  401993 <explode_bomb>
  4014d2:	b8 74 00 00 00       	mov    $0x74,%eax
  4014d7:	e9 c1 00 00 00       	jmpq   40159d <phase_3+0x14b>
  4014dc:	81 7c 24 08 81 00 00 	cmpl   $0x81,0x8(%rsp)
  4014e3:	00 
  4014e4:	75 0a                	jne    4014f0 <phase_3+0x9e>
  4014e6:	b8 6c 00 00 00       	mov    $0x6c,%eax
  4014eb:	e9 ad 00 00 00       	jmpq   40159d <phase_3+0x14b>
  4014f0:	e8 9e 04 00 00       	callq  401993 <explode_bomb>
  4014f5:	b8 6c 00 00 00       	mov    $0x6c,%eax
  4014fa:	e9 9e 00 00 00       	jmpq   40159d <phase_3+0x14b>
  4014ff:	81 7c 24 08 7f 02 00 	cmpl   $0x27f,0x8(%rsp)
  401506:	00 
  401507:	75 0a                	jne    401513 <phase_3+0xc1>
  401509:	b8 68 00 00 00       	mov    $0x68,%eax
  40150e:	e9 8a 00 00 00       	jmpq   40159d <phase_3+0x14b>
  401513:	e8 7b 04 00 00       	callq  401993 <explode_bomb>
  401518:	b8 68 00 00 00       	mov    $0x68,%eax
  40151d:	eb 7e                	jmp    40159d <phase_3+0x14b>
  40151f:	81 7c 24 08 12 01 00 	cmpl   $0x112,0x8(%rsp)
  401526:	00 
  401527:	75 07                	jne    401530 <phase_3+0xde>
  401529:	b8 6e 00 00 00       	mov    $0x6e,%eax
  40152e:	eb 6d                	jmp    40159d <phase_3+0x14b>
  401530:	e8 5e 04 00 00       	callq  401993 <explode_bomb>
  401535:	b8 6e 00 00 00       	mov    $0x6e,%eax
  40153a:	eb 61                	jmp    40159d <phase_3+0x14b>
  40153c:	81 7c 24 08 74 03 00 	cmpl   $0x374,0x8(%rsp)
  401543:	00 
  401544:	75 07                	jne    40154d <phase_3+0xfb>
  401546:	b8 76 00 00 00       	mov    $0x76,%eax
  40154b:	eb 50                	jmp    40159d <phase_3+0x14b>
  40154d:	e8 41 04 00 00       	callq  401993 <explode_bomb>
  401552:	b8 76 00 00 00       	mov    $0x76,%eax
  401557:	eb 44                	jmp    40159d <phase_3+0x14b>
  401559:	81 7c 24 08 b9 03 00 	cmpl   $0x3b9,0x8(%rsp)
  401560:	00 
  401561:	75 07                	jne    40156a <phase_3+0x118>
  401563:	b8 75 00 00 00       	mov    $0x75,%eax
  401568:	eb 33                	jmp    40159d <phase_3+0x14b>
  40156a:	e8 24 04 00 00       	callq  401993 <explode_bomb>
  40156f:	b8 75 00 00 00       	mov    $0x75,%eax
  401574:	eb 27                	jmp    40159d <phase_3+0x14b>
  401576:	81 7c 24 08 af 00 00 	cmpl   $0xaf,0x8(%rsp)
  40157d:	00 
  40157e:	75 07                	jne    401587 <phase_3+0x135>
  401580:	b8 64 00 00 00       	mov    $0x64,%eax
  401585:	eb 16                	jmp    40159d <phase_3+0x14b>
  401587:	e8 07 04 00 00       	callq  401993 <explode_bomb>
  40158c:	b8 64 00 00 00       	mov    $0x64,%eax
  401591:	eb 0a                	jmp    40159d <phase_3+0x14b>
  401593:	e8 fb 03 00 00       	callq  401993 <explode_bomb>
  401598:	b8 6c 00 00 00       	mov    $0x6c,%eax
  40159d:	38 44 24 07          	cmp    %al,0x7(%rsp)
  4015a1:	75 05                	jne    4015a8 <phase_3+0x156>
  4015a3:	48 83 c4 18          	add    $0x18,%rsp
  4015a7:	c3                   	retq   
  4015a8:	e8 e6 03 00 00       	callq  401993 <explode_bomb>
  4015ad:	eb f4                	jmp    4015a3 <phase_3+0x151>

地址40146f调用了sscanf函数:获取格式化的字符串中的参数。接下来检查sscanf的返回值%eax:若%eax<=2,则引爆炸弹。
检查sscanf的格式串(存在0x4031a6):
在这里插入图片描述

第一个参数和第三个参数是int,第二个是char。从0x401456~0x401460传入的参数可以知道,三个参数分别存在0x7(%rsp),0x8(%rsp),0xc(%rsp)处。有了参数的输入格式,可以开始分析整个程序了。
在地址0x401480附近,可以得到如下结论:第一个参数一定要<=7,否则炸弹直接爆炸。紧接着为一条switch跳转语句:

跳转表位于0x4031c0处。而switch的对象是%rax(之前把第一个参数挪到%rax),而第一个参数<=7,所以猜测表项共有8项,即0~7.因此用gdb的x指令看0x4031c0处开始的八个八字节:
在这里插入图片描述

就获取了转移表的表项。选取一个最靠近函数末尾的进行分析:即跳转表偏移为7*8字节处的0x401576,当第一个参数为7时,跳转到0x401576.

401576:	81 7c 24 08 af 00 00 	cmpl   $0xaf,0x8(%rsp)
40157d:	00 
40157e:	75 07                	jne    401587 <phase_3+0x135>
401580:	b8 64 00 00 00       	mov    $0x64,%eax
401585:	eb 16                	jmp    40159d <phase_3+0x14b>
401587:	e8 07 04 00 00       	callq  401993 <explode_bomb>
40158c:	b8 64 00 00 00       	mov    $0x64,%eax
401591:	eb 0a                	jmp    40159d <phase_3+0x14b>
401593:	e8 fb 03 00 00       	callq  401993 <explode_bomb>
401598:	b8 6c 00 00 00       	mov    $0x6c,%eax
40159d:	38 44 24 07          	cmp    %al,0x7(%rsp)
4015a1:	75 05                	jne    4015a8 <phase_3+0x156>
4015a3:	48 83 c4 18          	add    $0x18,%rsp
4015a7:	c3                   	retq   
4015a8:	e8 e6 03 00 00       	callq  401993 <explode_bomb>
4015ad:	eb f4                	jmp    4015a3 <phase_3+0x151>

该部分的逻辑较简单。比较0xaf与0x8(%rsp);%al与0x7(%rsp).%al即%eax的低位字节,0x64代表ASCII中的’d’。因此只需使0x7(%rsp)和0x8(%rsp)取到’d’和0xaf(175)即可。因此第一个参数为7,第二个参数为’d’,第三个参数为175.

阶段四

考察点:递归

00000000004015ec <phase_4>:
  4015ec:	48 83 ec 18          	sub    $0x18,%rsp
  4015f0:	48 8d 4c 24 08       	lea    0x8(%rsp),%rcx
  4015f5:	48 8d 54 24 0c       	lea    0xc(%rsp),%rdx
  4015fa:	be 4f 33 40 00       	mov    $0x40334f,%esi
  4015ff:	b8 00 00 00 00       	mov    $0x0,%eax
  401604:	e8 07 fb ff ff       	callq  401110 <__isoc99_sscanf@plt>
  401609:	83 f8 02             	cmp    $0x2,%eax
  40160c:	75 0d                	jne    40161b <phase_4+0x2f>
  40160e:	8b 44 24 0c          	mov    0xc(%rsp),%eax
  401612:	85 c0                	test   %eax,%eax
  401614:	78 05                	js     40161b <phase_4+0x2f>
  401616:	83 f8 0e             	cmp    $0xe,%eax
  401619:	7e 05                	jle    401620 <phase_4+0x34>
  40161b:	e8 73 03 00 00       	callq  401993 <explode_bomb>
  401620:	ba 0e 00 00 00       	mov    $0xe,%edx
  401625:	be 00 00 00 00       	mov    $0x0,%esi
  40162a:	8b 7c 24 0c          	mov    0xc(%rsp),%edi
  40162e:	e8 7c ff ff ff       	callq  4015af <func4>
  401633:	83 f8 04             	cmp    $0x4,%eax
  401636:	75 07                	jne    40163f <phase_4+0x53>
  401638:	83 7c 24 08 04       	cmpl   $0x4,0x8(%rsp)
  40163d:	74 05                	je     401644 <phase_4+0x58>
  40163f:	e8 4f 03 00 00       	callq  401993 <explode_bomb>
  401644:	48 83 c4 18          	add    $0x18,%rsp
  401648:	c3                   	retq  

0x401604处调用sscanf;紧接着比较%eax与2,仅当%eax==2时,炸弹才不会爆炸。因此输入参数为两个。接下来的语句限制arg[0](第一个参数,0xc(%rsp))必须大于等于0(由js指令看出);之后(0x401620~0x40162a)分别把arg[0],0,0xe移动到func4的三个参数上。即调用func4(arg[0], 0, 15)。0x401633处比较返回值%eax是否等于4,因此,需要分析当arg[0]为多少时,func4的值为4.于是对func4进行反汇编:

x in %edi, y in %esi, z in %edx
00000000004015af <func4>:
  4015af:	48 83 ec 08          	sub    $0x8,%rsp
  4015b3:	89 d1                	mov    %edx,%ecx
  4015b5:	29 f1                	sub    %esi,%ecx
  
int ret = ((((z - y) >> 31) + (z  - y)) >> 1) + y;

  4015b7:	89 c8                	mov    %ecx,%eax
  4015b9:	c1 e8 1f             	shr    $0x1f,%eax
  4015bc:	01 c8                	add    %ecx,%eax
  4015be:	d1 f8                	sar    %eax
  4015c0:	01 f0                	add    %esi,%eax
  4015c2:	39 f8                	cmp    %edi,%eax
  4015c4:	7f 0c                	jg     4015d2 <func4+0x23>
  4015c6:	7c 16                	jl     4015de <func4+0x2f>

if(ret == 0) return 0;
  4015c8:	b8 00 00 00 00       	mov    $0x0,%eax
  4015cd:	48 83 c4 08          	add    $0x8,%rsp
  4015d1:	c3                   	retq   
if(ret > 0) return 2 * func4(x, y, ret - 1);
  4015d2:	8d 50 ff             	lea    -0x1(%rax),%edx
  4015d5:	e8 d5 ff ff ff       	callq  4015af <func4>
  4015da:	01 c0                	add    %eax,%eax
  4015dc:	eb ef                	jmp    4015cd <func4+0x1e>
if(ret < 0) return 2 * func4(x, ret + 1, z);
  4015de:	8d 70 01             	lea    0x1(%rax),%esi
  4015e1:	e8 c9 ff ff ff       	callq  4015af <func4>
  4015e6:	8d 44 00 01          	lea    0x1(%rax,%rax,1),%eax
  4015ea:	eb e1                	jmp    4015cd <func4+0x1e>

在函数开始前标注了参数所在寄存器。在0x4015c2处比较%edi与%eax的值,%edi为传入的第一个参数x;%eax经过前几行的一系列操作,其值为449行注释的表达式。接下来分别于459,465,472行标注了比较的三种情况。可以将func4近似为如下的C语言递归函数:

int func4(int x, int l, int r) {
    
    
	int mid = (((r - l) >> 31) + (r - l))/ 2 + l;
	if(mid == x) return 0;
	else if(mid > x) return 2 * func(x, l,mid - 1);
	else return 2 * func(x, mid + 1, r) + 1;
} 

用程序进行检验,很容易发现当arg[0]=2时,func4的值为4.
在这里插入图片描述

接下来回到phase_4的末尾部分:只要0x8(%rsp)(第二个参数)==4即可。因此答案为2 4.

阶段五

考察点:数组(指针)

0000000000401649 <phase_5>:
  401649:	53                   	push   %rbx
  40164a:	48 89 fb             	mov    %rdi,%rbx
  40164d:	e8 4b 02 00 00       	callq  40189d <string_length>
  401652:	83 f8 06             	cmp    $0x6,%eax
  401655:	75 25                	jne    40167c <phase_5+0x33>
  401657:	b9 00 00 00 00       	mov    $0x0,%ecx
  40165c:	b8 00 00 00 00       	mov    $0x0,%eax
  401661:	83 f8 05             	cmp    $0x5,%eax
  401664:	7f 1d                	jg     401683 <phase_5+0x3a>
  401666:	48 63 d0             	movslq %eax,%rdx
  401669:	0f b6 14 13          	movzbl (%rbx,%rdx,1),%edx
  40166d:	83 e2 0f             	and    $0xf,%edx
  401670:	03 0c 95 00 32 40 00 	add    0x403200(,%rdx,4),%ecx
  401677:	83 c0 01             	add    $0x1,%eax
  40167a:	eb e5                	jmp    401661 <phase_5+0x18>
  40167c:	e8 12 03 00 00       	callq  401993 <explode_bomb>
  401681:	eb d4                	jmp    401657 <phase_5+0xe>
  401683:	83 f9 31             	cmp    $0x31,%ecx
  401686:	75 02                	jne    40168a <phase_5+0x41>
  401688:	5b                   	pop    %rbx
  401689:	c3                   	retq   
  40168a:	e8 04 03 00 00       	callq  401993 <explode_bomb>
  40168f:	eb f7                	jmp    401688 <phase_5+0x3f>

注意到0x40164d处调用了string_length函数,在0x401652处将返回值与6比较,推测输入为一个长度为6的字符串。接下来初始化%ecx=0,%eax=0,在%eax>5时,跳转至地址0x401683.因此下面是一个循环变量i(%eax)从0到5共6次的循环.接下来,0x401669对(%rbx,%rdx,1)做零扩展传送到%edx,将其按位与0xf(即取低四位)。先检验一下该内存地址是什么:
在这里插入图片描述
这次检测中输入了字符串”string”,而用char*方式解释%rbx开始的位置正好就是输入的字符串”string”.因此,%rbx就是输入的字符串的首地址,而%rdx=%eax是循环变量i。因此循环每次取arg[i],即输入字符串的第i位字符;并将其按位与0xf后的值存入%edx中。
继续看0x401670处:

401670:	03 0c 95 00 32 40 00 	add    0x403200(,%rdx,4),%ecx

发现它把0x403200偏移4*%edx处的四字节值累加到%ecx.因此可以推测出0x403200处有一个含16个int的数组(因为%edx是按位与0xf得来的,共有16种可能)。用x指令查看该数组:
在这里插入图片描述
该数组即

int A[] = {
    
    2, 10, 6, 1, 12, 16, 9, 3,
           4, 7, 14, 5, 11, 8, 15, 13};

查看phase_5出循环后的代码:其比较%ecx是否等于0x31=49.

401683:	83 f9 31             	cmp    $0x31,%ecx

经过上述分析,可以将过程转化为如下C代码:

void phase_5() {
    
    
	char input[10];
	scanf("%s", input);
	if(strlen(input) != 6) explode_bomb();
	int i, sum = 0;
	for(i = 0; i < 6; i++) {
    
    
		int index = input[i] & 0xf;
		sum += A[index];
	}
	if(sum != 49) explode_bomb();
}

因此,关键就在于选择输入字符串的值,使得6次累加的值为49.可以在A数组中找到7+7+7+7+7+14=49,即5个A[9]和1个A[10],接下来只要找到char型的ch1,ch2使得(ch1&0xf == 9) && (ch2&0xf == 10)即可。选择ch1=’i’,ch2=’j’即满足要求。因此只要输入5个i和1个j即可。(顺序可以调换)

阶段六

考察点:结构体,链表
phase_6的反汇编有80多行,分为多个步骤,接下来分步骤说明。

0000000000401691 <phase_6>:
  401691:	41 54                	push   %r12
  401693:	55                   	push   %rbp
  401694:	53                   	push   %rbx
  401695:	48 83 ec 50          	sub    $0x50,%rsp
  401699:	48 8d 74 24 30       	lea    0x30(%rsp),%rsi
  40169e:	e8 14 03 00 00       	callq  4019b7 <read_six_numbers>

首先与前面的阶段一样,读入6个数字;由传入的参数(0x401699)可以知道,第一个读入的数字存在0x30(%rsp)处。

let rbp = i, ebx = j
  4016a3:	bd 00 00 00 00       	mov    $0x0,%ebp
  4016a8:	eb 29                	jmp    4016d3 <phase_6+0x42>
  4016aa:	e8 e4 02 00 00       	callq  401993 <explode_bomb>
  4016af:	eb 36                	jmp    4016e7 <phase_6+0x56>
  4016b1:	e8 dd 02 00 00       	callq  401993 <explode_bomb>
  4016b6:	83 c3 01             	add    $0x1,%ebx
  4016b9:	83 fb 05             	cmp    $0x5,%ebx
  4016bc:	7f 12                	jg     4016d0 <phase_6+0x3f>
  4016be:	48 63 c5             	movslq %ebp,%rax
  4016c1:	48 63 d3             	movslq %ebx,%rdx
edi = arg[j]
  4016c4:	8b 7c 94 30          	mov    0x30(%rsp,%rdx,4),%edi
compare arg[i] and arg[j]
  4016c8:	39 7c 84 30          	cmp    %edi,0x30(%rsp,%rax,4)
arg[i] != arg[j],jump to bomb_explode
  4016cc:	75 e8                	jne    4016b6 <phase_6+0x25>
  4016ce:	eb e1                	jmp    4016b1 <phase_6+0x20>
  4016d0:	44 89 e5             	mov    %r12d,%ebp
  4016d3:	83 fd 05             	cmp    $0x5,%ebp
  4016d6:	7f 18                	jg     4016f0 <phase_6+0x5f>
  4016d8:	48 63 c5             	movslq %ebp,%rax
  4016db:	8b 44 84 30          	mov    0x30(%rsp,%rax,4),%eax
  4016df:	83 e8 01             	sub    $0x1,%eax
  4016e2:	83 f8 05             	cmp    $0x5,%eax
  4016e5:	77 c3                	ja     4016aa <phase_6+0x19>
  4016e7:	44 8d 65 01          	lea    0x1(%rbp),%r12d
  4016eb:	44 89 e3             	mov    %r12d,%ebx
  4016ee:	eb c9                	jmp    4016b9 <phase_6+0x28>

接下来(经过分析)是一个二重循环。首先初始化%ebp=0,设其为i;随后jmp到0x4016d3,当i>5时,结束这一部分。否则,把%ebp作符号扩展传送到%rax,把0x30(%rsp,%rax,4)即arg[i](rsp+0x30+4*%rax)传送到%eax.然后比较%eax-1与5(即%eax与6),若%eax>6,炸弹爆炸。因此推测出arg[i]<=6(i=0,1,…,5)
在0x4016e7,0x4016eb两行把%rbp+1即i+1赋给%ebx,跳转到0x4016b9.紧接着1比较%ebx与5,当%ebx>5时跳转(正因如此确定这是一个二重循环)。设%ebx为j,接下来的几行比较arg[j]与arg[i],当arg[i] == arg[j]时,炸弹爆炸。因此这一部分可以转化为如下的C代码:

int i, j;
for(i = 0; i < 6; i++) {
    
    
	for(j = i + 1; j < 6; j++) {
    
    
		if(arg[i] == arg[j]) {
    
    
			explode_bomb();
		}
	}
}

从这一段C代码就可以看出这一部分是确保对任意的i != j,arg[i] != arg[j].再结合之前的arg[i]<=6,可以得出,输入应当是一个1~6的排列。

继续下一段:

let eax = i
  4016f0:	b8 00 00 00 00       	mov    $0x0,%eax
  4016f5:	eb 13                	jmp    40170a <phase_6+0x79>
  4016f7:	48 63 c8             	movslq %eax,%rcx
  4016fa:	ba 07 00 00 00       	mov    $0x7,%edx
  4016ff:	2b 54 8c 30          	sub    0x30(%rsp,%rcx,4),%edx
  401703:	89 54 8c 30          	mov    %edx,0x30(%rsp,%rcx,4)
  401707:	83 c0 01             	add    $0x1,%eax
  40170a:	83 f8 05             	cmp    $0x5,%eax
  40170d:	7e e8                	jle    4016f7 <phase_6+0x66>

开始初始化%eax=0,立即跳转到0x40170a与5进行比较.由经验,%eax一般为第一层循环变量,设其为i;在i<=5时,跳转到0x4016f7,接下来的0x4016fa~0x401703将7-arg[i]赋值给arg[i],即对参数进行了一次变换,但六个参数还是一个1~6的排列。

下一段:

let esi = i, eax = j, edx = (pointer*)ptr, rcx = k
  40170f:	be 00 00 00 00       	mov    $0x0,%esi
  401714:	eb 17                	jmp    40172d <phase_6+0x9c>
  401716:	48 8b 52 08          	mov    0x8(%rdx),%rdx
  40171a:	83 c0 01             	add    $0x1,%eax
  40171d:	48 63 ce             	movslq %esi,%rcx
  401720:	39 44 8c 30          	cmp    %eax,0x30(%rsp,%rcx,4)
  401724:	7f f0                	jg     401716 <phase_6+0x85>
  401726:	48 89 14 cc          	mov    %rdx,(%rsp,%rcx,8)
  40172a:	83 c6 01             	add    $0x1,%esi
  40172d:	83 fe 05             	cmp    $0x5,%esi
  401730:	7f 0c                	jg     40173e <phase_6+0xad>

loop initialize:
  401732:	b8 01 00 00 00       	mov    $0x1,%eax
  401737:	ba d0 52 40 00       	mov    $0x4052d0,%edx
  40173c:	eb df                	jmp    40171d <phase_6+0x8c>

本段初始化%esi为0,随后jmp到0x40172d与5比较。由经验设%esi=i,当i<=5时,把%eax赋值1,%edx赋值0x4052d0.因此推测,%eax是一个int型临时变量j;%edx是一个指针型临时变量,但不确定其类型,记为pointer *ptr.
做完这些初始化后jmp到0x40171d,把i赋给%rcx(记为k,这些对应关系在这段汇编的开始标注);比较arg[k]与j,若arg[k]>j,跳到0x401716,做

401716:	48 8b 52 08          	mov    0x8(%rdx),%rdx

这样一条操作,随后j++。图中指令形式很类似于链表,即跳转到下一个结点:ptr=ptr->next.把自己指向的位置偏移8处赋给自身,说明链表节点的next指针在首地址偏移8处。因此节点数据大小应为8字节。推测链表结构声明如下:

struct node {
    
    
	long val;//8 bytes
	node *next;//pointer
};

继续分析:程序做完上面的操作后又会落到判断arg[k]和j的大小关系处,可以把这里转化为一个while循环。当跳出while循环后,

  401726:	48 89 14 cc          	mov    %rdx,(%rsp,%rcx,8)
  40172a:	83 c6 01             	add    $0x1,%esi

把%rdx(ptr)赋值给rsp偏移为8*%rcx(8*i)处,因此这部分汇编可转换为如下C语言(其中的rsp[k]可视为一个指针数组:pointer *rsp[6]; ):

//let esi = i,eax = j,edx = ptr,rcx = k
int i, j, k, ptr;
for(i = 0; i <= 5;i++) {
    
    
  k = i;
	j = 1;
	ptr = 0x4052d0;
	while(arg[k] > j) {
    
    
		ptr = ptr->next;
		j++;
	}//add until arg[i] == j
	rsp[k] = ptr;
}

可以看到,对于每个arg[i],ptr初始化都在0x4052d0处,每次j自增,ptr都会指向自己的下一个元素。最后,把ptr最终指向的值赋给rsp寄存器偏移k的位置。注意到在单次循环内k没有改变,因此可以把k和i等同,从而消除k:

int i, j, ptr;
for(i = 0; i <= 5;i++) {
    
    
  	j = 1;
  	ptr = 0x4052d0;
  	while(arg[i] > j) {
    
    
  		ptr = ptr->next;
  		j++;
  	}//add until arg[i] == j
  	rsp[i] = ptr;
}

为了验证猜想,我们需要查看运行时0x4052d0的内存。经过上面的假设,可以得到节点大小为16字节,应该共有6个这样的节点。因此,运行时用x指令查看:
在这里插入图片描述
查看的结果很明显:结构体名为node1~node6,每个节点的第二个数据首尾相接(如,0x4052d0的第二个数据段为0x4052e0,正好是下一块内存的地址),第一个数据应当就是数据域。
验证了上面的猜想后,就可以解释这段代码的行为了:rsp[i]指向从0x4052d0开始的第arg[i]个节点(更准确地说,rsp[i]保存该节点的首地址)。如arg[0]=5,那么j应该向后跳4次,ptr指向第5个节点(0x405310)。由于arg[i]是一个排列,因此rsp[i]指向的内容也应该不同。

let rbx = ptr1, rcx = ptr2, eax = i, rdx = j, ebp = k
The arg[i]-th node's next node is the arg[i+1]-th node
  40173e:	48 8b 1c 24          	mov    (%rsp),%rbx
  401742:	48 89 d9             	mov    %rbx,%rcx
  401745:	b8 01 00 00 00       	mov    $0x1,%eax
  40174a:	eb 11                	jmp    40175d <phase_6+0xcc>
  40174c:	48 63 d0             	movslq %eax,%rdx
  40174f:	48 8b 14 d4          	mov    (%rsp,%rdx,8),%rdx
  401753:	48 89 51 08          	mov    %rdx,0x8(%rcx)
  401757:	83 c0 01             	add    $0x1,%eax
  40175a:	48 89 d1             	mov    %rdx,%rcx
  40175d:	83 f8 05             	cmp    $0x5,%eax
  401760:	7e ea                	jle    40174c <phase_6+0xbb>
  401762:	48 c7 41 08 00 00 00 	movq   $0x0,0x8(%rcx)
  401769:	00 

下一部分:把rsp[0]移动到%rbx,设%rbx为node *ptr1;又将%rbx传送给%rcx,设%rcx为node *ptr2.%eax初始化为1,设为i。对于i<=5的情况,程序跳转到0x40174c:把%rdx赋值为rsp[i](在0x40174f),再把0x8(%rcx)赋值%rdx,即ptr2->next = rsp[i];
0x40175a处的mov指令把%rcx赋值%rdx=rsp[i].出循环后(0x401762)给ptr2->next赋值0,即NULL.结合起来即

node *ptr2 = rsp[0];
for(int i = 1; i <= 5; i++) {
    
    
  	ptr2->next = rsp[i];
  	ptr2 = ptr2->next;
}
ptr->next = NULL;

因此,这段代码的本质即对链表进行了一次重排序,规则为:第arg[i]个节点的下一个节点为第arg[i+1]个节点。如:arg[]={3,2,1,6,5,4},那么第3个节点为头,随后连接第2个节点,以此类推。(这里的第i个节点指的是内存的排列顺序:0x4052d0为第一个,0x4052e0为第二个)

接下来就是phase_6的最后一部分:对重排序的链表判断是否符合要求。

let rbx = ptr1, rcx = ptr2, eax = i, rdx = j, ebp = k
  40176a:	bd 00 00 00 00       	mov    $0x0,%ebp
  40176f:	eb 07                	jmp    401778 <phase_6+0xe7>
  401771:	48 8b 5b 08          	mov    0x8(%rbx),%rbx
  401775:	83 c5 01             	add    $0x1,%ebp
  401778:	83 fd 04             	cmp    $0x4,%ebp
  40177b:	7f 11                	jg     40178e <phase_6+0xfd>
  40177d:	48 8b 43 08          	mov    0x8(%rbx),%rax
  401781:	8b 00                	mov    (%rax),%eax
  401783:	39 03                	cmp    %eax,(%rbx)
  401785:	7d ea                	jge    401771 <phase_6+0xe0>
  401787:	e8 07 02 00 00       	callq  401993 <explode_bomb>
  40178c:	eb e3                	jmp    401771 <phase_6+0xe0>
  40178e:	48 83 c4 50          	add    $0x50,%rsp
  401792:	5b                   	pop    %rbx
  401793:	5d                   	pop    %rbp
  401794:	41 5c                	pop    %r12
  401796:	c3                   	retq  

把%ebp初始化为0,随即跳到0x401778.将其与4比较。若%ebp>4,就成功到达phase_6的结尾,因此只需度过这个循环即可。0x40177d把ptr1->next赋值给%rax,然后把(%rax)即ptr1->next->val(由于结构体偏移为0的地方为其数据段)赋值给%eax,比较%eax=ptr1->next->val与(%rbx)=ptr1->val.这里比较的逻辑很明显:比较ptr1指向的节点的数据是否比ptr1->next的大。还有一点需要注意:由于cmp指令其中一个参数是%eax,因此cmp应补为cmpl,按int型进行比较,而非long型。

那么答案的构造方法就清楚了:只需在重排序步骤中,使得重排序后的链表的数据递减。之前内存中各节点的数据从左到右分别为(按int型解释,用gdb print指令)258,355,577,973,465,330.
那么表头应该为973,随后是577,465,355,330,258.
按重排序的规则,很容易构造出符合的arg[]={4,3,5,2,6,1}.(第四个节点后面是第三个,然后是第二个,…)但是在前面程序又做了一步处理:arg[i] = 7 - arg[i].因此,需要把这个arg数组还原(用7减)。因此最初输入的arg就应该是{3,4,2,5,1,6}.
在漫长的分析后,终于得出六个参数依次为:3 4 2 5 1 6.

隐藏阶段

考察点:递归,结构体,二叉查找树
在破解phase_6的时候就可以发现下面还有fun7和secret_phase函数。搜索secret_phase的调用,发现其调用在phase_defused中:

0000000000401b24 <phase_defused>:
  401b24:	83 3d 41 3c 00 00 06 	cmpl   $0x6,0x3c41(%rip)        # 40576c <num_input_strings>
  401b2b:	74 01                	je     401b2e <phase_defused+0xa>
  401b2d:	c3                   	retq   
  401b2e:	48 83 ec 68          	sub    $0x68,%rsp
  401b32:	4c 8d 44 24 10       	lea    0x10(%rsp),%r8
  401b37:	48 8d 4c 24 08       	lea    0x8(%rsp),%rcx
  401b3c:	48 8d 54 24 0c       	lea    0xc(%rsp),%rdx
  401b41:	be 99 33 40 00       	mov    $0x403399,%esi
  401b46:	bf 70 58 40 00       	mov    $0x405870,%edi
  401b4b:	b8 00 00 00 00       	mov    $0x0,%eax
  401b50:	e8 bb f5 ff ff       	callq  401110 <__isoc99_sscanf@plt>
  401b55:	83 f8 03             	cmp    $0x3,%eax
  401b58:	74 0f                	je     401b69 <phase_defused+0x45>
  401b5a:	bf d8 32 40 00       	mov    $0x4032d8,%edi
  401b5f:	e8 fc f4 ff ff       	callq  401060 <puts@plt>
  401b64:	48 83 c4 68          	add    $0x68,%rsp
  401b68:	c3                   	retq   
  401b69:	be a2 33 40 00       	mov    $0x4033a2,%esi
  401b6e:	48 8d 7c 24 10       	lea    0x10(%rsp),%rdi
  401b73:	e8 39 fd ff ff       	callq  4018b1 <strings_not_equal>
  401b78:	85 c0                	test   %eax,%eax
  401b7a:	75 de                	jne    401b5a <phase_defused+0x36>
  401b7c:	bf 78 32 40 00       	mov    $0x403278,%edi
  401b81:	e8 da f4 ff ff       	callq  401060 <puts@plt>
  401b86:	bf a0 32 40 00       	mov    $0x4032a0,%edi
  401b8b:	e8 d0 f4 ff ff       	callq  401060 <puts@plt>
  401b90:	b8 00 00 00 00       	mov    $0x0,%eax
call secret_phase!
  401b95:	e8 3a fc ff ff       	callq  4017d4 <secret_phase>
  401b9a:	eb be                	jmp    401b5a <phase_defused+0x36>

phase_defused是一个在每次通过阶段后都调用的一个函数。为了使控制到达secret_phase,需要在0x401b2b处通过检验0x3c41(%rip)==6.注意到后面标记40576c <num_input_strings>,推测它是0x3c41(%rip)处的一个int型。(事实上0x3c41+%rip=0x3c41+0x401b2b=0x40576c,考虑到%rip应该指向下一条指令)
经过多次检验,num_input_strings是输入字符串数(由于字符串不对就会立即爆炸,故也是通过阶段数)。那么隐藏阶段就是在第六阶段通过后进行判断的。在0x401b2e处设置断点,通过六阶段后控制也的确转移到这里。再看0x401b55处的判断:cmp $0x3,%eax,判断之前的一个sscanf的读入参数的个数是否为3.既然此处没有读入(完成六阶段后程序直接结束),那么这个sscanf就应该读的是之前的字符串。可以看到第一个参数是%edi=0x405870。在之前读入字符串的函数(read_line)后设置断点,可以发现0x405870是第四次读入的答案首地址。表面上第四阶段读入两个数字(2,4),但隐藏阶段需要在这里额外输入另一个参数触发。
在这里插入图片描述
查看sscanf的格式串(存于0x403399),可以直到第三个参数应为字符串。注意到后面调用了一次strings_not_equal,用一阶段的方法可以得到这个字符串为”DrEvil”.于是在第四阶段后面额外附加上这个字符串,进入了隐藏阶段。
在这里插入图片描述
接下来分析secret_phase:下面为其反汇编:

00000000004017d4 <secret_phase>:
  4017d4:	53                   	push   %rbx
  4017d5:	e8 1c 02 00 00       	callq  4019f6 <read_line>
  4017da:	48 89 c7             	mov    %rax,%rdi
  4017dd:	e8 5e f9 ff ff       	callq  401140 <atoi@plt>
  4017e2:	89 c3                	mov    %eax,%ebx
  4017e4:	8d 40 ff             	lea    -0x1(%rax),%eax
  4017e7:	3d e8 03 00 00       	cmp    $0x3e8,%eax
if(val > 1001) explode_bomb();
  4017ec:	77 22                	ja     401810 <secret_phase+0x3c>
  4017ee:	89 de                	mov    %ebx,%esi
  4017f0:	bf f0 50 40 00       	mov    $0x4050f0,%edi
tmp = fun7(0x4050f0, val)
  4017f5:	e8 9d ff ff ff       	callq  401797 <fun7>
  4017fa:	83 f8 03             	cmp    $0x3,%eax
if(tmp != 3) explode_bomb();
  4017fd:	75 18                	jne    401817 <secret_phase+0x43>
  4017ff:	bf 80 31 40 00       	mov    $0x403180,%edi
  401804:	e8 57 f8 ff ff       	callq  401060 <puts@plt>
  401809:	e8 16 03 00 00       	callq  401b24 <phase_defused>
  40180e:	5b                   	pop    %rbx
  40180f:	c3                   	retq   
  401810:	e8 7e 01 00 00       	callq  401993 <explode_bomb>
  401815:	eb d7                	jmp    4017ee <secret_phase+0x1a>
  401817:	e8 77 01 00 00       	callq  401993 <explode_bomb>
  40181c:	eb e1                	jmp    4017ff <secret_phase+0x2b>

可以看到0x4017dd调用了atoi,那么输入为一个字符串,并将其转为数字。0x4017e2~0x404017ec判断了该数字应<=1001.随后调用fun7(0x4050f0, %edi)
(%edi就是输入的值),将其返回值与0x3比较。因此还需分析0x4050f0处的内存和fun7的行为,使得fun7返回3.先看0x4050f0处的内存:
在这里插入图片描述
可以看到和阶段6类似,每个结构体大小为32字节,每个数据占8字节;推测第一个为数据域,而第二个第三个显然为地址形式,推测为指针。第四个应为对齐要求,所有都为0.对于每个结构体有两个指针,很容易想到它是一棵二叉树。

可以推测节点定义如下:

struct node {
    
    
	long val;
	node *lson;
	node *rson;
};

在这里插入图片描述
继续检查内存,发现这棵二叉树共有15个节点,正好符合4层满二叉树的节点个数。按照内存画出树的结构:
在这里插入图片描述
(只画出数据,因为地址意义不大)
可以看到它正好符合二叉查找树的结构:左儿子<根<右儿子。那么现在只需要弄清楚fun7的行为即可。fun7的反汇编:

let rdi = ptr, esi = val
0000000000401797 <fun7>:
if(ptr == NULL) return -1;
  401797:	48 85 ff             	test   %rdi,%rdi
  40179a:	74 32                	je     4017ce <fun7+0x37>
  40179c:	48 83 ec 08          	sub    $0x8,%rsp
compare val and ptr->val
  4017a0:	8b 07                	mov    (%rdi),%eax
  4017a2:	39 f0                	cmp    %esi,%eax
  4017a4:	7f 0c                	jg     4017b2 <fun7+0x1b>
  4017a6:	75 17                	jne    4017bf <fun7+0x28>
drop to here(when val == ptr->val):
  4017a8:	b8 00 00 00 00       	mov    $0x0,%eax
  4017ad:	48 83 c4 08          	add    $0x8,%rsp
  4017b1:	c3                   	retq   
if(val < ptr->val) return 2 * fun7(ptr->lson, val);
  4017b2:	48 8b 7f 08          	mov    0x8(%rdi),%rdi
  4017b6:	e8 dc ff ff ff       	callq  401797 <fun7>
  4017bb:	01 c0                	add    %eax,%eax
  4017bd:	eb ee                	jmp    4017ad <fun7+0x16>
if(val > ptr->val) return 2 * fun7(ptr->rson, val);
  4017bf:	48 8b 7f 10          	mov    0x10(%rdi),%rdi
  4017c3:	e8 cf ff ff ff       	callq  401797 <fun7>
  4017c8:	8d 44 00 01          	lea    0x1(%rax,%rax,1),%eax
  4017cc:	eb df                	jmp    4017ad <fun7+0x16>
  4017ce:	b8 ff ff ff ff       	mov    $0xffffffff,%eax
  4017d3:	c3                   	retq  

fun7的行为比较清晰。如注释标记,先检查第一个参数%rdi(称为ptr)是否为0(即NULL),若为NULL,返回0xffffffff即-1;然后把(%rdi),即ptr->val传送给%eax,将其与第二个参数%esi(称为val)比较,这与二叉查找树的查找过程非常相似。随后分出三种情况:大于/等于/小于。
( 1 ) (1) (1)对于val==ptr->val的情况,直接下落到0x4017a8,返回0;
( 2 ) (2) (2)对于valval的情况,返回2*fun7(0x8(%rdi),%esi),由节点的定义可知就是2*func(ptr->lson,val);
( 3 ) (3) (3)对于val>ptr->val的情况,返回
2*fun7(0x10(%rdi),%esi)+1=2*fun7(ptr->rson,val)+1.
可以写出C代码:

int fun7(node *ptr, int val) {
    
    
	if(ptr == NULL) return -1;
	if(ptr->val == val) return 0;
	if(val < ptr->val) return 2 * fun7(ptr->lson, val);
	if(val > ptr->val) return 2 * fun7(ptr->rson, val);
}

接下来只需要寻找输入val,使得fun7(root,val)返回3即可。3可以通过
v 1 = 2 ∗ 0 + 1 = 1 ; v_1=2*0+1=1; v1=20+1=1;
v 2 = 2 ∗ v 1 + 1 = 3 v_2=2*v_1+1=3 v2=2v1+1=3
得到。从二叉查找树的根root向下找,由于都是 2 v + 1 2v+1 2v+1的形式,因此需要走两次右儿子,对照二叉树图可得该值为0x6b=107.也就是说,若输入107,在0x6b处函数返回0,然后在0x32处返回2*0+1=1,最后在根处返回2*1+1=3.因此,输入值应当为107.
在这里插入图片描述

答案

以下为七个阶段(包含隐藏阶段)的ans.txt内容(答案可能不唯一,如第三阶段和第五阶段):

I was trying to give Tina Fey more material.
1 2 4 8 16 32
7 d 175
2 4 DrEvil
iiiiij
3 4 2 5 1 6
107

猜你喜欢

转载自blog.csdn.net/wyn1564464568/article/details/123918590