Tuesday, July 18, 2006

World of Game Hacking - Breaking the packet encrypting in Tibia 7.72

World of Game Hacking - Breaking the packet encrypting in Tibia 7.72


Breaking the packet encrypting in Tibia 7.72 PDF Print E-mail
Written by Arne Banan
Friday, 16 June 2006

Discuss this article on the forums. (5 posts)

This tutorial will show you how to use Tibia's own encryption routines from an injected
DLL, this will require som reversing and assembly coding, if you have not done any of
that before this tutorial probably isn't for you but I'll try to explain things as best I
can.This tutorial will not cover the packet format, mainly because I don't know it.
Neither will I include what encryption routine it uses or how to code it, if you want to
make an .EXE you can just rip the encryption routine and include it as inline asm in
your program...
This is the first tutorial I have EVER written, so bare with me Embarassed.

Tools used: WPE Pro 0.9a, OllyDbg 1.10 and MASM 8.2

stretch

First, let's see how these packets looks like, so hook up Tibia with WPE and start
capturing. I wrote some random letters in the chat and this is what I got:

wpe

The first two bytes are, quite clearly, the size of the data. But there are no letters in
the rest which means that it's probably encrypted, which further means that we got
some reversing to do.

Now, what would be the easiest way to find the encryption routine? Let's take a look
at the send()'s prototype from MSDN:

int send(
SOCKET s,
const char* buf,
int len,
int flags
);

Tibia will surely write to that buffer during or shortly after the encryption routine, let's
put a breakpoint on it.

Start Olly, open tibia.exe from Olly and log in, the first packet sent by Tibia is
unencrypted and probably exchanges the key so we should not break here. When
you're logged in open up the command plugin (it can be found at www.ollydbg.de)
and write "bp send" which puts a breakpoint on the first instruction of send() in
ws2_32.dll. Write something in the chat and watch Olly break. Watch the stack and
you'll see Olly's neatly formatted arguments:

0012E5BC 0052F255 /CALL to send from Tibia.0052F24F
0012E5C0 0000010C |Socket = 10C
0012E5C4 00719540 |Data = Tibia.00719540
0012E5C8 00000012 |DataSize = 12 (18.)
0012E5CC 00000000 \Flags = 0

So, the send data is at 00719540? Then let's check it, this is what I got:

00719540 10 00 EA D6 02 D5 6B 58 29 44 88 E5 25 48 ED DE .êÖ ÕkX)Dˆå%HíÞ
00719550 23 26 #&

Yours is probably different. These are the first 18 bytes, since that was all it's going to
send (DataSize = 12 (18.). We can see the unencrypted first two bytes and then the
rest of the packet. Strange though, I wrote something shorter when I captured the
packet with WPE but the size is still 10 in hex. It still makes sense that the first two
bytes is the size because all the bytes after the first two are 16 (10 in hex). So what
does this mean? It could be that it is some kind of padding to make all the packets a
certain length or that the encryption routine needs a certain length, it's something we
have to find out. But now, let's put a breakpoint on memory access. Write something
more in the chat and it breaks!

0050B5AC |. 881438 MOV BYTE PTR DS:[EAX+EDI],DL
0050B5AF |. 8B06 MOV EAX,DWORD PTR DS:[ESI]
0050B5B1 |. 8B56 08 MOV EDX,DWORD PTR DS:[ESI+8]
0050B5B4 |. 886C10 01 MOV BYTE PTR DS:[EAX+EDX+1],CH
0050B5B8 |. 8B46 08 MOV EAX,DWORD PTR DS:[ESI+8]
0050B5BB |. 8B8C24 D000000>MOV ECX,DWORD PTR SS:[ESP+D0]
0050B5C2 |. 83C0 02 ADD EAX,2
0050B5C5 |. 8946 08 MOV DWORD PTR DS:[ESI+8],EAX
0050B5C8 |. 5F POP EDI
0050B5C9 |. 5E POP ESI
0050B5CA |. 64:890D 000000>MOV DWORD PTR FS:[0],ECX
0050B5D1 |. 81C4 D4000000 ADD ESP,0D4
0050B5D7 \. C2 0400 RETN 4

Hmm... There's nothing at 00719540 yet and if you scroll up we can't see any
mathematical computations or anything of the like, which often shows that it's a
encryption routine, but it might be in one of those calls up there. Let's go back and
check there later if we don't find anything by running again.

00508CD0 /$ 8B4424 04 MOV EAX,DWORD PTR SS:[ESP+4] ; MOVs the first argument into EAX. The first argument happens to be 00719542, that's right after the packet size!
00508CD4 |. 8B08 MOV ECX,DWORD PTR DS:[EAX] ; MOVs four bytes from EAX (00719542) into ECX.
00508CD6 |. 8B50 04 MOV EDX,DWORD PTR DS:[EAX+4] ; MOVs four bytes from EAX+4 (00719546) into EDX.
00508CD9 |. 53 PUSH EBX
00508CDA |. 55 PUSH EBP
00508CDB |. 56 PUSH ESI ; Save som registers on the stack.
00508CDC |. 8B7424 14 MOV ESI,DWORD PTR SS:[ESP+14] ; MOVs the second argument into ESI.
00508CE0 |. 57 PUSH EDI ; Save EDI too...
00508CE1 |. 33C0 XOR EAX,EAX ; Nulls EAX
00508CE3 |. BF 08000000 MOV EDI,8
00508CE8 |> 8BDA /MOV EBX,EDX ; Start of a big loop full of XORs, ADDs, SUBs, SHRs and SHLs, probably the encryption.
; After the loop.
00508DEF |. 8B4424 14 MOV EAX,DWORD PTR SS:[ESP+14] ; MOVs the first argument into EAX, again.
00508DF3 |. 5F POP EDI
00508DF4 |. 5E POP ESI
00508DF5 |. 5D POP EBP ; POPs the registers off the stack.
00508DF6 |. 8908 MOV DWORD PTR DS:[EAX],ECX ; MOVs ECX to the first four bytes in EAX (00719542).
00508DF8 |. 8950 04 MOV DWORD PTR DS:[EAX+4],EDX ; MOVs EDX to the fours bytes at EAX+4 (00719546)
00508DFB |. 5B POP EBX ; POP EBX too...
00508DFC \. C3 RETN ; Return to the calling function.

void 00508CD0(char*,char*);

Well, my friend, I think we just stumbled upon the encryption routine! This function
takes the eight bytes from the first argument and does some long computations, some
using the second argument, on them an then puts them back. You can see that the
first eight bytes are now completely different than from before, it seems that the
encryption routine only encrypts eight bytes at a time which explains the packet
lengths. This is not a normal calling convention and the arguments are only MOVed
from the stack, not removed from it so the calling function must remove the
arguments from the stack after the call. If we want to use this function we need to
have a buffer with the data to be encrypted and the second argument which seems
to be some sort of key. Let's see in more detail what the second argument is by
following the RETN back to the calling function.

00508F60 /$ 8B4424 04 MOV EAX,DWORD PTR SS:[ESP+4] ; MOVs the first argument into EAX.
00508F64 |. 85C0 TEST EAX,EAX ; Checks if the argument is null.
00508F66 |. 74 0D JE SHORT Tibia.00508F75 ; If it is, return.
00508F68 |. 83C1 04 ADD ECX,4 ; Else add ECX with 4.
00508F6B |. 51 PUSH ECX ; PUSHes the second argument, the key, to the encryption function.
00508F6C |. 50 PUSH EAX ; PUSHes the first argument to the encryption function.
00508F6D |. E8 5EFDFFFF CALL Tibia.00508CD0 ; CALLs the encryption function.
00508F72 |. 83C4 08 ADD ESP,8 ; Removes the arguments from the stack by adding 8 to ESP.
00508F75 \> C2 0400 RETN 4 ; Returns to the calling function.

This didn't make the whereas of the key any clearer. But it shows that we have to
manually remove the arguments from the stack after calling the encryption function.
The first argument here is the buffer to be encrypted and ECX should contain the
address of the key - 4. You can use this function instead of the on at 00508CD4 if you
want to. Let's go to the calling function and check how ECX get it's value:

004CBEB0 > 8D4F 02 LEA ECX,DWORD PTR DS:[EDI+2]
004CBEB3 . 3BF1 CMP ESI,ECX
004CBEB5 . 0F8D B0000000 JGE Tibia.004CBF6B
004CBEBB . 8D96 40957100 LEA EDX,DWORD PTR DS:[ESI+719540] ; The address to the bytes to be encrypted are stored in EDX.
004CBEC1 . 52 PUSH EDX ; PUSHes the first argument.
004CBEC2 . B9 749D7100 MOV ECX,Tibia.00719D74 ; 00719D74 + 4 is the address to the key.
004CBEC7 . E8 94D00300 CALL Tibia.00508F60 ; CALLs the function before the encryption routine.
004CBECC . 83C6 08 ADD ESI,8 ; ADDs 8 to ESI to encrypt the next 8 bytes, or end the loop.
004CBECF .^EB DF JMP SHORT Tibia.004CBEB0 ; Continue looping.

We land in the middle of a long function, in a short loop. The first three instructions are
used to see if the necessary bytes are encrypted and it's time to end the loop. The
address to the key is hardcoded at 00719D74 and is increased by 4 in the function it
calls which makes the address to the key 00719D78.


Now we have all the information needed to encrypt a packet, the decryption routine
can be found the same way but by putting a brakpoint at recv and then a memory
breakpoint on that buffer. The decryption function is located at 00508E00 and uses
the same arguments as the encryption function at 00508CD0, the first argument
should be a pointer to the buffer and the second a pointer to the key. The functions
en/decrypts 8 bytes every function call so we need to add 8 to the pointer and call it
again until the whole buffer is encrypted. They don't remove the arguments from the
stack which means we have to ADD 8 to ESP after every a CALL or else we'll mess the
stack up.
We also want to try if our functions work by sending a packet to the server, but we
need to find the address to the socket for that. The address is DMA though, so put a
breakpoint on the send function using bp send again and make it brake. Press Alt + F9
to get back to tibia.exe and step through the RETN.

0052F240 . FF7424 0C PUSH DWORD PTR SS:[ESP+C] ; /Flags
0052F244 . FF7424 0C PUSH DWORD PTR SS:[ESP+C] ; |DataSize
0052F248 . FF7424 0C PUSH DWORD PTR SS:[ESP+C] ; |Data
0052F24C . FF71 04 PUSH DWORD PTR DS:[ECX+4] ; |Socket
0052F24F . FF15 0CB65500 CALL DWORD PTR DS:[<&WS2_32.#19>] ; \send
0052F255 C2 0C00 RETN 0C

And you get here:

004CBFBA . 8B0D 509D7100 MOV ECX,DWORD PTR DS:[719D50] ; MOVs the first four bytes from [00719D50] into ECX.
004CBFC0 . 8B11 MOV EDX,DWORD PTR DS:[ECX] ; MOVs the first four bytes from [ECX] to EDX.
004CBFC2 . 6A 00 PUSH 0 ; PUSHes the last argument to [EDX+18], which is also the last argument used in the call to send.
004CBFC4 . 83C6 02 ADD ESI,2 ; ESI is the size of the packet to send minus the first two bytes which are added here.
004CBFC7 . 56 PUSH ESI ; PUSHes the third argument to [EDX+18] and also to send.
004CBFC8 . 68 42957100 PUSH Tibia.00719542 ; PUSHes the data to be sent.
004CBFCD . FF52 18 CALL DWORD PTR DS:[EDX+18] ; CALLs the function before send, which resolves the socket from [ECX+4]

We should mimic this code when we want to send our packets, or just take the socket
from [ECX+4].

Now we can start coding our DLL, it will be really short, simple and just send a packet
making you say: "It works...". Here it is:

.486
.model flat, stdcall
option casemap :none

include \masm32\include\windows.inc
include \masm32\include\user32.inc
include \masm32\include\kernel32.inc
include \masm32\macros\macros.asm

includelib \masm32\lib\user32.lib
includelib \masm32\lib\kernel32.lib ; The usually includes and options...

.data
packetsize db 18h,00h ; The packet header with the size.
packet db 0Fh,00h,96h,01h,0Bh,00h,49h,74h,20h,77h,6Fh,72h,6Bh,73h,2Eh,2Eh,2Eh ; The main packet, you can easily find those by breaking at the encryption routine.
padding db 00,00,00,00,00,00,00 ; Padding to make our packet 24 bytes and thus encryptable if we didn't do this we would overwrite our code, I think...

.code
DllEntry proc hInst:DWORD, reason:DWORD, unused:DWORD ; The usually arguments passed to DllEntry...
.if reason == DLL_PROCESS_ATTACH ; Check if we're just getting attached using the .if macro.
push 00719D78h ; PUSH the address to the key.
push offset packet ; PUSH the first 8 bytes to be encrypted.
mov edi, 00508CD0h ; Load the functions address into EDI.
call edi ; CALL the encryption function.
add DWORD PTR[esp], 8 ; ADDs 8 bytes to the first argument to the encryption function.
call edi ; And CALL it again using the same arguments.
add DWORD PTR[esp], 8 ; ADDs 8 bytes to the first argument again.
call edi ; And CALLs it.
add esp, 8 ; Clear the stack of the arguments.
push 0 ; The last arguments to the send function.
push 1Ah ; The total size to send.
push offset packetsize ; The address to the buffer to send.
mov eax, DWORD PTR[00719D50h]
mov ecx, DWORD PTR[eax]
mov eax, DWORD PTR[ecx] ; Finds the address to the socket and the function to call.
call DWORD PTR[eax+18h] ; CALL the function used to send the packet.
.endif
ret ; Return.
DllEntry endp
end DllEntry

I think the comments are quite self explainable. Use a DLL injector to inject it, there is
one in the tools section by Max_Power. A simple DLL injection tool isn't very hard to
code though, I think you can get quite alot of info on it using Google.

Thanks goes too:
g3nuin3 & hunter for their great tutorial on breaking the packet enryption in Water
Margin, that's what got me going on breaking the Tibia packet enryption.
And all the members of WOGH!

That wasn't a lot of thanking... Undecided

>

No comments:

Post a Comment