Deobfuscate and inspect code passed into exec() and eval()
Project description
safe-exec
safe-exec provides a context manager that prevents calls to exec and eval in Python code, and reveals the code in calls that were attempted.
This is useful for retrieving deobfuscated code from obfuscated malware samples that use exec or eval as the entry point.
[!CAUTION] This package does not guarantee that a script is safe to run. It is only a tool to help deobfuscate code. It does not sandbox code, nor does it intend to be foolproof. Never run potentially malicious code outside of a secure sandboxed environment.
Installation
pip install safe-exec
Or install from source:
pip install git+https://github.com/lemonyte/safe-exec.git
Python versions 3.9 through 3.13 are supported.
Simple example
The safe context manager takes either exec or eval as an argument.
>>> from safe_exec import safe
>>> with safe(exec):
... from base64 import b64decode
... exec(b64decode("cHJpbnQoJ29iZnVzY2F0ZWQgY29kZScp"))
safe_exec.ExecBlockedError: blocked execution of b"print('obfuscated code')"
You can also use the runpy module to execute a different script in the safe context:
>>> import runpy
>>> with safe(exec), safe(eval):
... runpy.run_path("path/to/script.py")
Advanced usage
safe can also be used as a decorator to block calls to exec or eval in a function:
from safe_exec import safe
@safe(exec)
@safe(eval)
def untrused_func():
exec("print('This is an untrusted function')")
And as a function to register a third-party function as an untrusted caller:
from safe_exec import safe
import runpy
runpy.run_path = safe(exec)(runpy.run_path)
However, the context manager is recommended for most use cases, as it ensures the functions that are overwritten are restored after the context exits.
Allowing certain calls
safe-exec provides allow_exec and allow_eval context decorators to allow specific calls to exec and eval, respectively.
These can be used as decorators on functions that should be allowed to call exec or eval:
from safe_exec import allow_exec, allow_eval
@allow_exec
@allow_eval
def trusted_func():
exec("print('This is a trusted function')")
eval("print('This is a trusted function')")
trusted_func()
[!CAUTION] If other code calls the decorated function, the
execandevalcalls will still be allowed.
As context managers to temporarily allow a function to call exec or eval:
from safe_exec import allow_exec, allow_eval
def semi_trusted_func():
exec("print('This is a semi-trusted function')")
eval("print('This is a semi-trusted function')")
with allow_exec(semi_trusted_func), allow_eval(semi_trusted_func):
semi_trusted_func() # Calls are allowed here.
semi_trusted_func() # Calls are blocked here.
Or as functions to register a third-party function as an allowed caller:
from safe_exec import allow_eval
import collections
allow_eval(collections.namedtuple)
safe vs allow_exec and allow_eval
The two groups of context managers have slightly different purposes, and therefore different interfaces.
safe creates a 'safe' context wherein calls to exec or eval are blocked.
If used as a decorator, it does not do anything with the decorated function itself.
allow_exec and allow_eval on the other hand need to take a callable (besides exec or eval) as an argument.
This is because they register the callable as a trusted caller of exec or eval, whether they are used as decorators or context managers.
False positives
Because exec and eval are used in a number of places within the Python standard library, safe-exec may prevent some necessary code from running. If you encounter a false positive, please open an issue.
As a temporary workaround, if you can determine the calling function that depends on exec or eval, you can use the allow_exec and allow_eval context managers to register it as a trusted caller.
Legitimate calls to exec and eval are allowed by inspecting the caller's frame. If the caller's code object is present in the list of trusted callers, the call is allowed. Such calls are logged as informational messages.
>>> from safe_exec import safe
>>> import logging
>>> logging.basicConfig(level=logging.INFO)
>>> from typing import NamedTuple
>>> with safe(eval):
... Foo = NamedTuple("Foo", [("bar", int)])
INFO:safe_exec:allowed eval call by 'namedtuple' from '.../lib/collections/__init__.py':345
License
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file safe_exec-0.0.2.tar.gz.
File metadata
- Download URL: safe_exec-0.0.2.tar.gz
- Upload date:
- Size: 7.1 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.1.0 CPython/3.11.11
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
f5fa8a60ec64e11143eeca547a39311c355e01613bb2865f9ce95cb489d95279
|
|
| MD5 |
2c2864c4dbdd0fa3b897ef47b130bbb9
|
|
| BLAKE2b-256 |
70aef7e30538d2c5384cae9407d1742f527ed464c62a07db47347a6473cd3b06
|
File details
Details for the file safe_exec-0.0.2-py3-none-any.whl.
File metadata
- Download URL: safe_exec-0.0.2-py3-none-any.whl
- Upload date:
- Size: 7.0 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.1.0 CPython/3.11.11
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
1ca9717cf8ab499be54ef921466db81102c454b9e3040e0a11e8f94a160de30f
|
|
| MD5 |
93a336916c5b09515e378fa19e456d3f
|
|
| BLAKE2b-256 |
64efd12d797c1f9aa0fcf405ebb846d7babe9e71e5bdda9fabf9323f4defa205
|