看雪学院 09月20日
OLLVM混淆环境搭建及控制流平坦化去混淆指南
index_new5.html
../../../zaker_core/zaker_tpl_static/wap/tpl_guoji1.html

 

本文详细介绍了OLLVM(Obfuscator-LLVM)的编译环境搭建过程,重点讲解了Ubuntu 22.04下使用Docker编译OLLVM 4.0的步骤,包括源码克隆、Docker环境准备以及编译脚本的使用。同时,文章深入探讨了OLLVM的控制流平坦化(FLA)混淆技术,解释了其工作原理、关键组件(序言、分发器、真实块等)以及ollvm编译选项。最后,文章提供了使用IDA Python和Unicorn/Angr等工具进行FLA混淆代码去混淆的详细方法和脚本示例,旨在帮助开发者理解和分析OLLVM混淆后的二进制文件。

🚀 **OLLVM环境搭建**:文章提供了在Ubuntu 22.04环境下使用Docker编译OLLVM 4.0的详细步骤,包括源码克隆、Docker镜像拉取和编译脚本执行,确保用户能够成功构建OLLVM编译环境。

💡 **控制流平坦化(FLA)原理**:深入剖析了OLLVM的FLA混淆技术,解释了其如何通过分割基本块、引入虚假控制流(序言、主分发器、预处理器、真实块、retn块)来隐藏程序的真实执行逻辑,并列出了相应的ollvm编译选项。

🔍 **FLA去混淆方法**:文章提供了多种去混淆策略,包括使用IDA Python脚本自动化识别和移除虚假控制流,以及利用Unicorn或Angr等动态分析工具进行模拟执行和追踪,以还原真实的程序逻辑。

⚙️ **脚本示例与实践**:提供了具体的IDA Python和Unicorn模拟执行脚本,演示了如何定位真实块、分析块间关系以及进行代码补丁(NOP掉虚假指令或修改跳转),为读者提供了可操作的实践指导。

xiaowaaa 2025-09-17 17:59 上海

看雪论坛作者ID:xiaowaaa

Ollvm混淆学习:

环境搭建:

ollvm 搭建的环境为

◆ubuntu22.04

下载 ollvm 4.0 源码

git clone -b llvm-4.0 --depth=1 https://github.com/obfuscator-llvm/obfuscator.git

安装 docker

sudo apt install docker.io

安装编译 ollvm 的 docker 环境

sudo docker pull nickdiego/ollvm-build

编译 ollvm

下载编译脚本

git clone --depth=1 https://github.com/oacia/docker-ollvm.git

编译

ollvm-build.sh`  后面跟的参数是 `ollvm的源码目录sudo docker-ollvm/ollvm-build.sh /home/xw/Desktop/ollvm/obfuscator/

创建软链接

#!/usr/bin/env bash# 一键部署 OLLVM 可执行文件到 /usr/local/ollvm/bin,并加 -ollvm 后缀set -eSRC_DIR="/home/xw/Desktop/ollvm/obfuscator/build_release/bin"DEST_DIR="/usr/local/ollvm/bin"sudo mkdir -p "${DEST_DIR}"echo ">>> 正在批量创建软链接 ..."for f in "${SRC_DIR}"/*; do    base=$(basename "$f")    sudo ln -sf "$f" "${DEST_DIR}/${base}-ollvm"doneecho ">>> 已完成链接到 ${DEST_DIR}"

vim ~/.bashrcexport PATH="/usr/local/ollvm/bin:$PATH"source ~/.hasbrc

测试代码:

#include<stdio.h>/*RC4初始化函数*/voidrc4_init(unsignedchar* s, unsignedchar* key, unsignedlong Len_k){int i = 0, j = 0;char k[256] = { 0 };unsignedchar tmp = 0;for (i = 0; i < 256; i++) {        s[i] = i;        k[i] = key[i % Len_k];    }for (i = 0; i < 256; i++) {        j = (j + s[i] + k[i]) % 256;        tmp = s[i];        s[i] = s[j];        s[j] = tmp;    }}/*RC4加解密函数unsigned char* Data     加解密的数据unsigned long Len_D     加解密数据的长度unsigned char* key      密钥unsigned long Len_k     密钥长度*/voidrc4_crypt(unsignedchar* Data, unsignedlong Len_D, unsignedchar* key, unsignedlong Len_k) //加解密{unsignedchar s[256];rc4_init(s, key, Len_k);int i = 0, j = 0, t = 0;unsignedlong k = 0;unsignedchar tmp;for (k = 0; k < Len_D; k++) {        i = (i + 1) % 256;        j = (j + s[i]) % 256;        tmp = s[i];        s[i] = s[j];        s[j] = tmp;        t = (s[i] + s[j]) % 256;        Data[k] = Data[k] ^ s[t];    }}intmain(){//字符串密钥unsignedchar key[] = "zstuctf";unsignedlong key_len = sizeof(key) - 1;//数组密钥//unsigned char key[] = {};//unsigned long key_len = sizeof(key);//加解密数据unsignedchar data[] = { 0x7E0x6D0x550xC00x0C0xF00xB40xC70xDC0x45,0xCE0x150xD10xB50x1E0x110x140xDF0x6E0x95,0x770x9A0x120x99 };//加解密rc4_crypt(data, sizeof(data), key, key_len);for (int i = 0; i < sizeof(data); i++)    {printf("%c", data[i]);    }printf("\n");return 0;}//zstuctf{xXx_team_Is_GooD

虚假控制流(BCF):

ollvm编译:

clang-ollvm -mllvm -bcf -mllvm -bcf_loop=3 -mllvm -bcf_prob=40 test.c -o test-bcf

接下来就得到了混淆后的代码,先看一下两者的对比

对比:

正常代码:

混淆代码:

而且基本所有的函数都是这样的,很难看,不够在我测试的时候,我发现ida9.0的伪代码会直接将这个基本的bcf去除,得到和源码一样的伪代码,可能是混淆程度太低了。

伪代码对比:

BCF混淆后的代码:

可以发现很多的x,y操作,同时看汇编也会发现有许多的垃圾指令

具有很多的( y_3 >= 10 && (((x_2 - 1) * x_2) & 1) != 0 )

其实这个肯定是虚假的,x*(x-1)肯定是偶数,&1的结果肯定为假,就是一堆虚假指令。

去除:

看了oacia大佬关于去除这方面的方法,我觉得还是将对x,y赋值的地方直接进行nop比较好,使用idapython进行统一的nop,又快又有通用性还具有方便性。

具体修改如下:

使用idapython统一修改

#去除bcf混淆import ida_xref     #交叉引用操作import ida_idaapiimport ida_bytesimport ida_segmentdef do_patch(ea):if(ida_bytes.get_bytes(ea,1)==b"\x8B"):        reg=(ord(ida_bytes.get_bytes(ea+1,1)) & 0b00111000)>>3        ida_bytes.patch_bytes(ea,(0xB8+reg).to_bytes(1,"little")+b"\x00\x00\x00\x00\x90\x90")  #patch_bytes(ea, buf)else:print("error")seg=ida_segment.get_segm_by_name(".bss")start=seg.start_eaend=seg.end_eafor addr in range(start,end,4):    ref=ida_xref.get_first_dref_to(addr)print(hex(ref).center(20,'-'))#获取所有的交叉引用while (ref!=ida_idaapi.BADADDR):#BADADDR如果不是无效地址        do_patch(ref)print("patch at"+hex(ref))'''        ida_idaapi.ea_t get_next_dref_to  (   ida_idaapi.ea_t     to,        ida_idaapi.ea_t   current )        '''        ref=ida_xref.get_next_dref_to(addr,ref)print("-"*20)print("BCF 去除 finish")

很完美的去除

还有一种方法就是D810,但是如果是魔改的就需要自己去添加规则了

具体方法可以参考大佬的介绍

另外一个方法就是将bss段设置成只读

指令替换(SUB):

ollvm编译:

clang-ollvm -mllvm -sub -mllvm -sub_loop=3 test.c -o test-sub

对比:

在流程方面倒是没有很大的差距,但是在伪代码方面非常的复杂

源代码:

混淆代码:

只能说非常的抽象啊

这个让我想起来了腾讯安卓23年初赛的那道vm,都是类似于这种的。

去除:

1、D810:可以用d810试试

2、GAMBA简化运算

可以参考一下这个项目的识别。不过我用了一下,成功的概率不是很大,不如直接交给ai或者使用python手动赋值观察

控制流平坦化(FLA):

这个控制流平坦化是我们平常最容易见到的一种混淆,学习一下这个的混淆与去混淆

原理

控制流平坦化就是模糊基本块之间的关系,添加一些虚假的控制块来处理

借鉴一下几个大佬的图

正常逻辑是这个:

而添加了虚假块之后:

形成的流程图:

各部分介绍:

序言:函数的第一个执行的基本块主 (子) 分发器:控制程序跳转到下一个待执行的基本块retn 块:函数出口真实块:混淆前的基本块,程序真正执行工作的块预处理器:跳转到主分发器

ollvm编译:

clang-ollvm -mllvm -fla -mllvm -split -mllvm -split_num=3 test.c -o test-fla

◆-mllvm -fla : 激活控制流平坦化

◆-mllvm -split : 激活基本块分割

◆-mllvm -split_num=3 : 指定基本块分割的数目

标准的ollvm控制流平坦化

去混淆思路:

对于经典的ollvm来说,各个块之间的关系如下:

序言块:函数的开始部分,也就是函数的入口主分发器:序言块的后继预处理器:处理序言块外的另外一个主分发器的前驱真实块:预处理器的所有前驱子分发器:除此之外的所有块返回块:ret的地方

去混淆的步骤:

1、找到真实块:手动找、idapython通过各个块之间的关系找、angr模拟执行找、unidbg找2、得到真实块之间的关系:模拟执行、trace3、修复各个真实块之间的跳转关系,nop掉虚假的控制块

通过idapython来寻找真实块

#idapython寻找真实块import idaapiimport idctarget_func=0x401CE0#要处理的函数地址preprocess_block=0x402057#预处理块的地址#寻找所有预处理器的前驱True_Block=[]Fake_Block=[]'''FlowChartConstructor@param f: A func_t type, use get_func(ea) to get a reference@param bounds: A tuple of the form (start, end). Used if "f" is None@param flags: one of the FC_xxxx flags.这个函数就是返回ida流程图中的所有块中的操作权,比如查看函数起始地址,结束地址等等'''f_block=idaapi.FlowChart(idaapi.get_func(target_func),flags=idaapi.FC_PREDS)for block in f_block:#print(block)#预处理器的前驱都是真实块if block.start_ea==preprocess_block:        Fake_Block.append((block.start_ea,idc.prev_head(block.end_ea)))print("Find True Bolcks\n")        tbs=block.preds()for tb in tbs:            True_Block.append((tb.start_ea,idc.prev_head(tb.end_ea)))print(True_Block)elif not [x for x in block.succs()]:print("find ret")        True_Block.append((block.start_ea,idc.prev_head(block.end_ea)))    elif block.start_ea!=target_func:        Fake_Block.append((block.start_ea,idc.prev_head(block.end_ea)))print('True block')print(True_Block)print("Fake Block")print(Fake_Block)

模拟执行:

那接下来的任务就很明确了,就是寻找这些真实块之间的关系,然后patch一下,将所有的虚假块全部nop

这里考虑的就是使用angr还是unidbg/unicoin

unicorn:

#使用unicorn来模拟执行hookfrom threading import stack_sizefrom unicorn import *from unicorn.x86_const import *from keystone import * #pip install keystone-enginefrom capstone import *Base=0x400000Code=Base+0x0Code_size=0x100000Stack=0x7F00000000stack_size=0x100000Fs=0x7FF0000000Fs_size=0x100000ks=Ks(KS_ARCH_X86,KS_MODE_64)uc=Uc(UC_ARCH_X86,UC_MODE_64)cs=Cs(CS_ARCH_X86,CS_MODE_64)tbs=[(42025724202582), (42021374202137), (42021424202229), (42022344202248), (42022534202260), (42022654202278), (42022834202300), (42023054202329), (42023344202356), (42023614202376), (42023814202403), (42024084202425), (42024304202463), (42024684202481), (42024864202502), (42025074202520), (42025254202532), (42025374202567)]tb_call=[]main_addr=0x401CE0main_end=0x402056def hook_code(uc:unicorn.Uc,address,size,user_data):#反汇编这一句的指令for i in cs.disasm(CODE_DATA[address-Base:address-Base+size],address):if i.mnemonic =="call":print("调用call,跳过--")            uc.reg_write(UC_X86_REG_RIP,address+size)elif i.mnemonic == "ret":print("执行结束")            uc.emu_stop()print(tb_call)for tb in tbs:if address==tb[1]:#print(tb)#print (uc.reg_read (UC_X86_REG_FLAGS))#ZF 标志位在第 6 位                ZF_flag=(uc.reg_read(UC_X86_REG_FLAGS)&0b1000000)>>6                tb_call.append((tb,ZF_flag))breakdef hook_mem_access(uc:unicorn.Uc,type,address,size,value,userdata):    pc=uc.reg_read(UC_X86_REG_RSP)print('pc:%x type:%d addr:%x size:%x' % (pc, type, address, size))return Truedef inituc(uc:unicorn.Uc):    uc.mem_map(Code,Code_size,UC_PROT_ALL)#分配代码段内存    uc.mem_map(Stack,stack_size,UC_PROT_ALL)#分配栈内存    uc.mem_write(Code,CODE_DATA)#分配代码数组    uc.reg_write(UC_X86_REG_RSP,Stack+0x10000)#设置栈顶指针    uc.hook_add(UC_HOOK_CODE,hook_code)#设置hook代码    uc.hook_add(UC_HOOK_MEM_UNMAPPED, hook_mem_access)#当代码访问未映射内存是触发    uc.hook_add(UC_HOOK_INTR, hook_mem_access)  # 来捕捉中断with open("./test-fla""rb"as f:    CODE_DATA = f.read()inituc(uc)try:    uc.emu_start(main_addr,0)except Exception as e:print(e)

因为大概率会针对so,写一下arm64的(好像不是很兼容?)

from unicorn import *from unicorn.arm64_const import *from capstone import *# 模拟内存区域配置BASE = 0x400000CODE_ADDR = BASECODE_SIZE = 0x100000STACK_ADDR = 0x80000000STACK_SIZE = 0x100000# 初始化 disasm 与 unicorn 引擎cs = Cs(CS_ARCH_ARM64, CS_MODE_ARM)uc = Uc(UC_ARCH_ARM64, UC_MODE_ARM)# 你自己定义的 block 范围(例)tbs = [(0x4010000x401010), (0x4010200x401040)]  # 请按实际地址替换tb_call = []main_addr = 0x401000  # 请设置成入口点main_end = 0          # 设为0表示直到遇到异常或 ret# code hookdef hook_code(uc, address, size, user_data):for i in cs.disasm(CODE_DATA[address - BASE:address - BASE + size], address):print("Executing: 0x%x:\t%s\t%s" % (i.address, i.mnemonic, i.op_str))if i.mnemonic == "blr" or i.mnemonic == "bl":print("调用 bl/blr,跳过")# 模拟压栈 + 跳转            sp = uc.reg_read(UC_ARM64_REG_SP)            ret_addr = address + size            uc.mem_write(sp - 16, ret_addr.to_bytes(8'little'))            uc.reg_write(UC_ARM64_REG_SP, sp - 16)            uc.reg_write(UC_ARM64_REG_PC, ret_addr)elif i.mnemonic == "ret":print("执行 ret,停止")            uc.emu_stop()print("tb_call:", tb_call)for tb in tbs:if address == tb[1]:                nzcv = uc.reg_read(UC_ARM64_REG_NZCV)                ZF = (nzcv >> 30) & 1  # NZCV: bit 30 is Z                tb_call.append((tb, ZF))break# memory hookdef hook_mem_invalid(uc, access, address, size, value, user_data):print(f"[!] Memory Access Violation at 0x{address:x}")return False  # 返回 False 让 Unicorn 抛出异常停止# 初始化内存与 hookdef init_uc():    uc.mem_map(CODE_ADDR, CODE_SIZE)    uc.mem_map(STACK_ADDR, STACK_SIZE)    uc.mem_write(CODE_ADDR, CODE_DATA)    uc.reg_write(UC_ARM64_REG_SP, STACK_ADDR + STACK_SIZE - 0x10)    uc.hook_add(UC_HOOK_CODE, hook_code)    uc.hook_add(UC_HOOK_MEM_UNMAPPED, hook_mem_invalid)# 加载代码with open("test-fla-arm64""rb"as f:    CODE_DATA = f.read()# 运行init_uc()try:    uc.emu_start(main_addr, main_end)except Exception as e:print(f"[!] Emulation error: {e}")

angr:

安装使用(直接使用docker)

docker pull angr/angrdocker run -it angr/angr

angr的模拟执行就是deflt.py项目相关,可以参考一下大佬写的讲解:

deflt.py:https://1ens.github.io/2024/12/04/OLLVM%E5%8F%8D%E6%B7%B7%E6%B7%86/#deflt-py

patch:

