Grand Chase is an online game(and one of my favourite online game in my childhood) and today I want to explain how the encrypt communication between client and server works. Before you start reading it, be sure you know at least the basic of programming and sockets(it’s essential to understand some concepts explained here), I will assume you know all of it, so let’s go.
The game is not much different from the most of MMORPG, so the first thing I thought was try to sniff the packets, one of my favourites packet sniffer (WPE Pro) didn’t work, the explanation to it is because the game uses IOCP Communication. The IOCP communication comes to solve the one-thread-per-client bottleneck, handle asynchronous I/O and send/receive, using only few processing threads(you can read more about it here). So the solution was to use wireshark, it solved the problem.
If you take a look the game packets:
We can’t do much with this, the only thing we can identify here is:
The first byte is the packet size(or the first two bytes), an 8 bytes char array, then, probably they are encrypted, you can’t understand the whole packet(only the begin), you can’t do much conclusions, take a look on the login/password packet:
Okay, as you can see, you can’t even see your login and password, so probably the packet is encrypted.
Now everything we can do is analyze the main executable, I used a private server (Grand Chase Resistance) to do this, I used as well two programs, the first one is IDA Pro and the second is Ollydbg, they are amazing to done this kind of work ;].
The first thing I did was open the client on IDA Pro. As the executable doesn’t have any kind of protection, it shows the assembly without problems, the *biggest* hint to us here is finding where the socket communication happens. IDA Pro can show us the Imports used on the program, if you take a look at address 0x00FC89C0 you will see all imports used from ws2_32.dll(Winsock2 DLL), it is a big hint to us ! now we can see where the WSASend and WSARecv is on our program, let’s go to the function which uses the WSARecv, it’s on 0x00C14890(with some debug strings, I labeled this with KSocketObject::InitRecv).
Perfect, now we have the function with WSARecv, but we need to know if it really works, the solution here is to set a breakpoint with Ollydbg, just open the client and attach the process with OllyDBG, change the module to Launcher(Alt +E) and go to the address, put a breakpoint(F2) and wait a packet and *jackpot*, we have the function which receive a packet, so let’s go do some debug on this.
First of all we need to know the arguments used on WSARecv, Microsoft has a good documentation about it, anyway, take a look at the “lpBuffers”, it is a structure used to store the buffer lenght and buffer addr, that is perfect for us, but how to discovery where the lpBuffers is stored ? let’s do some reverse engineering:
IDA says the lpBuffers is the value on “eax” register, that is used by push instruction, it happens because some arguments on x86 are passed on stack, so what we can understand with it:
The struct socket_information(that is on the stack), has an instance to WSABUF, (like socket_information->wsa_buff), so we need to confirm if it’s true, to do this, let’s use Ollydbg again. On ollydbg set a break-point on 0x00C14924, look the eax register value and follow the value on dump, if you remember how the WSABuf structure is, you won’t have problem here, the first 4 bytes on memory is the size of buffer and the next 4 bytes on memory is the the address to the buffer itself, follow this address on memory and press F7 to go step by step on ollydbg and *jackpot again*:
You can do the same thing with WSASend(look the WSABuf, follow the address and see the encrypted data on memory), so I decide to explore the WSASend instead of WSARecv, anyway, now the boring part starts, you should find who stores the value on WSABuf, I did it with the WSASend function. The function used to send data(KSocketObject::SendData @ 0x00C14E10 ) receives on his argument one structure, one ptr to a data and the size of data, they are the packet data(encrypted) and packet size, it’s stored on the structure and later send via WSASend, let’s find out who stores the values on data/size.
The function who call the KSocketObject::SendData is the socket_sendPacket@0x00C132E0, I won’t explain the whole reverse engineering I did here(it took me countless hours of debugging on OllyDBG and reading assembly on IDA Pro) but basically what this function do is:
1º Get a buffer
2º Create a KSecurity Object
3º Call the object function KSecBuff.Create who recives the buffer
4º Later, call two functions (KSec_GetSize and KSec_GetPayload), who will return the payload addr and payload size to two variables and send to KSocketObject::SendData(ObjSocket, data, size);
I confirmed it debugging with ollydbg, as you can see with this screenshot:
Well, if you have good eyes(thanks to my friend gdkchan), you will notice this buffer is compressed with zlib(deflate), the header is “0x78 0x01”, this is the buffer will be passed to the function and later used on KSecBuff.Create to be encrypted and output the final packet to be sent via WSASend(note: not necessarily all packets are compressed with zlib, only some, this example is from the packet who send the player login/passworld to server).
I made a very-fast program to decompress this buffer and here the output:
(Ignore my login lol), anyway, as you can see, here contains both login and passworld(in md5), so the first step is compress the packet buffer with zlib. After it, I followed the function KSecBuff.Create with ollydbg and I bumped on the function @ 0x00E03130, I labeled it with encrypt_packet, and finally here is our encrypt function !
The function receives in his argument a structure, a ptr to encrypt_data(it can be data either compressed with zlib or uncompressed), a output addr(the client clear the region memory with the size of packet) and the size itself. I followed the whole function and noticied some things:
The function gets 8 bytes from the buffer to be encrypted, stores on a buffer, encrypt it and store output buffer, so it works with blocks of 8 bytes.
So finally, after countless debugging and trying to understand the function, what happens is that Grand Chase uses a XOR(xorbuf) between a generated 8 bytes (it is always words, like AAAA.., BBB.., ^^^… base64 ? no idea yet) and the first 8 bytes of the buffer to be encrypted, it generated a buffer *XORed*, this buffer *XORed* is now encrypted with DES Algorithm(I figured this out looking and comparing values on the LookUp Tables inside the function, google is always your best friend when you need to find some hexadecimal values ;] and it uses an 8 bytes block) and the output is the encrypted block, it does a memcpy and store on the output buffer, the *8 bytes block generated by DES* is now used to XOR(xorbuf) with the next 8 bytes block loaded from the buffer to be encrypted.
Okay okay.. it’s a bit confuse, but I made this diagram to simplify the things:
So that is the process, where the “8 bytes block encrypted” is re-used on the “8 bytes to be xored” after the first process finish. Another observation the algorithm used by Grand Chase is that it is very similar to the Crypto++ and WolfSSL (more near to the WolfSSL to be honest, including the xorbuf function that is the same), and in the dissasembly there are some strings which are from Crypto++ Library.
So finally the last one is the decrypt algorithm, it’s the function decrypt_packet @ 0x00E03290, it receives on his arguments a structure, pointer to encrypted data, out buffer and the size. The algorithm works in the reverse form, first it will copy a block of 8 bytes from the encrypted data, decrypt it with DES Algorithm and finally do a XOR with the decrypted algorithm with 8 bytes words(AAA.. BBB..), but wait, how I will know where the hell is these bytes ? remember more early when I was explaning the packet structure, the firsts bytes is the size and there is a char array (on our example QQQQ…) yeah ! this is the first 8 bytes used to XOR the decrypted packet, after the xor, we have the *XORed* 8 bytes block and decrypted block, it now stored the block on a buffer and copy more 8 bytes encrypted and use the DES decrypt output to XOR this next block, but okay,a bit confused again, here another diagram to simplify the things:
Remember, the *8 bytes decrypted* is used in the next XOR process(in the place of 8 bytes to be XORed), so I made a program(copying some codes from WolfSSL *cof cof*, credits goes to them), and here the result:
It’s the first packet send by server to client, later, the keys are changed to the keys send by server on this packet : }
If you want to know the addresses of these functions, here the addr(it works both on IDA Pro and Ollydbg):
About the packet structure, I don’t know much how the structure works, but It is a matter to be dealt with later.
So, that is all folks, special thanks to my friends Hadley and Gdkchan !