CSAPP-sol/labs/bomb/README.md

353 lines
7.6 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# bomblab
输入命令 `objdump -s -S -d -M att bomb > bomb.s` 进行反汇编。
## Phase 1
`phase_1` 进行逆向:
```c
void phase_1(char str[]) {
if (strings_not_equal(str, "Border relations with Canada have never been better.") != 0) {
explode_bomb();
}
}
```
`explode_bomb` 函数将炸弹引爆,我们不希望它被调用;而 `strings_not_equal` 接受两个字符串作为参数,在两者不相等时返回 `1`,否则返回 `0`
显然,输入应为 `Border relations with Canada have never been better.`
## Phase 2
这个 phase 的难点在于跳转指令互相交错,相当混乱。为此,我们通过调整指令的顺序并相应改变的跳转指令,使代码符合循环的一般模式。
在这里,我们将 `400f0a``400f3a` 部分整理如下:
```esm
cmpl $0x1,(%rsp)
je .L1
call explode_bomb
.L1:
lea 0x4(%rsp),%rbx
lea 0x18(%rsp),%rbp
.L2:
mov -0x4(%rbx),%eax
add %eax,%eax
cmp %eax,(%rbx)
je .L3
call explode_bomb
.L3:
add $0x4,%rbx
cmp %rbp,%rbx
jne .L2
```
基于此逆向得到:
```c
void phase_2(char str[]) {
int x[6];
read_six_numbers(x);
if (x[0] != 1) {
explode_bomb();
} else {
for (int i = 1; i < 6; i++) {
if (x[i] != x[i - 1] * 2) {
explode_bomb();
}
}
}
}
```
`read_six_numbers(int x[])` 读入 6 个整数,并依次存贮到 `x[0]``x[5]`
由此可知,输入应为 `1 2 4 8 16 32`
## Phase 3
这个 phase 包含一个 switch 语句。
```c
void phase_3(char str[]) {
int x, y;
if (sscanf(str, "%d %d", &x, &y) <= 1) {
explode_bomb();
}
if (x > 7 || x < 0) {
explode_bomb();
}
int z;
switch (x) {
case 0:
z = 0xcf;
break;
case 2:
z = 0x2c3;
break;
case 3:
z = 0x100;
break;
case 4:
z = 0x185;
break;
case 5:
z = 0xce;
break;
case 6:
z = 0x2aa;
break;
case 7:
z = 0x147;
break;
default:
z = 0x137;
}
if (y != z) {
explode_bomb();
}
}
```
由此可知,输入的第 1 个数必须在 0 到 7 之间,第二个数与之相应即可。
## Phase 4
`400fd6``400fdd` 部分使用了原书 2.3.7 中提到的方法实现除以 2。
```c
int func4(int x, int y, int z) {
int mid = y + (z - y) / 2;
if (mid <= x) {
if (mid >= x) {
return 0;
} else {
return 2 * func4(x, mid + 1, z) + 1;
}
} else {
return 2 * func4(x, y, mid - 1);
}
}
void phase_4(char str[]) {
int x, y;
if (sscanf(str, "%d %d", &x, &y) != 2) {
explode_bomb();
}
if (x > 15 || x < 0) {
explode_bomb();
}
if (func4(x, 0, 15) != 0 || y != 0) {
explode_bomb();
}
}
```
结合线段树知识,输入的第 1 个数可以是 `0``1``3``7`,第二个数是 `0`
## Phase 5
`40107f``4010c6` 部分整理如下:
```esm
cmp $0x6,%eax
je .L1
call explode_bomb
.L1:
mov $0x0,%eax
.L2:
movzbl (%rbx,%rax,1),%ecx
mov %cl,(%rsp)
mov (%rsp),%rdx
and $0xf,%edx
movzbl 0x4024b0(%rdx),%edx
mov %dl,0x10(%rsp,%rax,1)
add $0x1,%rax
cmp $0x6,%rax
jne .L2
movb $0x0,0x16(%rsp)
mov $0x40245e,%esi
lea 0x10(%rsp),%rdi
call strings_not_equal
test %eax,%eax
je .L3
call explode_bomb
.L3:
```
基于此逆向得到:
```c
void phase_5(char str[]) {
if (string_length(str) != 6) {
explode_bomb();
}
char str2[7];
for (int i = 0; i < 6; i++) {
str2[i] = "maduiersnfotvbyl"[str[i] & 0xF];
}
str2[6] = 0;
if (strings_not_equal(str2, "flyers") != 0) {
explode_bomb();
}
}
```
一个可行的输入是 `9?>567`
## Phase 6
`401176``4011a9` 部分整理如下:
```esm
mov $0x0,%esi
.L1:
mov $0x6032d0,%edx
mov (%rsp,%rsi,1),%ecx
cmp $0x1,%ecx
jle .end
mov $0x1,%eax
.L2:
mov 0x8(%rdx),%rdx
add $0x1,%eax
cmp %ecx,%eax
jne .L2
.end:
mov %rdx,0x20(%rsp,%rsi,2)
add $0x4,%rsi
cmp $0x18,%rsi
jne .L1
```
为了理解代码对以 `0x6032d0` 开头的一段内存的读取与写入,我们还需研究其中数据的组织方式,例如:
```txt
6032d0 4c010000 01000000 e0326000 00000000 L........2`.....
```
结合汇编代码,不难猜到这块区域依次存储了两个 `int` 和一个指针,组成一个结构。我们给出它的声明:
```c
struct chain_node {
int val;
int id;
struct chain_node *next;
};
```
综合以上,逆向得到:
```c
struct chain_node {
int val;
int id;
struct chain_node *next;
} c[6] = {{0x014c, 1, &c[1]},
{0x00a8, 2, &c[2]},
{0x039c, 3, &c[3]},
{0x02b3, 4, &c[4]},
{0x01dd, 5, &c[5]},
{0x01bb, 6}};
void phase_6(char str[]) {
int x[6];
read_six_numbers(x);
for (int i = 0; ; i++) {
if (x[i] <= 0 || x[i] > 6) {
explode_bomb();
}
if (i == 5) {
break;
}
for (int j = i + 1; j < 6; j++) {
if (x[i] == x[j]) {
explode_bomb();
}
}
}
for (int i = 0; i < 6; i++) {
x[i] = 7 - x[i];
}
struct chain_node *y[6];
for (int i = 0; i < 6; i++) {
chain_node *p = c[0];
for (int j = 1; j < x[i]; j++) {
p = p->next;
}
y[i] = p;
}
for (int i = 1; i < 6; i++) {
y[i - 1]->next = y[i];
}
y[5]->next = NULL;
chain_node *p = y[0];
for (int i = 5; i > 0; i--) {
if (p->val < p->next->val) {
explode_bomb();
}
p = p->next;
}
}
```
结合链表知识,输入应为 `4 3 2 1 6 5`
## 进入 Secret Phase
唯一对 `secret_phase` 的调用位于 `phase_defused` 中。观察 `phase_defused`,在 `num_input_strings`(每次调用 `read_line` 都会使它加 1等于 6即完成 Phase 6 后的调用时,该函数从以 `0x603870` 开头的字符串中先后提取了两个整数和一个字符串,并检查提取的字符串是否与 `DrEvil` 相等,若相等,则调用 `secret_phase`
`0x603870` 这个地址并没有在其他任何地方出现过,但是在 `skip``read_line` 中,出现了地址 `0x603780`。对这两个函数逆向,大致如下:
```c
char input[MAXLEN]; // 0x603780
int num_input_strings = 0;
FILE *infile;
int skip() {
fgets(input + 90 * num_input_strings, 90, infile);
...
}
char* read_line() {
...
char *start = input + 90 * num_input_strings;
...
num_input_strings++;
...
return start;
}
```
由此可知,以 `0x603870` 开头的字符串就是 phase 4 时输入的字符串,而在 phase 4 中恰好要输入两个整数,在它们之后再输入 `MrEvil`,我们就能够进入 Secret Phase。
## Secret Phase
```c
struct tree_node {
int val;
struct tree_node *ls, *rs;
long place_holder;
} t[15] = {{0x024, &t[1], &t[2]},
{0x008, &t[5], &t[3]},
{0x032, &t[4], &t[6]},
{0x016, &t[12], &t[10]},
{0x02d, &t[7], &t[13]},
{0x006, &t[8], &t[11]},
{0x06b, &t[9], &t[14]},
{0x028, NULL, NULL},
{0x001, NULL, NULL},
{0x063, NULL, NULL},
{0x023, NULL, NULL},
{0x007, NULL, NULL},
{0x014, NULL, NULL},
{0x02f, NULL, NULL},
{0x3e9, NULL, NULL}};
int fun7(struct tree_node *x, int y) {
if (x == NULL) {
return -1;
} else {
if (x->val <= y) {
if (x->val == y) {
return 0;
} else {
return 2 * fun7(x->rs, y) + 1;
}
} else {
return 2 * fun7(x->ls, y);
}
}
}
void secret_phase() {
long x = strtol(read_line(), NULL, 10);
if (x > 1001 || x < 1) {
explode_bomb();
}
if (fun7(t, x) != 2) {
explode_bomb();
}
...
}
```
结合二叉搜索树知识,输入应为 `22`