免责声明:本篇文章仅用来学习交流,请勿用于商业用途,如因违反规定产生任何法律纠纷,本人概不负责。假如本文影响到官方任何利益,请联系文章作者告知,我会在第一时间将文章删除,感谢~


# 记录下某 c电新闻apk 的逆向,技术点包括 sslpinning ,脱壳及静态分析, hmac sha256md5 算法还原。因为之前研究过一次,这次顺便帮个兄弟看看 search 接口

  1. # 抓包分析接口

    • appsslpinning ,所以使用 frida 脚本或者 objection 封装的 sslpinng 过掉,这里我用的 objection ,因为方便😁

      # 一条指令解决,-P 是载入插件,后面脱壳用
      objection -g  包名 explore -s "android sslpinning disable --quiet" -P .objection/plugins

      关于 objection 的用法和脱壳插件使用,之前的文章有介绍,可以翻阅。

    • 猛一看有点劝退,不过不要慌,大部分加密参数不是必须的, postman 测试了一下,发现加密的参数只有 signature 是必须的

  2. # 脱壳并静态分析

    • apk 拖到 jadx 不难发现是 360 加固,或者拖进 PKID 看一下

    • frida-dexdump 一把梭,再次鼓吹一波,葫芦娃大佬的插件真特么好用,这里再推荐一波葫芦娃大佬的一键解 ollvm 混淆神器: Obpo

      # 用 objection 配合 frida_dexdump 使用的,输入以下指令等待脱壳完成
      plugin dexdump dump

    • 忽略上面的报错,先 jadx 分析一哈,大部分时候这些报错的 dex 不影响我们分析,实在不行上寒冰大佬的 fart

    • 那么多参数,随便一搜,参数都怼一起了,真就随便搜, q 方法生成 signature , str4 逻辑在红框位置

    • 先用 objection hook 下这个类,发现确实调用了一些函数,说明位置没错

      android hooking watch class  com.touchtv.internetSDK.network.d 	# hook 类下面的所有 method

    • hook 具体 method 确定了加密位置,刚开始搜到的那个 s 方法和生成 signatureq 方法,明文就这么呈现在眼前; s 方法传入四个参数: urlparampostextraParams 类实例, q 方法传入两个参数:第一个是 url ,第二个是 posturl时间戳base64密文\n 连接的字串

    • 再看刚才的逻辑, str 就是 urlstr4 是带 base64 的参数,所以只能是中间那里生成,就是 md5base64 结果

    • 跟进 q 看生成 signature 的逻辑,是 Hmac SHA256

    • 又跟进去确认了一下 key 的值是固定的,且频道页和详情页不同

    • 上面那个 base64 的密文还没确定是什么参数,既然是 MessageDigest 加密,直接通用加密脚本 hook

      var messageDigest = Java.use("java.security.MessageDigest");
      console.log("success")
      messageDigest.update.overload('byte').implementation = function (data) {
          console.log("MessageDigest.update('byte') is called!");
          return this.update(data);
      }
      messageDigest.update.overload('java.nio.ByteBuffer').implementation = function (data) {
          console.log("MessageDigest.update('java.nio.ByteBuffer') is called!");
          return this.update(data);
      }
      messageDigest.update.overload('[B').implementation = function (data) {
          console.log("MessageDigest.update('[B') 被调用了!");
          // 调用 getAlgorithm 得到算法名,因为 getAlgorithm 为静态方法用 this 调用
          var algorithm = this.getAlgorithm();
          var tag = algorithm + "调用update得到的数据:";
          //tag 为标签,data 为数据
          toUtf8(tag, data);
          toHex(tag, data);
          toBase64(tag, data);
          console.log("=======================================================");
          return this.update(data);
      }
      messageDigest.update.overload('[B', 'int', 'int').implementation = function (data, start, length) {
          console.log("MessageDigest.update('[B', 'int', 'int') 被调用了!");
          var algorithm = this.getAlgorithm();
          var tag = algorithm + "调用update得到的数据:";
          toUtf8(tag, data);
          toHex(tag, data);
          toBase64(tag, data);
          console.log("=======================================================", start, length);
          return this.update(data, start, length);
      }
      messageDigest.digest.overload().implementation = function () {
          console.log("MessageDigest.digest() 被调用了!");
          var result = this.digest();
          var algorithm = this.getAlgorithm();
          var tag = algorithm + " 调用digest返回输出的数据:";
          toHex(tag, result);
          toBase64(tag, result);
          console.log("=======================================================");
          return result;
      }
      messageDigest.digest.overload('[B').implementation = function (data) {
          console.log("MessageDigest.digest('[B') 被调用了!");
          var algorithm = this.getAlgorithm();
          var tag = algorithm + " 调用digest得到的数据:";
          toUtf8(tag, data);
          toHex(tag, data);
          toBase64(tag, data);
          var result = this.digest(data);
          var tags = algorithm + " 调用digest返回输出的数据:";
          toHex(tags, result);
          toBase64(tags, result);
          console.log("=======================================================");
          return result;
      }
      messageDigest.digest.overload('[B', 'int', 'int').implementation = function (data, start, length) {
          console.log("MessageDigest.digest('[B', 'int', 'int') 被调用了!");
          var algorithm = this.getAlgorithm();
          var tag = algorithm + " 调用digest得到的数据:";
          toUtf8(tag, data);
          toHex(tag, data);
          toBase64(tag, data);
          var result = this.digest(data, start, length);
          var tags = algorithm + " 调用digest返回输出的数据:";
          toHex(tags, result);
          toBase64(tags, result);
          console.log("=======================================================", start, length);
          return result;
      }
    • 可以看到明文就是参数,密文是 base64 的结果,注意这里不是 md5 结果再转 base64 !!!

  3. # 算法还原

    • 思路已经理顺

      ①请求参数进行 md5 加密,并获取 base64 的结果,

      ②然后再和 post , url , 时间戳 拼接生成字串,

      ③最后用固定的 key 对这段字串进行 HmacSHA256 加密

    • python 还原算法,详情页也跟了下加密逻辑一样,只是公钥不同

      import base64
      import hmac
      from hashlib import sha256
      def get_hash():
              param = """{"pageSize":"15","keyword":"苏州","pageNum":1,"searchType":0,"type":0}""" 
              md5 = hashlib.md5()
              md5.update(param.encode("utf-8"))
              ret = base64.b64encode(md5.digest()).decode()	# 注意这里不是 hexdigest
              
      def get_signature(url, ts, ret, ch):
          """
          :param ts: 请求头的时间戳
          :param ch: 判断是频道页还是详情页
          :type url: post请求的url
          :return: 返回签名
          """
          hash_code = get_hash()
          if ch:
              key = "****************************************************************".encode('utf-8')  # 频道页公钥
          else:
              key = "****************************************************************".encode('utf-8')  # 详情页公钥
          final = "POST\n%s\n%s\n%s" % (url, ts, ret)		# 拼接明文
          data = final.encode()
          signature = base64.b64encode(hmac.new(key, data, digestmod=sha256).digest())
          return signature
更新于 阅读次数

请我喝[茶]~( ̄▽ ̄)~*

渣渣文 微信支付

微信支付

渣渣文 支付宝

支付宝