7.6 KiB
输入命令 objdump -s -S -d -M att bomb > bomb.s
进行反汇编。
Phase 1
对 phase_1
进行逆向:
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
逆向工程循环的一些技巧:
- 框选出循环范围,将跳转指令的地址改成标号;
- 尝试移动整段代码,并修改相应的跳转指令;
- 最终使之符合任意一种通用策略。
在这里,我们将 400f0a
至 400f3a
部分整理如下:
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
基于此逆向得到:
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 语句。
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。
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
部分整理如下:
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:
基于此逆向得到:
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
部分整理如下:
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
开头的一段内存的读写操作,我们还需研究其中数据的组织方式,例如:
6032d0 4c010000 01000000 e0326000 00000000 L........2`.....
结合汇编代码,不难猜到这块区域依次存储了两个 4 字整数和一个指针,组成一个结构。我们给出它的声明:
struct chain_node {
int val;
int id;
struct chain_node *next;
};
综合以上,逆向得到:
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
。对这两个函数逆向,大致如下:
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
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
。