深入理解计算机系统-bomblab详解

下载实验用的文件们戳这里
bomblab的背景很有趣。Dr. Evil把“二进制炸弹”装在了教室的机子里。想要拆掉炸弹,你必须反编译“炸弹”,通过其中的汇编指令推测出可以拆掉炸弹的phrase。
好啦,我们看一看下载的文件夹里都有什么。bomb就是要我们去拆的“炸弹”;bomb.c是炸弹的源代码,但是最为关键的部分被删掉了,只保留了骨架。gdbnotes-x86-64是个重要的文件,里面有这个实验里用到的各种工具的使用方法。剩下的就是实验的各种说明了~

Phase 1

我们来看一看phase_1的反汇编是什么样子。一种方法是直接在终端里输入"objdump -d bomb > bomb.txt", 直接把objdump的输出重定向到bomb.txt里,之后就可以直接在记事本里面看反汇编了;或者,先在终端里输入"gdb bomb", 进到gdb里面;再输入"disas phase_1", 就能看到phase_1的反汇编了。我们这里用第一种方法,最终在记事本里看到的结果是下面这个样子:

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   

那我们就先来捋顺这段汇编的逻辑吧。这段代码先把一个值,更准确地说,是字符串的地址(0x402400)放到%esi里,之后调用一个叫"strings_not_equal"的函数;最后判断这个函数的返回值:等于零,通过;不等于零,调用"explode_bomb"把炸弹炸掉(或许你会问%edi在哪里?其实%edi就是我们输入的字符串的地址)。显然,这短短的一段汇编里,最重要的就是对strings_not_equal函数的调用。至于这个函数是干什么的,猜也能猜得出来:判断两个字符串是不是相等:相等,返回零;否则返回非零(仔细看strings_not_equal的实现,实际上不相等时返回1)(当然你也可以自己翻到0x401338,看一看这个推测是否正确。)。
现在让我们考虑一下strings_not_equal这个函数的两个参数。%rdi中的值,就是我们输入的字符串的地址;%rsi中的值是后面传进去的0x402400.那么0x402400指向的是什么字符串呢?我们打开gdb看一看。在终端中输入:

gdb bomb
x /s 0x402400

输出是什么呢?

0x402400:	"Border relations with Canada have never been better."

只要我们的输入和上面这个字符串相同就行了。打开终端试一试~
(P.S. 这个字符串是2016年初更新的。它的出处是,2001年时任美国总统的乔治·布什,为欢迎加拿大总理访美所作的讲话。原句为"Border relations between Canada and Mexico have never been better. " 5年后,布什签署法案,授权在美墨边境修建隔离墙。2015年9月,特朗普宣布竞选美国总统,而其竞选承诺中有一条就是“在美墨边境修墙”。从这个细节,我们似乎可以一瞥作者对美墨边境相关政策的态度。)

Phase 2

前面那个Phase就当热身啦,接下来的几个Phase才是重头戏。还是刚才的办法,我们把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>
  ...                           ...

这段汇编先“读入“六个数(0x400f05:read_six_numbers)。从哪里“读入”呢?当然不是stdin。我们注意到,和上面那个Phase一样,%rdi并没有在调用这个函数之前出现。也就是说,phase_2函数的第一个参数,被原封不动地传到了read_six_numbers的第一个参数。那么%rsi,也就是第二个参数,代表的是什么呢?注意看这两行汇编:

  400efe:	48 83 ec 28          	sub    $0x28,%rsp
  400f02:	48 89 e6             	mov    %rsp,%rsi

知道了吗?%rsi里放的是地址!结合read_six_numbers第一个参数的含义(恰好是我们输入的字符串)大胆猜想,我们可以知道—— 1. read_six_numbers从我们自己输进去的字符串里"read"; 2. %rsi在源代码里应该是个指针,这个指针指向一个数组的开头,而这个数组就是放read_six_numbers从字符串里抽出的六个数用的。不过我们还是来看一看read_six_numbers具体是怎么实现的:

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 <__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 

%rsi是什么?我们猜是数组首个元素的地址。0x4(%rsi)是什么?我们猜是数组第二个元素的地址。那么0x14(%rsi)、0x10(%rsi)等等呢?同理。以上这些都被read_six_numbers做成了sscanf的末几个参数(不是"scanf"!多了一个"s"!!!)(注意到了吗?有几个地址,因为寄存器放不下,被送到栈里面了)。sscanf的第一个参数,从汇编来看,是read_six_numbers的第一个参数,也就是我们输入的字符串;而第二个参数是存储在0x4025c3的字符串。和上面一样,我们来看一看0x4025c3里有什么。

0x4025c3:	"%d %d %d %d %d %d"

觉得熟悉吗?事实上,sscanf的作用与scanf类似,只不过scanf从sdtin中读取数据,sscanf从它的第一个参数中读取数据。read_six_numbers就是借助sscanf,把我们输入的字符串中的数抽出来,放到一个数组里。我们的猜测是对的。
现在,我们知道了phase_2的要求是输入6个特定的数,数与数之间用空格隔开。那么这六个数又是什么呢?我们接着往下看。

  ...                          	    ...
  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>
  ...                          	    ...

我们已经知道了,%rsp里放的就是数组第一个元素的地址。所以说,很显然,这段汇编在判断我们输入的六个数,第一个数是不是等于一。判断了之后就跳到0x400f30:

  ...                           	...
  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>
  ...                               ...

嗯,这三行汇编把第二个数(int是四个字节)的地址放进%rbx里,把最后一个数下一个字节( ( 18 ) 16 = ( 24 ) 10 (18)_{16} = (24)_{10} (18)16=(24)10)的地址放进%rbp里(是判断循环终止条件用的),之后跳到0x400f17.

  ...                           ...
  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>
  ...                           ...

从0x400f17这里,程序就开始循环处理我们输进去的六个数了。首先,程序把%rbx指向的数的前一个放到%eax里,之后让它翻倍:

  400f17:	8b 43 fc             	mov    -0x4(%rbx),%eax
  400f1a:	01 c0                	add    %eax,%eax

翻倍之后再检查%eax里的值是不是和%rbx当前指向的数相同:

  400f1c:	39 03                	cmp    %eax,(%rbx)
  400f1e:	74 05                	je     400f25 <phase_2+0x29>
  400f20:	e8 15 05 00 00       	callq  40143a <explode_bomb>

如果相同呢,就跳到0x400f25;如果不相同,就调用explode_bomb引爆炸弹。看来,这好像是个等比数列!1为首项,2为公比!
我们最后再来看一看0x400f25那里的汇编是什么。

  400f25:	48 83 c3 04          	add    $0x4,%rbx
  400f29:	48 39 eb             	cmp    %rbp,%rbx
  400f2c:	75 e9                	jne    400f17 <phase_2+0x1b>

和我们想得一样,这里程序把%rbx里的指针后移四字节,再判断指针是否到达了数组末尾。
所以,要过这个phase,只要输入以一为首项,二为公比的等比数列前六项就行了。

Phase 3

这个phase考的是switch语句的汇编表示。
好啦,先看题吧。phase_3开头和上面那个read_six_numbers很像,也调用了sscanf,从我们输入的字符串里提取数字。只不过这次只有两个数,第一个放在0x8(%rsp)那里,第二个放在0xc(%rsp)那里。
仔细看汇编。我们发现,0x8(%rsp)和0xc(%rsp)这两个值,只在两个地方出现过。对于0x8(%rsp), 这个地方是:

  400f71:	8b 44 24 08          	mov    0x8(%rsp),%eax
  400f75:	ff 24 c5 70 24 40 00 	jmpq   *0x402470(,%rax,8)

而对于0xc(%rsp), 这个地方是:

  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>

在0x400f75这一行,代码究竟要跳转到哪里呢?应该是0x402470这个地址中储存的值(星号解引用,和指针一样),和8乘%rax里的值加在一起组成的地址。%rax里的值我们知道,就是我们输入的第一个数;那么0x402470这个地址中的值又是什么呢?还是像前面那样,我们在gdb中输入x /wx 0x402470,看一看输出:

(gdb) x /wx 0x402470
0x402470:	0x00400f7c

0x400f7c

评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值