howl.moe

Adding data to the end of a Go executable

Published (click to see context) on 05/04/2020 by Morgan Bazalgette • 2 minutes

This is a trick for including additional data at the end of your Go executables, a trick which is used in many places but which I notably remember the LÖVE engine using. I wanted to use this in a project of mine, although in the end I didn’t even need it. Still, I think it might be useful to some people, so I thought I’d write it down and share the trick.

Destructuring the problem

We have two files: Executable and Data. Executable is a normal executable file, and we want to append the data to the Executable and be able to read it from the executable file. In other words, when we do this:

cat Data >> Executable

and then run Executable, the program should be able to understand there’s appended data at the end of the file. On major platforms, this does not corrupt the executable, although it may break signing mechanisms.

We can break the problem down to a simple: the executable should know how much it’s long using a constant in the code itself.

So, what do we do?
package main

import (
    "encoding/binary"
    "fmt"
)

// The string is chosen randomly so that it can be modified later by the shell
// script; you can have anything, really, just make it unique.
const eofHack = "EOFFRICTIONCHICKEN?!@@\x00\x00\x00\x00"

func main() {
    execLen := binary.LittleEndian.Uint32([]byte(eofHack[len(eofHack)-4:]))
    fmt.Println("execLen", execLen)
}

Running this program in the Go playground always returns an execLen of 0. In fact, we will need to add a step to our build process, but don’t worry, it can be done in a few shell commands.

#!/bin/sh
# Changes the constant eofHack so that it contains the correct number of bytes that the file has.
# Usage: ./makesize <executable>

# Get the filesize
file_size=$(wc -c $1 | cut -f1 -d' ')

# get cutoff point - aka where the constant string is in the executable
cutoff=$(grep -oba 'EOFFRICTIONCHICKEN?!@@' $1 | cut -d: -f1)

# We basically split the file into two parts, so we write the data up to the
# beginning of the binary encoded size, then write the binary encoded size using
# perl, then write the rest of the file
head -c $(expr $cutoff + 22) $1 > /tmp/file
perl -e "print pack('L', $file_size)" >> /tmp/file
tail -c +$(expr $cutoff + 27) $1 >> /tmp/file

# Swap temp file to current and give executable bit.
mv /tmp/file $1
chmod +x $1

#blogpost #golang #dev