tl;dr

I spent way too much time creating a Security Information and Event Monitoring experience (SIEM) in Minecraft code, demo video. The process was actually more complex and fun than I was expecting it to be, and could serve as a blueprint to understanding the detection lifecycle end-to-end.

Picture of a pig in SIEMCRAFT with detection information above its head

From boredom, big things grow

At the start of the year, I found myself on PTO without the ability to travel to the place I had booked the PTO for.

With my kiddo in daycare, I had nothing of importance to do, so while idly browsing the internet I came across the awesome KubeCraftAdmin by Eric Jadi, a project that allows you to manage Kubernetes clusters from Minecraft. To be clear, Eric had put the correct amount of effort into KubeCraftAdmin, making it functional and portable enough for the lighthearted project that it was.

But for me, with more time than sense, noticed a few ‘rough edges’ to the project that bugged me, namely that while it could send data from Kubernetes into the Minecraft server, it couldn’t receive and data back from the Minecraft player, at least not directly. Again for a project like Eric’s, this is fine, but I had nothing but time to devote to pedantic things. And besides, it couldn’t be that hard, right? right….?

Bursting at the SIEM

With the Kubernetes controller idea already being taken, I wanted to look for another project with the same basic idea of ‘controlling something unusual from Minecraft’. Back in 2014, I had created a controller in Minecraft for Metasploit, although the code has since been lost to time, and was written in LUA for a now very outdated Minecraft version.

So instead I turned from red to blue and decided to write a Security Information and Event Monitoring system, aka a SIEM. But I wanted to write the project end-to-end, from raw events to detection, visualisation, and human interaction. So I came up with the following requirements, putting on my (very ill-fitting) Business Analyst hat (TM) that I used to wear many years ago:


Requirement #1: Must have the ability to collect raw events

Business people tend to focus on CEOs, so I decided to focus solely on Windows, as the only CEO I know personally that uses Linux just rants about parasites all day. I also chose to only focus on Process Creation events because I’m lazy. However, as a stretch goal, I wanted to be able to collect raw events from an entire domain of Machines, not just a single box.

Requirement #2: Must generate detections based on raw events

Displaying a firehose of raw events isn’t very useful to anyone, so I wanted to be able to write detections based on the raw events, and only alert the User/Player if something suspicious is detected.

Requirement #3: Must display detections to player

Pretty obvious, Player must be able to see the detection, be able to tell the difference between a high- and low-severity detection, and be able to see all relevant process information to help them decide if it is a True or False Positive.

Requirement #4: Must allow a player to take action

I thought it’d be hilarious to make it so that a player can kill the offending process from Minecraft if they detected it was a True Positive. Spoiler for the rest of the blog, this turned out to be way more complex than I expected it to be.


1. Collecting raw events

This turned out to be the easiest of steps. In the real world, the best free solution would be to use Sysmon for Windows, which generates events about Process activity and writes these events to the Windows Event log.

But to truly create an end-to-end solution, I made my own event collector (or really used an event collector I already made) called Sealighter. Sealighter uses ETW to collect raw information from Windows, so I used this basic Sealighter config to collect ‘Process Start’ events from the Kernel ETW Provider and send the events to the Windows Event Log as a JSON object:

{
  "session_properties": {
    "session_name": "Sealighter-Kernel-Process-Start",
    "output_format": "event_log"
  },
  "kernel_traces": [{
    "trace_name": "kernel_proc_start_trace",
    "provider_name": "process",
    "filters": { "any_of": { "opcode_is": 1 } }
  }]
}

By using Event logs to store the raw events, we can also make use of another built-in feature of Windows, Windows Event Forwarding. This means that if we deploy Sealighter to every machine in a Windows Domain, we can forward all the events to a single PC, where we can do the heavier and more complex detection parts of SIEMCraft from a single machine, without writing any extra code:

Diagram showing Windows Events being forwarded to a central machine using WEF

Once WEF is set up, a SIEMCraft binary can make use of the EvtSubscribe API to collect and process all the Sealighter events as they come in. I had planned to write SIEMCraft in Go and found this awesome library from RawSec that made it easy to collect and parse Windows Event logs, ready to generate detections from.

2. Generating Detections

The correct way to write and generate Open Source detections is to make use of the amazing Sigma project, which led by the incredibly dedicated Florian Roth.

Sigma defines a standard schema in which people can create and share detections across multiple operating systems and applications. SIEMCraft needed to be able to ingest the 100s of free Sigma detection rules (as well as any other rules people create) for its detection engine, rather than attempting to create my own proprietary format.

Thankfully Bradley Kemp had already created a really neat open source Go library that could run and check Sigma rules against arbitrary data. I had to do some data formatting to ingest a list of rules from the filesystem, convert the Data from Sealighter to Sigma-Go, and account for some differences of opinion on the Sigma schema, but otherwise, Florian and Bradley also made this step super easy for me.

With these two pieces of the puzzle, I could write a basic SIGMA rule to detect the most well-known of attacker tools - whoami.exe:

title: Whoami Execution
id: 36de6a23-651e-485a-ba69-3966d66707af
status: experimental
description: Whoami.exe runs
references:
    - https://blog.tofile.dev
tags:
    - attack.execution
author: pathtofile
date: 2022/01/15
logsource:
    category: process_creation
    product: windows
detection:
    selection:
        Image|endswith: '\whoami.exe'
    condition: selection
falsepositives:
    - unknown
level: high

Whoami is used in almost every single intrusion I’ve observed, from eCrime to APT. Just look at the MiTRE page!. And what real user doesn’t already know who they are, right?

So now we had raw Process events flowing from every domain PC into SIEMCraft, which uses SIGMA to look for the dastardly whoami.exe (as well as any other actual rules we might create). Once SIEMCraft detects the whoami intrusion, it was time to send that data into Minecraft.

3. Send detects into Minecraft

This step would have been easier if I had used the old, Java version of Minecraft, which allows Mods to do basically whatever they want.

But the multiplayer server of the modern, better version of Minecraft (“Bedrock edition”, send Minecraft opinions to @maybe-sybr) isn’t designed to allow custom data into or out of a Minecraft server. Instead of ‘Mods’, Minecraft Bedrock has ‘Addons’, allowing you to do lots of cool things inside the world (new monsters, animations, etc.), while not allowing the Addons to read or write anything outside of Minecraft. This is great for security, but not great for SIEMCraft, which both needs to read Windows Events Logs and spawn creatures in Minecraft.

I could have written/used a custom server program, but after hanging out of the Minecraft hacker discords I learnt this would prevent other Mods from running at the same time as SIEMCraft, which wasn’t ideal.

But it turns out that in a very roundabout way, I could make use of a WebSocket designed for “Minecraft Educational Edition” to poke a hole in the security boundary. Anand describes the process best, but essentially Minecraft and a remote process can communicate over a WebSocket to send a receive several undocumented message types. This means we can run a SIEMCraft process ‘outside’ of Minecraft, to do the things Addons can’t do and use it to control an Addon inside of Minecraft.

The first step was to get the detection data into Minecraft, so I first made use of the ‘Command Request’ message type. This Message can be sent to a Minecraft server, allowing you to run any /command console from the player’s perspective. For example, to spawn a pig named ‘snuffles’ right next to the Player, you send this JSON command:

{
      "header": {
        "version": 1,
        "requestId": "36de6a23-651e-485a-ba69-3966d66707af",
        "messagePurpose": "commandRequest",
        "messageType": "commandRequest"
      },
      "body": {
        "version": 1,
        "commandLine": "summon pig \"snuffles\" ~ ~ ~",
        "origin": { "type": "player" }
      }
    }

Using these messages (via a minecraft websocket library by Sandertv), I could Spawn an animal near the player with their ‘name’ containing all the important detection details:

Minecraft Panda with its name containing a whoami detection

I decided to make different animals correspond to different detection severities, with “low” severity spawning cows and chickens, and “high” spawning spiders, pandas, and bears.

I needed to create and use a new Addon to get this all to work, to prevent the animal names/detection details from being hidden too quickly and to add bears to the game, as Minecraft only has polar bears.

But now, thanks to the Websocket, A player can be cruising around a Minecraft server doing Minecraft things, and if a detection comes in a chicken, spider, or bear will helpfully spawn nearby, alerting the user that a whoami.exe attack is afoot.

4. Allow players to take action

The final piece of the puzzle was to allow the player to take action, and kill a process identified by a detection if they deem it suspicious enough. This would be accomplished by having the play kill the detection-named animal specifically with a diamond sword (any other type of death would be treated as the player ignoring the alert).

In theory, this should have been easy, as the WebSocket exposes a MobKilled event which is sent from Minecraft to the remote Process whenever an animal or monster is killed. However, the message lacks any information to tell exactly which animal was killed, only the fact they were killed by the player. This means that while SIEMCraft could tell the player wants to respond to a detection, if it has sent multiple detections to the player it can’t tell which one.

A Minecraft Addon can get more information from inside the game, however. Addons can run small JavaScript programs which have access to a wider array of message types they can subscribe to, including the minecraft:entity_death message. This message type does contain all the information we need about exactly what mob was killed by who and with what. But as we know Addons can’t send this data out of Minecraft, so after many days of hacking, I decided on a really dodgy workaround: the PlayerMessage event.

PlayerMessage

This message is sent from the Minecraft WebSocket to the remote process and contains any text players (or other entities) send each other. The messages contain a bunch of information about who was talking to who, where they were talking from, and most importantly the exact text they typed. So I simply made the internal Javascript Addo ‘whisper’ the data to the player:

// Listen for entity_death events
var system = server.registerSystem(0,0);
system.initialize = function() {
  this.listenForEvent("minecraft:entity_death", (eventData) => this.onEntityDeath(eventData));
};

system.onEntityDeath = function (eventData) {
  // Get the name of the mob
  let killed_name = this.getComponent(eventData.data.entity, "minecraft:nameable");

  // Get weapon in main hand, assume it was what was used to kill it
  let handContainer = system.getComponent(eventData.data.killer, "minecraft:hand_container");
  let mainHandItem = handContainer.data[0];

  // Create JSON string to send
  less message = 'killed_name='+ killed_name + ', weapon=' + mainHandItem.item;

  // Send message by used the /say command
  let ExecuteEventData = this.createEventData("minecraft:execute_command");
  ExecuteEventData.data.command = "/tell @a \"" + message + "\"";
  this.broadcastEvent("minecraft:execute_command", ExecuteEventData);
};

And this results in the following message being sent to the SIEMCraft process over the Websocket:

{
  "header": {
    "messagePurpose": "event",
    "requestId": "00000000-0000-0000-0000-000000000000",
    "version": 1
  },
  "body": {
    "eventName": "PlayerMessage",
    "measurements": null,
    "properties": {
      // ...
      "Sender": "Script Engine",
      "Message": "killed_name=snuffles, weapon=diamond_sword",
      "MessageType": "tell",
      // ...
    }
  }
}

After some tweaking to do things like base64 and JSON encode the data to avoid parsing errors, I now had the full loop of data going from SIEMCraft -> Minecraft -> SIEMCraft. I could then read the weapon name to check it was a diamond sword, and check the mob name to extract the Process ID of the process the player wished to kill. I could have implemented logic to kill remote processes across the domain, but for now, I’m happy with just killing processes locally.

Shaving the GameTest

Everything was working fine, and I began writing up this blog and sharing screenshots with mates.

Unfortunetly one of them came across this blog from the Minecraft developers, stating they were removing the Addons Javascript engine in … 1 month’s time.

This would completely destroy the addon’s ability to talk back to SIEMCraft to kill processes, so I had to find a new solution. The replacement Javascript engine Game Test framework didn’t have the same events and had no events related to a mob’s death. But just as I was ready to throw the feature in the bin, a new beta version of Minecraft dropped, and deep within its patch notes contained a new event, beforeDataDrivenEntityTriggerEvent:

Minecraft Beta release notes detailing new event

I have never Yak Shaved harder to solve a problem in my life.

In…short:

Combining this feature with a new Addon allowed me to define a new type of animal that looks like a chicken/cow/spider/etc. These animals would only ever be created by SIEMCraft, and unlike a normal animal, they had a special “event”, that trigged only when they were specifically killed by a player holding a diamond sword. Events are used by Minecraft to trigger behaviour, e.g. give an egg to a player when they pet a chicken. I created an event that didn’t actually do anything, but it was enough to trigger some Game Test Framework Javascript code that was listening to the new beforeDataDrivenEntityTriggerEvent event. This event contained the event and animal names, which I could then (by filtering only on my custom ‘killed by a diamond sword’ event) send back to SIEMCraft using the same playerMessage technique as before.

In diagram form, it looks like this: rediculous diagram explaining how SIEMCraft kills a process

While technically we’re past the deprecation date and the old version of the Javascript addon still works, SIEMCraft is now protected from future releases of Minecraft…almost.


Finishing SIEMCraft

By this stage, SIEMCraft had consumed not only my PTO but many weeks of free time beyond that. I think I yak-shaved too hard and ended up shaving part of my brain, so after I shared my journey of bullshit at my local security meetup, I took a long break before finalising the code and writing this blog. During this time Minecraft made another breaking change, requiring me to fork and fix the Minecraft Websocket library I was using.

But it’s now all completed and working (for now). You can get SIEMCraft code here: https://github.com/pathtofile/siemcraft

Instructions on how to set up Minecraft and run it are in the README, but please don’t use it for real things. Or do, and send me the screenshots of the ensuring dumpster fire. If Minecraft changes something again and breaks SIEMCRAFT, I am unlikley to fix it, as it has already taken up way too much of my time for a such a silly thing. So try it out while you can.

Many, many thanks to all the library authors I used for this project (Bradley Kemp, RawSec, Florian Roth, Sander Ten Veldhuis), S Anand’s blog as well as everyone who puts up with this garbage.

Oh, did I mention it works in Minecraft VR? https://youtu.be/8Vyf8Y5wcRY

Minecraft VR

Is this the first Metaverse SIEM?