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原理

posted @ 2021-08-08 17:43  第七子007  阅读(5358)  评论(1编辑  收藏  举报