3.1 IDA Pro编写IDC脚本入门

news2024/11/20 19:44:36

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();
    
    // origEA = OEP 如果origEA 不在函数内则返回-1
    funcStart = GetFunctionAttr(origEA,FUNCATTR_START);
    funcEnd = GetFunctionAttr(origEA,FUNCATTR_END);
    Message("OEP: %x 起始地址: %x --> 结束地址: %x \n",origEA,funcStart,funcEnd);
    
    // NextHead 在currEA开始的位置寻找下一条指令的地址
    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();

    // origEA = OEP 如果origEA 不在函数内则返回-1
    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);
    
    // 如果提取字符串使用 AR_STR 标记 ,提取整数使用 AR_LONG
    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);
    
    // 删除数组的0号元素
    DelArrayElement(AR_STR,ptr,0);
    // 注销整个数组
    DeleteArray(ptr);
}

3.1.6 字符串处理

IDC中读者可以使用form()函数实现对特定字符串的格式化输出操作,IDC中同样也内置了各类转换函数,如下代码所示,则是IDC中可以经常被用到的函数调用,读者可自行参考;

#include <idc.idc>

static main()
{
    // 格式化字符串,类似于sprintf
    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);
    
    // 返回opcode机器码
    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");
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1195679.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

程序员的护城河:技术、创新与软实力的完美融合

作为IT行业的从业者&#xff0c;我们深知程序员在保障系统安全、数据防护以及网络稳定方面所起到的重要作用。他们是现代社会的护城河&#xff0c;用代码构筑着我们的未来。那程序员的护城河又是什么呢&#xff1f;是技术能力的深度&#xff1f;是对创新的追求&#xff1f;还是…

Linux 基于 LVM 逻辑卷的磁盘管理【简明教程】

一、传统磁盘管理的弊端 传统的磁盘管理&#xff1a;使用MBR先对硬盘分区&#xff0c;然后对分区进行文件系统的格式化最后再将该分区挂载上去。 传统的磁盘管理当分区没有空间使用进行扩展时&#xff0c;操作比较麻烦。分区使用空间已经满了&#xff0c;不再够用了&#xff…

Linux系统初步了解

Linux系统由4个主要部分组成&#xff1a;内核、Shell、文件系统和应用程序。 本专题主要是围绕这四个来展开的。 POSIX&#xff08;可移植操作系统接口&#xff09;定义了操作系统应该为应用程序提供的标准接口&#xff0c;其意愿是获得源码级别的软件可移植性。所以Linux选择…

程序员的那些坏习惯!来看看你有几个?

一、前言 写了20多年代码&#xff0c;我见过不下于4位数的程序员&#xff0c;我觉得程序员的能力水平可以分为4个阶段&#xff1a;线性级、逻辑级、架构级和工程级。 同样的在这些人当中&#xff0c;我也发现了8个程序员最常见的陋习&#xff0c;基本上可以覆盖90%的人&#…

高德资深技术专家孙蔚:海量用户应用数据库选型、升级实践

高德地图&#xff08;以下简称“高德”&#xff09;作为一款用户出行必备、拥有海量用户数据的导航软件&#xff0c;对系统运行稳定性要求极高。 一直以来&#xff0c;高德每时每刻都在生产的一些数据库中的数据已经达到数百 TB&#xff0c;数据量的增长不仅带来存储成本的迅速…

关于Office阻止访问嵌入对象的解决办法

问题 Word文档中想要下载嵌入的文件时被Office阻止了&#xff0c;无法下载。 解决办法 打开文件——选项——信任中心&#xff0c;在宏设置中启用所有宏&#xff0c;关于Macro、Acitve X插件等项目设置上&#xff0c;建议暂时全部设置为允许&#xff0c;看下相关对象的访问…

try-catch-finally执行以及他们在有return的情况下,基本数据类型、对象以及有异步赋值情况异同分析

这两天面试,遇到好几个人,都是那种我感觉我肚子里的墨水都吐出来完了,难不倒人家,于是问了下家里那位老狗,从最开始就念叨着你问他try-catch在有return的情况下怎么执行的,执行结果是啥,我前面没理,后面确实有点遭不住了,来看看吧,肚子里添点墨水,别把脸丢大了~ 做…

分布式搜索引擎ES

文章目录 初识elasticsearch了解ES倒排索引正向索引倒排索引正向和倒排 es的一些概念文档和字段索引和映射mysql与elasticsearch 安装ES部署kibana安装IK分词器扩展词词典停用词词典 索引库操作mapping映射属性索引库的CRUD创建索引库和映射查询索引库修改索引库删除索引库 文档…

