欢迎来到 黑吧安全网 聚焦网络安全前沿资讯,精华内容,交流技术心得!

利用Intel Pin和IDA实现代码跟踪

来源:本站整理 作者:佚名 时间:2018-08-16 TAG: 我要投稿

多数调试器都有一个代码跟踪(Trace)功能,可以记录程序运行时执行过的命令,有助于调试工具的使用者弄清代码的执行流程,但调试器提供的跟踪功能多是基于程序异常的,开启跟踪后会大幅降低程序运行速度。本文将介绍Intel出品的程序动态插桩工具Pin,并利用Pin实现性能损耗更小的代码追踪工具。
Intel Pin介绍
Pin是一个动态的二进制插桩工具。动态意味着它的插桩在运行时执行,而不需要程序的源码,这一点是十分契合逆向工程或是漏洞挖掘的要求的。关于Pin的实现原理,我在这里直接引用官方的说法:
“Pin intercepts the execution of the first instruction of the executable and generates (“compiles”) new code for the straight line code sequence starting at this instruction. It then transfers control to the generated sequence. The generated code sequence is almost identical to the original one, but Pin ensures that it regains control when a branch exits the sequence. After regaining control, Pin generates more code for the branch target and continues execution.”
简而言之,Pin会将原程序的代码拆分为各个小块,当程序执行到其中每一块的开头时,Pin会根据这一块代码的内容生成新的代码(例如在原始代码中插桩,对代码进行修改, 保存、恢复执行环境等等),然后将控制权转交给新生成的代码;当新生成的代码执行结束后,Pin重新获得控制权,然后再对下一块代码进行处理。因此,被插桩程序中的原始指令仅仅只是作为一个参照,并没有被执行,被执行的是由Pin动态生成的新指令。
整个Pin的架构如下

前期准备
Pin套件可在其官网下载(https://software.intel.com/en-us/articles/pin-a-binary-instrumentation-tool-downloads)
另外,若在Windows上使用Pin,最好还下载Cygwin,并在其默认安装中额外添加’make’工具,以便我们后续编译我们自己写的Pin工具.
代码跟踪工具编写
在编写工具前,我们首先整理一下思路:
本工具只跟踪程序的执行流程,不需要详细记录每一条指令前后的寄存器变化。
根据第一条,我们的插桩粒度只需要做到基本块的级别。
在每一个基本块执行前插入代码,保存当前程序的pc计数器,并将该信息写入文件保存。
由于ALSR的存在,我们还需要保存模块的装载地址,否则第三点记录的地址便没有意义。
为了避免追踪到系统模块造成不必要的性能开销,我们暂时只对可执行程序主体模块进行插桩。
在理清思路后,我们开始代码的编写。首先是main函数的总体框架:
#include
#include
#include
#include
#include "pin.h"
ofstream out;
int main(int argc, char *argv[]) {
    if (PIN_Init(argc, argv))
        return usage();
    out.open("PIN_Trace.tlog");
    IMG_AddInstrumentFunction(Image, 0);
    TRACE_AddInstrumentFunction(Trace, 0);
    PIN_AddFiniFunction(Fini, 0);
    PIN_StartProgram();
    return 0;
}
PIN_Init()函数用来初始化Pin框架,若初始化失败则执行usage()函数,显示错误信息。
IMG_AddInstrumentFunction(Image, 0)注册了一个名为‘Image’的回调函数,该函数将在每一个程序映像被装载时调用。下面是Image函数的具体实现:
void Image(IMG img, void * v) {
    if (IMG_IsMainExecutable(img)) {
        char temp[1024];
        sprintf(temp, "%s loaded at %p", IMG_Name(img).c_str(), (void *)IMG_StartAddress(img));
        out endl;
    }
}
代码很好理解,记录下程序主模块的装载基地址并记录在文件中。
我们回到main函数,TRACE_AddInstrumentFunction(Trace, 0)同样注册了名为Trace的回调函数。Trace的实现如下:
void Trace(TRACE trace, void * v) {
    if (!IMG_IsMainExecutable(IMG_FindByAddress(TRACE_Address(trace))))
        return ;
    BBL bbl = TRACE_BblHead(trace); //返回本次Trace的第一个基本块
    for (; BBL_Valid(bbl); bbl = BBL_Next(bbl)) {
        BBL_InsertCall(bbl, IPOINT_BEFORE, (AFUNPTR)BBLHit, IARG_INST_PTR, IARG_END);
    }
}
Trace函数首先判断本次Trace是不是在程序主模块中,若不是则直接返回,不进行插桩。然后遍历本次Trace中的每一个基本块,BBL_InsertCall()中的’IPOINT_BEFORE’参数指定了在基本块执行前进行插桩,插入的函数为BBLHit。于是我们只需要在BBLHit函数中进行代码地址的登记即可。
setstring> stringSet;
void BBLHit(void *ip) {
    char temp[1024];
    sprintf(temp, "%p", ip);
    string str1(temp);
    if (stringSet.find(str1) == stringSet.end()) {
        stringSet.insert(str1);
        out endl;
    }
}
在上述的BBLHit函数中,我还额外用了一个set保存已登记的基本块,避免一个基本块被多次记录。当然,是否需要重复记录这一点根据不同的任务需要会有不同的结论。

[1] [2] [3]  下一页

【声明】:黑吧安全网(http://www.myhack58.com)登载此文出于传递更多信息之目的,并不代表本站赞同其观点和对其真实性负责,仅适于网络安全技术爱好者学习研究使用,学习中请遵循国家相关法律法规。如有问题请联系我们,联系邮箱admin@myhack58.com,我们会在最短的时间内进行处理。
  • 最新更新
    • 相关阅读
      • 本类热门
        • 最近下载