JavaScript逆向爬虫(一)

本文涉及的产品
密钥管理服务KMS,1000个密钥,100个凭据,1个月
简介: JavaScript逆向爬虫(一)

JavaScript逆向爬虫
随着前端技术的发展,前端代码的打包技术、混淆技术、加密技术也层出不穷,各个公司可以在前端对JavaScript代码采取一定的保护,比如变量混淆、执行逻辑混淆、反调试、核心逻辑加密等,这些保护手段使得我们没法很轻易地找出JavaScript代码中包含的执行逻辑。

针对这些反爬防护措施,解决方案:逆向JavaScript代码,找出其中的加密逻辑,直接实现该加密逻辑进行爬取。如果加密逻辑过于复杂,我们也可以找出一些关键入口,从而实现对加密逻辑的单独模拟执行和数据爬取。常用的JavaScript逆向技巧,包括浏览器工具的使用、Hook技术、AST技术、特殊混淆技术的处理、WebAssembly技术的处理。掌握这些技术,可以更从容的应对JavaScript防护技术。

网站加密和混淆技术简介
爬取网站的时候,会遇到一些需要分析接口或URL信息的情况,这时会有各种各样类似加密的情形。

  • 某个网站的URL带有一些看不太懂的长串加密参数,要抓取就必须懂得这些参数是怎么构造的,否则我们连完整的URL都构造不出来,更不用说爬取了。
  • 在分析某个网站的Ajax接口时,可以看到接口的一些参数也是加密的,Request Headers里面也可能带有一些加密参数,如果不知道这些参数的具体构造逻辑,就没法直接用程序模拟这些Ajax请求。
  • 查看网站的JavaScript源代码,可以发现很多压缩了或者看不太懂的字符,比如JavaScript文件名编码,文件的内容被压缩成几行,变量被修改成单个字符或者一些十六进制字符……
    这些导致我们无法轻易根据JavaScript源代码找出某些接口的加密逻辑。
    以上的保护措施,可以归为两大类:
  1. URL/API参数加密
  2. 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?

相关文章
|
7月前
|
数据采集 Web App开发 JavaScript
JavaScript爬虫进阶攻略:从网页采集到数据可视化
JavaScript爬虫进阶攻略:从网页采集到数据可视化
|
2月前
|
数据采集 JSON 前端开发
JavaScript逆向爬虫实战分析
JavaScript逆向爬虫实战分析
31 4
|
2月前
|
数据采集 JavaScript 前端开发
JavaScript逆向爬虫——使用Python模拟执行JavaScript
JavaScript逆向爬虫——使用Python模拟执行JavaScript
36 2
|
2月前
|
数据采集 JavaScript 前端开发
JavaScript逆向爬虫——无限debugger的原理与绕过
JavaScript逆向爬虫——无限debugger的原理与绕过
88 2
|
2月前
|
数据采集 编解码 前端开发
JavaScript逆向爬虫(二)
JavaScript逆向爬虫(二)
27 1
|
1月前
|
数据采集 JavaScript 前端开发
JavaScript重定向对网络爬虫的影响及处理
JavaScript重定向对网络爬虫的影响及处理
|
4月前
|
数据采集 资源调度 JavaScript
Node.js 适合做高并发、I/O密集型项目、轻量级实时应用、前端构建工具、命令行工具以及网络爬虫和数据处理等项目
【8月更文挑战第4天】Node.js 适合做高并发、I/O密集型项目、轻量级实时应用、前端构建工具、命令行工具以及网络爬虫和数据处理等项目
61 5
|
5月前
|
数据采集 JavaScript Python
【JS逆向课件:第十三课:异步爬虫】
回调函数就是回头调用的函数
|
6月前
|
数据采集 Web App开发 XML
详尽分享用Node.js写爬虫,撸羞羞的图片
详尽分享用Node.js写爬虫,撸羞羞的图片
38 0
|
6月前
|
数据采集 前端开发 JavaScript
Python爬虫技术:动态JavaScript加载音频的解析
Python爬虫技术:动态JavaScript加载音频的解析