最近在研究IoT设备的过程中遇到一种情况。

一个IoT设备,官方不提供固件包,网上也搜不到相关的固件包,所以我从flash中直接读取。因为系统是VxWorks,能看到flash布局,所以能很容易把uboot/firmware从flash中分解出来。对于firmware的部分前一半左右是通过lzma压缩,后面的一半,是相隔一定的区间有一部分有lzma压缩数据。而固件的符号信息就在这后半部分。因为不知道后半部分是通过什么格式和前半部分代码段一起放入内存的,所以对于我逆向产生了一定的阻碍。所以我就想着看看uboot的逻辑,但是uboot不能直接丢入ida中进行分析,所以有了这篇文章,分析uboot格式,如何使用ida分析uboot。

uboot格式

正常的一个uboot格式应该如下所示:

$ binwalk bootimg.bin

DECIMAL       HEXADECIMAL     DESCRIPTION
--------------------------------------------------------------------------------
13648         0x3550          CRC32 polynomial table, big endian
14908         0x3A3C          uImage header, header size: 64 bytes, header CRC: 0x25ED0948, created: 2019-12-02 03:39:51, image size: 54680 bytes, Data Address: 0x80010000, Entry Point: 0x80010000, data CRC: 0x3DFB76CD, OS: Linux, CPU: MIPS, image type: Firmware Image, compression type: lzma, image name: "u-boot image"
14972         0x3A7C          LZMA compressed data, properties: 0x5D, dictionary size: 33554432 bytes, uncompressed size: 161184 bytes

而这uboot其实还得分为三部分:

1.从0×00 – 0x346C是属于bootstrap的部分

2.0x346C-0x34AC有0×40字节的uboot image的头部信息

3.从0x34AC到结尾才是uboot image的主体,经过lzma压缩后的结果

那么uboot是怎么生成的呢?Github上随便找了一个uboot源码:https://github.com/OnionIoT/uboot,编译安装了一下,查看uboot的生成过程。

1.第一步,把bootstrap和uboot源码使用gcc编译成两个ELF程序,得到bootstrap和uboot

2.第二步,使用objcopy把两个文件分别转换成二进制流文件。

$ mips-openwrt-linux-uclibc-objcopy --gap-fill=0xff -O binary bootstrap bootstrap.bin
$ mips-openwrt-linux-uclibc-objcopy --gap-fill=0xff -O binary uboot uboot.bin
$ binwalk u-boot/bootstrap

DECIMAL       HEXADECIMAL     DESCRIPTION
--------------------------------------------------------------------------------
0             0x0             ELF, 32-bit MSB executable, MIPS, version 1 (SYSV)
13776         0x35D0          CRC32 polynomial table, big endian
28826         0x709A          Unix path: /uboot/u-boot/cpu/mips/start_bootstrap.S

$ binwalk u-boot/bootstrap.bin

DECIMAL       HEXADECIMAL     DESCRIPTION
--------------------------------------------------------------------------------
13648         0x3550          CRC32 polynomial table, big endian

$ binwalk u-boot/u-boot

DECIMAL       HEXADECIMAL     DESCRIPTION
--------------------------------------------------------------------------------
0             0x0             ELF, 32-bit MSB executable, MIPS, version 1 (SYSV)
132160        0x20440         U-Boot version string, "U-Boot 1.1.4  (Dec  2 2019, 11:39:50)"
132827        0x206DB         HTML document header
133794        0x20AA2         HTML document footer
134619        0x20DDB         HTML document header
135508        0x21154         HTML document footer
135607        0x211B7         HTML document header
137363        0x21893         HTML document footer
137463        0x218F7         HTML document header
138146        0x21BA2         HTML document footer
138247        0x21C07         HTML document header
139122        0x21F72         HTML document footer
139235        0x21FE3         HTML document header
139621        0x22165         HTML document footer
139632        0x22170         CRC32 polynomial table, big endian
179254        0x2BC36         Unix path: /uboot/u-boot/cpu/mips/start.S

$ binwalk u-boot/u-boot.bin

DECIMAL       HEXADECIMAL     DESCRIPTION
--------------------------------------------------------------------------------
132032        0x203C0         U-Boot version string, "U-Boot 1.1.4  (Dec  2 2019, 11:39:50)"
132699        0x2065B         HTML document header
133666        0x20A22         HTML document footer
134491        0x20D5B         HTML document header
135380        0x210D4         HTML document footer
135479        0x21137         HTML document header
137235        0x21813         HTML document footer
137335        0x21877         HTML document header
138018        0x21B22         HTML document footer
138119        0x21B87         HTML document header
138994        0x21EF2         HTML document footer
139107        0x21F63         HTML document header
139493        0x220E5         HTML document footer
139504        0x220F0         CRC32 polynomial table, big endian

3.把u-boot.bin使用lzma算法压缩,得到u-boot.bin.lzma

$ binwalk u-boot/u-boot.bin.lzma

DECIMAL       HEXADECIMAL     DESCRIPTION
--------------------------------------------------------------------------------
0             0x0             LZMA compressed data, properties: 0x5D, dictionary size: 33554432 bytes, uncompressed size: 161184 bytes

4.使用mkimage,给u-boot.bin.lzma加上0×40字节的头部信息得到u-boot.lzming

$ binwalk u-boot/u-boot.lzimg

