【JS 逆向百例】steam 登录 Protobuf 协议详解

00

声明

本文章中所有内容仅供学习交流使用,不用于其他任何目的,不提供完整代码,抓包内容、敏感网址、数据接口等均已做脱敏处理,严禁用于商业用途和非法用途,否则由此产生的一切后果均与作者无关!

本文章未经许可禁止转载,禁止任何修改后二次传播,擅自使用本文讲解的技术而导致的任何意外,作者均不负责,若有侵权,请在公众号【K哥爬虫】联系作者立即删除!

目标

目标:steam 登录协议逆向分析

网址:aHR0cHM6Ly9zdG9yZS5zdGVhbXBvd2VyZWQuY29tL2xvZ2luLw==

逆向分析

输入账密后点击登录,首先看接口 GetPasswordRSAPublicKey/v1,看接口命名可以了解到这个这个接口应该是返回 RSA 加密的公钥信息,先不管这些,观察参数 ,很明显加密参数为 input_protobuf_encoded

01

这里直接全局搜索,可以定位到两处:

02

可以看到 input_protobuf_encoded 的值为 a ,而 a 的值为 r.JQ(o)

03

先看参数 o 的值,为 n.SerializeBody() ,其中 n 是一个对象,包含我们输入的账号信息:

04

这里 n 是一个实例对象,这里可以直接通过原型进到它的构造函数中:

05

进到构造函数中后,在 super 位置下断:

06

可以发现实例化的时候传了一个类:

07

进到这个类 c 中,这里需要清下缓存重新下断:

08

这里可以看到,在初始化的时候,会检查当前实例的 account_name 属性,很明显这个是有关于账号的属性,如果不存在(这里可以理解为首次实例化)则会调用 c.M() 方法创建一个对象,格式如下:

{proto: c,fields: {account_name: {n: 1,br: n.FE.readString,bw: n.Xc.writeString}}
}

到这里无论是从 n.aR 方法入手,还是从 account_name 的几个属性以及这几个类统一的父类 o入手,都会进入到一个新的文件中,到这就可以引出本期的主角 protobuf 协议了:

09

Protocol Buffers

10

从第一点可以了解到, protobuf 协议根据特定的语法来定义数据结构。我们发送数据以及接收数据都需要讲数据字段约定好才能进行生成与解析。

字段定义

初步了解 protobuf 协议后就能理解上文中的代码了,上文中的类正是对 account_name 字段进行定义。

那么我们就可以根据 JS 代码中的格式来编写我们自己的 proto 文件:

account_name: {n: 1,br: n.FE.readString,bw: n.Xc.writeString
}

protobuf 常见的数据类型有以下几种:

数据类型描述
int32int64
uint32 uint64
sint32 sint64
fixed32 fixed64
sfixed32 sfixed64
float单精度浮点数
double双精度浮点数
bool布尔值
string字符串
bytes二进制数据
enum枚举类型,表示一组命名整数值
message消息类型,可以包含其他数据类型的字段,用于嵌套结构
map映射类型,用于定义键值对的映射关系
Any用于包装任意类型的消息
repeated表示一个字段可以包含多个值,类似于数组或列表
Timestamp表示时间戳,用于表示一个特定时间点
Duration表示时间间隔,用于表示一段时间的持续
StructValue

除了上述数据类型,还支持自定义类型。

这里我们新建一个 proto 文件(需配置环境),定义 account_name 字段:

syntax = "proto3";message CAuthenticationGetPasswordRsaPublicKeyRequest {string account_name = 1;
}

执行命令 protoc --python_out=. xx.protoproto 文件转为 python 代码。

转成的 py 文件格式如下:

11

使用起来也很简单:

from loguru import loggerfrom steam_pb2 import (CAuthenticationGetPasswordRsaPublicKeyRequest
)def get_rsa_public_key(username):message = CAuthenticationGetPasswordRsaPublicKeyRequest(account_name=username)logger.info(message.SerializeToString())logger.info(type(message))if __name__ == '__main__':get_rsa_public_key("a123456789")
"""
OUTPUT:
b'\n\na123456789'
<class 'steam_pb2.CAuthenticationGetPasswordRsaPublicKeyRequest'>
"""

那么回到逆向流程中,我们已经知道了 o 的生成方式,那么还剩 r.JQ 方法,这里很简单,直接扣下来即可,根据经验也可以看出这是 base64 编码:

o = n.SerializeBody()
a = r.JQ(o);

到这就生成了 input_protobuf_encoded 的值,那么还需要解决接口返回值。

响应信息解析

这里推荐下 xhr 断点,断在请求发送的地方。一路往下跟直到看到响应信息解析的地方:

12

这里 l.data 就是响应信息,u.At 主要就是对响应信息格式进行处理,并且声明一些方法,做一些读写操作等。s.BinaryReader 也是类似,都是对响应信息做了一些预处理。

关键看 r.deserializeBinaryFromReader ,单步跟,会进入到一个 MBF 静态方法中:

13

这个很像上文中类 c 构造方法中的一段代码,都是判断 protobuf 数据格式是否定义,如果没有定义的话会进行定义,那么这里与上文也一样,进到 l.M() 中就可以看到定义的字段:

