Hacking Punkbuster

Daniel Prizmant
10 min readSep 25, 2020

--

A Directory Traversal Attack on Punkbuster Server can be Leveraged to Gain Remote Code Execution

Who We Are

We are two gamers that love the Battlefield series and also happened to like reverse engineering projects.

Daniel Prizmant — A senior security researcher at Palo Alto Networks who loves video games reverse engineering— https://www.linkedin.com/in/prizmant/https://twitter.com/prizdan

Mauricio Sandt — Second semester CS student doing low level programming and reverse engineering as a hobby — https://www.linkedin.com/in/mauriciosandt/

Some Background

Back in the day, before kernel-driver-backed anti cheats, Punkbuster was the gold standard in the industry. Punkbuster was revolutionary and introduced many new techniques to fight cheaters. It was a very long time ago, but back then all the big titles, like Battlefield 2 and Call of Duty: Modern Warfare were using Punkbuster as their go-to anti cheat. Sadly, Punkbuster didn’t manage to keep up with the industry and it has been years since a new online game chose to use its services (the last game to use it was Battlefield Hardline released in March 2015).

How did We Get Here?

Recently we started playing Battlefield 4 again, and were surprised to find out there are still many active servers, almost 1000 servers in total with 50,000 unique daily players, and most of them are Punkbuster protected.

We thought it would be a cool side project to take a look at the internals of Punkbuster and fundamentally understand how it works, so we did.

For security reasons we will only give a high level description of the vulnerability and won’t dive deep into the actual reverse engineering process.

We won’t share a working exploit either as there are still some servers that didn’t update yet.

Client Server Architecture

Both client and server side of Punkbuster work as modules. Both load the main Punkbuster module which just exports two functions:

  • ca and cb for the client
  • sa and sb for the server

Those two functions handle all the Punkbuster events for both sides. Event messages are encoded and decoded twice, once by Punkbuster itself (not dependent on the game) and once by the game itself.

For example, when a Punkbuster client sends a new message to the server it encodes it, then passes it to the game client which encodes it too as part of the game encoding. Eventually the message is received at the game server which decodes it and passes it to the Punkbuster module which decodes it using the Punkbuster algorithm.

This means that using tools like Wireshark to tap the communication won’t get you far as all the Punkbuster communication is encoded twice. Note that RCON commands are not encoded and received in the server as plain text.

The Screenshots Mechanism

One of Punkbuster’s features is the capability of requesting a screenshot from the game clients. The official description of the feature is the following:

The PunkBuster Screen Capture Facility allows Server Admins to request actual screenshots from players’ screens while they are playing. The screenshots are transferred over the network and saved by the Server Admins either for private use or possibly for publication to a website.

So, basically, admins can ask clients to send their screenshots to the server for observation. This can also be scheduled so each client will be requested a screenshot each x amount of seconds.

Screenshot Hello World

Without getting into too much details about the protocol, suffice to say that when an admin wants to get a screenshot from a client, either manually or because x amount of seconds passed since the last screenshot, it sends the client a message in a format we will explain later in the post.

Consider the following log which was captured on the internals of the server side of Battlefield 4 (after the 2 decoding processes) for this research:

Msg FROM slot #-1
00000000 | 70 62 5f 73 76 5f 67 65 | pb_sv_ge
00000008 | 74 73 73 20 31 | tss 1
Msg TO slot #0
00000000 | 49 30 20 31 32 39 34 36 | I0 12946
00000008 | 38 39 37 35 34 20 31 20 | 89754 1
00000010 | 33 32 30 20 32 34 30 20 | 320 240
00000018 | 35 30 20 35 30 20 31 00 | 50 50 1.
Msg FROM slot #0
00000000 | 47 31 34 34 33 38 30 20 | G144380
00000008 | 43 31 44 36 36 39 39 41 | C1D6699A
00000010 | 45 34 42 31 45 42 34 31 | E4B1EB41
00000018 | 37 38 43 35 45 36 46 35 | 78C5E6F5
00000020 | 45 41 42 38 30 46 35 39 | EAB80F59
00000028 | 20 41 70 62 30 30 30 30 | Apb0000
00000030 | 30 31 2e 70 6e 67 00 | 01.png.
Msg FROM slot #0
00000000 | 48 00 00 41 89 50 4e 47 | H..A.PNG
00000008 | 0d 0a 1a 0a 00 00 00 0d | ........
00000010 | 49 48 44 52 00 00 02 2e | IHDR....
00000018 | 00 00 01 32 08 02 00 00 | ...2....
00000020 | 00 52 d4 cc f5 00 00 00 | .R......
00000028 | e6 74 45 58 74 63 6f 6d | .tEXtcom
00000030 | 6d 65 6e 74 00 50 75 6e | ment.Pun
00000038 | 6b 42 75 73 74 65 72 20 | kBuster
00000040 | 53 63 72 65 65 6e 73 68 | Screensh
00000048 | 6f 74 20 28 f1 29 20 42 | ot (.) B
00000050 | 46 34 20 20 4c 65 76 65 | F4 Leve
00000058 | 6c 73 2f 4d 50 2f 4d 50 | ls/MP/MP
00000060 | 5f 41 62 61 6e 64 6f 6e | _Abandon
00000068 | 65 64 2f 4d 50 5f 41 62 | ed/MP_Ab
00000070 | 61 6e 64 6f 6e 65 64 0a | andoned.
...
...
...

