U.r[2] 字符串生成算法逐步运算

图:算法输入 PAYLOAD 的部分十六进制转储。
本文是 yb-u-r-results-algorithm.md 的运算展开版。前一文保存完整输入字节、RSA 模数、捕获的 221 字节 PS 及主要 trace 行号;本文按数据流展示具体操作次序、使用的公式和本次可手算核对的中间值。trace 没有给出的上游业务含义仍不作推断。
所有数组下标从 0 开始。|| 表示字节拼接,XOR 表示按位异或,LE32 / BE32 表示 32 位小端 / 大端编码。数学上的模 2^32 加法按 32 位无符号数回绕。
0. 数据依赖
ASCII("1")、ASCII(key)、ASCII(params)、ASCII(time)、ASCII(nonce)
-> S -> MD5 -> H
-> 第二次 clock_gettime -> u -> ChaCha20 -> CHACHA16
-> F、P、K、M -> MD5(P||K) -> D -> X -> R
-> 26-byte body || 8 个 00 -> 小写 hex -> sign
PAYLOAD -> pad -> 内层 AES-CBC -> 5-byte frame header
-> pad -> 外层 AES-CBC(A32[0:16], A32[16:32])
-> Base64 -> params
PS、A32、RSA 公钥(n,e) -> EM -> RSA_C -> Base64 -> key
fd 3 nonce 输入的前 8 字节 -> 小写 hex -> nonce
第一次 clock_gettime -> 十进制毫秒 ASCII -> time
ASCII("1")、appKey -> JSON 文本和转义 -> crypt、extra
1. crypt 与 extra
1.1 crypt
- 顶层
crypt片段由memmove从0xe4ffdac1复制 13 字节;copy 参数和结果见trace29.log:8130328-8130359。其中 JSON 冒号和引号之间的值字节是31,即 ASCII1。8130000-8130029的X6=0x31参数属于extra的内层对象构造,不是这里的顶层片段。 -
把捕获到的 13 字节按固定前缀、单字节值、后缀拆开:
7b 22 63 72 79 70 74 22 3a 22 || 31 || 22 2c { " c r y p t " : " 1 " , - 结果长
10+1+2=13字节,即{"crypt":"1",。缓冲区转储和长度见8130328-8130359,随后它被并入外层对象,见8155999-8156008。
1.2 extra
- 内层构造调用把
X6=0x31作为 ASCIIcrypt值,并从0x1220e000指向的数据取 appKey;调用参数见trace29.log:8130000-8130029。appKey 的 16 字节0AND05U9F09CE1CW及复制见411345-411352。 -
把 appKey 与 ASCII
1放进已捕获的紧凑文本:E = {"appKey":"0AND05U9F09CE1CW","crypt":"1"}E 长 41 字节。
0x31参数、appKey 输入和紧凑文本相关构造见8130000-8130060。 -
作为外层 JSON 的字符串值时,对 E 的每一个双引号前插入一个反斜杠。E 中有 8 个双引号,所以转义内容长度为
41+8=49字节:{\"appKey\":\"0AND05U9F09CE1CW\",\"crypt\":\"1\"}49 字节内容转储见
8130040-8130057。 - 外层字段依次追加 9 字节
"extra":"、上述 49 字节和 1 字节结束引号,总计9+49+1=59字节。完整片段见8130347-8130359。JSON 解析后去掉的只是转义层,extra字符串值仍为 41 字节的 E。
2. key
2.1 组装 RSA 编码消息
-
RSA 后缀
A32为 32 字节:A32[0:16] = 03 00 00 f3 重复 4 次 A32[16:32] = 04 00 00 f4 重复 4 次两个 16 字节区段的暂存和转储见
trace29.log:4039997-4040002、4040044-4040049;复制进填充缓冲区见4161069-4161079。相同 A32 稍后被用作外层 AES 的 key 与 IV。 - 取 270 字节 DER 公钥,解析出 256 字节模数
n和指数e=65537=0x10001;DER 与寄存器解析见4088824-4088825、4152370-4152414。完整n字节见yb-u-r-results-algorithm.md的key小节。 - 填充缓冲区起始两个字节写
00 02。随后依次调用rand(),令候选字节b = return_value & 0xff:若b=00,丢弃并重试;否则把 b 写入 PS 并将写指针加一。重复至 221 个非零字节。分支和写入见4157735-4157768、4157790-4161058。本次完整 PS 列表见前一文的key小节,最后一个字节为0d。 -
在 PS 后写一个分隔字节
00,再复制 A32。布局及长度为:EM = 00 02 || PS[221] || 00 || A32[32] len(EM) = 2 + 221 + 1 + 32 = 256分隔零位于缓冲区索引
0xdf,A32 从索引0xe0开始,见4161068-4161079。
2.2 公钥幂模与 256 字节结果
-
将 EM 按大端解释成整数
m=OS2IP(EM)。运算为c=m^e mod n。由于e=65537=2^16+1,一种等价的重复平方过程是:q0 = m mod n q1 = q0*q0 mod n q2 = q1*q1 mod n ... q16 = q15*q15 mod n c = q16*m mod n每一步都在模 n 下约简。trace 中的大整数运算区间为
4498101-4498150、7100000-7183490。 -
把 c 用大端补零到 256 字节:
RSA_C=I2OSP(c,256)。trace 中大整数输出的内部字节序先做反序搬移,形成该 256 字节大端序列后再送入 Base64;搬移见7183460-7183490、7186300-7186490。
2.3 Base64 转为字符串
-
每 3 字节
b0,b1,b2组成 24 位数,切成 4 个 6 位索引:i0 = b0 >> 2 i1 = ((b0 & 0x03) << 4) | (b1 >> 4) i2 = ((b1 & 0x0f) << 2) | (b2 >> 6) i3 = b2 & 0x3f -
本次首组三字节是
53 3e 4e:i0 = 0x53 >> 2 = 0x14 i1 = ((0x53 & 0x03) << 4) | (0x3e >> 4) = 0x33 i2 = ((0x3e & 0x0f) << 2) | (0x4e >> 6) = 0x39 i3 = 0x4e & 0x3f = 0x0e以上为十六进制索引
0x14,0x33,0x39,0x0e,分别查 Base64 字符表得到U,z,5,O。编码器读写和表访问见7186723、7186790、7186859、7186925;输出以Uz5O开始,见7214231-7214244。 -
对 256 字节输入,
256=3*85+1。最后一组只有 1 个数据字节,编码器输出两个有效字符并补==,正文 342 字符,总长 344 字符。长度及==写入见7214231-7214289;完整字段值见8053222-8053229。
3. nonce
- fd 3 把 32 字节读入
0x125643a0;调用和返回长度见trace29.log:8051612-1619。这次算法只使用缓冲区前 8 字节。 -
首字节 load 取得
read[0]=05。接着两个未对齐 32 位 load 取得:read[1:5] = 00 00 f5 05 read[4:8] = 05 00 00 f5读取地址和寄存器值见
8051725-1733。 -
写入暂存区时,单字节
05放在有效数据偏移 0,第一组 4 字节放在偏移 1 至 4,第二组放在偏移 4 至 7。两组在偏移 4 重叠,值都是05,故最终八字节仍为:05 || 00 00 f5 05 || 00 00 f5 = 05 00 00 f5 05 00 00 f5load/store 和暂存缓冲区转储见
8051745-1774。 - 对每个输入字节 b,先查
hex_table[b >> 4],再查hex_table[b & 0x0f]。例如0x05 -> '0' || '5',0xf5 -> 'f' || '5'。八个字节得到小写 ASCII050000f5050000f5,转换表和缓冲区见8054185-4207。该 16 字节文本被复制为nonce字段,见8157235-7237。
4. params
4.1 内层 AES-128-CBC
- 输入
PAYLOAD是 trace 中完整捕获的 2504 字节;MD5 为c380a76de14498edb0806eb394c4ca06,完整转储见trace29.log:3474891-3475050。算法把捕获字节直接作为明文,不先改变其中的 ASCII 或二进制字段。 - 内层 AES key 是
ff802003f67f55be8b535a97b01b459d,IV 是b8221969c27a8019d533b139ade71bcc。查找参数和实际字节见3474100-3474605、3475647-3476576。 -
PKCS#7 以 16 为块长。计算
p=16-(2504 mod 16)=8,于是末尾补 8 个08:INNER_PLAIN = PAYLOAD || 08 08 08 08 08 08 08 08 len(INNER_PLAIN) = 2512 = 157*16填充字节的写入见
3477250、3477390-3477419。 -
AES-128 把 16 字节 key 切成四个 32 位字
w[0..3],再扩展为w[0..43],每四个字组成一个 16 字节轮密钥,共 11 个。对i=4..43:temp = w[i-1] if i mod 4 == 0: temp = SubWord(RotWord(temp)) XOR Rcon[i/4] w[i] = w[i-4] XOR tempRotWord循环左移一个 byte,SubWord对四个 byte 分别查 AES S-box。trace 中的 key 输入和扩展循环见3475647-3476552。 - CBC 先做明文块与 IV 的异或,再送入 AES:
C1=AES_K(P1 XOR IV);后续块为Ci=AES_K(Pi XOR C(i-1))。每个 AES-128 block 的轮步骤是:初始 AddRoundKey;第 1 至 9 轮依次 SubBytes、ShiftRows、MixColumns、AddRoundKey;第 10 轮执行 SubBytes、ShiftRows、AddRoundKey,不执行 MixColumns。ShiftRows 将状态的第 0、1、2、3 行分别循环左移 0、1、2、3 字节;MixColumns 对每列在 GF(2^8) 中乘以 AES 矩阵[02 03 01 01; 01 02 03 01; 01 01 02 03; 03 01 01 02],域多项式为0x11b。trace 中首块 CBC 异或、key expansion 和后续块链式异或见3477507-3477511、3475647-3476552、3481057-3481069。 -
首块可以直接复算。PAYLOAD 前 16 字节和 IV 分别为:
P1 = 0a 79 08 01 12 1d 31 37 39 30 34 33 38 36 35 39 IV = b8 22 19 69 c2 7a 80 19 d5 33 b1 39 ad e7 1b cc P1 XOR IV = b2 5b 11 68 d0 67 b1 2e ec 03 85 0a 95 d1 2e f5 C1 = AES_K(P1 XOR IV) = 98 26 a6 b8 ca b8 ed 3a 4f ab 85 18 f7 ce fa f3第一字节
0a XOR b8 = b2的 trace 证据见3477507-7511;整块输出见4036840-6848。
4.2 帧封装、外层 AES-128-CBC
-
将 5 字节头
03 00 00 00 01前置于 2512 字节内层密文:FRAME = 03 00 00 00 01 || INNER_CIPHER len(FRAME) = 5 + 2512 = 2517头部写入见
4036827-6832,合并长度见4036840-6848、4037015-7018。 - 外层 key 取
A32[0:16],即03 00 00 f3重复四次;外层 IV 取A32[16:32],即04 00 00 f4重复四次。A32 的字节见4161069-1079;key schedule 和 IV 载入见7216359-6382、7217283-7289。 -
2517 mod 16=5,所以 PKCS#7 补16-5=11个0b:OUTER_PLAIN = FRAME || 0b 0b 0b 0b 0b 0b 0b 0b 0b 0b 0b len(OUTER_PLAIN) = 2517 + 11 = 2528 = 158*16余数及填充值计算见
7217283-7292,填充写入见7217351-7432、7217744-8401。 -
外层第一 CBC 块与 IV 异或。它由帧头和内层密文首 11 字节组成:
OUTER_PLAIN[0:16] = 03 00 00 00 01 98 26 a6 b8 ca b8 ed 3a 4f ab 85 OUTER_IV[0:16] = 04 00 00 f4 04 00 00 f4 04 00 00 f4 04 00 00 f4 XOR = 07 00 00 f4 05 98 26 52 bc ca b8 19 3e 4f ab 71对该结果执行外层 AES-128,得到首密文块
c6 6b 0a f2 fe 63 87 7d 7f e3 c0 d3 93 66 79 0a,见7781193-1195、7782326-2332。之后各块使用前一密文块,见7222007-2023。
4.3 Base64
- Base64 输入是 2528 字节
OUTER_CIPHER。按每 3 字节切组,分别用key小节列出的 4 个 6 位索引公式查 64 字符表。 -
首组
c6 6b 0a的索引逐项为:c6 >> 2 = 31 ((c6 & 03) << 4) | (6b >> 4) = 26 ((6b & 0f) << 2) | (0a >> 6) = 2c 0a & 3f = 0a索引
31,26,2c,0a查表得到xmsK,与params首四个字符相同。读入密文字节和编码写入见7782993-7783231。 2528=3*842+2,最后组只有两个输入字节,因此最后输出三个字符和一个=。总字符数为4*ceil(2528/3)=3372。末尾填充和完整 Base64 文本见7782331-2333、8053454-3461、8053671。
5. time
- 第一次
clock_gettime(0)返回tv_sec=0x6ab7ed14=1790438676和tv_nsec=0x23957f40=597000000,调用与读数见trace29.log:8052788-2812。 -
先把纳秒截断为微秒:
tv_nsec / 1000 = 597000 微秒 U = tv_sec*1,000,000 + 597000 = 1,790,438,676,597,000 微秒trace 里的中间寄存器值为
0x65c64fd4f8908,运算见8052795-2810。 - 再将微秒总数截断除以 1000 得毫秒:
floor(U/1000)=1790438676597。等价地,tv_sec*1000 + floor(tv_nsec/1,000,000)得到同一值。该整数被转成 13 个十进制 ASCII 字节,调用参数和输出见8052850-2931、8054091-4093。
6. sign
6.1 首次 MD5 输入与 H
-
在字段缓冲区按顺序拷贝原始 ASCII,不先 Base64 解码,也不做 JSON 转义:
S = ASCII("1") || ASCII(key) || ASCII(params) || ASCII(time) || ASCII(nonce) - 字节数依次为
1,344,3372,13,16,故len(S)=3746。首部、各字段复制和 MD5 标记见trace29.log:8054285-4290、8053222-3229、8053454-3461、8054091-4093、8054200-4207。首字节是31;末尾 ASCII 是1790438676597,接 nonce050000f5050000f5,见8054287-4289、8054519-4521。 - MD5 预处理把消息 bit 长度作为小端 64 位数追加。此处
3746*8=29968=0x7510;3746 mod 64=34,先追加80,再追加 21 个00,最后追加LE64(29968)=10 75 00 00 00 00 00 00,得到 59 个 64 字节块。 -
每块按 16 个小端 32 位字
W[0..15]读取;MD5 状态初始化为A=0x67452301, B=0xefcdab89, C=0x98badcfe, D=0x10325476。每块执行 64 步,按轮号选择函数与字索引:0..15: f=(B&C)|(~B&D) g=i 16..31: f=(D&B)|(~D&C) g=(5*i+1) mod 16 32..47: f=B XOR C XOR D g=(3*i+5) mod 16 48..63: f=C XOR (B|~D) g=(7*i) mod 16 temp = (A + f + AC[i] + W[g]) mod 2^32 (A,B,C,D) = (D, (B + ROTL32(temp,s[i])) mod 2^32, B, C)AC[i]=floor(2^32*abs(sin(i+1))),移位表依次为[7,12,17,22]重复 4 次、[5,9,14,20]重复 4 次、[4,11,16,23]重复 4 次、[6,10,15,21]重复 4 次。64 步后把工作状态加回块入口状态;所有 32 位加法均模 2^32。最终 A、B、C、D 各按小端输出为 16 字节 H。trace 对本次 3746 字节输入记录的 MD5 为H=c04762cdd7f0992a8725c1ecc2399bcd,见8054285。
6.2 第二次时钟与 ChaCha20
- 第二次时钟样本给出
tv_nsec=0x240f9140=605000000。取本秒微秒部分u=floor(tv_nsec/1000) mod 1,000,000=605000=0x00093b48,见8054834-4856、8097900-7954。 -
初始化 ChaCha20 64 字节状态,按 little-endian 32 位 word 顺序:
constants = ASCII("expand 32-byte k") key = LE32(u) || 00*28 = 48 3b 09 00 || 00*28 counter = 00*8 nonce = a3 a2 44 ef 7f e5 a4 65常量、key、counter、nonce 的装载见
8097930-7935、8099250-9350。nonce 按两个 LE32 word 读入,即ef44a2a3、65a4e57f。 -
每个 ChaCha quarter-round 对四个 32 位 word
(a,b,c,d)依序做:a = a+b; d = ROTL32(d XOR a,16) c = c+d; b = ROTL32(b XOR c,12) a = a+b; d = ROTL32(d XOR a, 8) c = c+d; b = ROTL32(b XOR c, 7)一轮依次处理列 quarter-round 和对角线 quarter-round;共 10 个 double-round,即 20 轮。指令中的 add、xor 和 rotate 见
8099500-8100430。最后把轮后状态与初始状态逐 word 相加,并以 little-endian 写回 64 字节块。 -
本次 ChaCha 块前 16 字节为
CHACHA16=a60ac71c5c9da46356884f470f4de6b0。逐字节与 H 异或:H = c0 47 62 cd d7 f0 99 2a 87 25 c1 ec c2 39 9b cd CHACHA16 = a6 0a c7 1c 5c 9d a4 63 56 88 4f 47 0f 4d e6 b0 F = 66 4d a5 d1 8b 6d 3d 49 d1 ad 8e ab cd 74 7d 7d向量异或指令和 F 的写入见
8100410-8100450。
6.3 生成 P、K、M 和第二次 MD5
-
32 字节常量 raw 为
36474030333e36313b4d4c4f3e3e3f5227502a512d26212d212f5c5e282a2810,见8100524-0617。对i=0..31逐字节计算:ascii[i] = raw[i] XOR (i+1) XOR 0x21 XOR 0x20例如:
i=0: 0x36 XOR 0x01 XOR 0x21 XOR 0x20 = 0x36 -> ASCII '6' i=1: 0x47 XOR 0x02 XOR 0x21 XOR 0x20 = 0x44 -> ASCII 'D' i=2: 0x40 XOR 0x03 XOR 0x21 XOR 0x20 = 0x42 -> ASCII 'B'全部结果是 ASCII
6DB579083FFB211C7C8D917494FC4561。每两字符作为一个 hex byte 解码,得到P=6db579083ffb211c7c8d917494fc4561。循环、两字符解析及 P 写入见8100631-8101431、8101439-8101446、8101608-8103934。 -
K 从模板
28 00 47 2b 00 2c 4c 4b开始,用F[5]=6d覆盖偏移 1,用H[4]=d7覆盖偏移 4:K = 28 || 6d || 47 2b || d7 || 2c 4c 4b = 28 6d 47 2b d7 2c 4c 4b读值和写入见
8100505-0514、8103780-3947。 -
M 按 16 字节位置拼装:
M = H[0:4] || 00*4 || F[1:5] || LE32(0x12345678) = c0 47 62 cd || 00 00 00 00 || 4d a5 d1 8b || 78 56 34 12 = c04762cd000000004da5d18b78563412来源切片和常量写入见
8104220-4259。 -
第二次 MD5 输入是 16 字节 P 后接 8 字节 K:
P||K = 6d b5 79 08 3f fb 21 1c 7c 8d 91 74 94 fc 45 61 28 6d 47 2b d7 2c 4c 4b len(P||K)=24 bytes; bit_length=192=0xc0MD5 预处理为
P||K || 80 || 00*31 || LE64(192),其中LE64(192)=c0 00 00 00 00 00 00 00,恰好构成一个 64 字节块。24 字节拼接及 MD5 长度见8104001-4043;digest 为D=72c8328262f0171c0a2971766aa59388,见8105152-5170。
6.4 两层异或和最终 hex
-
将八字节 K 循环两次,与 D 异或:
D = 72 c8 32 82 62 f0 17 1c 0a 29 71 76 6a a5 93 88 K repeated = 28 6d 47 2b d7 2c 4c 4b 28 6d 47 2b d7 2c 4c 4b X = D XOR K = 5a a5 75 a9 b5 dc 5b 57 22 44 36 5d bd 89 df c3i mod 8索引循环及异或指令见8105152-5550。 -
X 与 M 逐字节异或:
X = 5a a5 75 a9 b5 dc 5b 57 22 44 36 5d bd 89 df c3 M = c0 47 62 cd 00 00 00 00 4d a5 d1 8b 78 56 34 12 R = X XOR M = 9a e2 17 64 b5 dc 5b 57 6f e1 e7 d6 c5 df eb d1 - 26 字节正文由以下部分按次序组成:
LE32(0x1085434c)=4c438510、H[4]=d7、F[5]=6d、BE32(u)=00093b48、16 字节 R。总长4+1+1+4+16=26。缓冲区各部分写入见8105360-5552。 -
正文后追加 8 个零字节,成为 34 字节。每字节拆为高、低半字节,分别查小写 hex 表,因此 34 字节生成 68 个 ASCII 字符。补零、hex 转换循环见
8105559-5564、8105652-8110048;最终文本为:4c438510d76d00093b489ae21764b5dc5b576fe1e7d6c5dfebd10000000000000000
7. JSON 序列化
- 字段按
crypt, extra, key, nonce, params, sign, time顺序写入对象,相关拼接片段见8155999-8156017、8157235-7237、8401235-1481、8401491-1498、8402458-2462。 extra是一个 JSON 文本字符串,所以内层 8 个引号各用\"表示;解析外层 JSON 时反斜杠被解码掉,字符串内容回到 41 字节 E。key和params的 Base64 中有/。最终 JSON 文本把它们输出为\/,JSON 解析后仍是/。这类 JSON 转义发生在字符串算法之后,不参加 RSA、AES 或 MD5 输入。字段片段和转义后的请求 JSON 见8156017-6024、8398846、8398861、8401265。
8. 复核边界
完整 PAYLOAD、RSA 模数和 PS 原始记录、各最终字段文本及逐字段长度见 yb-u-r-results-algorithm.md。本文展开算法转换与可手算的中间块;对 trace 未标明的路径字符串、随机数 seed、PAYLOAD 字段 schema 和业务语义不作推断。
Trace 文件搜索工具:本文分析使用的工具是 https://github.com/AndroidReverser-Test/trace_search。
评论