DECIMAL       HEXADECIMAL     DESCRIPTION
--------------------------------------------------------------------------------
0             0x0             uImage header, header size: 64 bytes, header CRC: 0x25ED0948, created: 2019-12-02 03:39:51, image size: 54680 bytes, Data Address: 0x80010000, Entry Point: 0x80010000, data CRC: 0x3DFB76CD, OS: Linux, CPU: MIPS, image type: Firmware Image, compression type: lzma, image name: "u-boot image"
64            0x40            LZMA compressed data, properties: 0x5D, dictionary size: 33554432 bytes, uncompressed size: 161184 bytes

5.最后把bootstrap.binu-boot.lzming合并到一起,然后根据需要uboot的实际大小,比如需要一个128k的uboot,在末尾使用0xff补齐到128k大小

使用ida处理bootstrap二进制流文件

在上面的结构中,需要注意几点:

1.Data Address: 0×80010000, Entry Point: 0×80010000表示设备启动后,会把后续uboot通过lzma解压出来的数据存入内存地址0×80010000,然后把$pc设置为: 0×80010000,所以uboot最开头4字节肯定是指令。

2.uncompressed size: 161184 bytes,可以使用dd把LZMA数据单独取出来,然后使用lzma解压缩,解压缩后的大小要跟这个字段一样。如果还想确认解压缩的结果有没有问题,可以使用CRC算法验证。

接下来就是通过dd或者其他程序把二进制流从uboot中分离出来,再丢到ida中。先来看看bootstrap,首先指定相应的CPU类型,比如对于上例,则需要设置MIPS大端。

随后我们暂时设置一下起始地址为0×80010000,通电以后CPU第一个执行的地址默认情况下我们是不知道的,不同CPU有不同的起始地址。设置如下图所示:

bootstrap最开头也指令,所以按C转换成指令,如下图所示:

跳转到0×80010400, 随后是一段初始化代码,下一步我们需要确定程序基地址,因为是mips,所以我们可以根据$gp来判断基地址。

如上图所示,因为bootstrap的大小为0x3a3c bytes,所以可以初步估计基地址为0x9f000000,所以下面修改一下基地址:

并且修改在Options -> General -> Analysis -> Processor specific ……设置$gp=0x9F0039A0

0x9F0039A0地址开始属于got表的范围,存储的是函数地址,所以把0x9F0039A0地址往后的数据都转成word:

到此就处理完毕了,后面就是存逆向的工作了,具体bootstrap代码都做了什么,不是本文的重点,所以暂不管。

使用ida处理uboot流文件

处理bootstrap,我们再看看uboot,和上面的处理思路大致相同。

1.使用dd或其他程序,把uboot数据先分离出来。

 2.使用lzma解压缩 

3.丢到ida,设置CPU类型,设置基地址,因为uboot头部有明确定义基地址为0×80010000,所以不用再自己判断基地址

 4.同样把第一句设置为指令

正常情况下,uboot都是这种格式,0×80010008为got表指针,也是$gp的值。

5.根据0×80010008的值,去设置$gp 6.处理got表,该地址往后基本都是函数指针和少部分的字符串指针。结尾还有uboot命令的结构体。

到此uboot也算基础处理完了,后续也都是逆向的工作了,也不是本文的关注的内容。

编写idapython自动处理uboot

拿uboot的处理流程进行举例,使用Python编写一个ida插件,自动处理uboot二进制流文件。

1.我们把0×80010000设置为__start函数

idc.add_func(0x80010000)
idc.set_name(0x80010000, "__start")

2.0×80010008是got表指针,因为我们处理了0×80010000,所以got表指针地址也被自动翻译成了代码,我们需要改成word格式。

idc.del_items(0x80010008)
idc.MakeDword(0x80010008)
got_ptr = idc.Dword(0x80010008)
idc.set_name(idc.Dword(0x80010008), ".got.ptr")

3.把got表都转成Word格式,如果是字符串指针,在注释中体现出来

def got():
    assert(got_ptr)
    for address in range(got_ptr, end_addr, 4):
        value = idc.Dword(address)
        if value == 0xFFFFFFFF:2019-12-03 15:36:56 星期二
            break
        idc.MakeDword(address)
        idaapi.autoWait()
        if idc.Dword(value) != 0xFFFFFFFF:
            func_name = idc.get_func_name(value)
            if not idc.get_func_name(value):
                idc.create_strlit(value, idc.BADADDR)
            else:
                funcs.append(func_name)

基本都这里就ok了,后面还可以加一些.text段信息,但不是必要的,最后的源码如下:

#!/usr/bin/env python
# -*- coding=utf-8 -*-

import idc
import idaapi

