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