Steam Friends
Introduction
Steam Friends is an instant messaging protocol that is built into Steam, a game content delivery system developed by Valve. This page details the protocol specification for Steam Friends as was ascertained though reverse-engineering.
Connection
Steam Friends uses UDP on port 27017. Connections have been made to at least the following servers:
- 68.142.64.165
- 68.142.64.164 (resolves to valve-68-142-64-164.cust.phx3.llnw.net)
- 72.165.61.185
- 69.28.145.170
- 69.28.145.172
The client is using WSASendTo to send messages to friends
Layers
On top of UDP, another layer is implemented to keep track of sequencing and splitting of packets. This layers prepends every packet with the following 36-byte structure:
offset | 0x00 | 0x02 | 0x04 | 0x06 | 0x08 | 0x0A | 0x0C | 0x0E |
---|---|---|---|---|---|---|---|---|
0x00 | "VS01" | packet len | type & bits | source | destination | |||
0x10 | sequence # | last rcv.# | split count | seq. # of 1st packet | ||||
0x20 | data length |
Note: These values are little-endian, which is not network order.
Steam Protocol Header Fields Description
Field | Type | Length (bytes) | Description |
---|---|---|---|
"VS01" | 4 chars | 4 | the 4 characters 'V', 'S', '0', '1' (0x56, 0x53, 0x30, 0x31) |
packet len | integer | 2 | the length of the packet after this header |
type & bits | byte & bit-field | 2 | the first byte is possibly some type identifier. It is always <8 |
destination | integer | 4 | the destination ID of the packet |
source | integer | 4 | the source ID of the packet |
sequence # | integer | 4 | the packet's sequence number. server and client keep track of own numbers |
last recv. # | integer | 4 | the sequence number of the last packet received |
split count | integer | 4 | the number of packets the current message was split in to |
seq. # of 1st packet | integer | 4 | the sequence number of the first packet for current message |
data length | integer | 4 | the length of the data in this message (which will be greater than packet length if the message is split) |
Login sequence
Client initiates the login by sending a type: 0x0001, seq: 1, src: 0x00000200, dst: 0x00000000, split: 0, and no data to the server
00: 56 53 30 31 00 00 01 00 00 02 00 00 00 00 00 00
10: 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
20: 00 00 00 00
Server responds with type: 0x0002, seq: 1, ack: 1, src: 0x00000000, dst: 0x00000200, split: 0, with 8 bytes of data attached
00: 56 53 30 31 08 00 02 00 00 00 00 00 00 02 00 00
10: 01 00 00 00 01 00 00 00 00 00 00 00 00 00 00 00
20: 00 00 00 00 d6 3c 10 f0 a4 00 00 00
Client responds with type: 0x0403, sequence: 1, last: 1, dst: 0x00000200, split: 1, with 4 bytes of data attached. This 4 bytes corresponds to the first 4 bytes that the server sent (in LE) XORed with 0xA426DF2B
00: 56 53 30 31 04 00 03 04 00 02 00 00 00 00 00 00
10: 01 00 00 00 01 00 00 00 01 00 00 00 01 00 00 00
20: 04 00 00 00 fd e3 36 54
Server responds with type: 0x0404, seq: 2, ack: 1, src: this becomes the dest for all subsequent packets from client, split: 1
00: 56 53 30 31 00 00 04 04 00 eb b9 14 00 02 00 00
10: 02 00 00 00 01 00 00 00 01 00 00 00 02 00 00 00
20: 00 00 00 00
Server sends type: 0x0406, seq: 3, acq: 1, with the following 28 byte data stream: "17 05 00 00 ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff 01 00 00 00 01 00 00 00"
00: 56 53 30 31 1c 00 06 04 00 eb b9 14 00 02 00 00
10: 03 00 00 00 01 00 00 00 01 00 00 00 03 00 00 00
20: 1c 00 00 00 17 05 00 00 ff ff ff ff ff ff ff ff
30: ff ff ff ff ff ff ff ff 01 00 00 00 01 00 00 00
Client responds type: 0x0406, seq: 2, acq: 3, with the following data stream: "18 05 00 00 ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff 01 00 00 00 80 00 00 00 [128 byte RSA encrypted data block containing a 32 byte random session key generated by the client, xx] [CRC32 of preceding 128 bytes, cc] 00 00 00 00".
00: 56 53 30 31 a4 00 06 04 00 02 00 00 00 34 7e fe
10: 03 00 00 00 03 00 00 00 01 00 00 00 03 00 00 00
20: a4 00 00 00 18 05 00 00 ff ff ff ff ff ff ff ff
30: ff ff ff ff ff ff ff ff 01 00 00 00 80 00 00 00
40: xx xx xx xx xx xx xx xx xx xx xx xx xx xx xx xx
50: xx xx xx xx xx xx xx xx xx xx xx xx xx xx xx xx
60: xx xx xx xx xx xx xx xx xx xx xx xx xx xx xx xx
70: xx xx xx xx xx xx xx xx xx xx xx xx xx xx xx xx
80: xx xx xx xx xx xx xx xx xx xx xx xx xx xx xx xx
90: xx xx xx xx xx xx xx xx xx xx xx xx xx xx xx xx
a0: xx xx xx xx xx xx xx xx xx xx xx xx xx xx xx xx
b0: xx xx xx xx xx xx xx xx xx xx xx xx xx xx xx xx
c0: cc cc cc cc 00 00 00 00
Server responds with type: 0x0406 with the following 24 bytes of data: "19 05 00 00 ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff 01 00 00 00"
00: 56 53 30 31 18 00 06 04 00 34 7e fe 00 02 00 00
10: 04 00 00 00 03 00 00 00 01 00 00 00 04 00 00 00
20: 18 00 00 00 19 05 00 00 ff ff ff ff ff ff ff ff
30: ff ff ff ff ff ff ff ff 01 00 00 00
Server and Client now sends messages which are AES encrypted with Cipher-block chaining (CBC). The key for the packets is transferred within the previous message exchange. The IV used for the CBC is computed from the first 16 bytes of each message. The decypted message appear to be compressed uses the zlib libraries, however I've been unable to decompress them thus far.
Encryption
The encryption method has yet to be determined. There was some discussion on the pidgin mailing list that they might use the ICE (Information Concealment Engine) for encryption. According to the Valve Developer pages, ICE is used to encrypt script files used for games, so it does not seem completely unreasonable that they could use it for their communication protocol, however, it doesn't necessarily imply that they do use it for encryption of network data.
Dissecting the steamclient.dll, there doesn't seem to be anything obvious that would identify the use of ICE. However, there are several references to the Crypto++ Library:
- CryptoPP.
- AlgorithmParametersBase
- IKeyCallback
- RSAFunction
- CipherModeBase: GetNextIV() must be called on an encryption object
- CCrypto::RSAEncrypt
- CCrypto::HexDecode
In addition, there is some thought that the 128 bytes transferred from the client to the server (see above) might be a 1024-bit RSA public encryption key.
Noticing that the client begins sending data to the server before the server has been able to send a key to the client, it is likely that the client has the server's public key stored locally somewhere. However, this doesn't help with figuring out the requests being sent by the client, unless we were to somehow insert a fake public key for which we have the private key, then decrypt what the client tries to send to the server. Once that's been done, the rest would be fairly straightforward: Just send our own public key to the server, then decrypt the responses we get back.
Possible public keys
128 unique bytes marked with {}
Public
30 81 9D 30 0D 06 09 2A 86 48 86 F7 0D 01 01 01
05 00 03 81 8B 00 30 81 87 02 81 81 00{DF EC 1A
D6 2C 10 66 2C 17 35 3A 14 B0 7C 59 11 7F 9D D3
D8 2B 7A E3 E0 15 CD 19 1E 46 E8 7B 87 74 A2 18
46 31 A9 03 14 79 82 8E E9 45 A2 49 12 A9 23 68
73 89 CF 69 A1 B1 61 46 BD C1 BE BF D6 01 1B D8
81 D4 DC 90 FB FE 4F 52 73 66 CB 95 70 D7 C5 8E
BA 1C 7A 33 75 A1 62 34 46 BB 60 B7 80 68 FA 13
A7 7A 8A 37 4B 9E C6 F4 5D 5F 3A 99 F9 9E C4 3A
E9 63 A2 BB 88 19 28 E0 E7 14 C0 42 89}02 01 11
Beta
30 81 9D 30 0D 06 09 2A 86 48 86 F7 0D 01 01 01
05 00 03 81 8B 00 30 81 87 02 81 81 00{AE D1 4B
C0 A3 36 8B A0 39 0B 43 DC ED 6A C8 F2 A3 E4 7E
09 8C 55 2E E7 E9 3C BB E5 5E 0F 18 74 54 8F F3
BD 56 69 5B 13 09 AF C8 BE B3 A1 48 69 E9 83 49
65 8D D2 93 21 2F B9 1E FA 74 3B 55 22 79 BF 85
18 CB 6D 52 44 4E 05 92 89 6A A8 99 ED 44 AE E2
66 46 42 0C FB 6E 4C 30 C6 6C 5C 16 FF BA 9C B9
78 3F 17 4B CB C9 01 5D 3E 37 70 EC 67 5A 33 48
F7 46 CE 58 AA EC D9 FF 4A 78 6C 83 4B}02 01 11
External Links
- Third-party Plugin developed for Pidgin (GNU GPL v3, uses Steam Mobile API as of version 1.0, users of this appear to the network as if on a mobile device)
- Valve developer wiki page for Steam Friends
- Wikipedia page
- Ticket requesting support for the Steam protocol within libpurple
- Ticket request: Create cross-platform Steam chat plugin with Steamkit2
- December 2007 Pidgin mailing list thread
- February 2008 Pidgin mailing list thread
- Valve Developer Information about ICE
- Vapor - open-source Steam client for Linux/Windows
- Open SteamWorks - open-source, platform independent Steam client, written in Mono (MS NET)
- SteamRE - Successor of Vapor, written in Mono
- libsteam - C-Library which offers basic steam protocol functions such as login
- SteamDroid - SteamFriends chat client for android devices (source, written in java)
- Even more implementations: