-
-
[原创]OLLVM (一)字符串混淆
-
发表于: 2025-6-15 17:34 1024
-
前言
最近学习了OLLVM,代码工程链接。我对原有代码添加了注释并做了代码分析。
目标
- 对全局字符串加密。
- 对使用全局变量的全局变量加密。
字符串混淆步骤
所谓的字符串混淆就是字符串加密。步骤:
遍历模块内的所有全局字符串
- 识别到全局字符串后对其加密
- 构建对应的解密函数
- 创建解密后字符串的存储位置,如:
@dec0.str、@dec1.str.1 - 创建字符串解密后的状态标志,如:
@dec_status_0.str、@dec_status_1.str.1
对使用全局变量的全局变量加密,然后构建对应的解密函数
创建加密字符串表
EncryptedStringTable,存储所有加密的字符串遍历模块内的所有命令,替换 全局变量和使用它的全局变量 为解密相关代码
替换 解密函数内的全局变量 为解密相关代码
删除未使用的全局变量
混淆后代码解释
; ModuleID = '/Users/xx/Documents/Codes/llvm-build/test/OllvmTest/calculate.cpp'
source_filename = "/Users/xx/Documents/Codes/llvm-build/test/OllvmTest/calculate.cpp"
target datalayout = "e-m:o-i64:64-i128:128-n32:64-S128-Fn32"
target triple = "arm64-apple-macosx15.0.0"
; 定义一个结构体类型,包含一个整型和一个指针
%struct.StructTest = type { i32, ptr }
; 定义全局变量,用于存储解密后的字符串
@dec0.str = private global [31 x i8] zeroinitializer, align 1 ; 解密字符串缓冲区1
@dec_status_0.str = private global i32 0 ; 解密状态标志1
@dec1.str.1 = private global [20 x i8] zeroinitializer, align 1 ; 解密字符串缓冲区2
@dec_status_1.str.1 = private global i32 0 ; 解密状态标志2
@dec__ZL11struct_test = private global %struct.StructTest zeroinitializer, align 8 ; 解密后的结构体实例
@dec_status__ZL11struct_test = private global i32 0 ; 结构体解密状态标志
; 加密字符串表,存储所有加密的字符串
@EncryptedStringTable = private global [141 x i8] c"\E3pjW;\F7X \87l\93\F0\D7\9C\18{$\14\95;\EB\E3\D0\D05\01\8B\F1e\7F1\7F\0A\CB\A1\CD\B6\91P\BE\CE7\BD\22\A4\FA;\F30'\1EM*&=\82~a\9D\B7-m\C8`\1An-m\BF R\9Dw\92\F5\B2f7\B0D-\BD\F1p\F4\17\85\F1:\B0\B7,\9E\02\D0C\1B\13\AA\02\B2\CD\C6\E39\BE\9D\AE\1FL\06\02H\C4\A0U\A0\EEj\92\BE\8C\FD\19\FB'(,,&'\18kl\18\19?\9Dg\82\F5"
; 计算函数,根据操作符对两个双精度浮点数进行运算
; Function Attrs: mustprogress noinline optnone ssp uwtable(sync)
define noundef double @_Z9calculateddc(double noundef %a, double noundef %b, i8 noundef signext %op) #0 {
entry:
%retval = alloca double, align 8 ; 返回值存储
%a.addr = alloca double, align 8 ; 参数a存储
%b.addr = alloca double, align 8 ; 参数b存储
%op.addr = alloca i8, align 1 ; 操作符存储
store double %a, ptr %a.addr, align 8
store double %b, ptr %b.addr, align 8
store i8 %op, ptr %op.addr, align 1
; 解密并打印调试信息
%0 = load double, ptr %a.addr, align 8
%1 = load double, ptr %b.addr, align 8
%2 = load i8, ptr %op.addr, align 1
%conv = sext i8 %2 to i32
; 解密字符串
call void @goron_decrypt_string_0(ptr @dec0.str, ptr getelementptr inbounds ([141 x i8], ptr @EncryptedStringTable, i32 0, i32 27))
%call = call i32 (ptr, ...) @printf(ptr noundef @dec0.str, double noundef %0, double noundef %1, i32 noundef %conv)
; 根据操作符进行不同的运算
%3 = load i8, ptr %op.addr, align 1
%conv1 = sext i8 %3 to i32
switch i32 %conv1, label %sw.default [
i32 43, label %sw.bb ; '+' 加法
i32 45, label %sw.bb2 ; '-' 减法
i32 42, label %sw.bb3 ; '*' 乘法
i32 47, label %sw.bb4 ; '/' 除法
]
sw.bb: ; 加法分支
%4 = load double, ptr %a.addr, align 8
%5 = load double, ptr %b.addr, align 8
%add = fadd double %4, %5
store double %add, ptr %retval, align 8
br label %return
sw.bb2: ; 减法分支
%6 = load double, ptr %a.addr, align 8
%7 = load double, ptr %b.addr, align 8
%sub = fsub double %6, %7
store double %sub, ptr %retval, align 8
br label %return
sw.bb3: ; 乘法分支
%8 = load double, ptr %a.addr, align 8
%9 = load double, ptr %b.addr, align 8
%mul = fmul double %8, %9
store double %mul, ptr %retval, align 8
br label %return
sw.bb4: ; 除法分支
%10 = load double, ptr %b.addr, align 8
%cmp = fcmp une double %10, 0.000000e+00 ; 检查除数是否为0
br i1 %cmp, label %if.then, label %if.else
if.then: ; 除数不为0的情况
%11 = load double, ptr %a.addr, align 8
%12 = load double, ptr %b.addr, align 8
%div = fdiv double %11, %12
store double %div, ptr %retval, align 8
br label %return
if.else: ; 除数为0的情况
store double -1.000000e+00, ptr %retval, align 8 ; 返回-1表示错误
br label %return
sw.default: ; 默认情况(无效操作符)
store double -1.000000e+00, ptr %retval, align 8 ; 返回-1表示错误
br label %return
return: ; 返回处理
%13 = load double, ptr %retval, align 8
ret double %13
}
; 标准printf函数声明
declare i32 @printf(ptr noundef, ...) #1
; 主函数
; Function Attrs: mustprogress noinline norecurse optnone ssp uwtable(sync)
define noundef i32 @main(i32 noundef %argc, ptr noundef %argv) #2 {
entry:
%retval = alloca i32, align 4
%argc.addr = alloca i32, align 4
%argv.addr = alloca ptr, align 8
store i32 0, ptr %retval, align 4
store i32 %argc, ptr %argc.addr, align 4
store ptr %argv, ptr %argv.addr, align 8
; 初始化全局结构体变量
call void @__global_variable_initializer__ZL11struct_test(ptr @dec__ZL11struct_test)
; 解密并打印结构体中的字符串
; 解密后获得字符串类型的可变参数
%0 = getelementptr inbounds %struct.StructTest, ptr @dec__ZL11struct_test, i32 0, i32 1
%1 = load ptr, ptr %0, align 8
; 解密后获得打印格式化字符串
call void @goron_decrypt_string_1(ptr @dec1.str.1, ptr getelementptr inbounds ([141 x i8], ptr @EncryptedStringTable, i32 0, i32 100))
; 打印
%call = call i32 (ptr, ...) @printf(ptr noundef @dec1.str.1, ptr noundef %1)
; 测试计算函数
%call1 = call noundef double @_Z9calculateddc(double noundef 0.000000e+00, double noundef 0.000000e+00, i8 noundef signext 47) ; 0/0
%call2 = call noundef double @_Z9calculateddc(double noundef 1.000000e+00, double noundef 2.000000e+00, i8 noundef signext 47) ; 1/2
%call3 = call noundef double @_Z9calculateddc(double noundef 1.000000e+00, double noundef 2.000000e+00, i8 noundef signext 43) ; 1+2
%call4 = call noundef double @_Z9calculateddc(double noundef 1.000000e+00, double noundef 2.000000e+00, i8 noundef signext 45) ; 1-2
ret i32 0
}
; 字符串解密函数0
define private void @goron_decrypt_string_0(ptr nocapture %plain_string, ptr nocapture %data) {
Enter:
%0 = getelementptr inbounds i8, ptr %data, i32 17 ; 获取加密数据起始位置
%1 = load i32, ptr @dec_status_0.str, align 4 ; 检查解密状态
%2 = icmp eq i32 %1, 1 ; 如果已解密则跳过
br i1 %2, label %Exit, label %LoopBody.preheader
LoopBody.preheader: ; 解密循环开始
br label %LoopBody
LoopBody: ; 解密循环主体
%3 = phi i32 [ %23, %LoopEnd ], [ 0, %LoopBody.preheader ] ; 循环计数器
%4 = phi i8 [ %21, %LoopEnd ], [ 0, %LoopBody.preheader ] ; 解密中间值
%5 = getelementptr inbounds i8, ptr %0, i32 %3 ; 获取当前加密字符位置
%6 = load volatile i8, ptr %5, align 1 ; 加载加密字符
%7 = urem i32 %3, 17 ; 计算密钥索引
%8 = getelementptr inbounds i8, ptr %data, i32 %7 ; 获取密钥字符
%9 = load i8, ptr %8, align 1 ; 加载密钥字符
%10 = zext nneg i8 %9 to i32 ; 密钥字符转为整数
%11 = mul nuw nsw i32 %7, %10 ; 计算解密参数
%12 = and i32 %11, 1 ; 判断解密路径
%13 = icmp eq i32 %12, 0
br i1 %13, label %LoopBr0, label %LoopBr1
LoopBr0: ; 解密路径0
%14 = add i8 %6, %4 ; 加法解密步骤
%15 = xor i8 %14, %9 ; 异或解密步骤
%16 = xor i8 %15, -1 ; 取反解密步骤
br label %LoopEnd
LoopBr1: ; 解密路径1
%17 = sub i8 %6, %4 ; 减法解密步骤
%18 = xor i8 %17, %9 ; 异或解密步骤
%19 = sub i8 0, %18 ; 取负解密步骤
br label %LoopEnd
LoopEnd: ; 解密循环结束处理
%20 = phi i8 [ %16, %LoopBr0 ], [ %19, %LoopBr1 ] ; 选择解密结果
%21 = xor i8 %20, %9 ; 最终异或操作
%22 = getelementptr inbounds i8, ptr %plain_string, i32 %3 ; 获取结果存储位置
store i8 %21, ptr %22, align 1 ; 存储解密字符
%23 = add nuw nsw i32 %3, 1 ; 计数器递增
%24 = icmp eq i32 %23, 31 ; 检查循环结束条件
br i1 %24, label %UpdateDecStatus, label %LoopBody
UpdateDecStatus: ; 更新解密状态
store i32 1, ptr @dec_status_0.str, align 4 ; 标记为已解密
br label %Exit
Exit: ; 函数退出
ret void
}
; 字符串解密函数1 (与解密函数0类似,参数不同)
define private void @goron_decrypt_string_1(ptr nocapture %plain_string, ptr nocapture %data) {
Enter:
%0 = getelementptr inbounds i8, ptr %data, i32 21 ; 不同的偏移量
%1 = load i32, ptr @dec_status_1.str.1, align 4
%2 = icmp eq i32 %1, 1
br i1 %2, label %Exit, label %LoopBody.preheader
LoopBody.preheader:
br label %LoopBody
LoopBody:
%3 = phi i32 [ %23, %LoopEnd ], [ 0, %LoopBody.preheader ]
%4 = phi i8 [ %21, %LoopEnd ], [ 0, %LoopBody.preheader ]
%5 = getelementptr inbounds i8, ptr %0, i32 %3
%6 = load volatile i8, ptr %5, align 1
%7 = urem i32 %3, 21 ; 不同的模数
%8 = getelementptr inbounds i8, ptr %data, i32 %7
%9 = load i8, ptr %8, align 1
%10 = zext nneg i8 %9 to i32
%11 = mul nuw nsw i32 %7, %10
%12 = and i32 %11, 1
%13 = icmp eq i32 %12, 0
br i1 %13, label %LoopBr0, label %LoopBr1
LoopBr0:
%14 = add i8 %6, %4
%15 = xor i8 %14, %9
%16 = xor i8 %15, -1
br label %LoopEnd
LoopBr1:
%17 = sub i8 %6, %4
%18 = xor i8 %17, %9
%19 = sub i8 0, %18
br label %LoopEnd
LoopEnd:
%20 = phi i8 [ %16, %LoopBr0 ], [ %19, %LoopBr1 ]
%21 = xor i8 %20, %9
%22 = getelementptr inbounds i8, ptr %plain_string, i32 %3
store i8 %21, ptr %22, align 1
%23 = add nuw nsw i32 %3, 1
%24 = icmp eq i32 %23, 20 ; 不同的循环次数
br i1 %24, label %UpdateDecStatus, label %LoopBody
UpdateDecStatus:
store i32 1, ptr @dec_status_1.str.1, align 4
br label %Exit
Exit:
ret void
}
; 全局结构体变量初始化函数
define private void @__global_variable_initializer__ZL11struct_test(ptr nocapture %this) {
Enter:
%0 = load i32, ptr @dec_status__ZL11struct_test, align 4
%1 = icmp eq i32 %0, 1 ; 检查是否已初始化
br i1 %1, label %Exit, label %InitBlock
InitBlock: ; 初始化结构体
store i32 2, ptr @dec__ZL11struct_test, align 4 ; 设置结构体整型成员为2
%2 = getelementptr %struct.StructTest, ptr @dec__ZL11struct_test, i32 0, i32 1
%3 = getelementptr inbounds [141 x i8], ptr @EncryptedStringTable, i32 0, i32 27
call void @goron_decrypt_string_0(ptr @dec0.str, ptr %3) ; 解密字符串
store ptr @dec0.str, ptr %2, align 8 ; 设置结构体指针成员
store i32 1, ptr @dec_status__ZL11struct_test, align 4 ; 标记为已初始化
br label %Exit
Exit:
ret void
}
缺点
字符串解密线程不安全,可以继续迭代。
[培训]Windows内核深度攻防:从Hook技术到Rootkit实战!
最后于 2025-6-15 17:38
被不歪编辑
,原因:
赞赏
他的文章
- [原创]OLLVM (三)控制流平坦化 607
- [原创]OLLVM (二)条件跳转分支混淆 497
- [原创]OLLVM (一)字符串混淆 1025
- [原创]自定义Android15系统跟踪Java方法 3811
- [原创]不歪 - 自定义ROM文章索引贴 6874
赞赏
雪币:
留言: