二进制炸弹(深入理解计算机系统)

实验介绍

本实验是对CSAPP配套实验的简化改编版本,主要考察阅读X86汇编的能力。总共分为四小关。

  • phase_1: 输入字符串密码

  • phase_2:输入六个数的密码

  • phase_3: 输入两个数,满足switch-case的关系

  • phase_4:输入一个数,满足斐波那契数列的规律

实验环境

  • wsl2.0:Windows的Linux子系统
  • gdb:调试工具
  • objdump:反汇编工具

phase_1

0000000000000c92 <phase_1>:
 c92:	48 83 ec 08          	sub    $0x8,%rsp
 c96:	48 8d 35 03 14 20 00 	lea    0x201403(%rip),%rsi        # 2020a0 <unique_str>
 c9d:	b8 00 00 00 00       	mov    $0x0,%eax
 ca2:	e8 8f ff ff ff       	callq  c36 <strings_not_equal>
 ca7:	85 c0                	test   %eax,%eax
 ca9:	75 05                	jne    cb0 <phase_1+0x1e>
 cab:	48 83 c4 08          	add    $0x8,%rsp
 caf:	c3                   	retq   
 cb0:	b8 00 00 00 00       	mov    $0x0,%eax
 cb5:	e8 d2 fe ff ff       	callq  b8c <explode_bomb>
 cba:	eb ef                	jmp    cab <phase_1+0x19>
  • c92: 为当前函数分配栈空间
  • c96:把0x201403(%rip)这个固定地址传给%rsi
  • c9d:eax清零,之后要放返回值
  • ca2:执行strings_not_equal函数,此函数的返回值放在eax中
  • ca7:检查eax是否为0(实际做的是逻辑与操作,若eax为0,则将ZF位置为1)
  • ca9:eax若不为0则跳转到cb0,可以看到跳转后炸弹爆炸
  • cab: 释放为此函数分配的栈空间
  • caf:返回main函数
  • cb0:eax清零
  • cb5:炸弹爆炸
  • cba:炸弹爆炸后也要把为此函数分配的栈空间释放
0000000000000c36 <strings_not_equal>:
 c36:	48 83 ec 08          	sub    $0x8,%rsp
 c3a:	e8 c1 fb ff ff       	callq  800 <strcmp@plt>
 c3f:	85 c0                	test   %eax,%eax
 c41:	0f 95 c0             	setne  %al
 c44:	0f b6 c0             	movzbl %al,%eax
 c47:	48 83 c4 08          	add    $0x8,%rsp
 c4b:	c3                   	retq  
  • 查看strings_not_equal的汇编,发现若两个字符串相等,eax中返回的是0.

分析以上两段汇编代码知,
我们要输入一段字符串,和0x201403(%rip)这个地址中的字符串进行比较,所以现在只需要打印出0x201403(%rip)这个地址中的内容就可以找到答案了。

在这里插入图片描述

phase_2

0000000000000cbc <phase_2>:
 cbc:	53                   	push   %rbx
 cbd:	48 83 ec 20          	sub    $0x20,%rsp
 cc1:	64 48 8b 04 25 28 00 	mov    %fs:0x28,%rax
 cc8:	00 00 
 cca:	48 89 44 24 18       	mov    %rax,0x18(%rsp)
 ccf:	31 c0                	xor    %eax,%eax
 cd1:	48 89 e6             	mov    %rsp,%rsi
 cd4:	e8 73 ff ff ff       	callq  c4c <read_six_numbers>
 cd9:	bb 00 00 00 00       	mov    $0x0,%ebx
 cde:	eb 03                	jmp    ce3 <phase_2+0x27>
 ce0:	83 c3 01             	add    $0x1,%ebx
 ce3:	83 fb 05             	cmp    $0x5,%ebx
 ce6:	7f 1e                	jg     d06 <phase_2+0x4a>
 ce8:	48 63 c3             	movslq %ebx,%rax
 ceb:	48 8d 15 8e 13 20 00 	lea    0x20138e(%rip),%rdx        # 202080 <magic_num>
 cf2:	8b 0c 82             	mov    (%rdx,%rax,4),%ecx
 cf5:	39 0c 84             	cmp    %ecx,(%rsp,%rax,4)
 cf8:	74 e6                	je     ce0 <phase_2+0x24>
 cfa:	b8 00 00 00 00       	mov    $0x0,%eax
 cff:	e8 88 fe ff ff       	callq  b8c <explode_bomb>
 d04:	eb da                	jmp    ce0 <phase_2+0x24>
 d06:	48 8b 44 24 18       	mov    0x18(%rsp),%rax
 d0b:	64 48 33 04 25 28 00 	xor    %fs:0x28,%rax
 d12:	00 00 
 d14:	75 06                	jne    d1c <phase_2+0x60>
 d16:	48 83 c4 20          	add    $0x20,%rsp
 d1a:	5b                   	pop    %rbx
 d1b:	c3                   	retq   
 d1c:	e8 af fa ff ff       	callq  7d0 <__stack_chk_fail@plt>

分析代码:

  • cbc到cca是在分配栈空间和进行堆栈溢出检查 把当前栈的rsp存到rsi中作为read_six_numbers的参数 。
  • 执行完read_six_numbers后,开始循环,ebx从0到5,每次循环对比以0x20138e(%rip)为起始地址的数组中的数和我们输入的放在rsp到rsp+18中的数是否相等,若不相等则炸弹爆炸。所以我们只需要输入以0x20138e(%rip)为起始地址的数组中的数即可。

此时程序将标准答案的数组地址放到rdx中
查看此时rdx中的内容,这个内容就是标准答案的起始地址。
查看答案
此时我们就得到了要输的6个数。

read_six_numbers

0000000000000c4c <read_six_numbers>:
 c4c:	48 83 ec 08          	sub    $0x8,%rsp
 c50:	48 89 f2             	mov    %rsi,%rdx
 c53:	48 8d 76 14          	lea    0x14(%rsi),%rsi
 c57:	48 8d 42 10          	lea    0x10(%rdx),%rax
 c5b:	48 8d 4a 04          	lea    0x4(%rdx),%rcx
 c5f:	56                   	push   %rsi
 c60:	50                   	push   %rax
 c61:	4c 8d 4a 0c          	lea    0xc(%rdx),%r9
 c65:	4c 8d 42 08          	lea    0x8(%rdx),%r8
 c69:	48 8d 35 a0 13 20 00 	lea    0x2013a0(%rip),%rsi        # 202010 <fmt_six_num>
 c70:	b8 00 00 00 00       	mov    $0x0,%eax
 c75:	e8 a6 fb ff ff       	callq  820 <__isoc99_sscanf@plt>
 c7a:	48 83 c4 10          	add    $0x10,%rsp
 c7e:	83 f8 05             	cmp    $0x5,%eax
 c81:	7e 05                	jle    c88 <read_six_numbers+0x3c>
 c83:	48 83 c4 08          	add    $0x8,%rsp
 c87:	c3                   	retq   
 c88:	b8 00 00 00 00       	mov    $0x0,%eax
 c8d:	e8 fa fe ff ff       	callq  b8c <explode_bomb>

分析代码:

read_numbers中主要是调用了__isoc99_sscanf@plt函数,这个函数接受了8个参数。
eax放返回值,rsi放字符串格式,rdx中放了phase_2栈帧的rsp的地址也就是第一个数要放的地址,rcx放第二个数的地址,r8放第三个数的地址,r9放第四个数的地址,超过六个参数要通过栈来传递参数,所以可以看到c5f和c60这两句是把第五个数的地址和第六个数的地址压到栈中了。所以我们输入的六个数存放在rsp到rsp+0x18这个空间内。这也是后面返回phase_2函数的时候为什么可以用rsp+4*rax来遍历我们输入的数组的原因。

phase_3

0000000000000d21 <phase_3>:
 d21:	48 83 ec 18          	sub    $0x18,%rsp
 d25:	64 48 8b 04 25 28 00 	mov    %fs:0x28,%rax
 d2c:	00 00 
 d2e:	48 89 44 24 08       	mov    %rax,0x8(%rsp)
 d33:	31 c0                	xor    %eax,%eax
 d35:	48 8d 4c 24 04       	lea    0x4(%rsp),%rcx
 d3a:	48 89 e2             	mov    %rsp,%rdx
 d3d:	48 8d 35 ff 12 20 00 	lea    0x2012ff(%rip),%rsi        # 202043 <fmt_phase_3>
 d44:	e8 d7 fa ff ff       	callq  820 <__isoc99_sscanf@plt>
 d49:	83 f8 01             	cmp    $0x1,%eax
 d4c:	7e 29                	jle    d77 <phase_3+0x56>
 d4e:	83 3c 24 00          	cmpl   $0x0,(%rsp)
 d52:	78 2f                	js     d83 <phase_3+0x62>
 d54:	83 3c 24 07          	cmpl   $0x7,(%rsp)
 d58:	7f 35                	jg     d8f <phase_3+0x6e>
 d5a:	83 3c 24 07          	cmpl   $0x7,(%rsp)
 d5e:	0f 87 0c 01 00 00    	ja     e70 <phase_3+0x14f>
 d64:	8b 04 24             	mov    (%rsp),%eax
 d67:	48 8d 15 22 04 00 00 	lea    0x422(%rip),%rdx        # 1190 <_IO_stdin_used+0x1e0>
 d6e:	48 63 04 82          	movslq (%rdx,%rax,4),%rax
 d72:	48 01 d0             	add    %rdx,%rax
 d75:	ff e0                	jmpq   *%rax
 d77:	b8 00 00 00 00       	mov    $0x0,%eax
 d7c:	e8 0b fe ff ff       	callq  b8c <explode_bomb>
 d81:	eb cb                	jmp    d4e <phase_3+0x2d>
 d83:	b8 00 00 00 00       	mov    $0x0,%eax
 d88:	e8 ff fd ff ff       	callq  b8c <explode_bomb>
 d8d:	eb c5                	jmp    d54 <phase_3+0x33>
 d8f:	b8 00 00 00 00       	mov    $0x0,%eax
 d94:	e8 f3 fd ff ff       	callq  b8c <explode_bomb>
 d99:	eb bf                	jmp    d5a <phase_3+0x39>
 d9b:	8b 44 24 04          	mov    0x4(%rsp),%eax
 d9f:	39 05 bb 12 20 00    	cmp    %eax,0x2012bb(%rip)        # 202060 <magic_num_p3>
 da5:	0f 84 cf 00 00 00    	je     e7a <phase_3+0x159>
 dab:	b8 00 00 00 00       	mov    $0x0,%eax
 db0:	e8 d7 fd ff ff       	callq  b8c <explode_bomb>
 db5:	e9 c0 00 00 00       	jmpq   e7a <phase_3+0x159>
 dba:	8b 44 24 04          	mov    0x4(%rsp),%eax
 dbe:	39 05 a0 12 20 00    	cmp    %eax,0x2012a0(%rip)        # 202064 <magic_num_p3+0x4>
 dc4:	0f 84 b0 00 00 00    	je     e7a <phase_3+0x159>
 dca:	b8 00 00 00 00       	mov    $0x0,%eax
 dcf:	e8 b8 fd ff ff       	callq  b8c <explode_bomb>
 dd4:	e9 a1 00 00 00       	jmpq   e7a <phase_3+0x159>
 dd9:	8b 44 24 04          	mov    0x4(%rsp),%eax
 ddd:	39 05 85 12 20 00    	cmp    %eax,0x201285(%rip)        # 202068 <magic_num_p3+0x8>
 de3:	0f 84 91 00 00 00    	je     e7a <phase_3+0x159>
 de9:	b8 00 00 00 00       	mov    $0x0,%eax
 dee:	e8 99 fd ff ff       	callq  b8c <explode_bomb>
 df3:	e9 82 00 00 00       	jmpq   e7a <phase_3+0x159>
 df8:	8b 44 24 04          	mov    0x4(%rsp),%eax
 dfc:	39 05 6a 12 20 00    	cmp    %eax,0x20126a(%rip)        # 20206c <magic_num_p3+0xc>
 e02:	74 76                	je     e7a <phase_3+0x159>
 e04:	b8 00 00 00 00       	mov    $0x0,%eax
 e09:	e8 7e fd ff ff       	callq  b8c <explode_bomb>
 e0e:	eb 6a                	jmp    e7a <phase_3+0x159>
 e10:	8b 44 24 04          	mov    0x4(%rsp),%eax
 e14:	39 05 56 12 20 00    	cmp    %eax,0x201256(%rip)        # 202070 <magic_num_p3+0x10>
 e1a:	74 5e                	je     e7a <phase_3+0x159>
 e1c:	b8 00 00 00 00       	mov    $0x0,%eax
 e21:	e8 66 fd ff ff       	callq  b8c <explode_bomb>
 e26:	eb 52                	jmp    e7a <phase_3+0x159>
 e28:	8b 44 24 04          	mov    0x4(%rsp),%eax
 e2c:	39 05 42 12 20 00    	cmp    %eax,0x201242(%rip)        # 202074 <magic_num_p3+0x14>
 e32:	74 46                	je     e7a <phase_3+0x159>
 e34:	b8 00 00 00 00       	mov    $0x0,%eax
 e39:	e8 4e fd ff ff       	callq  b8c <explode_bomb>
 e3e:	eb 3a                	jmp    e7a <phase_3+0x159>
 e40:	8b 44 24 04          	mov    0x4(%rsp),%eax
 e44:	39 05 2e 12 20 00    	cmp    %eax,0x20122e(%rip)        # 202078 <magic_num_p3+0x18>
 e4a:	74 2e                	je     e7a <phase_3+0x159>
 e4c:	b8 00 00 00 00       	mov    $0x0,%eax
 e51:	e8 36 fd ff ff       	callq  b8c <explode_bomb>
 e56:	eb 22                	jmp    e7a <phase_3+0x159>
 e58:	8b 44 24 04          	mov    0x4(%rsp),%eax
 e5c:	39 05 1a 12 20 00    	cmp    %eax,0x20121a(%rip)        # 20207c <magic_num_p3+0x1c>
 e62:	74 16                	je     e7a <phase_3+0x159>
 e64:	b8 00 00 00 00       	mov    $0x0,%eax
 e69:	e8 1e fd ff ff       	callq  b8c <explode_bomb>
 e6e:	eb 0a                	jmp    e7a <phase_3+0x159>
 e70:	b8 00 00 00 00       	mov    $0x0,%eax
 e75:	e8 12 fd ff ff       	callq  b8c <explode_bomb>
 e7a:	48 8b 44 24 08       	mov    0x8(%rsp),%rax
 e7f:	64 48 33 04 25 28 00 	xor    %fs:0x28,%rax
 e86:	00 00 
 e88:	75 05                	jne    e8f <phase_3+0x16e>
 e8a:	48 83 c4 18          	add    $0x18,%rsp
 e8e:	c3                   	retq   
 e8f:	e8 3c f9 ff ff       	callq  7d0 <__stack_chk_fail@plt>
  • d44调用了__isoc99_sscanf@plt函数,参数存在rsi,rdx,rcx,rax中,rsi放的是输入的两个数的格式,rdx放输入的第一个数要存的地址,rcx放输入的第二个数存的地址。rax放返回值,也就是正确匹配的个数。
  • d49: 检查你输入的数的个数是否大于1,不大于1的话就爆炸。
  • d4e-d5e: 检查第一个数的大小是否在0到7这个范围内。不在这个范围内就爆炸。
  • d64-d75: 根据生成的第一个数生成一个地址并跳转。
  • d9b:输入的第一个数为0的话就跳转到这个地址,此时将输入的第二个数放到eax中,然后和0x2012bb(%rip)中的数作比较,若不相等则爆炸,所以我们只需要打印出此时这个地址中的数即可知道正确答案。

找答案
这是当第一个数为0的情况,其他7个数找答案的方法同理。

phase_4

这一关并不复杂,主要是理解递归算法的思想。

0000000000000ec2 <phase_4>:
 ec2:	48 83 ec 18          	sub    $0x18,%rsp
 ec6:	64 48 8b 04 25 28 00 	mov    %fs:0x28,%rax
 ecd:	00 00 
 ecf:	48 89 44 24 08       	mov    %rax,0x8(%rsp)
 ed4:	31 c0                	xor    %eax,%eax
 ed6:	48 8d 54 24 04       	lea    0x4(%rsp),%rdx
 edb:	48 8d 35 5e 11 20 00 	lea    0x20115e(%rip),%rsi        # 202040 <fmt_phase_4>
 ee2:	e8 39 f9 ff ff       	callq  820 <__isoc99_sscanf@plt>
 ee7:	83 f8 01             	cmp    $0x1,%eax
 eea:	75 07                	jne    ef3 <phase_4+0x31>
 eec:	83 7c 24 04 00       	cmpl   $0x0,0x4(%rsp)
 ef1:	7f 0a                	jg     efd <phase_4+0x3b>
 ef3:	b8 00 00 00 00       	mov    $0x0,%eax
 ef8:	e8 8f fc ff ff       	callq  b8c <explode_bomb>
 efd:	8b 7c 24 04          	mov    0x4(%rsp),%edi
 f01:	e8 8e ff ff ff       	callq  e94 <func4>
 f06:	83 f8 37             	cmp    $0x37,%eax
 f09:	74 0a                	je     f15 <phase_4+0x53>
 f0b:	b8 00 00 00 00       	mov    $0x0,%eax
 f10:	e8 77 fc ff ff       	callq  b8c <explode_bomb>
 f15:	48 8b 44 24 08       	mov    0x8(%rsp),%rax
 f1a:	64 48 33 04 25 28 00 	xor    %fs:0x28,%rax
 f21:	00 00 
 f23:	75 05                	jne    f2a <phase_4+0x68>
 f25:	48 83 c4 18          	add    $0x18,%rsp
 f29:	c3                   	retq   
 f2a:	e8 a1 f8 ff ff       	callq  7d0 <__stack_chk_fail@plt>
 f2f:	90                   	nop

