Create More Complicated Binaries with Nuitka & LLVM-Obfuscator

Create More Complicated Binaries with Nuitka & LLVM-Obfuscator

Nuitka and OLLVM (Obfuscator-LLVM) can be used in combination to create more complicated and secure binaries. Nuitka is a Python compiler that converts Python code into C++ code, which can then be compiled into a binary executable. OLLVM, on the other hand, is a tool that obfuscates LLVM IR (Intermediate Representation) code, making it more difficult for hackers to reverse-engineer the binary. By using Nuitka to convert Python code into C++ code, and then using OLLVM to obfuscate the generated LLVM IR code, developers can create more complex and secure binaries that are more difficult for hackers to understand and exploit.

Brief Information

Nuitka

Nuitka is an open-source Python compiler that converts Python code into C++ code. It is a source-to-source compiler, meaning it takes the source code written in Python and converts it into a C++ equivalent, which is then compiled into machine code. Nuitka is designed to be fully compatible with the Python language and can run any Python code that is compatible with the version of Python it is using. It also supports various features such as packaging, optimization, and profiling. It is an open-source project, which means that it is free to use and can be modified by anyone. This allows for a wider range of use cases and adaptability to specific needs.

Nuitka works by taking the Python code and analyzing it to create an abstract syntax tree (AST), which represents the structure of the code. It then transforms the AST into a form that can be converted into C++ code. Nuitka uses a technique called “partial evaluation” to eliminate unnecessary computations and further optimize the code.

#display help page
python -m nuitka --help

#same as nuitka but it tries to compile and directly execute given Python script.
python -m nuitka-run --help

#compile Python script
python -m nuitka script.py

#compile a whole program recursively, including imports
python -m nuitka --follow-imports program.py

#compile with including spesific python library which can not be found by recursing after normal import statements
python -m nuitka --follow-imports --include-plugin-directory=plugin_dir program.py

#compile a single extension module
python -m nuitka --module some_module.py

#compile a whole package and embed all modules
python -m nuitka --module some_package --include-package=some_package

#compile for distribution with standalone module
python -m nuitka --standalone program.py

#compile program to onefile
python -m nuitka --onefile program.py

# More information: https://nuitka.net/doc/user-manual.html

Obfuscator-LLVM (OLLVM)

LLVM (Low Level Virtual Machine) is a collection of modular and reusable compiler and toolchain technologies for various programming languages. It is designed to be used as a library that can be integrated into various compilers, allowing for the optimization of code, generation of machine code, and more. Obfuscator-LLVM is a tool that uses LLVM to obfuscate code. Obfuscation is the process of making code difficult to understand or reverse-engineer by making it hard to read, understand or modify while still maintaining its original functionality.

OLLVM works by applying various code transformations to the program’s intermediate representation (IR) in order to make it more difficult to understand and reverse engineer. These transformations can include things like renaming variables and functions, inserting dummy code, and rearranging code blocks.

The resulting obfuscated code is then compiled and linked using the LLVM tools to produce a binary executable. Obfuscator-LLVM can be used to obfuscate code written in languages that can be compiled with LLVM, such as C, C++, and Rust.

Obfuscator-LLVM inner workings are heavily randomized. They make use of a cryptographically secure pseudo-random generator (PRNG) built around the AES128 block cipher operated in counter mode.

Every time the compiler is invoked, the PRNG is seeded through /dev/random. This makes the generated binary more unpredictable and secure.

#compile C/C++ program with completely flatten the control flow graph.
./clang -mllvm -fla input.cpp -o output

#compile C/C++ program with adding unnecessary branches and jumps to code to make it more complex and harder to understand.
./clang -mllvm -bcf -mllvm -bcf_loop=3 -mllvm -bcf_prob=40 input.cpp -o output

#compile C/C++ program with replacing instructions in the code with functionally equivalent but harder to understand instructions
./clang -mllvm -sub -mllvm -sub_loop=3 input.cpp -o output

# More information: https://github.com/obfuscator-llvm/obfuscator/wiki/Features

Enviroment Setup

Before installing OLLVM, make sure the system is up to date. This procedure tested on Ubuntu 20.04.5 LTS.

sudo apt update && sudo apt upgrade -y

Nuitka Installation

To start using Nuitka and OLLVM together, the first step is to install Nuitka with Python. Once Nuitka is installed, you can then use it to convert your Python code into C++ code by running the command given below.

python3 -m pip install nuitka
#Usage
python3 -m nuitka <name of your python file>

Obfuscator-LLVM Setup & Installation

Before Installation

