BYOBin
Quick article on how to build self contained binaries for rust. This is mainly because I had to do this for a pentest recently, because the environment was a very minimal container which only had about 5 binaries in it including bash...so pretty locked down and was missing almost all the libraries needed to do anything.
Most everything I needed I could do in bash, but there were a few things extra that I couldn't do very easily. So, I thought golang and rust are the perfect tools for this since they allow binaries to be self-contained (or I believe the technical description of it is called statically linked (instead of dynamically linked which relies on libraries)). While golang doesn't appear to normally need any extra work (because they are self-contained by default) rust on the other hand wasn't as straight forward for me. So, I'm posting this so I can remember in the future how to do it.
Lets for example take this project and make it self-contained:
My first attempt was to try and compile like how this article mentioned against Glibc/GCC, but unfortunately it error'ed out at the last second...maybe if I had more time I could have figured it out.
Command ran: RUSTFLAGS='-C target-feature=+crt-static' cargo build --release --target x86_64-unknown-linux-gnu
result:
Since I was on a tight time crunch though I attempted to build against musl instead since it was supposed to be the more tested way of building. Fortunately the exa project was able to build against musl (or at least said how to in their documentation). Now for some reason while I'm writing this post it isn't giving me the same error as it did, and compiled completely fine...to highlight the error I was expecting I swapped over to this project and manually ran their build command from their Makefile. When doing that it caused this error.
So, I ran the command that it told me to: rustup target add x86_64-unknown-linux-musl
and then everything worked fine...but again wasn't supposed to...so I ran a make clean
command in that same project and it ran cargo clean
...which is probably why everything was working when it wasn't supposed to 🤦
After that it produced the error I wanted when I went to build again 😅
So, I looked for musl
in the apt repository: apt update && apt search musl
looked at the info for musl-tools
( apt info musl-tools
), which showed it'll install musl
and gcc
so I figured that would probably work. Next I installed it with: apt install -y musl-tools
. Then everything worked as expected (again 🙃) 😁. The binary was placed here: target/x86_64-unknown-linux-musl/release/procs
Summary
So, to summarize if you want to build against musl in the official rust docker image, then you'll need to do the following:
rustup target add x86_64-unknown-linux-musl
apt update && apt install -y musl-tools
These were the commands I used to launch the containers I used to build things in. I did this instead of virtual machine so I could use all 24 cores on my laptop to build against compared to whatever subset the VM would have. I also don't like to clutter up my host OS, which is why I used containers and didn't install directly onto my host to run things.
# rust container for builds
podman container run --name rust_builds -d -v "${PWD}":/app -w /app docker.io/rust sleep infinity
# exec into
podman exec -it rust_builds bash
# golang container for builds
podman container run --name golang_builds -d -v "${PWD}":/app -w /app docker.io/golang sleep infinity
# specific version of golang
podman container run --name golang_builds_1.14 -d -v "${PWD}":/app -w /app docker.io/golang:1.14 sleep infinity
# exec into container
podman exec -it golang_builds bash
I imagine this will only increase in prevalence since more and more container technology is getting used. So, here are some of my bash commands I use when in containers to do unconventional things.
# download a binary (like wget or curl)
# src: https://unix.stackexchange.com/a/421403
function __download() {
read proto server path <<< "${1//"/"/ }"
DOC=/${path// //}
HOST=${server//:*}
PORT=${server//*:}
[[ x"${HOST}" == x"${PORT}" ]] && PORT=80
exec 3<>/dev/tcp/${HOST}/$PORT
# send request
echo -en "GET ${DOC} HTTP/1.0\r\nHost: ${HOST}\r\n\r\n" >&3
# read the header, it ends in a empty line (just CRLF)
while IFS= read -r line ; do
[[ "$line" == $'\r' ]] && break
done <&3
# read the data
nul='\0'
while IFS= read -d '' -r x || { nul=""; [ -n "$x" ]; }; do
printf "%s$nul" "$x"
done <&3
exec 3>&-
}
# list files (ls) in current directory
echo *
# cat file
function __cat { for i in ${*} ; do echo "$(<"${i}")"; done; }
Other helpful article:
- https://doc.rust-lang.org/reference/linkage.html
- https://dev.to/msfjarvis/building-static-rust-binaries-for-linux-56a
- https://blog.davidvassallo.me/2021/06/10/lessons-learned-building-statically-linked-rust-binaries-openssl/
- https://msfjarvis.dev/posts/building-static-rust-binaries-for-linux/
- List of rust binaries from this podcast episode: https://changelog.com/podcast/451, which lead to this post, and then led to this GH repo: https://github.com/ibraheemdev/modern-unix
- Used for reverse shell: https://medium.com/@sathish__kumar/undetectable-reverse-shell-with-golang-4fd4b1e172c1 (GH repo)