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 . Tools used: WPE Pro 0.9a, OllyDbg 1.10 and MASM 8.2
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:
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... |
No comments:
Post a Comment