class Anlysis:
    def __init__(self):
        self.start_addr = idc.MinEA()
        self.end_addr = idc.MaxEA()
        self.funcs = []

    def uboot_header(self):
        idc.add_func(self.start_addr)
        idc.set_name(self.start_addr, "__start")
        idc.del_items(self.start_addr + 0x8)
        idc.MakeDword(self.start_addr + 0x8)
        self.got_ptr = idc.Dword(self.start_addr+8)
        idc.set_name(idc.Dword(self.start_addr+8), ".got.ptr")

    def got(self):
        assert(self.got_ptr)
        for address in range(self.got_ptr, self.end_addr, 4):
            value = idc.Dword(address)
            if value == 0xFFFFFFFF:
                break
            idc.MakeDword(address)
            idaapi.autoWait()
            if idc.Dword(value) != 0xFFFFFFFF:
                func_name = idc.get_func_name(value)
                if not idc.get_func_name(value):
                    idc.create_strlit(value, idc.BADADDR)
                else:
                    self.funcs.append(func_name)

    def get_max_text_addr(self):
        assert(self.funcs)
        max_addr = 0
        for func_name in self.funcs:
            addr = idc.get_name_ea_simple(func_name)
            end_addr = idc.find_func_end(addr)
            if end_addr > max_addr:
                max_addr = end_addr
        if max_addr % 0x10 == 0:
            self.max_text_addr = max_addr
        else:
            self.max_text_addr = max_addr + 0x10 - (max_addr % 0x10)

    def add_segment(self, start, end, name, type_):
        segment = idaapi.segment_t()
        segment.startEA = start
        segment.endEA = end
        segment.bitness = 1
        idaapi.add_segm_ex(segment, name, type_, idaapi.ADDSEG_SPARSE | idaapi.ADDSEG_OR_DIE)

    def start(self):
        # text seg
        self.uboot_header()
        self.got()
        self.get_max_text_addr()
        self.add_segment(self.start_addr, self.max_text_addr, ".text", "CODE")
        # end
        idc.jumpto(self.start_addr)


if __name__ == "__main__":
    print("Hello World")

*本文作者:[email protected]知道创宇404实验室,转载请注明来自FreeBuf.COM

介绍

什么是climacros?

CLI宏是一种生产力工具,可让您在IDA的命令行界面(Python,IDC,WinDbg,BochDbg,Gdb等)中定义和使用静态或动态宏。

climacros-vid-1.gif

用法

安装后,climacros始终处于活动状态。它带有一组预定的宏

为IDA命令行模式增加宏支持功能的插件

要创建或编辑新宏,只需从“快速插件视图”窗口(Ctrl-3)调用宏编辑器。

静态宏

静态宏在CLI中按原样替换。例如以下宏:

为IDA命令行模式增加宏支持功能的插件

执行时输出以下内容:

为IDA命令行模式增加宏支持功能的插件

动态宏

可以定义动态宏,这些宏通过评估返回字符串的Python表达式而得到扩展。例如,该idc.here()命令非常有用,可以缩写为较短的宏,例如$!${here}。要定义动态宏,只需用${和包围其表达式}$。长形式的宏${here}用于idc.here()表达的定义如下:

为IDA命令行模式增加宏支持功能的插件

简短形式$!

为IDA命令行模式增加宏支持功能的插件

当CLI命令中存在宏时,将调用该宏:

为IDA命令行模式增加宏支持功能的插件

内联替换

您不必定义宏以便在CLI中进行表达式扩展。如果您需要在CLI中进行一次性表达式扩展,只需定义内联表达式:

fn =  “ test _ $ {str(sum(range(10)))} $。bin ”

要么:

v =  “ $ {str(1 + 2 + 3 + 4)} $ ”

表达式应始终求值为字符串,因此始终记住str()表达式,或者"%x" % expr如果它不返回字符串,则将其格式化。

安装

climacros是用C ++编写的,带有IDA的SDK,因此它应该像常规插件一样进行部署。将插件二进制文件复制到以下任一位置:/plugins%APPDATA%\Hex-Rays/plugins由于该插件使用IDA的SDK而没有其他特定于操作系统的功能,因此该插件应该可以编译为macOS和Linux。我只提供MS Windows二进制文件。请查看*参考来源:

*本文原创作者:无。,本文属FreeBuf原创奖励计划,未经许可禁止转载

多数调试器都有一个代码跟踪(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 Arch.jpg

前期准备

Pin套件可在其官网下载(https://software.intel.com/en-us/articles/pin-a-binary-instrumentation-tool-downloads

另外,若在Windows上使用Pin,最好还下载Cygwin,并在其默认安装中额外添加’make’工具,以便我们后续编译我们自己写的Pin工具.

代码跟踪工具编写

在编写工具前,我们首先整理一下思路:

本工具只跟踪程序的执行流程,不需要详细记录每一条指令前后的寄存器变化。

根据第一条,我们的插桩粒度只需要做到基本块的级别。

在每一个基本块执行前插入代码,保存当前程序的pc计数器,并将该信息写入文件保存。

由于ALSR的存在,我们还需要保存模块的装载地址,否则第三点记录的地址便没有意义。

为了避免追踪到系统模块造成不必要的性能开销,我们暂时只对可执行程序主体模块进行插桩。

在理清思路后,我们开始代码的编写。首先是main函数的总体框架:

#include <iostream>
#include <fstream>
#include <string>
#include <set>
#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 << temp << 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函数中进行代码地址的登记即可。

set<string> 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 << temp << endl;
    }
}

在上述的BBLHit函数中,我还额外用了一个set保存已登记的基本块,避免一个基本块被多次记录。当然,是否需要重复记录这一点根据不同的任务需要会有不同的结论。

由于PIN_StartProgram()这个函数是不会返回的,所以一些清理工作(例如关闭所打开的文件)需要用PIN_AddFiniFunction()这个函数来完成。这部分就不再赘述了。至此,Pintool已编写完成。

编译、运行Pintool

对Windows平台来说,从官方下载的Pin sdk中的%PIN_DIR%\source\tools\MyPinTool是一个Visual Studio的项目,也是一个pintool的开发模板。你可以直接把上述代码复制到MyPinTool项目中然后编译。

或者你可以先将写好的代码复制入MyPinTool.cpp中,然后利用之前下载好的Cygwin,进入Cygwin Terminal,切换至该文件夹并输入’make’即可完成编译。

