Menu

GMKrypt

Hugh Greene

GMKrypt is an encryption/obfuscation method developed for Game Maker 7,
and was used to encrypt GM7 GMKs, EXEs, DATs,
and GEXs
.

The main aspect of the encryption is a #Substitution
Cipher
generated by a
#Key. The key may be hidden in
#Junk data, and underneath the cipher, the data may
also be #Additively encrypted. GMKs use all of
these together, while GEXs and DATs only use the key and cipher.

Key

The key is a 32-bit integer usually stored next to the data which is
used to generate a cipher. When generating a key, GM typically uses the
formula random(25600) + 3328, resulting in a range of [3328,28928),
although GM is perfectly fine reading in keys that are out of that
range, so user-generated keys (e.g. those produced by
LateralGM) are perfectly fine.

It is worth noting that all of the even keys (0, 2, 3328, etc) produce
poor ciphers, where every lookup either maps to itself or the next
closest character (so 'c' will map to 'b', 'c', or 'd'). In fact, every
250 keys, starting at key 248 (248, 498, 748... 3498, 3748... 28498,
29748 -- a total of 102 in-range keys) is an identity key, wherein every
lookup maps to itself, so the data would essentially remain unencrypted,
barring other encryption methods of course (although this is not
indicative that every 250th table is identical. For instance, tables 249
and 499 are different). Despite this, GM still uses even keys and
identity keys, provided they are in the aforementioned range. LateralGM
uses key 248.

Since the key is somewhat sensitive information to just leave out in the
open, it is sometimes surrounded by Junk data, especially in GMKs. In
GEXs and DATs, it is not.

Junk

In order to hide the key, GMK surrounds it with Junk data, so that the
casual hex editor would be looking for a needle in a haystack (or in
this case, a key in a junkyard). This method does significantly increase
filesize (anywhere from 1424 to 25416 extra bytes), which is probably
one of the reasons GMKrypt was not used in subsequent versions of the GM
format.

For those keen to guessing, the junk data itself is just a bunch of
32-bit integers, each generated using the formula random(3000). This
means the junk data remains in the range of [0,3000). This means the
maximum value of GM-generated junk is 2999, while the minimum value of a
GM-generated key is 3328. To keep with the needle-haystack analogy, this
would be like if the needle were painted red.

For those who are sane, the junkyard is prefixed by 2 pieces of data,
which I affectionately call Bill and Fred, which determine the number of
junk integers before and after the key, respectively. Using our
documentation guidelines, explained in Format
info
, we can document the junkyard as such:

...(Header information)...
Bill
Fred
<bill>
{
Junk
}
Key
<fred>
{
Junk
}
...(Encrypted data)...</fred></bill>

In GM, Bill and Fred are generated by the formula random(3000) + N
where N = 123 for Bill, and N = 231 for Fred.

Please notice that Fred and Fred's junk are not encrypted, even though
they follow the key.

Also note, for your confusion, in GMKs, the first byte of useful data
after the junkyard is not encrypted, but is left as-is. The
remainder of the data thereafter (including the remaining 3 bytes of the
first longint field) is, though.

Additive

The additive component of the encryption is a very simple additive
combiner stream cipher
where the key is simply the position in the file. For those who don't
know cryptography terminology, that means, for every byte, we return the
byte plus its position in the file, modulo 256 if necessary (to keep it
as a byte). In C, this is written as:

fputc(fgetc(in) + ftell(in),out);
  • fputc writes a character (first argument) to a stream (second
    argument)
  • fgetc reads a character from a stream
  • ftell returns the current position in a stream

Notice that, if a modulo 256 is necessary in your language (e.g.
typeless languages), consider using the more efficient bitwise and 255:
(val & 0xFF) or (val & 255)

A recommended alternative, for languages where a call to ftell is
expensive or unavailable, is to simply keep track of the stream position
in your own variable (and since we're only using it to combine with
another byte, we could store it in an unsigned byte and let overflow
roll back to 0).

 unsigned char pos = (unsigned char) ftell(in);
 size_t len = fread(buf,BUFSIZ,sizeof(unsigned char),in);
 for (int i = 0; i < len; i++) {
  buf[i] = (unsigned char) (buf[i] + pos + i)
 fwrite(buf,len,sizeof(unsigned char),out);

Decrypt

To reverse this, simply subtract the file position, instead of adding,
and then roll the value back into the byte range (& 0xFF can be used
again, if necessary).

Substitution Cipher

The rest of this still needs documenting. If you would like to help,
please first gain an understanding of the encryption, fully documented
here: http://lateralgm.org/formats/gmkrypt1.html and then try to
reword it in a way that is more wiki-friendly (since the document can be
kinda confusing).

Putting it together

  • Encrypt: Add, then cipher
  • Decrypt: Reverse Cipher, then Subtract

Category:Formats


Related

Wiki: Category:Formats
Wiki: Format_info
Wiki: GEX_format
Wiki: GM_format
Wiki: LateralGM
Wiki: User:IsmAvatar

MongoDB Logo MongoDB