Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Security🔐] Leak source code by hacking marshal.loads function #111

Open
ZhaoQi99 opened this issue Nov 6, 2024 · 19 comments
Open

[Security🔐] Leak source code by hacking marshal.loads function #111

ZhaoQi99 opened this issue Nov 6, 2024 · 19 comments

Comments

@ZhaoQi99
Copy link

ZhaoQi99 commented Nov 6, 2024

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
@6b3478
Copy link

6b3478 commented Dec 5, 2024

> 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'

@ZhaoQi99
Copy link
Author

ZhaoQi99 commented Dec 5, 2024

> 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

@property
def magic(self):
if sys.version_info >= (3, 7):
# reference python source code
# python/Lib/importlib/_bootstrap_external.py _code_to_timestamp_pyc() & _code_to_hash_pyc()
# MAGIC + HASH + TIMESTAMP + FILE_SIZE
magic = 16
elif sys.version_info >= (3, 3):
# reference python source code
# python/Lib/importlib/_bootstrap_external.py _code_to_bytecode()
# MAGIC + TIMESTAMP + FILE_SIZE
magic = 12
else:
# load pyc from memory
# reference http://stackoverflow.com/questions/1830727/how-to-load-compiled-python-modules-from-memory
# MAGIC + TIMESTAMP
magic = 8
return magic

@6b3478
Copy link

6b3478 commented Dec 5, 2024

3.12

@6b3478
Copy link

6b3478 commented Dec 5, 2024

pyconcrete => python3.9-bookworm

@ZhaoQi99
Copy link
Author

ZhaoQi99 commented Dec 5, 2024

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

else:
# load pyc from memory
# reference http://stackoverflow.com/questions/1830727/how-to-load-compiled-python-modules-from-memory
# MAGIC + TIMESTAMP
magic = 8
return magic

@ZhaoQi99
Copy link
Author

ZhaoQi99 commented Dec 5, 2024

> 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

@6b3478
Copy link

6b3478 commented Dec 5, 2024

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

@6b3478
Copy link

6b3478 commented Dec 5, 2024

LOL =)

@ZhaoQi99
Copy link
Author

ZhaoQi99 commented Dec 5, 2024

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?

@ZhaoQi99
Copy link
Author

ZhaoQi99 commented Dec 5, 2024

You can get .pyc file using wrapper in hack.py. @6b3478

@6b3478
Copy link

6b3478 commented Dec 5, 2024

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

@ZhaoQi99
Copy link
Author

ZhaoQi99 commented Dec 5, 2024

I've successfully reproduced in docker with Python 3.9. And I don't understand what you're doing and saying.

  1. You should use decompyle3 rb2.pyc, but decompyle3 rb2.pye.
  2. The logic of issue is to hack marshal.loads function to generate the .pycfile
    of script.py. And then translates .pyc to Python source code using decompyle3.
  3. decompile3 currently only supports Python 3.8 and below, so you may need to change L106 to version == (3, 9) https://github.com/rocky/python-decompile3/blob/2118134478b5867ecf5ce193435ba49c7baf8b11/decompyle3/parsers/main.py#L105-L108

Note

decompyle3 translates Python bytecode back into equivalent Python source code.

  1. 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.

@dx-77
Copy link
Contributor

dx-77 commented Dec 14, 2024

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

@dx-77
Copy link
Contributor

dx-77 commented Dec 16, 2024

@ZhaoQi99
Already described in
#23

@Falldog
Copy link
Owner

Falldog commented Dec 17, 2024

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.

@ZhaoQi99
Copy link
Author

@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?

@ZhaoQi99
Copy link
Author

@Falldog Can you take a look at pyencrypt-pye when you have time? May be the project has the same problem as pyconcrete?

@dx-77
Copy link
Contributor

dx-77 commented Dec 17, 2024

@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

@Falldog
Copy link
Owner

Falldog commented Dec 18, 2024

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants