Justin Lei

Justin Lei

Study forever

17 Aug 2021

cs:app 第三章 & bomblab 记录


这里可以找到实验代码

程序代码的汇编表示

寄存器

整数寄存器

image-20210817103601810

条件码寄存器

  • CF:进位标志,表示最高位产生了进位
  • ZF:零标志,最近的操作结果为0
  • SF:符号标志,最近的操作结果为负数
  • OF:溢出标志,导致补码溢出,正溢出或负溢出

栈寄存器

栈指针 %rsp 保存栈顶元素的数据,将数据压入栈是将 %rsp-8,然后存储数据;弹出栈是将 M[%rsp] 取出,然后%rsp+8。

编译器会为每个函数计算需要的栈空间,然后在函数的开头对%rsp进行减操作,分配栈空间,在函数结束返回的时候对%rsp加操作,恢复栈空间

pc寄存器

%rip 代表指令计数器,表示当前执行指令的地址

ATT与Intel格式的区别

ATT是AT&T提出的汇编代码格式,GCC、OBJDUMP等工具的默认格式。而Intel格式是Intel提出的,通常用于Microsoft的工具和Intel自身的文档。

两者区别是

  • Intel省略了指示大小的后缀,例如l,q等
  • Intel省略了寄存器的‘%’前缀
  • Intel使用QWORD PTR [rbx]来表示内存位置,而不是(rbx)这种形式
  • 在带有多个操作数的情况下,操作数的顺序相反,即目的寄存器在前,源寄存器在后。

在gcc生成汇编代码的时候,可以使用-masm=intel选项指定生成Intel格式的汇编代码,默认是ATT格式。

常见指令

条件指令

img

常见TEST %rax, %rax,判断%rax是否等于0,如果等于0,会设置ZF标志位,如果后跟je指令,就满足条件,跳转

跳转指令

在跳转的时候,有些指令会采用PC相对寻址,即指令编码中只记录offset,实际跳转的目的指令地址是next PC + offset,即下一条指令地址+offset,而不是当前PC的值

算术运算

add S,D -> D = D + S

sub S,D -> D = D - S

数据传送

mov S,D -> D = S

注意movl传送四字节,会将高位四字节设置为0,这样是因为 x86-64 的惯例是任何为寄存器生成 32 位值的指令都会把该寄存器的高位部分置成0。

读取有效地址

lea并不会从指定地址读取数据到目的位置,而是将地址存储到目的位置

操作数格式

栈帧

x86-64提供六个寄存器用于函数参数传递(%rdi, %rsi, %rdx, %rcx, %r8, %r9),每一个调用的函数都会事先在栈上开辟空间,填入需要存储的变量。%rsp 指向栈顶位置,可以通过 pushq 和 popq 指令对 %rsp 进行操作。当函数 P 调用函数 Q 时,函数 P 会首先将函数 Q 执行完毕后的返回地址压入栈,通常返回地址是调用函数 Q 语句的下一个执行语句地址。

进入函数 Q 后,通常第一步就是事先分配栈空间,保存寄存器的值,分配局部变量,或者为函数 Q 调用的其他函数设置传递的参数。如果函数需要的参数非常多,六个寄存器不够放,那么就需要在调用者的栈中另外开辟空间,如图中的传入函数 Q 的参数7至参数n。

通常分配的栈空间是确定的,也就是在汇编代码里使用的是立即数,而对于需要使用变长栈帧的场景,例如需要动态分配栈空间,gcc会事先保存寄存器 %rbp 的值,然后将%rbp指向当前新的栈底。

可变栈帧

当进入新的函数时(call指令),返回地址被压入栈,在栈中的地址是 %rsp

结构体

对于

这样的结构体,假设%rdi存储结构体的地址,那么

movl (%rdi) %eax # %eax = st->i
movl %eax 4(%rdi) # st->j = %eax

条件分支

条件分支转换成汇编代码,通常将有条件和无条件的跳转结合起来使用。

对于

if (test-expr)
  then-statement
else
  else-statement

