【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)
'''
```
到这里就分析结束啦!也希望能帮到一些和我一样的新手们 417788939 发表于 2020-11-11 15:02
表哥之家
这软件好像是手表发烧友的最爱
是滴呢,被发现了 虽然不懂这个 但APP我天天用{:301_1004:}
谢谢分享 表哥之家{:17_1068:}
这软件好像是手表发烧友的最爱 可以的! 谢谢你的分享
谢谢你的分享 满满的干货,有东西的!!! 思路清晰,过程明确,非常棒!感谢分享