2019年末逆向複習系列之Boss直聘Cookie加密欄位__zp_stoken__逆向分析

  • 2019 年 12 月 24 日
  • 筆記

嗨!這個最有深度的『研究筆記』由程式設計師界最會排版の追星族運營?

2019年

12月16日

推薦幾個高品質公眾號!

小編:Lateautumn4lin(逆向小小小學生)

鄭重聲明:本項目的所有程式碼和相關文章, 僅用於經驗技術交流分享,禁止將相關技術應用到不正當途徑,因為濫用技術產生的風險與本人無關。

這篇文章是公眾號《雲爬蟲技術研究筆記》的《2019年末逆向複習系列》的第七篇:《Boss直聘Cookie加密欄位__zp_stoken__逆向分析》

本次案例的程式碼都已上傳到Review_Reverse上面,後面會持續更新,大家可以Fork一波。

具體加密JS可以在Review_Reverse.boss_zp.encrypt.js中看到,替換具體的參數即可。

背景分析

Boss直聘大概可以算是目前最火的招聘軟體了!幾個月前的火遍全網的「找工作,馬上,和老闆談!」真的是洗腦了一遍又一遍啊!

鑒於Boss直聘的職位更新速度快,職位發布多的兩大特點,很多做行業分析和各地崗位差距報告的人都會利用Boss直聘的數據來做分析,幾個月前,Boss直聘對他們的介面在Cookie方面做了__zp_stoken__欄位的加密,這次案例就用它來做逆向分析。

逆向分析

__zp_stoken__欄位生成流程分析

我們需要分析的是__zp_stoken__欄位,我們看看它是在哪裡生成的

我們要獲取Cookie生成的過程,先把網站的數據清空,在Chrome中的Application中利用Clear site data清理好該網站下的數據,然後我們重新刷新網站(我們需要在具體的職位資訊頁面刷新,因為首頁不需要Cookie就能直接訪問),看看Network列表中的Cookie生成過程

我們首先會訪問具體的職位資訊頁面,得到一個HttpCode302的響應,代表這次的訪問重定向到一個新的URL了,這個URL可以在響應中的Location中看到,拿到的是security-check.html這個URL,我們看看這個URL是什麼內容

這個URL具體有幾個Param

  • name
  • ts
  • callbackUrl
  • srcReferer
  • seed

根據多次的訪問測試,發現變化的欄位主要是seednamets,也就是這個URL是動態的,我們直接下斷點是打不到具體的地方的,在Chrome里我們也拿不到這個鏈接的具體響應內容,我們利用Fiddler來獲取鏈接內容

保存響應內容到html中,具體看看裡面的內容,文件中搜索__zp_stoken__

var Cookie = {
    get: function(name) {          var arr,              reg = new RegExp("(^| )" + name + "=([^;]*)(;|$)");          if ((arr = document.cookie.match(reg))) {              return unescape(arr[2]);          } else {              return null;          }      },      set: function(name, value, time, domain, path) {          var str = name + "=" + encodeURIComponent(value);          if (time) {              var date = new Date(time).toGMTString();              str += ";expires=" + date;          }          str = domain ? str + ";domain=" + domain : str;          str = path ? str + ";path=" + path : str;          document.cookie = str;      }  };    var url = window.location.href;  var seed = decodeURIComponent(getQueryString("seed")) || "";  var ts = getQueryString("ts");  var fileName = getQueryString("name");  var callbackUrl = decodeURIComponent(getQueryString("callbackUrl"));  var srcReferer = decodeURIComponent(getQueryString("srcReferer")||'');    if (seed && ts && fileName) {      seriesLoadScripts("security-js/" + fileName + ".js", function() {          var expiredate = new Date().getTime() + 32 * 60 * 60 * 1000 * 2;          var code = "";          var nativeParams = {};          var ABC = window.ABC || frame.contentWindow.ABC;          try {              code = new ABC().z(seed, parseInt(ts)+(480+new Date().getTimezoneOffset())*60*1000);          } catch (e) {}          if (code && callbackUrl) {              Cookie.set("__zp_stoken__", code, expiredate, COOKIE_DOMAIN, "/");              // 據說iOS 客戶端存在有時寫cookie失敗的情況,因此調用客戶端提供的方法,交由客戶端額外寫一次cookie              if (typeof window.wst != "undefined" && typeof wst.postMessage == "function") {                  nativeParams = {                      name: "setWKCookie",                      params: {                          url: COOKIE_DOMAIN,                          name: "__zp_stoken__",                          value: encodeURIComponent(code),                          expiredate: expiredate,                          path: "/"                      }                  };                  window.wst.postMessage(JSON.stringify(nativeParams));              }                if(srcReferer && isSeo(srcReferer) && srcReferer != 'https://m.baidu.com/'){                  window.location.replace(srcReferer);              } else {                  window.location.replace(callbackUrl);              }          } else {              window.history.back();          }      });  } else {      if(srcReferer && isSeo(srcReferer) && srcReferer != 'https://m.baidu.com/'){          window.location.replace(srcReferer);      }else if (callbackUrl) {          window.location.replace(callbackUrl);      } else {          window.history.back();      }  }  

可以看出__zp_stoken__是這麼生成的

var seed = decodeURIComponent(getQueryString("seed")) || "";  var ts = getQueryString("ts");  code = new ABC().z(seed, parseInt(ts)+(480+new Date().getTimezoneOffset())*60*1000);  

簡化一下

480+new Date().getTimezoneOffset() = 0  code = ABC.z(seed,ts)  

如上所示,通過傳入的seedts參數調用實例ABCc方法來生成,給當前的document對象加上帶__zp_stoken__Cookie之後重新載入頁面,這樣整個從無Cookie訪問到生成Cookie重載頁面訪問的流程就分析到這裡。

__zp_stoken__生成逆向分析

1. 切中關鍵語句,下斷點

剛才說了security-check.html這個問題是動態的,我們不能去給這個文件下斷點,重新看看security-check.html的頁面源碼

var fileName = getQueryString("name");  seriesLoadScripts("security-js/" + fileName + ".js", function())  

載入了包含加密方法的Js文件,fileNamesecurity-check.htmlparams中的name欄位,我們在SourcesPage中找到該Js文件,整個文件一共是5381

上面我們分析了加密使用ABCz方法,在文件中搜索ABC字元,找到了z方法

先對最後的return語句打斷點

重新刷新頁面,可以獲取的整個方法中各個參數的值,接下來開始分析整個加密方法的流程

2. 刪繁就簡,核心流程分析

分析z加密方法

ABC[_0x56ae('0x3e')]['z'] = function(_0x172aa3, _0x38e762) {    //_0x56ae('0x3e') 是 prototype,ABC[_0x56ae('0x3e')]['z']也就是給ABC方法的prototype屬性中的鍵z指定方法,_0x172aa3是seed,ts是時間戳      var _0x3a1f8c = {};      _0x3a1f8c[_0x56ae('0x3f')] = function(_0x5362ae, _0x320fdd) {          return _0x5362ae == _0x320fdd;      }      ;      _0x3a1f8c[_0x56ae('0x40')] = _0x56ae('0x41');      _0x3a1f8c[_0x56ae('0x42')] = _0x56ae('0x43');      _0x3a1f8c[_0x56ae('0x44')] = function(_0x2e473d, _0x3ad5ac, _0x1c497c) {          return _0x2e473d(_0x3ad5ac, _0x1c497c);      }      ;      _0x3a1f8c[_0x56ae('0x45')] = function(_0x1f3c2a, _0x218684) {          return _0x1f3c2a + _0x218684;      }      ;      try {          if (document && _0x3a1f8c[_0x56ae('0x3f')](_0x3a1f8c[_0x56ae('0x40')], typeof document[_0x56ae('0x46')])) {              _0x5606fe = _0x172aa3;              _0x2f4975 = _0x38e762;          } else {              _0x5606fe = _0x3a1f8c[_0x56ae('0x42')];          }      } catch (_0x1f6304) {          _0x5606fe = _0x3a1f8c[_0x56ae('0x42')];      }      //上面幾項是給dict賦值,沒有什麼難度,可以不做修改,重點是最後的return語句中的方法      return _0x3a1f8c[_0x56ae('0x44')](_0x49423a, this, _0x3a1f8c[_0x56ae('0x45')](_0x172aa3[_0x56ae('0x47')], 0xb));  

return語句是這樣,我們在console中獲取每個參數的值,看看能不能做精簡

  • _0x3a1f8c[_0x56ae('0x44')]
function(_0x2e473d, _0x3ad5ac, _0x1c497c) {          return _0x2e473d(_0x3ad5ac, _0x1c497c);  }

_0x3a1f8c[_0x56ae('0x44')]是這樣一個方法,參數一是方法,參數二、三作為參數一方法的參數

  • this
function ABC() {      this['i0'] = 0x0;      this['d'] = [this];  }

this就是ABC實例,可以在同一個文件中搜索ABC可以得到

  • _0x3a1f8c[_0x56ae('0x45')](_0x172aa3[_0x56ae('0x47')], 0xb

這個參數直接放在console裡面運行,得到固定值是55,

從上面分析來看,return語句可以直接簡化成

_0x49423a(ABC,55)  

3. 還是那句話,缺啥補啥

我們現在拿到了關鍵語句,接下來就是調試_0x49423a方法,往js文件裡面添加缺少的變數和函數方法,出於簡單的考慮,直接使用node來調用js文件

直接拿node運行js文件得出來的結果和在Chromeconsole里運行js內容得到的結果不一樣,看來還需要調試在哪個地方引用的windowsthis之類的值兩個運行環境有差別

__zp_stoken__參數生成注意要求

  1. Js加密函數所在文件不同,不過文件內容邏輯基本相同,只用分析一個文件即可
  2. __zp_stoken__參數生成之後,不能直接在Cookie中使用,需要做URLencode才能使用

複習要點

  1. 這個案例也是很好的動態Cookie生成的案例,和之前的努比亞論壇的Cookie生成案例類似,利用window.location.href/replace/reload()這些機制來重載頁面,具體如下圖所示
  2. 在調試的時候注意關鍵詞的值,比如thisdocumentwindows這些,往往有時候雖然方法走通了,但是值不對,很有可能就是上面這些值在賦值時發生了錯誤。

END

鉄子,「在看」來一個