通常会转换成

    t = test-expr;
    if (!t)
	  goto false;
    then-statement
    goto done;
false:
    else-statement
done:

条件分支根据条件是否满足决定走哪条路径,但是现代处理器使用流水线执行,如果条件预测失败,产生的错误惩罚成本会非常高。因此一种替代的策略是使用数据的条件传送,即计算一个条件操作的两个结果,然后根据条件满足选取其中一个,不过这种策略仅在受限条件下才可行。

例如

long cmovdiff(long x, long y) {
  long result;
  if (x < y)
    result = y - x;
  else 
    result = x - y;
  return result;
}

优化后的汇编指令为

absdiff:
	movq %rsi, %rax
	subq %rdi, %rax # 计算 y-x
	movq %rdi, %rdx
	subq %rsi, %rdx # 计算 x-y
  cmpq %rsi, %rdi
  cmovge %rdx, %rax # 使用条件移动指令,
  ret

条件传送指令不需要使用预测,只是读取源数据,检查条件码,然后更新目的寄存器或者保持不变。

如何确定分支预测错误的处罚

假设分支预测错误的处罚概率是p,如果没有预测错误,执行代码时间是$T_{ok}$,预测错误的处罚是$T_{MP}$,那么平均时间是$T = (1-p)T_{ok}+p(T_{ok}+T_{MP})$

条件传送的限制

条件传送的限制是有些分支可能会产生错误条件或者副作用,因此不适用于条件数据传送

循环

do…while

do…while翻译成汇编语言的例子如下

long fact_do(long n) {
  long result = 1;
loop:
  result *= n;
  n = n - 1;
  if (n > 1)
    goto loop;
  return result;
}
fact_do:
	movl $1, %eax
.L2:
	imulq %rdi, %rax
	subq $1, %rdi
	cmpq $1, %rdi
	jg   .L2
	rep; ret

while

while循环有两种翻译方式,一种是jump to middle(跳转到中间),使用无条件跳转到判断条件,再跳转到

    goto test;
loop:
		body-statement
test:
		t = test-expr;
		if (t)
			goto loop;

第二种方法,称之为guarded-do,使用条件分支,如果初始条件不成立,就跳过循环,把代码变成do-while循环。当使用-O1级别优化的时候,会采用这种方式

t = test-exptr;
if (!t) 
	goto done;
loop:
	body-statement
	if (t) goto loop
done:
	some-statements

for

for循环通常的翻译方式是转换成while循环再翻译,例如

for(init-expr; test-expr; update-expr)
	body-statement
init-expr;
while(test-expr) {
	body-statement
  update-expr
}

然后选择jump to middle或者guarded-do方法

bomb lab

分为5个阶段,需要使用objdump对bomb这个二进制文件进行反汇编

objdump -d ./bomb > bomb.s

使用gdb打断点,layout asm可以切换到汇编代码界面,si单步执行,ni调到下一条汇编指令

常用gdb调试命令

  • x/[length][format] addr:打印memory[addr]的值

  • info registers reg:打印寄存器的值,例如info registers rbp

  • set $reg=value:设置寄存器的值

  • ctrl-p/n:在文本模式下读取上一个/下一个执行过的命令

  • ctrl-b/f:在文本模式下光标左/右移动

gdb断点自动执行命令

因为起初我们无法得知字符串的具体内容,为了加快效率,我们在得到当前字符后需要将之前输入的字符进行修改,让程序继续执行,提高调试效率。gdb提供了在断点后添加命令的功能,能够在每次断点触发后自动执行。具体可以参考这里

注意事项:bomb lab支持使用文件输入代替终端输入,文件中的每一行输入后面都要带有一个空格,并且是作为参数传递,而不是文件重定向输入,即

./bomb answer.txt

而不是

./bomb < answer.txt

在gdb中传入命令行参数可以使用run param1 param2...

phase 1

通过strings_not_equal来判断输入的字符串和当前谜题字符串是否相同

第一个字符串是输入的字符串,第二个字符串是谜题字符串

  401338:	41 54                	push   %r12
  40133a:	55                   	push   %rbp
  40133b:	53                   	push   %rbx
  40133c:	48 89 fb             	mov    %rdi,%rbx # 将第一个参数保存到rbx
  40133f:	48 89 f5             	mov    %rsi,%rbp # 将第二个参数保存到rbp
  401342:	e8 d4 ff ff ff       	callq  40131b <string_length>
  401347:	41 89 c4             	mov    %eax,%r12d # r12d 保存第一个字符串的长度 (用户输入的)
  40134a:	48 89 ef             	mov    %rbp,%rdi
  40134d:	e8 c9 ff ff ff       	callq  40131b <string_length>
  401352:	ba 01 00 00 00       	mov    $0x1,%edx
  401357:	41 39 c4             	cmp    %eax,%r12d # 比较两个字符串的长度
  40135a:	75 3f                	jne    40139b <strings_not_equal+0x63> # 如果不相同就结束
  40135c:	0f b6 03             	movzbl (%rbx),%eax  # 拷贝第一个字符串的当前字符到eax
  40135f:	84 c0                	test   %al,%al # if (al & 0xff == 0) je 401388
  401361:	74 25                	je     401388 <strings_not_equal+0x50>
  401363:	3a 45 00             	cmp    0x0(%rbp),%al # 比较用户输入字符串的当前字符
  401366:	74 0a                	je     401372 <strings_not_equal+0x3a> # 相同就跳到401372
  401368:	eb 25                	jmp    40138f <strings_not_equal+0x57>
  40136a:	3a 45 00             	cmp    0x0(%rbp),%al # 比较字符串是否相同
  40136d:	0f 1f 00             	nopl   (%rax)
  401370:	75 24                	jne    401396 <strings_not_equal+0x5e> # 不相同跳到401396,退出
  401372:	48 83 c3 01          	add    $0x1,%rbx # rbx和rbp都加1,也就是循环继续
  401376:	48 83 c5 01          	add    $0x1,%rbp
  40137a:	0f b6 03             	movzbl (%rbx),%eax # 拷贝第一个字符串当前字符到eax
  40137d:	84 c0                	test   %al,%al # 如果字符串结束,就退出
  40137f:	75 e9                	jne    40136a <strings_not_equal+0x32> # 循环继续
  401381:	ba 00 00 00 00       	mov    $0x0,%edx # 返回0
  401386:	eb 13                	jmp    40139b <strings_not_equal+0x63>
  401388:	ba 00 00 00 00       	mov    $0x0,%edx
  40138d:	eb 0c                	jmp    40139b <strings_not_equal+0x63>
  40138f:	ba 01 00 00 00       	mov    $0x1,%edx
  401394:	eb 05                	jmp    40139b <strings_not_equal+0x63>
  401396:	ba 01 00 00 00       	mov    $0x1,%edx # 返回1,代表不相同
  40139b:	89 d0                	mov    %edx,%eax
  40139d:	5b                   	pop    %rbx
  40139e:	5d                   	pop    %rbp
  40139f:	41 5c                	pop    %r12
  4013a1:	c3                   	retq   

通过strings_not_equal的第二个参数拿到谜题字符串的内存首地址,使用x/[length]c,可以拿到谜题字符串的具体内容。

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) ; (%rsp)为第一个数,要求为1
  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 ; 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   
  
 000000000040145c <read_six_numbers>:
  40145c:	48 83 ec 18          	sub    $0x18,%rsp
  401460:	48 89 f2             	mov    %rsi,%rdx ; 第一个数字的位置(old rsp)
  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)  ; 第六个数字的位置放到%rsp+8位置
  401470:	48 8d 46 10          	lea    0x10(%rsi),%rax ; 第五个数字的位置
  401474:	48 89 04 24          	mov    %rax,(%rsp)    ; 第五个数字的地址放到%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   

