pyconcrete
pyconcrete copied to clipboard
[Security🔐] Leak source code by hacking `marshal.loads` function
We can hack the marshal.loads function to get the pyc file, and then use decompyle3 to decompile and get the python source code.
Environment:
- Python:
3.8.20 - pyconcrete:
pyconcrete "0.15.1" [Python "3.8.20"] - decompyle3:
3.9.2 - OS:
Debian GNU/Linux 12
Files
script.py
def fun():
print('Hello')
fun()
hack.py
import marshal
import copy
hack = copy.deepcopy(marshal.loads)
import imp
def wrapper(*args, **kwargs):
result = hack(*args, **kwargs)
with open('script.pyc','wb') as f:
f.write(imp.get_magic() + b'\x00'*12 + args[0])
return result
marshal.loads = wrapper
from script import *
fun()
Preparation:
~$ pyconcrete-admin.py compile --source=script.py --pye
~$ rm script.py
~$ pyconcrete script.pye
~$ pip install decompyle3
Hack:
~$ python hack.py
~$ decompyle3 script.pyc
# decompyle3 version 3.9.2
# Python bytecode version base 3.8.0 (3413)
# Decompiled from: Python 3.8.20 (default, Sep 27 2024, 06:05:08)
# [GCC 12.2.0]
# Embedded file name: script.py
def fun():
print('Hello')
# okay decompiling script.pyc
> decompyle3 rb2.pye
# file rb2.pye
# path rb2.pye must point to a Python source that can be compiled, or Python bytecode (.pyc, .pyo)
python3 hack.py
Traceback (most recent call last):
File "/tmp/hack.py", line 13, in <module>
from rb2 import *
ImportError: bad magic number in 'rb2': b'\xa2\x9f\xa4R'
> decompyle3 rb2.pye # file rb2.pye # path rb2.pye must point to a Python source that can be compiled, or Python bytecode (.pyc, .pyo) python3 hack.py Traceback (most recent call last): File "/tmp/hack.py", line 13, in <module> from rb2 import * ImportError: bad magic number in 'rb2': b'\xa2\x9f\xa4R'
What's your Python version? -> f.write(imp.get_magic() + b'\x00'*12 + args[0])
In [3]: imp.get_magic()
Out[3]: b'U\r\r\n'
In [4]: len(imp.get_magic())
Out[4]: 4
https://github.com/Falldog/pyconcrete/blob/0cc69150f96db5ce202be61b8e810b167fb030cc/src/pyconcrete/init.py#L43-L60
3.12
pyconcrete => python3.9-bookworm
3.12
imp module is remove in 3.12. https://docs.python.org/3.12/whatsnew/3.12.html#whatsnew312-removed-imp
pyconcrete mayn't work under 3.12. fix: issues related to python 3.12
https://github.com/Falldog/pyconcrete/blob/0cc69150f96db5ce202be61b8e810b167fb030cc/src/pyconcrete/init.py#L55-L60
> decompyle3 rb2.pye # file rb2.pye # path rb2.pye must point to a Python source that can be compiled, or Python bytecode (.pyc, .pyo) python3 hack.py Traceback (most recent call last): File "/tmp/hack.py", line 13, in <module> from rb2 import * ImportError: bad magic number in 'rb2': b'\xa2\x9f\xa4R'
Why? decompyle3 rb2.pye ?
It should be decompyle3 rb2.pyc
looks like .pyc - pythoncompile, .pye - encrypted. maybe u need to install pyconcrete this way:
PYCONCRETE_PASSPHRASE="$(dd if=/dev/urandom bs=1k count=1 | head -c10 | base64)" pip3.9 install pyconcrete
pyconcrete-admin.py compile -s /usr/bin/rb2.py --pye --remove-py &&
pyconcrete /usr/bin/rb2.pye --help
LOL =)
looks like .pyc - pythoncompile, .pye - encrypted. maybe u need to install pyconcrete this way: PYCONCRETE_PASSPHRASE="$(dd if=/dev/urandom bs=1k count=1 | head -c10 | base64)" pip3.9 install pyconcrete pyconcrete-admin.py compile -s /usr/bin/rb2.py --pye --remove-py && pyconcrete /usr/bin/rb2.pye --help
I don't quite understand what you mean.
What you say is pyconcrete's beat practice,is it related to hack?
Have you successfully reproduced the logic of the hack?
You can get .pyc file using wrapper in hack.py. @6b3478
yes. your hack don't work. may be in your home lab. also i inspect your python super-encryption-with-license repo =)) i have a friend in russia. they say: в своем глазу - бревна не замечает, а в чужом соринки разглядывает ;-) have a nice day
I've successfully reproduced in docker with Python 3.9. And I don't understand what you're doing and saying.
- You should use
decompyle3 rb2.pyc, butdecompyle3 rb2.pye. - The logic of issue is to hack
marshal.loadsfunction to generate the.pycfile ofscript.py. And then translates.pycto Python source code usingdecompyle3. decompile3currently only supportsPython 3.8and below, so you may need to change L106 toversion == (3, 9)https://github.com/rocky/python-decompile3/blob/2118134478b5867ecf5ce193435ba49c7baf8b11/decompyle3/parsers/main.py#L105-L108
[!NOTE]
decompyle3translates Python bytecode back into equivalent Python source code.
- Python import: https://docs.python.org/3/library/importlib.html#importlib.machinery.SourcelessFileLoader
Environment
~$ docker pull python:3.9
~$ docker run --name=py39 -d python:3.9 sleep 3600000
~$ docker exec -it py39 bash
Hack
root@cde87253aac7:/pyconcrete# pyconcrete-admin.py compile --source=script.py --pye
root@cde87253aac7:/pyconcrete# rm script.py
root@cde87253aac7:/pyconcrete# python hack.py
root@cde87253aac7:/pyconcrete# vim /usr/local/lib/python3.9/site-packages/decompyle3/bin/decompile.py
root@cde87253aac7:/pyconcrete# decompyle3 script.pyc
# decompyle3 version 3.9.2
# Python bytecode version base 3.9.0 (3425)
# Decompiled from: Python 3.9.9 (main, Dec 21 2021, 10:03:34)
# [GCC 10.2.1 20210110]
# Embedded file name: script.py
def fun():
print("Hello")
fun()
# okay decompiling ../script.pyc
[!CAUTION]
Finally, Why don't you try it in the same environment as me? And why don't you read the issue carefully? @6b3478 Btw,I am Chinese, not Russian.
[!TIP]
pyconcrete is an experimental project, there is always a way to decrypt .pye files, but pyconcrete just make it harder.
Hi @ZhaoQi99 Thx for you issue! Your vulnerability works but hacker have to do access to server with *.pye files with write and execute permissions If hacker has write and execute permissions to your server this "pye problem" will be the least dangerous compared to other problems)
Also your can remove pyconcrete package and launch pye files with pyconcrete binary only without importing pyconcrete in code. In this case your vulnerability does not works because "from script import *" will fails with error.
Also stealing pye files without the server pyconcrete lib package files will not help in successful decompilation. So chmod and last os updates will help you )
@Falldog May be it will be good to add this case in README.md
@ZhaoQi99 Already described in https://github.com/Falldog/pyconcrete/issues/23
Thanks the elaboration of @dx-77
@ZhaoQi99 I think you are using the partial encrypted solution.
Partial encrypted (README Link). I think there are hundreds way to hack it. If your are senior python engineer.
Recommend the Full encrypted solution (README Link). It will not allow user to import pyconrete by customized scripts. It should be "more safe" than partial encryption.
I think we should put the Deprecated or Non safety mark on the section of partial encrypted solution in README. Make developer notice it.
@Falldog Thanks for your replay.
Yep! You are right.It seems that what I use is the partial encrypted solution.
In fact, I didn't do anything extra besides installing it by python setup.py install.
And /usr/local/lib/python3.9/site-packages/pyconcrete does not contain any source code.🤔
Is this still considered partial encryption?
I just found out why there is no error when executing python hack.py. And I don't import pyconcrete in hack.py. It's so amazing.
~$ git clone https://github.com/Falldog/pyconcrete.git --depth=1
~$ cd pyconcrete/
~$ python setup.py install
...
copying build/scripts-3.9/pyconcrete -> /usr/local/bin
creating /usr/local/lib/python3.9/site-packages/pyconcrete.pth
After I remove /usr/local/lib/python3.9/site-packages/pyconcrete and pyconcrete.pth.
python hack.py will not work,but pyconcrete script.pye still works well.
ModuleNotFoundError: No module named 'script'
root@cde87253aac7:/usr/local/lib/python3.9/site-packages/pyconcrete# pwd
/usr/local/lib/python3.9/site-packages/pyconcrete
root@cde87253aac7:/usr/local/lib/python3.9/site-packages/pyconcrete# ls
__init__.py __pycache__ _pyconcrete.cpython-39-x86_64-linux-gnu.so version.py
root@cde87253aac7:/usr/local/lib/python3.9/site-packages/pyconcrete# whereis pyconcrete
pyconcrete: /usr/local/bin/pyconcrete
root@cde87253aac7:/# python hack.py
Traceback (most recent call last):
File "/hack.py", line 13, in <module>
from script import *
ModuleNotFoundError: No module named 'script'
In my view, Django can only use partial encrypted solution. Is it this?
@Falldog Can you take a look at pyencrypt-pye when you have time? May be the project has the same problem as pyconcrete?
@ZhaoQi99 Yes, now Django can only use unsafe partial encrypted solution.
Unfortunately, pyencrypt-pye as well as any other software written in Python and launched by the "standard" Python interpreter is vulnerable from the start. That's why pyconcrete in full encrypted variant uses binary to launch pye files instead python
In my view, Django can only use partial encrypted solution. Is it this?
In develop & staging environment, you could encrypt django entrypoint manage.py and launch it by pyconcrete to achieve full encryption. But in production mode, the best practice should be launch django by uwsgi or gunicorn. If you want to fully encryption and you must make uwsgi or gunicorn able to import .pye and decrypt files.
Agree with @dx-77. pyencrypt-pye is more like partial encryption. Once the launcher is python default interpreter, and it's easy to hack by senior python engineer.
After v1.1.0 released, the full encryption would be default option. Reference #119
In general case, you better should use the fully encryption mode (pyconcrete exe). For the web service case, such as Django or others, you could able consider partial encryption mode (pyconcrete lib). And you need to take care the environment you may expose to 3rd party about security.
Close the issue since fully encryption already be default option. And marshal.loads is not easy to be hacked.