IDA的脚本主要有2种语法,一种类似于C语言(高版本更接近C++)的IDC
,一种支持python的IDAPython
疑问
:《IDA Pro权威指南第二版》的15.5章 编写IDA脚本,已经有详细的介绍了,为什么还要写下面的内容?copy不浪费时间吗?
说明
:因为代码这个东西,看着会了和在自己的环境上运行符合预期是两回事,下面是自己以前通过《IDA Pro权威指南》学习IDA脚本的笔记(加入了自己学习时的记录),方便自己查看
提示
:自己手动敲和调试一遍,最多花费半天时间,IDA的脚本就入门了,更加复杂的就看自己的需求了
问题
:学IDA脚本有什么用?复杂的不说,看完下面最起码了解了IDA的很多实现原理,比如交叉引用的原理
重要的事情说3遍:
笔记更新换位置了!
笔记更新换位置了!
笔记更新换位置了!
新位置
:我写的新系列,刚开始写没几天,后续文章主要在新地址更新,欢迎支持;写作不易,且看且珍惜(点击跳转,欢迎收藏)
#include
static 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 %x\n", name, addr, end);
Message(" Local variable area is %d bytes\n", 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)
#include
static 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 instructions\n", Name(func), count);
}
else {
Warning("No function found at %x\n", ScreenEA());
}
}
运行结果,符合预期
交叉引用实际上是有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);
/* 1.to 相关函数 */
long RfirstB(long to) //返回转交控制权到给定地址的第一个位置
long RnextB(long to, long current); //如果 current 已经在前一次调用 RfirstB 或 RnextB时返回,
//则返回下一个转交控制权到给定地址(to)的位置
/* 2.from 相关函数 */
//RfirstB和RnextB去掉B的两个函数,用法相同,不介绍了
/* 3.交叉引用类型 */
#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 flow
#include
static 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 %s\n", bad_func, addr, source);
}
}
}
}
static main() {
list_callers("ExitProcess");
list_callers("MessageBoxA");
}
运行结果:
明白了上面的示例,这个示例也很容易,就是更改一下RfirstB
函数
#include
static 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%x\n", name, Name(target), inst);
}
}
}
}
else {
Warning("No function found at location %x", ScreenEA());
}
}
运行结果:
下面是一个典型的.idt
文件的格式,《IDA Pro权威指南第二版》的13章 扩展IDA的知识有详细介绍
每个函数的信息会存储在一行,基本格式:序号ord
+ 空格
+ Name=函数
+ Pascal
+ Comment
,这些如果你看过PE文件的输入表和导出表,就不会陌生了,简单说明如下:
.idt
文件描述的库的名称stdcall
调用约定(有这个记录显示使用的是stdcall
),并指出该函数在返回时共栈中删除的字节个数编写idsutils
提供的库解析器,利用IDA的函数分析功能生成更详细的.idt
文件
下面脚本在IDA中打开一个共享库后生成一个.idt
文件
//生成.idt文件的脚本
#include
static 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 4\n");
fprintf(fd, "0 Name=%s\n", 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=%d\n", ord, addr, name, purged);
}
else {
fprintf(fd, "[%3d] 0x%x Name=%s\n", ord, addr, name);
}
}
}
操作步骤示例如下:
WeChatWin.dll
,微信外层exe只是一个简单的壳,真正功能是在这个dll实现的),查看一下导入表(最后生成结果来这里对比一下,看看是否生成的结果符合预期).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@
可以看到,我们可以按照自己的格式要求输出不同的内容
正常函数参数都是通过压栈push指令传递的,但是gcc的3.4版本以后直接使用mov语句将参数压入栈中,这导致IDA识别可能异常
正常push传递参数示例:
mov传递参数示例:
可以使用下面的脚本自动识别函数调用,设置参数的指令
//参数自动识别
//原理:对于每一条使用 esp 作为基址寄存器向内存位置写入数据的指令,该脚本确定上述内存位置在栈中的深度,
// 并添加一条注释,指出被压入的是哪一个参数
#include
static 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
}
}
}
}
}
期望效果:
说明:手里没有现成的案例进行测试,就先不测了,记录一下这个功能
学习前可以快速参考: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 APIidc
模块:负责提供IDC中多有函数功能,这个模块基本上是IDC API的写照idautils
模块:提供大量使用函数,许多函数可生成各种数据库相关对象的python列表注意:所有
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))
提示:上面IDC写的几个示例都可以转成IDAPython的,都是IDC翻译成python的体力活,不写了,累
1.《IDA Pro权威指南第二版》的15.5章 编写IDA脚本
2.《IDA Pro权威指南第二版》的13章 扩展IDA的知识