static M() {return l.sm_m || (l.sm_m = {proto: l,fields: {publickey_mod: {n: 1,br: n.FE.readString,bw: n.Xc.writeString},publickey_exp: {n: 2,br: n.FE.readString,bw: n.Xc.writeString},timestamp: {n: 3,br: n.FE.readUint64String,bw: n.Xc.writeUint64String}}}),l.sm_m}

那么又显而易见了,按照 JS 代码中的字段与类型进行定义即可:

message CAuthenticationGetPasswordRsaPublicKeyResponse {string publickey_mod = 1;string publickey_exp = 2;uint64 timestamp = 3;
}

完整请求代码:

import base64
import requestsfrom steam_pb2 import (CAuthenticationGetPasswordRsaPublicKeyRequest,CAuthenticationGetPasswordRsaPublicKeyResponse
)headers = {'user-agent': "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"
}def get_rsa_public_key(username):origin = 'https://steamcommunity.com'message = CAuthenticationGetPasswordRsaPublicKeyRequest(account_name=username)protobuf = base64.b64encode(message.SerializeToString()).decode()url = f'https://api.steampowered.com/IAuthenticationService/GetPasswordRSAPublicKey/v1'params = {"origin": origin,"input_protobuf_encoded": protobuf}response = requests.get(url, params=params, headers=headers, timeout=3)# 解析响应信息response = CAuthenticationGetPasswordRsaPublicKeyResponse.FromString(response.content)print(response)if __name__ == '__main__':get_rsa_public_key("a123456789")
"""
OUTPUT:
publickey_mod: "a2fdc8f523c87c6c27e904c89c91ecb56c1199dfcfa2c0fc34c4977c3582aa0f49a3f8fe33cffbd780cc71cfc61d3b7a6f98efc8a14d21174792ef47a8e0b8a6a21c35271ebe384196e60d5d26f010e2539db9b8112873e2bfd08fe73d27f0f15457028ad5da27db4fffb4e17702191f1a7d7f96e60d172835333fea40daf707b38e2030f143b518173453bb5c9e9bf1cbe946e2b4b00d037c9691c2ae9608c4f63263306663f2d8066674d870eb2f142e7c9819416d0499cdc1cc76d47b689ae753648a29cd4d82f6c8f18374ab38c6cb2338652ef5214d620e986e8e7c399e4ef6739485eaccd8cea56d14d61dcd7e8e4f51be82803cea77c7be522e2cfebd"
publickey_exp: "010001"
timestamp: 127222000000
"""

到这里第一个接口的请求参数与响应信息我们就都搞定了,这里返回了三个参数:publickey_modpublickey_exptimestamp,很明显是用于进行 RSA 加密的,那么看下一个接口:

14

这个接口为登录接口,会返回账号的登录结果信息。该接口参数只有一个 input_protobuf_encoded,那么依旧在老地方下断,根据 t 值来判断接口:

15

那么还是一样的操作,找到约定字段的地方进行改写:

fields: {device_friendly_name: {n: 1,br: n.FE.readString,bw: n.Xc.writeString},account_name: {n: 2,br: n.FE.readString,bw: n.Xc.writeString},encrypted_password: {n: 3,br: n.FE.readString,bw: n.Xc.writeString},encryption_timestamp: {n: 4,br: n.FE.readUint64String,bw: n.Xc.writeUint64String},remember_login: {n: 5,br: n.FE.readBool,bw: n.Xc.writeBool},platform_type: {n: 6,br: n.FE.readEnum,bw: n.Xc.writeEnum},persistence: {n: 7,d: 1,br: n.FE.readEnum,bw: n.Xc.writeEnum},website_id: {n: 8,d: "Unknown",br: n.FE.readString,bw: n.Xc.writeString},device_details: {n: 9,c: u},guard_data: {n: 10,br: n.FE.readString,bw: n.Xc.writeString},language: {n: 11,br: n.FE.readUint32,bw: n.Xc.writeUint32},qos_level: {n: 12,d: 2,br: n.FE.readInt32,bw: n.Xc.writeInt32}
}

这里需要注意的是 device_details ,可以看到这里这个字段并没有声明类型,这种就属于自定义类型,u 就是它的类型:

16

结构定义好后可以继续往下跟,找到传输的数据字段:

17

这里密码是被加密过的,加密方法为 h.IC(a, t),这里根据上一个接口的明文规范可以直接推断出为 RSA 加密。publickey_exppublickey_mod 为模数与指数,用于生成公钥:

18

密码生成后,登录接口 BeginAuthSessionViaCredentials/v1 的参数就解决了。至于响应数据的解析依旧是按上文中的方法,这里就不再赘述。

至此,整个逆向流程就结束了。

结果验证

19

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/307496.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

MySQL函数、AVG | MIN | MAX | COUNT | SUM、慢查询

MySQL函数、慢查询 1、函数1.日期函数2.两个日期的时间差3.查询距离时间的间隔时间4.常用的字符串函数5.常用的数学函数6.常用的聚合函数 2、慢查询1.什么是慢查询&#xff1f; 从需求出发&#xff0c;在数据的操作过程中经常会有以下的问题&#xff1b;求和、最大值、最小值、…

亚马逊bsr排名的影响因素,如何提高BSR排名?-站斧浏览器

亚马逊BSR排名的影响因素有哪些&#xff1f; 销售速度&#xff1a;BSR排名主要基于产品的销售速度&#xff0c;即最近一段时间内的销售量。销售速度越快&#xff0c;BSR排名越高。 销售历史&#xff1a;亚马逊会考虑产品的历史销售数据&#xff0c;新上架的产品可能需要一段时…

Java虚拟机中的垃圾回收

2 垃圾回收 2.1 判断一个对象是否可回收 2.1.1 引用计数法 如果一个对象被另一个对象引用&#xff0c;那么它的引用计数加一&#xff0c;如果那个对象不再引用它了&#xff0c;那么引用计数减一。当引用计数为 0 时&#xff0c;该对象就应该被垃圾回收了。 但是下面这种互相…

开放网络+私有云=?星融元的私有云承载网络解决方案实例

在全世界范围内的云服务市场上&#xff0c;开放网络一直是一个备受关注的话题。相比于传统供应商的网络设备&#xff0c;开放网络具备软硬件解耦、云原生、可选组件丰富等优势&#xff0c;对云服务商和超大型企业有足够的吸引力。 SONiC作为开源的网络操作系统&#xff0c;使得…

基于YOLOv8的遥感SAR舰船小目标识别

&#x1f4a1;&#x1f4a1;&#x1f4a1;本文摘要&#xff1a;基于YOLOv8的遥感SAR舰船小目标&#xff0c;阐述了整个数据制作和训练可视化过程 1.YOLOv8介绍 Ultralytics YOLOv8是Ultralytics公司开发的YOLO目标检测和图像分割模型的最新版本。YOLOv8是一种尖端的、最先进的…

如何从RTP包的AP类型包,获取h265的PPS、SPS、VPS信息

ffmpeg播放rtp流&#xff0c;为了降低首开延迟&#xff0c;需要在SDP文件中指定PPS、SPS、VPS信息。抓包后发现wireshark无法解析AP包。需要自己进行AP包解析。RTP协议AP包格式如下&#xff1a; 根据如上信息&#xff0c;我们可以解析AP包&#xff0c;效果如下 40 01&#xff…

MPI并行程序设计 —— C 和 fortran 环境搭建 openmpi 示例程序

1.安装环境 wget https://download.open-mpi.org/release/open-mpi/v4.1/openmpi-4.1.6.tar.g tar zxf openmpi-4.1.6.tar.gz cd openmpi-4.1.6/ 其中 configure 选项 --prefix/.../ 需要使用绝对路径&#xff0c;例如&#xff1a; ./configure --prefix/home/hipper/ex_open…

【C++干货铺】STL中set和map的介绍和使用

个人主页点击直达&#xff1a;小白不是程序媛 C系列专栏&#xff1a;C干货铺 代码仓库&#xff1a;Gitee 目录 序列式容器 关联式容器 键值对 树形结构的关联式容器 set set的介绍 set的使用 set的模板参数列表 set的构造 ​编辑 set的容量 set的删除和查找 mult…

4.16 构建onnx结构模型-And

前言 构建onnx方式通常有两种&#xff1a; 1、通过代码转换成onnx结构&#xff0c;比如pytorch —> onnx 2、通过onnx 自定义结点&#xff0c;图&#xff0c;生成onnx结构 本文主要是简单学习和使用两种不同onnx结构&#xff0c; 下面以 And 结点进行分析 方式 方法一&…

马斯克谈六西格玛:管理质量与火箭科学如何相辅相成

如果你问我&#xff0c;作为一个有志于送人类上火星的家伙&#xff0c;六西格玛管理有没有用&#xff1f;我会说&#xff0c;如果把火箭控制的精度与六西格玛得到的生产质量相比&#xff0c;你会发现两者都追求同一件事&#xff1a;接近零缺陷的完美。 六西格玛不只是一组工具…

【2023 CCF 大数据与计算智能大赛】基于TPU平台实现超分辨率重建模型部署 基于FSRCNN的TPU平台超分辨率模型部署方案

2023 CCF 大数据与计算智能大赛 基于TPU平台实现超分辨率重建模型部署 基于FSRCNN的TPU平台超分辨率模型部署方案 WELL 刘渝 人工智能 研一 西安交通大学 中国-西安 1461003622qq.com 史政立 网络空间安全 研一 西安交通大学 中国-西安 1170774291qq.com 崔琳、张…

MySQL报错:1366 - Incorrect integer value: ‘xx‘ for column ‘xx‘ at row 1的解决方法

我在插入表数据时遇到了1366报错&#xff0c;报错内容&#xff1a;1366 - Incorrect integer value: Cindy for column name at row 1&#xff0c;下面我演示解决方法。 根据上图&#xff0c;原因是Cindy’对应的name字段数据类型不正确。我们在左侧找到该字段所在的grade_6表&…