tp2 - tersafe2update工作分析
在/data/data/包名/files/libtersafeupdate2.so 创建该文件即可触发逻辑

CRC32校验
相同的目录有个16字节的文件tss_up_flag.dat,

其中的v12(汇编内为w20)就是crc32的值
阶段1 替换特定字符串
阶段1不具备什么验证,具体代码就是下面的了
// Stage A: patch all "#*0789#" occurrences by writing "12345678" at [match+7]
if (filesize > 7) {
const uint64_t patch8 = a12345678z_qword; // "12345678"
for (uint32_t off = 0; off + 7 <= filesize - 1; ++off) {
if (memcmp(map_base + off, NEEDLE7, 7) == 0) {
*(uint64_t*)(map_base + off + 7) = patch8;
}
}
}
阶段2 寻找hdr地址
extern void* dec_str_func_sub_4f841c(uint32_t key); // 0x7851 -> 16 bytes ("ASS_SDK_SIGDATA"), then sig24[0]='T'
uint8_t sig24[24] = {0};
{
const uint8_t* p16 = (const uint8_t*)dec_str_func_sub_4f841c(0x7851);
memcpy(sig24, p16, 16);
sig24[0] = 'T';
}
// Find header marker (stride 0x100 then linear)
uint8_t* match = NULL;
if (filesize > 0x18) {
for (uint32_t off = 0; off + 0x18 <= filesize; off += 0x100) {
if (memcmp(map_base + off, sig24, 24) == 0) { match = map_base + off; break; }
}
if (!match) {
for (uint32_t off = 0; off + 0x18 <= filesize; ++off) {
if (memcmp(map_base + off, sig24, 24) == 0) { match = map_base + off; break; }
}
}
}
if (!match) return RET_NO_MARKER;
阶段2就是在寻找TSS_SDK_SIGDATA 开头大小为0x150的一个验证hdr
const unsigned char TSS_SIG24_FULL[0x150 + 24] = {
0x54,0x53,0x53,0x5F,0x53,0x44,0x4B,0x5F,0x53,0x49,0x47,0x44,0x41,0x54,0x41,0x00,
0x00,0x00,0x00,0x00, // flags = 0
0x50,0x01,0x00,0x00, // length = 0x150 (LE)
// padding to 0x150
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
};
我们可以这样构造一个出来,这样就能通过阶段2的验证了
阶段3 清空hdr区域,对整个so文件进行sha1(标准)
uint32_t flags = *(uint32_t*)(match + 0x10);
uint32_t length = *(uint32_t*)(match + 0x14);
if (flags != 0) return RET_HDR_FLAG;
if (length != 0x150) {
// Length mismatch: fall into the same error as signature path does in your asm (via loc_1D4984)
return RET_HDR_FLAG; // conservative: treat as unsupported header
}
// Copy 0x150 header to a safe buffer and wipe original area in map
uint8_t hdr[0x150];
memcpy(hdr, match, 0x150);
memset(match, 0, 0x150);
// Compute SHA-1 over the entire mapped file
uint8_t sha20[20];
sha1_encode(map_base, filesize, sha20);
if (a3 > 0x13 && out_sha1_opt) {
memcpy(out_sha1_opt, sha20, 20);
}
阶段4 RSA解密HDR+0x13C部分
解密出来的部分才是sha1的真正的值,使用与内置公钥 unk_533950 匹配的私钥对 EM 进行签名(EM 的布局满足解密后 digest 在最后 20 字节,当前实现不做 PKCS#1 v1.5 全结构检查,但仍需要能让 y=x^e mod N 的结果在 +0x13C 放入 digest,这本质上还是“用私钥签名”问题)
const void* key = pubkey_opt ? pubkey_opt : (const void*)/*builtin*/(uintptr_t)0x533950; // unk_533950
int ok = sub_4984EC(key, &hdr[0x50], &hdr[0x50], 0x100);
if (ok == 0) return RET_SIG_FAIL;
// Compare SHA-1 against header's stored digest at +0x13C
const uint8_t* expect = &hdr[0x13C];
if (memcmp(sha20, expect, 20) == 0) {
return 1;
}
没有私钥,仅写 hdr+0x13C 为 SHA‑1 无法通过;因为解密会覆盖这 20 字节。
想要返回 1 (返回1,则tp2会dlopen该so文件):要么用对应私钥生成匹配签名,要么切换到你掌控的公钥并用你的私钥签名,要么在运行时修改/绕过对比逻辑。