Search code examples
goexeexecutableelfportable-executable

How can I inject user-specific data (like a public key) into a pre-built Go binary without needing Go installed?


I am working on a Go application that encrypts sensitive files using a public key. The goal of my project is to create a customizable encryption tool that allows users to generate an executable tailored to their needs. The idea is that the user will input their own public key, and the tool will produce a unique executable, which they can then use to securely encrypt their files.

The challenge is that I cannot assume the target machine has Go installed for me to compile the code locally. Additionally, I would like to ensure the solution is portable, meaning the executable should be able to run on any machine without requiring Go setup.

How can I inject a user-specific public key into a pre-built Go binary after it’s been compiled? Or are there better solutions that I could not think of?


Solution

  • You can use objcopy for this with the --add-section flag, there might be other better ways to do this but this is the first one I thought of. You can parse it by either using the elf.h C API or Go's debug/elf directly if you're only worried about ELF executables or you can use something like BFD (I think there's no Go bindings for this) if you want to support other executable formats. objcopy itself uses BFD so you can use it for all the formats it supports. Here's the debug/elf approach:

    package main
    
    import (
        "debug/elf"
        "os"
        "fmt"
        "log"
    )
    
    func main() {
        exePath, err := os.Executable()
        if err != nil {
            log.Fatal(err)
        }
        exeFile, err := os.Open(exePath)
        if err != nil {
            log.Fatal(err)
        }
        defer exeFile.Close()
        exeELF, err := elf.NewFile(exeFile)
        if err != nil {
            log.Fatal(err)
        }
        myData := exeELF.Section("my_data")
        if myData == nil {
            log.Fatal("my_data section missing")
        }
        data, err := myData.Data()
        if err != nil {
            log.Fatal(err)
        }
        fmt.Println(string(data)) // "lorem ipsum"
    }
    

    compile it and add the section. objcopy takes the section data from a file, I called it "data", it contains "lorem ipsum" and my go binary is "goembed", so I'd do objcopy --add-section my_data=data goembed goembed which will overwrite the goembed binary with a new one but with the new section and when you run the program it prints "lorem ipsum". That section can also be edited with --update-section in a similar way. You could even get rid of the objcopy dependency and just do this section adding/editing yourself in Go.