赞
踩
原文链接:https://share.whuboy.com/weapp.html
感觉写的很好,学习了,转载保存。
起初在研究对移动网络传输进行功耗优化,在一次意外的监听网络传输包中截获了微信小程序的请求包,借此来窥探当下前端代码安全。
Segment | Name | Length | Remark |
---|---|---|---|
Header | FirstMark | 1 byte | 0xBE 固定值 |
Edition | 4 bytes | 0 -> 微信分发到客户端 1 -> 开发者上传到微信后台 | |
IndexInfoLength | 4 bytes | 索引段的长度 | |
BodyInfoLength | 4 bytes | 数据段的长度 | |
LastMark | 1 byte | 0xED 固定值 | |
FileCount | 4 bytes | 文件数量 | |
Index | NameLength | 4 bytes | 文件名长度 |
Name | NameLength bytes | 文件名,长度为NameLength | |
FileOffset | 4 bytes | 文件在数据段位置 | |
FileSize | 4 bytes | 文件大小 | |
LOOP...... | |||
Data | Files Package...... |
包结构非常清晰,分为三个部分:
由此可见,数据完全没有经过压缩或者加密,连包的签名信息也没有。这导致只能在制品流程上进行严格控制,例如在开发者上传代码过程中需要授信,必须经过审查,也一定得由授信平台进行代码分发等。这些都无关风月,毕竟App Store就是这种模式,但是......
其实拆解小程序这种格式并不需要花费特别多的精力,因为其格式比较简单,而且从下图流程上来说,后缀为wx的二进制格式很可能与wxapkg格式是同源的。
从开发者工具的代码中的pack.js
很容易发现一些对wx格式封装的痕迹,只不过其中unpack.js
的代码被隐去了。通过实际的分析发现(wxapkg文件可以通过截获网络包请求获得或者在本地的微信appbrand目录下可以发现),wxapkg格式就是将wx格式进行了转化:Wxml -> Html、 Wxml -> JS、Wxss -> Css,其二进制格式跟后缀名为wx二进制格式完全一致。我写了两个版本的解析二进制包的代码(Javascript版本传送门,python版本传送门),其实非常简单,根据小程序包结构一步一步解析,基本上没啥难度。但如果要将Html -> Wxml, JS -> Wxml, Css -> Wxss进行还原,其中JS -> Wxml的过程中需要将if语句转变成wx-if标签、for语句转化成wx-for标签有点麻烦,需要对解析包后的page-frame.html
中 JS 代码进行修改,修改细节太多就不再详说了,总之微信小程序的代码没有经过额外的保护措施,比较容易进行还原。
(PS:暴露一下微信小程序未公开的API,openUrl
- 在小程序中打开外部网页;getGroupInfo
- 获得群的名称,群内成员的昵称等数据;getOpenidToken
- 获得用户openid;这些权限微信应该是没有准备开放的。每次在进入小程序时,客户端都需要先去请求该小程序的元数据,例如应用名、版本号、一些权限列表、代码包下载地址等描述信息,修改这些元数据可以获得相应的权限,小程序的关键信息完全由后台控制进行配置,另外小程序的本地文件存储采用HASH映射机制进行文件定位,文件存储在外部存储,本身通过自定义算法实现完整性校验 - 首先,小程序最终存储的文件名是:对称加密(文件流内容Alder32校验和 | 原始文件名)生成的,最终文件名和文件内容会通过自校验判断完整性;其次,本地缓存是通过HASH映射查找文件。所以即使能破解文件名和文件内容,绕过文件自身签名校验,篡改为攻击者的伪造文件,小程序APP也无法映射到该伪造文件进行使用。)
由上可见,微信并没有在代码安全上进行过多的考虑。这导致需要在应用审核过程中花费比较多的功夫,不然作品太容易被复制窜改,以至于会失去渠道先机,这对流量是致命打击。由于历史原因,前端的代码安全技术发展的比较缓慢,相比其他被编译成二进制的应用,前端这种纯文本应用,太容易被辨识与窜改。
对前端代码进行保护的目的在于让机器容易识别相关的指令,而使人难以理解代码的逻辑,但往往在对前端代码进行保护过程中,很难既兼顾指令效率又能使可读性降低。因此,常常需要在现有的代码中增加一些额外的验证逻辑,例如一些增加无效的代码进行混淆、采用守护代码保护业务代码不能在其他的域名下正常运行、增加一些防止调试跟踪的断点等,这些措施都是使得破解代码时人工成本增加,从而增加代码的安全性。
下面提供一些能够增加前端代码安全性的策略:
这是最简单且无害的方法,精简代码能减少代码体积,从而减小数据传输的负荷,同时也能降低代码的可读性。在小程序开发者工具中也提供该选项。对Javascript代码进行精简大致可以从以下几个方面入手:
常用的工具有很多:YUI Compressor、UglifyJS、Google Closure Compiler、JS Packer、JS Min...
使用工具对代码进行精简时需要注意:1. 最好备份原始代码,方便调试与后期修改。 2. 用于调试精简代码时保存的sourcemap,在线上应该删除。 3.编写代码的时候应该严格按照规范,最好使用lint工具对代码进行检查,精简代码后导致代码不可用时,调试非常困难。
这种简单的方法虽然很实用,但是也很容易被还原出源代码,使用一些代码格式化工具可以补齐被删除的空格、换行、符号等,例如jsbeautifier。另外2015年就有相关的研究,从大量的代码中推测出被精简的代码,因为人写代码总有固定的范式,所写的代码相似性都非常的高,如果用统计方式就很容易反推源代码,苏黎世联邦理工大学Martin Vechev教授领带下开发的工具JSNice就是一款运用条件随机场(Conditional Random Fields)机器学习和程序分析方法来还原Javascript代码利器,利用大量的开源代码,去学习命名和类型的规律。JSNice可以用于以下不同的方面:反精简的JavaScript代码、对当前的代码提供更多的更有意义的变量名、自动化程序的注释等。相关论文传送门 后台代码传送门
混淆可以减低代码的可读性,防止被轻易追踪出程序逻辑。常见的混淆方法有如下几种:
一般来说,提供代码精简的工具都会提供一些混淆的方法,除此之外,比较知名的商业工具有jasob、jscrambler,一般越商业的越难被反混淆,然而这些高级的代码混淆也常会被用于隐藏应用中的恶意代码。对恶意代码进行混淆是为了躲避杀毒软件的检测,这些代码在被混淆扩充后会难以被识别为恶意软件。相应的也有一些反混淆的工具出现,例如上面提到的JSNice工具能够对混淆的代码进行推理,另外反混淆工具JSDetox专门针对一些混淆方法做过专门的支持。反混淆一直是一项体力活,根据不同的混淆策略需要进行反推演算,这就是一场攻与防的游戏罢了。
加密的关键思想在于将需要执行的代码进行一次编码,在执行的时候还原出浏览器可执行的合法的脚本,在某个角度也可以认为是一种混淆的形式,看上去和可执行文件的加壳有点类似。Javascript提供了将字符串当做代码执行(evaluate)的能力,可以通过 Function constructor
、eval
、setTimeout
、setInterval
、Worker
、DOM event
等将字符串传递给JS引擎进行解析执行,由于有些需要用到eval
函数,会导致代码性能会减低。以Worker
执行举例:
var URL = window.URL || window.webkitURL;
var Blob = window.Blob || window.webkitBlob;
var blobURL = URL.createObjectURL(
new Blob(['console.log("Hello World!")'], {type: 'application/javascript'})
);
new Worker(blobURL);
URL.revokeObjectURL(blobURL);
有以下常见的几种加密方法:
atob
以及eval
进行解码然后运行,另外一种采用base62编码技术更为常见,其最明显的特征是生成的代码以eval(function(p,a,c,k,e,r))
开头。无论代码如何进行变形,其最终都要调用一次eval
等函数。解密的方法不需要对其算法做任何分析,只需要简单地找到这个最终的调用,改为console.log
或者其他方式,将程序解码后的结果按照字符串输出即可。 这些加密的方式都很容易通过对源代码进行词法分析、语法分析进行还原,首先将代码的字符串转换为抽象语法树(Abstract Syntax Tree, AST)的数据形式,然后从语法树的根节点开始,使用深度优先遍历整棵树的所有节点,根据节点上分析出来的指令逐个执行,直到脚本结束返回结果。这种方法大多数用于对代码进行优化,例如最近Facebook开源了代码优化工具Prepack,可以自动消除冗余代码,降低打包体积和执行时间,基本上就可以用来将这些加密的字符串进行还原,毕竟编码这些字符串都是可以通过词法语法推测出来的。
Github上有一份清单记录了所有Javascript扩展语言,这些语言都可以通过编译器转化为Javascript语言,这也是前端发展的一个趋势,原来写的html,css,Javascript已经开始变成了一个“中间语言”,而且越来越多的团队也有了自己的一套前端编译系统。Javascript越来越像Web中的汇编语言,特别是近些年Node的普及,让前端变得越来越复杂,大量前端框架的出现,使得Javascript代码可以通过手工编写,也可以从另一种语言编译而来,详情参考几年前Brendan Eich(JavaScript之父)、Douglas Crockford(JSON之父),还有Mike Shaver(Mozilla技术副总裁)的邮件。通过编译后的Javascript代码越方便机器的理解,降低可读性,在某一定角度上讲,这也不愧为一种代码保护措施。据说几大科技巨头正在酝酿给浏览器应用设计一款通用的字节码标准——WebAssembly,一旦这个设想得以实现,代码保护将可以引入真正意义上的“加壳”或者虚拟机保护,对抗技术又将提升到一个新的台阶。目前在桌面端,使用NW.js框架可以JavaScript应用程序的源代码可以被编译为本地代码,在运行时通过NW.js动态还原出源代码,但是这种方法目前会比正常的JS代码慢30%左右。
对代码进行破解分析无非分为静态分析和动态分析,如果对代码进行混淆加密等形式操作,那么静态分析就很麻烦了,对代码调试跟踪分析可以对代码整体逻辑有一个宏观的把控。例如首先判断浏览器是否开启了开发者工具控制台(目前最完美的解决方案传送门),如果检测出控制台开启则堵塞Javascript执行或让代码异常跳出。另外Android 4.4及以上和iOS是支持webkit remote debug的,因此应该在debug模式下,设置代码可以被debug,release模式下,禁止debug以保护数据安全。
首先得强调的事情是不要在前端放敏感数据,前端容易破解,因此需要配合后端进行安全防护,例如微信小程序的登录,必须利用授信的后端配合才能完成此项功能,另外在小程序的网络请求中的referer是不可以设置的,格式固定为https://servicewechat.com/{appid}/{version}/page-frame.html
,其中{appid}
为小程序的appid,通过验证appid字段可以抵御一些直接的山寨,其次就是加快迭代速度更改代码保护策略,这样可以让之前的分析失效,增加破解的成本。
以上就是对当前前端代码安全进行的探索,最后用一句话结束:
Beneath this mask, there is more than flesh. Beneath this mask, there is an idea. And ideas are bulletproof.
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。