Triton 学习

2019-05-13 约 3457 字 预计阅读 7 分钟

声明:本文 【Triton 学习】 由作者 hackedbylh 于 2019-05-13 08:20:00 首发 先知社区 曾经 浏览数 132 次

感谢 hackedbylh 的辛苦付出!

介绍

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"\x40\xf6\xee"),      # imul   sil
    (0x40003, b"\x66\xf7\xe9"),      # imul   cx
    (0x40006, b"\x48\xf7\xe9"),      # imul   rcx
    (0x40009, b"\x6b\xc9\x01"),      # imul   ecx,ecx,0x1
    (0x4000c, b"\x0f\xaf\xca"),      # imul   ecx,edx
    (0x4000f, b"\x48\x6b\xd1\x04"),  # imul   rdx,rcx,0x4
    (0x40013, b"\xC6\x00\x01"),      # mov    BYTE PTR [rax],0x1
    (0x40016, b"\x48\x8B\x10"),      # mov    rdx,QWORD PTR [rax]
    (0x40019, b"\xFF\xD0"),          # call   rax
    (0x4001b, b"\xc3"),              # ret
    (0x4001c, b"\x80\x00\x01"),      # add    BYTE PTR [rax],0x1
    (0x4001f, b"\x64\x48\x8B\x03"),  # 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 = "elite\x00"
    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

关键词:[‘安全技术’, ‘二进制安全’]


author

旭达网络

旭达网络技术博客,曾记录各种技术问题,一贴搞定.
本文采用知识共享署名 4.0 国际许可协议进行许可。

We notice you're using an adblocker. If you like our webite please keep us running by whitelisting this site in your ad blocker. We’re serving quality, related ads only. Thank you!

I've whitelisted your website.

Not now