小程序主要的代码是html+css+js,而这些代码相对来说比较容易被反编译,为了保护小程序代码安全,微信和百度做了很多安全措施,如代码混淆、加密等,为了防止有心人获取编译后的代码,百度和微信都设计了独有小程序的包结构,今天我就带大家一起来窥探下微信和百度智能小程序的包结构。
我们先来看下微信的包结构(wxapkg)
段 | 名称 | 类型 | 备注 |
Header | FirstMark | Ushort | 魔法数 190 |
From | UInt32 | 0:用于微信客户端分发 1:开发者上传到微信 | |
IndexInfoLength | UInt32 | 索引段的长度 | |
BodyInfoLength | UInt32 | 数据段的长度 | |
LastMark | Ushort | 魔法数 237 | |
FileCount | UInt32 | 文件数量 | |
Index | NameLength | UInt32 | 文件名长度 |
Name | Char* | 文件名,长度为NameLength | |
FileOffset | UInt32 | 文件在数据段位置 | |
FileSize | UInt32 | 文件大小 | |
…… | |||
Data | 文件数据…… |
可以看出微信小程序包分为三个部分:
1.头部段:FirstMark和LastMark这两个魔法数用于识别该包为微信小程序包,From标识是否是微信用于分发(我猜想该字段标识是否是服务端已经处理过,避免服务端多次处理同一个包),索引段长度和数据段长度用于数据校验,避免读取的时候溢出,通过文件数量能够遍历索引段获取文件信息。2.索引段:包含文件数据段相关信息(文件名、文件偏移、文件大小),由于微信包数据段是紧挨着的,其实文件偏移和文件大小只要其中一个就能解析出整个包,当然这样做不利于后续微信升级数据段格式。3.数据段:如上所说,目前数据段文件是紧挨着的,通过索引段解析出的信息,能够解析出所有的文件。可以看出,微信小程序包没有任何压缩和加密措施,很容易通过16进制工具看出整个包的结构,这里我们试着用UltraEdit打开wxapkg文件
可以看出大部分是明文,一些专业人士通过这些信息就足够分析出wxapkg的文件格式了,除了文件格式容易被破解之外,这里我实验了4个小程序包zip和wxapkg文件格式对比
包 | wxapkg包大小 | zip包大小 | zip压缩率diff |
1.wxapkg | 3754KB | 1535KB | 59% |
2.wxapkg | 1693KB | 1500KB | 11% |
3.wxapkg | 1054KB | 404KB | 62% |
4.wxapkg | 1810KB | 1250KB | 31% |
从数据上来看微信小程序包,压缩率最高的能达到62%,最低的也有11%,压缩率主要和小程序包里面的内容有关,如果内容图片较多且图片已经进行过压缩了那么包整体压缩率不会那么明显,但是事实上这样操作的开发者为少数,微信小程序包在结构上还有很大的优化空间。
而百度智能小程序针对微信包格式的缺陷做了优化,一种新的smapp包格式。
段 | 名称 | 类型 | 备注 |
Header | File Header | UInt32 | 魔法数 0xBD180704,用于判断一个二进制文件是否为小程序包 |
Version Code | UInt32 | 小程序版本号 | |
File Number | UInt32 | 文件数量 | |
Index Length | UInt32 | 索引区(第二段)内容长度 | |
Data Length | UInt32 | 文件内容(第三段)解压后长度 | |
App ID | UInt64 | 小程序AppID(非AppKey) | |
Cipher | Char* 48 Bytes | 用于解密索引区的加密数据 | |
Index(通过AES加密) | File Offset | UInt32 | 文件内容(第三段)解压后Offset |
File Size | UInt32 | 文件大小 | |
File Path Length | UInt32 | 文件路径字符串长度 | |
File Path | Char* | 文件路径 | |
…… | |||
Data | 文件数据拼接后通过Gizp进行压缩…… |
百度智能小程序包也分为三个部分:
1.头部段:包含File Header用于校验包为百度智能小程序,这里的魔法数是0xBD180704,我猜是因为百度智能小程序是在18年7月4日开发者大会发布版本号,文件数量,索引长度,文件区长度,小程序AppID,48字节的Cipher(这个是百度小程序的关键,客户端预置了一个384位的RSA公钥,使用公钥对该段数据进行解密,得到32字节解密后的数据,前16位是key,后16位是iv)。2.索引段:通过获取的key和iv以及整个索引段长度,对索引段数据进行解密得到索引段数据。3.数据段:通过Gzip对数据段进行解压,就可以通过索引段得到的信息进行解析数据。可以看到百度智能小程序,在头部段加了Cipher,针对索引段做了加密,数据段做了压缩,解决了微信小程序包格式太大及容易被破解的问题,这里我们来看下一份数据,线上我们拿智能小程序和智能小程序官方示例作为小流量测试
包 | smapp包大小 | zip包大小 | zip压缩率diff |
智能小程序 | 56KB | 58KB | -1% |
智能小程序官方示例 | 75KB | 144KB | -92% |
可以看出,smapp包大小的压缩率比zip更高,智能小程序smapp包比zip包小了2KB,智能小程序官方示例smapp包比zip包小了69KB,几乎小了一半。
很多人可能会想问一些这样的问题:
1.为什么smapp格式比zip格式来的更小?2.为什么只针对索引段加密,而不是索引段和数据段结构加密?3.为什么只针对数据段做压缩?
针对问题1,为什么会出现这样的现象?这里要从zip的格式说起,zip格式主要由file data、central directory、end of central directory record组成,我们来看下zip的格式(来自官方文档) zip format
[local file header 1]
[file data 1]
[data descriptor 1]
.
.
.
[local file header n]
[file data n]
[data descriptor n]
[archive decryption header] (EFS)
[archive extra data record] (EFS)
[central directory]
[zip64 end of central directory record]
[zip64 end of central directory locator]
[end of central directory record]
Central directory structure:
[file header 1]
.
.
.
[file header n]
[digital signature]
File header:
central file header signature 4 bytes (0x02014b50)
version made by 2 bytes
version needed to extract 2 bytes
general purpose bit flag 2 bytes
compression method 2 bytes
last mod file time 2 bytes
last mod file date 2 bytes
crc-32 4 bytes
compressed size 4 bytes
uncompressed size 4 bytes
file name length 2 bytes
extra field length 2 bytes
file comment length 2 bytes
disk number start 2 bytes
internal file attributes 2 bytes
external file attributes 4 bytes
relative offset of local header 4 bytes
file name (variable size)
extra field (variable size)
file comment (variable size)
Digital signature:
header signature 4 bytes (0x05054b50)
size of data 2 bytes
signature data (variable size)
central directory中的file header能确定每个文件实体的位置,并且可以通过遍历central directory获取File header的个数来确定文件实体的个数,可以看出zip格式在central directory记录非常详细的文件信息如CRC-32校验,文件最后修改时间,压缩后大小,压缩前大小等等,而smapp相当于只记录了一个文件的信息,也就是随着文件数量的增多,zip和smapp的大小差距越明显。
针对问题2,索引段和数据段都加密当然是最好的,这样会更安全,但是为了小程序的解析性能,加密段不应该太长,从索引段和数据段来看,索引段通常来说会小很多,而且索引段如不能被正确的解析,那么数据段解析将非常困难,所以在这里选择加密索引段既能保证性能又能保障数据安全。
针对问题3,应该是为了性能考虑,头部为固定76字节没必要压缩,索引段由于已经做了加密,而且通常来说数据不会很大,为了保证整体性能又能保证压缩占比,我们选择使用gzip压缩整个数据段。
百度智能小程序的包(smapp)相对于微信的(wxapkg)来说,基于特定的格式和压缩方案使得同样的内容包体积来说远小于微信的wxapkg,甚至比zip格式更小。再加上合理使用了RSA&AES加密算法使得整体破解起来更是难上加难。