首页
社区
课程
招聘
对目前开源OLLVM的字符串加密混淆学习笔记
发表于: 2020-10-29 03:16 8792

对目前开源OLLVM的字符串加密混淆学习笔记

2020-10-29 03:16
8792

目前找到了如下几种开源的OLLVM字符串加密源码
armariris
yag00
hikari
goron
学习了几天llvm了, 逐一分析下目前几种字符串加密方案的原理,流程和测试
今天先分析了前面三种, 我都集成到了llvm4.0一起编译, goron是基于8.0写的目前还在编译中...

学习记录:

遍历Module所有<ConstantDataSequential>的GlobalVariable,
根据名称判断是否为字符串
.str
.str.
并且过滤掉特定Section
Llvm.metadata
__objec_methname
然后获取原GV的数据dyn_cast<ConstantDataSequential>(gv->getInitializer())->getRawDataValues().data()
加密后生成新的GV, 并用replaceAllUsesWith替换成新GV
并创建一个针对当前Module所有新GV的初始化函数(appendToGlobalCtors)来解密,
之后调用gv->eraseFromParent()删除原GV

测试
可加密所有类型的ANSI, UNICODE字符串常量, 但是无法加密字符串数组, 因为字符串数组的全局符号名不是.str开头.

遍历Module所有<ConstantDataSequential>的GlobalVariable,
并根据CDS为isString和isCString进行过滤
并用getAsString或getAsCString获取原GGV数据进行加密
然后用加密的数据创建名称前缀为.encstr的新GV并用过replaceAllUsesWith替换
之后调用eraseFromParent删除原GV

遍历Module中所有Function的BaiscBlock的Instruction, 根据指令类型进行处理
dyn_cast<CallInst>:
遍历Call指令所有参数
llvm::dyn_cast<llvm::ConstantExpr> 过滤CE类型的参数
用constExpr->getOpcode()过滤Instruction::GetElementPtr类型的参数
用GEP的getPointerOperand()->getName()过滤加密的GV
然后通过setArgOperand用新生成的Alloc指令Value替换Call指令的参数

dyn_cast<LoadInst>:
用 Load->getPointerOperand()获取Load指令操作数
用dyn_cast<GlobalVariable>过滤操作数为GV的Load指令
用dyn_cast<ConstantExpr>过滤CE类型的GV
用constExpr->getOpcode()过滤操作为Instruction::GetElementPtr类型的GV
用GEP的getPointerOperand()->getName()过滤加密的GV
然后通过replaceAllUsesWith用新生成的Alloc指令Value替换原Load指令的Value

dyn_cast<InvokeInst> 同dyn_cast<CallInst>
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)指令

遍历Module中所有的Function
为当前Function生成一个解密状态GV并存入encstatus
循环遍历Function中的BasicBlock中的Instruction中的Operand操作数
过滤所有dyn_cast<GlobalVariable>的Operand并存入Globals表,
并将Instruction存入Usrs表
如果为dyn_cast<User>则将Operand再存入Users表

遍历Globals表并过滤掉以下GV
llvm.metadata
objc
OBJC
过滤GV为struct.
NSConstantString_tag类型并存入objCStringg表,
并将GV结构的第2个成员(字符串常量GV)存入rawStrings表
过滤isa<ConstantDataSequential>类型的GV并存入rawStrings表
过滤isa<ConstantArray>类型的GV并遍历Operands
将dyn_cast<GlobalVariable>类型的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:LoadEncryptionStatus(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变量引用的字符串常量
无法加密结构变量中的字符串常量定义, 包括全局结构变量和局部结构变量

下面附上测试源码:

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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;
}
int printf(const char *format, ...)
{
    return *(int*)format;
 
}
 
struct StringStruct {
    int i;
    const char *s;
};
 

[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!

最后于 2020-10-29 03:22 被wx_tuancc编辑 ,原因:
收藏
免费 4
支持
分享
最新回复 (3)
雪    币: 1062
活跃值: (619)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
2
您好,我最近在学习llvm,发现在使用armariris对unicode字符串进行加密时clang报错了,错误为clang frontend command failed due to signal(use -v to see invocation)。请问一下,这个可能是什么造成的呢
2021-1-15 16:16
0
雪    币: 477
活跃值: (1412)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
3
@张总
2021-7-7 20:09
0
雪    币: 213
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
4
漆寒 您好,我最近在学习llvm,发现在使用armariris对unicode字符串进行加密时clang报错了,错误为clang frontend command failed due to signal( ...
我也与遇到了同样的问题,请问有解决办法吗?
2023-7-7 18:21
0
游客
登录 | 注册 方可回帖
返回
//