首页
社区
课程
招聘
[原创]Steam新版登录分析,深入了解Protobuf
2023-9-4 13:45 9394

[原创]Steam新版登录分析,深入了解Protobuf

2023-9-4 13:45
9394

#序言
最近无聊,看到steam,登录更新了,所以手痒试试,正好学习,go语言和protobuf
开工,
#抓包
逆向第一步,必然抓包看看啦,抓的这是啥,登录只有两个包,
看看第一个包
urlhttps://*****/IAuthenticationService/GetPasswordRSAPublicKey/v1?origin=https:%2F%2Fstore.steampowered.com&input_protobuf_encoded=CgoxMTExMTExMTEx
掐指一算,~~嗯嗯是一串有乱码的字符串,偷瞄一眼发现rsa密钥相关标志010001
再看url=GetPasswordRSAPublicKey 百度翻译=获取rsa公钥,
图片描述
知道了这个包是获取RSA公钥,那么问题来了,为什么会中间有乱码呢?
#protobuf数据格式
引入正题,protobuf是什么,具体百度一下你就知道,简单来说protobuf是一种Google提供的高效的协议数据交换格式,就像JSON一样,有固定的格式,正向开发中需要编写.proto 文件来秒数这个数据具体都有什么信息,逆向没有这个文件,就只能根据值盲猜了,我为什么知道这个是protobuf数据格式,没人天生知道,都是踩了很多坑,查了很多资料得出的经验,所以一下能判断大概是什么数据
https://gchq.github.io/CyberChef/
把响应数据HEx拿到这个网站解析
抓包rsa
成功获取解析出数据,左边可以边写.proto边查看右边解析结果,
ok第一个包完成文件编写

1
2
3
4
5
6
7
8
9
10
syntax = "proto3";
package Protoc;
option go_package = ".";
 
//响应
message Rsa_Response{
  string PublicKey= 1;    //公钥
  string MoShu= 2;        //模数
  int64 SuiJiShu= 3;      //随机数
}

运行protoc --go_out=./__/ *.proto 生成go代码
#第一个包取RSA公钥
顺便看一下url内的两个参数,第一个固定,第二个是base64编码后的用户名,就不细说了
然后编写调用测试一下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
func (集 *Api) D登录_取Rsa公钥() bool {
    局_网址 := "https://api.steampowered.com/IAuthenticationService/GetPasswordRSAPublicKey/v1"
 
    Http请求 := 集.Http客户端.DevMode().R()
 
    局_网址 += `?origin=https:%2F%2Fstore.steampowered.com&input_protobuf_encoded={input_protobuf_encoded}`
    //SetPathParam 会自动url编码, 无需再编码
    Http请求.SetPathParam(`input_protobuf_encoded`, B编码_BASE64编码([]byte(string([]byte{10, 11})+集.账号)))
 
    局_post := map[string]string{}
    Http请求.EnableForceMultipart().SetFormData(局_post)
 
    Http请求.SetHeader("accept", " application/json, text/plain, */*")
    Http请求.SetHeader("accept-language", " zh-CN,zh;q=0.9")
    Http请求.SetHeader("cache-control", " no-cache")
    Http请求.SetHeader("dnt", " 1")
    Http请求.SetHeader("origin", " https://store.steampowered.com")
    Http请求.SetHeader("pragma", " no-cache")
    Http请求.SetHeader("referer", " https://store.steampowered.com/login/?snr=1_4_4__more-content-login")
    Http请求.SetHeader("user-agent", "Mozilla/5.0 (iPhone; CPU iPhone OS 13_2_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.3 Mobile/15E148 Safari/604.1")
    Http请求.SetHeader("Host", "api.steampowered.com")
    Http请求.SetHeader("Connection", "keep-alive")
 
    var 局_请求结果 *req.Response
    var err error
    for i := 0; i < 3; i++ {
        局_请求结果, err = Http请求.Get(局_网址)
        if len(局_请求结果.Bytes()) > 0 || err != nil {
            break
        }
    }
 
    局_pb := &集.Rsa
    err = proto.Unmarshal(局_请求结果.Bytes(), 局_pb)
    if err != nil {
        fmt.Println("取rsa公钥反序列化失败 ", err)
    }
    if 局_pb.PublicKey != "" {
        return true
    }
    集.错误信息 = "RSA密钥获取失败"
    fmt.Printf(局_请求结果.Dump())
    return false
 
}

测试ok 没问题正常获取到Rsa公钥参数
#第二个包登录包
抓包
看看这个包的提交信息,好家伙这么长,经验判断应该是base64,解码失败看都有什么信息
抓包rsa
掐指一算,嗯嗯,又是明文+乱码,protobuf没跑了,转换成hex继续盲反序列化
图片描述
大概看看
"2"应该是账号
"3"这么长,联想到刚才获取rsa,估计就是加密后的密码了
"4"和rsa包一起返回的随机数
"9"可能是浏览器信息,
其他的就看不出来了,
这就是probuf逆向的麻烦事,没有proto文件不知道值什么信息,当然要求不高直接用也可以,反正也不用改变不知道的数据,咱们只需要修改"2","3","4"这几个值就行了,但是!!!经过我一顿断点操作,还真让我翻出了每个参数的名字,
图片描述
在看看返回包,登录失败,返回信息只有两个字节??? 这是什么鬼,盲反序列化也没数据,
那前端是怎么判断登录结果的呢?,此事必有蹊跷. -_/
经过反复对比疑点在这里,响应协议头内x-eresult参数代表操作是否成功,1=成功,5=帐密错误,等等
图片描述
再看看成功都返回的数据
图片描述
还是一样,盲反序列化一条龙,再根据js判断键名
图片描述
图片描述
嘿嘿以为登录成功了,没想到吧没还需要验证码
不过登录包的请求和响应protoc,文编编写完成了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
syntax = "proto3";
package Protoc;
option go_package = ".";
 
// 请求
message eMsg9804 {
  string account_name = 2// 1111111111
  string encrypted_password = 3// rsa加密密码
  int64 encryption_timestamp = 4// 570240150000
  int32 set_remember_login = 5// 1   是否记住登录
  bool set_persistence = 7// 1   坚持不懈?
  string website_id = 8// Mobile   app内是  Mobile  pc是 Store
  repeated device_details device_details = 9//  设备信息 手机和电脑不一样 
  int32 language = 11// 6  语言,直接设置6就可以,中国人  app没有这个值  pc=6
}
 
message device_details {//电脑网页设备信息
  string device_friendly_name = 1//app=Pixel, pc= Dalvik/2.1.0 (Linux; U; Android 7.1.2; Pixel Build/NJH47F; Valve Steam App Version/3)
  int32 platform_type = 2// 2  平台类型  pc=2 app=3
  int32 os_type = 3//
  uint32 gaming_device_type = 4//
  uint32 client_count = 5//
  bytes machine_id = 6//
}
 
 
//响应============================================================
//CAuthentication_BeginAuthSessionViaCredentials_Response
//const {client_id: i, request_id: n, interval: a, allowed_confirmations: s, steamid: u, weak_token: d} = h.Body().toObject();
message Response_EMsg147 {
  uint64 client_id = 1//
  bytes request_id= 2// í”'*c¡²m*¨cށƒ
  fixed32 interval= 3// 1084227584
  repeated allowed_confirmations allowed_confirmations= 4
  uint64 steamid= 5/
  string weak_token= 6//
  string   agreement_session_url = 7;   //协议会话url
  string extended_error_message = 8// {}
}
 
message allowed_confirmations{
  int32 confirmation_type= 1;
  string associated_message= 2;
}
//仅供参考
/*{
"allowedConfirmations": [
{
"confirmationType": 2,
    "associatedMessage": "qq.com"
    },
{
"confirmationType": 6,
    "associatedMessage": ""
    }
],
    "clientId": 17287054.....000,
    "requestId": "je2U....bSqoY96Bgw==",
"interval": 1084227584,
    "steamid": 76561191...6700,
    "weakToken": "eyAidHlwI...bT1kBHhTJj-Sd8yK2SYdYIxifwfQZmb-1yq3zogxU9JzDa6RLyDgFxwr4bmQeNXVBA",
    "agreementSessionUrl": "",
    "extendedErrorMessage": ""
    }*/

密码是标准rsa加密,随便找个RSA的js加密填写上参数就可以用了.
生成go代码,编写测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
// 本命令由自动生成,请配合[ go get -u gitee.com/anyueyinluo/Efunc ]库使用。
func (集 *Api) D登录_登录() bool {
    局_网址 := `https://api.steampowered.com/IAuthenticationService/BeginAuthSessionViaCredentials/v1`
    Http请求 := 集.Http客户端.EnableDumpAll().R()
    Http请求.SetHeader(`Accept`, `application/json, text/plain, */*`)
    Http请求.SetHeader(`Accept-Language`, `zh-CN,zh;q=0.9`)
    Http请求.SetHeader(`Cache-Control`, `no-cache`)
    Http请求.SetHeader(`Content-Type`, `multipart/form-data; boundary=----WebKitFormBoundarybxBPD59cJtdK0xKS`)
    Http请求.SetHeader(`Origin`, `https://store.steampowered.com`)
    Http请求.SetHeader(`Pragma`, `no-cache`)
    Http请求.SetHeader(`Referer`, `https://store.steampowered.com/login/?redir=&redir_ssl=1&snr=1_4_4__global-header`)
    局_设备信息 := []*__.DeviceDetails{{
        DeviceFriendlyName: "Pixel",
        PlatformType:       3,
    }}
 
    局_pb := __.EMsg9804{
        AccountName:         集.账号,
        EncryptedPassword:   密码加密(集.密码, 集.Rsa.PublicKey, 集.Rsa.MoShu),
        EncryptionTimestamp: 集.Rsa.SuiJiShu,
        SetPersistence:      true,
        SetRememberLogin:    1,
        WebsiteId:           "Mobile",
        Language:            6,
        DeviceDetails:       局_设备信息,
    }
 
    局_pb字节集, err := proto.Marshal(&局_pb)
    if err != nil {
        集.错误信息 = "登录信息序列化失败:" + err.Error()
        return false
    }
     
    局_post := map[string]string{
        "input_protobuf_encoded": base64.StdEncoding.EncodeToString(局_pb字节集),
    }
    Http请求.EnableForceMultipart().SetFormData(局_post)
 
    var 局_请求结果 *req.Response
    for i := 0; i < 3; i++ { // 重试三次防止意外
        局_请求结果, err = Http请求.Post(局_网址)
        if len(局_请求结果.Bytes()) > 0 || err != nil {
            break
        }
    }
 
    fmt.Printf("X-eresult:%s\n", 局_请求结果.GetHeader("X-eresult"))
    if 局_请求结果.GetHeader("X-eresult") != "1" {
        i, _ := strconv.Atoi(局_请求结果.GetHeader("X-eresult"))
        switch i {
        default:
            集.错误信息 = 局_请求结果.GetHeader("X-eresult") + 局_请求结果.String()
            fmt.Print("\n", 局_请求结果.Dump())
        case 2:
            集.错误信息 = "#Login_RefreshReason_Generic"
            集.错误信息 = "#请再次登录"
        case 7:
            集.错误信息 = "#Login_RefreshReason_Generic"
            集.错误信息 = "#请再次登录"
        case 6:
            集.错误信息 = "#Login_RefreshReason_LoggedInElsewhere"
            集.错误信息 = "#此帐户已在另一台计算机上登录"
        case 34:
            集.错误信息 = "#Login_RefreshReason_LogonSessionReplaced"
            集.错误信息 = "#此帐户已在别处登录"
        case 5:
            集.错误信息 = "#Login_RefreshReason_InvalidPassword"
            集.错误信息 = "您的帐户凭据已更改,密码错误"
        case 26:
            集.错误信息 = "#Login_RefreshReason_Revoked"
            集.错误信息 = "#您的会话已结束"
        case 27:
            集.错误信息 = "#Login_RefreshReason_Expired"
            集.错误信息 = "#您的会话已过期"
        case 49:
            集.错误信息 = "#Login_RefreshReason_PasswordRequiredToKickSession"
            集.错误信息 = "#确认您的凭据以从另一台计算机退出"
        case 43:
            集.错误信息 = "#Login_RefreshReason_AccountDisabled"
            集.错误信息 = "#您的帐户已被禁用"
        case 69:
            集.错误信息 = "#Login_RefreshReason_ParentalControlRestricted"
            集.错误信息 = "#您帐户的家长控制要求您确认凭据"
        case 84:
            集.错误信息 = "#Login_Error_RateLimit_Description"
            集.错误信息 = "#短期内来自您所在位置的失败登录过多。请稍后再试。"
        }
        return false
    }
 
    fmt.Printf("登录响应Hex:%s\n", hex.EncodeToString(局_请求结果.Bytes()))
 
    err = proto.Unmarshal(局_请求结果.Bytes(), &集.Response_EMsg147)
    if err != nil {
        集.错误信息 = "登录结果反序列化失败:" + err.Error()
        return false
    }
    fmt.Print("\n", 局_请求结果.Dump())
    if 集.Response_EMsg147.Steamid == 0 {
        return false
    }
    return true
}

#提交邮件验证码包
既然要验证码,那也没办法继续吧,本以为可以收工了(真的想多了)
这个包就很简单了,需要的信息上边包都返回了,至直接使用提交即可
x-eresult: 65=代码错误 1=成功
图片描述
proto文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
syntax = "proto3";
package Protoc;
option go_package = ".";
/*
i.SetEMsg(9804),
i.Body().set_client_id(this.m_strClientID),
i.Body().set_steamid(this.m_steamid),
i.Body().set_code(e),
i.Body().set_code_type(r ? 2 : 3);
*/
 
//Authentication.UpdateAuthSessionWithSteamGuardCode#1
message UpdateAuthSessionWithSteamGuardCode {
  uint64 client_id = 1;     //47388000    登录返回的信息
  fixed64 steamId= 2;        // 10168
  string code= 3;          //""   验证码
  int64 code_type= 4;    // 2  2是邮件验证码  3可能是动态码
}

#刷新状态包
但是有个问题啊,为什么提交完验证码没返回登录凭据呢acctoken
原来还有一个包,在这了获取,因为页面跳转浏览器不会缓存这个返回数据,我用花瓶查看到的,
qing
图片描述
返回信息就在这里
图片描述
proto文件编写

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
syntax = "proto3";
package Protoc;
option go_package = ".";
 
 
message PollAuthSessionStatus_Request {
  uint64 ClientID = 1;
  bytes request_id= 2//登录包返回
}
 
 
 
//======Response
//
message PollAuthSessionStatus_Response {
  uint64 ClientID = 1;
  string new_challenge_url = 2;
  string refresh_token = 3;
  string access_token= 4;
  bool had_remote_interaction= 5;
  string account_name= 6;
  string new_guard_data= 7;
  string agreement_session_url= 8;
}

