Skip to content

Compile Static Binaries

Creating static binaries easily, repeatably, and on your own.

Each section was originally part of other notes taken over the last few years. Initially these were put together to build static binaries when competing in a live cyber attack-defense event, where systems are already assumed to be compromised in some way. Bringing your own static binaries along with uac is a really effective IR technique.

Eventually static builds came up again, recently with pspy, just to understand golang and using Docker to build software. This is an effort to centralize all of these notes for public reference.

Why?

The first time ever using naabu and seeing how it could work after being moved to other systems, was what prompted creating the notes in this post. It's also a great way to learn more about reviewing and modifying code.

  1. Binaries linked to shared libraries will often only run on the system(s) they were compiled on. Static binaries bake in those dependencies and have a level of portability to them.
  2. Static binaries of common tools are often most useful for pivoting, where you have shell access to a system and you need the capability to do something local to that machine without the ability to compile anything on it.
  3. Customization and signature evasion
  4. Compiling for other architectures and systems
  5. Most importantly, to trust the tool. For some tools, you can pull the source directly from the Linux repositories which already have to be trusted to some degree. Almost none of those projects release static binaries by default, and this is easier than reverse engineering existing unofficial binaries from third-parties if any exist.

Getting Started

Work in Progress

The notes in this post as of right now are close to their original state, and need to be updated to use musl and Docker in the build process.

These resources are necessary starting points to succeed here.

The andrew-d/static-binaries repository has a great approach that's fairly easy to understand and learn from. Familiarity with docker, CICD, and devops in general also helps; a lot of modern projects will include a dockerfile among other build tools to help you compile things from source. Part of the purpose of this post is to help unwind and determine how these are working, to modify and use them, and ultimately build our own.

Using makefiles

Essentially, these are the steps you'll encounter with makefiles:

./configure # There's often a configuration shell script
make        # Compiles the code
make clean  # Revert project directory to state before running make, great if you encounter errors or want to reconfigure and build again

Handling Compile Errors

When handling errors note:

  • Which header file, or library, is causing the error(s)
  • Does it require different dependancies
  • Does something specific in the configuration of the build need to be changed

musl-libc

What uses musl?

In addition to the andrew-d/static-binaries project, threathunters-io/laurel also uses musl to build static binaries for laurel.

⚠️ TO DO ⚠️

openssl

OpenSSL is a protocol implementation and library for cryptography. Specifically from the project README:

OpenSSL is a robust, commercial-grade, full-featured Open Source Toolkit for the TLS (formerly SSL), DTLS and QUIC protocols.

The protocol implementations are based on a full-strength general purpose cryptographic library, which can also be used stand-alone. Also included is a cryptographic module validated to conform with FIPS standards.

OpenSSL is descended from the SSLeay library developed by Eric A. Young and Tim J. Hudson.

The official Home Page of the OpenSSL Project is www.openssl.org.

These are the main resources we'll need for reference to build OpenSSL:

Where to download from?

The downloads on https://openssl-library.org/source/ point to https://github.com/openssl/openssl/releases.

Clone the library source, detached signature, checksums, and finally obtain the signing key to verify the file integrity.

# Install jq, if you want to use this block
if ! command -v jq; then sudo apt update; sudo apt install -y jq; fi

# Obtain the latest release info
gh_release_info="$(curl -Lf https://api.github.com/repos/openssl/openssl/releases/latest)"
latest_version="$(echo "$gh_release_info" | jq -r '.tag_name')"

# Download the files
curl -LfO "https://github.com/openssl/openssl/releases/download/${latest_version}/${latest_version}.tar.gz"
curl -LfO "https://github.com/openssl/openssl/releases/download/${latest_version}/${latest_version}.tar.gz.asc"
curl -LfO "https://github.com/openssl/openssl/releases/download/${latest_version}/${latest_version}.tar.gz.sha256"

# Obtain the signing key
# pub   rsa4096/0x216094DFD0CB81EF 2024-04-08 [SC] [expires: 2026-04-08]
#       Key fingerprint = BA54 73A2 B058 7B07 FB27  CF2D 2160 94DF D0CB 81EF
# uid                   [ unknown] OpenSSL <openssl@openssl.org>
gpg --keyserver hkps://keyserver.ubuntu.com:443 --recv-keys 'BA5473A2B0587B07FB27CF2D216094DFD0CB81EF'

# Integrity checks
sha256sum -c "${latest_version}.tar.gz.sha256" --ignore-missing
gpg --verify "${latest_version}.tar.gz.asc" "${latest_version}.tar.gz"

# Build the library
tar -xvzf "${latest_version}.tar.gz"
cd "${latest_version}"
./Configure LDFLAGS=-static no-shared
make

This will result in a static binary for OpenSSL under apps/openssl,

nmap

OpenSSL Dependancy

You may want to compile a static version of OpenSSL first and point to it with --with-openssl=<directoryname> when building a static nmap binary.

Obtain and verify source (nmap.org)

# Set which versions you'd like to compile
nmap_version='7.97'
openssl_version='3.5.0'

# Download the source archive, and signature information
wget "https://nmap.org/dist/nmap-${nmap_version}.tar.bz2"
wget "https://nmap.org/dist/sigs/nmap-${nmap_version}.tar.bz2.asc"
wget "https://nmap.org/dist/sigs/nmap-${nmap_version}.tar.bz2.digest.txt"

# If you don't already have nmap's signing key, you'll see the abbreviated signature you can use to obtain it
# Check these places to verify the signature fingerprint:
# https://nmap.org/book/install.html#inst-integrity
# https://svn.nmap.org/nmap/docs/nmap_gpgkeys.txt
# https://keyserver.ubuntu.com/pks/lookup?search=436D66AB9A798425FDA0E3F801AF9F036B9355D0&fingerprint=on&op=index
#
# pub   dsa1024/0x01AF9F036B9355D0 2005-04-24 [SC]
#       Key fingerprint = 436D 66AB 9A79 8425 FDA0  E3F8 01AF 9F03 6B93 55D0
# uid                   [ unknown] Nmap Project Signing Key (http://www.insecure.org/)
# sub   elg2048/0x44AEF5D7A50A6A94 2005-04-24 [E]

# Obtain the key with:
gpg --keyserver hkps://keyserver.ubuntu.com:443 --recv-keys '436D 66AB 9A79 8425 FDA0 E3F8 01AF 9F03 6B93 55D0'

# Verify:
gpg --verify "nmap-${nmap_version}.tar.bz2.asc" "nmap-${nmap_version}.tar.bz2"

# Compare hashes:
cat "nmap-${nmap_version}.tar.bz2.digest.txt"
gpg --print-md sha256 "nmap-${nmap_version}.tar.bz2"

Obtain and verify source (apt)

This may not work, try obtaining nmap from nmap.org if it fails.

Instructions in Kali

Interestingly, as of Kali in 2025 and possibly earlier, you'll get a message for some packages when downloading the source code via apt:

$ apt source nmap
NOTICE: 'nmap' packaging is maintained in the 'Git' version control system at:
https://gitlab.com/kalilinux/packages/nmap.git
Please use:
git clone https://gitlab.com/kalilinux/packages/nmap.git
to retrieve the latest (possibly unreleased) updates to the package.
# Add the source repo in Kali, adjust this based on the Linux distro
echo "deb-src http://http.kali.org/kali kali-rolling main contrib non-free non-free-firmware" | sudo tee -a /etc/apt/sources.list
sudo apt update

# Download the source code
cd ~/Download
apt source nmap
sudo apt-get build-dep nmap

# Check the gpg and sha256 signatures:
gpg --verify "nmap_${nmap_version}+dfsg1-1kali1.dsc"
# If you're missing the key, get it from the ubuntu keyserver and verify again
gpg --keyserver hkps://keyserver.ubuntu.com:443 --recv-keys '<key-id>'
gpg --verify "nmap_${nmap_version}+dfsg1-1kali1.dsc"
cat "nmap_${nmap_version}+dfsg1-1kali1.dsc" # Take the signature for the archive you want to verify
sha256sum nmap_<version>.tar.xz | grep <sha256sum>

Prepare and compile nmap:

# If you obtained it from apt:
tar -xf "nmap_${nmap_version}+dfsg1.orig.tar.xz"
# If you obtained it from nmap.org:
bzip2 -cd "nmap-${nmap_version}.tar.bz2" | tar xvf -

cd "nmap-${nmap_version}"

# Don't build the libpcap.so file, this was discovered from andrew-d/static-binaries
sed -i -e 's/shared\: /shared\: #/' libpcap/Makefile

./configure --help # See the configuration options
./configure LDFLAGS=-static --with-liblua=included --with-openssl=../openssl-${openssl_version} --with-pcap=linux --without-ndiff --without-zenmap --withou-nmap-update
make -j4

If you encounter an error and need to attempt another build be sure to run:

make clean

Socat

Add the Kali source repo:

echo "deb-src http://http.kali.org/kali kali-rolling main contrib non-free non-free-firmware" | sudo tee -a /etc/apt/sources.list
sudo apt update

Download the source code

cd ~/Download
apt source socat
sudo apt-get build-dep socat

Check the gpg and sha256 signatures:

gpg --verify socat_1.7.4.4-2.dsc
# If you're missing the key, get it from the ubuntu keyserver and verify again
gpg --keyserver hkps://keyserver.ubuntu.com:443 --recv-keys '<key-id>'
gpg --verify socat_1.7.4.4-2.dsc
cat socat_1.7.4.4-2.dsc # Take the signature for the archive you want to verify
sha256sum socat_<version>.tar.gz | grep <sha256sum>

Compile a static binary:

rm -rf socat_<version> # We want to unpack and use files from the archive we verified hashes on
tar -xzf socat_<version>.tar.gz
cd socat_<version>
./configure LDFLAGS=-static # This works here, while --enable-static does not
make

After some errors and warnings, you'll have a static socat binary for transfer.

pspy

pspy is a command line tool designed to snoop on processes without need for root permissions. It allows you to see commands run by other users, cron jobs, etc. as they execute. Great for enumeration of Linux systems in CTFs. Also great to demonstrate your colleagues why passing secrets as arguments on the command line is a bad idea.

The tool gathers the info from procfs scans. Inotify watchers placed on selected parts of the file system trigger these scans to catch short-lived processes.

Go uses go.mod to version-pin dependencies. The go.sum file is the cryptographic signature of each dependency, which is also mirrored in a global signature database hosted by Google. This should help with understanding how dependencies can be verified.

The build instructions in the README don't seem to be current, as there is no build-buid-image in the Makefile. Instead, line 44 is what builds the "build" container, and it's commented out. We can change the variables to work with bash, and create the build container:

# Clone the Kali fork, or the upstream source on GitHub if you prefer
git clone git@github.com:DominicBreuker/pspy.git pspy-github
git clone git@gitlab.com:kalilinux/packages/pspy.git pspy-gitlab
cd pspy-gitlab/

# Build the build image, you have to run this manually, it's commented out in the Makefile
BUILD_IMAGE='local/pspy-build:latest'
BUILD_DOCKERFILE="docker/Dockerfile.build"
docker build -f "$BUILD_DOCKERFILE" -t "$BUILD_IMAGE" .

# Build all 4 binaries
make build

Now if we want to build binaries to work on arm, it's very easy to do so by duplicating one of the build sections, and setting GOARCH=arm64 or GOARM=5 GOARCH=arm.

Go and ARM

https://go.dev/wiki/GoArm

Go is fully supported on Linux and Darwin. Any Go program that you can compile for x86/x86_64 should work on Arm. Besides Linux and Darwin, Go is also experimentally supported on FreeBSD, OpenBSD and NetBSD.

With that in mind, here's a section that was copied to swap out amd64 for arm64, while also renaming the output file to bin/pspy64a:

docker run -it \
            --rm \
            -v $(PROJECT_DIR):/go/src/github.com/dominicbreuker/pspy \
            -w "/go/src/github.com/dominicbreuker/pspy" \
            --env CGO_ENABLED=0 \
            --env GOOS=linux \
            --env GOARCH=arm64 \
            $(BUILD_IMAGE) /bin/sh -c "go build -a -ldflags '-s -w -X main.version=${VERSION} -X main.commit=${BUILD_SHA} -extldflags \"-static\"' -o bin/pspy64a main.go"

Add this to the Makefile, and run make build to compile a static arm64 binary in addition to the others.

⚠️ TO DO ⚠️

yara

⚠️ TO DO ⚠️

busybox

⚠️ TO DO ⚠️