<menu id="guoca"></menu>
<nav id="guoca"></nav><xmp id="guoca">
  • <xmp id="guoca">
  • <nav id="guoca"><code id="guoca"></code></nav>
  • <nav id="guoca"><code id="guoca"></code></nav>

    angr符號變量轉LLVM IR

    VSole2022-05-12 16:13:42

    前言

    之前看到過國外的一篇文章。關于如何處理虛擬機的,并給出了針對tigress虛擬機的攻擊方法。

    具體是這樣處理的:

    • 裝載目標文件
    • 初始化一些hook
    • 對虛擬機函數進行符號執行,獲取各種符號變量
    • 將符號變量轉化成llvm的ir
    • 使用llvm編譯優化重編譯

    后面那作者還研究出來了攻擊VMP的方法,具體可以參考。

    https://github.com/JonathanSalwan/VMProtect-devirtualization

    這個方法確實很巧妙,但是使用的triton,然而其python接口基本沒有說明。屬實勸退。

    angr?

    觀察下具體的實現,發現是將triton處理出的符號變量的expr進行遍歷,然后將某些語義節點轉化為對應的llvm ir。從而實現了將expr lift到llvm的ir。然后就可以享用llvm自帶的豐富的優化pass了。

    由于triton的學習成本較大(x,所以我們可以采用更加簡單的符號執行工具angr。同樣的angr也使用樹結構來描述一個符號變量。然后將其轉化成llvm的ir。

    claripy?

    眾所周知,angr的約束求解框架前端是claripy,執行出來的符號變量被claripy表示出來,然后交給后端的約束求解引擎,例如z3。

    claripy的表示中有很多種操作,這里列出兩種常見又難以處理的操作。

    • concat,將兩個符號變量鏈接
    • extract,提取符號變量中的某幾位。

    由于使用python編寫,所以考慮使用llvmlite來構建llvm的ir。安裝過程略。

    對于一些其他的算數指令,可以直接將其映射到llvm的某個指令上去,而對于上面兩個稍微復雜的操作,考慮直接用位運算來表示。

    這里是一張圖,表示了claripy下的某個符號變量:

    concat

    鏈接a和b兩個變量

    concat(a,b)

    c = (zext(a, size(a) + size(b)) << size(b)) | (zext(b, size(a) + size(b)))

    將兩個符號變量擴展到兩個符號變量位數之和,然后將前面的變量左移后面變量的位數,然后或上后面的那個變量即可實現兩個符號變量的鏈接。該運算在angr中表示為a..b

    extract

    提取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中的常量。

    import angrimport claripyfrom llvmlite import irimport llvmlite.binding as llvmunop_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)
    

    目前并未進行大規模測試,可能存在bug。小測一波。

    這是被測試的函數:

    測試結果:

    小結

    這里可以想出一個簡單vm的處理方法,首先我們找到虛擬機的內存和上下文,將其設置為符號變量,其他內容則初始化為常量,然后開始符號執行,然后提取并化簡上下文中的符號表達式,然后重新編譯優化即可重建虛擬機的邏輯。

    當然這個虛擬機要足夠簡單,然而虛擬機往往都有跳轉語句,符號執行在遇到跳轉的時候將變得極其爆炸。生成的表達式也可能極其復雜,llvm的優化也沒有用。所以可以考慮分而治之。

    找到虛擬機的跳轉語句的handler,然后在angr中hook,根據跳轉目的地和當前跳轉語句地址(控制流生成算法),能夠很好的將虛擬機的opcode構造控制流圖,然后針對每一個opcode的basicblock進行符號執行,將上下文全部符號化,然后評估basicblock執行完畢后的上下文的變化,如果發生了改變則可以說明該basicblock干了什么。

    然后將這些副作用(表達式)收集起來,則可以代表這個basicblock到底干了什么,最后處理整個控制流圖,即可實現程序的重構。這種自動化的虛擬機代碼重構,處理能力還比較弱,也只是筆者的一個想法,以后準備細細研究一波。

    irllvm
    本作品采用《CC 協議》,轉載必須注明作者和本文鏈接
    之前看chenx6大佬的博客學習了一下編寫基礎的LLVM Pass,但是那個有很明顯的問題是,作者為了處理Function內部重復引用的多次解密的問題,特判了引用次數,如果存在多處對global string的引用是無法進行混淆的。但是實際的編程中很難不會引用多處字符串,所以那個只能混淆簡單代碼。之后學習了一下pluto-obfuscator項目,里面有一份GlobalEncryption.cpp,借此機會學習一下,順便寫一份New PassManager版本的。runOnModule首先獲取Module的LLVMContext,獲取所有的全局變量,添加到GVs中。
    angr符號變量轉LLVM IR
    2022-05-12 16:13:42
    關于如何處理虛擬機的,并給出了針對tigress虛擬機的攻擊方法。從而實現了將expr lift到llvmir。claripy的表示中有很多種操作,這里列出兩種常見又難以處理的操作。
    源碼分析1、LLVM編譯器簡介LLVM 命名最早源自于底層虛擬機的縮寫,由于命名帶來的混亂,LLVM就是該項目的全稱。LLVM 核心庫提供了與編譯器相關的支持,可以作為多種語言編譯器的后臺來使用。自那時以來,已經成長為LLVM的主干項目,由不同的子項目組成,其中許多是正在生產中使用的各種 商業和開源的項目,以及被廣泛用于學術研究。
    LLVM PASS類pwn題入門
    2022-06-30 16:46:54
    同時IR也是一個編譯器組件接口。LLVMIR有三種表示形式:內存格式,只保存在內存中,人無法看到。不可讀的IR,被稱作bitcode,文件后綴為bc。可讀的IR,介于高級語言和匯編代碼之間,文件后綴為ll。而LLVM PASS就是去處理IR文件,通過opt利用寫好的so庫優化已有的IR,形成新的IR。而LLVM PASS類的pwn就是利用這一過程中可能會出現的漏洞。
    STL容器逆向與實戰
    2023-02-08 09:53:04
    當然可能還存在許許多多的STL容器,但是大體的分析思路是類似的。
    Intel SGX采用基于應鍵的內存加密技術來保護獨立應用程序邏輯和敏感數據。要使用這種基于硬件的安全機制,需要嚴格的內存使用編程模型,開發者需要應用謹慎的編程實踐來確保程序的安全。
    iddm帶你讀論文——SymQEMU:Compilation-based symbolic execution for binaries 本篇文章收錄于2021年網絡安全頂會NDSS,介紹了最新的符號執行技術,并且清晰地比較了當前流行的各種符號執行的引擎的優劣勢,可以比較系統的了解符號執行技術的相關知識
    二進制代碼相似判斷有著廣泛的用途,如 Bug 搜索、惡意軟件聚類、惡意軟件檢測、惡意軟件譜系跟蹤、補丁生成、跨程序版本移植信息和軟件剽竊檢測等應用場景。其常見的八種應用如下所示:
    需要llvm 11+,這是當前afl支持的效率最高的選擇,也意味著編譯要花更長時間。實現了編譯級插樁,效果比匯編級插樁更好。從編譯的實現流程上理解插樁模式差異:afl-gcc插樁分析考慮到afl的插樁方式隨編譯器的選擇而變化,從最簡單的afl-gcc開始入手。
    假如想在x86平臺運行arm程序,稱arm為source ISA, 而x86為target ISA, 在虛擬化的角度來說arm就是Guest, x86為Host。這種問題被稱為Code-Discovery Problem。每個體系結構對應的helper函數在target/xxx/helper.h頭文件中定義。
    VSole
    網絡安全專家
      亚洲 欧美 自拍 唯美 另类