鄭重聲明:本項(xiàng)目的所有代碼和相關(guān)文章, 僅用于經(jīng)驗(yàn)技術(shù)交流分享,禁止將相關(guān)技術(shù)應(yīng)用到不正當(dāng)途徑,因?yàn)闉E用技術(shù)產(chǎn)生的風(fēng)險(xiǎn)與本人無(wú)關(guān)。
這篇文章是公眾號(hào)《云爬蟲(chóng)技術(shù)研究筆記》的《2019年末逆向復(fù)習(xí)系列》的第五篇:《知乎登錄formdata加密逆向破解》
本次案例的代碼都已上傳到Review_Reverse上面,后面會(huì)持續(xù)更新,大家可以Fork一波。

逆向背景
知乎作為國(guó)內(nèi)最大的、最優(yōu)質(zhì)的的問(wèn)答平臺(tái)之一,它的高質(zhì)量回答是作為自然語(yǔ)言處理的最好的語(yǔ)料來(lái)源之一,不過(guò)想要獲取更全的知乎回答數(shù)據(jù)必須要登錄,涉及登錄的話必然少不了做自動(dòng)化登錄的處理,不過(guò)知乎的登錄post請(qǐng)求中的formdata是加密的


因此我們需要去尋找它的加密邏輯,本篇文章就是講解如何尋找破解formdata的加密,實(shí)現(xiàn)自動(dòng)化登錄知乎。
分析流程與逆向破解
因?yàn)閒ormdata只要一個(gè)加密后的字符串,對(duì)于我們來(lái)說(shuō),沒(méi)有一個(gè)明顯的特征讓我們?nèi)ト炙阉?,因此我們采用的是xhr斷點(diǎn)的方法去尋找哪里加密了formdata,我們通過(guò)initiator進(jìn)入

根據(jù)登錄api的url: /api/v3/oauth/sign_in來(lái)打xhr斷點(diǎn)

重復(fù)我們之前的登錄邏輯,可以看到,xhr斷點(diǎn)打在如圖所示位置

接下來(lái),我們就可以通過(guò)call stack調(diào)用棧來(lái)尋找哪里加密了formdata,一個(gè)個(gè)調(diào)用分析之后,看到如圖所示的地方比較符合我們期待的加密點(diǎn)

url確實(shí)是登錄的api,并且在post請(qǐng)求的data處,使用了r.decamelizeKeys()方法來(lái)處理,初步估計(jì)是加密方法,我們?cè)賹?duì)r.decamelizeKeys打斷點(diǎn),同時(shí)去掉之前的xhr斷點(diǎn),這樣能夠幫助我們更快、更準(zhǔn)確的定位到加密處


我們現(xiàn)在定位到decamelizeKeys的加密方法邏輯,這個(gè)方法包含兩個(gè)參數(shù),e和t,t現(xiàn)在我們還不能夠準(zhǔn)確了解它的含義,e參數(shù)的值可以直接在console里面進(jìn)行打印
captcha: "mdgv"
clientId: "c3cef7c66a1843f8b3a9e6a1e3160e20"
grantType: "password"
lang: "en"
password: "asdd"
refSource: "people"
signature: "f45d273cd16f4f80e4fee3434d1c3009fb2248cf"
source: "com.zhihu.web"
timestamp: 1575300515085
username: "+8617610771895"
utmSource: undefined
我們分析,captcha和signature這兩個(gè)參數(shù)相對(duì)其他參數(shù)來(lái)說(shuō)還是比較重要,因此我們著重分析這兩個(gè)參數(shù)
1. 尋找signature加密參數(shù)的加密邏輯
signature這個(gè)參數(shù)還是具有明顯特征的,我們可以全局搜索

match的地方只有兩個(gè)js文件,我們具體查看,可以在其中一個(gè)文件搜索signature關(guān)鍵字,發(fā)現(xiàn)它的相關(guān)代碼段,這段代碼很明顯的是hmac算法

涉及到的值有clientId、timstamp、grandType等
插入一句:Hmac算法大致解釋如下

Python的簡(jiǎn)單實(shí)現(xiàn)
import hmac
from hashlib import sha1
def hash_hmac(key, code, sha1):
hmac_code = hmac.new(key.encode(), code.encode(), sha1)
return hmac_code.hexdigest()
if __name__ == '__main__':
print(hash_hmac('08F5B4886112BC6F1E04FE42DACDB2E8', 'xinxin', sha1)
大概了解hmac算法是什么之后,我們?cè)倩仡^看看signature的hmac算法的邏輯是什么

具體的思路如圖所示,使用sha-1作為hash函數(shù),key是寫(xiě)死的,使用其他四個(gè)參數(shù)值作為message進(jìn)行加密,整理思路可以用python表達(dá)出來(lái)
def _get_signature(timestamp: int) -> str:
ha = hmac.new(
b"d1b964811afb40118a12068ff74a12f4",
digestmod=hashlib.sha1
)
grant_type = _login_data["grant_type"]
client_id = _login_data["client_id"]
source = _login_data["source"]
ha.update(
bytes(
(grant_type + client_id + source + str(timestamp)),
"utf-8"
)
)
return ha.hexdigest()
分析完signature參數(shù)之后,我們接著來(lái)分析captcha參數(shù)
2. 分析captcha的不同場(chǎng)景
captcha,顧名思義是驗(yàn)證碼結(jié)果相關(guān)的參數(shù),這個(gè)參數(shù)的值應(yīng)該是驗(yàn)證碼相關(guān),如圖所示,驗(yàn)證碼圖片是由這個(gè)接口來(lái)返回的


我們請(qǐng)求了這個(gè)接口,返回了圖片的base64格式,我們可以這么進(jìn)行保存
with open("./captcha.jpg", "wb") as f:
f.write(base64.b64decode(img_base64))
關(guān)于captcha的分析呢不涉及到具體的js,因?yàn)閏aptcha的值我們可以很明顯的看出來(lái)
當(dāng)我們請(qǐng)求中文驗(yàn)證碼lang=cn-也就是翻轉(zhuǎn)漢字點(diǎn)選,我們傳的值是:
captcha: "{"img_size":[200,44],"input_points":[[43.39996337890625,30.79999542236328],[135.39996337890625,22.79999542236328]]}"
clientId: "c3cef7c66a1843f8b3a9e6a1e3160e20"
當(dāng)我們請(qǐng)求英文驗(yàn)證碼lang=en-也就是四位英文字符,我們傳的值是:
captcha: "mdgv"
所以關(guān)于captcha,是根據(jù)我們請(qǐng)求驗(yàn)證碼類(lèi)型不同傳不同的值的,captcha參數(shù)也分析到這里,我們?cè)侔哑渌膮?shù)整理一下
3. 分析剩余其他參數(shù)
其他的參數(shù)就比較好看出來(lái)了
clientId: "c3cef7c66a1843f8b3a9e6a1e3160e20" 多次試驗(yàn),寫(xiě)死的
grantType: "password" 非第三方登錄的話都是這個(gè)值
lang: "en" 針對(duì)不同驗(yàn)證碼類(lèi)型
password: "asdd" 密碼
refSource: "people" 不變
source: "com.zhihu.web" 不變
timestamp: 1575300515085 時(shí)間戳
username: "+8617610771895" 用戶(hù)名
utmSource: undefined 不變
到這里我們關(guān)于decamelizeKeys加密方法的e參數(shù)已經(jīng)分析好了,t參數(shù)還未知,那我們就繼續(xù)從剛才decamelizeKeys函數(shù)那里繼續(xù)往下找,分析decamelizeKeys函數(shù)的加密邏輯
4. 分析decamelizeKeys的加密邏輯
同樣是針對(duì)decamelizeKeys函數(shù)下斷點(diǎn),看到跳到這個(gè)地方,m方法返回n方法

查看o方法的具體邏輯

o = function e(t, n, r) {
if (!d(n) || p(n) || h(n) || v(n) || l(n)) //基本沒(méi)什么用,可以跳過(guò)
return n;
var i, o = 0, a = 0;
if (f(n)) //這里做邏輯判斷,可以扣具體js
for (i = [],
a = n.length; o < a; o++)
i.push(e(t, n[o], r));
else
for (var s in i = {}, // 循環(huán)n(也就是data),不斷的和下面那個(gè)c函數(shù)做處理
n)
Object.prototype.hasOwnProperty.call(n, s) && (i[t(s, r)] = e(t, n[s], r));
return i
}
c = function(e, t) {
return function(e, t) {
var n = (t = t || {}).separator || "_"
, r = t.split || /(?=[A-Z])/;
return e.split(r).join(n)
}(e, t).toLowerCase()
}
加密的邏輯主要是上面這個(gè)部分,沒(méi)有混淆和平坦化什么的,大家可以自行扣扣
代碼實(shí)戰(zhàn)
根據(jù)上面的思路,我們完善代碼
關(guān)于加密部分


注意下圖我們?cè)谡?qǐng)求headers中加了兩個(gè)參數(shù)
- "x-zse-83": "3_1.1" 這個(gè)參數(shù)是用來(lái)驗(yàn)證客戶(hù)端的版本,大概是和clientId相關(guān),如果我們不傳的話,會(huì)提示請(qǐng)求參數(shù)異常,請(qǐng)升級(jí)客戶(hù)端后重試這個(gè)錯(cuò)誤
- "x-xsrftoken": _get_xsrf() 這個(gè)參數(shù)是跟跨域相關(guān),是為了防Xsrf跨站的Token認(rèn)證,訪問(wèn)首頁(yè)時(shí)從Response Headers的Set-Cookie字段中可以找到

關(guān)于實(shí)戰(zhàn)部分

注意,我們獲取的是加密方法返回的headers、data、session,之所以要拿headers,是因?yàn)槲覀冊(cè)谡?qǐng)求驗(yàn)證碼的時(shí)候,返回的Response Headers的Set-Cookie中有個(gè)CAPSION_TICKET字段,如果我們?cè)趐ost的時(shí)候不傳這個(gè)cookie字段,會(huì)報(bào)錯(cuò)
{"error":{"message":"缺少驗(yàn)證碼票據(jù)","code":120002,"name":"ERR_CAPSION_TICKET_NOT_FOUND"}}
最后注意請(qǐng)求的順序,先獲取驗(yàn)證碼,再post,如果你成功的看完這篇文章,那么你會(huì)收到登錄成功的結(jié)果。
復(fù)習(xí)要點(diǎn)
從這個(gè)復(fù)習(xí)的案例我們可以總結(jié)下思路:
- 在參數(shù)沒(méi)有明顯特征的時(shí)候,打xhr斷點(diǎn)
- 在做自動(dòng)化登錄的時(shí)候,每一步的header都很重要,如果你算出加密的結(jié)果卻還是報(bào)錯(cuò),看看是不是你漏了哪一個(gè)請(qǐng)求返回給你的某樣?xùn)|西
作者相關(guān)
號(hào)主介紹
多年反爬蟲(chóng)破解經(jīng)驗(yàn),AKA“逆向小學(xué)生”,沉迷數(shù)據(jù)分析和黑客增長(zhǎng)不能自拔,虛名有CSDN博客專(zhuān)家和華為云享專(zhuān)家。
私藏資料
嘔心瀝血從浩瀚的資料中整理了獨(dú)家的“私藏資料”,公眾號(hào)內(nèi)回復(fù)“私藏資料”即可領(lǐng)取爬蟲(chóng)高級(jí)逆向教學(xué)視頻以及多平臺(tái)的中文數(shù)據(jù)集
小學(xué)生都推薦的好文
2019年末逆向復(fù)習(xí)系列之今日頭條WEB端_signature、as、cp參數(shù)逆向分析
2019年末逆向復(fù)習(xí)系列之百度指數(shù)Data加密逆向破解
2019年末逆向復(fù)習(xí)系列之努比亞Cookie生成逆向分析
2019年末逆向復(fù)習(xí)系列之淘寶M站Sign參數(shù)逆向分析
