Wintermute's Weekend Workshop

@mafiabot zeus epok again

:zeus::zeus::zeus::zeus::zeus::zeus::zeus::zeus::zeus::zeus::zeus:
-(                    )
(                            _)
(-                        --)
 (                -----)
    (---------)----'
     -/  /
    /   -/
    -/  /
   / --/
 -/  /
/--/
//
/'
epok again is struck down by his god
:zeus::zeus::zeus::zeus::zeus::zeus::zeus::zeus::zeus::zeus::zeus:

1 Like

Hahahaha get fucked

Hahahahahahaha.

Lmao

-----WEEKEND OFFICILALY BEGINS-----

--------YOU MAY BEGIN WORKING--------

Hahahahaha

Help Iā€™m fucking dead

Ruining Your Mafia Game: An Ethan Jones Story

Public demo of 'JonesBot:' - a robot that uses a now-publicly-known site exploit to construct lists of likely mafia. Concept, construction, results. Was it effective? How can we prevent this behavior in the future? Why does Ethan Jones keep trying to ruin the mafia subforum?

The Exploit

"Last Post" on player profiles updates to "just now" when they post in a private thread (such as mafia PM chat). The activity feed containing recent posts does not.

We can append .json to either of these pages to see a json readout of exactly what time their "last post" was, compare this to the timestamp on the latest post in the activity feed, and map exactly which time different players are PMing.

This exploit has already been brought to the attention of the makers of Discourse in order to hopefully get it patched. In the meantime, we can use chatzy or private profiles to avoid ruining mafia games. I recommend chatzy over privating every profile because private profiles can't be ISO'd and prevent the functioning of non-malicious mafia bots.

Reconstructing the Bot

Glossary

AWS Cloud, AWS CLI, AWS SDK

-Buzzwords for people who want to pretend they're better than you. Don't let them get away with it!

Lambda

-"Serverless" function that lives on AWS which we can call and pass arguments
-also a buzzzword

Cloudwatch

-Allows us to write rules which execute on a schedule to invoke and pass args to our Lambdas
-buzzword

DynamoDB

-"No Sequel" "Database" - whatever that means

API

-Connection point for us to receive data and send data to the discourse website programmatically

Code

const AWS = require('aws-sdk');
AWS.config.update({
    region: "us-east-1"
});

class JonesBot {

  main(username) {
    console.log("Running...");
    this.getLastPost(username).then(res1 => {
      this.getLastVisible(username).then(res2 => {
        console.log(res1)
        console.log(res2)
          if (res1 != res2) {
            console.log("Hey " + username)
              this.postToDynamo(username, res1)
          }
      })
    })
  }

  postToDynamo(username, res1) {
    let ddb = new AWS.DynamoDB({apiVersion: '2012-10-08'});

    let params = {
      TableName: 'jonesBot7',
      Item: {
        'username' : {S: username},
        'lastPosted' : {S: res1}
      }
    }

    ddb.putItem(params, function(err, data) {
      if (err) {
        console.log("Dynamo error: ", err)
      } else {
        console.log("Dynamo success: ", data)
      }
    })
  }

  getLastPost(username) {
    const request1 = `https://namafia.com/u/${username}.json`;
    let lastPosted;
    var d1;
    var d2;

    return fetch(request1, {
      method: 'GET',
      mode: 'cors',
      credentials: 'include',
      headers: {
        "Content-Type": "application/json; charset=utf-8"
      }
    })
    .then(res => {return res.json()})
    .then(res => {return lastPosted = res.user.last_posted_at})
  }


  getLastVisible(username) {
    const request2 = `https://namafia.com/user_actions.json?username=${username}`;
    let lastVisiblePostTime;

    return fetch(request2, {
      method: 'GET',
      mode: 'cors',
      credentials: 'include',
      headers: {
        "Content-Type": "application/json; charset=utf-8"
      }
    })
    .then(res => {return res.json()})
    .then(res => {for(let i = 0; i<res.user_actions.length; i++) {
      if (res.user_actions[i].acting_username == username && res.user_actions[i].username == username) {
        return res.user_actions[i].created_at
      }
    }})
    .then(res => {return lastVisiblePostTime = res})
  }

}


exports.lambdaHandler = function(input, context = null) {
  let username = input.username;
  let jsbot = new JonesBot();
  jsbot.main(username);
}
const fetch = require('isomorphic-fetch')
const AWS = require('aws-sdk');
var Q = require('q');
const uuidv1 = require('uuid/v1');

AWS.config.update({
    region: "us-east-1"
});

class DynamoScan {

  scanDynamo() {
    let ddb = new AWS.DynamoDB();
    let params = {
      TableName: 'jonesBot7'
    };

    return Q.nfcall(ddb.scan.bind(ddb), params).then(function(data) {
      return data;
    });
  }

