Thread: Finding Opcodes
View Single Post
  #35  
Old 07-24-2008, 06:21 AM
Derision
Developer
 
Join Date: Feb 2004
Location: UK
Posts: 1,540
Default

Here is a quick and dirty tutorial on how I found the OP_Charm opcode for Titanium, based on information in the Wiki on
disassembly using IDA.

Requirements: The 6.2 Client, The Titanium Client, and an OpCode that is known for the 6.2 client
but unknown in Titanium (this is what we want to find).

Download IDA Freeware 4.9 from: http://www.hex-rays.com/idapro/idadown.htm

Fire up IDA, Select 'New', Select PE Executable, then navigate to your 6.2 eqgame.exe, then let IDA
analyse it. This will take several minutes. It will say 'Idle' in the status bar when it has finished.

In 6.2, Opcodes are handled in the 'Dispatch' routine at address 45A8B6.

To Jump to this address, press G and enter the address 45A8B6.

The routine looks like this ( I added the comments down near the bottom):

Code:
.text:0045A8B6
.text:0045A8B6 sub_45A8B6      proc near               ; CODE XREF: sub_407D2B+27p
.text:0045A8B6
.text:0045A8B6 var_5980        = dword ptr -5980h
.text:0045A8B6 var_597C        = dword ptr -597Ch
.text:0045A8B6 var_5978        = dword ptr -5978h
.text:0045A8B6 var_5974        = dword ptr -5974h
.text:0045A8B6 var_5970        = dword ptr -5970h
.text:0045A8B6 var_596C        = dword ptr -596Ch
.text:0045A8B6 var_5968        = dword ptr -5968h
.text:0045A8B6 var_5964        = dword ptr -5964h
.text:0045A8B6
.text:0045A8B6                 mov     eax, offset loc_5F3AC7
.text:0045A8BB                 call    __EH_prolog
.text:0045A8C0                 mov     eax, 5954h
.text:0045A8C5                 call    __alloca_probe
.text:0045A8CA                 mov     eax, dword_75D3D4
.text:0045A8CF                 mov     edx, [ebp+0Ch]
.text:0045A8D2                 push    ebx
.text:0045A8D3                 push    esi
.text:0045A8D4                 mov     esi, [ebp+10h]
.text:0045A8D7                 mov     [ebp-10h], eax
.text:0045A8DA                 push    edi             ; char
.text:0045A8DB                 mov     eax, 4665h
.text:0045A8E0                 xor     edi, edi
.text:0045A8E2                 cmp     edx, eax
.text:0045A8E4                 mov     [ebp-58B8h], ecx
.text:0045A8EA                 mov     ecx, 5ACh
.text:0045A8EF                 mov     ebx, offset byte_95E7B8
.text:0045A8F4                 ja      loc_45C52C
.text:0045A8FA                 jz      loc_45DA84
.text:0045A900                 mov     eax, 2089h     ; The Opcode of the packet being processed is in register edx
.text:0045A905                 cmp     edx, eax       
.text:0045A907                 ja      loc_45B969     ; If the Opcode > 0x2089, jump to 45B969
.text:0045A90D                 jz      loc_45B95E
.text:0045A913                 mov     eax, 1336h
.text:0045A918                 cmp     edx, eax
.text:0045A91A                 ja      loc_45ADB6     ; If the Opcode > 0x1336, jump to 45ADB6
.text:0045A920                 jz      loc_45BA1B
.text:0045A926                 mov     eax, 0A2Ah
.text:0045A92B                 cmp     edx, eax
.text:0045A92D                 ja      loc_45AB94     ; If the Opcode > 0x0A2A, jump to 45AB94
.text:0045A933                 jz      loc_45AB89
Starting at address 45A900, is where it is checking which routine to jump to, based on the Opcode.

We know that for 6.2, OP_Charm is 0x10A1, so the first test that will pass is, is opcode > 0x0A2A, and
we jump to loc_45AB94.

Move the cursor over the loc_45AB94 in the ja instruction, right click, and select jump to operand, which
takes us to 45AB94:

Code:
.text:0045AB94 loc_45AB94:                             ; CODE XREF: sub_45A8B6+77j
.text:0045AB94                 mov     eax, 0F40h
.text:0045AB99                 cmp     edx, eax
.text:0045AB9B                 ja      loc_45ACAC
.text:0045ABA1                 jz      loc_45DB94
There are some more comparisons. In this case, our opcode 0x10A1 is greater than 0x0F40, so we now jump to
loc_45ACAC. (Jump to operand as before).

Code:
.text:0045ACAC loc_45ACAC:                             ; CODE XREF: sub_45A8B6+2E5j
.text:0045ACAC                 mov     eax, edx
.text:0045ACAE                 sub     eax, 0F66h
.text:0045ACB3                 jz      loc_45AD6E
.text:0045ACB9                 sub     eax, 9Eh
.text:0045ACBE                 jz      loc_45AD63
.text:0045ACC4                 sub     eax, 9Dh
.text:0045ACC9                 jz      loc_45AD58      ; Jump to Charm Routine
.text:0045ACCF                 sub     eax, 5
.text:0045ACD2                 jz      short loc_45AD4A
.text:0045ACD4                 sub     eax, 4Eh
.text:0045ACD7                 jz      loc_45DE25
What it does now is start subtracting values from the opcode, then jumping to a routine if the result is 0.
0x10A1 - F66 - 9E - 9D = 0, so we jump to 45AD58.

