本文為看雪論優秀文章
看雪論壇作者ID:misskings
題目出自3W班9月的題目用ollvm9.0實現字符串簡單加密。#include <stdio.h>#include <cstring>#include <string> int main(int argc, char** argv) { std::string str1="hello world!!!"; 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 \r\n",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("\r\n"); } printf("encdata: %s\r\n",encres); char decres[str1.size()]; for(int i=0;i<str1.size();i++){ printf("cur enc: %x \r\n",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("\r\n"); } printf("res: %s\r\n",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; 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){ } }; }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!!!\00", 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"kanxue\00", 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代碼看到所有字符串都是全局的,所以要先轉換成全局的對象,然後再轉換成數值。然後看這裡的列印結果。
獲取到所有的字符串了之後。接下來。我們要先把這個字符串加密,然後再用插入指令塊來進行解密。下面繼續完善,先把之前搞好的加密算法遷移進來。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]); 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--){ ConstantInt *eor_data = ConstantInt::get(Type::getInt8Ty(func.getContext()),y); AllocaInst* eor_alloc=new AllocaInst(Type::getInt8Ty(func.getContext()),0,nullptr,1,Twine(stripOp->getName()+".alloc.y"),&ins); StoreInst* store_eor=new StoreInst(eor_data,eor_alloc); store_eor->insertAfter(eor_alloc); 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); StoreInst* store_data=new StoreInst(dec_res,element); store_data->insertAfter(dec_res);}
上面就是把c++的解密流程用插入指令塊的方式實現的方式。流程比較繁瑣,但是大概意思是差不多的。最後這裡完成後,我們就可以刪除指令塊中的字符串明文部分。然後只保留密文。val->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。
因為不在同一個作用域,所以為了防止出現這種情況,我在解密時再做一個特殊的處理,我們先獲取第一個指令塊的位置,然後所有的字符串解密指令塊,都插入在最開始的位置,這樣就不會出現作用域的問題了。最後貼上完整代碼: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; 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); 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]); 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--){ ConstantInt *eor_data = ConstantInt::get(Type::getInt8Ty(func.getContext()),y); AllocaInst* eor_alloc=new AllocaInst(Type::getInt8Ty(func.getContext()),0,nullptr,1,Twine(stripOp->getName()+".alloc.y"),begin_ins); StoreInst* store_eor=new StoreInst(eor_data,eor_alloc); store_eor->insertAfter(eor_alloc); 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); StoreInst* store_data=new StoreInst(dec_res,element); store_data->insertAfter(dec_res); } 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);}看雪ID:misskings
https://bbs.pediy.com/user-home-659397.htm
*本文由看雪論壇 misskings 原創,轉載請註明來自看雪社區。
安卓應用層抓包通殺腳本發布!《高研班》2021年3月班開始招生!👇* 戳圖片了解詳情