跳转至

IDA 强大的插件 IDApython

idc 和 IDApython 帮助分析者利用与 IDA 进行交互,得到自动化分析程序的结果

IDApython

IDApython 的安装可以很容易在网上查到,但是对于 IDApython 教程的资料除了看到的一个官方文档以外没看到特别多特别好的资料,于是想稍微记录一下自己搞 IDApython 遇到的一些坑和 IDApython 常用的函数 (希望学弟和学妹明年做的时候不会没资料 =.=

安装 IDApython

首先确认自己的 IDApython 是否装好了,有两种办法。

一种从 IDA 上面的 File->Script Command 打开之后看 Scripting language 是否是存在 IDC 和 Python, 然后可以测试一下库是否全,基本上可以利用一个小脚本检测 import 库是否会报错

import idaapi
import idc
import idautils

if __name__ == "__main__":
    fp = open("Test.txt", "w")
    fp.write("Success\n")
    fp.close()

当然运行脚本的时候需要用 IDA 打开一个随便的文件,然后运行完脚本可以在那个文件的那一层里面多出来一个 Test.txt 文件,里面写着 Success 就说明没问题,否则库就是没装全

另外一种办法就是在 IDA 最下面有一个 Output window 下面有一个可选的,查看是否存在 IDC 和 Python,在里面选 Python 然后尝试 import 三个库就可以了,没报错就没问题。import 一个不存在的库试一下 import requests 就会发现报错

(如果没安装好,可以直接查询吾爱破解 IDA 7.0 可以看到一个已经绿色版+配好 Python 的 IDA

一些脚本

下面是我看到的一些脚本,我拿到本地测试过能跑的

example 01

打印全部函数的名字

# -*- coding: utf-8 -*-
from idaapi import *
from idautils import *
import idc

idc.Wait()              # 单线程的感觉,分析完一个之后才会运行后续脚本
ea = BeginEA()          # 找到程序 _start (针对 ELF 而言) 返回是 <type 'long'>

fp = open("Z:\\home\\vangelis\\Desktop\\rop_fun_output.txt", "w")
fp.write("check\n")

idx = 0

# 在.text段中 SegStart() 找到程序地址最小的汇编指令的地址,SegEnd() 找到程序地址最大的汇编指令的地址

for funcea in Functions(SegStart(ea), SegEnd(ea)):  # Functions() 返回为 <type 'generator'>
    functionName = GetFunctionName(funcea)          # funcea 类型为 <type 'long'>  为地址 GetFunctionName() 将地址转换为函数名
    fp.write("%d " % idx)
    fp.write(functionName + "\n")
    idx += 1

fp.close()
idc.Exit(0)             # 在脚本运行结束之后能够自动关闭 IDA
idc Wait()
Wait():
$ grep -H "Wait" ./*.py
./idc_bc695.py:def Wait(): return auto_wait()

idc.py 
 412   │ def auto_wait():
 413   │     """
 414   │     Process all entries in the autoanalysis queue
 415   │     Wait for the end of autoanalysis
 416   │ 
 417   │     @note:    This function will suspend execution of the calling script
 418   │             till the autoanalysis queue is empty.
 419   │     """
 420   │     return ida_auto.auto_wait()

在 IDA 文件里面用 powershell 打开,运行 .\ida.exe -c -S"test.py" input_file 即可, -c -S 是两个参数,

然后我神经刀,跑到 linux 里面来试行不行,我的 IDA 放在 ~/ 目录下,然后运行脚本的时候开始报错,后来觉得是因为用的 wine 调用的 ida.exe 所以在 -S 里面还是要用 windows 下反斜杠而不是斜杠

# vangelis @ vangelis-PC in ~/IDA [22:47:42] C:2
$ ./ida.exe -c -S".\Vangelis\test.py" ~/Desktop/Analysis_File/rop/rop
同时在脚本里面也是要用映射过去的地址,所以用的 Z:\\home\\vangelis\\Desktop\\rop_fun_output.txt

后来再改进一下,变成了 nohup ./ida.exe -c -S".\Vangelis\test.py" ~/Desktop/Analysis_File/rop/rop > outfile 2>&1 & 把标准输出和错误全部输出到 outfile 文件中

再后来觉得每次开 IDA 十分不舒服就查了一下运行 IDA 的参数,最后用的 -B -S -B batch mode. IDA will generate .IDB and .ASM files automatically 因为最后会产生相应的 .idb 或者 .asm 所以最后处理一下,不处理其实也没什么

example 02

寻找危险函数的被引用的地址

# -*- coding: utf-8 -*-
from idaapi import *
from idautils import *
import idc

idc.Wait()
danger_funcs = ["strcpy", "read", "gets"]

fp = open("Z:\\home\\vangelis\\Desktop\\Analysis_File\\rop\\rop_danger_call.txt", "w")
fp.write("check\n")

for func in danger_funcs :
    fp.write("#############\n")
    fp.write(func + "\n")
    addr = LocByName(func)
    if addr != BADADDR:
        cross_regs = CodeRefsTo(addr, 0)
        for ref in cross_regs:
            fp.write(str(hex(ref)) + "\n")

fp.close()
idc.Exit(0)
idc LocByName

经过尝试 LocByName 函数存在则返回函数地址,否则返回 0xffffffff (32位) 而 BADADDR 就是 0xffffffff (32位)

Python>hex(BADADDR)
0xffffffffL
Python>hex(LocByName("ABCD"))
0xffffffffL

$ grep -H "LocByName" ./*.py
./idc_bc695.py:def LocByName(name): return get_name_ea_simple(name)

$ grep -H "get_name_ea_simple" ./*.py
./idc.py:def get_name_ea_simple(name):

1879   │ def get_name_ea_simple(name):
1880   │     """
1881   │     Get linear address of a name
1882   │ 
1883   │     @param name: name of program byte
1884   │ 
1885   │     @return: address of the name
1886   │              BADADDR - No such name
1887   │     """
1888   │     return ida_name.get_name_ea(BADADDR, name)

  72   │ __EA64__ = ida_idaapi.BADADDR == 0xFFFFFFFFFFFFFFFFL
  73   │ WORDMASK = 0xFFFFFFFFFFFFFFFF if __EA64__ else 0xFFFFFFFF

idatils CodeRefsTo

找到 to 该地址的一个列表

Python>CodeRefsTo(0x806d290L, 0)
<generator object refs at 0x000000000C26B2D0>

  68   │ def CodeRefsFrom(ea, flow):
  69   │     """
  70   │     Get a list of code references from 'ea'
  71   │
  72   │     @param ea:   Target address
  73   │     @param flow: Follow normal code flow or not
  74   │     @type  flow: Boolean (0/1, False/True)
  75   │
  76   │     @return: list of references (may be empty list)
  77   │
  78   │     Example::
  79   │
  80   │         for ref in CodeRefsFrom(get_screen_ea(), 1):
  81   │             print ref
  82   │     """
  83   │     if flow == 1:
  84   │         return refs(ea, ida_xref.get_first_cref_from, ida_xref.get_next_cref_from)
  85   │     else:
  86   │         return refs(ea, ida_xref.get_first_fcref_from, ida_xref.get_next_fcref_from)

example 03

打印函数栈中的局部变量

from idaapi import *
from idautils import *
import idc
import types

if __name__ == "__main__":
    idc.Wait()
    fp = open("Z:\\home\\vangelis\\Desktop\\Analysis_File\\rop\\stack-member.txt", "w")
    fp.write("check\n")

    ea = BeginEA()
    for func in Functions(SegStart(ea), SegEnd(ea)):
        funcName = GetFunctionName(func)
        #  if(funcName != "overflow"):
            #  continue
        fp.write("###############\n")
        fp.write("Function Name is %s\n" % funcName)

        stack_frame = GetFrame(func)
        frame_size = GetStrucSize(stack_frame)

        fp.write("frame_size: %d\n" % frame_size)

        frame_counter = 0

        while frame_counter < frame_size:
            stack_var = GetMemberName(stack_frame, frame_counter)
            if type(stack_var) == type('a'):
                fp.write("[*] Function:%s -> Stack Variable:%s(%d bytes)\n" % (funcName, stack_var, GetMemberSize(stack_frame, frame_counter)))
                try :
                    frame_counter = frame_counter + GetMemberSize(stack_frame, frame_counter)
                except:
                    frame_counter += 1
            else :
                frame_counter += 1

    fp.close()
    idc.Exit(0)

注: stack frame 是从某一函数的 参数到栈顶 之间全部内容

idc GetMemberName

通过 stack id 和 offset 得到变量的名字

Python>stack = GetFrame(0x804887c) # "overflow" function's address
Python>hex(stack)
0xff00006cL
Python>GetMemberName(stack, 0xc)
var_C

$ grep -H "GetMemberName" ./*.py
./idc_bc695.py:def GetMemberName(id, member_offset): return get_member_name(id, member_offset)

5092   │ def get_member_name(sid, member_offset):
5093   │     """
5094   │     Get name of a member of a structure
5095   │
5096   │     @param sid: structure type ID
5097   │     @param member_offset: member offset. The offset can be
5098   │                           any offset in the member. For example,
5099   │                           is a member is 4 bytes long and starts
5100   │                           at offset 2, then 2,3,4,5 denote
5101   │                           the same structure member.
5102   │
5103   │     @return: None if bad structure type ID is passed
5104   │              or no such member in the structure
5105   │              otherwise returns name of the specified member.
5106   │     """
5107   │     s = ida_struct.get_struc(sid)
5108   │     if not s:
5109   │         return None
5110   │
5111   │     m = ida_struct.get_member(s, member_offset)
5112   │     if not m:
5113   │         return None
5114   │
5115   │     return ida_struct.get_member_name(m.id)

idc GetMemberSize

得到一个变量的结构所占字节数

5146   │ def get_member_size(sid, member_offset):
5147   │     """
5148   │     Get size of a member
5149   │
5150   │     @param sid: structure type ID
5151   │     @param member_offset: member offset. The offset can be
5152   │                           any offset in the member. For example,
5153   │                           is a member is 4 bytes long and starts
5154   │                           at offset 2, then 2,3,4,5 denote
5155   │                           the same structure member.
5156   │
5157   │     @return: None if bad structure type ID is passed,
5158   │              or no such member in the structure
5159   │              otherwise returns size of the specified
5160   │              member in bytes.
5161   │     """
5162   │     s = ida_struct.get_struc(sid)
5163   │     if not s:
5164   │         return None
5165   │
5166   │     m = ida_struct.get_member(s, member_offset)
5167   │     if not m:
5168   │         return None
5169   │
5170   │     return ida_struct.get_member_size(m)


5173   │ def get_member_flag(sid, member_offset):
5174   │     """
5175   │     Get type of a member

其他函数

取值
Byte(ea) Word(ea) Dword(ea) Qword(ea) GetFloat(ea) GetDouble(ea)
GetManyBytes(ea, size, use_dbg = False)
GetString(ea, length = -1, strtype = STRTYPE_C)

GetFlags(ea).isCode() / .isData() / .isUnknown()

数据或代码的 to / from
DataRefsTo(ea) DataRefsFrom(ea) | XrefsTo(ea, flag=0) XrefsFrom(ea, flag=0) / CodeRefsTo(ea, flag=0) CodeRefsFrom(ea, flag=0) 

分割函数中指令地址
FuncItems(start) 一般和 GetDisasm() 一起使用

 317   │ def FuncItems(start):
 318   │     """
 319   │     Get a list of function items
 320   │ 
 321   │     @param start: address of the function
 322   │ 
 323   │     @return: ea of each item in the function
 324   │     """
 325   │     func = ida_funcs.get_func(start)
 326   │     if not func:
 327   │         return
 328   │     fii = ida_funcs.func_item_iterator_t()
 329   │     ok = fii.set(func)
 330   │     while ok:
 331   │         yield fii.current()
 332   │         ok = fii.next_code()

反汇编
GetDisasm(ea)

Patch 文件
PatchByte(ea, value) PatchWord(ea, value) PatchDword(ea, value)

取当前地址
here() ScreenEA()

实例

下午写了一个用来查某些函数中的库函数调用的脚本 该查询了一下 main 函数和 main 函数开启线程中函数的调用,发现了一个很强的函数 GetDisasm() 可以将 IDA 中分析结果也打印出来,意思就是 比如 call esi ,在GetDisasm()会打印call esi ; Function 这样写分析脚本起来就很容易,以下脚本是放在 IDA 中跑的,所以没加 idc.Wait()idc.Exit(0)

实例的题目我放在 github 上面了 Lab05

# -*- coding: utf-8 -*-

from idaapi import *
from idautils import *
import idc

# github idapython ex_import.py
def Enum_Import_WinApi():
    def imp_cb(ea, name, offset):
        if name:
            WinApiDir[ea] = name
        # True -> Continue enumeration
        # False -> Stop enumeration
        return True

    WinApiDir = {}
    WinDll = ['']
    nimps = get_import_module_qty()
    for i in xrange(0, nimps):
        name = get_import_module_name(i)

        enum_import_names(i, imp_cb)

    return WinApiDir

def AddAddr(CalledAddr, CallWinApi, WinApiDir, CalledName=""):
    if CalledAddr != 0:
        CalledName = WinApiDir[CalledAddr]

    if CalledName not in CallWinApi:    # 如果 CallWinApi 里面没有,就添加
        CallWinApi.append(CalledName)

'''
def SearchFunc(addr):                   # 原本想用作向上查找 mov 的函数,但是发现 GetDisasm 是个神仙函数
    return
'''

def SearchWinApi(ea, CallWinApi, WinApiDir):
    for addr in FuncItems(ea):
        if GetMnem(addr) == "call":                 # 判断指令是否为 call
            CalledAddr = GetOperandValue(addr, 0)        # 得到被 call 函数地址
            #  print(GetDisasm(addr))
            #  print(GetOpnd(addr, 0))

            if CalledAddr in WinApiDir.keys():      # 如果函数地址在枚举的 WinApiDir 里面就尝试添加
                AddAddr(CalledAddr, CallWinApi, WinApiDir)

            else:                                   # 系统函数跳转一步才到结果 例如 strlen: jmp ds:__imp_strlen
                if GetMnem(CalledAddr) == "jmp":
                    CalledAddr = GetOperandValue(CalledAddr, 0)
                    AddAddr(CalledAddr, CallWinApi, WinApiDir)

                else :
                    op = GetOpnd(addr, 0)
                    if op == 'esi' or op == 'ebx' or op == 'eax' or op == 'ebp':
                        '''
                        可以不必这么写,因为 GetDisasm 会将 IDA 分析的 esi ebx eax ebp 打印出来
                        if op == 'ebp':             # 查看源码发现 op == ebp 时 只有调用 closesocket 的时候
                            if 'closesocket' in CallWinApi:
                                return
                            else :
                                CallWinApi.append('closesocket')
                        else :                      # 向上查找 mov 指令
                        '''
                        disasm = GetDisasm(addr)
                        CalledName = disasm[disasm.find(";")+1:]
                        AddAddr(0, CallWinApi, WinApiDir, CalledName)



if __name__ == "__main__":
    Main = [0x1000D02E]
    sub_func = [0x10001074, 0x10001365, 0x10001656]
    WinApiDir = {}
    CallWinApi = []
    flag = 1

    WinApiDir = Enum_Import_WinApi()
    #  print(WinApiDir)

    if flag :
        for ea in Main:
            SearchWinApi(ea, CallWinApi, WinApiDir)
    else:
        for ea in sub_func:
            SearchWinApi(ea, CallWinApi, WinApiDir)

    print(CallWinApi)

参考

脚本 | https://blog.csdn.net/ojshilu/article/details/12905405
博客 | https://blog.csdn.net/qq1084283172/article/details/64130118
part1-6 | https://unit42.paloaltonetworks.com/using-idapython-to-make-your-life-easier-part-1/
官方文档| https://www.hex-rays.com/products/ida/support/idapython_docs/