又一期的
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
的內容,請參考這里!