ida python使用
idapython脚本
ida 中内置python2和idc两种形式的脚本
一般我们使用ida python
# 结构
主要的文件都在./ida/python
目录下,
主要是分为三个模块,
- idc提供了idc脚本兼容过来的api
- idaapi提供一些比较底层的操作
- idautils提供一些封装好的的函数
新版本增加了更多的模块,大多是对idaapi的扩展,文件多为: ./ida/python/ida_*.py
,
# 关于api兼容性
ida在7.0版本重新设计了api, 但是为了保证向之前版本兼容,设置了ida/python/idc_bc695.py
文件,
但是在新的7.4以后的版本中, 这个向后兼容的文件默认不会被加载,于是新版本中的很多函数(如Byte,Dword等)不能被使用,
可以在配置文件中进行修改: ida/cfg/idapython.cfg
中的AUTOIMPORT_COMPAT_IDA695
选项设置为yes即可开启,
# 地址
可以通过here()
函数或idc.get_screen_ea()
函数, 获取当前光标所在地址,
获取最大地址: idc.get_inf_attr(INF_MAX_EA)
,
获取最小地址:idc.get_inf_attr(INF_MIN_EA)
,
# 反编译
首先,要获取的地址记作(int)ea
,
反编译某行: (str) idc.generate_disasm_line((int)ea, (int)flag)
, 返回对应的反编译出的汇编代码,
- 其中flag为0或1,
获取某个地址反编译代码对应的某一部分:
获取助记符: (str)idc.print_insn_mnem((int)ea)
, 返回对应地址的反编译代码的助记符,
获取参数: (str)idc.print_operand((int)ea, (int)n)
, 返回地址对应的反编译代码的参数, 其中参数(n) + 1
代表第几个参数,
如
print_operand(ea, 0)
会返回第一个参数
ea = here()
print(idc.generate_disasm_line(ea, 0))
print(idc.print_insn_mnem(ea))
print(idc.print_operand(ea, 0))
print(idc.print_operand(ea, 1))
'''
mov eax, [rbp+var_4]
mov
eax
[rbp+var_4]
'''
2
3
4
5
6
7
8
9
10
11
12
# 段
获取地址所在段的段名, (str)idc.get_segm_name((int)ea)
,
获取地址所在段起始地址:(int)idc.get_segm_start((int)ea)
获取地址所在段的结束地址:
(int)idc.get_segm_end((int)ea)
获取所有的段首地址: (generator)idautils.Segments()
此函数没有参数,返回一个所有段起始地址的迭代器, 我们可以通过for循环遍历他
for i in idautils.Segments():
print("%s:\t0x%x\t0x%x" %(
idc.get_segm_name(i),
idc.get_segm_start(i),
idc.get_segm_end(i)))
'''
...
.fini: 0x400b74 0x400b7d
LOAD: 0x400b7d 0x400b80
.rodata: 0x400b80 0x400c1f
LOAD: 0x400c1f 0x400c20
.eh_frame_hdr: 0x400c20 0x400c84
LOAD: 0x400c84 0x400c88
.eh_frame: 0x400c88 0x400e28
.init_array: 0x601e10 0x601e18
....
'''
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 函数
# 常规
获取函数名:(str)idc.get_func_name((int)ea)
获取所有函数的首地址:(generator)idautils.Functions((int)start_addr, (int)end_addr)
该函数具有两个参数,表示在一个地址区间内的所有函数。
如果没有参数,则默认位从头到尾
for i in idautils.Functions():
print("0x%x\t%s" % (
i,
idc.get_func_name(i)
))
'''
...
0x602130 read
0x602138 __libc_start_main
0x602140 malloc
0x602148 setvbuf
0x602150 atoi
0x602158 exit
...
'''
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# func_t
对象
通过函数(ida_funcs.func_t)idaapi.get_func((int)ea)
会返回一个ida_funcs.func_t
对象, 他拥有许多的特性, 定义在ida_funcs.py
中,主要使用的应该是start_ea
和end_ea
,
func = idaapi.get_func(ea)
print("0x%x--0x%x" % (func.start_ea, func.end_ea))
2
# func_attr
属性
可以通过(int)idc.get_func_attr((int)ea, FUNCATTR_FLAG)
, 获取对应的属性,
其中返回对应函数的属性,可以通过flag & FUNC_NORET
判断,
# 关于数据
# 获取数据
对于程序内的地址可以直接引用, 然后获取其数据需要以对应的数据类型获取数据,如: Byte(ea)
, Dword(ea)
,
获取数据的脚本大致如下:
addr = 0xaaaa
arr = []
for i in range(xxx):
arr.append(Byte(addr + i))
arr.append(Dword(addr + 4 * i))
print(arr)
2
3
4
5
6
# 设置数据
设置数据类型,使用MakeQword(addr)
, MakeDword(addr)
, 将对应地址设置为所需要的类型,
# 修改数据
ida python 中提供对应的函数为:PatchByte(addr, byte)
会将地址addr中的数据替换为byte, 注意参数12分别为地址和数据,
一般常用在自解密的文件,我们使用ida python脚本可以自己解密出来正确的数据,
自解密的话我们也要对应的原本的数值,注意在程序内的取地址内数值用
Byte(addr)
,下面简单演示一个异或的情况的自解密。
addr = 0xaaaa
for i in range(xxxx):
PatchByte(addr +i, Byte(addr + i)^ 0x123)
2
3
# 字符串相关
使用GetStr(addr)
获取一个从指定位置开始的c语言风格字符串(以'\x00'结尾的), 也可以使用MakeStr(start_addr, end_addr)
设置指定长度的字符串,
warning
在ida 7.0中MakeStr会报错 (opens new window), 问题在于目录 ida/python 中python的api转接到idc的位置出现参数错误,修改如下:
# /ida/python/idc_bc695.py
# 原
def MakeStr(ea, endea): return create_strlit(ea, 0 if (endea) == ida_idaapi.BADADDR else endea-ea)
# 修改后
def MakeStr(ea, endea): return create_strlit(ea, endea)
2
3
4
5
# 设置注释
MakeComm(addr, dec)
# dump 程序
ida并没有直接提供dump的功能, 我们可以通过脚本实现,
import idaapi
data = idaapi.dbg_read_memory(start_address, data_length)
fp = open('path/to/dump', 'wb')
fp.write(data)
fp.close()
2
3
4
5
6
# 宏观结构和相关操作
# 符号
符号,命名称为Name, 使用Names()
可以获得相对应的所有位置的命名,
MakeName(addr, dec)
可以定名符号,
在设置命名时, ida中有一定的规则, 我们一般需要洗掉一些不能非法字符:
STRIP_CHARS = ['(', ')', '[', ']', '{', '}', ' ', '"']
REPLACE_CHARS = ['.', '*', '-', ',', ';', ':', '/', '\xb7']
def clean_function_name(in_str):
# Kill generic 'bad' characters
s = ""
for c in in_str.decode():
if c in string.printable:
s += c
for c in STRIP_CHARS:
s = s.replace(c, '')
for c in REPLACE_CHARS:
s = s.replace(c, '_')
return s
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 动态调试相关
然后在动调主要使用到的函数:
AddBpt(long Address)
: 在指定的地址设置断点
GetRegValue(string Register)
:获取一个寄存器的名称
SetRegValue(long Value, string Register)
设置寄存器的值
RunTo(long Address)
: 运行到指定的地址,然后停下。
GetDebuggerEvnt(WFNE_SUSP, -1)
:应该是获取调试某一过程中信息,一般就在RunTo()
后和操作寄存器前使用,解决脚本和调试异步而导致的寄存器读取报错的问题 (opens new window)
一般使用的:
- 用于程序运行, 获取相关位置,设定相关寄存器。
RunTo(xxxx)
GetDebuggerEvent(WFNE_SUSP, -1)
SetRegValue(xxx, 'xxx')
xxx = GetRegValue("xxx")
2
3
4
- 获取栈内数据:
RunTo(xxx)
GetDebuggerEvent(WFNE_SUSP, -1)
stack = GetRegValue("esp")
2
3
这样,运行到某个位置,获取esp/rsp的值,为栈顶,通过偏移可以获取相对应的栈内地址,然后使用Byte()
/ Dword()
来获取相关数据,如果需要的话我们还可以通过PatchByte(long Addr, byte)
来进行数据的修改,
- 程序多次运行
idapyhton似乎是不可以多次重复调动调试,但是我们可以通过修改eip/rip来让其重复运行一段位置,一般进行爆破。
这个时候要注意的是修改eip/rip回跳的位置,要慎重选择。
# 插件制作
一些ida插件的技巧。
# golang_rev
目前仿造lazy_ida写的一个插件,用于在golang逆向中快速恢复符号,设置字符串,已经ok:golang_rev (opens new window),