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?

相关文章
|
1天前
|
机器学习/深度学习 自动驾驶 人机交互
深度学习之虚拟人类行为模拟
基于深度学习的虚拟人类行为模拟是指使用深度学习技术来模仿和预测虚拟环境中人类的行为,从而创建逼真的、智能化的虚拟角色。
11 4
|
1天前
|
缓存 监控 JavaScript
|
1天前
|
Web App开发 数据采集 JavaScript
JavaScript动态渲染页面爬取——Selenium的使用(二)
JavaScript动态渲染页面爬取——Selenium的使用(二)
|
1天前
|
安全
DVWA环境【文件上传漏洞】安全low级别存在bug?
DVWA环境【文件上传漏洞】安全low级别存在bug?
|
6天前
|
自动驾驶 5G
5G技术中的时分双工(TDD)与频分双工(FDD)的应用区别
5G技术中的时分双工(TDD)与频分双工(FDD)的应用区别
149 63
|
1天前
|
数据采集 JSON 算法
Python爬虫——基于JWT的模拟登录爬取实战
Python爬虫——基于JWT的模拟登录爬取实战
Python爬虫——基于JWT的模拟登录爬取实战
|
1天前
|
Web App开发 前端开发 JavaScript
JavaScript动态渲染页面爬取——Selenium的使用(一)
JavaScript动态渲染页面爬取——Selenium的使用(一)
10 4
|
6天前
|
机器学习/深度学习 存储 自然语言处理
NLP 面试揭秘:解锁 注意力机制
NLP 面试揭秘:解锁 注意力机制
93 65
NLP 面试揭秘:解锁 注意力机制
|
1天前
|
数据采集 自然语言处理 API
Python反爬案例——验证码的识别
Python反爬案例——验证码的识别
|
1天前
|
存储 数据处理 Python
Python读写文件
Python读写文件
84 60