• Triton 学习


    介绍

    Triton 是一款动态二进制分析框架,它支持符号执行和污点分析,同时提供了 pintoolspython 接口,我们可以使用 python 来使用 pintools 的功能。 Triton 支持的架构有 x86, x64, AArch64.

    所有相关文件位于

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

    安装

    首先需要安装依赖

    sudo apt-get install libz3-dev  libcapstone-dev libboost-dev  libopenmpi-dev
    

    然后根据官网教程进行安装

    $ git clone https://github.com/JonathanSalwan/Triton.git
    $ cd Triton
    $ mkdir build
    $ cd build
    $ cmake ..
    $ sudo make -j install
    

    报错的解决方案

    缺少 openmp 库

    [ 86%] Built target python-triton
    [ 87%] Linking CXX executable simplification
    ../../libtriton/libtriton.so: undefined reference to `omp_get_thread_num'
    ../../libtriton/libtriton.so: undefined reference to `omp_get_num_threads'
    ../../libtriton/libtriton.so: undefined reference to `omp_destroy_nest_lock'
    ../../libtriton/libtriton.so: undefined reference to `omp_set_nest_lock'
    ../../libtriton/libtriton.so: undefined reference to `omp_get_num_procs'
    ../../libtriton/libtriton.so: undefined reference to `omp_unset_nest_lock'
    ../../libtriton/libtriton.so: undefined reference to `GOMP_critical_name_end'
    ../../libtriton/libtriton.so: undefined reference to `omp_in_parallel'
    ../../libtriton/libtriton.so: undefined reference to `omp_init_nest_lock'
    ../../libtriton/libtriton.so: undefined reference to `GOMP_parallel'
    ../../libtriton/libtriton.so: undefined reference to `omp_set_nested'
    ../../libtriton/libtriton.so: undefined reference to `GOMP_critical_name_start'
    collect2: error: ld returned 1 exit status
    
    

    CMakeLists.txt 增加编译参数

    CMakeLists.txt 增加编译参数

    set(CMAKE_C_FLAGS "-fopenmp")
    set(CMAKE_CXX_FLAGS "-fopenmp")
    

    z3版本太老

    如果使用 ubuntu 16.04 由于 aptz3 版本太老,需要下载最新版的 z3 进行编译, 然后使用新版的 z3 来编译.

    cmake .. -DZ3_INCLUDE_DIRS="/home/hac425/z3-4.8.4.d6df51951f4c-x64-ubuntu-16.04/include"  -DZ3_LIBRARIES="/home/hac425/z3-4.8.4.d6df51951f4c-x64-ubuntu-16.04/bin/libz3.a"
    

    使用介绍

    下面以一些使用示例来介绍 Triton 的使用, Triton 的基本使用流程是提取出指令的字节码和指令的地址,然后传递给 Triton 去执行指令,在指令的执行过程中会维持符号量和污点值的传播。

    模拟执行

    Triton 首先的一个应用场景就是模拟执行,在 Triton 中执行的执行是由我们控制的,污点分析和符号执行都是基于模拟执行实现的。

    下面是一个模拟执行的示例

    #!/usr/bin/env python2
    # -*- coding: utf-8 -*-
    
    from __future__ import print_function
    from triton import TritonContext, ARCH, Instruction, OPERAND
    import sys
    
    # 每一项的结构是 (指令的地址, 指令的字节码)
    code = [
        (0x40000, b"x40xf6xee"),      # imul   sil
        (0x40003, b"x66xf7xe9"),      # imul   cx
        (0x40006, b"x48xf7xe9"),      # imul   rcx
        (0x40009, b"x6bxc9x01"),      # imul   ecx,ecx,0x1
        (0x4000c, b"x0fxafxca"),      # imul   ecx,edx
        (0x4000f, b"x48x6bxd1x04"),  # imul   rdx,rcx,0x4
        (0x40013, b"xC6x00x01"),      # mov    BYTE PTR [rax],0x1
        (0x40016, b"x48x8Bx10"),      # mov    rdx,QWORD PTR [rax]
        (0x40019, b"xFFxD0"),          # call   rax
        (0x4001b, b"xc3"),              # ret
        (0x4001c, b"x80x00x01"),      # add    BYTE PTR [rax],0x1
        (0x4001f, b"x64x48x8Bx03"),  # mov    rax,QWORD PTR fs:[rbx]
    ]
    if __name__ == '__main__':
        Triton = TritonContext()
        # 首先设置后面需要模拟执行的代码的架构, 这里是 x64 架构
        Triton.setArchitecture(ARCH.X86_64)
        for (addr, opcode) in code:
            
            # 新建一个指令对象 
            inst = Instruction()
            inst.setOpcode(opcode)  # 传递字节码
            inst.setAddress(addr)   # 传递指令的地址
    
            # 执行指令
            Triton.processing(inst)
    
            # 打印指令的信息
            print(inst)
            print('    ---------------')
            print('    Is memory read :', inst.isMemoryRead())
            print('    Is memory write:', inst.isMemoryWrite())
            print('    ---------------')
            for op in inst.getOperands():
                print('    Operand:', op)
                if op.getType() == OPERAND.MEM:
                    print('    - segment :', op.getSegmentRegister())
                    print('    - base    :', op.getBaseRegister())
                    print('    - index   :', op.getIndexRegister())
                    print('    - scale   :', op.getScale())
                    print('    - disp    :', op.getDisplacement())
                print('    ---------------')
            print()
        sys.exit(0)
    

    这个脚本的功能是 code 列表中的指令,并打印指令的信息。

    • 首先需要新建一个 TritonContextTritonContext 用于维护指令执行过程的状态信息,比如寄存器的值,符号量的传播等,后面指令的执行过程中会修改 TritonContext 里面的一些状态。
    • 然后调用 setArchitecture 设置后面处理指令集的架构类型,在这里是 ARCH.X86_64 表示的是 x64 架构,其他两个可选项分别为: ARCH.AARCH64ARCH.X86 .
    • 之后就可以去执行指令了,首先需要用 Instruction 类封装每条指令,设置指令的地址和字节码。
    • 然后通过 Triton.processing(inst) 就可以执行一条指令。
    • 同时 Instruction 对象里面还有一些与指令相关的信息可以使用,比如是否会读写内存,操作数的类型等,在这个示例中就是简单的打印这些信息。

    下面再以 cmubomb 题目中 phase_4 为实例,加深 Triton 执行指令的流程。

    首先看看 phase_4 的代码逻辑

    unsigned int __cdecl phase_4(int a1)
    {
      unsigned int v2; // [esp+4h] [ebp-14h]
      int v3; // [esp+8h] [ebp-10h]
      unsigned int v4; // [esp+Ch] [ebp-Ch]
    
      v4 = __readgsdword(0x14u);
      if ( __isoc99_sscanf(a1, "%d %d", &v2, &v3) != 2 || v2 > 0xE )
        explode_bomb();
      if ( func4(v2, 0, 14) != 5 || v3 != 5 )
        explode_bomb();
      return __readgsdword(0x14u) ^ v4;
    }
    

    要求输入两个数字存放到 v2, v3 , 其中 v3 为 5, v2不能大于 0xe, 之后 v2 会传入 func4 , 并且要求 func4 的返回值为 5。这里 v2 的可能取值只有 0xe 次,这里使用 Triton 来模拟执行这段代码,然后爆破 v2 的解。我们的目标是让 func4 的返回值为 5 , 所以只需要在调用 func4 函数前开始模拟执行即可。

    调用 func4 的汇编代码如下

    .text:08048CED                 push    0Eh
    .text:08048CEF                 push    0
    .text:08048CF1                 push    [ebp+var_14]  # var_14 --> -14
    .text:08048CF4                 call    func4
    .text:08048CF9                 add     esp, 10h
    .text:08048CFC                 cmp     eax, 5
    

    v2 保存在 ebp-14 的位置,在爆破的过程中不断的重新设置 v2 (ebp-14 ) 即可。

    具体代码如下

    # -*- coding: utf-8 -*-
    from __future__ import print_function
    from triton import ARCH, TritonContext, Instruction, MODE, MemoryAccess, CPUSIZE
    from triton import *
    import os
    import sys
    
    
    EBP_ADDR = 0x100000
    # 存放参数的地址
    ARG_ADDR = 0x200000
    
    Triton = TritonContext()
    Triton.setArchitecture(ARCH.X86)
    
    def init_machine():
        Triton.concretizeAllMemory()
        Triton.concretizeAllRegister()
        Triton.clearPathConstraints()
        Triton.setConcreteRegisterValue(Triton.registers.ebp, EBP_ADDR)
    
        # 设置栈
        Triton.setConcreteRegisterValue(Triton.registers.ebp, EBP_ADDR)
        Triton.setConcreteRegisterValue(Triton.registers.esp, EBP_ADDR -  0x2000)
    
        for i in range(2):
            Triton.setConcreteMemoryValue(MemoryAccess(EBP_ADDR - 0x14 + i * 4, CPUSIZE.DWORD), 5)
    
    # 加载 elf 文件到内存
    def loadBinary(path):
        import lief
        binary = lief.parse(path)
        phdrs = binary.segments
        for phdr in phdrs:
            size = phdr.physical_size
            vaddr = phdr.virtual_address
            print('[+] Loading 0x%06x - 0x%06x' % (vaddr, vaddr+size))
            Triton.setConcreteMemoryAreaValue(vaddr, phdr.content)
        return
    
    def crack():
        i = 1
        Triton.setConcreteMemoryValue(MemoryAccess(EBP_ADDR - 0x14, CPUSIZE.DWORD), i)
        pc = 0x8048CED
        while pc:
    
            # x86 指令集的字节码的最大长度为 15
            opcode = Triton.getConcreteMemoryAreaValue(pc, 16)
            instruction = Instruction()
            instruction.setOpcode(opcode)
            instruction.setAddress(pc)
            Triton.processing(instruction)
    
            if instruction.getAddress() == 0x08048D01:
                print("solve!  answer: %d" %(i))
                break
    
    
            if instruction.getAddress() == 0x8048D07:
                pc = 0x8048CED
                i += 1
                # 重置运行时 
                init_machine()
                # 再次设置参数
                Triton.setConcreteMemoryValue(MemoryAccess(EBP_ADDR - 0x14, CPUSIZE.DWORD), i)
                continue
            pc = Triton.getConcreteRegisterValue(Triton.registers.eip)
        print('[+] Emulation done.')    
    
    
    if __name__ == '__main__':
        init_machine()
        loadBinary(os.path.join(os.path.dirname(__file__), 'bomb'))
        crack()
        sys.exit(0)
    

    一些 api 的解释

    Triton.setConcreteRegisterValue(Triton.registers.ebp, EBP_ADDR) 
    设置具体的寄存器值,设置 ebp 为 EBP_ADDR
    
    Triton.setConcreteMemoryValue(MemoryAccess(EBP_ADDR - 0x14, CPUSIZE.DWORD), i)
    设置具体的内存值,第一个参数是一个 MemoryAccess 对象,表示一个内存范围,实例化的时候会给出内存的地址和内存的长度, 第二个参数是需要设置的值,设置值的时候会根据架构的情况按大小端设置,比如 x86 就会以小端的方式设置内存值。 这里就是往 EBP_ADDR - 0x14 的位置写入 DWORD (4 字节) 的数据,数据的内容为 i , 按照小端的方式存放、
    
    Triton.getConcreteMemoryAreaValue(pc, 16)
    获取内存数据,第一个参数是内存的地址,第二个是需要获取的内存数据的长度。这里表示从 pc 出,取出 16 字节的数据。
    
    instruction.getAddress() 
    获取指令执行的地址
    
    
    Triton.getConcreteRegisterValue(Triton.registers.eip)
    这里可以获取下一条指令的地址,在 Triton 处理完一条指令后会更新 eip 的值为下一条指令的起始地址
    

    程序的流程如下:

    • 首先 init_machine 的作用就是初始化 TritonContext ,同时设置ebpesp 的值,伪造一个栈。因为程序一开始和每次爆破都要保证 TritonContext 的一致性。
    • 然后使用 loadBinary 函数把 bomb 二进制文件加载进内存,加载使用了 lief 模块。
    • 之后调用 crack 函数开始暴力破解的过程。crack函数的主要流程是在 栈上设置 v2 的值 ,然后从 0x8048CED 开始执行,当返回值不是 5 时(此时会执行到 0x8048D07)初始化 TritonContext 同时设置栈里面的参数,修改 pc 回到 0x8048CED 继续爆破,直到求出结果(此时会执行到 0x08048D01)为止。

    运行输出如下

    hac425@ubuntu:~/pin-2.14-71313-gcc.4.4.7-linux/source/tools/Triton$ /usr/bin/python /home/hac425/pin-2.14-71313-gcc.4.4.7-linux/source/tools/Triton/src/examples/python/ctf-writeups/bomb/p4.py
    [+] Loading 0x8048034 - 0x8048154
    [+] Loading 0x8048154 - 0x8048167
    [+] Loading 0x8048000 - 0x804a998
    [+] Loading 0x804bf08 - 0x804c3a0
    [+] Loading 0x804bf14 - 0x804bffc
    [+] Loading 0x8048168 - 0x80481ac
    [+] Loading 0x804a3f4 - 0x804a4f8
    [+] Loading 0x000000 - 0x000000
    [+] Loading 0x804bf08 - 0x804c000
    solve!  answer: 10
    [+] Emulation done.
    

    求出解是 10 .

    污点分析

    污点分析通过标记污点源,然后通过在执行指令时进行污点传播,来最终数据的走向。本节以 crackme_xor 二进制程序为例来介绍污点分析的使用。

    程序的主要功能是把命令行参数传给 check 函数去校验, 函数的代码如下:

    signed __int64 __fastcall check(__int64 a1)
    {
      signed int i; // [rsp+14h] [rbp-4h]
    
      for ( i = 0; i <= 4; ++i )
      {
        if ( ((*(i + a1) - 1) ^ 0x55) != serial[i] )
          return 1LL;
      }
      return 0LL;
    }
    

    通过分析代码,输入的字符串的长度为 5 个字节,然后会对输入进行一些简单的变化然后和 serial 数组进行比较。下面我们使用 Triton 的污点分析来看看追踪程序对输入内存的访问情况。

    脚本如下:

    #!/usr/bin/env python2
    # -*- coding: utf-8 -*-
    from __future__ import print_function
    from triton import TritonContext, ARCH, MODE, AST_REPRESENTATION, Instruction, OPERAND
    from triton import *
    import sys
    import os
    import lief
    
    # 加载 elf 文件到内存
    INPUT_ADDR = 0x100000
    
    RBP_ADDR = 0x600000
    RSP_ADDR = RBP_ADDR - 0x200000
    def loadBinary(ctx, path):
        binary = lief.parse(path)
        phdrs = binary.segments
        for phdr in phdrs:
            size = phdr.physical_size
            vaddr = phdr.virtual_address
            print('[+] Loading 0x%06x - 0x%06x' % (vaddr, vaddr+size))
            ctx.setConcreteMemoryAreaValue(vaddr, phdr.content)
        return
    
    if __name__ == '__main__':
        ctx = TritonContext()
        ctx.setArchitecture(ARCH.X86_64)
        ctx.enableMode(MODE.ALIGNED_MEMORY, True)
    
        loadBinary(ctx, os.path.join(os.path.dirname(__file__), 'crackme_xor'))
        ctx.setAstRepresentationMode(AST_REPRESENTATION.PYTHON)
    
        pc = 0x0400556
        # 参数是输入字符串的指针
        ctx.setConcreteRegisterValue(ctx.registers.rdi, INPUT_ADDR)
    
        # 设置栈的值
        ctx.setConcreteRegisterValue(ctx.registers.rsp, RSP_ADDR)
        ctx.setConcreteRegisterValue(ctx.registers.rbp, RBP_ADDR)
    
        # ctx.taintRegister(ctx.registers.rdi)
        
        input = "elitex00"
        ctx.setConcreteMemoryAreaValue(INPUT_ADDR, input)
        ctx.taintMemory(MemoryAccess(INPUT_ADDR, 8))
    
        while pc != 0x4005B1:
            # Build an instruction
            inst = Instruction()
            opcode = ctx.getConcreteMemoryAreaValue(pc, 16)
            inst.setOpcode(opcode)
            inst.setAddress(pc)
    
            # 执行指令
            ctx.processing(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()))
    
            # 取出下一条指令的地址
            pc = ctx.getConcreteRegisterValue(ctx.registers.rip)
        sys.exit(0)
    

    这个脚本的作用是打印对参数字符串所在内存的访问情况, 脚本流程如下:

    • 程序首先构造好栈帧, 然后把输入字符串存放到 INPUT_ADDR 内存处, 同时设置RDIINPUT_ADDR 因为在 x64 下第一个参数通过 RDI 寄存器设置。
    • 之后把输入字符串所在的内存区域转换为污点源,之后随着指令的执行会执行污点传播过程。
    • 通过 inst.isTainted() 可以判断该指令的操作数中是否包含污点值,如果指令包含污点值,就把对污点内存的访问情况给打印出来。

    脚本的输出如下:

    hac425@ubuntu:~/pin-2.14-71313-gcc.4.4.7-linux/source/tools/Triton$ /usr/bin/python /home/hac425/pin-2.14-71313-gcc.4.4.7-linux/source/tools/Triton/src/examples/python/taint/taint.py
    [+] Loading 0x400040 - 0x400270
    [+] Loading 0x400270 - 0x40028c
    [+] Loading 0x400000 - 0x4007f4
    [+] Loading 0x600e10 - 0x601048
    [+] Loading 0x600e28 - 0x600ff8
    [+] Loading 0x40028c - 0x4002ac
    [+] Loading 0x4006a4 - 0x4006e0
    [+] Loading 0x000000 - 0x000000
    [+] Loading 0x600e10 - 0x601000
    [+] Loading 0x000000 - 0x000000
    read:0x00100000, size:1
    read:0x00100001, size:1
    read:0x00100002, size:1
    read:0x00100003, size:1
    read:0x00100004, size:1
    

    可以看到成功监控了对输入字符串(0x00100000 开始的 5 个字节)的访问。

    符号执行

    符号执行首先要设置符号量,然后随着指令的执行在 Triton 可以维持符号量的传播,然后我们在一些特点的分支出设置约束条件,进而通过符号执行来求出程序的解。

    下面还是以 crackme_xor 为例介绍一下符号执行的使用。

    通过分析可知,在对输入字符串的每个字符进行简单变化后,会把变化后的字符与 serial 里面的相应字符进行比较,然后在 0x400599 会根据比较的结果决定是否需要跳转。

    如果输入的字符串正确的话,程序会走图中染色的分支,所以我们需要在执行完 0x400597指令设置约束条件为 ZF 寄存器为 1 ,这样就可以跳转到染色的分支进而可以求出程序的解。最终的脚本如下:

    #!/usr/bin/env python2
    # -*- coding: utf-8 -*-
    
    from __future__ import print_function
    from triton import TritonContext, ARCH, MODE, AST_REPRESENTATION, Instruction, OPERAND
    from triton import MemoryAccess,CPUSIZE
    import sys
    import os
    import lief
    
    # 加载 elf 文件到内存
    
    
    INPUT_ADDR = 0x100000
    
    RBP_ADDR = 0x600000
    RSP_ADDR = RBP_ADDR - 0x200000
    
    def loadBinary(ctx, path):
        binary = lief.parse(path)
        phdrs = binary.segments
        for phdr in phdrs:
            size = phdr.physical_size
            vaddr = phdr.virtual_address
            print('[+] Loading 0x%06x - 0x%06x' % (vaddr, vaddr+size))
            ctx.setConcreteMemoryAreaValue(vaddr, phdr.content)
        return
    
    if __name__ == '__main__':
        ctx = TritonContext()
        ctx.setArchitecture(ARCH.X86_64)
        ctx.enableMode(MODE.ALIGNED_MEMORY, True)
    
        loadBinary(ctx, os.path.join(os.path.dirname(__file__), 'crackme_xor'))
        ctx.setAstRepresentationMode(AST_REPRESENTATION.PYTHON)
    
        pc = 0x0400556
    
        # 参数是输入字符串的指针
        ctx.setConcreteRegisterValue(ctx.registers.rdi, INPUT_ADDR)
    
        # 设置栈的值
        ctx.setConcreteRegisterValue(ctx.registers.rsp, RSP_ADDR)
        ctx.setConcreteRegisterValue(ctx.registers.rbp, RBP_ADDR)
    
    
        for index in range(5):
                ctx.setConcreteMemoryValue(MemoryAccess(INPUT_ADDR + index, CPUSIZE.BYTE), ord('b'))
                ctx.convertMemoryToSymbolicVariable(MemoryAccess(INPUT_ADDR + index, CPUSIZE.BYTE))
    
        ast = ctx.getAstContext()
        while pc:
            # Build an instruction
            inst = Instruction()
            opcode = ctx.getConcreteMemoryAreaValue(pc, 16)
            inst.setOpcode(opcode)
            inst.setAddress(pc)
    
            # 执行指令
            ctx.processing(inst)
    
            if inst.getAddress() == 0x400597:
                zf   = ctx.getRegisterAst(ctx.registers.zf)
                cstr  = ast.land([
                            ctx.getPathConstraintsAst(),
                            zf == 1
                        ])
                # 为暂时求出的解具体化
                model = ctx.getModel(cstr)
                for k, v in list(model.items()):
                    value = v.getValue()
                    ctx.setConcreteVariableValue(ctx.getSymbolicVariableFromId(k), value)
    
            if inst.getAddress() == 0x4005B1:
                model = ctx.getModel(ctx.getPathConstraintsAst())
                answer = ""
                for k, v in list(model.items()):
                    value = v.getValue()
                    answer += chr(value)
                print("answer: {}".format(answer))
                break        
    
            # 取出下一条指令的地址
            pc = ctx.getConcreteRegisterValue(ctx.registers.rip)
    
        sys.exit(0)
    
    • 首先使用 convertMemoryToSymbolicVariable 将字符串所在的内存转换为符号量
    • 然后在运行到 0x400599 后, 使用 ast.land 把之前搜集到的约束和走染色分支需要的约束集合起来,然后求出每个字符对应的解,并设置符号量为具体的解。
    • 然后在 0x4005B1 说明输入的所有字符都是正确的,此时打印所有的解即可。

    运行结果如下:

    hac425@ubuntu:~/pin-2.14-71313-gcc.4.4.7-linux/source/tools/Triton$ /usr/bin/python /home/hac425/pin-2.14-71313-gcc.4.4.7-linux/source/tools/Triton/src/examples/python/taint/sym.py
    [+] Loading 0x400040 - 0x400270
    [+] Loading 0x400270 - 0x40028c
    [+] Loading 0x400000 - 0x4007f4
    [+] Loading 0x600e10 - 0x601048
    [+] Loading 0x600e28 - 0x600ff8
    [+] Loading 0x40028c - 0x4002ac
    [+] Loading 0x4006a4 - 0x4006e0
    [+] Loading 0x000000 - 0x000000
    [+] Loading 0x600e10 - 0x601000
    [+] Loading 0x000000 - 0x000000
    answer: elite
    

    参考

    https://triton.quarkslab.com/documentation/doxygen/#install_sec

    https://github.com/JonathanSalwan/Triton/tree/master/src/examples/python

  • 相关阅读:
    python 爬虫应用——校园网搜索引擎(crawler application——Campus web search engine part-two)(下)
    python 列表寻找满足某个条件的开始索引和结束索引(python find the starting and ending indices of values that satisfy a certain condition in a list)
    python 爬虫应用——校园网搜索引擎(crawler application——Campus web search engine part-one)(上)
    python 根据空格切割英文单词(python split string according to space)
    vscode 配置 typeScript+nodejs 和 react+typeScript 开发环境
    vscode electron error (Refused to execute inline event handler because it violates the following Content Security Policy directive ... 和 Uncaught ReferenceError: process is not defined)
    vscode 运行和调试 javascript 代码 (vscode run and debug javascript code)
    Matplotlib:绘图和可视化
    Pandas
    Numpy
  • 原文地址:https://www.cnblogs.com/hac425/p/10831600.html
Copyright © 2020-2023  润新知