未加固
版本:9.2.3
前置知识:
(v41 & 0xFFFFFFFFFFFFFFFELL)
是一种高效的奇偶检查方法,用于判断数值 v41
是否为奇数。
std::sort<std::lessstd::string,std::string &,std::string>(a1, v6, s); 排序算法
# 完全等价的字符串列表排序 strings = ["banana", "apple", "cherry"] strings.sort() # 原地排序 # 或 sorted_strings = sorted(strings) # 返回新列表
hash常见系列防护:
1.加盐salt的值
2.魔改算法,修改算法的部分常量
3.也就是我们今天遇到的,利用自定义的校验和计算与数据重排算法
我们可以先认识一下这段代码:生成校验值(0-10)然后对其进行重新排序
def reorder_before_sha256(input_hex):"""SHA256前的重排序算法(精确还原C++逻辑):param input_hex: 输入hex字符串:return: 重排后的hex字符串"""# 1. 将hex转换为可变字节数组data = bytearray.fromhex(input_hex)length = len(data)if length == 0:return ""# 2. 计算校验值(模拟C++的v19计算)checksum = 0i = 0# 成对处理字节(偶数部分)paired_len = length & 0xFFFFFFFFFFFFFFFE # 取偶数长度while i < paired_len:checksum ^= data[i] ^ data[i + 1]i += 2# 处理可能的最后一个字节(奇数长度)if length % 2 != 0:checksum ^= data[-1]checksum %= 11 # 最终校验值(0-10)# 3. 动态重排(核心逻辑)reordered = bytearray(length)for original_pos in range(length):# 计算新位置:(校验值 + 原位置) % 总长度new_pos = (checksum + original_pos) % lengthreordered[new_pos] = data[original_pos]return reordered
一、抓包确定位置:
这里的signature,触发在登录之后每次点击我的页面会触发:相关类是 weread.encrypt.EncryptUtils 的nativeGetSignatures方法
我们可以看到输入一个strArr数组 返回的是64位 通过下表我们可以猜测可能是经过了sha256的加密。
找到关键so libencrypt.so 这个上面jadx关键类下就有就不多提了。
构建主动调用:
function call() {Java.perform(function () {// 1. 获取 EncryptUtils 类let EncryptUtils = Java.use("weread.encrypt.EncryptUtils");let instance = EncryptUtils.$new();
// 2. 构造主动调用函数try {let inputArray = ["09cdcfd7f","1045","1145656655","12676517_1736697599_1779983999_0_1_0_935_34327344_23181744",]// let inputArray = [// "sgh256245",// "7895",// "1745675066",// "52676517_1736697599_1779983999_0_1_0_935_34327344_23181744",// ]// 3. 将 JavaScript 数组转换为 Java String[]let javaStringArray = Java.array('java.lang.String', inputArray);// 4. 调用 nativeGetSignatures 方法let result = instance.nativeGetSignatures(javaStringArray);// 5. 打印结果console.log("主动调用 nativeGetSignatures 结果:", result);return result;} catch (e) {console.error("主动调用失败:", e);}})
}
二、算法分析:
Java_weread_encrypt_EncryptUtils_nativeGetSignatures(JNIEnv *a1, __int64 a2, void *rucan_list)
此函数是我们加密生成Signature的函数:
可以看到这个位置 调用了生成GenSignature
这个n其实是我们的js_string类型的strArr数组:我们可以hook weread::GenSignature(n);
这里我们只看到了第一个的值 其他值应该是在指针里面 或者是一个结构体 这里我们只是合理猜测 但是没具体去实践。
但是我是直接hook的 weread::GenSignature 中的 std::string::append(&v66, v14, v15);函数
我们可以看到 他是先进行了排序在进行拼接成一个字符串。 我们可以看到我们这里就有5个参数进行了append
那么我们传递了四个参数为什么是5个进行了append
let inputArray = [
"09cdcfd7f",
"1045",
"1145656655",
"12676517_1736697599_1779983999_0_1_0_935_34327344_23181744",
]
而且拼接的也有规律 是按照了 sort排序 我们由下图可以看出。
原来这里 的5a6f1其实是盐的值呢
那下面我们可以解决一下为什么我们的
let inputArray = [
"09cdcfd7f",
"1045",
"1145656655",
"12676517_1736697599_1779983999_0_1_0_935_34327344_23181744",
] 以及盐的值
明文变成了上述那些字符嘞?他的上一层函数中有weread::RemapString(n); 很是可疑。weread::RemapString
很可能是一个字符串重映射函数
关键byte_D9FC字符数组
byte_D9FC = [0x34, 0xCA, 0x55, 0x40, 0x1D, 0xB6, 0x93, 0xC6, 0x31,0x30, 0x29, 0x35, 0x32, 0xA7, 0xB8, 0x11, 0xC2, 0xB5,0x16, 0xFA, 0x8B, 0xB1, 0x24, 0xA4, 0x10, 0x90, 4,0xE9, 8, 0xF8, 0x3B, 0x8A, 0x9C, 0x8C, 0x44, 0xF9,0xBC, 0x5C, 0x69, 0xE2, 0xA1, 0xDA, 0xD2, 0xD3, 0x75,0x89, 0xF7, 0x1E, 0x2D, 0x50, 0x56, 0xD7, 0x72, 0x53,0xBF, 0x22, 0xFB, 0x20, 0xF, 1, 0x2E, 0x45, 0x87, 0x6E,0x66, 0x48, 0xF2, 0xE0, 0xCD, 0xFE, 0x67, 0xA9, 0x43,0xF4, 0x94, 0x51, 0xCE, 0xA5, 0x4A, 0xEE, 0x13, 0x26,0x8E, 0xCC, 0xAA, 0x33, 0x14, 0x5D, 0xE, 0x39, 0xBB,0xCF, 0x91, 0x2B, 0x81, 0x4D, 0xEA, 0x99, 0xEC, 0x1A,0x2C, 0x85, 0xC5, 0xD9, 0x36, 0x74, 0x4B, 0x18, 0xE1,0xF1, 0x3D, 0x9D, 0x41, 0x9F, 0xB4, 0x17, 0xD, 0xD6,0x4C, 0xBE, 0xDC, 0xAF, 0x97, 0x28, 0x77, 0xF0, 0x62,0xFF, 0x71, 0xC1, 0xC8, 0x27, 0x8F, 0x6C, 0x68, 0xA8,0x9B, 0xE6, 0x59, 0x1C, 0x1B, 0x12, 9, 0x98, 0x4E,0x3F, 6, 0x37, 0, 0xBA, 0x1F, 0xA, 0x19, 0x2F, 0xC9,0xD5, 0xD0, 0x57, 0x49, 0x6F, 0xFD, 0x25, 0xE4, 0x61,0xC, 0x42, 0xCB, 0x96, 0x64, 0x5F, 0xDB, 0xAD, 0x60,0x23, 0x8D, 0x9A, 0x6D, 0xC3, 0xC4, 0x5E, 0x3E, 0xB9,0x92, 0x6A, 0xBD, 0x5B, 7, 0x7F, 0x76, 0x95, 0xED,0x4F, 0xAB, 0x84, 0x7A, 0x80, 0xE7, 0x78, 0xC7, 0xE5,0xEB, 0x73, 0x83, 0x6B, 0xFC, 0x38, 0x46, 0x7D, 0x47,0x65, 0xB3, 0x52, 0x63, 0x3A, 5, 0xD1, 0xEF, 0xA3,0xA6, 0xDE, 0x9E, 0x3C, 2, 0xAE, 0xB2, 0x7B, 0xA0,0xF6, 0xF3, 0x2A, 0xC0, 0xAC, 0x86, 3, 0x5A, 0x54,0xB, 0xF5, 0x82, 0xD4, 0x7E, 0xE3, 0xDF, 0xB0, 0xD8,0xDD, 0x21, 0xE8, 0x7C, 0x88, 0xA2, 0x79, 0x58, 0x70,0xB7, 0x15]
这里不带大家验证了 其实就是我们的传入的数组做了这个的映射
代码:
def Tranfrom(input_str):byte_D9FC = [0x34, 0xCA, 0x55, 0x40, 0x1D, 0xB6, 0x93, 0xC6, 0x31,0x30, 0x29, 0x35, 0x32, 0xA7, 0xB8, 0x11, 0xC2, 0xB5,0x16, 0xFA, 0x8B, 0xB1, 0x24, 0xA4, 0x10, 0x90, 4,0xE9, 8, 0xF8, 0x3B, 0x8A, 0x9C, 0x8C, 0x44, 0xF9,0xBC, 0x5C, 0x69, 0xE2, 0xA1, 0xDA, 0xD2, 0xD3, 0x75,0x89, 0xF7, 0x1E, 0x2D, 0x50, 0x56, 0xD7, 0x72, 0x53,0xBF, 0x22, 0xFB, 0x20, 0xF, 1, 0x2E, 0x45, 0x87, 0x6E,0x66, 0x48, 0xF2, 0xE0, 0xCD, 0xFE, 0x67, 0xA9, 0x43,0xF4, 0x94, 0x51, 0xCE, 0xA5, 0x4A, 0xEE, 0x13, 0x26,0x8E, 0xCC, 0xAA, 0x33, 0x14, 0x5D, 0xE, 0x39, 0xBB,0xCF, 0x91, 0x2B, 0x81, 0x4D, 0xEA, 0x99, 0xEC, 0x1A,0x2C, 0x85, 0xC5, 0xD9, 0x36, 0x74, 0x4B, 0x18, 0xE1,0xF1, 0x3D, 0x9D, 0x41, 0x9F, 0xB4, 0x17, 0xD, 0xD6,0x4C, 0xBE, 0xDC, 0xAF, 0x97, 0x28, 0x77, 0xF0, 0x62,0xFF, 0x71, 0xC1, 0xC8, 0x27, 0x8F, 0x6C, 0x68, 0xA8,0x9B, 0xE6, 0x59, 0x1C, 0x1B, 0x12, 9, 0x98, 0x4E,0x3F, 6, 0x37, 0, 0xBA, 0x1F, 0xA, 0x19, 0x2F, 0xC9,0xD5, 0xD0, 0x57, 0x49, 0x6F, 0xFD, 0x25, 0xE4, 0x61,0xC, 0x42, 0xCB, 0x96, 0x64, 0x5F, 0xDB, 0xAD, 0x60,0x23, 0x8D, 0x9A, 0x6D, 0xC3, 0xC4, 0x5E, 0x3E, 0xB9,0x92, 0x6A, 0xBD, 0x5B, 7, 0x7F, 0x76, 0x95, 0xED,0x4F, 0xAB, 0x84, 0x7A, 0x80, 0xE7, 0x78, 0xC7, 0xE5,0xEB, 0x73, 0x83, 0x6B, 0xFC, 0x38, 0x46, 0x7D, 0x47,0x65, 0xB3, 0x52, 0x63, 0x3A, 5, 0xD1, 0xEF, 0xA3,0xA6, 0xDE, 0x9E, 0x3C, 2, 0xAE, 0xB2, 0x7B, 0xA0,0xF6, 0xF3, 0x2A, 0xC0, 0xAC, 0x86, 3, 0x5A, 0x54,0xB, 0xF5, 0x82, 0xD4, 0x7E, 0xE3, 0xDF, 0xB0, 0xD8,0xDD, 0x21, 0xE8, 0x7C, 0x88, 0xA2, 0x79, 0x58, 0x70,0xB7, 0x15]encoded_bytes = []for char in input_str:# 获取字符的ASCII码作为索引index = ord(char)# 确保索引在查找表范围内if index < 0 or index >= len(byte_D9FC):raise ValueError(f"字符 '{char}' (ASCII: {index}) 超出查找表范围")# 使用查找表进行编码encoded_byte = byte_D9FC[index]encoded_bytes.append(encoded_byte)# 将编码后的字节转换为十六进制字符串hex_str = ''.join([f"{b:02x}" for b in encoded_bytes])return hex_str
三、两次sha256算法
ida中的代码
v18 = 0;if ( (mingwen_str & 1) != 0 )v19 = v17;elsev19 = mingwen_str >> 1;if ( (mingwen_str & 1) != 0 )v20 = v16;elsev20 = &mingwen_strv66 + 1;if ( v19 ){v21 = v20;if ( v19 == 1 )goto LABEL_34;v22 = 0;v23 = 0;v21 = &v20[v19 & 0xFFFFFFFFFFFFFFFELL];v24 = v20 + 1;v25 = v19 & 0xFFFFFFFFFFFFFFFELL;do{v26 = *(v24 - 1);v27 = *v24;v24 += 2;v25 -= 2LL;v22 ^= v26;v23 ^= v27;}while ( v25 );v18 = v23 ^ v22;if ( v19 != (v19 & 0xFFFFFFFFFFFFFFFELL) ){
LABEL_34:v28 = &v20[v19];do{v29 = *v21++;v18 ^= v29;}while ( v28 != v21 );}v18 %= 11;}v63[1] = 0LL;v64 = 0LL;v63[0] = 0LL;if ( v19 << 32 ){std::string::append(v63, v19, 0);if ( v19 <= 0 )goto LABEL_47;}else{*(v63 + v19 + 1) = 0;LOBYTE(v63[0]) = 2 * v19;if ( v19 <= 0 )goto LABEL_47;}v30 = 0LL;do{v31 = v67;v32 = (v18 + v30) % v19;v33 = v64;if ( (mingwen_strv66 & 1) == 0 )v31 = &mingwen_strv66 + 1;v34 = v31[v30++];if ( (v63[0] & 1) == 0 )v33 = v63 + 1;v33[v32] = v34;}while ( v19 != v30 );
LABEL_47:
============================================
SHA256();v36 = 0LL;v37 = s;v74 = 0;v72 = 0u;v73 = 0u;v71 = 0u;*s = 0u;do{sub_3684(v37, -1LL, v35, v75[v36++]);v37 += 2;}while ( v36 != 32 );std::string::basic_string<decltype(nullptr)>(&sha256_res, s);v38 = v62;v39 = 0;if ( (sha256_res & 1) != 0 )v40 = *&v61[7];elsev40 = sha256_res >> 1;if ( (sha256_res & 1) == 0 )v38 = v61;if ( v40 ){v41 = v38;if ( v40 == 1 )goto LABEL_59;v42 = 0;v43 = 0;v41 = &v38[v40 & 0xFFFFFFFFFFFFFFFELL];v44 = v38 + 1;v45 = v40 & 0xFFFFFFFFFFFFFFFELL;do{v46 = *(v44 - 1);v47 = *v44;v44 += 2;v45 -= 2LL;v42 ^= v46;v43 ^= v47;}while ( v45 );v39 = v43 ^ v42;if ( v40 != (v40 & 0xFFFFFFFFFFFFFFFELL) ){
LABEL_59:v48 = &v38[v40];do{v49 = *v41++;v39 ^= v49;}while ( v48 != v41 );}v39 %= 11;}v58[1] = 0LL;v59 = 0LL;v58[0] = 0LL;if ( v40 << 32 ){std::string::append(v58, v40, 0);if ( v40 <= 0 )goto LABEL_72;}else{*(v58 + v40 + 1) = 0;LOBYTE(v58[0]) = 2 * v40;if ( v40 <= 0 )goto LABEL_72;}v50 = 0LL;do{v51 = v62;v52 = (v39 + v50) % v40;v53 = v59;if ( (sha256_res & 1) == 0 )v51 = v61;v54 = v51[v50++];if ( (v58[0] & 1) == 0 )v53 = v58 + 1;v53[v52] = v54;}while ( v40 != v50 );
LABEL_72:
============================================SHA256();
我们可以看到有两次sha256算法我们hook一下sha256函数看下入参:
进入Sha256================
"rr- ,�,"�5a6f1P-rSPPrS�S��SSPV�"�SP"MP"� "S MP"" �� M-MPM-M �SM�r�V"�rrMV�P�P
0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF
79b4efde50 22 72 72 2d 20 1a 2c 1a c5 2c 22 c5 35 61 36 66 "rr- .,..,".5a6f
79b4efde60 31 50 2d 72 53 50 50 72 53 bf 53 bf bf 53 53 50 1P-rSPPrS.S..SSP
79b4efde70 56 bf 22 bf 53 50 22 4d 50 22 d7 bf bf 20 22 53 V.".SP"MP"... "S
79b4efde80 20 20 4d 50 22 22 20 20 fb d7 20 20 20 4d 2d 4d MP"" .. M-M
79b4efde90 50 4d 2d 4d 20 d7 53 4d d7 72 d7 56 22 d7 72 72 PM-M .SM.r.V".rr
79b4efdea0 4d 56 d7 50 fb 50 MV.P.P
长度 86
Sha256 result= 0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF
778d94dac8 ac 03 64 81 c4 68 6f 92 94 7a ed 19 bd 46 7d f5 ..d..ho..z...F}.
778d94dad8 6c 82 1b 96 10 cd b8 f4 ef f7 da 61 84 fc 43 e8 l..........a..C.
进入Sha256================
3e8ac036481c4686f92947aed19bd467df56c821b9610cdb8f4eff7da6184fc4
0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF
79a4edb990 33 65 38 61 63 30 33 36 34 38 31 63 34 36 38 36 3e8ac036481c4686
79a4edb9a0 66 39 32 39 34 37 61 65 64 31 39 62 64 34 36 37 f92947aed19bd467
79a4edb9b0 64 66 35 36 63 38 32 31 62 39 36 31 30 63 64 62 df56c821b9610cdb
79a4edb9c0 38 66 34 65 66 66 37 64 61 36 31 38 34 66 63 34 8f4eff7da6184fc4
长度 64
Sha256 result= 0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF
778d94dac8 36 c0 8d b7 02 cb 2f 47 7e f9 92 31 f2 97 5d 1b 6...../G~..1..].
778d94dad8 97 d7 a7 87 75 81 93 0a c4 67 34 28 3b 97 d1 45 ....u....g4(;..E
主动调用 nativeGetSignatures 结果: 36c08db702cb2f477ef99231f2975d1b97d7a7877581930ac46734283b97d145
对比append函数:
进入append================
0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF
7a2505af71 2d 20 1a 2c 1a c5 2c 22 c5 - .,..,".
进入append================0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF
7a2505af89 35 61 36 66 31 5a6f1
进入append================0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF
7a2505afa1 50 2d 72 53 P-rS
进入append================0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF
7a2505afb9 50 50 72 53 bf 53 bf bf 53 53 PPrS.S..SS
进入append================0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF
7994eb1450 50 56 bf 22 bf 53 50 22 4d 50 22 d7 bf bf 20 22 PV.".SP"MP"... "
7994eb1460 53 20 20 4d 50 22 22 20 20 fb d7 20 20 20 4d 2d S MP"" .. M-
7994eb1470 4d 50 4d 2d 4d 20 d7 53 4d d7 72 d7 56 22 d7 72 MPM-M .SM.r.V".r
7994eb1480 72 4d 56 d7 50 fb 50 22 72 72 rMV.P.P"rr
我们发现两次sha256之前均进行了重排序。
两次重排序:
-
计算校验和:通过异或运算生成一个0-10的校验值
-
数据重排序:根据校验值对输入字节进行循环位移
def process_sha256_result(sha256_res):# sha256_res = bytearray.fromhex(input_hex)# 1. 初始化参数v40 = 0length = len(sha256_res)# 2. 计算校验和(异或)if length >= 2:v43 = v44 = 0for i in range(0, length - 1, 2):v43 ^= sha256_res[i]v44 ^= sha256_res[i + 1]v40 = (v43 ^ v44) % 11# 3. 数据重排result = bytearray(length)for i in range(length):new_pos = (v40 + i) % lengthresult[new_pos] = sha256_res[i]return bytes(result)
这些代码 根据ida 代码翻译得来:
例如翻译这个:
do{v46 = *(v44 - 1);v47 = *v44;v44 += 2;v45 -= 2LL;v42 ^= v46;v43 ^= v47;}while ( v45 );v39 = v43 ^ v42;if ( v40 != (v40 & 0xFFFFFFFFFFFFFFFELL) ){
LABEL_59:v48 = &v38[v40];do{v49 = *v41++;v39 ^= v49;}while ( v48 != v41 );}v39 %= 11;}v58[1] = 0LL;v59 = 0LL;v58[0] = 0LL;if ( v40 << 32 ){std::string::append(v58, v40, 0);if ( v40 <= 0 )goto LABEL_72;}else{*(v58 + v40 + 1) = 0;LOBYTE(v58[0]) = 2 * v40;if ( v40 <= 0 )goto LABEL_72;}v50 = 0LL;do{v51 = v62;v52 = (v39 + v50) % v40;v53 = v59;if ( (sha256_res & 1) == 0 )v51 = v61;v54 = v51[v50++];if ( (v58[0] & 1) == 0 )v53 = v58 + 1;v53[v52] = v54;}while ( v40 != v50 );
所以我们在两次sha256之前 进行重排序即可。