Preventing cheating with trainers, memory editing or packet forgery

Previous topic - Next topic

Wampus

Just a thought I had. It is good to protect a game from being interfered with by custom made trainers, DIY trainer tools like CheatEngine or packet editors. Encoding or even encrypting critical variables when you send them across a network or store them in memory or file would be a way to do that. Doing something to prevent cheating is important if you have a game that could be ruined by it, e.g. online multiplayer games and/or a game that relies on slow accumulation of in-game items and stats.

Here is a little bit of code showing a simple example of it could be done:-

Code (glbasic) Select
GLOBAL money%, moneynorm%, ivalue%, uvalue%

WHILE KEY(01) = 0

ivalue = RND(32000) // This is the value to assign to money
uvalue = RND(32000) // This is the value to assign to moneynorm

money = ASL(bXOR(ivalue, 21845), 16)

moneynorm = uvalue

ivalue = 0
uvalue = 0

WHILE KEY (57) = 0

PRINT "Money variable decoded is "+bXOR(ASR(money, 16), 21845), 0, 00
PRINT "Encoded stored value is "+money, 0, 10

PRINT "Moneynorm variable unencoded is "+moneynorm, 0, 30
PRINT "Unencoded stored value is "+moneynorm, 0, 40

PRINT "Press space for next value assignment", 0, 60

SHOWSCREEN

WEND

WEND

END


In the above example the variable money is protected by simple bXOR encoding and a 16bit shift. The variable moneynorm is stored normally. I tested the routine using CheatEngine to "cheat" by trying to find and change the stored variables in memory. It is easy with moneynorm but very difficult with money (without knowing how the value is being encoded). Such a technique won't stop a very determined & experienced cheater but is fairly good basic protection. Its also not very expensive for CPU - its a fast way to do it. It would be possible to encrypt stored variables instead but that would be more CPU intensive and some countries have restrictions on the use of encryption.

Does anyone else use techniques like this or have other ideas about how to protect games from cheaters?

MrTAToad

Whilst networking stuff, you would use encryption.

To protect against trainers etc all, you would normally scan the process list and look for the relevant program.

You could also make it appear that you are store storing random data - so for example, instead of 3 lives, its really 27, and perhaps increase and decrease two seperate variables to calculate the right one.

You could also add in sanity checks - if for example a variable suddenly contains a value that should be invalid, then do something like end the program...

You might be interested in : http://www.gamasutra.com/view/feature/3149/how_to_hurt_the_hackers_the_scoop_.php

It flits between networking stuff and standard games, but its all relevant.

Hark0

Hmmm good!;)

Should be applicable to save game state :)

http://litiopixel.blogspot.com
litiopixel.blogspot.com - Desarrollo videojuegos Indie · Pixel-Art · Retroinformática · Electrónica Development Indie Videogames · Pixel-Art · Retrocomputing · Electronic

Wampus