根据代码分析,要求输入的六个数,后一个数是前一个数的两倍,且第一个数为1

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 <__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>
  400f6a:	83 7c 24 08 07       	cmpl   $0x7,0x8(%rsp)					
  400f6f:	77 3c                	ja     400fad <phase_3+0x6a>  ; 读取的第一个数不能大于7
  400f71:	8b 44 24 08          	mov    0x8(%rsp),%eax
  400f75:	ff 24 c5 70 24 40 00 	jmpq   *0x402470(,%rax,8)			; switch语句
  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   

根据代码分析phase3是一个switch结构,查看0x4025cf地址,发现格式化字符串是"%d %d",即读取两个数,第一个数不能大于7,然后根据第一个数选择不同的分支

phase 4

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 ; "%d %d", 读取两个整数
  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) ; 第一个数要 < 14
  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> ; func4(num1, 0, 14)
  40104d:	85 c0                	test   %eax,%eax			; 要求返回值必须为0
  40104f:	75 07                	jne    401058 <phase_4+0x4c>
  401051:	83 7c 24 0c 00       	cmpl   $0x0,0xc(%rsp) ; 第二个数 == 0
  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   
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

注意 sar %eax,只有一个参数,表示sar %eax, 1

将func4转换成c代码如下

int func4(int64_t a1, int64_t a2, int64_t a3) {
    // v1: $eax, v2: $ecx;
    int64_t v1, v2;
    v1 = a3 - a2;
    v2 = unsigned(v1) >> 31;
    v1 = (v1 + v2) >> 1;
    v2 = (v1 & 0xffff) + a2;
    if (v2 <= a1) {
        // 400ff2
        if (v2 >= a1) return 0;
        int ret = func4(a1, v2 & 0xffff + 1, a3);
        return 2 * ret + 1;
    } else {
        return 2 * func4(a1, a2, v2 & 0xffff - 1);
    }
}

phase 4要求func4返回0,而a2初始为0,a3初始为14,计算得第一次调用func4,v2==7,因此可以设置a1为7,直接返回0即可

phase 5

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 ; 输入的字符串长度为6
  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 ; %rbx为输入的字符串
  40108f:	88 0c 24             	mov    %cl,(%rsp) 
  401092:	48 8b 14 24          	mov    (%rsp),%rdx ; %rdx存储输入字符串的字符
  401096:	83 e2 0f             	and    $0xf,%edx ; 与0xf与得到索引
  401099:	0f b6 92 b0 24 40 00 	movzbl 0x4024b0(%rdx),%edx # 按照索引取0x4024b0字符串里的字符
  4010a0:	88 54 04 10          	mov    %dl,0x10(%rsp,%rax,1) # 将转换好的字符串依次放到起始位置为rsp + 0x10的字符串
  4010a4:	48 83 c0 01          	add    $0x1,%rax
  4010a8:	48 83 f8 06          	cmp    $0x6,%rax
  4010ac:	75 dd                	jne    40108b <phase_5+0x29>
  4010ae:	c6 44 24 16 00       	movb   $0x0,0x16(%rsp)
  4010b3:	be 5e 24 40 00       	mov    $0x40245e,%esi # 与在0x40245e的字符串进行比较
  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 <__stack_chk_fail@plt>
  4010ee:	48 83 c4 20          	add    $0x20,%rsp
  4010f2:	5b                   	pop    %rbx
  4010f3:	c3                   	retq

根据代码分析,我们要输入一个长度为6的字符串,然后输入字符串的每个字符和0xf进行与运算,得到低4字节作为索引,用这个索引去取0x4024b0处存放的字符串中的字符(该字符串是maduiersnfotvbyl),将新形成的字符与0x40245e处的字符串进行比较(字符串是flyers)

phase 6

代码比较长,分析下来就是对链表进行操作,转换成的C代码如下

struct node {
    int data;
    int index;
    struct node* next;
};

struct Node list[6] = {
    {332, 1, &list[1]},
    {168, 2, &list[2]},
    {924, 3, &list[3]},
    {691, 4, &list[4]},
    {477, 5, &list[5]},
    {433, 6, NULL}
};

struct Node* stack[21];

