Packet Hacking and Reversing HASH1 in mmo Water Margin
This article covers methods of packet deciphering and emulating raw packets and only covers reversing hash one for the sent packet data. We won’t reveal every bit of it, but hopefully cover enough. This tutorial assumes a bit of assembly knowledge and knowing how to debug and understanding some debugging terms will help as well.
Many MMO’s nowadays are taking drastic measures to protect their game, their databases and to protect their players. I for one am not against this and I’m not the type of person to destroy their games up out of pure evil. Its for knowledge and practice. and fun…With that said, what you do with the information revealed in this article is not my responsibility..
When embarking on a mission to decipher games packets there are few things to expect. For one, failure. and lots of it…Second. time. and LOTS of it, and skill…lots of it would be nice. Understanding assembly is a must. Because depending on how tough the game encrypts its data. you will be tracing quite a bit of code.
Now, there’s no one way to find out how a games packets are to be deciphered, so please don’t take this article and try to use the same techniques for all other games. You probably won’t get far. There are many obstacles that may get in the way, such as anti-hack protections that use their own encryptions on the games protocol (XTrap, Hackshield, Nprotect), and in some cases where an attack on the server is the only way to manipulate the games important data…
Water Margin in this case takes none of those into consideration, just simple protocol encryption between client and server using crc32 tables and hashes.
I’m sure by now you’ve heard of a tool called WPE Pro. It’s a middle man program which injects a dll into a preferred process and attempts to hook some known winsock functions, the hook simply transfers any raw packet data used by the send/WSASend/recv/WSArecv API’s and puts it into a nice little format for us to use. we can then do what we please with the data, edit it, send it back. modify incoming data and edit them on the fly. Now you may think this is effective. But in our case, not much, only when a game uses no kind of encryption is wpe totally useful. When working with encrypted data, it can only help us identify that the game uses encryption. Now, how do we know if we’re working with encrypted packets (I will be referring to packets that expire or timestamped as well as other terms as ‘encrypted’ for simplicity)?
If you have Water Margin, open it up and target it with WPE Pro. The easiest but not best way to work with packets is by seeing what you get from the chat packets.(Although some games don’t encrypt their chat packets, it’s a great starting point to trace code from) Understand that packets are only just hex data being sent to the server from the client. In WPE I like to filter the options for the current data I’m analyzing. For now we want to log the sent packets so in wpe go to VIEW -> Option. Under Winsock 1.1 tick only Send. And under Winsock 2.0 tick .., the buffer size can remain where it is. Mine is at 5000 (we will probably never log that much at once).
Ok. Now that Water Margin is running, you’re in the server, wherever you may be it doesn’t matter right now. In WPE start recording by pressing on the ‘PLAY’ looking button. In the game type the letter ‘A’. Now Type the letter ‘B’, then type the word ‘ABC’. Ok, that should suffice for now. Let us take a look at what we got.
For the letter ‘A’ I got:
AA 0F 00 00 00 09 00 00 00 52 CB F9 D3 78 9C 13 17 60 66 00 02 47 06 00 01 EF 00 6C
For the letter ‘B’ I got:
AA 0F 00 00 00 09 00 00 00 5B 39 F0 39 78 9C 13 17 60 66 00 02 27 06 00 01 F1 00 6D
And for ‘ABC’ I got:
AA 11 00 00 00 0B 00 00 00 B3 C5 AD AA 78 9C 13 17 60 65 00 02 47 27 67 06 00 04 25 00 F3
NOTE: When working with packets, you will usually notice that the first one or two hex data’s are a ‘packet signature’, this will usually tell you what kind of packet you are working with, signifies ‘type of packet’. The other will more than likely be the size of the packet data. Then comes some miscellaneous data. Whether it be the encrypted data. Some hash, timestamp, etc. it will probably also end with some sort of ‘packet footer’ which signifies end of packet data. This is not ALWAYS the case, so be cautious and do your research.
Now, if you’re a thinker you would probably have noticed that our packets won’t be the same at every point. Now if we try and recognize what these packets are we will be wasting a lot of time. How do we know we’re working with encrypted data? Take a look at the dump in WPE…notice anything strange? Yes. if we typed in A, why isn’t it in the raw dump? Encryption is the only answer to that question.
Ok so now that we did a small recording, its safe to say that WPE’s job is done. yep, that’s right. its of no use to us. You can go ahead and re-send the packet data for the chat. you will successfully send it again J , you’re probably saying why the hell then are we trying to decipher them… well, take a look at your movement packets and try resending those Our mission in this paper is to show you how to chase packet encryption and nevertheless. The chat packets are being encrypted. But they are not expired till the next execution of the game. This game uses what we call a session key. This is when you login to the game and the server sends a unique key that will be the basis of the encryption of the packets for that game session. Let’s prove this theory, close water margin and save the packet data from wpe. Now restart Water margin and try sending the previous sessions packets, it won’t work . Unfortunately they were at least smart enough to not make the same mistake for movement packets. And more important things . But that’s for another day to explain.
Anyways, now what do we do next? We go fishing for some raw data J Think about it, you are typing strings, when you press enter; the data you write has to be held somewhere to be encrypted correct? Well then. Let’s see if we can find this. on the way to that we will find other things like encryption routines and such. But before we begin to fish, let’s cheat a little, since we know that we are dealing with some sort of encryption; let us see if it’s using any kind of public encryption. We will use PEID along with the KANAL plug-in to do this. If I’m not mistaken PEID comes with this plug-in equipped, if not I will have it attached along with this paper. So after using the Krypto Analzyer plug-in here is what we get.
And the unfiltered references.
Ok, now this here could mean anything, it could mean that the game uses crc32 to calculate some other checksum, but there are quite a few references to the crc table. Let’s try and remember these just in case we stumble upon them later. We give special attention to the first reference “Referenced at 005651D3”
Tracing the Code
Ok, we’re now at the part where we are going to try and find some sign of raw data, before its encrypted state. This is where your own intuition comes in, to make educated guesses as to what routine we should trace, at the beginning you’ll find yourself tracing through a lot of code, and it gets boring. So the first thing we do is make sure we have a debugger handy. OllyDebugger, in my case.
So now Run the game, and attach olly to it. Our goal now is to back-trace from the Winsock Send() function, and hopefully find some interesting routines to work with.
Once we successfully have done that, we type “bp send” in the olly command bar.
Now in Water Margin type anything…We quickly break inside the call to send().
We see this in our local stack window in olly. For now this information isn’t important. But just to explain the “Data” argument holds the packet data we see in WPE Pro, this of course being the already encrypted data. ( If you want to verify this. Record with WPE, and then while having a breakpoint set on send in olly, send some data. Olly will break, right click the data parameter and go to “Follow in Dump”, then press F9 to continue execution, the same data in that dump will be the packets you send out in WPE.)
Ok now in the local stack window, right click the line “Call to send from 108Online.xxxxxxxx” Press “Follow in Dissassembler”. The CPU window changes to this.
CALL NEAR EDI was the call to the send function. Now the send function lies inside of one of the games own procedures. many things go on before the initial data is sent, lets find the beginning of this function.
Scrolled up and we find this prologue routine.
Not your average prologue, but who cares. Now I took a careful look at the function and tried to pick out some suspicious calls or suspicious code, being a bit familiar with this type of work my eyes for some odd reason looked at the first call. I pressed F9 to let the game continue and set a breakpoint on the call at address 00584ADA. After setting the breakpoint, I once again typed random gibberish in the game, the breakpoint was hit. I immediately take a look at my stack.
My first inference was. hmm nothing interesting yet, I followed the dump at ECX. But nothing seemed interesting at the time.
So now I followed this first Call, pressing F7 in olly. We have this very small but very interesting routine J.
005845D0 8B41 04 /MOV EAX, DWORD PTR DS:[ECX+4]
005845D3 8B40 08 |MOV EAX, DWORD PTR DS:[EAX+8]
005845D6 0341 08 ADD EAX, DWORD PTR DS:[ECX+8]
005845D9 C3 RETN
Following it line by line, let’s take a look at what we get. First instruction put the DWORD value pointed to by ECX+4 into EAX. My EAX now held 02165C10
Next line took the DWORD Value pointed to by EAX+8 and put it into EAX. Now EAX held 0933BE00. So I followed this address in the dump window. and to my surprise look at what I got.
Can you guess what I typed in? Yep. “fff” was what I typed in the game. Quickly I knew that this was some place where our original data was being held. I highlighted it all the way to the ‘00’, I assumed that it would end with a Null Terminator. So from here I decided to test how it would go if I changed this data at this point to something else.
I highlighted the text “fff” in the dump window, right clicked it and went to Binary -> Edit. Keeping the size intact I wrote another sequence of three letters.
I changed it to YYY, then I continued execution of the game. Here’s what I got J.
Yup, Now we could stop here and do this all the time, but come on, we want to be better than that. now I thought. hmm. this data was being accessed from some place, but this is not where we actually see the data being created, another good alternative is to find somewhere ‘only’ the data is being passed as an argument to a procedure, this way we can hook it and send data ourselves.
Another inference was that the data we got earlier in that function where our raw data was, is some kind of structure…Why would we think that you ask? Well, lets try it out, lets move ingame instead of write something and see how the dump changes. After moving and breaking on that call, I got this in my dumped data.
Isn’t it nice how olly tells us in red what data has changed? So looking here we can make a few educated guesses. In our chat dump the first byte was 17, now its 19. hmmm, the third byte in our chat packet was some number other than 10, depending on what you typed right?, lets just for sanity’s sake record one more chat packet, this time I typed in 4 letters ( ‘gggg’). My dump looked similar to the first except one part. The third byte, three letters was 05, now 4 letters is 06. hmmm, could this third byte represent the size of data? so I also took a look at the actual data. Our previous chat data was 3 letters, meaning 3 bytes, but remember it ended with a null terminator, so 4 bytes. I took another look and realized that for some odd reason, the packet data must also be preceded with an 00, why? Well who cares, there could be no other explanation for it. So the third byte is presumed to be the size of the real data. And data is preceded with and ends with Null terminators, or for clarity 0’s. And the First WORD is the type of packet.
Retrieving Hashes
Ok since we made a few inferences about how the packet structure is formed we really need a suitable spot to test this. After some RE work and help with a Curse-x Fellow member, we found a very interesting function that used a pointer to the raw data as well as return a hash using it. This is the hash we will emulate in this paper. We decided it was a suitable place to make a hook to and send our own chat data’s to retrieve its hash. But first I’ll show you the function. The location we settled with was 005655F1. In Olly the function looked like this.
Seeing the function the first thing I wanted to see was what it would return, looking a bit below you see that the value of EAX was being moved into EBX+30. Right now it doesn’t matter what EBX+30 is, so setting a breakpoint here I wrote gibberish ingame and pressed enter, it broke here(005655F1). When it broke EAX held some value, I then Stepped OVER the function in olly with F8. Immediately the value of EAX in the registers window was red and another value greeted me, a strange value, that’s when I decided that this function returns some value using the data being passed to it. It returns a hash, let’s get this hash eh.
At first sight we can see that it pushes 3 arguments onto the stack. Let’s find out what it’s pushing, So what I did was break several times in the function and analyzed the arguments each time. I take a look at my Local Stack in Olly. ( The local stack will show you the values that are being push’d or pop’d onto/from the stack.) I break several times and this was my local stack values.
Since it pushes 3 values I look at the first 3 values currently on the stack, then I followed the second parameter in Olly’s Dump. I got this:
The address to our raw packet structure. Being sufficed with that, the first parameter always seemed to be 1. The last parameter was the size of the packet structure in bytes.
Ok so we found a function that uses our raw packet structure only to perform some actions, for this paper it doesn’t really matter what it uses the data for ( although a very important function overall ). So now that we’ve gotten the packet structure reversed ( by pure EDUCATED GUESSES EH and some RE skills) lets see if we can code something to test this. Lets code a bit.
Coding Hash-Getter.
Ok, up to this part we’ve done a lot of reversing and guess work, but we can’t really prove that our assumptions are correct yet about the packet structure.
Since we are working with chat packets only here let’s put some of our information together. We know that the first two bytes represent a packet ID, identifying the action being taken, all of our chat packets thus far have started with 17 10. Knowing how memory works and the intel’s reversed byte structure (Little Endian) This is represented as 0x1017 in C++ language. The next 4 bytes are the size of the data. Remember a 0 is appended to the beginning and also ends with a null terminator. Then comes the data.
So we have our Packet Structure defined.
struct Packet {
short packetID;
int dataSize;
char data[50];
};
The maximum data size later on after stepping into that function we had earlier came out to be 5552 (15b0h). We can change it to that if we want, but it doesn’t matter as long as it doesn’t exceed this limit. Now all that’s needed is a simple Edit box to retrieve some text from, we can then fill in the packet structure according to this. The meat of the code lies In here:
int SendInfo1(Packet *p, char *data)
{
int rett;//returned hash
p->PacketID = 0x1017;//chat ID
strcpy(p->data+1, (data)-1);//copy the data in the data element +1 ( remember the prepended 00)
p->data[0] = 0; //the 0 before the data.
p->datasize = 2 + strlen(p->data); // the size of the data is the length of the actual data plus the prepended 0.
Packet *pp = p;
int sizeofPacket = sizeof(short)+sizeof(int)+ p->datasize; // PacketID + PacketSize + data
unsigned int func = 0x0056D445;
__asm{
push sizeofPacket
push pp
push 1
call func
mov rett, eax //this is just that hash we mentioned, not doing anything with it now ;)
}
return rett;
}
With this function utilized, rett will hold the hash created by the function located in 0056D445. To lazy reversers this would be enough to stop. But the point of reversing a games packets is to to be able to re-create them ourselves, We will now go back to Reversing and reverse this hash function and try and create our own hashes for our packets.
Reversing Hash Function.
Ok. Remember where the function was that we wanted correct. Indeed it was, lets use IDA along with Olly to reverse this and code our own version of it. An Ollydebug strip of the code looks like this:
108Onlin_0056D445: ;<= Procedure Start
PUSH EBP
MOV EBP, ESP
MOV ECX, [ARG.2]; ECX holds pointer to raw data
PUSH ESI
PUSH EDI
MOV EDI, [ARG.1] ;Holds key. (always 1 :/)
MOV ESI, EDI ; temporary ‘key’ holder (first param
AND ESI, 0xFFFF ; AND’s the temp key with 0xFFFF(65535)
SHR EDI, 0x10 ; right shifts argument 1 with 0x10(16)
TEST ECX, ECX ; checks if there is any data to work with
JNZ 108Onlin_0056D467 ; does check for size parameter if there is.
XOR EAX, EAX ; if not
INC EAX ; returns 1
JMP 108Onlin_0056D55A ; exit
108Onlin_0056D467:
CMP [ARG.3], 0 ; size if zero or below?
JBE 108Onlin_0056D553 ; jmp
PUSH EBX ; ebx is pointer to original data
108Onlin_0056D472: //Checks size limit
MOV EDX, 0x15B0
CMP [ARG.3], EDX
JNB 108Onlin_0056D47F; If the size is larger than 5552, truncate it
MOV EDX, [ARG.3]
108Onlin_0056D47F: //
SUB [ARG.3], EDX ; subtracts size from itself.
CMP EDX, 0x10 ; jump is the datas size is lower than 16.
JL 108Onlin_0056D522 ; takes the jump for our chats
MOV EAX, EDX
SHR EAX, 4
MOV EBX, EAX
NEG EBX
SHL EBX, 4
ADD EDX, EBX
108Onlin_0056D499: // this routine is NOT very important after some tracing.
MOVZX EBX, BYTE PTR DS:[ECX]
ADD ESI, EBX
MOVZX EBX, BYTE PTR DS:[ECX+1]
ADD EDI, ESI
ADD ESI, EBX
MOVZX EBX, BYTE PTR DS:[ECX+2]
ADD EDI, ESI
ADD ESI, EBX
MOVZX EBX, BYTE PTR DS:[ECX+3]
ADD EDI, ESI
ADD ESI, EBX
MOVZX EBX, BYTE PTR DS:[ECX+4]
ADD EDI, ESI
ADD ESI, EBX
MOVZX EBX, BYTE PTR DS:[ECX+5]
ADD EDI, ESI
ADD ESI, EBX
MOVZX EBX, BYTE PTR DS:[ECX+6]
ADD EDI, ESI
ADD ESI, EBX
MOVZX EBX, BYTE PTR DS:[ECX+7]
ADD EDI, ESI
ADD ESI, EBX
MOVZX EBX, BYTE PTR DS:[ECX+8]
ADD EDI, ESI
ADD ESI, EBX
MOVZX EBX, BYTE PTR DS:[ECX+9]
ADD EDI, ESI
ADD ESI, EBX
MOVZX EBX, BYTE PTR DS]
ADD EDI, ESI
ADD ESI, EBX
MOVZX EBX, BYTE PTR DS:[ECX+0xB]
ADD EDI, ESI
ADD ESI, EBX
MOVZX EBX, BYTE PTR DS]
ADD EDI, ESI
ADD ESI, EBX
MOVZX EBX, BYTE PTR DS:[ECX+0xD]
ADD EDI, ESI
ADD ESI, EBX
MOVZX EBX, BYTE PTR DS]
ADD EDI, ESI
ADD ESI, EBX
MOVZX EBX, BYTE PTR DS:[ECX+0xF]
ADD EDI, ESI
ADD ESI, EBX
ADD EDI, ESI
ADD ECX, 0x10
DEC EAX
JNZ 108Onlin_0056D499
108Onlin_0056D522:
TEST EDX, EDX ; another check to see if it was 0 (size of data)
JE 108Onlin_0056D531 ; if it was 0 jump.This eventually means that no data will be sent in send()
108Onlin_0056D526: // this little routine here adds up the byte data in our packet struct.
MOVZX EAX, BYTE PTR DS:[ECX] ; ECX is our packet structs data.
ADD ESI, EAX ; ESI is the result of our bytes being added.
INC ECX ; move to next byte
ADD EDI, ESI ; add this byte to EDI
DEC EDX ; EDX is size of packet, decreases till end.
JNZ 108Onlin_0056D526 ; loop
108Onlin_0056D531:
MOV EAX, ESI ; the end result is put into EAX
XOR EDX, EDX ; zeroes out EDX
MOV EBX, 0xFFF1 ;
MOV ESI, EBX ;
DIV ESI
MOV EAX, EDI ; EDI held small ‘checksum’ of our data being added.
MOV ESI, EDX ;EDX == the end result ESI Held.
XOR EDX, EDX ;Zeroes out EDX
DIV EBX ; EBX == EDI.
CMP [ARG.3], 0 ; checks third argument, remember it was zeroed out.
MOV EDI, EDX ; EDI = EDX
JA 108Onlin_0056D472 ; no jump.
POP EBX ;
108Onlin_0056D553:
MOV EAX, EDI ; EAX = EAX << 16 || ESI
SHL EAX, 0x10;
OR EAX, ESI;
108Onlin_0056D55A:
POP EDI
POP ESI
POP EBP
RETN ;<= Procedure End
This output was used with Olly plugin “CodeRipper” , thanks to whoever wrote it . Ok so now that we had figured out the routine. the real meat was really where it started to move the data “MOVZX EAX, BYTE PTR DS:[ECX]”. So here’s the C++ implementation of this function, removing most of the junk not needed to create the hash.
unsigned int __cdecl getHashB(int key, void *data,unsigned int size)
{
unsigned char *pData = (unsigned char *)data; //save data in temp as to not corrupt it
unsigned int sum = key & 65535; // temp being the key AND's with 0xFFFF
key >>= 16; //shr 0X10
if(pData == 0) {
return 1;
} else {
if(size > 0) { // truncating if size is larger than limit
if(size > 5552)
size = 5552;
for (int i=0;i < size;i++) { //
sum += ( pData[i] & 255);
key += sum;
}
}
return (key << 16) | sum;
/* MOV EAX, EDI ; EAX = EAX << 16 || ESI
SHL EAX, 0x10;
OR EAX, ESI; */
}
}
With that completed we had to test if our hash function returned the same hash as the games’. So I added a few more additions to the Hash Test Program to see this comparison. And the results are?
And this Modified SendInfo Function, called SendInfo2.
int SendInfo2(Packet *p, char *pdata)
{
int rett;//returned hash
p->PacketID = 0x1017;//chat ID
strcpy(p->data+1, (pdata)-1);//copy the data in the data element +1 ( remember the prepended 00)
p->data[0] = 0; //the 0 before the data.
p->datasize = 2 + strlen(p->data); // the size of the data is the length of the actual data plus the prepended 0.
Packet *pp = p;
int sizeofPacket = sizeof(short)+sizeof(int)+ p->datasize; // PacketID + PacketSize + data
int i = 1; // arg 1 == 1.
rett = getHashB(i, pp, sizeofPacket);
return rett;
}
Well we’ve successfully reversed a hash returning function, lets congratulate ourselves.
written by g3nuin3 & hunter
Download full source code and PEID plugin