My first guess to locate the Packet Distributor involved finding all code references to the Packet Table. But the client changed too much over the years for me to developed a reliable technique. Therefor I decided to brute-force the whole thing and analyze the jump tables. (The official word is Branch Table but I prefer Jump Table, if you don't know what that is, read here : http://en.wikipedia.org/wiki/Branch_table)
IDA Pro put comments where it finds a jump table thus by scanning the whole EXE, storing the addresses IDA commented and then printing the more interesting ones, we can limit the search for the Packet Distributor. This turned out to be a good method, as it worked for Pre-Alpha up to the latest client I tried (7.0.2.x). This is the IDAPython script I used, it will first calculate the average size of the jumptables in the EXE and then print out the ones who are twice above the average.
- Code: Select all
ly = []
for f in AllFunctions():
for x in FuncItems(f):
if Comment(x) != None and "switch" in Comment(x) and "cases" in Comment(x):
y = Comment(x)[:-6][7:]
ly.append(int(y))
avg = sum(ly) / len(ly)
print "Average = " + str(avg)
for f in AllFunctions():
for x in FuncItems(f):
if Comment(x) != None and "switch" in Comment(x) and "cases" in Comment(x):
y = Comment(x)[:-6][7:]
if int(y) > avg*2:
print Hex(x) + ": " + Comment(x) + "-->" + y
Let's look at some of the code found:
- Code: Select all
1.23 138 cases + 0x11 = 155 (158 packets in client)
====
mov cl, [eax]
mov [ebp+var14], ecx
mov edx, [ebp+var14]
sub edx, 0x11
mov [ebp+var14], edx
; switch 138 cases
- Code: Select all
1.23.37b & 1.25 147 cases + 0x11 = 164 (165 packets in client)
====
mov al, [edi]
add eax, 0xFFFFFFEF -> sub eax, 0x11
; switch 147 cases
- Code: Select all
1.25.35 169 cases + 0x11 = 186 (186 packets in client)
====
mov al, [esi]
add eax, 0xFFFFFFEF -> sub eax, 0x11
; switch 169 cases
- Code: Select all
5.0.8.3 213 cases + 0x0B = 224
=======
mov dl, [esi]
mov [esp+xxx], dl
mov ebx, [esp+xxx]
and ebx, 0xFF
lea eax, [ebx-0x0B]
; switch 213 cases
- Code: Select all
7.0.0.4 233 cases + 0x0B = 244
=======
movzx edi, byte ptr [esi]
lea eax, [edi-0x0B]
; switch 233 cases
Depending on the client the assembler code looks different but overall it does the same thing.
- 1. Extract the byte from the packet
2. Modify the byte by removing the first X unknown packets
3. Convert the byte to an address and jump (not shown above)
Now, the above script will fly-by the demo & pre-alpha jump tables, for the demo there are 4 tables in the EXE, 3 of them are server side, they are:
- Pre-Login Packets
Post-Login, No God Rights
Post-Login, God Rights
- Code: Select all
def IsDemoServerSidePreLoginPacketDistributor(ea):
return ea == 0x47ED2E and GetOperandValue(ea, 1) == 0x91
def IsDemoServerSideNonGodPacketDistributor(ea):
return ea == 0x49D1B3 and GetOperandValue(ea, 1) == 0xAF
def IsDemoServerSideGodPacketDistributor(ea):
return ea == 0x49D5D7 and GetOperandValue(ea, 1) == 0x93
def IsDemoServerSidePostLoginPacketDistributor(ea):
return IsDemoServerSideNonGodPacketDistributor(ea) or IsDemoServerSideGodPacketDistributor(ea)
def IsDemoServerSidePacketDistributor(ea):
return IsDemoServerSidePreLoginPacketDistributor(ea) or IsDemoServerSidePostLoginPacketDistributor(ea)
def IsPreAlphaPacketDistributor(ea):
return ea == 0x4085AF and GetOperandValue(ea, 1) == 0xA2
The actual code that will locate the possible Packet Distributors is this one:
- Code: Select all
def LocatePossiblePacketDistributors():
l = []
# Scan all functions for an instructions that matches a predefined switch/case pattern
for f in AllFunctions():
for i in FuncItems(f):
c = Comment(i)
if c and "switch" in c and "cases" in c:
#x = int(c[:-6][7:]) # Extract the number from the comment
p = PrevHead(i, f)
ok = False
if IsDemoServerSidePacketDistributor(i) or IsPreAlphaPacketDistributor(i):
ok = True
elif GetMnem(p) == "mov":
o = GetOpnd(p, 0)
p = PrevHead(p, f)
if GetMnem(p) == "sub" and (GetOperandValue(p, 1) == 0x0E or GetOperandValue(p, 1) == 0x11):
ok = True
elif GetMnem(p) == "add" and (GetOperandValue(p, 1) == 0xFFFFFFEF or GetOperandValue(p, 1) == 0xFFFFFFF2):
ok = True
elif GetMnem(p) == "lea" and (GetOperandValue(p, 1) == 0xFFFFFFEF or GetOperandValue(p, 1) == 0xFFFFFFF5):
ok = True
if ok:
l.append(i)
return l
For the oldest clients this script will return 1 address, for most clients 2 addresses should be returned (both within the same function). For the Demo 4 addresses will be returned.
Therefor the locate the real client-side table I've created the following script:
- Code: Select all
def LocateClientPacketDistributor(l=None): # l = list of possible addresses
if l == None:
l = LocatePossiblePacketDistributors()
if not l:
return "Error! Unable to locate the Packet Distributor"
if len(l) >= 2:
# If the Demo Server Side is in the list, than we can safely remove it
m = map(IsDemoServerSidePacketDistributor, l)
for i in range(m.count(True)):
ea = l.pop(m.index(True))
if len(l) == 2:
if GetFunctionStart(l[0]) == GetFunctionStart(l[1]):
# Assume the first one is not correct, so remove it
l.pop(0)
if len(l) != 1:
return "Error! Unable to determine the Packet Distributor"
return l[0]
Once you have the address of the packet distributor, it's only a few steps to locate the jump table, decode it, parse the complete jump table and then rename all functions in it. If you are in need of such a script ask gently
