安卓端某音乐类 APP 逆向分享(五):NMDI 参数加密分析
NMDI 参数的加密分析是本系列逆向过程中技术难度最高的环节。NMDI 由一个名为 caesarson 的自研加密算法生成,本文将从 Java 层调用链溯源出发,经由 Frida Hook、IDA 静态分析、unidbg 动态追踪,最终完整还原 caesarson 加密算法的 Python 实现。
一、参数定位
使用 Jadx 搜索关键字 NMDI,可定位到 NMDI 参数的生成位置位于:
com.example.music.core.security.c.intercept
其中第 22 行将变量 c 赋值给 NMDI,c 由第 17 行调用 b.c() 函数返回。

继续追踪 b.c() 函数调用链,最终可追溯到 CaesarsonCryptor.encrypt 函数。该函数在 Java 层仅有声明,标注为 native,其真正的实现位于 Native 库 libcaesarson.so 中。

Frida Hook 验证
使用 Frida Hook CaesarsonCryptor.encrypt 函数,观察传入参数及返回值。
注意:需要在首次启动 APP(或清除现有数据后)以 spawn 模式执行 Hook,确保在加密初始化之前介入。
// hook_caesarson.jsJava.perform(function () {var CaesarsonCryptor = Java.use("com.example.music.crypto.caesarson.CaesarsonCryptor" ); CaesarsonCryptor.encrypt.overload("java.lang.String").implementation = function (str) {var res = this.encrypt(str);console.log("[caesarson] param : " + str);console.log("[caesarson] result : " + res);return res; };});
以 spawn 模式运行:
frida -U -l hook_caesarson.js -f com.example.music --no-pause
Hook 输出如下:

入参(JSON 对象):
{"ie":"[MASKED_IMEI]","mc":"[MASKED_MAC]","ydid":"[MASKED_YDID]"}
返回值(Base64 密文):
Q1NKTQkBDAD7j0c5Ehyf9tpVp/trAAAA9EOlfKiR9O8qT7ldbUCgy0d0934KFsERECA4NUn8KHTI9IEOc3QXlpIMz8tWLrlVsRA3JACQsNksXZxW+w/7K4pJinJv2JRRctdr84bNQKbIHo+DXnkbVrCF1ll78Rs9q045NwID7Y+hWik=
二、参数分析及伪造
传入 CaesarsonCryptor.encrypt 的 JSON 参数包含三个字段:
|
|
|
|
|---|---|---|
ie |
|
|
mc |
|
|
ydid |
|
|
对于爬虫场景,真实设备数量有限,需要按照字段的内在约束规则伪造合法值。
2.1 IMEI 串号伪造
IMEI(国际移动设备识别码)由 15 位数字组成,其结构如下:
|
|
|
|
|---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
import randomdefrandom_imei(imei: str) -> str:""" 基于真实 IMEI 伪造新 IMEI。 保留前 8 位 TAC,随机化序列号(9-14位),重新计算 Luhn 校验码。 :param imei: 真实 IMEI(15位) :return: 伪造的合法 IMEI """# 随机化序列号部分(第9-14位) imei = imei[:8] + str(random.randint(0, 1_000_000)).zfill(6)# 计算 Luhn 校验码 checksum = 0for n in range(14): digit = int(imei[n])if n % 2 == 1:# 奇数位(0-indexed):乘2后十位与个位相加 checksum += (digit * 2) % 10 + (digit * 2) // 10else: checksum += digit remainder = checksum % 10 imei += str(0if remainder == 0else10 - remainder)return imei
2.2 MAC 地址伪造
MAC 地址由 6 字节(48 位) 组成,通常以冒号分隔的十六进制表示,如 xx:xx:xx:xx:xx:xx。
|
|
|
|
|---|---|---|
|
|
|
|
|
|
|
|
import randomdefrandom_mac(mac: str) -> str:""" 基于真实 MAC 地址伪造新 MAC 地址。 保留前 3 字节 OUI,随机化后 3 字节序列号。 :param mac: 真实 MAC 地址,格式 'xx:xx:xx:xx:xx:xx' :return: 伪造的 MAC 地址 """ hex_chars = "0123456789abcdef" random_parts = ["".join(random.choices(hex_chars, k=2))for _ in range(3) ]return mac[:8] + ":".join(random_parts)
2.3 ydid 伪造
ydid 字段疑似为某种设备指纹 ID,具体生成逻辑尚未深入分析。目前策略是随机生成一个 32 位十六进制字符串(MD5 格式),后续如有需要再进行函数级逆向分析。
import uuiddefrandom_ydid() -> str:"""随机生成 ydid,格式为 32 位十六进制字符串(不含连字符)。"""return uuid.uuid4().hex
三、NMDI 加密分析
3.1 IDA 静态代码分析
使用 IDA Pro 打开 libcaesarson.so,在导出函数窗口中找到加密函数入口:

定位到 JNI 导出函数 Java_com_example_music_crypto_caesarson_CaesarsonCryptor_native_1encrypt:

继续追踪内部调用链:CaesarsonCryptor::encryptAsBase64 ↓

→ CaesarsonCryptorImpl::encrypt ↓

→ sub_6630(内部核心加密函数,逻辑较复杂,静态分析难以直接理解):

静态分析到 sub_6630 后已难以直接读懂逻辑,下一步改用 unidbg 动态追踪。
3.2 unidbg 调用加密函数
使用 unidbg 加载 libcaesarson.so,直接调用 JNI 导出函数,同时开启汇编指令流追踪(traceCode),将执行过程输出到文件供后续分析。
package every.app.unidbg;import com.github.unidbg.AndroidEmulator;import com.github.unidbg.Emulator;import com.github.unidbg.Module;import com.github.unidbg.hook.hookzz.*;import com.github.unidbg.linux.android.AndroidEmulatorBuilder;import com.github.unidbg.linux.android.AndroidResolver;import com.github.unidbg.linux.android.dvm.*;import com.github.unidbg.memory.Memory;import com.github.unidbg.utils.Inspector;import com.sun.jna.Pointer;import java.io.File;import java.io.FileNotFoundException;import java.io.FileOutputStream;import java.io.PrintStream;import java.util.Arrays;import java.util.List;// 继承 AbstractJni 处理 JNI 回调publicclassMusicAppextendsAbstractJni{privatefinal VM vm;privatefinal Module module;privatefinal AndroidEmulator emulator;privatefinal String traceFile = "unidbg-workspace/src/main/resources/musicapp/trace.txt"; MusicApp() throws FileNotFoundException {// 创建 32 位 ARM 模拟器,进程名与真实 APP 保持一致以规避进程名检测 emulator = AndroidEmulatorBuilder .for32Bit() .setProcessName("com.example.music") .build();final Memory memory = emulator.getMemory(); memory.setLibraryResolver(new AndroidResolver(23));// 加载 APK 以辅助完成签名等校验 vm = emulator.createDalvikVM(new File("unidbg-workspace/src/main/resources/musicapp/musicapp-8.8.50.apk") );// 加载目标 SO,true 表示调用 JNI_OnLoad DalvikModule dm = vm.loadLibrary("caesarson", true);module = dm.getModule();// 开启汇编指令流追踪,重定向到文件 PrintStream traceStream = new PrintStream(new FileOutputStream(traceFile), true); emulator.traceCode(module.base, module.base + module.size).setRedirect(traceStream); vm.setJni(this); vm.setVerbose(true); dm.callJNI_OnLoad(emulator); }// 处理 JNI setIntField 回调@OverridepublicvoidsetIntField(BaseVM vm, DvmObject<?> dvmObject, String signature, int value){if ("com/example/music/crypto/caesarson/ErrorObject->errorCode:I".equals(signature)) {// 忽略错误码写入 } }// 处理 JNI setObjectField 回调@OverridepublicvoidsetObjectField(BaseVM vm, DvmObject<?> dvmObject, String signature, DvmObject<?> value){if ("com/example/music/crypto/caesarson/ErrorObject->message:Ljava/lang/String".equals(signature)) {// 忽略错误信息写入 } }/** 调用 native encrypt 函数 */public String native_encrypt(String str){ DvmClass ErrorObject = vm.resolveClass("com/example/music/crypto/caesarson/ErrorObject"); List<Object> list = Arrays.asList( vm.getJNIEnv(), 0, vm.addLocalObject(new StringObject(vm, str)), vm.addLocalObject(ErrorObject.newObject(null)) ); Number number = module.callFunction( emulator,"Java_com_example_music_crypto_caesarson_CaesarsonCryptor_native_1encrypt", list.toArray() );return vm.getObject(number.intValue()).getValue().toString(); }/** 调用 native init 函数(传入 Base64 编码的初始化密钥) */publicvoidinit(String str){ List<Object> list = Arrays.asList( vm.getJNIEnv(), 0, vm.addLocalObject(new StringObject(vm, str)) );module.callFunction( emulator,"Java_com_example_music_crypto_caesarson_CaesarsonCryptor_native_1init", list.toArray() ); }/** * Hook sub_6B14 函数(核心加密子函数), * 打印入参与出参内存内容,辅助分析数据流。 */publicvoidhookSub6B14(){ IHookZz hookZz = HookZz.getInstance(emulator); hookZz.wrap(module.base + 0x6B14 + 1, new WrapCallback<HookZzArm32RegisterContext>() {@OverridepublicvoidpreCall(Emulator<?> emulator, HookZzArm32RegisterContext ctx, HookEntryInfo info){ Pointer input1 = ctx.getPointerArg(0); Pointer input2 = ctx.getPointerArg(1); Inspector.inspect(input1.getByteArray(0, 0x150), "6B14 arg1"); Inspector.inspect(input2.getByteArray(0, 0x150), "6B14 arg2"); }@OverridepublicvoidpostCall(Emulator<?> emulator, HookZzArm32RegisterContext ctx, HookEntryInfo info){ Inspector.inspect(ctx.getR4Pointer().getByteArray(0, 300), "6B14-R4 output"); } }); }publicstaticvoidmain(String[] args)throws FileNotFoundException { MusicApp app = new MusicApp(); app.hookSub6B14();// 传入 Base64 编码的初始化参数(脱敏) app.init("Ce****==");// 使用脱敏后的设备信息调用加密 String result = app.native_encrypt("{\"ie\":\"[MASKED_IMEI]\",\"mc\":\"[MASKED_MAC]\",\"ydid\":\"[MASKED_YDID]\"}" ); System.out.println(result); }}
unidbg 验证结果
调用 native_encrypt 后,返回的 Base64 密文经解码后转为字节序列:
b'\x43\x53\x4a\x4d\x09\x01\x0c\x00\x12\x1e\x33\x83\x85\xf3\xff\x7d \x6d\x8d\xf9\xdd\x6b\x00\x00\x00\x55\xad\xe1\xac\xe9\x4f\x78\xcf ...'
逐字节对照分析,验证了输出结构如下:
|
|
|
|
|---|---|---|
|
|
0x43 0x53 0x4a 0x4d |
CSJM(大端) |
|
|
0x09 |
|
|
|
0x01 |
|
|
|
0x0c |
|
|
|
0x00 |
|
|
|
0x12 1e 33 83 85 f3 ff 7d 6d 8d f9 dd |
|
|
|
0x6b
|
|
|
|
0x00 0x00 0x00 |
|
|
|
|
|
|
|
|
|
结论:caesarson 是一种 AES-GCM 变体,采用 CTR 模式加密 + 自实现的 GHASH 类认证,整体结构与 AEAD 加密方案一致。
3.3 汇编指令流分析
Step 1:从密文逆向 XOR 链定位 AES 函数
密文第 25-28 字节为 0x55 0xad 0xe1 0xac,小端存储为 0xace1ad55。
在汇编指令流中搜索该值:

发现 0xace1ad55 由两数 XOR 得到:
0xace1ad55 = 0xc9888f2e XOR 0x7b226965
其中 0x7b226965 以大端还原为 {"ie,正是明文 JSON 字符串的前 4 字节——这验证了 CTR 模式的 XOR 加密特征。
继续搜索 0xc9888f2e(即 AES keystream 的前 4 字节):

发现 0xc9888f2e 由 0xa5cfe38c XOR 0x8b406b45 得到,位于偏移 0x854a。
Step 2:结合 IDA 定位 AES 函数
跳转到偏移 0x854a 对应的 IDA 反编译代码,确认这是 sub_838c 函数中的一条语句:

上层调用函数为 sub_8340:

该函数的代码特征与 AES 加密函数高度吻合。使用 IDA 插件 Findcrypt 进一步验证:

Findcrypt 识别出 RijnDael_AES_7E00(AES S-Box 特征魔数),查看其引用关系:

确认 sub_8340 即为 AES ECB 加密函数(用于生成 CTR keystream 块)。
Step 3:提取 AES 明文与密钥
在汇编指令流中向上追溯 sub_8340 的调用位置(从第 34231 行向上搜索):

提取到的 AES 输入参数:
|
|
|
|---|---|
| 明文
|
0x121e338385f3ff7d6d8df9dd00000002 |
| 密钥
|
0xf3645cc3db63ae5828925612412de6d9 |
| AES 输出
|
0x2e8f88c9cb755af7cfca4ace2f5dbda7... |
验证:AES 输出的前 4 字节 0x2e8f88c9,以小端存储为 0xc9888f2e,与 Step 1 中的追踪结果完全吻合。
Step 4:CTR 模式结构归纳
定义 CTR keystream 块序列(nonce = 随机 12 字节):
AesData0 = AES_ECB(nonce || 0x00000000) # 用于认证标签初始化AesData1 = AES_ECB(nonce || 0x00000001) # 用于认证标签最终 XORAesData2 = AES_ECB(nonce || 0x00000002) # 加密明文第 1-16 字节AesData3 = AES_ECB(nonce || 0x00000003) # 加密明文第 17-32 字节AesData4 = AES_ECB(nonce || 0x00000004) # 加密明文第 33-48 字节...
密文 Part 4 的加密规则:
ciphertext[0:16] = plaintext[0:16] XOR AesData2[0:16]ciphertext[16:32] = plaintext[16:32] XOR AesData3[0:16]...
举例验证(明文第 1-16 字节 {"ie":"[MASKED_I):
plaintext hex : 0x7b226965223a22383637393739303231AesData2 : 0x2e8f88c9cb755af7cfca4ace2f5dbda7XOR result : 0x55ade1ace94f78cff9fd73f9166d8f96 ← 对应密文第 25-40 字节 ✓
Step 5:最后 16 字节(认证标签)分析
最后 16 字节的计算较为复杂,涉及三张内部数据表和两个核心函数:
// 初始化常量(硬编码于 .so 中)data_bb6e = [0xaa, 0x45, 0x6e, 0xc7,0xd8, 0x29, 0x72, 0x0b,0x0f, 0xce, 0xe3, 0xfa,0x0e, 0xbd, 0x94, 0x3f]// GF(2^128) 多项式约减表data_b9c0 = [0x0000, 0x1C20, 0x3840, 0x2460,0x7080, 0x6CA0, 0x48C0, 0x54E0,0xE100, 0xFD20, 0xD940, 0xC560,0x9180, 0x8DA0, 0xA9C0, 0xB5E0]// GHASH 乘法系数表(16x4 的 uint32 矩阵)data_6b14 = [ [0x00000000, 0x00000000, 0x00000000, 0x00000000], [0xf5ee727b, 0xd665e73c, 0xacb31f70, 0x5829b1d9],// ... 共 16 行(外加最后一行边界条目)]
认证标签的完整计算步骤:
-
b9f0(ciphertext_blocks, part4):对所有完整密文块执行 GHASH 类多项式乘累加,更新data_bb6e状态 -
末尾不足 16 字节的部分块:与对应 keystream 字节 XOR 后累加入 data_bb6e -
bb14()(第一次):对data_bb6e进行一轮完整的多项式变换 -
长度编码处理: data_bb6e[7] ^= 0x40,并将len(plaintext) * 8(比特数)编码后 XOR 入data_bb6e[12:16] -
bb14()(第二次):再进行一轮多项式变换 -
最终 XOR: auth_tag[i] = data_bb6e[i] ^ AesData1[i]
算法本质:caesarson 加密是 AES-GCM 的自定义实现。AES key 硬编码于 .so 中,nonce 每次随机生成,认证标签通过 GF(2^128) 上的 GHASH 类运算计算。
四、Python 完整实现
以下是逆向还原的完整 Python 实现,包含 IMEI/MAC 伪造和 caesarson 加密两部分:
import uuidimport randomimport base64from binascii import b2a_hex, unhexlifyfrom Crypto.Cipher import AESfrom typing import Optional# ============================# 设备标识伪造# ============================defrandom_imei(imei: str) -> str:""" 基于真实 IMEI 伪造合法 IMEI。 - 保留前 8 位 TAC(厂商标识码) - 随机化第 9-14 位序列号 - 按 Luhn 算法计算第 15 位校验码 """ imei = imei[:8] + str(random.randint(0, 1_000_000)).zfill(6) checksum = 0for n in range(14): digit = int(imei[n])if n % 2 == 1: checksum += (digit * 2) % 10 + (digit * 2) // 10else: checksum += digit rem = checksum % 10return imei + ("0"if rem == 0else str(10 - rem))defrandom_mac(mac: str) -> str:""" 基于真实 MAC 地址伪造合法 MAC。 - 保留前 3 字节 OUI - 随机化后 3 字节 """ hex_chars = "0123456789abcdef" suffix = ":".join("".join(random.choices(hex_chars, k=2)) for _ in range(3))return mac[:8] + suffixdefrandom_ydid() -> str:"""生成随机 32 位十六进制字符串作为 ydid。"""return uuid.uuid4().hex# ============================# Caesarson 加密实现# ============================classCaesarson:""" caesarson 加密算法的 Python 还原实现。 本质是 AES-GCM 变体:CTR 模式加密 + GHASH 类认证标签。 AES key 硬编码于 libcaesarson.so,由逆向分析提取(脱敏展示)。 """# AES 密钥(从 .so 静态分析提取,16 字节) AES_KEY: bytes = b'\xf3\x64\x5c\xc3\xdb\x63\xae\x58\x28\x92\x56\x12\x41\x2d\xe6\xd9'def__init__(self) -> None:# GF(2^128) 多项式约减表(用于 GHASH 乘法) self._b9c0 = [0x0000, 0x1C20, 0x3840, 0x2460,0x7080, 0x6CA0, 0x48C0, 0x54E0,0xE100, 0xFD20, 0xD940, 0xC560,0x9180, 0x8DA0, 0xA9C0, 0xB5E0, ]# 认证标签初始状态(硬编码常量) self._bb6e: list[int] = [0xaa, 0x45, 0x6e, 0xc7,0xd8, 0x29, 0x72, 0x0b,0x0f, 0xce, 0xe3, 0xfa,0x0e, 0xbd, 0x94, 0x3f, ]# GHASH 乘法系数矩阵(16 行 x 4 列 uint32) self._6b14: list[list[int]] = [ [0x00000000, 0x00000000, 0x00000000, 0x00000000], [0xf5ee727b, 0xd665e73c, 0xacb31f70, 0x5829b1d9], [0xebdce4f6, 0xaccbce79, 0x59663ee1, 0xb05363b3], [0x1e32968d, 0x7aae2945, 0xf5d52191, 0xe87ad26a], [0xd7b9c9ed, 0x59979cf3, 0xb2cc7dc3, 0xa2a6c766], [0x2257bb96, 0x8ff27bcf, 0x1e7f62b3, 0xfa8f76bf], [0x3c652d1b, 0xf55c528a, 0xebaa4322, 0x12f5a4d5], [0xc98b5f60, 0x2339b5b6, 0x47195c52, 0x4adc150c], [0xaf7393db, 0xb32f39e7, 0x6598fb86, 0x874d8ecd], [0x5a9de1a0, 0x654adedb, 0xc92be4f6, 0xdf643f14], [0x44af772d, 0x1fe4f79e, 0x3cfec567, 0x371eed7e], [0xb1410556, 0xc98110a2, 0x904dda17, 0x6f375ca7], [0x78ca5a36, 0xeab8a514, 0xd7548645, 0x25eb49ab], [0x8d24284d, 0x3cdd4228, 0x7be79935, 0x7dc2f872], [0x9316bec0, 0x46736b6d, 0x8e32b8a4, 0x95b82a18], [0x66f8ccbb, 0x90168c51, 0x2281a7d4, 0xcd919bc1], [0x4000bb15, 0x4000b9f1, 0x4000641b, 0x00000000], ]# ---- 内部工具方法 ----def_hex2bytes(self, s: str, length: Optional[int] = None) -> bytes:"""十六进制字符串 -> bytes,支持补零对齐。""" s = s.lstrip("0x") if s.startswith("0x") else sif len(s) % 2: s = "0" + sif length and length > len(s): s = "0" * (length - len(s)) + sreturn unhexlify(s.encode())def_aes_ecb(self, block: bytes) -> bytes:"""AES ECB 加密单块(无 padding,block 必须为 16 字节)。""" cipher = AES.new(self.AES_KEY, AES.MODE_ECB)return cipher.encrypt(block)def_random_nonce(self) -> bytes:"""生成 12 字节随机 nonce(uuid4 hex 截取前 32 位)。"""return self._hex2bytes(uuid.uuid4().hex.replace("-", ""))[:12]# ---- GHASH 核心函数 ----def_b9f0(self, data_bytes: bytes, part4: list[int]) -> None:""" GHASH 类多项式乘累加,处理所有完整的 16 字节密文块。 更新 self._bb6e 状态。 """ n_blocks = len(data_bytes) // 16 v4 = part4[15] v5 = self._bb6e[15]for i in range(n_blocks): v6 = v4 ^ v5 v7 = 14 row = self._6b14[v6 & 0xF] v9, v10, v11, v12 = row row13 = self._6b14[(v6 & 0xF0) >> 4] v14 = self._b9c0[v9 & 0xF] v15 = (row13[0] ^ (v9 >> 4) ^ (v10 << 28)) & 0xFFFFFFFF v16 = (row13[1] ^ (v10 >> 4) ^ (v11 << 28)) & 0xFFFFFFFF v17 = (row13[2] ^ (v11 >> 4) ^ (v12 << 28)) & 0xFFFFFFFF v20 = (row13[3] ^ (v12 >> 4) ^ (v14 << 16)) & 0xFFFFFFFF v18 = part4[i * 16 + 14] ^ self._bb6e[14] v19 = v18 & 0xF0 v4 = v18 & 0xFwhile v7 >= 0: v7 -= 1 v21 = self._6b14[v4] v4 = 2 * (v15 & 0xF) v22, v23, v24, v25 = v21[0], v21[3], v21[1], v21[2] v26 = (v22 ^ (v15 >> 4) ^ (v16 << 28)) & 0xFFFFFFFF v27 = (v24 ^ (v16 >> 4) ^ (v17 << 28)) & 0xFFFFFFFF v28 = self._b9c0[v4 // 2] v29 = (v25 ^ (v17 >> 4)) & 0xFFFFFFFFif v7 >= 0: v4 = part4[i * 16 + v7] v30 = (v29 ^ (v20 << 28)) & 0xFFFFFFFF v31 = (v23 ^ (v20 >> 4)) & 0xFFFFFFFF v32 = self._6b14[v19 // 16] v33 = (v31 ^ (v28 << 16)) & 0xFFFFFFFF v19 = 2 * (v26 & 0xF) v34, v35, v36, v37 = v32[0], v32[1], v32[2], v32[3] v38 = (v34 ^ (v26 >> 4)) & 0xFFFFFFFFif v7 >= 0: v34 = self._bb6e[v7] v15 = (v38 ^ (v27 << 28)) & 0xFFFFFFFF v39 = (v35 ^ (v27 >> 4)) & 0xFFFFFFFF v40 = self._b9c0[v19 // 2] v16 = (v39 ^ (v30 << 28)) & 0xFFFFFFFF v17 = (v36 ^ (v30 >> 4) ^ (v33 << 28)) & 0xFFFFFFFFif v7 >= 0: v4 ^= v34 v41 = (v37 ^ (v33 >> 4)) & 0xFFFFFFFFif v7 >= 0: v19 = v4 & 0xF0 v4 &= 0xF v20 = (v41 ^ (v40 << 16)) & 0xFFFFFFFF# 将本轮输出写回 _bb6e v5 = v15if i + 1 < n_blocks: v4 = part4[(i + 1) * 16 + 15]for j, val in enumerate([v20, v17, v16, v15]): chunk = self._hex2bytes(hex(val), length=8) self._bb6e[j * 4: j * 4 + 4] = list(chunk)def_bb14(self) -> None:""" 对 _bb6e 执行一轮完整的 GHASH 多项式变换(不输入新数据)。 用于认证标签的最终化处理。 """ v2 = self._bb6e[15] v3 = v2 & 0xF0 v4 = 14 row = self._6b14[v2 & 0xF] v6, v7, v8, v9 = row v10 = self._bb6e[14] row2 = self._6b14[v3 // 16] v11, v12, v13, v14 = row2 v15 = 2 * (v6 & 0xF) v16 = (v11 ^ (v6 >> 4)) & 0xFFFFFFFF v17 = self._b9c0[v15 // 2] v18 = (v16 ^ (v7 << 28)) & 0xFFFFFFFF v19 = (v12 ^ (v7 >> 4) ^ (v8 << 28)) & 0xFFFFFFFF v20 = (v13 ^ (v8 >> 4) ^ (v9 << 28)) & 0xFFFFFFFF v21 = v10 & 0xF0 v22 = (v14 ^ (v9 >> 4) ^ (v17 << 16)) & 0xFFFFFFFF v23 = v10 & 0xFwhile v4 >= 0: v4 -= 1 v24 = self._6b14[v23] v23 = 2 * (v18 & 0xF) v25, v26, v27, v28 = v24[0], v24[3], v24[1], v24[2] v29 = (v25 ^ (v18 >> 4) ^ (v19 << 28)) & 0xFFFFFFFF v30 = (v27 ^ (v19 >> 4) ^ (v20 << 28)) & 0xFFFFFFFF v31 = self._b9c0[v23 // 2] v32 = (v28 ^ (v20 >> 4)) & 0xFFFFFFFFif v4 >= 0: v23 = self._bb6e[v4] v33 = (v32 ^ (v22 << 28)) & 0xFFFFFFFF v34 = (v26 ^ (v22 >> 4)) & 0xFFFFFFFF v35 = self._6b14[v21 // 16] v36 = (v34 ^ (v31 << 16)) & 0xFFFFFFFF v21 = 2 * (v29 & 0xF) v18 = (v35[0] ^ (v29 >> 4) ^ (v30 << 28)) & 0xFFFFFFFF v37 = self._b9c0[v21 // 2] v19 = (v35[1] ^ (v30 >> 4) ^ (v33 << 28)) & 0xFFFFFFFF v20 = (v35[2] ^ (v33 >> 4) ^ (v36 << 28)) & 0xFFFFFFFF v38 = (v35[3] ^ (v36 >> 4)) & 0xFFFFFFFFif v4 >= 0: v21 = v23 & 0xF0 v23 &= 0xF v22 = (v38 ^ (v37 << 16)) & 0xFFFFFFFFfor j, val in enumerate([v22, v20, v19, v18]): chunk = self._hex2bytes(hex(val), length=8) self._bb6e[j * 4: j * 4 + 4] = list(chunk)# ---- 主加密函数 ----defencrypt(self, plaintext: str) -> str:""" caesarson 加密。 :param plaintext: 待加密的 JSON 字符串 :return: Base64 编码的密文 """ data_bytes = plaintext.encode("utf-8")# 重置认证状态(每次加密独立) self._bb6e = [0xaa, 0x45, 0x6e, 0xc7,0xd8, 0x29, 0x72, 0x0b,0x0f, 0xce, 0xe3, 0xfa,0x0e, 0xbd, 0x94, 0x3f, ]# 生成随机 nonce(12 字节) nonce = self._random_nonce()# Part 1: 魔数头部 part1 = [0x43, 0x53, 0x4a, 0x4d, 0x09, 0x01, 0x0c, 0x00]# Part 2: nonce part2 = list(nonce)# Part 3: 输出长度字段(明文长度 + 16,4 字节小端) total_len = len(data_bytes) + 16 part3 = list(self._hex2bytes(hex(total_len), length=8))# 生成 CTR keystream 并加密明文 n_blocks = (len(data_bytes) // 16) + 1 aes_stream: list[int] = []for i in range(n_blocks): counter = self._hex2bytes(hex(i + 2), length=8) # counter 从 2 开始 block = self._aes_ecb(nonce + counter) aes_stream.extend(block[:16]) part4 = [data_bytes[i] ^ aes_stream[i] for i in range(len(data_bytes))]# 计算认证标签 self._b9f0(data_bytes, part4)# 处理末尾不足 16 字节的余量 remainder = len(data_bytes) % 16for i, j in enumerate(range(remainder, 0, -1)): self._bb6e[i] ^= (data_bytes[len(data_bytes) - j] ^ aes_stream[len(data_bytes) - j]) self._bb14()# 长度编码最终化 self._bb6e[7] ^= 0x40 bit_len_bytes = self._hex2bytes(hex(len(data_bytes) << 3), length=8)for i, b in enumerate(bit_len_bytes): self._bb6e[12 + i] ^= b self._bb14()# 最终 XOR with AesData1(nonce || 0x00000001) aes_data1 = self._aes_ecb(nonce + b"\x00\x00\x00\x01") part5 = [self._bb6e[i] ^ aes_data1[i % 16] for i in range(16)] result = bytes(part1 + part2 + part3 + part4 + part5)return base64.b64encode(result).decode()# ============================# 示例:生成完整 NMDI 参数# ============================defgenerate_nmdi(real_imei: str, real_mac: str) -> str:""" 生成伪造设备标识并加密,返回 NMDI 参数值。 :param real_imei: 真实设备 IMEI(仅用于提取前 8 位 TAC) :param real_mac: 真实设备 MAC(仅用于提取前 3 字节 OUI) :return: Base64 编码的 NMDI 密文 """ ie = random_imei(real_imei) mc = random_mac(real_mac) ydid = random_ydid() payload = f'{{"ie":"{ie}","mc":"{mc}","ydid":"{ydid}"}}'return Caesarson().encrypt(payload)if __name__ == "__main__":# 示例(使用脱敏的真实设备前缀) nmdi = generate_nmdi( real_imei="86000000000000", # 替换为真实设备 IMEI real_mac="xx:xx:xx:00:00:00", # 替换为真实设备 MAC ) print("NMDI:", nmdi)
附录:caesarson 与标准 AES-GCM 对比
|
|
|
|
|---|---|---|
|
|
|
|
|
|
|
b9f0 + bb14) |
|
|
|
|
|
|
|
libcaesarson.so |
|
|
|
|
|
|
|
CSJM
|
输出结构图

图注:caesarson 输出字节布局全览,展示各 Part 与 AES CTR keystream 的对应关系及认证标签生成流程
夜雨聆风
