目标
对 Steam 登录接口进行逆向分析。 目标网址:https://store.steampowered.com/login/
逆向分析
输入账号密码点击登录后,首先观察接口 GetPasswordRSAPublicKey/v1。从命名可以看出该接口返回 RSA 加密的公钥信息。观察请求参数,核心加密参数为 input_protobuf_encoded。
在浏览器中全局搜索该参数名,可以定位到两处调用位置。可以看到 input_protobuf_encoded 的值经过处理,其源头是 r.JQ(o),而 o 的值为 n.SerializeBody()。这里的 n 是一个包含账号信息的实例对象。
我们可以通过原型链进入构造函数进行断点调试。在 super 位置下断,发现实例化时传入了一个类定义。初始化时会检查 account_name 属性,若不存在则调用特定方法创建对象。
无论是从 n.aR 方法入手,还是追踪 account_name 的属性及其父类,最终都会指向 protobuf 协议的处理逻辑。
Protocol Buffers 基础
Protobuf 协议根据特定的语法定义数据结构,发送和接收数据前必须约定好字段格式。上文中的类正是对 account_name 字段的定义。我们可以参考 JS 代码中的格式编写自己的 .proto 文件。
Protobuf 常见的数据类型包括 int32、string、bool、bytes、message 等。
新建一个 proto 文件(需配置环境),定义 account_name 字段:
syntax = "proto3";
message CAuthenticationGetPasswordRsaPublicKeyRequest {
string account_name = 1;
}
执行命令 protoc --python_out=. xx.proto 将 proto 文件转为 Python 代码。生成的 py 文件可以直接使用。
使用示例如下:
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")
回到逆向流程,我们已经知道了 o 的生成方式,剩下的 r.JQ 方法通常是 Base64 编码。组合起来就生成了 input_protobuf_encoded 的值。


