| 对目前开源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_castAlloc指令生成:
 通过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表并过滤掉以下GVllvm.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原子加载解密状态GV
  oadEncryptionStatus(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;
 }
 
 
 | 
 
 |