使用ollvm自定义简单的字符串加密

admin 2021年4月30日17:58:50评论177 views字数 14043阅读46分48秒阅读模式
使用ollvm自定义简单的字符串加密

本文为看雪论优秀文章

看雪论坛作者ID:misskings

题目出自3W班9月的题目用ollvm9.0实现字符串简单加密。

题目主要是为了能熟练ollvm中如何进行一个简单的加密,以及c++部分怎么生成对应的IR指令来达到像c++函数效果。所以主要我们的思路可以切换成:

1、首先确定加密的核心逻辑并用C++实现
2、根据C++的算法。生成一份IR指令来为我们提供参考
3、ollvm里面如何用C++生成相应的IR指令
4、根据参考的IR指令来生成我们需要的加密和解密函数


做完这个题目,基本就可以对ollvm的工作原理有一定的了解,并且改造属于自己的加密或者混淆了。
 
先贴上测试好的结果: https://github.com/dqzg12300/kOLLVM.git

想要写一个字符串加密的pass,第一步就是先实现一遍c++的算法流程,然后再看一看生成的IR文件,然后再写对应的加密pass,下面看一个自己实现的简单c++字符串加密。
#include <stdio.h>#include <cstring>#include <string> int main(int  argc, char** argv) {    //加密    std::string str1="hello world!!!";    //这里是随机的key,先写固定,真实实现的时候再每个字节使用一个随机key    int randkey=11;      //加密复杂度    int kstr_size=10;    int enclen=randkey+kstr_size;    char encres[str1.size()];    int idx=0;    memset(encres,0,enclen);    //这里大概就是遍历字符串,每个字符根据加密复杂度进行一定数量迭代异或,最后一次的迭代使用取反再异或    for(int i=0;i<str1.size();i++){        printf("cur: %x rn",str1[i]);        for(int y=randkey;y<enclen;y++){            if(y==randkey){                encres[i]=str1[i]^y;            }else if(y==enclen-1){                encres[i]=encres[i]^(~y);            }else{                encres[i]=encres[i]^y;            }            printf("%x ",encres[i]);            idx++;        }        printf("rn");    }    printf("encdata: %srn",encres);    //下面是解密函数    char decres[str1.size()];    for(int i=0;i<str1.size();i++){        printf("cur enc: %x rn",encres[i]);        for(int y=enclen-1;y>=randkey;y--){            if(y==enclen-1){                decres[i]=encres[i]^(~y);            }else{                decres[i]=decres[i]^y;            }            printf("%x ",decres[i]);        }        printf("rn");    }    printf("res: %srn",decres);    return 0;}

这个简单加密的意思,就是根据复杂度参数。来进行一定次数的迭代,将当前字符每次都异或一下,最后一次是先去反,再异或,解密就是反之。测试结果能正常加密和解密后,我们就先输出一份ir文件。看看在ir中间语言中是如何进行加密和解密的。
 
clang -emit-llvm -S main.cpp -o main.ll

生成好对应的ir文件后,我们开始写这个加密pass,然后再写的过程中,根据逻辑需要,去ir中找对应的指令处理方式。
 
在ir文件中的层级划分:Module(模块)的下一层是若干Function(函数),然后在Function的下一层是若干BasicBlock(基本快),再BasicBlock的下一层是若干Instruction(指令块)。
 
