Skip to content

Instantly share code, notes, and snippets.

@shajra
Last active January 28, 2017 15:42
Show Gist options
  • Save shajra/afed560778a556e487b201b5b4c2ad52 to your computer and use it in GitHub Desktop.
Save shajra/afed560778a556e487b201b5b4c2ad52 to your computer and use it in GitHub Desktop.
Nix work to replace strings in a binary
source "$stdenv/setup"
mkdir -p "$out/bin"
cp "$src/strings-replace" "$out/bin"
wrapProgram "$out/bin/strings-replace" \
--prefix PATH : "${binutils}/bin" \
--prefix PATH : "${coreutils}/bin" \
--prefix PATH : "${gnugrep}/bin"
{ stdenv
, binutils
, coreutils
, gnugrep
, makeWrapper
}:
stdenv.mkDerivation {
name = "strings-replace";
buildInputs = [ makeWrapper ];
src = ./.;
inherit binutils coreutils gnugrep;
builder = ./builder.sh;
}
#!/bin/sh -eu
PROG_NAME="$0"
USAGE="$PROG_NAME [-h] PATTERN REPLACEMENT TARGET_BINARY_FILE
Replace references as detected by the 'strings' command in a binary file.
The replacement will be null terminated and overlaid on top of the
original reference (in-place). This means the replacement string can not
be longer than the string replaced, which this script checks for.
"
main()
(
while getopts h o
do
case $o in
h) echo "$USAGE"; exit 0;;
esac
done
shift $((OPTIND - 1))
set +u
if [ -z "$1" -o -z "$2" -o -z "$3" ]
then echo "$USAGE"; exit 1
fi
set -u
target="$1"
pattern="$2"
replacement="$3"
work "$target" "$pattern" "$replacement"
)
work()
(
target="$1"
pattern="$2"
replacement="$3"
replacement_file="$(make_replacement_file "$replacement")"
trap "rm $replacement_file" INT QUIT TSTP TERM EXIT
offsets="$(get_offsets "$target" "$pattern" "$replacement")"
for offset in $offsets
do replace "$replacement_file" "$offset" "$target"
done
)
get_offsets()
(
target="$1"
pattern="$2"
replacement="$3"
strings -t d "$target" \
| grep -e "$pattern" \
| while read -r line
do
offset="$(echo "$line" | cut -d ' ' -f 1)"
orig="$(echo "$line" | cut -d ' ' -f 2)"
if [ "${#orig}" -lt "${#replacement}" ]
then
msg="replacement too long"
msg="${msg}\n orig: ${orig}"
msg="${msg}\n new: ${replacement}"
fail "$msg"
fi
echo "$offset"
done
)
make_replacement_file()
(
replacement="$1"
tmpfile="$(mktemp)"
printf "$replacement\0" > "$tmpfile"
echo "$tmpfile"
)
replace()
(
replacement_file="$1"
offset="$2"
target="$3"
dd if="$replacement_file" of="$target" obs=1 seek="$offset" conv=notrunc
)
fail()
(
msg="$1"
echo "ERROR: $msg" > /dev/stderr
exit 1
)
main "$@"
@shajra
Copy link
Author

shajra commented Jan 28, 2017

I was statically linking a Haskell binary with Nix, because when you dynamically link, the transitive closure of all the dependencies in /nix/store can be very large.

However, because ekg uses Cabal "data-files", hard references to the ekg shared library in /nix/store get compiled into the resultant binary.

So I made another derivation of ekg that pruned away everything but the files needed at runtime. And I use the work above to replace
the reference to the original ekg in /nix/store with my new one:

strings-replace "$path_to_binary" '/nix/store/.*ekg' "$new_path_in_nix_store"

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment