Triton 学习 - pintool 篇

isnull   ·   发表于 2019-05-14 11:24:26   ·   漏洞文章

介绍

本节介绍 tritonpin 使用方式以及一些有意思的demopin 是一个二进制插桩工具,可以在程序运行时通过回调函数的机制监控程序的运行,可以用来做代码覆盖率,污点分析等。tritonpin 包装了一层 python 的接口,现在我们可以使用 python 来运行 pin, 非常的方便。

相关资源位于

https://gitee.com/hac425/data/tree/master/triton/learn

简单使用

下面以一个简单示例来看看如何在 Triton 里面使用  pin

#!/usr/bin/env python2
## -*- coding: utf-8 -*-

from pintool import *
from triton  import ARCH

count = 0
def mycb(inst):
    global count
    count += 1

def fini():
    print("Instruction count : ", count)

if __name__ == '__main__':
    ctx = getTritonContext()
    ctx.enableSymbolicEngine(False)
    ctx.enableTaintEngine(False)

    # 从程序入口开始插桩
    startAnalysisFromEntry()

    # 在每条指令执行前调用 mycb
    insertCall(mycb, INSERT_POINT.BEFORE)

    # 程序运行完毕后的回调函数
    insertCall(fini, INSERT_POINT.FINI)
    runProgram()

