Monday, 14 May 2018

Reverse engineering the Path of Exile game protocol - Part 3: Introducing exileSniffer



Up to this point I haven't talked much about my workflow for extracting and examining the protocol data - so here a tool you can use to do it. This will walk through what it does and how it works.
Requirements: Windows, winpcap  (If Wireshark works then this should work too)


Starting packet capture

Initial screen with no Path of Exile clients running

When exileSniffer starts it will start scanning the process list for Path of Exile clients and sniff on the default interface for logon packets. It doesn't matter which order the processes are started in, but it's essential that exileSniffer is launched and listening before login starts.


A game client is detected - exileSniffer is scanning its memory for keys


The player has logged in - we are waiting for keys and testing them against the logon traffic



We found the correct salsa key and have started decrypting the logon stream.

The above shows packet 0x4 from the login server highlighted - so far I've ignored it and it has no purpose disaplayed. If we look at the hex pane we can see a bunch of zeroes and a hex 12 at the end, which doesn't really tell us much. It comes after the authentication data so maybe it's a "Logon success" message. Try a bad password and report back if you care about the login process I guess.



Analysis pane showing a League List packet

For most packets I've encountered I've attempted to give them a meaningful description in the packets list, but for some with lots of interesting data I've added some analysis in the Analysis pane.

There are a few workflow features
  • Filters and Filter Lists to prune out noisy packets and highlight interesting ones
  • A hash search function in 'Utilities' so we can test if any data we find is a known hash
  • Logging to disk - both raw and filtered streams
  • A JSON pipe, which I'll talk about next


Using exileSniffer decryption with other tools


The deserialisation and decoding is done in entirely seperate threads to the analysis and display of the data. exileSniffer opens a named pipe so that external tools can subscribe to the same decoded data as the UI receives, which allows us to solve some problems in new ways.

Here are some example Python 3 scripts.

Tool #1: Monitoring player health

import json
import codecs

def connectToPipe():
    pipename = "\\\\.\\pipe\\ExilePipe"
    pipe = codecs.open(pipename, 'rb', encoding='utf-16-le')
    return pipe

if __name__ == "__main__":

    currentPlayerID = 0
    
    pipe = connectToPipe()
    while True:
        ln = pipe.readline()
        if len(ln) == 0: continue
        js = json.loads(ln)

        if js['MsgType'] == 'SRV_NOTIFY_PLAYERID':
            currentPlayerID = js['Payload']['ID1']
            continue

        if js['MsgType'] == 'SRV_MOBILE_UPDATE_HMS':
            if js['Payload']['Stat'] == 0: #life
                print(js['Payload']['NewValue'])
            continue
        
    pipe.close()

This should hopefully be straightforward.

  • It looks for the SRV_NOTIFY_PLAYERID packet sent when joining the gameserver to figure out what our characters unique ID is.
  • It looks for SRV_MOBILE_UPDATE_HMS packets sent whenever the health/mana/shield of a mobile changes.
  • If the mobiles ID is our player, and the stat is Life, it prints the new value.

One improvement could be to read the players maximum health from the object creation packet and alert on low percentage.

All the packets I've found so far are listed in messageTypes.json in the application directory, but only the basic ones will be there. I suspect a huge amount of more obscure ones for certain skills, certain monsters, certain maps, crafts, vendor actions, league specific stuff, etc are all waiting to be found. You could also just enumerate them by injecting packets with the unknown ID's into the client, but if there is anything I don't need in life it's more of these packets to work through.

Tool #2: Monitoring party members

Here is a question someone asked on the PathOfExileDev subreddit.

Title: "Players joining your party not logged?"
Text: "It seems almost everything is in the log file, including changing instances, global chat, whispers, etc, but not members joining your party? Is this somewhere else in another log? Can I somehow keep track of when someone joins a party?"
The only reply:  "nope, the only log is clients.txt"