#patch脚本#先明确目的,nop掉所有的虚假块,按照刚才模拟执行的顺序patch成有顺序的块import idaapiimport ida_bytesimport idcfrom keystone import *ks=Ks(KS_ARCH_X86,KS_MODE_64)#因为正常的ollvm真实块最后都是jmp 预处理器,所以判断jump修改就行#同时有的逻辑会存在if语句,所以我们要根据特殊情况来处理jnz和jz的情况def jump_patch(start,target,j_code='jmp'):global debug    patch_byte,count=ks.asm(f"{j_code}{hex(target)}",addr=start)#获取当前地址指令长度,补齐字节    patch_byte=bytes(patch_byte)+b"\x00"*(idc.get_item_size(start)+len(patch_byte))print(hex(start),f"{j_code}{hex(target)}",patch_byte)    ida_bytes.patch_bytes(start,patch_byte)#将无关代码全部nopdef patch_nop(start,end):while start<end:        ida_bytes.patch_bytes(start,bytes([0x90]))        start+=1#nop掉某一行不再是大范围nop了def patch_nop_line(addr):    patch_nop(addr,addr+idc+idc.get_item_size(addr))preamble_block = 0x402057  # 序言块的地址internal_reg = '[rbp+var_B4]'#中间变量的名称,遇到这个想都不用想直接 NOPtb_path= [((42021424202229), 1), ((42022344202248), 1), ((42022534202260), 1), ((42022654202278), 1), ((42022834202300), 1), ((42023054202329), 0), ((42023344202356), 0), ((42023614202376), 1), ((42023814202403), 0), ((42024084202425), 1), ((42024304202463), 1), ((42024684202481), 1), ((42024864202502), 0), ((42025074202520), 1), ((42025254202532), 1), ((42022654202278), 1), ((42022834202300), 1), ((42023054202329), 0), ((42023344202356), 0), ((42023614202376), 1), ((42023814202403), 0), ((42024084202425), 1), ((42024304202463), 1), ((42024684202481), 1), ((42024864202502), 0), ((42025074202520), 1), ((42025254202532), 1), ((42022654202278), 1), ((42022834202300), 1), ((42023054202329), 0), ((42023344202356), 0), ((42023614202376), 1), ((42023814202403), 0), ((42024084202425), 1), ((42024304202463), 1), ((42024684202481), 1), ((42024864202502), 0), ((42025074202520), 1), ((42025254202532), 1), ((42022654202278), 1), ((42022834202300), 1), ((42023054202329), 0), ((42023344202356), 0), ((42023614202376), 1), ((42023814202403), 0), ((42024084202425), 1), ((42024304202463), 1), ((42024684202481), 1), ((42024864202502), 0), ((42025074202520), 1), ((42025254202532), 1), ((42022654202278), 1), ((42022834202300), 1), ((42023054202329), 0), ((42023344202356), 0), ((42023614202376), 1), ((42023814202403), 0), ((42024084202425), 1), ((42024304202463), 1), ((42024684202481), 1), ((42024864202502), 0), ((42025074202520), 1), ((42025254202532), 1), ((42022654202278), 1), ((42022834202300), 1), ((42023054202329), 0), ((42023344202356), 0), ((42023614202376), 1), ((42023814202403), 0), ((42024084202425), 1), ((42024304202463), 1), ((42024684202481), 1), ((42024864202502), 0), ((42025074202520), 1), ((42025254202532), 1), ((42022654202278), 1), ((42022834202300), 1), ((42023054202329), 0), ((42023344202356), 0), ((42023614202376), 1), ((42023814202403), 0), ((42024084202425), 1), ((42024304202463), 1), ((42024684202481), 1), ((42024864202502), 0), ((42025074202520), 1), ((42025254202532), 1), ((42022654202278), 1), ((42022834202300), 1), ((42023054202329), 0), ((42023344202356), 0), ((42023614202376), 1), ((42023814202403), 0), ((42024084202425), 1), ((42024304202463), 1), ((42024684202481), 1), ((42024864202502), 0), ((42025074202520), 1), ((42025254202532), 1), ((42022654202278), 1), ((42022834202300), 1), ((42023054202329), 0), ((42023344202356), 0), ((42023614202376), 1), ((42023814202403), 0), ((42024084202425), 1), ((42024304202463), 1), ((42024684202481), 1), ((42024864202502), 0), ((42025074202520), 1), ((42025254202532), 1), ((42022654202278), 1), ((42022834202300), 1), ((42023054202329), 0), ((42023344202356), 0), ((42023614202376), 1), ((42023814202403), 0), ((42024084202425), 1), ((42024304202463), 1), ((42024684202481), 1), ((42024864202502), 0), ((42025074202520), 1), ((42025254202532), 1), ((42022654202278), 1), ((42022834202300), 1), ((42023054202329), 0), ((42023344202356), 0), ((42023614202376), 1), ((42023814202403), 0), ((42024084202425), 1), ((42024304202463), 1), ((42024684202481), 1), ((42024864202502), 0), ((42025074202520), 1), ((42025254202532), 1), ((42022654202278), 1), ((42022834202300), 1), ((42023054202329), 0), ((42023344202356), 0), ((42023614202376), 1), ((42023814202403), 0), ((42024084202425), 1), ((42024304202463), 1), ((42024684202481), 1), ((42024864202502), 0), ((42025074202520), 1), ((42025254202532), 1), ((42022654202278), 1), ((42022834202300), 1), ((42023054202329), 0), ((42023344202356), 0), ((42023614202376), 1), ((42023814202403), 0), ((42024084202425), 1), ((42024304202463), 1), ((42024684202481), 1), ((42024864202502), 0), ((42025074202520), 1), ((42025254202532), 1), ((42022654202278), 1), ((42022834202300), 1), ((42023054202329), 0), ((42023344202356), 0), ((42023614202376), 1), ((42023814202403), 0), ((42024084202425), 1), ((42024304202463), 1), ((42024684202481), 1), ((42024864202502), 0), ((42025074202520), 1), ((42025254202532), 1), ((42022654202278), 1), ((42022834202300), 1), ((42023054202329), 0), ((42023344202356), 0), ((42023614202376), 1), ((42023814202403), 0), ((42024084202425), 1), ((42024304202463), 1), ((42024684202481), 1), ((42024864202502), 0), ((42025074202520), 1), ((42025254202532), 1), ((42022654202278), 1), ((42022834202300), 1), ((42023054202329), 0), ((42023344202356), 0), ((42023614202376), 1), ((42023814202403), 0), ((42024084202425), 1), ((42024304202463), 1), ((42024684202481), 1), ((42024864202502), 0), ((42025074202520), 1), ((42025254202532), 1), ((42022654202278), 1), ((42022834202300), 1), ((42023054202329), 0), ((42023344202356), 0), ((42023614202376), 1), ((42023814202403), 0), ((42024084202425), 1), ((42024304202463), 1), ((42024684202481), 1), ((42024864202502), 0), ((42025074202520), 1), ((42025254202532), 1), ((42022654202278), 1), ((42022834202300), 1), ((42023054202329), 0), ((42023344202356), 0), ((42023614202376), 1), ((42023814202403), 0), ((42024084202425), 1), ((42024304202463), 1), ((42024684202481), 1), ((42024864202502), 0), ((42025074202520), 1), ((42025254202532), 1), ((42022654202278), 1), ((42022834202300), 1), ((42023054202329), 0), ((42023344202356), 0), ((42023614202376), 1), ((42023814202403), 0), ((42024084202425), 1), ((42024304202463), 1), ((42024684202481), 1), ((42024864202502), 0), ((42025074202520), 1), ((42025254202532), 1), ((42022654202278), 1), ((42022834202300), 1), ((42023054202329), 0), ((42023344202356), 0), ((42023614202376), 1), ((42023814202403), 0), ((42024084202425), 1), ((42024304202463), 1), ((42024684202481), 1), ((42024864202502), 0), ((42025074202520), 1), ((42025254202532), 1), ((42022654202278), 1), ((42022834202300), 1), ((42023054202329), 0), ((42023344202356), 0), ((42023614202376), 1), ((42023814202403), 0), ((42024084202425), 1), ((42024304202463), 1), ((42024684202481), 1), ((42024864202502), 0), ((42025074202520), 1), ((42025254202532), 1), ((42022654202278), 1), ((42022834202300), 1), ((42023054202329), 0), ((42023344202356), 0), ((42023614202376), 1), ((42023814202403), 0), ((42024084202425), 1), ((42024304202463), 1), ((42024684202481), 1), ((42024864202502), 0), ((42025074202520), 1), ((42025254202532), 1), ((42022654202278), 1), ((42022834202300), 1), ((42023054202329), 0), ((42023344202356), 0), ((42023614202376), 1), ((42023814202403), 0), ((42024084202425), 1), ((42024304202463), 1), ((42024684202481), 1), ((42024864202502), 0), ((42025074202520), 1), ((42025254202532), 1), ((42022654202278), 1), ((42022834202300), 1), ((42023054202329), 0), ((42023344202356), 0), ((42023614202376), 1), ((42023814202403), 0), ((42024084202425), 1), ((42024304202463), 1), ((42024684202481), 1), ((42024864202502), 0), ((42025074202520), 1), ((42025254202532), 1), ((42022654202278), 1), ((42022834202300), 1), ((42023054202329), 1), ((42023344202356), 1), ((42025374202567), 1)]tbs=[(42025724202582), (42021374202137), (42021424202229), (42022344202248), (42022534202260), (42022654202278), (42022834202300), (42023054202329), (42023344202356), (42023614202376), (42023814202403), (42024084202425), (42024304202463), (42024684202481), (42024864202502), (42025074202520), (42025254202532), (42025374202567)]fbs=[(42017214201738), (42017444201744), (42017494201760), (42017664201766), (42017714201782), (42017884201788), (42017934201804), (42018104201810), (42018154201826), (42018324201832), (42018374201851), (42018574201857), (42018624201876), (42018824201882), (42018874201901), (42019074201907), (42019124201926), (42019324201932), (42019374201951), (42019574201957), (42019624201976), (42019824201982), (42019874202001), (42020074202007), (42020124202026), (42020324202032), (42020374202051), (42020574202057), (42020624202076), (42020824202082), (42020874202101), (42021074202107), (42021124202126), (42021324202132), (42021374202137), (42021424202229), (42022344202248), (42022534202260), (42022654202278), (42022834202300), (42023054202329), (42023344202356), (42023614202376), (42023814202403), (42024084202425), (42024304202463), (42024684202481), (42024864202502), (42025074202520), (42025254202532), (42025374202567), (42025834202583)]block_info = {}  #判断每一个真实块有没有 patch 结束for i in range(len(tbs)):    block_info[tbs[i][0]] = {'finish'0,'ret':0}#nop掉所有的无关块for fb in fbs:    patch_nop(fb[1],fb[1]+idc.get_item_size(fb[1]))#分析真实块的指令for tb in tbs:    dont_patch=False    current_addr=tb[0]while current_addr<=tb[1]:if "cmov" in idc.print_insn_mnem(current_addr):            patch_nop_line(current_addr)            dont_patch=Trueelif internal_reg in idc.print_operand(current_addr,0):print("非法指令")            patch_nop_line(current_addr)elif 'ret' in idc.print_insn_mnem(current_addr):            block_info[tb[0]]['ret']=1            dont_patch=True#对每一个真实块的结尾地址进行nopif not dont_patch:        patch_nop_line(tb[1])        block_info[tb[0]]['finish']=1#序言块到第一个真实块jump_patch(preamble_block,tb_path[0][0][0])for i in range(len(tb_path)-1):# 不是返回块,也未完成 patch, 剩下的指令都是有分支跳转的.if block_info[tb_path[i][0][0]]['finish']==0 and block_info[tb_path[i][0][0]]['ret']==0:        ZF=tb_path[i][1]#如果当前真实块的尾地址不是下一个真实块的始地址就进行patch jnzjz,否则就是正常连接if(idc.next_head(tb_path[i][0][1]) != tb_path[i+1][0][0]):#打上标记,避免重复            block_info[tb_path[i][0][0]]['finish']=1            j_code=('jnz','jz')            jump_patch(tb_path[i][0][1],tb_path[i+1][0][0],j_code[ZF])

