前言
Steam游戲平臺在今年三四月份更新了登錄協(xié)議岂却,放棄了原有的簡單RSA加密健田,數(shù)據(jù)傳輸格式改成了Protobuf協(xié)議,難度相比之前大了許多予颤,主要還是對于Protobuf協(xié)議不熟悉旅敷,最近看了之前RSA加密相關(guān)視頻凶硅,懷著熱血想小試牛刀一下,結(jié)果當(dāng)看到返回的數(shù)據(jù)格式時(shí)扫皱,一下子就傻眼了足绅,經(jīng)過了5天的研究調(diào)試,期間差點(diǎn)放棄韩脑,好在最后還是被我逆向出來了
目標(biāo)
看過新版steam請求的都知道氢妈,來到登錄界面,輸入賬號密碼后點(diǎn)擊登錄段多,會出現(xiàn)一個(gè)get請求首量,查看這個(gè)請求的負(fù)載發(fā)現(xiàn)有兩個(gè)參數(shù),origin自不用說是個(gè)固定的进苍,可以看出來input_protobuf_encoded是個(gè)被加密過的
同時(shí)查看響應(yīng)的數(shù)據(jù)加缘,掐指一算,是一串有亂碼的字符串觉啊,看來這個(gè)響應(yīng)數(shù)據(jù)也是被加密過的(準(zhǔn)確的說是利用protobuf協(xié)議序列化過)
所以本節(jié)主要內(nèi)容就是對請求參數(shù)中加密的input_protobuf_encoded進(jìn)行解析拣宏,以及對響應(yīng)的數(shù)據(jù)進(jìn)行解析。
protobuf數(shù)據(jù)格式
引入正題杠人,protobuf是什么勋乾,具體百度一下你就知道,簡單來說protobuf是一種Google提供的高效的協(xié)議數(shù)據(jù)交換格式嗡善,就像JSON一樣辑莫,有固定的格式,正向開發(fā)中需要編寫罩引。proto 文件來秒數(shù)這個(gè)數(shù)據(jù)具體都有什么信息各吨,逆向沒有這個(gè)文件,就只能根據(jù)值盲猜了袁铐,我為什么知道這個(gè)是protobuf數(shù)據(jù)格式揭蜒,沒人天生知道,經(jīng)過這幾天的研究才知道大概是什么數(shù)據(jù)
那如何使用protocol buffers昭躺?
開發(fā)者需要先編寫proto文件忌锯,在proto文件中編寫預(yù)期的數(shù)據(jù)類型、數(shù)據(jù)字段领炫、默認(rèn)值等
然后,通過編譯器生成张咳,編程語言對應(yīng)的開發(fā)包帝洪!開發(fā)時(shí)調(diào)開發(fā)包中的對應(yīng)方法進(jìn)行序列化和反序列化似舵。
Protobuf 實(shí)現(xiàn)基本過程
1、編寫 .proto 文件
2葱峡、將 .proto 文件編譯成對應(yīng)編程語言的包
3砚哗、將包導(dǎo)入,通過代碼實(shí)現(xiàn) .proto 文件的序列化砰奕,并將序列化后的內(nèi)容保存到 .bin 文件中蛛芥,序列化后的數(shù)據(jù)可讀性極差
Protobuf 序列化完成,此時(shí)就可以在網(wǎng)絡(luò)間進(jìn)行傳輸军援,接收端若想還原為可讀數(shù)據(jù)仅淑,則需進(jìn)行反序列化操作。
既然如此胸哥,我們可以簡單的實(shí)現(xiàn)以上過程
1涯竟、創(chuàng)建.proto文件,編寫簡單的數(shù)據(jù)
2空厌、將.proto編譯成.js文件庐船,至于編譯器網(wǎng)上很多教程,我這里提供一個(gè)編譯器下載地址:https://github.com/protocolbuffers/protobuf/releases/
安裝完后嘲更,對.proto進(jìn)行編譯,定位到文件所在目錄,執(zhí)行如下命令筐钟,生產(chǎn)對應(yīng)_pb.js文件
protoc --proto_path=.\ [目標(biāo)文件名].proto --js_out=import_style=commonjs:. --grpc-web_out=import_style=commonjs,mode=grpcwebtext:.
大致查看了解一下結(jié)構(gòu)
大概的操作就是以我們自己編寫的.proto文件中的數(shù)據(jù)格式為準(zhǔn),將對應(yīng)格式的數(shù)據(jù)進(jìn)行二進(jìn)制序列化或者反序列protobuf格式的二進(jìn)制數(shù)據(jù)為.proto文件定義的對象的格式赋朦。
(這里可以多注意一下serializeBinary這個(gè)方法名盗棵,方法體的代碼寫法,后面會用到)
3北发、使用生成的xx_pb.js文件可以用來干嘛纹因?
-
將對象數(shù)據(jù)序列化成二進(jìn)制protobuf格式的亂碼數(shù)據(jù)
生成的xx_pb.js中有一個(gè)初始化代碼,先進(jìn)行初始化琳拨,傳入的參數(shù)是個(gè)數(shù)組瞭恰,每個(gè)數(shù)組的索引位置對應(yīng).proto中一個(gè)唯一的序號
image.png
image.png
上面生成的e 就是二進(jìn)制protobuf格式的亂碼數(shù)據(jù)
-
解析二進(jìn)制protobuf格式的亂碼數(shù)據(jù)
(這里我不使用生成的.js文件來解析,因?yàn)槲易罱K目的是用python爬蟲狱庇,所以我打算使用.proto生成對應(yīng)的.py文件來進(jìn)行解析)
執(zhí)行類似上面的命令,生成對應(yīng)的_pb2.py文件
image.png
image.png
這代碼我是看不懂的惊畏,但是用還是會用的。。
現(xiàn)在我們可以使用hhh_pb2.py中的代碼來解析proto_buf了。首先岔乔,我們需要導(dǎo)入生成的代碼:
import hhh_pb2
然后篓吁,創(chuàng)建一個(gè)Body實(shí)例,并使用ParseFormString()方法解析:
body = hhh_pb2.Body()
body.ParseFromString(proto_buf_data)
在上面的代碼中迫横,proto_buf_data是一個(gè)包含了proto_buf數(shù)組的字節(jié)流酷窥。
一旦我們成功地解析了proto_buf數(shù)據(jù)继找,我們就可以通過訪問Body實(shí)例的字段來獲取數(shù)據(jù)了口猜。例如负溪,我們可以獲取name字段的值
name = body.name
此外,對于repeated類型的字段济炎,我們可以使用類似于列表的方法來訪問他們的值:
for hobby in body.hobbies:
print(hobby)
這樣就完成了對proto_buf數(shù)據(jù)的解析
逆向解密請求參數(shù)input_protobuf_encoded
第一步根據(jù)input_protobuf_encoded名字找到對應(yīng)加密的js文件川抡,并且定位到input_protobuf_encoded賦值的地方,這里就不一一調(diào)試须尚,網(wǎng)上有很多教學(xué)崖堤,我這里直接定位到關(guān)鍵代碼
可以看到執(zhí)行完a = r.JQ(o)后生成的a值和請求參數(shù)對比一下發(fā)現(xiàn)就是加密后的數(shù)據(jù),后面d.params.input_protobuf_encoded = a 這樣的代碼也證明了這里就是需要逆向的關(guān)鍵代碼耐床,鼠標(biāo)移到s密幔,發(fā)現(xiàn)這個(gè)值是請求的基礎(chǔ)url,在這里并沒有什么用處可以忽略咙咽。
第二步老玛,創(chuàng)建js文件,將關(guān)鍵代碼復(fù)制進(jìn)去
運(yùn)行報(bào)錯(cuò) n is not defined钧敞,鼠標(biāo)移動(dòng)到n.SerializeBody蜡豹,定位到主要方法
將代碼拷貝到j(luò)s,改寫一下代碼溉苛,在運(yùn)行出現(xiàn)報(bào)錯(cuò)镜廉,提示沒有serializeBinary這個(gè)方法,
老規(guī)矩愚战,進(jìn)入serializeBinary對應(yīng)的方法
將代碼再拷貝到j(luò)s運(yùn)行娇唯,發(fā)現(xiàn)還是繼續(xù)報(bào)錯(cuò),根據(jù)我們一步一步補(bǔ)環(huán)境寂玲,發(fā)現(xiàn)非常麻煩塔插,可能到最后還是毫無頭緒。
這時(shí)候我們不妨換個(gè)思路拓哟,通過請求的響應(yīng)數(shù)據(jù)可以看出想许,該請求是使用protobuf協(xié)議來傳輸數(shù)據(jù)的,那么只有在響應(yīng)返回的數(shù)據(jù)中才進(jìn)行處理嗎断序?會不會在請求的參數(shù)中也進(jìn)行了protobuf處理流纹?
有了這個(gè)思路,我們觀察一下補(bǔ)寫js代碼時(shí)出現(xiàn)的代碼
這個(gè)代碼中的方法serializeBinary以及里面的實(shí)現(xiàn)是不是跟上面我們在講protobuf協(xié)議的時(shí)候生成的_pb.js代碼中的serializeBinary很相似违诗?
在觀察有一個(gè)這樣的代碼漱凝,可以大概知道這個(gè)c類主要就是對account_name這個(gè)字段進(jìn)行處理
看到這些代碼,我腦海里有個(gè)想法诸迟,這個(gè)請求對請求參數(shù)的處理是不是將用戶名先通過protobuf協(xié)議處理了一下茸炒,然后再用base64加密呢愕乎??
有了這個(gè)思路扣典,我們試著根據(jù)上面的代碼編寫一下用戶名對應(yīng)的accountName.proto文件并編譯成js
結(jié)合上面我們拷貝到j(luò)s中的SerializeBody方法妆毕,進(jìn)行改寫
先初始化將用戶名數(shù)組傳入慎玖,然后調(diào)用編譯生成的serializeBinary方法
運(yùn)行發(fā)現(xiàn)e有結(jié)果了贮尖!之后就是普通的代碼改寫運(yùn)行發(fā)現(xiàn)生成了一串?dāng)?shù)據(jù),對比一下就是我們想要的數(shù)據(jù)
protobuf協(xié)議反序列化字節(jié)流數(shù)據(jù)
首先趁怔,根據(jù)上面生成的參數(shù)發(fā)起請求湿硝,得到返回的序列化過的protobuf字節(jié)流數(shù)據(jù)。
因?yàn)槟嫦驔]有proto這個(gè)文件润努,就只能根據(jù)值盲猜了关斜,那怎么獲取值呢,查資料我找到了兩種比較好的方法
- 到github搜索blackboxprotobuf
下載blackboxprotobuf-master.zip铺浇,解壓將blackboxprotobuf拖入到python庫中
image.png
導(dǎo)入blackboxprotobuf痢畜,調(diào)用decode_message方法解析
打印出這樣的數(shù)據(jù)
根據(jù)這樣的數(shù)據(jù)結(jié)構(gòu)找找有沒有相關(guān)的代碼,果然被我找到了鳍侣,可以根據(jù)這里定義的類型更加容易編寫.proto文件
編譯成_pb2.py
導(dǎo)入RSAPublicKey_pb2調(diào)用解析數(shù)據(jù)
這樣一來就成功反序列化得到我們需要的數(shù)據(jù)啦~~
2)除了上面查看字節(jié)流數(shù)據(jù)的方法外丁稀,還可以通過將字節(jié)流數(shù)據(jù)保存成.bin文件,在使用protoc --decode_raw < v1.bin命令來生成
小結(jié)
到此倚聚,這個(gè)請求該解析的都解析了线衫,其實(shí)思路并不難,最大難點(diǎn)是對protobuf協(xié)議的不熟悉惑折,我們了解了協(xié)議后授账,就能知道該網(wǎng)站的處理方法是先對數(shù)據(jù)進(jìn)行protobuf協(xié)議序列化,之后可能是在對序列化數(shù)據(jù)在加密或者甚至就沒加密直接返回惨驶。
到這里本節(jié)結(jié)束白热,算是給自己這幾天研究的結(jié)果做一個(gè)總結(jié)記錄!
(可能講的有點(diǎn)亂粗卜,需要相關(guān)的代碼可以聯(lián)系我哦)
這里先對a = r.JQ(o)這行進(jìn)行改寫屋确,鼠標(biāo)移動(dòng)到r.JQ,定位到對應(yīng)的方法
復(fù)制到j(luò)s休建,改寫一下代碼