void phase_6() {
    int nums[6];
    for(int i = 0; i < 6; ++i) {
        if (nums[i] > 6) return -1; // bomb!
        for(int j = i + 1; j <= 5; j++) {
            if (nums[i] == nums[j]) return -1; // bomb!
        }
    }
    for(int i = 0; i < 6; ++i) {
        nums[i] = 7 - nums[i];
    }
    for(int i = 0; i < 6; ++i) {
        if (nums[i] <= 1) {
            stack[8 + 2 * i] = list;
        } else {
            // t: %rdx, cnt: %eax
            struct Node* t = list;
            int cnt = 1;
            do {
                t = t->next;
                cnt++;
            } while(cnt != nums[i]);
            // 401188
            stack[8 + 2 * i] = t;
        }
    }
    // 原始翻译
    // struct Node** rsi = &stack[20];
    // struct Node** rax = &stack[10];
    // struct Node*  rbx = stack[8];
    // struct Node*  rcx = rbx;
    // struct node*  rdx;
    // do {
    //     rdx = *rax;
    //     rcx->next = rdx;
    //     rax += 2;
    //     if (rsi == rax) {
    //         break;
    //     }
    //     rcx = rdx;
    // } while(1);

    struct Node* node = stack[8];
    struct Node** next = &stack[10];
    for(node = stack[8]; node != stack[20]; node = node->next) {
        node->next = *next;
        next += 2; // 在stack中向后移动两个
    }
    
    start->next = 0;

    start = stack[8];
    int t = 5, data;
    while(t != 0) {
        next = start->next;
        data = rax->data;
        // 检查新生成的链表是否是倒序排列
        if (start->data < data) {
            return -1;
        } else {
            start = start->next;
            t--;
        }
    }
}

要求输入6个数,每个数均<=6,且这6个数各不相同。7-数字得到索引,根据后面的代码,可以得出这些索引是用来指示新的倒序排序的链表中的数字,在原始链表中的位置(从1开始)。比如924在新的链表中排在第一位,原始链表中是在第3位,那么输入的第一个数应该就是4。

secret_phase

在反汇编代码中,我们可以看到有个secret_phase的函数,表明有一个隐藏问题。根据调用关系,该函数是在phase_defused中被调用。代码中首先执行了sscanf,根据得到的参数个数,如果是3个且最后一个字符串和0x402622的字符串相同,就会调用secret_phase。

由于sscanf读取的字符串是固定位置(0x603870),在整个代码中并没有找到对应位置,所以就在此处下断点,查看每次解完一个谜题之后该处字符串的变化,在输入第4个谜题之后,发现此处字符串为我们输入的字符串,因此可以确定是在输入第三个谜题答案之后再跟一个字符串(0x402622的字符串)即可开启隐藏谜题。

