JS反混淆——構造可知DeviceToken、nouce與token

  • 清空全部cookies後打開任意一本書的詳情頁,打開控制檯中的Network。以下圖所示,瀏覽器發出的第一個XHR請求爲/bind,其中DeviceToken,nonce,Token均爲POST中提交的數據。
  • 找到訪問網頁的請求,以下圖所示,返回頭中沒有出現set-cookie字段,能夠判斷DeviceToken,nonce,Token均爲js生成的。
  • 找到發送/bind/請求的Initiator,發現只與vendor.*.jsapp.*.js有關,且出現了webpackJsonpcomputedupdateRoute等字段,猜想該網站是使用vue-cli構建的。
  • 下載並解碼vendor.*.jsapp.*.js,在app.*.js中搜索/bind,找到相關代碼。

    該段函數流程大體爲發送/bind請求至後端,若是返回的結果中Success字段爲true,則將數據中的DeviceToken,DeviceKey放到cookie中。而發送的DeviceTokennewGuid()函數生成。
    • 找到該段函數的函數名爲registerApp,搜索調用。
    • 找到調用registerApp的代碼,根據上文watchmethods能夠判斷出這是一個vue的組件,監控到路由變化則調用registerApp
    • 不可貴出得出全部暴露到this中的函數名均存在變量66_0x3458中。
    • newGuid轉爲utf-8編碼,爲\x6E\x65\x77\x47\x75\x69\x64,找到其在變量66_0x3458的位置
    • 將163轉爲16進製爲a3,在app.*.js中搜索0xa3,找到定義。
    • _0x6bc57['JiAvF']_0x6bc57['jybZN']爲調用的其餘函數,分別搜索得出對應函數:

    • 用對應代碼替換上述函數,丟進jsnice轉換:
    • 重命名變量,最後得出newGuid的代碼以下,生成一個32的uuid:
function newGuid() {
    let uuid = "", i = 1;
    for (; i <= 32; i++) {
        uuid = uuid + Math.floor(16 * Math.random()).toString(16);
        if (!(8 !== i && 12 !== i && 16 !== i && i !== 20)) {
            uuid = uuid + "-";
        }
    }
    return uuid;
}
  • 返回/bind的代碼,請求中的data實際只有三個字段,與看到XHR請求中字段數量不一致,判斷是作了一個攔截器,而通常的vue-cli項目中使用的http請求庫爲axios,因此直接搜索interceptors找到攔截器代碼。
  • 全部的字段均在攔截器中定義嗎,其中nonce字段爲調用的uuid函數,直接搜索['prototype']['uuid'],找到函數定義:
    • 函數中僅有a66_0x39d4('0xb9')是未知的,調用函數,找到對應數值爲'0123456789abcdef':
    • 拖進jsnice,再稍加修飾,發現nouce是生成一個36位的uuid:
function uuid() {
    let s = [];
    for (let i = 0; i < 36; i++)
        s[i] = '0123456789abcdef'.substr(Math.floor(16 * Math.random()), 1);
    s[14] = '4';
    s[19] = '0123456789abcdef'.substr(3 & s[19] | 8, 1);
    s[8] = s[13] = s[18] = s[23] = '-';
    return s.join('');
}
  • 最後剩下Token字段,在攔截器代碼中找到定義:
    • 其中a66_0x39d4('0x87')爲default,a66_0x39d4('0x53')爲prototype,能夠判斷二者處理方式徹底一致,都是將data拷貝一份,調用deleteKey函數後,再調用setToken函數,將Token賦值進data,再發送。
      • 搜索['prototype']['deleteKey']找到deleteKey函數:
      • 稍加修飾,deleteKey函數就是刪除無用字段:
function deleteKey(data) {
    for (let word in data) {
        if (data.hasOwnProperty(word)) {
            const val = data[word];
            if (!(0 === val || val || "boolean" == typeof val)) {
                delete data[word];
            }
        }
    }
    return data;
}
-  搜索`setToken`找到setToken函數,初步判斷setToken主要是將Object類型的data根據必定格式轉爲字符串後加入混淆字符`OI2W_YeeUw%OHutl`後再加密:


- 搜索MbnVH找到MbnVH函數,用來判斷變量類型

- 搜索_0x1e733e找到定義,發現其上方的與_0x591b6a有關。

- 在app.*.js中搜索fZjL無果,轉到vendor.*.js中搜索,找到定義:

- 繼續在vendor.*.js中搜索jFbC,找到定義:

- 將未知量替換後,setToken代碼以下:vue

function setToken(data) {
    let c = Object.assign({}, data);
    let s = Object.keys(c).map(function (k) {
        return k.toLowerCase() + "/" + k + "=" + c[k];
    }).sort().map((vo) => {
        return vo.substring(vo.indexOf("/") + 1);
    }).join("") + "OI2W_YeeUw%OHutl";
    return encryption(s,1);
}
- Token的值最終與encryption函數有關,將encryption轉爲utf-8編碼\x65\x6E\x63\x72\x79\x70\x74\x69\x6F\x6E,找到位置:


- 將174轉爲16進製爲0xae,在app.*.js中找到定義:

- 傳入的參數值爲*,1時,只調用case 0x1中的代碼,看到['toString'](CryptoJS[a66_0x39d4('0xb0')]['Hex']),初步判斷是將字符串進行SHA1或MD5加密後,再進行Hex編碼。

- 搜索_0x33c78e找到定義,發現其與_0x7558ee有關,簡單搜索發現_0x7558ee的值爲_0xbc0af2('Ff/Y'):


- 在vendor.*.js中搜索Ff/Y找到代碼:

- 根據_doReset中定義的數組與crypto-js中SHA1與MD5代碼比對,發現Ff/Y對應的是SHA1:

- 通過修飾,encryption函數的代碼以下,將字符串SHA1加密後再Hex編碼:webpack

function encryption(val) {
    return Crypto.SHA1(val.toString()).toString(Crypto.enc.Hex);
}
  • 至此已經知道了三個關鍵字段的構造方法,假裝一個/bind請求測試,返回結果成功:
相關文章
相關標籤/搜索