jidesheng6 发表于 2020-11-11 12:54

【X表之家】的加密分析

本帖最后由 jidesheng6 于 2020-11-11 12:55 编辑

> 这次分析的原因是因为群里有人想让我帮他做个签到脚本,我晚上无聊看了一下,结果发现这个软件对响应体进行了加密,于是便有了这篇文章

# 1.准备工作

`APP样本`

`Xposed`

`反射大师`

`MT管理器`

`模拟器`

我本人使用的是IOS,所以用模拟器来分析安卓版本的加密过程。

# 2. 脱壳

安装软件之后,使用`MT管理器`查看软件的加固方式,可以看到,下面的图片显示:`腾讯御安全`。



使用`反射大师`进行脱壳(具体过程不做阐述,站内有教程,只是写出dex记得长按,就能写出所有dex)。

# 3.抓包

脱壳之后,先不要急着看源码,首先进行`抓包`。
我用的是IOS上的`Thor`。

我用登陆的接口作为演示,首先是登陆的`请求头`信息,如下图所示(个人觉得没什么有用的信息)



接着看登陆接口的`返回`信息,看起来像是AES/DES的某种加密,这样就有思路了,搜索的范围也可以稍微小一点。



# 4.代码分析

我们使用`MT管理器`搜索AES、DES、descrypt等关键字,因为这几个关键字有可能是某个类/方法/字符串,能够极大提高我们找到关键信息的成功率。



我首先使用`AES`搜索,这里需要注意,搜索结果会很多,我们一般去看和`APP包名`一样的`packages`名的就可以了,如下图所示。



接着我们点击进去,转换成java(需要更换引擎,几个都试试),这个类中的代码如下:

```java
package com.xbiao.utils;

import android.annotation.SuppressLint;
import android.util.Base64;
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;

public class AESedeUtil {
    private static final String INIT_VECTOR = "6di50aH901duea7d";

    @SuppressLint({"NewApi"})//解密方法
    public static String decrypt(String var0, String var1) {
      try {
            IvParameterSpec var3 = new IvParameterSpec("6di50aH901duea7d".getBytes("UTF-8"));
            SecretKeySpec var2 = new SecretKeySpec(var0.getBytes("UTF-8"), "AES");
            Cipher var5 = Cipher.getInstance("AES/CBC/NOPADDING");
            var5.init(2, var2, var3);
            var0 = new String(var5.doFinal(Base64.decode(var1, 0)));
            return var0;
      } catch (Exception var4) {
            var4.printStackTrace();
            return null;
      }
    }

    @SuppressLint({"NewApi"})//加密方法
    public static String encrypt(String var0, String var1) {
      try {
            IvParameterSpec var2 = new IvParameterSpec("6di50aH901duea7d".getBytes("UTF-8"));
            SecretKeySpec var3 = new SecretKeySpec(var0.getBytes("UTF-8"), "AES");
            Cipher var5 = Cipher.getInstance("AES/CBC/NOPADDING");
            var5.init(1, var3, var2);
            byte[] var6 = var5.doFinal(var1.getBytes());
            var0 = java.util.Base64.getEncoder().encodeToString(var6);
            return var0;
      } catch (Exception var4) {
            var4.printStackTrace();
            return null;
      }
    }
}
```

我没学过java,只学过类似的c#,上面的`encrypt`和`decrypt`的算法,还不知道是不是用于解密`响应消息`的,从代码中我们能得到的信息是:`使用了AES的CBC模式,还有一个NOPADDING参数`,同时我们从开头的定义还知道了,那个变量的值应该是偏移`iv`的值,所以也需要留意一下,既然已经知道了方法名,我们就回去搜索一下`decrypt`,看看哪里调用了这个方法,解密的是什么内容。

我们在一个叫做`NetContent`的类中找到了如下图所示的字段



其中有个方法名引起了我的注意:`decryptResponse`,翻译过来就是解密响应信息,我觉得八九不离十,就是这里了,打开进行反编译,代码段内容如下:

```java
private static String decryptResponse(String var0, String var1) {
      String var2 = LocalCookieManager.getInstance().getSecretKey();
      if (!TextUtils.isEmpty(var0) && !TextUtils.isEmpty(var2)) {
            var0 = AESedeUtil.decrypt(var2, var0);//调用了decrypt的方法
      } else {
            var0 = "";
      }

      label20: {
            try {
                var2 = changeCharset(var0, "utf-8");
            } catch (UnsupportedEncodingException var3) {
                var3.printStackTrace();
                break label20;
            }

            var0 = var2;
      }

      var2 = var0.trim();
      var0 = var2;
      if (TextUtils.isEmpty(var2)) {
            var0 = "";
      }

      StringBuilder var4 = new StringBuilder();
      var4.append("url:");
      var4.append(var1.replace(NewsAccessor.BASE_HEAD_URL, ""));
      var4.append("最终结果response***************:");
      var4.append(var0);
      Log.i("liuguangyou", var4.toString());
      return var0;
    }
```
从上面的代码中,可以看到是调用了我们之前找的那个类里面的方法了的。
所以我们要分析一下参数`var2`和`var0`是怎么来的
我们看到`var2`是调用了一个类中的方法来生成的:
```java
String var2 = LocalCookieManager.getInstance().getSecretKey();
```
由于本身这个`decryptResponse`也是要被调用的,所以着重分析`var2`的生成方式

我们看到这里的类叫做`LocalCookieManager`所以可能就是请求头里面携带的Cookie内容,这样我们的范围就能缩小了一点,再看后面的`getSecretKey`方法,翻译过来就是密钥,所以`var2`就是这个算法的解密密钥,我们去`MT`搜索`getSecretKey`。

搜索到了`LocalCookieManager`类中,反编译代码的时候,发现如下一串代码:
```java
private static LocalCookieManager infoManager;
    private String searchCookiePublicKey;
    private String secretKey;
   
    private LocalCookieManager() {
      this.searchCookiePublicKey = "publicKey=";
    }
```
因为这个类是Cookie的类,于是回去寻找抓包信息中的Cookie中,是否有这个KEY

果然,在之前的抓包结果中,是有如上的KEY的,一切都证明我们的方向是正确的


在这个类中,一直看到最后,看到了一个方法名`subCookieSecretKey`,字面意思是分割Cookie密钥,于是猜测,可能是从`publicKey`的值中生成密钥,因为变量的名字也很明确,`searchCookiePublicKey`.搜索的就是这个KEY,于是反编译代码:

```java
public void subCookieSecretKey(String s) {
      if (!TextUtils.isEmpty((CharSequence)s)) {
            s = URLDecoder.decode(s.replaceAll(":eq:", "; "));
            final int index = s.indexOf(this.searchCookiePublicKey);
            if (index != -1) {
                int n;
                if ((n = s.indexOf(";", index)) == -1) {
                  n = s.length();
                }
                final String replace = s.substring(index, n).trim().replace(this.searchCookiePublicKey, "");
                s = replace.substring(0, 5);
                final String substring = replace.substring(replace.length() - 11, replace.length());
                final StringBuilder sb = new StringBuilder();
                sb.append(s);
                sb.append(substring);
                this.secretKey = sb.toString();
                if (TextUtils.isEmpty((CharSequence)this.secretKey)) {
                  this.secretKey = SharedPreferencesPublicKeyUtil.getStringValueFromSP("secretKey");
                }
                SharedPreferencesPublicKeyUtil.setStringDataIntoSP("secretKey", this.secretKey);
            }
      }
```
前面有一端是判断值是否存在的,所以只要看一部分就好
```
final String replace = s.substring(index, n).trim().replace(this.searchCookiePublicKey, "");
```
上面这段可以看出,是把有`publicKey=`的这一段给替换去除,所以也就是这个KEY的值了,接下来都按照这个思路去走。

