使用ollvm自定義簡單的字符串加密

2021-03-02 看雪學院

本文為看雪論優秀文章

看雪論壇作者ID:misskings

題目出自3W班9月的題目用ollvm9.0實現字符串簡單加密。

題目主要是為了能熟練ollvm中如何進行一個簡單的加密,以及c++部分怎麼生成對應的IR指令來達到像c++函數效果。所以主要我們的思路可以切換成:
2、根據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!!!";        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月班開始招生!👇

* 戳圖片了解詳情

相關焦點

  • 利用Xposed對ollvm後的so中flag爆破
    題目要求:請編寫Xposed插件完對該app的flag的暴破(已知so中test函數參數為一個字符串,該字符串長度為3且僅包含大小寫字母)。 考察知識點:ollvm控制流混淆代碼的分析方法,以及利用Xposed對so中函數的主動調用。1.分析ollvm控制流混淆的so找到關鍵函數首先利用Jadx軟體打開apk,核心代碼如下,將用戶輸入字符串作為參數調用jni函數jnitest進行判斷。
  • 一個簡單例子說明php自定義函數過濾字符串功能實現!
    有時候用戶在網站上發布內容或者留言時,系統提示使用了禁止詞語禁止發布。為了避免用戶在網站上發布違規違法內容,網站開發人員為了規範網站內容採取了過濾字符串的手段,今天為大家講解一下自定義函數過濾字符串。2、定義具有過濾功能的驗證函數,設定過濾規則。3、調用函數去判斷用戶提交內容是否符合規則。代碼如下:PHP編程實例:簡單的自定義函數過濾字符串功能實現!
  • PHP編程實例:自定義函數實現簡單數字加密和解密算法
    加密和解密一般用於電子商務,但是一般的網站開發中也會用涉及到到加密和解密,特別是文件處理上,今天為大家講解一個自定義函數簡單的數字加密/解密算法實例。3、定義加密數字和解密數字的函數。4、調用自定義函數處理用戶輸入的數據,輸出加密數字和解密數字。
  • PHP自定義函數刪除字符串中出現的空白、回車、換行
    我寫了一個PHP自定義函數,使用它可以刪除代碼中的多餘空格,回車,換行,使用方法很簡單,只需要把下面的代碼複製到PHP文件中,在需要用到的位置引用即可(自定義函數你懂得);  使用方法:  $a = trimall($str);  $a就是刪除處理之後的內容哦
  • 【VBA自定義函數】315個自定義函數
    74、刪除單元格自定義名稱75、從文件路徑中取得文件名76、取得一個文件的擴展名77、取得一個文件的路徑78、取得一個文件的路徑279、取得一個文件的路徑380、十進位轉二進位81、檢查一個數組是否為空82、字母欄名轉數字欄名83、數字欄名轉文字欄名84、判斷一件活頁夾中是否還有子目錄85、判斷一個文件是否在使用中86、列出檔案詳細摘要信息87、獲取菜單ID編號及名稱列表88、狀態列動態顯示文字89
  • php幾種常用的加密解密算法
    ';echo '默認MD5加密的字符串為:'.md5($str)."\r\n"; echo '第二個參數為TRUE MD5加密的字符串為:'.md5($str,true) .\r\n"; echo '第二個參數為TRUE sha1加密的字符串為:'.sha1($str,true) . "\n"; crypt() 將字符串用 UNIX 的標準加密 DES 模塊加密。這是單向的加密函數,無法解密。欲比對字符串,將已加密的字符串的頭二個字符放在 salt 的參數中,再比對加密後的字符串。
  • 如何使用PHP生成隨機字符串
    源 / php中文網      源 / www.php.cn使用PHP生成隨機,唯一,字母數字字符串。>輸出1:輸出2:方法2:使用哈希函數PHP有一些函數,如md5(),sha1()和hash(),可用於根據某些算法(如「sha1」,「sha256」,「md5」等)對字符串進行哈希處理。
  • [基礎] 幾種好用的PHP自定義加密函數(可逆/不可逆)
    項目中有時我們需要使用PHP將特定的信息進行加密,也就是通過加密算法生成一個加密字符串,這些加密後的字符串可以通過解密算法進行解密,便於程序對解密後的信息進行處理。最常見的應用在用戶登錄以及一些API數據交換的場景。加密解密原理一般都是通過一定的加密解密算法,將密鑰加入到算法中,最終得到加密解密結果。
  • php字符串處理函數大全
    count_chars — 返回一個字符串裡面的字符使用信息crc32 — 計算一個字符串的crc32多項式crypt — 單向散列加密函數echo — 用以顯示一些內容explode — 將一個字符串用分割符轉變為一數組形式fprintf — 按照要求對數據進行返回,並直接寫入文檔流get_html_translation_table
  • 講解php字符串處理函數
    count_chars —— 返回一個字符串裡面的字符使用信息crc32 —— 計算一個字符串的crc32多項式crypt —— 單向散列加密函數explode —— 將一個字符串用分割符轉變為一數組形式fprintf —— 按照要求對數據進行返回,並直接寫入文檔流get_html_translation_table
  • Python 入門 – 使用字符串
    Python 3將字符串定義為「文本序列類型」。你可以使用內置的str()函數將其他類型轉換為字符串。在本文中,你將學習如何:創建字符串字符串方法字符串格式化字符串連接字符串切片讓我們從學習創建字符串的不同方法開始吧!
  • LAMBDA函數,讓EXCEL自定義函數告別VBA
    一提起自定義函數(UDF),很多表哥表姐想到的Alt+F11打開VBE編輯器,寫一個Function.隨著EXCEL版本的更新,EXCEL也像其它程式語言一樣,推出了表達式函數LAMBDA,通過這個函數加名稱管理器,我們可以在編寫簡短的自定義函數時,不再需要使用VBE窗體。
  • 使用JavaScript截斷字符串
  • MySQL敏感數據怎麼加密 數據加密解密教程
    MySQL加密函數的方式2.1  MySQL加密將明文表中的數據插入到f_user_m中,同時對pwd密碼欄位進行加密存儲,注意要記住加密的字符串,因為解密的時候要用到這個值。Python base64 加密方法3.1  使用Python的encodestring方法加密編寫python腳本,將數據加密後插入表中#!
  • 用ASP.NET加密Cookie數據
    一、.NET的密碼系統概要簡單地說,加密就是將原始字符(字節)串轉變為完全不同的字符串的處理過程,達到原始字符無法破譯的目的。這個處理過程是用另一個字符串(稱為「密鑰」),採取複雜的、混合的算法,「搗進」原始字符串。有時還使用一個稱為「初始向量」的字符串,在密鑰搗進之前先打亂目標字符串,預防目標字符串中較明顯的內容被識破。
  • VBA自定義函數
    可以,這就是本集所講的自定義函數 '2 怎麼編寫自定義函數? '我們可以按下面的結構編寫自定義函數 ' Function 函數名稱(參數1,參數2....) '代碼 '函數名稱=返回的值或數組 ' End Function '1 怎麼讓自定義函數在所有工作簿中使用?
  • 如何使用NotePad++工具對文本進行加密解密
    本期小編要分享的文章是如何使用NotePad++工具對文本進行加密和解密的演示操作步驟,希望對大家能有所幫助。第一步:打開電腦上安裝的"NotePad++"工具軟體。第五步:新建一個文檔,添加一個明文字符串,如"Hello World"。
  • 對稱加密與非對稱加密
    在上述的例子中,紙條是承載信息的載體,紙條裡的內容是信息,漢語詞典是密鑰,將文字映射到漢語詞典的頁碼和順序是加密方式(算法)。 類似於上面這種,在加密和解密時使用相同的密鑰,或是使用兩個可以簡單地相互推算的密鑰的加密方式就是對稱密鑰加密(Symmetric-key algorithm),簡稱對稱加密。
  • PHP字符串處理函數,每天學習5個,運用自如!
    count_chars — 返回一個字符串裡面的字符使用信息crc32 — 計算一個字符串的crc32多項式crypt — 單向散列加密函數echo — 用以顯示一些內容explode — 將一個字符串用分割符轉變為一數組形式fprintf — 按照要求對數據進行返回,並直接寫入文檔流get_html_translation_table
  • 如何為 Python 基本類型自定義方法?
    我們在寫自己的類時,常常會定義很多的方法。那麼,當字符串自帶的方法不夠時,我們是否有辦法自定義一些方法呢?比如說,我想要一個方法叫做.are_you_short(),如果字符串的長度小於5,返回True,字符串的長度大於等於5,返回False。