深入理解计算机系统bomb实验

深入理解计算机系统Bomb实验

  • 前言
  • 准备阶段
    • 上传bomb.c文件
    • 生成汇编代码
    • 进入gdb调试模式
    • 获取主要函数的汇编代码
  • 实验阶段
    • Phase1
      • 实验探究
      • 输入字符串首地址的保存
      • 继续phase1的研究
      • 通关密钥
    • Phase2
      • 实验探究
      • 通关密钥
    • Phase3
      • 实验探究
      • sscanf语句
      • swith-case语句
      • 通关密钥
    • Phase4
      • 实验探究
      • 通关密钥
    • Phase5
      • 实验探究
      • 通关密钥
    • Phase6
      • 实验探究
      • 通关密钥
  • 小结

前言

最近我在学计算机系统时,做到了一个蛮有趣的实验游戏——bomb实验(其实就是一个c程序)。这个实验有六关,每一关需要输入一个字符串(可以称之为密钥),每一关只有输入正确的密钥才能通过,否则“炸弹“将会爆炸。因此,我们需要通过汇编c代码找出汇编文件中藏有的密钥的信息。通过这个实验,我的汇编语言能力获得了极好的锻炼,因此纪录过程以供大家分享并作为纪念。

准备阶段

上传bomb.c文件

首先,据实验的要求,我们需要一台linux机器进行gdb调试。我们可以使用linux虚拟机或者租一台linux云主机,在这里我使用的是腾讯云的云主机。在这台主机上,我新建一个文件夹,并将实验所需要的bomb.c文件进行上传。
使用工具:xftp
复制bomb.c文件

生成汇编代码

在上传完成后,我们打开已连接上服务器的xshell或者虚拟机的teminal应用,使用cd命令进入到刚才上传到的文件夹中。之后,我们使用objdump -d bomb > bomb_assembly.S命令生成一个名为bomb_assembly.S的汇编代码文件
生成汇编代码

进入gdb调试模式

使用gdb bomb命令进入bomb.c文件的调试模式
进入gdb调试

获取主要函数的汇编代码

首先,我们打开已经生成好的汇编语言文件(可以在linux的文件管理器中打开或者通过xftp打开),找到main函数,将其复制下来,单独保存至一个文件中,方便查看,

0000000000400da0 <main>:
  # ...省略了以上的部分汇编代码
  400e32:	e8 67 06 00 00       	callq  40149e <read_line>
  400e37:	48 89 c7             	mov    %rax,%rdi
  400e3a:	e8 a1 00 00 00       	callq  400ee0 <phase_1>
  400e3f:	e8 80 07 00 00       	callq  4015c4 <phase_defused>
  400e44:	bf a8 23 40 00       	mov    $0x4023a8,%edi
  400e49:	e8 c2 fc ff ff       	callq  400b10 <puts@plt>
  400e4e:	e8 4b 06 00 00       	callq  40149e <read_line>
  400e53:	48 89 c7             	mov    %rax,%rdi
  400e56:	e8 a1 00 00 00       	callq  400efc <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>
  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>
  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>
  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>
  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   
  400ed7:	90                   	nop
  400ed8:	90                   	nop
  400ed9:	90                   	nop
  400eda:	90                   	nop
  400edb:	90                   	nop
  400edc:	90                   	nop
  400edd:	90                   	nop
  400ede:	90                   	nop
  400edf:	90                   	nop
  # ...省略了以下phase3-phase6的汇编代码

我们发现,phase1-phase6函数似乎就恰好对应题目中给出的第一关到第六关。于是,在gdb中,我们可以快速地使用disas命令进入这些函数的汇编代码中一探究竟。

  400e3a:	e8 a1 00 00 00       	callq  400ee0 <phase_1>  #找到函数名前的函数开始指令的地址
  400e3f:	e8 80 07 00 00       	callq  4015c4 <phase_defused>

进入gdb模式,使用disas 0x400ee0进入phase1的汇编代码。
disas 命令
重复上述步骤,依次快速复制phase1到phase6的代码,将代码分别复制到不同文件中。

实验阶段

Phase1

实验探究

打开复制下来的phase1的代码文件
关于每一步的解释以# 的注释标在代码后

   0x0000000000400ee0 <+0>:	sub    $0x8,%rsp  # 在栈中开辟一个8字节的临时空间
   0x0000000000400ee4 <+4>:	mov    $0x402400,%esi  # 将0x402400的值作为<string not equal>的参数传入
   0x0000000000400ee9 <+9>:	callq  0x401338 <strings_not_equal>
   0x0000000000400eee <+14>:	test   %eax,%eax   # 测试该函数返回值
   0x0000000000400ef0 <+16>:	je     0x400ef7 <phase_1+23>   # 若返回值为0则跳过炸弹爆炸函数
   0x0000000000400ef2 <+18>:	callq  0x40143a <explode_bomb>  # 返回值为1,炸
   0x0000000000400ef7 <+23>:	add    $0x8,%rsp  # 恢复栈
   0x0000000000400efb <+27>:	retq  

由字面意义可知,<string_no_equal>函数起到了一个比较字符串是否相等的作用。于是,猜测该函数具有两个参数——一个是我们输入的字符串的首地址,另一个是待比较的字符串的首地址

输入字符串首地址的保存

返回main函数保存的文件中,查看调用phase1函数之前的几句代码,发现确实如此。参数寄存器%rdi被<read_line>函数的返回值赋值。因此,猜测<read_line>函数用于读取一行字符串,将返回值保存于%rax,而被赋值的%rdi中存储的就是输入的字符串的首地址

  400e32:	e8 67 06 00 00       	callq  40149e <read_line>
  400e37:	48 89 c7             	mov    %rax,%rdi
  400e3a:	e8 a1 00 00 00       	callq  400ee0 <phase_1>

再观察其他的phase函数调用前的语句,我们都可以发现类似情况。因此,我们认为这些%rdi寄存器在phase函数的一开始,起到的是存储输入字符串首地址的作用

  400e4e:	e8 4b 06 00 00       	callq  40149e <read_line>
  400e53:	48 89 c7             	mov    %rax,%rdi
  400e56:	e8 a1 00 00 00       	callq  400efc <phase_2>

继续phase1的研究

于是,我们可以继续猜想,在调用<string_no_equal>函数之前的%esi寄存器中是否也存储了一个待比较的字符串首地址。

  0x0000000000400ee4 <+4>:	mov    $0x402400,%esi  # 将0x402400的值作为<string not equal>的参数传入

使用,x/s命令将0x402400中存储的字符串导出。答案如我们所愿。
x/s 0x402400
因此,phase1函数的作用只是单纯的让我们输入一个字符串。再将我们输入的字符串和存储的字符串进行比较而已。

通关密钥

密钥即为“Border relations with Canada have never been better.”,输入即可通关。

使用run命令执行bomb.c程序,再输入第一关密钥
run

Phase2

实验探究

打开phase2汇编代码被保存的文件

   0x0000000000400efc <+0>:	push   %rbp
   0x0000000000400efd <+1>:	push   %rbx
   0x0000000000400efe <+2>:	sub    $0x28,%rsp  # 产生一块40字节大小的临时空间
   0x0000000000400f02 <+6>:	mov    %rsp,%rsi  # 将栈指针赋值给参数寄存器
   0x0000000000400f05 <+9>:	callq  0x40145c <read_six_numbers>
   0x0000000000400f0a <+14>:	cmpl   $0x1,(%rsp)  # 比较1和栈顶元素的大小
   0x0000000000400f0e <+18>:	je     0x400f30 <phase_2+52>  
   0x0000000000400f10 <+20>:	callq  0x40143a <explode_bomb>  # 若不相等则炸
   0x0000000000400f15 <+25>:	jmp    0x400f30 <phase_2+52>
   0x0000000000400f17 <+27>:	mov    -0x4(%rbx),%eax  # 将栈中的上一个元素值赋值
   0x0000000000400f1a <+30>:	add    %eax,%eax  # 栈中的上一个元素值*2后保存
   0x0000000000400f1c <+32>:	cmp    %eax,(%rbx)  # 将上一个元素值的2倍与%rbx对应的值(现元素值)进行比较
   0x0000000000400f1e <+34>:	je     0x400f25 <phase_2+41>
   0x0000000000400f20 <+36>:	callq  0x40143a <explode_bomb>
   0x0000000000400f25 <+41>:	add    $0x4,%rbx   # 将%rbx的+=4
   0x0000000000400f29 <+45>:	cmp    %rbp,%rbx  # 比较是否等于尾指针
   0x0000000000400f2c <+48>:	jne    0x400f17 <phase_2+27>
   0x0000000000400f2e <+50>:	jmp    0x400f3c <phase_2+64>
   0x0000000000400f30 <+52>:	lea    0x4(%rsp),%rbx  # 将栈指针加4的赋值
   0x0000000000400f35 <+57>:	lea    0x18(%rsp),%rbp  # 将栈的尾指针赋值
   0x0000000000400f3a <+62>:	jmp    0x400f17 <phase_2+27>
   0x0000000000400f3c <+64>:	add    $0x28,%rsp
   0x0000000000400f40 <+68>:	pop    %rbx
   0x0000000000400f41 <+69>:	pop    %rbp
   0x0000000000400f42 <+70>:	retq 

首先函数将栈指针传入参数寄存器,考虑到紧挨的read_six_number函数。猜测栈指针作为参数用于保存数字,而另一参数%rdi(上文提到)给出输入字符串地址。因此,函数read_six_number函数用于将输入的字符串转换为6个数字。 得知,本轮需要输入6个数字作为密钥。

接下来从<+14>语句中得知,第一个输入的数字是1。


<+41>(偏移量++)
<+57>(尾指针的赋值)
<+45> (偏移量等于尾值)
判断出,这函数当中存在一个循环。循环中%rbx依次保存栈中的所有元素的地址。而又由<+27>-<+32>语句中可知,栈中元素满足这样的排列:栈中每一元素是它上一元素的两倍 ,即需输入一个首项为1,公比为2,项数为6的等比数列。

通关密钥

在这里插入图片描述

Phase3

