預備知識:
1、瞭解PWN(溢出)
PWN是一個黑客語法的俚語詞,自"own"這個字引申出來的,這個詞的含意在於,玩家在整個遊戲對戰中處在勝利的優勢,或是說明競爭對手處在完全慘敗的 情形下,這個詞習慣上在網絡遊戲文化主要用於嘲笑競爭對手在整個遊戲對戰中已經完全被擊敗(例如:"You just got pwned!")。有一個非常著名的國際賽事叫做Pwn2Own,相信你現在已經能夠理解這個名字的含義了,即通過打敗對手來達到擁有的目的。
CTF中PWN題型通常會直接給定一個已經編譯好的二進制程序(Windows下的EXE或者Linux下的ELF文件等),然後參賽選手通過對二進制程 序進行逆向分析和調試來找到利用漏洞,並編寫利用代碼,通過遠程代碼執行來達到溢出攻擊的效果,最終拿到目標機器的shell奪取flag。
2、Linux管道
Linux管道可以將一個進程的標準輸出作爲另一個進程的標準輸入,管道的操作符號爲“|”,比如ls命令可用於查看當前目錄下的文件列表,而grep命 令可用於匹配特定的字符,因此ls | grep test命令可用於列出當前目錄下文件名包含test的文件。
3、Python基礎
在Linux shell中執行python -c "print 'Hello'"可以執行雙引號中的Python語句,即通過print打印出Hello字符串。Python中單引號和雙引號沒有區別,因爲這裏使用雙 引號修飾Python語句,因此使用單引號修飾字符串。
4、gdb調試器
gdb是Linux下常用的一款命令行調試器,擁有十分強大的調試功能。本實驗中需要用到的gdb命令如下:
5、彙編基礎
讀懂常見的彙編指令是CTF競賽中PWN解題的基本要求,本實驗中需要理解的彙編指令如下:
注:圖爲AT&T風格的彙編指令。
彙編語言中,esp寄存器用於指示當前函數棧幀的棧頂的位置,函數中局部變量都存儲在棧空間中,棧的生長方向是向下的(即從高地址往低地址方向生長)。
ebp存儲着當前函數棧底的地址,棧底通常作爲基址,可以通過棧底地址和偏移相加減來獲取變量地址。
緩衝區溢出是指當計算機向緩衝區內填充數據位數時超過了緩衝區本身的容量,使得溢出的數據覆蓋在合法數據上,理想的情況是程序檢查數據長度並不允許輸入超 過緩衝區長度的字符,但是絕大多數程序都會假設數據長度總是與所分配的儲存空間相匹配,這就爲緩衝區溢出埋下隱患。
實驗描述:
主機/home/test/1目錄下有一個pwn1程序,執行這個程序的時候可以輸入數據進行測試,pwn1程序會輸出Please try again.的提示信息,請對pwn1程序進行逆向分析和調試,找到程序內部的漏洞,並構造特殊的輸入數據,使之輸出Congratulations, you pwned it.信息。
實驗步驟:
1、源碼審計
(在沒有源代碼的情況下,我們通常使用IDA Pro對二進制程序進行逆向分析,使用IDA的Hex-Rays插件可以將反彙編代碼還原爲C語言僞代碼,可以達到類似源代碼的可讀效果,在後期的實驗中會專門對IDA的使用進行講解)
使用cd /home/test/1切換到程序所在目錄,執行cat pwn1.c即可看到源代碼:
使用gets函數讀取輸入數據時,並不會對buffer緩衝區的長度進行檢查,輸入超長的輸入數據時會引發緩衝區溢出。
2、使用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,按下 Enter鍵程序就在斷點處斷下了,然後在gdb中輸入x $esp+0x5C,查看modified變量的值已經被修改成了0x00000042,而0x42就是字符’B’的ASCII值,表明我們成功用輸入數據的第65個字節覆蓋了modified變量:
在gdb中連續兩次執行ni命令(用來斷點、定位),可以看到je指令沒有跳轉,說明modified的值不爲0,程序進入輸出通過信息的if語句分支,然後在gdb中輸入c命令就可以讓程序繼續執行,看到輸出了通過提示信息:
3、體驗溢出攻擊效果
通過上面的步驟已經知道了如果控制輸入數據來進行攻擊,以達到進入if語句分支的目的。下面我們就可以通過構造輸入數據進行攻擊了。
還沒有退出gdb,輸入q命令就可以退出gdb。下面通過python語句構造輸入數據,然後通過管道傳給pwn1程序,執行命令python -c "print 'A'*64+'B'" | ./pwn1
實驗總結:
1、linux安裝gdb:sudo apt-get install gdb
2、gdb調試:gdb 文件名
3、查看main函數彙編代碼,在gdb執行命令:disas main
4、gdb設置斷點:
源文件的某一行設置斷點:break/b 行號
一個特定的函數設置斷點:break/b 函數名
設置條件斷點:break/b 行號 if 條件
5、gdb刪除斷點:delete 行號
6、查看斷點信息並保存:info b
7、運行調試的程序:run/r
8、跳轉到斷點命令(countine 簡寫c):c
9、退出gdb命令:quit/q
10、當使用gdb調試一個正在運行的進程時參考:https://blog.csdn.net/zxh2075/article/details/76850092
更多GDB命令參考:gbd命令