Articles Archive
Articles Search
Director Wiki
 

Simple encryption for Shockwave games

November 9, 2000
by Jim Hayes

Shockwave makes a great platform for online games. But what happens when the game becomes truly competitive and prizes are offered for the winners? Take a look at this to find out. Cheating in online contests is quite a popular scenario these days, as is the fact that many online games can be very easily "hacked". In many cases, a little simple guesswork is all that's required to place oneself at the top of a high score board and win a prize.

I know of one competition that had some very nice consumer electronics offered as prizes. I also happened to know somebody working elsewhere on that site, and there was considerable trouble and nastiness for the game's developer when it became obvious that it was insecure. Perhaps he or his client should have looked at the security aspects, but they were overlooked. Guess whose responsibility it was judged to be? Overall, it's going to be better if you can avoid this sort of situation.

This article aims to provide some simple techniques to prevent your game being compromised quite so easily. It won't offer totally foolproof or unhackable solutions; the aim is to make it more difficult for a casual cheat. First, we'll look at an example of a simple cheat on a Shockwave game.

A Simple Cheat

Here's a simple example of a Shockwave "game" that submits the user's score to a server-side high score CGI script. The "score" in this case is randomly generated (in order to save me the effort of making a game for this article, and you the effort of playing it). This movie uses postNetText to upload the "name" and "Score" variables to the high score board.

Now, put yourself in the position of a cheat, and try to guess the name of the variables and their likely values. As the movie itself quite clearly lists "name" and "score", those would be the most obvious choices. In many cases, the server-side script that displays the high score board is the same as the one that receives get/post variables and updates it, and you can safely assume that this is the case here. The obvious hack is to try to use a simple URL equivalent to the "get" request, along these lines:

http://the_server.com/swgame/hi_score.cgi?name=dirtycheat&
score=integer_whatever_you_need_to_win

Now cheat by altering the score in the url string to an integer a little above the top score on the high score board. You can alter the name parameter as well. Press return: you are now in top position, and win the prize!

Try it here.

This cheat has actually worked. In the real world, it would work only if you used getNetText from Shockwave rather than postNetText, or were using postNetText and a server-side language that treats get and post variables the same way (without specific testing as to whether it received a post or get request). In this case, I used PHP; and variables sent via either a get or post request are globally available to the script by default. You may have to specifically check for get/post variables, in your language of choice. If you stick to postNetText in SW, and your server-side script checks and uses only "posted" variables, then you are immune from the simple "url" attack shown above.

You are not, however, immune from a simple web form that posts the information, like the one below. It just takes a very little more ingenuity from your garden-variety cheat.

name:

Score:

What can be learned from the above example?

  1. Don't use the same page to update and display your high score board. This hands the potential cheat the URL for your high score updating script on a plate, no further effort required.
  2. Use postNetText in Shockwave, and only post variables in your server-side script. At the very least a cheat will have to make a form to post the variables to your high score script.
  3. Don't make your variable names easy to guess.

A Not-So-Simple Cheat