  writeData(data) {
    let dynamoOutput = {};
    for(let i = 0; i < data.Items.length; i++) {
      let name = data.Items[i].username.S
      if(!dynamoOutput[name]) {
        dynamoOutput[name] = 1;
      } else {
        dynamoOutput[name]++;
      }
    }
    return dynamoOutput;
  }

  sortData(data) {
    let keys = []

    for(let key in data) {
      if (data.hasOwnProperty(key)) {
        keys.push(key);
      }
    }

    for(let i = 0; i < keys.length; i++) {
      let temp = keys[i];
      let j =  i - 1;
      while(j >= 0 && data[keys[j]] > data[temp]){
        keys[j + 1] = keys[j]
        j--;
      }
      keys[j + 1] = temp;
    }
    let string = "";
    for (let i = 0; i<keys.length; i++) {
      string += keys[i]
      string += "\n"
    }
    string += uuidv1();
    return string;
  }

  postToWebsite(params) {
    const request = `https://namafia.com/posts.json`

    fetch(request, {
      method: "POST",
      body: JSON.stringify(params),
      headers: {
        "Content-Type": "application/json"
      },
      credentials: "same-origin"
    })
  }
}

exports.lambdaHandler = function(input, context = null) {
  let dynamoScan = new DynamoScan();
  dynamoScan.scanDynamo()
    .then(res => {
      let data = dynamoScan.sortData(dynamoScan.writeData(res));
      let params = {
        "topic_id": 1755,
        "raw": data,
        "archetype": "private_message",
        "created_at": "2001-09-11"
      }
      dynamoScan.postToWebsite(params);
    })
}

Results:

Roragok
asgharwhk
jdance
Matticus
Nyte
Wintermute
62c10880-e4ed-11e8-9cef-b7bd9c7f61f1

1 Like

yns
Roragok
Nyte
epok
jdance
Wintermute
Matticus
flopagis
DEEPTHROAT
6a87d8f0-e4ed-11e8-b58b-3b970107feb7

1 Like

TreeWonSevin
flopagis
VoHiYo
asgharwhk
yns
klaze
Nmagane
Friend
im_cool_your_weird
slowdive
Roragok
ian
LuckyArtist
DEEPTHROAT
Matticus
epok
jdance
Wintermute
Nyte
6d858890-e4ed-11e8-be1d-c7aa7101d29f

1 Like

flopagis
DEEPTHROAT
yns
ersu
jdance
Nmagane
iaafr
Nyte
Friend
epok
Roragok
TreeWonSevin
klaze
6fee9950-e4ed-11e8-a6dd-933ab9ce92e0

1 Like

iaafr
jdance
ersu
epok
Friend
TreeWonSevin
Nyte
flopagis
Roragok
730e01c0-e4ed-11e8-8b48-898d12370158

1 Like

DEEPTHROAT
yns
ersu
Friend
epok
flopagis
Nyte
iaafr
759aee30-e4ed-11e8-8ff9-958f5e128a5b

1 Like

Tables 1-3:

Junk data and preliminary tests on jdance's game (wasn't filtering out dead players and didn't have anything printing yet)

At the time I posted in the graveyard, I took the list and manually removed dead players/players not in the game and then posted it in Jdance's GY. He immediately asked me to remove it because it was 100% accurate.

This is that post from the spoiler GY (scum sorted to the top)

Tables 4-7 (beginning with the one with klaze and 317 at the bottom) are individual days of the latest game.

I sorted town to the top and scum to the bottom based on reasoning that we are more likely to have false positives for scum, but could use the bot to find people who are conclusively not scum (provided no foul play occurs - ersu had private profile for most of d1).

You can see that these results are not particularly accurate.

Day 1: Klaze somehow comes up as most scum. 317 correctly found but on a fluke - he was PMing for non-game-related reasons. Masons sorted to the top, some junk data.

Day 2: Masons heavily weighted to top, scum close behind them.

Day 3: 2 remaining scum are near the back of the pack.

In conclusion, the bot does not work and Ethan Jones's idea sucks.

Extracting tools and lessons from the exercise

-We can pull data from the site via API, do something with it, and log it to dynamo, then spit it back in some semi-readable format

-Roragok wants to do this by bot rather than by API, but it is the same concept. We can apply the same design pattern to a bot that measures lynch votes or some other form of data

-If goy club comes to you with an idea to exploit mafia games, save yourself the 3 weekends and say No, thank you!

http://notes.namafia.com/Mafia/MafiaBot/
http://notes.namafia.com/Mafia/MafiaBotSchema/

draft please review.

Umm, you knew about this exploit and didn't say anything about it? That's kind of fucked

Roragok, Dan, etc knew about the exploit for several games before this

I actually assumed you ā€œfoundā€ the exploit by being told by NMA because I had mentioned it to Brendan after he died (itā€™s technically Brendanā€™s exploit)