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我們可以生成,其他的明顯是加密後資料和簽名。
一點都不高能的預警
先說一下思路:
- 撈出核心JS檔案
- 讀懂加密過程,撈出關鍵引數
- 用其他語言實現涉及到的加密函式
- 對比加密結果是否一致,嘗試去偽造請求
撈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
}
所以重點程式碼又回到這裡了,看懂這裡就是所有的邏輯了。
讀一下,也就這樣。
- 獲取當前時間戳 O
- 生成隨機字串 Y
- 把傳入的b(body)和時間戳組合到一起,設定IV向量為Y,使用AES 加密
- 把密文 j 和 Y進行SHA256簽名
- 最用把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)
好了,
真完了,
睡覺睡覺。