前言
Steam游戏平台在今年三四月份更新了登录协议,放弃了原有的简单RSA加密,数据传输格式改成了Protobuf协议,难度相比之前大了许多,主要还是对于Protobuf协议不熟悉,最近看了之前RSA加密相关视频,怀着热血想小试牛刀一下,结果当看到返回的数据格式时,一下子就傻眼了,经过了5天的研究调试,期间差点放弃,好在最后还是被我逆向出来了
目标
看过新版steam请求的都知道,来到登录界面,输入账号密码后点击登录,会出现一个get请求,查看这个请求的负载发现有两个参数,origin自不用说是个固定的,可以看出来input_protobuf_encoded是个被加密过的
同时查看响应的数据,掐指一算,是一串有乱码的字符串,看来这个响应数据也是被加密过的(准确的说是利用protobuf协议序列化过)
所以本节主要内容就是对请求参数中加密的input_protobuf_encoded进行解析,以及对响应的数据进行解析。
protobuf数据格式
引入正题,protobuf是什么,具体百度一下你就知道,简单来说protobuf是一种Google提供的高效的协议数据交换格式,就像JSON一样,有固定的格式,正向开发中需要编写。proto 文件来秒数这个数据具体都有什么信息,逆向没有这个文件,就只能根据值盲猜了,我为什么知道这个是protobuf数据格式,没人天生知道,经过这几天的研究才知道大概是什么数据
那如何使用protocol buffers?
开发者需要先编写proto文件,在proto文件中编写预期的数据类型、数据字段、默认值等
然后,通过编译器生成,编程语言对应的开发包!开发时调开发包中的对应方法进行序列化和反序列化。
Protobuf 实现基本过程
1、编写 .proto 文件
2、将 .proto 文件编译成对应编程语言的包
3、将包导入,通过代码实现 .proto 文件的序列化,并将序列化后的内容保存到 .bin 文件中,序列化后的数据可读性极差
Protobuf 序列化完成,此时就可以在网络间进行传输,接收端若想还原为可读数据,则需进行反序列化操作。
既然如此,我们可以简单的实现以上过程
1、创建.proto文件,编写简单的数据
2、将.proto编译成.js文件,至于编译器网上很多教程,我这里提供一个编译器下载地址:https://github.com/protocolbuffers/protobuf/releases/
安装完后,对.proto进行编译,定位到文件所在目录,执行如下命令,生产对应_pb.js文件
protoc --proto_path=.\ [目标文件名].proto --js_out=import_style=commonjs:. --grpc-web_out=import_style=commonjs,mode=grpcwebtext:.
大致查看了解一下结构
大概的操作就是以我们自己编写的.proto文件中的数据格式为准,将对应格式的数据进行二进制序列化或者反序列protobuf格式的二进制数据为.proto文件定义的对象的格式。
(这里可以多注意一下serializeBinary这个方法名,方法体的代码写法,后面会用到)
3、使用生成的xx_pb.js文件可以用来干嘛?
-
将对象数据序列化成二进制protobuf格式的乱码数据
生成的xx_pb.js中有一个初始化代码,先进行初始化,传入的参数是个数组,每个数组的索引位置对应.proto中一个唯一的序号
image.png
image.png
上面生成的e 就是二进制protobuf格式的乱码数据
-
解析二进制protobuf格式的乱码数据
(这里我不使用生成的.js文件来解析,因为我最终目的是用python爬虫,所以我打算使用.proto生成对应的.py文件来进行解析)
执行类似上面的命令,生成对应的_pb2.py文件
image.png
image.png
这代码我是看不懂的,但是用还是会用的。。
现在我们可以使用hhh_pb2.py中的代码来解析proto_buf了。首先,我们需要导入生成的代码:
import hhh_pb2
然后,创建一个Body实例,并使用ParseFormString()方法解析:
body = hhh_pb2.Body()
body.ParseFromString(proto_buf_data)
在上面的代码中,proto_buf_data是一个包含了proto_buf数组的字节流。
一旦我们成功地解析了proto_buf数据,我们就可以通过访问Body实例的字段来获取数据了。例如,我们可以获取name字段的值
name = body.name
此外,对于repeated类型的字段,我们可以使用类似于列表的方法来访问他们的值:
for hobby in body.hobbies:
print(hobby)
这样就完成了对proto_buf数据的解析
逆向解密请求参数input_protobuf_encoded
第一步根据input_protobuf_encoded名字找到对应加密的js文件,并且定位到input_protobuf_encoded赋值的地方,这里就不一一调试,网上有很多教学,我这里直接定位到关键代码
可以看到执行完a = r.JQ(o)后生成的a值和请求参数对比一下发现就是加密后的数据,后面d.params.input_protobuf_encoded = a 这样的代码也证明了这里就是需要逆向的关键代码,鼠标移到s,发现这个值是请求的基础url,在这里并没有什么用处可以忽略。
第二步,创建js文件,将关键代码复制进去
运行报错 n is not defined,鼠标移动到n.SerializeBody,定位到主要方法
将代码拷贝到js,改写一下代码,在运行出现报错,提示没有serializeBinary这个方法,
老规矩,进入serializeBinary对应的方法
将代码再拷贝到js运行,发现还是继续报错,根据我们一步一步补环境,发现非常麻烦,可能到最后还是毫无头绪。
这时候我们不妨换个思路,通过请求的响应数据可以看出,该请求是使用protobuf协议来传输数据的,那么只有在响应返回的数据中才进行处理吗?会不会在请求的参数中也进行了protobuf处理?
有了这个思路,我们观察一下补写js代码时出现的代码
这个代码中的方法serializeBinary以及里面的实现是不是跟上面我们在讲protobuf协议的时候生成的_pb.js代码中的serializeBinary很相似?
在观察有一个这样的代码,可以大概知道这个c类主要就是对account_name这个字段进行处理
看到这些代码,我脑海里有个想法,这个请求对请求参数的处理是不是将用户名先通过protobuf协议处理了一下,然后再用base64加密呢??
有了这个思路,我们试着根据上面的代码编写一下用户名对应的accountName.proto文件并编译成js
结合上面我们拷贝到js中的SerializeBody方法,进行改写
先初始化将用户名数组传入,然后调用编译生成的serializeBinary方法
运行发现e有结果了!之后就是普通的代码改写运行发现生成了一串数据,对比一下就是我们想要的数据
protobuf协议反序列化字节流数据
首先,根据上面生成的参数发起请求,得到返回的序列化过的protobuf字节流数据。
因为逆向没有proto这个文件,就只能根据值盲猜了,那怎么获取值呢,查资料我找到了两种比较好的方法
- 到github搜索blackboxprotobuf
下载blackboxprotobuf-master.zip,解压将blackboxprotobuf拖入到python库中
image.png
导入blackboxprotobuf,调用decode_message方法解析
打印出这样的数据
根据这样的数据结构找找有没有相关的代码,果然被我找到了,可以根据这里定义的类型更加容易编写.proto文件
编译成_pb2.py
导入RSAPublicKey_pb2调用解析数据
这样一来就成功反序列化得到我们需要的数据啦~~
2)除了上面查看字节流数据的方法外,还可以通过将字节流数据保存成.bin文件,在使用protoc --decode_raw < v1.bin命令来生成
小结
到此,这个请求该解析的都解析了,其实思路并不难,最大难点是对protobuf协议的不熟悉,我们了解了协议后,就能知道该网站的处理方法是先对数据进行protobuf协议序列化,之后可能是在对序列化数据在加密或者甚至就没加密直接返回。
到这里本节结束,算是给自己这几天研究的结果做一个总结记录!
(可能讲的有点乱,需要相关的代码可以联系我哦)
这里先对a = r.JQ(o)这行进行改写,鼠标移动到r.JQ,定位到对应的方法
复制到js,改写一下代码