某網站高度加密混淆的javascript的分析

996.icu

前言

對某網站加密混淆後的javascript代碼也算分析了一段時間了,雖然還沒搞出來,但多少有些心得,這裏記錄一下。javascript

工具和資料

分析過程

獲取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中了php

    • 一部分無參的函數調用,其實返回的就是常量字符串
    • 還有一些_$xx.call的,看了一下,其實就是系統方法,好比String.fromCharCode,Array.prototype.slice等
  • 所以能夠編寫一段控制檯腳本,遍歷window對象中全部形似_$xx的成員,判斷其類型和函數執行結果。這樣便可將常量字符串映射、系統方法映射等搞出來。在控制檯執行下面這段代碼就能夠把字符串映射表弄到。html

    (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行全是正面硬懟的……
這裏記錄一部分已經發現的反破解手法吧。java

不斷主動中斷干擾調試,並檢測是否有動態分析行爲

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,如今還不明白,但確定也是檢查瀏覽器特徵手段之一
相關文章
相關標籤/搜索