这个脚本的作用是统计被测程序从 Entry 开始执行过的指令条数。

  • 首先通过 getTritonContextpintools 里面获取一个 TritonContext 实例用于维持程序执行过程中的状态信息,比如污点传播的信息,符号执行的信息等。
  • 然后为了节省效率关闭了污点分析和符号执行引擎。
  • 然后使用 startAnalysisFromEntry 设置 pin 在程序入口时进行插桩。
  • 通过 insertCall 可以在程序执行的过程中设置回调函数,监控程序的执行。比如 INSERT_POINT.BEFORE 就是在每次指令执行的时候会调用回调函数,回调函数接收一个参数表示接下来要执行的指令。INSERT_POINT.FINI 表示在程序执行完毕后调用回调函数。为了统计运行的指令只需要在指令执行 count += 1 即可。
  • 准备好 pin 的参数后,就可以 runProgram 运行程序了。
  • 使用了 pintool 模块的脚本需要用编译目录下的 triton 来加载脚本执行。脚本执行的语法的是

    sudo ./build/triton 脚本的路径 要运行的目标应用程序 目标应用程序的参数

    下面对 crackme_xor 插桩得到的结果。

    hac425@ubuntu:~/pin-2.14-71313-gcc.4.4.7-linux/source/tools/Triton$ sudo ./build/triton src/examples/pin/learn/count_inst.py src/samples/crackmes/crackme_xor aaaaa
    fail
    ('Instruction count : ', 59417)

    Triton 中我们可以指定 pin 插桩的位置,可以用的 api 如下

    startAnalysisFromAddress(addr)
    # 从 addr 开始插桩程序
    
    startAnalysisFromEntry()   
    # 从程序入口, 即 start 函数开始分析
    
    startAnalysisFromOffset(integer offset)   
    # 从程序的偏移处开始分析

    同时 triton 支持以下几种指令执行过程中的回调

    INSERT_POINT.AFTER每条指令执行之后,执行回调函数
    INSERT_POINT.BEFORE每条指令执行之前,执行回调函数
    INSERT_POINT.BEFORE_SYMPROC每条指令符号化处理之前,执行回调函数
    INSERT_POINT.FINI程序运行结束后
    INSERT_POINT.ROUTINE_ENTRY进入函数时
    INSERT_POINT.ROUTINE_EXIT退出函数时
    INSERT_POINT.IMAGE_LOAD镜像加载到内存时
    INSERT_POINT.SIGNALS出现一个信号时
    INSERT_POINT.SYSCALL_ENTRY系统调用执行前
    INSERT_POINT.SYSCALL_EXIT系统调用执行后

    详细的信息可以看官方文档

    https://triton.quarkslab.com/documentation/doxygen/py_INSERT_POINT_page.html

    特别的,对于指令执行相关的回调,它们的执行顺序是

    BEFORE_SYMPROC
    ir processing, 做污点分析与符号执行相关的操作
    BEFORE
    Pin ctx update, 执行指令, 修改 TritonContext 里面的运行时信息
    AFTER

    污点分析

    之前我们通过模拟执行的方式使用了污点分析的功能,这节介绍使用 pin 来在程序运行过程中实现污点分析,还是以 crachme_xor 为例

    #!/usr/bin/env python2
    # -*- coding: utf-8 -*-
    from triton import ARCH, MemoryAccess, OPERAND
    from pintool import *
    
    Triton = getTritonContext()
    def cbeforeSymProc(instruction):
        if instruction.getAddress() == 0x400556:
            rdi = getCurrentRegisterValue(Triton.registers.rdi)
            # 内存要对齐
            Triton.taintMemory(MemoryAccess(rdi, 8))
    
    
    def cafter(inst):
        if inst.isTainted():
            # print('[tainted] %s' % (str(inst)))
    
            if inst.isMemoryRead():
                for op in inst.getOperands():
                    if op.getType() == OPERAND.MEM:
                        print("read:0x{:08x}, size:{}".format(
                            op.getAddress(), op.getSize()))
    
            if inst.isMemoryWrite():
                for op in inst.getOperands():
                    if op.getType() == OPERAND.MEM:
                        print("write:0x{:08x}, size:{}".format(
                            op.getAddress(), op.getSize()))
    
    
    if __name__ == '__main__':
        startAnalysisFromSymbol('check')
        insertCall(cbeforeSymProc,  INSERT_POINT.BEFORE_SYMPROC)
        insertCall(cafter,          INSERT_POINT.AFTER)
        runProgram()
  • 首先设置 pincheck 函数开始插桩
  • 然后为 BEFORE_SYMPROCAFTER 设置回调函数。
  • cbeforeSymProc 函数的作用就是在进入 check 函数的时候,设置参数对应的内存区域为污点源,之后程序的执行就可以实现污点传播了。
  • cafter 的作用是打印对污点内存的访问情况。
  • 执行结果如下:

    hac425@ubuntu:~/pin-2.14-71313-gcc.4.4.7-linux/source/tools/Triton$ sudo ./build/triton ./src/examples/pin/learn/taint.py  ./src/samples/crackmes/crackme_xor elite
    [sudo] password for hac425: 
    read:0x7ffcd5b8d626, size:1
    read:0x7ffcd5b8d627, size:1
    read:0x7ffcd5b8d628, size:1
    read:0x7ffcd5b8d629, size:1
    read:0x7ffcd5b8d62a, size:1
    Win

    符号执行

    本节还是以 crackme_xor 为例介绍基于 pin 的符号执行的使用。triton 支持快照功能,我们可以在执行待分析函数之前拍个快照,然后在后面某个时间恢复快照就可以继续从快照点开始执行了,如图所示:

    脚本如下:

    # -*- coding: utf-8 -*-
    # sudo ./build/triton ./src/examples/pin/learn/crackme_xor_snapshot.py ./src/samples/crackmes/crackme_xor a
    from triton import ARCH
    from pintool import *
    import sys
    
    password = dict()
    cur_char_ptr = None
    Triton = getTritonContext()
    
    
    def csym(instruction):
        global cur_char_ptr
    
        # print(instruction)
    
        # 目标函数的入口地址, 第一次执行就拍个快照
        if instruction.getAddress() == 0x400556 and isSnapshotEnabled() == False:
            takeSnapshot()
            return
        if instruction.getAddress() == 0x400574:
            rax = getCurrentRegisterValue(Triton.registers.rax)
            cur_char_ptr = rax  # 每次取字符的地址
    
            # 如果这个位置已经求出解了,就设置解
            if rax in password:
                setCurrentMemoryValue(rax, password[rax])
            return
    
        # check 函数的结尾, 判断返回值是否满足要求
        if instruction.getAddress() == 0x4005b2:
            rax = getCurrentRegisterValue(Triton.registers.rax)
            # 如果 rax 不是 0 , 说明还需要继续求解
            if rax != 0:
                # 恢复快照,继续运行
                restoreSnapshot()
            else:
                disableSnapshot()
                # 把解由地址从低往高开始打印
                addrs = password.keys()
                addrs.sort()
                answer = ""
                for addr in addrs:
                    c = chr(password[addr])
                    answer += c
                    print("0x{:08x}: {}".format(addr, c))
                print("answer: {}".format(answer))
            return
        return
    
    def cafter(instruction):
        global password
        # print(instruction)
    
        # 0000400574 movzx   eax, byte ptr [rax]
        # 执行完 0x400574 后, rax 里面存放的是刚刚从输入字符串中取出的字符
        # 将取出的字符设置为符号量, 后面用来求解
        if instruction.getAddress() == 0x400574:
            var = Triton.convertRegisterToSymbolicVariable(Triton.registers.rax)
            return
    
        # 400597 cmp     ecx, eax
        # 开始求解约束
        if instruction.getAddress() == 0x400597:
            astCtxt = Triton.getAstContext()
            zf = Triton.getRegisterAst(Triton.registers.zf)
            # 如果正确的话,需要 zf == 1, 增加约束
            cstr = astCtxt.land([
                Triton.getPathConstraintsAst(),
                zf == 1
            ])
            models = Triton.getModel(cstr)
            for k, v in list(models.items()):
                # 把计算的结果保存
                password.update({cur_char_ptr: v.getValue()})
            return
        return
    
    
    def fini():
        print('[+] Analysis done!')
        return
    
    if __name__ == '__main__':
        setupImageWhitelist(['crackme_xor'])
        startAnalysisFromAddress(0x0400556)
        insertCall(cafter,  INSERT_POINT.AFTER)
        insertCall(csym,    INSERT_POINT.BEFORE_SYMPROC)
    
        insertCall(fini,    INSERT_POINT.FINI)
        runProgram()

    脚本的流程如下

  • 通过 setupImageWhitelist 设置分析镜像的白名单,减少程序的运行时间。
  • 通过 startAnalysisFromAddress 设置 pin0x0400556 (即 check函数的入口)分析。
  • 然后设置了几个回调。
  • 在程序第一次进入 0x400556 时使用 takeSnapshot 拍摄一个快照, 然后在 0x0400597 这个位置设置约束条件不断求出解,最后在函数执行完毕后检查返回值,如果返回值不为 0 说明解还没有完全求出,那么恢复快照继续去求解。

    脚本运行如下:

    hac425@ubuntu:~/pin-2.14-71313-gcc.4.4.7-linux/source/tools/Triton$ sudo ./build/triton ./src/examples/pin/learn/crackme_xor_snapshot.py ./src/samples/crackmes/crackme_xor a
    0x7ffc3471661f: e
    0x7ffc34716620: l
    0x7ffc34716621: i
    0x7ffc34716622: t
    0x7ffc34716623: e
    answer: elite
    Win
    [+] Analysis done!


    除了手动设置约束外,我们还可以基于分支指令的约束来不断的求出解,如下所示


    #!/usr/bin/env python2
    ## -*- coding: utf-8 -*-
    ## sudo ./build/triton ./src/examples/pin/learn/path_constraints.py ./src/samples/crackmes/crackme_xor a
    ##  [+] 10 bytes tainted from the argv[1] (0x7ffd4a50c60e) pointer
    
    from triton  import *
    from pintool import *
    
    TAINTING_SIZE = 10
    
    Triton = getTritonContext()
    
    
    def tainting(threadId):
        rdi = getCurrentRegisterValue(Triton.registers.rdi) # argc
        rsi = getCurrentRegisterValue(Triton.registers.rsi) # argv
    
        # 将 argv 的最后一个参数设置为符号量
        while rdi > 1:
            argv = getCurrentMemoryValue(rsi + ((rdi-1) * CPUSIZE.QWORD), CPUSIZE.QWORD)
            offset = 0
            while offset != TAINTING_SIZE:
                Triton.taintMemory(argv + offset)
                concreteValue = getCurrentMemoryValue(argv + offset)
                Triton.setConcreteMemoryValue(argv + offset, concreteValue)
                Triton.convertMemoryToSymbolicVariable(MemoryAccess(argv + offset, CPUSIZE.BYTE))
                offset += 1
            print('[+] %02d bytes tainted from the argv[%d] (%#x) pointer' %(offset, rdi-1, argv))
            rdi -= 1
    
        return
    
    
    def fini():
        pco = Triton.getPathConstraints()
        astCtxt = Triton.getAstContext()
        for pc in pco:
            if pc.isMultipleBranches():
                b1   =  pc.getBranchConstraints()[0]['constraint']
                b2   =  pc.getBranchConstraints()[1]['constraint']
                seed = list()
    
                # Branch 1
                models  = Triton.getModel(b1)
                for k, v in list(models.items()):
                    seed.append(v)
    
                # Branch 2
                models  = Triton.getModel(b2)
                for k, v in list(models.items()):
                    seed.append(v)
    
                if seed:
                    print('进入分支B1的要求: %s (%c)  |  进入分支B2的要求: %s (%c)' %(seed[0], chr(seed[0].getValue()), seed[1], chr(seed[1].getValue())))
        return
    
    if __name__ == '__main__':
        # Start the symbolic analysis from the 'main' function
        startAnalysisFromSymbol('main')
    
        # Align the memory
        Triton.enableMode(MODE.ALIGNED_MEMORY, True)
    
        # Only perform the symbolic execution on the target binary
        setupImageWhitelist(['crackme_xor'])
    
        # Add callbacks
        insertCall(tainting, INSERT_POINT.ROUTINE_ENTRY, 'main')
        insertCall(fini,     INSERT_POINT.FINI)
    
        # Run the instrumentation - Never returns
        runProgram()

    这个脚本的作用是在进入 main 函数前将命令行参数设置为符号量, 然后在程序执行完毕后,对搜集到的约束条件进行遍历, 对其中的分支指令,对每种分支的约束进行求解,然后打印进入每条分支需要的值。

    运行结果如下:



    打赏我,让我更有动力~

    0 条回复   |  直到 2019-5-14 | 1297 次浏览
    登录后才可发表内容
    返回顶部 投诉反馈

    © 2016 - 2024 掌控者 All Rights Reserved.