arm64架构的:

import idcimport idaapiimport ida_bytesfrom keystone import Ks, KS_ARCH_ARM64, KS_MODE_LITTLE_ENDIANks = Ks(KS_ARCH_ARM64, KS_MODE_LITTLE_ENDIAN)# ==== 配置部分 ====# preamble block 跳转到主路径preamble_block = 0x400800# 被用于混淆的 filler blocks,全 NOP 掉fbs = [    (0x4008200x40083C),    (0x4008400x40085C),# ...]# 有效代码块(基本块范围),用于 patch 分支tbs = [    (0x4008600x40087C),    (0x4008800x40089C),# ...]# 动态执行模拟得到的路径(块 + 是否跳转)tb_path = [    ((0x4008600x40087C), 1),  # ZF==1 跳转    ((0x4008800x40089C), 0),# ...]# ===== PATCH工具函数 =====def patch_nop(start, end):while start < end:        ida_bytes.patch_bytes(start, b"\x1F\x20\x03\xD5")  # NOP        start += 4  # 每条 ARM64 指令 4 字节def patch_nop_line(addr):    patch_nop(addr, addr + 4)def assemble_and_patch(addr, asm_str):    encoding, count = ks.asm(asm_str, addr)if not encoding:print(f"[!] Keystone failed on: {asm_str}")return    patch_bytes = bytes(encoding)    ida_bytes.patch_bytes(addr, patch_bytes)print(f"[+] PATCH {hex(addr)}{asm_str} -> {patch_bytes.hex()}")# patch B label 或 B.EQ label 等跳转指令def patch_branch(start_addr, target_addr, condition=None):if condition is None:        asm = f"B #{target_addr}"else:        asm = f"B.{condition.upper()} #{target_addr}"    assemble_and_patch(start_addr, asm)# ========== 逻辑 ==========# NOP 掉所有 filler blocksfor fb in fbs:    patch_nop(fb[0], fb[1])# 每个真实块处理条件跳转与非法变量引用block_info = {}for tb in tbs:    start, end = tb    block_info[start] = {'ret'0'finish'0}    addr = startwhile addr < end:        mnem = idc.print_insn_mnem(addr)if "CSEL" in mnem or "CMOV" in mnem:            patch_nop_line(addr)elif "RET" in mnem:            block_info[start]['ret'] = 1        addr = idc.next_head(addr)# 序言块跳转第一个基本块first_tb_start = tb_path[0][0][0]patch_branch(preamble_block, first_tb_start)# 主路径 patch:连接每个路径的结尾到下一个块for i in range(len(tb_path) - 1):    curr_tb, ZF = tb_path[i]    next_tb, _ = tb_path[i + 1]    block_start = curr_tb[0]    block_end = curr_tb[1]if block_info[block_start]['ret'or block_info[block_start]['finish']:continue    jump_addr = block_end    next_addr = next_tb[0]if ZF == 1:        patch_branch(jump_addr, next_addr, condition="eq")else:        patch_branch(jump_addr, next_addr, condition="ne")    block_info[block_start]['finish'] = 1

非标准控制流平坦化:

最开始是为了腾讯23 final题目而写的这篇文章,所以上面的学到的是为了这个做准备,研究一下这个非标准的控制流平坦化怎么解决,也不知道能实现什么地步。

先来看一下流程图以及伪代码:

其实不去混淆的话也能看,毕竟关键的csel条件跳转已经去除了,但是为了学习还是研究一下怎么去除。

寻找真实块:

错解:

经过我的观察,我觉得类似这种的像是真实块,(好像猜错了)

结尾一B或者BL跳转的是真实块,先用颜色标注一下看看都有多少地方以及哪些地方是

import idcimport idaapi#根据感觉来识别真实块:def set_color(start,end):for addr in range(start,end):        idc.set_color(addr,idc.CIC_ITEM,0xd0ffc0)target_func=0xE4C50True_Block=[]Fake_Block=[]f_block=idaapi.FlowChart(idaapi.get_func(target_func),flags=idaapi.FC_PREDS)cnt=0for block in f_block:    ea=block.start_ea    ed=block.end_ea    last=idc.prev_head(ed)#可以先判断块的size,如果小于5条指令(20),那么应该不是#然后如果结尾不是B或者BL就不是if ((ed-ea>4and (idc.print_insn_mnem(last).lower() in ("b","bl"))):        cnt+=1        True_Block.append((ea,ed-4))        set_color(ea,ed)else:continueprint(f"共找到{cnt}处")print("True_block:",True_Block)print("finish")'''得到:[(937040, 937340), (937448, 937540), (937592, 937700), (937788, 937856), (937916, 937984), (938016, 938096), (938152, 938188), (938364, 938436), (938440, 938508), (938728, 938752), (938936, 938988), (939348, 939400), (939404, 939596), (939628, 939668), (939672, 939840), (939844, 939892), (939896, 939964), (939968, 940140)]加上返回块 (0xe5884,0xe58a0)'''

找到了18处,加上返回块应该是19处

但是经过验证不是上面的做法

首先应该找到函数的循环头,通过循环头可以直接找到所有对应的真实块,这个循环头就是许多真实块最终跳转的地方,也就用广搜来搜索多次经过的地方。

def find_loop_heads(func):    loop_heads = set()    queue = deque()    block = get_block_by_address(func)    queue.append((block, []))    while len(queue) > 0:        cur_block, path = queue.popleft()        if cur_block.start_ea in path:            loop_heads.add(cur_block.start_ea)            continue        path = path+ [cur_block.start_ea]        queue.extend((succ, path) for succ in cur_block.succs())    all_loop_heads = list(loop_heads)    all_loop_heads.sort()#升序排序,保证函数开始的主循环头在第一个    return all_loop_heads

也就是这个地方,序言的后面,许多函数最后经过的地方

寻找汇聚块

对于非标准OLLVM的汇聚块基本就是循环头了,而对于标准FLA而言,汇聚块就是预处理块,我们根据预处理块寻找真实块,现在不就是根据汇聚块寻找真实块吗。

#idapython寻找真实块import idaapiimport idctarget_func=0x98E50#要处理的函数地址preprocess_block=0x98f74#预处理块的地址#寻找所有预处理器的前驱True_Block=[]Fake_Block=[]'''FlowChartConstructor@param f: A func_t type, use get_func(ea) to get a reference@param bounds: A tuple of the form (start, end). Used if "f" is None@param flags: one of the FC_xxxx flags.这个函数就是返回ida流程图中的所有块中的操作权,比如查看函数起始地址,结束地址等等'''f_block=idaapi.FlowChart(idaapi.get_func(target_func),flags=idaapi.FC_PREDS)for block in f_block:#print(block)#预处理器的前驱都是真实块if block.start_ea==preprocess_block:        Fake_Block.append((block.start_ea,idc.prev_head(block.end_ea)))print("Find True Bolcks\n")        tbs=block.preds()for tb in tbs:if(tb.end_ea-tb.start_ea>4):                True_Block.append((tb.start_ea,idc.prev_head(tb.end_ea)))print(True_Block)elif not [x for x in block.succs()]:print("find ret")        True_Block.append((block.start_ea,idc.prev_head(block.end_ea)))    print('True block')for st,ed in True_Block:print(f"({hex(st),hex(ed)})\t")    print(True_Block) # 创建集合以便快速查找True_Block_Starts = {start for start, end in True_Blocks}True_Block_Ranges = set(True_Blocks)def is_true_block(block):"""检查一个块是否在真实块列表中"""# 检查起始地址是否在真实块集合中if block.start_ea in True_Block_Starts:return True# 检查整个块范围是否在真实块集合中if (block.start_ea, block.end_ea) in True_Block_Ranges:return Truereturn False# 获取目标函数的流程图f = idaapi.get_func(target_func)         Fake_Blocks = []# 创建流程图对象f_block = idaapi.FlowChart(f, flags=idaapi.FC_PREDS)# 遍历所有基本块for block in f_block:# 跳过预处理块if block.start_ea == preprocess_block:continue# 如果不是真实块,则添加到虚假块列表if not is_true_block(block):        Fake_Blocks.append((block.start_ea, block.end_ea))# 打印结果print("虚假块列表:")print(Fake_Blocks)  print(f"\n共找到 {len(Fake_Blocks)} 个虚假块")'''得到:[(626256, 626520), (626524, 626544), (626672, 626724), (626828, 626856), (626888, 626968), (627000, 627080), (627140, 627180), (627240, 627292), (627352, 627420), (627480, 627508), (627536, 627748), (627820, 627884), (627888, 627936), (628004, 628056), (628200, 628284), (628320, 628344), (628348, 628404), (628612, 628620)]好像遗漏了一个[(0x996D0,0x99780)],但是这个没有跳转指令a,所以把他和下一个基本快合并一下看看[(0x996D0,0x9978C)]最后:[(626256, 626520), (626524, 626544), (626672, 626724), (626828, 626856), (626888, 626968), (627000, 627080), (627140, 627180), (627240, 627292), (627352, 627420), (627480, 627508), (627536, 627748), (627820, 627884), (627888, 627936), (628004, 628056), (628200, 628284), (628320, 628344), (628348, 628404), (0x996D0,0x9978C)]'''

模拟执行:

我差不多得到啦这些跳转的关系:

