DFIR Tooling - Command Handling

This week I laid the groundwork for Timmy's asynchronous command handler

Nov. 5, 2023

Last week's post: DFIR Tooling - First Code Review

My Client struct is getting a bit more bloated than I'd like it to be, but I don't know enough about Go's concurrency model to refactor it just yet. This week's changes were small but significant, laying the groundwork for asynchronous command handling within Timmy.

The first of these changes adds a simple "command.Message" struct to replace the string value within command.Handler's "Handle" method. The new command.Message struct looks like this right now.

package command

type Message struct {
	Handler    string
	Operation  string
	Parameters string
}

I've settled on JSON to handle serialization and deserialization for Timmy's wire protocol. The command.Message struct serves as a data model for commands passed from the listener back to Timmy. Using JSON is a KISS decision is 100% for sure based on laziness. My last iteration used a bespoke binary serialization format that was just plain overkill. I'm trying to actually finish this project this time instead of just toying with cool ideas, so JSON it is.

Now that the command.Message format is defined I can start to picture how commands will be issued by the user within the CLI and passed to Timmy for execution. For example, the directory listing command will be structured like this: "fs list /path/to/directory". Where position 0 in the string defines the handler, position 1 defines the operation, and anything else in the string is passed to the command handler as a set of parameters for the handler to deal with. Below you'll see what this example command will look like when serialized and passed to Timmy for execution.

{
    "Handler": "fs",
    "Operation": "list",
    "Parameters": "/path/to/directory"
}

If you read last week's blog post you'll know that I've started messing around with concurrency in the Client struct. You can see a bit about how that's coming together if you check out commit 91747b3. There are a few significant changes in here. Most notably, I added a couple of functions to handle asynchronous receiving, decryption, and deserialization of command messages. These two methods can be found in internal/connection/client/receiver.go and look like this right now:

package client

import (
	"bufio"
	"encoding/json"
	"net/textproto"

	"github.com/gnzlabs/tim/internal/command"
)

func (c *SecureClient) unpackMessage(packedMessage string) (message command.Message, err error) {
	if jsonMessage, e := c.messageHandler.Decrypt(packedMessage); e != nil {
		err = e
	} else {
		err = json.Unmarshal(jsonMessage, &message)
	}
	return
}

func (c *SecureClient) messageReceiver() {
	reader := bufio.NewReader(c.connection)
	textReader := textproto.NewReader(reader)
	for c.active {
		if packedMessage, err := textReader.ReadLine(); err != nil {
			c.log.Error("Read error", "error", err.Error())
		} else if message, err := c.unpackMessage(packedMessage); err != nil {
			c.log.Error("Parsing error", "error", err.Error())
		} else {
			c.commandChannel <- message
		}
	}
}

SecureClient.messageReceiver is designed to run as a goroutine that's started when SecureClient.Connect is called. It simply polls SecureClient.connection for any new commands, decrypts and deserializes the command messages, and passes those messages through SecureClient.commandChannel for execution. I still need to make a command handling goroutine. Its job will be to listen on the other end of SecureClient.commandChannel, execute the desired command, and send the results of that execution back to the listener. I still haven't decided on the "Response" message format, but I'll get around to that when I get to it.

The other (small but significant) change is that Connection.Decrypt now returns a byte slice instead of a string. This is a bit of convenience I added to allow SecureClient.unpackMessage to unmarshall decrypted command messages directly into the command.Message struct using JSON (seen here).

That's all the progress I've made this week, but stay tuned next week if you'd like to learn more!

Next update: DFIR Tooling - python-tim

Return to blog