IDAPython学习笔记与traceRe的初步实现

Posted by Qfrost on 2021-05-18
Estimated Reading Time 10 Minutes
Words 2.3k In Total
Viewed Times

PS:鉴于IDA7.5已经全面普及,IDAPython也由原来的Python2变成了Python3,同时带来的是IDA API的全面更新,故重写本篇博客

作为逆向菜鸡,已经重度依赖地表最强工具 ———— IDA Pro了,“OD是不可能OD的,只有靠IDA Pro才能勉强维持下去的样子”(你看那迷人的笑容,有了它还要npy干什么 逃…

IDA Pro当然是最强的静态分析工具,但是在动调调试上,它相较于OD仍然还是有诸多不足之处。IDAPython环境提供了大量的IDA API,一定程度上缓解了它动调上的缺陷。今天这里做一个IDAPython的学习笔记

IDAPython2学习笔记

指令相关的API

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
例子:
# 0x100000E8C jnz loc_100000EAB
# 0x100000e92 lea rdi, aSuccess ; "Success"

PrevHead(addr) 获得当前地址指向指令的前一条指令的地址
print hex(PrevHead(0x100000e92))
# 0x100000e8cL

NextHead(addr) 获得当前地址指向指令的后一条指令的地址
print hex( NextHead(0x100000E8C) )
# 0x100000e92L

GetDisasm(addr) 得到addr的反汇编语句
print GetDisasm(0x100000E8C)
# jnz loc_100000EAB

GetMnem(addr) 获得当前指令(字符串返回)
print GetMnem(0x100000e92)
# "lea"

GetOpnd(addr, num) 获得当前指令的第num+1个操作数(字符串返回)
print GetOpnd(0x100000e92, 0)
# rdi

GetOperandValue(addr, num) 获得当前指令的第num+1个操作数的值
print hex(GetOperandValue(0x100000e92, 1))
print type(GetOperandValue(0x100000e92, 1))
# 0x100000fa2L
# <type 'long'>

GetString(addr) 获得addr处的字符串(这个API在有的时候并不会那么好用,比如要做原生数据的提取,那就要使用Byte()API自己写函数判断'\x00'提取了)
print GetString( GetOperandValue(0x100000e92, 1) ) # 先用GetOperandValue()API获取到字符串的地址

MakeComm(addr, dec) 对addr处添加批注dec
print MakeComm(0x100000E8C, "This is comm")
# True
# 0x100000E8C jnz loc_100000EAB ; "This is comm"

MakeName(addr, name) 对addr处变量改名
# 0x401A80 mov cs:qword_6CBBE8, r9
operand_value = GetOperandValue(0x401A80, 0)
MakeName(operand_value, "cnt")
# 0x401A80 mov cs:cnt, r9

功能相关API

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
add_bpt(address, 0, BPT_SOFT) 设置断点
print add_bpt(0x41A580, 0, BPT_SOFT)
# True

del_bpt(address) 删除断点
print del_bpt(0x41A580)
# True

enable_bpt(address, True) 设置断点是否开启,True为开启,False为关闭
print enable_bpt(0x41A580, True)
# True

RunPlugin("python", 3) 将python设置为默认编程语言 在设置条件断点时不设置Python为默认语言的话只能使用idc

条件断点
condition = """
if 1 == 1:
print hex(GetRegValue("EIP"))
print SetRegValue(GetRegValue("EIP")+16, "EIP")
return True
"""
RunPlugin("python", 3)
address = 0xD7A580
add_bpt(address, 0, BPT_SOFT)
# enable_bpt(address, True)
SetBptCnd(address, condition)


XrefsTo(addr, flags) 查看交叉引用,返回一个迭代器,包含idautils._xref对象
for addr in XrefsTo(0x100000FA2, flags=0):
print hex(addr.frm) # idautils._xref.frm返回交叉引用地址

patch_byte(addr, val) patch一个字节
patch_word(addr, val) patch一个字
patch_dword(addr, val) patch一个双字
patch_dword(addr, val) patch一个四字

调试相关

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37

GetRegValue( string Register ) 通过寄存器名获得寄存器值
print GetRegValue("EIP")
# 0xd7a580L

SetRegValue( long Value, string Register )
print SetRegValue(GetRegValue("EIP")+16, "EIP") 通过寄存器名修改寄存器值
# True

StartDebugger("","",""); 开启调试
continue_process()

BeginEA() 返回程序入口点地址
print hex(BeginEA())
# 0x4014e0L

RunTo(addr) 运行到指定地址
print RunTo(0x81A580)
# True

GetDebuggerEvent(EVENT_TYPE, flags) 获取并清除调试器事件代码,普通代码返回0x20, 断点、ret指令返回0x10,程序结束返回负数
必须在导致进程执行的每个函数之后调用,以便检索调试器的事件代码,否则可能会阻止后续尝试单步执行或运行进程。例如,以下代码片段将只单步执行调试器一次,因为在两次调用StepOver之间没有调用以清除最后一个事件类型
StepOver()
StepOver()
StepOver()
正确的做法应该是在每一次调用StepOver后调用一次GetDebuggerEvent
StepOver()
GetDebuggerEvent(WFNE_SUSP, -1) # 是否获取返回值都可以清除调试器事件代码,从而继续执行调试
StepOver()
GetDebuggerEvent(WFNE_SUSP, -1)
StepOver()
GetDebuggerEvent(WFNE_SUSP, -1)
事件类型“WFNE_SUSP”将等待导致被调试进程挂起的事件,例如异常或断点
事件类型“WFNE_CONT”可以恢复被挂起的进程,继续执行
如:
event = GetDebuggerEvent(WFNE_SUSP, -1)
event = GetDebuggerEvent(WFNE_ANY|WFNE_CONT, -1)

traceRe

通过以上知识,就可以实现一个简单的traceRe

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
#coding=utf-8
StartAddr = 0x4015EA # 起始地址
EndAddr = 0x4016E1 # 终止地址

def get_new_color(current_color):
colors = [0xffe699, 0xffcc33, 0xe6ac00, 0xb38600]
if current_color == 0xFFFFFF:
return colors[0]
if current_color in colors:
pos = colors.index(current_color)
if pos == len(colors)-1:
return colors[pos]
else:
return colors[pos+1]
return 0xFFFFFF

heads = Heads(SegStart(ScreenEA()), SegEnd(ScreenEA())) # 清除原先设置的所有颜色
for i in heads:
SetColor(i, CIC_ITEM, 0xFFFFFF)

add_bpt(EndAddr, 0, BPT_SOFT)
RunTo(StartAddr)
event = GetDebuggerEvent(WFNE_SUSP, -1) # 阻塞式等待程序运行到StartAddr
# print (hex(GetEventEa()),event)

EnableTracing(TRACE_STEP, 1)
event = GetDebuggerEvent(WFNE_ANY|WFNE_CONT, -1)

while True:
event = GetDebuggerEvent(WFNE_ANY, -1)
addr = GetEventEa()
current_color = GetColor(addr, CIC_ITEM)
new_color = get_new_color(current_color)
SetColor(addr, CIC_ITEM, new_color)
print "Debug: current address", hex(addr), "| debug event", hex(event)
if addr == EndAddr: break # 通过地址判断trace结束

执行后效果大致如图

advance

可以看到,样例程序的5000次循环也只用了2分钟就trace完毕了,这个速度是在可接受范围内的。如果要进一步优化,可以判断每一条指令是否为call,若有函数的调用直接把函数名输出出来,这样就可以理清这一段代码的流程了。

IDAPython3学习笔记

IDA7.5是真的香,不管是从控制流分析、抗花指令、静态函数识别还是动态调试都远远胜过IDA 7.0。但同样带来的问题就是原本使用于IDAPython的IDA脚本全部都失效了。但同样的,IDA开发者们提供了一个宏,可以让IDAPython3可以继续使用原先的IDAPython2的API。 这个宏位于 IDA_INSTALL_DIR\python\3\idc_bc695.py,只要在原先的脚本中Import这个宏就可以继续使用IDAPython2的API了。

下面来介绍一下常用的IDAPython3的API

指令相关的API

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
例子:
# 0401568 lea rcx, Buffer ; "a=1"
# 040156F call puts

# 040409F aArgumentSingul db 'Argument singularity (SIGN)',0

prev_head(addr) 获得当前地址指向指令的前一条指令的地址
print(prev_head(0x40156F))
# 0x401568

next_head(addr) 获得当前地址指向指令的后一条指令的地址
print(next_head(0x401568))
# 0x40156f

generate_disasm_line(addr, flags=0) 得到addr的反汇编语句
print(generate_disasm_line(0x401568))
# 'lea rcx, Buffer; "a=1"'

print_insn_mnem(addr) 获得当前指令(字符串返回)
print(print_insn_mnem(0x401568))
# 'lea'

print_operand(addr, num) 获得当前指令的第num+1个操作数(字符串返回)
print(print_operand(0x401568, 0))
# 'rcx'

get_operand_value(addr, num) 获得当前指令的第num+1个操作数的值
print(hex(get_operand_value(0x401568, 1)))
print(type(get_operand_value(0x401568, 1)))
# "0x404000"
# <class 'int'>

get_strlit_contents(addr, length=-1, strtype=0) 获得addr处的字符串
print(get_strlit_contents(0x40409F))
# b'Argument singularity (SIGN)'

set_cmt(addr, comm, rptble) 对addr处添加批注comm; rptble=True时若存在批注则不会更改,为False时会替换批注
print(set_cmt(0x40156F, "This is comm", False))
# True
# 0x40156F call puts ; This is comm

set_name(addr, name, flags=SN_CHECK) 对addr处变量改名
print(set_name(0x404000, "MyBuffer", SN_CHECK))

功能相关API

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
add_bpt(address, 0, BPT_SOFT) 设置断点
print add_bpt(0x41A580, 0, BPT_SOFT)
# True

del_bpt(address) 删除断点
print del_bpt(0x41A580)
# True

enable_bpt(address, True) 设置断点是否开启,True为开启,False为关闭
print enable_bpt(0x41A580, True)
# True

load_and_run_plugin("python", 3) 将python设置为默认编程语言 在设置条件断点时不设置Python为默认语言的话只能使用idc

# 条件断点
condition = """
if 1 == 1:
print(hex(get_reg_value("EIP")))
print set_reg_value(get_reg_value("EIP")+16, "EIP")
return True
"""
load_and_run_plugin("python", 3)
address = 0xD7A580
add_bpt(address, 0, BPT_SOFT)
# enable_bpt(address, True)
set_bpt_cond(address, condition)

XrefsTo(addr, flags) 查看交叉引用,返回一个迭代器,包含idautils._xref对象
for addr in XrefsTo(0x404000, flags=0):
print(hex(addr.frm)) # idautils._xref.frm返回交叉引用地址


get_wide_byte(addr) 获得addr处1字节的值
get_wide_word(addr) 获得addr处2字节的值
get_wide_dword(addr) 获得addr处4字节的值
get_qword(addr) 获得addr处8字节的值

patch_byte(addr, val) patch一个字节
patch_word(addr, val) patch一个字
patch_dword(addr, val) patch一个双字
patch_dword(addr, val) patch一个四字

调试相关

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34

get_reg_value( string Register ) 通过寄存器名获得寄存器值
print(get_reg_value("EIP"))
# 0xd7a580L

set_reg_value( long Value, string Register )
print(set_reg_value(get_reg_value("EIP")+16, "EIP")) 通过寄存器名修改寄存器值
# True

start_process("","",""); 开启调试
continue_process()

run_to(addr) 运行到指定地址
print(run_to(0x81A580))
# True

wait_for_next_event(EVENT_TYPE, flags) 获取并清除调试器事件代码,普通代码返回0x20, 断点、ret指令返回0x10,程序结束返回负数
必须在导致进程执行的每个函数之后调用,以便检索调试器的事件代码,否则可能会阻止后续尝试单步执行或运行进程。例如,以下代码片段将只单步执行调试器一次,因为在两次调用StepOver之间没有调用以清除最后一个事件类型
step_over()
step_over()
step_over()
正确的做法应该是在每一次调用step_over后调用一次wait_for_next_event
step_over()
wait_for_next_event(WFNE_SUSP, -1) # 是否获取返回值都可以清除调试器事件代码,从而继续执行调试
step_over()
wait_for_next_event(WFNE_SUSP, -1)
step_over()
wait_for_next_event(WFNE_SUSP, -1)

事件类型“WFNE_SUSP”将等待导致被调试进程挂起的事件,例如异常或断点
事件类型“WFNE_CONT”可以恢复被挂起的进程,继续执行
如:
event = wait_for_next_event(WFNE_SUSP, -1)
event = wait_for_next_event(WFNE_ANY|WFNE_CONT, -1)

参考资料

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

  2. https://www.cnblogs.com/0xHack/p/9399321.html

  3. https://books.google.com.hk/books?id=kOJ5G_mAbZoC&pg=PA533&lpg=PA533&dq=GetDebuggerEvent&source=bl&ots=Fae9u9XE1y&sig=ACfU3U0gyVlitw4uOhzw7EEJOwyNpWNgxA&hl=zh-CN&sa=X&ved=2ahUKEwi84Zybo7fpAhUDHXAKHW6sDOcQ6AEwAnoECAoQAQ#v=onepage&q=GetDebuggerEvent&f=false

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

  5. https://www.hex-rays.com/products/ida/support/ida74_idapython_no_bc695_porting_guide.shtml