CSAPP Bomb Lab V.2016.01. 要求如下
/**
A "binary bomb" is a Linux executable C program that consists of six
"phases." Each phase expects the student to enter a particular string
on stdin. If the student enters the expected string, then that phase
is "defused." Otherwise the bomb "explodes" by printing "BOOM!!!".
The goal for the students is to defuse as many phases as possible.
*/
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
的字符串进行比较,若两字符串不相同,则引爆炸弹。使用GDB
查看地址为0x402400
的字符串为Border relations with Canada have never been better.
,即为要输入的字符串。
(gdb) x /s 0x402400
0x402400: "Border relations with Canada have never been better."
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>
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>
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>
400f2e: eb 0c jmp 400f3c <phase_2+0x40>
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>
400f3c: 48 83 c4 28 add $0x28,%rsp
400f40: 5b pop %rbx
400f41: 5d pop %rbp
400f42: c3 retq
本题需要先理解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
/*
* read six numbers from input,
* and store them in an arr
* */
void read_six_numbers(input, arr) {
int result =
sscanf(input, // rdi
0x4025c3, // rsi // "%d %d %d %d %d %d"
arr, // rdx
arr + 0x4, // rcx
arr + 0x8, // r8
arr + 0xc, // r9
arr + 0x10, // rsp
arr + 0x14); // rsp + 8
}
也就是接受一个字符串输入,使用sscanf
函数从该字符串中读入6个数字,把结果存放在地址为arr
的数组中。
phase_2
的翻译如下
void phase_2() {
read_six_numbers(input, rsp); // a,b,c,d,e,f
/**
* a = &(rsp)
* b = &(rsp + 0x4)
* c = &(rsp + 0x8)
* d = &(rsp + 0xc)
* e = &(rsp + 0x10)
* f = &(rsp + 0x14)
* */
if(a != 1) bomb(); // a == 1
else {
rbx = rsp + 4;
rbp = rsp + 18;
while(true) {
// b = 2, c = 4, d = 8, e = 16, f = 32
if (&rbx != (&(rbx - 4) * 2)) bomb();
else {
rbx += 4;
if(rbp == rbx) break;
}
}
}
}
很容易发现,输入的这6个数第一个是1
,其后的数依次为前者的两倍,故而输入的字符串为1 2 4 8 16 32
phase_3
0000000000400f43 <phase_3>:
400f43: 48 83 ec 18 sub $0x18,%rsp
400f47: 48 8d 4c 24 0c lea 0xc(%rsp),%rcx
400f4c: 48 8d 54 24 08 lea 0x8(%rsp),%rdx
400f51: be cf 25 40 00 mov $0x4025cf,%esi
400f56: b8 00 00 00 00 mov $0x0,%eax
400f5b: e8 90 fc ff ff callq 400bf0 <__isoc99_sscanf@plt>
400f60: 83 f8 01 cmp $0x1,%eax
400f63: 7f 05 jg 400f6a <phase_3+0x27>
400f65: e8 d0 04 00 00 callq 40143a <explode_bomb>
400f6a: 83 7c 24 08 07 cmpl $0x7,0x8(%rsp)
400f6f: 77 3c ja 400fad <phase_3+0x6a>
400f71: 8b 44 24 08 mov 0x8(%rsp),%eax
400f75: ff 24 c5 70 24 40 00 jmpq *0x402470(,%rax,8)
400f7c: b8 cf 00 00 00 mov $0xcf,%eax
400f81: eb 3b jmp 400fbe <phase_3+0x7b>
400f83: b8 c3 02 00 00 mov $0x2c3,%eax
400f88: eb 34 jmp 400fbe <phase_3+0x7b>
400f8a: b8 00 01 00 00 mov $0x100,%eax
400f8f: eb 2d jmp 400fbe <phase_3+0x7b>
400f91: b8 85 01 00 00 mov $0x185,%eax
400f96: eb 26 jmp 400fbe <phase_3+0x7b>
400f98: b8 ce 00 00 00 mov $0xce,%eax
400f9d: eb 1f jmp 400fbe <phase_3+0x7b>
400f9f: b8 aa 02 00 00 mov $0x2aa,%eax
400fa4: eb 18 jmp 400fbe <phase_3+0x7b>
400fa6: b8 47 01 00 00 mov $0x147,%eax
400fab: eb 11 jmp 400fbe <phase_3+0x7b>
400fad: e8 88 04 00 00 callq 40143a <explode_bomb>
400fb2: b8 00 00 00 00 mov $0x0,%eax
400fb7: eb 05 jmp 400fbe <phase_3+0x7b>
400fb9: b8 37 01 00 00 mov $0x137,%eax
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>
400fc9: 48 83 c4 18 add $0x18,%rsp
400fcd: c3 retq
这是一个关于switch
跳转表的题,翻译如下
eax = sscanf(input, 0x4025cf, rsp + 0x8, rsp + 0xc); // 0x4025cf : "%d %d"
if (eax <= 1) bomb();
else {
if( &(rsp + 0x8) > 0x7) {
bomb();
} else {
eax = &(rsp + 0x8);
/* jmp to &(0x402470 + 8 * %rax)
*
* Jump table:
*
* Address Value &(rsp + 0x8) => eax == &(rsp + 0xc)
* 0x402470 0x400f7c 0 0xcf
* 0x402478 0x400fb9 1 0x137
* 0x402480 0x400f83 2 0x2c3
* 0x402488 0x400f8a 3 0x100
* 0x402490 0x400f91 4 0x185
* 0x402498 0x400f98 5 0xce
* 0x4024a0 0x400f9f 6 0x2aa
* 0x4024a8 0x400fa6 7 0x147
*/
// then jump to 0x400fbe
if (eax != &(rsp + 0xc)) bomb()
}
}
故共有8种输入。
[(0, 207), (1, 311), (2, 707), (3, 256), (4, 389), (5, 206), (6, 682), (7, 327)]
phase_4
0000000000400fce <func4>:
400fce: 48 83 ec 08 sub $0x8,%rsp
400fd2: 89 d0 mov %edx,%eax
400fd4: 29 f0 sub %esi,%eax
400fd6: 89 c1 mov %eax,%ecx
400fd8: c1 e9 1f shr $0x1f,%ecx
400fdb: 01 c8 add %ecx,%eax
400fdd: d1 f8 sar %eax
400fdf: 8d 0c 30 lea (%rax,%rsi,1),%ecx
400fe2: 39 f9 cmp %edi,%ecx
400fe4: 7e 0c jle 400ff2 <func4+0x24>
400fe6: 8d 51 ff lea -0x1(%rcx),%edx
400fe9: e8 e0 ff ff ff callq 400fce <func4>
400fee: 01 c0 add %eax,%eax
400ff0: eb 15 jmp 401007 <func4+0x39>
400ff2: b8 00 00 00 00 mov $0x0,%eax
400ff7: 39 f9 cmp %edi,%ecx
400ff9: 7d 0c jge 401007 <func4+0x39>
400ffb: 8d 71 01 lea 0x1(%rcx),%esi
400ffe: e8 cb ff ff ff callq 400fce <func4>
401003: 8d 44 00 01 lea 0x1(%rax,%rax,1),%eax
401007: 48 83 c4 08 add $0x8,%rsp
40100b: c3 retq
000000000040100c <phase_4>:
40100c: 48 83 ec 18 sub $0x18,%rsp
401010: 48 8d 4c 24 0c lea 0xc(%rsp),%rcx
401015: 48 8d 54 24 08 lea 0x8(%rsp),%rdx
40101a: be cf 25 40 00 mov $0x4025cf,%esi
40101f: b8 00 00 00 00 mov $0x0,%eax
401024: e8 c7 fb ff ff callq 400bf0 <__isoc99_sscanf@plt>
401029: 83 f8 02 cmp $0x2,%eax
40102c: 75 07 jne 401035 <phase_4+0x29>
40102e: 83 7c 24 08 0e cmpl $0xe,0x8(%rsp)
401033: 76 05 jbe 40103a <phase_4+0x2e>
401035: e8 00 04 00 00 callq 40143a <explode_bomb>
40103a: ba 0e 00 00 00 mov $0xe,%edx
40103f: be 00 00 00 00 mov $0x0,%esi
401044: 8b 7c 24 08 mov 0x8(%rsp),%edi
401048: e8 81 ff ff ff callq 400fce <func4>
40104d: 85 c0 test %eax,%eax
40104f: 75 07 jne 401058 <phase_4+0x4c>
401051: 83 7c 24 0c 00 cmpl $0x0,0xc(%rsp)
401056: 74 05 je 40105d <phase_4+0x51>
401058: e8 dd 03 00 00 callq 40143a <explode_bomb>
40105d: 48 83 c4 18 add $0x18,%rsp
401061: c3 retq
翻译如下。
void func4(edi, esi, edx) {
eax = edx - esi;
ecx = eax;
ecx >>>= 0x1f; // sign bit
eax += ecx;
eax /= 2;
ecx = rax + rsi;
/*
* ecx = ((edx - esi) / 2) + rsi = (edx + esi) / 2
*
* edx esi ecx edi (to stop)
* 14 0 7 7
* 6 0 3 3
* 2 0 1 1
* 1 0 0 0
*/
if (ecx > edi) {
edx = rcx - 1;
return 2 * func4(edi, esi, edx);
} else {
if (ecx < edi) { // unreachable
esi = rcx + 1;
return 1 + 2 * func4(edi, esi, edx);
} else {
return 0; // final
}
}
}
void phase_4(input) { // input two number a,b
n = sscanf(input, 0x4025cf, rsp + 0x8, rsp + 0xc);
if (n != 2) bomb();
else {
if(&(rsp + 0x8) > 0xe) bomb(); // a < 14
else {
eax = func4(&(rsp + 0x8), 0x0, 0xe); // must return 0
if(eax != 0) bomb();
else if (&(rsp + 0xc) != 0x0) bomb(); // b must be 0
}
}
}
输入两个数,第二个必须为0,第一个数作为递归函数func4
的终止条件,有四种取值。故本题有四种输入:
[(0, 0), (1, 0), (3, 0), (7, 0)]
phase_5
0000000000401062 <phase_5>:
401062: 53 push %rbx
401063: 48 83 ec 20 sub $0x20,%rsp
401067: 48 89 fb mov %rdi,%rbx
40106a: 64 48 8b 04 25 28 00 mov %fs:0x28,%rax
401071: 00 00
401073: 48 89 44 24 18 mov %rax,0x18(%rsp)
401078: 31 c0 xor %eax,%eax
40107a: e8 9c 02 00 00 callq 40131b <string_length>
40107f: 83 f8 06 cmp $0x6,%eax
401082: 74 4e je 4010d2 <phase_5+0x70>
401084: e8 b1 03 00 00 callq 40143a <explode_bomb>
401089: eb 47 jmp 4010d2 <phase_5+0x70>
40108b: 0f b6 0c 03 movzbl (%rbx,%rax,1),%ecx
40108f: 88 0c 24 mov %cl,(%rsp)
401092: 48 8b 14 24 mov (%rsp),%rdx
401096: 83 e2 0f and $0xf,%edx
401099: 0f b6 92 b0 24 40 00 movzbl 0x4024b0(%rdx),%edx
4010a0: 88 54 04 10 mov %dl,0x10(%rsp,%rax,1)
4010a4: 48 83 c0 01 add $0x1,%rax
4010a8: 48 83 f8 06 cmp $0x6,%rax
4010ac: 75 dd jne 40108b <phase_5+0x29>
4010ae: c6 44 24 16 00 movb $0x0,0x16(%rsp)
4010b3: be 5e 24 40 00 mov $0x40245e,%esi
4010b8: 48 8d 7c 24 10 lea 0x10(%rsp),%rdi
4010bd: e8 76 02 00 00 callq 401338 <strings_not_equal>
4010c2: 85 c0 test %eax,%eax
4010c4: 74 13 je 4010d9 <phase_5+0x77>
4010c6: e8 6f 03 00 00 callq 40143a <explode_bomb>
4010cb: 0f 1f 44 00 00 nopl 0x0(%rax,%rax,1)
4010d0: eb 07 jmp 4010d9 <phase_5+0x77>
4010d2: b8 00 00 00 00 mov $0x0,%eax
4010d7: eb b2 jmp 40108b <phase_5+0x29>
4010d9: 48 8b 44 24 18 mov 0x18(%rsp),%rax
4010de: 64 48 33 04 25 28 00 xor %fs:0x28,%rax
4010e5: 00 00
4010e7: 74 05 je 4010ee <phase_5+0x8c>
4010e9: e8 42 fa ff ff callq 400b30 <__stack_chk_fail@plt>
4010ee: 48 83 c4 20 add $0x20,%rsp
4010f2: 5b pop %rbx
4010f3: c3 retq
本题是一个码表转换问题,翻译如下
rbx = rdi;
/*
* input is a string containing 6 characters [a, b, c, d, e, f].
* */
if(string_length(input) != 6 ) bomb();
else {
eax = 0;
while(rax != 6) { // 40108b
ecx = &(rbx + rax);
rdx = %cl; // last byte of each char
edx &= 0xf;
edx = &(rdx + 0x4024b0);
/* offset = last four bits of each char
*
* base = 0x4024b0 =>
*
* use these offsets to choose (0x40245e: "flyers") from
* "maduiersnfotvbylSo you think you can stop the bomb with ctrl-c, do you?"
*
* So:
*
* offset char
* 9 f a & 0xf = 9
* F l b & 0xf = F
* E y c & 0xf = E
* 5 e d & 0xf = 5
* 6 r e & 0xf = 6
* 7 s f & 0xf = 7
*
* */
&(rsp + 10 + rax) = dl;
rax += 1;
}
&(rsp + 16) = 0; // '\0'
if(strings_not_equal(rsp + 10, 0x40245e)) bomb(); // 0x40245e: "flyers"
}
输入的六个字符对应的ASCII
值取后四位,将其作为在字符串0x4024b0
中的位移,取出对应的6个字符必须为flyers
。因此,输入的六个字符对应的ASCII
值的后四位依次为9 F E 5 6 7
。下面的python代码求出所有可以打印出来的情况(剔除空白字符)
import itertools, string
map(lambda p: ''.join(p),
itertools.product(
*map(lambda i:
filter(lambda c: c in string.printable and c not in string.whitespace,
map(lambda x: chr(((x << 4) + i)), range(0, 0xf + 1))
),
[0x9, 0xf, 0xe, 0x5, 0x6, 0x7]
)
)
)
phase_6
00000000004010f4 <phase_6>:
4010f4: 41 56 push %r14
4010f6: 41 55 push %r13
4010f8: 41 54 push %r12
4010fa: 55 push %rbp
4010fb: 53 push %rbx
4010fc: 48 83 ec 50 sub $0x50,%rsp
401100: 49 89 e5 mov %rsp,%r13
401103: 48 89 e6 mov %rsp,%rsi
401106: e8 51 03 00 00 callq 40145c <read_six_numbers>
40110b: 49 89 e6 mov %rsp,%r14
40110e: 41 bc 00 00 00 00 mov $0x0,%r12d
401114: 4c 89 ed mov %r13,%rbp
401117: 41 8b 45 00 mov 0x0(%r13),%eax
40111b: 83 e8 01 sub $0x1,%eax
40111e: 83 f8 05 cmp $0x5,%eax
401121: 76 05 jbe 401128 <phase_6+0x34>
401123: e8 12 03 00 00 callq 40143a <explode_bomb>
401128: 41 83 c4 01 add $0x1,%r12d
40112c: 41 83 fc 06 cmp $0x6,%r12d
401130: 74 21 je 401153 <phase_6+0x5f>
401132: 44 89 e3 mov %r12d,%ebx
401135: 48 63 c3 movslq %ebx,%rax
401138: 8b 04 84 mov (%rsp,%rax,4),%eax
40113b: 39 45 00 cmp %eax,0x0(%rbp)
40113e: 75 05 jne 401145 <phase_6+0x51>
401140: e8 f5 02 00 00 callq 40143a <explode_bomb>
401145: 83 c3 01 add $0x1,%ebx
401148: 83 fb 05 cmp $0x5,%ebx
40114b: 7e e8 jle 401135 <phase_6+0x41>
40114d: 49 83 c5 04 add $0x4,%r13
401151: eb c1 jmp 401114 <phase_6+0x20>
401153: 48 8d 74 24 18 lea 0x18(%rsp),%rsi
401158: 4c 89 f0 mov %r14,%rax
40115b: b9 07 00 00 00 mov $0x7,%ecx
401160: 89 ca mov %ecx,%edx
401162: 2b 10 sub (%rax),%edx
401164: 89 10 mov %edx,(%rax)
401166: 48 83 c0 04 add $0x4,%rax
40116a: 48 39 f0 cmp %rsi,%rax
40116d: 75 f1 jne 401160 <phase_6+0x6c>
40116f: be 00 00 00 00 mov $0x0,%esi
401174: eb 21 jmp 401197 <phase_6+0xa3>
401176: 48 8b 52 08 mov 0x8(%rdx),%rdx
40117a: 83 c0 01 add $0x1,%eax
40117d: 39 c8 cmp %ecx,%eax
40117f: 75 f5 jne 401176 <phase_6+0x82>
401181: eb 05 jmp 401188 <phase_6+0x94>
401183: ba d0 32 60 00 mov $0x6032d0,%edx
401188: 48 89 54 74 20 mov %rdx,0x20(%rsp,%rsi,2)
40118d: 48 83 c6 04 add $0x4,%rsi
401191: 48 83 fe 18 cmp $0x18,%rsi
401195: 74 14 je 4011ab <phase_6+0xb7>
401197: 8b 0c 34 mov (%rsp,%rsi,1),%ecx
40119a: 83 f9 01 cmp $0x1,%ecx
40119d: 7e e4 jle 401183 <phase_6+0x8f>
40119f: b8 01 00 00 00 mov $0x1,%eax
4011a4: ba d0 32 60 00 mov $0x6032d0,%edx
4011a9: eb cb jmp 401176 <phase_6+0x82>
4011ab: 48 8b 5c 24 20 mov 0x20(%rsp),%rbx
4011b0: 48 8d 44 24 28 lea 0x28(%rsp),%rax
4011b5: 48 8d 74 24 50 lea 0x50(%rsp),%rsi
4011ba: 48 89 d9 mov %rbx,%rcx
4011bd: 48 8b 10 mov (%rax),%rdx
4011c0: 48 89 51 08 mov %rdx,0x8(%rcx)
4011c4: 48 83 c0 08 add $0x8,%rax
4011c8: 48 39 f0 cmp %rsi,%rax
4011cb: 74 05 je 4011d2 <phase_6+0xde>
4011cd: 48 89 d1 mov %rdx,%rcx
4011d0: eb eb jmp 4011bd <phase_6+0xc9>
4011d2: 48 c7 42 08 00 00 00 movq $0x0,0x8(%rdx)
4011d9: 00
4011da: bd 05 00 00 00 mov $0x5,%ebp
4011df: 48 8b 43 08 mov 0x8(%rbx),%rax
4011e3: 8b 00 mov (%rax),%eax
4011e5: 39 03 cmp %eax,(%rbx)
4011e7: 7d 05 jge 4011ee <phase_6+0xfa>
4011e9: e8 4c 02 00 00 callq 40143a <explode_bomb>
4011ee: 48 8b 5b 08 mov 0x8(%rbx),%rbx
4011f2: 83 ed 01 sub $0x1,%ebp
4011f5: 75 e8 jne 4011df <phase_6+0xeb>
4011f7: 48 83 c4 50 add $0x50,%rsp
4011fb: 5b pop %rbx
4011fc: 5d pop %rbp
4011fd: 41 5c pop %r12
4011ff: 41 5d pop %r13
401201: 41 5e pop %r14
401203: c3 retq
本题汇编比较长,而且跳转指令比较多。先初步翻译如下
r13 = rsp
rsi = rsp
read_six_numbers // start at rsp
r14 = rsp
r12d = 0
rbp = r13 // 0x401114
eax = &r13
eax = eax - 1
if(eax > 5) bomb
else { // 0x401128
r12d = r12d + 1
if (r12d != 6) {
ebx = r12d
eax = ebx /*0x401135*/
eax = &(rsp + 4 * eax)
if (eax == &rbp) bomb
else { // 0x401145
ebx = ebx + 1
if (ebx > 5) {
r13 = r13 + 4
//jmp to 0x401114
} else // jmp to 0x401135
}
} else { // 0x401153
rsi = rsp + 0x18
rax = r14
ecx = 0x7
edx = ecx /* 0x401160 */
edx = edx - &rax
&rax = edx
rax = rax + 4
if(rax == rsi) {
esi = 0
// jmp to 0x401197
} else // jump to 0x401160
}
}
/* 0x401197 */
ecx = &(rsp + rsi)
ecx = exc + 1
if(ecx > 1) {
eax = 0x1
edx = 0x6032d0
/* 0x401176 */
rdx = &(rdx + 8)
eax = eax + 1
if (ecx != eax) // jmp to 0x401176
else // jmp to 0x401188
} else { // 0x401183
edx = 0x6032d0
&(rsp + 2 * rsi + 0x20) = rdx // 0x401188
rsi = rsi + 4
if (rsi != 0x18) {
// jmp to 0x401197
} else // jmp to 0x4011ab
}
/* 0x4011ab */
rbx = &(rsp + 0x20)
rax = rsp + 0x28
rsi = rsp + 0x50
rcx = rbx
rdx = &rax // 0x4011bd
&(rcx + 8) = rdx
rax = rax + 8
if (rax == rsi) // jmp to 0x4011d2
else {
rcx = rdx
// jmp to 0x4011bd
}
/* 0x4011d2 */
(rdx + 0x8) = 0
ebp = 0x5
rax = &(rbx + 0x8) // 0x4011df
eax = &rax
if(&rbx < eax) // bomb
else {
rbx = &(rbx + 0x8)
ebp = ebp - 0x1
if (ebp != 0) // jmp to 0x4011df
else // over
}
然后用循环简化其中的跳转指令
r13 = rsp;
rsi = rsp;
read_six_numbers(input,rsp); // array [a,b,c,d,e,f] start at rsp
r14 = rsp;
r12d = 0;
while(true){
rbp = r13;
eax = &r13 - 1;
if(eax > 5) bomb(); // each number is not more than 6
else {
r12d++;
if(r12d < 6) {
ebx = r12d;
do {
if (&(rsp + 4 * ebx) == &rbp) // each number differs from others
bomb();
else
ebx++;
} while(ebx <= 5)
r13 = r13 + 4;
} else{
break;
}
}
}
rsi = rsp + 0x18;
rax = r14;
ecx = 7;
do {
&rax = 7 - &rax; // map with (7 - _)
rax += 4;
} while (rax < rsi)
rsi = 0;
do () { // At 0x401197
ecx = &(rsp + rsi);
if(ecx > 1) {
eax = 1;
edx = 0x6032d0;
do {
rdx = &(rdx + 8); // At 0x401176
eax++;
} while (eax < ecx)
} else { // At 0x401183
edx = 0x6032d0;
}
/* address of the value of the linked list
* +8 is the address of next node
*
* rsp + 0x20 + 0x00 = 0x6032d0 + ((7 - a) - 1) * 16
* rsp + 0x20 + 0x08 = 0x6032d0 + ((7 - b) - 1) * 16
* rsp + 0x20 + 0x10 = 0x6032d0 + ((7 - c) - 1) * 16
* rsp + 0x20 + 0x18 = 0x6032d0 + ((7 - d) - 1) * 16
* rsp + 0x20 + 0x20 = 0x6032d0 + ((7 - e) - 1) * 16
* rsp + 0x20 + 0x28 = 0x6032d0 + ((7 - f) - 1) * 16
* */
&(rsp + 2 * rsi + 0x20) = rdx; // At 0x401188
rsi += 4;
} while (rsi < 0x18)
/* The original linked list
*
* Address Value Next
* 0x6032d0 0x10000014c 0x6032e0
* 0x6032e0 0x2000000a8 0x6032f0
* 0x6032f0 0x30000039c 0x603300
* 0x603300 0x4000002b3 0x603310
* 0x603310 0x5000001dd 0x603320
* 0x603320 0x6000001bb 0x000000
*/
rbx = &(rsp + 0x20);
rax = rsp + 0x28;
rsi = rsp + 0x50; // limit
rcx = rbx;
// relink the linked list from (rsp + 0x20) to (rsp + 0x48) in sequence
while(true) {
rdx = &rax;
&(rcx + 8) = rdx;
rax += 8;
if(rax < rsi) rcx = rdx;
else break;
}
(rdx + 0x8) = 0; // set Nil to the end of list
ebp = 5;
do {
rax = &(rbx + 0x8);
eax = &rax; // next value, truncate the higher 32 bits
// &rbx is the current value
if(&rbx < eax) bomb(); // so the relinked list is descending sorted
else {
rbx = &(rbx + 0x8);
ebp -= 1;
}
} while(ebp > 0)
/* let's sort the original list:
*
* Value Index Calculation
* 0x39c 2 7 - a - 1 = 2 => a = 4
* 0x2b3 3 7 - b - 1 = 3 => b = 3
* 0x1dd 4 7 - c - 1 = 4 => c = 2
* 0x1bb 5 7 - d - 1 = 5 => d = 1
* 0x14c 0 7 - e - 1 = 0 => e = 6
* 0x0a8 1 7 - f - 1 = 1 => f = 5
* */
可以发现这其实是一个简单的链表问题。输入六个互不相同且不大于6的正整数。记为Xi,则按(7 - Xi - 1)
作为在链表中的位置,依次将对应的元素取出并按照取出顺序重新链接。变换后的链表必须满足降序排列。
将原链表按降序排列就知道该按什么顺序重新排列了,所以最后得到的输入为4 3 2 1 6 5
secret_phase
本实验还有一个隐藏关卡。但是观察main
函数的汇编代码并没有发现有调用这个隐藏关卡secret_phase
。查找发现,隐藏函数在0x401630
处被调用,即在phase_defused
函数中被调用。
$ objdump -d bomb | grep secret
0000000000401242 <secret_phase>:
401265: 76 05 jbe 40126c <secret_phase+0x2a>
40127b: 74 05 je 401282 <secret_phase+0x40>
401630: e8 0d fc ff ff callq 401242 <secret_phase>
观察phase_defused
可以发现,当输入的字符串个数大于6时,会进入隐藏关卡。并用sscanf
函数接收格式为0x402619: "%d %d %s"
的输入,输入的字符串地址为0x603870
,并不是再次读取用户的输入,而是从一个固定地址读入。从这个地址读入的字符串必须是与在0x402622
处的字符串DrEvil
相同。
00000000004015c4 <phase_defused>:
4015c4: 48 83 ec 78 sub $0x78,%rsp
4015c8: 64 48 8b 04 25 28 00 mov %fs:0x28,%rax
4015cf: 00 00
4015d1: 48 89 44 24 68 mov %rax,0x68(%rsp)
4015d6: 31 c0 xor %eax,%eax
4015d8: 83 3d 81 21 20 00 06 cmpl $0x6,0x202181(%rip) # 603760 <num_input_strings>
4015df: 75 5e jne 40163f <phase_defused+0x7b>
4015e1: 4c 8d 44 24 10 lea 0x10(%rsp),%r8
4015e6: 48 8d 4c 24 0c lea 0xc(%rsp),%rcx
4015eb: 48 8d 54 24 08 lea 0x8(%rsp),%rdx
4015f0: be 19 26 40 00 mov $0x402619,%esi
4015f5: bf 70 38 60 00 mov $0x603870,%edi
4015fa: e8 f1 f5 ff ff callq 400bf0 <__isoc99_sscanf@plt>
4015ff: 83 f8 03 cmp $0x3,%eax
401602: 75 31 jne 401635 <phase_defused+0x71>
401604: be 22 26 40 00 mov $0x402622,%esi
401609: 48 8d 7c 24 10 lea 0x10(%rsp),%rdi
40160e: e8 25 fd ff ff callq 401338 <strings_not_equal>
401613: 85 c0 test %eax,%eax
401615: 75 1e jne 401635 <phase_defused+0x71>
401617: bf f8 24 40 00 mov $0x4024f8,%edi
40161c: e8 ef f4 ff ff callq 400b10 <puts@plt>
401621: bf 20 25 40 00 mov $0x402520,%edi
401626: e8 e5 f4 ff ff callq 400b10 <puts@plt>
40162b: b8 00 00 00 00 mov $0x0,%eax
401630: e8 0d fc ff ff callq 401242 <secret_phase>
401635: bf 58 25 40 00 mov $0x402558,%edi
40163a: e8 d1 f4 ff ff callq 400b10 <puts@plt>
40163f: 48 8b 44 24 68 mov 0x68(%rsp),%rax
401644: 64 48 33 04 25 28 00 xor %fs:0x28,%rax
40164b: 00 00
40164d: 74 05 je 401654 <phase_defused+0x90>
40164f: e8 dc f4 ff ff callq 400b30 <__stack_chk_fail@plt>
401654: 48 83 c4 78 add $0x78,%rsp
401658: c3 retq
尝试在汇编代码中查找0x603870
却无果。想到这个DrEvil
一定是用户输入的,程序刚开始运行时利用GDB发现该处的字符串为空串,那么该字符串一定是在运行时改变的。于是用GDB监视地址为0x603870
处的内存变化。
(gdb) watch *0x603870
Hardware watchpoint 1: *0x603870
(gdb) r inputs.txt
Starting program: /home/yieldnull/CSAPP/lab2-bomb/src/bomb inputs.txt
Welcome to my fiendish little bomb. You have 6 phases with
which to blow yourself up. Have a nice day!
Phase 1 defused. How about the next one?
That's number 2. Keep going!
Halfway there!
Hardware watchpoint 1: *0x603870
Old value = 0
New value = 49
__memcpy_sse2 () at ../sysdeps/x86_64/multiarch/../memcpy.S:74
74 ../sysdeps/x86_64/multiarch/../memcpy.S: No such file or directory.
(gdb) x /s 0x603870
0x603870 <input_strings+240>: "1"
(gdb) next
75 in ../sysdeps/x86_64/multiarch/../memcpy.S
(gdb) next
80 in ../sysdeps/x86_64/multiarch/../memcpy.S
(gdb) next
81 in ../sysdeps/x86_64/multiarch/../memcpy.S
(gdb) next
83 in ../sysdeps/x86_64/multiarch/../memcpy.S
(gdb) next
84 in ../sysdeps/x86_64/multiarch/../memcpy.S
(gdb) next
Hardware watchpoint 1: *0x603870
Old value = 49
New value = 3153969
__memcpy_sse2 () at ../sysdeps/x86_64/multiarch/../memcpy.S:86
86 in ../sysdeps/x86_64/multiarch/../memcpy.S
(gdb) x /s 0x603870
0x603870 <input_strings+240>: "1 0"
发现该地址处内存在第四次输入时改变,而且正好是第四次输入的字符串。于是在第四次输入字符串后加入DrEvil
即可进入隐藏关卡。
秘密关卡的汇编代码如下:
0000000000401204 <fun7>:
401204: 48 83 ec 08 sub $0x8,%rsp
401208: 48 85 ff test %rdi,%rdi
40120b: 74 2b je 401238 <fun7+0x34>
40120d: 8b 17 mov (%rdi),%edx
40120f: 39 f2 cmp %esi,%edx
401211: 7e 0d jle 401220 <fun7+0x1c>
401213: 48 8b 7f 08 mov 0x8(%rdi),%rdi
401217: e8 e8 ff ff ff callq 401204 <fun7>
40121c: 01 c0 add %eax,%eax
40121e: eb 1d jmp 40123d <fun7+0x39>
401220: b8 00 00 00 00 mov $0x0,%eax
401225: 39 f2 cmp %esi,%edx
401227: 74 14 je 40123d <fun7+0x39>
401229: 48 8b 7f 10 mov 0x10(%rdi),%rdi
40122d: e8 d2 ff ff ff callq 401204 <fun7>
401232: 8d 44 00 01 lea 0x1(%rax,%rax,1),%eax
401236: eb 05 jmp 40123d <fun7+0x39>
401238: b8 ff ff ff ff mov $0xffffffff,%eax
40123d: 48 83 c4 08 add $0x8,%rsp
401241: c3 retq
0000000000401242 <secret_phase>:
401242: 53 push %rbx
401243: e8 56 02 00 00 callq 40149e <read_line>
401248: ba 0a 00 00 00 mov $0xa,%edx
40124d: be 00 00 00 00 mov $0x0,%esi
401252: 48 89 c7 mov %rax,%rdi
401255: e8 76 f9 ff ff callq 400bd0 <strtol@plt>
40125a: 48 89 c3 mov %rax,%rbx
40125d: 8d 40 ff lea -0x1(%rax),%eax
401260: 3d e8 03 00 00 cmp $0x3e8,%eax
401265: 76 05 jbe 40126c <secret_phase+0x2a>
401267: e8 ce 01 00 00 callq 40143a <explode_bomb>
40126c: 89 de mov %ebx,%esi
40126e: bf f0 30 60 00 mov $0x6030f0,%edi
401273: e8 8c ff ff ff callq 401204 <fun7>
401278: 83 f8 02 cmp $0x2,%eax
40127b: 74 05 je 401282 <secret_phase+0x40>
40127d: e8 b8 01 00 00 callq 40143a <explode_bomb>
401282: bf 38 24 40 00 mov $0x402438,%edi
401287: e8 84 f8 ff ff callq 400b10 <puts@plt>
40128c: e8 33 03 00 00 callq 4015c4 <phase_defused>
401291: 5b pop %rbx
401292: c3 retq
程序比较简单,翻译如下
/* Address Value
* 0x6030f0 0x24
* 0x6030f8 0x603110
* 0x603110 0x8
* 0x603120 0x603150
* 0x603150 0x16
*
* rdi : 0x6030f0
* esi : num - 1
* */
void fun7(rdi, esi) { // must return 2
if(rdi == NULL) {
return -1;
} else {
edx = &rdi;
if(edx > esi) {
return 2 * fun7(&(rdi + 8), esi); // No.1 => esi <= 0x24
} else {
if (edx == esi) {
return 0; // No.3 esi == 0x16
} else {
return 1 + 2 * fun7(&(rdi + 0x10), esi); // No.2 => esi > 0x8
}
}
}
}
void secret() {
rdi = read_line();
rax = strtol(rdi, NULL, 10);
rbx = rax;
eax = rax - 1;
if(eax > 0x3eb) bomb();
else {
eax = fun7(0x6030f0, ebx);
if(eax != 0x2) bomb();
}
}
func7
的返回值必须为2,那么func7
的调用返回流程必须是2 * (1 + 2 * 0)
,故很容易得出输入为22
。
sample_input
输入示例
$ cat inputs.txt
Border relations with Canada have never been better.
1 2 4 8 16 32
1 311
1 0 DrEvil
iOnE&7
4 3 2 1 6 5
22
$ ./bomb inputs.txt
Welcome to my fiendish little bomb. You have 6 phases with
which to blow yourself up. Have a nice day!
Phase 1 defused. How about the next one?
That's number 2. Keep going!
Halfway there!
So you got that one. Try this one.
Good work! On to the next...
Curses, you've found the secret phase!
But finding it and solving it are quite different...
Wow! You've defused the secret stage!
Congratulations! You've defused the bomb!
(完)
翻译后的代码见[YieldNull/CSAPP]