JavaScript逆向爬虫
随着前端技术的发展,前端代码的打包技术、混淆技术、加密技术也层出不穷,各个公司可以在前端对JavaScript代码采取一定的保护,比如变量混淆、执行逻辑混淆、反调试、核心逻辑加密等,这些保护手段使得我们没法很轻易地找出JavaScript代码中包含的执行逻辑。
针对这些反爬防护措施,解决方案:逆向JavaScript代码,找出其中的加密逻辑,直接实现该加密逻辑进行爬取。如果加密逻辑过于复杂,我们也可以找出一些关键入口,从而实现对加密逻辑的单独模拟执行和数据爬取。常用的JavaScript逆向技巧,包括浏览器工具的使用、Hook技术、AST技术、特殊混淆技术的处理、WebAssembly技术的处理。掌握这些技术,可以更从容的应对JavaScript防护技术。
网站加密和混淆技术简介
爬取网站的时候,会遇到一些需要分析接口或URL信息的情况,这时会有各种各样类似加密的情形。
- 某个网站的URL带有一些看不太懂的长串加密参数,要抓取就必须懂得这些参数是怎么构造的,否则我们连完整的URL都构造不出来,更不用说爬取了。
- 在分析某个网站的Ajax接口时,可以看到接口的一些参数也是加密的,Request Headers里面也可能带有一些加密参数,如果不知道这些参数的具体构造逻辑,就没法直接用程序模拟这些Ajax请求。
- 查看网站的JavaScript源代码,可以发现很多压缩了或者看不太懂的字符,比如JavaScript文件名编码,文件的内容被压缩成几行,变量被修改成单个字符或者一些十六进制字符……
这些导致我们无法轻易根据JavaScript源代码找出某些接口的加密逻辑。
以上的保护措施,可以归为两大类:
- URL/API参数加密
- JavaScript压缩、混淆和加密
网站数据防护方案
- URL/API参数加密
比如客户端和服务端约定一种接口校验逻辑,客户端在每次请求服务端接口的时候都会附带一个sign参数,这个sign参数可能是由当前时间信息、请求的URL、请求的数据、设备的ID、双方约定好的密钥经过一些加密算法构造而成的,客户端会实现这个加密算法来构造sign,然后每次请求服务器的时候附带这个参数。服务端会根据约定好的算法和请求的数据对sign进行校验,只有校验通过,才返回对应的数据,否则拒绝响应。
登录状态的校验也可以看作此类方案,比如一个API的调用必须传一个token,这个token必须在用户登录后才能获取,如果请求的时候不带token,API就不会返回任何数据。
JavaScript压缩、混淆和加密
接口加密技术看起来的确是一个不错的解决方案,但单纯依靠它并不能很好地解决问题,为啥呢?
对于网页来说,其逻辑是依赖于JavaScript来实现的。JavaScript有如下特点。
- JavaScript代码运行于客户端,也就是它必须在用户浏览器加载并运行。
- JavaScript代码是公开透明的,也就是说浏览器可以直接获取到正在运行的JavaScript的源码。
基于这两个原因,JavaScript代码是不安全的,任何人都可以读、分析、复制、盗用甚至篡改代码。
想要实现数据的防护和安全,那么就需要用到JavaScript压缩、混淆和加密技术了。
压缩、混淆和加密技术简述如下:
a. JavaScript压缩:
Minification(最小化):通过删除注释、空格和不必要的字符,将JavaScript代码压缩到最小体积,从而减少文件大小以提高加载速度。工具如UglifyJS、Terser等可用于最小化JavaScript代码。
Tree Shaking:移除未使用的代码,只保留被引用的部分,进一步减小文件体积。
b. JavaScript混淆:
变量重命名:将变量、函数名和参数进行混淆,使代码更难以理解。例如将var userName = 'John';变成var a = 'John';。
字符串混淆:对字符串进行编码或加密,使其在代码中难以识别。例如将明文字符串转换为Base64编码。
函数内联:将函数内联到调用处,减少函数调用开销并增加代码复杂度。
c. JavaScript加密:
- 加密算法:使用加密算法(如AES、RSA)对JavaScript代码进行加密,在运行时解密执行。这样可以保护代码逻辑,但会增加性能开销。
- 解密密钥:确保密钥安全存储,以免遭到恶意获取。
1.URL/API参数加密
- 现在绝大多数网站的数据一般都是通过服务器提供的API来获取的,网站或App可以请求某个时间API获取到对应的数据,然后再把获取的数据展示出来。不同API的实现对应着不同的安全防护级别。
- 为了提升接口的安全性,客户端会和服务端约定一种接口校验方式,一般来说会用到各种加密和编码算法,如Base64、Hex编码、MD5、AES、DES、RSA等对称或非对称加密。
- 例如客户端和服务端约定一个sign用作接口的签名校验,其生成逻辑是客户端URL路径进行MD5加密,然后拼接上URL的某个参数再进行Base64编码,最后得到一个字符串sign,这个sign会通过Request URL的某个参数或Request Headers发送给服务器。服务器接收到请求后,对URL路径同样进行MD5加密,然后拼接上URL的某个参数,进行Base64编码,也会得到一个sign。接着比对生成的sign和客户端发来的sign是否一致,如果一致,就返回正确的结果,否则拒绝响应。如果有人想要调用这个接口的话,必须定义好sign的生成逻辑,否则无法正常调用接口的。
2.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)};
这里的参数都被简化了,代码中的空格也去掉了,被压缩成一行,整体可读性降低了。
JavaScript压缩技术只能在很小程度上起到防护作用,要想真正提高防护效果,还得依靠JavaScript混淆和加密技术。
a. JavaScript混淆
JavaScript混淆技术是一种通过修改源代码的结构、格式和命名规则,使其难以阅读和理解的方法。混淆技术可以增加代码的复杂性,提高代码的安全性,防止他人轻易理解和反编译代码。以下是一些常见的JavaScript混淆技术:
b. 变量重命名:将变量、函数名和参数重新命名为无意义的短字符或符号,使代码更加晦涩。例如,将userName重命名为a。
c. 字符串混淆:对字符串进行编码或加密处理,以防止直接查看和识别明文内容。例如,将明文字符串转换为Base64编码或使用简单的替换算法。
d. 控制流转换:修改代码中的条件语句和循环结构,使代码的执行顺序变得随机化或不易理解。这可以通过代码展开、逻辑重组等方式实现。
e. 函数内联:将函数内容内联到调用处,减少函数调用开销,并增加代码复杂性。这可以使代码更加难以理解,尤其在函数数量众多的情况下。
f. 代码拆分:将代码拆分成多个文件或模块,然后混合和重组这些模块,使得代码的执行路径变得更加难以跟踪和理解。
g. 虚假代码插入:在代码中插入一些没有实际作用的代码片段,以增加代码量和混淆对手。
h. 死代码注入:在代码中添加无效的代码块或条件,使反混淆者难以分辨哪些代码段是实际有效的。
i. 混淆器工具:使用专门设计的JavaScript混淆器工具,如UglifyJS、 Obfuscator.io、Javascript-obfuscator等,自动化实现代码混淆。
j. 域名锁定:使JavaScript代码只能在指定域名下执行。
k. 特殊编码:使JavaScript完全编码为人不可读的代码,如表情符号、特殊表示内容,等等。
以上方案都是JavaScript混淆的实现方式,可以在不同程度上保护JavaScript代码。
在前端开发中,现在JavaScript混淆的主流实现是javascript-obfuscator和terser这两个库。它们都能提供一些代码混淆功能,也都有对应的webpack和Rollup打包工具插件。利用它们,可以方便地实现页面的混淆,最终输出压缩和混淆后的JavaScript代码,使得JavaScript代码的可读性大大降低。
以javascript-obfuscator为例,首先,需要安装好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' + 1
console.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,需要被混淆的代码;另一个混淆选项options,是一个Object。接下来,引入javascript-obfuscator这个库,然后定义了一个方法,给其传入code和options来获取混淆后的代码,最后控制台输出混淆后的代码。
代码逻辑比较简单,执行一下代码:
node main.js
输出结果如下:
function _0x3901(_0x397ea4, _0x34af54) {
const _0x1492ed = _0x1492();
return _0x3901 = function (_0x3901b0, _0x3bfff4) {
_0x3901b0 = _0x3901b0 - 0xc4;
let _0x517f84 = _0x1492ed[_0x3901b0];
return _0x517f84;
}, _0x3901(_0x397ea4, _0x34af54);
}
(function (_0x241ce0, _0x212e8f) {
const _0x18ac11 = _0x3901, _0x50c203 = _0x241ce0();
while (!![]) {
try {
const _0x2b0064 = -parseInt(_0x18ac11(0xc9)) / 0x1 * (parseInt(_0x18ac11(0xca)) / 0x2) + -parseInt(_0x18ac11(0xc6)) / 0x3 * (-parseInt(_0x18ac11(0xcb)) / 0x4) + -parseInt(_0x18ac11(0xc4)) / 0x5 * (parseInt(_0x18ac11(0xcd)) / 0x6) + -parseInt(_0x18ac11(0xce)) / 0x7 * (-parseInt(_0x18ac11(0xcf)) / 0x8) + -parseInt(_0x18ac11(0xc5)) / 0x9 * (parseInt(_0x18ac11(0xc7)) / 0xa) + parseInt(_0x18ac11(0xc8)) / 0xb + -parseInt(_0x18ac11(0xcc)) / 0xc;
if (_0x2b0064 === _0x212e8f)
break;
else
_0x50c203['push'](_0x50c203['shift']());
} catch (_0x34bddd) {
_0x50c203['push'](_0x50c203['shift']());
}
}
}(_0x1492, 0x26b62));
let x = '1' + 0x1;
console['log']('x', x);
function _0x1492() {
const _0x3a35ae = [
'15002jmoENJ',
'588lKEAkD',
'155052pDmOSa',
'28602jVtPmw',
'175jXAnkL',
'30952fAYWHu',
'20PjKACo',
'19017dFTBFv',
'3360ROyzrR',
'500TfAfCx',
'960773RpCnER',
'7nRkobq'
];
_0x1492 = function () {
return _0x3a35ae;
};
return _0x1492();
}
看到了吧,那么简单的代码,被我们混淆成了这个样子,其实这里我们就是设定了“控制流平坦化”选项。整体看来, 代码的可读性大大降低了,JavaScript调试的难度也大大加大了。
代码压缩
这里javascript-obfuscator也提供了代码压缩的功能,使用其参数compact即可完成JavaScript代码的压缩,输出为一行内容。参数compact的默认值是true,如果定义为false,则混淆后的代码会分行显示。
示例如下:
const code = `
let x = '1' + 1
console.log('x', x)
`
const options = {
compact:false,
}
这里我们先把代码压缩选项的参数compact设置为false,运行结果如下:
let x = '1' + 0x1;
console['log']('x', x);
如果不设置compact 或者把compact设置为true,结果如下:
node main.js
const _0x14bd37=_0x49a1;(function(_0x239bc0,_0x1e0569){
const _0x2eaf9f=_0x49a1,_0x50afa7=_0x239bc0();while(!![]){
try{
const _0x43de9a=parseInt(_0x2eaf9f(0x1aa))/0x1+parseInt(_0x2eaf9f(0x1a3))/0x2*(-parseInt(_0x2eaf9f(0x1a9))/0x3)+parseInt(_0x2eaf9f(0x1a4))/0x4*(-parseInt(_0x2eaf9f(0x1a5))/0x5)+-parseInt(_0x2eaf9f(0x1a8))/0x6+parseInt(_0x2eaf9f(0x1a7))/0x7*(parseInt(_0x2eaf9f(0x1ac))/0x8)+-parseInt(_0x2eaf9f(0x1ab))/0x9*(-parseInt(_0x2eaf9f(0x1a2))/0xa)+-parseInt(_0x2eaf9f(0x1a6))/0xb*(parseInt(_0x2eaf9f(0x1ae))/0xc);if(_0x43de9a===_0x1e0569)break;else _0x50afa7['push'](_0x50afa7['shift']());}catch(_0x57e668){
_0x50afa7['push'](_0x50afa7['shift']());}}}(_0x23d7,0xabd3b));function _0x23d7(){
const _0x267d9d=['44wvZcig','7FZYYIp','2009382VavdPg','57klbHPK','209656DtLYLS','2295lXMyOh','10153928QCeIxA','log','1038552qpDhiQ','46390KjUfWe','123454QvBTGy','212UuWPlk','9825WBKkTx'];_0x23d7=function(){
return _0x267d9d;};return _0x23d7();}let x='1'+0x1;function _0x49a1(_0x438ab1,_0x261688){
const _0x23d744=_0x23d7();return _0x49a1=function(_0x49a144,_0x41042b){
_0x49a144=_0x49a144-0x1a2;let _0x39db73=_0x23d744[_0x49a144];return _0x39db73;},_0x49a1(_0x438ab1,_0x261688);}console[_0x14bd37(0x1ad)]('x',x);
变量名混淆
变量名混淆可以通过在javascript-obfuscator中配置identifierNamesGenerator参数来实现。我们通过这个参数可以控制变量名混淆的方式,如将其设置为hexadecimal,则会将变量名替换为十六进制形式的字符串。该参数的取值如下。
hexadecimal: 将变量名替换为十六进制形式的字符串,如0xabc123
mangled: 将变量名替换为普通的简写字符,如a、b、c等。
该参数的默认值为hexadecimal。
我们将该参数修改为mangled来试一下:
const code = `
let hello = '1' + 1
console.log('hello', hello)
`
const options = {
compact: true,
identifierNamesGenerator: 'mangled'
}
const obfuscator = require('javascript-obfuscator')
function obfuscate(code, options) {
return obfuscator.obfuscate(code, options).getObfuscatedCode()
}
console.log(obfuscate(code, options))
运行结果如下:
const i=b;function b(c,d){
const e=a();return b=function(f,g){
f=f-0x19c;let h=e[f];return h;},b(c,d);}(function(c,d){
const h=b,e=c();while(!![]){
try{
const f=-parseInt(h(0x1a9))/0x1*(-parseInt(h(0x19f))/0x2)+parseInt(h(0x1a8))/0x3*(-parseInt(h(0x19d))/0x4)+-parseInt(h(0x19c))/0x5+-parseInt(h(0x1a6))/0x6*(parseInt(h(0x1a2))/0x7)+parseInt(h(0x1a5))/0x8*(parseInt(h(0x1a4))/0x9)+-parseInt(h(0x1a0))/0xa*(parseInt(h(0x1a7))/0xb)+parseInt(h(0x1a1))/0xc;if(f===d)break;else e['push'](e['shift']());}catch(g){
e['push'](e['shift']());}}}(a,0x86880));function a(){
const j=['62268DcCuSV','log','489546hKAdvn','830bWwpWA','21753120Qqqfbu','4049059RQKUcm','hello','1656ATTPcc','19520sPNVyO','6qzRZVj','119262bBRGeV','21HMbwOh','1dPsZVe','1840805sOUQnD'];a=function(){
return j;};return a();}let hello='1'+0x1;console[i(0x19e)](i(0x1a3),hello);
可以看到,这里的变量名都变成了a、b等形式。
如果我们将identifierNamesGenerator修改为hexadecimal或者不设置,运行结果如下:
function _0x37d7(){
const _0x1ce359=['108054HKfmiQ','6618LoSOAr','390woRCwq','log','22932JgBpnU','5214528ZoEooO','199884DMktwc','hello','265208hUtRwu','90UGPBTa','1229018eMvTDk','6ukGXeH','272uFYvwl','10eWBJwn'];_0x37d7=function(){
return _0x1ce359;};return _0x37d7();}const _0x3239af=_0x288b;(function(_0xadb17f,_0x287df9){
const _0x2424ec=_0x288b,_0x169d6a=_0xadb17f();while(!![]){
try{
const _0x500ead=-parseInt(_0x2424ec(0x19c))/0x1+parseInt(_0x2424ec(0x1a8))/0x2*(-parseInt(_0x2424ec(0x19d))/0x3)+parseInt(_0x2424ec(0x1a0))/0x4*(parseInt(_0x2424ec(0x19e))/0x5)+-parseInt(_0x2424ec(0x1a7))/0x6*(-parseInt(_0x2424ec(0x1a6))/0x7)+parseInt(_0x2424ec(0x1a4))/0x8*(-parseInt(_0x2424ec(0x1a5))/0x9)+parseInt(_0x2424ec(0x1a9))/0xa*(parseInt(_0x2424ec(0x1a1))/0xb)+-parseInt(_0x2424ec(0x1a2))/0xc;if(_0x500ead===_0x287df9)break;else _0x169d6a['push'](_0x169d6a['shift']());}catch(_0x554ebe){
_0x169d6a['push'](_0x169d6a['shift']());}}}(_0x37d7,0x5324f));function _0x288b(_0x51ec13,_0x1ecade){
const _0x37d7cf=_0x37d7();return _0x288b=function(_0x288b05,_0x189c08){
_0x288b05=_0x288b05-0x19c;let _0x200f58=_0x37d7cf[_0x288b05];return _0x200f58;},_0x288b(_0x51ec13,_0x1ecade);}let hello='1'+0x1;console[_0x3239af(0x19f)](_0x3239af(0x1a3),hello);
可以看到,选用了mangled,其代码体积更小,但选用hexadecimal到可读性会更低。另外,还可以通过设置identifiersPrefix参数来控制混淆后到变量前缀,示例如下:
const Bruce_Python_0x16e5d9=Bruce_Python_0x2de5;(function(_0x377998,_0x130a48){
const _0x367270=Bruce_Python_0x2de5,_0x3a480b=_0x377998();while(!![]){
try{
const _0x13b168=-parseInt(_0x367270(0x126))/0x1+parseInt(_0x367270(0x129))/0x2*(parseInt(_0x367270(0x12b))/0x3)+-parseInt(_0x367270(0x12c))/0x4+parseInt(_0x367270(0x128))/0x5*(-parseInt(_0x367270(0x12d))/0x6)+-parseInt(_0x367270(0x124))/0x7*(-parseInt(_0x367270(0x12a))/0x8)+parseInt(_0x367270(0x125))/0x9+parseInt(_0x367270(0x127))/0xa*(parseInt(_0x367270(0x12e))/0xb);if(_0x13b168===_0x130a48)break;else _0x3a480b['push'](_0x3a480b['shift']());}catch(_0x406457){
_0x3a480b['push'](_0x3a480b['shift']());}}}(Bruce_Python_0x4f65,0xa5772));let hello='1'+0x1;function Bruce_Python_0x2de5(_0x2ff7c3,_0x466caa){
const _0x4f6530=Bruce_Python_0x4f65();return Bruce_Python_0x2de5=function(_0x2de5dc,_0x5c68da){
_0x2de5dc=_0x2de5dc-0x123;let _0x165420=_0x4f6530[_0x2de5dc];return _0x165420;},Bruce_Python_0x2de5(_0x2ff7c3,_0x466caa);}function Bruce_Python_0x4f65(){
const _0x130db5=['1533IGMGmo','9924147MQciRr','125954UYcDQH','320dyLAeJ','20DNpUrz','65234gnvlgS','12088jYJNMe','39ZUzLTh','1880324umpKui','919140UWcOXh','9944UgQJza','hello'];Bruce_Python_0x4f65=function(){
return _0x130db5;};return Bruce_Python_0x4f65();}console['log'](Bruce_Python_0x16e5d9(0x123),hello);
可以看到,混淆后到变量前缀加上了我们自定义的字符串Bruce_Python。
另外,renameGlobals这个参数还可以指定是否混淆全局变量和函数名称,默认值为false。示例如下:
const code = `
var $ = function(id) {
return document.getElementById(id);
};
`
const options = {
renameGlobals: true
}
const obfuscator = require('javascript-obfuscator')
function obfuscate(code, options) {
return obfuscator.obfuscate(code, options).getObfuscatedCode()
}
console.log(obfuscate(code, options))
运行结果如下:
function _0x2cb7(){
var _0x560738=['170OFwIEg','12LYKASp','5292aVIqMb','1416DlJWQM','1741341kTeKzD','49675kJHwxA','getElementById','344905vYLQcM','158340cXVPdf','48RrdgiG','824965bJsnOe','17058nnhrVL'];_0x2cb7=function(){
return _0x560738;};return _0x2cb7();}function _0x4a01(_0x4dd1a0,_0x2e7701){
var _0x2cb7ef=_0x2cb7();return _0x4a01=function(_0x4a0117,_0x2156e2){
_0x4a0117=_0x4a0117-0x91;var _0x5b33ef=_0x2cb7ef[_0x4a0117];return _0x5b33ef;},_0x4a01(_0x4dd1a0,_0x2e7701);}(function(_0x3062c4,_0x1bc3bb){
var _0x385e8a=_0x4a01,_0x3635d0=_0x3062c4();while(!![]){
try{
var _0x3aa207=-parseInt(_0x385e8a(0x9c))/0x1+parseInt(_0x385e8a(0x96))/0x2*(parseInt(_0x385e8a(0x94))/0x3)+-parseInt(_0x385e8a(0x93))/0x4+-parseInt(_0x385e8a(0x95))/0x5*(-parseInt(_0x385e8a(0x98))/0x6)+parseInt(_0x385e8a(0x9b))/0x7+parseInt(_0x385e8a(0x9a))/0x8*(parseInt(_0x385e8a(0x99))/0x9)+-parseInt(_0x385e8a(0x97))/0xa*(parseInt(_0x385e8a(0x92))/0xb);if(_0x3aa207===_0x1bc3bb)break;else _0x3635d0['push'](_0x3635d0['shift']());}catch(_0x21ef66){
_0x3635d0['push'](_0x3635d0['shift']());}}}(_0x2cb7,0x30182));var _0x418f04=function(_0x430019){
var _0x4f0e1d=_0x4a01;return document[_0x4f0e1d(0x91)](_0x430019);};
可以看到,这里我们声明了一个全局变量这个变量也被替换了。如果后文用到了这个$对象,可能就会有找不到定义的错误,因此这个参数可能导致代码执行不通。
如果我们不设置renameGlobals或者将其设置为false,结果如下:
function _0x313e(_0x35e65a,_0x37f159){
var _0x507669=_0x5076();return _0x313e=function(_0x313e90,_0x1e5b89){
_0x313e90=_0x313e90-0x8a;var _0x1af528=_0x507669[_0x313e90];return _0x1af528;},_0x313e(_0x35e65a,_0x37f159);}(function(_0x38971b,_0x5ce6b6){
var _0x13f61f=_0x313e,_0x47adcc=_0x38971b();while(!![]){
try{
var _0x28ae62=parseInt(_0x13f61f(0x8b))/0x1+-parseInt(_0x13f61f(0x92))/0x2+-parseInt(_0x13f61f(0x8c))/0x3*(parseInt(_0x13f61f(0x90))/0x4)+-parseInt(_0x13f61f(0x95))/0x5*(-parseInt(_0x13f61f(0x94))/0x6)+parseInt(_0x13f61f(0x8f))/0x7+parseInt(_0x13f61f(0x8d))/0x8*(parseInt(_0x13f61f(0x8e))/0x9)+parseInt(_0x13f61f(0x91))/0xa*(-parseInt(_0x13f61f(0x93))/0xb);if(_0x28ae62===_0x5ce6b6)break;else _0x47adcc['push'](_0x47adcc['shift']());}catch(_0x4fe759){
_0x47adcc['push'](_0x47adcc['shift']());}}}(_0x5076,0x49b8f));function _0x5076(){
var _0x182894=['711mjFXTq','998053ISmizq','476996SAPyAI','1184870WQSWpq','578726rrcplt','22swJLmk','1002VpjrKz','9355xnYNZa','getElementById','153250knZupB','6wreAkH','46432iFHoda'];_0x5076=function(){
return _0x182894;};return _0x5076();}var $=function(_0x2d3ba6){
var _0x29ed73=_0x313e;return document[_0x29ed73(0x8a)](_0x2d3ba6);};
可以看到,最后还是有$的声明,其全局名称没有被改变。
接下文 JavaScript逆向爬虫(二)https://developer.aliyun.com/article/1621749?