OK, so you've followed the above advice and stopped the really easy cheats. Using a browser alone, you can't discover the URL of the high score updating script; and even if you could, the variable names are fiendishly difficult to guess. Safe? No! As in a browser, getNetText and postNetText in Shockwave use HTTP. Without going into the specifics of how to do it (but I managed it, so it's not difficult), HTTP requests can be monitored. The HTTP request on using postNetText from the example above looks a bit like this:

  {POST /cgi-bin/hiscore.cgi HTTP/1.0^M
  Accept: */*
  Content-type: application/x-www-form-urlencoded
  Accept-Encoding: gzip, deflate
  User-Agent: Mozilla/4.0 (compatible; MSIE 5.0; Windows 98; DigExt)
  Host: www.the_server.com
  Content-Length: 39
  Proxy-Connection: Keep-Alive
  Pragma: no-cache
  Cookie: ssAllowCookie=true
  name=filthydirtycheat&score=2500}

A quick scan gives all the info you need to cheat the high score board. You would now know which variables to post, and to which URL -- unless, that is, you use some method of encryption for your data. If you wish to make it more difficult to cheat, without encryption, check the score necessary to get on the high score board. If the current user's score is less than that score, don't post to the high score script, just display it. That way, a cheat must play the game well or long enough to get on the board before he gets the information he needs.

Encryption

There are two obvious routes to take here:

  1. Use SSL (https), which will encrypt your post requests for you. Director/Shockwave supports it, but you will need to enable it on the server and obtain certificates, etc. The URL you post to will be visible, and it may still be possible for a cheat to guess the variable names/values. Also, SW uses the browser's SSL layer, and does not do any encryption/interaction with the server itself. This makes the postNetText command vulnerable if your .dcr is downloaded and used in another environment. In particular, it's possible to import a .dcr as a Linked Director movie, and then insert castmember scripts that can intercept the native postNetText command and alert or otherwise output unencrypted information sent to it.
  2. Encrypt your data within Shockwave, via Lingo functions, and be able to decrypt it on your server-side script. This is the option I will expand upon in the remainder of this article. (Or you could use a combination of both the above!)

Bitwise XOR Encryption In Shockwave

This method is fairly simple to implement, both in Director and in any server-side languages you are likely to use. The downside is that it is crackable, and your only defence against that would be to make it more difficult or time-consuming to crack your scheme than would be worth the cracker's time and effort. Given this article's objective, which is to prevent casual cheating on Shockwave games, it should be sufficient. It works as follows:

--Take a character to encode, such as "e"
-- convert it to its ASCII equivalent number
asc =chartonum("e")

-- do the same with a "key" character (say, "x")
keyasc = chartonum("x")

--perform a bitwise XOR on their ASCII values, then convert the
-- resulting integer back to an ASCII character
encryptedChar = numToChar (bitXor(asc,keyasc ).

The encryptedChar is now another completely different ASCII character. In other words, it's been "encoded" with the keyasc character. If (and only if) you know the "key" character used to create encryptedChar, then you can perform the same operations on encryptedChar and the "key" character, and get back your original character (that is, "e"). Extending this to a more useful Lingo handler that handles multi-character strings, we arrive at something like this:

on encrypt strToEncrypt, keyString

  keycount = keyString.chars.count
  buffer = ""
  strToEncryptCount = strToEncrypt.chars.count

  repeat with i = 1 to strToEncryptCount
    encChar = numToChar(bitXor(charToNum(strToEncrypt.char[i]),(charToNum(keyString.char[((i-1) mod keycount)+1]))))
    buffer = buffer & encChar
  end repeat

  return buffer

end

You can test this in the message window (assuming the above handler is in a movie script):

-- Welcome to Director --
test = encrypt ("this is a test", "thisisakeystring")
put test
-- ""
put encrypt (test, "thisisakeystring")
-- "this is a test" 

Notice that in this case, the function has returned what looks like an empty string. In fact, that's not the case. Again in the message window, try:

put test.chars.count
-- 14

What is actually is that the encoded string is made of characters that Director does not display. Using a different key string may well produce some that are visible. Although the above handler will handle key strings that are shorter than the string you wish to encrypt, it's much more secure to use one that is at least as long.

Its important that you do not store your key values in Field or Text members. It's possible to extract media from protected.dxr/.dcr movies, and that includes these member types. Rather, write Lingo code in a behaviour or movie script that returns your key(s) when requested. This is compiled when you save as Shockwave, and cannot be recovered.

Here's a sample handler:

on getsinglekey()
  return "this is a key string to use when encrypting data"
end

If your casual cheat correctly guesses the data values you are sending, and that you are using XOR encryption, it's fairly easy for him/her to decode (if you only encode it once).

Say that your game displays the player's score as being 567. You encrypt that score and the resulting string is captured by the hacker. Working on the first character only, he/she would take the first encrypted character and XOR it with all the char numbers from 0 to 127, until the expected result is returned. Running through all the chars in your encrypted message in this fashion would reveal the key used. Once again, it's more secure to post variable names/values that are not easily guessed.

If you wish to increase the security of your data, then encrypt it multiple times with different key strings, something like this. This will make it more difficult for a cracker to decode your data.

on multipleEncrypt (strToEncrypt,LinearListOfkeyStrings)

  repeat with i in LinearListOfkeyStrings
    strToEncrypt = encrypt( strToEncrypt ,i)
  end repeat

  return strToEncrypt

end

You might wish, at this stage, to think about which data you want to encrypt. Encrypting just your variables' values will make it easier for the would-be cracker to decode. Sending just one encrypted string containing all your name/value pairs will prove more difficult. You'd have to delimit them with a suitable character and then split them up from the unencrypted string on the server.

Having encrypted your data, you now need to post it to the server using postNetText. Unfortunately, some of the ASCII characters returned by the encrypt handler above don't pass through this process well, effectively corrupting your data on its way to the server. I have tried urlencoding it, but I still had problems.

To overcome this, I use a function to convert those characters to their ASCII numbers and append those numbers to a comma-delimited string that will pass through post/getNetText with no problems. This string is then reassembled into the original encrypted string on the server before decryption. It would be possible to pad each number to a fixed length, or perhaps to convert to hex values, but I find a character-delimited string is easier to deal with. For encryption with a single key, it's rather wasteful to do this in a separate handler; you'd be better off modifying the crypt handler to return this string in the first place. However, for clarity, and to allow for multiple key encryption, I've kept the handlers separate. You may also realize that this uses rather more bandwidth, but in the case of Shockwave games, the amount of information sent is likely to be small enough to make this effect negligible.

on makePostString encString

  encStringCount = encString.chars.count
  buffer = ""

  repeat with i = 1 to encStringCount
    buffer = buffer & charToNum(encString.char[i]) & ","
  end repeat

  delete buffer.char[buffer.chars.count] -- delete superfluous comma at end
  return buffer

end

In the message window, it looks like this:

put makePostString(test)
-- "0,28,1,26,83,0,0,65,10,69,13,22,7,6"

Bitwise XOR Decryption On Server

Now that you've posted your encrypted data to your server script, you are faced with the task of decrypting it before you can use that information. Unless, of course, you only have to display/process your high score board within Shockwave, in which case you can just store the string and use your Lingo functions to decode it and act upon the results. Luckily, the XOR encryption above uses only functions that are commonly available in server-side languages, so it's not too difficult to rewrite the encrypt Lingo handler in whatever language you happen to use.

It is important that your chosen language uses the same character set as Director (i.e. ASCII). I wrote the equivalent of the crypt handler in Javascript, for instance, only to discover that the Javascript equivalents to charToNum and NumToChar use the unicode char set rather than ASCII. This meant the script was effectively useless (unless you were to write a handler/lookup list to convert ASCII to Unicode).

Personally, I use PHP, and I'll use this as an example. The first task is to translate the comma-separated string of numbers posted from the Shockwave movie back into the original encrypted string:

-- example PHP code
<?php
function convertDirectorString($directorstr){
$temp = explode(',',$directorstr) ; 
$buffer = '' ;
$tempcount = count($temp) ;
for($i = 0;$i <$tempcount; $i++){
// --comment: In PHP, the '.' operator 
// is the same as '&' in lingo, i.e. 
// it concatenates strings, in this context
$buffer = $buffer.chr($temp[$i]);
}
return $buffer ;
}
?>

There are other ways of doing it, but the above should be (reasonably?) readable. Experts will write their own versions. Once you have that string, it needs to be put through the PHP equivalent of the Director crypt handler (above comment applies!).

<?php
function crypt($msg,$key){ 
$keycount = strlen($key);
$msgcount = strlen($msg) ; 
$buffer = ''; 
for($i = 0;$i < $msgcount;$i++){
$charnum = ord($msg[$i]) ^ ord($key[($i % $keycount)]);
$buffer = $buffer.chr($charnum) ;
}
return $buffer ; 
}
?>

You will, of course, need to store the key string(s) on the server. Sending it with the other variables would rather defeat the object of the exercise (but is handy for testing). A sample movie is available for download in Mac or Windows format.It includes all the code mentioned above, rolled into a single behaviour that handles encryption and the postNetText process. The full text of the PHP code is also encluded in a text cast member.

Conclusion

The bitwise XOR encryption detailed above is fairly easy to implement and provides some protection against casual cheating. There are other ways to obfuscate your scores, and plenty of common sense you can apply without even doing that. (What is the maximum possible score for your game? Do you check high score submissions against the maximum score?) However, the main point I'd like to make is that you should think about your game's security if prizes are on offer, if only to clarify the point with your client or employer.

There's no satisfaction to be gained from merely cheating a high score board; it was the chance of a valuable prize that led me to sit down and work out how to do so. I was alarmed at how easy it was -- but I never did put myself in the winning position! My apologies to those gamers that I knocked from the bottom of scoreboards during the course of my research.

Copyright 1997-2017, Director Online. Article content copyright by respective authors.