实验探究

   0x0000000000400f43 <+0>:	sub    $0x18,%rsp  #栈指针减24用来存放3个临时变量(看大小决定个数)
   0x0000000000400f47 <+4>:	lea    0xc(%rsp),%rcx  #%rcx=栈指针+12(参数)
   0x0000000000400f4c <+9>:	lea    0x8(%rsp),%rdx  #%rdx=栈指针+8(参数)
   0x0000000000400f51 <+14>:	mov    $0x4025cf,%esi  #某个参数的传递
   0x0000000000400f56 <+19>:	mov    $0x0,%eax  #对返回值赋值0,为sscanf语句做准备
   0x0000000000400f5b <+24>:	callq  0x400bf0 <__isoc99_sscanf@plt>#按格式读入输入
   0x0000000000400f60 <+29>:	cmp    $0x1,%eax    #将返回值与1进行比较
   0x0000000000400f63 <+32>:	jg     0x400f6a <phase_3+39>  #若返回值大于1(说明scanf的参数大于1),jump39
   0x0000000000400f65 <+34>:	callq  0x40143a <explode_bomb>   #否则炸
   0x0000000000400f6a <+39>:	cmpl   $0x7,0x8(%rsp)  #比较第一个数字与7的大小
   0x0000000000400f6f <+44>:	ja     0x400fad <phase_3+106>  #若>7,跳转106,炸;并且是无符号数的比较。
   0x0000000000400f71 <+46>:	mov    0x8(%rsp),%eax    #把第一个数字的值给%eax
   0x0000000000400f75 <+50>:	jmpq   *0x402470(,%rax,8)   #这是属于跳转表的形式,
   0x0000000000400f7c <+57>:	mov    $0xcf,%eax   #以下就是把某一个值放到%eax中在做<+123>的过程,就是switch-case语句
   0x0000000000400f81 <+62>:	jmp    0x400fbe <phase_3+123>  
   0x0000000000400f83 <+64>:	mov    $0x2c3,%eax
   0x0000000000400f88 <+69>:	jmp    0x400fbe <phase_3+123>
   0x0000000000400f8a <+71>:	mov    $0x100,%eax  
   0x0000000000400f8f <+76>:	jmp    0x400fbe <phase_3+123>
   0x0000000000400f91 <+78>:	mov    $0x185,%eax
   0x0000000000400f96 <+83>:	jmp    0x400fbe <phase_3+123>
   0x0000000000400f98 <+85>:	mov    $0xce,%eax
   0x0000000000400f9d <+90>:	jmp    0x400fbe <phase_3+123>
   0x0000000000400f9f <+92>:	mov    $0x2aa,%eax
   0x0000000000400fa4 <+97>:	jmp    0x400fbe <phase_3+123>
0x0000000000400fa6 <+99>:	mov    $0x147,%eax
   0x0000000000400fab <+104>:	jmp    0x400fbe <phase_3+123>
   0x0000000000400fad <+106>:	callq  0x40143a <explode_bomb>
   0x0000000000400fb2 <+111>:	mov    $0x0,%eax
   0x0000000000400fb7 <+116>:	jmp    0x400fbe <phase_3+123>
   0x0000000000400fb9 <+118>:	mov    $0x137,%eax
   0x0000000000400fbe <+123>:	cmp    0xc(%rsp),%eax #都是在拿rsp+12的地址对应的值与eax进行比较
   0x0000000000400fc2 <+127>:	je     0x400fc9 <phase_3+134>  #若等就会结束,成功;不等,就会炸
   0x0000000000400fc4 <+129>:	callq  0x40143a <explode_bomb>
   0x0000000000400fc9 <+134>:	add    $0x18,%rsp
   0x0000000000400fcd <+138>:	retq

sscanf语句

在该汇编语言中,使用了sscanf格式化输入。sscanf的语句同read_six_number函数类似,只是具有了更灵活的形式。参数有需要规定的输入形式 ,本语句中以%esi参数寄存器传入。通过该参数,函数可以将输入的合法字符串转换为规定的数字或者字符串。

使用x/s 命令查看,得知是“%d %d”,即需要输入两个整数。
%d %d

而另外两个参数,分别是栈的+12地址,栈的+8地址。这两个参数用作保存转换的数字。返回值是输入成功的值的个数。这里是两个%d,所以若正常按格式输入两个数字,返回值应大于1。据<+32>得,若不大于1,则炸弹爆炸。

swith-case语句

接着是一个典型的swith-case语句。首先在<+44>中,将第一个数字与7进行无符号的小于比较。这是在规定输入的第一个数字必须是0-6之间的第一个数(包含0,6)。然后,是一个经典的跳转表形式。
在这里插入图片描述
通过<+46>语句,第一个数字成为了跳转表的参数<+50>。<+57><+64>等语句,分别对应的输入第一个数字为0-6情况的不同跳转。

在跳转后,将某个值(每个跳转对应的值均不同)存入%eax寄存器中。接着统一跳转至<+123>,0-6对应的不同的%eax的结果与第二个数字进行比较。若相等,方可通过。

第一个数字对应的case语句下取出的值
00xcf=207
10x137=311
20x2c3=707
30x100=256
40x185=389
50xce=206
60x2aa

通关密钥

因此,本局关卡需要输入两个数字。第一个数字必须是0-6中的一个,而第二个数字通过swith-case语句对应0-6,需输入不同数字。

通关实例 (以第一个数字0为例)
在这里插入图片描述

Phase4

实验探究

   0x000000000040100c <+0>:	sub    $0x18,%rsp     
   0x0000000000401010 <+4>:	lea    0xc(%rsp),%rcx  //要用的参数,放入参数寄存器中给scanf存
   0x0000000000401015 <+9>:	lea    0x8(%rsp),%rdx  //要用的参数
   0x000000000040101a <+14>:	mov    $0x4025cf,%esi    //这个也是“%d %d”
   0x000000000040101f <+19>:	mov    $0x0,%eax
   0x0000000000401024 <+24>:	callq  0x400bf0 <__isoc99_sscanf@plt>
   0x0000000000401029 <+29>:	cmp    $0x2,%eax  //不是正常的两个参数就炸
   0x000000000040102c <+32>:	jne    0x401035 <phase_4+41> 
   0x000000000040102e <+34>:	cmpl   $0xe,0x8(%rsp)  //这个值和14
   0x0000000000401033 <+39>:	jbe    0x40103a <phase_4+46>  //低于或者相等
   0x0000000000401035 <+41>:	callq  0x40143a <explode_bomb>
   0x000000000040103a <+46>:	mov    $0xe,%edx
   0x000000000040103f <+51>:	mov    $0x0,%esi
   0x0000000000401044 <+56>:	mov    0x8(%rsp),%edi
   0x0000000000401048 <+60>:	callq  0x400fce <func4>
   0x000000000040104d <+65>:	test   %eax,%eax  //返回0才是正确做法
   0x000000000040104f <+67>:	jne    0x401058 <phase_4+76>
   0x0000000000401051 <+69>:	cmpl   $0x0,0xc(%rsp)   //再比较第二个输入值和0的关系
   0x0000000000401056 <+74>:	je     0x40105d <phase_4+81>  //需等于0,否则炸
   0x0000000000401058 <+76>:	callq  0x40143a <explode_bomb>
   0x000000000040105d <+81>:	add    $0x18,%rsp
   0x0000000000401061 <+85>:	retq   

