It's been a while.
Believe it or not I have not been on a distance island selling 0days and stocks options.
While working on some python internals and source code review, I thought, how easy would it be to backdoor python after OS exploitation or some other form of access?
The answer is "really easy." But you want to stay hidden, not in plain sight, and within the constructs of what is already on disk. This assumes you have root access and that the machine has python installed.
And it is all via the pyc file. I'm not talking about patching python itself with BDF. That could be easier to catch.
Also before I forget, most of this blog post was written before I found the right google search term for prior research and work. Here's what I found:
The prior work included in the Pytroj github repo includes pyc infection of bytecode, that infects other pyc bytecode in the loaded program, for python27.
As most of you know, pyc files are python bytecode that is either created by importing the file into an interpreter, or another file, or by calling python -m compileall [path]..
... or py_compile...
When a python file is called and a pyc file is present for an existing py file, python will check the timestamp four bytes into the pyc file and if this timestamp equals the timestamp of the parent py file modified time, then it will not over write the pyc file. That's it. That's all the integrity checks for python27. Python3.X adds a check to see that the size is correct. You can simply copy the size over from the old pyc file to your new pyc file. That's really the only time it is checked. To restore the child pyc file you need to either delete the pyc file or modify the parent. Running py_compile will not modify the child pyc as the timestamp will be the same as the parent.
|Checking Modified Timestamps|
The POC that I have is for python 2.7 and python3.X. And you can get it here https://github.com/secretsquirrel/backdoor-pyc/
You may be asking yourself so what? Someone could just update the python code directly.
You are right. Here have a cookie.
However, when was the last time you decompiled your python bytecode because you thought it was modified from the original python file?
OK. How does it work?
I've selected ./Lib/utf_8.pyc as my code to patch in python2.7. Why? Because when looking at the loading of python via python -v it's one of the later modules to load. And because of that, I can modify it with a payload, and most of the modules that I need are already available to use.
For python3.X, you are better off going with the rlcompleter.py.
I made a simple payload from of a python shell (from trustedsec) and added multiprocessing to it so that python will execute after the shell has been spawned.
First, the parent file is copied to /tmp/ and the POC is appended to the end of the py file. Then it is py_compiled to pyc. Next the timestamp and size (if 3.X) is modified to match the parent file. Finally the pyc file is copied to the original location under the parent file, or in __pycache__ in python3.X.