您好,登錄后才能下訂單哦!
在挖掘設(shè)備的固件漏洞時(shí),會(huì)面臨沒(méi)有源代碼、無(wú)法動(dòng)態(tài)跟蹤調(diào)試的情況,此時(shí)就需要進(jìn)行靜態(tài)的人工分析。在靜態(tài)人工分析過(guò)程中,往往需要圍繞危險(xiǎn)函數(shù)、用戶(hù)輸入提取需要重點(diǎn)分析的執(zhí)行路徑,以有效縮小分析范圍。本文利用IDA Python腳本,實(shí)現(xiàn)了自動(dòng)提取函數(shù)正、反向調(diào)用關(guān)系的功能,可有效輔助分析危險(xiǎn)函數(shù)調(diào)用路徑,用戶(hù)輸入流向等。
一、問(wèn)題描述
近期在研究某款設(shè)備,由于該設(shè)備使用MIPS架構(gòu),IDA Pro的F5無(wú)法使用,安裝的RetDec插件也不給力,給出的偽代碼不忍直視;還有不少代碼IDA Pro沒(méi)有識(shí)別出來(lái),且無(wú)法識(shí)別庫(kù)函數(shù);此外,固件使用傳統(tǒng)的嵌入式操作系統(tǒng)(非類(lèi)Linux)實(shí)現(xiàn),不存在進(jìn)程等高級(jí)概念,因此也沒(méi)有解決動(dòng)態(tài)調(diào)試的問(wèn)題。在這種情況下,似乎只能人工靜態(tài)分析了。
進(jìn)行初步分析之后,發(fā)現(xiàn)需要先解決兩個(gè)問(wèn)題:一是將IDA pro無(wú)法識(shí)別的代碼強(qiáng)制轉(zhuǎn)換成代碼,以完善IDA pro的交叉引用關(guān)系;二是以文本方式提取函數(shù)調(diào)用關(guān)系(正向、反向),取IDA pro的Xrefs graph to和Xrefs graphp from功能,IDA pro的這兩個(gè)功能對(duì)稍微復(fù)雜一定的文件根本不存在實(shí)用性——顯示的圖形根本看不清。
二、強(qiáng)制轉(zhuǎn)換未解析的代碼
針對(duì)第一個(gè)問(wèn)題,考慮到MIPS的指令均為32bit,可利用IDAPython遍歷指定的地址空間,把未定義的部分全部轉(zhuǎn)換成代碼。具體的代碼如下:
def define_func(beg, end):
cur = beg
if beg%4 != 0:
cur = beg + 4 - beg%4 # 對(duì)齊
end = end - end%4
while cur < end:
if ida_kernwin.user_cancelled():
print('Cancelled')
break
cur_func = ida_funcs.get_func(cur)
print("cur 0x%08x" % cur)
if cur_func is None:
if ida_funcs.add_func(cur):
cur = ida_funcs.get_func(cur).endEA
else:
cur = cur + 4
else:
cur = cur_func.endEA
使用時(shí)步驟如下:
1、按shift+f2,在Execute script窗口中,Script language選擇Python
2、把上述代碼粘貼到Please enter script body中
3、點(diǎn)擊Run,關(guān)閉Execute script窗口。
4、在Output window下方的Python【IDC】按鈕右側(cè),執(zhí)行define_func(0x8000000,0x80002000),參數(shù)僅作示例,根據(jù)實(shí)際情況調(diào)整
三、獲取函數(shù)調(diào)用樹(shù)
1、正向調(diào)用樹(shù)
正向調(diào)用樹(shù)以指定函數(shù)為起點(diǎn),根據(jù)指定遞歸深度,獲取其所有子函數(shù),通常應(yīng)用于跟蹤用戶(hù)輸入數(shù)據(jù)的流向。實(shí)現(xiàn)思路如下:遍歷指定函數(shù)(由參數(shù)指定)代碼,如果當(dāng)前指令為函數(shù)調(diào)用,則遞歸,直到達(dá)到遞歸深度或者沒(méi)有子函數(shù)的函數(shù)。具體代碼如下:
import idautils
call_chain = [] # 存放正向調(diào)用鏈信息
def gen_call_chain(func_name, osintneting):
del call_chain[:]
f_call_out = open('d:\\call.csv', 'w')
get_my_callee(func_name, osintneting, f_call_out)
f_call_out.close()
def get_my_callee(func_name, osintneting, fl):
#print('call %s %d' % (func_name, osintneting))
if ida_kernwin.user_cancelled():
print('Cancelled')
fl.close()
exit()
str = '{0}\t'.format(func_name)
call_chain.append(str)
addr = get_name_ea(0, func_name)
# 獲取所有子函數(shù)
dism_addr = list(idautils.FuncItems(addr))
xref_froms = []
for ea in dism_addr:
if ida_idp.is_call_insn(ea) is False:
continue
else:
callee = get_first_fcref_from(ea)
if callee != addr:
xref_froms.append(callee)
xref_froms = set(xref_froms)
# 嵌套結(jié)束條件
osinteneting_end = False
if len(xref_froms) == 0:
osinteneting_end = True
elif osintneting == -1:
osinteneting_end = False
elif osintneting == 1:
osinteneting_end = True
if osinteneting_end is True:
for callee in call_chain:
sys.stdout.write(callee)
fl.write(callee)
sys.stdout.write('\r\n')
fl.write('\r\n')
call_chain.pop()
return
# 深度優(yōu)先
for xref_from in xref_froms:
callee_name = get_func_name(xref_from)
if osintneting == -1:
get_my_callee(callee_name, -1, fl)
else:
get_my_callee(callee_name, osintneting - 1, fl)
call_chain.pop()
使用方法參照“強(qiáng)制轉(zhuǎn)換未解析的代碼”一節(jié)中的方法,調(diào)用gen_call_chain函數(shù)即可。gen_call_chain函數(shù)的第一個(gè)參數(shù)是函數(shù)名,第二參數(shù)是遞歸的次數(shù)限制,如果為-1,則會(huì)一直遞歸到葉子函數(shù)(無(wú)子函數(shù)的函數(shù))。在生成調(diào)用樹(shù)時(shí),每條調(diào)用路徑對(duì)應(yīng)一行文本,在IDA pro的Output window的輸出如下
Python>gen_call_chain('start', 5)
start sub_4010E0 sub_400DD0 sub_401B40 sub_401B80
start sub_4010E0 sub_400DD0 sub_401B40 sub_401A00
start sub_4010E0 sub_400DD0 sub_444750 sub_472EB0
start sub_4010E0 sub_400DD0 sub_444750 sub_43F8C0
start sub_4010E0 sub_400DD0 sub_444750 sub_472FE0
start sub_4010E0 sub_400DD0 sub_444750 sub_43F920
start sub_4010E0 sub_400DD0 sub_40EFF0 sub_40EE10
2、反向調(diào)用樹(shù)
反向調(diào)用樹(shù)以指定函數(shù)為起點(diǎn),根據(jù)指定遞歸深度,獲取其所有父函數(shù),通常應(yīng)用于跟蹤危險(xiǎn)函數(shù)被調(diào)用的路徑。實(shí)現(xiàn)思路如下:先獲取引用指定函數(shù)(由參數(shù)指定)的函數(shù),然后依次遞歸,直到達(dá)到遞歸深度或者沒(méi)有父函數(shù)的函數(shù)。具體代碼如下:
import idautils
r_call_chain = [] # 存放反向調(diào)用鏈信息
def gen_r_call_chain(func_name, osintneting):
del r_call_chain[:]
f_r_call_out = open('d:\\r_call.csv', 'w')
get_my_caller(func_name, osintneting, f_r_call_out)
f_r_call_out.close()
def get_my_caller(func_name, osintneting, fl):
if ida_kernwin.user_cancelled():
print('Cancelled')
fl.close()
exit()
str = '{0}\t'.format(func_name)
r_call_chain.append(str)
addr = get_name_ea(0, func_name)
addr_ref_to = get_first_fcref_to(addr)
# 嵌套結(jié)束條件
osinteneting_end = False
if addr_ref_to == BADADDR:
osinteneting_end = True
elif osintneting == -1:
osinteneting_end = False
elif osintneting == 1:
osinteneting_end = True
if osinteneting_end is True:
length = len(r_call_chain)
for idx in range(length):
fl.write(r_call_chain[length - idx - 1])
sys.stdout.write(r_call_chain[length - idx - 1])
fl.write("\n")
sys.stdout.write('\r\n')
r_call_chain.pop()
return
# 深度優(yōu)先
while (addr_ref_to != BADADDR) and (addr_ref_to != addr):
parent_func_name = get_func_name(addr_ref_to)
get_my_caller(parent_func_name, osintneting - 1, fl)
addr_ref_to = get_next_fcref_to(addr, addr_ref_to)
if addr_ref_to == BADADDR:
r_call_chain.pop() # 如果沒(méi)有引用函數(shù),彈出當(dāng)前函數(shù)
break
使用方法參照“強(qiáng)制轉(zhuǎn)換未解析的代碼”一節(jié)中的方法,調(diào)用gen_r_call_chain函數(shù)即可。gen_r_call_chain函數(shù)的第一個(gè)參數(shù)是函數(shù)名,第二參數(shù)是遞歸的次數(shù)限制,如果為-1,則會(huì)一直遞歸到頂層函數(shù)(無(wú)父函數(shù)的函數(shù))。在生成調(diào)用樹(shù)時(shí),每條調(diào)用路徑對(duì)應(yīng)一行文本,在IDA pro的Output window的輸出如下:
Python>gen_r_call_chain('sub_4432E0', 5)
start sub_4010E0 sub_400DD0 sub_4432E0
sub_47D3F0 sub_4767D0 sub_474770 sub_4432E0
sub_496370 sub_4767D0 sub_474770 sub_4432E0
sub_480930 sub_47D020 sub_4432E0
sub_48C450 sub_47D020 sub_4432E0
sub_499C60 sub_47D020 sub_4432E0
sub_480930 sub_47D020 sub_4432E0
sub_48C450 sub_47D020 sub_4432E0
四、小結(jié)
通過(guò)上述IDAPython腳本,可方便獲取指定函數(shù)的調(diào)用樹(shù)。調(diào)用樹(shù)的輸出有兩處,一處IDA pro的Output window;另一處是指定的文件,文件路徑是硬編碼的,各位看官可自行修改。暫時(shí)沒(méi)有以插件方式實(shí)現(xiàn),有興趣的同學(xué)可以嘗試下。
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。