Hark0 totally! If you meant save files that a game uses then they should be protected (if you're so inclined) and even if a complete snapshot of a game state is taken and examined by someone there are ways to make that unproductive for them if they're looking for ways to cheat.

MrTAToad good ideas and very informative article.

I have reservations about techniques like scanning the process list (even if it could be done with standard GLBasic commands, which I don't think it can). That might be deemed intrusive or set off alerts with some types of security software. Also, there are 100% effective ways to prevent being detected that can be used by popular cheating tools.

I thought of a robust way to protect variables from direct memory hacking tools like Cheat Engine. Its a bit of a hassle but but it works. Goes like this: At the beginning of every loop the variables to be used are loaded & decoded with a unique key from a fixed area of memory. The decoded values are assigned to a temporary storage area that will be in different locations of memory from one loop to the next so they can't be watched by a memory hacking program. The temporary storage area is also wiped at the end of the loop so as not to leave behind any clues as to what it is being used for. During the loop all calculations are performed on the temporarily stored values and then at the end of the loop they are encoded with a new key & saved back to the fixed area of memory. This makes memory hacking attempts practically pointless. To break the protection it might be quickest to reverse engineer the compiled code and that isn't all that quick.

Below is a proof of concept I wrote. It needs to be improved since there is one (minor) exploitable weakness that needs to be cleared up. There are also probably more CPU efficient ways of doing stuff like this for you C++ coders but, it works as is. :-

Code (glbasic) Select
GLOBAL money%, exp%, str%, dex%, def%, mag%, health%, mana% // Variables that hold encoded stats
GLOBAL moneys%[], exps%[], strs%[], dexs%[], defs%[], mags%[], healths%[], manas%[] // temp variables that hold unencoded stats

// Array needn't be bigger than 10
DIM moneys[10]
DIM exps[10]
DIM strs[10]
DIM dexs[10]
DIM defs[10]
DIM mags[10]
DIM healths[10]
DIM manas[10]

GLOBAL c% = 0, encode% = 0 // c = array being used for temporary space to hold true values, encode = encoding for bXOR - must be 0 at initialisation

// The variables that will storing the true values from one iteration of the main loop to another are initially given real values

money = 2147483647 // Maximum value that 32bit signed will take (on PC)
exp = -2147483648 // Minimum value that 32bit signed will take (on PC)
str = 15
dex = 18
def = 14
mag = 11
health = 4300
mana = 2100

WHILE KEY(01) = 0

// Assign the values that will be used for the main loop
// These will be the true values for the game variables

moneys[c] = bXOR(money, encode)
exps[c] = bXOR(exp, encode)
strs[c] = bXOR(str, encode)
dexs[c] = bXOR(dex, encode)
defs[c] = bXOR(def, encode)
mags[c] = bXOR(mag, encode)
healths[c] = bXOR(health, encode)
manas[c] = bXOR(mana, encode)


// BEGIN of main loop
//
// For demonstration purposes this part just prints the values in memory
// and increases str value by 1

PRINT "Temporary stat variables in changing memory locations show real game values:-",0,0

PRINT "money: "+moneys[c],0,20 // Actual value used in game
PRINT "experience: "+exps[c],0,30 // Actual value used in game
PRINT "strength: "+strs[c],0,40 // Actual value used in game
PRINT "dexterity: "+dexs[c],0,50 // Actual value used in game
PRINT "defence: "+defs[c],0,60 // Actual value used in game
PRINT "magic: "+mags[c],0,70 // Actual value used in game
PRINT "health: "+healths[c],0,80 // Actual value used in game
PRINT "mana: "+manas[c],0,90 // Actual value used in game

PRINT "How the stat variables are being stored in fixed memory:-",0,110

PRINT "money: "+money,0,130 // Value as stored in memory from one loop iteration to next
PRINT "experience: "+exp,0,140 // Value as stored in memory from one loop iteration to next
PRINT "strength: "+str,0,150 // Value as stored in memory from one loop iteration to next
PRINT "dexterity: "+dex,0,160 // Value as stored in memory from one loop iteration to next
PRINT "defence: "+def,0,170 // Value as stored in memory from one loop iteration to next
PRINT "magic: "+mag,0,180 // Value as stored in memory from one loop iteration to next
PRINT "health: "+health,0,190 // Value as stored in memory from one loop iteration to next
PRINT "mana: "+mana,0,200 // Value as stored in memory from one loop iteration to next

INC strs[c] // increase str just to do something to the numbers other than leave them static

SHOWSCREEN

// END of main loop
//
// Now we need to store the variables in memory again, but encoded
// We also need to clear the temporary registers so they can't give away any clues


// Now, after the main loop has gone once through
// Set new value to encode with bXOR
// Probably better to use something other than RND
// But this will do for demonstration purposes

encode = RND(2147483647)

// Values are encoded again and stored in memory

money = bXOR(moneys[c], encode) // value stored in memory is encoded
moneys[c] = 0 // temp variable is cleared
exp = bXOR(exps[c], encode) // value stored in memory is encoded
exps[c] = 0 // temp variable is cleared
str = bXOR(strs[c], encode) // value stored in memory is encoded
strs[c] = 0 // temp variable is cleared
dex = bXOR(dexs[c], encode) // value stored in memory is encoded
dexs[c] = 0 // temp variable is cleared
def = bXOR(defs[c], encode) // value stored in memory is encoded
defs[c] = 0 // temp variable is cleared
mag = bXOR(mags[c], encode) // value stored in memory is encoded
mags[c] = 0 // temp variable is cleared
health = bXOR(healths[c], encode) // value stored in memory is encoded
healths[c] = 0 // temp variable is cleared
mana = bXOR(manas[c], encode) // value stored in memory is encoded
manas[c] = 0

INC c // change the array that will be used for temporary variable assignment
IF c = 10 THEN c = 0 // loop the array

// done

WEND

END