3262 字
16 分钟
某宝app逆向分析

这个样本是一道面试题,先来看看题目要求吧

Terminal window
面试题:
要求
1.guid溯源
2.定位到oz生成的方法
3.还原算法

guid 溯源#

先扫一下这个 apk 文件,看看有哪些 sdk 的 so 值得注意一下

Terminal window
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 需要注意一下
image.png

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

image.png 会发现很多都会去调用 getPhoneGuidAndGen 这个方法,可能这儿会进行赋值啥的
image.png 去到 genPhoneGuid 中
image.png 发现最后的值会读取私有目录下的.pid 这个文件
image.png 这个值应该也是 app 初始化自动设置的 同时另外一些地方也能够发现 guid 的赋值是跟私有目录下的.pid 文件有关,那也要找哪儿设置的,直接搜索这个值 mPhoneGuid,看在哪儿赋值的
image.png

看一下交叉引用,发现只有一个
image.png 定位到这儿,发现就是服务端返回的
image.png

还想在这儿进行写入的 image.png

总结:

服务端响应 -> RspHead.phoneGuid
-> Global.setPhoneGuid(head.phoneGuid)
-> Global.mPhoneGuid
-> FileUtil.write(files/.pid)
-> 下次启动 Global.genPhoneGuid() 再读取这个.pid文件的值

oz 算法分析#

下列是 unidbg 补环境的代码

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;
}
}

这里能够返回出相应的值了
image.png

但是在经常算法分析的过程中,往往都会调用 memcpy,那一起 hook 上吧

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 来验证一下
image.png 发现它的明文值是 1772c8eda17f152f2d698243eef9b963066f728ceed46ddcf11ca5fd946caaa9 这个 hex 数据,看一下 memcpy 有没有复制这个值
image.png 有的,有的,看一下这个的写入在哪个地址

emulator.traceWrite(0x125bd000, 0x125bd000 + 32);

看一下它的日志呢?
image.png 可以看到,它在 ida 中对应的偏移地址是 0x27330,直接跳转到 ida 中去分析一波
image.png 进去就看到这个伪码了,处理 4 次,每次处理 4 个字节,而且这儿有点像 AES 最后一轮的轮密钥加
debugger 一下

emulator.attach().addBreakPoint(module.base+0x2732C);

image.png image.png image.png 这儿应该就能够判断是 AES 算法的最后一轮的轮密钥加了,测试一下

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;
}
});

得到的结果如下:

Terminal window
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

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

hook 一下

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 结果如下:

Terminal window
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 image.png 那最后就还剩下 key 需要验证了,key 是什么呢? 继续看就发现到了这儿
image.png a1+176,在 AES128 中,密钥扩展的长度刚好是 176 个字节,v8 被赋值成 a1+16,16 应该是主密钥的长度,160 是需要扩展的密钥的长度,a1 又是当前函数的参数,直接 hook 一下当前的函数
image.png 好了,现在得到了 key,iv,明文,就可以去 cyberchef 中验证一下了
image.png image.png 所以,就得到了算法

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"))

某宝app逆向分析
https://819229.xyz/posts/某宝app逆向分析/
作者
0xnonce
发布于
2026-04-27
许可协议
CC BY-NC-SA 4.0

分享文章

生成精美分享图或复制链接,与更多人分享本文。

继续阅读

沿着主题读

基于共同的标签与分类

换条路线

从其他文章中稳定抽取

评论

正在加载评论...
0/2000

    最后更新于 ,距今已过 125

    部分内容可能已过时