媒介
对某网站加密殽杂后的javascript代码也算剖析了一段时间了,虽然还没搞出来,但多少有些心得,这里纪录一下。
东西和材料
- QQ群 – Javascript高等爬虫 – 作者自建群,迎接到场!
- awesome-java-crawler – 我收集的爬虫相干东西和材料
- 中国商标网加密接口 – 仅作演示
- 前一篇文章 – 纪录了之前尝试的一些开端研究成果
- java-curl – java HTTP库,可用来替代chrome收集后端,更轻易掌握底层行动,如缓存、代办、监控、修正请乞降应对等
- cdp4j – java版的Chrome Devtools Protocol完成,用于掌握Chrome浏览器。最大的特性就是没有“特性”,你懂的……
- beautifier.io – js代码在线花样化
- estree – ECMAScript笼统语法树(AST)业界范例
- ECMAScript262言语范例 – 协助邃晓estree
- acornjs – ECMAScript编译器前端,将js源码剖析成estree花样的AST
- astring – ECMAScript代码生成器,将AST从新复原成js源码
- nashorn – java8以上自带的javascript诠释器,机能靠近原生node
- java中挪用npm模块 – 我的事情言语是java和kotlin,运用此计划挪用js原生库
- 商标局网站剖析 – 相似的加密,神箭手云的大佬写的
- 裁判文书网剖析 – 另一篇相似网站剖析
- 很早的一篇剖析文 – 看特性是这类加密的初期版本
剖析历程
猎取javascript代码
- 加密的中心代码只要一小部份是直接写在网页的<script>内里的,大部份代码是eval出来的,另有部份是jsonp体式格局异步加载的
- 能够用cdp4j监听Debugger.ScriptParsed事宜,并在监听器中挪用Debugger.getScriptSource来猎取js代码文本
- 如许是能够猎取到一切前端javascript源码的,纵然源码在收集应对中是加密的,但用eval实行前也会复原为正当的js源码
- 为了轻易剖析,将代码保留为文件。该网站js会用定时器不停反复eval一段代码,因而能够用ScriptParsed.hash作为文件名,防止反复保留文件
- 如许搞出来的,用一样的殽杂体式格局加密的js代码共有4段,个中两段迥殊长是中心代码,另有两段应当是编解码算法,一共加起来约5000行
猎取常量映照
- 拿到js以后,花样化一下,发明照样一团乱麻,一切的变量,函数都是”_$xx”,可读性即是0
在Chrome掌握台里试了一下,发明全局变量和函数都保留在window中了
- 一部份无参的函数挪用,实在返回的就是常量字符串
- 另有一些
_$xx.call
的,看了一下,实在就是体系要领,比方String.fromCharCode,Array.prototype.slice等
因而能够编写一段掌握台剧本,遍历window对象中一切形似_$xx的成员,推断其范例和函数实行效果。如许即可将常量字符串映照、体系要领映照等搞出来。在掌握台实行下面这段代码就能够把字符串映照表弄到。
(function () { for (var p in window) { if (p.substr(0, 2) !== "_$") continue; if (typeof window[p] !== "function" || window[p].name !== "") continue; try { var s = window[p](); console.log(p + "=" + s) } catch (e) {} } })()
可读性复原
- 拿到映照关联以后是不是是简朴用正则表达式替代归去就高枕无忧了呢?哪有那末简朴!函数的部分变量、部分函数有很大能够性和全局变量重名,假如用正则无脑替代归去相对会被坑死!!如果代码少倒也罢了,这里可有5000行代码,差之毫厘谬以千里!
- 别的,差别函数的部分变量也存在大批重名,静态剖析时滋扰严峻。因而,应当将部分变量也替代成唯一且更有意义的名字,比方<函数名>_<变量索引>
- 因而,准确的要领是基于编译道理举行语法级别的替代。看到这里是不是是要弃疗了?老子爬点数据还要写编译器?!
- 还好,js上已有很成熟的业界范例和多少老到的第三方库了,最少不必从龙书搞起……
- 我这里挑选了acornjs和astring,前者用于将js源码剖析成笼统语法树AST,后者将AST复原成js源码。固然,有了AST就能够上下其手了……
- 为了在java代码中运转acornjs和astring,请拜见参考中《java中挪用npm模块》一文。注重astring还依靠endswith和repeat两个polyfill,均能够npm下载到
简朴形貌一下AST变更算法。用
acorn.parse()
搞到AST以后,递归扫描每一个节点:- 进入每一个FunctionDeclaration/FunctionExpression节点前,建立一个新的作用域对象放到栈顶,内里放该域内一切部分变量(含函数参数)和新称号的映照表;退出时将栈顶弹出
- 碰到Identifier节点,首先在作用域栈中自顶向下顺次寻觅当前变量名,找到了,则是本要领部分变量或闭包外部分变量,用新名字替代之;不然,则是全局变量,去映照表中查找替代之
- 注重,碰到CallExpression须特别处置惩罚,前面的AST变更只触及修正标识符名,而为了将
_$xx()
变更为"xxx"
,则触及到构造变更,要把CallExpression节点修正为Literal节点并增加value属性
- 悉数处置惩罚完成后,就能够用
astring.generate()
发作复原后的代码了 - 可读性恢复前后的代码能够看看下面的对照:
处置惩罚前:一团乱麻,完整不知所云
处置惩罚后:虽然还很费力,最少看得出来这是在挂种种事宜监听器。别的,看看人家监听多少种事宜啊……
代码剖析
上面步骤完成后,这代码最少委曲能看了,别放松,背面另有无数的坑……
复原前的代码只能是让人一脸懵逼,复原后的代码则足以让人痛心疾首啊,多大仇啊,满满登登5000行满是正面硬怼的……
这里纪录一部份已发明的反破解手腕吧。
不停主动中缀滋扰调试,并检测是不是有动态剖析行动
var eI_v1 = window["eval"]("(function() {var a = new Date(); debugger; return new Date() - a > 100;}())");
_$n1 = _$n1 || eI_v1;
//这个在上篇文章剖析了,在这找到挪用泉源了。注重,在可读性复原之前这货长如许:
var _$pW = _$u9[_$mz()](_$oi());
_$n1 = _$n1 || _$pW;
js代码动态殽杂
- 上一篇文章已说过了,每次革新js代码都邑完整变化,包含全局/部分变量名、函数分列递次等
- 设断点会被滋扰,且代码没法反复实行关于调试意味着什么?
搜检症结函数是不是被注入替代
function __RW_checkNative(rh_p0, rh_p1) { // 函数名我手动改的
try {
var rh_v2 = Function["prototype"]["toString"]["apply"](rh_p0);
var rh_v3 = new RegExp("{\\s*\\[native code\\]\\s*}");
if (typeof rh_p0 !== "function" || !rh_v3["test"](rh_v2) || rh_p1 != undefined && rh_p0 !== rh_p1) __GL_undefined_$sy = true;
} catch (_$r0) {}
}
- 会用这个函数检测eval, Function, setTimeout, setInterval几个体系函数是不是是被注入了
- 晓得这块逻辑,就能够用一些手腕骗过去,不晓得的话……
检测当前窗口是不是隐蔽状况
document["addEventListener"]("visibilitychange", _$r0);
- 会监控当前窗口是不是在最上方,假如多开浏览器并行爬取……
检测Selenium, WebDriver, PhantomJS等
var rm_v5 = "_Selenium_IDE_Recorder,_selenium,callSelenium"
, rm_v6 = "__driver_evaluate,__webdriver_evaluate,__selenium_evaluate,__fxdriver_evaluate,__driver_unwrapped,__webdriver_unwrapped,__selenium_unwrapped,__fxdriver_unwrapped,__webdriver_script_func,__webdriver_script_fn"
, rm_v7 = ["selenium", "webdriver", "driver"];
if (_$un(window, "callPhantom,_phantom")) { ... }
- 看到这里想必就晓得会发作些什么了……
Hook住AJAX
var ec_v4 = window["XMLHttpRequest"];
if (ec_v4) {
var ec_v5 = ec_v4["prototype"];
if (ec_v5) {
__GL_f_open = ec_v5["open"];
__GL_f_send = ec_v5["send"];
ec_v5["open"] = function () {
_$t5();
arguments[1] = _$pK(arguments[1]);
return __GL_f_open["apply"](this, arguments);
};
} else { ... }
}
- 会自动在ajax要求后增加一个加密参数MmEwMD,参数值中能够包含鼠标轨迹等信息
搜检navigator是不是是捏造的
var hi_v14 = window["navigator"];
for (hi_v11 in hi_v14) {
try {
hi_v13 = hi_v14["hasOwnProperty"](hi_v11);
} catch (_$r0) {
hi_v13 = false;
}
}
- 假如你注入的navigator对象是用
{...}
建立的水货版本,那就露馅了……
搜检浏览器特性
- 这块代码很长很庞杂,还没剖析完,如今能看出来的包含:
navigator.languages
– 在headless chrome中是没有这个字段的navigator.plugins
– 无头和有头的chrome返回的插件列表不一样
WebGL才能搜检
- 有一大段代码是在canvas上用webgl画图,没搞过webgl,如今还不邃晓,但一定也是搜检浏览器特性手腕之一