天天看点

Windows微信文本压缩算法分析

文章目录

    • 免责声明
    • 写在前面
    • 分析过程
      • HEX数据
      • 原文
    • 规律总结
    • 写在后面
    • 2022.12.31补充

免责声明

文章仅供交流学习使用,请勿用于非法用途和商业用途,如因此产生任何法律纠纷,均与作者无关。如您选择继续阅读,则默认您认可此声明。

文章如有侵权,请联系我立即删除。

写在前面

算法很早之前就分析过了,不过一直没有什么时间写文章。或许可以为公众号爬虫提供新的思路。

分析过程

之前写了一系列分析Windows微信数据库的文章,包括调用微信内部函数执行sql、在线备份、离线解密。在这个过程中发现了

PublicMsg.db

,顾名思义,这个库保存着接收到的公众号消息。

不过解密后发现消息主体是乱码,数据库字段名称是CompressContent,打开后能看到一些原文内容:

򖼭sg>
    <appmsg appid="" sdkver="0"!   򂼴itle><![CDATA[共青团中央部署新时代希望工程事业发展战略]]></P    F<des^ "     “action></	  type>5</  Ashow 51</  {contentt  
( eattr>0!  % 6urlI ퟮ�tp://mp.weixin.qq.com/s?__biz=MzA3NTE5MzQzMA==&mid=2655107330&idx=1&sn=8d47e9b4ca8bb04a61ad3c203e6946b0&chksm=84c1543bb3b6dd2d550a11fd3d93be55c98dea8101152c4d3c262852a2ff48519a64720a7442&scene=0&xtrack=1#rd]]></url>
©  I<low󠁰 –appattachG  <totallenP 7   <8  idퟄ� +idE ¤fileext></
 B /</z  ´<extinfo></
 1 wmmreadeӁ   <category H罢20" count="3 O<nam"#" w  z<topnew᠄ j<cover^s_­biz.qpic.cn 񛟪pg/7hxJRjsnhfmAMk9m2O1dOCxtiaWrvlRvdhRsSdiaKia5ztYLFQ8NQuhWV4icnLPIVibyv3icJkicyZG0exB8hia0CUmWUA/640?wxѪpeg&wxfrom=0ߠ« 
ࠠ `<width򁂉 % `height& 
 ' \diges» 
2  d_+ Oitem‡  G90</ 
J   3M <‚ψ i<shortŽ žNlong5  4 õpub_time>1585741112</ 4 9 œÀ¡Qtweet‘
 ‡𠁋 2  À©d>507623635҅񅄂 h<sourc " ȃ ­& "</‰ g  § P<styl񈓯	 qnative_¦/
 . €del_flag? + 爏Ո
 ¢<play_lengą b " ¹ 2
	 6 톅4 › H  s<music_¶90</ 3 ppic_numR ) v    Wautho٠ 2 ¢recommendaø
	G B  ² Rurls>½  * j @t_to4 ퟞ�>1277565542685147137އ  & M –ver_235_1¶ öŽ± Ӡ	1Ѡ:ÿ,xIyc5D1ibNpz1Nia2VurteKKTbR53I2ybsaL8kvGxPEw8QlrcL2FfOcw/30‚	
ª ʠ덠_like_ʼn!>2-
 ; bvideo_¾	0삅 1 	ɉ2  3 񠩳_pay_subscrib§
 
; 	‰)  µ	(    ɫ񛨇´敬:那些平凡如你我的志愿者þQ
¤   Ή?ÿ2&sn=685c7e585163763916b205cfbcb22136Pÿ-ad1efde26955c8c12cc5fa4a5845295b50ff3bd0f6870295788ce60aefe7P <·	
C 2¸	
5  4 ¹	Ήhÿ)CeufW5SxGB1d8gIpj9XBJicEYd99Yic7r00Id8LvVyZsbySmB9QSCh0A還¿	ɉF0Á	ÿÿÿ|¯3305904129Á	l鄈¼	^̠I¼	ÿeÿ/¿˜记得“陪你看日落”吗?人间美好,你很值得щ_3щÿfb30eb4c9eab61c89dbe6cd5a4ed6073щÿ-3a482fca3cedd281f2544d7097f7b12fa962fcc5bfe751521aaaba790928щÿÿ(H9kcl4kDLzibpHcXcpqPDWVf5RdY6uor5Z2yYWfGNxUibSX7wWJyMlB؍щÿÿÿõŸ402732441’m鄈щ^ÿ*wUyEMdm9pgEuAITRMbqmiaQL7iclPqokCKQVuwYmx8siadqV3hoNSzCTQ‡҉ÿ$</\
Ն<publish톁Z<userd񠧨_9cda6ee40e37‡	& 
õZ<nickA Ǣ렅& 	A 蛏› ²<template_hD ,</ 
I  `detail#B  
0 Ҧorbid_forwar_2022-12-30 16:47:59 星期五
 / .</ɠi<thumbŠ‰® ՠ%<k ߁꓏	* K  ߤ  "   R<versT$
  ?app*% 7 𠩳forceupdate>1¾ ) 8 ‹"  p</msg> 
           

可以确定微信官方没有对数据进行加密,而是压缩,目的自然是为了节约存储空间,毕竟用户的C盘是寸byte寸金的。

HEX数据

将上面的数据以十六进制显示如下:

# 只是部分数据
F2 16 3C 6D 73 67 3E 0A 20 20 20 20 3C 61 70 70
6D 73 67 20 61 70 70 69 64 3D 22 22 20 73 64 6B
76 65 72 3D 22 30 22 21 00 00 02 00 F2 42 3C 74
69 74 6C 65 3E 3C 21 5B 43 44 41 54 41 5B E5 85
B1 E9 9D 92 E5 9B A2 E4 B8 AD E5 A4 AE E9 83 A8
E7 BD B2 E6 96 B0 E6 97 B6 E4 BB A3 E5 B8 8C E6
9C 9B E5 B7 A5 E7 A8 8B E4 BA 8B E4 B8 9A E5 8F
91 E5 B1 95 E6 88 98 E7 95 A5 5D 5D 3E 3C 2F 50
00 01 81 00 00 02 00 46 3C 64 65 73 5E 00 01 22
00 00 12 00 06 20 00 93 61 63 74 69 6F 6E 3E 3C
2F 09 00 06 1A 00 81 74 79 70 65 3E 35 3C 2F 08
00 06 17 00 41 73 68 6F 77 13 00 35 31 3C 2F 0C
00 06 1F 00 7B 63 6F 6E 74 65 6E 74 74 00 04 16
00 0D 28 00 65 61 74 74 72 3E 30 21 00 01 0F 00
06 25 00 36 75 72 6C 49 00 F0 CB 68 74 74 70 3A
2F 2F 6D 70 2E 77 65 69 78 69 6E 2E 71 71 2E 63
6F 6D 2F 73 3F 5F 5F 62 69 7A 3D 4D 7A 41 33 4E
54 45 35 4D 7A 51 7A 4D 41 3D 3D 26 6D 69 64 3D
32 36 35 35 31 30 37 33 33 30 26 69 64 78 3D 31
26 73 6E 3D 38 64 34 37 65 39 62 34 63 61 38 62
62 30 34 61 36 31 61 64 33 63 32 30 33 65 36 39
34 36 62 30 26 63 68 6B 73 6D 3D 38 34 63 31 35
34 33 62 62 33 62 36 64 64 32 64 35 35 30 61 31
           

原文

原文也需要转换成HEX形式:

3C 6D 73 67 3E 0A 20 20 20 20 3C 61 70 70 6D 73
67 20 61 70 70 69 64 3D 22 22 20 73 64 6B 76 65
72 3D 22 30 22 3E 0A 20 20 20 20 20 20 20 20 3C
74 69 74 6C 65 3E 3C 21 5B 43 44 41 54 41 5B E5
85 B1 E9 9D 92 E5 9B A2 E4 B8 AD E5 A4 AE E9 83
A8 E7 BD B2 E6 96 B0 E6 97 B6 E4 BB A3 E5 B8 8C
E6 9C 9B E5 B7 A5 E7 A8 8B E4 BA 8B E4 B8 9A E5
8F 91 E5 B1 95 E6 88 98 E7 95 A5 5D 5D 3E 3C 2F
74 69 74 6C 65 3E 0A 20 20 20 20 20 20 20 20 3C
64 65 73 3E 3C 21 5B 43 44 41 54 41 5B 5D 5D 3E
3C 2F 64 65 73 3E 0A 20 20 20 20 20 20 20 20 3C
61 63 74 69 6F 6E 3E 3C 2F 61 63 74 69 6F 6E 3E
0A 20 20 20 20 20 20 20 20 3C 74 79 70 65 3E 35
3C 2F 74 79 70 65 3E 0A 20 20 20 20 20 20 20 20
3C 73 68 6F 77 74 79 70 65 3E 31 3C 2F 73 68 6F
77 74 79 70 65 3E 0A 20 20 20 20 20 20 20 20 3C
63 6F 6E 74 65 6E 74 3E 3C 21 5B 43 44 41 54 41
5B 5D 5D 3E 3C 2F 63 6F 6E 74 65 6E 74 3E 0A 20
20 20 20 20 20 20 20 3C 63 6F 6E 74 65 6E 74 61
74 74 72 3E 30 3C 2F 63 6F 6E 74 65 6E 74 61 74
74 72 3E 0A 20 20 20 20 20 20 20 20 3C 75 72 6C
3E 3C 21 5B 43 44 41 54 41 5B 68 74 74 70 3A 2F
2F 6D 70 2E 77 65 69 78 69 6E 2E 71 71 2E 63 6F
6D 2F 73 3F 5F 5F 62 69 7A 3D 4D 7A 41 33 4E 54
           

压缩后的xml,前两个字节是

0xF2,0x16

后面的37个字节跟原文一致:

3C 6D 73 67 3E 0A 20 20 20 20 3C 61 70 70 6D 73
67 20 61 70 70 69 64 3D 22 22 20 73 64 6B 76 65
72 3D 22 30 22
# 原文第37个字节后面的内容
3E 0A 20 20 20 20 20 20 20 20 3C 74 69 74 6C 65
           

经过分析,37来自

0xF2 >> 4 + 0x16

,可能大家会问,为什么要写的这么麻烦呢,直接写0x25不就可以了么?

我最初分析的时候也很疑惑,但再往后面看就会比较明了,0xF2里面的2自然有它的作用。

密文37个字节后的内容:

可以看到,到

3C 74 ...

这里,密文又能跟原文对上了,而中间的

3E 0A 20 20 20 20 20 20 20 20

需要还原。

在最初的37个字节中可以找到

3E 0A 20 20 20 20

,与当前位置的偏移量是37 - 4 = 33 = 0x21,需要拷贝6个字节。

这里暂时看不出跟0xF2里面的2有什么关系,继续:

将已分析还原过的数据写入一个缓冲区,并且向后移动密文的坐标,此时的缓冲区数据:

密文:

00 00 02 00 F2 42 3C 74 ...
           

这里需要还原4个0x20,只能考虑0x02的作用,缓冲区当前位置前面就是四个0x20,暂时将其理解为偏移量为2,循环两次。

当然密文中的第1个0和第三个0都是有作用的,看看下一段密文,说不定能有答案:

# 当前缓冲区
...30 22 3E 0A 20 20 20 20 20 20 20 20
# 当前密文
F2 42 3C 74 69 74 6C 65 3E 3C 21 5B 43 44 41 54
41 5B E5 85 B1 E9 9D 92 E5 9B A2 E4 B8 AD E5 A4
AE E9 83 A8 E7 BD B2 E6 96 B0 E6 97 B6 E4 BB A3
E5 B8 8C E6 9C 9B E5 B7 A5 E7 A8 8B E4 BA 8B E4
B8 9A E5 8F 91 E5 B1 95 E6 88 98 E7 95 A5 5D 5D
3E 3C 2F 50 00 01 81 00 ...
           

我们已经知道

0xF2 >> 4 + 0x42

是未压缩的文本,将其加入到缓冲区:

# 当前缓冲区
...20 20 20 20 3C 74 69 74 6C 65 3E 3C 21 5B 43
44 41 54 41 5B E5 85 B1 E9 9D 92 E5 9B A2 E4 B8
AD E5 A4 AE E9 83 A8 E7 BD B2 E6 96 B0 E6 97 B6
E4 BB A3 E5 B8 8C E6 9C 9B E5 B7 A5 E7 A8 8B E4
BA 8B E4 B8 9A E5 8F 91 E5 B1 95 E6 88 98 E7 95
A5 5D 5D 3E 3C 2F
           

密文中接下来的0x50表示返回到50个字节前拷贝信息,通过分析原文,得到要拷贝的内容是:

74 69 74 6C 65 3E
           

共6个字节,将其加入到缓冲区:

...5D 3E 3C 2F 74 69 74 6C 65 3E
           

暂且可以将0x50后面的0当做段分隔符,看看后面的密文:

01 81 00 00 02 00 46 3C...
           

采用前面的思路,这里需要将

0x01 >> 4 + 0x81

个字节拷贝到缓冲区,但是接下来的内容包含两个连续的0,根据缓冲区中已有的内容,可以判断编码为utf-8,文本中出现0,除unicode编码外,我目前还没有见过,更不要说两个连续的0。

所以这个0x81,只能将其当做偏移量,看看该处的缓冲区和接下来原文中的内容:

缓冲区:

原文:

第6个字节出现了不同,所以这里需要拷贝五个字节到缓冲区,此时就可以考虑到,

5 = 4 + 0x1

6 = 4 + 0x2

,也就是说,至少拷贝4个字符,至多拷贝

4 + 0xF

个字符(但这是不对的,需要继续分析)。

将五个字节拷贝到缓冲区:

当前密文:

00 02 00

还原4个0x20,接下来是

46 3C

,这里因为

46 >> 4

不等于0xF,所以后面的字节直接就是原文内容,长度为0x4,然后返回到0x5E个字节前,拷贝4 + 0x6个字节。

规律总结

至此已经可以总结一些规律:

1、段的第一个字节高位如果是0xF,则需要加上第二个字节,作为原文的长度,否则,第二个字节开始即是原文。

2、段的第一个字节低位加上0x4,是要返回缓冲区某处拷贝的字节长度。

3、从段中取出原文后,接下来的第一个字节代表偏移量,第二个字节是结束符。

可以用上面的规律先写一个简单的解压的代码,但是不出意外会报错,因为要考虑以下情况:

1、如果段的第一个字节高位为0xF,并且段的第二个字节为0xFF,第三个字节是不是原文。

2、段的第一个字节低位为0xF时,可以拷贝19个字节,但这个数字很小,会不会保存有其他的长度信息。

3、如果结束符不为0,应该怎么处理。

不过我已经帮大家总结过了,在这里把答案告诉大家,源代码就不放了。

1、如果段的第一个字节高位为0xF,则一直加到不为0xFF的字节,就是接下来的数据中原文的长度,比如:

F2 FF FF 01...

,就是:

0xF + 0xFF + 0xFF + 0x1

2、如果段的第一个字节低位为0xF,则在段结束符之后,一直加到不为0xFF的字节,再加上0x4,比如:

FF 22 ... 50 0A FF 01...

,要返回到0xA50个字节前,拷贝:

0xF + 0xFF + 0x1 + 0x4

个字节。

3、偏移量固定占用两个字节,最大为0xFFFF,这是一种少量文本的压缩算法,用来压缩XML比较合适。

写在后面

可能大家看着比较头大,我写的也很头大。

与文中算法比较相似的是lz4,lz77等,尝试之后发现无法完全还原,只能硬着头皮分析二进制了。

2022.12.31补充

昨天发布完看到了一篇文章Windows微信:消息数据库架构演进,可以确定微信采用的是lz4压缩算法,项目地址:lz4。

已测试解压没有问题,压缩可能会少一个字节,新版本去掉了最后的一个0x00。

使用python的lz4库,解压出来跟原文差别很大,可能算法不一致 。

继续阅读