Verifying a client binary (c/s ports)

As many of you know, cheating can be an issue in open source games, and many such games have come up with various methods of fighting it. I've come up with a method that, while not effective against all cheats, is effective at stopping certain classes of attacks.

This requires a couple prerequisites:

- An account system, with a unique username and any password
- A clientside launcher program, separate from the game executable

This is how it works:

- Launcher authenticates to the account system with user's info
- Account System generates and compiles a program (referred to as the "verifier"), and sends it to Launcher
- Launcher executes Verifier in a separate process, and assumes its work is done

= Verifier:

Verifier is important in two ways, execution and composition.

== Verifier Execution:

Verifier contains in it a randomly generated token. Verifier reads the game exe into memory and creates an SHA-1 hash of the data combined with the randomly generated token (referred to as the ticket). It then sends this hash to Account System, which knows what the hash ought to be. Next, it creates a randomly-named folder and writes the game exe's data there. It executes the new game exe, passing the ticket as an argument (like -ticket "sdf90sdflksdf90sdf09wlk2309"), and finally removes the temporary folder when the program exits.

== Verifier Composition:

Verifier programs are fundamentally "obfuscated" programs. They are compressed (UPX) and contain junk instructions and data, thus they are not easily disassembled. They also have no library dependencies, so traditional DLL-injection is harder.

= Account System

Another important component of the system is the account system. It keeps track of when tickets are generated because tickets have a short lifespan and can only be used once. This mitigates brute force and forgery attacks.

= Weaknesses

There are, of course, things this scheme doesn't handle:

- Attaching a debugger to the game process afterwards
- High-level DLL-injection (say user32.dll or kernel32.dll)
- VirtualAllocEx/CreateRemoteThread calls on Windows
- Hooking
- Indirect attacks (driver hacks, video buffer scanning, etc.)

Some of these are mitigated by the nature of c/s, if the attack is not fast enough, the client will timeout and they will have to get a new ticket, restart the client, and retry the attack. Others are problems that any native game has (open source or not), therefore I'm not too worried about them.

Share this post


Link to post

This can be trivially circumvented by a user simply using their own binary instead of the "verified" binary, and telling everyone else they *are* using the "verified" binary where necessary.

It's a losing game you're trying to play. There is no solution the problem - on a fundamental level it's impossible to know that the information you're receiving over the network, from an opponent you're playing against, is generated by a particular binary or not.

Your suggestions of using "obfuscated" code really just boil down to a futile attempt at security through obscurity. It's a complicated waste of time that doesn't even solve the problem it's supposed to address.

If you want to avoid cheaters, what you really need is a social solution to the problem - make sure you play with friends that you trust not to cheat.

Share this post


Link to post

That's why the verifier reads the exe into memory, creates a random temporary folder, and writes the exe there before executing it. This does create a race condition; I can solve it on POSIX platforms but I don't know about Windows... probably there is a way though.

If you mean just re-using a ticket after they get a good one, tickets only work for a single connection.

= EDIT 1:

And yeah I know that hooking & hot-patching aren't really addressed with this, but any native game will have these issues too. I'm really only trying to mitigate problems that might arise from being open source, everything past that is bonus points.

= EDIT 2:

Maybe this would work on Windows: http://blogs.msdn.com/b/larryosterman/archive/2004/04/19/116084.aspx

Share this post


Link to post

The second you let go of your verified executable it can be swapped out by another process behind your back.

Share this post


Link to post

If the attacking process knows the path to the new game exe, which it won't. Also I wonder if I can CreateProcess() on Windows while having the exe file open O_EXCL.

Share this post


Link to post

Of course it knows the location of your exe, why wouldn't it? You can easily peek at the handles opened by another process, this is how many worms work.

Share this post


Link to post

Fair point, but you'd have to be extremely lucky to be able to swap the file in the 18ns between when the new exe is finished being written, and when the verifier executes it.

Share this post


Link to post
Ladna said:

That's why the verifier reads the exe into memory, creates a random temporary folder, and writes the exe there before executing it. This does create a race condition; I can solve it on POSIX platforms but I don't know about Windows... probably there is a way though.

If you mean just re-using a ticket after they get a good one, tickets only work for a single connection.

= EDIT 1:

And yeah I know that hooking & hot-patching aren't really addressed with this, but any native game will have these issues too. I'm really only trying to mitigate problems that might arise from being open source, everything past that is bonus points.

And all these efforts will be in vain, because they ignore the fundamental problem - the program receives data from the network; it is impossible to tell what sent it.

When you try and go down this road, you just end up with an arms race between hackers and game designers, and the result is horrible overly-intrusive programs like PunkBuster that everyone hates. And you still don't solve the problem, either.

Share this post


Link to post
Ladna said:

Fair point, but you'd have to be extremely lucky to be able to swap the file in the 18ns between when the new exe is finished being written, and when the verifier executes it.

I beg to differ; you can wait-on-release and then lock the other process out while you swap in your nefarious executable. However you don't even need to do that, given the other already mentioned approaches.

You *may* be able to implement a completely secure system, even on Windows, however is it really worth all the effort? Personally, I'd spend my time on effective anti-cheat measures (like denying clients the information they need to aim-/wall- hack in the first place).

Share this post


Link to post

Yeah, after doing some research, I don't think there's a way to write to a file and then execute it while being sure of its contents. You might be able to achieve the ultimate goal of executing a known good binary blob by messing up your own stack, but that's obviously not a solution. This goes for Windows and Linux, Linux won't let you execute a file that's open for writing either, and its file locking mechanism is purely "advisory". On Windows, it might be possible to "lock" a file (or part of a file) and execute it, but those locks are write locks and probably prevent the file from being executed as well. That's pretty disappointing. I thought at least on Linux you could create a file descriptor, unlink it, and still execute it, but you can't execute a file descriptor; it has to exist on disk.

Back to the drawing board... :)

Share this post


Link to post

Some years ago I had to "secure" a piece of code running in a consumer appliance type machine, where everything about the environment could be considered known and stable (unlike PC where everything changes at user's whims). It actually ran a very minimal and customized version of Debian Linux. Anyway, the boss was afraid the chinese would copy our code and then mass-produce the appliances for much cheaper, and I tried to convince him it wasn't possible to stop that but ultimately still had to do something. So I encrypted the code, modified the kernel to disable user-level (root) raw access to /dev/mem & friends, etc. and had the decrypting process check some things (like kernel, libs, etc.) to see if the environment was "sane". And also I used various anti-reverse-engineering tricks I found in books to make life harder for people who wanted to disassemble the code or run it in a debugger. But there was one attack vector I couldn't find any way to defeat: if someone ran the whole system in a VM, they would be able to step through the decrypted code, save memory dumps, and do whatever else they want...

Ultimately there's no sure way to stop someone from hacking something that's running on a machine they physically control. You can try to make things more difficult, jump through a lot of hoops and waste lots of time, but it only takes one person to hack the system and then release his exploit... Maybe some hardware DRM would make securing code easier, but then again that can also probably be defeated with the right equipment.

Share this post


Link to post

Write your program so that is bytecode is a huge prime number (like DeCSS). Then make it all self-modifying code so that on startup it will regenerate itself by computing this prime number and overwriting itself with it.

:p

Share this post


Link to post

Thats still deterministic, therefore its "merely" obfuscation in this context :p

Share this post


Link to post
hex11 said:

Ultimately there's no sure way to stop someone from hacking something that's running on a machine they physically control.

Exactly. I expect these kind of futile tactics from commercial games, and companies like Valve can get away with it because they can exact a costly price on people they catch cheating (eg. locking out their CD key so they can't play again) so it's expensive to reverse engineer. I don't expect open source games to waste their time with this nonsense, and there's basically no disincentive at all against hacking it.

Share this post


Link to post
chungy said:

Plus... scenes like this make for much entertainment :)


That whole scene was set up.

Share this post


Link to post

Sauerbraten dropped their anti-cheating code, it is now solely detect and kick the cheater out.

It will be an attraction to someone to break the protection, they cannot resist trying.

If you really feel the need to do something, then just detect impossible play by a player and notify the other players. If it keeps happening then they can kick the player out.

A few companies that tried too hard to protect their code got sued when they went too far. One installed a root kit ...

Any code from a central server can be downloaded into a sandbox or VirtualMachine or just a modified loader, where it can be copied and attacked. The tools for doing this have existed from when AppleII and DOS games came on protected boot disks, and they broke most of them within days.

Share this post


Link to post
AlexMax said:

That whole scene was set up.


"$2000 1337 machine?" :-p

More like a mid 90s throwaway beige tower pre-filled with loose junk ;-)

Only Doom Marine knows for sure, though.

Share this post


Link to post

You cannot rely upon Linux system calls for anything when dealing with a hostile remote system. Cheaters can recompile Linux to defeat any mechanism.
Cheaters can modify Linux to detect your code (by several methods), then set a flag to activate other hooks. They can then intercept the system calls to defeat anything you try. Because the system automates the defeat of your tests, 18 ms is plenty of time.

They really only have to substitute one prearranged binary for a final downloaded one. Once they have a copy of your code, they can devise a test for detecting the final download and have a the linux system call you use substitute binaries there.

Share this post


Link to post

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!


Register a new account

Sign in

Already have an account? Sign in here.


Sign In Now