Lets go over this line by line. First the server received, from slot #-1 (RCON admin) a request to take a screenshot from client on slot 1:

pb_sv_getss 1

The server then sends the following message to slot 1:

I0 1294689754 1 320 240 50 50 1

This is the screenshot message. The format of that message is the following:

I[delay] [rand] [file_id] [width] [height] [x] [y] [rate][delay] = Maximum delay client waits before capturing screenshot
[rand] = Random identifier, basically the ID of the screenshot
[file_id] = The file name of the screenshot by the format pb%06.png
[width] = The requested width, in pixels, of the screenshot
[height] = The requested height, in pixels, of the screenshot
[x] = The horizontal center of the screenshot, in %
[y] = The vertical center of the screenshot, in %
[rate] = The rate the server will receive the screenshot in kb/s

Most of those variables can be configured with the following punkbuster server commands:

pb_sv_SsWidth 320 //Requested pixel width of remote screenshots
pb_sv_SsHeight 240 //Requested pixel height of remote screenshots
pb_sv_SsXpct 50 //Percentage across screen for remote screenshots
pb_sv_SsYpct 50 //Percentage down screen for remote screenshots
pb_sv_SsSrate 1 //Sample Rate for remote screenshots
pb_sv_SsDelay 0 //Maximum delay client waits before capturing screenshot

Back to the log. After the server sends the client the screenshot request the client answers with the following message after capturing the screenshot:

G144380 C1D6699AE4B1EB4178C5E6F5EAB80F59 Apb000001.png

This format is much simpler:

G[size] [md5] [letter][pb%06.png][size] = The size of the screenshot, in bytes
[md5] = MD5 hash of the screenshot data
[letter] = Screenshot counter, A for 1st, B for 2nd etc
[pb%06.png] = Screenshot name, [file_id] is taken from the server request

After this header you can see the client begin transferring the screenshot data, note the PNG magic string on the last message from the client.

The Problem

Some of you may have already noticed the possible problem here. The server tells the client the file_id for the screenshot but then the client actually tells the server the screenshot name! This is, of-course, not a problem on its own, because the server can still handle malicious file names on its side.

But in this case — it didn’t.

Consider the following code which is a simplified version of the server’s code:

char ssPath[0x100];
// clientMsg = "104553 8E4B1C2F39151F72E364E461FB43F931 Apb000001.png";
int ssSize = stoi(GetParamBySpace(clientMsg, 0));
char *ssHash = GetParamBySpace(clientMsg, 1);
// Add 1 to remove the ss counter, A in this case
char *ssFileName = GetParamBySpace(clientMsg, 2) + 1;
if (!strnicmp(&ssFileName[strlen(ssFileName) - 3], "png", 3)) {
// ssFileName indeed ends with png
// Do some checks against trivial directory traversal attacks
// like search for "//" and ".." and then cat
// the filename to \\svss\\ in the pb folder
// eventually do something like:
sprintf(ssPath, "%s\\svss\\%s", pbMasterFolderPath, ssFileName);
} else { // <- Problem is here
// No checks for directory traversal attacks in this branch
sprintf(ssPath, "%s\\svss\\%s", pbMasterFolderPath, ssFileName);
}



ssFileData = malloc(ssSize);

Exploiting the Bug

At this point it is pretty obvious what one needs to do in order to exploit this bug. An attacker can simply modify the file name so it won’t end with a png, passing all the directory traversal mitigations Punkbuster implemented, basically gaining the ability to write any file to anywhere in the server’s filesystem.

We crafted the following screenshot message:

G22 86FCCBBB45E7AB6CFB84B295B4F29360 ..\..\..\..\..\Desktop\malware.exe

With the following file’s data:

MZ___EVIL_MALWARE_DATA

Note that we had to provide a correct MD5 for that data or otherwise the server will just drop the message.

The following log represents the server side when we tested such an attack:

Msg FROM slot #-1
00000000 | 70 62 5f 73 76 5f 67 65 | pb_sv_ge
00000008 | 74 73 73 20 31 | tss 1
Msg TO slot #0
00000000 | 49 30 20 38 30 31 31 31 | I0 80111
00000008 | 33 31 32 36 20 32 20 33 | 3126 2 3
00000010 | 32 30 20 32 34 30 20 35 | 20 240 5
00000018 | 30 20 35 30 20 31 00 | 0 50 1.
Msg FROM slot #0
00000000 | 47 32 32 20 38 36 46 43 | G22 86FC
00000008 | 43 42 42 42 34 35 45 37 | CBBB45E7
00000010 | 41 42 36 43 46 42 38 34 | AB6CFB84
00000018 | 42 32 39 35 42 34 46 32 | B295B4F2
00000020 | 39 33 36 30 20 2e 2e 5c | 9360 ..\
00000028 | 2e 2e 5c 2e 2e 5c 2e 2e | ..\..\..
00000030 | 5c 2e 2e 5c 44 65 73 6b | \..\Desk
00000038 | 74 6f 70 5c 6d 61 6c 77 | top\malw
00000040 | 61 72 65 2e 65 78 65 00 | are.exe.
Msg FROM slot #0
00000000 | 48 00 00 2e 4d 5a 5f 5f | H...MZ__
00000008 | 5f 45 56 49 4c 5f 4d 41 | _EVIL_MA
00000010 | 4c 57 41 52 45 5f 44 41 | LWARE_DA
00000018 | 54 41 | TA

And indeed, malware.exe was written to the Desktop, far far away from the Punkbuster folder:

procmon showing malware.exe was written to the user’s Desktop

As an attacker you don’t actually need to wait for the server to ask for a screenshot and can just send the relevant data as it was requested and it will still be received on the server. Mind that exploiting this vulnerability might not be that easy, as the client side, which is the part needed to actually achieve that, is heavily obfuscated.

Applications

The ability to write any file anywhere is practically a remote code execution because an attacker can obviously just overwrite modules that get loaded frequently which will load the attacker’s code.

Punkbuster might be old but there are many servers using its service to this day. It is important to remember that these servers are not cheap. Those are gaming servers with expensive hardware that can be a jackpot for hackers to install miners on.

Sensitive information might be on those servers because usually only the hosting company staff have access to them.

This can also lead to a bigger attack from inside the network.

Even Balance Response

Tony from Even Balance was quick to respond. He thanked us and assured Even Balance will address this vulnerability right away.

A few days ago a new patch (1.905) was released that supposedly addresses this issue. All servers that are running Punkbuster will automatically download the new version.

We offered our help with addressing this issue and asked that the fixing process will be collaborated with us but Even Balance chose not to do that (which is completely fine, of-course), so we can’t be responsible that the fix actually addresses the issue well.

With that being said, we did test our old exploit against the new version and it didn’t work. We didn’t actually reverse and BinDiffed the new patch, so we can’t be absolutely sure it isn’t exploitable anymore, but from a superficial examination it seems that the issue has been fixed.

Even Balance also posted the following message on their website:

A serious security (remote code execution) flaw involving PunkBuster was recently discovered and reported. An attacker with knowledge of the flaw can exploit it by replacing any unprotected (non-read-only) executable file with the attacker's provided file, even outside the "pb" subdirectory, thus compromising the game server's host environment. This flaw does not affect player installations nor the PunkBuster client. New PB Server version v1.905 which addresses the flaw has been released for all supported games via our auto-update system and for use by the PBSETUP stand-alone tool. Server administrators running a PB Server version prior to version 1.905 for any game should disable PunkBuster on their game server(s). Thanks to Daniel Prizmant at Palo Alto Networks for discovering the flaw and reporting it to us in a professional manner.

--

--