lateautumn4lin 发表于 2020-8-3 23:28

快手7.5版本sig参数逆向分析

# 快手7.5版本sig参数逆向分析

------

前几天发了一下相同标题的文章,当时比较匆忙,因为是第一次在吾爱写文加发文,只是想试试吾爱的发文感觉而已,不想点了发送,就把“满满”是图片的文章发了出去,也让很多读者觉得莫名其妙,在这里,给大家道歉了,这次重新给大家来个正式的分析文。

主要分析步骤

> * Jadx1.1.0结合Jeb1.0静态分析
> * Ida pro7.0静态分析+还原

## 1.Jadx部分

我们需要分析的是sig参数,所以直接在jadx中搜索是否有相关的引用

![](https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/faa93d53ea2845a28bdc8dde363df3d1~tplv-k3u1fbpfcp-zoom-1.image)

当然,由图可见,搜索sig的结果非常多,所以我们换个思路,因为sig很大可能是“sig”这样带引号的存在格式(不排除有其他的形式),所以我们再来搜索下“sig”
![](https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/9b72af691ffa4ba6adc7aa51e4cfd3aa~tplv-k3u1fbpfcp-zoom-1.image)

没错,结果确实少了很多,由搜索结果可见,两个跟view相关,三个跟push服务相关,明显就不是,因此,我们只要分析第一个和最后一个就好,现在我们进入最后一个的具体代码看看

![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/ba1f421a81184043bc26b4859ae14b94~tplv-k3u1fbpfcp-zoom-1.image)

代码由于jadx自身的原因,导致有些代码没有编译好,不过也是能看到大致的逻辑,调用了一个方法计算出了r6,并把r6赋值给了r8,也就是sig参数,为了直观点,我们再利用jeb来看下

## 2.Jeb部分

我们已经在jadx中知道了具体的代码位置,直接找到相关代码处,tab键转化为更加直观的java代码
![](https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/09dac0ea866f43ba82dacf96d93e495f~tplv-k3u1fbpfcp-zoom-1.image)
![](https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/272ce810453f4ab994279c2d69d4da6f~tplv-k3u1fbpfcp-zoom-1.image)
可以看到,sig参数是由CPU.getClock的方法调用的,我们再进入具体的方法代码中查看
![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/dd5cd5c6bfc94114bce86d4e069c2cc3~tplv-k3u1fbpfcp-zoom-1.image)
由图中可知,getClock的方法是native方法,有libcore.so文件引入,下面就开始继续分析libcore.so文件

## 3.Ida部分

相信so文件你们都会有方法直接拿到的吧,这部分就不细说了,打开ida之后第一步当然是通过Exports的tab页查看是否有相关函数导出,我们可以看到,有CPU_getClock的方法存在,这个命名方法是由Java_类名_方法名组成,所以就是图中这个方法
![](https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/68abb7107ecd4589a9ed42042e96953e~tplv-k3u1fbpfcp-zoom-1.image)
![](https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/ff8116425aa243f299d476dd72ccfa9f~tplv-k3u1fbpfcp-zoom-1.image)
我们直接进入方法内部查看,可以看到,图中__unwind的大括号内就是具体的方法汇编代码
![](https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/f2125ed33c01426d87eebcff6e9464f2~tplv-k3u1fbpfcp-zoom-1.image)
![](https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/9b520752a1f84be3b89a88f6df7f6356~tplv-k3u1fbpfcp-zoom-1.image)
为了方便查看,我们F5切换到伪代码界面,加上导入jni.h头,F5刷新,更新JNI Env参数等操作,嗯,逻辑已经清楚多了
![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/15d0c9f4172c407c834322a25e456b32~tplv-k3u1fbpfcp-zoom-1.image)
我们可以看看具体逻辑,首先由两个判断,判断cpu_inited、cpu_cnt的值是否存在,这两个判断没有逻辑可言,我们可以跳过,当然,读者们也可以跳进方法内部去看,调用的都是些系统方法,我们直接从if(!cpu_cnt)之后的大括号内的逻辑来看,经过一些byte数组相关的转化,来到了第一个由自定义函数得到的值--v12,v12是由sDecryptedText方法得到的,我们进入方法看看
![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/50a8408d45534741b62a7b157d919499~tplv-k3u1fbpfcp-zoom-1.image)
可以看到,v12值对应的地址是6074,v12的值是个固定值,大家可以具体深入sDecryptedText方法内部看看,这里我们直接使用frida来hook出v12的值,上代码和运行结果
![](https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/554190f6abc44cdda61676d9e3883add~tplv-k3u1fbpfcp-zoom-1.image)