Linux平台也可以用类似的方法编译pintool,此处不再赘述。

我们的pintool将会被编译成.so或者.dll的形式。编译完成后,我们用下面的命令行运行它。pin [Pin Args] [-t <Tool DLL> [Tool Args]] -- <App EXE> [App args]

例如pin -t e:\myPinTool.dll -- cmd /c dir

skd中根目录下的pin.exe其实是一个启动器,会根据实际情况启动32位或64位的真正的pin.exe。或者你也可以进入ia32或intel64文件夹,直接用该目录下的pin主程序。

执行完毕之后,我们打开PIN_Trace.tlog文件,可以看到代码追踪的结果。

pintrace.png

结果很不错,程序运行基地址和执行到的基本块的地址都被记录了下来。不过缺点显而易见:太丑!

跟踪结果可视化

在这里,我们通过IDA脚本读取上述的PIN_Trace.tlog文件的内容,并将追踪结果用直观的形式表现出来。具体地,我们完成以下几个目标:

在IDA的图像界面把每个跟踪到的基本块上色。

建立一个侧边栏方便我们查询和选取跟踪记录。

相比于IDA的原生脚本IDC而言,IDAPython脚本更容易开发,代码也相对更易读懂。因此我们用IDAPython实现上述目标。

由于IDA中假设的基地址和实际运行时的基地址并不一定相同,我们首先读取tlog文件中记录的基地址,并将IDA中预设的基地址调整到这个位置上来。

#get the trace log generated by PIN
fileName = AskFile(1, "*.tlog", "Load the trace file")

#parse log file
res = ReadTraceFromFile(fileName)
addr_list = res[0]
base_in_file = res[1]

#rebase the program
base_addr = AskStr(defval=base_in_file, prompt="Enter the base address.")
base_addr = int(base_addr, base=16)
delta = base_addr - idaapi.get_imagebase()
rebase_program(delta, MSF_FIXONCE)

我们将采用SetColor这一API进行上色,但是SetColor是针对于指令的,而我们记录的是基本块的位置,因此,我们需要找到给定基本块对应的所有指令地址, 然后再用SetColor给每一条指令上色。

def SetBBColor(ea, color):
    f = idaapi.get_func(ea)
    if not f:
        SetColor(ea, CIC_ITEM, color)
        return
    fc = idaapi.FlowChart(f)
    tag = False
    for BB in fc:
        if BB.startEA <= ea:
            if BB.endEA > ea:
                tag = True
                break
    if not tag:
        print 'No BB found! ea is %x' % ea
        SetColor(ea, CIC_ITEM, color)
        return 
    for ea in range(BB.startEA, BB.endEA):
        SetColor(ea, CIC_ITEM, color)

上色部分结束。接下来,我们需要新建一个Chooser窗口,将追踪到的基本块的列表展现在此窗口中。并且,当用户双击列表中的项目时IDA将自动导航至该基本块的位置。

我们通过继承IDAPython中的Choose2类来实现这个目标。

class TraceChooser(Choose2):
    def __init__(self, title, cols, embedded = False):
        Choose2.__init__(self, title, cols, embedded = False)
        self.items = []

    def GetItem(self, n):
        return self.items[n]

    def AddItem(self, item):
        self.items.append(item)

    def OnGetLine(self, n):
        return self.items[n]

    def OnGetSize(self):
        return len(self.items)

    def OnSelectLine(self, n):
        item = self.GetItem(n)
        idc.Jump(long(item[0], 16))

整个IDAPython脚本的代码如下:

def ReadTraceFromFile(fileName):
    file = open(fileName)
    addr_list = []
    try:
        fileLine = file.readline()
        idx = fileLine.rfind('loaded at ')
        if (-1 == idx):
            print 'parse file failed!'
            return
        idx += len('loaded at ')
        base = fileLine[idx:]
        msg('base address in tlog is %s' % base)        
        while (True):
            fileLine = file.readline()
            addr_list.append(int(fileLine, 16))
    finally:
        file.close()
        return [addr_list, base]

def SetBBColor(ea, color):
    f = idaapi.get_func(ea)
    if not f:
        SetColor(ea, CIC_ITEM, color)
        return
    fc = idaapi.FlowChart(f)
    tag = False
    for BB in fc:
        if BB.startEA <= ea:
            if BB.endEA > ea:
                tag = True
                break
    if not tag:
        print 'No BB found! ea is %x' % ea
        SetColor(ea, CIC_ITEM, color)
        return 
    for ea in range(BB.startEA, BB.endEA):
        SetColor(ea, CIC_ITEM, color)

class TraceChooser(Choose2):
    def __init__(self, title, cols, embedded = False):
        Choose2.__init__(self, title, cols, embedded = False)
        self.items = []

    def GetItem(self, n):
        return self.items[n]

    def AddItem(self, item):
        self.items.append(item)

    def OnGetLine(self, n):
        return self.items[n]

    def OnGetSize(self):
        return len(self.items)

    def OnSelectLine(self, n):
        item = self.GetItem(n)
        idc.Jump(long(item[0], 16))

#get the trace log generated by PIN
fileName = AskFile(1, "*.tlog", "Load the trace file")

#parse log file
res = ReadTraceFromFile(fileName)
addr_list = res[0]
base_in_file = res[1]

#rebase the program
base_addr = AskStr(defval=base_in_file, prompt="Enter the base address.")
base_addr = int(base_addr, base=16)
delta = base_addr - idaapi.get_imagebase()
rebase_program(delta, MSF_FIXONCE)

