对某网站加密混淆后的javascript代码也算分析了一段时间了,虽然还没搞出来,但多少有些心得,这里记录一下。工具和资料前一篇文章 - 记录了之前尝试的一些方案awesome-java-crawler - 我收集的爬虫相关工具和资料java-curl - 本人写的java HTTP库,可用来替换chrome网络后端,更方便控制底层行为,如缓存、代理、监控、修改请求和应答等cdp4j - java版的Chrome Devtools Protocol实现,用于控制Chrome浏览器。最大的特点就是没有“特点”,你懂的……beautifier.io - js代码在线格式化estree - ECMAScript抽象语法树(AST)业界标准ECMAScript262语言规范 - 帮助理解estreeacornjs - ECMAScript编译器前端,将js源码解析成estree格式的ASTastring - ECMAScript代码生成器,将AST重新还原成js源码nashorn - java8以上自带的javascript解释器,性能接近原生nodejava中调用npm模块 - 我一直用的java和kotlin,为了调用js原生库,需用这个方案类似网站的破解 - 神箭手云的大佬写的很早的一篇分析文 - 看特征是这种加密的早期版本分析过程获取javascript代码加密的核心代码只有一小部分是直接写在网页的<script>里面的,有些代码是eval出来的可以用cdp4j监听Debugger.ScriptParsed事件,并在监听器中调用Debugger.getScriptSource来获取js代码文本这样是可以获取到所有前端javascript源码的,即使源码在网络应答中是加密的,但用eval执行前也必须还原为合法的js源码为了方便分析,可将代码保存为文件。该网站js会用定时器不断重复eval一段代码,因此可以用ScriptParsed.hash作为文件名,避免重复保存文件获取常量映射拿到js之后,格式化一下,发现还是一团乱麻,所有的变量,函数都是"$xx",可读性约等于0在Chrome控制台里试了一下,发现全局变量和函数都保存在window中了一部分无参调用的函数,其实返回的就是常量字符串还有一些$xx.call的,看了一下,其实就是系统方法,比如String.fromCharChode等因此可以编写一段代码,遍历window对象中所有形似_$xx的成员,这样既可将常量字符串映射、系统方法映射等搞出来可读性还原拿到映射关系之后是不是简单用正则表达式替换回去就万事大吉了呢?哪有那么简单!函数的局部变量、局部函数有很大可能性和全局变量重名,如果用正则无脑替换回去绝对会被坑死!!要是代码少倒也罢了,这里可有5000行代码,差之毫厘谬以千里!另外,不同函数的局部变量也存在大量重名,静态分析时干扰严重,因此,应该将局部变量也替换成唯一且更有意义的名字,比如<函数名><变量索引>因此,正确的方法是基于编译原理进行语法级别的替换,看到这里是不是要弃疗了?老子爬点数据还要写编译器?!还好,js上已经有很成熟的业界标准和若干老练的第三方库了,至少不用从龙书搞起……我这里选择了acornjs和astring,前者用于将js源码解析成抽象语法树AST,后者将AST还原成js源码。当然,在AST上我们是可以上下其手的……为了在java代码中运行acornjs和astring,请参见参考中《java中调用npm模块》一文。注意astring还依赖endswith和repeat两个polyfill,均可以npm下载到用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();$n1 = _$n1 || $pW;js代码动态混淆上一篇文章已经说过了,每次刷新js代码都会完全变化,包括全局/局部变量名、函数排列顺序等设断点会被干扰,而且代码无法重复执行对于调试意味着什么?检查关键函数是否被注入替换function __RW_checkNative(rh_p0, rh_p1) { // 函数名我手动改的 try { var rh_v2 = Function[“prototype”][“toString”]“apply”; var rh_v3 = new RegExp("{\s*\[native code\]\s*}"); if (typeof rh_p0 !== “function” || !rh_v3“test” || rh_p1 != undefined && rh_p0 !== rh_p1) _GL_undefined$sy = true; } catch ($r0) {}}会用这个函数检测eval, Function, setTimeout, setInterval几个系统函数是不是被注入了搞明白了,就可以用一些手段骗过去,不明白的话……检测当前窗口是否隐藏状态 document[“addEventListener”](“visibilitychange”, _$r0);会监控当前窗口是否在最上方,要是用cdp4j多开浏览器同时爬取……检测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 { … } }检查navigator是否是伪造的 var hi_v14 = window[“navigator”]; for (hi_v11 in hi_v14) {try { hi_v13 = hi_v14“hasOwnProperty”;} catch ($r0) { hi_v13 = false;} }如果你的navigator对象是自己注入的水货版本,那就露馅了……检查浏览器特征这块代码很复杂,还没分析完,现在能看出来的包括:navigator.languages - 据说在headless chrome中是没有这个字段的navigator.plugins - 据说无头和有头的chrome返回的插件列表不一样WebGL能力检查有一大段代码是在canvas上用webgl绘图,没搞过webgl,现在还不明白,但肯定也是检查浏览器特征手段之一破解思路其实并没有,还在抓瞎中……一些很不成熟的点子:可以拦截window对象的写入,把全局函数通过toString()搞到源码,再通过acorn/astring大法搞事情之后替换成eval版本但是函数名、变量名全部都混淆了,因此需要用一些特征来检测每个函数,目前想到的点子是将AST上所有节点分类计数连接成字符串,这样绝大多数方法的特征都是唯一的需要将浏览器特征检测代码在桌面浏览器、手机浏览器、桌面无头浏览器分别运行,看看到底有啥区别,然后注入代码修改特征进行欺骗