随着大数据时期的发展,各个公司的数据保护意识越来越强,大家都在想尽办法保护自家产品的数据不轻易被爬虫爬走。由于网页是供应信息和做事的主要载体,以是对网页上的信息进行保护就成了至关主要的一个环节。
网页是运行在浏览器真个,当我们浏览一个网页时,其 HTML 代码、 JavaScript 代码都会被下载到浏览器中实行。借助浏览器的开拓者工具,我们可以看到网页在加载过程中所有网络要求的详细信息,也能清楚地看到网站运行的 HTML 代码和 JavaScript 代码,这些代码中就包含了网站加载的全部逻辑,如加载哪些资源、要求接口是如何布局的、页面是如何渲染的等等。正由于代码是完备透明的,以是如果我们能够把个中的实行逻辑研究出来,就可以仿照各个网络要求进行数据爬取了。
然而,事情没有想象得那么大略。随着前端技能的发展,前端代码的打包技能、稠浊技能、加密技能也层出不穷,借助于这些技能,各个公司可以在前端对 JavaScript 代码采纳一定的保护,比如变量名稠浊、实行逻辑稠浊、反调试、核心逻辑加密等,这些保护手段使得我们没法很轻易地找出 JavaScript 代码中包含的的实行逻辑。

在前几章的案例中,我们也试着爬取了各种形式的网站。个中有的网站的数据接口是没有任何验证或加密参数的,我们可以轻松仿照并爬取个中的数据;但有的网站稍显繁芜,网站的接口中增加了一些加密参数,同时对 JavaScript 代码采纳了上文所述的一些防护方法,当时我们没有直接考试测验去破解,而是用 Selenium 等类似工具来实现仿照浏览器实行的办法来进行“所见即所得“的爬取。实在对付后者,我们还有其余一种办理方案,那便是直接逆向 JavaScript 代码,找出个中的加密逻辑,从而直接实现该加密逻辑来进行爬取。如果加密逻辑实在过于繁芜,我们也可以找出一些关键入口,从而实现对加密逻辑的单独仿照实行和数据爬取。这些方案难度可能很大,比如关键入口很难探求,或者加密逻辑难以仿照,可是一旦成功找到打破口,我们便可以不用借助于 Selenium 等工具进行整页数据的渲染而实现数据爬取,这样爬取效率会大幅提升。
本章我们首先会对 JavaScript 防护技能进行先容,然后先容一些常用的 JavaScript 逆向技巧,包括浏览器工具的利用、Hook 技能、AST 技能、分外稠浊技能的处理、WebAssembly 技能的处理。理解了这些技能,我们可以更从容地应对 JavaScript 防护技能。
1. 引入我们在爬取网站的时候,会碰着一些情形须要剖析一些接口或 URL 信息,在这个过程中,我们会碰着各种各样类似加密的环境,比如说:
某个网站的 URL 带有一些看不太懂的长串加密参数,要抓取就必须要懂得这些参数是怎么布局的,否则我们连完全的 URL 都布局不出来,更不用说爬取了。剖析某个网站的 Ajax 接口的时候,可以看到接口的一些参数也是加密的,或者 Request Headers 里面也可能带有一些加密参数,如果不知道这些参数的详细布局逻辑就没法直接用程序来仿照这些 Ajax 要求。翻看网站的 JavaScript 源代码,可以创造很多压缩了或者看不太懂的字符,比如 JavaScript 文件名被编码,JavaScript 的文件内容都压缩成几行,JavaScript 变量也被修正成单个字符或者一些十六进制的字符,导致我们不好轻易根据 JavaScript 找出某些接口的加密逻辑。这些情形呢,基本上都是网站为了保护其本身的一些数据不被轻易抓取而采纳的一些方法,我们可以把它归类为两大类:
URL/API 参数加密JavaScript 压缩、稠浊和加密这一节我们就来理解下这两类技能的基本事理和一些常见的示例。心腹知彼,百战不殆,理解了这些技能的实现事理之后,我们才能更好地去逆向个中的逻辑,从而实现数据爬取。
2. 网站数据防护方案当本年夜数据时期,数据已经变得越来越主要,网页和 App 现在是主流的数据载体,如果其数据的 API 没有设置任何保护方法,在爬虫工程师办理了一些基本的反爬如封 IP、验证码的问题之后,那么数据还是可以被轻松爬取到的。
那么,有没有可能在 URL/API 层面或 JavaScript 层面也加上一层防护呢?答案是可以。
URL/API 参数加密网站运营者首先想到防护方法可能是对某些数据接口的参数进行加密,比如说对某些 URL 的一些参数加上校验码或者把一些 id 信息进行编码,使其变得难以阅读或布局;或者对某些 API 要求加上一些 token、sign 等署名,这样这些要求发送到做事器时,做事器会通过客户端发来的一些要求信息以及双方约定好的秘钥等来对当前的要求进行校验,如果校验通过,才返回对应数据结果。
比如说客户端和做事端约定一种接口校验逻辑,客户端在每次要求做事端接口的时候都会附带一个 sign 参数,这个 sign 参数可能是由当前韶光信息、要求的 URL、要求的数据、设备的 ID、双方约定好的秘钥经由一些加密算法布局而成的,客户端会实现这个加密算法布局 sign,然后每次要求做事器的时候附带上这个参数。做事端会根据约定好的算法和要求的数据对 sign 进行校验,如果校验通过,才返回对应的数据,否则谢绝相应。
当然登录状态的校验也可以看作是此类方案,比如一个 API 的调用必须要传一个 token,这个 token 必须用户登录之后才能获取,如果要求的时候不带该 token,API 就不会返回任何数据。
倘若没有这种方法,那么基本上 URL 或者 API 接口是完备公开可以访问的,这意味着任何人都可以直接调用来获取数据,险些是零防护的状态,这样是非常危险的,而且数据也可以被轻易地被爬虫爬取。因此对 URL/API 参数一些加密和校验是非常有必要的。
JavaScript 压缩、稠浊和加密接口加密技能看起来的确是一个不错的办理方案,但纯挚依赖它并不能很好地办理问题。为什么呢?
对付网页来说,其逻辑是依赖于 JavaScript 来实现的,JavaScript 有如下特点:
JavaScript 代码运行于客户端,也便是它必须要在用户浏览器端加载并运行。JavaScript 代码是公开透明的,也便是说浏览器可以直接获取到正在运行的 JavaScript 的源码。由于这两个缘故原由,至使 JavaScript 代码是不屈安的,任何人都可以读、剖析、复制、盗用,乃至修改。
以是说,对付上述环境,客户端 JavaScript 对付某些加密的实现是很随意马虎被找到或仿照的,理解了加密逻辑后,仿照参数的布局和要求也便是轻而易举了,以是如果 JavaScript 没有做任何层面的保护的话,接口加密技能基本上对数据起不到什么防护浸染。
如果你不想让自己的数据被轻易获取,不想他人理解 JavaScript 逻辑的实现,或者想降落被不怀美意的人乃至是黑客攻击。那么就须要用到 JavaScript 压缩、稠浊和加密技能了。
这里压缩、稠浊和加密技能简述如下:
代码压缩:即去除 JavaScript 代码中的不必要的空格、换行等内容,使源码都压缩为几行内容,降落代码可读性,当然同时也能提高网站的加载速率。代码稠浊:利用变量更换、字符串阵列化、掌握流平坦化、多态变异、僵尸函数、调试保护等手段,使代码变地难以阅读和剖析,达到终极保护的目的。但这不影响代码原有功能。是空想、实用的 JavaScript 保护方案。代码加密:可以通过某种手段将 JavaScript 代码进行加密,转成人无法阅读或者解析的代码,如借用 WebAssembly 技能,可以直接将 JavaScript 代码用 C/C++ 实现,JavaScript 调用其编译后形成的文件来实行相应的功能。下面我们对上面的技能分别予以先容。
3. URL/API 参数加密现在绝大多数网站的数据一样平常都是通过做事器供应的 API 来获取的,网站或 App 可以要求某个数据 API 获取到对应的数据,然后再把获取的数据展示出来。但有些数据是比较宝贵或私密的,这些数据肯定是须要一定层面上的保护。以是不同 API 的实现也就对应着不同的安全防护级别,我们这里来总结下。
为了提升接口的安全性,客户端会和做事端约定一种接口校验办法,一样平常来说会利用到各种加密和编码算法,如 Base64、Hex 编码,MD5、AES、DES、RSA 等对称或非对称加密。
举个例子,比如说客户端和做事器双方约定一个 sign 用作接口的署名校验,其天生逻辑是客户端将 URL Path 进行 MD5 加密然后拼接上 URL 的某个参数再进行 Base64 编码,末了得到一个字符串 sign,这个 sign 会通过 Request URL 的某个参数或 Request Headers 发送给做事器。做事器吸收到要求后,对 URL Path 同样进行 MD5 加密,然后拼接上 URL 的某个参数,也进行 Base64 编码也得到了一个 sign,然后比对天生的 sign 和客户端发来的 sign 是否是同等的,如果是同等的,那就返回精确的结果,否则谢绝相应。这便是一个比较大略的接口参数加密的实现。如果有人想要调用这个接口的话,必须要定义好 sign 的天生逻辑,否则是无法正常调用接口的。
当然上面的这个实现思路比较大略,这里还可以增加一些韶光戳信息增加时效性判断,或增加一些非对称加密进一步提高加密的繁芜程度。但不管若何,只要客户端和做事器约定好了加密和校验逻辑,任何形式加密算法都是可以的。
这里要实现接口参数加密就须要用到一些加密算法,客户端和做事器肯定也都有对应的 SDK 实现这些加密算法,如 JavaScript 的 crypto-js,Python 的 hashlib、Crypto 等等。
但还是如上文所说,如果是网页的话,客户端实现加密逻辑如果是用 JavaScript 来实现,其源代码对用户是完备可见的,如果没有对 JavaScript 做任何保护的话,是很随意马虎弄清楚客户端加密的流程的。
因此,我们须要对 JavaScript 利用压缩、稠浊等办法来对客户真个逻辑进行一定程度上的保护。
4. JavaScript 压缩这个非常大略,JavaScript 压缩即去除 JavaScript 代码中的不必要的空格、换行等内容或者把一些可能公用的代码进行处理实现共享,末了输出的结果都压缩为几行内容,代码可读性变得很差,同时也能提高网站加载速率。
如果仅仅是去除空格换行这样的压缩办法,实在险些是没有任何防护浸染的,由于这种压缩办法仅仅是降落了代码的直接可读性。如果我们有一些格式化工具可以轻松将 JavaScript 代码变得易读,比如利用 IDE、在线工具或 Chrome 浏览器都能还原格式化的代码。
比如这里举一个最大略的 JavaScript 压缩示例,原来的 JavaScript 代码是这样的:
function echo(stringA, stringB) { const name = "Germey"; alert("hello " + name);}
压缩之后就变成这样子:
function echo(d, c) { const e = "Germey"; alert("hello " + e);}
可以看到这里参数的名称都被简化了,代码中的空格也被去掉了,全体代码也被压缩成了一行,代码的整体可读性降落了。
目前主流的前端开拓技能大多都会利用 Webpack、Rollup 等工具进行打包,Webpack、Rollup 会对源代码进行编译和压缩,输出几个打包好的 JavaScript 文件,个中我们可以看到输出的 JavaScript 文件名带有一些不规则字符串,同时文件内容可能只有几行内容,变量名都是一些大略字母表示。这个中就包含 JavaScript 压缩技能,比如一些公共的库输出成 bundle 文件,一些调用逻辑压缩和转义成冗长的几行代码,这些都属于 JavaScript 压缩。其余个中也包含了一些很根本的 JavaScript 稠浊技能,比如把变量名、方法名更换成一些大略字符,降落代码可读性。
但整体来说,JavaScript 压缩技能只能在很小的程度上起到防护浸染,要想真正提高防护效果还得依赖 JavaScript 稠浊和加密技能。
5. JavaScript 稠浊JavaScript 稠浊是完备是在 JavaScript 上面进行的处理,它的目的便是使得 JavaScript 变得难以阅读和剖析,大大降落代码可读性,是一种很实用的 JavaScript 保护方案。
JavaScript 稠浊技能紧张有以下几种:
变量稠浊:将带有含义的变量名、方法名、常量名随机变为无意义的类乱码字符串,降落代码可读性,如转成单个字符或十六进制字符串。字符串稠浊:将字符串阵列化集中放置、并可进行 MD5 或 Base64 加密存储,使代码中不涌现明笔墨符串,这样可以避免利用全局搜索字符串的办法定位到入口点。属性加密:针对 JavaScript 工具的属性进行加密转化,隐蔽代码之间的调用关系。掌握流平坦化:打乱函数原有代码实行流程及函数调用关系,使代码逻变得混乱无序。无用代码注入:随机在代码中插入不会被实行到的无用代码,进一步使代码看起来更加混乱。调试保护:基于调试器特性,对当前运行环境进行考验,加入一些逼迫调试 debugger 语句,使其在调试模式下难以顺利实行 JavaScript 代码。多态变异:使 JavaScript 代码每次被调用时,将代码自身即急速自动发生变异,变革为与之前完备不同的代码,即功能完备不变,只是代码形式变异,以此杜绝代码被动态剖析调试。锁定域名:使 JavaScript 代码只能在指定域名下实行。反格式化:如果对 JavaScript 代码进行格式化,则无法实行,导致浏览器假去世。分外编码:将 JavaScript 完备编码为人不可读的代码,如表情符号、分外表示内容等等。总之,以上方案都是 JavaScript 稠浊的实现办法,可以在不同程度上保护 JavaScript 代码。
在前端开拓中,现在 JavaScript 稠浊主流的实现是 javascript-obfuscator (https://github.com/javascript-obfuscator/javascript-obfuscator) 和 terser (https://github.com/terser/terser) 这两个库,其都能供应一些代码稠浊功能,也都有对应的 Webpack 和 Rollup 打包工具的插件,利用它们我们可以非常方便地实现页面的稠浊,终极可以输出压缩和稠浊后的 JavaScript 代码,使得 JavaScript 代码可读性大大降落。
下面我们以 javascript-obfuscator 为例来先容一些代码稠浊的实现,理解了实现,那么自然我们就对稠浊的机理有了更加深刻的认识。
javascript-obfuscator 的官网地址为:https://obfuscator.io/,其官方先容内容如下:
A free and efficient obfuscator for JavaScript (including ES2017). Make your code harder to copy and prevent people from stealing your work.
它是支持 ES8 的免费、高效的 JavaScript 稠浊库,它可以使得你的 JavaScript 代码经由稠浊后难以被复制、盗用,稠浊后的代码具有和原来的代码千篇一律的功能。
怎么利用呢?首先,我们须要安装好 Node.js 12.x 版本及以上,确保可以正常利用 npm 命令,详细的安装办法可以参考:https://setup.scrape.center/nodejs。
接着新建一个文件夹,比如 js-obfuscate,然后进入该文件夹,初始化事情空间:
npm init
这里会提示我们输入一些信息,创建一个 package.json 文件,这就完成了项目初始化了。
接下来我们来安装 javascript-obfuscator 这个库:
npm i -D javascript-obfuscator
稍等少焉,即可看到本地 js-obfuscate 文件夹下天生了一个 node_modules 文件夹,里面就包含了 javascript-obfuscator 这个库,这就解释安装成功了,文件夹构造如图所示:
接下来我们就可以编写代码来实现一个稠浊样例了,如新建一个 main.js 文件,内容如下:
const code = `let x = '1' + 1console.log('x', x)`;const options = { compact: false, controlFlowFlattening: true,};const obfuscator = require("javascript-obfuscator");function obfuscate(code, options) { return obfuscator.obfuscate(code, options).getObfuscatedCode();}console.log(obfuscate(code, options));
在这里我们定义了两个变量,一个是 code,即须要被稠浊的代码,另一个是稠浊选项,是一个 Object。接下来我们引入了 javascript-obfuscator 这库,然后定义了一个方法,传入 code 和 options,来获取稠浊后的代码,末了掌握台输出稠浊后的代码。
代码逻辑比较大略,我们来实行一下代码:
node main.js
输出结果如下:
var _0x53bf = ["log"];(function (_0x1d84fe, _0x3aeda0) { var _0x10a5a = function (_0x2f0a52) { while (--_0x2f0a52) { _0x1d84fe["push"](_0x1d84fe["shift"]()); } }; _0x10a5a(++_0x3aeda0);})(_0x53bf, 0x172);var _0x480a = function (_0x4341e5, _0x5923b4) { _0x4341e5 = _0x4341e5 - 0x0; var _0xb3622e = _0x53bf[_0x4341e5]; return _0xb3622e;};let x = "1" + 0x1;console[_0x480a("0x0")]("x", x);
看到了吧,那么大略的两行代码,被我们稠浊成了这个样子,实在这里我们便是设定了一个「掌握流平坦化」的选项。整体看来,代码的可读性大大降落,也大大加大了 JavaScript 调试的难度。
好,那么我们来随着 javascript-obfuscator 走一遍,就能详细知道 JavaScript 稠浊到底有多少方法了。
把稳:由于这些例子中,调用 javascript-obfuscator 进行稠浊的实现是一样的,以是下文的示例只解释 code 和 options 变量的修正,完全代码请自行补全。
代码压缩这里 javascript-obfuscator 也供应了代码压缩的功能,利用其参数 compact 即可完成 JavaScript 代码的压缩,输出为一行内容。默认是 true,如果定义为 false,则稠浊后的代码会分行显示。
示例如下:
const code = `let x = '1' + 1console.log('x', x)`;const options = { compact: false,};
这里我们先把代码压缩 compact 选项设置为 false,运行结果如下:
let x = "1" + 0x1;console["log"]("x", x);
如果不设置 compact 或把 compact 设置为 true,结果如下:
var _0x151c = ["log"];(function (_0x1ce384, _0x20a7c7) { var _0x25fc92 = function (_0x188aec) { while (--_0x188aec) { _0x1ce384["push"](_0x1ce384["shift"]()); } }; _0x25fc92(++_0x20a7c7);})(_0x151c, 0x1b7);var _0x553e = function (_0x259219, _0x241445) { _0x259219 = _0x259219 - 0x0; var _0x56d72d = _0x151c[_0x259219]; return _0x56d72d;};let x = "1" + 0x1;console[_0x553e("0x0")]("x", x);
可以看到单行显示的时候,对变量名进行了进一步的稠浊,这里变量的命名都变成了 16 进制形式的字符串,这是由于启用了一些默认压缩和稠浊配置导致的。总之我们可以看到代码的可读性比较之前大大降落了。
变量名稠浊变量名稠浊可以通过在 javascript-obfuscator 中配置 identifierNamesGenerator 参数实现,我们通过这个参数可以掌握变量名稠浊的办法,如 hexadecimal 则会更换为 16 进制形式的字符串,在这里我们可以设定如下值:
hexadecimal:将变量名更换为 16 进制形式的字符串,如 0xabc123。mangled:将变量名更换为普通的简写字符,如 a、b、c 等。该参数的值默认为 hexadecimal。
我们将该参数修正为 mangled 来试一下:
const code = `let hello = '1' + 1console.log('hello', hello)`;const options = { compact: true, identifierNamesGenerator: "mangled",};
运行结果如下:
var a = ["hello"];(function (c, d) { var e = function (f) { while (--f) { c["push"](c["shift"]()); } }; e(++d);})(a, 0x9b);var b = function (c, d) { c = c - 0x0; var e = a[c]; return e;};let hello = "1" + 0x1;console["log"](b("0x0"), hello);
可以看到这里的变量命名都变成了 a、b 等形式。
如果我们将 identifierNamesGenerator 修正为 hexadecimal 或者不设置,运行结果如下:
var _0x4e98 = ["log", "hello"];(function (_0x4464de, _0x39de6c) { var _0xdffdda = function (_0x6a95d5) { while (--_0x6a95d5) { _0x4464de["push"](_0x4464de["shift"]()); } }; _0xdffdda(++_0x39de6c);})(_0x4e98, 0xc8);var _0x53cb = function (_0x393bda, _0x8504e7) { _0x393bda = _0x393bda - 0x0; var _0x46ab80 = _0x4e98[_0x393bda]; return _0x46ab80;};let hello = "1" + 0x1;console[_0x53cb("0x0")](_0x53cb("0x1"), hello);
可以看到选用了 mangled,其代码体积会更小,但 hexadecimal 其可读性会更低。
其余我们还可以通过设置 identifiersPrefix 参数来掌握稠浊后的变量前缀,示例如下:
const code = `let hello = '1' + 1console.log('hello', hello)`;const options = { identifiersPrefix: "germey",};
运行结果如下:
var germey_0x3dea = ["log", "hello"];(function (_0x348ff3, _0x5330e8) { var _0x1568b1 = function (_0x4740d8) { while (--_0x4740d8) { _0x348ff3["push"](_0x348ff3["shift"]()); } }; _0x1568b1(++_0x5330e8);})(germey_0x3dea, 0x94);var germey_0x30e4 = function (_0x2e8f7c, _0x1066a8) { _0x2e8f7c = _0x2e8f7c - 0x0; var _0x5166ba = germey_0x3dea[_0x2e8f7c]; return _0x5166ba;};let hello = "1" + 0x1;console[germey_0x30e4("0x0")](germey_0x30e4("0x1"), hello);
可以看到稠浊后的变量前缀加上了我们自定义的字符串 germey。
其余 renameGlobals 这个参数还可以指定是否稠浊全局变量和函数名称,默认为 false。示例如下:
const code = `var $ = function(id) { return document.getElementById(id);};`;const options = { renameGlobals: true,};
运行结果如下:
var _0x4864b0 = function (_0x5763be) { return document["getElementById"](_0x5763be);};
可以看到这里我们声明了一个全局变量 $,在 renameGlobals 设置为 true 之后,$ 这个变量也被更换了。如果后文用到了这个 $ 工具,可能就会有找不到定义的缺点,因此这个参数可能导致代码实行不通。
如果我们不设置 renameGlobals 或者设置为 false,结果如下:
var _0x239a = ["getElementById"];(function (_0x3f45a3, _0x583dfa) { var _0x2cade2 = function (_0x28479a) { while (--_0x28479a) { _0x3f45a3["push"](_0x3f45a3["shift"]()); } }; _0x2cade2(++_0x583dfa);})(_0x239a, 0xe1);var _0x3758 = function (_0x18659d, _0x50c21d) { _0x18659d = _0x18659d - 0x0; var _0x531b8d = _0x239a[_0x18659d]; return _0x531b8d;};var $ = function (_0x3d8723) { return document[_0x3758("0x0")](_0x3d8723);};
可以看到,末了还是有 $ 的声明,其全局名称没有被改变。
字符串稠浊字符串稠浊,即将一个字符串声明放到一个数组里面,使之无法被直接搜索到。我们可以通过掌握 stringArray 参数来掌握,默认为 true。
我们还可以通过 rotateStringArray 参数来掌握数组化后结果的的元素顺序,默认为 true。还可以通过 stringArrayEncoding 参数来掌握数组的编码形式,默认不开启编码,如果设置为 true 或 base64,则会利用 Base64 编码,如果设置为 rc4,则利用 RC4 编码。其余可以通过 stringArrayThreshold 来掌握启用编码的概率,范围 0 到 1,默认 0.8。
示例如下:
const code = `var a = 'hello world' `;const options = { stringArray: true, rotateStringArray: true, stringArrayEncoding: true, // 'base64' 或 'rc4' 或 false stringArrayThreshold: 1,};
运行结果如下:
var _0x4215 = ["aGVsbG8gd29ybGQ="];(function (_0x42bf17, _0x4c348f) { var _0x328832 = function (_0x355be1) { while (--_0x355be1) { _0x42bf17["push"](_0x42bf17["shift"]()); } }; _0x328832(++_0x4c348f);})(_0x4215, 0x1da);var _0x5191 = function (_0x3cf2ba, _0x1917d8) { _0x3cf2ba = _0x3cf2ba - 0x0; var _0x1f93f0 = _0x4215[_0x3cf2ba]; if (_0x5191["LqbVDH"] === undefined) { (function () { var _0x5096b2; try { var _0x282db1 = Function( "return\x20(function()\x20" + "{}.constructor(\x22return\x20this\x22)(\x20)" + ");" ); _0x5096b2 = _0x282db1(); } catch (_0x2acb9c) { _0x5096b2 = window; } var _0x388c14 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="; _0x5096b2["atob"] || (_0x5096b2["atob"] = function (_0x4cc27c) { var _0x2af4ae = String(_0x4cc27c)["replace"](/=+$/, ""); for ( var _0x21400b = 0x0, _0x3f4e2e, _0x5b193b, _0x233381 = 0x0, _0x3dccf7 = ""; (_0x5b193b = _0x2af4ae["charAt"](_0x233381++)); ~_0x5b193b && ((_0x3f4e2e = _0x21400b % 0x4 ? _0x3f4e2e 0x40 + _0x5b193b : _0x5b193b), _0x21400b++ % 0x4) ? (_0x3dccf7 += String["fromCharCode"]( 0xff & (_0x3f4e2e >> ((-0x2 _0x21400b) & 0x6)) )) : 0x0 ) { _0x5b193b = _0x388c14["indexOf"](_0x5b193b); } return _0x3dccf7; }); })(); _0x5191["DuIurT"] = function (_0x51888e) { var _0x29801f = atob(_0x51888e); var _0x561e62 = []; for ( var _0x5dd788 = 0x0, _0x1a8b73 = _0x29801f["length"]; _0x5dd788 < _0x1a8b73; _0x5dd788++ ) { _0x561e62 += "%" + ("00" + _0x29801f["charCodeAt"](_0x5dd788)["toString"](0x10))[ "slice" ](-0x2); } return decodeURIComponent(_0x561e62); }; _0x5191["mgoBRd"] = {}; _0x5191["LqbVDH"] = !![]; } var _0x1741f0 = _0x5191["mgoBRd"][_0x3cf2ba]; if (_0x1741f0 === undefined) { _0x1f93f0 = _0x5191["DuIurT"](_0x1f93f0); _0x5191["mgoBRd"][_0x3cf2ba] = _0x1f93f0; } else { _0x1f93f0 = _0x1741f0; } return _0x1f93f0;};var a = _0x5191("0x0");
可以看到这里就把字符串进行了 Base64 编码,我们再也无法通过查找的办法找到字符串的位置了。
如果将 stringArray 设置为 false 的话,输出便是这样:
var a = "hello\x20world";
字符串就仍旧是明文显示的,没有被编码。
其余我们还可以利用 unicodeEscapeSequence 这个参数对字符串进行 Unicode 转码,使之更加难以辨认,示例如下:
const code = `var a = 'hello world'`;const options = { compact: false, unicodeEscapeSequence: true,};
运行结果如下:
var _0x5c0d = ["\x68\x65\x6c\x6c\x6f\x20\x77\x6f\x72\x6c\x64"];(function (_0x54cc9c, _0x57a3b2) { var _0xf833cf = function (_0x3cd8c6) { while (--_0x3cd8c6) { _0x54cc9c["push"](_0x54cc9c["shift"]()); } }; _0xf833cf(++_0x57a3b2);})(_0x5c0d, 0x17d);var _0x28e8 = function (_0x3fd645, _0x2cf5e7) { _0x3fd645 = _0x3fd645 - 0x0; var _0x298a20 = _0x5c0d[_0x3fd645]; return _0x298a20;};var a = _0x28e8("0x0");
可以看到,这里字符串被数字化和 Unicode 化,非常难以辨认。
在很多 JavaScript 逆向的过程中,一些关键的字符串可能会作为切入点来查找加密入口。用了这种稠浊之后,如果有人想通过全局搜索的办法搜索 hello 这样的字符串找加密入口,也没法搜到了。
代码自我保护我们可以通过设置 selfDefending 参数来开启代码自我保护功能。开启之后,稠浊后的 JavaScript 会以逼迫一行形式显示,如果我们将稠浊后的代码进行格式化或者重命名,该段代码将无法实行。
示例如下:
const code = `console.log('hello world')`;const options = { selfDefending: true,};
运行结果如下:
var _0x26da = ["log", "hello\x20world"];(function (_0x190327, _0x57c2c0) { var _0x577762 = function (_0xc9dabb) { while (--_0xc9dabb) { _0x190327["push"](_0x190327["shift"]()); } }; var _0x35976e = function () { var _0x16b3fe = { data: { key: "cookie", value: "timeout" }, setCookie: function (_0x2d52d5, _0x16feda, _0x57cadf, _0x56056f) { _0x56056f = _0x56056f || {}; var _0x5b6dc3 = _0x16feda + "=" + _0x57cadf; var _0x333ced = 0x0; for ( var _0x333ced = 0x0, _0x19ae36 = _0x2d52d5["length"]; _0x333ced < _0x19ae36; _0x333ced++ ) { var _0x409587 = _0x2d52d5[_0x333ced]; _0x5b6dc3 += ";\x20" + _0x409587; var _0x4aa006 = _0x2d52d5[_0x409587]; _0x2d52d5["push"](_0x4aa006); _0x19ae36 = _0x2d52d5["length"]; if (_0x4aa006 !== !![]) { _0x5b6dc3 += "=" + _0x4aa006; } } _0x56056f["cookie"] = _0x5b6dc3; }, removeCookie: function () { return "dev"; }, getCookie: function (_0x30c497, _0x51923d) { _0x30c497 = _0x30c497 || function (_0x4b7e18) { return _0x4b7e18; }; var _0x557e06 = _0x30c497( new RegExp( "(?:^|;\x20)" + _0x51923d["replace"](/([.$?|{}()[]\/+^])/g, "$1") + "=([^;])" ) ); var _0x817646 = function (_0xf3fae7, _0x5d8208) { _0xf3fae7(++_0x5d8208); }; _0x817646(_0x577762, _0x57c2c0); return _0x557e06 ? decodeURIComponent(_0x557e06[0x1]) : undefined; }, }; var _0x4673cd = function () { var _0x4c6c5c = new RegExp( "\x5cw+\x20\x5c(\x5c)\x20{\x5cw+\x20[\x27|\x22].+[\x27|\x22];?\x20}" ); return _0x4c6c5c["test"](_0x16b3fe["removeCookie"]["toString"]()); }; _0x16b3fe["updateCookie"] = _0x4673cd; var _0x5baa80 = ""; var _0x1faf19 = _0x16b3fe["updateCookie"](); if (!_0x1faf19) { _0x16b3fe["setCookie"]([""], "counter", 0x1); } else if (_0x1faf19) { _0x5baa80 = _0x16b3fe["getCookie"](null, "counter"); } else { _0x16b3fe["removeCookie"](); } }; _0x35976e();})(_0x26da, 0x140);var _0x4391 = function (_0x1b42d8, _0x57edc8) { _0x1b42d8 = _0x1b42d8 - 0x0; var _0x2fbeca = _0x26da[_0x1b42d8]; return _0x2fbeca;};var _0x197926 = (function () { var _0x10598f = !![]; return function (_0xffa3b3, _0x7a40f9) { var _0x48e571 = _0x10598f ? function () { if (_0x7a40f9) { var _0x2194b5 = _0x7a40f9["apply"](_0xffa3b3, arguments); _0x7a40f9 = null; return _0x2194b5; } } : function () {}; _0x10598f = ![]; return _0x48e571; };})();var _0x2c6fd7 = _0x197926(this, function () { var _0x4828bb = function () { return "\x64\x65\x76"; }, _0x35c3bc = function () { return "\x77\x69\x6e\x64\x6f\x77"; }; var _0x456070 = function () { var _0x4576a4 = new RegExp( "\x5c\x77\x2b\x20\x2a\x5c\x28\x5c\x29\x20\x2a\x7b\x5c\x77\x2b\x20\x2a\x5b\x27\x7c\x22\x5d\x2e\x2b\x5b\x27\x7c\x22\x5d\x3b\x3f\x20\x2a\x7d" ); return !_0x4576a4["\x74\x65\x73\x74"]( _0x4828bb["\x74\x6f\x53\x74\x72\x69\x6e\x67"]() ); }; var _0x3fde69 = function () { var _0xabb6f4 = new RegExp( "\x28\x5c\x5c\x5b\x78\x7c\x75\x5d\x28\x5c\x77\x29\x7b\x32\x2c\x34\x7d\x29\x2b" ); return _0xabb6f4["\x74\x65\x73\x74"]( _0x35c3bc["\x74\x6f\x53\x74\x72\x69\x6e\x67"]() ); }; var _0x2d9a50 = function (_0x58fdb4) { var _0x2a6361 = ~-0x1 >> (0x1 + (0xff % 0x0)); if (_0x58fdb4["\x69\x6e\x64\x65\x78\x4f\x66"]("\x69" === _0x2a6361)) { _0xc388c5(_0x58fdb4); } }; var _0xc388c5 = function (_0x2073d6) { var _0x6bb49f = ~-0x4 >> (0x1 + (0xff % 0x0)); if ( _0x2073d6["\x69\x6e\x64\x65\x78\x4f\x66"]((!![] + "")[0x3]) !== _0x6bb49f ) { _0x2d9a50(_0x2073d6); } }; if (!_0x456070()) { if (!_0x3fde69()) { _0x2d9a50("\x69\x6e\x64\u0435\x78\x4f\x66"); } else { _0x2d9a50("\x69\x6e\x64\x65\x78\x4f\x66"); } } else { _0x2d9a50("\x69\x6e\x64\u0435\x78\x4f\x66"); }});_0x2c6fd7();console[_0x4391("0x0")](_0x4391("0x1"));
如果我们将上述代码放到掌握台,它的实行结果和之前是千篇一律的,没有任何问题。
如果我们将其进行格式化,然后贴到到浏览器掌握台里面,浏览器会直接卡去世无法运行。这样如果有人对代码进行了格式化,就无法正常对代码进走运行和调试,从而起到了保护浸染。
掌握流平坦化掌握流平坦化实在便是将代码的实行逻辑稠浊,使其变得繁芜难读。其基本思想是将一些逻辑处理块都统一加上一个先驱逻辑块,每个逻辑块都由先驱逻辑块进行条件判断和分发,构成一个个闭环逻辑,导致全体实行逻辑十分繁芜难读。
比如说这里有一段示例代码:
console.log(c);console.log(a);console.log(b);
代码逻辑一览无余,依次在掌握台输出了 c、a、b 三个变量的值,但如果把这段代码进行掌握流平坦化处理后,代码就会变成这样:
const s = "3|1|2".split("|");let x = 0;while (true) { switch (s[x++]) { case "1": console.log(a); continue; case "2": console.log(b); continue; case "3": console.log(c); continue; } break;}
可以看到,稠浊后的代码首先声明了一个变量 s,它的结果是一个列表,实在是 ["3", "1", "2"],然后下面通过 switch 语句对 s 中的元素进行了判断,每个 case 都加上了各自的代码逻辑。通过这样的处理,一些连续的实行逻辑就被冲破了,代码被修正为一个 switch 语句,原来我们可以一眼看出的逻辑是掌握台先输出 c,然后才是 a、b,但是现在我们必须要结合 switch 的判断条件和对应 case 的内容进行判断,我们很难再一眼每条语句的实行顺序了,这就大大降落了代码的可读性。
在 javascript-obfuscator 中我们通过 controlFlowFlattening 变量可以掌握是否开启掌握流平坦化,示例如下:
const options = { compact: false, controlFlowFlattening: true,};
利用掌握流平坦化可以使得实行逻辑更加繁芜难读,目前非常多的前端稠浊都会加上这个选项。但启用掌握流平坦化之后,代码的实行韶光会变长,最长达 1.5 倍之多。
其余我们还能利用 controlFlowFlatteningThreshold 这个参数来掌握比例,取值范围是 0 到 1,默认 0.75,如果设置为 0,那相称于 controlFlowFlattening 设置为 false,即不开启掌握流扁平化 。
无用代码注入无用代码即不会被实行的代码或对高下文没有任何影响的代码,注入之后可以对现有的 JavaScript 代码的阅读形成滋扰。我们可以利用 deadCodeInjection 参数开启这个选项,默认为 false。
比如这里有一段代码:
const a = function () { console.log("hello world");};const b = function () { console.log("nice to meet you");};a();b();
这里就声明了方法 a 和 b,然后依次进行调用,分别输出两句话。
但经由无用代码注入处理之后,代码就会变成类似这样的结果:
const _0x16c18d = function () { if (!![[]]) { console.log("hello world"); } else { console.log("this"); console.log("is"); console.log("dead"); console.log("code"); }};const _0x1f7292 = function () { if ("xmv2nOdfy2N".charAt(4) !== String.fromCharCode(110)) { console.log("this"); console.log("is"); console.log("dead"); console.log("code"); } else { console.log("nice to meet you"); }};_0x16c18d();_0x1f7292();
可以看到,每个方法内部都增加了额外的 if else 语句,个中 if 的判断条件还是一个表达式,其结果是 true 还是 false 我们还不太一眼能看出来,比如说 _0x1f7292 这个方法,它的 if 判断条件是:
"xmv2nOdfy2N".charAt(4) !== String.fromCharCode(110)
在不等号前面实在是从字符串中取出指定位置的字符,不等号后面则调用了 fromCharCode 方法来根据 ascii 码转换得到一个字符,然后比较两个字符的结果是否是不一样的。前者经由我们推算可以知道结果是 n,但对付后者,多数情形下我们还得去查一下 ascii 码表才能知道其结果也是 n,末了两个结果是相同的,以是全体表达式的结果是 false,以是 if 后面跟的逻辑实际上便是不会被实行到的无用代码,但这些代码对我们阅读代码起到了一定的滋扰浸染。
因此,这种稠浊办法通过混入一些分外的判断条件并加入一些不会被实行的代码,可以对代码起到一定的稠浊滋扰浸染。
在 javascript-obfuscator 中,我们可以通过 deadCodeInjection 参数掌握无用代码的注入,配置如下:
const options = { compact: false, deadCodeInjection: true,};
其余我们还可以通过设置 deadCodeInjectionThreshold 参数来掌握无用代码注入的比例,取值 0 到 1,默认是 0.4。
工具键名更换如果是一个工具,可以利用 transformObjectKeys 来对工具的键值进行更换,示例如下:
const code = `(function(){ var object = { foo: 'test1', bar: { baz: 'test2' } };})(); `;const options = { compact: false, transformObjectKeys: true,};
输出结果如下:
var _0x7a5d = ["bar", "test2", "test1"];(function (_0x59fec5, _0x2e4fac) { var _0x231e7a = function (_0x46f33e) { while (--_0x46f33e) { _0x59fec5["push"](_0x59fec5["shift"]()); } }; _0x231e7a(++_0x2e4fac);})(_0x7a5d, 0x167);var _0x3bc4 = function (_0x309ad3, _0x22d5ac) { _0x309ad3 = _0x309ad3 - 0x0; var _0x3a034e = _0x7a5d[_0x309ad3]; return _0x3a034e;};(function () { var _0x9f1fd1 = {}; _0x9f1fd1["foo"] = _0x3bc4("0x0"); _0x9f1fd1[_0x3bc4("0x1")] = {}; _0x9f1fd1[_0x3bc4("0x1")]["baz"] = _0x3bc4("0x2");})();
可以看到,Object 的变量名被更换为了分外的变量,使得可读性变差,这样我们就不好直接通过变量名进行征采了,这也可以起到一定的防护浸染。
禁用掌握台输出可以利用 disableConsoleOutput 来禁用掉 console.log 输出功能,加大调试难度,示例如下:
const code = `console.log('hello world')`;const options = { disableConsoleOutput: true,};
运行结果如下:
var _0x3a39 = [ "debug", "info", "error", "exception", "trace", "hello\x20world", "apply", "{}.constructor(\x22return\x20this\x22)(\x20)", "console", "log", "warn",];(function (_0x2a157a, _0x5d9d3b) { var _0x488e2c = function (_0x5bcb73) { while (--_0x5bcb73) { _0x2a157a["push"](_0x2a157a["shift"]()); } }; _0x488e2c(++_0x5d9d3b);})(_0x3a39, 0x10e);var _0x5bff = function (_0x43bdfc, _0x52e4c6) { _0x43bdfc = _0x43bdfc - 0x0; var _0xb67384 = _0x3a39[_0x43bdfc]; return _0xb67384;};var _0x349b01 = (function () { var _0x1f484b = !![]; return function (_0x5efe0d, _0x33db62) { var _0x20bcd2 = _0x1f484b ? function () { if (_0x33db62) { var _0x77054c = _0x33db62[_0x5bff("0x0")](_0x5efe0d, arguments); _0x33db62 = null; return _0x77054c; } } : function () {}; _0x1f484b = ![]; return _0x20bcd2; };})();var _0x19f538 = _0x349b01(this, function () { var _0x7ab6e4 = function () {}; var _0x157bff; try { var _0x5e672c = Function( "return\x20(function()\x20" + _0x5bff("0x1") + ");" ); _0x157bff = _0x5e672c(); } catch (_0x11028d) { _0x157bff = window; } if (!_0x157bff[_0x5bff("0x2")]) { _0x157bff[_0x5bff("0x2")] = (function (_0x7ab6e4) { var _0x5a8d9e = {}; _0x5a8d9e[_0x5bff("0x3")] = _0x7ab6e4; _0x5a8d9e[_0x5bff("0x4")] = _0x7ab6e4; _0x5a8d9e[_0x5bff("0x5")] = _0x7ab6e4; _0x5a8d9e[_0x5bff("0x6")] = _0x7ab6e4; _0x5a8d9e[_0x5bff("0x7")] = _0x7ab6e4; _0x5a8d9e[_0x5bff("0x8")] = _0x7ab6e4; _0x5a8d9e[_0x5bff("0x9")] = _0x7ab6e4; return _0x5a8d9e; })(_0x7ab6e4); } else { _0x157bff[_0x5bff("0x2")][_0x5bff("0x3")] = _0x7ab6e4; _0x157bff[_0x5bff("0x2")][_0x5bff("0x4")] = _0x7ab6e4; _0x157bff[_0x5bff("0x2")]["debug"] = _0x7ab6e4; _0x157bff[_0x5bff("0x2")][_0x5bff("0x6")] = _0x7ab6e4; _0x157bff[_0x5bff("0x2")][_0x5bff("0x7")] = _0x7ab6e4; _0x157bff[_0x5bff("0x2")][_0x5bff("0x8")] = _0x7ab6e4; _0x157bff[_0x5bff("0x2")][_0x5bff("0x9")] = _0x7ab6e4; }});_0x19f538();console[_0x5bff("0x3")](_0x5bff("0xa"));
此时,我们如果实行这个代码,创造是没有任何输出的,这里实际上便是将 console 的一些功能禁用了。
调试保护我们知道,在 JavaScript 代码中如果加入 debugger 这个关键字,那么在实行到该位置的时候掌握它就会进入断点调试模式。如果在代码多个位置都加入 debugger 这个关键字,或者定义某个逻辑来反复实行 debugger,那就会不断进入断点调试模式,原来的代码无法就无法顺畅地实行了。这个过程可以称为调试保护,即通过反复实行 debugger 来使得原来的代码无法顺畅实行。
厥后果类似于实行了如下代码:
setInterval(() => { debugger;}, 3000);
如果我们把这段代码粘贴到掌握台,它就会反复地实行 debugger 语句进入断点调试模式,从而滋扰正常的调试流程。
在 javascript-obfuscator 中可以利用 debugProtection 来启用调试保护机制,还可以利用 debugProtectionInterval 来启用无限 Debug ,使得代码在调试过程中会不断进入断点模式,无法顺畅实行,配置如下:
const options = { debugProtection: true, debugProtectionInterval: true,};
稠浊后的代码会不断跳到 debugger 代码的位置,使得全体代码无法顺畅实行,对 JavaScript 代码的调试形成一定的滋扰。
域名锁定我们还可以通过掌握 domainLock 来掌握 JavaScript 代码只能在特定域名下运行,这样就可以降落代码被仿照或盗用的风险。
示例如下:
const code = `console.log('hello world')`;const options = { domainLock: ["cuiqingcai.com"],};
这里我们利用了 domainLock 指定了一个域名叫做 cuiqingcai.com,也便是设置了一个域名白名单,稠浊后的代码结果如下:
var _0x3203 = [ "apply", "return\x20(function()\x20", "{}.constructor(\x22return\x20this\x22)(\x20)", "item", "attribute", "value", "replace", "length", "charCodeAt", "log", "hello\x20world",];(function (_0x2ed22c, _0x3ad370) { var _0x49dc54 = function (_0x53a786) { while (--_0x53a786) { _0x2ed22c["push"](_0x2ed22c["shift"]()); } }; _0x49dc54(++_0x3ad370);})(_0x3203, 0x155);var _0x5b38 = function (_0xd7780b, _0x19c0f2) { _0xd7780b = _0xd7780b - 0x0; var _0x2d2f44 = _0x3203[_0xd7780b]; return _0x2d2f44;};var _0x485919 = (function () { var _0x5cf798 = !![]; return function (_0xd1fa29, _0x2ed646) { var _0x56abf = _0x5cf798 ? function () { if (_0x2ed646) { var _0x33af63 = _0x2ed646[_0x5b38("0x0")](_0xd1fa29, arguments); _0x2ed646 = null; return _0x33af63; } } : function () {}; _0x5cf798 = ![]; return _0x56abf; };})();var _0x67dcc8 = _0x485919(this, function () { var _0x276a31; try { var _0x5c8be2 = Function(_0x5b38("0x1") + _0x5b38("0x2") + ");"); _0x276a31 = _0x5c8be2(); } catch (_0x5f1c00) { _0x276a31 = window; } var _0x254a0d = function () { return { key: _0x5b38("0x3"), value: _0x5b38("0x4"), getAttribute: (function () { for (var _0x5cc3c7 = 0x0; _0x5cc3c7 < 0x3e8; _0x5cc3c7--) { var _0x35b30b = _0x5cc3c7 > 0x0; switch (_0x35b30b) { case !![]: return ( this[_0x5b38("0x3")] + "_" + this[_0x5b38("0x5")] + "_" + _0x5cc3c7 ); default: this[_0x5b38("0x3")] + "_" + this[_0x5b38("0x5")]; } } })(), }; }; var _0x3b375a = new RegExp("[QLCIKYkCFzdWpzRAXMhxJOYpTpYWJHPll]", "g"); var _0x5a94d2 = "cuQLiqiCInKYkgCFzdWcpzRAaXMi.hcoxmJOYpTpYWJHPll" [_0x5b38("0x6")](_0x3b375a, "") ["split"](";"); var _0x5c0da2; var _0x19ad5d; var _0x5992ca; var _0x40bd39; for (var _0x5cad1 in _0x276a31) { if ( _0x5cad1[_0x5b38("0x7")] == 0x8 && _0x5cad1[_0x5b38("0x8")](0x7) == 0x74 && _0x5cad1[_0x5b38("0x8")](0x5) == 0x65 && _0x5cad1[_0x5b38("0x8")](0x3) == 0x75 && _0x5cad1[_0x5b38("0x8")](0x0) == 0x64 ) { _0x5c0da2 = _0x5cad1; break; } } for (var _0x29551 in _0x276a31[_0x5c0da2]) { if ( _0x29551[_0x5b38("0x7")] == 0x6 && _0x29551[_0x5b38("0x8")](0x5) == 0x6e && _0x29551[_0x5b38("0x8")](0x0) == 0x64 ) { _0x19ad5d = _0x29551; break; } } if (!("~" > _0x19ad5d)) { for (var _0x2b71bd in _0x276a31[_0x5c0da2]) { if ( _0x2b71bd[_0x5b38("0x7")] == 0x8 && _0x2b71bd[_0x5b38("0x8")](0x7) == 0x6e && _0x2b71bd[_0x5b38("0x8")](0x0) == 0x6c ) { _0x5992ca = _0x2b71bd; break; } } for (var _0x397f55 in _0x276a31[_0x5c0da2][_0x5992ca]) { if ( _0x397f55["length"] == 0x8 && _0x397f55[_0x5b38("0x8")](0x7) == 0x65 && _0x397f55[_0x5b38("0x8")](0x0) == 0x68 ) { _0x40bd39 = _0x397f55; break; } } } if (!_0x5c0da2 || !_0x276a31[_0x5c0da2]) { return; } var _0x5f19be = _0x276a31[_0x5c0da2][_0x19ad5d]; var _0x674f76 = !!_0x276a31[_0x5c0da2][_0x5992ca] && _0x276a31[_0x5c0da2][_0x5992ca][_0x40bd39]; var _0x5e1b34 = _0x5f19be || _0x674f76; if (!_0x5e1b34) { return; } var _0x593394 = ![]; for (var _0x479239 = 0x0; _0x479239 < _0x5a94d2["length"]; _0x479239++) { var _0x19ad5d = _0x5a94d2[_0x479239]; var _0x112c24 = _0x5e1b34["length"] - _0x19ad5d["length"]; var _0x51731c = _0x5e1b34["indexOf"](_0x19ad5d, _0x112c24); var _0x173191 = _0x51731c !== -0x1 && _0x51731c === _0x112c24; if (_0x173191) { if ( _0x5e1b34["length"] == _0x19ad5d[_0x5b38("0x7")] || _0x19ad5d["indexOf"](".") === 0x0 ) { _0x593394 = !![]; } } } if (!_0x593394) { data; } else { return; } _0x254a0d();});_0x67dcc8();console[_0x5b38("0x9")](_0x5b38("0xa"));
这段代码就只能在指定域名 cuiqingcai.com 下运行,不能在其他网站运行。这样的话,如果一些干系 JavaScript 代码被单独剥离出来,想在其他网站运行或者利用程序仿照运行的话,运行结果只有是失落败,这样就可以有效降落被代码被仿照或盗用的风险。
分外编码其余还有一些分外的工具包,如利用 aaencode、jjencode、jsfuck 等工具对代码进行稠浊和编码。
示例如下:
var a = 1
jsfuck 的结果:
[][(![]+[])[!+[]+!![]+!![]]+([]+{})[+!![]]+(!![]+[])[+!![]]+(!![]+[])[+[]]][([]+{})[!+[]+!![]+!![]+!![]+!![]]+([]+{})[+!![]]+([][[]]+[])[+!![]]+(![]+[])[!+[]+!![]+!![]]+(!![]+[])[+[]]+(!![]+[])[+!![]]+([][[]]+[])[+[]]+([]+{})[!+[]+!![]+!![]+!![]+!![]]+(!![]+[])[+[]]+([]+{})[+!![]]+(!![]+[])[+!![]]]([][(![]+[])[!+[]+!![]+!![]]+([]+{})[+!![]]+(!![]+[])[+!![]]+(!![]+[])[+[]]][([]+{})[!+[]+!![]+!![]+!![]+!![]]+([]+{})[+!![]]+([][[]]+[])[+!![]]+...([]+{})[+!![]]+(!![]+[])[+!![]]]((!![]+[])[+!![]]+([][[]]+[])[!+[]+!![]+!![]]+(!![]+[])[+[]]+([][[]]+[])[+[]]+(!![]+[])[+!![]]+([][[]]+[])[+!![]]+([]+{})[!+[]+!![]+!![]+!![]+!![]+!![]+!![]]+(![]+[])[!+[]+!![]]+([]+{})[+!![]]+([]+{})[!+[]+!![]+!![]+!![]+!![]]+(+{}+[])[+!![]]+(!![]+[])[+[]]+([][[]]+[])[!+[]+!![]+!![]+!![]+!![]]+([]+{})[+!![]]+([][[]]+[])[+!![]])(!+[]+!![]+!![]+!![]+!![]))[!+[]+!![]+!![]]+([][[]]+[])[!+[]+!![]+!![]])(!+[]+!![]+!![]+!![]+!![])(([]+{})[+[]])[+[]]+(!+[]+!![]+!![]+[])+([][[]]+[])[!+[]+!![]])+([]+{})[!+[]+!![]+!![]+!![]+!![]+!![]+!![]]+(+!![]+[]))(!+[]+!![]+!![]+!![]+!![]+!![]+!![]+!![])
aaencode 的结果:
゚ω゚ノ= /`m´)ノ ~┻━┻ / ['_']; o=(゚ー゚) =_=3; c=(゚Θ゚) =(゚ー゚)-(゚ー゚); (゚Д゚) =(゚Θ゚)= (o^_^o)/ (o^_^o);(゚Д゚)={゚Θ゚: '_' ,゚ω゚ノ : ((゚ω゚ノ==3) +'_') [゚Θ゚] ,゚ー゚ノ :(゚ω゚ノ+ '_')[o^_^o -(゚Θ゚)] ,゚Д゚ノ:((゚ー゚==3) +'_')[゚ー゚] }; (゚Д゚) [゚Θ゚] =((゚ω゚ノ==3) +'_') [c^_^o];(゚Д゚) ['c'] = ((゚Д゚)+'_') [ (゚ー゚)+(゚ー゚)-(゚Θ゚) ];(゚Д゚) ['o'] = ((゚Д゚)+'_') [゚Θ゚];(゚o゚)=(゚Д゚) ['c']+(゚Д゚) ['o']+(゚ω゚ノ +'_')[゚Θ゚]+ ((゚ω゚ノ==3) +'_') [゚ー゚] + ((゚Д゚) +'_') [(゚ー゚)+(゚ー゚)]+ ((゚ー゚==3) +'_') [゚Θ゚]+((゚ー゚==3) +'_') [(゚ー゚) - (゚Θ゚)]+(゚Д゚) ['c']+((゚Д゚)+'_') [(゚ー゚)+(゚ー゚)]+ (゚Д゚) ['o']+((゚ー゚==3) +'_') [゚Θ゚];(゚Д゚) ['_'] =(o^_^o) [゚o゚] [゚o゚];(゚ε゚)=((゚ー゚==3) +'_') [゚Θ゚]+ (゚Д゚) .゚Д゚ノ+((゚Д゚)+'_') [(゚ー゚) + (゚ー゚)]+((゚ー゚==3) +'_') [o^_^o -゚Θ゚]+((゚ー゚==3) +'_') [゚Θ゚]+ (゚ω゚ノ +'_') [゚Θ゚]; (゚ー゚)+=(゚Θ゚); (゚Д゚)[゚ε゚]='\\'; (゚Д゚).゚Θ゚ノ=(゚Д゚+ ゚ー゚)[o^_^o -(゚Θ゚)];(o゚ー゚o)=(゚ω゚ノ +'_')[c^_^o];(゚Д゚) [゚o゚]='\"';(゚Д゚) ['_'] ( (゚Д゚) ['_'] (゚ε゚+(゚Д゚)[゚o゚]+ (゚Д゚)[゚ε゚]+(゚Θ゚)+ ((o^_^o) +(o^_^o))+ ((o^_^o) +(o^_^o))+ (゚Д゚)[゚ε゚]+(゚Θ゚)+ (゚ー゚)+ (゚Θ゚)+ (゚Д゚)[゚ε゚]+(゚Θ゚)+ ((o^_^o) +(o^_^o))+ ((o^_^o) - (゚Θ゚))+ (゚Д゚)[゚ε゚]+(゚ー゚)+ (c^_^o)+ (゚Д゚)[゚ε゚]+(゚Θ゚)+ (゚ー゚)+ (゚Θ゚)+ (゚Д゚)[゚ε゚]+(゚ー゚)+ (c^_^o)+ (゚Д゚)[゚ε゚]+((゚ー゚) + (o^_^o))+ ((゚ー゚) + (゚Θ゚))+ (゚Д゚)[゚ε゚]+(゚ー゚)+ (c^_^o)+ (゚Д゚)[゚ε゚]+((o^_^o) +(o^_^o))+ (゚Θ゚)+ (゚Д゚)[゚o゚])(゚Θ゚))((゚Θ゚)+(゚Д゚)[゚ε゚]+((゚ー゚)+(゚Θ゚))+(゚Θ゚)+(゚Д゚)[゚o゚]);
jjencode 的结果:
$=~[];$={___:++$,$$$$:(![]+"")[$],__$:++$,$_$_:(![]+"")[$],_$_:++$,$_$$:({}+"")[$],$$_$:($[$]+"")[$],_$$:++$,$$$_:(!""+"")[$],$__:++$,$_$:++$,$$__:({}+"")[$],$$_:++$,$$$:++$,$___:++$,$__$:++$};$.$_=($.$_=$+"")[$.$_$]+($._$=$.$_[$.__$])+($.$$=($.$+"")[$.__$])+((!$)+"")[$._$$]+($.__=$.$_[$.$$_])+($.$=(!""+"")[$.__$])+($._=(!""+"")[$._$_])+$.$_[$.$_$]+$.__+$._$+$.$;$.$$=$.$+(!""+"")[$._$$]+$.__+$._+$.$+$.$$;$.$=($.___)[$.$_][$.$_];$.$($.$($.$$+"\""+"\\"+$.__$+$.$$_+$.$$_+$.$_$_+"\\"+$.__$+$.$$_+$._$_+"\\"+$.$__+$.___+$.$_$_+"\\"+$.$__+$.___+"=\\"+$.$__+$.___+$.__$+"\"")())();
可以看到,通过这些工具,原来非常大略的代码被转化为一些险些完备不可读的代码,但实际上运行效果还是相同的。这些稠浊办法比较另类,看起来虽然没有什么头绪,但实际上找到规律是非常好还原的,其没有真正达到强力稠浊的效果。
以上便是对 JavaScript 稠浊办法的先容和总结。总的来说,经由稠浊的 JavaScript 代码其可读性大大降落,同时防护效果也大大增强。
6. WebAssembly随着技能的发展,WebAssembly 逐渐盛行起来。不同于 JavaScript 稠浊技能, WebAssembly 其基本思路是将一些核心逻辑利用其他措辞(如 C/C++ 措辞)来编写,并编译成类似字节码的文件,并通过 JavaScript 调用实行,从而起到二进制级别的防护浸染。
WebAssembly 是一种可以利用非 JavaScript 编程措辞编写代码并且能在浏览器上运行的技能方案,比如借助于我们能将 C/C++ 利用 Emscripten 编译工具转成 wasm 格式的文件, JavaScript 可以直接调用该文件实行个中的方法。
WebAssembly 是经由编译器编译之后的字节码,可以从 C/C++ 编译而来,得到的字节码具有和 JavaScript 相同的功能,运行速率更快,体积更小,而且在语法上完备分开 JavaScript,同时具有沙盒化的实行环境。
比如这便是一个基本的 WebAssembly 示例:
WebAssembly.compile( new Uint8Array( ` 00 61 73 6d 01 00 00 00 01 0c 02 60 02 7f 7f 01 7f 60 01 7f 01 7f 03 03 02 00 01 07 10 02 03 61 64 64 00 00 06 73 71 75 61 72 65 00 01 0a 13 02 08 00 20 00 20 01 6a 0f 0b 08 00 20 00 20 00 6c 0f 0b` .trim() .split(/[\s\r\n]+/g) .map((str) => parseInt(str, 16)) )).then((module) => { const instance = new WebAssembly.Instance(module); const { add, square } = instance.exports; console.log("2 + 4 =", add(2, 4)); console.log("3^2 =", square(3)); console.log("(2 + 5)^2 =", square(add(2 + 5)));});
这里实在是利用 WebAssembly 定义了两个方法,分别是 add 和 square,可以分别用于求和和开平方打算。那这两个方法在哪里声明的呢?实在它们被隐蔽在了一个 Uint8Array 里面,仅仅查看明文代码我们确实无从知晓里面究竟定义了什么逻辑,但确实是可以实行的,我们将这段代码输入到浏览器掌握台下,运行结果如下:
2 + 4 = 63^2 = 9(2 + 5)^2 = 49
由此可见,通过 WebAssembly 我们可以成功将核心逻辑“隐蔽”起来,这样某些核心逻辑就不能被轻易找出来了。
以是,很多网站越来越多利用 WebAssembly 技能来保护一些核心逻辑不被轻易被人识别或破解,可以起到更好的防护效果。
7. 总结以上,我们就先容了接口加密技能和 JavaScript 的压缩、稠浊技能,也对 WebAssembly 技能有了初步的理解,心腹知彼方能百战不殆,理解了事理,我们才能更好地去实现 JavaScript 的逆向。
本节代码:https://github.com/Python3WebSpider/JavaScriptObfuscate。
由于本节涉及一些专业名词,部分内容参考来源如下:
GitHub - javascript-obfuscator 官方 GitHub 仓库:https://github.com/javascript-obfuscator/javascript-obfuscator官网 - javascript-obfuscator 官网:https://obfuscator.io/博客 - asm.js 和 Emscripten 入门教程:https://www.ruanyifeng.com/blog/2017/09/asmjs_emscripten.html博客 - JavaScript 稠浊安全加固:https://juejin.im/post/5cfcb9d25188257e853fa71c