首页
社区
课程
招聘
[原创]angr符号变量转LLVM IR
发表于: 2022-3-14 10:02 18023

[原创]angr符号变量转LLVM IR

2022-3-14 10:02
18023

之前看到过国外的一篇文章。
关于如何处理虚拟机的,并给出了针对tigress虚拟机的攻击方法。
具体是这样处理的:

后面那作者还研究出来了攻击VMP的方法,具体可以参考。
https://github.com/JonathanSalwan/VMProtect-devirtualization

这个方法确实很巧妙,但是使用的triton,然而其python接口基本没有说明。属实劝退。

观察下具体的实现,发现是将triton处理出的符号变量的expr进行遍历,然后将某些语义节点转化为对应的llvm ir。从而实现了将expr lift到llvm的ir。然后就可以享用llvm自带的丰富的优化pass了。
由于triton的学习成本较大(x,所以我们可以采用更加简单的符号执行工具angr。同样的angr也使用树结构来描述一个符号变量。然后将其转化成llvm的ir。

众所周知,angr的约束求解框架前端是claripy,执行出来的符号变量被claripy表示出来,然后交给后端的约束求解引擎,例如z3。
claripy的表示中有很多种操作,这里列出两种常见又难以处理的操作。

由于使用python编写,所以考虑使用llvmlite来构建llvm的ir。安装过程略。
对于一些其他的算数指令,可以直接将其映射到llvm的某个指令上去,而对于上面两个稍微复杂的操作,考虑直接用位运算来表示。
这里是一张图,表示了claripy下的某个符号变量

链接a和b两个变量
concat(a,b)
c = (zext(a, size(a) + size(b)) << size(b)) | (zext(b, size(a) + size(b)))
将两个符号变量扩展到两个符号变量位数之和,然后将前面的变量左移后面变量的位数,然后或上后面的那个变量即可实现两个符号变量的链接。该运算在angr中表示为a..b

提取a变量的第l位到第h位。
extract(a,l,h)
y = (a & bitmask) >> (h - l + 1)
先将变量与上一个bitmask,可以与出对应的数据,然后一位,让数据从低0位开始,最后trunc截取一下长度。该运算在angr中表示为a[l:h]

这样就可以做到将所有的操作用llvm的ir表示出来,接下来要做的就是遍历获取的expr的ast,然后边遍历边构造llvm的ir。出来又在run一下llvm自带的优化。

直接上代码:
解释一下,lifter是一个visitor,遍历ast,然后在某个节点应用对应的处理函数,cur则代表当前节点对应的值(对应LLVM的Value概念)。所以先visit子节点,调用函数结束的时候cur就是子节点对应的value,然后可以继续构造当前节点的value。
当节点是BVS或者是BVV时停止向下访问,然后分别处理其他未知变量和常量。未知变量设定为llvm的函数参数,常量可以直接提取出来,转化成llvm ir中的常量

目前并未进行大规模测试,可能存在bug。小测一波
这是被测试的函数

测试结果

 
 
 
 
 
 
import angr
import claripy
from llvmlite import ir
import llvmlite.binding as llvm
unop_llvm = {
    '__invert__':ir.IRBuilder.not_,
    '__neg__':ir.IRBuilder.neg
}
binop_llvm = {
    '__add__':ir.IRBuilder.add,
    '__floordiv__':ir.IRBuilder.udiv,
    'SDiv':ir.IRBuilder.sdiv,
    '__mul__':ir.IRBuilder.mul,
    '__sub__':ir.IRBuilder.sub,
    '__mod__':ir.IRBuilder.urem,
    'SMod':ir.IRBuilder.srem,
    '__and__':ir.IRBuilder.and_,
    '__or__':ir.IRBuilder.or_,
    '__xor__':ir.IRBuilder.xor,
    '__lshift__':ir.IRBuilder.shl,
    '__rshift__':ir.IRBuilder.ashr,
    'LShR':ir.IRBuilder.lshr
}
signed_op = ['SDiv','SMod']
supported_op = ['Concat','ZeroExt','SignExt','Extract','RotateLeft','RotateRight'] + list(unop_llvm.keys()) + list(binop_llvm.keys())
supported_type = ['BVV','BVS']
class lifter:
    def __init__(self):
        self.expr = None
        self.cur = None
        self.count = 0
        self.value_array = []
        self.builder = None
        self.func = None
        self.args = {}  
        self.node_count = 0
 
    def new_value(self, value, expr):
        assert value.type.width == expr.size()
        n = self.count
        self.value_array.append(value)
        self.count += 1
        return n
 
    def get_value(self, idx):
        return self.value_array[idx]
 
    def _visit_value(self, expr):
        if expr.op == 'BVV':
            self.cur = self.new_value(ir.Constant(ir.IntType(expr.size()), expr.args[0]), expr)
        else:
            self.cur = self.new_value(self.func.args[self.args[expr]], expr)
        pass
 
    def _visit_binop(self, expr):
        left = None
        for a in expr.args:
            self._visit_ast(a)
            if left is None:
                left = self.cur
            else:
                v = self.cur
                lhs = self.get_value(left)
                rhs = self.get_value(v)
                self.cur = self.new_value(binop_llvm[expr.op](self.builder, lhs, rhs, name = "node" + str(self.node_count)), expr)
                left = self.cur
                self.node_count += 1
        pass
 
    def _visit_unop(self, expr):
        self._visit_ast(expr.args[0])
        v0 = self.cur
        self.cur = self.new_value(unop_llvm[expr.op](self.builder, self.get_value(v0), name = "node" + str(self.node_count)), expr)
        self.node_count += 1
        pass
 
    def _visit_concat(self, expr):
        left = None
        for a in expr.args:
            self._visit_ast(a)
            if left is None:
                left = self.cur
            else:
                v = self.cur
                lens = self.get_value(left).type.width + self.get_value(v).type.width
                val0 = self.builder.zext(self.get_value(left), ir.IntType(lens))
                val1 = self.builder.zext(self.get_value(v), ir.IntType(lens))
                self.cur = self.new_value(self.builder.or_(self.builder.shl(val0, ir.Constant(ir.IntType(lens), self.get_value(v).type.width)), val1, name = "node" + str(self.node_count)), expr)
                left = self.cur
                self.node_count += 1
        pass
 
 
    def get_bit_mask(self, low, high):
        mask = 0
        for i in range(low, high + 1):
            mask += 2 ** i
        return mask
 
    def _visit_extract(self, expr):
        high = expr.args[0]
        low = expr.args[1]
        self._visit_ast(expr.args[2])
        v0 = self.cur
        val = self.get_value(v0)
        mask = self.get_bit_mask(low, high)
        self.cur = self.new_value(self.builder.trunc(self.builder.lshr(self.builder.and_(val, ir.Constant(val.type, mask)), ir.Constant(val.type, low)), ir.IntType(high - low + 1), name = "node" + str(self.node_count)), expr)
        self.node_count += 1
        pass
 
    def _visit_zeroext(self, expr):
        length = expr.args[0]
        self._visit_ast(expr.args[1])
        v0 = self.cur
        self.cur = self.new_value(self.builder.zext(self.get_value(v0), ir.IntType(length + expr.args[1].size()), name = "node" + str(self.node_count)), expr)
        self.node_count += 1
        pass
 
    def _visit_signext(self, expr):
        length = expr.args[0]
        self._visit_ast(expr.args[1])
        v0 = self.cur
        self.cur = self.new_value(self.builder.sext(self.get_value(v0), ir.IntType(length + expr.args[1].size()), name = "node" + str(self.node_count)), expr)
        self.node_count += 1
        pass
 
    def _visit_rotateleft(self,expr):
        bit = expr.args[1]
        self._visit_ast(expr.args[0])
        v0 = self.cur
        val = self.get_value(v0)
        width = val.type.width
        self.cur = self.new_value(self.builder.or_(self.builder.lshr(val, ir.Constant(val.type, width - bit)), self.builder.shl(val.type, ir.Constant(val.type, bit)), name = "node" + str(self.node_count)), expr)
        self.node_count += 1
        pass
 
    def _visit_rotateright(self,expr):
        bit = expr.args[1]
        self._visit_ast(expr.args[0])
        v0 = self.cur
        val = self.get_value(v0)
        width = val.type.width
        self.cur = self.new_value(self.builder.or_(self.builder.shl(val, ir.Constant(val.type, width - bit)), self.builder.lshr(val.type, ir.Constant(val.type, bit)), name = "node" + str(self.node_count)), expr)
        self.node_count += 1
        pass
 
    def _visit_op(self, expr):
        if expr.op in binop_llvm.keys():
            self._visit_binop(expr)
        elif expr.op in unop_llvm.keys():
            self._visit_unop(expr)
        else:
            func = getattr(self, '_visit_' + expr.op.lower())
            func(expr)
 
    def _visit_ast(self, expr):
        assert isinstance(expr, claripy.ast.base.Base)
        if expr.op in supported_op:
            self._visit_op(expr)
        elif expr.op in supported_type:
            self._visit_value(expr)
        else:
            raise Exception("unsupported operation!")
 
    def lift(self, expr):
        self.expr = expr
        self.count = 0
        self.value_array = []
        self.args = {}
        c = 0
        for i in expr.leaf_asts():
            if i.op == 'BVS':
                self.args[i] = c
                c += 1
        items = sorted(self.args.items(),key=lambda x:x[1])
        print("Function arguments: ")
        print(items)
        type_list = []
        for i in items:
            type_list.append(ir.IntType(i[0].size()))
        fnty = ir.FunctionType(ir.IntType(expr.size()), tuple(type_list))
        module = ir.Module(name=__file__)
        self.func = ir.Function(module, fnty, name="dump")
        block = self.func.append_basic_block(name="entry")
        self.builder = ir.IRBuilder(block)
        self._visit_ast(expr)
        self.builder.ret(self.get_value(self.cur))
        return str(module)
import angr
import claripy
from llvmlite import ir
import llvmlite.binding as llvm
unop_llvm = {
    '__invert__':ir.IRBuilder.not_,
    '__neg__':ir.IRBuilder.neg
}
binop_llvm = {
    '__add__':ir.IRBuilder.add,
    '__floordiv__':ir.IRBuilder.udiv,
    'SDiv':ir.IRBuilder.sdiv,
    '__mul__':ir.IRBuilder.mul,
    '__sub__':ir.IRBuilder.sub,
    '__mod__':ir.IRBuilder.urem,
    'SMod':ir.IRBuilder.srem,
    '__and__':ir.IRBuilder.and_,
    '__or__':ir.IRBuilder.or_,
    '__xor__':ir.IRBuilder.xor,
    '__lshift__':ir.IRBuilder.shl,
    '__rshift__':ir.IRBuilder.ashr,
    'LShR':ir.IRBuilder.lshr
}
signed_op = ['SDiv','SMod']
supported_op = ['Concat','ZeroExt','SignExt','Extract','RotateLeft','RotateRight'] + list(unop_llvm.keys()) + list(binop_llvm.keys())
supported_type = ['BVV','BVS']
class lifter:
    def __init__(self):
        self.expr = None
        self.cur = None
        self.count = 0
        self.value_array = []
        self.builder = None
        self.func = None
        self.args = {}  
        self.node_count = 0
 
    def new_value(self, value, expr):
        assert value.type.width == expr.size()
        n = self.count
        self.value_array.append(value)
        self.count += 1
        return n
 
    def get_value(self, idx):
        return self.value_array[idx]
 
    def _visit_value(self, expr):
        if expr.op == 'BVV':
            self.cur = self.new_value(ir.Constant(ir.IntType(expr.size()), expr.args[0]), expr)
        else:
            self.cur = self.new_value(self.func.args[self.args[expr]], expr)
        pass
 
    def _visit_binop(self, expr):
        left = None
        for a in expr.args:
            self._visit_ast(a)
            if left is None:
                left = self.cur
            else:
                v = self.cur
                lhs = self.get_value(left)
                rhs = self.get_value(v)
                self.cur = self.new_value(binop_llvm[expr.op](self.builder, lhs, rhs, name = "node" + str(self.node_count)), expr)
                left = self.cur
                self.node_count += 1
        pass
 
    def _visit_unop(self, expr):
        self._visit_ast(expr.args[0])
        v0 = self.cur
        self.cur = self.new_value(unop_llvm[expr.op](self.builder, self.get_value(v0), name = "node" + str(self.node_count)), expr)
        self.node_count += 1
        pass
 
    def _visit_concat(self, expr):
        left = None
        for a in expr.args:
            self._visit_ast(a)
            if left is None:
                left = self.cur
            else:
                v = self.cur
                lens = self.get_value(left).type.width + self.get_value(v).type.width
                val0 = self.builder.zext(self.get_value(left), ir.IntType(lens))
                val1 = self.builder.zext(self.get_value(v), ir.IntType(lens))
                self.cur = self.new_value(self.builder.or_(self.builder.shl(val0, ir.Constant(ir.IntType(lens), self.get_value(v).type.width)), val1, name = "node" + str(self.node_count)), expr)
                left = self.cur
                self.node_count += 1
        pass
 
 

[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课

最后于 2022-3-14 10:08 被R1mao编辑 ,原因:
收藏
免费 12
支持
分享
打赏 + 50.00雪花
打赏次数 1 雪花 + 50.00
 
赞赏  Editor   +50.00 2022/05/12 恭喜您获得“雪花”奖励,安全圈有你而精彩!
最新回复 (5)
雪    币: 8452
活跃值: (5046)
能力值: ( LV4,RANK:45 )
在线值:
发帖
回帖
粉丝
2
昂爹来了
2022-3-14 21:41
0
雪    币: 14303
活跃值: (10786)
能力值: ( LV12,RANK:360 )
在线值:
发帖
回帖
粉丝
3
昂爹666
2022-3-14 21:56
0
雪    币: 18
活跃值: (1006)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
4
看不懂,先mark。
2022-3-21 09:41
0
雪    币: 26
活跃值: (1656)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
5
大牛,这个lifter代码是怎样测试帖子里的二进制代码的?命令行是什么?二进制原件在哪?菜鸟不懂,虚心请教。
2023-3-12 11:02
0
雪    币: 3115
活跃值: (3298)
能力值: ( LV9,RANK:150 )
在线值:
发帖
回帖
粉丝
6
imliuxin 大牛,这个lifter代码是怎样测试帖子里的二进制代码的?命令行是什么?二进制原件在哪?菜鸟不懂,虚心请教。
这个东西没有测试过 可能有bug 直接new lifter().lift(expr),其中expr是angr的Claripy表达式的表示。
2023-3-17 22:32
0
游客
登录 | 注册 方可回帖
返回
//