package com.tengxun2023;import capstone.Capstone;import capstone.api.Instruction;import com.alibaba.fastjson.util.IOUtils;import com.github.unidbg.AndroidEmulator;import com.github.unidbg.Module;import com.github.unidbg.arm.backend.Backend;import com.github.unidbg.arm.backend.CodeHook;import com.github.unidbg.arm.backend.UnHook;import com.github.unidbg.linux.android.AndroidEmulatorBuilder;import com.github.unidbg.linux.android.AndroidResolver;import com.github.unidbg.linux.android.dvm.DalvikModule;import com.github.unidbg.linux.android.dvm.DvmClass;import com.github.unidbg.linux.android.dvm.VM;import com.github.unidbg.memory.Memory;import com.github.unidbg.virtualmodule.android.AndroidModule;import keystone.Keystone;import keystone.KeystoneArchitecture;import keystone.KeystoneEncoded;import keystone.KeystoneMode;//import sun.security.tools.PathList;import unicorn.Arm64Const;import unicorn.MemHook;import javax.sound.midi.Patch;import java.io.File;import java.io.FileInputStream;import java.nio.file.Files;import java.util.ArrayDeque;import java.util.ArrayList;import java.util.*;import java.util.List;import java.util.Stack;import static unicorn.UnicornConst.UC_MEM_WRITE_UNMAPPED;//我要在这个里面写一个不同类型的混淆的去除手段public class ollvm_anti {//初始化环境public final AndroidEmulator emulator;public final VM vm;public final Memory memory;public final Module module;private Set<Long> executedAddresses = new HashSet<>();public String inname="unidbg-android/src/test/java/com/tengxun2023/input.so";public String outname="unidbg-android/src/test/java/com/tengxun2023/output.so";    DvmClass cNative;public ollvm_anti(){        emulator = AndroidEmulatorBuilder.for64Bit().build();        memory =  emulator.getMemory();        memory.setLibraryResolver(new AndroidResolver(23));        emulator.getSyscallHandler().setEnableThreadDispatcher(true);        vm = emulator.createDalvikVM();new AndroidModule(emulator,vm).register(memory);DalvikModuledalvikModule = vm.loadLibrary(new File("unidbg-android/src/test/java/com/tengxun2023/libsec2023.so"), true);module = dalvikModule.getModule();        System.out.println(module.base);        set_hook();        save_code(0xe4c50,0xE58A0);//vm.callJNI_OnLoad(emulator,module);module.callFunction(emulator,0x000000000094368,114514);    }public static void main(String[] args){        ollvm_anti anti_ob=new ollvm_anti();        anti_ob.destory();    }/** 记录真实块信息 */static class BlkInfo {long start, end;   // 起止偏移int  zf;           // 进入下一块时 NZCV.Z (ZF) 的取值        BlkInfo(long s, long e, int z) {            start = s;            end   = e;            zf    = z;        }/** 打印成 ((4202234, 4202248), 1) 这样的形式               */@Overridepublic String toString() {// 这里用十进制,如需十六进制可把 %d 换成 %xreturn String.format("((%d, %d), %d)", start, end, zf);        }    }public List<BlkInfo> save_code(long begin, long end) {        Map<Long, Long> realBlocks = new HashMap<>();        realBlocks.put(0x0E5884L0x0E58A0L);        realBlocks.put(937040L937344L);        realBlocks.put(937448L937544L);        realBlocks.put(937592L937704L);        realBlocks.put(937788L937860L);        realBlocks.put(937916L937988L);        realBlocks.put(938016L938100L);        realBlocks.put(938152L938192L);        realBlocks.put(938364L938440L);        realBlocks.put(938440L938512L);        realBlocks.put(938728L938756L);        realBlocks.put(938936L938992L);        realBlocks.put(939348L939404L);        realBlocks.put(939404L939600L);        realBlocks.put(939628L939672L);        realBlocks.put(939672L939844L);        realBlocks.put(939844L939896L);        realBlocks.put(939896L939968L);        realBlocks.put(939968L940144L);final List<BlkInfo> pathList = new ArrayList<>();final Capstonecap =new Capstone(Capstone.CS_ARCH_ARM64, Capstone.CS_MODE_ARM);long pass[]={0xE5694,0xE5588,0x94368,0xe55a0 };        emulator.getBackend().hook_add_new(new CodeHook() {@Overridepublic void hook(Backend backend, long address, int size, Object user) {try{//System.out.printf("    [Trace] Executing at offset: 0x%x\n", address - module.base);if (address == 0x94440 + module.base) {                        backend.reg_write(Arm64Const.UC_ARM64_REG_X0, 0);                        System.out.println("修改cmp");                    }longoff = address - module.base;LongblkEnd = realBlocks.get(off);for(int i=0;i<pass.length;i++){if(off==pass[i]){                            System.out.println("跳过call函数");                            backend.reg_write(Arm64Const.UC_ARM64_REG_PC, address + 4);return;                        }                    }if (blkEnd != null) {                           // 命中真实块/* 读取 ARM64 ZF(NZCV bit 30) */longnzcv = backend.reg_read(Arm64Const.UC_ARM64_REG_NZCV).longValue();intzf   = (int) ((nzcv >> 30) & 1);                        System.out.printf("((0x%x,0x%x),%d)",                                off, blkEnd, zf);/* 顺序追加 */                        pathList.add(new BlkInfo(off, blkEnd, zf));                    }                }catch (Exception e){                    e.printStackTrace();                }            }@Overridepublic void onAttach(UnHook unHook) {                System.out.println("attach");            }@Overridepublic void detach() {                System.out.println("detach");            }        }, module.base + begin, module.base + end, null);return pathList;    }void destory(){        IOUtils.close(emulator);        System.out.println("destory");    }public void set_hook(){        emulator.getBackend().hook_add_new(new CodeHook() {@Overridepublic void hook(Backend backend, long address, int size, Object user) {if (address==0x94440+module.base){                    backend.reg_write(Arm64Const.UC_ARM64_REG_X0,0);                }            }@Overridepublic void onAttach(UnHook unHook) {            }@Overridepublic void detach() {            }        },0x9443cmodule.base,0x94444module.base,null);    }}/*得到:((0x98e50,0x98f58),1)((0x996d0,0x9978c),1)跳过call函数((0x9908c,0x990a8),1)((0x995e8,0x9963c),1)((0x99350,0x99424),1)跳过call函数((0x991c4,0x991ec),1)((0x99228,0x9925c),1)((0x99318,0x99334),1)((0x98ff0,0x99024),1)((0x995e8,0x9963c),1)((0x99350,0x99424),1)跳过call函数((0x991c4,0x991ec),1)((0x99228,0x9925c),1)((0x99318,0x99334),1)((0x98ff0,0x99024),1)((0x995e8,0x9963c),1)((0x99350,0x99424),1)跳过call函数((0x991c4,0x991ec),1)((0x99228,0x9925c),1)((0x99318,0x99334),1)((0x98ff0,0x99024),1)((0x995e8,0x9963c),1)((0x99350,0x99424),1)跳过call函数((0x991c4,0x991ec),1)((0x99228,0x9925c),1)((0x99318,0x99334),1)((0x98ff0,0x99024),1)((0x995e8,0x9963c),1)((0x99350,0x99424),1)跳过call函数((0x991c4,0x991ec),1)((0x99228,0x9925c),1)((0x99318,0x99334),1)((0x98ff0,0x99024),1)((0x995e8,0x9963c),1)((0x99350,0x99424),1)跳过call函数     ......一堆循环*/

