1. 程式人生 > >利用IDA Python靜態分析函數調用路徑

利用IDA Python靜態分析函數調用路徑

item cell 定義 人工分析 偽代碼 復雜 輸出 結束 next

在挖掘設備的固件漏洞時,會面臨沒有源代碼、無法動態跟蹤調試的情況,此時就需要進行靜態的人工分析。在靜態人工分析過程中,往往需要圍繞危險函數、用戶輸入提取需要重點分析的執行路徑,以有效縮小分析範圍。本文利用IDA Python腳本,實現了自動提取函數正、反向調用關系的功能,可有效輔助分析危險函數調用路徑,用戶輸入流向等。

一、問題描述
近期在研究某款設備,由於該設備使用MIPS架構,IDA Pro的F5無法使用,安裝的RetDec插件也不給力,給出的偽代碼不忍直視;還有不少代碼IDA Pro沒有識別出來,且無法識別庫函數;此外,固件使用傳統的嵌入式操作系統(非類Linux)實現,不存在進程等高級概念,因此也沒有解決動態調試的問題。在這種情況下,似乎只能人工靜態分析了。

進行初步分析之後,發現需要先解決兩個問題:一是將IDA pro無法識別的代碼強制轉換成代碼,以完善IDA pro的交叉引用關系;二是以文本方式提取函數調用關系(正向、反向),取IDA pro的Xrefs graph to和Xrefs graphp from功能,IDA pro的這兩個功能對稍微復雜一定的文件根本不存在實用性——顯示的圖形根本看不清。

二、強制轉換未解析的代碼
針對第一個問題,考慮到MIPS的指令均為32bit,可利用IDAPython遍歷指定的地址空間,把未定義的部分全部轉換成代碼。具體的代碼如下:

def define_func(beg, end):

cur = beg

if beg%4 != 0:

    cur = beg + 4 - beg%4 # 對齊

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

使用時步驟如下:

1、按shift+f2,在Execute script窗口中,Script language選擇Python

2、把上述代碼粘貼到Please enter script body中

3、點擊Run,關閉Execute script窗口。

4、在Output window下方的Python【IDC】按鈕右側,執行define_func(0x8000000,0x80002000),參數僅作示例,根據實際情況調整

三、獲取函數調用樹
1、正向調用樹
正向調用樹以指定函數為起點,根據指定遞歸深度,獲取其所有子函數,通常應用於跟蹤用戶輸入數據的流向。實現思路如下:遍歷指定函數(由參數指定)代碼,如果當前指令為函數調用,則遞歸,直到達到遞歸深度或者沒有子函數的函數。具體代碼如下:

import idautils

call_chain = [] # 存放正向調用鏈信息

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)

# 獲取所有子函數

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)

# 嵌套結束條件

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

# 深度優先

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()

使用方法參照“強制轉換未解析的代碼”一節中的方法,調用gen_call_chain函數即可。gen_call_chain函數的第一個參數是函數名,第二參數是遞歸的次數限制,如果為-1,則會一直遞歸到葉子函數(無子函數的函數)。在生成調用樹時,每條調用路徑對應一行文本,在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、反向調用樹
反向調用樹以指定函數為起點,根據指定遞歸深度,獲取其所有父函數,通常應用於跟蹤危險函數被調用的路徑。實現思路如下:先獲取引用指定函數(由參數指定)的函數,然後依次遞歸,直到達到遞歸深度或者沒有父函數的函數。具體代碼如下:

import idautils

r_call_chain = [] # 存放反向調用鏈信息

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)

# 嵌套結束條件 

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

# 深度優先

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() # 如果沒有引用函數,彈出當前函數

        break

使用方法參照“強制轉換未解析的代碼”一節中的方法,調用gen_r_call_chain函數即可。gen_r_call_chain函數的第一個參數是函數名,第二參數是遞歸的次數限制,如果為-1,則會一直遞歸到頂層函數(無父函數的函數)。在生成調用樹時,每條調用路徑對應一行文本,在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

四、小結
通過上述IDAPython腳本,可方便獲取指定函數的調用樹。調用樹的輸出有兩處,一處IDA pro的Output window;另一處是指定的文件,文件路徑是硬編碼的,各位看官可自行修改。暫時沒有以插件方式實現,有興趣的同學可以嘗試下。

利用IDA Python靜態分析函數調用路徑