csapp lab2 bomb

前置知识

你最好知道e前缀和r前缀的区别,推荐阅读《X86-64寄存器和栈帧》

对gdb常见命令有所了解。

懂得常见的ATT格式的汇编指令。

 

 

准备工作

下载一个实验文件,网上有,如果找不到可以用我的。

https://download.****.net/download/w55100/11156497

 

解压

tar -xvf bomb.tar

进入解压后出现的文件夹

cd bomb
ls -l

看到有3个文件,bomb、bomb.c,以及README。

反汇编炸弹文件

objdump -d bomb > bomb.txt

csapp lab2 bomb

我的linux环境放在服务器上,所以需要利用工具下载到pc,然后notepad打开。

长这个样子

csapp lab2 bomb

 

从264行main开始快速浏览

csapp lab2 bomb

很快就发现了规律

csapp lab2 bomb

 csapp lab2 bomb

csapp lab2 bomb

以我九年义务教育的阅读理解水平,可以合理猜测,phase_1~phase_6显然就是传说中的6大关卡了。

secret_phase就是秘密关卡。

记录一下,phase_6的起始地址为0x4010f4。

详细浏览一下phase_6,在588行看到明文explode_bomb,这个显然是爆炸命令。

而587行有一个很显然的跳转命令jge,跳到phase_6+0xfa的位置。

csapp lab2 bomb

通过计算可以知道,0x4010f4+0xfa=0x4011ee,而4011ee恰为第589行。

也就是说,这里就是验证密码的地方,如果密码正确就会跳过588行,从587直接来到589行。反之爆炸。

 

 

PHASE_1

开始调试

gdb bomb

在爆炸之前打断点

b explode_bomb

csapp lab2 bomb

0x40143a就是爆炸函数的地址。

csapp lab2 bomb

 

现在开始需要对gdb的常见命令有一定了解。可以先阅读 GDB disassemble

 

输入r,开始运行程序,再输入123,密码显然不正确,于是来到断点。

csapp lab2 bomb

大概明白了这个程序是干嘛的了。

 

现在,我们回到phase_1的源码。

csapp lab2 bomb

很明显,先把0x402400的值赋给%esi,然后运行strings_not_equal函数进行比较。

函数的返回值在%eax寄存器中,再进行test %eax,%eax  ,最后进行je判断。

你需要知道的是

je通过判断标志位ZF是否为0来跳转,ZF=0时跳。

而ZF是一个标志位,英文Zero Flag,表示是否为0。

简单点说,所有的对操作数进行算术和逻辑运算的指令,都会根据运算结果修改ZF标志。
第一点:
大多数情况下,运算结果对ZF的修改,由结果是否为0决定。
若运算结果为0,则ZF=1;
若运算结果非0,则ZF=0.

第二点:
但是,也有一些指令,会改变ZF标志,但其结果具有不确定性,没有运算结果是否为0的意义。
比如乘法指令 MUL和IMUL, 除法指令DIV和IDIV等
改变标志寄存器内容的SAHF指令,堆栈操作中的POPF指令,也不具有运算结果的意义。

我们来模拟一下运作过程。

若是用户输入的内容,与0x402400中的内容相同,则strings_not_equal函数返回False(即0),即%eax中为0。

那么test %eax,%eax,进行与运算得到0,于是ZF标志位设为1。

于是下一行的je指令失效,不跳转,触发bomb。

相反,若是用户输入内容与0x402400中的内容不同,则函数返回True为1,test %eax,%eax也得到1,ZF标志位设为0。

于是je成功跳转。

 

那么就很简单了,显然0x402400中的内容就是我们要找的秘钥。

x /s 0x402400

csapp lab2 bomb

得到第一个秘钥Border relations with Canada have never been better.

我发现每年这门课的老师做的bomb好像都会修改秘钥,所以如果你的秘钥跟我的不一样,而且秘钥所在地址不一样,这都是正常的。只有思路是不会改变的。

 

 

PHASE_2

老办法,先删除phase_1的断点,然后对phase_2打断点。

delete 2 【表示删除第2个断点,我的第2个就是phase_1】

b phase_2

你可以用info breakpoint来确认自己的断点,我的第4个就是phase_2了。

csapp lab2 bomb

