又一期的
wargame來了,這一期的wargame主要側重于逆向,基本上在gdb下把程序的思路弄清楚了,再利用一些簡單的滲透溢出技巧,就可以成功了。 Let's go!
依然是老方法,在游戲behemoth首頁可以找到登陸的服務器的賬號和密碼。ssh登陸上去,開始我們的wargame之旅。
登陸服務器,在游戲文件夾/behemoth下可以看到全部的可執行程序,首先執行./behemoth0,這是這一關待解決的程序。

程序讓我們輸入一個密碼,估計是將我們輸入的密碼和某個固定的密碼做匹配,可能是加密后最匹配,誰知道它怎么做。gdb走起。

從反匯編的代碼可能看到兩個令人激動的函數,一個是memfrob(),通過找男人(man)知道,這個函數是將輸入的指定長度的字符串中的字符與數字42做異或,既然是異或操作,就是可逆的。另一個函數是strcmp(),這個函數才是最令人激動的,從整個程序流程大概看到,程序通過scanf()獲取用戶輸入,然后通過memfrob()做異或處理,然后再送入strcmp()做匹配,所以,我們只要在strcmp()函數調用處下斷點,然后查看棧內容就可以得到真正的密碼了。

程序需要輸入的密碼是eatmyshorts,執行程序,這一關就過去了。

這一關程序也是要求輸入一個密碼,無法得到準備密碼,還是要gdb走起。


好吧,從逆向出來的匯編代碼看,程序很簡單,使用gets()得到用戶輸入,然后puts()輸出"Authentication failure.\nSorry."提示結束就可以了,沒有匹配,也就是沒有正確的密碼。不過從gets()這是一個不安全的函數,這里也沒有邊界檢查,說明存在緩沖區溢出漏洞,這是可以利用的。

通過驗證,確實存在緩沖區溢出漏洞,下面就是如何利用這個漏洞了,這個溢出利用前面的wargame已經玩得很多了。

這里程序執行似乎是要創建某個文件,多次執行發現,每次創建的文件名似乎都不相同,應該是跟pid相關。

還是看匯編代碼比較容易理解程序的執行意圖。

對反匯編代碼做個大概解釋,執行的流程如下
#!c
id = getpid()
s = lstat("touch " + str(id))
if(s & 0xf000 == 0x8000){
unlink(str(id);
system("touch " + ID);
}
sleep(0x7d0);
system("cat " + str(id));
程序在當前目錄下嘗試查找以自己pid為名的文件,如果不存在的話,就建立該文件,然后執行一個很長時間的sleep(),然后再打開文件。這里沒有輸入,不存在溢出,也沒有其它很明顯的漏洞。sleep(a_long_time)這個函數調用似乎沒有辦法越過,也沒法通過修改.got.plt來嘗試將sleep()替換成其它的函數。后來再參考了網上的類似writeup,發現這里在調用system()的時候使用的是相對路徑,既然是相對路徑,那么這個路徑就是我們可以控制的,通過修改PATH環境變量,這樣就可以使程序在路徑搜索的時候,先搜索我們指定的路徑,這樣就可以將touch程序替換成我們想要執行的程序,比如執行生成一個*shell*。這樣就順利通過這一關了。

這里需要注意路徑和偽 touch 程序的權限問題,開始我一直失敗就是因為權限配置的不對,浪費了不少時間按。
這里還是有一個輸入,既然有輸入就有可能有溢出點。

從程序執行的結果來看,程序打印了我們的輸入,猜測可能有緩沖區溢出,或者是格式化字符串漏洞,經過驗證,確實有格式化字符串漏洞,這樣就很容易了,基本上不需要看匯編代碼就可以搞定了。

從上面的測試我們發現,字符串存儲地址是在當前棧的第六個偏移的地方,即0x18(%esp),這個值在gdb中也是可以看到的。接下來就是構造攻擊程序了。我將shellcode放在環境變量中。

如上圖所示,環境變量即shellcode保存在0xffffd78c中,可能存在一點偏移,不過我們有Nop sled,Return Address處保存著程序原來的返回地址,我們需要將它修改為shellcode

這里執行的cat是為了防止管道關閉,在前面的wargame也使用了這個方法。其中需要注意的是,管道的右邊,一開始我使用的是相對路徑,導致總是沒法得到正確的結果,也不知道問題出現在哪里,后來無意中使用了絕對路徑,才搞定的。這里不知道為什么,等我之后看看管道的具體原理再做記錄。

這里執行的結果就給了一個提示,PID not found!,看樣子程序又是跟PID相關了。

從反匯編出來的代碼可以大概了解到程序的執行流程。


程序首先打開/tmp/pid文件,然后sleep(1)一秒,然后將文件內容輸出,這樣我們只要將文件軟鏈接到密碼文件,就可以讓程序打開密碼文件,同時輸出文件內容了。難點在于,我們如何知道程序的pid,雖然linux 下 pid 是遞增的,但是這也無法保證每次增加的就是 1 個單位。于是,我想到了一個不優雅的方法,我們先建立大量的可能的pid軟鏈接文件,然后一直執行程序,執行程序的pid會落到我們建立的文件范圍內的。
behemoth4.py
#!python
#!/usr/bin/env python
#coding=utf-8
import sys, os
passwd_file = "/etc/behemoth_pass/behemoth5"
if len(sys.argv) < 2:
print "usage %s [start pid num]"
sys.exit(-1)
try:
start_pid = int(sys.argv[1])
except ValueError:
print "usage %s [start pid num]"
sys.exit(-1)
# 建立 50 個符號鏈接文件
for i in range(50):
os.popen("ln -s " + passwd_file + " /tmp/" + str(i+start_pid))
# 執行 1000 次程序
for i in range(1000):
ret = os.popen("/behemoth/behemoth4")
ret = ret.read()
if not "not" in ret:
print ret
break
# 刪除所有建立的文件
for i in range(50):
os.popen("rm /tmp/" + str(i+start_pid))
主要是python下獲取子進程的pid太麻煩了,要不然這個爆破的代碼可以寫得更優雅一點。不過不影響結果,依然爆破出來了。這里需要注意的是,start pid即程序的參數應該要選得大一點,因為程序里面建立符號鏈接文件啟動了不少子進程,我這里在原來的基礎上增加了500個數,否則容易越過。

這一關執行程序沒有任何輸出,沒有提示,還是直接看反匯編出來的代碼吧。代碼





可以看到,程序首先打開了密碼文件/etc/behemoth_pass/behemoth6,然后建立了localhost:1337的socket,再用sendto函數將文件內容發送出去。程序的流程很明顯了,接下來我們只要監聽本地端口1337就可以收到密碼了。需要注意的是sendto是用UDP協議發送的,需要監聽該端口的UDP數據包。
使用瑞士軍刀nc進行監聽,然后在另外一個shell中執行程序,nc就會輸出收到的UDP數據包內容了。
shell 1 
shell 2 
shell 1 
這一關有兩個可執行程序,執行程序都沒有得到任何有意義的結果,還是直接看反匯編出來的代碼吧。


第一個主程序與第二個程序/behemoth/behemoth6_reader建立一個管道,然后通過管道讀取,如果讀到的內容等于HelloKitty,這樣就會執行execl(),建立一個shell。我們再看看第二個程序。第二個程序,執行就會輸出Couldn't open shellcode.txt!,看樣子是要建立一個名為shellcode.txt的文件,具體還是看看匯編代碼吧。

程序首先打開一個名為shellcode.txt的文件,然后將文件內容讀取到動態申請的存儲區,最后跳轉到動態存儲區,執行讀取到的內容。這樣就很容易理解了,我們在shellcode.txt文件中存放一段shellcode,這段shellcode只執行一個任務,就是向標準輸出stdout打印一段字符串HelloKitty就可以了。
section .text
global _start
_start:
mov ax, 0x7974 ; ty
movzx eax, ax ; zero-extend ax to 32bits
push eax
push 0x74694b6f ; oKit
push 0x6c6c6548 ; Hell
mov ecx, esp
xor ebx, ebx
inc ebx
xor edx, edx
mov dl, 10
xor eax, eax
mov al, 4
int 80h
; exit(0)
xor ebx, ebx
xor eax, eax
mov al, 1
int 80h
上面就是我寫的輸出HelloKitty的shellcode程序,匯編之后就可以得到可用的shellcode程序了。需要注意的是,behemoth6_reader程序使用的也是相對路徑,既然是相對路徑,就是我們可以控制的。在/tmp下建立文件夾behemoth6,將當前文件夾設置為/tmp/behemoth6,在該目錄下操作就可以了。

這一關的程序也是執行沒有任何輸出,所以說這次的wargame主要還是看逆向能力,基本上能逆向出來程序流程,后面的問題都很容易就可以解決了。匯編出來的程序很長,就不貼了。
唯一可能有點難度的是,在匯編代碼中有兩次調用call 0x8048420 <[email protected]>這一個函數,男人(man)告訴說,這個函數一般是<ctype.h>中的函數如isspace()、isalpha()等調用的,也就是在程序里執行的是類似的檢測,再加上從其它的代碼總體來看,得到結論。該程序檢測argv[1]中是否有Non-alpha字符存在,如果有的話那么就有可能是shellcode,這樣就提示錯誤。如圖所示,

如上圖所示,第一次執行時,argv[1]中有字符,存在,于是程序報錯,提示可能有shellcode存在。
不過程序只是檢測argv[1]中前256個字符,這樣我們只需要用alpha字符填充前面256個位置就可以了,后面可以進行緩沖區溢出利用。不過程序中將全部的環境變量都清空了,這就意味著我們不能將shellcode放置在環境變量中,需要找其它的地方存放,同時一開始,我將shellcode放在argv[1]字符串中的尾部,加上Nop sled,執行的時候提示Illegal Struction,猜測可能棧不可執行。最終我使用return-to-libc,將返回地址修改成system()函數的地址,同時將參數/bin/sh放置在argv[2]中,加上一定的猜測,最終搞定了。

又完成一期wargame了,現在的難度不是很大,都是一些很基礎的逆向、溢出的知識,不過作為一個新手,我深深地知道打好基礎才是最重要的,后面依然有很多好玩的。盡請期待~
想了解更多關于wargame的內容,請參考這里!