Skip to content
/ xovi Public

Universal extension framework for Linux applications

License

Notifications You must be signed in to change notification settings

asivery/xovi

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

25 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

XOVI - The universal LD_PRELOAD extension framework

What does it do?

XOVI lets you write extensions for applications which do not support it natively. It lets you hook arbitrary functions from the global symbol scope through the use of dlsym. After a global function gets redirected to your function, you have full control over its parameters, return value and behavior. You can invoke or suppress the invocation of the original function - it's your call! Note that XOVI does not allow multiple extensions to hook the same function.

Extensions can also import and export symbols from other extensions - XOVI doubles as a dynamic linker. (If you're importing a symbol from the global scope, XOVI will make sure to give you the unhooked version of the function, even if other extensions hook it.)

Right now XOVI expects to see the following directory structure:

/home/root/xovi
|
|-> extensions.d
|   |
|   \-> all the extension files (ending in .so
|
\-> exthome
    |
    \-> one directory for every extension in extensions.d
        (e.g. fileman.so (in extensions.d) uses the fileman directory here)

How to build it?

Right now, XOVI only supports the AARCH64 architecture. Since I wrote this program for the reMarkable Paper Pro, that is the only tested configuration.

First, setup your cross-compilation environment. Assuming your toolchain is installed in ~/Tools/remarkable-toolchain, run

. ~/Tools/remarkable-toolchain/environment-setup-cortexa53-crypto-remarkable-linux

The toolchain provided by reMarkable will be found by CMake automatically, if your toolchain isn't detected you might need to write a CMake toolchain file.

To build, run:

mkdir build
cd build
cmake ..
make -j$(nproc)

For your convenience, prebuilts for the reMarkable Paper Pro are provided in GitHub Releases.

How does it work?

XOVI iterates over the extensions 4 times, in order to:

  • Find all extensions' exports, imports and overrides (and store them internally) [Pass 1]
  • Define all overrides (hooks) [Pass 2a]
  • Build the links between imports and exports [Pass 2b]
  • Invoke all constructors and resolve the dependency map [Init]

Hooks

In order to hook functions, XOVI inserts trampolines at the start of each hooked function. For AARCH64, the hook consists of the following assembly code:

movx x8, <bits 0-15>
movk x8, <bits 16-31>
movk x8, <bits 32-47>
movk x8, <bits 48-63>
br x8

This code first copies the address of the hook destination function (extension function) into register x8, then jumps to it.

If an extension wants to jump to the unhooked version of a function, XOVI first replaces the trampoline with the function's original code. After that, it replaces the code that follows that with yet another (internal) trampoline and jumps to the function. The second trampoline's job is to remove itself by restoring the original code of the function, and replace the start of the function with the original trampoline, so as to prevent the function from ever being invoked untrampolined. (I am aware of there being a small race condition here, but I haven't managed to come up with a way to prevent it. If you know how to fix this, please open an issue) For more information, see untrampoline.S, aarch64.c.

Writing extensions

To write an extension, you first need to start with writing a description file for xovigen. This file describes all the imports, exports and overrides your extension needs.

Example:

example.xovi:

version 0.1.0       ; Self explanatory
file main.c         ; The directives below apply to the file 'main.c'
import strdup       ; The file requires an unmodified version of 'strdup'
export isDuck       ; The file exports a function called 'isDuck'
override strdup     ; The extension will override (hook) strdup
resource test:a.txt ; Load the contents of 'a.txt' as the variable r$test

condition globalf   ; Only load this extension if there exists a symbol 'globalf'
import? ext$globalf ; Equal to condition someEx..., import someEx...

copy header.h       ; The file header.h will be copied to the temporary build directory
make main.c         ; The autogenerated buildscript will build main.c

Such a description file will only result in the generation of one file - xovi.c (which will contain your module base), which can later be compiled and linked into the final extension's .so file.

This code would correspond to the following extension project:

main.c:

#include "header.h"

bool isDuck(char *string){ // Exports do not need to be marked in any way
    return strcmp(string, "duck") == 0;
}

char *override$strdup(char *string) { // Override functions have to be prefixed with 'override$'
    if(isDuck(string)){
        string = "pigeon";
    }
    return $strdup(string); // Imports are prefixed with a '$' character, if the function comes from the global scope, and use the format `extension$export`, if they come from another extension.
}

header.h:

#include <stdbool.h>
#include <string.h>

If you wish to write an extension in a language different to C, you need to write a 'module base' file, which will act as a bridge between XOVI and the rest of your code. The description file has to reflect that:

version 0.1.0

modulebase base.c
import ...
export ...

To build the extension, run xovigen to generate the temporary build directory:

python3 util/xovigen.py -o /tmp/builddir example.xovi

Running it like this will not generate the buildscript. To get xovigen to also write the buildscript, use this instead:

(This will prepare the extension for the remarkable paper pro)

source ~/Tools/remarkable-toolchain/environment-setup-cortexa53-crypto-remarkable-linux
python3 util/xovigen.py -c "aarch64-remarkable-linux-gcc -D_GNU_SOURCE --sysroot ~/Tools/remarkable-toolchain/sysroots/cortexa53-crypto-remarkable-linux" -m -o /tmp/builddir example.xovi

Then run the buildscript:

/tmp/builddir/make.sh

Happy hacking!