I feel like their expectations are already quite low if they consider the paltry contents of clients.txt to be 'almost everything' and seeing party member joins/quites doesn't seem like a big ask.

Looking into the party packets, it seems that the client doesn't get told 'X has joined, Y has left' but gets the whole party list every time the makeup of the party changes.

This Python script will track changes to the party and announce joiners and leavers.

import json
import codecs

def connectToPipe():
    pipename = "\\\\.\\pipe\\ExilePipe"
    pipe = codecs.open(pipename, 'rb', encoding='utf-16-le')
    return pipe

if __name__ == "__main__":
    inited = False
    oldNames = []
    
    pipe = connectToPipe()
    while True:
        ln = pipe.readline()
        if len(ln) == 0: continue
        js = json.loads(ln)

        if js['MsgType'] == 'SRV_PARTY_DETAILS':
            membersDict = js['Payload']['MemberList']
            updatedNames = [x['Name'] for x in membersDict if x['StatusText'] != 'Pending']
            
            if not inited:
                print("Party created with %d members"%(len(updatedNames)))
                inited = True
                oldNames = updatedNames
                continue

            newNames = list(set(updatedNames).difference(set(oldNames)))
            for x in newNames:
                print("%s joined the party"%x)
                
            leftNames = list(set(oldNames).difference(set(updatedNames)))
            for x in leftNames:
                print("%s left the party"%x)

            oldNames = updatedNames

        
    pipe.close()


Does this give an unfair advantage?

The first thing to consider is information: I was curious to see what the client was being sent that the player is not supposed to know about.

In general the server is quite good about only sending what the client needs to know - you can't see other players or monsters on the map unless they are almost on your screen. While you can see all of a players stats, devotions, XP, quests, etc - you can't see what items they are carrying or using - other than what is needed to render it.

I haven't played enough recently to remember if invisible things are a problem - but I suspect the 'is_hidden_monster' stat that some objects have could be made to lose it's effectiveness.

One thing I've noticed years ago when scanning game memory (and have found the cause for during this project) that probably could be exploited is the preload lists sent when you join a map. If a rogue exile, NPC grandmaster or strongbox exists: the server will tell the client as soon as it loads so the graphics can be preloaded. This allows you to load maps over and over again to look for something juicy without having to actually spend the time to explore them. My suggestion to the devs is to only send these to the client when the player is nearby, like when a new player joins the map and their totems are preloaded. The performance hit can't be that bad compared to people mass-loading new maps.

The second possible advantage is to be able to 'do' something that normal players can't do.

I know it's not going to be seen as much of a mitigation but it's worth highlighting that exileSniffer doesn't interact with the gameserver in anyway, the sole interaction with the client is reading key data while it is talking to the login server. This means players will still have to use other more intrusive programs to react to exileSniffer data. A google search for 'path of exile bots' shows that those programs are already out there and I'm not releasing anything that bot developers don't already know (and are using for a fee).

Further work

So I think the ability to write little scripts like that to interact with the games traffic is the coolest outcome from this project, but to write the above party script I had to go back and reverse the handling of the party info packet. This highlights the fact that huge swathes of the protocol are still left to be understood.

In particular:
  • Items. This is the big one as it's what PoE is all about but with the classes, mods, corruptions, sockets, links and up to 6 items within items also needing decoding  - it's a lot of work. It does at least lookup the hash to the GGPK item ID.
  • Objects. I've reversed most of the player creation packet so we can see handy things like all of the stats a player has and what microtransaction effects they are using, but monsters, npcs, pets, etc also need to be done.
  • The ID triad. An object identifier is (DWORD, DWORD, WORD) but I've not found any particular use for the second and third items, other than occasionally referring to the object that caused something to happen to the target object.
It would also probably be possible to remove the requirement to start sniffing before logon by brute forcing the Salsa iteration value.

1 comment:

  1. It doesn't work anymore, Please upgrade exileSniffer

    ReplyDelete