Skip to content

Latest commit

 

History

History
256 lines (157 loc) · 9.72 KB

README.md

File metadata and controls

256 lines (157 loc) · 9.72 KB

wrapin-py

Pronounced: Wrap-In-Pie

Wraps an executable binary file inside a Python source file and delegates calls to the wrapped binary, enabling the usage of a compiled executable in a sandbox or jailed execution environment that is usually limited to a python script.

wrapin-py is cross-platform and supporting both Python 2 and Python 3 environments.

Liability

Try at your own risk!

Although this "hack" does not attempt to do anything that requires high-privilege, neither does it use any non-conventional privilege-escalation technics, I cannot guarantee that it will work in your system's environment or that it will not break or violate anything.


But, Why?

Project Motivations (click to expand)

Project Motivations

This project serves me as a research and a Proof-of-Concept, about the idea of using Rust as a scripting language for platforms that usually support scripting with interpreted languages. Hopefully, scripting directly in the Rust programming language, will be a thing of the future for closed systems as well.

Languages used for scripting within closed source products are usually running inside a very limited sandbox or jailed environment, which at best, is able to offer us usage of the base libraries of the language or maybe even a few selected third-parties (e.g. requests). Also, some environments are still using Python 2 and do not support more advanced features, such as Python annotations that can help reduce errors (when used correctly) by guiding our IDE to catch misuse mistakes early on.

For the most part, Python is still powerful, easy, and good enough for about any scripting task. But for some more advanced use case scenarios, when logic can get real complex with advanced features, or, if we like some special features of programming language X, an alternative may fit better.

As I worked with the Rust programming language recently, I realized it has some big advantages that could serve me well when my projects (scripts or otherwise) get long and complex with their logical flows. An advantage well noticed when returning to an old Python project (even when it has a good or even a great design). This ultimately lead me to wonder if using Rust, a statically-compiled, strongly-typed, a new generation of a low-level language (comparable to C\C++ on steroids), could serve me for a complex, super capable script design while using its amazing compiler as an excellent guarantee for my code correctness. Modifying and maintaining a complex Rust program gets me a peace of mind as I know that if it compiles, it is correct.

Advantages of a Self-Contained low-level Binary (click to expand)

Advantages of a Self-Contained low-level Binary

  1. Single file, independent of any runtime environment, can be embedded with third-party libraries while offering more advanced APIs, such that are not part of the core Python libraries which are provided by default in the sandbox environment

    • Caveat: In restricted, stripped environments, this can work as long as your binary does not try to use dynamically linked APIs which may be restricted or completely missing in that sandbox environment (e.g. cmake or native OpenSSL).
  2. Written in a strongly typed, statically compiled programming language for a complex project, can detect potential problems at compile-time, long before they reach production, especially for the rare, "invisible" bug cases that camouflage themselves within tons of code so they can show up at the worse moment in production.

  3. It can provide you with bare-metal performance for some heavy calculations.


Usage

How to use the wrapin.py tool.

Help (click to expand)

Help

You can find the most updated command list in the help menu, using the --help switch.

python wrapin.py --help

Output

usage: wrapin.py [-h] [-o OUTPUT] [-t TARGET] [-e [ENV ...]] executable

Wraps an executable binary file inside a Python source file, to be used as a script in a closed system.

positional arguments:
  executable            The executable binary file to be wrapped inside a Python source file.

options:
  -h, --help            show this help message and exit
  -o OUTPUT, --output OUTPUT
                        Specify the output path for the Python source file.
  -t TARGET, --target TARGET
                        Specify the target operating system for the executable file: `Windows`, `Linux` or `Darwin`. By default, the current
                        operating system is selected. Mismatch of configurations with the wrapped file will cause a failure of execution and will     
                        exit with an error.
  -e [ENV ...], --env [ENV ...]
                        Environment variables to be passed to the executable. Format: KEY=VALUE
Wrap an Executable (click to expand)

Wrap an Executable

python wrapin.py hello_world_windows.exe

Will produce the wrapper file: hello_world_windows.exe.wrapped.py

📦 (Working directory)
┣ 📜hello_world_windows.exe
┣ 📜hello_world_windows.exe.wrapped.py 👈
┗ 📜wrapin.py

  • Upload the wrapper file as an automations script to your external system.
Wrap an Executable to a different Architecture (click to expand)

Wrap an Executable to a different Architecture

The next is an example of wrapping a Linux binary file from a Windows environment

python wrapin.py hello_world_linux.bin --target=linux
  • Providing a wrong platform, e.g. running the wrapper file in a Windows environment while it is configured for Linux, will simply exit with an error, without trying to run the wrapped executable.
Logical Flow Graph (click to expand)

Logical Flow Graph

Wrap & Upload the Executable

Alt text

Auto-Unwrap & Delegate Script Calls

Alt text

  • The wrapped file is unwrapped into a directory called unwrapped that is automatically created within the logged machine user's home directory (~). This is done to make sure that a lack of permissions will not pose a problem.

Language Guides

Tips and tricks for specific programming languages to create a proper binary file to wrap.

Select the language of choice:

Rust (click to expand)

Optimize for both Size & Performance

The following compiler configurations are highly recommended for minimalist environments such as AWS Lambdas, cloud Docker containers or any other realtime/near realtime systems.

  • Configure Cargo.toml to optimize the --release build for both size and speed
  • Use panic = 'abort' to exit on panic rather than unwind, unless you are catching unwind to recover from panics in your use case
[profile.release]
panic = 'abort'     # Reduces binary size
strip = true        # Reduces binary size
codegen-units = 1   # Improves optimization
lto = true          # Improves optimization
incremental = true  # Improves compile-time speed
opt-level = 3       # Optimizes binary execution-times
  • To further improve binary sizes, it is often recommended to disable default-features for specific crates, and pick only the required ones when reasonable and possible

BONUS: Compile-time Optimization for Linux

Install and configure the Mold Linker

Building Targets for Size & Performance


Windows 32-bit (click to expand)

Add i686-pc-windows-msvc to the rustup toolchain

rustup target add i686-pc-windows-msvc

Build target

cargo build --target=i686-pc-windows-msvc --release

Linux 32-bit / 64-bit (click to expand)

Since we are most probably building for a limited jail or sandbox environment, our best chance is to compile to musl instead of gnu, since gnu is using APIs that are dynamically linked and may be absent within the context of the sandbox.

GNU & MUSL

  • You can always try gnu first and fallback to musl
  • Using musl allows you to produce standalone executables that are self-sufficient. Combine this with statically linked libraries such as rusttls or openssl with the vendored feature enabled, and you can run it on every environment, including a scratch Docker image container.

Building Proper

Add x86_64-unknown-linux-musl to the rustup toolchain

rustup target add x84_64-unknown-linux-musl

Build target

cargo build --target=x84_64-unknown-linux-musl --release

Test List

This list only indicate what has been tested and proven to work so far and does not reflect possible usability for other systems

  • QRadar Community Edition 7.3.3 (Custom Actions)
    • Rust binary file, compiled for i686-unknown-linux-musl
    • Rust binary file, compiled for x86_64-unknown-linux-musl with the MiMalloc heap-memory allocator (tested both in normal and secure modes)

Contribution

Tried it on a system that is not listed here and it worked?
Feel free to add that system to the list in a pull request!
Just don't forget to mention how you achieved that, preferably in a markdown language that can later be converted into a guide.

Feel free to contribute in anyway you like:

  • Discussions
  • Issues (Report bugs, mistakes, feature requests, etc.)
  • Pull Requests
  • Blog Posts
  • Guides
  • etc.

License

This project and any direct contribution to it are licensed under the MIT license.

Please refer to the LICENSE file in this repository for more information.