0%

[CSAPP Lab]-II Bomb Lab

Phase 6

Phase 6 的汇编又臭又长,先把 Phase 6 反汇编出来

完整的代码就不放了,先来一段段分析

Section 1

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>

这一段是函数调用相关的,不用过多的在意,不过注意最后一行调用了 read_six_numbers ,根据之前的练习中,这个函数会读取 6 个数字并放在 %rsp~%rsp+0x14 中(这里可以用 GDBx/d 打印检查)

Section 2

下面开始正文了,先上代码

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 ; eax -= 0x1
40111e: 83 f8 05 cmp $0x5,%eax ; eax <= 0x5
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 ; r12d += 1
40112c: 41 83 fc 06 cmp $0x6,%r12d ; r12d == 0x6
401130: 74 21 je 401153 <phase_6+0x5f>
401132: 44 89 e3 mov %r12d,%ebx ; ebx = r12d
; 内层循环
401135: 48 63 c3 movslq %ebx,%rax ; rax = ebx
401138: 8b 04 84 mov (%rsp,%rax,4),%eax ; eax = *(rsp+rax*4)
40113b: 39 45 00 cmp %eax,0x0(%rbp) ; *(rbp) != eax
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 ; rbx += 1
401148: 83 fb 05 cmp $0x5,%ebx ; ebx <= 5
40114b: 7e e8 jle 401135 <phase_6+0x41>
40114d: 49 83 c5 04 add $0x4,%r13 ; r13 += 0x4
401151: eb c1 jmp 401114 <phase_6+0x20>

最后一行的 401151 可以看出外层嵌套了一个大的循环,401117 中,将 eax 赋值为 r13 指向的地址,也就是第 r12d 个输入变量,然后 40111e 的比较确保了输入变量是小于 6 的

内层循环中,则是取出第 r12d 个输入变量后边的,依次与 eax 比较,确保每个变量都不相同

Section 3

; 每一个输入用 7 减去
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>

rax 每次增加 4 个 Byte 来读取下一个 Int ,并且将 raxrsp+0x18 比较
每次用 0x7 减去

Section 4

40116f: be 00 00 00 00 mov $0x0,%esi ; esi=0
401174: eb 21 jmp 401197 <phase_6+0xa3>
; 链表索引,直到得到链表第 ecx 项,讲第 ecx 项的地址存入 rdx
401176: 48 8b 52 08 mov 0x8(%rdx),%rdx ; rax = *(rdx+0x8)
40117a: 83 c0 01 add $0x1,%eax ; eax += 1
40117d: 39 c8 cmp %ecx,%eax ; eax != ecx
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 ; edx = 0x6032d0
401188: 48 89 54 74 20 mov % rdx,0x20 (% rsp,% rsi,2) ; *(0x20+rsp+rsi*2) = rdx 在 Stack 顶部存入 rdx
40118d: 48 83 c6 04 add $0x4,% rsi ; rsi += 4 枚举下一个输入
401191: 48 83 fe 18 cmp $0x18,% rsi ; rsi == 0x18 检测是否到达输入末尾 (0x18 = 24 = 4 INT)
401195: 74 14 je 4011ab <phase_6+0xb7>
401197: 8b 0c 34 mov (%rsp,%rsi,1),%ecx ; ecx=*(rsp+rsi) input[rsi/4]
40119a: 83 f9 01 cmp $0x1,%ecx ; ecx <= 1
40119d: 7e e4 jle 401183 <phase_6+0x8f>
40119f: b8 01 00 00 00 mov $0x1,%eax ; eax = 1
4011a4: ba d0 32 60 00 mov $0x6032d0,%edx
4011a9: eb cb jmp 401176 <phase_6+0x82>

这里就开始比较恶心了,先看里边有个 Magic Number 0x6032d0 ,用 GDB 打印出来发现是这样的:

1
2
3
4
5
6
7
(gdb) x/24 0x6032d0
0x6032d0 <node1>: 332 1 6304480 0
0x6032e0 <node2>: 168 2 6304496 0
0x6032f0 <node3>: 924 3 6304512 0
0x603300 <node4>: 691 4 6304528 0
0x603310 <node5>: 477 5 6304544 0
0x603320 <node6>: 443 6 0 0

说实话我也不懂这个是什么 (???),一番百度 Google 后发现这是一个链表,结构大概是这样的

注意这里的 Next地址是十进制表示的,要记得把它转换成十六进制

地址 序号 Next 地址
0x6032d0 332 1 0x6032e0
0x6032e0 168 2 0x6032f0
0x6032f0 924 3 0x603300
0x603300 691 4 0x603310
0x603310 477 5 0x603320
0x603320 443 6 0

循环中, ecx 用来存取当前所读取的输入变量,最后一行跳转到 401176 ,因为链表的每个 Node 都是两个 Int 和一个 Pointer 组成的,所以 401176 中获得下一个 Node 的地址,并且用 eax 来做计数变量,可以看出这几行的作用就是使得 rdx 中存的是第 ecx 个链表元素

然后下边的就是在 0x20+rsp+rsi*2 后边添加上第 ecx 个 Node 的地址

举个例子,如果输入是 6 5 4 3 2 1 这一步就会把链表第六个 Node 的地址放到栈顶第一位,第五个放到第二位…

Section 5

; 按照上一个循环得到的顺序重新链接链表
4011ab: 48 8b 5c 24 20 mov 0x20 (% rsp),% rbx ; rbx = *(0x20+rsp) 这里是按照输入存取链表的地方,得到第一个指针
4011b0: 48 8d 44 24 28 lea 0x28 (% rsp),% rax ; rax = rsp+0x28 得到链表下一个 Node 的地址
4011b5: 48 8d 74 24 50 lea 0x50 (% rsp),% rsi ; rsi = rsp+0x50 链表结尾 (?)
4011ba: 48 89 d9 mov %rbx,%rcx ; rcx = rbx
4011bd: 48 8b 10 mov (% rax),% rdx ; rdx = *(rax) 得到链表的下一项地址
4011c0: 48 89 51 08 mov % rdx,0x8 (% rcx) ; *(rcx+0x8) = rdx 把当前的 Node->Next 设置为 rdx
4011c4: 48 83 c0 08 add $0x8,% rax ; rax += 0x8 rax 指向链表的 Pointer 区
4011c8: 48 39 f0 cmp % rsi,% rax ; rsi == rax 判断是否遍历所有链表
4011cb: 74 05 je 4011d2 <phase_6+0xde>
4011cd: 48 89 d1 mov % rdx,% rcx ; rcx = rdx 本循环中,rcx 存取当前 Node,rdx 存取下一个 Node
4011d0: eb eb jmp 4011bd <phase_6+0xc9>
4011d2: 48 c7 42 08 00 00 00 movq $0x0,0x8 (% rdx) ; *(rdx+0x8) = 0 链表最后一项 ->Next 设为 NULL

这里的工作就像注释所说的:把新的链表重新连接起来

Section 6

; rbx 存取的仍然是 *(0x20+rsp),也就是链表第一项
4011d9: 00
4011da: bd 05 00 00 00 mov $0x5,%ebp ; ebp = 0x5
4011df: 48 8b 43 08 mov 0x8 (% rbx),% rax ; rax = *(rbx+0x8) rax 指向链表下一项
4011e3: 8b 00 mov (% rax),% eax ; eax = *(rax) eax 设置为链表第 rax 项的值域
4011e5: 39 03 cmp %eax,(%rbx) ; *(rbx) >= eax
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 ; rbx = *(rbx+0x8) rbx 递增
4011f2: 83 ed 01 sub $0x1,%ebp ; ebp -= 0x1
4011f5: 75 e8 jne 4011df <phase_6+0xeb>

这里比较的是新链接的链表的前一个元素的 Value 大于后一个元素的

Final

综上,这个逻辑总算是理清楚了:首先从输入读取 6 个数字,确保每个数字都是小于等于 6 并且只出现一次,然后将链表重新排序为按照输入的顺序,最后验证新的链表的前一项大于后一项

链表中的值分别是 332 168 924 691 477 433 ,所以输入就是 4 3 2 1 6 5

有时间把之前的 Phase 补齐吧 (Flag)

EOF.