对目前开源OLLVM的字符串加密混淆学习笔记
目前找到了如下几种开源的OLLVM字符串加密源码
armariris
yag00
hikari
goron
学习了几天llvm了, 逐一分析下目前几种字符串加密方案的原理,流程和测试
今天先分析了前面三种, 我都集成到了llvm4.0一起编译, goron是基于8.0写的目前还在编译中...
学习记录: Armariris:遍历Module所有的GlobalVariable,
根据名称判断是否为字符串
.str
.str.
并且过滤掉特定Section
Llvm.metadata
__objec_methname
然后获取原GV的数据dyn_cast(gv->getInitializer())->getRawDataValues().data()
加密后生成新的GV, 并用replaceAllUsesWith替换成新GV
并创建一个针对当前Module所有新GV的初始化函数(appendToGlobalCtors)来解密,
之后调用gv->eraseFromParent()删除原GV
测试
可加密所有类型的ANSI, UNICODE字符串常量, 但是无法加密字符串数组, 因为字符串数组的全局符号名不是.str开头. Yag00:遍历Module所有的GlobalVariable,
并根据CDS为isString和isCString进行过滤
并用getAsString或getAsCString获取原GGV数据进行加密
然后用加密的数据创建名称前缀为.encstr的新GV并用过replaceAllUsesWith替换
之后调用eraseFromParent删除原GV
遍历Module中所有Function的BaiscBlock的Instruction, 根据指令类型进行处理
dyn_cast:
遍历Call指令所有参数
llvm::dyn_cast 过滤CE类型的参数
用constExpr->getOpcode()过滤Instruction::GetElementPtr类型的参数
用GEP的getPointerOperand()->getName()过滤加密的GV
然后通过setArgOperand用新生成的Alloc指令Value替换Call指令的参数
dyn_cast:
用 Load->getPointerOperand()获取Load指令操作数
用dyn_cast过滤操作数为GV的Load指令
用dyn_cast过滤CE类型的GV
用constExpr->getOpcode()过滤操作为Instruction::GetElementPtr类型的GV
用GEP的getPointerOperand()->getName()过滤加密的GV
然后通过replaceAllUsesWith用新生成的Alloc指令Value替换原Load指令的Value
dyn_cast 同dyn_cast
Alloc指令生成:
通过new AllocaInst 生成, 并返回Value供后续替换使用
循环解密的字符串数组:
用新生成的Alloc指令的Value和字符串索引创建GetElementPtrInst::CreateInBounds
用字符串数组和索引创建GetElementPtrInst::Create获取一个加密字符
用new LoadInst加载一个加密字符
用BinaryOperator::CreateXor解密字符
new StoreInst存储解密字符到Alloc
测试
可加密所有类型的ANSI字符串, 包括const字符串数组, 但是不能加密非const字符串数组.
也无法加密unicode字符串, 猜测可能是yag00使用的isString和isCString过滤导致
isString判断是否为i8数组, isCString判断是否为i8数组+null, 而Unicode是i16数组+null
无法处理char局部变量引用, IR里是store GEP, 但是yag00只处理了load,call,invoke三种指令. (可以处理char全局变量, IR里是load(GV)指令)
无法处理call对结构体字符串char的引用, 因为结构体中的char定义在常量, 无论是全局结构变量的load GEP还是局部结构体的llvm.memcpy都不会引用到字符串常量符号.
无法处理对字符串数组读取的引用, IR中是load(GEP)指令, 而非load(GV)指令 Hikari: 代码写的最复杂的了遍历Module中所有的Function
为当前Function生成一个解密状态GV并存入encstatus
循环遍历Function中的BasicBlock中的Instruction中的Operand操作数
过滤所有dyn_cast的Operand并存入Globals表,
并将Instruction存入Usrs表
如果为dyn_cast则将Operand再存入Users表
遍历Globals表并过滤掉以下GV
llvm.metadata
objc
OBJC
过滤GV为struct.NSConstantString_tag类型并存入objCStringg表,
并将GV结构的第2个成员(字符串常量GV)存入rawStrings表
过滤isa类型的GV并存入rawStrings表
过滤isa类型的GV并遍历Operands
将dyn_cast类型的Operands存入Globals表
遍历rawStrings表:
过滤掉所有ZeroValue和NullValue
过滤非IntegerType的GV (ConstantDataSequential)
根据IntergerType的类型进行处理, 支持以下Type
Type::getInt8Ty
Type::getInt16Ty
Type::getInt32Ty
Type::getInt64Ty
生成加密秘钥表KeyConst和加密数据表EncryptedConst,
将KeyConst存入GV2Keys表
并用EncryptedConst生成EncryptedRawGV并存入old2new表
遍历objCStrings表:
过滤掉不在oldrawString表里的OC字符串GV
通过ConstantExpr::getInBoundsGetElementPtr用old2new创建一个加密后的Constant
用加密后的Constant创建一个加密后的ConstantStruct
用加密后的ConstantStruct创建一个加密后的GV: EncryptedOCGV并存入old2new表
遍历Users表:
使用过replaceUsesOfWith将old2new遍历一遍并替换加密前后的GV引用
使用removeDeadConstantUsers将old2new中无引用的GV删除
遍历objCStrings表:
使用dropAllReferences和eraseFromParent清理GV
遍历old2new表:
使用removeDeadConstantUsers和dropAllReferences和eraseFromParent清理GV
从Function的EntryBlock(A) 分割出 PrecedingBlock(C),
并在中间插入BasicBlock: StringDecryptionBB(B)
通过BranchInst::Create创建一个到BasicBlock(B)的BR,
并使用ReplaceInstWithInst(A->getTerminator() 连接到BasicBlock(A)的结尾
调用HandleDecryptionBlock用B,C,GV2Keys生成解密块
用IRB.CreateLoad原子加载解密状态GVoadEncryptionStatus(encstatus)
用IRB.CreateICmpEQ和BranchInst::Create在A创建根据解密状态到B或C的条件分支
用IRBC.CreateStore在C中创建对解密状态的原子写操作
生成解密块:
遍历GV2Keys表获取加密KEY表和GV(ConstantDataArray)
循环加密表创建对GV的Load(GEP)和Xor及Store的解密操作
用IRB.CreateBr(C)创建到C的跳转
好了, fuck的Hikari终于分析完了, 代码耦合成一坨屎了, 格式一团糟(AS查看的源码)
测试
可以加密字符串数组, 包括const和非const
无法加密全局char或wchar变量引用的字符串常量
无法加密结构变量中的字符串常量定义, 包括全局结构变量和局部结构变量
下面附上测试源码: 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
| int printf(const char *format, ...)
{
return *(int*)format;
}
struct StringStruct {
int i;
const char *s;
};
static const struct StringStruct global_struct_string[] = {
{123, "string in global struct123"},
{456, "string in global struct456"}
};
char* global_var_string1 = "string in global var1";
char* global_var_string2 = "string in global var2";
char global_array_string[] = "string in global array";
const char const_global_array_string[] = "const string in global array";
wchar_t* global_unicode_string = L"unicode global string";
int main(int argc, char *argv[])
{
printf("", L"unicode arg string");
printf("", global_unicode_string);
printf((char*)global_array_string[0]);
printf(global_array_string);
printf((char*)const_global_array_string[0]);
printf(const_global_array_string);
printf("string in arg1");
printf("string in arg2", "string in arg2");
printf(global_var_string1);
printf(global_var_string2, global_var_string2);
char* stack_var_string = "string in stack var";
printf(stack_var_string);
char* stack_var_string2 = "string in stack var2";
printf(stack_var_string2, stack_var_string2);
printf(global_struct_string[0].s);
printf(global_struct_string[1].s, global_struct_string[1].s);
struct StringStruct stack_struct_string = {
789,
"string in stack struct"
};
printf(stack_struct_string.s);
printf(stack_struct_string.s, stack_struct_string.s);
return 0;
}
|
|