frida hook的逻辑大概是:

先拿到so的基地址->加上我们之前拿到的6074的偏移量->利用frida memory的方法读地址的指针,拿到了v12的值

![](https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/c8b812ddeca54400be6d4327aff569bf~tplv-k3u1fbpfcp-zoom-1.image)

下面接着分析,过了v12之后,我们可以看到三个比较明显的地方,首先是j_cpu_clock_start->j_cpu_clock_x->j_cpu_clock_end,cpu和clock这两个字符和方法很像,看来具体的加密逻辑就在这里面,接下来看
![](https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/25636c7e60064d199bd41c5b3c3f2bb1~tplv-k3u1fbpfcp-zoom-1.image)
先进入j_cpu_clock_start看
![](https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/ee0d181a4dc744bc8be08266d3b0939e~tplv-k3u1fbpfcp-zoom-1.image)
由黄字部分进具体的代码查看
![](https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/45ce5d0b9b244027be8efb7d07b66d01~tplv-k3u1fbpfcp-zoom-1.image)
这个方法初始了一些变量,下面看看j_cpu_clock_x方法,这个方法是主要写加密逻辑的地方,我们先看看它的调用图
![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/9bbe9f0e85ba4ea6a883939640227f6e~tplv-k3u1fbpfcp-zoom-1.image)
比较清晰,接着看代码
![](https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/de580e0f970b460295d60d048d00366b~tplv-k3u1fbpfcp-zoom-1.image)
定义了一些变量之后,发现调用了sub_1EF4方法,进去看代码
![](https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/b424c774cce246648d308e0e417c1d1d~tplv-k3u1fbpfcp-zoom-1.image)
![](https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/c761d7c27bf74a4caa8d2ea70b7d3801~tplv-k3u1fbpfcp-zoom-1.image)
结合之前的代码来看,有点MD5的感觉,上代码验证下可发现,确实是加salt之后的md5,代码如下
```
public class Sign {
    public static final String FANS_SALT = "382700b563f4";
    public static String SHA512(final String strText)
    {
      return SHA(strText, "SHA-512");
    }
    private static String SHA(final String strText, final String strType)
    {
      // 返回值
      String strResult = null;

      // 是否是有效字符串
      if (strText != null && strText.length() > 0)
      {
            try
            {
                // SHA 加密开始
                // 创建加密对象 并傳入加密類型
                MessageDigest messageDigest = MessageDigest.getInstance(strType);
                // 传入要加密的字符串
                messageDigest.update(strText.getBytes());
                // 得到 byte 類型结果
                byte byteBuffer[] = messageDigest.digest();

                // 將 byte 轉換爲 string
                StringBuffer strHexString = new StringBuffer();
                // 遍歷 byte buffer
                for (int i = 0; i < byteBuffer.length; i++)
                {
                  String hex = Integer.toHexString(0xff & byteBuffer);
                  if (hex.length() == 1)
                  {
                        strHexString.append('0');
                  }
                  strHexString.append(hex);
                }
                // 得到返回結果
                strResult = strHexString.toString();
            }
            catch (NoSuchAlgorithmException e)
            {
                e.printStackTrace();
            }
      }

      return strResult;
    }
    public static String genSigSignature(Map<String,String> params, String salt) {
//      sig的算法
      if(params == null){
            return null;
      }
      String sign = "";
      StringBuffer sb = new StringBuffer();
      try {
            // 1. 字典升序排序
            SortedMap<String,String> sortedMap = new TreeMap<>(params);
            // 2. 拼按URL键值对
            Set<String> keySet = sortedMap.keySet();
            for(String key : keySet){
                //sign不参与算法
                if(key.equals("sig") || key.equals("__NStokensig")){
                  continue;
                }
                String value = sortedMap.get(key);
                sb.append(key + "=" + URLDecoder.decode(value,"UTF-8"));
            }
            String uriString = sb.toString();
            uriString = uriString + salt;
            System.out.println("My String: \n" + uriString);
            // 3. MD5运算得到请求签名
            sign = md5(uriString);
            System.out.println("My Sign:\n" +sign);
      } catch (Exception e) {
            e.printStackTrace();
      }
      return sign.toLowerCase();
    }
    public final static String md5(String s) {
      char hexDigits[]={'0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F'};
      try {
            byte[] btInput = s.getBytes();
            // 获得MD5摘要算法的 MessageDigest 对象
            MessageDigest mdInst = MessageDigest.getInstance("MD5");
            // 使用指定的字节更新摘要
            mdInst.update(btInput);
            // 获得密文
            byte[] md = mdInst.digest();
            // 把密文转换成十六进制的字符串形式
            int j = md.length;
            char str[] = new char;
            int k = 0;
            for (int i = 0; i < j; i++) {
                byte byte0 = md;
                str = hexDigits;
                str = hexDigits;
            }
            return new String(str);
      } catch (Exception e) {
            e.printStackTrace();
            return null;
      }
    }
    public static Map<String,String> getMapFromStr(String str){
      String[] arr = str.split("\\&");
      Map<String,String> map = new HashMap<>();
      for(String item : arr){
            String[] itemArr = item.split("=",2);
            map.put(itemArr,itemArr);
      }
      return map;
    }
   
    private static char[] b(byte[] bArr) {
      char[] b = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
      char[] cArr = b;
      int length = bArr.length;
      char[] cArr2 = new char[(length << 1)];
      int i = 0;
      for (int i2 = 0; i2 < length; i2++) {
            int i3 = i + 1;
            cArr2 = cArr[(bArr & 240) >>> 4];
            i = i3 + 1;
            cArr2 = cArr & 15];
      }
      return cArr2;
    }
}
```