Code:
.text:0045AD58 loc_45AD58:                             ; CODE XREF: sub_45A8B6+413j
.text:0045AD58                 push    esi
.text:0045AD59                 call    sub_457270
.text:0045AD5E                 jmp     loc_45B3A0
This just takes us to 457270:

Code:
.text:00457270 sub_457270      proc near               ; CODE XREF: sub_45A8B6+4A3p
.text:00457270
.text:00457270 arg_0           = dword ptr  10h
.text:00457270
.text:00457270                 push    ebx
.text:00457271                 push    esi
.text:00457272                 push    edi
.text:00457273                 mov     edi, [esp+arg_0]
.text:00457277                 push    dword ptr [edi]
.text:00457279                 call    sub_402DD2
.text:0045727E                 push    dword ptr [edi+4]
.text:00457281                 mov     esi, eax
.text:00457283                 call    sub_402DD2
.text:00457288                 test    esi, esi
.text:0045728A                 pop     ecx
.text:0045728B                 pop     ecx
.text:0045728C                 mov     ebx, eax
.text:0045728E                 jz      short loc_4572EC
.text:00457290                 test    ebx, ebx
.text:00457292                 jz      short loc_4572EC
.text:00457294                 cmp     byte ptr [ebx+1B3h], 0
.text:0045729B                 jnz     short loc_4572EC
This is where it seems to start doing some processing, so we guess this is the guts of the charm routine.

Now we need to find the same routine in Titanium. Close IDA and start it up again, this time selecting the
Titanium eqgame.exe. Let it do it's thing for a few minutes.

When IDA is done processing, our goal is to find a routine in the Titanium client that looks the same, or very
similar to the one above. In this case, what I did was search for the 'immediate' value, 0x1B3.

Press Alt-I, and enter 0x1b3 in the search box. After a few seconds, a list of matches will be displayed. What
we are after is one that is:

Code:
cmp     byte ptr [ebx+1B3h], 0
to match the instruction in the 6.2 client. In the list of occurences of 0x1B3 in IDA, the second match is the one we are
after. Double click on it and it will take you to the routine. Scroll up a bit and it looks like this:

Code:
.text:00453D7B sub_453D7B      proc near               ; CODE XREF: sub_45B8F0+D1Dp
.text:00453D7B
.text:00453D7B arg_0           = dword ptr  10h
.text:00453D7B
.text:00453D7B                 push    ebx
.text:00453D7C                 push    esi
.text:00453D7D                 push    edi
.text:00453D7E                 mov     edi, [esp+arg_0]
.text:00453D82                 push    dword ptr [edi]
.text:00453D84                 call    sub_401B28
.text:00453D89                 push    dword ptr [edi+4]
.text:00453D8C                 mov     esi, eax
.text:00453D8E                 call    sub_401B28
.text:00453D93                 test    esi, esi
.text:00453D95                 pop     ecx
.text:00453D96                 pop     ecx
.text:00453D97                 mov     ebx, eax
.text:00453D99                 jz      short loc_453DF7
.text:00453D9B                 test    ebx, ebx
.text:00453D9D                 jz      short loc_453DF7
.text:00453D9F                 cmp     byte ptr [ebx+1B3h], 0
As you can see, this looks like the right routine, as it is identical in all ways that matter to the 6.2 code.

Now we need to work our way backwards. Click on the 'Sub_453D7B' which precedes 'proc near', and then press
the X key.

This will produce a list of locations that this subroutine is called from. There is only one place, so double
click it, and it takes us to:

Code:
.text:0045C60C loc_45C60C:                             ; CODE XREF: sub_45B8F0+BD0j
.text:0045C60C                 push    ebx
.text:0045C60D                 call    sub_453D7B
.text:0045C612                 jmp     loc_45F673
Nothing interesting here, so click on loc_45C60C, press X. This will produce a list of where 45C60C is called
from (just the one location). Double click on it in the result dialog box.

This looks more interesting.

Code:
.text:0045C4BA loc_45C4BA:                             ; CODE XREF: sub_45B8F0+B6Fj
.text:0045C4BA                 sub     ecx, 12E5h
.text:0045C4C0                 jz      loc_45C60C
.text:0045C4C6                 sub     ecx, 21h
.text:0045C4C9                 jz      short loc_45C517
.text:0045C4CB                 sub     ecx, 171h
.text:0045C4D1                 jnz     loc_45F674
Here the code is subtracting 12E5 from ecx (which is the opcode), and following a jump which leads to the
charm routine if the result is zero.

We could continue moving back through the call tree to find if there are any other subtractions being done
on the opcode value, but in this case there isn't, 0x12e5 is the opcode for OP_Charm in Titanium.
Reply With Quote