html网站源码分享怎么用,html网页制作源码

各位老铁们好,相信很多人对html网站源码分享怎么用都不是特别的了解,因此呢,今天就来为大家分享下关于html网站源码分享怎么用以及html网页制作源码的问题知识,还望可以帮助大家,解决大家的一些困惑,下面一起来看看吧!

随着大数据时代的发展,各个公司的数据保护意识越来越强,大家都在想尽办法保护自家产品的数据不轻易被爬虫爬走。由于网页是提供信息和服务的重要载体,所以对网页上的信息进行保护就成了至关重要的一个环节。

网页是运行在浏览器端的,当我们浏览一个网页时,其HTML代码、JavaScript代码都会被下载到浏览器中执行。借助浏览器的开发者工具,我们可以看到网页在加载过程中所有网络请求的详细信息,也能清楚地看到网站运行的HTML代码和JavaScript代码,这些代码中就包含了网站加载的全部逻辑,如加载哪些资源、请求接口是如何构造的、页面是如何渲染的等等。正因为代码是完全透明的,所以如果我们能够把其中的执行逻辑研究出来,就可以模拟各个网络请求进行数据爬取了。

然而,事情没有想象得那么简单。随着前端技术的发展,前端代码的打包技术、混淆技术、加密技术也层出不穷,借助于这些技术,各个公司可以在前端对JavaScript代码采取一定的保护,比如变量名混淆、执行逻辑混淆、反调试、核心逻辑加密等,这些保护手段使得我们没法很轻易地找出JavaScript代码中包含的的执行逻辑。

在前几章的案例中,我们也试着爬取了各种形式的网站。其中有的网站的数据接口是没有任何验证或加密参数的,我们可以轻松模拟并爬取其中的数据;但有的网站稍显复杂,网站的接口中增加了一些加密参数,同时对JavaScript代码采取了上文所述的一些防护措施,当时我们没有直接尝试去破解,而是用Selenium等类似工具来实现模拟浏览器执行的方式来进行“所见即所得“的爬取。其实对于后者,我们还有另外一种解决方案,那就是直接逆向JavaScript代码,找出其中的加密逻辑,从而直接实现该加密逻辑来进行爬取。如果加密逻辑实在过于复杂,我们也可以找出一些关键入口,从而实现对加密逻辑的单独模拟执行和数据爬取。这些方案难度可能很大,比如关键入口很难寻找,或者加密逻辑难以模拟,可是一旦成功找到突破口,我们便可以不用借助于Selenium等工具进行整页数据的渲染而实现数据爬取,这样爬取效率会大幅提升。

本章我们首先会对JavaScript防护技术进行介绍,然后介绍一些常用的JavaScript逆向技巧,包括浏览器工具的使用、Hook技术、AST技术、特殊混淆技术的处理、WebAssembly技术的处理。了解了这些技术,我们可以更从容地应对JavaScript防护技术。

1.引入

我们在爬取网站的时候,会遇到一些情况需要分析一些接口或URL信息,在这个过程中,我们会遇到各种各样类似加密的情形,比如说:

某个网站的URL带有一些看不太懂的长串加密参数,要抓取就必须要懂得这些参数是怎么构造的,否则我们连完整的URL都构造不出来,更不用说爬取了。分析某个网站的Ajax接口的时候,可以看到接口的一些参数也是加密的,或者RequestHeaders里面也可能带有一些加密参数,如果不知道这些参数的具体构造逻辑就没法直接用程序来模拟这些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用作接口的签名校验,其生成逻辑是客户端将URLPath进行MD5加密然后拼接上URL的某个参数再进行Base64编码,最后得到一个字符串sign,这个sign会通过RequestURL的某个参数或RequestHeaders发送给服务器。服务器接收到请求后,对URLPath同样进行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代码是这样的:

functionecho(stringA,stringB){\nconstname=&34;;\nalert(&34;+name);\n}

压缩之后就变成这样子:

functionecho(d,c){\nconste=&34;;\nalert(&34;+e);\n}

可以看到这里参数的名称都被简化了,代码中的空格也被去掉了,整个代码也被压缩成了一行,代码的整体可读性降低了。

目前主流的前端开发技术大多都会利用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/,其官方介绍内容如下:

AfreeandefficientobfuscatorforJavaScript(includingES2017).Makeyourcodehardertocopyandpreventpeoplefromstealingyourwork.

它是支持ES8的免费、高效的JavaScript混淆库,它可以使得你的JavaScript代码经过混淆后难以被复制、盗用,混淆后的代码具有和原来的代码一模一样的功能。

怎么使用呢?首先,我们需要安装好Node.js12.x版本及以上,确保可以正常使用npm命令,具体的安装方式可以参考:https://setup.scrape.center/nodejs。

接着新建一个文件夹,比如js-obfuscate,然后进入该文件夹,初始化工作空间:

npminit

这里会提示我们输入一些信息,创建一个package.json文件,这就完成了项目初始化了。

接下来我们来安装javascript-obfuscator这个库:

npmi-Djavascript-obfuscator

稍等片刻,即可看到本地js-obfuscate文件夹下生成了一个node_modules文件夹,里面就包含了javascript-obfuscator这个库,这就说明安装成功了,文件夹结构如图所示:

接下来我们就可以编写代码来实现一个混淆样例了,如新建一个main.js文件,内容如下:

constcode=`\nletx=&39;+1\nconsole.log(&39;,x)\n`;\n\nconstoptions={\ncompact:false,\ncontrolFlowFlattening:true,\n};\n\nconstobfuscator=require(&34;);\nfunctionobfuscate(code,options){\nreturnobfuscator.obfuscate(code,options).getObfuscatedCode();\n}\nconsole.log(obfuscate(code,options));

