IDA脚本一页纸(IDC+IDAPython)-示例版

文章目录

  • 1.IDC示例
    • 脚本1:枚举函数
    • 脚本2:枚举指令
    • 脚本3:枚举交叉引用
      • test 1:向上枚举调用方
      • test 2:向下找到被当前函数调用的
    • 脚本4:枚举导出函数
    • 脚本5:查找和标记函数参数
  • 2.IDAPython示例
    • 脚本1:枚举函数
  • 3.参考

IDA的脚本主要有2种语法,一种类似于C语言(高版本更接近C++)的IDC,一种支持python的IDAPython

疑问:《IDA Pro权威指南第二版》的15.5章 编写IDA脚本,已经有详细的介绍了,为什么还要写下面的内容?copy不浪费时间吗?

说明:因为代码这个东西,看着会了和在自己的环境上运行符合预期是两回事,下面是自己以前通过《IDA Pro权威指南》学习IDA脚本的笔记(加入了自己学习时的记录),方便自己查看

提示:自己手动敲和调试一遍,最多花费半天时间,IDA的脚本就入门了,更加复杂的就看自己的需求了

问题:学IDA脚本有什么用?复杂的不说,看完下面最起码了解了IDA的很多实现原理,比如交叉引用的原理


重要的事情说3遍:

笔记更新换位置了!
笔记更新换位置了!
笔记更新换位置了!

新位置:我写的新系列,刚开始写没几天,后续文章主要在新地址更新,欢迎支持;写作不易,且看且珍惜(点击跳转,欢迎收藏)

1.IDC示例

脚本1:枚举函数

#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)

脚本2:枚举指令

#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());
	}
}

运行结果,符合预期

IDA脚本一页纸(IDC+IDAPython)-示例版_第1张图片

脚本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);

/* 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

test 1:向上枚举调用方

#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");
}

运行结果:

IDA脚本一页纸(IDC+IDAPython)-示例版_第2张图片

test 2:向下找到被当前函数调用的

明白了上面的示例,这个示例也很容易,就是更改一下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());	
	}
}

运行结果:

IDA脚本一页纸(IDC+IDAPython)-示例版_第3张图片

脚本4:枚举导出函数

下面是一个典型的.idt文件的格式,《IDA Pro权威指南第二版》的13章 扩展IDA的知识有详细介绍

IDA脚本一页纸(IDC+IDAPython)-示例版_第4张图片

每个函数的信息会存储在一行,基本格式:序号ord + 空格 + Name=函数 + Pascal + Comment,这些如果你看过PE文件的输入表和导出表,就不会陌生了,简单说明如下:

  • 序号ord:等于0,有特殊用途,此时Name用来指定当前的.idt文件描述的库的名称
  • Pascal:标识函数是否使用stdcall调用约定(有这个记录显示使用的是stdcall),并指出该函数在返回时共栈中删除的字节个数
  • comment:注释,非必须

编写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);
        }
    }
}

操作步骤示例如下:

  • 1.IDA打开微信的核心dll(WeChatWin.dll,微信外层exe只是一个简单的壳,真正功能是在这个dll实现的),查看一下导入表(最后生成结果来这里对比一下,看看是否生成的结果符合预期)

IDA脚本一页纸(IDC+IDAPython)-示例版_第5张图片

  • 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 作为基址寄存器向内存位置写入数据的指令,该脚本确定上述内存位置在栈中的深度,
//     并添加一条注释,指出被压入的是哪一个参数
#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
                }
            }
        }
    }
}

期望效果:

在这里插入图片描述

说明:手里没有现成的案例进行测试,就先不测了,记录一下这个功能

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列表

注意:所有IDAPython脚本会自动导入idcidautils模块,如果需要idaapi模块,需要自己手动导入

脚本1:枚举函数

脚本就是将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的体力活,不写了,累

3.参考

  • 1.《IDA Pro权威指南第二版》的15.5章 编写IDA脚本

  • 2.《IDA Pro权威指南第二版》的13章 扩展IDA的知识

你可能感兴趣的:(调试和性能工具,c++,开发语言,安全,windows,python)