这个样本是一道面试题,先来看看题目要求吧
1
2
3
4
5
6
| 面试题:
要求
1.guid溯源
2.定位到oz生成的方法
3.还原算法
|
guid 溯源
先扫一下这个 apk 文件,看看有哪些 sdk 的 so 值得注意一下
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
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
| PS C:\Users\p1913> ApkCheckPack_windows_amd64.exe -f C:\Users\p1913\Desktop\面试题02\base.apk
APK检测工具 - 扫描配置:
- 文件路径: C:\Users\p1913\Desktop\面试题02\base.apk
- 检测类型: ROOT(true) 模拟器(true) 反调试(true) 代理(true) SDK(true) 硬编码(false) 证书(true)
- 最大文件大小: 500 MB
- 递归扫描: true
---------------------------------------------------
正在扫描APK文件: C:\Users\p1913\Desktop\面试题02\base.apk
- 加固特征扫描结果
- 未发现加固特征
- 安全检测特征扫描结果
- ROOT检测特征
- classes.dex -> /system/xbin/ (常见Root工具目录(分段检测))
- classes.dex -> com.kingroot.kinguser (KingRoot主程序)
- classes2.dex -> /data/local/bin/su (SU二进制文件常见路径)
- classes2.dex -> /data/local/su (SU二进制备用路径)
- classes2.dex -> /data/local/xbin/su (Xposed框架SU路径)
- classes2.dex -> /sbin/su (系统分区SU文件)
- classes2.dex -> /su/bin/su (Systemless SU路径)
- classes2.dex -> /system/app/Superuser.apk (Superuser安装包)
- classes2.dex -> /system/bin/failsafe/su (故障安全模式SU)
- classes2.dex -> /system/bin/su (系统内置SU)
- classes2.dex -> /system/sd/xbin/su (SD卡扩展SU路径)
- classes2.dex -> /system/xbin/su (常见SU存放路径)
- classes2.dex -> Superuser.apk (Superuser安装包(分段检测))
- classes2.dex -> /system/xbin/ (常见Root工具目录(分段检测))
- 模拟器检测特征
- classes.dex -> 000000000000000 (模拟器默认IMEI)
- classes.dex -> /dev/socket/qemud (QEMU守护进程socket)
- classes.dex -> /dev/qemu_pipe (QEMU管道通信接口)
- classes.dex -> emulator (模拟器标识)
- classes.dex -> goldfish (Android模拟器内核标识)
- classes.dex -> emulator (模拟器标识)
- classes2.dex -> 000000000000000 (模拟器默认IMEI)
- classes2.dex -> emulator (模拟器标识)
- classes2.dex -> 000000000000000 (模拟器默认IMEI)
- classes2.dex -> emulator (模拟器标识)
- classes2.dex -> emulator (模拟器标识)
- classes2.dex -> emulator (模拟器标识)
- classes2.dex -> test-keys (测试版系统特征)
- classes2.dex -> emulator (模拟器标识)
- classes2.dex -> emulator (模拟器标识)
- classes2.dex -> test-keys (测试版系统特征)
- classes3.dex -> emulator (模拟器标识)
- classes4.dex -> 000000000000000 (模拟器默认IMEI)
- classes4.dex -> 000000000000000 (模拟器默认IMEI)
- 反调试检测特征
- classes2.dex -> /proc/self/status (TracerPid状态检测)
- classes2.dex -> ro.debuggable (系统调试属性检测)
- classes3.dex -> ptrace (Ptrace调试检测)
- classes3.dex -> ptrace (Ptrace调试检测)
- classes4.dex -> /proc/self/status (TracerPid状态检测)
- 代理检测特征
- classes.dex -> Ljavax/net/ssl/X509TrustManager; (自定义证书信任管理器)
- classes.dex -> Ljavax/net/ssl/X509TrustManager; (自定义证书信任管理器)
- classes3.dex -> Ljavax/net/ssl/X509TrustManager; (自定义证书信任管理器)
- classes4.dex -> Ljavax/net/ssl/X509TrustManager; (自定义证书信任管理器)
- classes4.dex -> Lokhttp3/internal/proxy/NullProxySelector; (OkHttp空代理选择器)
- 第三方SDK特征扫描结果
- TDS 腾讯端服务
- Bugly -> lib/arm64-v8a/libBugly_Native.so
- Bugly -> lib/arm64-v8a/libbugly_dumper.so
- Bugly -> lib/arm64-v8a/libbuglybacktrace.so
- Bugly -> lib/armeabi/libBugly_Native.so
- Bugly -> lib/armeabi/libbugly_dumper.so
- Bugly -> lib/armeabi/libbuglybacktrace.so
- Tencent
- MMKV -> lib/arm64-v8a/libmmkv.so
- MMKV -> lib/armeabi/libmmkv.so
- Mars -> lib/arm64-v8a/libmarsxlog.so
- Mars -> lib/armeabi/libmarsxlog.so
- 腾讯云 HTTPDNS -> lib/arm64-v8a/libhttpdns.so
- 腾讯云 HTTPDNS -> lib/armeabi/libhttpdns.so
- 腾讯云语音识别 -> lib/arm64-v8a/libqcloud_asr_realtime.so
- sisong
- HDiffPatch -> lib/arm64-v8a/libhpatchz.so
- HDiffPatch -> lib/armeabi/libhpatchz.so
- 腾讯灯塔
- 腾讯灯塔终端 ID 精准识别体系 -> lib/arm64-v8a/libqimei.so
- 腾讯灯塔终端 ID 精准识别体系 -> lib/armeabi/libqimei.so
- 证书扫描结果
- 证书文件: META-INF/ANDROIDR.RSA
- 解析证书失败: x509: malformed tbs certificate
- [!]内嵌APK文件: assets/EmptyRes1.apk
- 内嵌APK大小: 0.01 MB
- 读取限制: 0.01 MB
- 加固特征扫描结果
- 未发现加固特征
- 安全检测特征扫描结果
- 未发现安全检测特征
- 证书扫描结果
- 证书文件: META-INF/CERT.RSA
- 解析证书失败: x509: malformed tbs certificate
- [!]内嵌APK文件: assets/kuikly/AppDetailPage.apk
- 内嵌APK大小: 0.36 MB
- 读取限制: 0.36 MB
- 加固特征扫描结果
- 未发现加固特征
- 安全检测特征扫描结果
- 未发现安全检测特征
- 证书扫描结果
- 未发现证书文件
- [!]内嵌APK文件: assets/kuikly/BattlePassPage.apk
- 内嵌APK大小: 0.20 MB
- 读取限制: 0.20 MB
- 加固特征扫描结果
- 未发现加固特征
- 安全检测特征扫描结果
- 未发现安全检测特征
- 证书扫描结果
- 未发现证书文件
- [!]内嵌APK文件: assets/kuikly/DiscoverTopicPage.apk
- 内嵌APK大小: 0.14 MB
- 读取限制: 0.14 MB
- 加固特征扫描结果
- 未发现加固特征
- 安全检测特征扫描结果
- 未发现安全检测特征
- 证书扫描结果
- 未发现证书文件
|
libqimei.so 需要注意一下

打开 jadx 看一下吧,一开始分析到一个 native 函数 x 了,但是那儿没感觉到它在做什么特别的事情,就像是专门用来搜集设备指纹的函数,根据传入的 key,去 system property 中查一下系统属性,感觉没做个啥,但是题目既然叫 guid 溯源,我不应该往后面走,应该往前面找

看一下这玩意儿的交叉引用,往前面找

会发现很多都会去调用 getPhoneGuidAndGen 这个方法,可能这儿会进行赋值啥的

去到 genPhoneGuid 中

发现最后的值会读取私有目录下的.pid 这个文件

这个值应该也是 app 初始化自动设置的
同时另外一些地方也能够发现 guid 的赋值是跟私有目录下的.pid 文件有关,那也要找哪儿设置的,直接搜索这个值 mPhoneGuid,看在哪儿赋值的

看一下交叉引用,发现只有一个

定位到这儿,发现就是服务端返回的

还想在这儿进行写入的

总结:
1
2
3
4
5
| 服务端响应 -> RspHead.phoneGuid
-> Global.setPhoneGuid(head.phoneGuid)
-> Global.mPhoneGuid
-> FileUtil.write(files/.pid)
-> 下次启动 Global.genPhoneGuid() 再读取这个.pid文件的值
|
oz 算法分析
下列是 unidbg 补环境的代码
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
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
| package com.yinyongbao;
import com.github.unidbg.AndroidEmulator;
import com.github.unidbg.Emulator;
import com.github.unidbg.Module;
import com.github.unidbg.arm.context.RegisterContext;
import com.github.unidbg.file.FileResult;
import com.github.unidbg.file.IOResolver;
import com.github.unidbg.hook.hookzz.HookEntryInfo;
import com.github.unidbg.hook.hookzz.HookZz;
import com.github.unidbg.hook.hookzz.HookZzArm64RegisterContext;
import com.github.unidbg.hook.hookzz.IHookZz;
import com.github.unidbg.hook.hookzz.WrapCallback;
import com.github.unidbg.linux.android.AndroidEmulatorBuilder;
import com.github.unidbg.linux.android.AndroidResolver;
import com.github.unidbg.linux.android.SystemPropertyHook;
import com.github.unidbg.linux.android.SystemPropertyProvider;
import com.github.unidbg.linux.android.dvm.*;
import com.github.unidbg.linux.android.dvm.api.Bundle;
import com.github.unidbg.memory.Memory;
import com.github.unidbg.pointer.UnidbgPointer;
import com.github.unidbg.virtualmodule.android.AndroidModule;
import com.sun.jna.Pointer;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.PrintStream;
import java.util.HashMap;
import java.util.Map;
public class OZ extends AbstractJni implements IOResolver {
// ================= 配置区 =================
// true 表示加载 arm64-v8a;false 表示加载 armeabi-v7a
private static final boolean IS_64BIT = true;
// 模拟进程名 / 包名
private static final String PROCESS_NAME = "com.tencent.android.qqdownloader";
// APK 路径。若不需要加载 APK,可设为 null
private static final String APK_PATH = "apks/yyb/base.apk";
// so 加载方式说明:
// false = 按 SO_PATH 指定的完整路径加载
// true = 按 SO_NAME 让 unidbg 从 APK 中解析并加载
private static final boolean LOAD_BY_NAME = true;
private static final String SO_PATH = ""; // LOAD_BY_NAME=false 时使用
private static final String SO_NAME = "qimei"; // LOAD_BY_NAME=true 时使用,不带 lib 前缀和 .so 后缀
// 是否打印 JNI 调用日志
private static final boolean VERBOSE = true;
// ========================================
public static AndroidEmulator emulator;
public static Memory memory;
public static VM vm;
public static Module module;
private static final String TARGET_STR = "F3LI7aF/FS8taYJD7vm5YwZvcozu1G3c8Ryl/ZRsqqk=";
// 这里可用于缓存 Bundle 模拟数据
private final Map<String, String> bundleData = new HashMap<>();
public OZ() {
// 1. 创建模拟器
emulator = IS_64BIT
? AndroidEmulatorBuilder.for64Bit().setProcessName(PROCESS_NAME).build()
: AndroidEmulatorBuilder.for32Bit().setProcessName(PROCESS_NAME).build();
// 2. 获取内存对象
memory = emulator.getMemory();
// 3. 设置 Android SDK 版本
memory.setLibraryResolver(new AndroidResolver(23));
// 4. 创建 Dalvik VM
vm = APK_PATH != null
? emulator.createDalvikVM(new File(APK_PATH))
: emulator.createDalvikVM();
vm.setJni(this);
vm.setVerbose(VERBOSE);
// 5. 注册系统模块并加载目标 so
new AndroidModule(emulator, vm).register(memory);
DalvikModule dm = LOAD_BY_NAME
? vm.loadLibrary(SO_NAME, false)
: vm.loadLibrary(new File(SO_PATH), false);
// 6. 调用 JNI_OnLoad
dm.callJNI_OnLoad(emulator);
// 7. 保存模块对象,后续下断点 / trace 要用
module = dm.getModule();
}
private static String safeReadCString(Pointer p) {
if (p == null) {
return "null";
}
try {
return p.getString(0);
} catch (Throwable e) {
return "<bad-ptr:" + p + ">";
}
}
@Override
public DvmObject<?> callObjectMethodV(BaseVM vm, DvmObject<?> dvmObject, String signature, VaList vaList) {
switch (signature) {
case "com/qq/AppService/RealAstApp->getPackageName()Ljava/lang/String;": {
return new StringObject(vm, "com.tencent.android.qqdownloader");
}
case "com/qq/AppService/RealAstApp->getContentResolver()Landroid/content/ContentResolver;": {
return vm.resolveClass("android/content/ContentResolver").newObject(null);
}
case "android/content/ContentResolver->call(Landroid/net/Uri;Ljava/lang/String;Ljava/lang/String;Landroid/os/Bundle;)Landroid/os/Bundle;": {
// 这里记录 ContentResolver.call 的 method / arg,后续在 Bundle.getString 再补返回值
String lastMethod = vaList.getObjectArg(1).getValue().toString();
String arg = vaList.getObjectArg(2).getValue().toString();
System.out.println("[*] ContentResolver.call => method=" + lastMethod + ", arg=" + arg);
// 返回一个 Bundle 对象,供后续 native 继续取值
return vm.resolveClass("android/os/Bundle").newObject(null);
}
case "android/os/Bundle->getString(Ljava/lang/String;)Ljava/lang/String;": {
String key = vaList.getObjectArg(0).getValue().toString();
System.out.println("[*] Bundle.getString => key=" + key);
// libqimei.so 在这里取 settings 中的 android_id
// 从 trace 可知:调用 GET_secure 后,会继续读取 key="value"
if ("value".equals(key)) {
String androidId = "22577096caea2b28";
System.out.println("[+] 返回模拟的 Android ID: " + androidId);
return new StringObject(vm, androidId);
}
// 其余 key 默认返回空字符串,避免 native 层拿到 null
return new StringObject(vm, "");
}
}
return super.callObjectMethodV(vm, dvmObject, signature, vaList);
}
@Override
public DvmObject<?> callStaticObjectMethodV(BaseVM vm, DvmClass dvmClass, String signature, VaList vaList) {
switch (signature) {
case "android/net/Uri->parse(Ljava/lang/String;)Landroid/net/Uri;": {
String urlString = vaList.getObjectArg(0).getValue().toString();
return vm.resolveClass("android/net/Uri").newObject(urlString);
}
}
return super.callStaticObjectMethodV(vm, dvmClass, signature, vaList);
}
@Override
public DvmObject<?> getStaticObjectField(BaseVM vm, DvmClass dvmClass, String signature) {
switch (signature) {
case "android/os/Bundle->EMPTY:Landroid/os/Bundle;": {
return vm.resolveClass("android/os/Bundle").newObject(null);
}
}
return super.getStaticObjectField(vm, dvmClass, signature);
}
public void z() {
DvmClass appClass = vm.resolveClass(
"com/qq/AppService/RealAstApp",
vm.resolveClass("android/app/Application"));
DvmObject<?> context = appClass.newObject(null);
DvmClass uClass = vm.resolveClass("com/tencent/qimei/uin/U");
DvmObject<?> ret = uClass.callStaticJniMethodObject(
emulator,
"z(Landroid/content/Context;)Ljava/lang/String;",
context);
System.out.println("[+] U.z() 返回值: " + ret.getValue());
}
public void consoleDebugger() {
emulator.attach().addBreakPoint(module.base + 0x6BB94);
// emulator.attach().addBreakPoint(module.base + 0xD0734);
}
public void traceCode() {
String traceFile = "unidbg-android/src/test/java/com/yinyongbao/traceCode.log";
PrintStream traceStream = null;
try {
traceStream = new PrintStream(new FileOutputStream(traceFile), true);
} catch (FileNotFoundException e) {
throw new RuntimeException(e);
}
// 对整个 libqimei.so 做指令级 trace,并重定向到日志文件
emulator.traceCode(module.base, module.base + module.size).setRedirect(traceStream);
}
public static void main(String[] args) {
OZ oz = new OZ();
oz.z();
}
@Override
public FileResult resolve(Emulator emulator, String pathname, int oflags) {
System.out.println("[IO] 访问路径: " + pathname);
return null;
}
}
|
这里能够返回出相应的值了

但是在经常算法分析的过程中,往往都会调用 memcpy,那一起 hook 上吧
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| public void hookMemcpy() {
final RegisterContext registerContext = emulator.getContext();
Symbol memcpySymbol = module.findSymbolByName("memcpy");
emulator.attach().addBreakPoint(memcpySymbol.getAddress(), new BreakPointCallback() {
@Override
public boolean onHit(Emulator<?> emulator, long address) {
System.out.print("call memcpy");
UnidbgPointer srcAddress = registerContext.getPointerArg(1);
int length = registerContext.getIntArg(2);
Inspector.inspect(srcAddress.getByteArray(0, length), "source:" + srcAddress);
return true;
}
});
}
|
首先这个值看起来特别符合 base64 的特征,先使用 cyberchef 来验证一下

发现它的明文值是 1772c8eda17f152f2d698243eef9b963066f728ceed46ddcf11ca5fd946caaa9 这个 hex 数据,看一下 memcpy 有没有复制这个值

有的,有的,看一下这个的写入在哪个地址
1
| emulator.traceWrite(0x125bd000, 0x125bd000 + 32);
|
看一下它的日志呢?

可以看到,它在 ida 中对应的偏移地址是 0x27330,直接跳转到 ida 中去分析一波

进去就看到这个伪码了,处理 4 次,每次处理 4 个字节,而且这儿有点像 AES 最后一轮的轮密钥加
debugger 一下
1
| emulator.attach().addBreakPoint(module.base+0x2732C);
|



