云计算百科
云计算领域专业知识百科平台

QQ QIMEI参数分析:从历史版本追溯加密方案

本文仅用于技术学习与安全研究交流,文中涉及的分析方法、还原代码及相关数据均基于公开可获取的APK样本逆向所得,不涉及未授权访问服务器或非法获取用户数据。请勿将本文内容用于任何侵犯用户隐私、破坏系统安全或其他违反法律法规的用途,由此产生的一切后果与作者无关。如相关方认为本文内容存在不当之处,请联系作者,将第一时间配合处理或下架。

1. 前言/缘起

最近抽空看了下QIMEI SDK的最新版本,本来是想快速过一遍有没有新变化,结果打开IDA之后就傻眼了——核心函数经过了混淆处理,体积异常庞大,反编译出来的伪代码看着密密麻麻一大坨,IDA分析的时候明显能感觉到卡顿,跳转、查看交叉引用这些基本操作都变得很迟钝。

硬啃肯定是不现实的,与其在这个混淆过的"庞然大物"里死磕,不如换个思路:既然是同一套SDK,早期版本很可能还没上这么重的混淆,核心逻辑说不定更清晰、更容易看懂。于是决定往回翻,找找这个参数最早是从哪个版本开始出现的,先把最原始的实现逻辑看懂——一方面看看这套算法能不能复用,另一方面也想借助分析老版本积累的经验,反过来帮助确定最新版本到底采集了哪些参数。

2. 版本筛选:为什么选9.0.20

确定了往老版本回溯的思路后,接下来的问题是:从哪个版本开始翻起,翻到哪个版本为止。

因为这个参数的特点是"一同意隐私协议就触发采集",行为比较明显、好判断,所以找最早出现这个参数的版本反而不算难——只要装上、点同意、抓包看有没有对应请求就行,不需要逐行看代码去确认。

具体操作是去豌豆荚这类应用商店找历史版本安装包,也可以找那种保存了近几年QQ所有历史版本的渠道/频道,把版本包收集齐。然后按大版本往前翻,先定位到9.0.x这个大版本区间,再往前几个版本装上试——发现再往前就没有这个参数了,这样就能确定是9.0.x里的某个具体版本刚加上的这个采集逻辑。

但翻这几个版本的过程中发现,最早加上这个参数的那几个版本,开发上好像还没做完善:App刚装上、一点进去,还没走到用户点同意隐私协议这一步,请求就已经发出去了。这种行为不符合正常预期(正常应该是要等同意之后才采集),拿来做分析起点不太合适。

继续往后翻,找到9.0.20——这个版本的行为就正常了:装上之后不会立刻发请求,需要点击同意隐私协议后才会触发。而且这个版本无论是spawn方式启动hook,还是先attach上再手动点同意,都能正常复现请求,调试起来很方便。所以最终选定9.0.20作为分析的起点。

3. 抓包分析:请求长什么样

版本定下来之后,先不急着扒so,第一步是把外部行为和代码入口对上——目的很明确,就是要逆这个采集参数(qimei)。

既然目标明确,直接从包名带qimei的类找起,很快在com.tencent.qimei.*包下面翻到一个URL:snowflake.qq.com。抓包核对了一下,确实能看到请求打到了这个域名,说明找的方向是对的。

但静态看这个URL常量的交叉引用,却没找到有代码在直接调用它——说明构造/发起请求这块逻辑不是简单的Java层字符串拼接调用,可能是走了反射,或者干脆下沉到了native层。这种情况下,静态分析很容易卡在这一步。

于是换成动态手段,用Frida hook相关方法,在请求发出的时机打印调用栈(stack trace)。栈回溯出来之后,能看到这个请求实际上是从com.tencent.qimei.uin.U.r这个方法触发的——而这个方法本身就是一个native方法,对应到so里的某个函数。

这样一来,方向就清楚了:Java层能做的静态分析已经到头了,接下来要真正定位参数是怎么生成的,就得进so里面去看了。

4. Native层入手:hook分析so

定位到com.tencent.qimei.uin.U.r是个native方法之后,下一步就是进到so里面去看这个函数到底干了什么。

先没有直接上IDA啃伪代码,而是先用Frida在Java层这个native方法上挂了个hook,观察它在一次完整流程(打开App到点同意)里到底被调用了几次、每次调用时的参数长什么样,心里先有个数。

进入so静态看的时候,发现既没有init函数,也没有init_array,那就直接从JNI_OnLoad看起。在RegisterNatives调用之前,有一个函数会执行一次很大内容的memcpy操作,memcpy完之后把这段内存的地址存进了一个全局变量。顺着这个全局变量做交叉引用,找到了使用它的函数,结合调用点的上下文看下来,能确定这就是一个字符串解密函数——那一大段memcpy进来的内容,就是加密后的字符串数据。

这个解密函数调用的时候需要传入两个数字参数,静态去推这两个数字对应哪个字符串、解出来是什么,搞起来比较麻烦。于是换成动态方式,写了个Frida脚本挂在这个解密函数的返回点上,把so运行过程中所有被解密出来的字符串都批量打印出来。

打印出来的这批字符串里,一眼能看出一堆文件路径,还有一些暂时看不出用途的内容。接下来就是顺着这些字符串做交叉引用,看它们分别被传到了哪些函数里使用,通过使用场景来反推这些字符串实际的用途,为后面深入分析加密逻辑做铺垫。

5. 定位加密逻辑

5.1 Java层追踪受阻

字符串这条线索理清楚之后,下一步自然是要顺藤摸瓜找到加密逻辑在哪——毕竟最终发出去的请求参数,肯定是经过加密处理的。

一开始还是想走Java层这条路,希望能像前面找请求发起点那样,顺着调用链一路跟到加密的地方。但这次没那么顺利:跟下去发现,从Java层能看到的调用链到某一步就断了,再往下已经看不到明显的加密相关调用了——逻辑大概率是整个下沉到native层里完成的,Java层这边只是简单传了个参数进去,具体怎么处理完全看不出来。

这样一来,单纯从Java层往下跟这条路走不通了,得换个思路,直接从native层入手去找加密逻辑的位置。

5.2 通过特征常量反推加密位置

Java层这条路走不通之后,思路换成了"结果导向":既然不知道加密逻辑具体在哪个函数里,那就不用管调用链,直接在so里找加密算法本身留下的痕迹。

常见的加密算法在实现的时候,通常会用到一些固定的特征常量,比如AES的S-box替换表这类初始化数据,这些常量在二进制文件里基本是不变的,可以直接搜。于是用了IDA的findcrypt插件对so扫了一遍,搜出来命中了MD5和AES相关的常量。

先顺着AES的常量往回找引用,找到一个sub_F200函数,里面有一处在判断某个入参的长度是否等于16。结合AES常见的key/iv长度就是16字节这一点,合理怀疑这里就是在校验传进来的key和iv的长度是否合法——如果这个判断成立,那这个函数附近大概率就是AES加密真正被调用、处理key/iv的地方。

这样一来,加密逻辑的位置基本锁定了,但具体是不是对应抓包看到的那个请求参数,还需要进一步动态验证。

5.3 hook验证,比对抓包结果

静态分析定位到疑似AES加密调用的位置之后,只能算是"猜测",还不能百分百确定这里就是最终请求参数的加密点。要验证这个猜测,最直接的办法就是动态跑起来,看实际输出的密文是不是和抓包抓到的请求内容对得上。

于是在这个疑似的AES函数上挂了Frida hook,先打印了一下入参内容。看了下发现这个函数其实是加解密复用的——是加密还是解密,是由入参里的某个标志位来控制的,并不是两个独立的函数。确认了这一点之后,把hook脚本完善了一下,把key、iv,以及加解密前后的内容都打印出来,方便后续对照。

跑起来之后发现一个细节:这个AES函数在一次流程里被调用了两次,也就是说请求参数实际上是经过两次AES加密得到的。通过对比两次调用前后的数据变化可以看出:第一次加密的时候,入参还是能看出明显结构的明文;第一次加密的结果出来之后,会在前面拼接上一段固定内容03000001,拼接之后再送进同一个函数做第二次加密,这才是最终发出去的密文。

另外多跑了几次流程反复测试对比,还发现一个规律:这两次加密用到的key和iv并不是完全独立随机的——只有第二次加密的key和iv是每次都会变化的,第一次加密的key和iv则是固定不变的。而且这个固定的key和iv,直接拿去前面打印出来的解密字符串列表里搜索,就能搜到——说明第一次加密用的key和iv本身就是硬编码在so里、以加密字符串的形式存在的,运行时解密出来直接用。

把最终第二次加密的结果做base64编码,和抓包里的请求参数做对比,是能对上的。这样就确认了:前面定位到的这个函数确实是标准的AES加密实现,而且是套了两层,第一层key/iv固定、第二层key/iv每次变化。

接下来要搞清楚的就是两块内容:第一次加密的明文到底是什么、怎么来的;以及第二次加密用到的、每次都会变化的key和iv是怎么生成的。

5.4 反向追溯明文来源

加密点确认之后,第一件要搞清楚的事就是:第一次AES加密的明文到底是什么结构,又是怎么拼出来的。

PB结构与文件信息

先看明文本身的特征。打印出来的明文数据开头是0a,这个特征在protobuf序列化数据里很常见——protobuf的字段标签(tag)由字段号和wire type组成,很多常见结构第一个字段编码出来就正好是0a开头,所以第一反应就是"这大概率是个PB结构"。直接丢进CyberChef,用protobuf解码功能反序列化了一下,果然能解出结构化的字段内容,内容整体看起来比较常规,字段类型和数值都还算规整,验证了这个猜测。

[>>>] 进入 sub_F200
arg0: 0x1
arg1 (Hexdump – 16 bytes): @ 0x6e75f81070
0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF
6e75f81070 ff 80 20 03 f6 7f 55 be 8b 53 5a 97 b0 1b 45 9d .. …U..SZ…E.
arg2 (Hexdump – 16 bytes): @ 0x6e75f81080
0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF
6e75f81080 b8 22 19 69 c2 7a 80 19 d5 33 b1 39 ad e7 1b cc .".i.z…3.9….
arg4 (Hexdump – Length: 2188): @ 0x6f05f81910
0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF
6f05f81910 0a 73 08 01 12 10 42 30 45 39 46 34 45 35 37 34 .s….B0E9F4E574
6f05f81920 43 43 46 32 36 39 22 10 30 53 32 30 30 4d 4e 4a CCF269".0S200MNJ
6f05f81930 54 38 30 37 56 33 47 45 2a 0b 32 2e 31 2e 32 2e T807V3GE*.2.1.2.
6f05f81940 31 30 33 2e 34 32 0b 39 2e 30 2e 32 30 2e 35 38 103.42.9.0.20.58
6f05f81950 34 34 3a 13 41 6e 64 72 6f 69 64 20 31 32 2c 6c 44:.Android 12,l
6f05f81960 65 76 65 6c 20 33 31 5a 14 63 6f 6d 2e 74 65 6e evel 31Z.com.ten
6f05f81970 63 65 6e 74 2e 6d 6f 62 69 6c 65 71 71 62 04 77 cent.mobileqqb.w
6f05f81980 69 66 69 68 03 12 94 10 0a 04 32 30 30 38 12 02 ifih……2008..
6f05f81990 33 31 1a 02 7b 7d 22 06 67 6f 6f 67 6c 65 2a c4 31..{}".google*.
6f05f819a0 0b 6b 31 3a 32 30 32 36 2d 30 33 2d 32 30 32 32 .k1:2026-03-2022
6f05f819b0 31 35 33 38 2e 36 38 31 30 30 30 30 32 33 2b 30 1538.681000023+0
6f05f819c0 38 30 30 3b 6b 32 3a 32 30 32 36 2d 30 33 2d 32 800;k2:2026-03-2
6f05f819d0 30 32 32 31 36 30 35 2e 33 39 37 36 36 39 33 35 0221605.39766935
6f05f819e0 32 2b 30 38 30 30 3b 6b 33 3a 30 30 30 30 30 30 2+0800;k3:000000
6f05f819f0 30 30 30 30 30 30 30 30 30 30 3b 6b 34 3a 64 62 0000000000;k4:db
6f05f81a00 38 65 66 62 64 39 62 39 63 32 63 35 38 61 3b 6b 8efbd9b9c2c58a;k
6f05f81a10 35 3a 31 33 35 32 32 34 32 37 3b 6b 36 3a 31 33 5:13522427;k6:13
6f05f81a20 31 39 35 37 37 36 3b 6b 37 3a 32 31 33 35 38 38 195776;k7:213588
6f05f81a30 3b 6b 38 3a 32 38 30 30 3b 6b 39 3a 63 35 65 36 ;k8:2800;k9:c5e6
6f05f81a40 39 38 37 31 2d 35 39 61 31 2d 34 63 66 33 2d 39 9871-59a1-4cf3-9
6f05f81a50 61 65 64 2d 65 34 35 38 66 38 32 37 30 61 30 31 aed-e458f8270a01
6f05f81a60 3b 6b 31 31 3a 31 37 31 3b 6b 31 32 3a 34 44 65 ;k11:171;k12:4De
6f05f81a70 76 69 63 65 74 79 70 65 30 2c 30 3b 6b 31 33 3a vicetype0,0;k13:
6f05f81a80 32 30 32 36 2d 30 33 2d 32 30 32 32 31 36 30 35 2026-03-20221605
6f05f81a90 2e 33 39 37 36 36 39 33 35 32 2b 30 38 30 30 3b .397669352+0800;
6f05f81aa0 6b 31 34 3a 32 30 32 36 2d 30 34 2d 32 38 31 34 k14:2026-04-2814
6f05f81ab0 30 31 31 33 2e 37 30 33 30 31 30 34 34 34 2b 30 0113.703010444+0
6f05f81ac0 38 30 30 3b 6b 31 35 3a 32 38 33 31 3b 6b 31 36 800;k15:2831;k16
6f05f81ad0 3a 31 38 44 65 76 69 63 65 74 79 70 65 30 2c 30 :18Devicetype0,0
6f05f81ae0 3b 6b 31 37 3a 32 30 32 36 2d 30 34 2d 33 30 31 ;k17:2026-04-301
6f05f81af0 31 32 38 34 39 2e 33 36 37 36 30 30 37 39 34 2b 12849.367600794+
6f05f81b00 30 38 30 30 3b 6b 31 38 3a 32 30 32 36 2d 30 34 0800;k18:2026-04
6f05f81b10 2d 33 30 31 31 32 38 34 39 2e 33 37 30 39 33 34 -30112849.370934
6f05f81b20 31 32 37 2b 30 38 30 30 3b 6b 31 39 3a 38 30 37 127+0800;k19:807
6f05f81b30 36 39 31 3b 6b 32 30 3a 34 44 65 76 69 63 65 74 691;k20:4Devicet
6f05f81b40 79 70 65 30 2c 30 3b 6b 32 31 3a 32 30 32 36 2d ype0,0;k21:2026-
6f05f81b50 30 34 2d 33 30 31 31 32 38 34 39 2e 33 37 30 39 04-30112849.3709
6f05f81b60 33 34 31 32 37 2b 30 38 30 30 3b 6b 32 32 3a 32 34127+0800;k22:2
6f05f81b70 30 32 36 2d 30 34 2d 33 30 31 31 32 38 34 39 2e 026-04-30112849.
6f05f81b80 33 36 37 36 30 30 37 39 34 2b 30 38 30 30 3b 6b 367600794+0800;k
6f05f81b90 32 33 3a 38 30 37 36 39 32 3b 6b 32 34 3a 32 44 23:807692;k24:2D
6f05f81ba0 65 76 69 63 65 74 79 70 65 30 2c 30 3b 6b 32 35 evicetype0,0;k25
6f05f81bb0 3a 31 39 37 34 2d 31 30 2d 30 38 31 31 30 36 32 :1974-10-0811062
6f05f81bc0 39 2e 30 33 33 33 33 33 39 38 34 2b 30 38 30 30 9.033333984+0800
6f05f81bd0 3b 6b 32 36 3a 31 39 37 34 2d 31 30 2d 30 38 31 ;k26:1974-10-081
6f05f81be0 31 30 36 32 39 2e 30 33 36 36 36 37 33 31 37 2b 10629.036667317+
6f05f81bf0 30 38 30 30 3b 6b 32 37 3a 32 33 33 36 33 3b 6b 0800;k27:23363;k
6f05f81c00 32 38 3a 31 35 44 65 76 69 63 65 74 79 70 65 30 28:15Devicetype0
6f05f81c10 2c 30 3b 6b 32 39 3a 3b 6b 33 30 3a 3b 6b 33 31 ,0;k29:;k30:;k31
6f05f81c20 3a 3b 6b 33 32 3a 3b 6b 33 33 3a 32 30 32 36 2d :;k32:;k33:2026-
6f05f81c30 30 33 2d 32 30 32 32 31 36 30 35 2e 39 34 31 30 03-20221605.9410
6f05f81c40 30 32 37 33 39 2b 30 38 30 30 3b 6b 33 34 3a 32 02739+0800;k34:2
6f05f81c50 30 32 36 2d 30 34 2d 33 30 31 31 32 30 32 33 2e 026-04-30112023.
6f05f81c60 38 32 37 35 35 30 34 33 33 2b 30 38 30 30 3b 6b 827550433+0800;k
6f05f81c70 33 35 3a 32 37 34 33 3b 6b 33 36 3a 35 33 44 65 35:2743;k36:53De
6f05f81c80 76 69 63 65 74 79 70 65 30 2c 30 3b 6b 33 37 3a vicetype0,0;k37:
6f05f81c90 32 30 32 36 2d 30 33 2d 32 30 32 32 31 36 30 35 2026-03-20221605
6f05f81ca0 2e 39 34 31 30 30 32 37 33 39 2b 30 38 30 30 3b .941002739+0800;
6f05f81cb0 6b 33 38 3a 32 30 32 36 2d 30 33 2d 32 30 32 32 k38:2026-03-2022
6f05f81cc0 31 36 30 35 2e 39 34 31 30 30 32 37 33 39 2b 30 1605.941002739+0
6f05f81cd0 38 30 30 3b 6b 33 39 3a 32 37 39 36 3b 6b 34 30 800;k39:2796;k40
6f05f81ce0 3a 35 44 65 76 69 63 65 74 79 70 65 30 2c 30 3b :5Devicetype0,0;
6f05f81cf0 6b 34 31 3a 3b 6b 34 32 3a 3b 6b 34 33 3a 3b 6b k41:;k42:;k43:;k
6f05f81d00 34 34 3a 3b 6b 34 35 3a 32 30 32 36 2d 30 33 2d 44:;k45:2026-03-
6f05f81d10 32 30 32 32 31 36 30 30 2e 36 35 31 30 30 32 32 20221600.6510022
6f05f81d20 31 32 2b 30 38 30 30 3b 6b 34 36 3a 32 34 38 36 12+0800;k46:2486
6f05f81d30 3b 6b 34 37 3a 32 30 32 36 2d 30 33 2d 32 30 32 ;k47:2026-03-202
6f05f81d40 32 31 35 33 38 2e 35 39 34 33 33 33 33 34 38 2b 21538.594333348+
6f05f81d50 30 38 30 30 3b 6b 34 38 3a 32 30 32 36 2d 30 33 0800;k48:2026-03
6f05f81d60 2d 32 30 32 32 31 35 34 30 2e 32 35 37 36 36 36 -20221540.257666
6f05f81d70 38 34 37 2b 30 38 30 30 3b 6b 34 39 3a 32 30 32 847+0800;k49:202
6f05f81d80 36 2d 30 33 2d 32 30 32 32 31 36 30 30 2e 37 32 6-03-20221600.72
6f05f81d90 37 36 36 38 38 38 36 2b 30 38 30 30 3b 6b 35 30 7668886+0800;k50
6f05f81da0 3a 32 34 39 32 3b 6b 35 31 3a 3b 6b 35 32 3a 3b :2492;k51:;k52:;
6f05f81db0 6b 35 33 3a 3b 6b 35 34 3a 32 30 32 36 2d 30 33 k53:;k54:2026-03
6f05f81dc0 2d 32 30 32 32 31 35 33 38 2e 35 39 31 30 30 30 -20221538.591000
6f05f81dd0 30 31 34 2b 30 38 30 30 3b 6b 35 35 3a 39 39 3b 014+0800;k55:99;
6f05f81de0 6b 35 36 3a 32 34 37 39 3b 6b 35 37 3a 32 34 39 k56:2479;k57:249
6f05f81df0 31 3b 6b 35 38 3a 32 30 32 36 2d 30 33 2d 32 30 1;k58:2026-03-20
6f05f81e00 32 32 31 35 35 30 2e 37 39 34 33 33 34 35 36 33 221550.794334563
6f05f81e10 2b 30 38 30 30 3b 6b 35 39 3a 32 30 32 36 2d 30 +0800;k59:2026-0
6f05f81e20 33 2d 32 30 32 32 31 35 35 30 2e 37 39 34 33 33 3-20221550.79433
6f05f81e30 34 35 36 33 2b 30 38 30 30 3b 6b 36 30 3a 31 37 4563+0800;k60:17
6f05f81e40 35 36 3b 6b 36 31 3a 35 35 39 31 3b 6b 36 32 3a 56;k61:5591;k62:
6f05f81e50 32 30 32 36 2d 30 33 2d 32 30 32 32 31 36 30 31 2026-03-20221601
6f05f81e60 2e 30 30 37 36 36 38 39 31 34 2b 30 38 30 30 3b .007668914+0800;
6f05f81e70 6b 36 33 3a 32 35 31 32 3b 6b 36 34 3a 32 30 32 k63:2512;k64:202
6f05f81e80 36 2d 30 33 2d 32 30 32 32 31 36 30 30 2e 33 30 6-03-20221600.30
6f05f81e90 37 36 36 38 38 34 34 2b 30 38 30 30 3b 6b 36 35 7668844+0800;k65
6f05f81ea0 3a 32 34 38 35 3b 6b 36 36 3a 31 37 31 3b 6b 36 :2485;k66:171;k6
6f05f81eb0 37 3a 32 30 32 36 2d 30 33 2d 32 30 32 32 31 35 7:2026-03-202215
6f05f81ec0 34 33 2e 32 37 37 36 36 37 31 34 38 2b 30 38 30 43.277667148+080
6f05f81ed0 30 3b 6b 36 38 3a 33 35 36 3b 6b 36 39 3a 3b 6b 0;k68:356;k69:;k
6f05f81ee0 37 30 3a 31 31 33 3b 6b 37 31 3a 32 30 32 36 2d 70:113;k71:2026-
6f05f81ef0 30 33 2d 32 30 32 32 31 35 33 38 2e 35 39 34 33 03-20221538.5943
6f05f81f00 33 33 33 34 38 2b 30 38 30 30 3b 6b 37 32 3a 31 33348+0800;k72:1
6f05f81f10 31 34 3b 6b 37 33 3a 32 30 32 36 2d 30 33 2d 32 14;k73:2026-03-2
6f05f81f20 30 32 32 31 35 33 38 2e 35 39 34 33 33 33 33 34 0221538.59433334
6f05f81f30 38 2b 30 38 30 30 3b 6b 37 34 3a 32 30 32 36 2d 8+0800;k74:2026-
6f05f81f40 30 33 2d 32 30 32 32 31 35 34 33 2e 33 39 37 36 03-20221543.3976
6f05f81f50 36 37 31 36 30 2b 30 38 30 30 3b 6b 37 35 3a 3b 67160+0800;k75:;
6f05f81f60 6b 31 30 3a 31 62 b7 03 7b 22 68 61 72 6d 6f 6e k10:1b..{"harmon
6f05f81f70 79 22 3a 22 30 22 2c 22 63 6c 6f 6e 65 22 3a 22 y":"0","clone":"
6f05f81f80 30 22 2c 22 63 6f 6e 74 61 69 6e 65 22 3a 22 22 0","containe":""
6f05f81f90 2c 22 6f 7a 22 3a 22 45 67 75 34 64 45 59 6e 61 ,"oz":"Egu4dEYna
6f05f81fa0 70 51 4a 6c 78 38 4e 35 52 47 4e 55 42 43 49 51 pQJlx8N5RGNUBCIQ
6f05f81fb0 78 4d 49 45 67 45 33 4c 39 5c 2f 6f 76 59 67 53 xMIEgE3L9\\/ovYgS
6f05f81fc0 50 52 51 3d 22 2c 22 6f 6f 22 3a 22 78 34 6d 52 PRQ=","oo":"x4mR
6f05f81fd0 39 6e 4b 33 63 65 5c 2f 2b 30 43 6d 57 48 49 62 9nK3ce\\/+0CmWHIb
6f05f81fe0 66 4e 67 3d 3d 22 2c 22 6b 65 6c 6f 6e 67 22 3a fNg==","kelong":
6f05f81ff0 22 30 22 2c 22 69 70 22 3a 22 31 39 32 2e 31 36 "0","ip":"192.16
6f05f82000 38 2e 31 30 30 2e 34 33 22 2c 22 6d 75 6c 74 69 8.100.43","multi
6f05f82010 55 73 65 72 22 3a 22 30 22 2c 22 62 6f 64 22 3a User":"0","bod":
6f05f82020 22 62 6c 75 65 6c 69 6e 65 22 2c 22 62 72 64 22 "blueline","brd"
6f05f82030 3a 22 67 6f 6f 67 6c 65 22 2c 22 64 76 22 3a 22 :"google","dv":"
6f05f82040 62 6c 75 65 6c 69 6e 65 22 2c 22 66 69 72 73 74 blueline","first
6f05f82050 4c 65 76 65 6c 22 3a 22 32 38 22 2c 22 6d 61 6e Level":"28","man
6f05f82060 75 66 61 63 74 22 3a 22 47 6f 6f 67 6c 65 22 2c ufact":"Google",
6f05f82070 22 6e 61 6d 65 22 3a 22 62 6c 75 65 6c 69 6e 65 "name":"blueline
6f05f82080 22 2c 22 68 6f 73 74 22 3a 22 61 62 66 61 72 6d ","host":"abfarm
6f05f82090 2d 72 65 6c 65 61 73 65 2d 72 62 65 2d 36 34 2d -release-rbe-64-
6f05f820a0 30 30 30 32 33 22 2c 22 6b 65 72 6e 65 6c 22 3a 00023","kernel":
6f05f820b0 22 4c 69 6e 75 78 20 6c 6f 63 61 6c 68 6f 73 74 "Linux localhost
6f05f820c0 20 34 2e 39 2e 32 37 30 2d 67 38 36 32 66 35 31 4.9.270-g862f51
6f05f820d0 62 61 63 39 30 30 2d 61 62 37 36 31 33 36 32 35 bac900-ab7613625
6f05f820e0 20 23 30 20 53 4d 50 20 50 52 45 45 4d 50 54 20 #0 SMP PREEMPT
6f05f820f0 54 68 75 20 41 75 67 20 35 20 30 37 3a 30 34 3a Thu Aug 5 07:04:
6f05f82100 34 32 20 55 54 43 20 32 30 32 31 20 61 61 72 63 42 UTC 2021 aarc
6f05f82110 68 36 34 22 2c 22 70 72 65 22 3a 22 30 22 7d 72 h64","pre":"0"}r
6f05f82120 05 50 68 6f 6e 65 7a 0a 31 37 37 37 34 35 36 35 .Phonez.17774565
6f05f82130 32 35 82 01 24 63 35 65 36 39 38 37 31 2d 35 39 25..$c5e69871-59
6f05f82140 61 31 2d 34 63 66 33 2d 39 61 65 64 2d 65 34 35 a1-4cf3-9aed-e45
6f05f82150 38 66 38 32 37 30 61 30 31 8a 01 40 34 34 42 35 8f8270a01..@44B5
6f05f82160 37 39 46 44 35 38 35 30 31 30 45 33 34 43 46 32 79FD585010E34CF2
6f05f82170 31 41 46 44 46 44 32 43 42 35 41 34 34 34 34 34 1AFDFD2CB5A44444
6f05f82180 45 33 38 42 37 39 42 43 42 43 38 42 38 42 34 34 E38B79BCBC8B8B44
6f05f82190 36 45 38 35 34 34 32 44 42 43 45 34 6E85442DBCE4

解出来的字段里,有几个字段的内容格式是这样的:

2026-03-20221538.681000023+0800;

一看这个格式就很像是stat出文件的时间信息——年月日、时分秒、纳秒级的小数部分,外加时区偏移,拼起来带个分号结尾。单纯看PB内容猜不出这些时间是哪个文件的;但结合前面第4节里,字符串解密打出来的那批字符串里本来就有一堆文件路径,两边一联想,基本可以确定:这些字段就是对应文件的时间信息,说明这个PB结构里打包的核心内容之一,就是设备上某些文件的时间信息。

方向确定之后,就要搞清楚这些文件信息具体是怎么获取到的。做法是回到字符串解密函数的调用点,一个个去看每次解密调用返回的是什么内容——找到了其中解密出文件路径字符串的那次调用,顺着这个调用往下跟它的返回值被传到了哪个函数里,跟着跟着就找到了实际读取文件信息的地方:这里没有用常规的stat之类的系统调用,而是用popen执行了一条命令,再用fgets一行行读取命令输出,把需要的文件信息从输出内容里解析出来。

为了确认这个判断,直接在popen和fgets这两个函数上挂了hook,把调用时传入的命令字符串、以及读取到的输出内容都打印出来,跟PB里解出来的文件时间字段对上号,就完美确认了这条获取文件信息的链路。

<!– 图:popen+fgets hook打印命令与输出内容的截图 –>

未知参数的计算

文件信息这块理清楚之后,PB结构里还剩一个字段一直没搞明白是什么——数值看起来完全没有规律,暂且把它叫做"未知参数"。这一块也是整个分析过程里花时间最久的部分。

找计算点

思路是从前面已经确认的文件信息入手:既然采集到的文件信息最终会存进一个全局变量,那这个全局变量大概率是为了保证后续多处使用时数据一致才这么做的。顺着这个全局变量的使用,找到一个在做strcat字符串拼接的函数,hook打印了一下,发现是把一系列参数依次拼接到一起。拼接完之后,紧接着调用了另一个函数——在这个函数的入口(onEnter)和出口(onLeave)分别打印入参和返回值,对比前后变化,就能看到"未知参数"是在这次调用里生成的,这样就把计算点精确定位到了这个函数。

IDA反编译出来这个函数因为有一些平坦化(flatten)处理,看着比较乱,懒得一点点还原控制流,索性还是走动态分析:直接在函数内部调用的几个子函数上挂hook,打印输入输出,看它们各自在干什么。这时候发现一个可疑点——同一个子函数被调用了两次,这基本可以确定,前面拼接好的字符串在这里被拆成了两份分别处理。

继续在这两次调用前后打印入参内容,一开始没看出规律。对比了两次调用各自拼接的固定字符串,发现内容分别是pzotrcm869和另一段——IDA里能看到原始形态是plzdontcrackme856c91。看到这个字符串的瞬间就反应过来了:这其实是提示"单双数分组取值"(plzdontcrackme——奇偶间隔着读),对应到逻辑上就是字符串被按奇偶位置拆成了两组,各自处理时前后拼接了不同的固定内容。

<!– 图:plzdontcrackme856c91字符串在IDA里出现的截图 –>

确认魔改SHA2

分组的问题搞清楚之后,剩下的就是这个子函数内部到底做了什么运算。跟进去看,全是大量的异或、左移右移操作,一看就是哈希类算法的典型写法。搜了一下常量,能在里面找到SHA2标准的那组常量(比如0x428A2F98这类),说明压缩函数本体就是标准SHA2;但是看函数开头取的初始化值,却对不上标准SHA2的8个初始化常量。

<!– 图:IDA里0x428A2F98等SHA2标准常量出现位置的截图 –>

既然压缩函数的常量表都在,只是初始化值不一样,第一反应就是"是不是只改了初始化常量"。验证方法是写个脚本,在so被dlopen加载的时候把这8个初始化常量patch成标准SHA2的值,再正常走一遍调用;同时把同样的输入丢到常规工具里跑一遍标准SHA2,两边结果做比对——完全一致。这样就确认了:这个哈希函数除了初始化常量之外,跟标准SHA2没有任何区别。

跨组取值

现在手上有两组各自的(魔改)SHA2结果,但直接看还是和最终的"未知参数"对不上。回去看这个哈希函数本身,把入参重命名成input、length、out方便阅读,然后对out做交叉引用,找到了取值的地方:第一组的哈希结果会按字节逐个做&0x1f运算,得到的值作为索引,去第二组的哈希结果里取对应位置的字节。逐个测试比对之后,确认就是这样跨组取值、拼出32位结果的。

到这一步,"未知参数"的来源和算法就完整搞清楚了:文件信息等内容拼接后按奇偶分组、分别加上不同固定字符串、各自做一次魔改SHA2(只改了初始化常量),再用第一组结果的&0x1f索引去第二组结果里取值拼接而成。

举个最终跑出来的结果例子(具体拼接顺序和常量细节详见后续正文):

44B579FD585010E34CF21AFDFD2CB5A44444E38B79BCBC8B8B446E85442DBCE

至此,第一次AES加密的明文——PB结构里的文件信息和这个未知参数——就都理清楚了。接下来要追的是第二次加密用到的、每次都会变化的key和iv是怎么生成的。

5.5 追溯key/iv来源

前面确认过,第二次AES加密用的key和iv是每次都会变化的(不像第一次那样是固定值)。追这种"每次都不一样"的值,正常顺着调用链去看数据流会比较麻烦——因为可能是某种运算结果,也可能就是简单地取了个随机数,没法一眼看出来是哪种。

先猜了两种可能:一是有时间戳参与计算,二是干脆就是直接生成的随机值。往random相关的函数名去找,并没有发现调用了常见的random系列函数;但在字符串列表里翻到/dev/urandom和/dev/random这两个路径,而且都是在同一个函数里出现的——一个作为主路径,另一个大概率是作为备用。

确认思路和之前一样:直接在这个函数上hook,把入参和返回值都打印出来。打印出来看,入参是要读取的长度,返回值就是读到的随机字节结果。多跑几次,拿这些结果跟实际用到的key、iv数据做比对,完美对得上。这样就确认了:第二次加密的key和iv,就是直接从/dev/urandom(或备用的/dev/random)读出来的随机字节,并不涉及额外的运算。

call 0x32E1C arg0:0x10
nonce ==> 0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF
6df5f94b70 5c 9a f7 45 32 cd f4 2f 35 e3 51 7d d1 df fb 38 \\..E2../5.Q}…8
call 0x32E1C arg0:0x10
nonce ==> 0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF
6df5f94b70 ef 70 fa a4 b7 09 46 4a f2 49 27 73 bf dd 43 f8 .p….FJ.I's..C.