independence 发表于 2020-10-20 17:43

虽然看不太懂但感觉好牛逼

wabc666 发表于 2020-10-20 17:26

wang-ang3 发表于 2020-8-20 20:15
楼主这边的 sDecryptedText 和 cpu_clock_x 是怎么显示出来的呀我这边显示的是dword_5078和sub_ ...

跟我一样,我已经导入jni.h了

wphoneveloper 发表于 2020-8-3 23:35

正好在分析快手签名算法,赞!

zuoruyi 发表于 2020-8-3 23:43

搞好在研究逆向,点赞

linrunqing521 发表于 2020-8-3 23:46

支持一下,不用客气

Eaglecad 发表于 2020-8-4 00:10

看着很大气,自己就是不会&#128514;

虎爷0724 发表于 2020-8-4 00:10

太深奥,需要学习学习

小小风浪 发表于 2020-8-4 00:35

感谢分享,认真学习了

chenjingyes 发表于 2020-8-4 00:36

感谢楼主分享,:lol

__不说 发表于 2020-8-4 08:26

虽然还看不太懂,但是这种有分析过程的就很喜欢!支持一下

shiina0831 发表于 2020-8-4 09:16

感谢大佬的帮助
页: [1] 2 3 4 5 6
查看完整版本: 快手7.5版本sig参数逆向分析