#inititalize the chooser
tc = TraceChooser('TraceChooser', [['Address', 10], ['Function', 10]])

for ea in addr_list:
    #SetBBColor(ea, 0xFFFFFF)
    SetBBColor(ea, 0xFFDEAD)
    temp = ['{0:X}'.format(ea), get_func_name(ea)]
    tc.AddItem(temp)

tc.Show()

最后我们在IDA中能看到这个效果

final.png

总结

Intel Pin是一个非常优秀的二进制插桩工具,用它实现的代码追踪相比于传统的追踪方式速度更快。本文阐述的追踪方式仅仅是一个粗糙的示例性的代码,希望能激发大家创建更加优秀的pintools。

Reference

https://www.hex-rays.com/products/ida/support/idapython_docs/

http://www.hexblog.com/?p=324

https://software.intel.com/sites/landingpage/pintool/docs/97619/Pin/html/

*本文原创作者:无。,本文属FreeBuf原创奖励计划,未经许可禁止转载

1.png

写在前面的话

当我去参加安全会议时,我总喜欢跟大神们讨论关于逆向工程方面的东西。因为这样我就可以从别人的经验中了解到他们是如何自动化实现那些繁琐操作的了。值得一提的是,很多人已经习惯使用IDA的人却不喜欢使用内置的API来完成他们的工作。为了改变这个现状,今天我准备给大家演示IDA配合上Python之后会有怎样强大的效果。

在这篇文章中,我将给大家介绍如何利用IDA Python来枚举Windows的系统调用表。

背景知识

Windows系统中所有发生的系统调用都会被分配一个ID,这个ID是一个唯一值,用于标识一个系统调用在执行时具体使用的是哪一个函数。在不同的Windows版本中,这些ID的变化可能会非常大,对于Windows 10来说,每一个发行版本中的这些ID都不同。但是对于普通应用程序而言,由于使用的是用户模式下的代码库,因此ID不会变化。

接下来,我将给大家演示如何手动枚举调用表,然后再演示如何通过Python实现自动化枚举。

手动枚举Windows系统调用表

在解析系统调用表时我们还有三个重要的符号需要识别:表的基址、表的大小和参数在栈中所占的字节大小。对于ntoskrnl.exe来说,这三个参数的名称分别为:KiServiceTable、KiServiceLimit和KiArgumentTable。对于win32k.sys来说,这三个参数就变成了W32pServiceTable、W32pServiceLimit和W32pArgumentTable。在32位系统架构中,这些符号名称中都会包含下划线。

比如说,我们来看一看Windows 7 64位版本中ntoskrnl.exe(版本6.1.7601.24117)的信息。KiServiceLimit数据如图1所示:

2.png根据这些信息,我们可以看到这里有401个系统调用(0×191)。KiServiceTable数据如图2所示:

3.png根据图2的数据,我们就可以将函数和它们的ID手动映射出来了。其中,NtMapUserPhysicalPagesScatter对应的ID为0×0000,NtWaitForSingleObject对应的ID为0×0001,NtCallbackReturn对应的ID为0×0002,后面的依此类推。

这里有两种特殊情况需要专门处理。如果我们分析的是win32k.sys,在枚举函数ID时需要在表地址上加0×1000。同样的,对于64位Windows10(build 1607)我们也需要进行不同的处理。在这个版本的系统中,系统调用表包含了四个字节的函数偏移量。

下面给出的是版本号为10.0.17134.48的ntoskrnl.exe信息。KiServiceTable数据如图3所示:

4.png这也就意味着,我们需要每次读取四个字节的值,然后将它们与基地址相加。

利用IDA完成自动化映射

首先我们来看一看需要调用的IDA函数:

1.   idaapi.get_imagebase:这个函数将会在我们所查看的模块中返回基地址。

2.   idc.GetInputFile:该函数可以返回IDB加载的文件名称。

3.   idc.BADADDR:这是一个值为-1(无符号整形)的常量,我们还可以用它来判断当前系统为32位还是64位模式。

4.   idc.Name:该函数可以返回给定地址的名称。

5.   idc.LocByName:它的功能跟idc.Name正好相反,它用于返回给定名称的地址。

6.   idc.Dword:该函数能返回给定地址的四字节值。

7.   idc.Qword:该函数可返回给定地址的八字节值。

8.   idautils.DataRefsFrom:该函数可根据任意数据引用枚举给定地址。

首先,我们需要确定正在分析的对象是ntoskrnl.exe还是win32k.sys:

5.png接下来,我们需要判断使用哪一个符号名称,并判断是否需要在变量中添加下划线:

6.png

如果表不存在,LocByName将返回BADADDR,所以我们就可以利用这一点来测试符号名称是否存在或是否需要添加下划线。

得到了正确的符号名称之后,我们需要获取表的实际大小:

7.png

首先获取LocByName的地址,然后利用Dword获取地址值。下面给出的是64位Windows 10的处理方法:

8.png

DataRefsFrom将会利用数据引用来遍历表的基地址,但如果目标是新版本的Windows 10系统,我们就需要在基地址上加上相应的值。接下来需要做的就是从表的基地址开始读取连续的值。我们可以使用Qword函数处理64位版本,用Dword处理32位版本。下面给出的是输出结果样本:

9.png

工具下载

完整源码获取:【GitHub传送门

总结

逆向工程分析本来就是一个枯燥乏味的工作,但任务的自动化实现却能够在无聊中添加一些趣味。希望大家能够喜欢这篇文章,并在日常工作中发现IDA和Python的更多功能。

* 参考来源:zerodayinitiative,FB小编Alpha_h4ck编译,转载请注明来自FreeBuf.COM

     利用业余开发了一款类似仿OLlyDbg界面的 IDA静态反编译工具,目前是1.0版本,功能不是很强大但是基本功能有了

1.  显示一个PE文件的汇编代码,多种颜色高亮显示,界面风格与OD界面相同,同时也显示二进制十六进制的数据窗口,还有PE头信息的窗口。

2.  目前只支持windowsPE,支持X86 / X64

3.  目前是全内存方式,暂时没有类似IDAidb文件方式,所以很大的PE 比如50M以上的PE会占比较大的内存

4.  暂时没实现OllyDbg的快捷键

5.  暂时还没实现动态调试。

6.  下面是软件截图

32位的PE文件

QQ图片20180322220505.png

64PE文件

QQ图片20180322220650.png


下载地址:

https://github.com/basketwill/Z0BPcTools

     利用业余开发了一款类似仿OLlyDbg界面的 IDA静态反编译工具,目前是1.0版本,功能不是很强大但是基本功能有了

1.  显示一个PE文件的汇编代码,多种颜色高亮显示,界面风格与OD界面相同,同时也显示二进制十六进制的数据窗口,还有PE头信息的窗口。

2.  目前只支持windowsPE,支持X86 / X64

3.  目前是全内存方式,暂时没有类似IDAidb文件方式,所以很大的PE 比如50M以上的PE会占比较大的内存

4.  暂时没实现OllyDbg的快捷键

5.  暂时还没实现动态调试。

6.  下面是软件截图

32位的PE文件

QQ图片20180322220505.png

64PE文件

QQ图片20180322220650.png


下载地址:

https://github.com/basketwill/Z0BPcTools

IDA pro 7.0版本

用到的工具有IDA pro 7.0  ,被反汇编的是百度云(BaiduNetdisk_5.6.1.2.exe)。



首先,IDA pro的长相如下:

image.png


共有(File , Edit , Jump , Search , View , Debugger , Options , Windows , Help)9个模块,还有下面的诸多小菜单。

现在我们点击File,选择Open打开一个文件,这里我们选择百度云盘PC端安装程序,出现如下图示:


image.png


这里我们直接默认OK即可。

此时,我们看到的视图是这样的:


image.png


然后我们对各个部分进行标号,单独进行介绍:


image.png


第一部分表示的是对不同代码块使用不同的颜色进行区分,我们可以直接点击相应的颜色块进行不同代码块的定位。

蓝色:表示代码段。

棕色:表示数据段。

红色:表示内核。

第二部分表示该程序的函数表,双击后可查看详细信息。


image.png


该函数对应的IDA View-A如下:


image.png


第三部分对应的就是整体程序或者某个函数的图标概述形式,可以大体把握功能和结构的走向。对整体的脱壳逆向有很大的帮助。


image.png


第四部分主要可以显示以下6部分信息:

(1)IDA View-A

(2)Hex View-1

(3)Structures

(4)Enums

(5)Imports

(6)Exports

其中IDA View-A表示的就是某个函数的图标架构,可以查看程序的逻辑树形图,把程序的结构更人性化地显示出来,方便我们的分析。

具体表示形式,上文中有截图可参考。

在Hex View-1中可以查看16进制代码,方便定位代码后使用其他工具修改,具体表示如下图所示:


image.png


在Stuuctures中可以查看程序的结构体:


image.png


在Enums中可以查看枚举信息:


image.png


在Imports中可以查看到输入函数,导入表即程序中调用到的外面的函数:


image.png


在Exports中可以查看到输出函数:


image.png


以上就是IDA主面板中的各个部分的功能介绍了。



接下来我们介绍9个菜单模块,即:

File , Edit , Jump , Search , View , Debugger , Options , Windows , Help

image.png

1.File 是用来打开,新建,装载一个应用程序的,这大家都知道的。

2.Edit 是用来编辑反汇编代码的,可以复制,筛选什么的。

3.Jump 是用来跳转的,可以有很多种类型的跳转,比如跳转到上一个位置或者下一个位置,跳转到某个指定的地址。还可以根据名字,函数来进行跳转,跳转到一个新的窗口,跳转某一个偏移量等等,总之很多了,具体大家可以慢慢积累了。这个模块就比较重要了。

4.Serach 是用来搜索的。

5.View 是用来选择显示方式的,或者显示某一特定模块信息的。比如以树形逻辑图显示,或者16进制形式显示。还可以单独显示某一特定信息,比如输入或者输出表等。

6.Debugger ,调试器被集成在IDA中,首先我们使用IDA装入文件,来生成数据库,用户可以使用反汇编功能,查看所有反汇编信息,这些均可以在调试器中进行和使用。

image.png

7.Options ,在这里可以进行一下常规性的设置。

8.Windows,

9.Help,使用IDA的一些帮助文档,检查更新等等。



使用IDA的一个大体步骤:

1.装入文件或程序

2.指令断点

3.程序运行

4.分析堆栈

5.添加监视

6.进行地址分析

7.单步跟踪

8.找到bug

9.使用硬件断点进行bug确认



本期就介绍到这里了,IDA有很多高级功能,会在之后的实战文章中进行讲解。

下一期我们将讲解《逆向动态调试之Ollydbg的使用》。

学完汇编,IDA,Ollydug我们就可以进行简单的逆向分析了。

IDA pro 7.0版本

用到的工具有IDA pro 7.0  ,被反汇编的是百度云(BaiduNetdisk_5.6.1.2.exe)。



首先,IDA pro的长相如下:

image.png


共有(File , Edit , Jump , Search , View , Debugger , Options , Windows , Help)9个模块,还有下面的诸多小菜单。

现在我们点击File,选择Open打开一个文件,这里我们选择百度云盘PC端安装程序,出现如下图示:


image.png


这里我们直接默认OK即可。

此时,我们看到的视图是这样的:


image.png


然后我们对各个部分进行标号,单独进行介绍:


image.png


第一部分表示的是对不同代码块使用不同的颜色进行区分,我们可以直接点击相应的颜色块进行不同代码块的定位。

蓝色:表示代码段。

棕色:表示数据段。

红色:表示内核。

第二部分表示该程序的函数表,双击后可查看详细信息。


image.png


该函数对应的IDA View-A如下:


image.png


第三部分对应的就是整体程序或者某个函数的图标概述形式,可以大体把握功能和结构的走向。对整体的脱壳逆向有很大的帮助。


image.png


第四部分主要可以显示以下6部分信息:

(1)IDA View-A

(2)Hex View-1

(3)Structures

(4)Enums

(5)Imports

(6)Exports

其中IDA View-A表示的就是某个函数的图标架构,可以查看程序的逻辑树形图,把程序的结构更人性化地显示出来,方便我们的分析。

具体表示形式,上文中有截图可参考。

在Hex View-1中可以查看16进制代码,方便定位代码后使用其他工具修改,具体表示如下图所示:


image.png


在Stuuctures中可以查看程序的结构体:


image.png


在Enums中可以查看枚举信息:


image.png


在Imports中可以查看到输入函数,导入表即程序中调用到的外面的函数:


image.png


在Exports中可以查看到输出函数:


image.png


以上就是IDA主面板中的各个部分的功能介绍了。



接下来我们介绍9个菜单模块,即:

File , Edit , Jump , Search , View , Debugger , Options , Windows , Help

image.png

1.File 是用来打开,新建,装载一个应用程序的,这大家都知道的。

2.Edit 是用来编辑反汇编代码的,可以复制,筛选什么的。

3.Jump 是用来跳转的,可以有很多种类型的跳转,比如跳转到上一个位置或者下一个位置,跳转到某个指定的地址。还可以根据名字,函数来进行跳转,跳转到一个新的窗口,跳转某一个偏移量等等,总之很多了,具体大家可以慢慢积累了。这个模块就比较重要了。

4.Serach 是用来搜索的。

5.View 是用来选择显示方式的,或者显示某一特定模块信息的。比如以树形逻辑图显示,或者16进制形式显示。还可以单独显示某一特定信息,比如输入或者输出表等。

6.Debugger ,调试器被集成在IDA中,首先我们使用IDA装入文件,来生成数据库,用户可以使用反汇编功能,查看所有反汇编信息,这些均可以在调试器中进行和使用。

image.png

7.Options ,在这里可以进行一下常规性的设置。

8.Windows,

9.Help,使用IDA的一些帮助文档,检查更新等等。



使用IDA的一个大体步骤:

1.装入文件或程序

2.指令断点

3.程序运行

4.分析堆栈

5.添加监视

6.进行地址分析

7.单步跟踪

8.找到bug

9.使用硬件断点进行bug确认



本期就介绍到这里了,IDA有很多高级功能,会在之后的实战文章中进行讲解。

下一期我们将讲解《逆向动态调试之Ollydbg的使用》。

学完汇编,IDA,Ollydug我们就可以进行简单的逆向分析了。

今天给大家介绍的是一款代码静态分析工具,该工具支持IDA,并且能够进行污点分析以及类型重构。

a1.png

BinCAT是什么?

BinCAT是一款静态二进制代码分析工具包,它可以整合进IDA Pro,并能够给逆向工程师的代码分析活动提供有效的帮助。

功能介绍

-值分析(寄存器+内存)

-污点分析

-类型重构

-前向以及后向分析

运行演示

感兴趣的同学可以点击下列视频了解BinCAT的运行演示:

基本分析功能:【视频地址

使用数据污点:【视频地址

详细使用教程:【传送门

参考文档:【传送门

支持的主机平台

-IDA插件:v6.9及其之后版本(BinCAT使用PyQt,而非PySide)

-分析器(本地或远程):Linux,Windows,macOS(未测试)

支持的CPU(目前)

-x86-32

-ARMv7

-ARMv8

工具安装

分析器

分析器可以在本地安装,或通过Web服务进行安装。

Windows平台下的程序代码中已包含分析器:【下载地址
在Linux平台下,你可以使用Docker【下载地址】或手动进行安装【手册】。

IDA插件

仅支持IDA v6.9及更高版本

Windows平台下的安装步骤:

-解压BinCAT

-在IDA中,点击“文件”->“脚本文件”(或按下ALT+F7)

-选择windows_install.py

-BinCAT将会安装在IDA的用户目录下

如果你想手动安装的话,请参考【安装手册】。

Linux平台下的安装

安装手册

使用BinCAT

1.      使用组合键Ctrl+Shift+B或进入Edit -> Plugins -> BinCAT菜单

2.      在IDA界面中选择操作指令,然后按下Ctrl+Shift+A组合键,或进入BinCAT -> Analyze from here菜单。

