Hacking Board Games with Python

First, a disclaimer: I in no way undermined the security or broke into any accounts. I got access to 0 privileged information, merely recorded information that was already presented by the website.

Recently, I’ve become re-enamored with an old school board game: Diplomacy. This board game, set in Europe around the turn of the century (19th-20th century, that is), features gameplay similar to Risk or Axis and Allies. The key difference in Diplomacy is that the gameplay is entirely free of luck. There are no dice rolls, no random chance, your strength comes entirely from the other nations you can con into helping you. It is a game of wicked social engineering, where every player is trying to get good intelligence on every other player. It is not uncommon for players to ‘release’ a tidbit of information to one player, and see how long it takes to find it’s way back to them from a different player. The math nerd in my head visualizes the connections between these nations as a directed graph — but that’s another post.

I’ve been playing the game online with a friend from the computer science department here, and he made an interesting suggestion — the game may be vulnerable to something called a ‘side channel attack‘. The basic concept behind a side channel attack is that there are trends and data that exist alongside the actual data you want access to. In Diplomacy, what I’d really like to have access to is the messages between other players, but that data is not available (and that would be cheating!) Web diplomacy, however, makes a certain amount of other data available. The player’s name, readiness, and online status are all displayed on every page load. With a little help from Python, it’s trivial to capture this data and use it to craft a side channel attack.

Example Status in Diplomacy

Example Status in Diplomacy

Allow me to explain the image real quick. On the left, we have the status of the country. Western Canada has not readied any orders, and it’s likely he hasn’t logged in since the last turn. The Near East has orders plugged in, but they are provisional, he hasn’t indicated he’s ready for the turn to progress. India has orders in, and he’s sure about them. To the right of the player’s name is a green dot that indicates whether the player is currently online. In the image above, India is online, the other two players are not. The final piece of relevant data is the last seen time, indicating (we believe) the last time webdiplomacy served a page to that player.

I’m a big fan of making data open and easy to access, but sadly we’re not at the point where all websites have an easy to access data API. So we cannot just write a quick script to pull down a bunch of nicely formatted data, instead we have to scrape the website ourselves and grab what  data we can. That’s where the beauty of Python comes into play. There are two simple and easy to use Python libraries that make this possible: Mechanize and Beautiful Soup. Mechanize emulates a fully featured web browser, allowing you to programmatically fill out forms, send mouse clicks, and navigate web pages. We will use it to log into Web Diplomacy and navigate to the game we want to watch. Beautiful Soup is a library that makes parsing html incredibly easy by allowing you to navigate the html tree rather than just the text of the html. Let’s look at the code

# Our imports. Nothing new or interesting here
import sys, time, os
from mechanize import Browser
from BeautifulSoup import BeautifulSoup
 
## The url for the logon page
LOGON_URL = 'http://webdiplomacy.net/logon.php'
 
## The url for the board you want to watch. You'll need to put in the game ID
BOARD_URL = 'http://webdiplomacy.net/board.php?gameID='
 
## Webdiplomacy login information
USERNAME= ''
PASSWORD= ''
 
## Define a simple class to hold the status of a country
class country:
    def __init__(self, cid, status, online):
        self.countryID = cid
        self.status = status
        self.online = online
 
## Our main function
def fetch():
    # Create a browser object. This is a Mechanize class that simulates
    # a fully featured browser
    br = Browser()
 
    # Tell our browser to open a URL. In this case, navigate to the logon page
    br.open(LOGON_URL)
 
    # Tell our browser to navigate to the first (and only) form on the page
    br.select_form(nr=0)
 
    # This form has two input boxes, populate them with our username and password
    br['loginuser'] = USERNAME
    br['loginpass'] = PASSWORD
 
    # Click the submit button, and ignore the response
    br.submit()
 
    # Now that we are cookied (we've logged in) navigate to the board to watch
    br.open(BOARD_URL)
 
    # And tell our browser to load the page, storing the response we download in resp
    resp = br.reload()
 
    # Response is the full web response, we just care about the HTML portion, so save it off
    html = resp.read();
 
    # Now, we need to create a BeautifulSoup object. A BeautifulSoup object is a
    # tree based way to navigate an html page. So we can get the  tag
    # and navigate through its children  and  tags. (For example)
    # We will be using it to search for specific things on the page, like the ready icon.
    soup = BeautifulSoup(''.join(html))
 
    # We begin by looking for a div, whose id is chatboxtabs. soup.find() returns the first
    # result
    chatboxtabs = soup.find('div', {'id':'chatboxtabs'})
 
    # Set up an empty dictionary to store the status of each country using the ID as the key
    onlineDict = {}
 
    # The empty list of countries to return
    returnList = []
    # The country doing the checking is not in the list of chatboxes, so we have to record
    # its status separately
    missingID = 1
 
    # For each anchor (a) tag inside our div
    for chatbox in chatboxtabs('a'):
        # If the anchor tag has a span within it
        if(chatbox('span') != []):
            # Then prune out the country id. These spans have a class that looks like:
            # "country 3 memberstatusplaying"
            # All we care about is the integer in the middle
            theID = chatbox('span')[0]['class'].replace("country", "").split(" ")[0]
 
            # If the ID we read out is not the next id in order,
            # increment it. (This means missingID will stop at the scipter's country ID)
            if (theID == str(missingID)):
                missingID += 1
 
            # If the country is online, then there is an image next to their name
            # Store the status for that country
            if (len(chatbox('img')) > 0):
                onlineDict[theID] = True
            else:
                onlineDict[theID] = False
 
    # Store the status of the scripting country
    onlineDict[str(missingID)] = True
 
    # Now we look for their readiness status. Each country has a td with a class
    # 'memberLeftSide'. We grab them all with soup.findAll
    details = soup.findAll('td', {'class':'memberLeftSide'})
 
    # Iterate through all the td elements
    for status in details:
        # There are a few we can skip because they have no images
        if (status.img != None):
            # The country ID is grabbed, just like the anchor tags above
            countryID = status.span.contents[2]['class']
            countryID = countryID.replace("country", "")
            countryID = countryID.split(" ")[0]
 
            # Get the alternate text for the image
            statStr = status.img['alt']
            statInt = 2 # Assume 'bad' status
 
            # Set our status integer based on the alt text
            if (statStr == "Not received"):
                statInt = 1
            elif (statStr == "Ready"):
                statInt = 4
            elif (statStr == "Completed"):
                statInt = 3
 
            # Create a country object and store it in our return list
            returnList.append(country(countryID, statInt, onlineDict[countryID]))
 
    # Return our country list
    return returnList

Pretty neat, pretty straightforward, and easy to fix up for your needs. What’d we do with it? I’d recommend you read Matt’s blog entry for the full details. The concept in brief, is that Matt took the above function, tweaked it a little, and stuck it in a loop. Every 2 minutes, he polls the server, and records the results in a SQL database. Over the next few days, Matt gathered a whole pile of data about when players logged in, logged out, and when they changed their status. With a little data visualization wizardry, Matt pieced together that there were two countries who were logging in and changing their statuses together. This was enough to suggest that the players were communicating and coordinating their actions together, which turned out to be the case.

Whether or not Matt acted wisely knowing what he knew is another matter.

Category: Chit Chat, Gaming, Side Projects, Technologies, Web | Tags: , , , , , , , , , , , , , One comment »

One Response to “Hacking Board Games with Python”

  1. Weenterorie

    If you have nothing selected, then it’ll duplicate the entire contents of the layer.


Leave a Reply



 

Back to top