现在准备就绪,下面开始先准备一个加密的pass,基本代码如下:
#include <kllvm/Transforms/Obfuscation/Utils.h>#include "kllvm/Transforms/Obfuscation/KStringEncode.h" #include <string>using namespace llvm; namespace {      //加密复杂度    const int defaultKStringSize = 0x10;    static cl::opt<int>            KStringSize("kstr_size", cl::desc("Choose the probability [%] each basic blocks will be obfuscated by the -kstr pass"), cl::value_desc("king string encode Encryption length"), cl::init(defaultKStringSize), cl::Optional);     struct KStringEncode: public FunctionPass{        static char ID; // Pass identification        bool flag;        KStringEncode() : FunctionPass(ID) {}        KStringEncode(bool flag) : FunctionPass(ID) {this->flag = flag; KStringEncode();}        virtual bool runOnFunction(Function &F){              //先检查加密复杂度是否在合法范围            if ( !((KStringSize > 0) && (KStringSize <= 100)) ) {                errs()<<"KStringEncode application basic blocks percentage -kstr_size=x must be 0 < x <= 100";                return false;            }            if(toObfuscate(flag,&F,"kstr")) {                kstr(F);            }            return false;        }        void kstr(Function& func){            //todo 这里再写具体的pass逻辑        }    }; }char KStringEncode::ID = 0;static RegisterPass<KStringEncode> X("kstr", "inserting bogus control flow"); Pass *llvm::createKStringEncode() {    return new KStringEncode();} Pass *llvm::createKStringEncode(bool flag) {    return new KStringEncode(flag);}

这里准备好了pass的基本代码后,最后就剩下最重要的核心逻辑,如何把c++的加密方式。在pass中实现,我们的功能是实现字符串加密,那么第一步应该是取得这个函数中的全部字符串,那么我们先看看ir中字符串的特征:
@.str = private unnamed_addr constant [15 x i8] c"hello world!!!0", align 1

可以看到,这个str是一个操作数,想要获取全部字符串,就得先遍历所有指令块中的操作数。然后再根据字符串的特征来进行过滤。下面先看如何遍历所有指令块:
void kstr(Function& func){            for(BasicBlock& bb:func){                for(Instruction& ins :bb){                    for(Value* val:ins.operands()){                        Value* stripOp=val->stripPointerCasts();                        if(stripOp->getName().contains(".str")){                            errs()<<ins<<"n";                            errs()<<*val<<"n";                            errs()<<*stripOp<<"n";                        }                    }                }            }        }

上面遍历了函数中的所有基本快,然后遍历所有指令块,然后遍历所有操作数,然后获取操作数的值,判断该操作数是否是一个字符串,并且打印这个指令块,操作数,以及取到的操作数的值,下面看看打印的结果:
  store i8* getelementptr inbounds ([7 x i8], [7 x i8]* @.str, i64 0, i64 0), i8** %str, align 8i8* getelementptr inbounds ([7 x i8], [7 x i8]* @.str, i64 0, i64 0)@.str = private unnamed_addr constant [7 x i8] c"kanxue0", align 1

那么看到了,我们想获取的字符串是在stripOp中。那么接下来就把所有字符串全部获取出来并转换成string。
//封装一个转换操作数值为字符串的函数std::string ConvertOpToString(Value* op){            GlobalVariable* globalVar= dyn_cast<GlobalVariable>(op);            if(!globalVar){                errs()<<"dyn cast gloabl err";                return "";            }            ConstantDataSequential* cds=dyn_cast<ConstantDataSequential>(globalVar->getInitializer());            if(!cds){                errs()<<"dyn cast constant data err";                return "";            }            return cds->getRawDataValues();;        }         void kstr(Function& func){            for(BasicBlock& bb:func){                for(Instruction& ins :bb){                    for(Value* val:ins.operands()){                        Value* stripOp=val->stripPointerCasts();                        if(stripOp->getName().contains(".str")){                            std::string strdata=ConvertOpToString(stripOp);                            errs()<<strdata<<"n";                        }                    }                }            }        }

之前看到的字符串的ir代码看到所有字符串都是全局的,所以要先转换成全局的对象,然后再转换成数值。然后看这里的打印结果。
kanxuehello ollvm:%d

获取到所有的字符串了之后。接下来。我们要先把这个字符串加密,然后再用插入指令块来进行解密。下面继续完善,先把之前搞好的加密算法迁移进来。
//转换字符串        std::string ConvertOpToString(Value* op){            GlobalVariable* globalVar= dyn_cast<GlobalVariable>(op);            if(!globalVar){                errs()<<"dyn cast gloabl err";                return "";            }            ConstantDataSequential* cds=dyn_cast<ConstantDataSequential>(globalVar->getInitializer());            if(!cds){                errs()<<"dyn cast constant data err";                return "";            }            return cds->getRawDataValues();;        }         void kstr(Function& func){            for(BasicBlock& bb:func){                for(Instruction& ins :bb){                    for(Value* val:ins.operands()){                        Value* stripOp=val->stripPointerCasts();                        if(stripOp->getName().contains(".str")){                            std::string strdata=ConvertOpToString(stripOp);                            errs()<<strdata<<"n";                             //加密流程                            uint8_t keys[strdata.size()];                            char encres[strdata.size()];                            int idx=0;                            memset(encres,0,strdata.size());                            for(int i=0;i<strdata.size();i++){                                uint8_t randkey=llvm::cryptoutils->get_uint8_t();                                keys[i]=randkey;                                int enclen=randkey+defaultKStringSize;                                for(int y=randkey;y<enclen;y++){                                    if(y==randkey){                                        encres[i]=strdata[i]^y;                                    }else if(y==enclen-1){                                        encres[i]=encres[i]^(~y);                                    }else{                                        encres[i]=encres[i]^y;                                    }                                    idx++;                                }                            }                        }                    }                }            }        }

这里大致流程和之前一样。只是key我们装起来了。然后每个字节处理都随机一次key。接下来的处理就是插入指令块来对这个加密数据encres进行解密还原处理。
 
我们想要处理这个加密的数据,首先要先创建一个内存指令,来存放这个加密后的数据。然后再对加密后的数据遍历。进行还原。所以,我们的下一步先创建一个BitCastInst。并且我们需要用一个int8的array来给这个内存指令进行赋值。下面的代码是先创建array指令,然后用array指令创建一个内存指令:
ArrayType* arrType=ArrayType::get(Type::getInt8Ty(func.getContext()),strdata.size());                            AllocaInst* arrayInst=new AllocaInst(arrType,0,nullptr,1,Twine(stripOp->getName()+".arr"),&ins);                            BitCastInst* bitInst=new BitCastInst(arrayInst,Type::getInt8PtrTy(func.getParent()->getContext()),Twine(stripOp->getName()+"bitcast"),&ins);

上面的就是先创建一个int8的array类型,然后用这个类型创建一个array,然后再用这个array创建内存指令,这些指令都插入在遍历到字符串指令的当前行的前方。这个bitcast将用来存放加密后的字符串数据。
 