该函数简单明了,同Phase3,同样使用了一个sscanf语句,同样是"%d %d"格式输入。因此,密钥仍为两个数字。其次,0x8(%rsp)作为第一个数字,应该满足<+34>语句,即低于或者小于14。最后,func4的返回值必须是0,而func4的参数在<+46>-<+56>中给出。

于是,我们通过disas 命令获取func4的汇编代码。(这里不再示例gdb的使用)

   0x0000000000400fce <+0>:	sub    $0x8,%rsp
   0x0000000000400fd2 <+4>:	mov    %edx,%eax   # result=14
   0x0000000000400fd4 <+6>:	sub    %esi,%eax # result-=0,不变
   0x0000000000400fd6 <+8>:	mov    %eax,%ecx  
   0x0000000000400fd8 <+10>:	shr    $0x1f,%ecx  # %ecx逻辑右移31位,补0,取最高位之意
   0x0000000000400fdb <+13>:	add    %ecx,%eax  # 拿自己的最高位加上result;(负数加1正数加0)14+0=0
   0x0000000000400fdd <+15>:	sar    %eax  # 算术右移,单操作数是只移动一位的意思  7
   0x0000000000400fdf <+17>:	lea    (%rax,%rsi,1),%ecx  # 7+0=%ecx
   0x0000000000400fe2 <+20>:	cmp    %edi,%ecx  # 比较第一个输入值和%ecx的关系
   0x0000000000400fe4 <+22>:	jle    0x400ff2 <func4+36>
   0x0000000000400fe6 <+24>:	lea    -0x1(%rcx),%edx  
   0x0000000000400fe9 <+27>:	callq  0x400fce <func4>
   0x0000000000400fee <+32>:	add    %eax,%eax
   0x0000000000400ff0 <+34>:	jmp    0x401007 <func4+57>
   0x0000000000400ff2 <+36>:	mov    $0x0,%eax  # 给出0
   0x0000000000400ff7 <+41>:	cmp    %edi,%ecx  # 再比较一次
   0x0000000000400ff9 <+43>:	jge    0x401007 <func4+57> #  大于等于
   0x0000000000400ffb <+45>:	lea    0x1(%rcx),%esi
   0x0000000000400ffe <+48>:	callq  0x400fce <func4>
   0x0000000000401003 <+53>:	lea    0x1(%rax,%rax,1),%eax
   0x0000000000401007 <+57>:	add    $0x8,%rsp
   0x000000000040100b <+61>:	retq   

根据注释,我们可以得出:当返回值为0时,第一个数字需为7。 再回到函数phase4中来,我们看到有<+69>语句,该语句规定了第二个数字需为0这一输入

通关密钥

在这里插入图片描述

Phase5

实验探究

   0x0000000000401062 <+0>:	push   %rbx
   0x0000000000401063 <+1>:	sub    $0x20,%rsp  //开辟一个32字节的空间
   0x0000000000401067 <+5>:	mov    %rdi,%rbx    //rdi是输入字符串数组地址 
   0x000000000040106a <+8>:	mov    %fs:0x28,%rax  //  栈溢出保护
   0x0000000000401073 <+17>:	mov    %rax,0x18(%rsp)  //把返回值存储到栈临时内存中
   0x0000000000401078 <+22>:	xor    %eax,%eax  //异或自己,置零
   0x000000000040107a <+24>:	callq  0x40131b <string_length>  
   0x000000000040107f <+29>:	cmp    $0x6,%eax  //字符串长度与6比较
   0x0000000000401082 <+32>:	je     0x4010d2 <phase_5+112>  //等于的话跳转,否则炸
   0x0000000000401084 <+34>:	callq  0x40143a <explode_bomb>
   0x0000000000401089 <+39>:	jmp    0x4010d2 <phase_5+112>
   0x000000000040108b <+41>:	movzbl (%rbx,%rax,1),%ecx  //将字符依次赋值
   0x000000000040108f <+45>:	mov    %cl,(%rsp)   //%rcx的最低字节(依次的元素的值)给栈顶内存存储
   0x0000000000401092 <+48>:	mov    (%rsp),%rdx  //将这个字符赋值给%rdx
   0x0000000000401096 <+52>:	and    $0xf,%edx   //使得%edx高位的值被0覆盖掉,只剩0-15
   0x0000000000401099 <+55>:	movzbl 0x4024b0(%rdx),%edx  // 将(0x4024b0+%rdx)对应内存的值给了%edx
   0x00000000004010a0 <+62>:	mov    %dl,0x10(%rsp,%rax,1)   //再将%edx的低位保存在栈中
   0x00000000004010a4 <+66>:	add    $0x1,%rax  
   0x00000000004010a8 <+70>:	cmp    $0x6,%rax
   0x00000000004010ac <+74>:	jne    0x40108b <phase_5+41>//似乎是一个循环
   0x00000000004010ae <+76>:	movb   $0x0,0x16(%rsp)  //把0的值改写到这个字符串对应的结尾字符串,所以最后有'\0'做结尾
   0x00000000004010b3 <+81>:	mov    $0x40245e,%esi  //这个是需要比较的字符串地址
   0x00000000004010b8 <+86>:	lea    0x10(%rsp),%rdi  //这个是输入字符串地址
   0x00000000004010bd <+91>:	callq  0x401338 <strings_not_equal>  
   0x00000000004010c2 <+96>:	test   %eax,%eax  //是0就是两字符串相等
   0x00000000004010c4 <+98>:	je     0x4010d9 <phase_5+119>
   0x00000000004010c6 <+100>:	callq  0x40143a <explode_bomb>
   0x00000000004010cb <+105>:	nopl   0x0(%rax,%rax,1) //对齐作用
   0x00000000004010d0 <+110>:	jmp    0x4010d9 <phase_5+119>
   0x00000000004010d2 <+112>:	mov    $0x0,%eax  //返回值后32位置0
   0x00000000004010d7 <+117>:	jmp    0x40108b <phase_5+41> 
   0x00000000004010d9 <+119>:	mov    0x18(%rsp),%rax
   0x00000000004010de <+124>:	xor    %fs:0x28,%rax  //看是否被改写,否则出现大问题,这可不是炸的问题了
   0x00000000004010e7 <+133>:	je     0x4010ee <phase_5+140>
   0x00000000004010e9 <+135>:	callq  0x400b30 <__stack_chk_fail@plt>
   0x00000000004010ee <+140>:	add    $0x20,%rsp
   0x00000000004010f2 <+144>:	pop    %rbx
   0x00000000004010f3 <+145>:	retq   

