En

浅谈IDA脚本在漏洞挖掘中的应用

作者:dragonltx[TSRC]公布时间:2012-08-30阅读次数:26039评论:1

分享

[目录]

 1 - 前言

 2 - IDC和IDAPython简介

 3 - 倚天剑:IDC应用

 4 - 屠龙刀:IDAPython应用

 5 - 结语

 

[1] - 前言

 

IDA毫无疑问是逆向领域里的一大神器,无所不能。有人的地方就有江湖,有江湖的地方就有武器。那么,在逆向这个江湖中,IDC和IDAPython就好比倚天剑和屠龙刀,威力无比。

 

在漏洞挖掘领域,IDA同样能够大展身手。OpenRCE上提供的BugScam脚本正是IDC应用的最好诠释,著名的Paimei也是应用了IDApython。

 

接下来,笔者将以自己的经验来分享下两把利器“倚天剑”和“屠龙刀”的应用。

 

[2] - IDC和IDAPython简介

 

事实上,没有哪一个应用程序能够满足每名用户的一切需求。应用开发者面临两种选择:要么满足用户提出的无止境的功能要求,要么提供一种方法,供用户解决问题。IDA采用了后一种方法,它集成了一个脚本引擎,让用户从编程角度对IDA的操作进行全面控制。

 

IDA脚本语言可看成是一种查询语言,它能够以编程方式访问IDA数据库的内容。IDA的脚本语言叫做IDC,之所以取这个名称,可能是因为它的语法与C语言的语法非常相似。

 

得益于IDA Pro极为开放的构架,Gergely Erdelyi和Ero Carrera在2004年发布了IDAPython--一款IDA Pro的插件。通过这款插件,逆向工程师能够以Python脚本的形式访问IDC脚本引擎核心、完整的IDA插件API,以及所有与Python捆绑在一起的常见模块。IDAPython无论是在商业产品中(例如Zynamics的BinNavi),还是在一些开源项目中(例如Paimei和PyEmu)均有所应用。

 

[3]- 倚天剑:IDC应用

 

如果各位读者对这篇文章感兴趣,应该都对IDC有了解。不过不了解也没关系,那就请先参考下相关资料[1],里面有详细的IDC语言介绍,这里就不再进行介绍。

 

当我们想通过自动化运行IDA获取一些对漏洞挖掘有用的信息,而不是手工运行IDA,该怎么做?

 

IDA提供了如下两个函数,可以帮助我们实现自动化。

 


 

在IDA启动后,IDA会执行一些自动分析操作。Wait函数会等待,直到这些自动分析结束。该函数会挂起我们的IDC脚本,直到自动分析队列为空。当自动分析队列为空时,就开始执行我们的IDC脚本。Exit函数会结束IDC函数的执行,并将idb关闭,然后结束IDA主进程,相当nice的功能。

 

有了这两个函数后,还不够,革命尚未成功。IDA还提供了丰富的命令行参数,帮助我们实现自动化。

 


 

“-A”参数是自动模式,IDA将不会显示对话框,是和“-S”参数一起使用。“-S”参数指定执行那个IDC脚本,后面可以跟IDC脚本的参数。参数会放在ARGV这个全局变量里,其中ARGV[0],存放的是IDC脚本名。IDA还提供了-c参数,用来反汇编一个文件[3]。

 

现在实现自动化的各个因素都凑齐了,“万事具备,只欠东风”,接下来是一个自动导出一个文件中的所有函数名、起始地址、结束地址的IDC脚本。

 

 

#include <idc.idc>

static main()
{

  auto addr, end, args, locals, frame, firstArg, name, ret ,handle, path, index, filename, outputfilename ,segaddr;

  addr = 0;

  Wait();    //等待直到IDA自动分析完成

  segaddr = MinEA();

  Message("Base:%x\n",segaddr);

  handle = fopen(ARGV[1],"w");

  for( addr = NextFunction(addr); addr != BADADDR; addr = NextFunction(addr))

  {
    name = Name(addr);

    end  = GetFunctionAttr(addr, FUNCATTR_END);
    if(substr(name,0,4) == "sub_")
    continue;
    
    Message("Function:%s, starts at %x,ends at %x\n", name, addr-segaddr, end-segaddr);

    fprintf(handle,"Function:%s, starts at %x,ends at %x\n", name, addr-segaddr, end-segaddr);

  }

  fclose(handle); 

  Exit(0);
}

当我们以这样的命令行idaq -c -A -S"dumpfunc.idc E:\func.txt" E:\test.dll运行IDA,结果就会自动保存在E盘的func.txt中,相当惬意吧!心动了吧,心动了就赶快行动吧!你可以尽情发挥自己的才华,向IDA获取你想要的东西。

 

[4] - 屠龙刀:IDAPython应用

 

在《Python灰帽子》[2]第十章中,Justin提供了一种自动化获取驱动程序IO控制码的方法,不过该脚本是基于Immunity Debugger的库。笔者用Immunity Debugger加载驱动文件,发现加载失败。后来一想,既然是基于静态分析的方法,何必用Immunity Debugger,IDA才是静态分析领域的王者。下面探讨用IDAPython来实现自动获取驱动程序IO控制码的初级版程序。

 

[4.1] - 获取驱动程序设备名

 

通过FindText这个函数来查找包含“\\Device\\”这个函数的偏移地址,然后通过GetString来获取字符串,如果获取的字符串为空,继续查找。

 

