溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶(hù)服務(wù)條款》

利用IDA Python靜態(tài)分析函數(shù)調(diào)用路徑

發(fā)布時(shí)間:2020-07-22 14:14:57 來(lái)源:網(wǎng)絡(luò) 閱讀:4422 作者:watertoeast 欄目:安全技術(shù)

在挖掘設(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é)可以嘗試下。

向AI問(wèn)一下細(xì)節(jié)

免責(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)容。

AI