3.1 IDA Pro编写IDC脚本入门

打印 上一主题 下一主题

主题 659|帖子 659|积分 1977

IDA Pro内置的IDC脚本语言是一种灵活的、C语言风格的脚本语言,旨在帮助逆向工程师更轻松地进行反汇编和静态分析。IDC脚本语言支持变量、表达式、循环、分支、函数等C语言中的常见语法结构,并且还提供了许多特定于反汇编和静态分析的函数和操作符。由于其灵活性和可扩展性,许多逆向工程师都喜欢使用IDC脚本语言来自动化反汇编和静态分析过程,以提高效率和准确性。
在IDA中如果读者按下Shift + F2则可调出脚本编辑器,如下图所示,其中左侧代表当前脚本的名称列表,右侧则代表脚本的具体实现细节,底部存在三个菜单,第一个按钮是运行脚本,第二个按钮是覆盖导入脚本,第三个则是追加导入,他们之间的功能个有不同,读者可自行体会;

3.1.1 IF语句的构建

IF语句的使用非常容易,如下代码,通过ScreenEA()函数识别到当前光标所在位置处的指令内存地址,并对比该内存地址是否符合特定的条件,如果符合则输出,不符合则最终输出没有找到;
  1. #include <idc.idc>
  2. static main()
  3. {
  4.     auto CurrAddress = ScreenEA();
  5.     if(CurrAddress == 0x0046E31A)
  6.     {
  7.        Message("程序OEP => 0x%x \n",CurrAddress);
  8.     }
  9.     else if(CurrAddress == 0x0046E331)
  10.     {
  11.        Message("程序OEP => 0x%x \n",CurrAddress);
  12.     }
  13.     else
  14.     {
  15.       Message("没有扎到OEP \n");
  16.     }
  17. }
复制代码
3.1.2 FOR语句的构建

与C语言格式几乎一致,For语句的构建也很容易理解,首先程序通过GetFunctionAttr()函数并设置FUNCATTR_START属性获取到当前光标所指向程序段的开始地址,通过FUNCATTR_END设置光标的结束位置,最后调用For循环,一次输出当前内存地址及下一个内存地址,直到将本段内容全部输出为止;
  1. #include <idc.idc>
  2. static main()
  3. {
  4.     auto origEA,currEA,funcStart,funcEnd;
  5.     origEA = ScreenEA();
  6.    
  7.     // origEA = OEP 如果origEA 不在函数内则返回-1
  8.     funcStart = GetFunctionAttr(origEA,FUNCATTR_START);
  9.     funcEnd = GetFunctionAttr(origEA,FUNCATTR_END);
  10.     Message("OEP: %x 起始地址: %x --> 结束地址: %x \n",origEA,funcStart,funcEnd);
  11.    
  12.     // NextHead 在currEA开始的位置寻找下一条指令的地址
  13.     for(currEA = funcStart; currEA != -1; currEA=NextHead(currEA,funcEnd))
  14.     {
  15.         Message("指令地址:%8x \n",currEA);
  16.     }
  17. }
复制代码
3.1.3 WHILE语句的构建

该语句的构建与FOR语句基本一致,与FOR语句唯一的不同在于该语句只能接受一个参数,如下代码中读者需要注意GetFunctionName()可用于获取当前光标所在位置处所属函数的名称。
  1. #include <idc.idc>
  2. static main()
  3. {
  4.     auto origEA,currEA,funcStart,funcEnd;
  5.     origEA = ScreenEA();
  6.     // origEA = OEP 如果origEA 不在函数内则返回-1
  7.     funcStart = GetFunctionAttr(origEA,FUNCATTR_START);
  8.     funcEnd = GetFunctionAttr(origEA,FUNCATTR_END);
  9.     Message("OEP: %x 起始地址: %x --> 结束地址: %x \n",origEA,funcStart,funcEnd);
  10.    
  11.     while(currEA != BADADDR)
  12.     {
  13.         Message("--> %x name: %s \n",currEA,GetFunctionName(currEA));
  14.         currEA = NextHead(currEA,funcEnd);
  15.     }
  16. }
复制代码
3.1.4 函数的实现

IDA中使用函数通常可在一个字符串之前定义为static,函数的参数列表一般而言是以逗号进行间隔开的,当函数存在返回值是则通过return语句返回。
  1. #include <idc.idc>
  2. // 定义一个函数
  3. static OutPutAddress(MyString)
  4. {
  5.     auto currAddress;
  6.     currAddress = ScreenEA();
  7.     Message("%d \n",MyString);
  8.     return currAddress;
  9. }
  10. // 传递多个参数
  11. static OutPutAddressB(x,y)
  12. {
  13.     return x+y;
  14. }
  15. static main()
  16. {
  17.     auto ret = OutPutAddress(123);
  18.     Message("返回当前地址 = 0x%x \n",ret);
  19.    
  20.     auto ref = OutPutAddressB(100,200);
  21.     Message("计算数值 = %d \n",ref);
  22.    
  23. }
复制代码
3.1.5 定义并使用数组

与高级语言类似,IDC脚本中同样支持数组操作,不同于C语言中的数组,IDC中在使用时首先需要通过CreateArray("array")创建一个数组,当数组指针被创建成功后下一步则是通过GetArrayId("array")得到该数组的指针,通过指针读者可以使用SetArrayString设置一个字符串变量,或使用SetArrayLong设置整数变量,当用户需要使用变量时则需要通过GetArrayElement()函数对数组内的数据进行提取,提取时AR_STR代表提取字符串,AR_LONG则代表提取整数类型,当读者需要删除数组内的特定元素可使用DelArrayElement()函数,最后使用结束调用DeleteArray()注销整个数组;
  1. #include <idc.idc>
  2. static main()
  3. {
  4.     // 创建数组元素
  5.     auto array_ptr = CreateArray("array");
  6.     // 获取数组指针
  7.     auto ptr = GetArrayId("array");
  8.    
  9.     Message("获取到的操作指针: %x \n",ptr);
  10.    
  11.     // 设置两个字符串变量
  12.     SetArrayString(ptr,0,"hello");
  13.     SetArrayString(ptr,1,"lyshark");
  14.    
  15.     // 设置两个整数变量
  16.     SetArrayLong(ptr,2,100);
  17.     SetArrayLong(ptr,3,200);
  18.    
  19.     // 如果提取字符串使用 AR_STR 标记 ,提取整数使用 AR_LONG
  20.     auto st = GetArrayElement(AR_STR,ptr,0);
  21.     auto st1 = GetArrayElement(AR_STR,ptr,1);
  22.     Message("提取字符串变量: %s %s !\n",st,st1);
  23.    
  24.     auto lo = GetArrayElement(AR_LONG,ptr,2);
  25.     Message("提取整数变量: %d \n",lo);
  26.    
  27.     // 删除数组的0号元素
  28.     DelArrayElement(AR_STR,ptr,0);
  29.     // 注销整个数组
  30.     DeleteArray(ptr);
  31. }
复制代码
3.1.6 字符串处理

IDC中读者可以使用form()函数实现对特定字符串的格式化输出操作,IDC中同样也内置了各类转换函数,如下代码所示,则是IDC中可以经常被用到的函数调用,读者可自行参考;
  1. #include <idc.idc>
  2. static main()
  3. {
  4.     // 格式化字符串,类似于sprintf
  5.     auto name = form("hello %s","lyshark");
  6.     Message("格式化后的内容: %s \n",name);
  7.    
  8.     Message("十六进制转为整数: %d \n",xtol("0x41"));
  9.     Message("十进制100转为八进制: %d \n",ltoa(100,8));
  10.     Message("十进制100转换二进制: %d \n",ltoa(100,2));
  11.     Message("字符A的ASCII: %d \n",ord("A"));
  12.     Message("计算字符串长度: %d \n",strlen("hello lyshark"));
  13.    
  14.     // 在主字符串中寻找子串
  15.     auto main = "hello lyshark";
  16.     auto sub = "lyshark";
  17.     Message("寻找子串: %d \n",strstr(main,sub));
  18. }
复制代码
3.1.7 枚举所有函数

如下脚本实现了枚举当前指针所在位置处所有函数名称及地址,首先通过ScreenEA()函数获取当前指针所在位置,通过SegStart()用于获取该指针所在位置处模块的开始地址,与之对应的是SegEnd();则用于获取结束地址,接着通过调用GetFunctionName();得到当前地址处的函数名,并依次通过NextFunction();得到下一个模块地址,最终输出所有函数名及其地址信息;
  1. #include <idc.idc>
  2. static main()
  3. {
  4.     auto currAddr,func,endSeg,funcName,counter;
  5.    
  6.     currAddr = ScreenEA();
  7.     func = SegStart(currAddr);
  8.     endSeg = SegEnd(currAddr);
  9.     Message("%x --> %x \n",func,endSeg);
  10.    
  11.     counter = 0;
  12.     while(func != BADADDR && func < endSeg)
  13.     {
  14.         funcName = GetFunctionName(func);
  15.         if(funcName != " ")
  16.         {
  17.             Message("%x --> %s \n",func,funcName);
  18.             counter++;
  19.         }
  20.         func = NextFunction(func);
  21.     }
  22. }
复制代码
当然读者可以通过增加IF语句来判断funcName函数名是否是我们所需要枚举的,如果是则输出,如果不是则继续下一个函数,依次类推实现函数枚举功能,读者只需要在上述代码基础上稍加改进即可实现;
  1. #include <idc.idc>
  2. static main()
  3. {
  4.     auto currAddr,func,endSeg,funcName,counter;
  5.    
  6.     currAddr = ScreenEA();
  7.     func = SegStart(currAddr);
  8.     endSeg = SegEnd(currAddr);
  9.     Message("%x --> %x \n",func,endSeg);
  10.     counter = 0;
  11.    
  12.     while(func != BADADDR && func < endSeg)
  13.     {
  14.         funcName = GetFunctionName(func);
  15.         if(funcName != " ")
  16.         {
  17.             if(funcName == "__lock")
  18.             {
  19.                 Message("%x --> %s \n",func,funcName);
  20.             }
  21.             counter++;
  22.         }
  23.         func = NextFunction(func);
  24.     }
  25. }
复制代码
3.1.8 设置内存区域标签高亮

标签高亮功能的实现依赖于SetColor函数,该函数传入三个参数,其中参数1用于指定需要检索的范围,该范围可以通过NextHead()函数获取到,只要该节点不会返回BADADDR则可以继续遍历下一个节点,第二个参数则代表标注类型,第三个参数代表要在那个位置进行标注;
  1. #include <idc.idc>
  2. static main(void)
  3. {
  4.     auto head, op;
  5.     head = NextHead(0x00000000, 0xFFFFFFFF);
  6.     while ( head != BADADDR )
  7.     {
  8.         op = GetMnem(head);
  9.         Message("%x %s \n",head,op);
  10.         
  11.         if ( op == "jmp" || op == "call" )
  12.             SetColor(head, CIC_ITEM, 0x010187);
  13.             
  14.         if (op == "xor")
  15.             SetColor(head, CIC_ITEM, 0x010198);
  16.         head = NextHead(head, 0xFFFFFFFF);
  17.     }
  18. }
复制代码
3.1.9 地址反汇编输出

在IDA中有时我们需要对特定位置进行反汇编,并以脚本的方式输出,此时读者可使用GetDisasm(inst)函数来实现,该函数传入一个RfirstB生成的迭代类型,并依次循环输出,直到对100行输出为止;
  1. #include <idc.idc>
  2. static main(void)
  3. {
  4.     auto decode = 0x401000;
  5.     auto xref;
  6.    
  7.     for(xref = RfirstB(decode); xref != BADADDR; xref = RnextB(decode,xref))
  8.     {
  9.         Message("xref: %x\n",xref);
  10.         auto i = 0;
  11.         auto inst = xref;
  12.         auto op;
  13.         
  14.        while((i < 100) )
  15.        {
  16.             // 向后枚举下一个
  17.             inst = FindCode(inst,0x00);
  18.         
  19.             // 输出反汇编
  20.             op = GetDisasm(inst);
  21.             Message("%x --> %s \n",inst,op);
  22.             i++;
  23.        }
  24.     }
  25. }
复制代码
当具备了反汇编功能后,那么读者则可通过各种方式实现对指令集的判断,并以此来实现过滤特定指令地址并输出的目的,如下所示,通过strstr()函数对符合特定条件的字符串进行过滤,当找到后返回该函数的所在位置;
  1. #include <idc.idc>
  2. static main()
  3. {
  4.     auto currAddr,startSeg,endSeg;
  5.    
  6.     currAddr = ScreenEA();
  7.     startSeg = SegStart(currAddr);
  8.     endSeg = SegEnd(currAddr);
  9.    
  10.     Message("OEP = %x 起始地址: %x 结束地址: %x \n",currAddr,startSeg,endSeg);
  11.    
  12.     while(startSeg < endSeg)
  13.     {
  14.         auto op = GetDisasm(startSeg);
  15.         
  16.         // 查找第一条指令
  17.         if(strstr(op,"push    esi")==0)
  18.         {
  19.             startSeg++;
  20.             op = GetDisasm(startSeg);
  21.             if(strstr(op,"push    edi"))
  22.             {
  23.                 Message("特征: %x \n",startSeg-1);
  24.             }
  25.         }
  26.         startSeg++;
  27.     }
  28. }
复制代码
当然反汇编函数并非只有GetDisasm读者同样可以使用GetMnem返回位于特定地址处的指令,GetOpnd用于返回特定位置处的机器码,同样可以使用FindBinary实现对特定地址的特征码搜索功能;
  1. #include <idc.idc>
  2. static main()
  3. {
  4.     // 搜索特征码
  5.     auto code = FindBinary(0x401020,1,"55 8B EC");
  6.     Message("%x \n",code);
  7.     // 返回反汇编代码
  8.     code = GetDisasm(0x401000);
  9.     Message("%s \n",code);
  10.    
  11.     // 返回位于地址处的指令
  12.     code = GetMnem(0x401000);
  13.     Message("%s \n",code);
  14.    
  15.     // 返回opcode机器码
  16.     code = GetOpnd(0x401070,0);
  17.     Message("%s \n",code);
  18. }
复制代码
3.1.10 枚举函数栈帧

生成每个函数的栈帧,通过NextFunction()函数可实现枚举当前模块内所有函数地址,通过循环并调用GetFram()来得到当前函数栈帧大小,并使用GetMemberOffset()保存栈中返回地址偏移量,依次循环输出当前函数内的完整栈帧数据;
  1. #include <idc.idc>
  2. static main()
  3. {
  4.     auto addr,args,end,locals,frame,firstArg,name,ret;
  5.    
  6.     for(addr = NextFunction(addr); addr != BADADDR; addr = NextFunction(addr))
  7.     {
  8.         name = Name(addr);
  9.         end = GetFunctionAttr(addr,FUNCATTR_END);
  10.         locals = GetFunctionAttr(addr,FUNCATTR_FRSIZE);
  11.         
  12.         // 得到栈帧大小
  13.         frame = GetFrame(addr);
  14.         
  15.         // 栈中保存返回地址偏移量
  16.         ret = GetMemberOffset(frame," r");
  17.         if(ret == -1)
  18.         {
  19.             continue;
  20.         }
  21.         
  22.         firstArg = ret +4;
  23.         args = GetStrucSize(frame) - firstArg;
  24.         
  25.         Message("函数: %s 开始: 0x%x 结束: 0x%x 大小: %d bytes 栈帧: %d bytes (%d args) \n",name,addr,end,locals,args,args/4);
  26.     }
  27. }
复制代码
3.1.11 检索交叉引用

枚举当前模块中的交叉引用,通过XrefType()函数可枚举出当前被分析程序中的交叉引用情况,如下案例中实现了对当前程序内所有交叉引用的枚举工作,并输出三个参数,参数1代表主函数,参数2代表被引用函数,参数3代表当前函数的内存地址;
  1. #include <idc.idc>
  2. static main()
  3. {
  4.     auto func,end,target,inst,name,flags,xref;
  5.     flags = SEARCH_DOWN | SEARCH_NEXT;
  6.     func = GetFunctionAttr(ScreenEA(),FUNCATTR_START);
  7.    
  8.     if(func != -1)
  9.     {
  10.         name =Name(func);
  11.         end = GetFunctionAttr(func,FUNCATTR_END);
  12.         for(inst = func;inst < end; inst = FindCode(inst,flags))
  13.         {
  14.             for(target = Rfirst(inst);target != BADADDR; target = Rnext(inst,target))
  15.             {
  16.                 xref = XrefType();
  17.                 if(xref == fl_CN || xref == fl_CF)
  18.                 {
  19.                     Message("%s | %s | %x \n",name,Name(target),inst);
  20.                 }
  21.             }
  22.         }
  23.     }
  24. }
复制代码
如果读者想要实现枚举特定一个函数的交叉引用信息,则可通过使用LocByName(bad_func)增加过滤条件,并依次实现过滤特定函数的目的,代码的修改只需要小改即可;
  1. #include <idc.idc>
  2. static FindFunction(bad_func)
  3. {
  4.     auto func,addr,xref,source;
  5.    
  6.     func = LocByName(bad_func);
  7.     if(func == BADADDR)
  8.     {
  9.         Message("error \n");
  10.     }
  11.     else
  12.     {
  13.         for(addr = RfirstB(func);addr != BADADDR; addr = RnextB(func,addr))
  14.         {
  15.             xref = XrefType();
  16.             if(xref == fl_CN || xref == fl_CF)
  17.             {
  18.                 source = GetFunctionName(addr);
  19.                 Message("%s call => %0x in %s \n",bad_func,addr,source);
  20.             }
  21.         }
  22.     }  
  23. }
  24. static main()
  25. {
  26.     FindFunction("LoadString");
  27. }
复制代码
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

正序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

玛卡巴卡的卡巴卡玛

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表