m1n9yu3-博客园

idapython脚本编写

api 和 例子

脚本执行方式

下面命令行

image-20201014191812578

file -> scriptcommand

image-20201014191927953

file -> script file

运行脚本文件

基础api

idc.ScreenEA() 获取当前光标所在行的地址, 返回一个 int 类型

MinEA() MaxEA() 获取当前idb 的最小地址 和 最大地址

idc.SegName(ea) ea是一个变量存储当前地址, 这个api 是获取当前地址所在的段

idc.GetDisasm(ea) 获取当前地址的反汇编代码

idc.GetMnem(ea) 获取当前地址的操作码

idc.GetOpnd(ea,0) 获取当前地址的操作数,第二个参数表示哪一个操作数

idc.SegStart(ea) 获取当前地址的段起始地址

idc.SegEnd(ea) 获取当前地址的段尾地址

idc.NextSeg(ea) 获取下一个段的起始地址

idautils.Segments() 返回一个可迭代的对象数组

idc.GetFunctionName(func) 通过地址,获取函数的名称

idautils.Functions() 返回一个可迭代的函数首地址数组,

idaapi.get_func(ea) 获取当前地址的函数首地址和尾地址

idc.NextFunction(ea) 获取下一个函数地址

idc.PrevFunction(ea) 获取前一个函数地址

idc.GetFunctionAttr(ea,FUNCATTR_START) 获取一个函数的边界地址

idc.GetDunctionAttr(ea,FUNCATTR_END) 获取一个函数的边界地址

idc.NextHead(当前地址,函数结尾) 获取一个当前指令的下一行地址,第二个参数为结尾

通过 api 打印所有 段名称 段起始地址 段结束地址

import idautils
import idc
for seg in idautils.Segments():
    print idc.SegName(seg),hex(idc.SegStart(seg)),hex(idc.SegEnd(seg))

打印所有段中的所有函数地址和名称

import idautils
import idc
for func in idautils.Functions():
    print hex(func),idc.GetFunctionName(func)
   

获取当前地址的函数首和尾

import idautils
import idc
ea = ida.ScreenEA()
print ea
func = idaapi.get_func(ea)
print type(func)
print "Start:0x%x , End:0x%x"%(func.startEA,func.endEA)

高级 api

idc.GetFunctionFlags(func)

拥有 9种状态

  1. FUNC_NORET 表示某个函数是否有返回值,本身的值是1,
  2. FUN_FAR 不常见,用于标志程序是否使用分段内存, 值为2
  3. FUN_USERFAR 不常见,官方文档描述为 "user has specified far-ness of the function" 它的值是 32
  4. FUN_LIB 表示用于寻找库函数的代码,它的值是4
  5. FUNC_STATIC 作用域是被该函数在编译的是否是一个静态函数, 在 c语言中静态函数被默认是认为全局的,如果作者吧这个函数定义为静态函数,那么这个函数只能被本文件中的函数访问.利用静态函数的判定我们可以更好地理解源代码的结构.
  6. FUNC_FRAME 表示函数是否使用了 ebp寄存器, 使用ebp寄存器的程序一般是windows 程序
  7. FUNC_BOTTOMBP 和 FUNC_FRAME 一样,用于跟着帧指针(ebp),它的作用是识别函数中帧指针是否等于堆栈指针 esp
  8. FUNC_HIDDEN 带有FUNC_HIDDEN 标志的函数意味着他们是隐藏的,这个函数需要展开才能查看
  9. FUNC_THUNK 表示这个函数是否是一个 thunk 函数, thunk 函数表示的是一个简单的跳转函数

idautils.Funcltems(ea) 来获取该函数中所有指令地址的集合

迭代器没有 len() 属性, 通过强制转换将其放入 list中可以实现获取指令的操作,

idc.NextHead(ea) idc.PrevHead(ea) 获取当前地址的下一条指令 或前一个 相当于 - 当前指令长度or +当前指令长度

idc.NextAddr(ea) idc.PrevAddr(ea) 获取当前地址的下一个地址 或前一个 相当于 -1 +1

使用 api 打印某个函数的所有汇编代码和地址

import idautils
import idc
ea = idc.ScreenEA()
print ea
start = idc.GetFunctionAttr(ea,FUNCATTR_START)
end = idc.GetFunctionAttr(ea,FUNCATTR_END)
cur_addr = start
while cur_addr < end:
    print hex(cur_addr),idc.GetDisasm(cur_addr)
    cur_addr = idc.NextHead(cur_addr,end)

枚举所有不会 ret 的函数

import idautils
ea = idc.ScreenEA()
print ea
for func in idautils.Functions():
    flags = idc.GetFunctionFlags(func)
    if flags & FUNC_NORET:
        print hex(func),"FUNC_NORET",GetFunctionName(func)

打印一个函数中所有指令

import idautils
ea = idc.ScreenEA()
print ea
dism_addr = list(idautils.Funcltems(ea))
print type(dism_addr)
print dism_addr
for line in dism_addr:
    print hex(line),idc.GetDisasm(line)

打印所有被调用的地址

有时候我们会逆向一个加壳的代码, 这时候知道代码中哪里进行了动态调用对分析是非常有帮助的. 一个动态的调用可能是由 call 或者 jmp 加上一个操作数来实现的, 比如 call eax, 或者 jmp edi. 我们可以通过下面的脚本将存在相应特征的指令打印处理.

import idautils
for func in idautils.Functions():
    flags = ida.GetFunctionFlags(func)
    if flags & FUNC_LIB or flags & FUNC_THUNK:
        continue
    dism_addr = list(idautils.FuncItems(func))
    for line in dism_addr:
        m = idc.GetMnem(line)
        if m == "call" or m == "jmp":
            op = idc.GetOpType(line,0)
            if op == o_reg:
                print "0x%x %s"%(line,idc.GetDisasm(line))

搜索api

idc.FindBinary(当前地址,flag,searcstr,radix=16) 来进行字节或者二进制的搜索, flag 代表搜索方向或条件

  • SEARCH_UP, SEARCH_DOWN 用来指明搜索方向
  • SEARCH_NEXT 用来获取下一个已经找到的对象
  • SEARCH_CASE 用来指明是否区分大小写
  • SEARCH_NOSHOW 来指明是否显示搜索的进度
  • SEARCH_UNICODE 用来将所有搜索字符串视为 Unicode

searcstr 是我们要查找的状态,

radix 参数在写处理器模块时使用, 一般用不到, 留空即可


idc.FindText(当前地址,flag,从当前地址开始搜索的行数,行中的坐标,searchar)

这个搜索的是 汇编代码中函数名称之类的, 不是内存中的字符串


idc.isCode(f) 如果该地址为代码则返回True

idc.isData(f) 如果该地址为数据返回true

idc.idUnknown(f) 无法鉴别是数据还是代码则返回True

idc.isHead(f) 如果为函数开头则返回true

idc.isTail(f) 如果为函数结尾则返回true

idc.GetFlags(地址) 以上一系列判断函数,都不能直接传递地址, 需要通过GetFlags(ea) 对地址进行转换


idc.FindCode(地址,标志) 获取下一个代码指令的地址, 比如我们需要获取某一块数据的结尾时,如果当前的 ea 是代码地址, idc.FindCode 将返回下一个代码指令的地址

idc.FindData(地址,标志) 获取下一个数据块类型地址的起始.

idc.Findlmmediate(地址,标志,值)

搜索字节

import idautils
pattern = '48 ff'   #inc rdx
addr = MinEA()
for x in range(0,5):
    addr = idc.FindBinary(addr,SEARCH_DOWN,pattern)

首先将 pattern 置为对应的字节, 然后使用 MinEA() 获取可执行文件的最开始的地址, 然后将 idc.FindBinary 的返回结果设置为 addr 变量

搜索字符串

import idautils
pattern = "check_value"
cur_addr = MinEA()
end_addr = MaxEA()
while cur_addr<end_addr:
    cur_addr = idc.FindText(cur_addr,SEARCH_DOWN,0,0,pattern)
    if cur_addr == idc.BADADDR:
        break
    else:
        print hex(cur_addr),idc.GetDisasm(cur_addr)
    cur_addr = idc.NextHead(cur_addr)

代码中 cur_addr 标记当前地址, end_addr 标记搜寻idb 的结束地址,通过函数FiindText 进行搜索. 我们在最后一行 cur_addr=idc.NextHead(cur_addr) 将每次操作的当前地址更新为下一行地址. 这样就可以搜索出全部的check_value.

利用 findcode 获取数据的结尾地址

import idautils
ea = idc.ScreenEA()
print hex(ea),idc.GetDisasm(ea)
addr = idc.FindCode(ea,SEARCH_DOWN|SEARCH_NEXT)
print hex(addr),idc.GetDisasm(addr)

搜寻所有带 0xa 的地址

import idautils
addr = MinEA()
while True:
    addr,operand = idc.Findlmmediate(addr,SEARCH_DOWN|SEARCH_NEXT,0X0A)
    if addr != BADADDR:
        print hex(addr),idc.GetDisasm(addr),"Operand",operand
    else:
        break

数据截取

idc.SelStart() 选中区域开始地址

idc.SelEnd() 选中区域结束地址

打印选中区域的字节

import idautils
start = idc.SelStart()
end = idc.SelEnd()

l = []
while start<end:
    l.append(idc.Byte(start))
    start += 1
print l
    

自动注释

idc.MakeComm(地址,注释内容str类型) 向指定地点添加注释

idc.GetCommentEx(地址,repeatable) 获取某地址的注释内容, repeatable 是一个布尔值

idc.SetFunctionCmt(ea,cmt,bool) ea为函数体中任意一处指令的地址, cmt 为需要注释的内容, bool 为布尔值, false 表示重复注释, true 为普通注释. 重复注释的意思就是生效后, 其他引用的地方也会自动生成注释

idc.GetFunctionCmt(ea,1) 获取某个函数的注释

自动给 xor eax,eax 类型的数据写注释

import idautils
for func in idautils.Functions():
    flags = idc.GetFunctionFlags(func)
    if flags & FUNC_LIB or flags & FUNC_THUNK:
        continue
    dism_addr = list(idautils.Funcltems(func))
    for ea in dism_addr:
        if idc.GetMnem(ea) == "xor":
            if idc.GetOpnd(ea,0) == idc.GetOpnd(ea,1):
                comment = "% s = 0" %(idc.GetOpnd(ea,0))
                idc.MakeComm(ea,comment)

查看刚才 xor 的注释

import idautils
for func in idautils:
    flags = idc.GetFunctionFlags(func)
    if flags & FUNC_LIB or flags & FUNC_THUNK:
        continue
    dism_addr = list(idautils.FuncItems(func))
    for ea in dism_addr:
        if idc.GetMnem(ea) == "xor":
            if idc.GetOpnd(ea,0) == idc.GetOpnd(ea,1):
                comment = "% s = 0"%(idc.GetOpnd(ea,0))
                idc.MakeComm(ea,comment)
                print idc.GetCommentEx(ea,False)

重命名 读取原始数据 api

idc.MakeName(ea,重命名内容) ea必须是 函数的起始地址.函数重命名,不知道放哪

idc.Byte(ea)

idc.Word(ea)

idc.Dword(ea)

idc.Qword(ea)

idc.GetFloat(ea)

idc.GetDouble(ea)

idc.GetManyBytes(ea,size,use_dbg=False) size 用于长度, use_dbg在调试内存时会用到, 一般不需要设置

将 main 函数重命名为 MAIN

import idautils
ea = idc.ScreenEA()
print hex(ea),idc.GetDisasm()
print idc.MakeName(ea,"MAIN")

总结

进一步全面了解IDAPython提供的函数可以参考官方文档:

https://www.hex-rays.com/products/ida/support/idapython_docs/

另外有更加扩展深入的学习资料:

https://unit42.paloaltonetworks.com/using-idapython-to-make-your-life-easier-part-1/

这是一系列的文章,国内蒸米大佬曾经在wooyun drops上翻译过,题目为IDAPython 让你的生活更滋润

以及一些安全研究者发出的IDAPython的实际应用文章

https://resources.infosecinstitute.com/saving-time-effort-idapython/#gref

https://www.thezdi.com/blog/2018/6/26/mindshare-variant-hunting-with-ida-python

https://www.fireeye.com/blog/threat-research/2018/01/simplifying-graphs-in-ida.html

虽然python2 语法看起来很别扭,但是自动化确实方便,加上自己由买不起 ida正版软件,凑合用吧

在这里给合天安全实验室打个广告,他们的课做的挺用心的

posted @ 2020-10-19 16:48  m1n9yu3  阅读(2934)  评论(4编辑  收藏  举报