【爬蟲系列】0. 無內鬼,破解前端JS參數簽名


PS:這是一個系列,坐等我慢慢填坑。

PS:不太會直接能跑的代碼,拋磚引玉。

PS:那些我也不太熟練的就不搞了,包括(破滑塊、驗證碼..)

PS: 反編譯搞Apk會有很長的幾個文章,稍后慢慢更。

 

 


 

最近,和某XX單位的網站gang上了。

他們家的網頁只允許在微信客戶端打開,抓包就跟蛋疼了。

不過,手上有Root后的Google Nexus5X,也有 whistle 跨平台抓包工具

這個倒沒太折騰,抓包工具證書往手機系統根證書一扔,完事。

安卓7.0及以上用戶證書導入的問題 - entr0py - 博客園

 

抓到了包,下面蛋疼的事情開始了。


前言: body 加密

嗯?請求Body是這一串東西?

嗯?時隔三年,神奇海螺又出現了?

// json { "encryKey": "14a625eb2ec957f9b53412b01de37044e7e2aa6b4b911111c75091cba2a0315b", "data": "44bc0dab8db8017603586f40554742d14a0c23dd009e35cae5b5ac87dbf7962a311fae30070763d2b48b564d72191fd07a881ebcccfb7c0fdd33e4067bc5119cee5e2fa5eaac10da995c86c8a092dcc3", "sign": "cc3f924bbb6d57a15bd3e130230f51e55a04fa9e459d177440fbd10bce4b02d0", "timestamp": 1627224237000 }

很明顯,每個單詞我們都知道,每個字母和數值我們也懂。

但是....

除了timestamp我們可以生成,其他的明顯是加密后數據和簽名。


一點都不高能的預警

先說一下思路:

  1. 撈出核心JS文件
  2. 讀懂加密過程,撈出關鍵參數
  3. 用其他語言實現涉及到的加密函數
  4. 對比加密結果是否一致,嘗試去偽造請求

撈JS

 

首先這貨的微信瀏覽器的,所以沒辦法使用瀏覽器開發者工具。

不過,抓包上面不是搞掂了么?直接從抓包結果看HTML就完事。

乖乖一個個請求看,找到第一個返回HTML的響應體。

於是,找到了這個...

哦, 看到了....

<script src="/umi.a029b1fd.js"></script>

看到這貨,本寶寶小心臟有點亂跳了。

訪問一看。

害,看起來沒的那么簡單啊,明顯這貨是被webpack打包后的JS文件。

先下載回來再說...


umi.a029b1fd.js 下載到本地,一看1.5M。

打開一看,毫無疑問沒有格式化...

得嘞,大JS文件格式化,先打開我的Ubuntu機器再說。

哦,VS Code format崩;加大內存,繼續崩。

搜了一波,找到了神器 Online JavaScript beautifier

文件扔上去,再下載下來...

完事。

 

毫無疑問,這就是webpack打包后的東西了。

沒得事,全局搜一波上面的參數。

完美,看到這個,是不是答案已經出來了。

看看,每個參數怎么算的都告訴我了,還能做撒?還需要做撒?

於是,我午睡去了。

........

其實,最頭疼的東西就在這里了。

這時候,很多人會說,上AST 還原JS嘛。

AST抽象語法樹--最基礎的javascript重點知識,99%的人根本不了解 - SegmentFault 思否

嘖嘖嘖。

道理是這個道理,不過還有其他的思路嗎?

直接寫個index.html 引入這個JS也成的啊。

<html> <body> <h1>test</h1> </body> <script src="./app.js"></script> </html>

 

開始解JS

 var O = Date.parse(new Date),
 Y = Object(h["e"])(!1, 16),
 j = Object(h["a"])(JSON.stringify({
 data: b,
 timestamp: O
                        }), Y),
 P = Object(h["f"])(j, Y);
 T = {
 encryKey: Object(h["a"])(Y, h["b"]),
 data: j,
 sign: P,
 timestamp: O
}

在代碼里面看到了一堆這種 h["a"] h["e"],然后跟着參數(j, Y)。

我們明顯知道,這是JavaScript的一個函數調用,h看起來是一個map或者是對象,

這里是在調用它的a方法,傳入了(j, Y)

在這里,我們最想知道的就是h["a"]的定義是什么樣的,

因為知道定義實現,也就能還原完整代碼邏輯。

跟一點代碼(VS Code跳轉定義功能),我們能看到h是什么?

