nForce
28th September 2006, 08:01
The version of GameGuard for the game I've been targetting was bumped up to 834. As a result I've decided to take a look at whats changed in the ERL encryption process.
Interestingly enough they took a bit of a smarter approach this time. Indeed they still use a byte by byte encryption algorithm with a single 32bit key along with a static key table. However, this time the initial key is being encrypted with the RSA algorithm. Unfortunately this is asymetric encryption, and even though we can easily obtain the public key, we can only use it to encrypt data. The private key is required for decryption of this initial key.
Yet there is a weak point in this system. If we can obtain the initial key before it is encrypted, we can easily decrypt the current sessions ERL log file.
I have provided some example code bellow:
BOOL WINAPI HookCryptEncrypt
(
HCRYPTKEY hKey,
HCRYPTHASH hHash,
BOOL Final,
DWORD dwFlags,
BYTE* pbData,
DWORD* pdwDataLen,
DWORD dwBufLen
)
{
LogCryptData ( ( const char * ) pbData, dwBufLen );
DCryptEncrypt * OriginalFunction = ( DCryptEncrypt * ) m_hkCryptEncrypt->OriginalFunction ();
BOOL bReturnValue = OriginalFunction
(
hKey,
hHash,
Final,
dwFlags,
pbData,
pdwDataLen,
dwBufLen
);
return bReturnValue;
}
void LogCryptData ( const char * szData, unsigned long nSize )
{
if ( nSize == 64 )
{
ofstream ofsKeyFile ( _T ( "KeyFile.dat" ), ios::binary );
if ( ofsKeyFile.is_open () )
{
ofsKeyFile.write ( szData, 4 );
ofsKeyFile.close ();
}
}
}
Now that we have the key in clear text, we can simply plug it into the reverse engineered byte by byte decryption algorithm.
/************************************************** **********************************
* Project: ERL (Type3) Decryptor *
* *
* Copyright (C) 2006 by nForce *
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program; if not, write to the Free Software *
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA *
************************************************** **********************************/
#include <iostream>
#include <fstream>
using namespace std;
int main ( int nArguments, const char * szArguments [] )
{
static const unsigned long nKeyTable [] =
{
0x4E626C2E, 0x83A3A023,
0x29C9E1E8, 0x0E8E828C,
0xD2D416C6, 0x080B0B03,
0x70314171, 0x1A0A1218,
0x39497178, 0x89818809
};
if ( nArguments > 2 )
{
std::string sOutput ( szArguments [ 1 ] );
std::string sKeyFile ( szArguments [ 2 ] );
ifstream ifsTargetFile ( sOutput.c_str (), ios::binary );
ifstream ifsKeyFile ( sKeyFile.c_str (), ios::binary );
if ( ( ifsTargetFile.is_open () ) &&
( ifsKeyFile.is_open () ) )
{
sOutput.append ( ".dec" );
ofstream ofsOutputFile ( ( sOutput.c_str () ) );
if ( ofsOutputFile.is_open () )
{
unsigned long nCryptoKey;
ifsKeyFile.read ( ( ( char * ) ( & nCryptoKey ) ), 4 );
if ( ! ifsKeyFile.fail () )
{
unsigned long nKeyIndex = ( ( nCryptoKey & 0xff00ffff ) % ( sizeof ( nKeyTable ) / sizeof ( ( * nKeyTable ) ) ) );
nCryptoKey ^= nKeyTable [ nKeyIndex ];
ifsTargetFile.seekg ( 68 );
if ( ! ifsTargetFile.fail () )
{
while ( ifsTargetFile.good () )
{
nCryptoKey *= 3;
nCryptoKey = ( ( nCryptoKey ^ ( ( nCryptoKey >> ( ( ( ( nCryptoKey >> 24 ) % 17 ) + 8 ) & 0xff ) ) & 0xff ) ) + 1 );
unsigned char nEncryptedByte;
ifsTargetFile.get ( ( * ( char * ) ( & nEncryptedByte ) ) );
nEncryptedByte ^= ( ( ( char ) ( nCryptoKey & 0xff ) ) + 46 );
ofsOutputFile.put ( ( * ( char * ) ( & nEncryptedByte ) ) );
}
}
}
else
{
cout << "Key file reading failure!" << endl;
}
}
else
{
cout << "Unable to open " << sOutput.c_str () << " for output!" << endl;
}
ofsOutputFile.close ();
}
else
{
if ( ! ifsTargetFile.is_open () )
cout << "Unable to open " << sOutput.c_str () << " for input!" << endl;
if ( ! ifsKeyFile.is_open () )
cout << "Unable to open " << sKeyFile.c_str () << " for input!" << endl;
}
ifsTargetFile.close ();
ifsKeyFile.close ();
}
else
{
cout << "Syntax Error!" << endl
<< "Syntax: " << szArguments [ 0 ] << " <Erl File> <Key File>" << endl;
}
return 0;
}
Note: I leave the process of actually hooking the CryptEncrypt function exported from AdvAPI32 in GameMon to the reader.
Interestingly enough they took a bit of a smarter approach this time. Indeed they still use a byte by byte encryption algorithm with a single 32bit key along with a static key table. However, this time the initial key is being encrypted with the RSA algorithm. Unfortunately this is asymetric encryption, and even though we can easily obtain the public key, we can only use it to encrypt data. The private key is required for decryption of this initial key.
Yet there is a weak point in this system. If we can obtain the initial key before it is encrypted, we can easily decrypt the current sessions ERL log file.
I have provided some example code bellow:
BOOL WINAPI HookCryptEncrypt
(
HCRYPTKEY hKey,
HCRYPTHASH hHash,
BOOL Final,
DWORD dwFlags,
BYTE* pbData,
DWORD* pdwDataLen,
DWORD dwBufLen
)
{
LogCryptData ( ( const char * ) pbData, dwBufLen );
DCryptEncrypt * OriginalFunction = ( DCryptEncrypt * ) m_hkCryptEncrypt->OriginalFunction ();
BOOL bReturnValue = OriginalFunction
(
hKey,
hHash,
Final,
dwFlags,
pbData,
pdwDataLen,
dwBufLen
);
return bReturnValue;
}
void LogCryptData ( const char * szData, unsigned long nSize )
{
if ( nSize == 64 )
{
ofstream ofsKeyFile ( _T ( "KeyFile.dat" ), ios::binary );
if ( ofsKeyFile.is_open () )
{
ofsKeyFile.write ( szData, 4 );
ofsKeyFile.close ();
}
}
}
Now that we have the key in clear text, we can simply plug it into the reverse engineered byte by byte decryption algorithm.
/************************************************** **********************************
* Project: ERL (Type3) Decryptor *
* *
* Copyright (C) 2006 by nForce *
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program; if not, write to the Free Software *
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA *
************************************************** **********************************/
#include <iostream>
#include <fstream>
using namespace std;
int main ( int nArguments, const char * szArguments [] )
{
static const unsigned long nKeyTable [] =
{
0x4E626C2E, 0x83A3A023,
0x29C9E1E8, 0x0E8E828C,
0xD2D416C6, 0x080B0B03,
0x70314171, 0x1A0A1218,
0x39497178, 0x89818809
};
if ( nArguments > 2 )
{
std::string sOutput ( szArguments [ 1 ] );
std::string sKeyFile ( szArguments [ 2 ] );
ifstream ifsTargetFile ( sOutput.c_str (), ios::binary );
ifstream ifsKeyFile ( sKeyFile.c_str (), ios::binary );
if ( ( ifsTargetFile.is_open () ) &&
( ifsKeyFile.is_open () ) )
{
sOutput.append ( ".dec" );
ofstream ofsOutputFile ( ( sOutput.c_str () ) );
if ( ofsOutputFile.is_open () )
{
unsigned long nCryptoKey;
ifsKeyFile.read ( ( ( char * ) ( & nCryptoKey ) ), 4 );
if ( ! ifsKeyFile.fail () )
{
unsigned long nKeyIndex = ( ( nCryptoKey & 0xff00ffff ) % ( sizeof ( nKeyTable ) / sizeof ( ( * nKeyTable ) ) ) );
nCryptoKey ^= nKeyTable [ nKeyIndex ];
ifsTargetFile.seekg ( 68 );
if ( ! ifsTargetFile.fail () )
{
while ( ifsTargetFile.good () )
{
nCryptoKey *= 3;
nCryptoKey = ( ( nCryptoKey ^ ( ( nCryptoKey >> ( ( ( ( nCryptoKey >> 24 ) % 17 ) + 8 ) & 0xff ) ) & 0xff ) ) + 1 );
unsigned char nEncryptedByte;
ifsTargetFile.get ( ( * ( char * ) ( & nEncryptedByte ) ) );
nEncryptedByte ^= ( ( ( char ) ( nCryptoKey & 0xff ) ) + 46 );
ofsOutputFile.put ( ( * ( char * ) ( & nEncryptedByte ) ) );
}
}
}
else
{
cout << "Key file reading failure!" << endl;
}
}
else
{
cout << "Unable to open " << sOutput.c_str () << " for output!" << endl;
}
ofsOutputFile.close ();
}
else
{
if ( ! ifsTargetFile.is_open () )
cout << "Unable to open " << sOutput.c_str () << " for input!" << endl;
if ( ! ifsKeyFile.is_open () )
cout << "Unable to open " << sKeyFile.c_str () << " for input!" << endl;
}
ifsTargetFile.close ();
ifsKeyFile.close ();
}
else
{
cout << "Syntax Error!" << endl
<< "Syntax: " << szArguments [ 0 ] << " <Erl File> <Key File>" << endl;
}
return 0;
}
Note: I leave the process of actually hooking the CryptEncrypt function exported from AdvAPI32 in GameMon to the reader.