前言
这几天真的是因为工作摸鱼没有开始写 Lab-6, 完全不是太难了搞不明白。
工具
Phase 6 —— 可惜你帅不过坂本大佬
第一个循环
这个 Phase 反复横跳,不过跟着它顺序执行还是能心中有数的。
从 phase_6 + 0x20 处开始到 phase + 0x5d 是第一个令人糟心的大循环,所以还是要拆开来看。
1
2
3
4
5
6
7
|
0x00401114 movq %r13, %rbp ; move quadword ; rbp=0xffffffffffffff90
0x00401117 movl (%r13), %eax ; rax=0xffffffff
0x0040111b subl $1, %eax ; eax=0xfffffffe ; of=0x0 ; sf=0x1 ; zf=0x0 ; pf=0x0 ; cf=0x0 ; af=0x0
0x0040111e cmpl $5, %eax ; 5 ; zf=0x0 ; cf=0x0 ; pf=0x1 ; sf=0x1 ; of=0x0 ; af=0x0
0x00401121 jbe 0x401128 ; jump short if below or equal/not above (cf=1 or zf=1) ; unlikely
0x00401123 callq explode_bomb ; sym.explode_bomb ; rsp=0xffffffffffffff80 ; rip=0x40143a -> 0x8ec8348 sym.explode_bomb
; CODE XREF from sym.phase_6 @ 0x401121
|
容易得 rsp 所指向的 long 类型的数据应当有 $ 1 \leq x \leq 6 $,至于为何应当大于等于 1 是因为 jbe
的比较是无符号的。
1
2
3
|
0x00401128 addl $1, %r12d ; r12d=0x1 ; of=0x0 ; sf=0x0 ; zf=0x0 ; cf=0x0 ; pf=0x0 ; af=0x0
0x0040112c cmpl $6, %r12d ; 6 ; zf=0x0 ; cf=0x1 ; pf=0x0 ; sf=0x1 ; of=0x0 ; af=0x1
0x00401130 je 0x401153 ; jump short if equal (zf=1) ; unlikely
|
显然这一段是 if-break 形式的语句,猜测可以将 r12d 视为循环中常用的 i
变量。
接下来我们来到了循环中的一个小循环。
1
2
3
4
5
6
7
8
9
10
11
|
0x00401132 movl %r12d, %ebx ; rbx=0x1
; CODE XREF from sym.phase_6 @ 0x40114b
0x00401135 movslq %ebx, %rax ; rax=0x1
0x00401138 movl (%rsp, %rax, 4), %eax ; rax=0xffffffff
0x0040113b cmpl %eax, (%rbp) ; zf=0x1 ; cf=0x0 ; pf=0x1 ; sf=0x0 ; of=0x0 ; af=0x0
0x0040113e jne 0x401145 ; jump short if not equal/not zero (zf=0) ; unlikely
0x00401140 callq explode_bomb ; sym.explode_bomb ; rsp=0xffffffffffffff78 ; rip=0x40143a -> 0x8ec8348 sym.explode_bomb
; CODE XREF from sym.phase_6 @ 0x40113e
0x00401145 addl $1, %ebx ; ebx=0x2 ; of=0x0 ; sf=0x0 ; zf=0x0 ; cf=0x0 ; pf=0x0 ; af=0x0
0x00401148 cmpl $5, %ebx ; 5 ; zf=0x0 ; cf=0x1 ; pf=0x0 ; sf=0x1 ; of=0x0 ; af=0x1
0x0040114b jle 0x401135 ; jump short if less or equal/not greater (zf=1 or sf!=of) ; rip=0x401135 -> 0x8bc36348 ; likely
|
在这里首先给 ebx 赋值 r12d,然后关注 ebx 的变化,发现 ebx 最大也仅仅到 5 就跳出这个小循环了,那么这个 ebx 猜测作用是双层循环里头的 j
;这么想就很简单了,我们知道栈上取出来的6个数在内存上是连续的,可以记成 int stack[6]
,在这里就是进行了:
1
2
3
4
|
mov i j
cmpl stack[j] (%rbp)
jne 0x401145
;...
|
那么 rbp 又是个甚么东西?我们看循环的开头,可以发现 rbp 指向 r13 指向的内容,那就是 stack[0] 了。当然无须担心出现 cmpl stack[0] stack[0]
,根据 0x00401128 我们的 j
是一定大于 i
- 1 的,而 i
- 1 又是大于等于 0 的。那么一个个和 stack[0] 比有甚么用呢?
1
2
|
0x0040114d addq $4, %r13 ; r13=0xffffffffffffff94 ; of=0x0 ; sf=0x1 ; zf=0x0 ; cf=0x0 ; pf=0x0 ; af=0x0
0x00401151 jmp 0x401114 ; jump ; rip=0x401114 -> 0x41ed894c ; 回到循环开头
|
我们发现这里 r13 会向下一个 stack 中的数移动,也就是说从 stack[0] 移动到了 stack[1]。所以说,所有的数都会互相进行比较一次。
稍加融会贯通,不说整个写出来,起码也知道了输入的数有两个条件:
于是我们可以爆破出来
第二个循环
所有数对 7 求出余数。
载入链表
反复横跳起来了,但是跟着他就不会出问题。
1
2
3
4
5
6
7
8
9
10
11
|
0x0040116f movl $0, %esi ; rsi=0x0
0x00401174 jmp 0x401197 ; jump ; rip=0x401197 ->
; ...
; ...
; CODE XREF from sym.phase_6 @ 0x401174
0x00401197 movl (%rsp, %rsi), %ecx ; rcx=0xffffffff
0x0040119a cmpl $1, %ecx ; 1 ; zf=0x0 ; cf=0x0 ; pf=0x0 ; sf=0x1 ; of=0x0 ; af=0x0
0x0040119d jle 0x401183 ; jump short if less or equal/not greater (zf=1 or sf!=of) ; rip=0x401183 -> 0x6032d0ba ; likely
0x0040119f movl $1, %eax ; rax=0x1
0x004011a4 movl $node1, %edx ; 0x6032d0 ; rdx=0x6032d0 -> 0x14c obj.node1
0x004011a9 jmp 0x401176 ; jump ; rip=0x401176 -> 0x8528b48
|
跟随这个 jmp,来到 0x401197,一来就拿 stack[0] 和 1 相比。
然后在纸上写写画画,加上自己的亿点点优化就可以写出如下代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
int i = 0;
long stack2[6];
do{
int edx = $node1;
if (stack[i] > 1) {
int tmp = 1;
do {
edx = *(edx+8);
tmp++;
} while(tmp != stack[i])
}
stack2[i] = rdx;
i++;
}while(i<6)
|
至于这个 $node1
到底是甚么,用 cutter
双击去看只能看到一行行代码,但是事实上这些是规整的数据。要想知道这些数据的值,只能看 objdump
或者 GDB
直接查看输出。
实际上源文件中的注释就已经提示我们这是一个链表结构,每一个链节的结构如下:
1
2
3
4
5
|
+---------------+
| int_64 data | <---- addr
+---------------+
| ptr_64 next | <---- addr + 0x8
+---------------+
|
每次访问 *(addr+8),事实上就相当于 p=p.Next()
, 可以用 gdb 查看得到各个 node 的顺序关系:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
gdb> x/d 0x6032d0
332
gdb> x/x 0x6032d0+0x8
0x6032e0
gdb> x/d 0x6032e0
168
gdb> x/x 0x6032e0+0x8
0x6032f0
# 是一个链表
# 查6个数就够了
+----------+ +----------+ +----------+ +----------+ +----------+ +----------+
|332 | ┌-> |168 | ┌-> |924 | ┌-> |691 | ┌-> |477 | ┌-> |443 |
+----------+ | +----------+ | +----------+ | +----------+ | +----------+ | +----------+
| 0x6032d0 | --┘ | 0x6032e0 | --┘ | 0x6032f0 | --┘ | 0x603300 | --┘ | 0x603310 | --┘ | 0x603320 |
+----------+ +----------+ +----------+ +----------+ +----------+ +----------+
|
优化一下就得到了具体逻辑:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
typedef struct Node{
long data;
Node* next;
} Node;
typedef struct LinkedList{
Node* head;
} LinkedList;
*Node Next(*Node node){return node.next;};
**Node MutNext(*Node node){return &node.next;};
long Data(*Node node){return node.data;};
LinkedList LOOKUP[6] = {{332, 0x6032d0}, {168,0x6032e0}, {924,0x6032f0}, {691,0x603300}, {477,0x603310}, {443,0x603320}};
int i = 0; long stack2[6];
do{
index = stack[i]; // stack array get from read_six_numbers
stack2[i] = LOOKUP[index - 1];
i++;
} while(i != 6)
|
链表重建
cutter 会将 %rsp + 0xYY 显示为 var_YYh
1
2
3
4
|
0x004011ab movq var_20h, %rbx ; move quadword ; rbx=0xffffffffffffffff
0x004011b0 leaq var_28h, %rax ; rax=0x28
0x004011b5 leaq var_50h, %rsi ; rsi=0x50
0x004011ba movq %rbx, %rcx ; move quadword ; rcx=0xffffffffffffffff
|
设上一个阶段的新数组为 long stack2[6], 可以知道有:
- stack2 = %rsp + 0x20
- rbx = stack2 = stack2[0]
- rax = stack2 + 0x8 = &stack2[1]
- rsi = stack2 + 0x30 = &stack2[6] (数组上界)
- rcx = rbx
1
2
3
4
5
6
7
8
|
; CODE XREF from sym.phase_6 @ 0x4011d0
0x004011bd movq (%rax), %rdx ; move quadword ; rdx=0xffffffffffffffff
0x004011c0 movq %rdx, 8(%rcx) ; move quadword
0x004011c4 addq $8, %rax ; rax=0x30 ; of=0x0 ; sf=0x0 ; zf=0x0 ; cf=0x0 ; pf=0x1 ; af=0x1
0x004011c8 cmpq %rsi, %rax ; zf=0x0 ; cf=0x1 ; pf=0x0 ; sf=0x1 ; of=0x0 ; af=0x0
0x004011cb je 0x4011d2 ; jump short if equal (zf=1) ; unlikely
0x004011cd movq %rdx, %rcx ; move quadword ; rcx=0xffffffffffffffff
0x004011d0 jmp 0x4011bd ; jump ; rip=0x4011bd -> 0x48108b48
|
经过亿番润色得到:
1
2
3
4
5
6
7
8
9
|
int i = 1;
while(true) {
// *(stack2[i-1] + 8) = stack2[i];
MutNext(stack2[i-1]) = stack2[i];
i++;
if (i == 6) {
break;
}
}
|
值得一提的是这里改写的是上面提到的 LOOKUP 数组,可以认为是对 LOOKUP 数组进行了一次重排。
可以理解为,将原来的链表按照 stack2 的存储顺序,进行一次重整!
检验时刻
接下来的代码很容易就看出来逻辑是:
1
2
3
4
5
6
7
8
9
|
// (int)*(stack2[5]+0x8)=0;
MutNext(stack2[5])=NULL;
for (int i = 5; i > 0 ; i--) {
int j = 0;
if (Data(stack2[j]) < Data(Next(stack2[j]))) {
bomb();
}
}
|
前面已经讲过这个是一个链表,只要访问相应的 addr
就能知道链表承载的具体数据如何。
1
2
3
4
5
6
7
|
gdb> x/d <addr>
0x6032d0 -> 332
0x6032e0 -> 168
0x6032f0 -> 924
0x603300 -> 691
0x603310 -> 477
0x603320 -> 443
|
因此我们知道最后的 stack2 内容一定是:
1
2
|
0x6032f0 0x603300 0x603310 0x603320 0x6032d0 0x6032e0
3 4 5 6 1 2
|
所以 stack 的内容是:
考虑到中途的对7求补,输入的六个数字应该是:
Congratulations! You’ve defused the bomb!
后记
在二进制里头发现了一个 func7,这是怎么回事呢?等以后再看看罢!