android逆向奇技淫巧二十:findcrypt和findhash查找疑似加密函數(五)


  上次用ida標記unidbg trace得到的指令時遇到如下報錯:

from PyQt5 import QtCore, QtWidgets, QtGui
ImportError: DLL load failed: %1 不是有效的 Win32 應用程序。

  當時就有好些小伙伴給我支招,我嘗試后發現確實可以,這里感謝各位熱心小伙伴的支持!具體做法如下:

    (1)先把ida安裝目錄下有個python目錄設置為環境變量

    (2)再用pip install sark安裝依賴包

   因為我裝了好幾個版本的python,用pip裝的時候老是自動裝到其他python的目錄,所以每次用idapython執行腳本都報錯;這次裝的時候指定sark的安裝目錄:pip install --target=D:\xxxx\xxxx\xxxx\Lib\site-packages sark(注意:一定要在site-package目錄下安裝),裝好后再用ida執行patch腳本就ok了!經過腳本的作色后,再靜態分析代碼,發現還是不對:作色(也就是執行過)的代碼數量不多,目測不超過15%,感覺不對勁:就算有OLLVM混淆,也不至於才執行了這么一點代碼呀!再去分析trace日志,特么發現后面又遇到了循環,只不過這次循環的范圍要大一些,前后間隔了100多條指令,都是B指令跳來跳去, 怎么辦了?如果強制改跳轉邏輯,萬一改變了原有正確的邏輯咋辦? 如果不改邏輯又一直“鬼打牆”式地循環(個人猜測是某些邏輯不滿足,導致一直循環),咋整? 模擬器畢竟是模擬器,要100%完全模擬真實的硬件和依賴的軟件環境是很難的,哎~~~~

  既然已經確認metasec和sscronet有”作案嫌疑“,現在的問題就是確認這兩個so里面的加密代碼了。我們回到逆向最初抓取的數據,四個關鍵字段的樣本如下:

"X-Argus":"dL63f9K4krgr8/WAfSIFeVfdfEcxLb0IszYsoVzV1+5/iO2yRYjhPTcNpp9D3PjyivcgIe5KYrbCD11veS20EKiNAqOk3bOJzl+L386i+SWzP8rAnbbqxfvkUWtO5Bc0oVLuMQ4MlA77tSKmgN23uBxTq0RgPvcEUxC9H2P9tKCiXcL85uubNM7L1FOAsHAEvNe+83Y341uq5UdMLixTXC5u21bYeHkr7SBBDFEWGQz7WDOfte4Tvq4ZyoIydKGHNlFb3tJUFav8IBrm/Fq3NgLq1WdP5h6eIzPoXKgs1/amjaNItSFY7POOz1qNLD/9fgJbj+f43UFI9kZzssJ8zfVj",
"X-Gorgon":"0404b8790005f2914fd644447ffb0acf11a197e54adb11afa680",
"X-Tyhon":"iXwqufMrDfPxVRKC1zs/8P8NUvLqKS2QsxUO15g=",
"X-Ladon":"wp8eCaQGfmHoPa38jhEcD+4ADTjDGs0I83D+1lWfekKesKn+",

  我們挨個分析:

  •   X-Argus:從加密后的結果看,大小寫字母、數字都有,除了末尾沒有==號外,其他的特征是不是和base64很像啊? 同理,X-Tyhon和X-Ladon是不是也和base64編碼后長得很像啊?
  •        X-Gorgon: 從加密后的結果看,有數字、有字母,但字母都都是小寫,沒有其他任何字符了,這是不是和MD5/sha等hash散列很像啊

  X音的研發人員會不會直接用這些現成的加密算法去加密字段了?🙂  我個人猜測不太可能:

  •   直接用開源的加密api太容易暴露了,通過符號或字符串就能搜到;
  •        導入表也沒找到任何hash散列、base64編碼的函數

       既然沒有直接調用開源api,是不是x音研發人員自己研發的加密函數了? 我個人覺得也不太可能,原有也很簡單:加解密算法是很”燒腦“的技術,不是普通的碼農能搞定的!能設計加密算法的都是專業數學家,並且還要經過幾十年的實戰檢驗(所以加解密算法都要開源,接受業界檢驗),這么高的要求,不是普通碼農能玩得轉的(普通碼農能了解這些加解密算法原理,知道每種算法的適用場景已經很不錯了,設計嚴謹的加解密算法想都不要想了)!不直接調用開源api,也會大面積地借(抄)鑒(襲)開源算法!怎么判定借鑒的是哪中算法了?之前寫了一篇總結:https://www.cnblogs.com/theseventhson/p/14852458.html  每種算法都有自己特有的magic number,現在就有大牛根據這些magic number來判斷加解密算法的類型,並且還做成了ida插件的形式,比如findcrypt插件,或則說findhash插件;這里我建議大家使用吾愛破解的7.5綠色版本,內置集成了很多插件,就包括findcrypt(文章末尾有下載鏈接)!用這個插件找到的magic number如下: (有點多,沒截全);有crc32、md5、RIPEMD160、sha1、base64等;其中md5、base64和sha1很像我們剛才分析的加密結果哦,這里是不是又把排查的范圍進一步縮小了?

  

   這里再實名安利另一個ida插件:findhash(鏈接在文章末尾第二個有),大概的原理是通過正則表達式匹配偽C代碼中的初始化魔數代碼以及哈希運算函數(和我之前那篇文章的原理是一樣的),居然還能自動生成frida的js代碼,省的我挨個寫hook代碼了,手動筆芯點贊!生成的代碼如下:

function monitor_constants(targetSo) {
    let const_array = [];
    let const_name = [];
    let const_addr = [['padding used in hashing algorithms (0x80 0 ... 0)', '0x9f2f0'], ['MD5 K table', '0x6a230']];

    for (var i = 0; i < const_addr.length; i++) {
        const_array.push({base:targetSo.add(const_addr[i][1]),size:0x1});
        const_name.push(const_addr[i][0]);
    }

    MemoryAccessMonitor.enable(const_array, {
        onAccess: function (details) {
            console.log("\n");
            console.log("監控到疑似加密常量的內存訪問\n");
            console.log(const_name[details.rangeIndex]);
            console.log("訪問來自:"+details.from.sub(targetSo)+"(可能有誤差)");
       console.log("訪問來自:"+details.address+"(可能有誤差)"); } }); }
function hook_suspected_function(targetSo) { const funcs = [['sub_8748', '函數sub_8748疑似哈希函數,包含初始化魔數的代碼。', '0x8749'], ['sub_9524', '函數sub_9524疑似哈希函數,包含初始化魔數的代碼。', '0x9525'], ['sub_9938', '函數sub_9938疑似哈希函數,包含初始化魔數的代碼。', '0x9939'], ['sub_F498', '函數sub_F498疑似哈希函數,包含初始化魔數的代碼。', '0xf499'], ['sub_FE20', '函數sub_FE20疑似哈希函數,包含初始化魔數的代碼。', '0xfe21'], ['sub_12EA0', '函數sub_12EA0疑似哈希函數,包含初始化魔數的代碼。', '0x12ea1'], ['sub_12FE8', '函數sub_12FE8疑似哈希函數,包含初始化魔數的代碼。', '0x12fe9'], ['sub_21590', '函數sub_21590疑似哈希函數,包含初始化魔數的代碼。', '0x21591'], ['sub_21714', '函數sub_21714疑似哈希函數,包含初始化魔數的代碼。', '0x21715'], ['sub_21DAC', '函數sub_21DAC疑似哈希函數,包含初始化魔數的代碼。', '0x21dad'], ['sub_21F00', '函數sub_21F00疑似哈希函數,包含初始化魔數的代碼。', '0x21f01'], ['sub_2375C', '函數sub_2375C疑似哈希函數運算部分。', '0x2375d'], ['sub_249AC', '函數sub_249AC疑似哈希函數,包含初始化魔數的代碼。', '0x249ad'], ['sub_24BD4', '函數sub_24BD4疑似哈希函數,包含初始化魔數的代碼。', '0x24bd5'], ['sub_37454', '函數sub_37454疑似哈希函數,包含初始化魔數的代碼。', '0x37455'], ['sub_37590', '函數sub_37590疑似哈希函數,包含初始化魔數的代碼。', '0x37591'], ['sub_3823C', '函數sub_3823C疑似哈希函數,包含初始化魔數的代碼。', '0x3823d'], ['sub_38F90', '函數sub_38F90疑似哈希函數,包含初始化魔數的代碼。', '0x38f91'], ['sub_393B4', '函數sub_393B4疑似哈希函數,包含初始化魔數的代碼。', '0x393b5'], ['sub_39728', '函數sub_39728疑似哈希函數,包含初始化魔數的代碼。', '0x39729'], ['sub_398CC', '函數sub_398CC疑似哈希函數,包含初始化魔數的代碼。', '0x398cd'], ['sub_45890', '函數sub_45890疑似哈希函數,包含初始化魔數的代碼。', '0x45891'], ['sub_46DF0', '函數sub_46DF0疑似哈希函數,包含初始化魔數的代碼。', '0x46df1'], ['sub_47174', '函數sub_47174疑似哈希函數,包含初始化魔數的代碼。', '0x47175'], ['sub_47518', '函數sub_47518疑似哈希函數,包含初始化魔數的代碼。', '0x47519'], ['sub_48118', '函數sub_48118疑似哈希函數,包含初始化魔數的代碼。', '0x48119'], ['sub_49888', '函數sub_49888疑似哈希函數,包含初始化魔數的代碼。', '0x49889'], ['sub_49C6C', '函數sub_49C6C疑似哈希函數,包含初始化魔數的代碼。', '0x49c6d'], ['sub_4FA0C', '函數sub_4FA0C疑似哈希函數,包含初始化魔數的代碼。', '0x4fa0d'], ['sub_506B0', '函數sub_506B0疑似哈希函數,包含初始化魔數的代碼。', '0x506b1'], ['sub_51FCC', '函數sub_51FCC疑似哈希函數,包含初始化魔數的代碼。', '0x51fcd'], ['sub_53554', '函數sub_53554疑似哈希函數,包含初始化魔數的代碼。', '0x53555'], ['sub_54974', '函數sub_54974疑似哈希函數,包含初始化魔數的代碼。', '0x54975'], ['sub_54C60', '函數sub_54C60疑似哈希函數,包含初始化魔數的代碼。', '0x54c61'], ['sub_54D7C', '函數sub_54D7C疑似哈希函數,包含初始化魔數的代碼。', '0x54d7d'], ['sub_54E28', '函數sub_54E28疑似哈希函數,包含初始化魔數的代碼。', '0x54e29'], ['sub_56AA4', '函數sub_56AA4疑似哈希函數,包含初始化魔數的代碼。', '0x56aa5'], ['sub_57C70', '函數sub_57C70疑似哈希函數,包含初始化魔數的代碼。', '0x57c71'], ['sub_5964C', '函數sub_5964C疑似哈希函數,包含初始化魔數的代碼。', '0x5964d'], ['sub_5983C', '函數sub_5983C疑似哈希函數,包含初始化魔數的代碼。', '0x5983d'], ['sub_59934', '函數sub_59934疑似哈希函數,包含初始化魔數的代碼。', '0x59935'], ['sub_59D28', '函數sub_59D28疑似哈希函數,包含初始化魔數的代碼。', '0x59d29'], ['sub_5A0C8', '函數sub_5A0C8疑似哈希函數,包含初始化魔數的代碼。', '0x5a0c9'], ['sub_5A264', '函數sub_5A264疑似哈希函數,包含初始化魔數的代碼。', '0x5a265'], ['sub_5A3A8', '函數sub_5A3A8疑似哈希函數,包含初始化魔數的代碼。', '0x5a3a9'], ['sub_5A634', '函數sub_5A634疑似哈希函數,包含初始化魔數的代碼。', '0x5a635'], ['sub_5AFC8', '函數sub_5AFC8疑似哈希函數,包含初始化魔數的代碼。', '0x5afc9'], ['sub_5B19C', '函數sub_5B19C疑似哈希函數,包含初始化魔數的代碼。', '0x5b19d'], ['sub_5B2A0', '函數sub_5B2A0疑似哈希函數,包含初始化魔數的代碼。', '0x5b2a1'], ['sub_5B4AC', '函數sub_5B4AC疑似哈希函數,包含初始化魔數的代碼。', '0x5b4ad'], ['sub_60320', '函數sub_60320疑似哈希函數,包含初始化魔數的代碼。', '0x60321'], ['sub_60A94', '函數sub_60A94疑似哈希函數,包含初始化魔數的代碼。', '0x60a95'], ['sub_61818', '函數sub_61818疑似哈希函數,包含初始化魔數的代碼。', '0x61819'], ['sub_61A6C', '函數sub_61A6C疑似哈希函數,包含初始化魔數的代碼。', '0x61a6d'], ['sub_61E34', '函數sub_61E34疑似哈希函數,包含初始化魔數的代碼。', '0x61e35'], ['sub_69D9C', '函數sub_69D9C疑似哈希函數運算部分。', '0x69d9d'], ['sub_6A760', '函數sub_6A760疑似哈希函數,包含初始化魔數的代碼。', '0x6a761'], ['sub_6A81C', '函數sub_6A81C疑似哈希函數運算部分。', '0x6a81d'], ['sub_6B910', '函數sub_6B910疑似哈希函數,包含初始化魔數的代碼。', '0x6b911'], ['sub_6DE9C', '函數sub_6DE9C疑似哈希函數,包含初始化魔數的代碼。', '0x6de9d'], ['sub_7383C', '函數sub_7383C疑似哈希函數,包含初始化魔數的代碼。', '0x7383d'], ['sub_73A8C', '函數sub_73A8C疑似哈希函數,包含初始化魔數的代碼。', '0x73a8d'], ['sub_73C90', '函數sub_73C90疑似哈希函數,包含初始化魔數的代碼。', '0x73c91'], ['sub_73E68', '函數sub_73E68疑似哈希函數,包含初始化魔數的代碼。', '0x73e69'], ['sub_740D8', '函數sub_740D8疑似哈希函數,包含初始化魔數的代碼。', '0x740d9'], ['sub_741B8', '函數sub_741B8疑似哈希函數,包含初始化魔數的代碼。', '0x741b9'], ['sub_742BC', '函數sub_742BC疑似哈希函數,包含初始化魔數的代碼。', '0x742bd'], ['sub_743C4', '函數sub_743C4疑似哈希函數,包含初始化魔數的代碼。', '0x743c5'], ['sub_745DC', '函數sub_745DC疑似哈希函數,包含初始化魔數的代碼。', '0x745dd'], ['sub_74758', '函數sub_74758疑似哈希函數,包含初始化魔數的代碼。', '0x74759']]; for (var i in funcs) { let relativePtr = funcs[i][2]; let funcPtr = targetSo.add(relativePtr); let describe = funcs[i][1]; let handler = (function() { return function(args) { console.log("\n"); console.log(describe); console.log(Thread.backtrace(this.context,Backtracer.ACCURATE).map(DebugSymbol.fromAddress).join("\n")); }; })(); Memory.protect(funcPtr, 64, 'rwx'); Interceptor.attach(funcPtr, {onEnter: handler}); } } function main() { //var targetSo = Module.findBaseAddress('libmetasec_ml.so'); var targetSo = new NativePointer(0x4200000); // 對疑似哈希算法常量的地址進行監控,使用frida MemoryAccessMonitor API,有幾個缺陷,在這里比較雞肋。 // 1.只監控第一次訪問,所以如果此區域被多次訪問,后續訪問無法獲取。可以根據這篇文章做改良和擴展。https://bbs.pediy.com/thread-262104-1.htm // 2.ARM 64無法使用 // 3.無法查看調用棧 // 在這兒用於驗證這些常量是否被訪問,訪問了就說明可能使用該哈希算法。 // MemoryAccessMonitor在別處可能有較大用處,比如ollvm過的so,或者ida xref失效/過多等情況。 // hook和monitor這兩個函數,只能分別注入和測試,兩個同時會出錯,這可能涉及到frida inline hook的原理 // 除非hook_suspected_function 沒結果,否則不建議使用monitor_constants。 // monitor_constants(targetSo); hook_suspected_function(targetSo); } setImmediate(main);

  直接運行,居然報錯,仔細一看,發現是老問題:findBaseAddress 找不到metasec_ml的基址(上一篇文章已經分享過了,有可能是操作系統維護的雙向鏈表被斷鏈導致的),這里直接通過cat /proc/pid/maps找到metasec_ml的基址,寫死到代碼!然后繼續運行,特么還是遇到了問題:

Spawned `com.ss.android.ugc.aweme`. Resuming main thread!
Error: access violation accessing 0x4208749
    at value (frida/runtime/core.js:316)
    at hook_suspected_function (/libmetasec_ml_findhash_1628171084.js:35)
    at main (/libmetasec_ml_findhash_1628171084.js:54)
    at apply (native)
    at <anonymous> (frida/runtime/core.js:45)

  從提示看,貌似是地址違規訪問,違規的代碼是Interceptor.attach(funcPtr, {onEnter: handler})這行:網上查了一下,發現可能是內存屬性導致的;熟悉hook原理的同學都知道:hook某個函數,是需要在函數的開始改寫原機器碼的,然后跳轉到自己的代碼執行,最后修復被破壞的代碼再跳轉回去(android下的inline hook原理:https://www.bilibili.com/video/BV1nh411z76Q),所以是需要寫內存的,這里可能沒有寫的權限導致;查看maps映射表,果然只有讀的權限(這里有個疑問:既然只有讀的權限,這里的代碼是怎么被執行的了?有知道原理的小伙伴請不吝賜教),如下:

04200000-04298000 r--p 00000000 08:12 131487     /data/app/com.ss.android.ugc.aweme-1/lib/arm/libmetasec_ml.so
04298000-0429f000 r--p 00097000 08:12 131487     /data/app/com.ss.android.ugc.aweme-1/lib/arm/libmetasec_ml.so
0429f000-042a0000 rw-p 0009e000 08:12 131487     /data/app/com.ss.android.ugc.aweme-1/lib/arm/libmetasec_ml.so

  一不做二不休,直接用Memory.protect(funcPtr, 64, 'rwx')來改內存屬性,然而大失所望,並沒有什么卵用,反復嘗試了好幾次還是報access violation accessing這個錯!先是so的基址用api找不到,直接寫死基址后又遇到了內存沒有寫屬性、無法hook的問題,這個問題暫時不知道怎么解決,又要依靠各位熱心的小伙伴幫忙想辦法了,小弟萬分感謝!

  好在findhash函數自動生成的腳本又提供了另一種思路:MemoryAccessMonitor,可以監控一個或多個內存塊的訪問,在觸發到內存訪問的時候發出通知(有點像內存斷點);從打印的日志看,9f088、6a000、9fbf0、6a7cc、9f088、6a718這個幾個地址(已經減去了4200000這個基址,所以這些地址都是偏移)都沒訪問過!這些代碼的地址分別被sub_BF10、sub_69D9C(內部充分了大量的邏輯操作,非常可疑)、sub_7D54、sub_6A79C、sub_BF10、sub_6A718被調用!除此之外,還有sub_6221C函數,使用了base64的碼表,也需要重點關注!

  先hook sub_69d9c函數試試了(注意這是個thumb函數,地址一定要+1,否則報錯:Error: unable to intercept function at 0x4269d9c; please file a bug):

function hook_69D9C(targetSo){
    var addr_69D9C = targetSo.add(0x69d9c+1);
    Interceptor.attach(addr_69D9C, {
        onEnter: function(args){
            console.log("enter 69D9C====================================================================");
            console.log(args[0]);
            console.log(args[1]);
            console.log(args[2]);
            console.log(args[3]);
        },onLeave: function(retval){
            console.log("retval:"+retval);
            console.log("leave 69D9C====================================================================");
        }
    });
}

function main() {
    var targetSo = new NativePointer(0x4200000);
    hook_69D9C(targetSo);
}

setImmediate(main);

  結果再次讓我抓狂:還是同樣的問題!what's the xxxx ..........  真心求教各位大佬這個問題該怎么解決!

Error: access violation accessing 0x4269d9d
    at value (frida/runtime/core.js:316)
    at hook_69D9C (/encryptHook.js:14)
    at main (/encryptHook.js:19)
    at apply (native)
    at <anonymous> (frida/runtime/core.js:45)

   改用ida試試:斷在函數開頭,查看寄存器保存的傳參,發現R1是個指針,指向的數據是128位的字符串;[R1]和R2剛好是字符串的長度0x80;這個字符串的“長相”和X-Gorgon神似,只不過長度不同(X-Gorgon只有52個字符),需要重點關注:

   再來看另一個函數sub_6221C,F5后發現有5個參數,每個參數都沒啥特殊的,這里繼續走:

   函數中大量使用了base64的碼表,並且返回了0;

*v19 = BASE64_table_90E78[v15 >> 2];
    v18 = BASE64_table_90E78[(v17 >> 6) & 0xFFFFFFC3 | (4 * (v16 & 0xF))];
    LOBYTE(v16) = BASE64_table_90E78[(v16 >> 4) & 0xFFFFFFCF | (16 * (v15 & 3))];
    v19[3] = BASE64_table_90E78[v17 & 0x3F];
    v19[2] = v18;
    v19[1] = v16;
  }
  if ( v14 < a5 )
  {
    v21 = *v20;
    v22 = v14 + 1;
    if ( v14 + 1 >= a5 )
      v23 = 0;
    else
      v23 = v20[1];
    v24 = v22 >= a5;
    v25 = 61;
    *v19 = BASE64_table_90E78[v21 >> 2];
    v19[1] = BASE64_table_90E78[(v23 >> 4) | (16 * (v21 & 3))];
    if ( !v24 )
      v25 = BASE64_table_90E78[4 * (v23 & 0xF)];
    v19[2] = v25;
    v19[3] = 61;
    v19 += 4;
  }
  *a3 = (unsigned int)&v19[-a1];
  result = 0;
  *v19 = 0;
  return result;

  這里直接現到最后一行代碼看看結果,這次沒讓我失望了:相信大家也都看出來了,R1是個指針,緊挨着R1有一長串字符:這段字符串有344個字符,長度和X-Argus完全一樣,並且字符串的表現形式也一樣,這基本確定了X-Argus就是在這個函數里面生成的!

    

    同樣在函數結尾,同樣是R1指向內存的前面,有48個字符,從長度和“長相”上看,和X-Ladon一摸一樣!

 

  還是這里:有40個字符,不論從長度還是長相都和X-Tyhon一摸一樣!

  

       這次就到這吧,后續會用Stalker或ida做指令級別的trace,具體分析X-Argus、X-Gorgon、X-Ladon和X-Tyhon的生成算法!這里為什么不繼續使用unicorn、unidbg這些模擬器了?模擬器調用某個函數,涉及到的上下文、參數等都要配置好,太麻煩了,稍有不慎就產生邏輯錯誤,甚至之前遇到的“鬼打牆”一樣的循環.......

  最后:F8單步調試過程中頻繁遇到這兩個彈窗,一致沒找到好的解決辦法,求各位大佬支招.......

       

   

 說明:我用的是15.5.0、32位的版本

 

參考:

1、https://qnmlgb.tech/articles/5ff669ba28cce0b0a854f386/   吾愛破解的7.5綠色版本,內置集成了很多插件

2、https://github.com/Pr0214/findhash  findhash插件

3、https://www.bilibili.com/video/BV1nh411z76Q  android下的inline hook原理和實踐

4、https://zhuanlan.kanxue.com/article-342.htm  MemoryAccessMonitor原理


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM