python

Let's talk about hacking in Linux

Unlike Windows, who is well known for keeping backward compatibility, most Linux distros simply don't care about this, it's very common that different distros use different system utilities, different libraries, even the most critial one --- C standard library.

Most Linux distros are based on GNU libc (Glibc), and Glibc itself frequently breaks compatibility, as a result, it's very unlikely that your dynamically linked ELF binaries can run with a different version of Glibc, the first error you might encounter will be something like "libc.so.x.x not found". Even if you managed to pack the whole ELF as static binary, Glibc can fail due to "kernel too old" error as well.

I think now you know why things like snap and flatpak or even docker exist.

Then let's talk about python. I'm sure you know why we need python in Linux post-exploitation, it's simple, because again, unlike Windows, Linux systems don't have a persistent, reliable, compatible scripting engine available, even if there were *sh and perl, you will find it very soon that you need dependencies that your target hosts are very unlikely to have, for example libssl.so (depends on Glibc again, my friend).

Although there are many versions of python interperters (C, Java, Dotnet, Rust, Go, etc), the most used one is still CPython, and many many python programs depend on C bindings, which bring us a question, how is it possible to pack our python application once and let it run on all major Linux environments, given that Glibc is such a pain in the ass?

The answer, my friend, is not to use Glibc at the first place. There are some alternative libc projects available, the most famous one is known as musl, even better, there's a Linux distro that is completely musl-based, it's called Alpine.

Binaries statically linked with musl have no external dependencies, even for features like DNS lookups or character set conversions that are implemented with dynamic loading on glibc. An application can really be deployed as a single binary file and run on any machine with the appropriate instruction set architecture and Linux kernel or Linux syscall ABI emulation layer.

So the solution is to build python under Alpine Linux, and extract everything python needs, so we can re-deploy on any Linux systems that supports musl libc.

Get Python to work

It's not as easy as it seems, getting a statically linked python binary is just a start.

I suggest install the officially packaged python3 from Alpine: apk add python3

Then use staticx to generate a static python binary, if you run it in a system without python pre-configured, you will see something like this:

~ # ./python3
Could not find platform independent libraries <prefix>
Could not find platform dependent libraries <exec_prefix>
Consider setting $PYTHONHOME to <prefix>[:<exec_prefix>]
Python path configuration:
  PYTHONHOME = (not set)
  PYTHONPATH = (not set)
  program name = '/tmp/staticx-bNKnmC/python3'
  isolated = 0
  environment = 1
  user site = 1
  import site = 1
  sys._base_executable = '/tmp/staticx-bNKnmC/python3'
  sys.base_prefix = '/usr'
  sys.base_exec_prefix = '/usr'
  sys.platlibdir = 'lib'
  sys.executable = '/tmp/staticx-bNKnmC/python3'
  sys.prefix = '/usr'
  sys.exec_prefix = '/usr'
  sys.path = [
    '/usr/lib/python39.zip',
    '/usr/lib/python3.9',
    '/usr/lib/lib-dynload',
  ]
Fatal Python error: init_fs_encoding: failed to get the Python codec of the filesystem encoding
Python runtime state: core initialized
ModuleNotFoundError: No module named 'encodings'

Current thread 0x00007f7459e5bb48 (most recent call first):
<no Python frame>

Don't get sad yet, you are about to see more like this later.

Of course, search for PYTHONPATH and PYTHONHOME, by all means, read the fucking docs.

You now know python doesn't just run on its own, it has a whole fucking SDK (who doesn't?), so you copy /usr/lib/python3.9 or something like that.

Now set PYTHONPATH to include that directory, your python can start, you see your shell, but wait, it's very unusable, what happened to arrow keys? And you can't import any third-party packages?

Actually these are two different problems, one, PYTHONPATH needs more than just /usr/lib/python3.9, just open up system python if you are unsure:

Python 3.9.7 (default, Nov 24 2021, 21:15:59)
[GCC 10.3.1 20211027] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import sys
>>> sys.path
['', '/usr/lib/python39.zip', '/usr/lib/python3.9', '/usr/lib/python3.9/lib-dynload', '/usr/lib/python3.9/site-packages']
>>>

Look at that, you need two more paths, one is for C libraries, the other is for python packages, as for the ZIP one, you don't need it, as it's non-existent in system python installation as well.

So reset the PYTHONPATH variable, you open python again, it seems you can import some packages, but some things are still wrong:

~ # PYTHONPATH=`pwd`/python3.9:`pwd`/python3.9/site-packages:`pwd`/python3.9/lib-dynload PYTHONHOME=`pwd`/python3.9 ./python3
Python 3.9.7 (default, Nov 24 2021, 21:15:59)
[GCC 10.3.1 20211027] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import requests
>>> ^[[A

In my testing I used python -vvv to find that python failed to load libreadline.so, in which case it may also fail to load a lot of C libraries that it needs, for example, without libssl.so, you won't be able to perform any HTTPS request.

The solution, is to bring Alpine's C libraries with this python environment, and specify LD_LIBRARY_PATH when running python, just put any libraries it needs in that directory.

And of course you can keep the default library search paths, such as /usr/lib and /lib, but it's better to bring your own as their libraries may not be compatible.

The Caveats

You won't be able to use system applications that are written in python since PYTHONPATH is changed, and also a lot of dynamically linked ELFs, since LD_LIBRARY_PATH is changed.

Don't export those environment variables, and when you need to unset them, just do it

If you need to add more packages to this python environment, python -m pip install won't work well because it invokes lsb_release (why the fuck?!) which is a system python application.

The easy way is to install whatever you need in Alpine Linux, and redo whatever you did to make a new python distribution


Comments

comments powered by Disqus