The official servers for Black & White were shut down in 2005, this project is
an attempt to emulate them completely to allow multiplayer connectivity across
the internet for anyone and to offer the same services provided between 2001-2005
by bwgame.com.

This post is a reflection write up of my GitHub repository bw1-mp that I worked
on during October 2016. We figure out how to mimic the bwgame.com servers to
allow a user to login and lobby, the next step would be to replicate GameSpy.
You can find the source code here.

Starting with the login request

Our first target to attack is the login server, we need to imitate the expected
values in order for our game client to get anywhere near the online process.
When you first start the multiplayer client you are greeted with a login screen
asking for your username and password; when the game first came out you were
required to register your username and password with your CD-Key online.

Using Wireshark we can monitor all network traffic the game attempts to send
to the server, however as there is no server we can not determine what the
client expects as a response, so we will require other methods such as guess
work and disassembling the game’s binary.

Submitting the form with the username and password set to mattmatt will
generate a HTTP GET request to login.bwgame.com:80 with the username and
password as query strings, knowing this we can easily create a simple
authentication system - now let’s work on the response.

GET /login/?username=matt&userpassword=matt

Responding to the login request

Like I said before, this isn’t as easy because we don’t have any packet dumps
from the actual server to work on. We’re going to need to open our disassembly of
the game and see how the client handles the response data to create a response.
Responding any data not in the correct format results in an incorrect password dialog.

Following some disassembly we find that the response data from the server is
parsed like so: bnwuserid:%d %d %d %s

We can immediately tell the first digit is a unique user id, the rest of the
values are currently unknown and don’t seem to effect anything noticeable.
We can look into them some other time.

Sending a response of bnwuserid:1 1 1 hello is enough to get us past the login
and onto the next step.

GET ConditionTemplate.txt

Firstly, there is a very simple GET request made to
storage.bwgame.com/bwmaps/ConditionTemplate.txt by the game client.
ConditionTemplate.txt - is a very basic CSV file format and luckily a copy
is included in the game itself, this file contains the win conditions for
all the different multiplayer maps.

We simply respond to the request with our copy of the file, no obfuscation or
encoding on this. Without this the game client refuses to go any further.

db.bwgame.com

Our next HTTP request is odd, a HTTP PUT request is made to db.bwgame.com/query
with the following query string:

Most of that data is self explanatory, except dbflags and query. This one
stumped me for quite a while but basically dbflags is the plaintext length of
the unencoded and unobfuscated query parameter.

Lionhead’s Base 26 web encoding

Looking into the query parameter, Lionhead decided to use their own base 26
[A-Z] web encoding, so for every 2 characters just 1 full byte is represented.

All the encoding is, is simply taking the bottom 4 bits [0-15] and the top 4 bits
[also 0-15] and adding 65 (ASCII A) to the value. We can work out the encoding this way:

In

»4 (L)

&0xF (H)

+65 (L)

+65 (H)

ASCII (L)

ASCII (H)

0xFF

0x0F

0x0F

0x50

0x50

P

P

0x64

0x06

0x04

0x47

0x45

G

E

0x03

0x00

0x03

0x41

0x44

A

D

We simply take each hex digit, and add 0x41 (65 dec) to it.

If we want to decode an encoded string we simply do the opposite:

turn both characters into their numerical ASCII integer

negate 0x41 (65 dec) from each integer

bit shift the first/lower integer left by 4 bits

add them together

In (L)

In (H)

-65 (L)

-65 (H)

Out

P

P

0x0F

0x0F

0xFF

G

E

0x06

0x04

0x64

A

D

0x00

0x03

0x03

Using this we can now decode our query string…. partly.

Tiny Encryption on query strings

Using our newly created LHWebDecode procedure on the query string the client
sends the server HPFFEGFCCPIKEOFDCMFIFBEJCJABCNLM, we don’t get a neat ASCII
string like I was expecting, we get a jumbled array with many unprintable
characters. Diving into the disassembly some more, the reason they encode the
query string in the first place is because they first obfuscate it.

The encryption would be difficult to figure out if it wasn’t for the constant
0x9E3779B9 or in decimal 2654435769 which is equal to 232 ÷ φ.
This is a common constant in Fibonacci hashing.

From the usage of Fibonacci hashing and the usage of a 128-bit key we can ascertain
that the encryption used is a derivative of TEA (Tiny Encryption Algorithm).
Most likely it uses XXTEA an improved upon version fixing several weaknesses
in the original algorithm. You can see below a diagram of how it works:

Luckily for us, the keys are included in the game client.
Encryption cracked, you can see the completed functions for encryption and
decryption here.

Now we can run our original query string sent by the game client through our
base-26 conversion and then our TEA decryption:

HPFFEGFCCPIKEOFDCMFIFBEJCJABCNLM -> BWMAPS_GETLIST

Constructing our query response

After searching the disassembly for BWMAPS_GETLIST it became easy to see how
the game client parsed the response; it would search the response string for:

[rows]:%d number of rows returned

[columns]:%d number of columns per row

[totalcolumns]:%d basically rows * columns

We can ascertain that the client expects a table of data in the response, the
client loops through each byte of the response looking for the byte values
0x02 (ASCII STX) and 0x03 (ASCII ETX): start of text and end of text.
For each pair of these, the data in between them is treated as the column data.

Let’s construct our response’s map table from the following:

ID

Name

File

Players

Hostname

Download Folder

1

Bombardment - 2 players

mpm_2p_1

2

storage.bwgame.xyz:80

/bwmaps/

2

King of the hill - 3 players

mpm_3p_1

3

storage.bwgame.xyz:80

/bwmaps/

3

The four corners of Eden - 4 players

mpm_4p_1

4

storage.bwgame.xyz:80

/bwmaps/

The column headers are assumed from later work disassembiling the game,
hostname & download folder are used by the client to download maps they do not
have.

First let’s make our data descriptions: [rows]:3[columns]:6[totalcolumns]:18 -
we obviously have 3 rows, each with 6 cols, so we have a total of 18 columns.
Now we loop the data and create our full data string from that:

Responding to the original query request with the above data string, our client
accepts the data as valid and continue through the process.

Another query: BWGETPEERCHAT

After our map list has been downloaded by the client, the client sends the database
server another request to /query/, thanks to our previous efforts we can instantly
decode and decrypt the query string as BWGETPEERCHAT. A quick search through
our disassembly will reveal the response is simply parsed as a 1x1 table containing
a host name and port like so:

Hostname : Port

peerchat.bwgame.com:6667

The tables don’t have column headers, I just add those to make data representation easier to understand.

After our client receives the string \0x2peerchat.bwgame.com:6667\0x3[rows]:1[columns]:1[totalcolumns]:1
the game client will open a TCP connection directly to the given hostname + port.

GameSpy’s Peerchat

Before we examine the TCP connection stream anymore, it’s important to know what
Peerchat is first; the original Peerchat server was written by GameSpy and used
to be hosted on peerchat.gamespy.com - until it was shutdown in 2014 making
hundreds of games multiplayer modes completely useless. It enabled a simple way
for game developers to create lobby based game systems enabled with cd-key
authentication and encryption.

PeerChat is also however just a classical IRC server which uses a very simple
encryption - and Black & White doesn’t even use the encryption.

Peerchat Handshake

If we go back to our TCP connection stream now and examine it, the client makes
the first move by sending a simple USRIP\r\n command. Luckily
aluigi has already reverse-engineered the
Peerchat protocol and we can see the response we have to make.

:s 302 :=+@0.0.0.0\r\n

As soon as we send this back, the client begins sending regular IRC commands:

Great. Let’s proxy them to a real IRC server now, I set a basic one up using
unrealircd running on classical IRC mode since the game is from 2001.
And if we proxy the responses back to the client we get past the handshake phase
into the current game list:

Peerchat Notes

The multiplayer pretty much entirely works over the Peerchat/IRC protocol - you
can create games which basically creates an IRC channel #GSP!bandw!X14saFv19X
where the name partially matches your encoded user credentials. Rooms are passworded,
locked etc.. using default IRC modes and the creator is channel OP.

NICK BNW_536871013 - The argument is basically the UID we provide in login but with the bitwise operators UID & 0x1FFFFFFF | 0x20000000 applied making the value a much higher integer.

Current game list:

JOIN #bandw_updates
PART #bandw_updates

Whenever the Refresh List button is pressed the client sends a join, waits (more like hangs whilst it waits for a response) and then leaves the channel. This happens when
the client first logs in too. It is unclear right now what the game expects as a response but we will look into it later.

Logging multiple clients into the game works, but they can not see each other games in the list yet.

Footnote

Emulating bwgame.com servers is pretty much done now, all we need is a GameSpy
peerchat emulator and we would have a complete lobby functionality.
All code is publically available on GitHub.