参考资料
Alex Hanel写的电子书:https://leanpub.com/IDAPython-Book
Magic Lantern写的:https://magiclantern.fandom.com/wiki/IDAPython
官方教程:https://hex-rays.com/products/ida/support/idapython_docs/
- ida_allins: 提供对 IDA Pro 支持的所有处理器指令集的访问。
- ida_auto: 提供自动分析功能,例如函数识别、变量类型推断等。
- ida_bitrange: 提供位范围操作功能,如设置、清除、测试位等。
- ida_bytes: 提供对 IDA Pro 数据库中字节数据的读写操作。
- ida_dbg: 提供调试器接口,用于控制调试过程和访问调试信息。
- ida_dirtree: 提供目录树操作功能,用于管理 IDA Pro 数据库文件。
- ida_diskio: 提供文件读写操作功能。
- ida_entry: 提供程序入口点相关功能。
- ida_enum: 提供枚举类型定义和操作功能。
- ida_expr: 提供表达式解析和计算功能。
- ida_fixup: 提供重定位信息操作功能。
- ida_fpro: 提供快速库识别和分析功能。
- ida_frame: 提供函数栈帧信息访问和操作功能。
- ida_funcs: 提供函数定义和操作功能。
- ida_gdl: 提供 IDA Pro 图形界面相关功能。
- ida_graph: 提供图形化视图相关功能,如流程图、调用图。
- ida_hexrays: 提供 Hex-Rays 反编译器接口。
- ida_ida: 提供 IDA Pro 核心功能的访问。
- ida_idaapi: 提供 IDA Python API 的主要入口点。
- ida_idc: 提供 IDC 脚本引擎接口。
- ida_idd: 提供 IDA Pro 数据库定义。
- ida_idp: 提供处理器模块接口。
- ida_ieee: 提供 IEEE 浮点数格式转换功能。
- ida_kernwin: 提供 IDA Pro 内核窗口相关功能。
- ida_lines: 提供代码行信息访问和操作功能。
- ida_loader: 提供文件加载器接口。
- ida_merge: 提供数据库合并功能。
- ida_mergemod: 提供数据库模块合并功能。
- ida_moves: 提供数据移动和复制功能。
- ida_nalt: 提供 Netnode API,用于处理 IDA Pro 数据库中的命名对象。
- ida_name: 提供名称解析和操作功能。
- ida_netnode: 提供 Netnode 对象操作功能。
- ida_offset: 提供偏移量计算和转换功能。
- ida_pro: 提供 IDA Pro 的基本信息和设置。
- ida_problems: 提供问题标记和管理功能。
- ida_range: 提供地址范围操作功能。
- ida_regfinder: 提供寄存器查找功能。
- ida_registry: 提供 IDA Pro 注册表访问功能。
- ida_search: 提供搜索功能。
- ida_segment: 提供段信息访问和操作功能。
- ida_segregs: 提供段寄存器信息访问和操作功能。
- ida_srclang: 提供源代码语言相关功能。
- ida_strlist: 提供字符串列表访问和操作功能。
- ida_struct: 提供结构体定义和操作功能。
- ida_tryblks: 提供异常处理块信息访问和操作功能。
- ida_typeinf: 提供类型信息访问和操作功能。
- ida_ua: 提供微码相关功能。
- ida_xref: 提供交叉引用信息访问和操作功能。
- idc: 提供 IDC 脚本执行环境。
- idautils: 提供 IDA Pro 实用函数的集合。
IDAPython 由三个独立模块组成。
第一个是 idc,它是封装 IDA 的 IDC 函数的兼容性模块。
第二个模块是 idautils,这是IDA 里的一个高级实用函数。
第三个模块是 idaapi,它允许访问更多低级数据,这些数据能够被类使用通过 IDA。
IDAPython 的强大来自于遍历所有的指令,交叉引用地址和搜索代码或数据。
1. 基础知识
.text:00401570 lea eax, [ebp+arg_0]
.text是段名称,地址是00401570 。lea是助记符,第一个操作是eax,第二个操作是[ebp+arg_0]
EA = here() / idc.get_screen_ea() 代表当前光标地址 返回整形值,就是地址0012529的十进制
Python>ea = idc.get_screen_ea() Python>print("0x%x %s" % (ea, ea)) >>> 0x401570 4199792 Python>ea = here() Python>print("0x%x %s" % (ea, ea)) >>> 0x401570 419972
idc.get_inf_attr(INF_MIN_EA) 获取最小地址 idc.get_inf_attr(INF_MAX_EA) 获取最大地址
Python>print("0x%x" % idc.get_inf_attr(INF_MIN_EA)) >>> 0x401000 Python>print("0x%x" % idc.get_inf_attr(INF_MAX_EA)) >>> 0x41d000
idc.get_segm_name() 获取当前段(Segments)名称
idc.generate_disasm_line() 获取汇编代码
idc.print_insn_mnem() 获取助记符
idc.print_operand() 获取操作数
Python>idc.get_segm_name(ea) # get text >>> .text Python>idc.generate_disasm_line(ea, 0) # get disassembly >>> lea eax, [ebp+arg_0] Python>idc.print_insn_mnem(ea) # get mnemonic >>> lea Python>idc.print_operand(ea,0) # get first operand >>> eax Python>idc.print_operand(ea,1) # get second operand >>> [ebp+arg_0]
idaapi.BADADDR idc.BADADDR BADADDR三个函数 验证地址是否在当前程序中存在
Python>idaapi.BADADDR >>> 4294967295 Python>print("0x%x" % idaapi.BADADDR) >>> 0xffffffff Python>if BADADDR != here(): print("valid address") >>> valid address
Python>idc.BADADDR >>> 18446744073709551615 Python>print("0x%x" % idaapi.BADADDR) >>> 0xffffffffffffffff
2. 段
遍历所有段 idautils.Segments()返回一个遍历类型对象
idc.get_next_seg(ea) 获取下一个段
idc.selector_by_name(segname) 返回段选择器并传递单个字符串段名称的参数
idc.get_segm_by_sel(idc.selector_by_name(str_SectionName)) 获取一个段的开始地址
Python>for seg in idautils.Segments():\ print("%s, 0x%x, 0x%x" % (idc.get_segm_name(seg), idc.get_segm_start(seg),idc.get_segm_end(seg))) >>> .textbss, 0x401000, 0x411000 >>> .text, 0x411000, 0x418000 >>> .rdata, 0x418000, 0x41b000 >>> .data, 0x41b000, 0x41c000 >>> .idata, 0x41c000, 0x41c228 >>> .00cfg, 0x41d000, 0x41e000
3. 函数
idautils.Functions() / idautils.Funtions(start_addr, end_addr)
idautils.Functions() 将返回一个已知函数列表
idc.get_func_name() 返回函数名称
idaapi.get_func(ea) 获取函数边界
start_ea和end_ea获得起始和结束地址
Python>for func in idautils.Functions(): print("0x%x, %s" % (func, idc.get_func_name(func))) Python> >>> 0x401000, sub_401000 >>> 0x401006, w_vfprintf >>> 0x401034, _main >>> …removed… >>> 0x401c4d, terminate >>> 0x401c53, IsProcessorFeaturePresent
访问上/下函数 idc.get_next_func(ea) 和 idc.get_prev_func(ea)
Python>func = idaapi.get_func(ea) Python>type(func) >>> <class 'ida_funcs.func_t'> Python>print("Start: 0x%x, End: 0x%x" % (func.start_ea, func.end_ea)) >>> Start: 0x45c7c3, End: 0x45c7cd
idc.get_func_attr(ea, FUNCATTR_START) 和 idc.get_func_attr(ea, FUNCATTR_END)访问函数边界
idc.generate_disasm_line(ea, 0) 打印当前汇编代码
idc.next_head(eax) 下一条指令,直到函数结束
由于边界是不可靠的最好的实践是调用 idautils.FuncItems(ea)去循环函数的每个地址。
Python>ea = here() Python>start = idc.get_func_attr(ea, FUNCATTR_START) Python>end = idc.get_func_attr(ea, FUNCATTR_END) Python>cur_addr = start Python> while cur_addr <= end: print("0x%x %s" % (cur_addr, idc.generate_disasm_line(cur_addr, 0))) cur_addr = idc.next_head(cur_addr, end) Python> >>> 0x45c7c3 mov eax, [ebp-60h] >>> 0x45c7c6 push eax ; void * >>> 0x45c7c7 call w_delete >>> 0x45c7cc retn
使用 idautils.Functions()来获取所有已知函数列表的地址
使用idc.get_func_attr(ea, FUNCATTR_FLAGS) 获取函数信息,它有9个参数
Python>import idautils Python>for func in idautils.Functions(): flags = idc.get_func_attr(func,FUNCATTR_FLAGS) if flags & FUNC_NORET: #无返回标志的函数 print("0x%x FUNC_NORET" % func) if flags & FUNC_FAR: # 这个标志很少出现,除非逆向软件使用分段内存。它的内部表示为一个整数 2。 print("0x%x FUNC_FAR" % func) if flags & FUNC_LIB: #此标志用于查找库代码,在内部表示为整数4。 print("0x%x FUNC_LIB" % func) if flags & FUNC_STATIC: #此标志用于标识基于静态ebp框架的库函数 print("0x%x FUNC_STATIC" % func) if flags & FUNC_FRAME: # 这个标志表明该函数使用帧指针 EBP。使用帧指针的函数通常以设置堆栈框架的标准函数序言开始。 print("0x%x FUNC_FRAME" % func) if flags & FUNC_USERFAR: #这个标志是罕见的,hexrays 描述标志为“用户指定了函数距离”。它的内部值为 32。 print("0x%x FUNC_USERFAR" % func) if flags & FUNC_HIDDEN: #函数带 FUNC_HIDDEN 标志意味着他们是隐藏的将需要扩展到视图。如果我们转到一个被标记为隐藏的函数的地址,它会自动扩展。 print("0x%x FUNC_HIDDEN" % func) if flags & FUNC_THUNK: #这标志标识函数是 thunk 函数。一个简单的功能是跳到另一个函数。 print("0x%x FUNC_THUNK" % func) if flags & FUNC_BOOTOMBP: # 此标志用于跟踪帧指针。标识指针指向堆栈指针的函数 print("0x%x FUNC_BOTTOMBP" % func) Python> >>> 0x401006 FUNC_FRAME#此标志用于标识作为静态函数编译的函数。在 C 函数中默认是全局的。如果作者定义了一个函数为静态只能访问内部文件等功能。 >>> 0x40107c FUNC_LIB >>> 0x40107c FUNC_STATIC
4. 提取函数参数
在IDAPython中,提取函数参数并不总是一项简单的任务。在许多情况下,需要标识函数的调用约定,并且必须使用反向跟踪或类似的技术手动解析参数。由于大量的调用约定,这在一般情况下并不总是可行的。IDAPython包含一个名为idaapi.get_arg_addrs(ea)的函数,能够识别被调用函数的原型。以下就是识别了SendMessageW函数的四个参数地址。
.text:000000014001B5FF js loc_14001B72B .text:000000014001B605 mov rcx, cs:qword_14002D368 ; hWnd .text:000000014001B60C xor r9d, r9d ; lParam .text:000000014001B60F xor r8d, r8d ; wParam .text:000000014001B612 mov edx, 0BDh ; '½' ; Msg .text:000000014001B617 call cs:SendMessageW .text:000000014001B61D xor esi, esi
Python>ea = 0x00014001B617 Python>idaapi.get_arg_addrs(ea) >>> [0x14001b605, 0x14001b612, 0x14001b60f, 0x14001b60c]
5. 指令
本节学习在函数中访问指令。
如果我们有一个函数的地址,我们能使用idautils.FuncItems(ea)获取列表中所有地址(前提是必须是在函数内,会显示该函数所有指令地址)
idautils.FuncItems(ea)实际返回一个迭代器类型但是被强转成一个 list。该列表将包含顺序连续的每个指令的起始地址。
dism_addr = list(idautils.FuncItems(here())) print dism_addr >>> [4573123, 4573126, 4573127, 4573132] for line in dism_addr: print("0x%x %s" % (line,idc.generate_disasm_line(line, 0))) >>> 0x45c7c3 mov eax, [ebp-60h] 0x45c7c6 push eax ; void * 0x45c7c7 call w_delete 0x45c7cc retn
以下是一个例子
我们调用 idautils.Functions()去获取所有已知函数列表。每个函数我们通过调用get_func_attr(func, FUNCATTR_FLAGS)检索函数的标志。
如果这个函数是库代码或者 thunk 函数这个函数被跳过。接下来我们调用 idautils.FuncItems(ea)来获取在函数里的所有的指令地址。
使用 for 循环循环遍历列表。由于我们只对 call 和 jmp 指令感兴趣我们需要通过调用 idc.print_insn_mnem(ea)(获取助记符)。
然后我们用一个简单的字符串比较检查法。如果是一个 jump 或 call 我们通过操作的类型调用idc.get_operand_type(ea, n)。这个函数将返回一个整数是内部调用 op_t.type。这个值可以用来确定如果操作数是一个寄存器、内存引用等。
然后我们检查 op_t.type 是一个寄存器。通过 idc.generate_disasm_line(line,0)打印汇编。
for func in idautils.Functions(): flags = idc.get_func_attr(func, FUNCATTR_FLAGS) if flags & FUNC_LIB or flags & FUNC_THUNK: continue dism_addr = list(idautils.FuncItems(func)) for line in dism_addr: #循环每条指令访问下一条指令 m = idc.print_insn_mnem(line) if m == 'call' or m == 'jmp': op = idc.get_operand_type(line, 0) if op == o_reg: print "0x%x %s" % (line, idc.generate_disasm_line(line,0)) >>>0x43ebde call eax ; VirtualProtect
如果只有一个地址,该怎么访问下一个指示呢?
移动到下一条指令的地址可以使用 idc.next_head(ea)和idc.prev_head(ea)获得前后一条指令地址 。这些功能将得到下一个指令的起始地址而不是下一个地址。
得到下一个地址我们使用 idc.next_addr(ea),得到前一个地址我们使用 idc.prev_addr(ea)
ea = here()
print("0x%x %s" % (ea, idc.generate_disasm_line(ea,0)))
>>> 0x10004f24 call sub_10004f32
next_instr = idc.next_head(ea)
print("0x%x %s" % (ea, idc.generate_disasm_line(next_instr,0)))
>>> 0x10004f29 mov [esi],eax
prev_instr = idc.prev_head(ea)
print("0x%x %s" % (ea, idc.generate_disasm_line(prev_instr,0)))
>>>0x10004f1e mov [esi+98h], eax
print ("0x%x" % idc.next_addr(ea))
>>> 0x10004f25 #注意和next_head的区别
print ("0x%x" % idc.prev_addr(ea))
>>>0x10004f23
在动态调试的示例中,IDAPython代码依赖于使用jmp和call的字符串比较,我们也可以使用idaapi.decode_insn(ea)来解码指令,而不是使用字符串比较,对一条指令进行解码是更加好的方法,因为使用整型指令表示可以更快、更少出错。不幸的是,整数表示是特定于IDA的,无法方便的移植到其它反汇编工具,下面是使用idaapi.decode_insn(ea并比较整数表示形式的相同示例
idaapi.decode_insn(insn_t, ea) 解码指令;
第一个参数是来自ida_ua的insn_t类,通过调用ida_ua.insn_t()获得。一旦idaapi.decode_insn函数调用,insn_t类就被属性填充。
第二个参数是被分析的地址。
前两行将jmp和call放入lists中,由于我们没有使用助记符字符串的表示形式。我们需要认识到,助记符(例如call和jmp)可以有多个值。
例如:jmp可以使用idaapi.NN_jmp表示跳转,idaapi.NN_jmpfi表示间接远跳,或者idaapi.NN_jmpni 表示间接近跳,X86和X64指令类型都以NN开头。
找到这超过1700多个指令类型,我们可以在命令行中执行[name for name in dir(idaapi) if “NN”],或者在IDA的SDK文件allins.hpp中查看它们。一旦我们在列表中有了指令,我们使用idautil . functions()和get_func_attr(ea, FUNCATTR_FLAGS)的组合来获得所有适用的函数,同时忽略库和thunks。我们通过调用idautil.funcitems (ea)来获取函数中的每条指令。这是调用新引入的函数idaapi.decode_insn(ea)的地方。这个函数找到我们想要解码指令的地址,一旦解码成功,我们可以通过idaapi.cmd访问指令的不同属性。
Python>JMPS = [idaapi.NN_jmp, idaapi.NN_jmpfi, idaapi.NN_jmpni] Python>CALLS = [idaapi.NN_call, idaapi.NN_callfi, idaapi.NN_callni] Python>for func in idautils.Functions(): flags = idc.get_func_attr(func, FUNCATTR_FLAGS) if flags & FUNC_LIB or flags & FUNC_THUNK: continue dism_addr = list(idautils.FuncItems(func)) for line in dism_addr: ins = ida_ua.insn_t() idaapi.decode_insn(ins, line) if ins.itype in CALLS or ins.itype in JMPS: if ins.Op1.type == o_reg: #这就找到了call 和 jmp指令地址 print("0x%x %s" % (line, idc.generate_disasm_line(line, 0)) Python> >>> 0x43ebde call eax ; VirtualProtect
6. 操作数
可以使用idc.get_operand_type(ea,n)得到的操作数的类型。ea 是地址,n 是索引
这里有 8 中不同类型的操作数类
o_void 如果一个指令没有任何操作数它将返回 0
Python>print("0x%x %s" % (ea, idc.generate_disasm_line(ea, 0))) >>> 0xa09166 retn Python>print(idc.get_operand_type(ea,0)) >>> 0
o_reg 如果一个操作数是一个普遍的寄存器将返回此类型。这个值在内部表示为 1。
Python>print("0x%x %s" % (ea, idc.generate_disasm_line(ea, 0))) >>> 0xa09163 pop edi Python>print(idc.get_operand_type(ea,0)) >>> 1
o_mem 如果一个操作数是直接内存引用它将返回这个类型。这个值在内部表示为 2。这种类型是有用的在 DATA 段查找引用。
Python>print("0x%x %s" % (ea, idc.generate_disasm_line(ea, 0))) >>> 0xa05d86 cmp ds:dword_A152B8, 0 Python>print(idc.get_operand_type(ea,0)) >>> 2
o_phrase 这个操作数被返回则这个操作数包含一个基本的寄存器或一个索引寄存器。这个值在内部表示为 3。
Python>print("0x%x %s" % (ea, idc.generate_disasm_line(ea, 0))) >>> 0x1000b8c2 mov [edi+ecx], eax Python>print(idc.get_operand_type(ea,0)) >>> 3
o_displ 这个操作数被返回则操作数包含寄存器和一个位移值,这个为位移值是一个整数,例如0x18。这是常见的当一条指令访问值在一个结构中。在内部,它表示为 4 的值。
Python>print("0x%x %s" % (ea, idc.generate_disasm_line(ea, 0))) >>> 0xa05dc1 mov eax, [edi+18h] Python>print(idc.get_operand_type(ea,1)) >>> 4
o_imm 操作数是这样一个为整数的 0xc 的值的类型。它在内部表示为 5。
Python>print("0x%x %s" % (ea, idc.generate_disasm_line(ea, 0))) >>> 0xa05da1 add esp, 0Ch Python>print(idc.get_operand_type(ea,1)) >>> 5
o_far 这个操作数不是很常见当逆向 x86 或 x86_64 时。它是用来寻找操作数的访问立即数远地址的。它在内部表示为 6。
o_near 这个操作数不是很常见当逆向 x86 或 x86_64 时。它是用来寻找操作数的访问立即数近地址的。它在内部表示为 7。
例子1
当逆向可执行文件的内存dump时,操作数不能被识别为偏移
seg000:00BC1388 push 0Ch seg000:00BC138A push 0BC10B8h seg000:00BC138F push [esp+10h+arg_0] seg000:00BC1393 call ds:_strnicmp
push 0bc10b8h是一个内存偏移量。如果我们将其改为数据类型,我们会看到字符串偏移量。如果要自动化,如下:
idc.get_inf_attr(INF_MIN_EA) 获得调用最小地址
idc.get_inf_attr(INF_MAX_EA) 获得调用最大地址
idc.op_plain_offset(ea, n, base) 将操作数转化为偏移量 第一个参数是地址,第二个参数n是操作数索引,第三个参数base是基地址
min = idc.get_inf_attr(INF_MIN_EA) max = idc.get_inf_attr(INF_MAX_EA) # for 每个已知函数 for func in idautils.Functions(): flags = idc.get_func_attr(func, FUNCATTR_FLAGS) # 跳过 library 和 thunk 函数 if flags & FUNC_LIB or flags & FUNC_THUNK: continue dism_addr = list(idautils.FuncItems(func)) for curr_addr in dism_addr: if idc.get_operand_type(curr_addr, 0) == 5 and (min < idc.get_operand_value(curr_addr, 0) < max): idc.OpOff(curr_addr, 0, 0) if idc.get_operand_type(curr_addr, 1) == 5 and (min < idc.get_operand_value(curr_addr, 1) < max): idc.op_plain_offset(curr_addr, 1, 0)
这样就变成了:
seg000:00BC1388 push 0Ch seg000:00BC138A push offset aNtoskrnl_exe ; "ntoskrnl.exe" seg000:00BC138F push [esp+10h+arg_0] seg000:00BC1393 call ds:_strnicmp
7. 基本块
基本块是没有分支的直线代码序列,有一个单入口点和一个单出口点组成。
在分析程序的控制流时,基本块非常有用。在使用函数的图分解视图时,通常可以观察到rda对基本块的表示。使用基本块进行分析的一些值得注意的例子是用于识别循环或控制流混淆。当一个基本块将控制权转移到另一个块时,下一个块称为后继块,前一个块称为predecessors(索性叫前赴块吧)。下面的流图是一个用单字节异或解密字符串的函数。
.text:00401034 push esi .text:00401035 push edi .text:00401036 push 0Ah ; Size .text:00401038 call ds:malloc .text:0040103E mov esi, eax .text:00401040 mov edi, offset str_encrypted .text:00401045 xor eax, eax ; eax = 0 .text:00401047 sub edi, esi .text:00401049 pop ecx .text:0040104A .text:0040104A loop: ; CODE XREF: _main+28↓j .text:0040104A lea edx, [eax+esi] .text:0040104D mov cl, [edi+edx] .text:00401050 xor cl, ds:b_key ; cl = 0 .text:00401056 inc eax .text:00401057 mov [edx], cl .text:00401059 cmp eax, 9 ; index .text:0040105C jb short loop .text:0040105E push esi .text:0040105F push offset str_format .text:00401064 mov byte ptr [esi+9], 0 .text:00401068 call w_vfprintf .text:0040106D push esi ; Memory .text:0040106E call ds:free .text:00401074 add esp, 0Ch .text:00401077 xor eax, eax ; eax = 0 .text:00401079 pop edi .text:0040107A pop esi .text:0040107B retn .text:0040107B _main endp
以上函数分为三个块,异或从0x40104a开始。0x401050是异或的关键点。
ea = 0x0401050 f = idaapi.get_func(ea) fc = idaapi.FlowChart(f, flags=idaapi.FC_PREDS) for block in fc: #这个函数的所有块 print("ID: %i Start: 0x%x End: 0x%x" % (block.id, block.start_ea,block.end_ea)) if block.start_ea <= ea < block.end_ea: print(" Basic Block selected") successor = block.succs() #包含后续地址的生成器 for addr in successor: print(" Successor: 0x%x" % addr.start_ea) pre = block.preds() #包含前身地址的生成器 for addr in pre: print(" Predecessor: 0x%x" % addr.end_ea) if ida_gdl.is_ret_block(block.type): #返回块 print(" Return Block")
>>> Successor: 0x40104a >>> ID: 1 Start: 0x40104a End: 0x40105e >>> Basic Block selected >>> Successor: 0x40105e >>> Successor: 0x40104a >>> Predecessor: 0x40104a >>> Predecessor: 0x40105e >>> ID: 2 Start: 0x40105e End: 0x40107c >>> Predecessor: 0x40105e >>> Return Block
每个块包含以下属性:
- id:函数中每个块都有一个唯一索引,从0开始
- type:有以下类型
- fcb_normal:表示普通块,内部为0
- fcb_indjump:是否以间接跳转结束,内部为1
- fcb_ret:返回块,内部为2
- fcb_cndret:是否为条件返回,内部为3
- fcb_noret:无返回块,内部为4
- fcb_enoret:不属于函数且没有返回的块,内部为5
- fcb_extern:外部普通块,内部为6
- fcb_error:通过函数结束来传递执行的块,内部为7
- start_ea:基本块开始地址
- end_ea:基本快结束地址
- preds:一个函数,它返回一个包含所有前身地址的生成器
- succs:一个函数,它返回包含所有后续地址的生成器。
8. 结构
在编译过程中,结构布局,结构名称和结构类型将会被删除。
重构结构和正确标记结构成员可以极大帮助逆向过程。
下面是x86 shellcode中常见的代码片段。
完整代码遍历器结构包含 线程环境块(TEB) 和 进程环境快(PEB) 来找到kernell32.dll的基地址。
seg000:00000000 xor ecx, ecx seg000:00000002 mov eax, fs:[ecx+30h] seg000:00000006 mov eax, [eax+0Ch] seg000:00000009 mov eax, [eax+14h]
shellcode的下一步是遍历PE文件,查找window api。
由于要解析所有不同结构,除非对结构偏移量进行标记,否则很容易丢失。
seg000:00000000 xor ecx, ecx seg000:00000002 mov eax, fs:[ecx+_TEB.ProcessEnvironmentBlock] seg000:00000006 mov eax, [eax+PEB.Ldr] seg000:00000009 mov eax, [eax+PEB_LDR_DATA.InMemoryOrderModuleList.Flink] seg000:0000000C mov eax, [eax+ecx]
使用下列代码标注对应结构名称:
idc.add_default_til 加载 类型库(TIL), TIL是IDA自己的c/c++格式头文件。它包含结构、枚举、联合和其他数据类型的定义。在IDA中可以通过shift+F11打开。idc.add_default_til会返回加载状态。
在TIL加载后,使用import_type将TIL各个定义导入带IDB中。
idc.import_type(idx, type_name),第一个参数是type的索引,每一个类型都有一个所以和id。-1代表这类型应该将该类型添加到IDA导入类型的末尾。
idc.get_struc_id 获得结构id
idc.op_stroff(ea, n, strid, delta) 偏移地址处增加名字。
第一个参数是包含将要被标记的偏移量的指令的地址(ea)。第二个参数是操作数,在下面的例子中,因为我们想要更改mov eax, fs:[ecx+30h]中的0x30标签,我们需要为第二个参数传递一个值1。第三个参数是类型id,第四个参数通常是0.
status = idc.add_default_til("ntapi") if status: idc.import_type(-1, "_TEB") idc.import_type(-1, "PEB") idc.import_type(-1, "PEB_LDR_DATA") ea = 2 teb_id = idc.get_struc_id("_TEB") idc.op_stroff(ea, 1, teb_id, 0) ea = idc.next_head(ea) #获得下一个指令地址 peb_ldr_id = idc.get_struc_id("PEB_LDR_DATA") idc.op_stroff(ea, 1, peb_ldr_id, 0) ea = idc.next_head(ea) idc.op_stroff(ea, 1, peb_ldr_id, 0)
还可以创建自己的结构。
对于以上结构,我们还可以使用windbg的dt nt!_PEB_LDR_DATA来dump类型定义。
0:000> dt nt!_PEB_LDR_DATA ntdll!_PEB_LDR_DATA +0x000 Length : Uint4B +0x004 Initialized : UChar +0x008 SsHandle : Ptr64 Void +0x010 InLoadOrderModuleList : _LIST_ENTRY +0x020 InMemoryOrderModuleList : _LIST_ENTRY +0x030 InInitializationOrderModuleList : _LIST_ENTRY +0x040 EntryInProgress : Ptr64 Void +0x048 ShutdownInProgress : UChar +0x050 ShutdownThreadId : Ptr64 Void
下面代码检查是否存在名为my_peb_ldr_data结构名。如果结构名存在,代码删除结构,创建一个新的并且增加结构成员:
idc.del_struc(id) 删除结构
idc.add_struc(index, name, is_union) 增加结构 第一个参数是索引,-1添加到后面,第二个参数是结构名字,第三个参数是定义的新结构是否为union,0代表不是union。
sid = idc.get_struc_id("my_peb_ldr_data") #获取结构名 if sid != idc.BADADDR: #验证地址是否存在 idc.del_struc(sid) sid = idc.add_struc(-1, "my_peb_ldr_data", 0) idc.add_struc_member(sid, "length", 0, idc.FF_DWORD, -1, 4) idc.add_struc_member(sid, "initialized", 4, idc.FF_DWORD, -1, 4) idc.add_struc_member(sid, "ss_handle", -1, idc.FF_WORD, -1, 2) idc.add_struc_member(sid, "in_load_order_module_list", -1, idc.FF_DATA, -1, 10) idc.add_struc_member(sid, "in_memory_order_module_list", -1, idc.FF_QWORD +idc.FF_WORD, -1, 10) idc.add_struc_member(sid, "in_initialization_order_module_list", -1, idc.FF_QWORD +idc.FF_WORD, -1, 10) idc.add_struc_member(sid, "entry_in_progress", -1, idc.FF_QWORD, -1, 8) idc.add_struc_member(sid, "shutdown_in_progress", -1, idc.FF_WORD, -1, 2) idc.add_struc_member(sid, "shutdown_thread_id", -1, idc.FF_WORD, -1, 8)
idc.add_struc_member(sid, name, offset, flag, typeid, nbytes) 标记结构成员 第二个参数是成员名,第三个参数是偏移,-1代表结尾。第四个参数是flag。
FF_BYTE 0x00000000 // byte FF_WORD 0x10000000 // word FF_DWORD 0x20000000 // dword FF_QWORD 0x30000000 // qword FF_TBYTE 0x40000000 // tbyte FF_STRLIT 0x50000000 // ASCII ? FF_STRUCT 0x60000000 // Struct ? FF_OWORD 0x70000000 // octaword (16 bytes/128 bits) FF_FLOAT 0x80000000 // float FF_DOUBLE 0x90000000 // double FF_PACKREAL 0xA0000000 // packed decimal real FF_ALIGN 0xB0000000 // alignment directive FF_CUSTOM 0xD0000000 // custom data type FF_YWORD 0xE0000000 // ymm word (32 bytes/256 bits) FF_ZWORD 0xF0000000 // zmm word (64 bytes/512 bits) FF_DATA 0x400 // data
第五个参数是typeid。最后一个参数是要分配的字节数。
9. 枚举类型
枚举类型:是一种使用符号常量来表示有意义名称的方法。在调用系统api时很常见。
比如在windows上调用createfileA时,GENERIC_READ被表示为0x80000000,不过会被编译器删除。
用有意义的名称重新填充常量有助于逆向。比如在api混淆时:
0xCA2BD06B是CreateThread的hash,这个哈希是通过每个字节ROR 13实现的。通常叫z0mbie hashing 技术,或者 ROR-13技术
seg000:00000018 push 0CA2BD06Bh ; ROR 13 hash of CreateThread seg000:0000001D push dword ptr [ebp-4] seg000:00000020 call lookup_hash seg000:00000025 push 0 seg000:00000027 push 0 seg000:00000029 push 0 seg000:0000002B push 4C30D0h ; StartAddress seg000:00000030 push 0 seg000:00000032 push 0 seg000:00000034 call eax ; CreateThread
那么就创建枚举,首先将一些windows DLL进行ROR-13计算,可以首先选用kernel.32,使用pefile导出符号名称。
idc.add_enum(idx, name, flag).创建枚举,第一个参数是id,-1是结尾,第二个参数是枚举名称,第三个参数是flag。
import pefile def ror32(val, amt): return ((val >> amt) & 0xffffffff) | ((val << (32 - amt)) & 0xffffffff) def add32(val, amt): return (val + amt) & 0xffffffff def z0mbie_hash(name): hash = 0 for char in name: hash = add32(ror32(hash, 13), ord(char) & 0xff) return hash def get_name_from_hash(file_name, hash): pe = pefile.PE(file_name) for exp in pe.DIRECTORY_ENTRY_EXPORT.symbols: if z0mbie_hash(exp.name) == hash: return exp.name api_name = get_name_from_hash("kernel32.dll", 0xCA2BD06B) if api_name: id = idc.add_enum(-1, "z0mbie_hashes", idaapi.hexflag()) idc.add_enum_member(id, api_name, 0xCA2BD06B, -1)
处理完就变成了
seg000:00000015 mov [ebp-4], ebx seg000:00000018 push CreateThread ; ROR 13 hash of CreateThread seg000:0000001D push dword ptr [ebp-4]
10. 交叉引用
能够找到交叉引用又名外部参考数据或代码是非常重要的。交叉引用是重要的因为他们因为它提供在某些数据被使用或一个函数被调用的位置。
例如,如果我们想找到 WriteFile被调用的地址。使用交叉引用我们需要做的是确定 WriteFile 的地址在导入表中然后找到所有的交叉引用。
idc.get_name_ea_simple(str) 获得字符串地址
idautils.CodeRefsTo(ea, flow) 获得交叉引用地址,但是重命名函数不会显示。
idautils.Names() 访问函数,例如打印 [x for x in idautils.Names()],就会打印所有函数和地址
Python>wf_addr = idc.get_name_ea_simple("WriteFile") Python>print("0x%x %s" % (wf_addr, idc.generate_disasm_line(wf_addr, 0))) >>> 0x1000e1b8 extrn WriteFile:dword Python>for addr in idautils.CodeRefsTo(wf_addr, 0): print("0x%x %s" % (addr, idc.generate_disasm_line(addr, 0))) >>> 0x10004932 call ds:WriteFile >>> 0x10005c38 call ds:WriteFile >>> 0x10007458 call ds:WriteFile
如果我们想要获得代码在哪里被引用,我们需要使用idautisl.CodeRefsFrom(ea,flow)
例如让我们获取在 0x10004932 被引用的地址。
Python>ea = 0x10004932 Python>print("0x%x %s" % (ea, idc.generate_disasm_line(ea, 0))) >>>> 0x10004932 call ds:WriteFile Python>for addr in idautils.CodeRefsFrom(ea, 0): print("0x%x %s" % (addr, idc.generate_disasm_line(addr, 0))) Python> >>> 0x1000e1b8 extrn WriteFile:dword
接下来我们将描述一个一般技术获得所有交叉引用。如果我们想要搜索交叉引用从数据中我们能使用
idautils.DataRefsTo(e) 或者 idautils.DataRefsFrom(ea)
Python>print("0x%x %s" % (ea, idc.generate_disasm_line(ea, 0))) >>> 0x1000e3ec db 'vnc32',0 Python>for addr in idautils.DataRefsTo(ea):\ print("0x%x %s" % (addr, idc.generate_disasm_line(addr, 0))) >>> 0x100038ac push offset aVnc32 ; "vnc32"
Python>print("0x%x %s" % (ea, idc.generate_disasm_line(ea, 0))) 0x100038ac push offset aVnc32 ; "vnc32" Python>for addr in idautils.DataRefsFrom(ea):\ print("0x%x %s" % (addr, idc.generate_disasm_line(addr, 0))) 0x1000e3ec db 'vnc32',0
对于逆向和显示地址我们调用 idautils.DataRefsFrom(ea),通过地址作为参数。他返回一个一个所有地址交叉引用返回给数据的迭代器。使用不同的代码和数据可能有点混乱。如前所述.
我们描述一个更通用的技术。这种方法可以通过调用单个函数获得所有对地址的交叉引用。
我们获得到一个地址的所有交叉引用使用 idautils.XrefsTo(ea, flags=0),获得从一个地址到所有交叉引用通过调用 idautils.XrefsFrom(ea, flags=0)。
Python>print("0x%x %s" % (ea, idc.generate_disasm_line(ea, 0))) 0x1000eee0 unicode 0, <Path>,0 Python>for xref in idautils.XrefsTo(ea, 1): print("%i %s 0x%x 0x%x %i" % (xref.type, idautils.XrefTypeName(xref.type),xref.frm, xref.to, xref.iscode)) Python> 1 Data_Offset 0x1000ac0d 0x1000eee0 0 Python>>print("0x%x %s" % (xref.frm, idc.generate_disasm_line(xref.frm, 0)) 0x1000ac0d push offset KeyName ; "Path"
第一行显示我的们的地址和一个字符串命名的<Path>。
我们使用 idautils.XrefsTo(ea, 1)获得的所有交叉引用的字符串。我们然后使用 xref.type 来打印交叉引用类型值。
idautils.XrefTypeName(xref.type)被用来打印这个类型的字符串表示。
有十二个不同的文件引用类型的值。该值可以在左边看到,它的对应名字可以被看到,如下所示。
0 = 'Data_Unknown'
1 = 'Data_Offset'
2 = 'Data_Write'
3 = 'Data_Read'
4 = 'Data_Text'
5 = 'Data_Informational'
16 = 'Code_Far_Call'
17 = 'Code_Near_Call'
18 = 'Code_Far_Jump'
19 = 'Code_Near_Jump'
20 = 'Code_User
21 = 'Ordinary_Flow'
xref.frm 打印从哪输出地址,xref.to 打印两个地址。xref.iscode 打印是否交叉引用是在一个代码段中。
在前面的例子中,我们使用 idautils.XrefsTo(ea, 1)的标志设置值为 1。如果这个标志为 0 任何交叉引用将不被显示。我们有如下反汇编。
.text:1000AAF6 jnb short loc_1000AB02 ; XREF
.text:1000AAF8 mov eax, [ebx+0Ch]
.text:1000AAFB mov ecx, [esi]
.text:1000AAFD sub eax, edi
.text:1000AAFF mov [edi+ecx], eax
.text:1000AB02
.text:1000AB02 loc_1000AB02: ; ea is
here()
.text:1000AB02 mov byte ptr [ebx], 1
我们在 1000AB02 出进行标记。这个地址从 1000AAF6 有一个交叉引用,但是它也有第二个交叉引用。
Python>print("0x%x %s" % (ea, idc.generate_disasm_line(ea, 0))) >>> 0x1000ab02 mov byte ptr [ebx], 1 Python>for xref in idautils.XrefsTo(ea, 1): print("%i %s 0x%x 0x%x %i" % (xref.type, idautils.XrefTypeName(xref.type),xref.frm, xref.to, xref.iscode)) Python> >>> 19 Code_Near_Jump 0x1000aaf6 0x1000ab02 1 Python>for xref in idautils.XrefsTo(ea, 0): print("%i %s 0x%x 0x%x %i" % (xref.type, idautils.XrefTypeName(xref.type),xref.frm, xref.to, xref.iscode)) Python> >>> 21 Ordinary_Flow 0x1000aaff 0x1000ab02 1 >>> 19 Code_Near_Jump 0x1000aaf6 0x1000ab02 1
第二个交叉引用是从 1000AAFF 到 1000AB02。
交叉引用不会造成分支指令。
他们也可以被普通的代码流影响。如果我们设置标志为 1 Ordinary_Flow 引用类型将不被增加。回到之前的 RtlCompareMemory 例子。
我们能使用 idautils.XrefsTo(ea,flow)来获取交叉引用.
Python>print("0x%x" % ea) >>> 0xa26c78 Python>idc.set_name(ea, "RtlCompareMemory", SN_CHECK) >>> True Python>for xref in idautils.XrefsTo(ea, 1): print("%i %s 0x%x 0x%x %i" % (xref.type, idautils.XrefTypeName(xref.type),xref.frm, xref.to, xref.iscode)) Python> >>> 3 Data_Read 0xa142a3 0xa26c78 0 >>> 3 Data_Read 0xa143e8 0xa26c78 0 >>> 3 Data_Read 0xa162da 0xa26c78 0
有时获得所有交叉引用时间有些长
Python>print("0x%x %s" % (ea, idc.generate_disasm_line(ea, 0))) >>> 0xa21138 extrn GetProcessHeap:dword Python> for xref in idautils.XrefsTo(ea, 1): print("%i %s 0x%x 0x%x %i" % (xref.type, idautils.XrefTypeName(xref.type),xref.frm, xref.to, xref.iscode)) Python> >>> 17 Code_Near_Call 0xa143b0 0xa21138 1 >>> 17 Code_Near_Call 0xa1bb1b 0xa21138 1 >>> 3 Data_Read 0xa143b0 0xa21138 0 >>> 3 Data_Read 0xa1bb1b 0xa21138 0 Python>print(idc.generate_disasm_line(0xa143b0, 0)) >>> call ds:GetProcessHeap
冗长来自 Data_read 和 Code_near 都添加到交叉引用。让所有的地址并将它们添加到一个集合可以使所有有用的地址被留下。
def get_to_xrefs(ea): xref_set = set([]) for xref in idautils.XrefsTo(ea, 1): xref_set.add(xref.frm) return xref_set def get_frm_xrefs(ea): xref_set = set([]) for xref in idautils.XrefsFrom(ea, 1): xref_set.add(xref.to) return xref_set
例如剩下的函数 GetProcessHeap 的例子。
Python>print("0x%x %s" % (ea, idc.generate_disasm_line(ea, 0))) 0xa21138 extrn GetProcessHeap:dword Python>get_to_xrefs(ea) set([10568624, 10599195]) Python>[("0x%x" % x) for x in get_to_xrefs(ea)] ['0xa143b0', '0xa1bb1b']
11. 搜索
有时我们需要搜索特定的字节,例如0x55,0x8B,0xEC。
为了查询二进制,可以使用 ida_search.find_binary(start, end, searchstr, radiux,sflag) ,start和end定义了搜索范围,searchstr是模式,radix编些处理器模块时使用,sflag是状态。如下所示。
SEARCH_UP = 0 #常用,搜索方向 SEARCH_DOWN = 1 #常用,搜索方向 SEARCH_NEXT = 2 #常用,获取下一个找到对象 SEARCH_CASE = 4 #常用,指定区分大小写 SEARCH_REGEX = 8 SEARCH_NOBRK = 16 SEARCH_NOSHOW = 32 #常用,不显示搜索进度 SEARCH_IDENT = 128 SEARCH_BRK = 256
第一行定义了搜索模式,可以是0x模式也可以是以下模式
ida_search.find_text(ea, y, x,searchstr, sflag) 搜索文本
idc.get_inf_attr(INF_MIN_EA) 获取可执行文件第一个地址
ida_search.find_binary(addr, idc.BADADDR, pattern,16,ida_search.SEARCH_DOWN)中的addr和idc.BADADDR是程序的起始地址和结束地址
Python> pattern = '55 8B EC' addr = idc.get_inf_attr(INF_MIN_EA) pattern = '55 8B EC' addr = idc.get_inf_attr(INF_MIN_EA) for x in range(0, 5): addr = ida_search.find_binary(addr, idc.BADADDR, pattern,16,ida_search.SEARCH_DOWN) if addr != idc.BADADDR: print("0x%x %s" % (addr, idc.generate_disasm_line(addr, 0))) Python> >>> 0x401000 push ebp >>> 0x401000 push ebp >>> 0x401000 push ebp >>> 0x401000 push ebp >>> 0x401000 push ebp
使用search_down不会向下搜索,要使用search_next
Python> pattern = '55 8B EC' addr = idc.get_inf_attr(INF_MIN_EA) pattern = '55 8B EC' addr = idc.get_inf_attr(INF_MIN_EA) for x in range(0, 5): addr = ida_search.find_binary(addr, idc.BADADDR, pattern, 16,ida_search.SEARCH_NEXT|ida_search.SEARCH_DOWN) if addr != idc.BADADDR: print("0x%x %s" % (addr, idc.generate_disasm_line(addr, 0)) Python> >>> 0x401000 push ebp >>> 0x401040 push ebp >>> 0x401070 push ebp >>> 0x4010e0 push ebp >>> 0x401150 push ebp
在搜索字符串时,可以使用[hex(y) for y inbytearray(“chrome.dll”)]将字符串转化为字节。
ida_search.find_text(ea, y, x,searchstr, sflag). 搜索文本格式
Python>cur_addr = idc.get_inf_attr(INF_MIN_EA) for x in range(0, 5): cur_addr = ida_search.find_text(cur_addr, 0, 0, "Accept",ida_search.SEARCH_DOWN) if cur_addr == idc.BADADDR: break print("0x%x %s" % (cur_addr, idc.generate_disasm_line(cur_addr, 0))) cur_addr = idc.next_head(cur_addr) Python> 0x40da72 push offset aAcceptEncoding; "Accept-Encoding:\n" 0x40face push offset aHttp1_1Accept; " HTTP/1.1\r\nAccept: */* \r\n " 0x40fadf push offset aAcceptLanguage; "Accept-Language: ru \r\n" ... 0x423c00 db 'Accept',0 0x423c14 db 'Accept-Language',0 0x423c24 db 'Accept-Encoding',0 0x423ca4 db 'Accept-Ranges',0
除了前面描述的模式搜索之外,还有两个函数可用于查找其他类型。
find api的命名约定使它很容易推断其总体功能。在讨论如何找到不同的类型之前,我们首先通过地址来识别类型。
有一个以“is”开头的api子集,可以用来确定地址的类型。这些api返回一个布尔值True或False。
f不是地址,而是内部标志。
- idc.is_code(f):如果ida将地址标记为代码,则返回True。
- idc.is_data(f):如果ida将地址标记为data,则返回True。
- idc.is_tail(f): 如果ida将地址标记为tail,则返回True。
- idc.is_unknown(f):如果IDA将该地址标记为未知,则返回True。当ida没有标识地址是代码还是数据时,将使用此类型。
- idc.is_head(f):如果ida将地址标记为head,则返回True。
idc.get_full_flags(ea)获得内部标志
Python>print("0x%x %s" % (ea, idc.generate_disasm_line(ea, 0))) 0x10001000 push ebp Python>idc.is_code(idc.get_full_flags(ea)) True
ida_search.find_code(ea, flag) 用于查找标记为“code”的下一个地址
Python>print("0x%x %s" % (ea, idc.generate_disasm_line(ea, 0))) 0x4140e8 dd offset dword_4140EC Python>addr = ida_search.find_code(ea, SEARCH_DOWN|SEARCH_NEXT) Python>print("0x%x %s" % (addr, idc.generate_disasm_line(addr, 0))) 0x41410c push ebx
ida_search.find_data(ea, flag) 它与ida_search.find_code(ea, flag)完全相同,查找下一个标记为“data”的地址
Python>print("0x%x %s" % (ea, idc.generate_disasm_line(ea, 0))) 0x41410c push ebx Python>addr = ida_search.find_data(ea, SEARCH_UP|SEARCH_NEXT) Python>print("0x%x %s" % (addr, idc.generate_disasm_line(addr, 0))) 0x4140ec dd 49540E0Eh, 746E6564h, 4570614Dh, 7972746Eh, 8, 1, 4010BCh
ida_search.find_unknown(ea, flag) 此函数用于查找未被ida标识为code或data的字节的地址。未知类型需要通过可视化或脚本进行进一步的手工分析。
Python>print("0x%x %s" % (ea, idc.generate_disasm_line(ea, 0))) 0x406a05 jge short loc_406A3A Python>addr = ida_search.find_unknown(ea, SEARCH_DOWN) Python>print("0x%x %s" % (addr, idc.generate_disasm_line(addr, 0)))) 0x41b004 db 0DFh ; ?
ida_search.find_defined(ea, flag) 用于查找ida标识为code或data的地址
0x41b900 db ? ; Python>addr = ida_search.find_defined(ea, SEARCH_UP) Python>print("0x%x %s" % (addr, idc.generate_disasm_line(addr, 0)))) 0x41b5f4 dd ?
这看起来可能没有任何实际价值,但是如果我们打印addr的交叉引用,我们会看到它正在被使用。
Python>for xref in idautils.XrefsTo(addr, 1): print("0x%x %s" % (xref.frm, idc.generate_disasm_line(addr, 0)))) Python> 0x4069c3 mov eax, dword_41B5F4[ecx*4]
ida_search.find_imm(ea, flag, value) 查找立即数 ,如果希望搜索特定的值,而不是搜索类型。例如,代码调用rand来生成一个随机数,但是我们找不到代码。如果我们知道rand使用值0x343FD作为种子,我们可以通过ida_search.find_imm(get_inf_attr(INF_MIN_EA), SEARCH_DOWN, 0x343FD)搜索该数字。
get_inf_attr(INF_MIN_EA) 获取最小地址
Python>addr = ida_search.find_imm(get_inf_attr(INF_MIN_EA), SEARCH_DOWN, 0x343FD ) Python>addr [268453092, 0] Python>print("0x%x %s %x" % (addr[0], idc.generate_disasm_line(addr[0], 0),addr[1])) 0x100044e4 imul eax, 343FDh 0
搜搜所有立即数
Python>addr = idc.get_inf_attr(INF_MIN_EA) while True: addr, operand = ida_search.find_imm(addr, SEARCH_DOWN | SEARCH_NEXT, 4) if addr == BADADDR: break print("0x%x %s Operand %i" % (addr, idc.generate_disasm_line(addr, 0),operand)) Python> >>> 400x402434 dd 9, 0FF0Bh, 0Ch, 0FF0Dh, 0Dh, 0FF13h, 13h, 0FF1Bh, 1Bh Operand 0 >>> 0x40acee cmp eax, 7Ah Operand 1 >>> 0x40b943 push 7Ah Operand 0 >>> 0x424a91 cmp eax, 7Ah Operand 1 >>> 0x424b3d cmp eax, 7Ah Operand 1 >>> 0x425507 cmp eax, 7Ah Operand 1
匹配yara非常快
12. 选择数据
我们并不总是需要搜索代码或数据。在某些情况下,我们已经知道代码或数据的位置,但是我们想选择它进行分析。
在这种情况下,我们可能只想突出显示代码并在IDAPython中开始使用它。
要获取所选数据的边界,我们可以使用idc.read_selection_start()获取起始数据,使用idc.read_selection_end()获取结束数据。
假设我们选择了下面的代码。
.text:00408E46 push ebp .text:00408E47 mov ebp, esp .text:00408E49 mov al, byte ptr dword_42A508 .text:00408E4E sub esp, 78h .text:00408E51 test al, 10h .text:00408E53 jz short loc_408E78 .text:00408E55 lea eax, [ebp+Data]
打印地址
Python>start = idc.read_selection_start() Python>print("0x%x" % start) 0x408e46 Python>end = idc.read_selection_end() Python>print("0x%x" % end) 0x408e58
13. 注释和重命名
添加注释、重命名函数以及与程序集交互是理解代码工作的最佳方式之一。
随着时间的推移,一些交互变得多余。在这种情况下,自动化流程是很有用的。
我们应该首先讨论注释和重命名的基础知识。
有两种类型的注释
- 第一个是常规注释。
- 第二个是可重复注释。
00411365 mov [ebp+var_214], eax 0041136B cmp [ebp+var_214], 0 ; 常规注释 00411372 jnz short loc_411392 ; 可重复注释 00411374 push offset sub_4110E0 00411379 call sub_40D060 0041137E add esp, 4 00411381 movzx edx, al 00411384 test edx, edx 00411386 jz short loc_411392 ; 可重复注释 00411388 mov dword_436B80, 1 00411392 00411392 loc_411392: 00411392 00411392 mov dword_436B88, 1 ; 可重复注释 0041139C push offset sub_4112C0
idc.set_cmt(ea, comment,0) 增加注释
idc.set_cmt(ea, comment, 1) 可重复注释
下面的代码在每次指令用异或置零寄存器或值时添加注释。
for func in idautils.Functions(): #遍历所有函数 flags = idc.get_func_attr(func, FUNCATTR_FLAGS) # skip library & thunk functions if flags & FUNC_LIB or flags & FUNC_THUNK: continue dism_addr = list(idautils.FuncItems(func)) #来循环所有指令 for ea in dism_addr: if idc.print_insn_mnem(ea) == "xor": #读取助记符 if idc.print_operand(ea, 0) == idc.print_operand(ea, 1): #验证操作数是否相等 comment = "%s = 0" % (idc.print_operand(ea, 0)) idc.set_cmt(ea, comment, 0)
0040B0F7 xor al, al ; al = 0 0040B0F9 jmp short loc_40B163
idc.get_cmt(ea, repeatable) 获取可重复注释
Python>print("0x%x %s" % (ea, idc.generate_disasm_line(ea, 0))) 0x40b0f7 xor al, al ; al = 0 Python>idc.get_cmt(ea, False) al = 0
idc.set_func_cmt(ea, cmt, repeatable) 为函数增加评论
idc.get_func_cmt(ea, repeatable) 获得函数评论
Python>print("0x%x %s" % (ea, idc.generate_disasm_line(ea, 0))) 0x401040 push ebp Python>idc.get_func_name(ea) sub_401040 Python>idc.set_func_cmt(ea, "check out later", 1) True
之后会看到函数注释
00401040 ; check out later 00401040 ; Attributes: bp-based frame 00401040 00401040 sub_401040 proc near 00401040 . 00401040 var_4 = dword ptr -4 00401040 arg_0 = dword ptr 8 00401040 00401040 push ebp 00401041 mov ebp, esp 00401043 push ecx 00401044 push 723EB0D5h
00401C07 push ecx 00401C08 call sub_401040 ; check out later 00401C0D add esp, 4