跑不完啊,难搞

patch

错误的,没写出来

import idaapi, ida_bytes, idcfrom keystone import Ks, KS_ARCH_ARM64, KS_MODE_LITTLE_ENDIAN# ────────────── 手动配置 ──────────────target_func = 0xE4C50                        # 序言块(dispatcher 之前的一跳)internal_reg   = "xzr"                         # 若某条指令引用到该寄存器就 NOP   (可按需改)# 真实块、虚假块、执行路径(示例)tbs = [                                         # (start, end)  — end 为最后 branch 的地址    (0xE4C50,0xE4D7C),(0xE566C,0xE5694),(0xE5698,0xE5740),    (0xE5554,0xE5588),(0xE558C,0xE564C),(0xE5870,0xE5880),    (0xE5884,0xE58A0),(0xe54dc,0xe54ec),(0xe5388,0xe5398),(0xe529c,0xe52ac),(0xe5528,0xe5534),    (0xe52e8,0xe5300),]tb_path = [                                     # ((start,end), ZF)((0xe4c50,0xe4d7c), 1),((0xe54dc,0xe54ec), 0),((0xe566c,0xe5694), 1),((0xe5698,0xe5740), 1),((0xe5388,0xe5398), 1),((0xe529c,0xe52ac), 1),((0xe5528,0xe5534), 1),((0xe5554,0xe5588), 1),((0xe558c,0xe564c), 1),((0xe52e8,0xe5300), 1),((0xe5870,0xe5880), 1),((0xe5884,0xe58a0), 1),]fbs = [                                         # filler blocks(示例)    (0xE54DC,0xE54EC),(0xE529C,0xE52AC),(0xE5528,0xE5534)]# ─────────────────────────────────────# Keystone 初始化ks = Ks(KS_ARCH_ARM64, KS_MODE_LITTLE_ENDIAN)# ---------- 帮助函数 ----------def nop_range(start, end):    nop = b"\x1F\x20\x03\xD5"    ea  = startwhile ea < end:        ida_bytes.patch_bytes(ea, nop)        ea += 4def patch_branch(addr, dst, cond=None):"""    把 addr 处的指令替换为:        • B   #dst          (cond=None)        • B.EQ #dst         (cond='eq')        • B.NE #dst         (cond='ne')    """if cond:        asm = f"B.{cond.upper()} #{hex(dst)}"else:        asm = f"B #{hex(dst)}"    enc, _ = ks.asm(asm, addr)    ida_bytes.patch_bytes(addr, bytes(enc))# ---------- 0. 预处理 ----------# 标记每个真实块是否已经处理完 | 是否是 ret-blockblk_info = { s: {'finish':0'ret':0for (s,_) in tbs }for start, end in tbs:    last = idc.prev_head(end)                  # 真·最后指令if idc.print_insn_mnem(last).upper() == "RET":        blk_info[start]['ret'] = 1# ---------- 1. NOP 所有 filler ----------for st,ed in fbs:    nop_range(st,ed)# ---------- 2. NOP 每个真实块里用不到的指令 ----------# for start, end in tbs:#     ea = start#     while ea < end:#         mnem = idc.print_insn_mnem(ea).lower()#         # 简单示例:碰到 cmov / 引用了 “internal_reg” 就 NOP#         if mnem.startswith("csel") or internal_reg in idc.print_operand(ea, 0):#             nop_range(ea, ea+4)#         ea = idc.next_head(ea)# ---------- 3. patch 跳转 ----------# 3-1  让序言块直接跳到首个真实块first_tb_start = tb_path[1][0][0]patch_branch(tb_path[1][0][0], first_tb_start)    # 无条件 B# 3-2  顺着 tb_path 逐块修复for i in range(len(tb_path)-1):    (cur_s, cur_e), zf = tb_path[i]    (nxt_s,_),       _ = tb_path[i+1]if blk_info[cur_s]['ret'or blk_info[cur_s]['finish']:continue    cond = "eq" if zf else "ne"                # ZF=1 ➜ 跳 eq    patch_branch(cur_e, nxt_s, cond)    blk_info[cur_s]['finish'] = 1

不对啊

希望有去混淆大手子教教,真不会了

如果上面内容有错误,还请各位大佬们教教

参考:

ollvm三种混淆模式的反混淆思路

https://oacia.dev/ollvm-study/

OLLVM 之虚假控制流源码学习

http://www.qfrost.com/posts/llvm/llvmbogus%E6%BA%90%E7%A0%81%E5%88%86%E6%9E%90/

OLLVM学习

https://lich4.github.io/cpp_posts/20240826_ollvm/

非标准ollvm-fla分析

https://bbs.kanxue.com/thread-286549.htm

*本文为看雪论坛优秀文章,由 xiaowaaa 原创,转载请注明来自看雪社区

1.25折门票开售!看雪·第九届安全开发者峰会(SDC 2025)

球分享

球点赞

球在看

点击阅读原文查看更多

阅读原文

跳转微信打开

Fish AI Reader

Fish AI Reader

AI辅助创作,多种专业模板,深度分析,高质量内容生成。从观点提取到深度思考,FishAI为您提供全方位的创作支持。新版本引入自定义参数,让您的创作更加个性化和精准。

FishAI

FishAI

鱼阅,AI 时代的下一个智能信息助手,助你摆脱信息焦虑

联系邮箱 441953276@qq.com

相关标签

OLLVM 代码混淆 控制流平坦化 FLA 逆向工程 IDA Python Unicorn Angr Docker GCC Clang Obfuscation Deobfuscation Reverse Engineering Control Flow Flattening
相关文章