在Shape,我们会遇到许多可疑的JavaScript代码片段。我们发现的脚本有些是被恶意注入页面的,有些是客户发来征求意见的,还有些是安全团队在网上发现的资源,其中似乎明确提到了我们服务的某些方面。深入研究这些脚本,了解它们的用途和工作原理,是我们的日常工作之一。这些脚本通常经过压缩,也经常经过混淆,而且必须进行多层修改,才能真正用于深入分析。
此前,进行这类分析最简单的方法,要么是使用支持手动编辑的本地缓存环境,要么是通过代理实时重写内容。本地方案最为便捷,但网站迁移到其他环境后并不总能正常运行,往往需要耗费大量时间排查问题,才能真正开始分析。代理非常灵活,但通常操作繁琐且不便移植;每个人都会针对自己的环境进行自定义配置,而且对不同代理工具的熟悉程度也各不相同。我开始使用Chrome及其DevTools协议,在请求和响应发生时进行拦截并实时修改。这种方法适用于任何安装了Chrome的平台,可以避开许多问题,并能与常用JavaScript工具良好集成。本文将介绍如何使用Chrome DevTools协议实时拦截和修改JavaScript。
我们将使用node,但只要能方便地访问devtools钩子,其中很多内容也适用于您选择的其他语言。
首先,如果您从未尝试过使用脚本控制Chrome,Eric Bidelman撰写了一篇很实用的《无头Chrome入门指南》。其中的技巧同时适用于无头Chrome和图形界面版Chrome(有一个特殊情况,我将在下一节介绍)。
启动Chrome
我们将使用npm中的chrome-launcher库来简化此操作。
chrome-launcher的作用正如其名,您可以原样传入平时在终端中使用的命令行开关(此处维护了一份实用的列表)。我们将传入以下选项:
–window-size=1200,800
- 自动将窗口大小设置为合理值。
–auto-open-devtools-for-tabs
- 自动打开开发者工具,因为我们经常使用它们。
–user-data-dir=/tmp/chrome-testing
- 设置固定的用户数据目录。(理想情况下并不需要这样做,但在Mac OSX上,非无头模式似乎必须使用此标志才能拦截请求。如果您知道更好的方法,请通过Twitter告诉我!)

尝试运行脚本,确保能够打开 Chrome。您应该会看到类似以下内容:

使用 Chrome DevTools Protocol
这也称为“Chrome调试器协议”,Google文档似乎交替使用这两个术语。首先,通过npm安装软件包chrome-remote-interface,该软件包提供了与DevTools协议交互的便捷方法。如果想深入了解,请随时查阅协议文档。
要使用CDP,需要连接到调试器端口。由于使用了 chrome-launcher 库,可以通过 chrome.port 方便地访问该端口。
该协议中的许多域需要先启用。我们将从Runtime域开始,以便接入console API,并将浏览器中的所有console调用传递到命令行。

现在运行脚本时,会打开一个功能完整的Chrome窗口,并将其中的所有控制台消息输出到终端。仅这一点就非常实用,尤其适合测试!
拦截请求
首先,需要向RequestPatterns提交要拦截的setRequestInterception列表。可以在“Request”阶段或“HeadersReceived”阶段进行拦截;要实际修改响应,则需要等待“HeadersReceived”阶段。资源类型对应于开发者工具网络面板中常见的类型。
别忘了像上文启用 Network 域时那样,将 Runtime,添加 Network.enable() 到同一个数组中。

注册事件处理程序相对简单,每个被拦截的请求都会附带一个interceptionId,可用于查询请求信息,或最终发出继续执行指令。这里我们只是介入处理,并将拦截到的每个请求记录到终端。

修改请求
要修改请求,需要安装一些用于编码和解码base64字符串的辅助库。有许多库可供选择,可以自行选用。这里将使用atob和btoa。
用于处理响应的API用起来有些不便。要处理响应,需要将所有响应逻辑都放在请求拦截中(而不是直接拦截响应),然后通过拦截ID查询响应正文。这是因为调用处理程序时,响应正文可能尚不可用;采用这种方式,可以明确等待所需的内容。响应正文也可能采用base64编码,因此不要直接传递,而应先检查并解码。

现在可以尽情发挥JavaScript的能力了。代码会介入响应处理过程,让您既能访问请求的完整JavaScript,又能返回修改后的响应。很棒!我们只需稍微调整一下JS,在末尾添加一个console.log,这样修改后的代码在浏览器中执行时,终端就会收到一条消息。
不能只传递修改后的正文,因为其内容可能与原始资源一同发送的标头冲突。由于目前正在积极测试和调整,建议先从基础内容入手,暂时不必过多考虑需要传递的其他标头信息。如有必要,可以通过传递给事件处理程序的 responseHeaders 访问响应标头;但现在,我们只需在数组中自行构建一组最精简的标头,以便日后操作和编辑。

要发送新响应,需要构造一个完整的、经base64编码的HTTP响应(包括HTTP状态行),并通过传递给rawResponse的对象中的continueInterceptedRequest属性发送该响应。

现在,执行脚本并浏览互联网时,终端中会显示类似以下内容:脚本会拦截JavaScript,修改后的JavaScript也会在浏览器中执行,而其中的console.log()调用则会通过本教程开头创建的钩子传递到终端。

基本示例的完整可运行代码如下:

后续步骤
可以先对源代码进行美化格式化,这始终是着手逆向工程的有效方法。当然,大多数现代浏览器都能做到这一点,但建议自行控制每个修改步骤,以确保不同浏览器及其版本之间的一致性,并在分析源代码时理清各处线索。分析陌生且经过混淆的代码时,我通常会随着对变量和函数用途的逐步了解,对其进行重命名。安全地修改JavaScript并非易事,足以另写一篇博客文章来讨论。不过目前,可以使用类似unminify的工具来还原常见的代码压缩和混淆处理。
可以通过npm安装unminify,然后调用unminify来封装新的JavaScript主体,以查看实际效果:

我们将在下一篇文章中深入探讨这些转换。如果您有任何问题、意见或其他实用技巧,欢迎通过Twitter!
关于作者
相关博客文章
Thingbot正转变为攻击平台
Lori MacVittie重点介绍了F5 Labs对2018年1月至6月全球物联网设备攻击的研究,并指出了其中令人警醒的影响。物联网设备不仅持续遭到利用,还被转变为攻击平台,这意味着攻击者可以更充分地利用这个必然增长的市场。
借助F5全新Quick Start,在AWS上快速开展业务
在AWS re:Invent开幕前,我们很高兴与大家分享F5与AWS之间全新的Quick Start集成。F5的Tom Atkins将介绍该服务如何启动、配置和运行在AWS上部署工作负载所需的计算、网络、存储及其他AWS服务。