```java
                s = replace.substring(0, 5);
                final String substring = replace.substring(replace.length() - 11, replace.length());
                final StringBuilder sb = new StringBuilder();
                sb.append(s);
                sb.append(substring);
                this.secretKey = sb.toString();
```
上面的代码中,可以看到,用`publicKey`的值,取其索引`第一位`到`第四位`。
为了方便讲解,我把我的KEY值贴上来。
我的KEY值是:5BJ3e+kjDdAIy9gEvnJasIRXqZFnjfTG3Om4ER/Tcz4=

所以此时s=5BJ3e,接着再取`KEY值长度`-11的位置到`KEY值`末尾的索引
这个时候substring=Om4ER/Tcz4=,后面把这两个值进行拼接,赋值给变量`secretKey`.
所以最后`secretKey`的值就是`5BJ3eOm4ER/Tcz4=`

我们逆推,看看`secretKey`是在哪里,我们在源码中找到如下代码:
```java
public String getSecretKey() {
      return this.secretKey;
    }
```
所以这就是在`NetContent`类中被调用的方法。

回到AES的解密中,看到这段代码:
```java
IvParameterSpec var3 = new IvParameterSpec("6di50aH901duea7d".getBytes("UTF-8"));
            SecretKeySpec var2 = new SecretKeySpec(var0.getBytes("UTF-8"), "AES");
            Cipher var5 = Cipher.getInstance("AES/CBC/NOPADDING");
            var5.init(2, var2, var3);
```
推测判断,`var3`为`iv偏移量`,`var2`为解密的密钥,参数`var0`为密文数据,通过`var5`解密返回原始数据。

为了验证这个猜想,我找到了一个AES解密的网站,分别填入`密文`、`解密KEY`、`iv偏移`,得到结果如下:



处理了一下,最终内容如下:




# 5.结语

后面的内容我没有继续深入下去,因为这个APP其实定时发送GET进行签到正常没事的,只是加密的内容你不知道是什么而已,我也很感谢那个提供APP的人,让我又学习到了新的东西。

在这过程中,踩了不少坑,比如手机上的iv偏移值不支持输入字符串,不支持选择填充,所以解密数据是不完整的,好在找到了一个不错的AES解密网站。

后期的脚本没有写,本来打算写JS版本的,想想还是算了,最后奉上在这次过程中,用于调试的python代码:
```python
Cookie="5BJ3e+kjDdAIy9gEvnJasIRXqZFnjfTG3Om4ER/Tcz4="
Front_Str = Cookie
After_Str = Cookie
Full_SecKey = Front_Str+After_Str
print(Full_SecKey)
Iv_Str = "6di50aH901duea7d"
'''
Iv_Bytes_Str = ""
for Byte in Iv_Str:
    Iv_Bytes_Str+=str(ord(Byte))

SecKey_Byte_Str = ""
for Key in Full_SecKey:
    SecKey_Byte_Str+=str(ord(Key))
print(SecKey_Byte_Str)
'''
```

到这里就分析结束啦!也希望能帮到一些和我一样的新手们

jidesheng6 发表于 2020-11-11 15:16

417788939 发表于 2020-11-11 15:02
表哥之家

这软件好像是手表发烧友的最爱

是滴呢,被发现了

宿醉的秃子 发表于 2020-11-11 13:12

虽然不懂这个   但APP我天天用{:301_1004:}

你把气球吹炸了 发表于 2020-11-11 14:21


谢谢分享

芽衣 发表于 2020-11-11 15:02

表哥之家{:17_1068:}

这软件好像是手表发烧友的最爱

大头BigHead 发表于 2020-11-11 15:58

可以的!

twticfvtk 发表于 2020-11-11 16:12

谢谢你的分享

jh767676 发表于 2020-11-11 16:29


谢谢你的分享

tenwool 发表于 2020-11-11 17:10

满满的干货,有东西的!!!

XS30 发表于 2020-11-11 17:31

思路清晰,过程明确,非常棒!感谢分享
页: [1] 2 3 4
查看完整版本: 【X表之家】的加密分析