The environment variables DYLD_INSERT_LIBRARIES
(macOS) and LD_PRELOAD
(Linux) let you instruct the dynamic linker to load dynamic libraries that are
not normally part of an application. Furthermore, the libraries specified are
loaded before any other libraries. This technique, which I will hereafter
refer to as “library injection”, provides a lot of creative ways to modify and
exploit software.
Hijacking dynamically linked functions
First and foremost, library injection allows you to override the implementation of dynamically linked functions. Sometimes larger (typically cross-platform) applications extract most of their business logic into a separate library. This extraction opens up an attack surface in which we can reimplement functions at our convenience.
Assume we are targeting a commercial application with the intent of extending
the trial period by one week. From our analysis we know the application calls a
(dynamically-linked) function, days_left
, to get the number of days left in
the trial. The code below shows how we can reimplement this function while also
preserving the implementation of the old one.
#include <dlfcn.h>
#include <unistd.h>
int (*old_days_left)(void) = NULL;
int days_left(void) {
if (!old_days_left) {
old_days_left = dlsym(RTLD_NEXT, "days_left");
}
return old_days_left() + 7;
}
Simply compile this code as a shared library and use the environment variables mentioned earlier to load it. The steps to do this on macOS look like this:
$ clang -shared extend.c -o libextend.dylib
$ DYLD_INSERT_LIBRARIES=./libextend.dylib ./example_application
When our library is injected into the application, our implementation will be called instead, and we can enjoy a longer trial period.
See the man page for dlsym
to better understand how the code above works. On
macOS you may need to use DYLD_FORCE_FLAT_NAMESPACE=1
to ensure your
implementation is used, and I’ve heard you may need to disable System Integrity
Protection as well.
Persistent injection on macOS
Should we want to make our changes from the previous examples permanent, we could replace the app’s main executable with a script which launches the application with our library injected.
To do this, rename the main executable (you can just put an underscore in front if it), and create an empty text file with the main executable’s old name. Open the file in a text editor and add the following:
#!/bin/sh
CONTENTS=$(dirname "${0}")
CONTENTS=$(cd "${CONTENTS}/.."; pwd)
# Here is where you would configure your injection. Copy your compiled library
# to somewhere inside the app bundle, such as the Frameworks directory.
export DYLD_INSERT_LIBRARIES="$CONTENTS/Frameworks/libextendedtrial.dylib"
# If your application was called "HelloWorld", make that this script's name.
"$CONTENTS/MacOS/_HelloWorld" "[email protected]"
Finally, make the script executable with chmod +x HelloWorld
. Now you should
be able to open the application normally, via the Dock, Spotlight, etc. and
your library will always be loaded. Note that some applications manually verify
their code signature, etc. and this method will not work in some of those
cases.