IDA Pro内置的IDC脚本语言是一种灵活的、C语言风格的脚本语言,旨在帮助逆向工程师更轻松地进行反汇编和静态分析。IDC脚本语言支持变量、表达式、循环、分支、函数等C语言中的常见语法结构,并且还提供了许多特定于反汇编和静态分析的函数和操作符。由于其灵活性和可扩展性,许多逆向工程师都喜欢使用IDC脚本语言来自动化反汇编和静态分析过程,以提高效率和准确性。
在IDA中如果读者按下Shift + F2
则可调出脚本编辑器,如下图所示,其中左侧代表当前脚本的名称列表,右侧则代表脚本的具体实现细节,底部存在三个菜单,第一个按钮是运行脚本,第二个按钮是覆盖导入脚本,第三个则是追加导入,他们之间的功能个有不同,读者可自行体会;
3.1.1 IF语句的构建 IF语句的使用非常容易,如下代码,通过ScreenEA()
函数识别到当前光标所在位置处的指令内存地址,并对比该内存地址是否符合特定的条件,如果符合则输出,不符合则最终输出没有找到;
#include <idc.idc> static main () { auto CurrAddress = ScreenEA(); if (CurrAddress == 0x0046E31A ) { Message("程序OEP => 0x%x \n" ,CurrAddress); } else if (CurrAddress == 0x0046E331 ) { Message("程序OEP => 0x%x \n" ,CurrAddress); } else { Message("没有扎到OEP \n" ); } }
3.1.2 FOR语句的构建 与C语言格式几乎一致,For语句的构建也很容易理解,首先程序通过GetFunctionAttr()
函数并设置FUNCATTR_START
属性获取到当前光标所指向程序段的开始地址,通过FUNCATTR_END
设置光标的结束位置,最后调用For循环,一次输出当前内存地址及下一个内存地址,直到将本段内容全部输出为止;
#include <idc.idc> static main () { auto origEA,currEA,funcStart,funcEnd; origEA = ScreenEA(); funcStart = GetFunctionAttr(origEA,FUNCATTR_START); funcEnd = GetFunctionAttr(origEA,FUNCATTR_END); Message("OEP: %x 起始地址: %x --> 结束地址: %x \n" ,origEA,funcStart,funcEnd); for (currEA = funcStart; currEA != -1 ; currEA=NextHead(currEA,funcEnd)) { Message("指令地址:%8x \n" ,currEA); } }
3.1.3 WHILE语句的构建 该语句的构建与FOR语句基本一致,与FOR语句唯一的不同在于该语句只能接受一个参数,如下代码中读者需要注意GetFunctionName()
可用于获取当前光标所在位置处所属函数的名称。
#include <idc.idc> static main () { auto origEA,currEA,funcStart,funcEnd; origEA = ScreenEA(); funcStart = GetFunctionAttr(origEA,FUNCATTR_START); funcEnd = GetFunctionAttr(origEA,FUNCATTR_END); Message("OEP: %x 起始地址: %x --> 结束地址: %x \n" ,origEA,funcStart,funcEnd); while (currEA != BADADDR) { Message("--> %x name: %s \n" ,currEA,GetFunctionName(currEA)); currEA = NextHead(currEA,funcEnd); } }
3.1.4 函数的实现 IDA中使用函数通常可在一个字符串之前定义为static
,函数的参数列表一般而言是以逗号进行间隔开的,当函数存在返回值是则通过return
语句返回。
#include <idc.idc> static OutPutAddress (MyString) { auto currAddress; currAddress = ScreenEA(); Message("%d \n" ,MyString); return currAddress; } static OutPutAddressB (x,y) { return x+y; } static main () { auto ret = OutPutAddress(123 ); Message("返回当前地址 = 0x%x \n" ,ret); auto ref = OutPutAddressB(100 ,200 ); Message("计算数值 = %d \n" ,ref); }
3.1.5 定义并使用数组 与高级语言类似,IDC脚本中同样支持数组操作,不同于C语言中的数组,IDC中在使用时首先需要通过CreateArray("array")
创建一个数组,当数组指针被创建成功后下一步则是通过GetArrayId("array")
得到该数组的指针,通过指针读者可以使用SetArrayString
设置一个字符串变量,或使用SetArrayLong
设置整数变量,当用户需要使用变量时则需要通过GetArrayElement()
函数对数组内的数据进行提取,提取时AR_STR
代表提取字符串,AR_LONG
则代表提取整数类型,当读者需要删除数组内的特定元素可使用DelArrayElement()
函数,最后使用结束调用DeleteArray()
注销整个数组;
#include <idc.idc> static main () { auto array_ptr = CreateArray("array" ); auto ptr = GetArrayId("array" ); Message("获取到的操作指针: %x \n" ,ptr); SetArrayString(ptr,0 ,"hello" ); SetArrayString(ptr,1 ,"lyshark" ); SetArrayLong(ptr,2 ,100 ); SetArrayLong(ptr,3 ,200 ); auto st = GetArrayElement(AR_STR,ptr,0 ); auto st1 = GetArrayElement(AR_STR,ptr,1 ); Message("提取字符串变量: %s %s !\n" ,st,st1); auto lo = GetArrayElement(AR_LONG,ptr,2 ); Message("提取整数变量: %d \n" ,lo); DelArrayElement(AR_STR,ptr,0 ); DeleteArray(ptr); }
3.1.6 字符串处理 IDC中读者可以使用form()
函数实现对特定字符串的格式化输出操作,IDC中同样也内置了各类转换函数,如下代码所示,则是IDC中可以经常被用到的函数调用,读者可自行参考;
#include <idc.idc> static main () { auto name = form("hello %s" ,"lyshark" ); Message("格式化后的内容: %s \n" ,name); Message("十六进制转为整数: %d \n" ,xtol("0x41" )); Message("十进制100转为八进制: %d \n" ,ltoa(100 ,8 )); Message("十进制100转换二进制: %d \n" ,ltoa(100 ,2 )); Message("字符A的ASCII: %d \n" ,ord("A" )); Message("计算字符串长度: %d \n" ,strlen ("hello lyshark" )); auto main = "hello lyshark" ; auto sub = "lyshark" ; Message("寻找子串: %d \n" ,strstr (main,sub)); }
3.1.7 枚举所有函数 如下脚本实现了枚举当前指针所在位置处所有函数名称及地址,首先通过ScreenEA()
函数获取当前指针所在位置,通过SegStart()
用于获取该指针所在位置处模块的开始地址,与之对应的是SegEnd();
则用于获取结束地址,接着通过调用GetFunctionName();
得到当前地址处的函数名,并依次通过NextFunction();
得到下一个模块地址,最终输出所有函数名及其地址信息;
#include <idc.idc> static main () { auto currAddr,func,endSeg,funcName,counter; currAddr = ScreenEA(); func = SegStart(currAddr); endSeg = SegEnd(currAddr); Message("%x --> %x \n" ,func,endSeg); counter = 0 ; while (func != BADADDR && func < endSeg) { funcName = GetFunctionName(func); if (funcName != " " ) { Message("%x --> %s \n" ,func,funcName); counter++; } func = NextFunction(func); } }
当然读者可以通过增加IF语句来判断funcName
函数名是否是我们所需要枚举的,如果是则输出,如果不是则继续下一个函数,依次类推实现函数枚举功能,读者只需要在上述代码基础上稍加改进即可实现;
#include <idc.idc> static main () { auto currAddr,func,endSeg,funcName,counter; currAddr = ScreenEA(); func = SegStart(currAddr); endSeg = SegEnd(currAddr); Message("%x --> %x \n" ,func,endSeg); counter = 0 ; while (func != BADADDR && func < endSeg) { funcName = GetFunctionName(func); if (funcName != " " ) { if (funcName == "__lock" ) { Message("%x --> %s \n" ,func,funcName); } counter++; } func = NextFunction(func); } }
3.1.8 设置内存区域标签高亮 标签高亮功能的实现依赖于SetColor
函数,该函数传入三个参数,其中参数1用于指定需要检索的范围,该范围可以通过NextHead()
函数获取到,只要该节点不会返回BADADDR
则可以继续遍历下一个节点,第二个参数则代表标注类型,第三个参数代表要在那个位置进行标注;
#include <idc.idc> static main (void ) { auto head, op; head = NextHead(0x00000000 , 0xFFFFFFFF ); while ( head != BADADDR ) { op = GetMnem(head); Message("%x %s \n" ,head,op); if ( op == "jmp" || op == "call" ) SetColor(head, CIC_ITEM, 0x010187 ); if (op == "xor" ) SetColor(head, CIC_ITEM, 0x010198 ); head = NextHead(head, 0xFFFFFFFF ); } }
3.1.9 地址反汇编输出 在IDA中有时我们需要对特定位置进行反汇编,并以脚本的方式输出,此时读者可使用GetDisasm(inst)
函数来实现,该函数传入一个RfirstB
生成的迭代类型,并依次循环输出,直到对100行输出为止;
#include <idc.idc> static main (void ) { auto decode = 0x401000 ; auto xref; for (xref = RfirstB(decode); xref != BADADDR; xref = RnextB(decode,xref)) { Message("xref: %x\n" ,xref); auto i = 0 ; auto inst = xref; auto op; while ((i < 100 ) ) { inst = FindCode(inst,0x00 ); op = GetDisasm(inst); Message("%x --> %s \n" ,inst,op); i++; } } }
当具备了反汇编功能后,那么读者则可通过各种方式实现对指令集的判断,并以此来实现过滤特定指令地址并输出的目的,如下所示,通过strstr()
函数对符合特定条件的字符串进行过滤,当找到后返回该函数的所在位置;
#include <idc.idc> static main () { auto currAddr,startSeg,endSeg; currAddr = ScreenEA(); startSeg = SegStart(currAddr); endSeg = SegEnd(currAddr); Message("OEP = %x 起始地址: %x 结束地址: %x \n" ,currAddr,startSeg,endSeg); while (startSeg < endSeg) { auto op = GetDisasm(startSeg); if (strstr (op,"push esi" )==0 ) { startSeg++; op = GetDisasm(startSeg); if (strstr (op,"push edi" )) { Message("特征: %x \n" ,startSeg-1 ); } } startSeg++; } }
当然反汇编函数并非只有GetDisasm
读者同样可以使用GetMnem
返回位于特定地址处的指令,GetOpnd
用于返回特定位置处的机器码,同样可以使用FindBinary
实现对特定地址的特征码搜索功能;
#include <idc.idc> static main () { auto code = FindBinary(0x401020 ,1 ,"55 8B EC" ); Message("%x \n" ,code); code = GetDisasm(0x401000 ); Message("%s \n" ,code); code = GetMnem(0x401000 ); Message("%s \n" ,code); code = GetOpnd(0x401070 ,0 ); Message("%s \n" ,code); }
3.1.10 枚举函数栈帧 生成每个函数的栈帧,通过NextFunction()
函数可实现枚举当前模块内所有函数地址,通过循环并调用GetFram()
来得到当前函数栈帧大小,并使用GetMemberOffset()
保存栈中返回地址偏移量,依次循环输出当前函数内的完整栈帧数据;
#include <idc.idc> static main () { auto addr,args,end,locals,frame,firstArg,name,ret; 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 ; args = GetStrucSize(frame) - firstArg; Message("函数: %s 开始: 0x%x 结束: 0x%x 大小: %d bytes 栈帧: %d bytes (%d args) \n" ,name,addr,end,locals,args,args/4 ); } }
3.1.11 检索交叉引用 枚举当前模块中的交叉引用,通过XrefType()
函数可枚举出当前被分析程序中的交叉引用情况,如下案例中实现了对当前程序内所有交叉引用的枚举工作,并输出三个参数,参数1代表主函数,参数2代表被引用函数,参数3代表当前函数的内存地址;
#include <idc.idc> static main () { 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 | %s | %x \n" ,name,Name(target),inst); } } } } }
如果读者想要实现枚举特定一个函数的交叉引用信息,则可通过使用LocByName(bad_func)
增加过滤条件,并依次实现过滤特定函数的目的,代码的修改只需要小改即可;
#include <idc.idc> static FindFunction (bad_func) { auto func,addr,xref,source; func = LocByName(bad_func); if (func == BADADDR) { Message("error \n" ); } 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 call => %0x in %s \n" ,bad_func,addr,source); } } } } static main () { FindFunction("LoadString" ); }