<menu id="guoca"></menu>
<nav id="guoca"></nav><xmp id="guoca">
  • <xmp id="guoca">
  • <nav id="guoca"><code id="guoca"></code></nav>
  • <nav id="guoca"><code id="guoca"></code></nav>

    使用unicorn engin還原Armariris字符串混淆

    VSole2022-05-09 09:03:03

    git clone git@github.com:gossip-sjtu/Armariris.git

    編譯

    cd Armariris
    mkdir build
    cd build
    cmake ../ -DCMAKE_BUILD_TYPE=Release -DLLVM_TARGETS_TO_BUILD="ARM;X86;AArch64"
    make -j8
    

    測試文件內容如下:

    #include 
    
    void fun(){
        printf("test 3333");
    }
    
    int main(int argc, char *argv[]) {
        printf("test 1111");
        printf("test 2222");
        fun();
        return 0;
    }
    

    使用編譯好的llvm編譯這個測試的文件

    clang -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk -mllvm -sobf test.c -o test

    使用isysroot指定sdk,然后使用-mllvm -sobf開啟字符串混淆

    Armariris是如何進行字符串混淆的

    我們直接看使用ida反匯編出來的代碼

    int __cdecl main(int argc, const char **argv, const char **envp)
    {
      printf(aRcur7777, argv, envp);
      printf(&byte_100001036);
      fun();
      return 0;
    }
    

    可以看到有兩個printf函數打印了一些數據出來,我們點第一個打印的字符串,雙擊aRcur7777跳轉到 字符串定義位置,這個字符串在data段

    這個字符串我們本來輸出的是test 1111這里顯然不是,我們查看aRcur7777的交叉引用,發現兩處, 其中一處是main函數中的printf,另一處應該就是還原這個字符串的位置了

    所以__datadiv_decode14953400483976599729這個函數就是還原這個字符的函數,我們看他是如何做的還原 。跳轉過去按F5反編譯,得到的結果如下:

    __int64 datadiv_decode14953400483976599729()
    {
      bool v0; // ST23_1
      bool v1; // ST17_1
      __int64 result; // rax
      bool v3; // ST0B_1
      unsigned int v4; // [rsp+8h] [rbp-1Ch]
      unsigned int v5; // [rsp+14h] [rbp-10h]
      unsigned int v6; // [rsp+20h] [rbp-4h]
    
      v6 = 0;
      do
      {
        aLKl[v6] ^= 0x38u;
        v0 = v6++ < 0xA;
      }
      while ( v0 );
      v5 = 0;
      do
      {
        aRcur7777[v5] ^= 6u;
        v1 = v5++ < 0xA;
      }
      while ( v1 );
      v4 = 0;
      do
      {
        byte_100001036[v4] ^= 0x71u;
        result = v4 - 10;
        v3 = v4++ < 0xA;
      }
      while ( v3 );
      return result;
    }
    

    我們可以看到aRcur7777的還原是和6做了異或操作,那我們來驗證一下是否是我們看到的這樣。

    aRcur7777的原始數據是[0x72, 0x63, 0x75, 0x72, 0x26, 0x37, 0x37, 0x37, 0x37]

    每一位和6異或之后的結果是[0x74, 0x65, 0x73, 0x74, 0x20, 0x31, 0x31, 0x31, 0x31]

    對于的ascii字符串就是test 1111

    他這里做字符串混淆用的是一個很簡單的原理,一個數字兩次異或同一個值,得到的結果是本事的值。也就是 第一次異或就給字符串混淆了,再異或一次就把數據還原了。

    源碼分析

    字符串混淆的源文件在lib/Transforms/Obfuscation/StringObfuscation.cpp這個位置, 實現字符串混淆的是一個ModulePass,關于ModulePass可以參考http://llvm.org/doxygen/classllvm11ModulePass.html#details 。在這個pass里面會遍歷字符串,然后把字符串和生成的key異或,并替換原始的值,關鍵代碼如下:

    // Duplicate global variable
    GlobalVariable *dynGV = new GlobalVariable(M,
                                              gv->getType()->getElementType(),
                                              !(gv->isConstant()), gv->getLinkage(),
                                              (Constant*) 0, gv->getName(),
                                              (GlobalVariable*) 0,
                                              gv->getThreadLocalMode(),
                                              gv->getType()->getAddressSpace());
    // dynGV->copyAttributesFrom(gv);
    dynGV->setInitializer(gv->getInitializer());
    
    std::string tmp=gv->getName().str();
    //  errs()<<"GV: "<<*gv<<"";
    
    Constant *initializer = gv->getInitializer();
    ConstantDataSequential *cdata = dyn_cast(initializer);
    if (cdata) {
            const char *orig = cdata->getRawDataValues().data();
            unsigned len = cdata->getNumElements()*cdata->getElementByteSize();
    
            encVar *cur = new encVar();
            cur->var = dynGV;
            cur->key = llvm::cryptoutils->get_uint8_t();
            // casting away const is undef. behavior in C++
            // TODO a clean implementation would retrieve the data, generate a new constant
            // set the correct type, and copy the data over.
            //char *encr = new char[len];
            //Constant *initnew = ConstantDataArray::getString(M.getContext(), encr, true);
            char *encr = const_cast(orig);
            // Simple xor encoding
            for (unsigned i = 0; i != len; ++i) {
                    encr[i] = orig[i]^cur->key;
            }
    
            // FIXME Second part of the unclean hack.
            dynGV->setInitializer(initializer);
    
            // Prepare to add decode function for this variable
            encGlob.push_back(cur);
    } else {
            // just copying default initializer for now
            dynGV->setInitializer(initializer);
    }
    
    // redirect references to new GV and remove old one
    gv->replaceAllUsesWith(dynGV);
    toDelConstGlob.push_back(gv);
    

    在替換了之后為了保證程序可以正常運行,還得加一個函數輸還原字符串,還原字符串的 函數生成代碼在addDecodeFunction中。在這里添加了.datadiv_decode開始的函數 加上一串隨機字符串,里面進行了異或操作,將數據還原。然后將這個函數加入到了entry,這個在 elf文件的話,就會被加入到.init_array,在mach-o文件中就會被加入到__mod_init_func。代碼也比較簡單,可以參照源碼看一下。 

    還原字符串

    前面講了原理其實很簡單,那么怎么還原字符串呢,其實也有很多方式,第一種是內存dump,因為他會在 初始化程序的時候就把原始字符串還原回去。但是有時候我就行靜態分析,不想執行之后去dump。如果只 靜態分析,也可以去人工還原字符串。但是如果字符串很多,人工還原工作量很大。其實我們還可以使用 unicorn之類的工具,模擬去執行他的指令,把字符串進行還原。

    還原混淆字符串的思路

    1. 找到所有.datadiv_decode開始的函數
    2. unicorn分配內存,將程序的.text段和.data段映射到unicorn分配的內存中
    3. 模擬執行所有.datadiv_decode開始的函數
    4. 最后將unicorn中分配的data讀出來,patch到程序中

    使用的工具

    因為不同操作系統可執行文件格式不一樣。為了簡單點,我們直接寫一個ida插件。所以需要以下工具:

    1. ida
    2. python2 (因為ida里面內置的python是python2)
    3. python2安裝unicorn和keystone庫

    找到所有的.datadiv_decode開始的函數

    idautils.Functions()可以遍歷函數,遍歷匹配含有datadiv_decode的函數,保存他們 的起始地址,代碼很簡單,如下:

    import idaapi
    import idc
    import idautils
    
    for func in idautils.Functions():
        func_name = idc.GetFunctionName(func)
        if "datadiv_decode" in func_name:
            func_data = idaapi.get_func(func)
            start = func_data.start_ea
            end = func_data.end_ea
    

    unicorn分配內存

    我這里分配內存的想法是直接用ida的api獲取data段和text段的內容,以及起始地址,然后在 unicorn里面對于起始分配內存,將data段和text段寫進去。

    unicorn分配內存還是有些坑,不能直接在任意地址分配,必須得整除1024的才可以,所以需 要稍微計算一下分配的地址。這里對基地址減去對(1024 * 1024)求余的結果作為新的基地址, 然后分配內存的長度增加(1024 * 1024),實現的代碼如下

    def get_base_and_len(base, length):
        _base = base - (base % (1024 * 1024))
        _length = (length / (1024 * 1024) + 1) * 1024 * 1024
        return _base, _length
    

    算出起始地址之后使用unicorn的mem_map方法分配內存即可

    模擬執行,patch程序

    模擬執行這里也比較簡單,直接調用unicorn的emu_start方法,然后傳入函數的起始地址即可開始 模擬執行,模擬執行完成之后將data段讀出來,模擬執行下一個函數的時候使用這個data加載到內存中。這樣所有的.datadiv_decode函數執行之后data段就被還原了。將還原的data段用ida的patch去 修改掉原始的data,這個時候你看到的字符串就是原始的字符串了。

    運行腳本前效果如下:

    運行腳本之后效果如下:

    這個時候可以選中字符串,然后按a就能得到下面的效果

    完整代碼以及示例二進制文件

    代碼以及二進制文件存放在https://github.com/smartdone/re_scripts/tree/master/ida 其中Armaririsstringobfuscationbypass.py是ida用來還原Armariris混淆過的字符串的插件。sample里面的testlinuxx8664和testmacosx86_64是示例二進制文件。

    如果本文說的有錯誤的地方,請及時指正,謝謝。

    字符串函數python字符串比較
    本作品采用《CC 協議》,轉載必須注明作者和本文鏈接
    為了得到更好的數據分析結果,需要對這些數據集進行數據清洗和停用詞過濾等操作,這里利用Jieba庫進行清洗數據。向量空間模型是用向量來表征一個文本,它將中文文本轉化為數值特征。貴州省位于中國的西南地區,簡稱“黔”或“貴”。
    系統安全第41篇是PowerShell系列的基礎知識和注冊表操作,希望您喜歡
    PHP弱類型總結
    2021-12-25 06:32:02
    轉自:中爾安全實驗室1.?代表有Java、.net、Python、C++等語言。弱類型語言是一種弱類型定義的語言,某一個變量被定義類型,該變量可以根據環境變化自動進行轉換,不需要經過現行強制轉換。
    可以看到有兩個printf函數打印了一些數據出來,我們點第一個打印的字符串
    Python圖像處理第13篇文章介紹三維頂帽和黑帽運算
    寫一個android中so文件反混淆的系列文章,目前這是第三篇。根據其他人的分析可知,libDexHelper.so是指令抽取的實現,libdexjni.so是VMP的實現。在android so文件攻防實戰-百度加固免費版libbaiduprotect.so反混淆中我們是交叉引用拿到加密后的字符串和它對應的解密函數的表然后frida主動調用得到的解密后的字符串,但是在這里這個方法就不太好用了。
    CVE-2021-40438-Apache httpd mod_proxy SSRF漏洞深入分析與復現。
    關注一波,謝謝各位師傅感謝ch1e師傅幫忙總結ch1e‘blog:https://ch1e.gitee.io
    trickbot病毒分析
    2022-03-25 06:45:38
    概述最近微軟發布了一款Trickbot掃描器[1]該木馬近期在app.any.run公開任務的提交趨勢如下[2]獲取一個樣本[2],進行分析原始樣本分析打開之后是這樣的這里包含一定的社會工程操作,如果受害者對此類攻擊不熟悉,就會點擊啟用宏導致樣本執行。使用oletools查看一下宏C:\Users\IEUser\Desktop\trickbot>mraptor?
    ByteCTF-WriteUp
    2021-10-19 06:38:36
    /files../路由發現了目錄穿越,在/var/lib/clickhouse/access/找到.sql文件,可以看到user_01用戶名密碼。
    VSole
    網絡安全專家
      亚洲 欧美 自拍 唯美 另类