This page looks best with JavaScript enabled

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

 ·  ☕ 7 min read · 👀... views

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
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
45
46
47
例子
# 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))

get_screen_ea()   # 获得当前光标地址 ScreenEA()、

idaapi.FlowChart(idaapi.get_func(choose_func("main"))).size # 统计main函数具有多少个基本块

功能相关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 )  通过寄存器名获得寄存器值
printget_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

Share on

Qfrost
WRITTEN BY
Qfrost
CTFer, Anti-Cheater, LLVM Committer