Modular binary injection framework, successor of libhooker
  • C 88.9%
  • CMake 8.1%
  • Dockerfile 1.1%
  • C++ 0.7%
  • Shell 0.7%
  • Other 0.5%
Find a file
2025-11-12 22:12:11 +01:00
.ci CI: fix Debian Buster mirror 2025-11-12 22:12:11 +01:00
arch ppc32 Linux support 2025-06-15 01:52:08 +02:00
cmake ppc32 Linux support 2025-06-15 01:52:08 +02:00
cmake-modules remove whitespace at EOL 2023-07-09 15:18:53 -04:00
crt fix windows x86 build 2025-06-01 01:41:49 +02:00
doc README: add demo gif 2024-10-20 16:29:14 +02:00
external/frida-gum add newline at end of all text files 2023-07-09 15:18:53 -04:00
os fix linux arm64 build error 2025-06-10 02:22:56 +02:00
patches add .gitattributes to enforce LF line endings 2023-07-09 20:03:58 -04:00
samples pyloader: unload Python only in non-persistent mode 2025-11-07 01:25:01 +01:00
targets hppa 1.1 (32bit parisc) support 2025-06-09 02:10:47 +02:00
test test_basic_injection: don't add double quotes on windows 2025-05-30 01:50:26 +02:00
.cirrus.yml CI: add linux_parisc32_task 2025-06-11 00:01:04 +02:00
.editorconfig add .editorconfig 2023-07-09 19:29:03 -04:00
.git-blame-ignore-revs add .git-blame-ignore-revs 2023-07-09 15:20:00 -04:00
.gitattributes add .gitattributes to enforce LF line endings 2023-07-09 20:03:58 -04:00
.gitignore Removed build artifact pushed by mistake 2020-07-16 23:38:15 +02:00
appveyor.yml appveyor (macOS): disable capstone installation 2025-05-29 05:03:11 +02:00
AUTHORS remove whitespace at EOL 2023-07-09 15:18:53 -04:00
build.sh create installable ezinject SDK 2025-05-24 09:40:17 +02:00
CMakeLists.txt ppc32 Linux support 2025-06-15 01:52:08 +02:00
config.h.in ppc32 Linux support 2025-06-15 01:52:08 +02:00
COPYING Updated README 2021-12-18 19:17:36 +01:00
dlfcn_compat.h hppa 1.1 (32bit parisc) support 2025-06-09 02:10:47 +02:00
elfparse.c fix Android NDK build 2025-05-31 11:46:06 +02:00
elfparse.h Added copyright headers 2021-09-02 02:43:25 +02:00
ezinject.c fix regression: log file path not working 2025-11-07 02:10:07 +01:00
ezinject.h add -p switch to force module persistence 2025-11-07 01:22:27 +01:00
ezinject_arch.h avoid spilling into unsaved stack memory when the stack grows up. 2025-06-09 19:11:16 +02:00
ezinject_common.h first implementation of self-unloading modules 2025-05-30 17:53:27 +02:00
ezinject_compat.h first implementation of self-unloading modules 2025-05-30 17:53:27 +02:00
ezinject_injcode.c fix windows build 2025-06-09 19:54:42 +02:00
ezinject_injcode.h add -p switch to force module persistence 2025-11-07 01:22:27 +01:00
ezinject_injcode_common.c implement injcode file-based logging 2025-05-25 01:56:52 +02:00
ezinject_injcode_glibc.c hppa 1.1 (32bit parisc) support 2025-06-09 02:10:47 +02:00
ezinject_injcode_posix.c partially decouple injcode_call from injected_fn (first step) 2025-06-09 17:42:16 +02:00
ezinject_injcode_posix.h hppa 1.1 (32bit parisc) support 2025-06-09 02:10:47 +02:00
ezinject_injcode_posix_common.c hppa 1.1 (32bit parisc) support 2025-06-09 02:10:47 +02:00
ezinject_injcode_uclibc.c build fixes 2025-06-09 02:42:43 +02:00
ezinject_injcode_util.c build fixes 2025-06-09 02:42:43 +02:00
ezinject_injcode_windows.c partially decouple injcode_call from injected_fn (first step) 2025-06-09 17:42:16 +02:00
ezinject_injcode_windows.h build fixes 2025-06-09 02:42:43 +02:00
ezinject_injcode_windows_common.c implement injcode file-based logging 2025-05-25 01:56:52 +02:00
ezinject_module.h first implementation of self-unloading modules 2025-05-30 17:53:27 +02:00
ezinject_util.c hppa 1.1 (32bit parisc) support 2025-06-09 02:10:47 +02:00
ezinject_util.h fix android NDK build error 2025-06-09 03:33:08 +02:00
ezpatch.c fix Android NDK build 2025-05-31 11:46:06 +02:00
hppa_patch_calls.php build fixes 2025-06-09 02:42:43 +02:00
log.c log: add buffered flag to make it possible to use unbuffered logging 2025-05-28 02:02:14 +02:00
log.h log: add buffered flag to make it possible to use unbuffered logging 2025-05-28 02:02:14 +02:00
README.md ppc32 Linux support 2025-06-15 01:52:08 +02:00
win32_syscalls.h Windows: dropped VirtualAllocEx and ntdll requirements 2023-10-29 16:12:24 +01:00

ezinject

poc

Modular binary injection framework Join us on Discord

What is ezinject

ezinject is a lightweight and flexible binary injection framework. it can be thought as a lightweight and less featured version of frida.

It's main and primary goal is to load a user module (.dll, .so, .dylib) inside a target process. These modules can augment ezinject by providing additional features, such as hooks, scripting languages, RPC servers, and so on. They can also be written in multiple languages such as C, C++, Rust, etc... as long as the ABI is respected.

NOTE: ezinject core is purposedly small, and only implements the "kernel-mode" (debugger) features it needs to run the "user-mode" program, aka the user module.

It requires no dependencies other than the OS C library (capstone is optionally used only by user modules)

Porting ezinejct is simple: No assembly code is required other than a few inline assembly statements, and an abstraction layer separates multiple OSes implementations.

As proof, it has been ported and battle-tested on a wild variety of Linux devices such as:

  • Asus DSL-N55U D1, a Mips BE DSL modem running uClibc and Linux 2.6
  • ADB/DLink DVA-5592, an ARM v7 FPU-less xDSL modem running uClibc and Linux 3.0
  • Samsung GT-i9003 (latona), an Android 2.3 smartphone running Linux 2.6
  • Samsung GT-i9070 (janice), running Android 4
  • Samsung GT-i9300 (smdk4x12), running Android 7
  • pocophone F1 (beryllium), running Android 10
  • TomTom GO 910, a standalone GPS nav running glibc 2.3 and Linux 2.6

as well as wildly different (both POSIX and non-POSIX OSes) such as

  • Windows
  • FreeBSD
  • Darwin (macOS)

Example of modules:

and so on...

Modules ABI

Shared modules must implement the following 2 functions:

int lib_preinit(struct injcode_user *user)

This function is provided to let the user control the module lifecycle.

For example, by setting user->persist to 1, the module can be kept persistent in memory once lib_main returns.

This function should always return 0 to signal success.

int lib_main(int argc, char *argv[])

It works just like a typical main function in C.

argv[0] holds the name of the module, while argv[1] onwards are user arguments that you passed to the ezinject binary (they are passed to the module as user supplied arguments)

NOTE: lib_main is executed synchronously, and ezinject will not complete until this function has returned. If you need to perform background work, you will need to make a copy of argv (important, as it will be freed upon return) and start a new thread. See the php module for an example

Supported Architectures:

  • Linux:

    • arm (arm+thumb)
    • aarch64
    • mips
    • x86
    • amd64
    • powerpc32
    • powerpc64
    • riscv64
    • hppa1.1
  • Windows: x86, x64

  • FreeBSD: x86, x64

  • Darwin: x64

Supported C Libraries:

  • Linux
    • glibc
    • uClibc (tested on ARM, MIPS)
    • Android (tested on Android 2.x - 10.x)
  • FreeBSD (tested on FreeBSD 12)
  • Windows
    • Win32 (tested on Windows 95)
    • NT (tested on Windows NT 3.51, Windows XP, Windows 10 64bit)
  • Darwin (tested on macOS 11)

How does it work

ezinject implements a single instrumentation primitive: remote calls

We proceed as following:

  • Create a remote memory segment that will hold the payload
    • If using shared memory, use remote syscalls to attach the shared memory in the target process
  • Invoke the payload remotely.

The stack at entry will contain a pointer to the context, and a pointer to the function to call.

  • The payload pops the parameters and the function to call from the stack, then calls the function in C (thus emitting a proper call with a stack frame)
  • The payload implementation creates a mutex/event, then opens the target library and awaits for the thread to be created.
  • The ezinject's crt (linked in the library) creates a local copy of the context, then creates a new thread.
  • The crt signals that the thread is ready to be awaited
  • The newly created thread prepares argv, then invokes lib_preinit and lib_main functions in the library
  • The user code is invoked. It can call any function inside the target, replace or hook functions (with libhooker as part of the CRT, in userland)

Build

The following is an example on Debian and derivates, needs to be adjusted for each platform.

  1. Install dependencies
  • build-essential
  • cmake
  • libcapstone-dev
  • pkg-config
  1. Build the project
./build.sh

Sample usage

Linux .so injection

On Terminal 1

$ cd build/samples/dummy
$ ./target

On Terminal 2

$ cd build
$ sudo ./ezinject `pidof target` samples/dummy/libdummy.so

Expected output

return1() = 1

changes to

return1() = 13370

Python injection

echo "print('hello ' * 3 + 'from python')" > hello.py
export EZPY=`python -c "import sys; import os; print(os.pathsep.join(sys.path))"`
echo "python path: $EZPY"

Find libpython:

find /usr/lib -name "libpython*"

Put correct libpython and paths in example below:

sudo ./ezinject `pidof target` samples/pyloader/libpyloader.so /usr/lib/x86_64-linux-gnu/libpython2.7.so.1 /usr/lib/python2.7 "$EZPY" hello.py

Credits

This project has initially been created for the openlgtv community, of which i'm a member. It has since evolved to be a generic tool

Thanks to all members of the openlgtv and webosbrew community for supporting the development by adopting and testing ezinject.

Special thanks to:

  • irsl, for the initial work on libhooker, which inspired me to get involved
  • mudkip908, for the preliminar ezinject proof of concept and code review

If you would like to support the ezinject development, you can

  • use it and spread the word
  • submit issues, suggestions, or pull requests
  • if you feel like, you can donate to me: [paypal]