在这里我们定义了两个变量,一个是code,即需要被混淆的代码,另一个是混淆选项,是一个Object。接下来我们引入了javascript-obfuscator这库,然后定义了一个方法,传入code和options,来获取混淆后的代码,最后控制台输出混淆后的代码。

代码逻辑比较简单,我们来执行一下代码:

nodemain.js

输出结果如下:

var_0x53bf=[&34;];\n(function(_0x1d84fe,_0x3aeda0){\nvar_0x10a5a=function(_0x2f0a52){\nwhile(–_0x2f0a52){\n_0x1d84fe[&34;](_0x1d84fe[&34;]());\n}\n};\n_0x10a5a(++_0x3aeda0);\n})(_0x53bf,0x172);\nvar_0x480a=function(_0x4341e5,_0x5923b4){\n_0x4341e5=_0x4341e5-0x0;\nvar_0xb3622e=_0x53bf[_0x4341e5];\nreturn_0xb3622e;\n};\nletx=&34;+0x1;\nconsole[_0x480a(&34;)](&34;,x);

看到了吧,那么简单的两行代码,被我们混淆成了这个样子,其实这里我们就是设定了一个「控制流平坦化」的选项。整体看来,代码的可读性大大降低,也大大加大了JavaScript调试的难度。

好,那么我们来跟着javascript-obfuscator走一遍,就能具体知道JavaScript混淆到底有多少方法了。

注意:由于这些例子中,调用javascript-obfuscator进行混淆的实现是一样的,所以下文的示例只说明code和options变量的修改,完整代码请自行补全。

代码压缩

这里javascript-obfuscator也提供了代码压缩的功能,使用其参数compact即可完成JavaScript代码的压缩,输出为一行内容。默认是true,如果定义为false,则混淆后的代码会分行显示。

示例如下:

constcode=`\nletx=&39;+1\nconsole.log(&39;,x)\n`;\nconstoptions={\ncompact:false,\n};

这里我们先把代码压缩compact选项设置为false,运行结果如下:

letx=&34;+0x1;\nconsole[&34;](&34;,x);

如果不设置compact或把compact设置为true,结果如下:

var_0x151c=[&34;];\n(function(_0x1ce384,_0x20a7c7){\nvar_0x25fc92=function(_0x188aec){\nwhile(–_0x188aec){\n_0x1ce384[&34;](_0x1ce384[&34;]());\n}\n};\n_0x25fc92(++_0x20a7c7);\n})(_0x151c,0x1b7);\nvar_0x553e=function(_0x259219,_0x241445){\n_0x259219=_0x259219-0x0;\nvar_0x56d72d=_0x151c[_0x259219];\nreturn_0x56d72d;\n};\nletx=&34;+0x1;\nconsole[_0x553e(&34;)](&34;,x);

可以看到单行显示的时候,对变量名进行了进一步的混淆,这里变量的命名都变成了16进制形式的字符串,这是因为启用了一些默认压缩和混淆配置导致的。总之我们可以看到代码的可读性相比之前大大降低了。

变量名混淆

变量名混淆可以通过在javascript-obfuscator中配置identifierNamesGenerator参数实现,我们通过这个参数可以控制变量名混淆的方式,如hexadecimal则会替换为16进制形式的字符串,在这里我们可以设定如下值:

hexadecimal:将变量名替换为16进制形式的字符串,如0xabc123。mangled:将变量名替换为普通的简写字符,如a、b、c等。

该参数的值默认为hexadecimal。

我们将该参数修改为mangled来试一下:

constcode=`\nlethello=&39;+1\nconsole.log(&39;,hello)\n`;\nconstoptions={\ncompact:true,\nidentifierNamesGenerator:&34;,\n};

运行结果如下:

vara=[&34;];\n(function(c,d){\nvare=function(f){\nwhile(–f){\nc[&34;](c[&34;]());\n}\n};\ne(++d);\n})(a,0x9b);\nvarb=function(c,d){\nc=c-0x0;\nvare=a[c];\nreturne;\n};\nlethello=&34;+0x1;\nconsole[&34;](b(&34;),hello);

可以看到这里的变量命名都变成了a、b等形式。

如果我们将identifierNamesGenerator修改为hexadecimal或者不设置,运行结果如下:

var_0x4e98=[&34;,&34;];\n(function(_0x4464de,_0x39de6c){\nvar_0xdffdda=function(_0x6a95d5){\nwhile(–_0x6a95d5){\n_0x4464de[&34;](_0x4464de[&34;]());\n}\n};\n_0xdffdda(++_0x39de6c);\n})(_0x4e98,0xc8);\nvar_0x53cb=function(_0x393bda,_0x8504e7){\n_0x393bda=_0x393bda-0x0;\nvar_0x46ab80=_0x4e98[_0x393bda];\nreturn_0x46ab80;\n};\nlethello=&34;+0x1;\nconsole[_0x53cb(&34;)](_0x53cb(&34;),hello);

可以看到选用了mangled,其代码体积会更小,但hexadecimal其可读性会更低。

另外我们还可以通过设置identifiersPrefix参数来控制混淆后的变量前缀,示例如下:

constcode=`\nlethello=&39;+1\nconsole.log(&39;,hello)\n`;\nconstoptions={\nidentifiersPrefix:&34;,\n};

运行结果如下:

vargermey_0x3dea=[&34;,&34;];\n(function(_0x348ff3,_0x5330e8){\nvar_0x1568b1=function(_0x4740d8){\nwhile(–_0x4740d8){\n_0x348ff3[&34;](_0x348ff3[&34;]());\n}\n};\n_0x1568b1(++_0x5330e8);\n})(germey_0x3dea,0x94);\nvargermey_0x30e4=function(_0x2e8f7c,_0x1066a8){\n_0x2e8f7c=_0x2e8f7c-0x0;\nvar_0x5166ba=germey_0x3dea[_0x2e8f7c];\nreturn_0x5166ba;\n};\nlethello=&34;+0x1;\nconsole[germey_0x30e4(&34;)](germey_0x30e4(&34;),hello);

可以看到混淆后的变量前缀加上了我们自定义的字符串germey。

另外renameGlobals这个参数还可以指定是否混淆全局变量和函数名称,默认为false。示例如下:

constcode=`\nvar$=function(id){\nreturndocument.getElementById(id);\n};\n`;\nconstoptions={\nrenameGlobals:true,\n};

运行结果如下:

var_0x4864b0=function(_0x5763be){\nreturndocument[&34;](_0x5763be);\n};

可以看到这里我们声明了一个全局变量$,在renameGlobals设置为true之后,$这个变量也被替换了。如果后文用到了这个$对象,可能就会有找不到定义的错误,因此这个参数可能导致代码执行不通。

如果我们不设置renameGlobals或者设置为false,结果如下:

var_0x239a=[&34;];\n(function(_0x3f45a3,_0x583dfa){\nvar_0x2cade2=function(_0x28479a){\nwhile(–_0x28479a){\n_0x3f45a3[&34;](_0x3f45a3[&34;]());\n}\n};\n_0x2cade2(++_0x583dfa);\n})(_0x239a,0xe1);\nvar_0x3758=function(_0x18659d,_0x50c21d){\n_0x18659d=_0x18659d-0x0;\nvar_0x531b8d=_0x239a[_0x18659d];\nreturn_0x531b8d;\n};\nvar$=function(_0x3d8723){\nreturndocument[_0x3758(&34;)](_0x3d8723);\n};

可以看到,最后还是有$的声明,其全局名称没有被改变。

字符串混淆

字符串混淆,即将一个字符串声明放到一个数组里面,使之无法被直接搜索到。我们可以通过控制stringArray参数来控制,默认为true。

我们还可以通过rotateStringArray参数来控制数组化后结果的的元素顺序,默认为true。还可以通过stringArrayEncoding参数来控制数组的编码形式,默认不开启编码,如果设置为true或base64,则会使用Base64编码,如果设置为rc4,则使用RC4编码。另外可以通过stringArrayThreshold来控制启用编码的概率,范围0到1,默认0.8。

示例如下:

constcode=`\nvara=&39;\n`;\nconstoptions={\nstringArray:true,\nrotateStringArray:true,\nstringArrayEncoding:true,//&39;或&39;或false\nstringArrayThreshold:1,\n};

运行结果如下:

var_0x4215=[&34;];\n(function(_0x42bf17,_0x4c348f){\nvar_0x328832=function(_0x355be1){\nwhile(–_0x355be1){\n_0x42bf17[&34;](_0x42bf17[&34;]());\n}\n};\n_0x328832(++_0x4c348f);\n})(_0x4215,0x1da);\nvar_0x5191=function(_0x3cf2ba,_0x1917d8){\n_0x3cf2ba=_0x3cf2ba-0x0;\nvar_0x1f93f0=_0x4215[_0x3cf2ba];\nif(_0x5191[&34;]===undefined){\n(function(){\nvar_0x5096b2;\ntry{\nvar_0x282db1=Function(\n&34;+\n&34;+\n&34;\n);\n_0x5096b2=_0x282db1();\n}catch(_0x2acb9c){\n_0x5096b2=window;\n}\nvar_0x388c14=\n&34;;\n_0x5096b2[&34;]||\n(_0x5096b2[&34;]=function(_0x4cc27c){\nvar_0x2af4ae=String(_0x4cc27c)[&34;](/=+$/,&34;);\nfor(\nvar_0x21400b=0x0,\n_0x3f4e2e,\n_0x5b193b,\n_0x233381=0x0,\n_0x3dccf7=&34;;\n(_0x5b193b=_0x2af4ae[&34;](_0x233381++));\n~_0x5b193b&&\n((_0x3f4e2e=\n_0x21400b%0x4?_0x3f4e2e*0x40+_0x5b193b:_0x5b193b),\n_0x21400b++%0x4)\n?(_0x3dccf7+=String[&34;](\n0xff&(_0x3f4e2e>>((-0x2*_0x21400b)&0x6))\n))\n:0x0\n){\n_0x5b193b=_0x388c14[&34;](_0x5b193b);\n}\nreturn_0x3dccf7;\n});\n})();\n_0x5191[&34;]=function(_0x51888e){\nvar_0x29801f=atob(_0x51888e);\nvar_0x561e62=[];\nfor(\nvar_0x5dd788=0x0,_0x1a8b73=_0x29801f[&34;];\n_0x5dd788<_0x1a8b73;\n_0x5dd788++\n){\n_0x561e62+=\n&34;+\n(&34;+_0x29801f[&34;](_0x5dd788)[&34;](0x10))[\n&34;\n](-0x2);\n}\nreturndecodeURIComponent(_0x561e62);\n};\n_0x5191[&34;]={};\n_0x5191[&34;]=!![];\n}\nvar_0x1741f0=_0x5191[&34;][_0x3cf2ba];\nif(_0x1741f0===undefined){\n_0x1f93f0=_0x5191[&34;](_0x1f93f0);\n_0x5191[&34;][_0x3cf2ba]=_0x1f93f0;\n}else{\n_0x1f93f0=_0x1741f0;\n}\nreturn_0x1f93f0;\n};\nvara=_0x5191(&34;);

可以看到这里就把字符串进行了Base64编码,我们再也无法通过查找的方式找到字符串的位置了。

如果将stringArray设置为false的话,输出就是这样:

vara=&34;;

字符串就仍然是明文显示的,没有被编码。

另外我们还可以使用unicodeEscapeSequence这个参数对字符串进行Unicode转码,使之更加难以辨认,示例如下:

constcode=`\nvara=&39;\n`;\nconstoptions={\ncompact:false,\nunicodeEscapeSequence:true,\n};

运行结果如下:

var_0x5c0d=[&34;];\n(function(_0x54cc9c,_0x57a3b2){\nvar_0xf833cf=function(_0x3cd8c6){\nwhile(–_0x3cd8c6){\n_0x54cc9c[&34;](_0x54cc9c[&34;]());\n}\n};\n_0xf833cf(++_0x57a3b2);\n})(_0x5c0d,0x17d);\nvar_0x28e8=function(_0x3fd645,_0x2cf5e7){\n_0x3fd645=_0x3fd645-0x0;\nvar_0x298a20=_0x5c0d[_0x3fd645];\nreturn_0x298a20;\n};\nvara=_0x28e8(&34;);

可以看到,这里字符串被数字化和Unicode化,非常难以辨认。

在很多JavaScript逆向的过程中,一些关键的字符串可能会作为切入点来查找加密入口。用了这种混淆之后,如果有人想通过全局搜索的方式搜索hello这样的字符串找加密入口,也没法搜到了。

代码自我保护

我们可以通过设置selfDefending参数来开启代码自我保护功能。开启之后,混淆后的JavaScript会以强制一行形式显示,如果我们将混淆后的代码进行格式化或者重命名,该段代码将无法执行。

示例如下:

constcode=`\nconsole.log(&39;)\n`;\nconstoptions={\nselfDefending:true,\n};

运行结果如下:

var_0x26da=[&34;,&34;];\n(function(_0x190327,_0x57c2c0){\nvar_0x577762=function(_0xc9dabb){\nwhile(–_0xc9dabb){\n_0x190327[&34;](_0x190327[&34;]());\n}\n};\nvar_0x35976e=function(){\nvar_0x16b3fe={\ndata:{key:&34;,value:&34;},\nsetCookie:function(_0x2d52d5,_0x16feda,_0x57cadf,_0x56056f){\n_0x56056f=_0x56056f||{};\nvar_0x5b6dc3=_0x16feda+&34;+_0x57cadf;\nvar_0x333ced=0x0;\nfor(\nvar_0x333ced=0x0,_0x19ae36=_0x2d52d5[&34;];\n_0x333ced<_0x19ae36;\n_0x333ced++\n){\nvar_0x409587=_0x2d52d5[_0x333ced];\n_0x5b6dc3+=&34;+_0x409587;\nvar_0x4aa006=_0x2d52d5[_0x409587];\n_0x2d52d5[&34;](_0x4aa006);\n_0x19ae36=_0x2d52d5[&34;];\nif(_0x4aa006!==!![]){\n_0x5b6dc3+=&34;+_0x4aa006;\n}\n}\n_0x56056f[&34;]=_0x5b6dc3;\n},\nremoveCookie:function(){\nreturn&34;;\n},\ngetCookie:function(_0x30c497,_0x51923d){\n_0x30c497=\n_0x30c497||\nfunction(_0x4b7e18){\nreturn_0x4b7e18;\n};\nvar_0x557e06=_0x30c497(\nnewRegExp(\n&34;+\n_0x51923d[&34;](/([.$?*|{}()[]\\/+^])/g,&34;)+\n&34;\n)\n);\nvar_0x817646=function(_0xf3fae7,_0x5d8208){\n_0xf3fae7(++_0x5d8208);\n};\n_0x817646(_0x577762,_0x57c2c0);\nreturn_0x557e06?decodeURIComponent(_0x557e06[0x1]):undefined;\n},\n};\nvar_0x4673cd=function(){\nvar_0x4c6c5c=newRegExp(\n&34;\n);\nreturn_0x4c6c5c[&34;](_0x16b3fe[&34;][&34;]());\n};\n_0x16b3fe[&34;]=_0x4673cd;\nvar_0x5baa80=&34;;\nvar_0x1faf19=_0x16b3fe[&34;]();\nif(!_0x1faf19){\n_0x16b3fe[&34;]([&34;],&34;,0x1);\n}elseif(_0x1faf19){\n_0x5baa80=_0x16b3fe[&34;](null,&34;);\n}else{\n_0x16b3fe[&34;]();\n}\n};\n_0x35976e();\n})(_0x26da,0x140);\nvar_0x4391=function(_0x1b42d8,_0x57edc8){\n_0x1b42d8=_0x1b42d8-0x0;\nvar_0x2fbeca=_0x26da[_0x1b42d8];\nreturn_0x2fbeca;\n};\nvar_0x197926=(function(){\nvar_0x10598f=!![];\nreturnfunction(_0xffa3b3,_0x7a40f9){\nvar_0x48e571=_0x10598f\n?function(){\nif(_0x7a40f9){\nvar_0x2194b5=_0x7a40f9[&34;](_0xffa3b3,arguments);\n_0x7a40f9=null;\nreturn_0x2194b5;\n}\n}\n:function(){};\n_0x10598f=![];\nreturn_0x48e571;\n};\n})();\nvar_0x2c6fd7=_0x197926(this,function(){\nvar_0x4828bb=function(){\nreturn&34;;\n},\n_0x35c3bc=function(){\nreturn&34;;\n};\nvar_0x456070=function(){\nvar_0x4576a4=newRegExp(\n&34;\n);\nreturn!_0x4576a4[&34;](\n_0x4828bb[&34;]()\n);\n};\nvar_0x3fde69=function(){\nvar_0xabb6f4=newRegExp(\n&34;\n);\nreturn_0xabb6f4[&34;](\n_0x35c3bc[&34;]()\n);\n};\nvar_0x2d9a50=function(_0x58fdb4){\nvar_0x2a6361=~-0x1>>(0x1+(0xff%0x0));\nif(_0x58fdb4[&34;](&34;===_0x2a6361)){\n_0xc388c5(_0x58fdb4);\n}\n};\nvar_0xc388c5=function(_0x2073d6){\nvar_0x6bb49f=~-0x4>>(0x1+(0xff%0x0));\nif(\n_0x2073d6[&34;]((!![]+&34;)[0x3])!==_0x6bb49f\n){\n_0x2d9a50(_0x2073d6);\n}\n};\nif(!_0x456070()){\nif(!_0x3fde69()){\n_0x2d9a50(&34;);\n}else{\n_0x2d9a50(&34;);\n}\n}else{\n_0x2d9a50(&34;);\n}\n});\n_0x2c6fd7();\nconsole[_0x4391(&34;)](_0x4391(&34;));

如果我们将上述代码放到控制台,它的执行结果和之前是一模一样的,没有任何问题。

如果我们将其进行格式化,然后贴到到浏览器控制台里面,浏览器会直接卡死无法运行。这样如果有人对代码进行了格式化,就无法正常对代码进行运行和调试,从而起到了保护作用。

控制流平坦化

控制流平坦化其实就是将代码的执行逻辑混淆,使其变得复杂难读。其基本思想是将一些逻辑处理块都统一加上一个前驱逻辑块,每个逻辑块都由前驱逻辑块进行条件判断和分发,构成一个个闭环逻辑,导致整个执行逻辑十分复杂难读。

比如说这里有一段示例代码:

console.log(c);\nconsole.log(a);\nconsole.log(b);

代码逻辑一目了然,依次在控制台输出了c、a、b三个变量的值,但如果把这段代码进行控制流平坦化处理后,代码就会变成这样:

consts=&34;.split(&34;);\nletx=0;\nwhile(true){\nswitch(s[x++]){\ncase&34;:\nconsole.log(a);\ncontinue;\ncase&34;:\nconsole.log(b);\ncontinue;\ncase&34;:\nconsole.log(c);\ncontinue;\n}\nbreak;\n}

可以看到,混淆后的代码首先声明了一个变量s,它的结果是一个列表,其实是[&34;,&34;,&34;],然后下面通过switch语句对s中的元素进行了判断,每个case都加上了各自的代码逻辑。通过这样的处理,一些连续的执行逻辑就被打破了,代码被修改为一个switch语句,原本我们可以一眼看出的逻辑是控制台先输出c,然后才是a、b,但是现在我们必须要结合switch的判断条件和对应case的内容进行判断,我们很难再一眼每条语句的执行顺序了,这就大大降低了代码的可读性。

在javascript-obfuscator中我们通过controlFlowFlattening变量可以控制是否开启控制流平坦化,示例如下:

constoptions={\ncompact:false,\ncontrolFlowFlattening:true,\n};

使用控制流平坦化可以使得执行逻辑更加复杂难读,目前非常多的前端混淆都会加上这个选项。但启用控制流平坦化之后,代码的执行时间会变长,最长达1.5倍之多。

另外我们还能使用controlFlowFlatteningThreshold这个参数来控制比例,取值范围是0到1,默认0.75,如果设置为0,那相当于controlFlowFlattening设置为false,即不开启控制流扁平化。

无用代码注入

无用代码即不会被执行的代码或对上下文没有任何影响的代码,注入之后可以对现有的JavaScript代码的阅读形成干扰。我们可以使用deadCodeInjection参数开启这个选项,默认为false。

比如这里有一段代码:

consta=function(){\nconsole.log(&34;);\n};\n\nconstb=function(){\nconsole.log(&34;);\n};\n\na();\nb();

这里就声明了方法a和b,然后依次进行调用,分别输出两句话。

但经过无用代码注入处理之后,代码就会变成类似这样的结果:

const_0x16c18d=function(){\nif(!![[]]){\nconsole.log(&34;);\n}else{\nconsole.log(&34;);\nconsole.log(&34;);\nconsole.log(&34;);\nconsole.log(&34;);\n}\n};\nconst_0x1f7292=function(){\nif(&34;.charAt(4)!==String.fromCharCode(110)){\nconsole.log(&34;);\nconsole.log(&34;);\nconsole.log(&34;);\nconsole.log(&34;);\n}else{\nconsole.log(&34;);\n}\n};\n\n_0x16c18d();\n_0x1f7292();

可以看到,每个方法内部都增加了额外的ifelse语句,其中if的判断条件还是一个表达式,其结果是true还是false我们还不太一眼能看出来,比如说_0x1f7292这个方法,它的if判断条件是:

&34;.charAt(4)!==String.fromCharCode(110)

在不等号前面其实是从字符串中取出指定位置的字符,不等号后面则调用了fromCharCode方法来根据ascii码转换得到一个字符,然后比较两个字符的结果是否是不一样的。前者经过我们推算可以知道结果是n,但对于后者,多数情况下我们还得去查一下ascii码表才能知道其结果也是n,最后两个结果是相同的,所以整个表达式的结果是false,所以if后面跟的逻辑实际上就是不会被执行到的无用代码,但这些代码对我们阅读代码起到了一定的干扰作用。

因此,这种混淆方式通过混入一些特殊的判断条件并加入一些不会被执行的代码,可以对代码起到一定的混淆干扰作用。

在javascript-obfuscator中,我们可以通过deadCodeInjection参数控制无用代码的注入,配置如下:

constoptions={\ncompact:false,\ndeadCodeInjection:true,\n};

另外我们还可以通过设置deadCodeInjectionThreshold参数来控制无用代码注入的比例,取值0到1,默认是0.4。

对象键名替换

如果是一个对象,可以使用transformObjectKeys来对对象的键值进行替换,示例如下:

constcode=`\n(function(){\nvarobject={\nfoo:&39;,\nbar:{\nbaz:&39;\n}\n};\n})();\n`;\nconstoptions={\ncompact:false,\ntransformObjectKeys:true,\n};

输出结果如下:

var_0x7a5d=[&34;,&34;,&34;];\n(function(_0x59fec5,_0x2e4fac){\nvar_0x231e7a=function(_0x46f33e){\nwhile(–_0x46f33e){\n_0x59fec5[&34;](_0x59fec5[&34;]());\n}\n};\n_0x231e7a(++_0x2e4fac);\n})(_0x7a5d,0x167);\nvar_0x3bc4=function(_0x309ad3,_0x22d5ac){\n_0x309ad3=_0x309ad3-0x0;\nvar_0x3a034e=_0x7a5d[_0x309ad3];\nreturn_0x3a034e;\n};\n(function(){\nvar_0x9f1fd1={};\n_0x9f1fd1[&34;]=_0x3bc4(&34;);\n_0x9f1fd1[_0x3bc4(&34;)]={};\n_0x9f1fd1[_0x3bc4(&34;)][&34;]=_0x3bc4(&34;);\n})();

可以看到,Object的变量名被替换为了特殊的变量,使得可读性变差,这样我们就不好直接通过变量名进行搜寻了,这也可以起到一定的防护作用。

禁用控制台输出

可以使用disableConsoleOutput来禁用掉console.log输出功能,加大调试难度,示例如下:

constcode=`\nconsole.log(&39;)\n`;\nconstoptions={\ndisableConsoleOutput:true,\n};

运行结果如下:

var_0x3a39=[\n&34;,\n&34;,\n&34;,\n&34;,\n&34;,\n&34;,\n&34;,\n&34;,\n&34;,\n&34;,\n&34;,\n];\n(function(_0x2a157a,_0x5d9d3b){\nvar_0x488e2c=function(_0x5bcb73){\nwhile(–_0x5bcb73){\n_0x2a157a[&34;](_0x2a157a[&34;]());\n}\n};\n_0x488e2c(++_0x5d9d3b);\n})(_0x3a39,0x10e);\nvar_0x5bff=function(_0x43bdfc,_0x52e4c6){\n_0x43bdfc=_0x43bdfc-0x0;\nvar_0xb67384=_0x3a39[_0x43bdfc];\nreturn_0xb67384;\n};\nvar_0x349b01=(function(){\nvar_0x1f484b=!![];\nreturnfunction(_0x5efe0d,_0x33db62){\nvar_0x20bcd2=_0x1f484b\n?function(){\nif(_0x33db62){\nvar_0x77054c=_0x33db62[_0x5bff(&34;)](_0x5efe0d,arguments);\n_0x33db62=null;\nreturn_0x77054c;\n}\n}\n:function(){};\n_0x1f484b=![];\nreturn_0x20bcd2;\n};\n})();\nvar_0x19f538=_0x349b01(this,function(){\nvar_0x7ab6e4=function(){};\nvar_0x157bff;\ntry{\nvar_0x5e672c=Function(\n&34;+_0x5bff(&34;)+&34;\n);\n_0x157bff=_0x5e672c();\n}catch(_0x11028d){\n_0x157bff=window;\n}\nif(!_0x157bff[_0x5bff(&34;)]){\n_0x157bff[_0x5bff(&34;)]=(function(_0x7ab6e4){\nvar_0x5a8d9e={};\n_0x5a8d9e[_0x5bff(&34;)]=_0x7ab6e4;\n_0x5a8d9e[_0x5bff(&34;)]=_0x7ab6e4;\n_0x5a8d9e[_0x5bff(&34;)]=_0x7ab6e4;\n_0x5a8d9e[_0x5bff(&34;)]=_0x7ab6e4;\n_0x5a8d9e[_0x5bff(&34;)]=_0x7ab6e4;\n_0x5a8d9e[_0x5bff(&34;)]=_0x7ab6e4;\n_0x5a8d9e[_0x5bff(&34;)]=_0x7ab6e4;\nreturn_0x5a8d9e;\n})(_0x7ab6e4);\n}else{\n_0x157bff[_0x5bff(&34;)][_0x5bff(&34;)]=_0x7ab6e4;\n_0x157bff[_0x5bff(&34;)][_0x5bff(&34;)]=_0x7ab6e4;\n_0x157bff[_0x5bff(&34;)][&34;]=_0x7ab6e4;\n_0x157bff[_0x5bff(&34;)][_0x5bff(&34;)]=_0x7ab6e4;\n_0x157bff[_0x5bff(&34;)][_0x5bff(&34;)]=_0x7ab6e4;\n_0x157bff[_0x5bff(&34;)][_0x5bff(&34;)]=_0x7ab6e4;\n_0x157bff[_0x5bff(&34;)][_0x5bff(&34;)]=_0x7ab6e4;\n}\n});\n_0x19f538();\nconsole[_0x5bff(&34;)](_0x5bff(&34;));

此时,我们如果执行这个代码,发现是没有任何输出的,这里实际上就是将console的一些功能禁用了。

调试保护

我们知道,在JavaScript代码中如果加入debugger这个关键字,那么在执行到该位置的时候控制它就会进入断点调试模式。如果在代码多个位置都加入debugger这个关键字,或者定义某个逻辑来反复执行debugger,那就会不断进入断点调试模式,原本的代码无法就无法顺畅地执行了。这个过程可以称为调试保护,即通过反复执行debugger来使得原来的代码无法顺畅执行。

其效果类似于执行了如下代码:

setInterval(()=>{\ndebugger;\n},3000);

如果我们把这段代码粘贴到控制台,它就会反复地执行debugger语句进入断点调试模式,从而干扰正常的调试流程。

在javascript-obfuscator中可以使用debugProtection来启用调试保护机制,还可以使用debugProtectionInterval来启用无限Debug,使得代码在调试过程中会不断进入断点模式,无法顺畅执行,配置如下:

constoptions={\ndebugProtection:true,\ndebugProtectionInterval:true,\n};

混淆后的代码会不断跳到debugger代码的位置,使得整个代码无法顺畅执行,对JavaScript代码的调试形成一定的干扰。

域名锁定

我们还可以通过控制domainLock来控制JavaScript代码只能在特定域名下运行,这样就可以降低代码被模拟或盗用的风险。

示例如下:

constcode=`\nconsole.log(&39;)\n`;\nconstoptions={\ndomainLock:[&34;],\n};

这里我们使用了domainLock指定了一个域名叫做cuiqingcai.com,也就是设置了一个域名白名单,混淆后的代码结果如下:

var_0x3203=[\n&34;,\n&34;,\n&34;,\n&34;,\n&34;,\n&34;,\n&34;,\n&34;,\n&34;,\n&34;,\n&34;,\n];\n(function(_0x2ed22c,_0x3ad370){\nvar_0x49dc54=function(_0x53a786){\nwhile(–_0x53a786){\n_0x2ed22c[&34;](_0x2ed22c[&34;]());\n}\n};\n_0x49dc54(++_0x3ad370);\n})(_0x3203,0x155);\nvar_0x5b38=function(_0xd7780b,_0x19c0f2){\n_0xd7780b=_0xd7780b-0x0;\nvar_0x2d2f44=_0x3203[_0xd7780b];\nreturn_0x2d2f44;\n};\nvar_0x485919=(function(){\nvar_0x5cf798=!![];\nreturnfunction(_0xd1fa29,_0x2ed646){\nvar_0x56abf=_0x5cf798\n?function(){\nif(_0x2ed646){\nvar_0x33af63=_0x2ed646[_0x5b38(&34;)](_0xd1fa29,arguments);\n_0x2ed646=null;\nreturn_0x33af63;\n}\n}\n:function(){};\n_0x5cf798=![];\nreturn_0x56abf;\n};\n})();\nvar_0x67dcc8=_0x485919(this,function(){\nvar_0x276a31;\ntry{\nvar_0x5c8be2=Function(_0x5b38(&34;)+_0x5b38(&34;)+&34;);\n_0x276a31=_0x5c8be2();\n}catch(_0x5f1c00){\n_0x276a31=window;\n}\nvar_0x254a0d=function(){\nreturn{\nkey:_0x5b38(&34;),\nvalue:_0x5b38(&34;),\ngetAttribute:(function(){\nfor(var_0x5cc3c7=0x0;_0x5cc3c7<0x3e8;_0x5cc3c7–){\nvar_0x35b30b=_0x5cc3c7>0x0;\nswitch(_0x35b30b){\ncase!![]:\nreturn(\nthis[_0x5b38(&34;)]+\n&34;+\nthis[_0x5b38(&34;)]+\n&34;+\n_0x5cc3c7\n);\ndefault:\nthis[_0x5b38(&34;)]+&34;+this[_0x5b38(&34;)];\n}\n}\n})(),\n};\n};\nvar_0x3b375a=newRegExp(&34;,&34;);\nvar_0x5a94d2=&34;\n[_0x5b38(&34;)](_0x3b375a,&34;)\n[&34;](&34;);\nvar_0x5c0da2;\nvar_0x19ad5d;\nvar_0x5992ca;\nvar_0x40bd39;\nfor(var_0x5cad1in_0x276a31){\nif(\n_0x5cad1[_0x5b38(&34;)]==0x8&&\n_0x5cad1[_0x5b38(&34;)](0x7)==0x74&&\n_0x5cad1[_0x5b38(&34;)](0x5)==0x65&&\n_0x5cad1[_0x5b38(&34;)](0x3)==0x75&&\n_0x5cad1[_0x5b38(&34;)](0x0)==0x64\n){\n_0x5c0da2=_0x5cad1;\nbreak;\n}\n}\nfor(var_0x29551in_0x276a31[_0x5c0da2]){\nif(\n_0x29551[_0x5b38(&34;)]==0x6&&\n_0x29551[_0x5b38(&34;)](0x5)==0x6e&&\n_0x29551[_0x5b38(&34;)](0x0)==0x64\n){\n_0x19ad5d=_0x29551;\nbreak;\n}\n}\nif(!(&34;>_0x19ad5d)){\nfor(var_0x2b71bdin_0x276a31[_0x5c0da2]){\nif(\n_0x2b71bd[_0x5b38(&34;)]==0x8&&\n_0x2b71bd[_0x5b38(&34;)](0x7)==0x6e&&\n_0x2b71bd[_0x5b38(&34;)](0x0)==0x6c\n){\n_0x5992ca=_0x2b71bd;\nbreak;\n}\n}\nfor(var_0x397f55in_0x276a31[_0x5c0da2][_0x5992ca]){\nif(\n_0x397f55[&34;]==0x8&&\n_0x397f55[_0x5b38(&34;)](0x7)==0x65&&\n_0x397f55[_0x5b38(&34;)](0x0)==0x68\n){\n_0x40bd39=_0x397f55;\nbreak;\n}\n}\n}\nif(!_0x5c0da2||!_0x276a31[_0x5c0da2]){\nreturn;\n}\nvar_0x5f19be=_0x276a31[_0x5c0da2][_0x19ad5d];\nvar_0x674f76=\n!!_0x276a31[_0x5c0da2][_0x5992ca]&&\n_0x276a31[_0x5c0da2][_0x5992ca][_0x40bd39];\nvar_0x5e1b34=_0x5f19be||_0x674f76;\nif(!_0x5e1b34){\nreturn;\n}\nvar_0x593394=![];\nfor(var_0x479239=0x0;_0x479239<_0x5a94d2[&34;];_0x479239++){\nvar_0x19ad5d=_0x5a94d2[_0x479239];\nvar_0x112c24=_0x5e1b34[&34;]-_0x19ad5d[&34;];\nvar_0x51731c=_0x5e1b34[&34;](_0x19ad5d,_0x112c24);\nvar_0x173191=_0x51731c!==-0x1&&_0x51731c===_0x112c24;\nif(_0x173191){\nif(\n_0x5e1b34[&34;]==_0x19ad5d[_0x5b38(&34;)]||\n_0x19ad5d[&34;](&34;)===0x0\n){\n_0x593394=!![];\n}\n}\n}\nif(!_0x593394){\ndata;\n}else{\nreturn;\n}\n_0x254a0d();\n});\n_0x67dcc8();\nconsole[_0x5b38(&34;)](_0x5b38(&34;));

这段代码就只能在指定域名cuiqingcai.com下运行,不能在其他网站运行。这样的话,如果一些相关JavaScript代码被单独剥离出来,想在其他网站运行或者使用程序模拟运行的话,运行结果只有是失败,这样就可以有效降低被代码被模拟或盗用的风险。

特殊编码

另外还有一些特殊的工具包,如使用aaencode、jjencode、jsfuck等工具对代码进行混淆和编码。

示例如下:

vara=1

jsfuck的结果:

[][(![]+[])[!+[]+!![]+!![]]+([]+{})[+!![]]+(!![]+[])[+!![]]+(!![]+[])[+[]]][([]+{})[!+[]+!![]+!![]+!![]+!![]]+([]+{})[+!![]]+([][[]]+[])[+!![]]+(![]+[])[!+[]+!![]+!![]]+(!![]+[])[+[]]+(!![]+[])[+!![]]+([][[]]+[])[+[]]+([]+{})[!+[]+!![]+!![]+!![]+!![]]+(!![]+[])[+[]]+([]+{})[+!![]]+(!![]+[])[+!![]]]([][(![]+[])[!+[]+!![]+!![]]+([]+{})[+!![]]+(!![]+[])[+!![]]+(!![]+[])[+[]]][([]+{})[!+[]+!![]+!![]+!![]+!![]]+([]+{})[+!![]]+([][[]]+[])[+!![]]+\n…\n([]+{})[+!![]]+(!![]+[])[+!![]]]((!![]+[])[+!![]]+([][[]]+[])[!+[]+!![]+!![]]+(!![]+[])[+[]]+([][[]]+[])[+[]]+(!![]+[])[+!![]]+([][[]]+[])[+!![]]+([]+{})[!+[]+!![]+!![]+!![]+!![]+!![]+!![]]+(![]+[])[!+[]+!![]]+([]+{})[+!![]]+([]+{})[!+[]+!![]+!![]+!![]+!![]]+(+{}+[])[+!![]]+(!![]+[])[+[]]+([][[]]+[])[!+[]+!![]+!![]+!![]+!![]]+([]+{})[+!![]]+([][[]]+[])[+!![]])(!+[]+!![]+!![]+!![]+!![]))[!+[]+!![]+!![]]+([][[]]+[])[!+[]+!![]+!![]])(!+[]+!![]+!![]+!![]+!![])(([]+{})[+[]])[+[]]+(!+[]+!![]+!![]+[])+([][[]]+[])[!+[]+!![]])+([]+{})[!+[]+!![]+!![]+!![]+!![]+!![]+!![]]+(+!![]+[]))(!+[]+!![]+!![]+!![]+!![]+!![]+!![]+!![])

