安卓端某音乐类 APP 逆向分享(四)NMDI参数分析
NMDI参数的加密分析属于X音乐逆向过程中最难的一部分,NMDI是通过一个叫caesarson的加密算法生成的。
一、参数定位
使用Jdax搜索"NMDI"关键字,能够定位NMDI参数生成位置是在com.neteasex.xmusic.core.security.c.intercept函数中。第22行将变量c赋值给NMDI,变量c是在17行的b.c函数返回的。
继续跟踪b.c函数,最终能跟到CaesarsonCryptor.encrypt函数上,这是一个native函数,java层声明该函数,真正实现的位置是在libcaesarson.so这个库文件中。
使用Frida hook CaesarsonCryptor.encrypt函数,看看传入的参数及返回值是什么。此处需要注意hook的时机,应在安装X音乐后首次启动应用时hook,也可以将现有的X音乐应用数据进行清除,再以spawn模式运行hook程序。
// FRIDA脚本,命名为163_caesarson.js
Java.perform(function(){var CaesarsonCryptor = Java.use("com.neteasex.xdmusic.crypto.caesarson.CaesarsonCryptor");CaesarsonCryptor.encrypt.overload("java.lang.String").implementation = function(str) {var res = this.encrypt(str);console.log("param: " + str);console.log("");console.log("result: " + res);return res;}
})
spawn模式运行hook程序。
frida -U -l 163_caesarson.js -f com.netease.cloudmusic --no-pause
CaesarsonCryptor.encrypt函数的参数及返回值。
参数
{"ie":"867979021665986","mc":"dc:ee:06:fe:8e:93","ydid":"d116af0a01a2893504851cc0fa215271"}
加密结果
Q1NKTQkBDAD7j0c5Ehyf9tpVp/trAAAA9EOlfKiR9O8qT7ldbUCgy0d0934KFsERECA4NUn8KHTI9IEOc3QXlpIMz8tWLrlVsRA3JACQsNksXZxW+w/7K4pJinJv2JRRctdr84bNQKbIHo+DXnkbVrCF1ll78Rs9q045NwID7Y+hWik=
二、参数分析及伪造
传入CaesarsonCryptor.encrypt函数的参数是
{"ie":"867979021665986","mc":"dc:ee:06:fe:8e:93","ydid":"d116af0a01a2893504851cc0fa215271"}
其中ie是设备的imei串号,mc是设备的mac地址,ydid疑似是某种设备id,暂时随机生成。对于爬虫而言,imei串号、mac地址也不可能完全使用真实的数据,毕竟真实的数据有限,所以这里的imei串号及mac地址也需要伪造。
2.1、imei串号伪造
参数伪造不表示随机生成,有些数据是有内在规律的,我们需要按照这个规律伪造数据。比如imei串号又称作国际移动设备识别码,一般是由15位数字组成。
imei串号的前8位是类型分配码,是区分手机品牌和型号的编码,所以imei串号伪造的前8位取自真机,不做变动。
imei串号的9-14位是序列号,区分每部手机的生产序列号,这部分是可以随机伪造的。
imei串号的最后一位是验证码,由前14位数字通过Luhn算法计算得出。
imei串号伪造代码,其中参数imei为真实串号。
def random_imei(imei):id15 = 0imei = imei[:8] + str(random.randint(0, 1000000)).zfill(6)for n in range(14):if n % 2 == 0:id15 = id15 + int(imei[n])else:id15 = id15 + (int(imei[n]) * 2) % 10 + (int(imei[n]) * 2) // 10id15 = int(id15) % 10if id15 == 0:imei = imei + str(id15)else:imei = imei + str(10 - id15)return imei
2.2、mac地址伪造
MAC地址的长度为48位(6个字节),通常表示为12个16进制数,如:dc:ee:06:fe:8e:93就是一个MAC地址,其中前3个字节,16进制数dc:ee:06代表网络硬件制造商的编号,它由IEEE(电气与电子工程师协会)分配,而后3个字节,16进制数fe:8e:93代表该制造商所制造的某个网络产品的序列号。因此我们在伪造mac地址时,一般只改动真实mac地址的后3个字节。
mac地址伪造代码,其中参数mac为真实mac地址。
def random_mac(self, mac):mac_strs = []mac_str = "0123456789abcdef"for _ in range(3):mac_strs.append("".join(random.choices(mac_str, k=2)))return mac[:9] + ":".join(mac_strs)
暂且将ydid伪造成随机的md5值,以后有需要再分析函数逻辑。
三、NMDI加密分析
3.1、静态代码分析
NMDI参数是由CaesarsonCryptor.encrypt函数返回的,该函数实现位置在libcaesarson.so文件中。使用Ida打开libcaesarson.so文件,通过导出函数窗口,能够看到encrypt函数的入口位置。
打开Java_com_neteasex_xmusic_crypto_caesarson_CaesarsonCryptor_native_1encrypt函数。
继续跟踪CaesarsonCryptor::encryptAsBase64函数。
继续跟踪CaesarsonCryptorImpl::encrypt函数。
继续跟踪sub_6630函数,看上去不是清楚。
3.2、unidbg调用加密函数
接下里使用unidbg,调用Java_com_neteasex_xmusic_crypto_caesarson_CaesarsonCryptor_native_1encrypt函数,打印汇编指令流继续跟踪,代码如下:
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类
public class Music163 extends AbstractJni {private final VM vm;private final Module module;private final AndroidEmulator emulator;private final String traceFile = "unidbg-chqi/src/main/resources/music163/trace.txt";Music163() throws FileNotFoundException {// 创建模拟器实例,进程名建议依照实际进程名填写,可以规避针对进程名的校验emulator = AndroidEmulatorBuilder.for32Bit().setProcessName("com.netease.cloudmusic").build();// 获取模拟器的内存操作接口final Memory memory = emulator.getMemory();// 设置系统类库解析memory.setLibraryResolver(new AndroidResolver(23));// 创建Android虚拟机,传入APK,unidbg可以替我们做部分签名校验的工作vm = emulator.createDalvikVM(new File("unidbg-chqi/src/main/resources/music163/music163-8.8.50.apk"));// 加载目标SODalvikModule dm = vm.loadLibrary("caesarson", true);// 获取本SO模块的句柄,后续需要用它module = dm.getModule();// 打开tracecodePrintStream traceStream = new PrintStream(new FileOutputStream(traceFile), true);emulator.traceCode(module.base, module.base+module.size).setRedirect(traceStream);// 设置JNIvm.setJni(this);// 打印日志vm.setVerbose(true);// 调用JNI OnLoaddm.callJNI_OnLoad(emulator);}@Overridepublic void setIntField(BaseVM vm, DvmObject<?> dvmObject, String signature, int value) {switch (signature) {case "com/netease/cloudmusic/crypto/caesarson/ErrorObject->errorCode:I": {}}}@Overridepublic void setObjectField(BaseVM vm, DvmObject<?> dvmObject, String signature, DvmObject<?> value) {switch (signature) {case "com/netease/cloudmusic/crypto/caesarson/ErrorObject->message:Ljava/lang/String": {}}}public String native_encrypt(String str) {DvmClass ErrorObject = vm.resolveClass("com/netease/cloudmusic/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_netease_cloudmusic_crypto_caesarson_CaesarsonCryptor_native_1encrypt", list.toArray());return vm.getObject(number.intValue()).getValue().toString();}public void init(String str) {List<Object> list = Arrays.asList(vm.getJNIEnv(), 0, vm.addLocalObject(new StringObject(vm, str)));module.callFunction(emulator, "Java_com_netease_cloudmusic_crypto_caesarson_CaesarsonCryptor_native_1init", list.toArray());}public void hookSub6B14(){IHookZz hookZz = HookZz.getInstance(emulator);hookZz.wrap(module.base + 0x6B14+ 1, new WrapCallback<HookZzArm32RegisterContext>() {@Override// 类似于 frida onEnterpublic void preCall(Emulator<?> emulator, HookZzArm32RegisterContext ctx, HookEntryInfo info) {System.out.println(ctx.getR0Long());System.out.println(ctx.getR1Long());Pointer input1 = ctx.getPointerArg(0);Inspector.inspect(input1.getByteArray(0, 0x150), "6B14 arg1");Pointer input2 = ctx.getPointerArg(1);Inspector.inspect(input2.getByteArray(0, 0x150), "6B14 arg2");};@Override// 类似于 frida onLeavepublic void postCall(Emulator<?> emulator, HookZzArm32RegisterContext ctx, HookEntryInfo info) {Inspector.inspect(ctx.getR4Pointer().getByteArray(0, 300), "6B14-R4 output2");}});}public static void main(String[] args) throws FileNotFoundException {Music163 m163 = new Music163();m163.hookSub6B14();m163.init("CeLwrg==");String data2 = m163.native_encrypt("{\"ie\":\"867979021665986\",\"mc\":\"dc:ee:06:fe:8e:93\",\"ydid\":\"d116af0a01a2893504851cc0fa215271\"}");System.out.println(data2);}
}
调用native_encrypt函数,参数是:
{"ie":"867979021665986","mc":"dc:ee:06:fe:8e:93","ydid":"d116af0a01a2893504851cc0fa215271"}
返回的加密结果是:
Q1NKTQkBDAASHjODhfP/fW2N+d1rAAAAVa3hrOlPeM/5/XP5Fm2PlgUcwQ4NxTNN9oBDVKs2zyaVvIF/gGEsDQ+G0cBQLBwcz0NpfIGyhuHFAJuEHOC3DC7NbsU7rQF/buWbiEcm7NgdrNrxXvkStGxkFepNdk92fqs65oUUP/W0Rfk=
base64解密:
b'CSJM\t\x01\x0c\x00\x12\x1e3\x83\x85\xf3\xff}m\x8d\xf9\xddk\x00\x00\x00U\xad\xe1\xac\xe9Ox\xcf\xf9\xfds\xf9\x16m\x8f\x96\x05\x1c\xc1\x0e\r\xc53M\xf6\x80CT\xab6\xcf&\x95\xbc\x81\x7f\x80a,\r\x0f\x86\xd1\xc0P,\x1c\x1c\xcfCi|\x81\xb2\x86\xe1\xc5\x00\x9b\x84\x1c\xe0\xb7\x0c.\xcdn\xc5;\xad\x01\x7fn\xe5\x9b\x88G&\xec\xd8\x1d\xac\xda\xf1^\xf9\x12\xb4ld\x15\xeaMvOv~\xab:\xe6\x85\x14?\xf5\xb4E\xf9'
转化成bytes:
['0x43', '0x53', '0x4a', '0x4d', '0x9', '0x1', '0xc', '0x0', '0x12', '0x1e', '0x33', '0x83', '0x85', '0xf3', '0xff', '0x7d', '0x6d', '0x8d', '0xf9', '0xdd', '0x6b', '0x0', '0x0', '0x0', '0x55', '0xad', '0xe1', '0xac', '0xe9', '0x4f', '0x78', '0xcf', '0xf9', '0xfd', '0x73', '0xf9', '0x16', '0x6d', '0x8f', '0x96', '0x5', '0x1c', '0xc1', '0xe', '0xd', '0xc5', '0x33', '0x4d', '0xf6', '0x80', '0x43', '0x54', '0xab', '0x36', '0xcf', '0x26', '0x95', '0xbc', '0x81', '0x7f', '0x80', '0x61', '0x2c', '0xd', '0xf', '0x86', '0xd1', '0xc0', '0x50', '0x2c', '0x1c', '0x1c', '0xcf', '0x43', '0x69', '0x7c', '0x81', '0xb2', '0x86', '0xe1', '0xc5', '0x0', '0x9b', '0x84', '0x1c', '0xe0', '0xb7', '0xc', '0x2e', '0xcd', '0x6e', '0xc5', '0x3b', '0xad', '0x1', '0x7f', '0x6e', '0xe5', '0x9b', '0x88', '0x47', '0x26', '0xec', '0xd8', '0x1d', '0xac', '0xda', '0xf1', '0x5e', '0xf9', '0x12', '0xb4', '0x6c', '0x64', '0x15', '0xea', '0x4d', '0x76', '0x4f', '0x76', '0x7e', '0xab', '0x3a', '0xe6', '0x85', '0x14', '0x3f', '0xf5', '0xb4', '0x45', '0xf9']
刚好可以验证加密结果的前21字节是否和我们文中分析的一样。
1-4字节:0x43, 0x53, 0x4a, 0x4d(小端模式存储即为0x4d4a5343)
5字节:0x09
6字节:0x01
7字节:0x0c(随机数nonce字节长度)
8字节:0x00
9-20字节:0x12, 0x1e, 0x33, 0x83, 0x85, 0xf3, 0xff, 0x7d, 0x6d, 0x8d, 0xf9, 0xdd(随机数nonce)
21字节:0x6b(输入字符串长度91,0x6b为107,多了16)
22-24字节:0x00, 0x00, 0x00
25-131字节:刚好107为,与第21字节对应
3.3、汇编指令流分析
我们从加密结果切入,逆向分析汇编指令流,看看加密结果如何产生的。第25-28字节是0x55ade1ac,小端模式的存储形式是0xace1ad55,汇编指令流中搜索0xace1ad55。
可以发现0xace1ad55是由0xc9888f2e与0x6569227b异或计算得到的,其中0x6569227b实际为0x7b226965,转化为字符串为{"ie,正好是输入字符串的前4个字节。
继续搜索0xc9888f2e,可见0xc9888f2e是由0xa5cfe38c与0x8b406b45异或得到的,偏移地址是0x854a。
结合Ida,跳转到偏移地址为0x854a的反编译代码上,可知这是sub_838c函数中一条语句。
再看看哪个函数调用了sub_838c,可以定位到sub_8340函数。
看着是不是很熟悉?像不像上一节介绍的Aes加密函数。也可以通过Ida第三方插件Findcrypt进行验证,下图是Findcrypt插件运行的结果。
其中RijnDael_AES_7E00是Aes加密过程中使用的特征魔数,查看RijnDael_AES_7E00引用情况,可知sub_8340函数调用了RijnDael_AES_7E00,因此能够确定sub_8340是Aes加密算法。
通过汇编指令流可以知道sub_8340加密算法的明文与密钥,我们从34231行向上搜索sub_8340。
可知明文,前12字节是之前生成的随机数nonce,后4字节是0x00000002
0x121e338385f3ff7d6d8df9dd00000002
密钥
0xf3645cc3db63ae5828925612412de6d9
加密结果
0x2e8f88c9cb755af7cfca4ace2f5dbda739558c0969a7a92a00107ce4aa95c3e6
加密结果的前4个字节是0x2e8f88c9,转化为小端存储正是与明文0x6569227b进行异或运算的0xc9888f2e。
继续分析:
定义AesData0为0x00000000000000000000000000000000进行Aes加密运算的结果。
定义AesData1为0x121e338385f3ff7d6d8df9dd00000001进行Aes加密运算的结果。
定义AesData2为0x121e338385f3ff7d6d8df9dd00000002进行Aes加密运算的结果。
定义AesData3为0x121e338385f3ff7d6d8df9dd00000003进行Aes加密运算的结果。
定义AesData4为0x121e338385f3ff7d6d8df9dd00000004进行Aes加密运算的结果。
以此类推:
其中0x121e338385f3ff7d6d8df9dd是随机数nonce。
则可以设置加密结果的第25-40字节为明文1-16字节与AesData2前16字节异或的结果。
则可以设置加密结果的第41-56字节为明文17-32字节与AesData3前16字节异或的结果。
则可以设置加密结果的第57-72字节为明文33-48字节与AesData4前16字节异或的结果。
直到把明文异或完成。
举例说明:明文1-16字节:
{"ie":"867979021
转化为16进制:
0x7b226965223a22383637393739303231
AesData2前16位:
0x2e8f88c9cb755af7cfca4ace2f5dbda7
异或结果,与加密结果的第25-40字节对应:
0x55ade1ace94f78cff9fd73f9166d8f96
那么还剩加密结果的最后16字节:
0xea4d764f767eab3ae685143ff5b445f9
最后16字节计算逻辑比较复杂,与AesData0、AesData1有关,需要结合汇编指令流与Ida反编译的代码一起分析。
简单来说,我们定义三个数据字典,后续的操作都是对data_bb6e的变换,加密结果的最后16字节为最终的data_bb6e与AesData1前16位异或的结果。
data_bb6e = [0xaa, 0x45, 0x6e, 0xc7,0xd8, 0x29, 0x72, 0x0b,0x0f, 0xce, 0xe3, 0xfa,0x0e, 0xbd, 0x94, 0x3f
]data_b9c0 = [0x0000, 0x1C20, 0x3840, 0x2460,0x7080, 0x6CA0, 0x48C0, 0x54E0,0xE100, 0xFD20, 0xD940, 0xC560,0x9180, 0x8DA0, 0xA9C0, 0xB5E0
]data_6b14 = [[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],
]
最后贴上逆向还原的代码。
import uuid
import base64
from Crypto.Cipher import AES
from binascii import b2a_hex, unhexlifyclass Caesarson:def __init__(self):self.aes_key = b'\xf3d\\\xc3\xdbc\xaeX(\x92V\x12A-\xe6\xd9'self.data_b9c0 = [0x0000, 0x1C20, 0x3840, 0x2460, 0x7080, 0x6CA0, 0x48C0, 0x54E0, 0xE100, 0xFD20, 0xD940, 0xC560, 0x9180, 0x8DA0, 0xA9C0, 0xB5E0]self.data_bb6e = [0xaa, 0x45, 0x6e, 0xc7, 0xd8, 0x29, 0x72, 0x0b, 0x0f, 0xce, 0xe3, 0xfa, 0x0e, 0xbd, 0x94, 0x3f]self.data_6b14 = [[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 random_key(self):random_data = str(uuid.uuid4()).replace("-", "")return self.hex2bytes(random_data)def hex2bytes(self, string, length=None):if string.startswith("0x"):string = string[2:]if len(string) % 2 != 0:string = "0" + stringif length and length > len(string):string = "0" * (length-len(string)) + stringhex = string.encode("utf-8")return unhexlify(hex)def bytes2hex(self, data_bytes):return b2a_hex(data_bytes.encode('utf-8'))def aes(self, string):encryptor = AES.new(self.aes_key, 1)string = string + (chr((16 - (len(string) % 16))).encode() * (16 - (len(string) % 16)))return encryptor.encrypt(string)def b9f0(self, data_bytes, part_4):i = 0v4 = part_4[15]v5 = self.data_bb6e[15]while(i != (len(data_bytes) // 16)):v6 = v4 ^ v5v7 = 14v8 = self.data_6b14[v6 & 0xF]v9 = v8[0]v10 = v8[1]v11 = v8[2]v12 = v8[3]v13 = self.data_6b14[(v6 & 0xF0) // 16]v14 = self.data_b9c0[v9 & 0xF]v15 = (v13[0] ^ (v9 >> 4) ^ (v10 << 28)) & 0xffffffffv16 = (v13[1] ^ (v10 >> 4) ^ (v11 << 28)) & 0xffffffffv17 = (v13[2] ^ (v11 >> 4) ^ (v12 << 28)) & 0xffffffffv20 = (v13[3] ^ (v12 >> 4) ^ (v14 << 16)) & 0xffffffffv18 = part_4[i * 16 + 14] ^ self.data_bb6e[14]v19 = v18 & 0xF0v4 = v18 & 0xFwhile(v7 >= 0):v7 -= 1v21 = self.data_6b14[v4]v4 = 2 * (v15 & 0xF)v22 = v21[0]v23 = v21[3]v24 = v21[1]v25 = v21[2]v26 = (v22 ^ (v15 >> 4) ^ (v16 << 28)) & 0xffffffffv27 = (v24 ^ (v16 >> 4) ^ (v17 << 28)) & 0xffffffffv28 = self.data_b9c0[v4 // 2]v29 = (v25 ^ (v17 >> 4)) & 0xffffffffif v7 >= 0:v4 = part_4[i * 16 + v7]v30 = (v29 ^ (v20 << 28)) & 0xffffffffv31 = (v23 ^ (v20 >> 4)) & 0xffffffffv32 = self.data_6b14[v19 // 16]v33 = (v31 ^ (v28 << 16)) & 0xffffffffv19 = 2 * (v26 & 0xF)v34 = v32[0]v35 = v32[1]v36 = v32[2]v37 = v32[3]v38 = (v34 ^ (v26 >> 4)) & 0xffffffffif v7 >= 0:v34 = self.data_bb6e[v7]v15 = (v38 ^ (v27 << 28)) & 0xffffffffv39 = (v35 ^ (v27 >> 4)) & 0xffffffffv40 = self.data_b9c0[v19 // 2]v16 = (v39 ^ (v30 << 28)) & 0xffffffffv17 = (v36 ^ (v30 >> 4) ^ (v33 << 28)) & 0xffffffffif v7 >= 0:v4 ^= v34v41 = (v37 ^ (v33 >> 4)) & 0xffffffffif v7 >= 0:v19 = v4 & 0xF0v4 &= 0xFv20 = (v41 ^ (v40 << 16)) & 0xffffffffi += 1v5 = v15if i != (len(data_bytes) // 16):v4 = part_4[i * 16 + 15]bytes_v20 = self.hex2bytes(hex(v20), length=8)bytes_v17 = self.hex2bytes(hex(v17), length=8)bytes_v16 = self.hex2bytes(hex(v16), length=8)bytes_v15 = self.hex2bytes(hex(v15), length=8)self.data_bb6e[:4] = bytes_v20self.data_bb6e[4:8] = bytes_v17self.data_bb6e[8:12] = bytes_v16self.data_bb6e[12:16] = bytes_v15def bb14(self):v2 = self.data_bb6e[15]v3 = v2 & 0xf0v4 = 14v6 = self.data_6b14[v2 & 0xf][0]v7 = self.data_6b14[v2 & 0xf][1]v8 = self.data_6b14[v2 & 0xf][2]v9 = self.data_6b14[v2 & 0xf][3]v10 = self.data_bb6e[14]v11 = self.data_6b14[v3 // 16][0]v12 = self.data_6b14[v3 // 16][1]v13 = self.data_6b14[v3 // 16][2]v14 = self.data_6b14[v3 // 16][3]v15 = 2 * (v6 & 0xf)v16 = (v11 ^ (v6 >> 4)) & 0xffffffffv17 = self.data_b9c0[v15 // 2]v18 = (v16 ^ (v7 << 28)) & 0xffffffffv19 = (v12 ^ (v7 >> 4) ^ (v8 << 28)) & 0xffffffffv20 = (v13 ^ (v8 >> 4) ^ (v9 << 28)) & 0xffffffffv21 = v10 & 0xF0v22 = (v14 ^ (v9 >> 4) ^ (v17 << 16)) & 0xffffffffv23 = v10 & 0xFwhile (v4 >= 0):v4 -= 1v24 = self.data_6b14[v23]v23 = 2 * (v18 & 0xF)v25 = v24[0]v26 = v24[3]v27 = v24[1]v28 = v24[2]v29 = (v25 ^ (v18 >> 4) ^ (v19 << 28)) & 0xffffffffv30 = (v27 ^ (v19 >> 4) ^ (v20 << 28)) & 0xffffffffv31 = self.data_b9c0[v23 // 2]v32 = (v28 ^ (v20 >> 4)) & 0xffffffffif v4 >= 0:v23 = self.data_bb6e[v4]v33 = (v32 ^ (v22 << 28)) & 0xffffffffv34 = (v26 ^ (v22 >> 4)) & 0xffffffffv35 = self.data_6b14[v21 // 16]v36 = (v34 ^ (v31 << 16)) & 0xffffffffv21 = 2 * (v29 & 0xF)v18 = (v35[0] ^ (v29 >> 4) ^ (v30 << 28)) & 0xffffffffv37 = self.data_b9c0[v21 // 2]v19 = (v35[1] ^ (v30 >> 4) ^ (v33 << 28)) & 0xffffffffv20 = (v35[2] ^ (v33 >> 4) ^ (v36 << 28)) & 0xffffffffv38 = (v35[3] ^ (v36 >> 4)) & 0xffffffffif v4 >= 0:v21 = v23 & 0xF0v23 &= 0xFv22 = (v38 ^ (v37 << 16)) & 0xffffffffbytes_v22 = self.hex2bytes(hex(v22), length=8)bytes_v20 = self.hex2bytes(hex(v20), length=8)bytes_v19 = self.hex2bytes(hex(v19), length=8)bytes_v18 = self.hex2bytes(hex(v18), length=8)self.data_bb6e[:4] = bytes_v22self.data_bb6e[4:8] = bytes_v20self.data_bb6e[8:12] = bytes_v19self.data_bb6e[12:16] = bytes_v18def encrypt(self, data_bytes):key = self.random_key()part_1 = [0x43, 0x53, 0x4a, 0x4d, 0x9, 0x1, 0xc, 0x0]part_2 = list(key)part_3 = [0x0, 0x0, 0x0, 0x0]for i, j in enumerate(self.hex2bytes(hex(len(data_bytes) + 16))):part_3[i] = jpart_4, aes_data = [], []for i in range((len(data_bytes) // 16) + 1):_aes_data = self.aes(key + self.hex2bytes(hex(i+2), length=8))aes_data.extend(_aes_data[:16])for i in range(len(data_bytes)):x = data_bytes[i] ^ aes_data[i]part_4.append(x)self.b9f0(data_bytes, part_4)for i, j in enumerate(range(len(data_bytes) % 16, 0, -1)):self.data_bb6e[i] ^= (data_bytes[len(data_bytes) - j] ^ aes_data[len(data_bytes) - j])self.bb14()self.data_bb6e[7] ^= 0x40temp_bytes = self.hex2bytes(hex(len(data_bytes) << 3), length=8)for i, b in enumerate(temp_bytes):self.data_bb6e[12 + i] ^= bself.bb14()part_5 = []for index, b in enumerate(self.data_bb6e):x = b ^ self.aes(key + b"\x00\x00\x00\x01")[index % 16]part_5.append(x)data = part_1 + part_2 + part_3 + part_4 + part_5return base64.b64encode(bytes(data)).decode()