javascript
某网站高度加密混淆的javascript的分析
前言
對某網站加密混淆后的javascript代碼也算分析了一段時間了,雖然還沒搞出來,但多少有些新得,這里記錄一下。
工具和資料
- https://jq.qq.com/?_wv=1027&k=5Bcu3YU? ?QQ群 - Javascript高級爬蟲 - 作者自建群,歡迎加入!
- https://github.com/rockswang/awesome-java-crawler? awesome-java-crawler - 我收集的爬蟲相關工具和資料
- https://github.com/rockswang/wsjs.saic.mmewmd?中國商標網加密接口 - 僅做演示
- https://segmentfault.com/a/1190000017286304? 前一篇文章 - 記錄了之前嘗試的一些初步研究成果
- https://github.com/rockswang/java-curl? java-curl - java HTTP庫,可用來替換chrome網絡后端,更方便控制底層行為,如緩存、代理、監控、修改請求和應答等
- https://github.com/webfolderio/cdp4j?cdp4j - java版的Chrome Devtools Protocol實現,用于控制Chrome瀏覽器。最大的特點就是沒有"特點",你懂的.....
- https://beautifier.io/? beautifier.io - js代碼在線格式化
- https://github.com/estree/estree? estree - ECMAScript抽象語法樹(AST)業界標準
- https://tc39.github.io/ecma262?ECMAScript262語言規范 - 幫助理解estree
- https://github.com/acornjs/acorn? acornjs - ECMAScript編譯器前端,將js源碼解析成estree格式的AST
- https://github.com/davidbonnet/astring? astring - ECMAScript代碼生成器,將AST重新還原成js源碼
- https://wiki.openjdk.java.net/display/Nashorn?nashorn - java8以上自帶的javascript解析器,性能接近原生node
- https://segmentfault.com/a/1190000017401661? java中調用npm模塊 - 我的工作語言是java和kotlin,使用此方案調用js原生庫
- https://zhuanlan.zhihu.com/p/29196829? 商標局網站分析 - 類似的加密, 神箭手云的大佬寫的
- http://www.anyun.org/m/view.php?aid=9457&tdsourcetag=s_pcqq_aiomsg? 裁判文書網分析 - 另一篇類似網站分析
- http://www.qingpingshan.com/jb/javascript/239312.html?很早的一篇分析文 - 看特征是這種加密的早期版本
分析過程
獲取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.formatCharCode,Array.prototype.slice等
- 因此可以編寫一段控制臺腳本,遍歷window對象中所有形式_$xx的成員,判斷其類型和函數執行結果。這樣即可將常量字符串映射、系統方法映射等搞出來。在控制臺執行下面這段代碼就可以把字符串映射表弄到。
可讀性還原
- 拿到映射關系之后是不是簡單用正則表達式替換回去就完事大吉了呢?哪有那么簡單!函數的局部變量、局部函數有很大可能性和全局變量重名,如果用正則無腦替換回去絕對會被坑死!!要是代碼少倒也罷了,這里可有5000行代碼,差之毫厘謬以千里!
- 另外,不同函數的局部變量也存在大量重名,靜態分析時干擾嚴重,因此,應該將局部變量也替換成唯一且更有意義的名字,比如<函數名>_<變量索引>
- 因此,正確的方法是基于編譯原理進行語法級別的替換。看到這里是不是要棄療了?老子爬點數據還要寫編譯器?!
- 還好,js上已經有很成熟的業界標準和若干老練的第三方庫了,至少不用從龍書搞起.......
- 我這里選擇了acornjs和astring,前者用于將js源碼解析成抽象語法樹AST,后者將AST還原成js源碼。當然,有了AST就可以上下其手了.....
- 為了在java代碼中運行acornjs和atring,請參見參考中《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幾個系統函數是不是被注入了
- 知道這塊邏輯,就可用一些手段(https://segmentfault.com/a/1190000018742189)騙過去,不知道的話...
檢測當前窗口是否隱藏狀態
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,現在還不明白,但肯定也是檢查瀏覽器特征手段之一
總結
以上是生活随笔為你收集整理的某网站高度加密混淆的javascript的分析的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 某数加密的流程与原理简析
- 下一篇: 简单的JavaScript互斥锁