def getDeviceName():

    """

    Get Device Name from a driver. 

    @rtype:   void

    @returns: void

    """

    ea = 0

    while True:

     ea =  FindText(ea, SEARCH_NEXT | SEARCH_REGEX, 0, 0, "\\\\Device\\\\")

     string = GetString(ea, -1, ASCSTR_UNICODE)

     if string is None:

        continue

     else:

        #Message("Find in %x\n" % ea)

        Message("device is %s\n" % string)

       Break

 

[4.2] - 获取驱动分发函数地址

 

首先用FindText查找mov dword ptr [edx+70h], offset sub_11010类似这种形式的指令,通过正则匹配查找。找到后,用GetOperandValue函数获取第二个操作数的值,即是分发函数的地址。

 

def getDispatchAddress():

    """

    Get Device Dispatch Address from a driver. 

    @rtype:   int

    @returns: Dispatch Address

    """

    ea = 0

    ea =  FindText(ea,  SEARCH_DOWN |SEARCH_NEXT | SEARCH_REGEX, 0, 0, "mov *dword *ptr *\\[[a-zA-Z]* *\\+ *70h\\],[a-zA-Z0-9_ ]*")

    #ea =  FindText(ea, SEARCH_NEXT | SEARCH_REGEX, 0, 0, "test *[a-zA-Z]*, +[a-zA-Z]*")

    #Message("Find in %x\n" % ea)

    if ea == BADADDR:

        Message("Cann't find the Dispatch address")

        address = BADADDR

    else:

        address = GetOperandValue(ea,1)

        Message("Dispatch address is %x\n" % address)

return address

 

[4.3] - 获取函数内所有指令或指令偏移

 

通过GetFunctionAttr获取函数的结束地址,再通过ItemSize来获取每条指令的大小,然后循环遍历即可获得这个函数的所有指令的偏移地址。这边先获取所有指令的偏移地址,而不是指令,下面获取io控制码会用到。

 

def getFunctionInstructions():

    """

    Get All Instructions from a function.

    Here,Just Get All Instructions Offset,and store them in list

    @rtype:   List

    @returns: List of All Instructions

    """

    Instructions = []

    DispatchBeginAddress = getDispatchAddress()

    if DispatchBeginAddress == BADADDR:

       Message("Cann't find the Function Instructions List")

       return None

    DispatchEndAddress = GetFunctionAttr(DispatchBeginAddress,FUNCATTR_END)

    i =  DispatchBeginAddress

    while True:

      #Instructions.append(GetDisasm(i))

      Instructions.append(i)

      tmp = i + ItemSize(i)

      if tmp < DispatchEndAddress:

         i = i + ItemSize(i)

      else:

         break

    address = i

    return Instructions


 

[4.4] - 获取驱动程序的所有IO控制码

 

获取分发函数的所有指令偏移后,倒序查找。如果碰到是jz或者是je的,且接下来是cmp的指令,并且比较操作的寄存器是否一样,一样的话,则把io控制码存储。(这样还是不够准确的,如果碰到其他的jz且连着jmp的指令,但不是io控制码。纯自动分析有时候不能识别)。

 

def getIoctlCode():

    """

    Get All IoctlCodes from a driver. 

    @rtype:   List

    @returns: List of All IoctlCodes

    """

    isConditionalJmp             = False

    isFirst                      = True

    BaseRegister                 = None

    OperRegister                 = None

    IoctlCode                    = []

    DispatchFunctionInstructions = []

    DispatchFunctionInstructions = getFunctionInstructions()[::-1]

    if DispatchFunctionInstructions == None:

       Message("Cann't get the IoctlCodes")

       return

    for i in DispatchFunctionInstructions:

      #Message("The instrucion of this function is %x\n" % i)

      mnem = GetMnem(i)

      if "jz" in mnem or "je" in mnem:

         isConditionalJmp = True

         continue

        

      if "cmp" in mnem and isConditionalJmp and isFirst:

         sisConditionalJmp = False

         BaseRegister  = GetOpnd(i,0)

         IoctlCode.append(GetOperandValue(i,1))  

         isFirst = False

         continue

         

      if "cmp" in mnem and isConditionalJmp and not isFirst:

         isConditionalJmp = False

         OperRegister  =  GetOpnd(i,0)

         if OperRegister == BaseRegister:

            IoctlCode.append(GetOperandValue(i,1))   

      

          

    for i in IoctlCode:

        Message("The ioctlcode of this driver is %x\n" % i)

 

[4.5]不足与缺陷

 

上面实现的自动获取io控制码的比较简单,有些情况没有考虑到,算是初级版。Switch反汇编的形式有很多种,上面只是考虑了cmp的形式。有兴趣的读者可以继续深入挖掘。上面的IDAPython脚本可以在这里(http://bbs.pediy.com/showthread.php?t=153965)获取到,里面还有对函数的解释。

 

[5] - 结语

 

本文主要对IDA脚本在漏洞挖掘领域应用进行简单的探讨,主要起到抛砖引玉的效果。希望对给位读者有所帮助。如果你有更好的思路,可以跟我探讨。

 

“思想有多远,就能走多远”。尽情发挥你的奇思妙想,在漏洞挖掘的海洋里尽情畅游吧!

 

 

References

[1]IDA权威指南

[2]Python灰帽子--黑客与逆向工程师的Python编程之道

[3]IDA Pro Documentation

 

评论留言

提交评论 您输入的漏洞名称有误,请重新输入