string encrypt pass是否有计划
Closed this issue · 49 comments
目前开源的针对字符串的pass相对来说都是转换成全局变量一次性还原,而非运行到该处时再还原,容易被破解
暂时还没有想到好的方案.有的话可能会尝试实现.
在cse分支上实现了个简单的c字符串加密,会到使用时再解密,大佬可以试试。
不过原字符串没有进行抹掉
这个要看llvm有没有优化掉.如果这个变量可以被外部引用,或者是存在我没有处理的使用,如字符串数组,那llvm就不会把他删除.
inline的话应该是最好的
不inline是因为我是想使用icall这个Pass对解密函数的调用进行混淆.
@amimo 针对引用删除我在原有代码上
`
Value *OutBuf = IRB.CreateBitCast(Entry->DecGV, IRB.getInt8PtrTy());
Value *Data = IRB.CreateInBoundsGEP(EncryptedStringTable, {IRB.getInt32(0), IRB.getInt32(Entry->Offset)});
IRB.CreateCall(Entry->DecFunc, {OutBuf, Data});
Inst->replaceUsesOfWith(GV, Entry->DecGV);
GV->removeDeadConstantUsers();
if (GV->getNumUses() == 0) {
GV->dropAllReferences();
GV->eraseFromParent();
}
Changed = true;
`
这样可以删除掉原有的局部变量,全局常量字符串或数组本身也未处理 张总之前的光也未处理全局的,一定程度上确实不太好处理,可能参考孤挺花的把全局常量统一进行加密后统一还原可能会好点
inline的话 可能我觉得比icall混淆好点把
`
Function *StringEncryption::buildDecryptFunction(Module *M, const StringEncryption::CSPEntry *Entry) {
LLVMContext &Ctx = M->getContext();
IRBuilder<> IRB(Ctx);
FunctionType *FuncTy = TypeBuilder<void(int8_t *, int8_t *), false>::get(Ctx);
Function *DecFunc =
Function::Create(FuncTy, GlobalValue::PrivateLinkage, "goron_decrypt_string_" + Twine::utohexstr(Entry->ID), M);
DecFunc->addFnAttr(Attribute::AlwaysInline);
`
大佬可以考虑下处理全局的😄
针对引用删除我在原有代码上
得仔细测试下,我觉得直接删除风险好大.删除的时候可能还得考虑链接的问题,像extern的变量。
inline的话 可能我觉得比icall混淆好点把
如是Inline的代码后能使用indbr Pass混淆的话,icall跟Inline的强度应该差不多.用这种方式inline后,解密代码就没有得到混淆了.
@qtfreet00 我可能知道为啥明文变量还在了,你是不是全局变量混淆也启用了?应该是这个pass阻止编译器优化了.
@amimo 我是windows下编译集成到ndk的,cmake命令也只开了
`
cmake {
cppFlags "-frtti -fexceptions -fPIC -mllvm --irobf-cse -landroid"
cFlags "-mllvm --irobf-cse"
}`
好的,明天我测试下,家里没编译环境🤗
目前还有个的就是全局定义的字符串未处理 应该是识别那边的问题 ,然后全局的也没办法做实时还原 我想到的也就是孤挺花那种在Constructor里还原
全局定义的字符串是像这种吗?const char *s = "xxxx";然后在函数里边使用,puts(s)?这种可以让编译器先优化成puts("xxxx");然后就可以混淆了.
你指的是我删除全局变量的提交无效吗?他应该跟你的代码一样才对.
不是,目前对局部变量的处理已经没有问题了,测试了下也没有异常
另外就是静态变量和全局变量都没有进行处理,函数内依然是明文状态
另外就是静态变量和全局变量都没有进行处理,函数内依然是明文状态
给个样本看看
demo.zip
demo上传了 包含测试代码和编译好的exe文件,可以直接ida看
编译命令:
clang.exe -mllvm -irobf-cse main.c
对了,我在字符串pass上增加了之前的inline,这个忽略
修改 llvm/lib/Transforms/IPO/PassManagerBuilder.cpp 把MPM.add(createObfuscationPassManager());
移动到"MPM.add(createGlobalOptimizerPass()); // Optimize out global vars" 这行后面试试看.
MPM.add(createGlobalOptimizerPass()); // Optimize out global vars
MPM.add(createObfuscationPassManager());
@amimo 我测试了下Windows下的demo确实可以
MPM.add(createGlobalOptimizerPass()); // Optimize out global vars MPM.add(createObfuscationPassManager());
但将编译后的clang移植到ndk里,就所有字符串都不会被处理,不太清楚原因
` externalNativeBuild {
cmake {
cppFlags "-O1 -frtti -fexceptions -fPIC -mllvm --irobf-cse -landroid -lEGL -lGLESv2"
cFlags " -O1 -mllvm --irobf-cse"
}
ndk {
abiFilters "armeabi","armeabi-v7a","arm64-v8a","x86","x86_64"
}`
更改之前
MPM.add(createGlobalOptimizerPass()); // Optimize out global vars MPM.add(createObfuscationPassManager());
ndk下只有全局和局部静态变量不会被处理
是不是编译的debug包,你编译release的看下
static const char *xx[];这种类型的吗?如果使用的常量来引用的话,像xx[0],也可以混淆.如果不是那就没辙了.
想到另外一个问题,如果你要inline解密函数的话,你可以在ObfuscationOptions::skipFunction不把它过滤,这样可以Inline混淆后的代码.
针对这种static const char *xx[]; 和全局可被其它引用的字符串类型,可以单独使用像Armariris这样的混淆方案,虽然容易被破解,但仅仅只对这些类型进行混淆,其它的类型使用你这种方式,这样可以做到全部混淆
我今天尝试了下 在
处遍历所有GlobalVariable ,再二次获取字符串后使用Armariris测试demo没什么问题,可以全部混淆剩余的字符串,不过实现有些问题
是用constructor解密没有处理的字符串吗?用ctor解密的话可能还需要考虑ctor执行顺序的问题,ctor引用字符串我们没有处理,又在我们前面执行的话,字符串就没有解密.
StringEncryption.zip
这是我加的,单文件编译测试没问题,不过多文件会有重复定义的问题- -
我研究下
这应该是Armariris的实现吧.我发现网上已经有还原他的工具了.
对的,unicorn可以直接还原
我觉得没啥太大必要集成了
难度确实一般,不过目前goron需要处理支持下静态数组变量,遍历型的
等我有好想法了再来实现.
如果可以修改项目源码的话,对与字符串数组的使用,可以用类似下面的帮助函数绕过去
static const char *status_code_to_string(int status) {
switch (status) {
case 0: return "aaa";
case 1: return "bbb";
default: return "xxx";
}
}
@qtfreet00 大佬, llvm-7.1.0-acse分支可以处理数组和结构体了,你试试.
static const char * const xx[] = {...};
static const struct ConstantStructure xx[] = {};
static const struct ConstantStructure xx = {};
我明天试试,😄
测试了下 针对android中的
static JNINativeMethod gMethods[] = { {"a", "(I)Z", (void *) test}, }
此类的字符串是无法处理的
改成下面这样试试
static const JNINativeMethod gMethods[] = { {"a", "(I)Z", (void *) test}, }
如果这样操作的话 需要在定义的时候为数组,使用的时候先强转为指针
没看明白.安卓都是这么定义的.AndroidRuntime.cpp android_app_Activity.cpp
我当前实现是在每处使用重建这些变量,如果他们被修改的话,这些改动会在重建时被我清除.如果你非常熟悉自己代码, 确定这些变量不会被修改,你可以修改isValidToEncrypt,把条件放宽.
是我参数类型没定义好 全局的注册函数数组修改成
static const JNINativeMethod gMethods[]
后,传参RegisterNative的时候也需要把参数修改成const,不然编译会出问题
代码写的不规范的问题
我后面也写了个测试,用const定义是没有问题. 等我再提交个保存解密状态的commit,这个加密就比较完善了.
提交了个解密优化,没有并发的情况下,字符串只解密一次,变量也只重建一次.这个issue是不是可以关了?或者你还有什么好的想法.
非常感谢,这个issue下面的讨论解决了我的问题