ChCore_lab0 做题笔记

简介

​ 在实验 0 中,你需要通过阅读汇编代码以及使用调试工具来拆除一个 二进制炸弹程序。本实验分为两个部分:第一部分介绍拆弹实验的基本知 识,包括 ARM 汇编语言、QEMU 模拟器、GDB 调试器的使用;第二部分 需要分析炸弹程序,推测正确的输入来使得炸弹程序能正常退出。

文档链接:Lab0:拆炸弹 - IPADS OS Course Lab Manual

军火展示(炸弹总览

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
#include <stdio.h>
#include "phases.h"
#include "utils.h"

int main() {
  char* input;
  printf("Type in your defuse password!\n");

  input = read_line();
  phase_0(input);
  phase_defused();

  input = read_line();
  phase_1(input);
  phase_defused();

  input = read_line();
  phase_2(input);
  phase_defused();

  input = read_line();
  phase_3(input);
  phase_defused();

  input = read_line();
  phase_4(input);
  phase_defused();

  input = read_line();
  phase_5(input);
  phase_defused();

  printf("Congrats! You have defused all phases!\n");
  return 0;
}

phase_0

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
0000000000400734 <phase_0>:
  400734:	a9bf7bfd 	stp	x29, x30, [sp, #-16]!
  400738:	910003fd 	mov	x29, sp
  40073c:	94000126 	bl	400bd4 <read_int> // 调用read_int函数读入一个整形
  400740:	90000501 	adrp	x1, 4a0000 <.got.plt+0x18>
  400744:	b9405421 	ldr	w1, [x1, #84] //获取0x4a0000+84中的值
  400748:	6b00003f 	cmp	w1, w0	//比较read_int的返回值与内存中获取的值
  40074c:	54000061 	b.ne	400758 <phase_0+0x24>  //两者不相等就爆炸
  400750:	a8c17bfd 	ldp	x29, x30, [sp], #16
  400754:	d65f03c0 	ret
  400758:	940000e7 	bl	400af4 <explode>
  40075c:	17fffffd 	b	400750 <phase_0+0x1c>

x0存的是我们输入的字符串,w0中存着read_int返回值,通过gdb的打印,我们可以知道答案为2022

phase_1

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
0000000000400760 <phase_1>:
  400760:	a9bf7bfd 	stp	x29, x30, [sp, #-16]!
  400764:	910003fd 	mov	x29, sp
  400768:	90000501 	adrp	x1, 4a0000 <.got.plt+0x18>
  40076c:	f9402c21 	ldr	x1, [x1, #88]  // 获取0x4a0000+88中的字符串
  400770:	94008504 	bl	421b80 <strcmp>  // 比较输入与获取的字符串
  400774:	35000060 	cbnz	w0, 400780 <phase_1+0x20>  // 不相等就爆炸
  400778:	a8c17bfd 	ldp	x29, x30, [sp], #16
  40077c:	d65f03c0 	ret
  400780:	940000dd 	bl	400af4 <explode>
  400784:	17fffffd 	b	400778 <phase_1+0x18>

x1中存储的是字符串的起始地址,所以在gdb打印的时候需要再次解引用

答案:The Network as a System and as a System Component.

phase_2

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
0000000000400788 <phase_2>:
  400788:	a9bc7bfd 	stp	x29, x30, [sp, #-64]!
  40078c:	910003fd 	mov	x29, sp
  400790:	a90153f3 	stp	x19, x20, [sp, #16]
  400794:	910083e1 	add	x1, sp, #0x20 //数字1的地址为sp+0x20
  400798:	940000f9 	bl	400b7c <read_8_numbers> //读八个数字
  40079c:	b94023e0 	ldr	w0, [sp, #32] // 获取数字1
  4007a0:	7100041f 	cmp	w0, #0x1  // 比较数字1和1
  4007a4:	54000081 	b.ne	4007b4 <phase_2+0x2c>  //数字1不等于1就爆炸
  4007a8:	b94027e0 	ldr	w0, [sp, #36] // 获取数字2
  4007ac:	7100041f 	cmp	w0, #0x1 // 比较数字2和1
  4007b0:	54000040 	b.eq	4007b8 <phase_2+0x30>  //数字2不等于1就爆炸
  4007b4:	940000d0 	bl	400af4 <explode>
  4007b8:	910083f3 	add	x19, sp, #0x20 // x19为数字1地址
  4007bc:	9100e3f4 	add	x20, sp, #0x38 // x20为数字8地址
  4007c0:	14000004 	b	4007d0 <phase_2+0x48>
  /* 以下代码的c语言大致为
  for(int i = 2; i < 8; i++){
	if (num[i] != num[i-1]+num[i-2]+3) explode();
  }
  */			
  4007c4:	91001273 	add	x19, x19, #0x4
  4007c8:	eb14027f 	cmp	x19, x20
  4007cc:	54000140 	b.eq	4007f4 <phase_2+0x6c>  // b.none
  4007d0:	b9400260 	ldr	w0, [x19] 
  4007d4:	b9400661 	ldr	w1, [x19, #4]
  4007d8:	0b010000 	add	w0, w0, w1
  4007dc:	11000c00 	add	w0, w0, #0x3
  4007e0:	b9400a61 	ldr	w1, [x19, #8]
  4007e4:	6b00003f 	cmp	w1, w0
  4007e8:	54fffee0 	b.eq	4007c4 <phase_2+0x3c>  // b.none
      
  4007ec:	940000c2 	bl	400af4 <explode>
  4007f0:	17fffff5 	b	4007c4 <phase_2+0x3c>
  4007f4:	a94153f3 	ldp	x19, x20, [sp, #16]
  4007f8:	a8c47bfd 	ldp	x29, x30, [sp], #64
  4007fc:	d65f03c0 	ret

根据注释中的讲解,我们很容易就推出答案:1 1 5 9 17 29 49 81

phase_3

这是一道多解题,这里只展示第一种解

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
0000000000400800 <phase_3>:
  400800:	a9be7bfd 	stp	x29, x30, [sp, #-32]!
  400804:	910003fd 	mov	x29, sp
  400808:	910063e3 	add	x3, sp, #0x18 // 数字2在sp+24中
  40080c:	910073e2 	add	x2, sp, #0x1c // 数字1在sp+28中
  400810:	90000321 	adrp	x1, 464000 <free_mem+0x40>
  400814:	911f6021 	add	x1, x1, #0x7d8
  400818:	9400195a 	bl	406d80 <__isoc99_sscanf> // 读取两个数字
  40081c:	7100081f 	cmp	w0, #0x2	
  400820:	54000161 	b.ne	40084c <phase_3+0x4c>  // 如果数字个数不等于2就爆炸
  400824:	b9401fe0 	ldr	w0, [sp, #28]
  400828:	71000c1f 	cmp	w0, #0x3	
  40082c:	540002c0 	b.eq	400884 <phase_3+0x84>  // 数字1为3则跳转到0x400884处
  400830:	7100181f 	cmp	w0, #0x6
  400834:	54000100 	b.eq	400854 <phase_3+0x54>  // b.none
  400838:	7100081f 	cmp	w0, #0x2
  40083c:	54000320 	b.eq	4008a0 <phase_3+0xa0>  // b.none
  400840:	940000ad 	bl	400af4 <explode>
  400844:	a8c27bfd 	ldp	x29, x30, [sp], #32
  400848:	d65f03c0 	ret
  40084c:	940000aa 	bl	400af4 <explode>
  400850:	17fffff5 	b	400824 <phase_3+0x24>
  400854:	b9401be2 	ldr	w2, [sp, #24]
  400858:	528ccce0 	mov	w0, #0x6667                	// #26215
  40085c:	72acccc0 	movk	w0, #0x6666, lsl #16
  400860:	9b207c40 	smull	x0, w2, w0
  400864:	9362fc00 	asr	x0, x0, #34
  400868:	4b827c00 	sub	w0, w0, w2, asr #31
  40086c:	0b000801 	add	w1, w0, w0, lsl #2
  400870:	4b010441 	sub	w1, w2, w1, lsl #1
  400874:	0b000020 	add	w0, w1, w0
  400878:	7100181f 	cmp	w0, #0x6
  40087c:	54fffe40 	b.eq	400844 <phase_3+0x44>  // b.none
  400880:	9400009d 	bl	400af4 <explode>
  400884:	b9401be0 	ldr	w0, [sp, #24] //读取数字2
  400888:	4a800c00 	eor	w0, w0, w0, asr #3
  40088c:	12000800 	and	w0, w0, #0x7 // 设数字2为x2,此时w0 = (x2 >> 3) & 7
  400890:	b9401fe1 	ldr	w1, [sp, #28] //读取数字1
  400894:	6b01001f 	cmp	w0, w1 
  400898:	54fffd60 	b.eq	400844 <phase_3+0x44> // 数字1等于w0则返回
  40089c:	94000096 	bl	400af4 <explode>
  4008a0:	b9401be0 	ldr	w0, [sp, #24]
  4008a4:	b9401fe1 	ldr	w1, [sp, #28]
  4008a8:	12000802 	and	w2, w0, #0x7
  4008ac:	6b01005f 	cmp	w2, w1
  4008b0:	54fffca0 	b.eq	400844 <phase_3+0x44>  // b.none
  4008b4:	d3431400 	ubfx	x0, x0, #3, #3
  4008b8:	6b00003f 	cmp	w1, w0
  4008bc:	54fffc40 	b.eq	400844 <phase_3+0x44>  // b.none
  4008c0:	9400008d 	bl	400af4 <explode>
  4008c4:	17ffffdf 	b	400840 <phase_3+0x40>

输入两个数x1,x2,若 x1 为 3,则 x1 == (x2 » 3) & 7

答案:3 3 or 3 24 …(符合上述规则都对)

phase_4

1
2
3
4
5
400300:	90000510 	adrp	x16, 4a0000 <.got.plt+0x18>
400304:	f9401a11 	ldr	x17, [x16, #48]	// 将0x4a0030所存的函数地址放到x17中
400308:	9100c210 	add	x16, x16, #0x30 
40030c:	d61f0220 	br	x17  // 跳转到该函数执行
// 通过gdb打印的地址,我们可以查询到函数的名字是 strlen
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
00000000004009e4 <phase_4>:
  4009e4:	a9be7bfd 	stp	x29, x30, [sp, #-32]!
  4009e8:	910003fd 	mov	x29, sp
  4009ec:	a90153f3 	stp	x19, x20, [sp, #16]
  4009f0:	aa0003f3 	mov	x19, x0  // 将输入复制一份到x19
  4009f4:	97fffe43 	bl	400300 <.plt+0x60>  // 跳转到外部链接(400300代码在上面)
  4009f8:	aa0003f4 	mov	x20, x0 // 将输入复制一份到x20
  4009fc:	7100281f 	cmp	w0, #0xa 
  400a00:	540001ec 	b.gt	400a3c <phase_4+0x58> // 如果返回值大于10就爆炸
  400a04:	2a1403e1 	mov	w1, w20
  400a08:	aa1303e0 	mov	x0, x19
  400a0c:	97ffffaf 	bl	4008c8 <encrypt_method1>  // 加密函数1
  400a10:	2a1403e1 	mov	w1, w20
  400a14:	aa1303e0 	mov	x0, x19
  400a18:	97ffffd3 	bl	400964 <encrypt_method2>  // 加密函数2
  400a1c:	90000500 	adrp	x0, 4a0000 <.got.plt+0x18>
  400a20:	f9403401 	ldr	x1, [x0, #104]  // 获取0x4a0000+104处的值,是一个字符串
  400a24:	aa1303e0 	mov	x0, x19
  400a28:	94008456 	bl	421b80 <strcmp>  // 调用strcmp函数比较
      					// 如果加密后的字符串与获取的字符串不等就爆炸
  400a2c:	350000c0 	cbnz	w0, 400a44 <phase_4+0x60> 
  400a30:	a94153f3 	ldp	x19, x20, [sp, #16]
  400a34:	a8c27bfd 	ldp	x29, x30, [sp], #32
  400a38:	d65f03c0 	ret
  400a3c:	9400002e 	bl	400af4 <explode>
  400a40:	17fffff1 	b	400a04 <phase_4+0x20>
  400a44:	9400002c 	bl	400af4 <explode>
  400a48:	17fffffa 	b	400a30 <phase_4+0x4c>
  1. 这道题有点难,但我们可以很快知道需要输入一个长度不大于10的字符串,且我们也可以很快知道最终要比较的字符串为isggstsvkt

  2. 接下来是要知道两个加密函数做了些什么,然而通过看代码,我们其实很难看懂是怎么加密的,但是我们可以通过不断地输入来获取算法结果,最终得到算法的加密方式。

  3. 通过输入0123456789我们可以得到经过加密函数1之后的结果为0246813579,很显然这是将位置做了一下调换。然而这个输入在加密函数2中直接就爆炸了

  4. 结合最终字符串以及输入0123456789的结果,我们可以猜测函数2是一个字母表的加密算法,故而我们尝试输入abcdefghij,得到经过加密函数2的结果为qetuowryip,之后我们可以很容易的得到abcdefghij在加密表中对应qwertyuiop,将26个字母都输入了一遍之后,我们就可以知道应该输入的字符串为helloworle

phase_5

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
0000000000400a4c <func_5>:
// 通过分析这个函数,我们可以知道参数2应该为一个二叉树的根地址
  400a4c:	b4000361 	cbz	x1, 400ab8 <func_5+0x6c> // 如果该节点为空就返回0
  400a50:	a9be7bfd 	stp	x29, x30, [sp, #-32]!
  400a54:	910003fd 	mov	x29, sp
  400a58:	a90153f3 	stp	x19, x20, [sp, #16]
  400a5c:	2a0003f4 	mov	w20, w0
  400a60:	aa0103f3 	mov	x19, x1
  400a64:	b9400020 	ldr	w0, [x1]
  400a68:	6b14001f 	cmp	w0, w20
  400a6c:	54000160 	b.eq	400a98 <func_5+0x4c>  // b.none
  400a70:	b9400260 	ldr	w0, [x19]
  400a74:	6b14001f 	cmp	w0, w20
  400a78:	5400014d 	b.le	400aa0 <func_5+0x54>
  400a7c:	f9400661 	ldr	x1, [x19, #8]
  400a80:	2a1403e0 	mov	w0, w20
  400a84:	97fffff2 	bl	400a4c <func_5>
  400a88:	531f7800 	lsl	w0, w0, #1
  400a8c:	a94153f3 	ldp	x19, x20, [sp, #16]
  400a90:	a8c27bfd 	ldp	x29, x30, [sp], #32
  400a94:	d65f03c0 	ret
  400a98:	94000017 	bl	400af4 <explode>
  400a9c:	17fffff5 	b	400a70 <func_5+0x24>
  400aa0:	f9400a61 	ldr	x1, [x19, #16]
  400aa4:	2a1403e0 	mov	w0, w20
  400aa8:	97ffffe9 	bl	400a4c <func_5>
  400aac:	531f7800 	lsl	w0, w0, #1
  400ab0:	11000400 	add	w0, w0, #0x1
  400ab4:	17fffff6 	b	400a8c <func_5+0x40>
  400ab8:	52800000 	mov	w0, #0x0                   	// #0
  400abc:	d65f03c0 	ret
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
0000000000400ac0 <phase_5>:
  400ac0:	a9bf7bfd 	stp	x29, x30, [sp, #-16]!
  400ac4:	910003fd 	mov	x29, sp
  400ac8:	94000043 	bl	400bd4 <read_int> // 读入一个整数
  400acc:	90000501 	adrp	x1, 4a0000 <.got.plt+0x18> 
  400ad0:	91016021 	add	x1, x1, #0x58
  400ad4:	91006021 	add	x1, x1, #0x18 
  400ad8:	97ffffdd 	bl	400a4c <func_5> // 调用一个函数,参数2为一个地址
  400adc:	71000c1f 	cmp	w0, #0x3 
  400ae0:	54000061 	b.ne	400aec <phase_5+0x2c>  //返回值不等于3就爆炸
  400ae4:	a8c17bfd 	ldp	x29, x30, [sp], #16
  400ae8:	d65f03c0 	ret
  400aec:	94000002 	bl	400af4 <explode>
  400af0:	17fffffd 	b	400ae4 <phase_5+0x24>

这道题也很难,但我们可以一力破万法,既然我们已经知道需要遍历一个二叉树,那我们一个个打印,可以知道二叉树的结构如下:

tree

如果你懒得看func_5的逻辑了,那么你可以跟我一样一个一个试,将所有二叉树插入节点的位置都试一遍,比如:0, 4, 21, 38, 50, 56, 89, 92,当我们试到89时就得到答案了。