首先,根据<+29>语句,<string_length>函数的返回值,即输入字符串的长度必须等于6。 紧接着,跳转至<+112>语句将%eax置0,然后才返回<+41>语句继续进行。<+41>语句将字符依次赋值给%ecx 。我们注意到,%rax在<+66>处++,并且在<+74>处跳转回去形成一个循环,而在这个循环中%rax是每次都改变的。因此每一次循环都使得%ecx获得到的是下一个的新字符。因此,称之为依次

之后通过一系列的操作:%ecx->栈顶->%rdx,将这个元素的值赋给%rdx。接着对%rdx使用0xf掩码,使其只剩低4位有效。(注意,这使得后面可以免去输入低位ASCLL码的麻烦

我们接着看到<+55>语句,这是关键;这一语句将不同的%rbx值作为偏移量,对0x4024b0的地址进行偏移,从而获得不同的字符放入%edx寄存器中。然后再将%edx寄存器中的值放如栈中保存。在循环语句退出后(循环执行6次,我们可以得知应输入6个字符),将第7个字符设为/0标志字符串的结尾。然后是一个简单字符串比较函数(看保存在栈中的字符串与0x40245e作为首地址的字符串是否相等——<+81>中给出参数)。

所以,Phase5的接题关键是,输入的字符作为偏移量,可以刚好使得0x4024b0作为首地址偏移所对应得新的字符与0x40245e对应得字符依次相等

因此,我们分别使用x/s 0x4024b0以及x/s 0x40245e命令查看两个字符串。
在这里插入图片描述
在这里插入图片描述
得到,偏移量应该为 9 15 14 5 6 7 ,才能依次对应“f l y e r s”。
由于前面使用了0xf作为掩码,所以可以使用字符串“9?>567”代替低ascll码得输入。

通关密钥

在这里插入图片描述

Phase6

实验探究

Section1 准备工作

  0x00000000004010f4 <+0>:	push   %r14
   0x00000000004010f6 <+2>:	push   %r13
   0x00000000004010f8 <+4>:	push   %r12
   0x00000000004010fa <+6>:	push   %rbp
   0x00000000004010fb <+7>:	push   %rbx
   0x00000000004010fc <+8>:	sub    $0x50,%rsp   //开辟80字节的临时空间
   0x0000000000401100 <+12>:	mov    %rsp,%r13  //将栈指针保存到被调用者寄存器r13
   0x0000000000401103 <+15>:	mov    %rsp,%rsi   //将栈指针传入参数,用于接受那6个数字
   0x0000000000401106 <+18>:	callq  0x40145c <read_six_numbers>
   0x000000000040110b <+23>:	mov    %rsp,%r14   //r14同样用来保存栈指针
   0x000000000040110e <+26>:	mov    $0x0,%r12d  

由上述代码可知,需要输入6个数字作为密钥。

Section2


   0x0000000000401114 <+32>:	mov    %r13,%rbp   //将栈指针的值赋给%rbp保存(这里,每一次循环都会+4%r13)
   0x0000000000401117 <+35>:	mov    0x0(%r13),%eax  //将栈指针指向的数字(%eax也是32位的)赋给%eax
   0x000000000040111b <+39>:	sub    $0x1,%eax  //数字的值--
   0x000000000040111e <+42>:	cmp    $0x5,%eax  //减完以后和5进行比较
   0x0000000000401121 <+45>:	jbe    0x401128 <phase_6+52>   //低于或者相等才可(也就是说,每一个数字都是要低于等于5的才行)(也不能是负数)
   0x0000000000401123 <+47>:	callq  0x40143a <explode_bomb>//否则炸
   0x0000000000401128 <+52>:	add    $0x1,%r12d  //0+1
   0x000000000040112c <+56>:	cmp    $0x6,%r12d  //比较和6比较大小,因此猜测是在一个循环中
   0x0000000000401130 <+60>:	je     0x401153 <phase_6+95>//这是跳出外层循环
   0x0000000000401132 <+62>:	mov    %r12d,%ebx   //将这个会变化的值(第一次是1)赋值给一个被调用者寄存器
   0x0000000000401135 <+65>:	movslq %ebx,%rax  //有符号数的低字节到高字节赋值,%rax被改变
   0x0000000000401138 <+68>:	mov    (%rsp,%rax,4),%eax  //将这个值对应的元素(每一次循环给一个)赋值给%eax
   0x000000000040113b <+71>:	cmp    %eax,0x0(%rbp)  //将这些数字与(%rbp进行比较)%rbp在外面其实一直在被递增(所以比较的始终是这个元素和它的上一个元素)
   0x000000000040113e <+74>:	jne    0x401145 <phase_6+81>  //不等于才是对的
   0x0000000000401140 <+76>:	callq  0x40143a <explode_bomb>
   0x0000000000401145 <+81>:	add    $0x1,%ebx  //再将这个计数器值加1
   0x0000000000401148 <+84>:	cmp    $0x5,%ebx  //将这个值与5进行比较
   0x000000000040114b <+87>:	jle    0x401135 <phase_6+65>  //这是一个嵌套循环
   0x000000000040114d <+89>:	add    $0x4,%r13                  //将%r13+4
   0x0000000000401151 <+93>:	jmp    0x401114 <phase_6+32>  

在这一部分,由注释可以得出,这一部分是一个嵌套循环。该嵌套循环有两层,外层循环作用是,确定输入这几个数字均在1-6之间(包含1,6)(见<+45>)。内层循环作用是,确定这几个数字互不相等(见<+71>)。

Section3

   0x0000000000401153 <+95>:	lea    0x18(%rsp),%rsi   //将指针+0x18地址对应的值赋值给%rsi参数
   0x0000000000401158 <+100>:	mov    %r14,%rax  //将栈指针的值传递给返回值寄存器
   0x000000000040115b <+103>:	mov    $0x7,%ecx  //将7赋值给第二个参数
   0x0000000000401160 <+108>:	mov    %ecx,%edx  //将第二个参数赋值给第三个参数    发现,第一个循环后%ecx不受影响,这是一个定值
   0x0000000000401162 <+110>:	sub    (%rax),%edx  //让7-%rax指向的数字
   0x0000000000401164 <+112>:	mov    %edx,(%rax)  //将这个结果赋值给这个数字
   0x0000000000401166 <+114>:	add    $0x4,%rax //让它指向第二个数字
   0x000000000040116a <+118>:	cmp    %rsi,%rax  //比较%rsi尾指针地址是否不同,这应该是最后一个数字,说明这是一个循环
---Type <return> to continue, or q <return> to quit---
   0x000000000040116d <+121>:	jne    0x401160 <phase_6+108>

该部分的作用即,将输入的数字分别转换为7-该数字。如1变为6…。其中栈指针对应的是栈顶元素。栈指针+8对应的是栈中的第二个元素。即栈中的每一个元素之间的地址间隔8个字节。

Section4

   0x000000000040116f <+123>:	mov    $0x0,%esi  //赋值0给%esi
   0x0000000000401174 <+128>:	jmp    0x401197 <phase_6+163>
   0x0000000000401176 <+130>:	mov    0x8(%rdx),%rdx   //将某个值给取出来赋值给%rdx==6304480
   0x000000000040117a <+134>:	add    $0x1,%eax  //将这个值+1
   0x000000000040117d <+137>:	cmp    %ecx,%eax  //这里是比较%ecx(每一个数字)和%eax(第一次是2)
   0x000000000040117f <+139>:	jne    0x401176 <phase_6+130>  //若不等,跳转回到130,这是一个循环
   0x0000000000401181 <+141>:	jmp    0x401188 <phase_6+148> 
   0x0000000000401183 <+143>:	mov    $0x6032d0,%edx            
   0x0000000000401188 <+148>:	mov    %rdx,0x20(%rsp,%rsi,2)  //将这个值存起来
   0x000000000040118d <+153>:	add    $0x4,%rsi   //将计数器++
   0x0000000000401191 <+157>:	cmp    $0x18,%rsi  //计数器退出条件
   0x0000000000401195 <+161>:	je     0x4011ab <phase_6+183>
   0x0000000000401197 <+163>:	mov    (%rsp,%rsi,1),%ecx   //这一看又是一个循环,目的,将不同的数字给依次取出
   0x000000000040119a <+166>:	cmp    $0x1,%ecx   //比较这些数字和1的大小关系     
   0x000000000040119d <+169>:	jle    0x401183 <phase_6+143>  //如果是小于等于1就直接到143
   0x000000000040119f <+171>:	mov    $0x1,%eax  //继续执行  将1赋值给%eax
   0x00000000004011a4 <+176>:	mov    $0x6032d0,%edx  //将这个值赋值给%edx(复原)
   0x00000000004011a9 <+181>:	jmp    0x401176 <phase_6+130>

这一部分起到了关键作用。首先将栈中元素取出<+163>(这里同样是依次取出),置于%ecx。接着是一个判断语句<+143>,我们首先考虑栈顶元素等于1(这是经过了section3后的,原值是6)的情况——这时,栈顶元素被覆盖为0x6032d0 <+148>。

那么,我们继续考虑栈中元素大于1的情况。此时均会跳转至<+130>处。在<+130>到<+141>之间是一个循环。若栈顶元素是2,则只执行一次<+130>语句后退出。若是3,则执行两次,依此类推。

而<+130>语句,实际上是对%rdx+8这一地址取值后,赋值给%rbx自己(可以看成是一个链表:p=p->next)

因此,不同的值对应的不同结果如下。栈中最后会按照原来对应的数字来保存不同的地址
在这里插入图片描述
Section5
在这里,我们为方便叙述,我们将栈中存的地址称为地址元素

   0x00000000004011ab <+183>:	mov    0x20(%rsp),%rbx   //给定开始地址元素
   0x00000000004011b0 <+188>:	lea    0x28(%rsp),%rax     //这是下一元素的栈地址
   0x00000000004011b5 <+193>:	lea    0x50(%rsp),%rsi   //这个是末尾元素的栈地址
   0x00000000004011ba <+198>:	mov    %rbx,%rcx    //开始地址元素的赋值
   0x00000000004011bd <+201>:	mov    (%rax),%rdx       //将栈中的下一个地址元素赋值给%rdx(中转站)
   0x00000000004011c0 <+204>:	mov    %rdx,0x8(%rcx)   //这是将栈中的下一个地址元素赋值给(上一地址元素+8)对应的内存中
   0x00000000004011c4 <+208>:	add    $0x8,%rax  //将%rax+8(下一栈地址)
   0x00000000004011c8 <+212>:	cmp    %rsi,%rax  //退出条件:下一栈地址等于末尾元素栈地址(所以只循环五次)此时%rdx为第五个地址元素
   0x00000000004011cb <+215>:	je     0x4011d2 <phase_6+222>
   0x00000000004011cd <+217>:	mov    %rdx,%rcx   //中转站中的值赋值给%rcx(这里是把第二个地址元素赋给(原来的开始元素),副本间的赋值)
   0x00000000004011d0 <+220>:	jmp    0x4011bd <phase_6+201>

该部分的作用:把栈中所有的下一个地址元素赋值给,其上一个地址元素+8对应的内存中,见<+204>。但是,栈本身存的地址元素并没有被改变。而且,这些地址元素指向的值确实也没有被改变,因为改变的是地址元素+8对应的内存值,而我们不是地址元素对应的内存值。(注:地址元素所对应的值的大小只占8字节的大小

Section6

   0x00000000004011d2 <+222>:	movq   $0x0,0x8(%rdx)   //将0值赋给(最后一个地址元素+8)对应内存中
   0x00000000004011da <+230>:	mov    $0x5,%ebp
   0x00000000004011df <+235>:	mov    0x8(%rbx),%rax  //这里是(现地址元素+8对应的内存值)对应的内存(注意,即下一个地址元素)
   0x00000000004011e3 <+239>:	mov    (%rax),%eax   //将这个地址元素再解引用得到(就是下一个地址元素指向的值!!注意,这个值是没有被改变的!)
   0x00000000004011e5 <+241>:	cmp    %eax,(%rbx)   //将这个值与上一个元素地址对应的内存(均没有被改变!)进行比较
   0x00000000004011e7 <+243>:	jge    0x4011ee <phase_6+250>  上一个元素地址对应的值需要大于等于下一个的
   0x00000000004011e9 <+245>:	callq  0x40143a <explode_bomb>
   0x00000000004011ee <+250>:	mov    0x8(%rbx),%rbx  //将%rbx++
   0x00000000004011f2 <+254>:	sub    $0x1,%ebp  //计数器,五次循环,比较只需要五次就可以比完
   0x00000000004011f5 <+257>:	jne    0x4011df <phase_6+235>  //若不等于则回去
---Type <return> to continue, or q <return> to quit---
   0x00000000004011f7 <+259>:	add    $0x50,%rsp
   0x00000000004011fb <+263>:	pop    %rbx
   0x00000000004011fc <+264>:	pop    %rbp
   0x00000000004011fd <+265>:	pop    %r12
   0x00000000004011ff <+267>:	pop    %r13
   0x0000000000401201 <+269>:	pop    %r14
   0x0000000000401203 <+271>:	retq   

在这部分,我们使用一个循环将栈中所有的地址元素对应的值,和它的下一个地址元素(见<+235><+239>)对应的值进行比较(见<+241>)。而比较的目的是,让下一个元素地址对应的值均大于现地址元素对应的值。

因此,我们使用x/g 语句,对于0x6032d0-0x603320对应的值查询。

地址
0x6032d0332
0x6032e0168
0x6032f0924
0x603300691
0x603310477
0x603320443

再对应上一张表,我们可以得出使得对应元素值从大到小数字串是"4 3 2 1 6 5"

通关密钥

在这里插入图片描述

小结

这一次的bomb实验,包含了计算机系统中第三章汇编语言的几乎所有知识点。通过本次的练习,我的汇编语言能力获得了很好的锻炼,对于一些重要知识点(如跳转表,循环)的知识点,掌握的更加牢靠。而本实验中包含的许多有趣实用的汇编语言技巧(如一些精巧的中间变量的使用、灵活的jump跳转指令的运用)使我更加注意编程技巧的学习。汇编语言的学习无疑是一件重中之重的学习任务,学习之途任重而道远,今发此文,与诸君共勉。

——ECNU 杨政 (转载请声明)

热门文章

暂无图片
编程学习 ·

Java输出数组的内容

Java输出数组的内容_一万个小时-CSDN博客_java打印数组内容1. 输出内容最常见的方式// List<String>类型的列表List<String> list new ArrayList<String>();list.add("First");list.add("Second");list.add("Third");list.ad…
暂无图片
编程学习 ·

母螳螂的“魅惑之术”

在它们对大蝗虫发起进攻的时候&#xff0c;我认认真真地观察了一次&#xff0c;因为它们突然像触电一样浑身痉挛起来&#xff0c;警觉地面对限前这个大家伙&#xff0c;然后放下自己优雅的身段和祈祷的双手&#xff0c;摆出了一个可怕的姿势。我被眼前的一幕吓到了&#xff0c;…
暂无图片
编程学习 ·

疯狂填词 mad_libs 第9章9.9.2

#win7 python3.7.0 import os,reos.chdir(d:\documents\program_language) file1open(.\疯狂填词_d9z9d2_r.txt) file2open(.\疯狂填词_d9z9d2_w.txt,w) words[ADJECTIVE,NOUN,VERB,NOUN] str1file1.read()#方法1 for word in words :word_replaceinput(fEnter a {word} :)str1…
暂无图片
编程学习 ·

HBASE 高可用

为了保证HBASE是高可用的,所依赖的HDFS和zookeeper也要是高可用的. 通过参数hbase.rootdir指定了连接到Hadoop的地址,mycluster表示为Hadoop的集群. HBASE本身的高可用很简单,只要在一个健康的集群其他节点通过命令 hbase-daemon.sh start master启动一个Hmaster进程,这个Hmast…
暂无图片
编程学习 ·

js事件操作语法

一、事件的绑定语法 语法形式1 事件监听 标签对象.addEventListener(click,function(){}); 语法形式2 on语法绑定 标签对象.onclick function(){} on语法是通过 等于赋值绑定的事件处理函数 , 等于赋值本质上执行的是覆盖赋值,后赋值的数据会覆盖之前存储的数据,也就是on…
暂无图片
编程学习 ·

Photoshop插件--晕影动态--选区--脚本开发--PS插件

文章目录1.插件界面2.关键代码2.1 选区2.2 动态晕影3.作者寄语PS是一款栅格图像编辑软件&#xff0c;具有许多强大的功能&#xff0c;本文演示如何通过脚本实现晕影动态和选区相关功能&#xff0c;展示从互联网收集而来的一个小插件&#xff0c;供大家学习交流&#xff0c;请勿…
暂无图片
编程学习 ·

vs LNK1104 无法打开文件“xxx.obj”

写在前面&#xff1a; 向大家推荐两本新书&#xff0c;《深度学习计算机视觉实战》和《学习OpenCV4&#xff1a;基于Python的算法实战》。 《深度学习计算机视觉实战》讲了计算机视觉理论基础&#xff0c;讲了案例项目&#xff0c;讲了模型部署&#xff0c;这些项目学会之后可以…
暂无图片
编程学习 ·

工业元宇宙的定义与实施路线图

工业元宇宙的定义与实施路线图 李正海 1 工业元宇宙 给大家做一个关于工业元宇宙的定义。对于工业&#xff0c;从设计的角度来讲&#xff0c;现在的设计人员已经做到了普遍的三维设计&#xff0c;但是进入元宇宙时代&#xff0c;就不仅仅只是三维设计了&#xff0c;我们的目…
暂无图片
编程学习 ·

【leectode 2022.1.15】完成一半题目

有 N 位扣友参加了微软与力扣举办了「以扣会友」线下活动。主办方提供了 2*N 道题目&#xff0c;整型数组 questions 中每个数字对应了每道题目所涉及的知识点类型。 若每位扣友选择不同的一题&#xff0c;请返回被选的 N 道题目至少包含多少种知识点类型。 示例 1&#xff1a…
暂无图片
编程学习 ·

js 面试题总结

一、js原型与原型链 1. prototype 每个函数都有一个prototype属性&#xff0c;被称为显示原型 2._ _proto_ _ 每个实例对象都会有_ _proto_ _属性,其被称为隐式原型 每一个实例对象的隐式原型_ _proto_ _属性指向自身构造函数的显式原型prototype 3. constructor 每个prot…
暂无图片
编程学习 ·

java练习代码

打印自定义行数的空心菱形练习代码如下 import java.util.Scanner; public class daYinLengXing{public static void main(String[] args) {System.out.println("请输入行数");Scanner myScanner new Scanner(System.in);int g myScanner.nextInt();int num g%2;//…
暂无图片
编程学习 ·

RocketMQ-什么是死信队列?怎么解决

目录 什么是死信队列 死信队列的特征 死信消息的处理 什么是死信队列 当一条消息初次消费失败&#xff0c;消息队列会自动进行消费重试&#xff1b;达到最大重试次数后&#xff0c;若消费依然失败&#xff0c;则表明消费者在正常情况下无法正确地消费该消息&#xff0c;此时…
暂无图片
编程学习 ·

项目 cg day04

第4章 lua、Canal实现广告缓存 学习目标 Lua介绍 Lua语法 输出、变量定义、数据类型、流程控制(if..)、循环操作、函数、表(数组)、模块OpenResty介绍(理解配置) 封装了Nginx&#xff0c;并且提供了Lua扩展&#xff0c;大大提升了Nginx对并发处理的能&#xff0c;10K-1000K Lu…
暂无图片
编程学习 ·

输出三角形

#include <stdio.h> int main() { int i,j; for(i0;i<5;i) { for(j0;j<i;j) { printf("*"); } printf("\n"); } }
暂无图片
编程学习 ·

stm32的BOOTLOADER学习1

序言 最近计划学习stm32的BOOTLOADER学习,把学习过程记录下来 因为现在网上STM32C8T6还是比较贵的,根据我的需求flash空间小一些也可以,所以我决定使用stm32c6t6.这个芯片的空间是32kb的。 #熟悉芯片内部的空间地址 1、flash ROM&#xff1a; 大小32KB&#xff0c;范围&#xf…
暂无图片
编程学习 ·

通过awk和shell来限制IP多次访问之学不会你打死我

学不会你打死我 今天我们用shell脚本&#xff0c;awk工具来分析日志来判断是否存在扫描器来进行破解网站密码——限制访问次数过多的IP地址&#xff0c;通过Iptables来进行限制。代码在末尾 首先我们要先查看日志的格式&#xff0c;分析出我们需要筛选的内容&#xff0c;日志…
暂无图片
编程学习 ·

Python - 如何像程序员一样思考

在为计算机编写程序之前&#xff0c;您必须学会如何像程序员一样思考。学习像程序员一样思考对任何学生都很有价值。以下步骤可帮助任何人学习编码并了解计算机科学的价值——即使他们不打算成为计算机科学家。 顾名思义&#xff0c;Python经常被想要学习编程的人用作第一语言…
暂无图片
编程学习 ·

蓝桥杯python-数字三角形

问题描述 虽然我前后用了三种做法&#xff0c;但是我发现只有“优化思路_1”可以通过蓝桥杯官网中的测评&#xff0c;但是如果用c/c的话&#xff0c;每个都通得过&#xff0c;足以可见python的效率之低&#xff08;但耐不住人家好用啊&#xff08;哭笑&#xff09;&#xff09…