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

news2024/11/26 20:25:17

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 logger

from 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 requests

from 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.coloradmin.cn/o/1342677.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Java创建线程执行任务的方法(一)

目录 1.继承Thread类 2.实现Runnab类 2.1实现Runnable类 2.2使用Lambda表达式 3.实现Callable类 3.1返回Integer类型数据 3.2返回String类型数据 3.3返回Object类型数据 4.匿名内部类 创建线程的方法&#xff1a;继承Thread类&#xff1b;实现Runnab类&#xff1b;匿名…

java使用JSON工具解析字符串、数组详解

一&#xff1a;问题 1.最近自己在前后端数据交互时需要进行JSON格式字符串、数组数据进行转换&#xff0c;进行问题整理 2.遇到需要JSON字符串转换的朋友可以阅读 二&#xff1a;解析步骤 1.第一点首先确定需求&#xff0c;明确需要转的字符串是一个对象还是一个数组&#…

腾讯云服务器怎么购买?流程来了

腾讯云服务器购买流程直接在官方秒杀活动上购买比较划算&#xff0c;在云服务器CVM或轻量应用服务器页面自定义购买价格比较贵&#xff0c;但是自定义购买云服务器CPU内存带宽配置选择范围广&#xff0c;活动上购买只能选择固定的活动机&#xff0c;选择范围窄&#xff0c;但是…

lag-llama源码解读(Lag-Llama: Towards Foundation Models for Time Series Forecasting)

Lag-Llama: Towards Foundation Models for Time Series Forecasting 文章内容&#xff1a; 时间序列预测任务&#xff0c;单变量预测单变量&#xff0c;基于Llama大模型&#xff0c;在zero-shot场景下模型表现优异。创新点&#xff0c;引入滞后特征作为协变量来进行预测。 获得…

【每天一个早下班技巧】NPM发包流程

发包流程 step1&#xff1a;设置包发布地址 参考资料 // 1.在package.json中设置发布地址 "publishConfig":{"registry":"http://registry.npm.xxx.com" }//2.设置别名 alias ynpm"npm --registryhttp://registry.npm.xxx.com" ynp…

Typora使用PicGo+Gitee上传图片

Typora使用PicGoGitee上传图片 1.下载PicGo(国内镜像) https://mirrors.sdu.edu.cn/github-release/Molunerfinn_PicGo/ 点击PicGo-Setup-2.3.0-x64.exe &#xff08;64位安装&#xff09; 然后打开gitee&#xff08;没注册先注册&#xff09; 2.下载node.js插件 https:/…

电脑系统坏了用U盘重装系统教程

我们平时办公、学习都会用到电脑&#xff0c;如果电脑系统坏了&#xff0c;就会影响自己正常使用电脑&#xff0c;这时候就可以通过U盘来重装一个正常的操作系统。如果您不知道具体的重装操作步骤&#xff0c;那么可以参考下面小编分享的利用U盘快速完成操作系统重装的步骤介绍…

4.14 构建onnx结构模型-Min

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

【网络安全 | XCTF】2017_Dating_in_Singapore

正文 题目描述&#xff1a; 01081522291516170310172431-050607132027262728-0102030209162330-02091623020310090910172423-02010814222930-0605041118252627-0203040310172431-0102030108152229151617-04050604111825181920-0108152229303124171003-261912052028211407-0405…

华为无线AC内三层漫游配置详解

重要说明 1、在一台ac中实现三层漫游 2、ac和核心的互联vlan和ap的管理vlan是同一个广播域&#xff0c;可以不用配option 43 3、直接转发模式&#xff0c;ac上可以不起业务vlan&#xff0c;ac和核心交换机上可以只放行一个互联vlan 10 4、ac上要启两个vap魔板&#xff0c;两个…

Git 浅入浅出

前提 最近和同事分模块联合开发代码&#xff0c;自然而然就要用到 Git 管理代码&#xff1b;借此机会&#xff0c;对 Git 进行简单介绍。 Git 的特征 文件系统 我们都知道 Git 是个版本控制系统&#xff0c;但是如果你深入了解其原理&#xff0c;就不难发现它更像一个文件管…

区间DP详解,思路分析,OJ详解

文章目录 前言问题引入暴力枚举自下而上状态设计状态转移方程 区间DP的分析状态设计状态转移时间复杂度翻译成递推 OJ详解P1880 [NOI1995] 石子合并记忆化搜索版本递推版本 HDU Dire WolfMultiplication PuzzlePolygon 总结 前言 区间dp属于动态规划中一类比较好理解的问题&…

概率论相关题型

文章目录 概率论的基本概念放杯子问题条件概率与重要公式的结合独立的运用 随机变量以及分布离散随机变量的分布函数特点连续随机变量的分布函数在某一点的值为0正态分布标准化随机变量函数的分布 多维随机变量以及分布条件概率max 与 min 函数的相关计算二维随机变量二维随机变…

超级详细的YOLOV8教程

超级详细的YOLOV8教程 YOLOV8介绍1. 数据标记1.1 第一种为在网站上下载&#xff0c;1.2 第二种为在CVAT上自定义数据 2. 制作数据集3. 部署YOLOV8的代码3.1 远程部署3.1.1 项目下载3.1.2 修改代码3.1.2.1 训练模型3.1.2.1 验证模型 3.2 本地部署3.2.1 YOLOV8项目部署3.2.2 cuda…

2023总结与展望--Empirefree

今年一篇博客都没写过了&#xff0c;好像完全在忙在工作和生活上面了&#xff0c;珍惜自我&#xff0c;保持热情&#xff0c;2024对我好点 文章目录 &#x1f525;1. 年终总结1.1.学习工作计划1.2. 生活计划1.3 个人总结 &#x1f525;2. 未来展望 &#x1f525;1. 年终总结 1…

基于Java学生成绩管理系统设计与实现(源码+部署文档+报告)

博主介绍&#xff1a; ✌至今服务客户已经1000、专注于Java技术领域、项目定制、技术答疑、开发工具、毕业项目实战 ✌ &#x1f345; 文末获取源码联系 &#x1f345; &#x1f447;&#x1f3fb; 精彩专栏 推荐订阅 &#x1f447;&#x1f3fb; 不然下次找不到 Java项目精品实…

两阶段提交协议

数据的强一致性 要么都修改 要么就都不修改。 不同的实体和过程 领导者和参与者、表决阶段和提交阶段 过程 一个不同意 提交就终止 存在的问题和解决的方案 如果一个领导者或者参与者的状态机中有阻塞状态&#xff0c;那么系统必须等他完成才能执行&#xff0c;这样就会…

【并发编程篇】线程安全问题_—_ConcurrentHashMap

文章目录 &#x1f354;情景引入&#x1f339;报错了&#xff0c;解决方案 &#x1f354;情景引入 我们运行下面的代码 package org.example.unsafe;import java.util.HashMap; import java.util.Map; import java.util.UUID;public class MapTest {public static void main(…

SpringBoot单点登录认证系统MaxKey(附开源项目地址)

1 项目介绍 MaxKey 单点登录认证系统&#xff0c;谐音马克思的钥匙寓意是最大钥匙&#xff0c;支持 OAuth 2.x/OpenID Connect、SAML 2.0、JWT、CAS、SCIM 等标准协议&#xff0c;提供简单、标准、安全和开放的用户身份管理(IDM)、身份认证(AM)、单点登录(SSO)、RBAC 权限管理…

如何把握品牌新五感,打造小红书品牌

随着社会经济的发展&#xff0c;市场的进步&#xff0c;以及人们思维方式的改变。年轻人面对市场&#xff0c;面对营销&#xff0c;关注点也在发生着改变。那什么是小红书品牌新五感&#xff0c;如何把握品牌新五感&#xff0c;打造小红书品牌&#xff01; 一、品牌五感是什么 …