aaencode的结果:

?ω??=/`m′)?~┻━┻/[&39;];o=(???)=_=3;c=(?Θ?)=(???)-(???);(?Д?)=(?Θ?)=(o^_^o)/(o^_^o);(?Д?)={?Θ?:&39;,?ω??:((?ω??==3)+&39;)[?Θ?],????:(?ω??+&39;)[o^_^o-(?Θ?)],?Д??:((???==3)+&39;)[???]};(?Д?)[?Θ?]=((?ω??==3)+&39;)[c^_^o];(?Д?)[&39;]=((?Д?)+&39;)[(???)+(???)-(?Θ?)];(?Д?)[&39;]=((?Д?)+&39;)[?Θ?];(?o?)=(?Д?)[&39;]+(?Д?)[&39;]+(?ω??+&39;)[?Θ?]+((?ω??==3)+&39;)[???]+((?Д?)+&39;)[(???)+(???)]+((???==3)+&39;)[?Θ?]+((???==3)+&39;)[(???)-(?Θ?)]+(?Д?)[&39;]+((?Д?)+&39;)[(???)+(???)]+(?Д?)[&39;]+((???==3)+&39;)[?Θ?];(?Д?)[&39;]=(o^_^o)[?o?][?o?];(?ε?)=((???==3)+&39;)[?Θ?]+(?Д?).?Д??+((?Д?)+&39;)[(???)+(???)]+((???==3)+&39;)[o^_^o-?Θ?]+((???==3)+&39;)[?Θ?]+(?ω??+&39;)[?Θ?];(???)+=(?Θ?);(?Д?)[?ε?]=&39;;(?Д?).?Θ??=(?Д?+???)[o^_^o-(?Θ?)];(o???o)=(?ω??+&39;)[c^_^o];(?Д?)[?o?]=&34;&39;_&39;_&34;&34;&34;&34;&34;&34;&34;&34;&34;&34;&34;&34;&34;&34;&34;&34;&34;\\&34;+&34;+$.__$+$.$$_+$.$$_+$.$_$_+&34;+$.__$+$.$$_+$._$_+&34;+$.$__+$.___+$.$_$_+&34;+$.$__+$.___+&34;+$.$__+$.___+$.__$+&34;&34;2+4=&34;3^2=&34;(2+5)^2=”,square(add(2+5)));\n});

这里其实是利用WebAssembly定义了两个方法,分别是add和square,可以分别用于求和和开平方计算。那这两个方法在哪里声明的呢?其实它们被隐藏在了一个Uint8Array里面,仅仅查看明文代码我们确实无从知晓里面究竟定义了什么逻辑,但确实是可以执行的,我们将这段代码输入到浏览器控制台下,运行结果如下:

2+4=6\n3^2=9\n(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

OK,本文到此结束,希望对大家有所帮助。

Published by

风君子

独自遨游何稽首 揭天掀地慰生平