这儿应该就能够判断是 AES 算法的最后一轮的轮密钥加了,测试一下
1
2
3
4
5
6
7
8
9
| emulator.attach().addBreakPoint(module.base + 0x2732C, new BreakPointCallback() {
@Override
public boolean onHit(Emulator<?> emulator, long address) {
int x15 = emulator.getBackend().reg_read(Arm64Const.UC_ARM64_REG_X15).intValue();
int x16 = emulator.getBackend().reg_read(Arm64Const.UC_ARM64_REG_X16).intValue();
System.out.printf("x15:: 0x%02x,x16:: 0x%02x%n,xor:: 0x%02x%n", x15, x16,x16^x15);
return true;
}
});
|
得到的结果如下:
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
| x15:: 0xa0,x16:: 0xb7
,xor:: 0x17
x15:: 0xc9,x16:: 0xbb
,xor:: 0x72
x15:: 0x58,x16:: 0x90
,xor:: 0xc8
x15:: 0x40,x16:: 0xad
,xor:: 0xed
x15:: 0x6f,x16:: 0xce
,xor:: 0xa1
x15:: 0x7d,x16:: 0x02
,xor:: 0x7f
x15:: 0x7f,x16:: 0x6a
,xor:: 0x15
x15:: 0x32,x16:: 0x1d
,xor:: 0x2f
x15:: 0x94,x16:: 0xb9
,xor:: 0x2d
x15:: 0x62,x16:: 0x0b
,xor:: 0x69
x15:: 0xa7,x16:: 0x25
,xor:: 0x82
x15:: 0x6b,x16:: 0x28
,xor:: 0x43
x15:: 0x23,x16:: 0xcd
,xor:: 0xee
x15:: 0xd6,x16:: 0x2f
,xor:: 0xf9
x15:: 0xbf,x16:: 0x06
,xor:: 0xb9
x15:: 0x34,x16:: 0x57
,xor:: 0x63
x15:: 0xa0,x16:: 0xa6
,xor:: 0x06
x15:: 0xc9,x16:: 0xa6
,xor:: 0x6f
x15:: 0x58,x16:: 0x2a
,xor:: 0x72
x15:: 0x40,x16:: 0xcc
,xor:: 0x8c
x15:: 0x6f,x16:: 0x81
,xor:: 0xee
x15:: 0x7d,x16:: 0xa9
,xor:: 0xd4
x15:: 0x7f,x16:: 0x12
,xor:: 0x6d
x15:: 0x32,x16:: 0xee
,xor:: 0xdc
x15:: 0x94,x16:: 0x65
,xor:: 0xf1
x15:: 0x62,x16:: 0x7e
,xor:: 0x1c
x15:: 0xa7,x16:: 0x02
,xor:: 0xa5
x15:: 0x6b,x16:: 0x96
,xor:: 0xfd
x15:: 0x23,x16:: 0xb7
,xor:: 0x94
x15:: 0xd6,x16:: 0xba
,xor:: 0x6c
x15:: 0xbf,x16:: 0x15
,xor:: 0xaa
x15:: 0x34,x16:: 0x9d
,xor:: 0xa9
|
继续往上面看,发现很多地方都在查表

这里的这个 byte_21C9E1 就是 S 盒,标准的
好,既然是 AES,那就要考虑是什么模式?ECB/CBC,key 是什么,有没有 iv,明文是什么,填充方式是什么?是不是白盒 AES,但这儿不像,流程很清楚的感觉
接着继续往上面看,在最开始的这个地方,16 个字节逐字节进行异或,还是在函数一开始,怀疑明文在与 iv 进行异或

hook 一下
1
2
3
4
5
6
7
8
9
| emulator.attach().addBreakPoint(module.base + 0x270A4, new BreakPointCallback() {
@Override
public boolean onHit(Emulator<?> emulator, long address) {
int x12 = emulator.getBackend().reg_read(Arm64Const.UC_ARM64_REG_X12).intValue();
int x14 = emulator.getBackend().reg_read(Arm64Const.UC_ARM64_REG_X14).intValue();
System.out.printf("x12:: 0x%02x,x14:: 0x%02x%n,xor:: 0x%02x%n", x12, x14,x12^x14);
return true;
}
});
|
hook 结果如下:
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
| source:unidbg@0xe4fff6a1, md5=1a5e931a0e2aaf547ade19321f85d811, hex=32323537373039366361656132623238
size: 16
0000: 32 32 35 37 37 30 39 36 63 61 65 61 32 62 32 38 22577096caea2b28
^-----------------------------------------------------------------------------^
x12:: 0x5a,x14:: 0x32
,xor:: 0x68
x12:: 0x73,x14:: 0x32
,xor:: 0x41
x12:: 0x30,x14:: 0x35
,xor:: 0x05
x12:: 0x6e,x14:: 0x37
,xor:: 0x59
x12:: 0x74,x14:: 0x37
,xor:: 0x43
x12:: 0x44,x14:: 0x30
,xor:: 0x74
x12:: 0x71,x14:: 0x39
,xor:: 0x48
x12:: 0x47,x14:: 0x36
,xor:: 0x71
x12:: 0x32,x14:: 0x63
,xor:: 0x51
x12:: 0x6a,x14:: 0x61
,xor:: 0x0b
x12:: 0x79,x14:: 0x65
,xor:: 0x1c
x12:: 0x68,x14:: 0x61
,xor:: 0x09
x12:: 0x4b,x14:: 0x32
,xor:: 0x79
x12:: 0x4e,x14:: 0x62
,xor:: 0x2c
x12:: 0x30,x14:: 0x32
,xor:: 0x02
x12:: 0x63,x14:: 0x38
,xor:: 0x5b
x12:: 0x17,x14:: 0x10
,xor:: 0x07
x12:: 0x72,x14:: 0x10
,xor:: 0x62
x12:: 0xc8,x14:: 0x10
,xor:: 0xd8
x12:: 0xed,x14:: 0x10
,xor:: 0xfd
x12:: 0xa1,x14:: 0x10
,xor:: 0xb1
x12:: 0x7f,x14:: 0x10
,xor:: 0x6f
x12:: 0x15,x14:: 0x10
,xor:: 0x05
x12:: 0x2f,x14:: 0x10
,xor:: 0x3f
x12:: 0x2d,x14:: 0x10
,xor:: 0x3d
x12:: 0x69,x14:: 0x10
,xor:: 0x79
x12:: 0x82,x14:: 0x10
,xor:: 0x92
x12:: 0x43,x14:: 0x10
,xor:: 0x53
x12:: 0xee,x14:: 0x10
,xor:: 0xfe
x12:: 0xf9,x14:: 0x10
,xor:: 0xe9
x12:: 0xb9,x14:: 0x10
,xor:: 0xa9
x12:: 0x63,x14:: 0x10
,xor:: 0x73
|
这里在对明文进行填充,0x10 就是填充的长度,正好是 pkcs7 的填充方式,明文就是补环境的那个 android_id 值,那 x12 就是 iv 了
推出 x12 就是 Zs0ntDqG2jyhKN0c

