- 1.IDC示例
- 脚本1:枚举函数
- 脚本2:枚举指令
- 脚本3:枚举交叉引用
- test 1:向上枚举调用方
- test 2:向下找到被当前函数调用的
- 脚本4:枚举导出函数
- 脚本5:查找和标记函数参数
- 2.IDAPython示例
- 脚本1:枚举函数
- 3.参考
IDA的脚本主要有2种语法,一种类似于C语言(高版本更接近C++)的IDC,一种支持python的IDAPython
1.IDC示例 脚本1:枚举函数疑问:《IDA Pro权威指南第二版》的15.5章 编写IDA脚本,已经有详细的介绍了,为什么还要写下面的内容?copy不浪费时间吗?
说明:因为代码这个东西,看着会了和在自己的环境上运行符合预期是两回事,下面是自己以前通过《IDA Pro权威指南》学习IDA脚本的笔记(加入了自己学习时的记录),方便自己查看
提示:自己手动敲和调试一遍,最多花费半天时间,IDA的脚本就入门了,更加复杂的就看自己的需求了
问题:学IDA脚本有什么用?复杂的不说,看完下面最起码了解了IDA的很多实现原理,比如交叉引用的原理
#includestatic main() { Message("[枚举函数示例]n"); auto addr, end, args, locals, frame, firstArg, name, ret; addr = 0; for (addr = NextFunction(addr); addr != BADADDR; addr = NextFunction(addr)) { name = Name(addr); end = GetFunctionAttr(addr, FUNCATTR_END); locals = GetFunctionAttr(addr, FUNCATTR_FRSIZE); frame = GetFrame(addr); //获取每个函数的栈帧的句柄 ret = GetMemberOffset(frame , " r"); //栈中保存的返回地址的偏移量 if (ret == -1) continue; firstArg = ret + 4; //第一个参数在stack中,是返回值所在位置+的位置 args = GetStrucSize(frame) - firstArg; //函数GetStrucSize确定栈帧的大小 Message("Function: %s, start at %x, ends at %xn", name, addr, end); Message(" Local variable area is %d bytesn", locals); Message(" Arguments occupy %d bytes (%d args)n", args, args /4); } }
运行结果,符合预期
[枚举函数示例] Function: start, start at 401000, ends at 401031 Local variable area is 0 bytes Arguments occupy 0 bytes (0 args) Function: sub_401031, start at 401031, ends at 401125 Local variable area is 80 bytes Arguments occupy 4 bytes (1 args) Function: sub_401125, start at 401125, ends at 401390 Local variable area is 0 bytes Arguments occupy 16 bytes (4 args)脚本2:枚举指令
#includestatic main() { Message("[枚举指令示例:计算光标当前所在位置的函数所包含的指令的数量]n"); auto func, end, count, inst; func = GetFunctionAttr(ScreenEA(), FUNCATTR_START);//确定光标所在位置的函数的起始地址 if (func != -1) { end = GetFunctionAttr(func, FUNCATTR_END); count = 0; inst = func; while (inst < end) { count++; inst = FindCode(inst, SEARCH_DOWN | SEARCH_NEXT); //遍历每条指令的交叉引用,可解决非相邻函数 } Warning("%s contains %d instructionsn", Name(func), count); } else { Warning("No function found at %xn", ScreenEA()); } }
运行结果,符合预期
脚本3:枚举交叉引用交叉引用实际上是有2个方向的:
- 向下:找到被当前位置调用的
- 向上:找到当前位置的调用方
下面是交叉引用常使用的函数或者宏在idc.idc的节选片段
//常用函数和宏汇总: #define Rfirst(From) get_first_cref_from(From) #define RfirstB(To) get_first_cref_to(To) #define Rnext(From, current) get_next_cref_from(From, current) #define RnextB(To, current) get_next_cref_to(To, current) // Get first code xref to 'to' long get_first_cref_to(long to); // Get next code xref to 'to' long get_next_cref_to(long to, long current); long RfirstB(long to) //返回转交控制权到给定地址的第一个位置 long RnextB(long to, long current); //如果 current 已经在前一次调用 RfirstB 或 RnextB时返回, //则返回下一个转交控制权到给定地址(to)的位置 //RfirstB和RnextB去掉B的两个函数,用法相同,不介绍了 #define XrefType() get_xref_type() // returns type of the last xref obtained by get_first_.../get_next_... functions. // Return values are fl_... or dr_... long get_xref_type(void); //返回一个常量,说明某个交叉引用查询函数(如 Rfirst)返回的最后 //一个交叉引用的类型 //类型说明: #define fl_CF 16 // Call Far #define fl_CN 17 // Call Near #define fl_JF 18 // jumpto Far #define fl_JN 19 // jumpto Near #define fl_F 21 // Ordinary flowtest 1:向上枚举调用方
#includestatic list_callers(bad_func) { Message("[枚举交叉引用:枚举一个函数的调用方]n"); auto func, addr, xref, source; func = LocByName(bad_func); //根据函数名称,查找函数的起始地址 if (func == BADADDR) { Warning("Sorry, %s not found in database", bad_func); } else { for (addr = RfirstB(func); addr != BADADDR; addr = RnextB(func, addr)) { xref = XrefType(); //获取交叉引用的类型 if (xref == fl_CN || xref == fl_CF) { //调用类型 source = GetFunctionName(addr); Message("%s is called from 0x%x in %sn", bad_func, addr, source); } } } } static main() { list_callers("ExitProcess"); list_callers("MessageBoxA"); }
运行结果:
test 2:向下找到被当前函数调用的明白了上面的示例,这个示例也很容易,就是更改一下RfirstB函数
#includestatic main() { Message("[枚举交叉引用:当前光标所在函数调用的每个函数]n"); auto func, end, target, inst, name, flags, xref; flags = SEARCH_DOWN | SEARCH_NEXT; func = GetFunctionAttr(ScreenEA(), FUNCATTR_START); if (func != -1) { name = Name(func); end = GetFunctionAttr(func, FUNCATTR_END); for (inst = func; inst < end; inst = FindCode(inst, flags)) { for (target = Rfirst(inst); target != BADADDR; target = Rnext(inst, target)) { xref = XrefType(); //获取交叉引用的类型 if (xref == fl_CN || xref == fl_CF) { //调用类型 Message("[%s] calls %s from 0x%xn", name, Name(target), inst); } } } } else { Warning("No function found at location %x", ScreenEA()); } }
运行结果:
脚本4:枚举导出函数下面是一个典型的.idt文件的格式,《IDA Pro权威指南第二版》的13章 扩展IDA的知识有详细介绍
每个函数的信息会存储在一行,基本格式:序号ord + 空格 + Name=函数 + Pascal + Comment,这些如果你看过PE文件的输入表和导出表,就不会陌生了,简单说明如下:
- 序号ord:等于0,有特殊用途,此时Name用来指定当前的.idt文件描述的库的名称
- Pascal:标识函数是否使用stdcall调用约定(有这个记录显示使用的是stdcall),并指出该函数在返回时共栈中删除的字节个数
- comment:注释,非必须
编写idsutils提供的库解析器,利用IDA的函数分析功能生成更详细的.idt文件
下面脚本在IDA中打开一个共享库后生成一个.idt文件
//生成.idt文件的脚本 #includestatic main() { Message("[枚举导出函数]n"); auto entryPoints, i, ord, addr, name, purged, file, fd; file = AskFile(1, "*.idt", "Select IDT save file"); fd = fopen(file, "w"); entryPoints = GetEntryPointQty();//返回库导出的符号的数量 fprintf(fd, "ALIGNMENT 4n"); fprintf(fd, "0 Name=%sn", GetInputFile());//GetInputFile()返回加载到IDA中的文件的名称 for (i = 0; i < entryPoints; i++) { ord = GetEntryOrdinal(i); //返回导出表中函数的序号 if (ord == 0) continue; addr = GetEntryPoint(ord); //返回与一个导出函数关联的地址 if (addr == ord) { continue; //entry point has no ordinal } name = Name(addr); purged = GetFunctionAttr(addr, FUNCATTR_ARGSIZE); if (purged > 0) { fprintf(fd, "[%3d] 0x%x Name=%s Pascal=%dn", ord, addr, name, purged); } else { fprintf(fd, "[%3d] 0x%x Name=%sn", ord, addr, name); } } }
操作步骤示例如下:
- 1.IDA打开微信的核心dll(WeChatWin.dll,微信外层exe只是一个简单的壳,真正功能是在这个dll实现的),查看一下导入表(最后生成结果来这里对比一下,看看是否生成的结果符合预期)
- 2.IDA执行脚本(先选择一个要保存的结果的.idt文件,再运行脚本),最后查看文件,有下面内容:
ALIGNMENT 4 0 Name=WeChatWin.dll [ 1] 0x1222ea70 Name=?$TSS0@?1??create@?$StaticObject@UPolymorphicCasters... [ 2] 0x100e6940 Name=??0IChannelLogWriter@@QAE@ABV0@@Z Pascal=4 [ 3] 0x100e6940 Name=??0IChannelLogWriter@@QAE@ABV0@@Z Pascal=4 [ 4] 0x100e6930 Name=??0IChannelLogWriter@@QAE@XZ [ 5] 0x100908b0 Name=??4ILogWriter@@QAEAAV0@ABV0@@Z Pascal=4 [ 6] 0x100908b0 Name=??4ILogWriter@@QAEAAV0@ABV0@@Z Pascal=4 [ 7] 0x100908b0 Name=??4ILogWriter@@QAEAAV0@ABV0@@Z Pascal=4 [ 8] 0x100908b0 Name=??4ILogWriter@@QAEAAV0@ABV0@@Z Pascal=4 [ 9] 0x100908b0 Name=??4ILogWriter@@QAEAAV0@ABV0@@Z Pascal=4 [ 10] 0x11d9ce34 Name=??_7IChannelLogWriter@@6B@
可以看到,我们可以按照自己的格式要求输出不同的内容
脚本5:查找和标记函数参数正常函数参数都是通过压栈push指令传递的,但是gcc的3.4版本以后直接使用mov语句将参数压入栈中,这导致IDA识别可能异常
正常push传递参数示例:
mov传递参数示例:
可以使用下面的脚本自动识别函数调用,设置参数的指令
//参数自动识别 //原理:对于每一条使用 esp 作为基址寄存器向内存位置写入数据的指令,该脚本确定上述内存位置在栈中的深度, // 并添加一条注释,指出被压入的是哪一个参数 #includestatic main() { //局限:脚本只针对基于EBP的帧 Message("[自动标记参数]n"); auto addr, op, end, idx; auto func_flags, type, val, search; search = SEARCH_DOWN | SEARCH_NEXT; addr = GetFunctionAttr(ScreenEA(), FUNCATTR_START); func_flags = GetFunctionFlags(addr); if (func_flags & FUNC_FRAME) { //Is this an ebp-based frame? end = GetFunctionAttr(addr, FUNCATTR_END); for(; addr < end && addr != BADADDR; addr = FindCode(addr, search)) { type = GetOpType(addr, 0); if (type == 3) { //Is this a register indirect operand? if (GetOperandValue(addr, 0) == 4) { //Is the register esp? MakeComm(addr, "arg_0"); //[esp] equal to arg_0 } } else if (type == 4) { //Is this a register + displacement operand? idx = strstr(GetOpnd(addr, 0), "[esp"); //Is the register esp? if (idx != -1) { val = GetOperandValue(addr, 0); //Get the displacement MakeComm(addr, form("arg_%d", val)); //add a comment } } } } }
期望效果:
2.IDAPython示例说明:手里没有现成的案例进行测试,就先不测了,记录一下这个功能
学习前可以快速参考:GitHub - yanxxd/IDA: idc脚本, IDAPython脚本, ida插件等.
IDAPython官方函数文档: IDAPython官方文档函数查询
IDC函数官方文档查询: IDC函数
IDA版本与版本之间的差异化函数查询: IDA版本函数差异化
IDAPython是一个插件,在IDA中集成了python解释器;IDAPython可以访问python的数据处理功能和所有的python模块
IDAPython通过3个python模块将python代码注入到IDA中
- idaapi模块:负责访问核心的IDA API
- idc模块:负责提供IDC中多有函数功能,这个模块基本上是IDC API的写照
- idautils模块:提供大量使用函数,许多函数可生成各种数据库相关对象的python列表
脚本1:枚举函数注意:所有IDAPython脚本会自动导入idc和idautils模块,如果需要idaapi模块,需要自己手动导入
脚本就是将IDC语法改成python,没啥难度;主要是会利用IDAPython官方函数文档: IDAPython官方文档函数查询,查找对应函数
funcs = Functions()
for addr in funcs:
name = get_func_name(addr)
end = find_func_end(addr)
locals = get_frame_lvar_size(addr)
args = get_frame_args_size(addr)
print("Function: %s, start at %x, ends at %x" % (name, addr, end))
print(" Local variable area is %d bytes" % locals)
print(" Arguments occupy %d bytes (%d args)n" % (args, args / 4))
3.参考提示:上面IDC写的几个示例都可以转成IDAPython的,都是IDC翻译成python的体力活,不写了,累
-
1.《IDA Pro权威指南第二版》的15.5章 编写IDA脚本
-
2.《IDA Pro权威指南第二版》的13章 扩展IDA的知识



