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 分鐘前