💻实验 2:Bomb Lab

实验环境

  • Ubuntu 20.04

实验要求

这个实验首先要求对汇编有一定的掌握,所以在此就不列举汇编的相关内容了。个人感觉用到最重要也是想要入手必须要具备的知识:一是学会使用反汇编及调试工具,二是了解函数调用的栈帧

这次的任务是破解二进制炸弹,一共有七关,六个常规关卡和一个隐藏关卡,每次我们需要输入正确的『拆弹密码』才能进入下一关,而具体的『拆弹密码』藏在汇编代码中。进入隐藏关卡的方式也在其中!这就需要我们一点一点探索蛛丝马迹了。

栈帧

想要了解栈帧的结构?我们还是先来回顾(review)以下有哪些和函数栈相关的寄存器吧。(这儿并没有包含浮点寄存器)

  • 所谓调用者保存,就是可以让被调用者(自身不作为另一个调用者)随意使用,也是为了自己用到的数据不被覆盖。

  • 所谓被调用者保存,恰恰与调用者保存相反。

  • 函数调用一般参数传递(非浮点)前 6 个参数存于寄存器,剩下的参数按照函数定义从右向左压栈

  • 栈指针指向函数栈栈顶。

  • %rax 用于保存函数调用返回值。

了解了这些寄存器,我们再来看看栈帧的结构