老办法,r重头运行,输入第一个秘钥,成功通过。

csapp lab2 bomb

 

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       	callq  40145c <read_six_numbers>
  400f0a:	83 3c 24 01          	cmpl   $0x1,(%rsp)
  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>
  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       	callq  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                   	retq   

phase_2的运作原理是,

read_six_numbers这个函数看名字应该是会读我们输入的6个数字。

 

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 <[email protected]>
  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   

可以看到上面的代码里,是用sscanf读取数字,读完之后有一个cmp $0x5,%eax的动作。如果读入的数字(存在%eax中) jg(大于) 0x5(五个),即最少读入6个数字,就跳转到401499,否则会callq explde_bomb。

 

前置知识:

在x86-64体系中,有6个通用寄存器可以拿来传参数。

%rdi ,1st

%rsi, 2nd

%rdx, 3rd

%rcx, 4th

%r8, 5th

%r9, 6th

 

在phase_2的汇编代码中,进入read_six_number之前,有一句mov %rsp,%rsi 。

于是,此时%rsi正是指向保存6个参数的栈顶位置。

再回到read_six_num的代码,就很清晰了。

根据参数从后往前进栈的calling convention,刚进入read_six_number的那个交界时刻,%rsi=&a[0]。

csapp lab2 bomb

对应到代码中。

这个代码很神奇,虽然空间上参数的确是【从后往前】进栈的,但执行顺序上,却不一定是倒序。

可以看到是先提取了 %rsi,%rsi+4,分别存到%rdx,%rcx,也就是提取了&a[0]和&a[1]的值。

紧接着却不是去处理%rsi+8,而是直接处理%rsi+0x14去了,也就是去处理&a[5]。接着是&a[4]。

最后才绕了一圈回来搞&a[3]和&a[2]。

csapp lab2 bomb

看上图还会有一个疑问,根据前置知识,rdx存放3rd,即第三个参数,为什么拿来存&a[0]了?

rcx是4th参数,为什么拿来存&a[1]?

答,显然。

sscanf(input,"%d %d %d %d %d %d",&a[0],&a[1],&a[2],&a[3],&a[4],&a[5]);

你数数看,&a[0]是不是第3rd个参数? &a[1]是不是第4th个参数/?

你再数数看,&a[4],&a[5]是不是第7th,8th个参数?

超出6th的部分,没有寄存器可以用了,所以只能用压栈的方式,存在新开出来的栈区空间里。

这就是为什么a[4],a[5]的画风和前面的弟兄们不一样。

 

我们可以画出,执行read_six_num前半段之后的栈区图。

csapp lab2 bomb

 

不要问我为什么&a[4]和&a[5]不是连在一起的,我也不知道!!!

代码里面就是一个在rsp,一个在rsp+8!

我还很好奇,空着的地方是放什么的,若有人知道请务必告知!

 

到目前为止,整个read_six_num的汇编代码已经被我们拆的干干净净。

上面是各种参数传递的操作,下面我们已经知道了是一个跳转判断。

csapp lab2 bomb

那么剩下这行mov    $0x4025c3,%esi,就显得很可疑了。

用x /s 命令一看,哦这不是格式字符串卿吗? 即sscanf的第2nd个参数。

csapp lab2 bomb

 

我又很好奇了,整个代码里面没看到第1st个参数去了哪里。

奇怪。

 

我们再回到phase_2

read完之后,400f0a这行,cmpl %0x1,(%rsp),就是拿a[0]跟1比较,如果不等于1就炸给你看。

于是我们知道了第1个数字必须是1。

之后的代码,先在400f30处,把rsp+0x4赋给rbx,此时rbx代表的是a[1]。

大概是在400f117这里开始有一个小循环。

循环里面每次mov -0x4(%rbx),%eax,就是把上一个数字赋给%eax。

然后double %eax,再 cmp %eax,(%rbx),相等时就跳去400f25。

说明每次都要求,当前的数字是上一个数字的2倍。

csapp lab2 bomb

.在400f25处,rbx+0x4,再去往下一个数字。

而在400f29处,cmp %rbp,%rbx,jne不相等时继续循环,显然这是判断循环结束条件。

说明400f35这行,%rsp+0x18,存放的是终止时的条件数字。

 

综上所述,每次都2倍的话,就是1,2,4,8,16,32。

输入秘钥,成功过了第2关。

csapp lab2 bomb

再次申明,不同年份的bomb好像都会变。我搜索到的文章里面的过关条件有好几种。

举例,有篇博主的是key[i+1]=key[i]+i 。

而我这个是 key[i]=2*key[i-1]。

如果你出现的代码跟我不一样,这是非常正常的。

请确保你使用的bomb文件跟我的是同一个才有可比较性。

 

有个大神甚至写出了源代码


static void read_six_numbers(const char *input, int *a)
{
    //                  %rdi         %rsi           %rdx    %rcx   %r8   %r9    (%rsp)  *(%rsp)
    int result = sscanf(input, "%d %d %d %d %d %d", &a[0], &a[1], &a[2], &a[3], &a[4], &a[5]);
    if (result <= 5) {
        explode_bomb();
    }
}
 
void phase_2(const char *input)
{
    int a[6];
    read_six_numbers(input, a);
 
    int *begin = &a[1];
    int *end = &a[6];
 
    for ( ; begin < end; ++begin) {
        int prev_value = begin[-1] * 2;
        if (prev_value != begin[0]) {
            explode_bomb();
        }
    }
}

//来源:https://blog.****.net/astrotycoon/article/details/73248709

 

PHASE_3

整完前2部分之后,补了很多基础知识,再看phase_3就简单多了。


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       	callq  400bf0 <[email protected]>
  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>
  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
  400f75:	ff 24 c5 70 24 40 00 	jmpq   *0x402470(,%rax,8)
  400f7c:	b8 cf 00 00 00       	mov    $0xcf,%eax
  400f81:	eb 3b                	jmp    400fbe <phase_3+0x7b>
  400f83:	b8 c3 02 00 00       	mov    $0x2c3,%eax
  400f88:	eb 34                	jmp    400fbe <phase_3+0x7b>
  400f8a:	b8 00 01 00 00       	mov    $0x100,%eax
  400f8f:	eb 2d                	jmp    400fbe <phase_3+0x7b>
  400f91:	b8 85 01 00 00       	mov    $0x185,%eax
  400f96:	eb 26                	jmp    400fbe <phase_3+0x7b>
  400f98:	b8 ce 00 00 00       	mov    $0xce,%eax
  400f9d:	eb 1f                	jmp    400fbe <phase_3+0x7b>
  400f9f:	b8 aa 02 00 00       	mov    $0x2aa,%eax
  400fa4:	eb 18                	jmp    400fbe <phase_3+0x7b>
  400fa6:	b8 47 01 00 00       	mov    $0x147,%eax
  400fab:	eb 11                	jmp    400fbe <phase_3+0x7b>
  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>
  400fb9:	b8 37 01 00 00       	mov    $0x137,%eax
  400fbe:	3b 44 24 0c          	cmp    0xc(%rsp),%eax
  400fc2:	74 05                	je     400fc9 <phase_3+0x86>
  400fc4:	e8 71 04 00 00       	callq  40143a <explode_bomb>
  400fc9:	48 83 c4 18          	add    $0x18,%rsp
  400fcd:	c3                   	retq   

首先,又是sscanf,需要传入2个要写入的参数地址,第3个放进%rdx,第4个放进%rcx。前两个是input和格式字符串。

sscanf结束之后%eax里面存着返回值,与0x1进行比较,jg(有符号大于)1才能继续,否则爆炸。

然后将第3个参数0x8(%rsp)的值,与0x7进行比较,小于等于7才能继续,ja(无符号大于)7就跳转去爆炸。

将第3个参数的值存入%eax。

然后jmpq   *0x402470(,%rax,8) ,显然这个跳转地址是受到我们输入的第1个数(即sscanf的第3个参数)影响的。

这条指令的含义为 jump [0x402470+8*%rax],要先去表达式计算出的地址取值,取出来的值作为地址来跳。

我们只需要修改输入的第1个数,让它能跳到后面这些区域就行了。

csapp lab2 bomb

例如↑跳到400f7c,就会把0xcf=207,存入%eax。

然后在400fbe这个位置是cmp 0xc(%rsp),%eax, 即输入的第2个数和%eax里的值进行比较。

本题有多组解。

 

问题来了,我们怎样才能通过改变第1个输入值,让它跳到0x400f7c呢?

我们看一下0x402470开始的地址内,分别存了什么值。

直接x /i是不够的,因为64位环境下的地址是8个字节的,需要g来控制显示8字节。

csapp lab2 bomb

 

显然,让第1个输入值为0,就会跳到0x400f7c。

为1,跳0x400fb9。

为2,跳0x400f83。

...

 

我们取第1个输入值为0,第2个输入值为207,过关成功。

csapp lab2 bomb

 

本题参考 @astrotycoon : https://blog.****.net/astrotycoon/article/details/73248964

@astrotycoon :如果读者有一点点的经验,就可以很容易看出此处是switch语句的跳转表,跳转表的首地址为0x402470,我们可以通过x命令看看这个表中都存储了哪些地址。



PHASE_4

现在我们的阅读能力应该已经大大提高了。

phase_4前几行秒杀,rcx和rdx表明这次又是用sscanf读了两个数。

x /s 0x4025cf,可以看到"%d %d",所以这两个数是整数。

读完之后要比较sscanf的返回值,所以我们的输入必须刚好是2个。


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 <[email protected]>
  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   

然后,输入的第1个数,与0xe比较,也就是14。jbe(小于等于)14时,跳转继续执行。

接着 3条mov,%edx=0xe=14, %esi=0, %edi=输入的第1个数。

callq func4。 说明我们给func4传入了3个参数,都是整数。

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       	callq  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       	callq  400fce <func4>
  401003:	8d 44 00 01          	lea    0x1(%rax,%rax,1),%eax
  401007:	48 83 c4 08          	add    $0x8,%rsp
  40100b:	c3                   	retq   

func4中,第二行mov之后%eax=0xe=14,用14减去0,结果还是0xe,存入%ecx。

shr表示逻辑右移,将%ecx中的0xe右移31位,于是%ecx=0。经过add,%eax=0xe,不变。

sar表示算数右移,不写操作数的情况下,默认移动1位,于是得到%eax= Binary(111)=0x7

经过lea,%ecx=%rax+1*%rsi = 7+1*0 = 0x7。

此时把%edi中存储的输入的第1个数,与%ecx比较,jle(有符号小于等于)时,继续执行,若是大于则经过处理后递归调用func4。

 

这个过程,我们假设func4(int a,int b,int c),根据calling conventions,%edi,%esi,%edx顺次表示123个参数。

所以 a =我们输入的第1个数,b=0,c=14 。

第一行sub 0x8,%rsp可以看出,func4有1个局部变量,设为int temp。

那么前半段的源代码应该是

int temp= [ (c-b)>>31+(c-b) ] >>1 ,结果存在%eax中。

下面lea那句,即为%ecx = %rax + %rsi * 1 =  [ (c-b)>>31+(c-b) ] >>1 + b 。

cmp    %edi,%ecx这句就更简单了。

int temp =  ( (c-b)>>31+(c-b) )>>1 + b;

if (  a <= temp ){
    jump to 0x400ff2 ;
}
else {
    c=  temp-1 ;
    temp = func4(a,b,c);
    temp = 2*temp;
    return temp;
}

 

而0x400ff2开始,又是一个cmp和jge。

if (a>=temp){
    return 0;    
}
else{
    b= temp+1;
    temp = func4(a,b,c);
    temp = 2*temp+1;
    return temp;
}

 

合并一下代码,我们就得到了。

int temp =  ( (c-b)>>31+(c-b) )>>1 + b;

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

}
else {
    c=  temp-1 ;
    temp = func4(a,b,c);
    temp = 2*temp;
    return temp;
}

 

回到phase_4中,看一下call完func4做了什么。

首先test %eax,%eax,其次,jne(非0或者不同)时跳转去爆炸。

所以要求我们,让func4的返回值为0。

那么很简单,只需要a=temp就可以了。

由于已知b=0,c=14,轻而易举的知道temp=7。

即,需要我们输入的第1个数字为7。 【也可以走递归,可以证明存在多组解】

 

继续看phase_4。

第二个数字就很简单了,与0进行比较。就是要求它等于0。

于是得到本关卡的答案,7,0 。

csapp lab2 bomb

 

 

PHASE_5

%rbx压栈。开32字节空间。

调用string_length判断我们输入的字符是否为6个,不是就爆炸。

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
  40107a:	e8 9c 02 00 00       	callq  40131b <string_length>
  40107f:	83 f8 06             	cmp    $0x6,%eax
  401082:	74 4e                	je     4010d2 <phase_5+0x70>
  401084:	e8 b1 03 00 00       	callq  40143a <explode_bomb>
  401089:	eb 47                	jmp    4010d2 <phase_5+0x70>
  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
  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 <phase_5+0x29>
  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       	callq  401338 <strings_not_equal>
  4010c2:	85 c0                	test   %eax,%eax
  4010c4:	74 13                	je     4010d9 <phase_5+0x77>
  4010c6:	e8 6f 03 00 00       	callq  40143a <explode_bomb>
  4010cb:	0f 1f 44 00 00       	nopl   0x0(%rax,%rax,1)
  4010d0:	eb 07                	jmp    4010d9 <phase_5+0x77>
  4010d2:	b8 00 00 00 00       	mov    $0x0,%eax
  4010d7:	eb b2                	jmp    40108b <phase_5+0x29>
  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       	callq  400b30 <[email protected]>
  4010ee:	48 83 c4 20          	add    $0x20,%rsp
  4010f2:	5b                   	pop    %rbx
  4010f3:	c3                   	retq   

来到40108b,见到movzbl (%rbx,%rax,1),%ecx,表示取[%rbx+1*%rax]这个地址的内容,存入%ecx。 

mov    %cl,(%rsp)
mov    (%rsp),%rdx
and    $0xf,%edx

这三行的前两行就是把%ecx的低8位(即%cl),运送到%rdx里面。

and的操作就是掩码操作,表示只取%rdx的低4位。

结合前一句movzbl来看,就是把上面[xxx] 地址里的内容的低4位存入%rdx中。

然后movzbl 0x4024b0(%rdx),%edx ,即%rdx= M[0x4024b0+%rdx] 。

mov    %dl,0x10(%rsp,%rax,1),即 M[0x10+%rsp+%rax*1] = %rdx的低16位。

add之后,%eax=1,cmp肯定是不相等的。于是跳回到40108b。

显然这里是个循环了,循环每次%eax += 1 ,知道 %eax == 6 ,退出循环。

也就是说

取M[%rbx+%rax]低4位给%rdx

%edx = M[0x4024b0+%rdx]

M[0x10+%rsp+%rax] = %rdx低16位

%rax++;

这个操作会重复6次。

由于前面有一个mov %rdi,%rbx。

也就是说phase_5接受了main传来的一个参数,这个参数指向我们输入的字符数组的起始地址,存在%rdi里面传进了phase_5。

分别顺次访问这个字符数组(的低4位),将它作为下标,来访问b=0x4024b0指向的数组b。

访问6次,从b=0x4024b0这个数组中取出6个字符(低16位),赋值给栈空间的对应位置。

可以看到0x4024b0的内容如下。

csapp lab2 bomb

那么我们到底要从0x4024b0里面取出哪些字符呢?

继续往下。

结束循环之后

mov    $0x40245e,%esi
lea    0x10(%rsp),%rdi
callq  401338 <strings_not_equal>
test   %eax,%eax
je     4010d9 <phase_5+0x77>

这就是用0x10(%rsp)里的内容,跟0x40245e里的内容,通过strings_not_equal函数来比较。

相同时,返回0,test的结果会将ZF置为1,于是跳转。

可以看到0x40245e处的内容为 flyers 。

csapp lab2 bomb

 

这回答了刚才的问题,我们需要从数组b里面取出f,l,y,e,r,s这几个字符。

csapp lab2 bomb

数一数就知道,这些对应的下标为9,15,14,5,6,7。

对应16进制为0x9,0xf,0xe,0x5,0x6,0x7。

即,要求我们输入的字符的低4位,c[0]=0x9, c[1]=0xf, c[2]=0xe, c[3]=0x5, c[4]=0x6, c[5]=0x7 。

 

整个过程用图表示如下:

csapp lab2 bomb

 而满足低4位,低4位,c[0]=0x9, c[1]=0xf, c[2]=0xe, c[3]=0x5, c[4]=0x6, c[5]=0x7 。

这样的字符挺多的,查ASCII码表可得一组解:

9?n567

成功过关

csapp lab2 bomb

 

只要整理出来这题还是不难的。

一口气下一关卡。

 

PHASE_6

一眼看去,又见到老朋友read_six_numbers了。

如果忘记了请回到phase_2,我们已经把这个函数拆得干干净净了。

00000000004010f4 <phase_6>:
  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       	callq  40145c <read_six_numbers>
  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>
  401123:	e8 12 03 00 00       	callq  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       	callq  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>
  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>
  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>
  4011ab:	48 8b 5c 24 20       	mov    0x20(%rsp),%rbx
  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)
  4011d9:	00 
  4011da:	bd 05 00 00 00       	mov    $0x5,%ebp
  4011df:	48 8b 43 08          	mov    0x8(%rbx),%rax
  4011e3:	8b 00                	mov    (%rax),%eax
  4011e5:	39 03                	cmp    %eax,(%rbx)
  4011e7:	7d 05                	jge    4011ee <phase_6+0xfa>
  4011e9:	e8 4c 02 00 00       	callq  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                   	retq   

 

工程量有点大,拆开来看吧。

前面几行,老老实实的push压栈,然后开0x50的栈空间,80字节。

把当前栈顶%rsp,分别传给了%r13,%r14和%rsi。

其中传给%rsi是为了给read_six_number用的,%r13,%r14是给自己用的。

而当前栈顶%rsp,根据phase_2的经验,显然是指向一个含有6个int元素的数组的首地址。

int a[6] ,%rsp=%r13=%r14=a 。

寄存器%r12d初始化为0,由于后面有给r12d+1的操作,结合phase_5的经验,这显然是个计数器。

csapp lab2 bomb

 

mov    %r13,%rbp,将当前栈顶a[0]作为栈底,此行过后,暂时%rsp=%rbp。

mov    0x0(%r13),%eax,令 (%eax)= a[0]。

sub    $0x1,%eax ,(%eax)=a[0]-1。

cmp    $0x5,%eax,后面跟了句jbe。就是说如果a[0]-1 <= 5,就跳转,否则爆炸。

换而言之,就是a[0] <=6 。

add    $0x1,%r12d

cmp    $0x6,%r12d

je     401153 <phase_6+0x5f>

这三行就是熟悉不过的循环了,在计数器%r12d等于6时才会离开循环,跳往0x401153。

在这之前继续执行。

mov    %r12d,%ebx , %ebx=i,即当前计数器的值,这是第二层循环了,%ebx取代了%r12d,作为第二层循环的计数器。

movslq %ebx,%rax , %rax =i,也等于计数器。

mov    (%rsp,%rax,4),%eax  , 令(%eax)=M[ %rsp + 4*%rax]= M[%rsp+4*i],根据计数器的值,隔4字节地移动。

也就是,分别取出a[1],a[2],a[3],a[4],a[5],赋值给%eax 。

cmp    %eax,0x0(%rbp),然后与 a[0]进行比较,如果与a[0]相同就爆炸。

add    $0x1,%ebx,第二层循环的计数器++。

cmp    $0x5,%ebx  ,jle (小于等于5)时不断循环。

第二层循的要求是,后面的5个数字,不能与第1个相同。

 

add    $0x4,%r13,这样一来,现在%r13指向a[1]。

jmp    401114 <phase_6+0x20>

mov    %r13,%rbp,将a[1]当成栈底。

后面的代码分析过了,是要求小于等于6。

所以说第一层循环的要求是,6个数字全都小于等于6。

 

现在我们可以跳出第一层循环,je     401153 <phase_6+0x5f>了。

记住,跳出后,%rbp和%r13都指向a[5]。

401153处,lea    0x18(%rsp),%rsi 指向数组的结尾处,即不存在的a[6]。

mov    %r14,%rax ,%r14一直没被动过,还是等于%rsp,指向a[0],于是%rax指向a[0]

mov    $0x7,%ecx,  %ecx= 7

mov    %ecx,%edx,%edx=7

