Locating the Packet Table (2d)

Discussion in 'RunUO Development' started by Chris, Jul 7, 2013.

  1. Chris

    Chris Renaissance Staff
    Renaissance Staff

    Joined:
    May 14, 2012
    Messages:
    3,385
    Likes Received:
    6,195
    Original Thread by Batlin, Archived from the JoinUO Forums
    http://www.joinuo.com/forums/viewtopic.php?f=28&t=686
    -----
    Today I'm gonna show you how I discovered an easy trick to locate the Packet Table. The Packet Table contains the size of the main packets as they are known by the client. Packet sizes have changed during the lifetime of UO and even though thanks to RunUO and POL we can get a good understanding of these packets, the older clients (pre-2000) are not really supported or even known anymore. By applying scripts using IDAPython & IDA Pro we can extract the information easily and then compare them. Like I've done for the Option Table http://www.joinuo.com/forums/viewtopic.php?f=28&t=683. The key to it all was the leaked GOD client. Of course my experience with reverse engineering the Ultima Online Demo also helped a lot http://www.joinuo.com/forums/viewforum.php?f=32.

    These are the steps I did to locate the table in the GOD client as the GOD client contains the packet names.

    Step 1, locate the packet names:
    [​IMG]

    Step 2, cross reference a name:
    [​IMG]

    Step 3, use your eyes on the table:
    [​IMG]


    Step 4, cross reference the table to get more info:
    [​IMG]

    The last screenshot tells us that the GOD client supports up to 205 (0xCD) packets and that each entry in the packet table is 10 (0x0A) bytes long.

    The next step I did was analyzing a newer client, client 7.0.2.2. This is a regular client and therefor no packet names are there to help you. But we can look for size identifier of packet 0, "dw 68h" (see cool table screenshot above). This will work if that packet hasn't changed since the year 2000.
    Step 1, text search:
    [​IMG]

    Step 2, the possible table:
    [​IMG]

    Step 3, cross-reference:
    [​IMG]

    Step 4, analyze:
    [​IMG]

    Even though this function looks so different compared with the screenshot of the GOD client (last one), it's purpose is exactly the same. Let's see, 244 (0xF4) packes are supported. A single entry size is 12 bytes. Why 12? Because ecx=(eax + eax*2) & ecx*4 equals 4*(eax*3) equals 4*3=12.

    Thus the table size has changed. Info could have been added or more likely, the compiler decided to create a different structured layout. Let's do the same for the oldest client we have, client 1.23. That client contains no "dw 68h" instead we can look for another value in the table, I used 0x30003, 0x30007 - 4 (4 is what the WALK packet grew later on).
    Step 1, search:
    [​IMG]
    Step 2, analyze:
    [​IMG]
    Step 3, more analysis:
    [​IMG]
    We learned that the entry size is 6 (see two screenshots higher). I could not find a compare that told me the packet size in client 1.23. That means overruns could occur in that client when it receives an unknown packet. But we can calculate the size based on the end and start of the table. My guess is that client support 158 (0x9E) packets.

    Now, all those codes looks weird and may scare you. But look beyond them, I wrote them down on a paper and tried to come up with a pattern. The following trick may help you:

    For the new client, create a structure in IDA:
    [​IMG]
    Apply the structure a few times:
    [​IMG]
    For the old client, do the same (packet size is 6 remember):
    [​IMG]
    Apply the structure a few times:
    [​IMG]
    I found that packet 01, 03 and 04 were pretty stable. Never changed. Whereas packet 00 and packet 02 changed. So the sequence of the size field of packet 03 and 04 is a good starting point to do a search.

    This is my IDAPython helper-script that will locate the table:
    Code:
    from idc import *
    from idautils import *
    
    def _convert_word_to_searchstring(val):
      h = (val >> 8) % 256
      l = val % 256
      return "%02X %02X" % (l, h)
    
    def _build_searchstring(size, a, b, c, d):
      result = ""
      if size == 6:
        result = result + _convert_word_to_searchstring(a)
        result = result + " "
        result = result + _convert_word_to_searchstring(b)
        result = result + " 0 0 "
        result = result + _convert_word_to_searchstring(c)
        result = result + " "
        result = result + _convert_word_to_searchstring(d)
        result = result + " 0 0"
      elif size == 8:
        result = result + _convert_word_to_searchstring(a)
        result = result + " 0 0 "
        result = result + _convert_word_to_searchstring(b)
        result = result + " 0 0 "
        result = result + _convert_word_to_searchstring(c)
        result = result + " 0 0 "
        result = result + _convert_word_to_searchstring(d)
        result = result + " 0 0"
      elif size == 10:
        result = result + "? ? ? ? "
        result = result + _convert_word_to_searchstring(a)
        result = result + " "
        result = result + _convert_word_to_searchstring(b)
        result = result + " 0 0 "
        result = result + "? ? ? ? "
        result = result + _convert_word_to_searchstring(c)
        result = result + " "
        result = result + _convert_word_to_searchstring(d)
        result = result + " 0 0"
      elif size == 12:
        result = result + "? ? ? ? "
        result = result + _convert_word_to_searchstring(a)
        result = result + " 0 0 "
        result = result + _convert_word_to_searchstring(b)
        result = result + " 0 0 "
        result = result + "? ? ? ? "
        result = result + _convert_word_to_searchstring(c)
        result = result + " 0 0 "
        result = result + _convert_word_to_searchstring(d)
        result = result + " 0 0"
      else:
        print "Invalid size!"
      return result
    
    def LocatePacketTable(size=0):
      ealist = []
      ea = BADADDR
      for seg in Segments():
        if SegName(seg) == ".data":
          ea = seg
      while ea != BADADDR:
        if size == 0:
          # Try the new client first
          tmpea = FindBinary(ea, SEARCH_DOWN, _build_searchstring(12, 0x8000, 0x0004, 0x0002, 0x0005))
          if tmpea != BADADDR:
            ealist.append((12, tmpea - 12 * 3 - 4))
          else:
            # Then the common clients
            tmpea = FindBinary(ea, SEARCH_DOWN, _build_searchstring(8, 0x8000, 0x0004, 0x0002, 0x0005))
            if tmpea != BADADDR:
              ealist.append((8, tmpea - 8 * 3 - 4))
            else:
              # Then the rare clients
              tmpea = FindBinary(ea, SEARCH_DOWN, _build_searchstring(10, 0x8000, 0x0004, 0x0002, 0x0005))
              if tmpea != BADADDR:
                ealist.append((10, tmpea - 10 * 3 - 4))
              else:
                # Then the oldest client
                tmpea = FindBinary(ea, SEARCH_DOWN, _build_searchstring(6, 0x8000, 0x0004, 0x0002, 0x0005))
                if tmpea != BADADDR:
                  ealist.append((6, tmpea - 6 * 3 - 4))
                else:
                  tmpea = BADADDR - 1
        else:
          # Search using predefined size
          tmpea = FindBinary(ea, SEARCH_DOWN, _build_searchstring(size, 0x8000, 0x0004, 0x0002, 0x0005))
          if tmpea != BADADDR:
            ealist.append((size, tmpea - size * 3 - 4))
          else:
            tmpea = BADADDR - 1
        ea = tmpea + 1
      return ealist
    
    Use this to create the Packet structure:
    Code:
    from idc import *
    from HELPER_IdentifyPacketTable import LocatePacketTable
    
    def print_helper(t):
      return str(t[0]) + ": " + hex(t[1])
    
    result = LocatePacketTable()
    if len(result) != 1:
      # This error is only expected for Pre-Alpha!
      if LocByName("WinMain") != 0x421B00:
        print "Error locating the Packet Table : " + str(map(print_helper, LocatePacketTable()))
    else:
      size = result[0][0]  
      id = GetStrucIdByName("struct_Packet")
      if id == BADADDR:
        id = AddStrucEx(-1, "struct_Packet", 0)
        
        if size == 6:
          mid = AddStrucMember(id, "ID",   0x0,    0x20100400,     -1,     4)
          mid = AddStrucMember(id, "Size", 0x4,    0x10100400,     -1,     2)
        elif size == 8:
          mid = AddStrucMember(id, "ID",   0x0,    0x20100400,     -1,     4)
          mid = AddStrucMember(id, "Size", 0x4,    0x20100400,     -1,     4)
        elif size == 10:
          mid = AddStrucMember(id, "ID",   0x0,    0x20100400,     -1,     4)
          mid = AddStrucMember(id, "Name", 0x4,    0x20500400,     0xFFFFFFFF,     4,      0xFFFFFFFF,     0x0,    0x000002)
          mid = AddStrucMember(id, "Size", 0x8,    0x10100400,     -1,     2);
        elif size == 12:
          mid = AddStrucMember(id, "ID",   0x0,    0x20100400,     -1,     4)
          mid = AddStrucMember(id, "Name", 0x4,    0x20500400,     0xFFFFFFFF,     4,      0xFFFFFFFF,     0x0,    0x000002)
          mid = AddStrucMember(id, "Size", 0x8,    0x20100400,     -1,     4)
        else:
          print "<<< ERROR! Invalid structure size requested! >>>"
    
    And this script will print the size and location of the Packet Table in a client:
    Code:
    from HELPER_IdentifyPacketTable import LocatePacketTable
    
    def print_helper(t):
      return str(t[0]) + ": " + hex(t[1])
    
    result = LocatePacketTable()
    print "Located Packet Table : " + str(map(print_helper, result))
    
    Between the taking of the screenshots and finalizing the final script I learned that the Packet Table does not start at the name field (as shown in the packet screenshots) but starts at the ID field. This ID field is incremental and can therefor be used to detect to number of packets in the table. This works in all the clients, including 1.23. Pre-Alpha does not use a array but direct switch/case statement, therefor the technique presented here cannot be used for that client. Use this script to make an array of the Packet structure in your IDA Pro disassembly:
    Code:
    from idc import *
    from HELPER_IdentifyPacketTable import LocatePacketTable
    
    if LocByName("GLOBAL_PacketTable") == BADADDR:
      id   = GetStrucIdByName("struct_Packet")
      size = GetStrucSize(id)
      if id == BADADDR:
        # This error is only expected for Pre-Alpha!
        if LocByName("WinMain") != 0x421B00:
          print "There is an error in the Packet-structure!"
      else:
        result = LocatePacketTable(size)
        if len(result) != 1:
          print "Error locating the Packet Table : " + str(map(print_helper, LocatePacketTable()))
        else:
          start_ea = result[0][1]
          end_ea   = start_ea
          count = 0
          while Dword(end_ea) == count:
            end_ea = end_ea + size
            count = count + 1
          MakeUnknown(start_ea, end_ea - start_ea, DOUNK_SIMPLE)
          MakeStruct(start_ea, GetStrucName(id))
          MakeName(start_ea, "GLOBAL_PacketTable")
          MakeArray(start_ea, count)
          print "The Packet Table contains " + str(count) + " packets."
    I've attached a file contain all sizes & addresses for all clients up to 7.0.2.2.


    EDIT: I've fixed a bug in the struct_Packet creation script in case the size was 6 bytes (Client 1.23)[/quote]
    Last edited: Aug 18, 2014
  2. Suchorski

    Suchorski New Member

    Joined:
    Feb 21, 2016
    Messages:
    1
    Likes Received:
    0
    Where is the file?

Share This Page