00000000004015c4 <phase_defused>:
  4015c4:	48 83 ec 78          	sub    $0x78,%rsp
  4015c8:	64 48 8b 04 25 28 00 	mov    %fs:0x28,%rax
  4015cf:	00 00 
  4015d1:	48 89 44 24 68       	mov    %rax,0x68(%rsp)
  4015d6:	31 c0                	xor    %eax,%eax
  4015d8:	83 3d 81 21 20 00 06 	cmpl   $0x6,0x202181(%rip)        # 603760 <num_input_strings>
  4015df:	75 5e                	jne    40163f <phase_defused+0x7b>
  4015e1:	4c 8d 44 24 10       	lea    0x10(%rsp),%r8
  4015e6:	48 8d 4c 24 0c       	lea    0xc(%rsp),%rcx
  4015eb:	48 8d 54 24 08       	lea    0x8(%rsp),%rdx
  4015f0:	be 19 26 40 00       	mov    $0x402619,%esi ; %d %d %s
  4015f5:	bf 70 38 60 00       	mov    $0x603870,%edi ; string
  4015fa:	e8 f1 f5 ff ff       	callq  400bf0 <__isoc99_sscanf@plt>
  4015ff:	83 f8 03             	cmp    $0x3,%eax
  401602:	75 31                	jne    401635 <phase_defused+0x71>
  401604:	be 22 26 40 00       	mov    $0x402622,%esi
  401609:	48 8d 7c 24 10       	lea    0x10(%rsp),%rdi
  40160e:	e8 25 fd ff ff       	callq  401338 <strings_not_equal>
  401613:	85 c0                	test   %eax,%eax
  401615:	75 1e                	jne    401635 <phase_defused+0x71>
  401617:	bf f8 24 40 00       	mov    $0x4024f8,%edi
  40161c:	e8 ef f4 ff ff       	callq  400b10 <puts@plt>
  401621:	bf 20 25 40 00       	mov    $0x402520,%edi
  401626:	e8 e5 f4 ff ff       	callq  400b10 <puts@plt>
  40162b:	b8 00 00 00 00       	mov    $0x0,%eax
  401630:	e8 0d fc ff ff       	callq  401242 <secret_phase>
  401635:	bf 58 25 40 00       	mov    $0x402558,%edi
  40163a:	e8 d1 f4 ff ff       	callq  400b10 <puts@plt>
  40163f:	48 8b 44 24 68       	mov    0x68(%rsp),%rax
  401644:	64 48 33 04 25 28 00 	xor    %fs:0x28,%rax
  40164b:	00 00 
  40164d:	74 05                	je     401654 <phase_defused+0x90>
  40164f:	e8 dc f4 ff ff       	callq  400b30 <__stack_chk_fail@plt>
  401654:	48 83 c4 78          	add    $0x78,%rsp
  401658:	c3                   	retq   
  401659:	90                   	nop
  40165a:	90                   	nop
  40165b:	90                   	nop
  40165c:	90                   	nop
  40165d:	90                   	nop
  40165e:	90                   	nop
  40165f:	90                   	nop

secret_phase的代码如下

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

根据代码分析可得,secret_phase期望fun7函数返回2,传入的参数是指向0x6030f0的指针,fun7的代码如下

0000000000401204 <fun7>:
  401204:	48 83 ec 08          	sub    $0x8,%rsp
  401208:	48 85 ff             	test   %rdi,%rdi
  40120b:	74 2b                	je     401238 <fun7+0x34>
  40120d:	8b 17                	mov    (%rdi),%edx
  40120f:	39 f2                	cmp    %esi,%edx          
  401211:	7e 0d                	jle    401220 <fun7+0x1c>
  401213:	48 8b 7f 08          	mov    0x8(%rdi),%rdi
  401217:	e8 e8 ff ff ff       	callq  401204 <fun7>
  40121c:	01 c0                	add    %eax,%eax
  40121e:	eb 1d                	jmp    40123d <fun7+0x39>
  401220:	b8 00 00 00 00       	mov    $0x0,%eax
  401225:	39 f2                	cmp    %esi,%edx
  401227:	74 14                	je     40123d <fun7+0x39>
  401229:	48 8b 7f 10          	mov    0x10(%rdi),%rdi
  40122d:	e8 d2 ff ff ff       	callq  401204 <fun7>      
  401232:	8d 44 00 01          	lea    0x1(%rax,%rax,1),%eax
  401236:	eb 05                	jmp    40123d <fun7+0x39>
  401238:	b8 ff ff ff ff       	mov    $0xffffffff,%eax
  40123d:	48 83 c4 08          	add    $0x8,%rsp
  401241:	c3                   	retq

是一个递归调用函数,翻译成c语言如下

struct node {
  int data;
  struct node* left;
  struct node* right;
}

int fun7(struct node* a1, int a2) {
  if (!a1) return -1;
  if (a1->data > a2) {
    a1 = a1->left;
    return 2 * fun7(a1, a2);
  } else if (a1->data < a2) {
    a1 = a1->right;
    return 2 * fun7(a1, a2) + 1;
  } else {
    return 0;
  }
}

是一个递归搜索二叉树的函数,因此我们可以找出0x6030f0位置的内容,分析出具体传入的数字。