可以看到ee2行调用了__isoc99_sscanf@plt函数,传了eax,rsp+4和rsi三个参数,此时打印rsi存放的地址中的内容就可以知道我们要输入的内容的格式。

格式
可以看到我们只需输入一个十进制整数。

  • efd:把输入的数放入edi,作为func4的参数
  • f06:比较func4的返回值和0x37的关系,如果相等的话函数正常结束,否则就爆炸。所以只需使fun4的返回值为十进制的55即可。

进入func4函数中:

0000000000000e94 <func4>:
 e94:	83 ff 01             	cmp    $0x1,%edi
 e97:	7f 06                	jg     e9f <func4+0xb>
 e99:	b8 01 00 00 00       	mov    $0x1,%eax
 e9e:	c3                   	retq   
 e9f:	55                   	push   %rbp
 ea0:	53                   	push   %rbx
 ea1:	48 83 ec 08          	sub    $0x8,%rsp
 ea5:	89 fb                	mov    %edi,%ebx
 ea7:	8d 7f ff             	lea    -0x1(%rdi),%edi
 eaa:	e8 e5 ff ff ff       	callq  e94 <func4>
 eaf:	89 c5                	mov    %eax,%ebp
 eb1:	8d 7b fe             	lea    -0x2(%rbx),%edi
 eb4:	e8 db ff ff ff       	callq  e94 <func4>
 eb9:	01 e8                	add    %ebp,%eax
 ebb:	48 83 c4 08          	add    $0x8,%rsp
 ebf:	5b                   	pop    %rbx
 ec0:	5d                   	pop    %rbp
 ec1:	c3                   	retq  

edi<=1时,返回1,否则返回执行func(n-1)+fun(n-2),所以这是一个满足斐波那契数列的关系式(1,1,2,3,5,8,13,21,34,55)。即:

def func:
	if n <= 1:
		return 1
	else:
		return func(n-1)+func(n-2)

当n==9时,恰好返回55.故答案为9

总结与反思

其实完成这个实验,找到正确答案并不难,更重要的是实验过程中对代码的理解,以及对函数调用中栈帧变化的理解,能够更深入地认识代码执行的过程,理解背后的原理,对于自己能力还是有一定锻炼的。
此外,还通过本次实验掌握了wsl的使用及gdb的调试,收获很大。

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
C#中的二进制序列化是将对象转换为二进制数据的过程,以便在存储或传输时能够保留对象的状态。深入理解二进制序列化可以帮助我们更好地掌握其工作原理和灵活运用。 在C#中,可以使用`BinaryFormatter`类来进行二进制序列化。首先,需要将要序列化的对象标记为可序列化,可以通过在类定义前加上`[Serializable]`特性来实现。然后,可以使用`BinaryFormatter`的`Serialize`方法将对象序列化为二进制数据。 以下是一个简单的示例: ```csharp [Serializable] class MyClass { public int MyProperty { get; set; } public string MyField; } class Program { static void Main() { MyClass obj = new MyClass(); obj.MyProperty = 42; obj.MyField = "Hello, world!"; BinaryFormatter formatter = new BinaryFormatter(); using (FileStream stream = File.Create("data.bin")) { formatter.Serialize(stream, obj); } } } ``` 上述代码将一个`MyClass`对象序列化为名为"data.bin"的二进制文件。 要理解二进制序列化的工作原理,可以了解以下几点: 1. 序列化过程会将对象的字段和属性转换为字节流,并将其写入流中。反序列化时则会将字节流读取并转换回对象的字段和属性。 2. `BinaryFormatter`会自动处理对象图中的引用关系,确保在序列化和反序列化过程中能够正确还原对象之间的引用关系。 3. 对于自定义类型,需要确保所有要序列化的字段和属性都是可序列化的。非可序列化的字段和属性可以通过标记为`[NonSerialized]`特性来排除。 4. 序列化过程中可能会遇到无法序列化的类型或对象,可以通过实现`ISerializable`接口来自定义序列化和反序列化过程。 5. 序列化是一种用于持久化对象状态或进行远程通信的常见技术,但需要注意安全性和性能等方面的考虑。 希望这些信息能够帮助你更深入地理解C#二进制序列化的概念和使用方法。如果有任何进一步的问题,请随时提问!

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值