#!/usr/bin/python import sys """ Author: Demetris Papapetrou and QSecure Creation Date: 05/10/2011 Product Affected: Alt-N MDaemon's WorldClient Purpose: Identify the seed value of the session ID generation process, when provided with a valid session ID. License -------- This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . NOTES ------ Session ID characters 2-7 are produced by WorldClient using the same method/loop, starting from the 7th and working backwards. During the process ebx is decremented by a variable size. However, when char 2 is produced, ebx contains a value between \x00 - \x0f (most of the times). For each of the 16 hex values that ebx may contain we work backwards and identify the starting ebx value ((seed * 0x08088405) + 1) that was used to produce the session ID characters 2-7. When we get all the 16 ebx values generated, we feed them in the generate_sessionIDs() function to -get the 1st char and- see which one matches our initial/original session ID. If one matches then the ebx value is the seed for the next session ID that WorldClient will generate. """ #set to True to become more verbose dbg = False def main(): if len(sys.argv) != 2: print("Usage: seed.py ") print("Example: seed.py XQFQYNP") sys.exit(1) if len(sys.argv[1]) != 7 or sys.argv[1].isalpha == False: print("Error: The Session ID must consist of 7 alphabetic characters.") sessionID = sys.argv[1].upper() for i in range(1, 16): # x1 = letter + (y * 0x1A) - 0x41 x1 = ord(sessionID[1:2]) + (i * 26) - 65 # use 2nd sid char x2 = ord(sessionID[2:3]) +(x1 * 26) - 65 # use 3rd sid char x3 = ord(sessionID[3:4]) +(x2 * 26) - 65 # use 4th sid char x4 = ord(sessionID[4:5]) +(x3 * 26) - 65 # use 5th sid char x5 = ord(sessionID[5:6]) +(x4 * 26) - 65 # use 6th sid char x6 = ord(sessionID[6:7]) +(x5 * 26) - 65 # use 7th sid char if dbg == True: print("**************************************************************************") if dbg == True: print("y: " + hex(i) + "\tx1: " + hex(x1) + "\tx2: " + hex(x2) + "\tx3: " + hex(x3)) if dbg == True: print("\t\t\tx4: " + hex(x4) + "\tx5: " + hex(x5) + "\tx6: " + hex(x6)) # sanitize x6 before sending it to the function # if x6 is of type Long then remove L from the end seed = hex(x6) if seed.find('L') != -1: seed = seed[:-1] sids = generate_sessionIDs(seed) if dbg == True: print(sids) for s in sids: if s == sessionID: print("Found valid ID: " + s + "\twith next seed: " + seed) # Returns a list with the 2 session IDs that are generated by the function def generate_sessionIDs(ebx): sessionID = '' sessionID_1 = '' sessionID_2 = '' # -------- MOV EBX,EAX -------- if dbg == True: print("EBX = " + ebx) if dbg == True: print("------------------------------------------") for i in range(1,7): eax = '0x4EC4EC4F' # -------- MUL EBX -------- (multiplies EAX with EBX and stores the result in EDX:EAX) tmp2 = int(ebx,16) * int(eax, 16) if dbg == True: print("MUL EBX\t\t\tEDX:EAX = " + hex(tmp2)) if tmp2 > 4294967295: # > \xFFFFFFFF which means EDX will be > 0 #print("EDX contains data") edx = hex(tmp2)[2:-9] #convert long int to hex and get the first 8 byte slice. EDX is now a string if dbg == True: print("\t\t\tEDX = " + edx + "\tEAX = " + hex(tmp2)[-9:-1]) # -------- SHR EDX,3 -------- which means divide EDX by 8 edx = hex(int(edx,16)/8) if dbg == True: print("SHR EDX,3\t\tEDX/8 = " + edx) # -------- MOV AL,DL -------- if len(edx[edx.find('x') + 1:]) == 1: #if edx length after 'x' is equal to 1 al = edx[-1:] else: al = edx[-2:] if dbg == True: print("MOV AL,DL\t\tAL = " + al) else: #print("EDX is zero") edx = hex(0) al = '0' if dbg == True: print("\t\t\tEDX = " + edx + "AL = " + al) # -------- IMUL CL -------- which multiplies AL with CL (CL=1A) and stores the result in AX if len(al) == 1 and ord(al) <= 57: # al's format is "3C" not "0x3C" or "x3C". Hence if "9" or "C" test its ascii code. If it is [0-9] then treat it as decimal ax = hex(int(al) * int('0x1A',16)) #print("---- AL1 = " + al) else: ax = hex(int(al,16) * int('0x1A',16)) #print("---- AL2 = " + al) if dbg == True: print("IMUL CL\t\t\tAX = " + ax) # -------- SUB BL,AL -------- if len(ax) < 4: #if len() < 4 then it is 0 becasue 1*1A = 4chars hence no case of 0x3 will exist (no len() = 3 instead of 4) al = "00" else: al = ax[-2:] if len(ebx[ebx.find('x') + 1:]) == 1: #if ebx's length after 'x' is 1 (e.g. 'x9') bl = ebx[-1:] else: bl = ebx[-2:] if (int(bl,16) < int(al,16)): #if AL is gt BL the result will be a negative number bl = hex(256 + (int(bl,16) - int(al,16))) #256 is added to deal with the negative overflow else: bl = hex(int(bl,16) - int(al,16)) if dbg == True: print("SUB BL,AL\t\tBL = " + bl) # -------- ADD BL,41 -------- which adds BL and \x41 (hex 41 = 65 decimal) bl = hex(int(bl,16) + 65) if dbg == True: print("ADD BL,41\t\tBL = " + bl) if len(bl[bl.find('x') + 1:]) > 2: #if bl's length after 'x' is gt 2 then we have an overflow of the register bl = hex(int(bl[-2:],16)) ascii_char = chr(int(bl,16)).upper() if dbg == True: print("\t\t\tCHAR = " + ascii_char) sessionID = ascii_char + sessionID if dbg == True: print("\t\t\tSessionID: " + sessionID) # -------- MOV EBX,EDX -------- ebx = edx if dbg == True: print("\t\t\tEBX = " + ebx) if dbg == True: print("------------------------------------------") #The last step is to generate the 1st char of the sessionID if len(ebx) < 3: sys.exit() elif len(ebx) == 3: # -------- ADD BL,0E -------- # -------- ADD BL,41 -------- which adds to BL: \x41 (hex 41 = 65 decimal) + \x0E (hex 0E = 14 decimal) bl = hex(int(ebx[-1:],16) + 65 + 14) if dbg == True: print("ADD BL,0E") if dbg == True: print("ADD BL,41\t\tBL = " + bl) ascii_char = chr(int(bl,16)).upper() if dbg == True: print("\t\t\tCHAR = " + ascii_char) if (int(bl,16) >= 65 and int(bl,16) <= 90) or (int(bl,16) >= 97 and int(bl,16) <= 122): sessionID_1 = ascii_char + sessionID #print("SessionID 1: " + sessionID_1) # -------- ADD BL,41 -------- which adds BL and \x41 (hex 41 = 65 decimal) bl = hex(int(ebx[-1:],16) + 65) if dbg == True: print("ADD BL,41\t\tBL = " + bl) ascii_char = chr(int(bl,16)).upper() if dbg == True: print("\t\t\tCHAR = " + ascii_char) if (int(bl,16) >= 65 and int(bl,16) <= 90) or (int(bl,16) >= 97 and int(bl,16) <= 122): sessionID_2 = ascii_char + sessionID #print("SessionID 2: " + sessionID_2) else: # -------- ADD BL,0E -------- # -------- ADD BL,41 -------- which adds to BL: \x41 (hex 41 = 65 decimal) + \x0E (hex 0E = 14 decimal) bl = hex(int(ebx[-2:],16) + 65 + 14) if dbg == True: print("ADD BL,0E") if dbg == True: print("ADD BL,41\t\tBL = " + bl) ascii_char = chr(int(bl,16)).upper() if dbg == True: print("\t\t\tCHAR = " + ascii_char) if (int(bl,16) >= 65 and int(bl,16) <= 90) or (int(bl,16) >= 97 and int(bl,16) <= 122): sessionID_1 = ascii_char + sessionID #print("SessionID 3: " + sessionID_1) # -------- ADD BL,41 -------- which adds BL and \x41 (hex 41 = 65 decimal) bl = hex(int(ebx[-2:],16) + 65) if dbg == True: print("ADD BL,41\t\tBL = " + bl) ascii_char = chr(int(bl,16)).upper() if dbg == True: print("\t\t\tCHAR = " + ascii_char) if (int(bl,16) >= 65 and int(bl,16) <= 90) or (int(bl,16) >= 97 and int(bl,16) <= 122): sessionID_2 = ascii_char + sessionID #print("SessionID 4: " + sessionID_2) sessionIDs = [sessionID_1, sessionID_2] return sessionIDs if __name__ == "__main__": main()