最近在分析微信读书接口时,发现请求中存在signature参数。
最开始尝试:
SHA256(timestamp+deviceId+random)
但是生成结果和真实signature不一致。
因此判断真实签名逻辑并不在Java层,而是在Native层的libencrypt.so中实现。
本文记录一次完整的逆向过程,目标是定位
weread.encrypt.EncryptUtils
中的
nativeGetSignatures
方法,并还原签名生成流程。
一、HookJava层定位入口
首先定位Java层:
weread.encrypt.EncryptUtils
找到:
nativeGetSignatures(String[]strArr)
方法。
通过FridaHook调试:
Java.perform(function () {
const EncryptUtils =
Java.use(
"weread.encrypt.EncryptUtils"
);
EncryptUtils.nativeGetSignatures
.implementation = function(arr) {
console.log(
"参数数量:"
+ arr.length
);
for(let i = 0; i < arr.length; i++){
console.log(
"arg[" + i + "]="
+ arr[i]
);
}
let result =
this.nativeGetSignatures(arr);
console.log(
"返回:"
+ result
);
return result;
};
});
运行后:
发现返回值长度:
64位:
dc3ea89d77e43ce790a0b9dfbddd02a2a8d3525abd5f35e4ecd7c822d1f32cfb
64位十六进制字符串符合SHA256输出特征。
因此初步判断:
signature=SHA256(...)
二、定位libencrypt.so
Java层最终进入Native。
找到:
libencrypt.so
其中核心JNI函数:
Java_weread_encrypt_EncryptUtils_nativeGetSignatures
继续分析,发现主要调用:
weread::GenSignature(n)
真正的签名生成逻辑在:weread::GenSignature中。
三、分析Native层参数处理
主动调用调试:
发现Java层提交的数组进入Native后,并不是直接拼接。
首先进行了sort()排序。
也就是说,Java层传入:
[
参数1,
参数2,
参数3
]
进入Native后:
排序后的参数
才参与后续签名计算。
因此:
参数顺序非常重要。
不能直接按照调用处看到的顺序进行拼接。
四、发现隐藏salt
继续分析:
在:
weread::GenSignature
中发现最终参与签名的数据比传入数组多一个字段。
原本:
3个参数
经过处理后:
4个参数
多出来的就是:
salt
最终类似:
参数1
+
参数2
+
参数3
+
salt
五、分析salt生成方式
继续跟踪,发现调用:
weread::RemapString()
进行字符串重映射。
Native中保存了一张:
256字节查找表
转换逻辑:
输入字符
↓
ASCII值作为索引
↓
查询byte数组
↓
生成新的字符串
Python还原:
def transform_string(input_str):
byte_Index = [
0x34,
0xCA,
0x55,
0x40,
0x1D,
0xB6,
0x93,
0xC6,
# ...
# 完整256字节表
0xB7,
0x15
]
result = []
for char in input_str:
index = ord(char)
if index < 0 or index >= len(byte_Index):
raise ValueError(
f"{char} 超出范围"
)
result.append(
byte_Index[index]
)
return bytes(result)
将IDA中找到的256字节表复制出来,即可还原真实salt。
六、分析Hash流程
继续跟踪:
发现签名并不是简单一次SHA256。
实际流程:
两次SHA256
并且每次Hash前都会进行一次数据重排序。
完整流程:
输入4个参数
↓
sort排序
↓
追加salt
↓
第一次数据重排序
↓
SHA256
↓
第二次数据重排序
↓
SHA256
↓
输出signature
七、自定义数据重排序算法
通过IDA分析发现:
每次SHA256前都会计算一个校验值。
1.计算校验值
通过异或:
checksum=byte1^byte2^byte3...
得到:
0-10
范围内的值。
2.根据校验值循环位移
根据checksum:
对输入数据进行循环移动。
类似:
def reorder(data):
checksum = 0
for b in data:
checksum ^= b
offset = checksum % 10
return (
data[offset:]
+
data[:offset]
)
八、最终签名流程
最终还原:
Java层参数
↓
nativeGetSignatures
↓
weread::GenSignature
↓
参数sort排序
↓
RemapString生成salt
↓
追加salt
↓
第一次数据重排序
↓
SHA256
↓
第二次数据重排序
↓
SHA256
↓
signature

评论
填写昵称与邮箱即可评论,无需登录。