目前找到了如下几种开源的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编辑
,原因: