💉 Hacky way to inject .so into remote processes

4 min read

linuxmemory-hackinghackingc++

There are plenty of methods to inject shared objects (.so files) into remote processes on Linux (ptrace, LD_PRELOAD, etc...) but in this post I will show an unusual and hacky way to achieve this.

Prerequisites

  • Minimal knowledge how GNU/Linux works.
  • GNU C++ compiler (usually included in your Linux distribution build tools).
  • strace tool.

Creating shared object

At the very beginning, we have to create our shared object that will be injected into remote process. Here is example code how I did this:

// test.cpp
#include <iostream>
#include <thread>

std::thread thread;
// Your code that will be executed in remote process.
void constructor() {
  printf("\n\nfarewell to control\nhey u got hacked\n\n\n");
}

// shared object's entry point
int __attribute__((constructor)) startup() {
  thread = std::thread { constructor };
  return 0;
}

So if our shared object source is already saved to file, we should compile it. Run g++ test.cpp -shared -fPIC -o inject.so to do so.

Lets stop here for a second and analyze this command and discuss what are these weird compiler flags...

  • g++ is command for executing GNU C++ compiler, obviously.
  • test.cpp is our input translation unit for the compiler.
  • -shared flag specifies that compiled binary will be shared object.
  • -fPIC flag tells the compiler that compiled machine code must not be dependent on being located at specific address in order to work, so our shared object can be loaded at any address in process' memory.
  • -o inject.so is nothing else than telling compiler where output file will be created and how it is gonna be called.

Injection trick

Here comes the stuff of this post. We want to find shared objects that our process is unsuccessfully trying to load. strace tool is perfect for determining that.

The program that will be analyzed is sysinfo (graphical tool that is able to display some hardware and software information about the computer it is run on) available to download here.

To save sysinfo' system calls trace output to text file, we need to execute this command: strace sysinfo &> sysinfo_trace.log.

Now, after opening the sysinfo_trace.log in our favorite text editor (Visual Studio Code) in my case we should search for lines like these:

[...]
access("/home/neg4n/.gtk-2.0/2.10.0/x86_64-pc-linux-gnu/modules/libgail.so", F_OK) = -1 ENOENT (No such file or directory)
access("/home/neg4n/.gtk-2.0/2.10.0/x86_64-pc-linux-gnu/modules/libgail.la", F_OK) = -1 ENOENT (No such file or directory)
access("/home/neg4n/.gtk-2.0/2.10.0/modules/libgail.so", F_OK) = -1 ENOENT (No such file or directory)
access("/home/neg4n/.gtk-2.0/2.10.0/modules/libgail.la", F_OK) = -1 ENOENT (No such file or directory)
access("/home/neg4n/.gtk-2.0/x86_64-pc-linux-gnu/modules/libgail.so", F_OK) = -1 ENOENT (No such file or directory)
access("/home/neg4n/.gtk-2.0/x86_64-pc-linux-gnu/modules/libgail.la", F_OK) = -1 ENOENT (No such file or directory)
access("/home/neg4n/.gtk-2.0/modules/libgail.so", F_OK) = -1 ENOENT (No such file or directory)
access("/home/neg4n/.gtk-2.0/modules/libgail.la", F_OK) = -1 ENOENT (No such file or directory)
[...]

We can clearly see that there are shared objects that could not be loaded because they don't exist in these paths. Our mission is to move our inject.so from previous section to one of these paths and rename it to look like module that was unsuccessfully loaded.

The last steps that we have to do:

  • Create module directory by executing mkdir -p $HOME/.gtk-2.0/2.10.0/modules/
  • Copy our shared object with changes name to this directory by executing cp inject.so $HOME/.gtk-2.0/2.10.0/modules/libgail.so

If everything is done, we can finally launch sysinfo.

neg4n@dev:~$ sysinfo
Gtk-Message: 00:01:19.327: Failed to load module "pantheon-filechooser-module"


farewell to control
hey u got hacked


Gtk-Message: 00:01:19.329: Failed to load module "gail"
Gtk-Message: 00:01:19.336: Failed to load module "canberra-gtk-module"

** (Sysinfo:4314): WARNING **: 00:01:19.336: (../atk-adaptor/bridge.c:993):atk_bridge_adaptor_init: runtime check failed: (root)

after starting new instance of sysinfo, we can see that there is message from our shared object in the application's stdout.

Ending speech

The following way to inject shared objects is very hacky way to do it and its presented here for educational and maybe entertainment purposes. I don't believe that it can find any serious usages because of two main drawbacks:

  1. Replacing originally intended to be loaded library with our shared object isn't limited per application and not cleaning it after injection will cause every software that tries to load this library to print message from our inject.so or even will prevent it from working.

  2. Doing everything takes time and its rather hard manual task than automated solution.

But after all, i hope that you enjoyed reading my first blog post and learned something new. If you have any questions, don't hesitate to pm me via email!

Did you find this article helpful?

Share it with the world!