就拿函数 P 的栈帧来说,从栈底到栈顶的方向分别存储以下内容:

  • 被保存的寄存器

  • 局部变量(sub $0x18,%rsp

  • 如果调用其他函数参数多于 6,便有参数构造区

  • 调用其他函数时需要将返回地址压栈

汇编初探

想要完成拆弹任务,不但需要理解不同寄存器的常用方法,也要弄明白具体的操作符是什么意思:

类型

语法

例子

备注

常量

符号$ 开头

$-42, $0x15213

一定要注意十进制还是十六进制

寄存器

符号 % 开头

%esi, %rax

可能存的是值或者地址

内存地址

括号括起来

(%rbx), 0x1c(%rax), 0x4(%rcx, %rdi, 0x1)

括号实际上是去寻址的意思

一些汇编语句与实际命令的转换:

指令

效果

mov %rbx, %rdx

rdx = rbx

add (%rdx), %r8

r8 += value at rdx

mul $3, %r8

r8 *= 3

sub $1, %r8

r8--

lea (%rdx, %rbx, 2), %rdx

rdx = rdx + rbx*2

比较与跳转是拆弹的关键,基本所有的字符判断就是通过比较来实现的,比方说 cmp b,a 会计算 a-b 的值,test b, a 会计算 a&b,注意运算符的顺序。例如

cmpl %r9, %r10
jg   8675309

等同于 if %r10 > %r9, jump to 8675309

各种不同的跳转:

指令

效果

指令

效果

jmp

Always jump

ja

Jump if above(unsigned >)

je/jz

Jump if eq / zero

jae

Jump if above / equal

jne/jnz

Jump if !eq / !zero

jb

Jump if below(unsigned <)

jg

Jump if greater

jbe

Jump if below / equal

jge

Jump if greater / eq

js

Jump if sign bits is 1(neg)

jl

Jump if less

jns

Jump if sign bit is 0 (pos)

jle

Jump if less / eq

x

x

举几个例子

cmp $0x15213, %r12
jge deadbeef

%r12 >= 0x15213,则跳转到 0xdeadeef

cmp %rax, %rdi
jae 15213b

如果 %rdi 的无符号值大于等于 %rax,则跳转到 0x15213b

test %r8, %r8
jnz (%rsi)

如果 %r8 & %r8 不为零,那么跳转到 %rsi 存着的地址中。

# 检查符号表
# 然后可以寻找跟 bomb 有关的内容
objdump -t bomb | less 

# 反编译
# 搜索 explode_bomb
objdump -d bomb > bomb.txt

# 显示所有字符
strings bomb | less

GDB 介绍

gdb bomb

# 获取帮助
help

# 设置断点
break explode_bomb
break phase_1

# 开始运行
run

# 检查汇编 会给出对应的代码的汇编
disas 

# 查看寄存器内容
info registers

# 打印指定寄存器
print $rsp

# 每步执行
stepi

# 检查寄存器或某个地址
x/4wd $rsp

用 ctl+c 可以退出,每次进入都要设置断点(保险起见),炸弹会用 sscanf 来读取字符串,到底需要输入什么。

GDB安装

方法一apt-get

打开终端,在终端里输入以下指令:

apt-get update
apt-get install  gdb

安装时需要选择 y 来确认安装 gdb。

方法二

在网址:http://ftp.gnu.org/gnu/gdb下载gdb源码包或者直接用wget命令下载:wget http://ftp.gnu.org/gnu/gdb/gdb-8.0.1.tar.gz

会下载到当前目录下。

使用tar -zxvf 命令解压缩你下载的源码包

安装完毕后,使用gdb -v查看是否安装完成

GDB基础命令

命令

功能

gdb filename

开始调试

run

开始运行

run 1 2 3

开始运行,并且传入参数1,2,3

kill

停止运行

quit

退出gdb

break sum

在sum函数的开头设置断点

break *0x8048c3

在0x8048c3的地址处设置断点

delete 1

删除断点1

clear sum

删除在sum函数入口的断点

stepi

运行一条指令

stepi 4

运行4条指令

continue

运行到下一个断点

disas sum

反汇编sum函数

disas 0X12345

反汇编入口在0x12345的函数

print /x /d /t $rax

将rax里的内容以16进制,10进制,2进制的形式输出

print 0x888

输出0x888的十进制形式

print (int)0x123456

将0x123456地址所存储的内容以数字形式输出

print (char*)0x123456

输出存储在0x123456的字符串

x/w $rsp

解析在rsp所指向位置的word

x/2w $rsp

解析在rsp所指向位置的两个word

x/2wd $rsp

解析在rsp所指向位置的word,以十进制形式输出

info registers

寄存器信息

info functions

函数信息

info stack

栈信息

实验过程

从main函数开始分析下反汇编。

0000000000400da0 <main>:
  400da0:   53                      push   %rbx
  400da1:   83 ff 01                cmp    $0x1,%edi                  #if (argc == 1)
  400da4:   75 10                   jne    400db6 <main+0x16>         # 不相等就跳转到400db6
  400da6:   48 8b 05 9b 29 20 00    mov    0x20299b(%rip),%rax        # 603748 <stdin@@GLIBC_2.2.5>
  400dad:   48 89 05 b4 29 20 00    mov    %rax,0x2029b4(%rip)        # 603768 <infile> 相等就读取输入
  400db4:   eb 63                   jmp    400e19 <main+0x79>         #跳转到initialize_bomb
  400db6:   48 89 f3                mov    %rsi,%rbx
  400db9:   83 ff 02                cmp    $0x2,%edi                  #else if (argc == 2)
  400dbc:   75 3a                   jne    400df8 <main+0x58>         #不相等跳转到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 <fopen@plt>         
  400dcc:   48 89 05 95 29 20 00    mov    %rax,0x202995(%rip)        # 603768 <infile>
  400dd3:   48 85 c0                test   %rax,%rax
  400dd6:   75 41                   jne    400e19 <main+0x79>         #跳转到initialize_bomb
  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>  #printf("%s: Error: Couldn't open %s\n", argv[0], argv[1]);
  400dee:   bf 08 00 00 00          mov    $0x8,%edi
  400df3:   e8 28 fe ff ff          callq  400c20 <exit@plt>          #exit(8);
   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>  #printf("Usage: %s [<input_file>]\n", argv[0]);
  400e0f:   bf 08 00 00 00          mov    $0x8,%edi
  400e14:   e8 07 fe ff ff          callq  400c20 <exit@plt>          #exit(8);
  400e19:   e8 84 05 00 00          callq  4013a2 <initialize_bomb>   #调用initialize_bomb();
  400e1e:   bf 38 23 40 00          mov    $0x402338,%edi
  400e23:   e8 e8 fc ff ff          callq  400b10 <puts@plt>          #printf("Welcome to my fiendish little bomb. You have 6 phases with\n");
  400e28:   bf 78 23 40 00          mov    $0x402378,%edi 
  400e2d:   e8 de fc ff ff          callq  400b10 <puts@plt>          #printf("which to blow yourself up. Have a nice day!\n");
  400e32:   e8 67 06 00 00          callq  40149e <read_line>         #调用read_line();
  400e37:   48 89 c7                mov    %rax,%rdi                  #传参
  400e3a:   e8 a1 00 00 00          callq  400ee0 <phase_1>           #调用phase_1();
  400e3f:   e8 80 07 00 00          callq  4015c4 <phase_defused>     #调用phase_defused();
  400e44:   bf a8 23 40 00          mov    $0x4023a8,%edi     
  400e49:   e8 c2 fc ff ff          callq  400b10 <puts@plt>          #printf("Phase 1 defused. How about the next one?\n");
  400e4e:   e8 4b 06 00 00          callq  40149e <read_line>         #调用read_line();
  400e53:   48 89 c7                mov    %rax,%rdi                  #传参
  400e56:   e8 a1 00 00 00          callq  400efc <phase_2>           #调用phase_2();
  400e5b:   e8 64 07 00 00          callq  4015c4 <phase_defused>
  400e60:   bf ed 22 40 00          mov    $0x4022ed,%edi
  400e65:   e8 a6 fc ff ff          callq  400b10 <puts@plt>
  400e6a:   e8 2f 06 00 00          callq  40149e <read_line>
  400e6f:   48 89 c7                mov    %rax,%rdi
  400e72:   e8 cc 00 00 00          callq  400f43 <phase_3>           #调用phase_3();
  400e77:   e8 48 07 00 00          callq  4015c4 <phase_defused>
  400e7c:   bf 0b 23 40 00          mov    $0x40230b,%edi
  400e81:   e8 8a fc ff ff          callq  400b10 <puts@plt>
  400e86:   e8 13 06 00 00          callq  40149e <read_line>
  400e8b:   48 89 c7                mov    %rax,%rdi
  400e8e:   e8 79 01 00 00          callq  40100c <phase_4>           #调用phase_4();
  400e93:   e8 2c 07 00 00          callq  4015c4 <phase_defused>
  400e98:   bf d8 23 40 00          mov    $0x4023d8,%edi
  400e9d:   e8 6e fc ff ff          callq  400b10 <puts@plt>
  400ea2:   e8 f7 05 00 00          callq  40149e <read_line>
  400ea7:   48 89 c7                mov    %rax,%rdi
  400eaa:   e8 b3 01 00 00          callq  401062 <phase_5>          #调用phase_4();
  400eaf:   e8 10 07 00 00          callq  4015c4 <phase_defused>
  400eb4:   bf 1a 23 40 00          mov    $0x40231a,%edi
  400eb9:   e8 52 fc ff ff          callq  400b10 <puts@plt>
  400ebe:   e8 db 05 00 00          callq  40149e <read_line>
  400ec3:   48 89 c7                mov    %rax,%rdi
  400ec6:   e8 29 02 00 00          callq  4010f4 <phase_6>          #调用phase_4();
  400ecb:   e8 f4 06 00 00          callq  4015c4 <phase_defused>
  400ed0:   b8 00 00 00 00          mov    $0x0,%eax
  400ed5:   5b                      pop    %rbx
  400ed6:   c3                      retq   

大概分析了下主函数,主要还是传参和函数的调用,想要得出结果还是要看phase_1 ~ phase_6这些函数的反汇编。

Phase 1

查看 bomb.c 得到 Phase 1 相关的 C 代码。

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");

可以看出,程序先使用 read_line() 函数读取输入,并让 input 变量指向它。然后将其传给 phase_1。此时 char 指针应该存储在 4(%esp) 中。然后查看 phase_1 的反汇编代码。包括 read_line 之类的函数并不需要详细了解,因为我们可以很容易看出来它干了什么。

0000000000400ee0 <phase_1>:
  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 <strings_not_equal>
  400eee:       85 c0                   test   %eax,%eax
  400ef0:       74 05                   je     400ef7 <phase_1+0x17>
  400ef2:       e8 43 05 00 00          callq  40143a <explode_bomb>
  400ef7:       48 83 c4 08             add    $0x8,%rsp
  400efb:       c3                      retq

phase_1十分简单,调用了strings_not_equal函数直接判断两个字符串是否相等,第二个字符串起始地址直接存储在%esi里,作为第二个参数传入,观察第二行我们可以发现用来比较的字符串存在0x402400里,直接用(char*)0x402400解析,得到字符串“Border relations with Canada have never been better.”即为第一步正确答案。

print (char*)0x402400

Phase 2

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

根据第五行,调用了read_six_numbers这个函数,显然这次要求的输入是6个数字,根据第八行和第九行,第一个数字必须是1,否则会爆炸,我们观察到如果第一个数字是1,跳转到+52,也就是lea 0x4(%rsp),%rbx%rsp是栈指针的地址,每个int的数据长度为4个bytes,这句话的意思就是说读取下一个数字的地址,存入%rbx里。

对于read_six_numbers可以将其反汇编:

 disas read_six_numbers 
Dump of assembler code for function read_six_numbers:
   0x000000000040145c <+0>:	sub    $0x18,%rsp
   0x0000000000401460 <+4>:	mov    %rsi,%rdx
   0x0000000000401463 <+7>:	lea    0x4(%rsi),%rcx
   0x0000000000401467 <+11>:	lea    0x14(%rsi),%rax
   0x000000000040146b <+15>:	mov    %rax,0x8(%rsp)
   0x0000000000401470 <+20>:	lea    0x10(%rsi),%rax
   0x0000000000401474 <+24>:	mov    %rax,(%rsp)
   0x0000000000401478 <+28>:	lea    0xc(%rsi),%r9
   0x000000000040147c <+32>:	lea    0x8(%rsi),%r8
   0x0000000000401480 <+36>:	mov    $0x4025c3,%esi
   0x0000000000401485 <+41>:	mov    $0x0,%eax
   0x000000000040148a <+46>:	callq  0x400bf0 <__isoc99_sscanf@plt>
   0x000000000040148f <+51>:	cmp    $0x5,%eax
   0x0000000000401492 <+54>:	jg     0x401499 <read_six_numbers+61>
   0x0000000000401494 <+56>:	callq  0x40143a <explode_bomb>
   0x0000000000401499 <+61>:	add    $0x18,%rsp
   0x000000000040149d <+65>:	retq   

下一行有个奇怪的数值0x18,十进制为24,24/4=6正好是6个数字,这一行的目的就是设置一个结束点,放在%rbp中,然后回到+27.

仔细分析27行,不难发现,这段程序是在循环判断一个数组是否为公比为2的等比数列,如果不是则引爆炸弹,由于第一个数字是1,我们不难得出答案:

1 2 4 8 16 32

Phase 3

观察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>
  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函数,通过查文档发现,这个函数是用来解析字符串里的数字,按照规定格式存到另一个字符串里,并返回所解析的数字的个数,第二个参数就是解析格式,print (char*)0x4025cf,发现格式字符串为“%d %d”,也就是两个int数字.

第7行,第8行说明输入的参数个数要大于1。第11行将第一个参数0x8(%rsp) 和7比较,大于7则爆炸,说明输入的参数要小于等于7,同时ja为无符号跳转,则参数还有大于0,因此得出第一个参数的范围[0,7]。第14行为间接跳转,以 *0x402470 处的值为基地址,再加上8 * %rax 进行跳转,不同的 %rax 跳转到不同的位置。

在内存中输入的六个数字分布如下:

分割出来的代码用类C语言可以表示为:

void phase_2()
{//Number in %rsp,Edge in %rbp,(%register)表示寻址得到的值
    if((%rsp)==1)   //保证第一个数是1
    {
        goto Label_400f30;
    }
Label_400f17:
    %eax=(%rbx-0x4);
    %eax=2*%eax;
    if((%rbx)!=%eax)   //保证后一个数为前一个数的两倍
    {
        explode_bomb();
    }
    %rbx=%rbx+0x4;
    if(%rbx==%rbp)
    {
        return;
    }
    else
    {
        goto Label_400f17;
    }
Label_400f30:
    %rbx=%rsp+0x4;
    %rbp=%rsp+0x18;
    goto Label_400f17;
}

所以根据第一个值决定跳转位置的代码非常明显:jmpq *0x402470(,%rax,8)。我们可以使用 GDB 在查看跳转表的内容:

这样我们就得到了跳转表:

目标值

0

207

1

311

2

707

3

256

4

389

5

206

6

682

7

327

也就是所,上表中的任意一对值都可以解除炸弹。

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
  40101f:       b8 00 00 00 00          mov    $0x0,%eax
  401024:       e8 c7 fb ff ff          callq  400bf0 <__isoc99_sscanf@plt>
  401029:       83 f8 02                cmp    $0x2,%eax
  40102c:       75 07                   jne    401035 <phase_4+0x29>
  40102e:       83 7c 24 08 0e          cmpl   $0xe,0x8(%rsp)
  401033:       76 05                   jbe    40103a <phase_4+0x2e>
  401035:       e8 00 04 00 00          callq  40143a <explode_bomb>
  40103a:       ba 0e 00 00 00          mov    $0xe,%edx
  40103f:       be 00 00 00 00          mov    $0x0,%esi
  401044:       8b 7c 24 08             mov    0x8(%rsp),%edi
  401048:       e8 81 ff ff ff          callq  400fce <func4>
  40104d:       85 c0                   test   %eax,%eax
  40104f:       75 07                   jne    401058 <phase_4+0x4c>
  401051:       83 7c 24 0c 00          cmpl   $0x0,0xc(%rsp)
  401056:       74 05                   je     40105d <phase_4+0x51>
  401058:       e8 dd 03 00 00          callq  40143a <explode_bomb>
  40105d:       48 83 c4 18             add    $0x18,%rsp
  401061:       c3                      retq

从第5行看起,0x4025cf指向的地方存储的仍然是 两个int型整数。第8行和2比较,说明输入参数的个数为2。第10行和14比较,说明输入的第一个参数一定要小于14。第13,14,15行向func4()传递三个参数0x8(%rsp),0,14 。第17行测试函数返回值是否为0,要想不爆炸,函数返回值一定要为0。第19行说明输入的第二个参数一定要为0。

所以,我们要确定的是当输入的第一个参数为多少的时候,fun4()的返回值为0。下面看下fun4()的反汇编。

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

将汇编翻译为C如下所示:

//x: %edi y:%esi z:%edx k: %ecx t:%eax
void func4(int x,int y,int z)
{//x in %rdi,y in %rsi,z in %rdx,t in %rax,k in %ecx
 //y的初始值为0,z的初始值为14
  int t=z-y;
  int k=t>>31;
  t=(t+k)>>1;
  k=t+y;
  if(k>x)
  {
    z=k-1;
    func4(x,y,z);
    t=2t;
    return;
  }
  else
   {
     t=0;
     if(k<x)
     {
        y=k+1;
        func4(x,y,z);
        t=2*t+1;
        return;
     }
     else
     {
         return;
     }
   }
}

当x == k时,返回值为0。所以第一个参数为7。

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
  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 <__stack_chk_fail@plt>
  4010ee:       48 83 c4 20             add    $0x20,%rsp
  4010f2:       5b                      pop    %rbx
  4010f3:       c3                      retq

关卡 5 的第7行 ~ 13行要求我们输入一个长度为 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>

接下来代码每次从字符串中取出一个字符,并做变换:

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)

第15行 ~ 23行为一个循环。输入的字符串存储在%rbx中,第15行表示把输入字符串的第%eax个字符的ASCII码值给%ecx%cl%ecx的低8位,所以第16行为取%ecx的低八位。

第18行表示再取低4位。

第19行的0x4024b0查看内容为maduiersnfotvbyl,这句话的意思是以0x4024b0为基地址,以%rdx为偏移,从maduiersnfotvbyl字符串中取字符的低32位,结果放在%edx中。

第20行,%dl中的值应为0x4024b0+%rdx表示的字符,将其赋值给0x10(%rsp,%rax,1),最后计数器%rax+1

第22行,表示是否循环够了6次。

第25行,0x40245e字符串为flyers,比较两个字符串,如果%eax为0(两个字符串相同),则解除炸弹,否则爆炸。

所以,0x4024b0 + %rdx = {flyers的ASCII码}。

我们不知道变换方法是什么,但是代码中有一个突兀的地址,我们打印地址中的内容:

x/sb 0x4024b0

里面开头是一段奇怪的字符:”maduiersnfotvbyl

然后,将变换后的字符串和存储在 0x40245e 的字符串作比较,判断是否相当,如果相等,最解除成功。所以我们查看目标字符串:

x/sb 0x40245e

在有了目标字符串后,我们现在知道了上述的字符串可以看做是一张查找表,可以得到对应关系:

0

1

2

3

4

5

6

7

8

9

a

b

c

d

e

f

m

a

d

u

i

e

r

s

n

f

o

t

v

b

y

l

flyers对应的ascii值 0x66 0x6c 0x79 0x65 0x72 0x73。

  • 与0x4024b0内存地址开始的查找表比较获得偏移量 0x9 0xF 0xE 0x5 0x6 0x72。

  • 因此输入长度为6的字符串中每个字符的低4bit的值分别为0x9 0xF 0xE 0x5 0x6 0x72。

  • 若输入为大写字母,将低4bit的值加上0x40,获得输入字符串IONEFG。

  • 若输入为小写字母,将低4bit的值加上0x60,获得输入字符串ionefg。

所以,我们要得到 “flyers”,就应该输入“9 f e 5 6 7”,将数字对应到 ASCII 码中的字符,可以得到“IONEFG”。

phase_6

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

关卡 6 需要仔细分析,通过使用 GDB 单步运行的方法来分析了运行过程。

首先,函数要求读入 6 个数,并确认个数是否为 6。

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>

然后,通过双重循环,判断 6 个数之间不存在重复

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>

这里有一个细节,就是这段代码保存了和 7 的差:

401158:	4c 89 f0             	mov    %r14,%rax
40115b:	b9 07 00 00 00       	mov    $0x7,%ecx
401160:	89 ca                	mov    %ecx,%edx

通过分析这个结构,我们可以看到它是一个链表,定义类似于:

struct Node {
	int value;
	int index;
	struct node *next;
}

接下来,代码要求由大到小获取链表中的值,所以我们打印链表的节点值:

从大到小排列他们的索引分别是:3 4 5 6 1 2,考虑被 7 减的操作,所以答案是:4 3 2 1 6

总结

  • 学会了 GDB 的使用方法,对调试又有了一定的认识

  • 学会理解了栈帧的设计

  • 熟悉了一些常用寄存器的用途

  • 熟悉了 AT&T x86-64 汇编指令

Last updated