amimo/goron

string encrypt pass是否有计划

Closed this issue · 49 comments

目前开源的针对字符串的pass相对来说都是转换成全局变量一次性还原,而非运行到该处时再还原,容易被破解

amimo commented

暂时还没有想到好的方案.有的话可能会尝试实现.

amimo commented

在cse分支上实现了个简单的c字符串加密,会到使用时再解密,大佬可以试试。

@amimo 我明天试试☺

@amimo 刚测试下 效果挺好的 不过原字符串没有进行抹掉,另外一个解密方法能inline的话应该是最好的☺

amimo commented

不过原字符串没有进行抹掉

这个要看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);

`

大佬可以考虑下处理全局的😄

amimo commented

针对引用删除我在原有代码上

得仔细测试下,我觉得直接删除风险好大.删除的时候可能还得考虑链接的问题,像extern的变量。

inline的话 可能我觉得比icall混淆好点把

如是Inline的代码后能使用indbr Pass混淆的话,icall跟Inline的强度应该差不多.用这种方式inline后,解密代码就没有得到混淆了.

@amimo 直接删除的我改动后测试了下自己之前的项目没有问题 字符串场景也挺多的 也确实需要多测试

amimo commented

@qtfreet00 我可能知道为啥明文变量还在了,你是不是全局变量混淆也启用了?应该是这个pass阻止编译器优化了.

@amimo 我是windows下编译集成到ndk的,cmake命令也只开了

`
cmake {
cppFlags "-frtti -fexceptions -fPIC -mllvm --irobf-cse -landroid"
cFlags "-mllvm --irobf-cse"

        }`
amimo commented

我测试了下,删除原始变量应该没有问题的.你试下这个提交dce

好的,明天我测试下,家里没编译环境🤗

目前还有个的就是全局定义的字符串未处理 应该是识别那边的问题 ,然后全局的也没办法做实时还原 我想到的也就是孤挺花那种在Constructor里还原

amimo commented

全局定义的字符串是像这种吗?const char *s = "xxxx";然后在函数里边使用,puts(s)?这种可以让编译器先优化成puts("xxxx");然后就可以混淆了.

@amimo 刚刚测试了下 静态变量 不论是全局还是局部 都不会被处理

amimo commented

你指的是我删除全局变量的提交无效吗?他应该跟你的代码一样才对.

不是,目前对局部变量的处理已经没有问题了,测试了下也没有异常

另外就是静态变量和全局变量都没有进行处理,函数内依然是明文状态

amimo commented

另外就是静态变量和全局变量都没有进行处理,函数内依然是明文状态

给个样本看看

demo.zip
demo上传了 包含测试代码和编译好的exe文件,可以直接ida看

编译命令:
clang.exe -mllvm -irobf-cse main.c

对了,我在字符串pass上增加了之前的inline,这个忽略

amimo commented

修改 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 测试无效 原本的加密也不进行了- -

amimo commented

我这么编译的

clang -O1 -mllvm -irobf-cse  main.c

image
除了ddddd都加密了,其他两个可以被外面引用,不能删除.如果把ddddd改成static,就看不到了.

@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下只有全局和局部静态变量不会被处理

amimo commented

是不是编译的debug包,你编译release的看下

@amimo 还真是,对比之前优化掉了全局静态变量 ,全局静态字符串数组会存在

amimo commented

static const char *xx[];这种类型的吗?如果使用的常量来引用的话,像xx[0],也可以混淆.如果不是那就没辙了.

amimo commented

想到另外一个问题,如果你要inline解密函数的话,你可以在ObfuscationOptions::skipFunction不把它过滤,这样可以Inline混淆后的代码.

针对这种static const char *xx[]; 和全局可被其它引用的字符串类型,可以单独使用像Armariris这样的混淆方案,虽然容易被破解,但仅仅只对这些类型进行混淆,其它的类型使用你这种方式,这样可以做到全部混淆

我今天尝试了下 在

处遍历所有GlobalVariable ,再二次获取字符串后使用Armariris测试demo没什么问题,可以全部混淆剩余的字符串,不过实现有些问题

amimo commented

是用constructor解密没有处理的字符串吗?用ctor解密的话可能还需要考虑ctor执行顺序的问题,ctor引用字符串我们没有处理,又在我们前面执行的话,字符串就没有解密.

StringEncryption.zip
这是我加的,单文件编译测试没问题,不过多文件会有重复定义的问题- -

amimo commented

我研究下

amimo commented

这应该是Armariris的实现吧.我发现网上已经有还原他的工具了.

对的,unicorn可以直接还原

amimo commented

我觉得没啥太大必要集成了

难度确实一般,不过目前goron需要处理支持下静态数组变量,遍历型的

amimo commented

等我有好想法了再来实现.
如果可以修改项目源码的话,对与字符串数组的使用,可以用类似下面的帮助函数绕过去

static const char *status_code_to_string(int status) {
    switch (status) {
        case 0: return "aaa";
        case 1: return "bbb";
        default: return "xxx";
    }
}
amimo commented

@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}, }

此类的字符串是无法处理的

amimo commented

改成下面这样试试

static const JNINativeMethod gMethods[] = { {"a", "(I)Z", (void *) test}, }

如果这样操作的话 需要在定义的时候为数组,使用的时候先强转为指针

amimo commented

没看明白.安卓都是这么定义的.AndroidRuntime.cpp android_app_Activity.cpp
我当前实现是在每处使用重建这些变量,如果他们被修改的话,这些改动会在重建时被我清除.如果你非常熟悉自己代码, 确定这些变量不会被修改,你可以修改isValidToEncrypt,把条件放宽.

是我参数类型没定义好 全局的注册函数数组修改成
static const JNINativeMethod gMethods[] 后,传参RegisterNative的时候也需要把参数修改成const,不然编译会出问题

代码写的不规范的问题

amimo commented

我后面也写了个测试,用const定义是没有问题. 等我再提交个保存解密状态的commit,这个加密就比较完善了.

amimo commented

提交了个解密优化,没有并发的情况下,字符串只解密一次,变量也只重建一次.这个issue是不是可以关了?或者你还有什么好的想法.

非常感谢,这个issue下面的讨论解决了我的问题