call 0x32E1C arg0:0x8
nonce ==> 0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF
6df5f7cf90 ed e7 e6 d3 06 79 98 d4 …..y..

5.6 参数1与参数3的生成

参数1:key/iv的RSA加密

key和iv本身的来源确认之后,还剩一个问题:抓包看到的请求里,这两个值并不是直接明文传上去的,得看看它们最终是怎么被处理、放进请求参数里的。

一开始注意到一个格式上的差异:前面hook AES函数打印出来的加密结果都是16进制格式,而抓包里请求携带的对应字段却是base64格式。这说明中间必然经过了一次编码转换,于是去搜base64相关的encode函数,根据编码表特征反向确认了对应的decode函数位置。

在encode函数上hook之后,请求发出的时候就能打印出对应参数(这里对应的就是参数1)编码前的16进制内容。多跑几次观察下来,这段数据的长度是固定的256字节。

顺着这个encode函数往回看调用栈:能看到有一个函数总共调用了6个子函数,其中两个是free,一个是前面提的encode,还有一个是decode,还有一个是字符串解密函数,看了下结果很像RSA公钥。这样一排除,实际参与"生成这段256字节数据"的来源函数就只剩下一个了。点进这个函数看了一下里面用到的字符串,很直接就能看出是RSA相关的实现——加上前面观察到的256字节这个固定长度,本身也是2048位RSA典型的密文长度,进一步印证了这个判断。

[Base64_Decode] Entering sub_1AF08
[*] Input Base64 String: MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEApLEpcsiHA/PE4MKl7WS7r11RijhytmEQH8jaY3nKhvq/KC29yEpJVAu+/asRp9mpoxCAiaTxJynVZMw5amwe24ff0Eau/MLCcfG9jFq8fp0LJwXmKtJuwha2hVr6zhW1+Q1h8f3T9hu+QMZTRxqQnIY1mjqDY1/ARAK2hMzS+uoB+COosppWgFxOcQhzkjcM7Oa0RO+rSaatWsgBrKspVEXFMYfHS+awD8qGXZyyOVaiDNvXgLfPecJerKCOFdujwR3isgtEEe8yyRryUm1FZgdIi8e+t23Lp0XDDB6LCox7DWLKmRjQ5K/5R7+HmSwNLANkw6yTUuLtYcoMY2nOUwIDAQAB
[*] Decoded Length: 294
[*] Decoded Data (Hexdump):
0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF
6e95f63070 30 82 01 22 30 0d 06 09 2a 86 48 86 f7 0d 01 01 0.."0…*.H…..
6e95f63080 01 05 00 03 82 01 0f 00 30 82 01 0a 02 82 01 01 ……..0…….
6e95f63090 00 a4 b1 29 72 c8 87 03 f3 c4 e0 c2 a5 ed 64 bb …)r………d.
6e95f630a0 af 5d 51 8a 38 72 b6 61 10 1f c8 da 63 79 ca 86 .]Q.8r.a….cy..
6e95f630b0 fa bf 28 2d bd c8 4a 49 54 0b be fd ab 11 a7 d9 ..(-..JIT…….
6e95f630c0 a9 a3 10 80 89 a4 f1 27 29 d5 64 cc 39 6a 6c 1e …….').d.9jl.
6e95f630d0 db 87 df d0 46 ae fc c2 c2 71 f1 bd 8c 5a bc 7e ….F….q…Z.~
6e95f630e0 9d 0b 27 05 e6 2a d2 6e c2 16 b6 85 5a fa ce 15 ..'..*.n….Z…
6e95f630f0 b5 f9 0d 61 f1 fd d3 f6 1b be 40 c6 53 47 1a 90 …a……@.SG..
6e95f63100 9c 86 35 9a 3a 83 63 5f c0 44 02 b6 84 cc d2 fa ..5.:.c_.D……
6e95f63110 ea 01 f8 23 a8 b2 9a 56 80 5c 4e 71 08 73 92 37 …#…V.\\Nq.s.7
6e95f63120 0c ec e6 b4 44 ef ab 49 a6 ad 5a c8 01 ac ab 29 ….D..I..Z….)
6e95f63130 54 45 c5 31 87 c7 4b e6 b0 0f ca 86 5d 9c b2 39 TE.1..K…..]..9
6e95f63140 56 a2 0c db d7 80 b7 cf 79 c2 5e ac a0 8e 15 db V…….y.^…..
6e95f63150 a3 c1 1d e2 b2 0b 44 11 ef 32 c9 1a f2 52 6d 45 ……D..2…RmE
6e95f63160 66 07 48 8b c7 be b7 6d cb a7 45 c3 0c 1e 8b 0a f.H….m..E…..
6e95f63170 8c 7b 0d 62 ca 99 18 d0 e4 af f9 47 bf 87 99 2c .{.b…….G…,
6e95f63180 0d 2c 03 64 c3 ac 93 52 e2 ed 61 ca 0c 63 69 ce .,.d…R..a..ci.
6e95f63190 53 02 03 01 00 01 S…..
[Base64_Decode] Leave sub_1AF08

============================================================
[+] 进入 sub_419A8 (RSA_Encrypt 包装函数)
[*] 公钥地址: 0x6e95f63070, 长度: 294
[*] 明文地址: 0x6d4cd80500, 长度: 32
[*] 密文输出地址: 0x6d4cd8047c
[*] RSA 公钥 Dump (DER Format):
0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF
6e95f63070 30 82 01 22 30 0d 06 09 2a 86 48 86 f7 0d 01 01 0.."0…*.H…..
6e95f63080 01 05 00 03 82 01 0f 00 30 82 01 0a 02 82 01 01 ……..0…….
6e95f63090 00 a4 b1 29 72 c8 87 03 f3 c4 e0 c2 a5 ed 64 bb …)r………d.
6e95f630a0 af 5d 51 8a 38 72 b6 61 10 1f c8 da 63 79 ca 86 .]Q.8r.a….cy..
6e95f630b0 fa bf 28 2d bd c8 4a 49 54 0b be fd ab 11 a7 d9 ..(-..JIT…….
6e95f630c0 a9 a3 10 80 89 a4 f1 27 29 d5 64 cc 39 6a 6c 1e …….').d.9jl.
6e95f630d0 db 87 df d0 46 ae fc c2 c2 71 f1 bd 8c 5a bc 7e ….F….q…Z.~
6e95f630e0 9d 0b 27 05 e6 2a d2 6e c2 16 b6 85 5a fa ce 15 ..'..*.n….Z…
6e95f630f0 b5 f9 0d 61 f1 fd d3 f6 1b be 40 c6 53 47 1a 90 …a……@.SG..
6e95f63100 9c 86 35 9a 3a 83 63 5f c0 44 02 b6 84 cc d2 fa ..5.:.c_.D……
6e95f63110 ea 01 f8 23 a8 b2 9a 56 80 5c 4e 71 08 73 92 37 …#…V.\\Nq.s.7
6e95f63120 0c ec e6 b4 44 ef ab 49 a6 ad 5a c8 01 ac ab 29 ….D..I..Z….)
6e95f63130 54 45 c5 31 87 c7 4b e6 b0 0f ca 86 5d 9c b2 39 TE.1..K…..]..9
6e95f63140 56 a2 0c db d7 80 b7 cf 79 c2 5e ac a0 8e 15 db V…….y.^…..
6e95f63150 a3 c1 1d e2 b2 0b 44 11 ef 32 c9 1a f2 52 6d 45 ……D..2…RmE
6e95f63160 66 07 48 8b c7 be b7 6d cb a7 45 c3 0c 1e 8b 0a f.H….m..E…..
6e95f63170 8c 7b 0d 62 ca 99 18 d0 e4 af f9 47 bf 87 99 2c .{.b…….G…,
6e95f63180 0d 2c 03 64 c3 ac 93 52 e2 ed 61 ca 0c 63 69 ce .,.d…R..a..ci.
6e95f63190 53 02 03 01 00 01 S…..
[*] 待加密明文 Dump:
0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF
6d4cd80500 5c 9a f7 45 32 cd f4 2f 35 e3 51 7d d1 df fb 38 \\..E2../5.Q}…8
6d4cd80510 ef 70 fa a4 b7 09 46 4a f2 49 27 73 bf dd 43 f8 .p….FJ.I's..C.

[Base64] =========================================
[*] Entering sub_1A950
[*] Input Length: 256
[*] Input Data:
0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF
6e85f58af0 18 1b a2 71 46 2d ef 02 75 71 ab a9 40 90 59 43 …qF-..uq..@.YC
6e85f58b00 30 b7 07 60 52 3b 48 a0 b5 69 91 72 29 14 6e df 0..`R;H..i.r).n.
6e85f58b10 af 74 a6 b6 38 6e 10 02 3a 5f 4c c6 02 4e 73 6f .t..8n..:_L..Nso
6e85f58b20 a1 10 58 8c 73 30 b3 b8 c5 28 c2 11 88 c4 c8 30 ..X.s0…(…..0
[*] Result String: GBuicUYt7wJ1caupQJBZQzC3B2BSO0igtWmRcikUbt+vdKa2OG4QAjpfTMYCTnNvoRBYjHMws7jFKMIRiMTIMM5aTLcZ3/FXTJQprOUkYc1VW42NIZAyqWdOM51Rtjy6l3SK1Gufti4jmT4lXMth00Weec5VlLeHTm0ocslm7lG0aJir+OBBlw+59TjNdArYkHZxSZLYtWxQrTFQs+5XkClpp+TbMWvR1810mXwCvF0iSs0aV3wTB0lZFQbUrc2Wz8zTt31U/s9tU9YqAYFE8kB/qLckEW8TO9+1FvrbBI6RSubm4zk/VnvocrpA9edd4P3e2gOvhvha7JZxMI2AtQ==
[*] Encoded Length: 344
[Base64] Leave sub_1A950

这里有个比较关键的发现:这个RSA的实现其实有两套方案——一套是走native层libcrypto的RSA实现,另一套是走Java层(调用Java标准库的RSA API)来做。而当前分析的这次调用,命中的是Java层这一套。这个点很关键,因为后续再去分析其他版本的时候,如果发现走的是另一套(libcrypto)实现,也不必意外——两套方案应该是共存的,具体走哪一套可能跟版本、设备环境或者内部的降级/兼容策略有关,分析的时候得留意区分清楚,别把两套实现的细节搞混。

综合起来,参数1的生成逻辑就是:先把内置的公钥做base64解码,还原出真正的公钥数据;然后把前面确认的key和iv直接拼接在一起,作为明文,用这个公钥做RSA加密(本次命中的是Java层实现),得到固定256字节的结果,再整体做一次base64编码,放进请求参数里。

公钥本身其实前面在批量打印字符串解密结果的时候就已经出现过了,只是当时那批解密出来的字符串里有好几个都是类似长度较长的内容,一开始没法一下子确定哪个才是公钥,这次结合RSA的分析回过头看才对上号。

参数3:签名的计算

定位SN计算点

在通过sub_33288算出ky(也就是前面提到的随机key)之后,顺着这个返回值继续往下跟,发现被传进了sub_126F8——从行为上看很像是在做类似strcat的字符串拼接操作。hook打印了一下拼接过程,能看到拼进去的内容依次是cpt、ky、pms,这几个字段名跟前面日志里出现的变量对得上。

拼接完之后,紧接着调用了sub_15144,这个函数的返回值又被传回sub_126F8继续拼接——就是在这一步的打印结果里,第一次看到了sn这个字段的身影,由此可以确定:sn就是sub_15144算出来的。

顺着sub_15144的返回值继续往上找调用来源,找到sub_18880,而sub_18880内部又调用了sub_18258——这个sub_18258才是真正实现SN计算逻辑的函数。

sub_18258因为经过了平坦化处理,控制流看起来很乱,加上里面有不少单字节取值和异或操作,这种细粒度的位运算逻辑,光看静态反编译很容易看漏或者搞混执行顺序,这块最后是用IDA挂上动态调试,单步跟着走一遍实际的执行流程,配合寄存器和内存里的实时数据,才把整个算法链路完整还原出来的。

第一步:对拼接字段做MD5

把cpt、ky、pms、时间戳tm、随机数nn这几个字段按固定顺序拼接成一个字符串,做一次MD5,得到16字节结果,记作md5_bytes。

[>>>] 进入函数 0xEBC0
[*] 参数1 (源数据): 0x6f2603c0f0
[*] 参数2 (长度): 3318 (0xcf6)
[*] 参数3 (输出缓冲区): 0x6d4cd80350
[*] 输入 Hexdump:
0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF
6f2603c0f0 31 47 42 75 69 63 55 59 74 37 77 4a 31 63 61 75 1GBuicUYt7wJ1cau
6f2603c100 70 51 4a 42 5a 51 7a 43 33 42 32 42 53 4f 30 69 pQJBZQzC3B2BSO0i
6f2603c110 67 74 57 6d 52 63 69 6b 55 62 74 2b 76 64 4b 61 gtWmRcikUbt+vdKa
6f2603c120 32 4f 47 34 51 41 6a 70 66 54 4d 59 43 54 6e 4e 2OG4QAjpfTMYCTnN
6f2603c130 76 6f 52 42 59 6a 48 4d 77 73 37 6a 46 4b 4d 49 voRBYjHMws7jFKMI
6f2603c140 52 69 4d 54 49 4d 4d 35 61 54 4c 63 5a 33 2f 46 RiMTIMM5aTLcZ3/F
6f2603c150 58 54 4a 51 70 72 4f 55 6b 59 63 31 56 57 34 32 XTJQprOUkYc1VW42
6f2603c160 4e 49 5a 41 79 71 57 64 4f 4d 35 31 52 74 6a 79 NIZAyqWdOM51Rtjy
6f2603c170 36 6c 33 53 4b 31 47 75 66 74 69 34 6a 6d 54 34 6l3SK1Gufti4jmT4
6f2603c180 6c 58 4d 74 68 30 30 57 65 65 63 35 56 6c 4c 65 lXMth00Weec5VlLe
6f2603c190 48 54 6d 30 6f 63 73 6c 6d 37 6c 47 30 61 4a 69 HTm0ocslm7lG0aJi
6f2603c1a0 72 2b 4f 42 42 6c 77 2b 35 39 54 6a 4e 64 41 72 r+OBBlw+59TjNdAr
6f2603c1b0 59 6b 48 5a 78 53 5a 4c 59 74 57 78 51 72 54 46 YkHZxSZLYtWxQrTF
6f2603c1c0 51 73 2b 35 58 6b 43 6c 70 70 2b 54 62 4d 57 76 Qs+5XkClpp+TbMWv
6f2603c1d0 52 31 38 31 30 6d 58 77 43 76 46 30 69 53 73 30 R1810mXwCvF0iSs0
6f2603c1e0 61 56 33 77 54 42 30 6c 5a 46 51 62 55 72 63 32 aV3wTB0lZFQbUrc2
6f2603c1f0 57 7a 38 7a 54 74 33 31 55 2f 73 39 74 55 39 59 Wz8zTt31U/s9tU9Y
6f2603c200 71 41 59 46 45 38 6b 42 2f 71 4c 63 6b 45 57 38 qAYFE8kB/qLckEW8
6f2603c210 54 4f 39 2b 31 46 76 72 62 42 49 36 52 53 75 62 TO9+1FvrbBI6RSub
6f2603c220 6d 34 7a 6b 2f 56 6e 76 6f 63 72 70 41 39 65 64 m4zk/VnvocrpA9ed
6f2603c230 64 34 50 33 65 32 67 4f 76 68 76 68 61 37 4a 5a d4P3e2gOvhvha7JZ
6f2603c240 78 4d 49 32 41 74 51 3d 3d 63 68 35 45 2b 57 64 xMI2AtQ==ch5E+Wd
6f2603c250 30 57 49 6e 4f 78 6e 34 6f 75 58 6b 62 38 75 76 0WInOxn4ouXkb8uv
6f2603c260 5a 74 38 38 42 4b 73 32 46 45 59 4e 6e 30 62 6d Zt88BKs2FEYNn0bm
6f2603c270 6e 6a 54 4b 79 35 76 2f 68 4c 4c 48 2b 32 75 7a njTKy5v/hLLH+2uz
6f2603c280 55 4d 68 6d 36 61 67 61 2b 6c 59 43 46 4b 65 77 UMhm6aga+lYCFKew
6f2603c290 74 2f 35 70 66 39 42 31 77 6e 4d 59 46 67 53 39 t/5pf9B1wnMYFgS9
6f2603c2a0 2b 34 36 4c 39 43 36 66 56 70 5a 30 65 79 75 4e +46L9C6fVpZ0eyuN
6f2603c2b0 39 73 75 54 6e 37 37 63 48 50 71 4c 4b 48 71 41 9suTn77cHPqLKHqA
6f2603c2c0 73 72 45 61 43 41 64 63 4b 41 67 45 4b 7a 61 59 srEaCAdcKAgEKzaY
6f2603c2d0 75 50 30 4f 58 75 4c 77 73 58 35 78 46 6b 4c 64 uP0OXuLwsX5xFkLd
6f2603c2e0 55 76 6c 32 54 43 37 63 68 58 72 4a 34 6a 64 6d Uvl2TC7chXrJ4jdm
6f2603c2f0 49 47 2b 53 66 31 47 30 78 57 69 52 79 2f 4a 47 IG+Sf1G0xWiRy/JG
6f2603c300 4b 2f 79 33 33 6b 35 48 62 74 4f 71 6b 79 58 38 K/y33k5HbtOqkyX8
6f2603c310 51 78 58 57 30 50 69 6a 2b 46 59 71 78 42 30 78 QxXW0Pij+FYqxB0x
6f2603c320 51 54 6c 76 31 59 4f 72 4b 58 58 6a 73 42 6c 68 QTlv1YOrKXXjsBlh
6f2603c330 62 52 47 72 67 76 75 6c 56 47 41 38 48 69 31 38 bRGrgvulVGA8Hi18
6f2603c340 61 76 76 45 34 6c 6e 33 79 4b 37 47 33 7a 44 45 avvE4ln3yK7G3zDE
6f2603c350 45 44 65 67 4c 4c 52 56 6c 77 38 64 34 57 6a 58 EDegLLRVlw8d4WjX
6f2603c360 6b 36 71 32 65 49 33 65 6d 75 67 62 32 62 73 6e k6q2eI3emugb2bsn
6f2603c370 6c 6e 6b 54 4d 69 39 63 49 57 66 64 36 76 45 53 lnkTMi9cIWfd6vES
6f2603c380 4a 46 35 2b 6f 62 4c 70 74 53 61 55 75 4a 36 65 JF5+obLptSaUuJ6e
6f2603c390 77 5a 66 75 43 41 6d 42 41 47 58 36 47 6f 79 31 wZfuCAmBAGX6Goy1
6f2603c3a0 39 2b 4e 67 57 70 44 65 32 4d 6a 39 64 5a 68 41 9+NgWpDe2Mj9dZhA
6f2603c3b0 62 76 6f 53 68 6b 6b 58 6d 39 6e 63 51 6f 39 5a bvoShkkXm9ncQo9Z
6f2603c3c0 54 78 51 58 69 69 70 6f 6e 6f 75 71 4a 4f 34 31 TxQXiiponouqJO41
6f2603c3d0 6f 6f 4b 37 5a 51 38 2b 77 59 47 59 69 38 78 70 ooK7ZQ8+wYGYi8xp
6f2603c3e0 5a 57 61 32 41 6c 33 36 58 47 65 45 2b 63 65 64 ZWa2Al36XGeE+ced
6f2603c3f0 63 78 6b 6e 74 4f 30 30 31 33 4c 2f 32 58 70 74 cxkntO0013L/2Xpt
6f2603c400 6d 33 49 47 61 34 56 62 55 77 78 6a 74 71 35 54 m3IGa4VbUwxjtq5T
6f2603c410 73 64 64 51 51 42 75 6e 6c 76 44 67 4f 33 6b 53 sddQQBunlvDgO3kS
6f2603c420 54 73 73 30 49 30 79 6c 31 61 4e 77 47 75 59 53 Tss0I0yl1aNwGuYS
6f2603c430 4a 4f 6d 77 46 70 6d 4e 56 74 78 73 67 69 33 64 JOmwFpmNVtxsgi3d
6f2603c440 77 75 64 54 51 77 5a 6c 41 4a 48 71 64 31 7a 56 wudTQwZlAJHqd1zV
6f2603c450 68 74 50 4b 56 76 36 69 68 5a 2f 50 4f 7a 61 52 htPKVv6ihZ/POzaR
6f2603c460 36 71 49 42 37 6f 36 4f 44 65 63 6b 45 38 5a 47 6qIB7o6ODeckE8ZG
6f2603c470 77 48 45 33 45 49 35 50 42 4b 41 66 53 72 51 30 wHE3EI5PBKAfSrQ0
6f2603c480 4f 71 65 71 44 46 68 6c 6b 39 4a 47 50 55 39 6f OqeqDFhlk9JGPU9o
6f2603c490 30 66 41 6c 7a 74 55 46 39 67 30 51 49 64 4e 77 0fAlztUF9g0QIdNw
6f2603c4a0 68 44 58 75 73 55 4a 77 53 4d 69 49 30 36 7a 34 hDXusUJwSMiI06z4
6f2603c4b0 30 63 58 59 47 4f 41 4d 76 64 43 32 68 35 41 4c 0cXYGOAMvdC2h5AL
6f2603c4c0 79 49 74 58 62 33 57 57 51 6d 63 36 31 54 6e 4c yItXb3WWQmc61TnL
6f2603c4d0 53 54 68 58 45 6f 46 4d 49 54 76 74 4e 4f 67 5a SThXEoFMITvtNOgZ
6f2603c4e0 79 33 42 69 30 32 30 65 49 6e 52 70 7a 47 32 64 y3Bi020eInRpzG2d
6f2603c4f0 68 59 4a 78 75 46 56 6d 39 4d 59 64 44 74 44 42 hYJxuFVm9MYdDtDB
6f2603c500 67 6f 4f 71 49 4a 75 73 46 78 62 52 65 6f 54 64 goOqIJusFxbReoTd
6f2603c510 66 6f 6e 71 44 43 66 2f 76 6b 74 74 48 2b 4c 48 fonqDCf/vkttH+LH
6f2603c520 61 35 45 58 54 47 57 6d 38 51 43 2f 38 58 45 47 a5EXTGWm8QC/8XEG
6f2603c530 2b 50 64 35 6b 50 46 37 67 32 2b 4d 79 53 66 69 +Pd5kPF7g2+MySfi
6f2603c540 77 63 51 68 58 4e 34 62 31 33 46 52 70 32 54 6f wcQhXN4b13FRp2To
6f2603c550 39 42 4a 77 59 54 6f 34 2f 6c 6b 66 32 46 38 69 9BJwYTo4/lkf2F8i
6f2603c560 37 54 67 41 53 37 34 68 54 50 66 31 6c 70 67 58 7TgAS74hTPf1lpgX
6f2603c570 79 66 4b 6a 56 39 5a 76 4f 52 55 6c 65 35 39 4c yfKjV9ZvORUle59L
6f2603c580 77 43 53 52 47 55 30 68 64 51 48 67 56 6a 34 42 wCSRGU0hdQHgVj4B
6f2603c590 30 79 73 6c 51 67 38 5a 41 46 4b 38 69 44 45 53 0yslQg8ZAFK8iDES
6f2603c5a0 67 5a 42 30 5a 4e 66 74 50 75 73 4c 41 47 6e 6f gZB0ZNftPusLAGno
6f2603c5b0 45 6c 57 77 31 69 6a 58 50 6c 54 48 74 56 46 57 ElWw1ijXPlTHtVFW
6f2603c5c0 68 7a 47 76 36 56 5a 77 47 36 58 6f 6e 51 39 63 hzGv6VZwG6XonQ9c
6f2603c5d0 6e 54 6f 53 48 39 62 51 44 4b 76 6c 39 59 54 50 nToSH9bQDKvl9YTP
6f2603c5e0 35 79 6a 44 6d 75 56 32 72 71 35 77 70 55 48 4c 5yjDmuV2rq5wpUHL
6f2603c5f0 54 33 70 49 6b 79 33 50 30 42 32 35 54 70 42 64 T3pIky3P0B25TpBd
6f2603c600 43 57 4b 32 6e 4b 36 58 51 63 4b 39 6a 50 73 74 CWK2nK6XQcK9jPst
6f2603c610 66 48 58 63 4f 78 6a 52 77 31 57 46 42 50 42 69 fHXcOxjRw1WFBPBi
6f2603c620 68 37 64 4f 4a 36 75 79 6a 62 61 55 6a 62 76 68 h7dOJ6uyjbaUjbvh
6f2603c630 49 57 52 4f 4b 48 74 44 68 51 59 74 68 47 6a 4a IWROKHtDhQYthGjJ
6f2603c640 7a 65 52 35 58 6a 59 4b 65 4b 50 56 6d 75 34 78 zeR5XjYKeKPVmu4x
6f2603c650 49 57 4f 48 53 55 36 48 31 38 64 2f 42 6a 75 2b IWOHSU6H18d/Bju+
6f2603c660 75 43 66 57 75 52 50 38 6d 56 37 65 4d 42 47 6c uCfWuRP8mV7eMBGl
6f2603c670 4e 71 33 37 67 52 59 32 66 54 75 4a 58 62 75 47 Nq37gRY2fTuJXbuG
6f2603c680 6b 49 69 77 34 4f 54 72 31 32 4f 69 33 75 53 6b kIiw4OTr12Oi3uSk
6f2603c690 48 79 79 50 52 45 2f 6e 58 67 42 31 68 4e 6d 45 HyyPRE/nXgB1hNmE
6f2603c6a0 68 79 4d 58 53 62 37 38 35 68 4e 39 79 74 50 49 hyMXSb785hN9ytPI
6f2603c6b0 78 4f 59 38 74 30 58 79 41 59 4e 58 61 6f 69 48 xOY8t0XyAYNXaoiH
6f2603c6c0 31 57 2f 62 32 71 63 38 35 67 35 47 36 50 73 78 1W/b2qc85g5G6Psx
6f2603c6d0 32 4e 56 4c 6b 4f 57 36 30 58 67 71 55 74 68 50 2NVLkOW60XgqUthP
6f2603c6e0 4b 48 78 43 47 67 4c 6f 75 49 79 5a 42 74 36 58 KHxCGgLouIyZBt6X
6f2603c6f0 30 37 41 43 5a 34 47 47 54 66 37 57 2b 45 4a 47 07ACZ4GGTf7W+EJG
6f2603c700 32 67 51 4c 4f 42 4b 73 4c 68 53 34 7a 74 74 4b 2gQLOBKsLhS4zttK
6f2603c710 46 6a 6e 45 51 66 66 67 45 46 35 45 63 39 2b 46 FjnEQffgEF5Ec9+F
6f2603c720 64 4c 70 2f 49 33 42 39 2b 62 37 44 4d 48 4f 4a dLp/I3B9+b7DMHOJ
6f2603c730 50 58 4e 6d 44 68 6b 48 76 2f 4b 78 75 65 78 31 PXNmDhkHv/Kxuex1
6f2603c740 5a 63 74 30 54 30 2f 46 56 72 52 67 74 51 44 2f Zct0T0/FVrRgtQD/
6f2603c750 66 78 65 37 32 76 73 37 58 50 70 63 6a 4f 79 78 fxe72vs7XPpcjOyx
6f2603c760 42 6d 63 45 41 51 68 6c 38 50 45 32 74 2f 2f 30 BmcEAQhl8PE2t//0
6f2603c770 6a 35 76 6b 48 6b 36 45 63 47 55 70 4e 45 70 42 j5vkHk6EcGUpNEpB
6f2603c780 61 39 75 70 7a 68 69 32 63 75 51 37 49 36 79 53 a9upzhi2cuQ7I6yS
6f2603c790 75 7a 66 45 72 53 77 51 54 36 42 6c 49 4f 45 50 uzfErSwQT6BlIOEP
6f2603c7a0 33 42 33 53 73 78 46 50 76 6b 35 61 6f 5a 59 34 3B3SsxFPvk5aoZY4
6f2603c7b0 62 31 56 6f 52 75 55 4f 52 43 5a 2b 4c 45 56 6f b1VoRuUORCZ+LEVo
6f2603c7c0 4c 6a 46 70 68 56 51 56 41 6b 68 6c 2f 31 2b 53 LjFphVQVAkhl/1+S
6f2603c7d0 65 6d 78 6a 62 5a 2b 79 72 31 6a 4b 56 47 58 58 emxjbZ+yr1jKVGXX
6f2603c7e0 38 41 6a 69 31 30 2b 58 51 70 6b 70 4e 55 64 63 8Aji10+XQpkpNUdc
6f2603c7f0 6f 35 66 39 4f 59 63 6e 4a 69 78 72 5a 74 67 30 o5f9OYcnJixrZtg0
6f2603c800 2f 2b 51 6a 77 35 73 50 52 34 5a 4c 7a 56 38 64 /+Qjw5sPR4ZLzV8d
6f2603c810 59 77 57 50 59 4d 6a 75 53 38 44 63 39 76 58 51 YwWPYMjuS8Dc9vXQ
6f2603c820 70 6a 62 35 57 69 65 57 43 33 38 4f 35 46 58 76 pjb5WieWC38O5FXv
6f2603c830 6c 51 78 6d 44 4f 34 63 35 39 74 53 63 55 51 47 lQxmDO4c59tScUQG
6f2603c840 77 33 72 39 6e 45 2b 51 79 68 69 53 34 32 51 4e w3r9nE+QyhiS42QN
6f2603c850 39 6a 58 37 47 64 68 4a 6a 4c 65 51 6a 39 4d 4f 9jX7GdhJjLeQj9MO
6f2603c860 63 33 4b 35 44 57 73 6b 6f 45 68 7a 54 38 44 58 c3K5DWskoEhzT8DX
6f2603c870 72 38 79 4a 31 6c 36 31 78 31 35 57 56 62 4c 52 r8yJ1l61x15WVbLR
6f2603c880 6b 68 45 51 56 46 2f 4b 32 39 34 75 6b 52 6e 47 khEQVF/K294ukRnG
6f2603c890 4c 5a 75 64 56 34 71 64 67 4f 37 47 6a 45 4d 67 LZudV4qdgO7GjEMg
6f2603c8a0 33 55 6e 32 33 38 67 5a 62 6f 71 31 55 42 32 71 3Un238gZboq1UB2q
6f2603c8b0 76 39 6f 57 79 2b 2b 67 66 57 75 57 6d 37 54 4f v9oWy++gfWuWm7TO
6f2603c8c0 62 4b 7a 67 51 6f 72 44 78 7a 4c 4c 65 61 53 44 bKzgQorDxzLLeaSD
6f2603c8d0 38 50 51 68 48 61 47 76 69 72 52 41 44 53 37 35 8PQhHaGvirRADS75
6f2603c8e0 43 6d 47 39 6e 50 53 37 6b 67 38 6a 6e 30 31 31 CmG9nPS7kg8jn011
6f2603c8f0 63 33 43 49 71 4f 51 35 74 61 66 33 4d 5a 4a 32 c3CIqOQ5taf3MZJ2
6f2603c900 49 35 72 38 79 44 54 79 4e 4c 4d 4a 36 68 63 52 I5r8yDTyNLMJ6hcR
6f2603c910 67 36 52 63 6f 38 33 71 71 4e 74 72 58 54 2b 6f g6Rco83qqNtrXT+o
6f2603c920 56 55 4c 4c 57 4f 79 59 2f 70 6d 70 57 46 74 45 VULLWOyY/pmpWFtE
6f2603c930 6b 68 58 35 45 4a 43 56 53 49 5a 37 74 59 2b 62 khX5EJCVSIZ7tY+b
6f2603c940 67 6e 72 2b 70 53 76 51 42 50 69 4d 30 63 62 51 gnr+pSvQBPiM0cbQ
6f2603c950 2b 31 36 66 4b 49 52 55 44 33 4d 76 6b 43 72 45 +16fKIRUD3MvkCrE
6f2603c960 4e 54 30 45 41 2f 51 4e 4a 4e 37 50 2b 42 46 59 NT0EA/QNJN7P+BFY
6f2603c970 7a 30 78 58 65 49 76 64 70 47 31 79 6e 4d 70 4b z0xXeIvdpG1ynMpK
6f2603c980 36 70 66 72 69 71 51 33 71 6c 38 50 31 61 36 57 6pfriqQ3ql8P1a6W
6f2603c990 68 67 52 39 71 6d 78 55 43 62 58 65 47 4d 38 78 hgR9qmxUCbXeGM8x
6f2603c9a0 59 2b 44 57 7a 37 57 61 2f 2b 44 76 68 58 75 33 Y+DWz7Wa/+DvhXu3
6f2603c9b0 69 6d 59 68 79 66 4e 51 4c 6d 54 6f 77 38 69 73 imYhyfNQLmTow8is
6f2603c9c0 57 78 51 53 74 6d 49 53 64 77 30 4f 6a 74 64 37 WxQStmISdw0Ojtd7
6f2603c9d0 32 74 6b 50 44 55 74 45 68 4b 50 4c 75 66 49 6a 2tkPDUtEhKPLufIj
6f2603c9e0 62 6f 61 45 76 53 68 48 35 77 73 76 68 68 41 52 boaEvShH5wsvhhAR
6f2603c9f0 74 5a 4c 73 45 6b 73 39 34 77 68 37 78 4f 36 4d tZLsEks94wh7xO6M
6f2603ca00 47 72 69 6a 68 6d 78 69 38 33 56 4f 7a 73 46 56 Grijhmxi83VOzsFV
6f2603ca10 64 75 6f 71 53 4c 4d 46 56 7a 6a 6d 32 34 53 6c duoqSLMFVzjm24Sl
6f2603ca20 77 66 31 6a 56 2b 48 34 52 79 2f 72 75 44 54 39 wf1jV+H4Ry/ruDT9
6f2603ca30 70 6d 30 48 62 49 7a 4f 57 76 2b 30 68 31 59 4b pm0HbIzOWv+0h1YK
6f2603ca40 37 55 45 57 6b 34 47 4b 55 62 39 52 7a 2f 6e 31 7UEWk4GKUb9Rz/n1
6f2603ca50 72 34 64 4b 68 74 50 6f 2b 70 61 38 4d 50 79 73 r4dKhtPo+pa8MPys
6f2603ca60 35 62 75 44 79 55 41 62 4f 6c 74 74 37 2b 69 48 5buDyUAbOltt7+iH
6f2603ca70 61 4f 49 56 79 39 65 5a 53 64 51 36 57 70 7a 6a aOIVy9eZSdQ6Wpzj
6f2603ca80 4c 56 43 69 55 6a 4a 7a 77 45 6c 47 44 71 2b 78 LVCiUjJzwElGDq+x
6f2603ca90 4d 5a 58 62 71 32 44 65 6e 32 46 32 31 49 4c 4b MZXbq2Den2F21ILK
6f2603caa0 39 47 69 4f 33 4d 38 33 74 44 61 37 35 41 6a 5a 9GiO3M83tDa75AjZ
6f2603cab0 59 45 38 50 73 6f 41 68 54 78 41 59 38 4c 6e 79 YE8PsoAhTxAY8Lny
6f2603cac0 41 77 58 57 6d 33 4e 58 6b 33 39 43 79 45 62 6d AwXWm3NXk39CyEbm
6f2603cad0 39 32 65 6d 38 35 6c 45 6a 6d 43 30 52 30 4e 6c 92em85lEjmC0R0Nl
6f2603cae0 62 37 36 6d 61 48 71 54 54 4f 33 4d 6d 39 30 57 b76maHqTTO3Mm90W
6f2603caf0 38 6e 56 77 65 4a 52 54 6f 38 50 41 70 2b 56 70 8nVweJRTo8PAp+Vp
6f2603cb00 65 2b 44 58 32 75 72 74 39 5a 71 4c 70 62 58 36 e+DX2urt9ZqLpbX6
6f2603cb10 62 6f 30 45 43 56 31 2b 61 39 58 59 7a 56 35 64 bo0ECV1+a9XYzV5d
6f2603cb20 49 61 61 36 73 69 46 66 64 33 69 68 74 4f 49 72 Iaa6siFfd3ihtOIr
6f2603cb30 67 73 6d 54 6d 78 36 56 56 7a 70 52 73 61 37 36 gsmTmx6VVzpRsa76
6f2603cb40 69 75 68 75 41 4a 77 43 76 4e 64 4a 6a 4e 64 69 iuhuAJwCvNdJjNdi
6f2603cb50 30 78 66 4e 52 72 44 73 67 48 45 75 4f 42 6d 56 0xfNRrDsgHEuOBmV
6f2603cb60 5a 4c 52 46 58 4f 4e 37 2f 7a 6e 41 4f 35 45 76 ZLRFXON7/znAO5Ev
6f2603cb70 6c 44 67 70 46 52 62 49 74 4a 71 64 43 2f 51 79 lDgpFRbItJqdC/Qy
6f2603cb80 41 30 42 50 4e 33 66 46 62 2f 67 69 2b 56 47 35 A0BPN3fFb/gi+VG5
6f2603cb90 34 2f 37 72 49 5a 32 74 71 75 6f 68 67 70 4a 4f 4/7rIZ2tquohgpJO
6f2603cba0 38 6d 6a 79 32 31 33 42 48 2f 70 41 42 52 2b 70 8mjy213BH/pABR+p
6f2603cbb0 6a 59 71 77 54 43 64 46 51 78 7a 78 51 4c 73 38 jYqwTCdFQxzxQLs8
6f2603cbc0 4b 63 79 49 63 49 74 76 31 31 5a 76 4d 6a 64 4c KcyIcItv11ZvMjdL
6f2603cbd0 69 44 79 62 4f 71 6c 2b 37 75 4e 6d 52 50 31 42 iDybOql+7uNmRP1B
6f2603cbe0 37 76 34 58 4d 59 5a 69 7a 72 78 70 35 6e 46 75 7v4XMYZizrxp5nFu
6f2603cbf0 54 63 54 63 38 37 75 7a 76 49 58 61 36 31 72 4c TcTc87uzvIXa61rL
6f2603cc00 62 4d 68 6b 57 62 79 59 79 52 53 51 6b 48 49 32 bMhkWbyYyRSQkHI2
6f2603cc10 34 43 44 79 75 71 70 77 5a 59 58 64 54 4c 7a 68 4CDyuqpwZYXdTLzh
6f2603cc20 55 35 4c 54 4f 52 2b 67 56 63 4c 37 4d 70 49 2f U5LTOR+gVcL7MpI/
6f2603cc30 6c 57 65 70 54 52 6b 64 2f 46 31 33 39 5a 71 6d lWepTRkd/F139Zqm
6f2603cc40 2b 42 76 30 2b 43 31 4c 37 37 79 69 56 78 36 47 +Bv0+C1L77yiVx6G
6f2603cc50 33 54 61 4d 68 37 2b 4c 77 6f 54 72 38 42 62 57 3TaMh7+LwoTr8BbW
6f2603cc60 33 77 74 56 70 4d 79 64 58 37 49 49 64 68 52 52 3wtVpMydX7IIdhRR
6f2603cc70 66 4d 32 31 79 48 37 71 31 74 4e 52 42 56 70 50 fM21yH7q1tNRBVpP
6f2603cc80 6e 70 44 56 33 6a 64 49 31 45 48 53 7a 63 64 66 npDV3jdI1EHSzcdf
6f2603cc90 35 73 69 79 75 39 46 4a 70 76 4f 6b 4a 4a 70 46 5siyu9FJpvOkJJpF
6f2603cca0 6b 54 4e 32 74 64 39 68 6f 4e 6a 30 54 4a 54 31 kTN2td9hoNj0TJT1
6f2603ccb0 50 66 55 4f 34 6e 6f 50 4c 30 39 64 72 6c 4d 31 PfUO4noPL09drlM1
6f2603ccc0 2b 2b 55 78 2b 36 46 32 6d 45 75 32 61 4a 48 72 ++Ux+6F2mEu2aJHr
6f2603ccd0 34 38 75 4c 37 41 55 58 66 57 6d 4a 34 78 32 63 48uL7AUXfWmJ4x2c
6f2603cce0 74 48 30 76 34 63 63 64 76 43 55 57 35 61 36 74 tH0v4ccdvCUW5a6t
6f2603ccf0 6e 49 51 70 76 43 65 66 36 72 4c 4f 51 2b 54 33 nIQpvCef6rLOQ+T3
6f2603cd00 32 4f 37 77 39 73 31 71 42 7a 67 54 71 61 2b 4d 2O7w9s1qBzgTqa+M
6f2603cd10 6d 2b 45 35 4e 74 65 66 34 6d 47 75 2b 4a 6d 64 m+E5Ntef4mGu+Jmd
6f2603cd20 6c 65 55 64 35 46 77 76 45 4c 35 6d 71 76 38 34 leUd5FwvEL5mqv84
6f2603cd30 59 49 65 53 55 37 48 6e 41 66 4f 71 56 34 2b 63 YIeSU7HnAfOqV4+c
6f2603cd40 4e 41 4f 61 4e 58 2f 6b 55 67 77 41 63 30 73 53 NAOaNX/kUgwAc0sS
6f2603cd50 6e 6e 38 6a 36 68 73 75 79 55 41 65 61 4d 50 4a nn8j6hsuyUAeaMPJ
6f2603cd60 53 57 59 4d 7a 58 56 46 36 38 4a 63 31 4c 76 79 SWYMzXVF68Jc1Lvy
6f2603cd70 55 47 38 69 35 48 6e 37 67 77 34 42 77 2b 2f 51 UG8i5Hn7gw4Bw+/Q
6f2603cd80 61 49 65 62 2b 73 38 72 31 70 4b 72 6a 31 63 58 aIeb+s8r1pKrj1cX
6f2603cd90 39 37 34 30 33 75 35 75 34 66 4d 78 38 4a 63 6e 97403u5u4fMx8Jcn
6f2603cda0 4a 55 41 76 37 45 61 69 41 30 4a 77 70 7a 4f 43 JUAv7EaiA0JwpzOC
6f2603cdb0 54 67 57 54 4f 65 78 41 62 2f 4c 44 70 58 6c 63 TgWTOexAb/LDpXlc
6f2603cdc0 46 78 49 31 6d 4c 42 50 53 31 37 37 37 35 31 39 FxI1mLBPS1777519
6f2603cdd0 37 33 33 39 37 35 45 44 45 37 45 36 44 33 30 36 733975EDE7E6D306
6f2603cde0 37 39 39 38 44 34 7998D4
[<<<] 函数 0xEBC0 返回
[*] 输出 (arg2) Hexdump:
0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF
6d4cd80350 13 11 49 8f 59 b3 6c 34 dd 18 22 c8 20 30 85 42 ..I.Y.l4..". 0.B
——————————————

第二步:ChaCha20

ChaCha20的key并不是固定值,而是取自当前时间戳——取时间戳的低4字节,作为key的原始内容。要注意的是,在Python里复现这一步的时候,需要先把这4字节反转字节序,再在右边补0补到32字节,才能得到跟so里实际使用的key一致的结果;nonce则是另一个内置的固定8字节值。用这组key/nonce,把第一步的md5_bytes(16字节)作为"明文"跑一次ChaCha20,得到16字节的chacha20_bytes。

[+] sub_3C618 (GetTimestampMs) 返回值: 0x19ddc6eacd8

============================================================
[+] Entering sub_1BDBC (Key/IV Component Derivation)
[*] Seed (Arg2): 0x65a4e57fef44a2a3
[*] Output Buffer (Arg3): 0x6d4cd80350, Length (Arg4): 16
[*] Arg0 Hexdump (Len: 4):
0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF
6d4cd802e8 d8 ac 6e dc ..n.
[*] Arg3 Hexdump (Generated Result, Len: 16):
0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF
6d4cd80350 13 11 49 8f 59 b3 6c 34 dd 18 22 c8 20 30 85 42 ..I.Y.l4..". 0.B
[*] Return Value (Pointer: 0x6de5f52370) Hexdump (Len: 16):
0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF
6de5f52370 4d 89 41 f7 cf 79 0d 7c d7 73 95 59 50 00 15 0c M.A..y.|.s.YP…
[+] sub_1BDBC Return Value (Raw): 0x6de5f52370
============================================================

第三步:构造第二次MD5的输入并计算

第二次MD5的24字节输入,是固定前缀+两个动态字节+固定填充拼出来的:

  • 前17字节是固定值
  • 第18字节,取自chacha20_bytes的第6个字节(索引5)
  • 接着2字节固定值
  • 第21字节,取自第一步md5_bytes的第5个字节(索引4)
  • 最后3字节固定值

拼成24字节后,对它整体做一次MD5,得到md5_bytes2。

[>>>] 进入函数 0xEBC0
[*] 参数1 (源数据): 0x6df5fbc2f0
[*] 参数2 (长度): 24 (0x18)
[*] 参数3 (输出缓冲区): 0x6d4cd80350
[*] 输入 Hexdump:
0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF
6df5fbc2f0 6d b5 79 08 3f fb 21 1c 7c 8d 91 74 94 fc 45 61 m.y.?.!.|..t..Ea
6df5fbc300 28 79 47 2b 59 2c 4c 4b (yG+Y,LK
[<<<] 函数 0xEBC0 返回
[*] 输出 (arg2) Hexdump:
0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF
6d4cd80350 8f 4d c0 ab f0 2c cd c7 66 86 86 89 5f f1 eb c0 .M…,..f…_…
——————————————

第四步:第一轮异或

把md5_bytes2和上一步24字节输入的最后8字节做异或——这最后8字节在16字节长度上重复使用两遍,得到first_xor。

8f 4d c0 ab f0 2c cd c7 66 86 86 89 5f f1 eb c0
28 79 47 2b 59 2c 4c 4b 28 79 47 2b 59 2c 4c 4b
上面两行上下xor得到下面
a7 34 87 80 a9 00 81 8c 4e ff c1 a2 06 dd a7 8b

第五步:拼出第二个异或对象

第二轮要异或的另一路16字节数据,是这样拼出来的:

  • 第1~4字节:第一步md5_bytes的前4字节
  • 第5字节:取自当前毫秒级时间戳(通过gettimeofday获取秒+微秒换算成毫秒)转成4字节后的最高字节。这个字节是动态变化的,每次请求都会随时间变化
  • 第6~8字节:固定的3个零字节
  • 第9~12字节:第二步chacha20_bytes的第2到第5字节(索引1:4)
  • 第13~16字节:固定值 78 56 34 12

a7 34 87 80 a9 00 81 8c 4e ff c1 a2 06 dd a7 8b
13 11 49 8f dc 00 00 00 89 41 f7 cf 78 56 34 12
上面两行上下xor得到下面
b4 25 ce 0f 75 00 81 8c c7 be 36 6d 7e 8b 93 99

第六步:第二轮异或,拼出最终结果

把first_xor和上一步拼出的16字节做异或,得到final_xor。

最终的参数3,是把这几部分依次拼接:固定4字节前缀4c438510 + 第一步MD5第5字节(1字节)+ 第二步ChaCha20第6字节(1字节)+ 第二步里用到的那个时间戳低4字节(原始顺序,未反转)+ final_xor(16字节),一共26字节,后面补0补到34字节,就是最终发出去的参数3。

[+] — sub_E3DC Called —
[*] Data Length: 34
[*] Input Binary (HexDump):
0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF
6e05fb3d90 4c 43 85 10 59 79 dc 6e ac d8 b4 25 ce 0f 75 00 LC..Yy.n…%..u.
6e05fb3da0 81 8c c7 be 36 6d 7e 8b 93 99 00 00 00 00 00 00 ….6m~………
6e05fb3db0 00 00 ..
[*] Output HexString: 4C4385105979DC6EACD8B425CE0F7500818CC7BE366D7E8B93990000000000000000
[+] ————————

6. 后续版本的迁移方法

因为QIMEI这个SDK不排除每次更新都会加新参数,所以即便这次的9.0.20分析完了,后续版本更新时还是得每次拿新版本检查一遍有没有变化。不过好在核心算法(RSA + 标准AES)这次已经摸透了,后续版本再遇到新的、被重度混淆的大函数,不需要再从头一步步硬啃,而是可以直接利用已知的算法特征去定位关键点,效率上会快很多。

思路:直接找RSA加密点,反推AES的key/iv

因为AES的key和iv本身是通过RSA公钥加密上传的,公钥加密天然没法反向解密,所以没办法从"密文倒推明文"这条路去拿key和iv。但反过来想,既然key和iv是在被送去做RSA加密之前用明文形式存在过,那只要能在新版本里定位到RSA加密调用的位置,在调用之前hook一下,就能直接拿到还没加密的key和iv原始值。

所以后续版本的分析思路可以简化成:不用再管加密函数本身是不是被套了新的混淆或平坦化,直接在so里搜RSA相关特征(比如常见的RSA算法字符串、或者继续用findcrypt搜常量),定位到RSA加密调用点,在这个点上hook,打印进去加密之前的输入内容,就是key和iv。

新版本里RSA只剩Java层这一套

之前分析9.0.20的时候提到过,RSA这块本来是有两套实现的:一套走native层libcrypto,一套走Java层。到了后续版本,发现libcrypto那一套方案直接被删掉了,只保留了Java层这一套。也就是说,现在再分析新版本,不用再去费劲找native RSA的痕迹,直接锁定Java层的RSA实现就行,范围反而更小更好定位了。

具体hook位置:ByteArray构造处

走Java层RSA之前,通常会先NewByteArray,然后用SetByteArrayRegion(或者对应的字节数组填充操作)把key和iv的内容填充进去,填完之后这个数组才会被传给RSA加密的方法。所以最直接的hook点,就是这个"创建好数组、开始SetByte填充"的位置——直接hook这里,把每次SetByte填入的值收集起来,就能完整拿到key和iv的原始内容,不用再去费劲追踪函数内部具体是怎么算出来、怎么走的。

拿到key/iv之后:两层AES解密

有了这组随机的key和iv之后,剩下的解密就和之前分析的完全一致:

  • 先用这组key和iv,对最外层的密文做一次AES解密,得到的结果开头会带有之前分析过的那段固定拼接内容(03000001)
  • 把这段开头的固定内容去掉之后,剩下的部分再用固定的(硬编码在so里、之前分析时已经确认过的)key和iv做第二次AES解密,就能还原出最原始的PB明文
  • 为什么要被迫走这种"直接定位关键点"的方式

    之所以后续版本要靠这种"直接抓关键函数"的方式而不是从头一步步分析,是因为新版本的核心函数普遍存在平坦化处理、而且函数体本身变得非常庞大,如果还按老办法一点点跟控制流、逐句反编译去看,效率会很低、也容易看错。与其死磕控制流,不如直接利用"RSA/AES这些密码学操作在函数里有固定特征"这一点,直接定位到关键调用点,把输入输出hook出来,反而更快更稳。

    采集参数本身相对稳定

    另外从目前观察下来,各版本采集的具体参数内容变化不大,基本还是走popen获取文件信息这一套,没有发现换成别的采集方式或者加别的新字段。所以除非以后腾讯把整个加密算法体系换掉(比如AES换成别的、或者RSA换成别的非对称算法),不然这套"定位RSA点→hook拿key/iv→两层AES解密"的分析流程,对后续版本应该是可以复用、通用的。

    题外话:其他腾讯系App的复用判断

    这套QIMEI采集方案并不是QQ独有的,实际上在腾讯系其他App里也有复用。所以分析完这一套之后,其实多了一个额外的收获:以后遇到别的腾讯系App,不用每次都重新去摸索"这个参数是怎么来的",而是可以先靠抓包判断——只要请求里能看到类似这样的内容:

    KlR0TnGOrnxDl4eae4SWYw==

    这种特征值出现,基本就能判断对方复用的是同一套方案,可以尝试把这次分析出来的算法流程搬过去复现,不用再从头逆一遍。

    总结

    这次分析下来,其实最大的经验就是开头那一步:遇到新版本混淆太重、平坦化太狠、函数体大到IDA都卡的时候,不用非得死磕当前版本,可以先回头找找老版本。同一套SDK早期实现往往更干净,把核心逻辑吃透之后,再回头看新版本会轻松很多,甚至可以直接靠算法特征去反推关键位置,不用再从头顺控制流。

    具体来说,这次反复用到的几个套路:

    • 静态卡住就换动态:调用链在Java层跟丢了、常量搜不到用途,别在那耗着,直接hook打印输入输出,往往比对着伪代码猜快得多。
    • 拿算法特征反推位置:AES的S-box、SHA2的0x428A2F98这类特征常量,比自己去理解一个平坦化过的函数靠谱多了,findcrypt这种工具可以先跑一遍再说。
    • 固定字符串有时候就是提示:像plzdontcrackme856c91这种,作者可能是留了个"彩蛋"或者暗示,遇到奇怪的固定字符串别急着略过,多想一步。
    • 版本更新不用每次从零开始:核心算法摸透之后,后续版本只需要盯住关键调用点(比如RSA加密前的key/iv),直接hook拿数据,没必要每次都重新走一遍完整分析流程
    赞(0)
    未经允许不得转载:网硕互联帮助中心 » QQ QIMEI参数分析:从历史版本追溯加密方案
    分享到: 更多 (0)

    评论 抢沙发

    评论前必须登录!