那最后就还剩下 key 需要验证了,key 是什么呢? 继续看就发现到了这儿

a1+176,在 AES128 中,密钥扩展的长度刚好是 176 个字节,v8 被赋值成 a1+16,16 应该是主密钥的长度,160 是需要扩展的密钥的长度,a1 又是当前函数的参数,直接 hook 一下当前的函数

好了,现在得到了 key,iv,明文,就可以去 cyberchef 中验证一下了


所以,就得到了算法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
| from Crypto.Cipher import AES
from Crypto.Util.Padding import pad
import base64
def qimei_encrypt(text, key, iv):
"""
AES-128-CBC 加密 + Base64 编码
"""
key_bytes = key.encode('utf-8')
iv_bytes = iv.encode('utf-8')
cipher = AES.new(key_bytes, AES.MODE_CBC, iv_bytes)
padded_data = pad(text.encode('utf-8'), AES.block_size)
encrypted_bytes = cipher.encrypt(padded_data)
result = base64.b64encode(encrypted_bytes).decode('utf-8')
return result
key = "lvcwmSYVr2Axv1gn"
iv = "Zs0ntDqG2jyhKN0c"
plaintext = "22577096caea2b28"
final_result = qimei_encrypt(plaintext, key, iv)
print(f"明文: {plaintext}")
print(f"加密结果: {final_result}")
|
总结:
btoa(aes("22577096caea2b28"))