工具配置

你可以通过Edit/BinCAT/Options菜单来进行全局配置。

默认配置和选项存储在$IDAUSR/idabincat/conf之中。

执行分析并观察结果

1.      通过组合键Ctrl + Shift + B加载BinCAT插件

2.      在IDA中打开get_key_x86可执行程序

3.      在IDA View-A试图中,按下g键进入地址0x93B

4.      使用组合键Ctrl + Shift + A开启分析窗口

5.      确保分析器的配置已设置为(new)

6.      检查Save configuration to IDB选项

7.      检查Remap binary选项

8.      点击Edit analyzer config按钮

9.      将下列内容复制到[state]区域的结尾,重写已存在的stack初始化(stack[0x1000*8192]…):

stack[0x1000*4099] = |00|?0xFF
stack[0x2004] = 5
stack[0x2008] = 0x200000
mem[0x200000] = 0x300100
mem[0x200004] = 0x300140
mem[0x200008] = 0x300180
mem[0x20000C] = 0x3001C0
mem[0x200010] = 0x300200
mem[0x300100] = |6c6f6c3300|
mem[0x300140] = |636f6d70616e7900|
mem[0x300180] = |64657000|
mem[0x3001C0] = |6c6f6c3100|
mem[0x300200] = |6c6f6c2100|

10.点击“Save”

11. 点击“Start”

12.选择结果代码的存储位置,该操作只需要首次分析时配置即可

13.给需要存储的配置文件命名

14.分析完成后,按下g键进入地址0×807,在BinCAT Registers视图中观察gsp寄存器的值(应该为0x1D50)。打开BinCAT Memory视图,观察buffer指针(char *)的内容。

更多内容

更多的使用方法请大家参考BinCAT提供的【入门教程】。

* 参考来源:bincat,FB小编Alpha_h4ck编译,转载请注明来自FreeBuf.COM

今天给大家介绍的是一款代码静态分析工具,该工具支持IDA,并且能够进行污点分析以及类型重构。

a1.png

BinCAT是什么?

BinCAT是一款静态二进制代码分析工具包,它可以整合进IDA Pro,并能够给逆向工程师的代码分析活动提供有效的帮助。

功能介绍

-值分析(寄存器+内存)

-污点分析

-类型重构

-前向以及后向分析

运行演示

感兴趣的同学可以点击下列视频了解BinCAT的运行演示:

基本分析功能:【视频地址

使用数据污点:【视频地址

详细使用教程:【传送门

参考文档:【传送门

支持的主机平台

-IDA插件:v6.9及其之后版本(BinCAT使用PyQt,而非PySide)

-分析器(本地或远程):Linux,Windows,macOS(未测试)

支持的CPU(目前)

-x86-32

-ARMv7

-ARMv8

工具安装

分析器

分析器可以在本地安装,或通过Web服务进行安装。

Windows平台下的程序代码中已包含分析器:【下载地址
在Linux平台下,你可以使用Docker【下载地址】或手动进行安装【手册】。

IDA插件

仅支持IDA v6.9及更高版本

Windows平台下的安装步骤:

-解压BinCAT

-在IDA中,点击“文件”->“脚本文件”(或按下ALT+F7)

-选择windows_install.py

-BinCAT将会安装在IDA的用户目录下

如果你想手动安装的话,请参考【安装手册】。

Linux平台下的安装

安装手册

使用BinCAT

1.      使用组合键Ctrl+Shift+B或进入Edit -> Plugins -> BinCAT菜单

2.      在IDA界面中选择操作指令,然后按下Ctrl+Shift+A组合键,或进入BinCAT -> Analyze from here菜单。

工具配置

你可以通过Edit/BinCAT/Options菜单来进行全局配置。

默认配置和选项存储在$IDAUSR/idabincat/conf之中。

执行分析并观察结果

1.      通过组合键Ctrl + Shift + B加载BinCAT插件

2.      在IDA中打开get_key_x86可执行程序

3.      在IDA View-A试图中,按下g键进入地址0x93B

4.      使用组合键Ctrl + Shift + A开启分析窗口

5.      确保分析器的配置已设置为(new)

6.      检查Save configuration to IDB选项

7.      检查Remap binary选项

8.      点击Edit analyzer config按钮

9.      将下列内容复制到[state]区域的结尾,重写已存在的stack初始化(stack[0x1000*8192]…):

stack[0x1000*4099] = |00|?0xFF
stack[0x2004] = 5
stack[0x2008] = 0x200000
mem[0x200000] = 0x300100
mem[0x200004] = 0x300140
mem[0x200008] = 0x300180
mem[0x20000C] = 0x3001C0
mem[0x200010] = 0x300200
mem[0x300100] = |6c6f6c3300|
mem[0x300140] = |636f6d70616e7900|
mem[0x300180] = |64657000|
mem[0x3001C0] = |6c6f6c3100|
mem[0x300200] = |6c6f6c2100|

10.点击“Save”

11. 点击“Start”

12.选择结果代码的存储位置,该操作只需要首次分析时配置即可

13.给需要存储的配置文件命名

14.分析完成后,按下g键进入地址0×807,在BinCAT Registers视图中观察gsp寄存器的值(应该为0x1D50)。打开BinCAT Memory视图,观察buffer指针(char *)的内容。

更多内容

更多的使用方法请大家参考BinCAT提供的【入门教程】。

* 参考来源:bincat,FB小编Alpha_h4ck编译,转载请注明来自FreeBuf.COM