首页
社区
课程
招聘
[原创]libtalaccsecurity.so sign与pwd算法分析
发表于: 2025-8-12 18:20 969

[原创]libtalaccsecurity.so sign与pwd算法分析

2025-8-12 18:20
969

在开始这篇文章之前,请阅读一下注意事项

本文章仅用于学习研究,如有侵犯贵司权益,请联系告知,会立即做出下架删除处理。

本文章不针对任何网站,APP ,禁止用于商业,转载,违法等用途。

观看则默认同意本约定,未经允许禁止任何形式的转载,保留追究法律责任的权利 !

逻辑分析

pwd算法分析

1
2
3
入参1明文参数:18888888888
入参2明文参数:1754629486905
aes加密结果:8az503s/zohmcg0f7vXXOw==1754629486905

该 So 就是静态注册的方法 Java_com_tal_user_fusion_util_JNISecurity_aesEncode

静态注册的方式 Java_开头 完成静态注册

动态注册的方法 在 JNI_OnLoad 方法中完成的动态注册

咱们静态来分析下

这里 v7 和v9参数就不多做描述了 下面我把代码复制下来给大家解析下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
v7 = GetStringUTFChars(a1, a3, v38);          // 明文
v8 = (*a1)->GetStringUTFChars;
v34 = a4
  v9 = v8(a1, a4, v38);                         // 时间戳
  v10 = strlen(v9);//获取时间戳长度
  v35 = v30;
v11 = &v30[-(((unsigned int)(v10 + 33) + 15LL) & 0x1FFFFFFF0LL)];//计算对齐后的栈偏移,确保 16 字节对齐
strcpy(v11, "zG#V&A2*YO7pUmHsAo55j#1mOC@jAtYi");//复制固定字符串到v11
v31 = v9;//时间戳又赋值给了v31
strcat(v11, v9);//复制时间戳到v11  //到了这里那么 v11的值就是拼接:"zG#V&A2*YO7pUmHsAo55j#1mOC@jAtYi" + timestamp
v11[(__int64)((v10 << 32) + 0x2000000000LL) >> 32] = 0;//冗余操作,实际等价于 v11[len] = 0(确保结尾)
v12 = strlen(v11);//计算 v11总长度 然后赋值给v12
v13 = &v30[-((v12 + 15) & 0xFFFFFFFFFFFFFFF0LL)];//对齐 v13
CharToUnChar(v11, v13, v12);//将 v11 字符串复制到 v13(语义上无变化,只是类型转换)
MD5Init(v43);
strlen(v11);
MD5Update((int)v43, v13);
MD5Final(v43, v44);//计算 v11的 MD5 值 → 存入 v44(16字节)
*(_QWORD *)v37 = *(_QWORD *)&v44[4];//取 MD5(v11)[4:12](8字节)→ 存入 v37
convertUnCharToStr(v37, v42, 8);//将 8 字节转为 16 字符的 hex 字符串 → v42
CharToUnChar(v42, v41, 16);//将 hex 字符串转为字节 → v41 是 AES 的 IV
v14 = strlen(v7);
v15 = v14;
v16 = v14 + 15;
if ( v14 >= 0 )
    v16 = v14;
v17 = v14 - (v16 & 0xFFFFFFF0);
v18 = (v16 & 0xFFFFFFF0) + 16;
v19 = (v16 & 0xFFFFFFF0) + 17;
v20 = (v19 + 15) & 0x1FFFFFFF0LL;
v21 = &v30[-v20];
v32 = v7;
strcpy(&v30[-v20], v7);//上面的操作就是复制明文到对齐缓冲区
if ( (int)(16 - v17) >= 1 )
    memset(&v21[v15], 16 - v17, 15 - v17 + 1LL);//PKCS#7 填充:用 0x10、0x0F... 填充
v21[v18] = 0;
CharToUnChar(&v30[-v20], &v30[-v20], v19);//再次“字符转字节”——实际无意义
CharToUnChar("zG#V&A2*YO7pUmHsAo55j#1mOC@jAtYi", v40, 32);//将固定字符串转为字节 → v40 是 AES 密钥
aes256_init(v39, v40);//初始化 AES 上下文,设置密钥
aes256_encrypt_cbc((int)v39, &v30[-v20]);//执行 AES-256-CBC 加密(使用 v41 作为 IV)
aes256_done(v39);//清理 AES 上下文
v22 = (const char *)b64_encode(&v30[-((v18 + 15LL) & 0x1FFFFFFF0LL)], v18);//Base64 编码加密结果

根据上面的静态分析我们可以发现他的逻辑如下:

函数执行流程(分步详解)

步骤 操作 说明
1 构造 salt salt = salt_prefix + timestamp
其中 salt_prefix = "zG#V&A2*YO7pUmHsAo55j#1mOC@jAtYi"(固定盐前缀)
2 计算 salt 的 MD5 哈希 md5_hash = MD5(salt) → 得到 16 字节二进制摘要
3 提取中间 8 字节 mid8 = md5_hash[4:12](即第 5 到第 12 字节)
4 转为十六进制小写字符串 hex_str = mid8 的 hex 表示(8 字节 → 16 字符,如 "a1b2c3d4e5f67890")
5 将 hex_str 作为 IV iv = hex_str.encode('utf-8')→ 得到 16 字节 ASCII 编码的 IV(非标准但保持一致性)
6 设置密钥 key key = salt_prefix.encode('utf-8')→ 32 字节,符合 AES-256 要求
7 初始化 AES-256-CBC 加密器 使用 key 和 iv 创建 CBC 模式加密对象
8 明文填充(PKCS#7) 对 plaintext.encode('utf-8') 进行块对齐填充(块大小 16)
9 执行加密 得到密文 ciphertext(二进制)
10 Base64 编码密文 b64_enc = base64.b64encode(ciphertext).decode('utf-8')
11 拼接时间戳 result_str = b64_enc + timestamp将 Base64 密文与原始 timestamp 直接拼接
12 返回结果 返回拼接后的字符串

下面我们把这个部分写个py代码验证一下

我们发现运行结果对上了 aes部分至此完结 下面是aes部分代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import hashlib
import base64
from Crypto.Cipher import AES
from Crypto.Util.Padding import unpad,pad
def aes_encode(plaintext: str, timestamp: str) -> str:
    salt_prefix = "zG#V&A2*YO7pUmHsAo55j#1mOC@jAtYi"
    salt = salt_prefix + timestamp
    md5_hash = hashlib.md5(salt.encode('utf-8')).digest()
    mid8 = md5_hash[4:12]
    hex_str = ''.join(f'{b:02x}' for b in mid8)
    iv = hex_str.encode('utf-8')
    key = salt_prefix.encode('utf-8')
    cipher = AES.new(key, AES.MODE_CBC, iv)
    padded_data = pad(plaintext.encode('utf-8'), AES.block_size)
    ciphertext = cipher.encrypt(padded_data)
    b64_enc = base64.b64encode(ciphertext).decode('utf-8')
    result_str = b64_enc + timestamp
    return result_str
key_hex = '18888888888'
plaintext_hex = '1754629486905'
ciphertext = aes_encode(key_hex, plaintext_hex)
print("加密结果:", ciphertext)

sign算法分析

1
2
3
4
入参1明文参数:password=c64XgGuLwqmvq9RoQtHYOw==1754629486921symbol=8az503s/zohmcg0f7vXXOw==1754629486905
入参2明文参数:1754629486923
sign加密结果:YW5kXzEuMjE6Y2I5NDRlZWI4MjY4NzRkNTEyODFjNTNmNGVhY2IxZDQwYjY0YmI2OQ==//这里是base64编码过的
sign base64解密后数据:and_1.21:cb944eeb826874d51281c53f4eacb1d40b64bb69

这里入参就不多做描述了 下面我把代码复制下来给大家解析下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
StatusReg = _ReadStatusReg(TPIDR_EL0);
v32 = *(_QWORD *)(StatusReg + 40);
v24[0] = 0;//初始化一个标志数组
v7 = (*a1)->GetStringUTFChars(a1, a3, v24);// 第一个参数明文数据
v8 = (*a1)->GetStringUTFChars(a1, a4, v24);//第二个参数时间戳
v30 = 0;
v29 = 0;
v28 = 0;
strcpy(v27, "and_1.21");//v27 是一个 9 字节的缓冲区。
*(_WORD *)&v27[strlen(v27)] = 58; // 58 是 ':' 的 ASCII 码
v9 = strlen(v27);//("and_1.21:" 长度)
v22[1] = v22;//v22 是一个 16 字节的栈数组(_QWORD[2])
v10 = (char *)v22 - (((unsigned int)(v9 + 41) + 15LL) & 0x1FFFFFFF0LL);//栈上分配对齐内存
strcpy(v10, v27);//strcpy(v10, v27) → v10 = "and_1.21:"
v11 = (__int64)((v9 << 32) + 0x2800000000LL) >> 32;
v10[v11] = 0;
v26[39] = 0;//初始化一个缓冲区(暂时无用)
v12 = strlen(v8);//第二个参数时间戳长度
v13 = v12 + strlen(v7) + 32;//总长度 = len(时间戳) + len(明文) + 32
v14 = (char *)v22 - (((unsigned int)(v13 + 1) + 15LL) & 0x1FFFFFFF0LL);
strcpy(v14, "zG#V&A2*YO7pUmHsAo55j#1mOC@jAtYi");
strcat(v14, v8);
strcat(v14, v7);
v14[v13] = 0;
sha1(v14, v31, v13);//"zG#V&A2*YO7pUmHsAo55j#1mOC@jAtYi" + v8 + v7 + '\0'
//sha1(input, output, len)
//输入:v14(拼接后的字符串)
//输出:v31(20 字节哈希结果)
//长度:v13
//v31 现在是 20 字节的 SHA-1 哈希值。
v15 = 0;
v16 = v26;
v31[20] = 0;
do
{
    v17 = v31[v15++];
    v18 = a0123456789abcd[v17 >> 4];
    LOBYTE(v17) = a0123456789abcd[v17 & 0xF];
    *(v16 - 1) = v18;
    *v16 = v17;
    v16 += 2;
}
while ( v15 != 20 );//上面代码是将 SHA-1 二进制结果转为十六进制字符串(40 字符)
strcat(v10, &v25);//拼接 hex_str 到 v10("and_1.21:" + hex_str)
(*a1)->ReleaseStringUTFChars(a1, a3, v7);
(*a1)->ReleaseStringUTFChars(a1, a4, v8);
v19 = (char *)b64_encode((const unsigned __int8 *)v10, v11);//???? Base64 编码拼接后的字符串
v20 = (*a1)->NewStringUTF(a1, v19);
free(v19);
return v20;

通过上面分析我们得到个流程:

1
2
3
4
5
6
7
8
1. 构造 salt 前缀:"and_1.21:"
2. 构造签名原文:salt_key + v8 + v7
"zG#V&A2*YO7pUmHsAo55j#1mOC@jAtYi" + v8 + v7
3. 计算 SHA-1 哈希(20 字节)
4. 转为 40 字符小写十六进制字符串
5. 拼接:"and_1.21:" + hex_str
6. Base64 编码该字符串
7. 返回 Base64 编码后的字符串

那么现在我们去用py写下代码看下结果

我们发现一模一样 下面是sign部分的完整代码

1
2
3
4
5
6
7
8
9
10
11
12
def sign(v7: str, v8: str) -> str:
    salt_key = "zG#V&A2*YO7pUmHsAo55j#1mOC@jAtYi"
    message = salt_key + v8 + v7
    sha1_hash = hashlib.sha1(message.encode('utf-8')).digest()
    hex_str = sha1_hash.hex()
    to_encode = "and_1.21:" + hex_str
    b64_str = base64.b64encode(to_encode.encode('utf-8')).decode('utf-8')
    return b64_str
key_hex = 'password=c64XgGuLwqmvq9RoQtHYOw==1754629486921symbol=8az503s/zohmcg0f7vXXOw==1754629486905'
plaintext_hex = '1754629486923'
ciphertext = sign(key_hex, plaintext_hex)
print("加密结果:", ciphertext)

算法还原

这里我就不再详细写Python 怎么一步一步复现了,直接附上写好的py代码

现在再回头看整个算法 难么? 不难 !

末尾

后续 也会推出一系列的文章,包括但不限于 魔改算法,VMP系列,小众算法

欢迎加V: ass7752321 会给大家拉一个交流群 互相交流一下思路


传播安全知识、拓宽行业人脉——看雪讲师团队等你加入!

收藏
免费 1
支持
分享
最新回复 (3)
雪    币: 1506
活跃值: (3878)
能力值: ( LV4,RANK:40 )
在线值:
发帖
回帖
粉丝
2
可以的老铁 ,再发几篇
2025-8-13 18:55
0
雪    币: 0
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
3
样本so看看
2025-8-26 20:14
0
雪    币: 973
活跃值: (2111)
能力值: ( LV3,RANK:30 )
在线值:
发帖
回帖
粉丝
4
对方账号因涉及违....
2025-9-22 00:53
0
游客
登录 | 注册 方可回帖
返回