写代码测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
// 本命令由自动生成,请配合[ go get -u gitee.com/anyueyinluo/Efunc ]库使用。
func (集 *Api) D登录_刷新状态() bool {
    局_网址 := `https://api.steampowered.com/IAuthenticationService/PollAuthSessionStatus/v1`
    Http请求 := 集.Http客户端.R()
    var 局_PB __.PollAuthSessionStatus_Request
    局_PB.ClientID = 集.Response_EMsg147.ClientId
    局_PB.RequestId = 集.Response_EMsg147.RequestId
 
    局_PB_字节集, _ := proto.Marshal(&局_PB)
 
    局_post := map[string]string{
        "input_protobuf_encoded": B编码_BASE64编码(局_PB_字节集),
    }
    Http请求.EnableForceMultipart().SetFormData(局_post)
    Http请求.SetHeader(`accept`, `application/json, text/plain, */*`)
    Http请求.SetHeader(`accept-language`, `zh-CN,zh;q=0.9`)
    Http请求.SetHeader(`cache-control`, `no-cache`)
    Http请求.SetHeader(`content-type`, `multipart/form-data; boundary=----WebKitFormBoundaryuudBrPBCeV3jLjkZ`)
    Http请求.SetHeader(`dnt`, `1`)
    Http请求.SetHeader(`origin`, `https://store.steampowered.com`)
    Http请求.SetHeader(`pragma`, `no-cache`)
    Http请求.SetHeader(`referer`, `https://store.steampowered.com/login/?redir=%3Fsnr%3D1_60_4__global-responsive-menu&redir_ssl=1&snr=1_4_4__global-header`)
    Http请求.SetHeader(`user-agent`, `Mozilla/5.0 (iPhone; CPU iPhone OS 13_2_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.3 Mobile/15E148 Safari/604.1`)
    var 局_请求结果 *req.Response
    var err error
    for i := 0; i < 3; i++ { // 重试三次防止意外
        局_请求结果, err = Http请求.Post(局_网址)
        if len(局_请求结果.Bytes()) > 0 || err != nil {
            break
        }
    }
    if 局_请求结果.GetHeader("X-eresult") != "1" {
        i, _ := strconv.Atoi(局_请求结果.GetHeader("X-eresult"))
        switch i {
        default:
            集.错误信息 = 局_请求结果.GetHeader("X-eresult") + 局_请求结果.String()
            fmt.Print("\n", 局_请求结果.Dump())
        case 2:
            集.错误信息 = "#Login_Error_Expired_Description"
            集.错误信息 = "#登录请求已失效"
        case 3:
            集.错误信息 = "#Login_Error_Network_Description"
            集.错误信息 = "#与 Steam 通信时出现问题。请稍后重试。"
        case 4:
            集.错误信息 = "#Login_Error_MoveAuthenticator_Description"
            集.错误信息 = "#在移动您的验证器时出现问题,请稍后再试。"
        case 5:
            集.错误信息 = "#Login_Error_RateLimit_Description"
            集.错误信息 = "#短期内来自您所在位置的失败登录过多。请稍后再试。"
        case 6:
            集.错误信息 = "#网页端和桌面版 Steam 客户端均不支持匿名登录;仅 steamcmd 支持匿名登陆。"
            集.错误信息 = "#短期内来自您所在位置的失败登录过多。请稍后再试。"
        }
        return false
    }
    fmt.Printf("刷新状态提交响应Hex:%s\n", hex.EncodeToString(局_请求结果.Bytes()))
    var 局_pb __.PollAuthSessionStatus_Response
    err = proto.Unmarshal(局_请求结果.Bytes(), &局_pb)
    if err != nil {
        集.错误信息 = "刷新状态结果反序列化失败:" + err.Error()
        return false
    }
    fmt.Print(json.Marshal(局_pb))
    fmt.Printf("%v\n", 局_pb)
    return true
}

测试ok成功获取到acctoken
#结语
这个网站其实没什么加密,只是使用了protobuf格式的数据,所以导致读取比较麻烦,尤其是某些语言(没错说的就是你,易语言,),真的很不方便,
我也是没办法才改使用go来操作,还别说,go语言还是挺方便的.
撒花完结,还差十几雪币,我就要成为正式会员了,嘿嘿嘿,不枉我每天签到,开心.


[培训]二进制漏洞攻防(第3期);满10人开班;模糊测试与工具使用二次开发;网络协议漏洞挖掘;Linux内核漏洞挖掘与利用;AOSP漏洞挖掘与利用;代码审计。

收藏
点赞5
打赏
分享
最新回复 (2)
雪    币: 16
活跃值: (295)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
沐仁浴义 5天前
2
0
牛逼
雪    币: 5887
活跃值: (4472)
能力值: ( LV10,RANK:160 )
在线值:
发帖
回帖
粉丝
3
0
mark
游客
登录 | 注册 方可回帖
返回