Downgrading gcc and g++ versions is essantial, if the installed version of gcc and g++ is 8.xx, go to the next step. If gcc9 & g++9 are installed on system, building Obfuscator-LLVM from its source code fails.

sudo apt-get install gcc-8 g++8 -y
sudoa pt-get install make
sudo apt-get install cmake

After installation, configure the priority of calling gcc & g++.

sudo update-alternatives - install /usr/bin/gcc gcc /usr/bin/gcc-8 8
sudo update-alternatives - install /usr/bin/g++ g++ /usr/bin/g++-8 8
sudo update-alternatives - install /usr/bin/gcc gcc /usr/bin/gcc-9 9
sudo update-alternatives - install /usr/bin/g++ g++ /usr/bin/g++-9 9

Switch gcc & g++ defaults to gcc 8 & g++ 8:

`sudo update-alternatives - config gcc`
`sudo update-alternatives - config g++`

Installing OLLVM done by cloning the its repository from Github and building it from source. Before building with the “make” command, some modifications and configirations should be made.

git clone -b llvm-4.0 https://github.com/obfuscator-llvm/obfuscator.git

Source code of OLLVM needs to be modified, edit 690. line of “directory/include/llvm/ExecutionEngine/Orc/OrcRemoteTargetClient.h” like given below.

Line 690, changed vector\<char> to vector\<uint8_t>

Expected<std::vector<uint8_t>> readMem(char *Dst, JITTargetAddress Src,
uint64_t Size) {
// Check for an 'out-of-band' error, e.g. from an MM destructor.
if (ExistingError)
return std::move(ExistingError);
return callB<ReadMem>(Src, Size);
}

Compile and Install

After modifications and configurations, it is ready to build.

mkdir build
cd build
cmake -DCMAKE_BUILD_TYPE=Release -DLLVM_INCLUDE_TESTS=OFF ../obfuscator/
make -j2

The program should be compiled like this in the bin directory:

./clang -mllvm -fla test.c -o test

Verify installation and run tests

Create a test folder on the same folder where build located and create a new file named test. Open test.cpp and type or paste the code below:

#include <stdio.h>
#include <stdlib.h>
int encryptFunc(int inputNum_1, int inputNum_2){
int tmpNum_1 = 666, tmpNum_2 = 888, tmpNum_3 = 777;
return tmpNum_1 ^ tmpNum_2 + tmpNum_3 * inputNum_1 - inputNum_2;
}
int main(int argc, char *argv[]){
int printNum = 55;
if (argc > 1) {
printNum = encryptFunc(printNum, atoi(argv[1]));
}else {
printNum = encryptFunc(printNum, argc);
}
printf("Hello OLLVM %d\r\n", printNum);
return 0;
}

1. Control flow flattening

`../build/bin/clang -mllvm -fla -mllvm -split -mllvm -split_num=3 test.cpp -o test1`

2. Instructions Substitution

`../build/bin/clang -mllvm -fla -mllvm -split -mllvm -split_num=3 test.cpp -o test1`

3. Bogus Control Flow

`../build/bin/clang -mllvm -fla -mllvm -split -mllvm -split_num=3 test.cpp -o test1`

Clang Output vs Nuitka & OLLVM Clang -fla Output

Combine Nuitka & OLLVM

Combination of this tools is being done by manipulating Nuitka’s “ — clang” parameter with a “clang” file written by us. Nuitka tries to execute “clang” located in “/usr/bin”, this process will be manipulated with a executable basic script named “clang” and when Nuitka searches “clang”, it will find the script.

sudo nano /usr/bin/clang
 - Paste script code - 
sudo chmod 777 /usr/bin/clang

Script code:

#!/bin/sh
~/build/bin/clang-4.0 -mllvm -fla -mllvm -split -mllvm -split_num=3 $*

Run Nuitka with OLLVM

Nuitka without parameter compiles itself but with “ — clang” parameter, it searches the path of “clang” from environment variable “PATH” and founds the script: “/usr/bin/clang”. Nuitka compiles python file already setten parameters in the script file and outputs an executable file. Tested with basic palindrome code and diffirence is given below.

python3 -m nuitka code.py - clang

#code.py
def isPalindrome(s):
      return s == s[::-1]


#drive code
s = "malayalam"
ans = isPalindrome(s)

if ans:
      print("Yes")
else:
      print("No")

Nuitka Output vs Nuitka & OLLVM -fla Combination Output

Writer: Veli Tekin

References:

- https://blog.csdn.net/weixin_51732593/article/details/126707046

- https://github.com/obfuscator-llvm/obfuscator/wiki/FAQ