Akamai保護的相關網站(IHG,TI)學習記錄
之前沒怎么看web,就看抖音的時候看過一下web版本,最近接觸了下Akamai,主要看了下面2個網站:
https://www.ihg.com/
https://www.ti.com
相比二進制的逆向,這個套路還是有點不同,這里記錄下分析過程,技術交流用。
現在回過頭來看,主要是兩個部分:
1、指紋檢測,發起的http請求中的ja3指紋要跟瀏覽器一致的(還要考慮不同版本的瀏覽器有特定的header字段,總之就是要偽裝成真實瀏覽器請求)。
2、JS逆向,這個主要就是反混淆調試邏輯了,處理sensor_data數據請求,對于搞過二進制逆向的,這個反混淆感覺也不是必須的,就算是直接硬扛,也比PC的VMP簡單,當然能夠處理,肯定更加方便調試。
1、彎路部分
剛開始也是網上搜了下,資料不多,不是舊版本就是沒怎么細講的(也有可能是講得太細,就會被封,之前發過一篇小紅書 V7.37 學習記錄(https://bbs.pediy.com/thread-272520.htm),感覺也沒寫很細,被移會員區了,其實就相當于刪帖了)。
初始搞的過程中最大問題就是發現請求結果不是預期的時候,不知道是指紋問題,還是 akamai sensor_data數據有問題。
首先直接用node自帶的http模塊請求,發現也能返回,自己維護cookie,按Chrome請求記錄來發送,發現最后也能登錄TI,但是看返回頭里面有不正常的情況:
'ti_bm=Unknown%20Bot%20(AB5B20DF4B5B288A17DACF5E811F9ED8)%3amonitor%3a%3aRequest%20Anomaly%3a%3a;
path=/; domain=.ti.com.cn'
剛開始發現能登錄,也沒管這個,后來請求加入購物車的時候,死活不行,發送sensor_data也不行,總是返回一個信息:You don't have permission to access ...
這個時候才又開始檢查之前的返回狀態,cookie字段太多了,看數據比較復雜,覺得相關的有bm_sv,bm_mi,ak_bmsc,_abck,bm_sz,ti_bm等。
因為也不清楚這些cookie的返回邏輯,跟瀏覽器請求記錄比較中,有時候發現瀏覽器會多某個字段,就想到會不會是少發了某個請求,畢竟自己協議請求比瀏覽器實際請求是少很多的,后來補了很多請求,這個過程實際花了不少時間折騰,結果一樣,接著就想到跟瀏覽器實際請求的差異問題了。
首先就是想搞一個帶源碼的瀏覽器調試環境,Chrome瀏覽器本身是不開源的,后來發現了miniblink:
weolar/miniblink49: a lighter, faster browser kernel of blink to integrate HTML UI in your app. 一個小巧、輕量的瀏覽器內核,用來取代wke和libcef (github.com)
(https://github.com/weolar/miniblink49)
把源碼拖下來編譯了個版本(新版本沒開源了),跑TI網站比較慢,最后屏蔽了一些圖片顯示相關的,走了下登錄流程,發現返回的cookie字段中也有異常情況:

就是ti_bm中也返回了Unknown%20Bot,看起來miniblink請求也是過不了檢測的,并且后來比較請求返回的網頁內容,其實跟正常瀏覽器也是有不同的,比如Chrome請求返回頁面中會包含下面腳本:
<script >bazadebezolkohpepadr="355903178"script><script type="text/javascript" src="https://www.ti.com.cn/akam/13/1536a743" defer>script>
但是使用miniblink請求,或者node自帶http模塊請求,就沒有這個信息。
當時的想法還是想找一個直接能用的請求庫,嘗試過mytls等幾個號稱可以修改指紋的庫,實際測試也是不行,一時沒找到,后來想到之前搞過抖音的Cronet庫,是調通了整個請求流程的,就嘗試用抖音的Cronet庫來請求TI網站:
Executor executor = Executors.newSingleThreadExecutor();UrlRequest.Builder requestBuilder = cronetEngine.newUrlRequestBuilder(strUrl, new MyUrlRequestCallback(), executor); requestBuilder.addHeader("accept","text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9");requestBuilder.addHeader("cookie",getSendCookieByUrl(strUrl)); UrlRequest request = requestBuilder.build();request.start();

測試后發現,還真能返回上面的script信息了,這部分跟瀏覽器請求的倒是一樣了,但是最后實際請求加入購物車協議的時候,還是失敗。
折騰了一圈,各種組合請求還是沒搞定,畢竟這個庫的請求指紋跟瀏覽器還是不同的,那還是要從請求上保持跟瀏覽器的完全一致才行。
2、解決指紋,讓請求跟瀏覽器一致
這個開始也是準備硬杠的,打算按照wireshark記錄的瀏覽器請求算法來修改請求的算法組,查資料的過程中,發現了
lwthiker/curl-impersonate - Docker Image | Docker Hub
(https://hub.docker.com/r/lwthiker/curl-impersonate)
這個號稱可以完全模擬器瀏覽器的請求,按照說明本地部署了下環境,需要安裝docker:

測試了下訪問 https://ja3er.com/json

看起來確實跟瀏覽器的一致了,這里有個容器操作的小坑:從docker拷貝文件出來要注意容器名稱,不是ID,之前搞錯了一直錯誤:Error: No such container:path: curl-impersonate-chrome:/build/out/libcurl-impersonate.so.4.7.0

它這整個環境是容器,想直接用還不好用,開始看見有提供so,考慮直接寫app調用so來發起http請求,最后折騰了下,發現開發效率是個問題,用的是win系統,還是想搞個win版本的,這就涉及到需要自己編譯curl了,直接把上面的curl,還有boringssl,brotli,nghttp2源碼都拖下來,改了個win版本,編譯后生成了exe。
curl-impersonate的組件信息:

自己編譯的curl:

重新加上libz,編譯后

用編譯的culr請求https://ja3er.com/json測試,發現ja3跟瀏覽器訪問一樣了。
這個時候已經有源碼了,可以用C開發,實際寫協議請求邏輯仍然覺得麻煩,還是要考慮走腳本方式。
首先還是考慮node能夠直接調用,準備自己寫模塊封裝插件,發現有很多綁定工作要做,搜了下,找到下面這個:JCMais/node-libcurl: libcurl bindings for Node.js (github.com)(https://github.com/JCMais/node-libcurl)
這個已經做好了curl綁定,node可以直接用的,那現在的思路就是替換這個模塊里面的curl為自己編譯的了。
直接新建一個node插件工程,導入node-libcurl的綁定相關代碼,動態引入自己編譯的curl dll:

最后編譯替換原有的node_libcurl.node,然后node請求測試:
const curl = new Curl(); curl.setOpt('URL', "https://ja3er.com/json"); curl.setOpt('CERTINFO', true) curl.setOpt('FOLLOWLOCATION', true) curl.setOpt('SSL_VERIFYPEER', false) curl.setOpt('SSL_VERIFYHOST', false) curl.enable(CurlFeature.NoDataParsing); curl.setOpt(Curl.option.COOKIEFILE, cookieJarFile) curl.setOpt(Curl.option.COOKIEJAR, cookieJarFile) curl.on('end', function (statusCode, data, headers) { console.info(headers); this.close(); }); curl.on('error', curl.close.bind(curl)); curl.perform();
返回的信息跟瀏覽器一致的,到這里指紋問題就基本解決了,并且也能夠自動維護cookie了。
3、js逆向,處理sensor_data數據請求
在部署了Akamai的網站上,頁面返回時候會帶上類似下面這種腳本:
<script type="text/javascript" src="/RiyQJ/Gbek/k6cy/do/h9Bat/iw3rGrXz/ejRAYyE8BQU/eCteLm16/SD0">
這個路徑,第一次是get,拿到這個js,后續會post數據到這個地址。這個post的觸發時機及次數不同版本有點不同,IHG和TI是初始加載的時候都會發送post sensor_data,TI的次數更多(我測試3,4次都遇到過),然后就是點擊的時候會觸發,TI是每次點擊都會觸發post,IHG不會,IGH是拿到驗證過的_abck后,就不會再觸發請求了,這個js里面有判斷邏輯,第一個-1如果換成0了:DAA11479B85BD170A757258B40E228AD~0~
就表示驗證過了,后面就不需要再post了,當然要是切換頁面,還是要再走上面邏輯。
剛開始分析這個js的時候,找了些資料:通過ast初步還原某js - 『脫殼破解區』 - 吾愛破解 - LCG - LSG |安卓破解|病毒分析|www.52pojie.cn(https://www.52pojie.cn/thread-1471146-1-1.html)
JS逆向:AST還原極驗混淆JS實戰_一生向風的博客-CSDN博客_ast逆向(https://blog.csdn.net/weixin_47202458/article/details/106597220)
對于這個調試,一個就是通過代理替換,定向到本地文件或自己的本地服務器,還一個就是可以自己保存網頁,架設一個自己的服務器,這樣可以隨時改代碼來調試了。
本地調試的時候的,這個js路徑要保持一致,有代碼會檢測這個路徑格式:

TI的這個js邏輯要比IHG的復雜:
1、post的sensor_data數據的,IT有加密,IHG沒有。
2、IHG只用到了cookie中的_abck,TI多了個bm_sz。
我沒看過Akamai的低版本,從網上看有提到1.75版本,然后開IGH提交的sensor_data數據頭部:key_ver: '7a74G7m23Vrp0o5c9231281.75'
那這么看后面的數字就是版本號了,但是TI的是不同的,解密后,頭部是這樣的:7a74G7m23Vrp0o5c924489Ac8AWA+tulfQVzVpsCpdMA==
前段時間版本升級了下的,頭部變成這樣:
7a74G7m23Vrp0o5c924493oWV4VVGxQUElcrjPIc9GQQ==
看起來前面一段是固定的,中間數字是時間相關的,后面一串是版本相關的,這個字符串是js里面的一個變量值。
看起來是個base64串,解了下也沒看出個所以然,不管了,這個也不影響。
對于本地調試,修改過的代碼要跳過校驗,js里面有邏輯會檢查函數是否修改過,下面這里是校驗幾個函數的hash,對修改過的函數,校驗不過就會走錯誤分支:

能調試后,單純的邏輯逆向,相比二進制就簡單些了,后面要做的就是用node來構造環境,生成需要發送的sensor_data數據,主要是一些瀏覽器相關的環境構造,包括webgl信息、語音組件信息等,當然這個也要花點時間,有些需要跟瀏覽器的數據比較,如果發送的數據不對,就會返回類似這種錯誤:
'ti_bm=Unknown%20Bot%20(8D4B186BE16BB4B3B59A0C24C27BAE69)%3aslow%3a%3aJavaScript%20Fingerprint%20Not%20Received%20(BETA)%3a%3a; path=/; domain=.ti.com',
這個搞完后測試,發現可以加入購物車了:

這個sensor_data post要模擬完全,不然可能出現協議成功率問題,持續添加可以后,那就是多賬戶問題了,這個可以通過差異化不同賬號的請求環境來處理,比如模擬不同的瀏覽器參數。
除了這個js邏輯外,還有個上面提到的頭部js,pixel前綴的,這個會用來提交本地的環境參數的,整個網站中,還有些反爬蟲的處理,有的頁面是直接返回的js:

這種就要解析出腳本來執行了。
現在回過頭來看,要是替換掉miniblink里面的curl模塊,那就是一個帶源碼的瀏覽器調試環境了,有個完整的瀏覽器環境,對于驗證還是比較方便的。