sub    (%rax),%edx。  %edx = 7 - a[0]

mov    %edx,(%rax) , a[0] = 7- a[0]

add    $0x4,%rax , 现在%rax指向a[1]

cmp    %rsi,%rax ,

jne    401160 <phase_6+0x6c>

这个判断是第三层循环了。如果%rax到达了数组结尾处,即不存在的a[6],那么退出循环,否则继续。

结束第三层循环之后,a[i]=7-a[i] 。

 

 

我们继续,从40116f开始看。

mov    $0x0,%esi  , %esi清零。

jmp    401197 <phase_6+0xa3>

mov    (%rsp,%rsi,1),%ecx ,  %ecx = M[%rsp+1*%rsi] ,现在等于a[0]。

cmp    $0x1,%ecx,

jle    401183 <phase_6+0x8f> 。

如果a[0]<=1,就回到401183。

显然这是第4层循环了,%rsi是计数器。

mov $0x6032d0,%edx

mov    %rdx,0x20(%rsp,%rsi,2), M[0x20+%rsp+2*%rsi]= M[0x6032d0]。

add    $0x4,%rsi , 计数器%rsi+=4

cmp    $0x18,%rsi 

je     4011ab <phase_6+0xb7>

当%rsi == 24 时,离开循环。

反之

如果a[0]>1,即原始的a[0]<6,被7减去之后会大于1。

从40119f处继续执行。

mov    $0x1,%eax ,%eax=1

mov    $0x6032d0,%edx ,%edx =某个地址

jmp    401176 <phase_6+0x82>

mov    0x8(%rdx),%rdx , %rdx = M[某个地址+8],这种写法显然是链表啊,当前链表地址+8的地方,存放着下一个链表的地址。

add    $0x1,%eax,%eax=2

cmp    %ecx,%eax,将a[0]与%eax对比

jne    401176 <phase_6+0x82>

如果不相等就继续循环,这是第五层循环。

直到%eax等于a[0],然后%rdx也到了某个合适的链表node。

若x=a[0],则来到0x6032d0开始的第x个node(取0x6032d0为第1个)。

csapp lab2 bomb

 

 %eax等于a[0]后,jmp到401188。这样又回到上面说的第四层循环了。

此时%rdx已经指向一个合适的node了,所以不需要401183这行来赋予第1个node。

现在我们如果回顾第四层循环,当a[0]=1时直接跳过去,就会有4001183来给%rdx第1个node。

所以我们可以说,%rdx总是指向第a[0]个node。

%rsi此时依然为0,于是开始%rsi+=4的循环,直到%rsi==24。

这个过程里,M[0x20+%rsp+2*%rsi]的值,不断被M[%rdx]的值填充。

离开循环后,我们来到4011ab。

 

离开循环后,此时M[0x20+%rsp]到M[0x20+%rsp+0x28],这里有6个8字节的数字,都是%rdx(即第a[0]个node)的值。

csapp lab2 bomb

 

mov    0x20(%rsp),%rbx ,于是%rbx = value of a[0]th node

lea    0x28(%rsp),%rax , %rax= 0x20+%rsp+0x8

lea    0x50(%rsp),%rsi ,众所周知我们只开了0x50的栈空间,所以%rsi此时指向栈底。

mov    %rbx,%rcx , %rcx =%rbx = value of a[0]th node

-

下面从4011bd处继续

mov    (%rax),%rdx, %rdx = value of a[0]th node

mov    %rdx,0x8(%rcx) ,   M[%rcx+0x8] = value of a[0]th node,而%rcx = value of a[0]th node

这句很有意思啊,像什么呢?

我们不如假设value of a[0]th  node是也是某个node的地址,设为p,

意思就是 p->next = p ,自己指向自己。

 

add    $0x8,%rax , %rax= 0x20+%rsp+0x10

cmp    %rsi,%rax

je     4011d2 <phase_6+0xde>

第六层循环的计数器是%rax,从0x20+%rsp+0x8开始, 到0x20+%rsp+0x28结束,步长为0x8。即循环5次。

不满足时,回到4011bd

mov    %rdx,%rcx , %rcx =  %rdx = value of a[0]th node

jmp    4011bd <phase_6+0xc9>