MySQL 常见面试题总结:索引 InnoDB索引 MyISAM索引

1.关系型数据库&#xff08;MySQL&#xff09;和非关系型数据库(nosql)区别 存储方式&#xff1a;关系型以表的形式 非关系型以键值对形式 应用场景&#xff1a;关系型一致性要求较高&#xff0c;非关系型并发性要求较高 2. Mysql如何实现的索引机制&#xff1f; MySQL中索…

WAF入侵防御系统标准检查表

软件开发全文档获取&#xff1a;进主页

『Linux升级路』基础开发工具——vim篇

&#x1f525;博客主页&#xff1a;小王又困了 &#x1f4da;系列专栏&#xff1a;Linux &#x1f31f;人之为学&#xff0c;不日近则日退 ❤️感谢大家点赞&#x1f44d;收藏⭐评论✍️ 目录 一、vim的基本概念 &#x1f4d2;1.1命令模式 &#x1f4d2;1.2插入模式 &…

ENVI IDL:如何监测代码运行时间(计时器函数实现)?

01 预想 我预想的是在循环中加入一个函数&#xff0c;可以监测相邻两次循环的运行时间&#xff0c;正常操作如此&#xff1a; pro unknowfor ix 0, 5 do beginstart_timekeeping systime(1)wait, randomu(systime(1), 1) ; 此处systime(1)仅仅作为seed种子end_timekeeping…

C# DirectoryInfo类的用法

在C#中&#xff0c;DirectoryInfo类是System.IO命名空间中的一个类&#xff0c;用于操作文件夹&#xff08;目录&#xff09;。通过DirectoryInfo类&#xff0c;我们可以方便地创建、删除、移动和枚举文件夹。本文将详细介绍DirectoryInfo类的常用方法和属性&#xff0c;并提供…

拥抱中国发展新机遇,原知因制药再次亮相2023进博会

11月5日至10日&#xff0c;第六届进博会在国家会展中心&#xff08;上海&#xff09;成功举办。作为世界上首个以进口为主题的国家级博览会&#xff0c;进博会成为构建新发展格局的窗口、高水平开放的载体&#xff0c;持续为世界经济注入正能量。 原知因制药再次亮相进博会&am…

开放领域问答机器人1

开放领域问答机器人是一种智能机器人&#xff0c;它不受限制&#xff0c;可以回答任何问题。这种机器人主要通过自然语言处理技术来理解用户的问题&#xff0c;并从大量的数据中获取相关信息&#xff0c;以提供准确的答案。它的应用领域广泛&#xff0c;包括客户服务、教育、医…

网易云音乐未登录接口返回301

网易云音乐 NodeJS 版 API (neteasecloudmusicapi.js.org) 上面是网易云音乐的官方API接口文档 当我调用接口发送请求的时候部分接口数据是需要登录之后进行获取的&#xff0c;但是当我发送请求的时候原生js项目中的跨端问题是比较难解决的。 遇到的问题&#xff1a;跨端请求…

嵌入式Linux系统中内存分配详解

Linux中内存管理 内存管理的主要工作就是对物理内存进行组织&#xff0c;然后对物理内存的分配和回收。但是Linux引入了虚拟地址的概念。 虚拟地址的作用 如果用户进程直接操作物理地址会有以下的坏处&#xff1a; 1、 用户进程可以直接操作内核对应的内存&#xff0c;破坏…

拓世法宝AI智能直播一体机,快速搭建品牌矩阵,开启扩张新里程

时光荏苒&#xff0c;数字时代悄然而至&#xff0c;短视频已成为品牌传播的新宠。在这个潮流中&#xff0c;短视频以一种无法阻挡的势头成为了品牌传播的新趋势。如何巧妙地利用短视频进行品牌传播&#xff0c;实现零成本的品牌升级&#xff0c;构建强大的品牌矩阵&#xff0c;…

Linux编辑器---vim的使用

Vim是一个高度可配置的文本编辑器&#xff0c;它是操作Linux的一款利器&#xff0c;旨在高效地创建和更改任何类型的文本。这款编辑器起源于"vi"&#xff0c;并在此基础上发展出了众多新的特性。Vim被普遍推崇为类Vi编辑器中最好的一个&#xff0c;事实上真正的劲敌来…

【每日OJ—— 206. 反转链表(链表)】

每日OJ—— 206. 反转链表&#xff08;链表&#xff09; 1.题目&#xff1a;206. 反转链表&#xff08;链表&#xff09;2.方法讲解&#xff1a;2.1解法&#xff1a;2.1.1.图文解析2.1.2.代码实现2.1.3.提交通过展示 1.题目&#xff1a;206. 反转链表&#xff08;链表&#xff…