接下来就是加密的逻辑处理。和我们之前c++的流程一样,只不过这里需要换成插入指令块的形式来进行加密数据的还原,我直接贴上解密的代码部分,然后里面有详细的注释。
ArrayType* arrType=ArrayType::get(Type::getInt8Ty(func.getContext()),strdata.size());AllocaInst* arrayInst=new AllocaInst(arrType,0,nullptr,1,Twine(stripOp->getName()+".arr"),&ins);BitCastInst* bitInst=new BitCastInst(arrayInst,Type::getInt8PtrTy(func.getParent()->getContext()),Twine(stripOp->getName()+".bitcast"),&ins);//创建一个对象用来存放当前加密字节解密时每次异或的结果AllocaInst* eor_res=new AllocaInst(Type::getInt8Ty(func.getContext()),0,nullptr,1,Twine(stripOp->getName()+".alloc.res"),&ins);for(int i=0;i<strdata.size();i++){    uint8_t randkey=keys[i];    int enclen=randkey+defaultKStringSize;    ConstantInt* enc_const=ConstantInt::get(Type::getInt8Ty(func.getContext()),encres[i]);    //用来存放解密结果的bitcat    ConstantInt* i_const=ConstantInt::get(Type::getInt8Ty(func.getContext()),i);    GetElementPtrInst* element=GetElementPtrInst::CreateInBounds(bitInst,i_const);    element->insertBefore(&ins);    StoreInst* last_store=nullptr;    for(int y=enclen-1;y>=randkey;y--){        /*下面是获取y的指令块*/        //先是创建一个数值y        ConstantInt *eor_data = ConstantInt::get(Type::getInt8Ty(func.getContext()),y);        //申请一个int8的内存来存放数值y        AllocaInst* eor_alloc=new AllocaInst(Type::getInt8Ty(func.getContext()),0,nullptr,1,Twine(stripOp->getName()+".alloc.y"),&ins);        //将数值y赋值给申请的内存空间        StoreInst* store_eor=new StoreInst(eor_data,eor_alloc);        store_eor->insertAfter(eor_alloc);        //从内存空间中加载里面的数值y        LoadInst* eor_load=new LoadInst(eor_alloc,"");        eor_load->insertAfter(store_eor);        //如果是第一次异或        if(y==enclen-1){            //然后进行取反计算            BinaryOperator* binNotOp=BinaryOperator::CreateNot(eor_load);            binNotOp->insertAfter(eor_load);              //然后异或            BinaryOperator* binXorOp=BinaryOperator::CreateXor(enc_const,binNotOp);            binXorOp->insertAfter(binNotOp);            //将加密字节设置为上次异或的结果            StoreInst* store_eor_res=new StoreInst(binXorOp,eor_res);            store_eor_res->insertAfter(store_data);        }else{            //加载获取上次异或的结果            LoadInst* eor_load_res=new LoadInst(eor_res,stripOp->getName()+".load");            eor_load_res->insertAfter(store_eor);            //然后再进行异或计算            BinaryOperator* binXorOp=BinaryOperator::CreateXor(eor_load_res,eor_load);            binXorOp->insertAfter(eor_load);            //将计算后的结果存放回数组中            StoreInst* store_data=new StoreInst(binXorOp,eor_res);            store_data->insertAfter(binXorOp);            //当循环到最后一次时,获取一下最后一次赋值的指令块地址。方便后面接着往后插指令块            if(y==randkey){                last_store=store_data;            }        }    }      //读取这个字节经过多次异或后的最终结果    LoadInst* dec_res=new LoadInst(eor_res,stripOp->getName()+".dec.res");    dec_res->insertAfter(last_store);    //将这个结果写入到前面用来存放的解密结果bitcat处    StoreInst* store_data=new StoreInst(dec_res,element);    store_data->insertAfter(dec_res);}

上面就是把c++的解密流程用插入指令块的方式实现的方式。流程比较繁琐,但是大概意思是差不多的。
 
最后这里完成后,我们就可以删除指令块中的字符串明文部分。然后只保留密文。
//将字符串操作数替换为我们准备好的解密结果的bitInstval->replaceAllUsesWith(bitInst);//然后再删掉之前我们获取到的字符串的明文部分。这样就只有密文数据和密文解密的流程,最后动态执行拿到解密字符串了GlobalVariable* globalVar= dyn_cast<GlobalVariable>(stripOp);globalVar->eraseFromParent();

到这里整个流程就完成了。这里还有一个点需要注意的是,由于字符串的特性,当使用了多个相同的字符串,实际在汇编层的代码中,会优化为一个字符串,所以在字符串加密的时候,我们要留意解密字符串的作用域。下面举一个例子:
int main(int  argc, char** argv) {    int a = argc;    if(a == 0)        printf("hello");    else        printf("hello");    return 0;}

这个例子中使用了两个hello。如果我们在使用这个字符串时,调用的解密。那么下面else中的代码则会无法访问到bitcat。

因为不在同一个作用域,所以为了防止出现这种情况,我在解密时再做一个特殊的处理,我们先获取第一个指令块的位置,然后所有的字符串解密指令块,都插入在最开始的位置,这样就不会出现作用域的问题了。最后贴上完整代码:

//// Created by king on 2020/10/7.// #include <kllvm/Transforms/Obfuscation/Utils.h>#include "kllvm/Transforms/Obfuscation/KStringEncode.h" #include <string>using namespace llvm; namespace {    const int defaultKStringSize = 0x3;    static cl::opt<int>            KStringSize("kstr_size", cl::desc("Choose the probability [%] each basic blocks will be obfuscated by the -kstr pass"), cl::value_desc("king string encode Encryption length"), cl::init(defaultKStringSize), cl::Optional);     struct KStringEncode: public FunctionPass{        static char ID; // Pass identification        bool flag;        KStringEncode() : FunctionPass(ID) {}        KStringEncode(bool flag) : FunctionPass(ID) {this->flag = flag; KStringEncode();}        virtual bool runOnFunction(Function &F){            if ( !((KStringSize > 0) && (KStringSize <= 0x20)) ) {                errs()<<"KStringEncode application basic blocks percentage -kstr_size=x must be 0 < x <= 100";                return false;            }            if(toObfuscate(flag,&F,"kstr")) {                kstr(F);//                printFunction(F);                 return true;            }            return false;        }        //转换字符串        std::string ConvertOpToString(Value* op){            GlobalVariable* globalVar= dyn_cast<GlobalVariable>(op);            if(!globalVar){                errs()<<"dyn cast gloabl err";                return "";            }            ConstantDataSequential* cds=dyn_cast<ConstantDataSequential>(globalVar->getInitializer());            if(!cds){                errs()<<"dyn cast constant data err";                return "";            }            return cds->getRawDataValues();;        }         void kstr(Function& func){            Instruction* begin_ins=nullptr;            for(BasicBlock& bb:func){                for(Instruction& ins :bb){                    if(begin_ins==nullptr){                        begin_ins=&ins;                    }                    for(Value* val:ins.operands()){                        Value* stripOp=val->stripPointerCasts();                        if(stripOp->getName().contains(".str")){                            std::string strdata=ConvertOpToString(stripOp);                            errs()<<strdata<<"n";                            if(strdata.size()<=0){                                continue;                            }                            //加密流程                            uint8_t keys[strdata.size()];                            char encres[strdata.size()];                            int idx=0;                            memset(encres,0,strdata.size());                            for(int i=0;i<strdata.size();i++){                                uint8_t randkey=llvm::cryptoutils->get_uint8_t();                                keys[i]=randkey;                                int enclen=randkey+defaultKStringSize;                                for(int y=randkey;y<enclen;y++){                                    if(y==randkey){                                        encres[i]=strdata[i]^y;                                    }else if(y==enclen-1){                                        encres[i]=encres[i]^(~y);                                    }else{                                        encres[i]=encres[i]^y;                                    }                                    printf("%x ",encres[i]);                                    idx++;                                }                            }                            ArrayType* arrType=ArrayType::get(Type::getInt8Ty(func.getContext()),strdata.size());                            AllocaInst* arrayInst=new AllocaInst(arrType,0,nullptr,1,Twine(stripOp->getName()+".arr"),begin_ins);                            BitCastInst* bitInst=new BitCastInst(arrayInst,Type::getInt8PtrTy(func.getParent()->getContext()),Twine(stripOp->getName()+".bitcast"),begin_ins);                            //创建一个对象用来存放当前加密字节解密时每次异或的结果                            AllocaInst* eor_res=new AllocaInst(Type::getInt8Ty(func.getContext()),0,nullptr,1,Twine(stripOp->getName()+".alloc.res"),begin_ins);                            for(int i=0;i<strdata.size();i++){                                uint8_t randkey=keys[i];                                int enclen=randkey+defaultKStringSize;                                ConstantInt* enc_const=ConstantInt::get(Type::getInt8Ty(func.getContext()),encres[i]);                                //用来存放解密结果的bitcat                                ConstantInt* i_const=ConstantInt::get(Type::getInt8Ty(func.getContext()),i);                                GetElementPtrInst* element=GetElementPtrInst::CreateInBounds(bitInst,i_const);                                element->insertBefore(begin_ins);                                StoreInst* last_store=nullptr;                                for(int y=enclen-1;y>=randkey;y--){                                    /*下面是获取y的指令块*/                                    //先是创建一个数值y                                    ConstantInt *eor_data = ConstantInt::get(Type::getInt8Ty(func.getContext()),y);                                    //申请一个int8的内存来存放数值y                                    AllocaInst* eor_alloc=new AllocaInst(Type::getInt8Ty(func.getContext()),0,nullptr,1,Twine(stripOp->getName()+".alloc.y"),begin_ins);                                    //将数值y赋值给申请的内存空间                                    StoreInst* store_eor=new StoreInst(eor_data,eor_alloc);                                    store_eor->insertAfter(eor_alloc);                                    //从内存空间中加载里面的数值y                                    LoadInst* eor_load=new LoadInst(eor_alloc,"");                                    eor_load->insertAfter(store_eor);                                    //                                    if(y==enclen-1){                                        //然后进行取反计算                                        BinaryOperator* binNotOp=BinaryOperator::CreateNot(eor_load);                                        binNotOp->insertAfter(eor_load);                                        //然后异或                                        BinaryOperator* binXorOp=BinaryOperator::CreateXor(enc_const,binNotOp);                                        binXorOp->insertAfter(binNotOp);                                        //将加密字节设置为上次异或的结果                                        StoreInst* store_eor_res=new StoreInst(binXorOp,eor_res);                                        store_eor_res->insertAfter(binXorOp);                                    }else{                                        LoadInst* eor_load_res=new LoadInst(eor_res,stripOp->getName()+".load");                                        eor_load_res->insertAfter(store_eor);                                        //然后进行异或计算                                        BinaryOperator* binXorOp=BinaryOperator::CreateXor(eor_load_res,eor_load);                                        binXorOp->insertAfter(eor_load);                                        //将计算后的结果存放回数组中                                        StoreInst* store_data=new StoreInst(binXorOp,eor_res);                                        store_data->insertAfter(binXorOp);                                        if(y==randkey){                                            last_store=store_data;                                        }                                    }                                }                                //读取这个字节经过多次异或后的最终结果                                LoadInst* dec_res=new LoadInst(eor_res,stripOp->getName()+".dec.res");                                dec_res->insertAfter(last_store);                                //将这个结果写入到前面用来存放的解密结果bitcat处                                StoreInst* store_data=new StoreInst(dec_res,element);                                store_data->insertAfter(dec_res);                            }                            //将字符串操作数替换为我们准备好的解密结果的bitInst                            val->replaceAllUsesWith(bitInst);                            //然后再删掉之前我们获取到的字符串的明文部分。这样就只有密文数据和密文解密的流程,最后动态执行拿到解密字符串了                            GlobalVariable* globalVar= dyn_cast<GlobalVariable>(stripOp);                            globalVar->eraseFromParent();                         }                    }                }            }        }    }; }char KStringEncode::ID = 0;static RegisterPass<KStringEncode> X("kstr", "inserting bogus control flow"); Pass *llvm::createKStringEncode() {    return new KStringEncode();} Pass *llvm::createKStringEncode(bool flag) {    return new KStringEncode(flag);}



使用ollvm自定义简单的字符串加密

- End -


使用ollvm自定义简单的字符串加密


看雪ID:misskings

https://bbs.pediy.com/user-home-659397.htm

 

 *本文由看雪论坛 misskings 原创,转载请注明来自看雪社区。



 安卓应用层抓包通杀脚本发布!

《高研班》2021年3月班开始招生!👇


使用ollvm自定义简单的字符串加密

* 戳图片了解详情




# 往期推荐






使用ollvm自定义简单的字符串加密
公众号ID:ikanxue
官方微博:看雪安全
商务合作:[email protected]



使用ollvm自定义简单的字符串加密

球分享

使用ollvm自定义简单的字符串加密

球点赞

使用ollvm自定义简单的字符串加密

球在看



使用ollvm自定义简单的字符串加密

点击“阅读原文”,了解更多!

本文始发于微信公众号(看雪学院):使用ollvm自定义简单的字符串加密

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2021年4月30日17:58:50
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   使用ollvm自定义简单的字符串加密https://cn-sec.com/archives/216067.html

发表评论

匿名网友 填写信息