Welcome!

Join our community of MMO enthusiasts and game developers! By registering, you'll gain access to discussions on the latest developments in MMO server files and collaborate with like-minded individuals. Join us today and unlock the potential of MMO server development!

Join Today!

Dragon Ball Online

Newbie Spellweaver
Joined
Aug 11, 2008
Messages
39
Reaction score
0
Hey there,

A team and I have the intention of emulating the soon-to-die Dragon Ball Online from NTL. We're struggling with packet cryptography at the moment and were wondering if anyone is willing to help out. It's a fun MMO and we have a team ready to develop, we just can't get past the biggest hurdle.. the encryption.

If you're interested please by all means post here or PM me. We could do with all the engineering help we can get.

Thanks for your time.
 
Joined
Mar 11, 2007
Messages
903
Reaction score
1,252
I have the old leaked client source code to this game, let me dig it up and post it's encryption method here. Maybe it will help you in achieving your goal. How many are in your team? and what language do you plan on using to write the emulator?

DBO supports 2 different Encryption Methods, XOR and a RandomNumberKey.

Here is their XOR Implementation (Actual values may have changed over the course of time)
Code:
#define NTL_ENCRYPTION_MAGIC_PATTERN_UPPER        (0x6B)#define NTL_ENCRYPTION_MAGIC_PATTERN_LOWER        (0x31)
#define NTL_ENCRYPTION_MAGIC_PATTERN        (NTL_ENCRYPTION_MAGIC_PATTERN_UPPER << 8 | NTL_ENCRYPTION_MAGIC_PATTERN_LOWER)
//-----------------------------------------------------------------------------------






//-----------------------------------------------------------------------------------
//        Purpose    :
//        Return    :
//-----------------------------------------------------------------------------------
DWORD Encrypt(void* pvOriginalString, DWORD dwOriginalLength, void* pvBuffer, DWORD dwBufferSize)
{
#ifdef _DEVEL
    if (FALSE != IsBadReadPtr(pvOriginalString, dwOriginalLength))
        return 0;
    if (FALSE != IsBadWritePtr(pvBuffer, dwBufferSize))
        return 0;
#else
    if (NULL == pvOriginalString)
        return 0;
    if (NULL == pvBuffer)
        return 0;
#endif
    if (dwBufferSize < dwOriginalLength)
        return 0;


    DWORD dwCurrentOffset = 0;


    while (dwCurrentOffset < dwOriginalLength)
    {
        if (dwOriginalLength - dwCurrentOffset >= 2)
        {
            *(WORD*)((BYTE*)pvBuffer + dwCurrentOffset) = NTL_ENCRYPTION_MAGIC_PATTERN ^ *(WORD*)((BYTE*)pvOriginalString + dwCurrentOffset);
        }
        else
        {
            // Assumes that this routine is executed on intel CPU or its compatible CPU(little endian).
            // - YOSHIKI
            *((BYTE*)pvBuffer + dwCurrentOffset) = NTL_ENCRYPTION_MAGIC_PATTERN_LOWER ^ *((BYTE*)pvOriginalString + dwCurrentOffset);
        }
        dwCurrentOffset++;
    }


    return dwCurrentOffset;
}




//-----------------------------------------------------------------------------------
//        Purpose    :
//        Return    :
//-----------------------------------------------------------------------------------
DWORD Decrypt(void* pvOriginalString, DWORD dwOriginalLength, void* pvBuffer, DWORD dwBufferSize)
{
    return Encrypt(pvOriginalString, dwOriginalLength, pvBuffer, dwBufferSize);
}

//Implementations
int CNtlPacketEncoder_XOR::RxDecrypt(CNtlPacket& rPacket)
{
    Decrypt( rPacket.GetPacketData(), rPacket.GetPacketDataSize(), rPacket.GetPacketData(), rPacket.GetPacketDataSize() );


    return NTL_SUCCESS;
}


 
//-----------------------------------------------------------------------------------
//        Purpose    :
//        Return    :
//-----------------------------------------------------------------------------------
int CNtlPacketEncoder_XOR::TxEncrypt(CNtlPacket& rPacket)
{
    Encrypt( rPacket.GetPacketData(), rPacket.GetPacketDataSize(), rPacket.GetPacketData(), rPacket.GetPacketDataSize() );


    return NTL_SUCCESS;
}

Here is their RandomNumberKey Implementation (same thing as before actual values may have changed)

Code:
const BYTE                CHECKSUM_INITIAL_VALUE = 0x7F;



const DWORD                RX_RANDOM_SEED_HEADER = 0xFFEECCBB;


const DWORD                TX_RANDOM_SEED_HEADER = 0xBBCCEEFF;


const DWORD                RX_RANDOM_SEED_BODY = 0xAABBCCDD;


const DWORD                TX_RANDOM_SEED_BODY = 0xDDBBCCAA;




CNtlRandomGenerator        s_rxHeaderKeyGenerator( RX_RANDOM_SEED_HEADER );


CNtlRandomGenerator        s_txHeaderKeyGenerator( TX_RANDOM_SEED_HEADER );


CNtlRandomGenerator        s_rxBodyKeyGenerator( RX_RANDOM_SEED_BODY );


CNtlRandomGenerator        s_txBodyKeyGenerator( TX_RANDOM_SEED_BODY );
//-----------------------------------------------------------------------------------




//-----------------------------------------------------------------------------------
// 
//-----------------------------------------------------------------------------------
const BYTE s_checksum_Table[ ] =
{
    0,  94, 188, 226,  97,  63, 221, 131, 194, 156, 126,  32, 163, 253,  31,  65,
    157, 195,  33, 127, 252, 162,  64,  30,  95,   1, 227, 189,  62,  96, 130, 220,
    35, 125, 159, 193,  66,  28, 254, 160, 225, 191,  93,   3, 128, 222,  60,  98,
    190, 224,   2,  92, 223, 129,  99,  61, 124,  34, 192, 158,  29,  67, 161, 255,
    70,  24, 250, 164,  39, 121, 155, 197, 132, 218,  56, 102, 229, 187,  89,   7,
    219, 133, 103,  57, 186, 228,   6,  88,  25,  71, 165, 251, 120,  38, 196, 154,
    101,  59, 217, 135,   4,  90, 184, 230, 167, 249,  27,  69, 198, 152, 122,  36,
    248, 166,  68,  26, 153, 199,  37, 123,  58, 100, 134, 216,  91,   5, 231, 185,
    140, 210,  48, 110, 237, 179,  81,  15,  78,  16, 242, 172,  47, 113, 147, 205,
    17,  79, 173, 243, 112,  46, 204, 146, 211, 141, 111,  49, 178, 236,  14,  80,
    175, 241,  19,  77, 206, 144, 114,  44, 109,  51, 209, 143,  12,  82, 176, 238,
    50, 108, 142, 208,  83,  13, 239, 177, 240, 174,  76,  18, 145, 207,  45, 115,
    202, 148, 118,  40, 171, 245,  23,  73,   8,  86, 180, 234, 105,  55, 213, 139,
    87,   9, 235, 181,  54, 104, 138, 212, 149, 203,  41, 119, 244, 170,  72,  22,
    233, 183,  85,  11, 136, 214,  52, 106,  43, 117, 151, 201,  74,  20, 246, 168,
    116,  42, 200, 150,  21,  75, 169, 247, 182, 232,  10,  84, 215, 137, 107,  53
} ;
//-----------------------------------------------------------------------------------






//-----------------------------------------------------------------------------------
//        Purpose    :
//        Return    : 8bit crc value
//-----------------------------------------------------------------------------------
int Encrypt(BYTE * pPlain, int nLength, DWORD dwCipherKey, BYTE * pbyCheckSum = NULL)
{
    if ( NULL == pPlain )
    {
        return 0;
    }




    if( pbyCheckSum )
    {
        *pbyCheckSum = CHECKSUM_INITIAL_VALUE;
    }




#ifdef __PACKET_ENCODE_OPTIMIZE__


    int nRound = nLength / sizeof(DWORD);
    DWORD * pdwRoundPlain = (DWORD*) pPlain;
    for( int i = 0; i < nRound; i++ )
    {
        if( pbyCheckSum )
        {
            *pbyCheckSum = s_checksum_Table[ *pbyCheckSum ^ (BYTE) pdwRoundPlain[ i ] ];
        }


        pdwRoundPlain[ i ] ^= (DWORD) dwCipherKey;
    }




    int nRest = nLength % sizeof(DWORD);
    BYTE * pbyRestPlain = (BYTE*) ( pdwRoundPlain + nRound );
    for( int i = 0; i < nRest; i++ )
    {
        if( pbyCheckSum )
        {
            *pbyCheckSum = s_checksum_Table[ *pbyCheckSum ^ pbyRestPlain[ i ] ] ;
        }


        pbyRestPlain[ i ] ^= (BYTE) dwCipherKey;
    }
#else
    for( int i = 0; i < nLength; i++ )
    {
        if( pbyCheckSum )
        {
            *pbyCheckSum = s_checksum_Table[ *pbyCheckSum ^ pPlain[ i ] ] ;
        }


        pPlain[ i ] ^= (BYTE) dwCipherKey;
    }
#endif




    return nLength;
}






//-----------------------------------------------------------------------------------
//        Purpose    :
//        Return    :
//-----------------------------------------------------------------------------------
int Decrypt(BYTE * pPlain, int nLength, DWORD dwCipherKey, BYTE * pbyCheckSum = NULL)
{
    if ( NULL == pPlain )
    {
        return 0;
    }




    if( pbyCheckSum )
    {
        *pbyCheckSum = CHECKSUM_INITIAL_VALUE;
    }




#ifdef __PACKET_ENCODE_OPTIMIZE__
    int nRound = nLength / sizeof(DWORD);
    DWORD * pdwRoundPlain = (DWORD*) pPlain;
    for( int i = 0; i < nRound; i++ )
    {
        pdwRoundPlain[ i ] ^= (DWORD) dwCipherKey;


        if( pbyCheckSum )
        {
            *pbyCheckSum = s_checksum_Table[ *pbyCheckSum ^ (BYTE) pdwRoundPlain[ i ] ];
        }
    }




    int nRest = nLength % sizeof(DWORD);
    BYTE * pbyRestPlain = (BYTE*) ( pdwRoundPlain + nRound );
    for( int i = 0; i < nRest; i++ )
    {
        pbyRestPlain[ i ] ^= (BYTE) dwCipherKey;


        if( pbyCheckSum )
        {
            *pbyCheckSum = s_checksum_Table[ *pbyCheckSum ^ pbyRestPlain[ i ] ] ;
        }
    }
#else
    for( int i = 0; i < nLength; i++ )
    {
        pPlain[ i ] ^= (BYTE) dwCipherKey;


        if( pbyCheckSum )
        {
            *pbyCheckSum = s_checksum_Table[ *pbyCheckSum ^ pPlain[ i ] ] ;
        }
    }
#endif


    return nLength;
}

CNtlPacketEncoder_RandKey::CNtlPacketEncoder_RandKey(bool bSeedSwap)
{
    if( bSeedSwap )
    {
        m_rxHeaderKeyGenerator = s_txHeaderKeyGenerator;


        m_txHeaderKeyGenerator = s_rxHeaderKeyGenerator;


        m_rxBodyKeyGenerator = s_txBodyKeyGenerator;


        m_txBodyKeyGenerator = s_rxBodyKeyGenerator;    
    }
    else
    {
        m_rxHeaderKeyGenerator = s_rxHeaderKeyGenerator;


        m_txHeaderKeyGenerator = s_txHeaderKeyGenerator;


        m_rxBodyKeyGenerator = s_rxBodyKeyGenerator;


        m_txBodyKeyGenerator = s_txBodyKeyGenerator;    
    }
}

int CNtlPacketEncoder_RandKey::RxDecrypt(void * pvPacketHeader)
{
    PACKETHEADER* pPacketHeader = (PACKETHEADER*)pvPacketHeader;


    if( pPacketHeader->bEncrypt )
    {
        Decrypt( (BYTE*) pPacketHeader, PACKET_HEADSIZE, m_rxHeaderKeyGenerator.Generate() );
        pPacketHeader->bEncrypt = false;
    }




    return NTL_SUCCESS;
}




//-----------------------------------------------------------------------------------
//        Purpose    :
//        Return    :
//-----------------------------------------------------------------------------------
int CNtlPacketEncoder_RandKey::TxEncrypt(void * pvPacketHeader)
{
    PACKETHEADER* pPacketHeader = (PACKETHEADER*)pvPacketHeader;


    if( false == pPacketHeader->bEncrypt )
    {
        Encrypt( (BYTE*) pPacketHeader, PACKET_HEADSIZE, m_txHeaderKeyGenerator.Generate() );
        pPacketHeader->bEncrypt = true;
    }


    return NTL_SUCCESS;
}

Their RandomNumberGenerator
Code:
void CNtlRandomGenerator::GenerateSeeds()
{
    int n;
    unsigned int msk, bit;


    m_nIndex1 = 0;
    m_nIndex2 = 103;


    for ( n = TABLE_SIZE - 1; n >= 0; n-- )
        m_table[n] = GenerateSimple();


    for ( n = 3, msk = 0xffffffff, bit = 0x80000000; bit; n += 7 )
    {
        m_table[n] = (m_table[n] & msk) | bit;
        msk >>= 1;
        bit >>= 1;
    }
}

unsigned int CNtlRandomGenerator::GenerateSimple()
{
    /* IL_Shift32Random from Inner Loops book */
    unsigned int n, bit, temp;
    temp = m_nState;


    for (n=0; n < 32; n++)
    {
        bit = ((temp >> 0) ^ (temp >> 1) ^ (temp >> 2) ^ (temp >> 3) ^ (temp >> 5) ^ (temp >> 7)) & 1;
        temp = (((temp >> 1) | (temp << 31)) & ~1) | bit;
    }


    m_nState = temp;


    return m_nState;
}

unsigned int CNtlRandomGenerator::Generate()
{
    unsigned int retval = (m_table[m_nIndex1] ^= m_table[m_nIndex2]); 


    m_nIndex1++;
    if (m_nIndex1 == TABLE_SIZE)
        m_nIndex1 = 0;


    m_nIndex2++;
    if (m_nIndex2 == TABLE_SIZE)
        m_nIndex2 = 0;




    return retval;
}
 
Last edited:
RaGEZONER || Webdevloper
Banned
Joined
Oct 6, 2011
Messages
614
Reaction score
130
What are your plans? This project open-source?
 
Newbie Spellweaver
Joined
Aug 11, 2008
Messages
39
Reaction score
0
My intention would be to make it open source, I don't know about the rest of the team we have.

cmb, if you mean the 2009 KR client source, we have already delved through it. We're surmising that the encryption methods there are not the same that are used in the most recent clients, but we're still not sure. It's a difficult one.

Thanks for your replies!
 
Joined
Mar 11, 2007
Messages
903
Reaction score
1,252
My intention would be to make it open source, I don't know about the rest of the team we have.

cmb, if you mean the 2009 KR client source, we have already delved through it. We're surmising that the encryption methods there are not the same that are used in the most recent clients, but we're still not sure. It's a difficult one.

Thanks for your replies!

Yes I did mean the 2009 KR client source. The actual encryption methods probably have not changed (the algos that is) but probably just key values have changed.
 
Newbie Spellweaver
Joined
Aug 11, 2008
Messages
39
Reaction score
0
Okay, cool. We're fairly certain they're using the RandKey algorithm because of indications in that source. Little luck getting anywhere with the packets we're currently receiving from the new clients though; it sends a seemingly random packet body each time, whereas this source implies that the same keys are used for each invocation of the encryption method.
 
Joined
Mar 11, 2007
Messages
903
Reaction score
1,252
Okay, cool. We're fairly certain they're using the RandKey algorithm because of indications in that source. Little luck getting anywhere with the packets we're currently receiving from the new clients though; it sends a seemingly random packet body each time, whereas this source implies that the same keys are used for each invocation of the encryption method.

It actually doesnt use the same key everytime as shown by
Code:
m_rxHeaderKeyGenerator.Generate()

and the implementation
Code:
unsigned int CNtlRandomGenerator::Generate()
{
    unsigned int retval = (m_table[m_nIndex1] ^= m_table[m_nIndex2]); 




    m_nIndex1++;
    if (m_nIndex1 == TABLE_SIZE)
        m_nIndex1 = 0;




    m_nIndex2++;
    if (m_nIndex2 == TABLE_SIZE)
        m_nIndex2 = 0;








    return retval;
}

Inspecting the Generate() function, we can notice 2 things. 1) They use the table with 2 indexes, seemingly 1 number away from each other, XOR'ing the two values together and putting it inside the retval. 2) those two indexes are increased each time the Generate() function is called, so everytime it Decrypts or Encrypts it increases the indexes so you would get seemingly random packet bodies each time.
 
Newbie Spellweaver
Joined
Aug 11, 2008
Messages
39
Reaction score
0
Ah yeah, but I was talking about these, I meant the same initialising key in this version of the client, sorry:

Code:
const DWORD				RX_RANDOM_SEED_HEADER = 0xFFEECCBB;

const DWORD				TX_RANDOM_SEED_HEADER = 0xBBCCEEFF;

const DWORD				RX_RANDOM_SEED_BODY = 0xAABBCCDD;

const DWORD				TX_RANDOM_SEED_BODY = 0xDDBBCCAA;


CNtlRandomGenerator		s_rxHeaderKeyGenerator( RX_RANDOM_SEED_HEADER );

CNtlRandomGenerator		s_txHeaderKeyGenerator( TX_RANDOM_SEED_HEADER );

CNtlRandomGenerator		s_rxBodyKeyGenerator( RX_RANDOM_SEED_BODY );

CNtlRandomGenerator		s_txBodyKeyGenerator( TX_RANDOM_SEED_BODY );

The "randomgenerator" then repeatedly creates pseudorandom ints that repeat every 250 iterations. I have reimplemented this in Javascript and tested against the C++ source to make sure. That's the reason I think this source is old.

When I connect to the current Taiwan server I immediately see a three-packet exchange between server and client that apparently isn't described anywhere in this old source, and it's different every time. :(

Edit: This is assuming a unique instantiation of CNtlRandomGenerator is used for each connection, but that's where I might be thinking incorrectly. If so, how the hell is the client meant to know how to decrypt what it receives?
 
Back
Top