h = n("jNxd"),

看到這里其實是很頭疼的,n是個什么玩意我們完全無從得知。

不過這里也能得到點信息,各種各樣的函數或者對象都是綁定在”n“上的,

我們只要拿到n,我們需要的h,h[a], h[b] 都知道是什么了。

怎么拿到n呢? 友情提示,善用debugger。


開始尋找n

剛剛我們已經完整把app.js(umi.a029b1fd.js格式化之后的文檔)導入我們的index.html

用瀏覽器打開看看頁面。

頁面沒什么問題,我們嘗試在app.js上面加點debugger吧。

加在哪呢?(目的只有一個,能獲取的到n)

....h附近前面可以嗎?

瀏覽器控制台打開,刷新頁面,切換到Console頁面。

 

試試這里能不能有n對象。

咦,看起來有戲。

試試 h = n("jNxd")

很好,很好,看起來這里是OK的,

h["a"]也是一個函數,符合我們上面看到的。

點擊一下上面h["a"]輸出的內容,可以跳轉到函數定義。

於是,我們來到了重頭戲。

           s = (e, t) => { var n = i.a.enc.Utf8.parse(t), r = i.a.enc.Utf8.parse(t), o = i.a.enc.Utf8.parse(e), a = i.a.AES.encrypt(o, n, { iv: r, mode: i.a.mode.CBC, padding: i.a.pad.Pkcs7 }); return i.a.enc.Hex.stringify(a.ciphertext) }, u = (e, t) => { var n = i.a.enc.Utf8.parse(t), r = i.a.enc.Utf8.parse(t), o = i.a.enc.Hex.parse(e), a = i.a.enc.Base64.stringify(o), s = i.a.AES.decrypt(a, n, { iv: r, mode: i.a.mode.CBC, padding: i.a.pad.Pkcs7 }); return s.toString(i.a.enc.Utf8).toString() }, c = (e, t) => i.a.enc.Hex.stringify(i.a.HmacSHA256(e, t)), l = (e, t, n) => { var r = "", i = t, o = ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z", "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z", "-", ".", "~", "!", "@", "#", "$", "%", "^", "*", "(", ")", "_", ":", "<", ">", "?"]; e && (i = Math.round(Math.random() * (n - t)) + t); for (var a = 0; a < i; a += 1) { var s = Math.round(Math.random() * (o.length - 1)); r += o[s] } return r } 

看看,代碼都出來了,還需要撒?

今天的教程結束,早點睡....

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 


微微一笑,好像沒那么簡單。

 

寶哥微微一笑,發現事情沒那么簡單。

 

 

已知,上面這一堆東西,

要不是 i.a.AES,要不是 HmacSHA256

沒什么花樣。

 

那么最大的問題就是,

這個加密過程是怎么搞的。

加密向量是什么?秘鑰在哪?

SHA256用的是什么參數?參與加密的數據是怎么拼接的?

 

PS:還在寫....

 


重頭戲上場

回到上面的代碼

if (p["d"] && "get" !== u.toLowerCase() && !g) { var O = Date.parse(new Date), Y = Object(h["e"])(!1, 16), j = Object(h["a"])(JSON.stringify({ data: b, timestamp: O }), Y), P = Object(h["f"])(j, Y); T = { encryKey: Object(h["a"])(Y, h["b"]), data: j, sign: P, timestamp: O } } 

這里可以看出每個變量是怎么來的。

encryKey = Object(h["a"])(Y, h["b"]) // 調用了a方法

O= Date.parse(newDate) // 生成了時間戳

Y=Object(h["e"])(!1,16) // 調用了e方法

P=Object(h["f"])(j,Y) 調用了f方法

 

於是我們執行一下看看。

 

Y看起來是個隨機字符串,j,p看起來都是字母+數字組合起來的字符串。

 

分別到定義出看看是撒。

h["e"]

 l = (e, t, n) => { var r = "", i = t, o = ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z", "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z", "-", ".", "~", "!", "@", "#", "$", "%", "^", "*", "(", ")", "_", ":", "<", ">", "?"]; e && (i = Math.round(Math.random() * (n - t)) + t); for (var a = 0; a < i; a += 1) { var s = Math.round(Math.random() * (o.length - 1)); r += o[s] } return r } 

哦,生成了隨機字符串。

h["a"]

            // n("jNxd")["a"] encryKey
            s = (e, t) => {
                var n = i.a.enc.Utf8.parse(t),
                    r = i.a.enc.Utf8.parse(t),
                    o = i.a.enc.Utf8.parse(e),
                    a = i.a.AES.encrypt(o, n, {
                        iv: r,
                        mode: i.a.mode.CBC,
                        padding: i.a.pad.Pkcs7
                    });
                return i.a.enc.Hex.stringify(a.ciphertext)
            },

哦, AES.encrypt 加密,使用的是CBC/Pkcs7對齊

h["f"] HmacSHA256

(e, t) => i.a.enc.Hex.stringify(i.a.HmacSHA256(e, t))

h["b"] 直接返回了一個固定的字符串。(毫無疑問是IV向量和加密Key)

 

看看,沒了啊。

核心的加密代碼就這點。

 

                        var O = Date.parse(new Date), Y = Object(h["e"])(!1, 16), j = Object(h["a"])(JSON.stringify({ data: b, timestamp: O }), Y), P = Object(h["f"])(j, Y); T = { encryKey: Object(h["a"])(Y, h["b"]), data: j, sign: P, timestamp: O } 

所以重點代碼又回到這里了,看懂這里就是所有的邏輯了。

讀一下,也就這樣。

  1. 獲取當前時間戳 O
  2. 生成隨機字符串 Y
  3. 把傳入的b(body)和時間戳組合到一起,設定IV向量為Y,使用AES 加密
  4. 把密文 j 和 Y進行SHA256簽名
  5. 最用把Y也用AES 加密,這個時候加密IV向量為h["b"]

 

換個人話

 

寫死了一個iv向量,隨機生成一個16位的key,從iv向量對這個Key加密,

用這個Key作為另一個iv變量對請求體Body加密,

然后把上面一堆東西做一個sha256的簽名。

哦,說好的前端參數簽名加密。


到這里,其實破解過程已經完成了。

這基本也是我睡醒之后,看了台風吃了晚飯回來之后,

開始抄Python 把上面邏輯實現一波的前置思路了。

這個時候,我們也要知道一些東西。

JS加密庫 CryptoJS

Python對應的加密庫 pycrypto

最后用Python實現這個完整邏輯還是折騰了好一會的,

也抄了不少別的代碼,最后貼一下。

from Crypto.Cipher import AES import base64 import time import binascii class AesEncrypt: def __init__(self, key, iv): self.key = key.encode('utf-8') self.iv = iv.encode('utf-8') # @staticmethod def pkcs7padding(self, text): """明文使用PKCS7填充 """ bs = 16 length = len(text) bytes_length = len(text.encode('utf-8')) padding_size = length if (bytes_length == length) else bytes_length padding = bs - padding_size % bs padding_text = chr(padding) * padding self.coding = chr(padding) return text + padding_text def aes_encrypt(self, content): """ AES加密 """ cipher = AES.new(self.key, AES.MODE_CBC, self.iv) # 處理明文 content_padding = self.pkcs7padding(content) # 加密 encrypt_bytes = cipher.encrypt(content_padding.encode('utf-8')) # 重新編碼 result = str(base64.b64encode(encrypt_bytes), encoding='utf-8') print("加密hex:", str(binascii.hexlify(encrypt_bytes),encoding='utf-8')) return result def aes_encrypt_to_hex(self, content): """ AES加密 """ cipher = AES.new(self.key, AES.MODE_CBC, self.iv) # 處理明文 content_padding = self.pkcs7padding(content) # 加密 encrypt_bytes = cipher.encrypt(content_padding.encode('utf-8')) # 重新編碼 # result = str(base64.b64encode(encrypt_bytes), encoding='utf-8') return str(binascii.hexlify(encrypt_bytes),encoding='utf-8') def aes_decrypt(self, content): """AES解密 """ cipher = AES.new(self.key, AES.MODE_CBC, self.iv) content = base64.b64decode(content) text = cipher.decrypt(content).decode('utf-8') return text.rstrip(self.coding) if __name__ == '__main__': key = '123' iv = '123' ts = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()) p_json = { "CompanyName": "testmall", "UserId": "test", "Password": "grasp@101", "TimeStamp": "2019-05-05 10:59:26" } a = AesEncrypt(key=key, iv=iv) e = a.aes_encrypt("123") d = a.aes_decrypt(e) print("加密:", e) print("解密:", d) 

 

好了,

真完了,

睡覺睡覺。

 

 

 

編輯於 8 分鍾前


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM