Triton学习笔记(一)

因为毕设的原因要学习Pin的使用,但是实在不想写C/C++,所以找到Triton,可以使用Python调用pin,第一篇准备介绍下Triton的一些简单的用法

Triton: https://github.com/JonathanSalwan/Triton

安装没啥问题,需要装z3和pin,按照官方文档可以顺利的安装成功

正常情况下是需要导入两个库:

1
2
from triton import *
from pintool import *

其中, pintool导入的时候会报错提示没有这个模块,这是因为需要使用triton来运行python脚本

也就是说: python xxx.py bin是找不到pintool模块的,需要triton xxx.py bin

来个简单的示例:

1
2
3
4
5
6
7
8
// test1.c
int main(void)
{
int i;
i = 0;
return i;
}
// gcc test1.c -o test1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# test.py
#! /usr/bin/env python
# -*- coding: utf-8 -*-
from triton import *
from pintool import *
def before(ins):
print ins
if __name__ == '__main__':
setArchitecture(ARCH.X86_64)
startAnalysisFromSymbol("main")
insertCall(before, INSERT_POINT.BEFORE)
runProgram()

test1main函数反汇编代码是:

1
2
3
4
5
6
7
8
9
10
11
$ objdump -d /tmp/test1 -M inte
...
080483db <main>:
80483db: 55 push ebp
80483dc: 89 e5 mov ebp,esp
80483de: 83 ec 10 sub esp,0x10
80483e1: c7 45 fc 00 00 00 00 mov DWORD PTR [ebp-0x4],0x0
80483e8: 8b 45 fc mov eax,DWORD PTR [ebp-0x4]
80483eb: c9 leave
80483ec: c3 ret
...

测试脚本的运行结果是:

1
2
3
4
5
$ build/triton /tmp/test1.py /tmp/test1
0x4004d6: push rbp
0x4004d7: mov rbp, rsp
0x4004da: mov dword ptr [rbp - 4], 0
0x4004e1: mov eax, dword ptr [rbp - 4]

这里来解释下脚本:

  • setArchitecture(ARCH.X86_64)
    初始化架构,讲道理这里应该是设置输入的二进制的架构,是32还是64位,但是如果我改成ARCH.x86却会报错:
    1
    E: Unable to load pin/source/tools/Triton/build/src/tracer/pin/libpintool.so. Check the architecture type.

查看发现libpintool.so是64位的so库,所以不能加载是正常的,所以是你编译的时候是啥架构,这里只能填啥架构(感觉可以去提个issue)

  • startAnalysisFromSymbol(“main”)
    这句就简单了,从符号表main开始分析,说简单点就是,只分析哪个函数
    除此之外还有
    1
    2
    3
    void startAnalysisFromAddress(integer addr) //从某个地址开始分析
    void startAnalysisFromEntry(void) //从_start函数开始分析
    void startAnalysisFromOffset(integer offset) //这个没试过,从bin的某个偏移地址开始分析

有开始的当然还有结束的

1
2
stopAnalysisFromAddress(integer addr)
stopAnalysisFromOffset(integer offset)

  • insertCall(before, INSERT_POINT.BEFORE)
    该函数的定义为: void insertCall(function, INSERT_POINT type)

作用就是,在啥情况下插入回调函数,我写的这句的含义就是在每句指令执行之前先跑一遍回调函数before,而每种类型传给回调函数的参数也不同,比如INSERT_POINT.BEFORE传入的就是将要执行的指令类,没错,是一个类,而不是字符串,这个类都有啥属性之后会研究

除此之外的类型还有:

1
2
3
4
5
6
7
8
9
INSERT_POINT.AFTER //每句指令结束之后,返回的也是执行结束的那个指令类
INSERT_POINT.BEFORE_SYMPROC //跟BEFORE基本一样,发现的唯一的不同就是这个类型可以跟踪污点传播
INSERT_POINT.FINI //程序结束之后,好像是不传入参数
INSERT_POINT.ROUTINE_ENTRY //进入一个函数时,返回的参数是线程id
INSERT_POINT.ROUTINE_EXIT //退出一个函数时,返回参数同上
INSERT_POINT.IMAGE_LOAD //暂时没用到还不知道,官方文档说的还不理解
INSERT_POINT.SIGNALS //当接收到信号时,返回值是线程id和信号id,暂时也还没用到过这个类型
INSERT_POINT.SYSCALL_ENTRY //这个好理解,系统调用时,返回参数是线程id加上一个std类,可以获取到标准输出,输入,错误等信息
INSERT_POINT.SYSCALL_EXIT //系统调用结束时,同上

  • runProgram()
    当所有要设置的都设置好后,使用该函数运行二进制程序

  • before()
    这个回调函数的作用就是输出传入的指令参数

看看官方文档和例子其实还挺简单的…但是功能挺屌的

比如我可以使用startAnalysisFromEntry()来查看一个程序从头到尾执行的指令,对学习一个二进制的加载和结束过程很有帮助,比如我可以得知一个程序其实是从_start函数开始跑的,期间还会跳到libc库中,然后还要跳到__libc_csu_init函数中,后面才轮到main函数,然后程序也不是return 0之后就结束了,还要运行一通其他玩意.

但是同样,这里也有一个坑点,上面我写的代码少所以看不出来,如果改一改调用个malloc或者其他系统函数,或者:

1
2
3
4
5
6
7
8
9
10
int main(void)
{
int i;
i = 100000;
while(i)
{
i--;
}
return i;
}

然后使用htop查看系统内存情况,你会发现内存会一直增长,直到程序停止,如果内存小的话直接GG, 一般是Pin会自动杀死进程,报一个out of memory的错误,或者直接死机,我给虚拟机配了6G的内存,不够跑完一个malloc函数的

解决办法有两个:

1
2
3
void setupImageBlacklist([string, ...])
// eg:
// setupImageBlacklist(["libc", "ld-linux"])

很简单的第一个思路,不跟进libc库中不就好了

但是解决不了我上面写的这种情况,你写的指令要跑几万条呢?

1
2
void enableMode(MODE mode, bool flag)
// enableMode(MODE.ONLY_ON_SYMBOLIZED, True)

使用这个函数设置一个ONLY_ON_SYMBOLIZED模式,内存就不会跟着跟踪指令而无限增长了,有啥含义…还没明白,有待研究….

第一篇就讲讲简单的使用….

文章目录