【JS逆向百例】cebupacificair 航空逆向分析

K哥爬虫發表於2024-11-18

750QYY.png

前言

近期在知識星球中,有位星友在逆向一個航司的時候,遇到了點阻礙,向我提問,本期就對該網站進行逆向分析:

752PCt.png

逆向目標

目標:cebupacificair 航空查詢逆向分析

網站:aHR0cHM6Ly93d3cuY2VidXBhY2lmaWNhaXIuY29tL2VuLVBILw==

抓包分析

開啟網站,找到返回機票資訊的機票查詢介面 ceb-omnix_proxy

75pJ63.png

75pjQj.png

目測,有這四個引數需要分析,分析之前先搜尋,免得是介面返回,發現 AuthorizationX-Auth-Token 是另外一個 ceb-omnix_proxy 介面返回的:

75pnv5.png

75pIIm.png

75pfL4.png

該介面有四個引數需要分析,也是有兩個 content,估計都大差不差,我們繼續先搜尋,發現 authorizationmain.xxx.js 檔案返回的:

75pixh.png

main.xxx.js 檔案是透過首頁載入的,大致流程都梳理清晰了,我們開始進行逆向分析:

75pd6U.png

逆向分析

cookie 值

從頭開始,我們請求首頁,發現他並沒有返回 main.xxx.js 檔案,而是返回的下圖內容:

75poNq.png

發現裡面的 JavaScript 程式碼就是設定了兩個 cookie 值,而且仔細就看會發現,它首頁介面請求了兩次:

75L9vs.png

請求 cookie 裡面就有設定的兩個 cookie 值,所以我們照著來操作就可以:

  • 注意點:存在指紋校驗,可以使用指紋庫,如 requests_go、curl_cffi 等等
__eccha_str = re.findall(r'var val = (.*?);', response.text)[0]
__ecbmchid = re.findall(r'__ecbmchid=(.*?)\"', response.text)[0]
__eccha = execjs.eval(__eccha_str)

cookies = {
    '__eccha':str(__eccha),
    '__ecbmchid': __ecbmchid
}

我們需要逆向分析的引數,就是兩個 ceb-omnix_proxy 介面的兩個 content,分佈在請求頭和請求引數中。

可以透過搜尋 ceb-omnix_proxy 或者 content: 就能定位到生成位置,也可以透過下 xhr 斷點或者 hook 等手段來跟值,本文都會提到。

第一個 ceb-omnix_proxy 介面

第一個 ceb-omnix_proxy 介面我們用 xhr 斷點來跟:

75LkIa.png

清空快取,重新整理網站,跟到第一次進入 main.xxx.js 檔案的位置,發現 e 變數中已經生成了 content 值:

75LT07.png

重新在 handle 下的位置上打下斷點,繼續往上跟:

75Lp4V.png

發現 m 由 i 生成,我們繼續重新打下斷點,清空快取,重新整理網頁:

75L0kL.png

定位到 t 為 ceb-omnix_proxy 介面,繼續往上跟:

75L3KJ.png

就找到生成位置了:

75LlPG.png

然後開始逆向分析,過程非常清晰,uniqueId window.crypto.randomUUID() 生成,Us 是由 main.xxx.js 檔案返回,透過搜尋就可以找到。message 是標準的 HmacSHA256 演算法加密生成,很容易就可以確認,重點就是兩個 content 值的生成,都是同一個加密,跟進去發現是 AES,但是跟標準的 AES 有區別:

75L7FB.png

第二個 ceb-omnix_proxy 介面

先不急,我們再來看第二個 ceb-omnix_proxy 介面,我們換個方式來跟,透過 hook headers

(function () {
    var _setRequestHeader = window.XMLHttpRequest.prototype.setRequestHeader;
    window.XMLHttpRequest.prototype.setRequestHeader = function (key, value) {
        if (key == 'content') {
            debugger;
        }
        return _setRequestHeader.apply(this, arguments);
    };
})();

75LQNt.png

點到 main.xxx.js 檔案中,發現 e 變數中已經生成了 content 值:

75LNab.png

我們重新下斷點,繼續跟:

75LqIe.png

跟到第二個 intercept 就發現引數 e 值中已經沒有生成 content 值了,大概生成邏輯就在下面。

我們也能發現相關引數確實就是在下面生成的,找到位置重新打斷點,進行分析:

75Lw0P.png

後面的過程,也非常清晰,重點同樣是 content 值的加密生成,這裡是由 this.cryptoService.eContent 函式加密生成的,跟進去後發現和第一個 ceb-omnix_proxy 介面的 content 值的加密函式一致:

75Ly5w.png

75LH46.png

重點是 Is.AES.encrypt 函式,可以發現 var Is = ce(7206);,進入 ce 後發現又是 webpack

不過跟我們平常扣的 webpack 又有點區別:

75LbkO.png

可以看到他的所有模組都是走的同一個函式,其實就是做的一個閉包:

75LgbQ.png

跟進 qa 這個函式,就會發現 D 就是呼叫的模組函式:

75LhPf.png

流程搞清楚了,就好分析了,兩種方式:

  1. 手動扣 webpack

可以只把 main.xxx.js 檔案中的所有模組複製下來,經過測試,沒問題:

75L8Fc.png

或者一個個扣,缺啥補啥,都是可以的。

  1. 自動扣 webpack

跟以往不同,不過人是活的,既然我們知道 D 就是呼叫的模組函式,而且有屬性 name,那還是跟之前一樣的方法,只不過位置不同,在 qa 裡新增一行程式碼,然後替換這個檔案:

75Lxq3.png

先斷到 var Is = ce(7206); 處,控制檯輸入 window.code = '';,然後斷到 return Is.AES.encrypt(e, t).toString(); 的位置上結束,最後在控制檯輸入 copy(window.code),這樣就獲得了需要的所有模組:

75L5aj.png

self = global;

var kkk;

!function (v) {
    var e,
    p = {};
  function n(e) {
    var a = p[e];
    if (void 0 !== a) return a.exports;
    var r = p[e] = {
      exports: {}
    };
    console.log(e)
    return v[e].call(r.exports, r, r.exports, n), r.exports;
  }
  n.m = v, e = [], n.O = (a, r, c, f) => {
    if (!r) {
      var u = 1 / 0;
      for (t = 0; t < e.length; t++) {
        for (var [r, c, f] = e[t], s = !0, l = 0; l < r.length; l++) (!1 & f || u >= f) && Object.keys(n.O).every(h => n.O[h](r[l])) ? r.splice(l--, 1) : (s = !1, f < u && (u = f));
        if (s) {
          e.splice(t--, 1);
          var o = c();
          void 0 !== o && (a = o);
        }
      }
      return a;
    }
    f = f || 0;
    for (var t = e.length; t > 0 && e[t - 1][2] > f; t--) e[t] = e[t - 1];
    e[t] = [r, c, f];
  }, n.n = e => {
    var a = e && e.__esModule ? () => e.default : () => e;
    return n.d(a, {
      a
    }), a;
  }, n.d = (e, a) => {
    for (var r in a) n.o(a, r) && !n.o(e, r) && Object.defineProperty(e, r, {
      enumerable: !0,
      get: a[r]
    });
  }, n.o = (e, a) => Object.prototype.hasOwnProperty.call(e, a), (() => {
    var e = {
      666: 0
    };
    n.O.j = c => 0 === e[c];
    var a = (c, f) => {
        var l,
          o,
          [t, u, s] = f,
          _ = 0;
        if (t.some(d => 0 !== e[d])) {
          for (l in u) n.o(u, l) && (n.m[l] = u[l]);
          if (s) var b = s(n);
        }
        for (c && c(f); _ < t.length; _++) n.o(e, o = t[_]) && e[o] && e[o][0](), e[o] = 0;
        return n.O(b);
      },
      r = self.webpackChunkOMNIX_Project_EN = self.webpackChunkOMNIX_Project_EN || [];
    r.forEach(a.bind(null, 0)), r.push = a.bind(null, r.push.bind(r));
  })();
  kkk = n;
}({
   // 複製需要的所有模組
});

ha = kkk(7206);

function encrypt(e, t){
    return ha.AES.encrypt(e, t).toString()
};

剩下的明文就比較簡單了,基本都是前面介面返回的東西,就不帶著分析了。

注意點 : 最後兩個 ceb-omnix_proxy 介面不要使用指紋庫去請求,可能會被風控。

學習程式碼,可於知識星球中領取,僅供參考。

結果驗證

75LAM5.png

相關文章