使用 xposed 模块修改加解密逻辑

前言

使用 xposed 模块修改加解密逻辑。

前文我们提到了某ji的内部中的某段解密逻辑,在那里使用到了 AES 来对网络返回的结果进行解密。

而这篇文章我们主要是来写一写,如何通过 Xposed 模块来修改类似的逻辑。

正文

本文的代码我们使用以下代码进行分析:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public final class C1057a {
public static String a(String str) throws Exception {
byte[] bArr;
byte[] bytes = "4afe899e18fa915e939a6676a2c6ce60".getBytes(StandardCharsets.UTF_8);
SecretKeySpec secretKeySpec = new SecretKeySpec(bytes, "AES");
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
String string = "fdd140e3d1b737a6";
byte[] bytes2 = string.getBytes(StandardCharsets.UTF_8);
cipher.init(2, secretKeySpec, new IvParameterSpec(bytes2));
byte[] bArr2 = new byte[str.length() / 2];
int length = str.length() / 2;
for (int i8 = 0; i8 < length; i8++) {
int i10 = i8 * 2;
int i11 = i10 + 1;
String strSubstring = str.substring(i10, i11);
int i12 = Integer.parseInt(strSubstring, 16);
String strSubstring2 = str.substring(i11, i10 + 2);
bArr2[i8] = (byte) ((i12 * 16) + Integer.parseInt(strSubstring2, 16));
}
byte[] bArrDoFinal = cipher.doFinal(bArr2);
Charset charsetForName = Charset.forName("utf-8");
return new String(bArrDoFinal, charsetForName);
}
}

什么,你问我代码哪来的,其实是我在梦中梦到的🥹,绝不是上篇帖子改的🤗。

这里我们以旧版 xposed api 来编写。

如果我们想让任何字符串进入该函数,统一返回相同的字符串,那么我们可以写:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
public class MainHook implements IXposedHookLoadPackage {
// 这里写包名,比如 com.a.b
static String TARGET_PACKAGE_NAME = "com.a.b";
// 这里写类名,注意要写完整路径
static String TARGET_CLASS_NAME = "C1057a";
// 这里写方法名
static String TARGET_METHOD_NAME = "a";

@Override
public void handleLoadPackage(XC_LoadPackage.LoadPackageParam param) {
if (!param.packageName.equals(TARGET_PACKAGE_NAME)) {
return;
}
hook(param);
}

private void hook(XC_LoadPackage.LoadPackageParam param) {
XposedHelpers.findAndHookMethod(
TARGET_CLASS_NAME,
param.classLoader, // classLoader 使用lpparam.classLoader
TARGET_METHOD_NAME, // 要hook的方法
String.class, // 要hook的方法的参数表,如果有多个就用逗号隔开
new XC_MethodHook() { // 最后一个填hook的回调
@Override
protected void beforeHookedMethod(MethodHookParam param) {
param.setResult("[{\"identity\":\"003-000-00001\",\"privilege\":{\"totalInServiceDays\":0},\"privilegeStatus\":\"activated\",\"canPay\":false}]".getBytes());
}

@Override
protected void afterHookedMethod(MethodHookParam param) {
}
}
);
}
}

这里的核心就是使用 findAndHookMethod 找到对应的方法,然后通过重写 XC_MethodHook 的 beforeHookedMethod 来修改返回值。

当然,这种方法有一个问题,就是当版本更新的时候,这个类名可能会由于混淆而变更。这样每个版本都得修改代码,非常的麻烦。

那么,有没有另一种方法可以一劳永逸呢?

其实,我们可以换个角度,思考下这个方法的结果是从哪来的,回答 cipher.doFinal ,而且这个方法是内置的方法,是不会被混淆的,那么,我们就可以 hook 这个方法,当然,我们必须得区分 cipher 这个实例是来自 C1057a.a 的,所以这里我们需要通过 key 和 iv 来做判断:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
public class MainHook implements IXposedHookLoadPackage {
static String TARGET_PACKAGE_NAME = "com.a.b";
static String TARGET_CLASS_NAME = "C1057a";
static String TARGET_METHOD_NAME = "a";

@Override
public void handleLoadPackage(XC_LoadPackage.LoadPackageParam param) {
if (!param.packageName.equals(TARGET_PACKAGE_NAME)) {
return;
}
hook(param);
}

// hook 底层的 Cipher ,判断对应的 key 和 iv 来做 patch
private static final WeakHashMap<Cipher, Boolean> targetCiphers = new WeakHashMap<>();

void hook(XC_LoadPackage.LoadPackageParam param) {
XposedHelpers.findAndHookMethod(
Cipher.class,
"init",
int.class,
Key.class,
AlgorithmParameterSpec.class,
new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) {
Key key = (Key) param.args[1];
AlgorithmParameterSpec spec = (AlgorithmParameterSpec) param.args[2];

if (!(key instanceof SecretKeySpec)) {
return;
}
if (!(spec instanceof IvParameterSpec)) {
return;
}
// init 返回的对象
Cipher cipher = (Cipher) param.thisObject;

// 需要为 AES
if (!cipher.getAlgorithm().contains("AES/CBC")) {
return;
}

// key 和 iv 判断
String keyHex = new String(key.getEncoded(), StandardCharsets.UTF_8);
String ivHex = new String(((IvParameterSpec) spec).getIV(), StandardCharsets.UTF_8);
if (keyHex.contains("06ec6c2a") && ivHex.contains("6a737b1d")) {
// 把对象存起来
targetCiphers.put(cipher, true);
}
}
}
);
XposedHelpers.findAndHookMethod(
Cipher.class,
"doFinal",
byte[].class,
new XC_MethodHook() {
@Override
protected void afterHookedMethod(MethodHookParam param) {
Cipher cipher = (Cipher) param.thisObject;

// chiper 对象是否在 WeakMap 中
if (!Boolean.TRUE.equals(targetCiphers.get(cipher))) {
return;
}

byte[] plain = (byte[]) param.getResult();
String text = new String(plain, StandardCharsets.UTF_8);

// 这里可以重写返回值
String fake = "";
param.setResult(fake.getBytes(StandardCharsets.UTF_8));
}
}
);
}
}

当然我们这个写法是旧版 xposed 的,使用新版 API 的写法可以查看我的另一个帖子:记一次将 xposed 模块迁移到新版 api。