ROPfuscator is a research proof of concept and is not intended for production use. The authors do not take any responsibility or liability for the use of the software. Please exercise caution and use at your own risk.
IMPORTANT: This repository has been deprecated and is no longer being maintained.
We have decided to deprecate this repository in favor of a new and improved repository, which provides stronger reproducibility of the entire toolset and will be the base for all future work. We highly recommend users switch to the new repository for the latest updates and ongoing support.
Please visit the new repository here.
If you have any questions or need assistance, please feel free to open an issue in the new repository or reach out to the team.
We encourage collaboration and are open to discussing potential extensions or improvements to the project. If you are interested in contributing, please reach out to us through the contact information provided above, or open an issue in the new repository.
Thank you for your understanding and support!
ROPfuscator is an LLVM backend extension that aims to perform code obfuscation taking advantage of ROP chains: supported instructions are replaced by semantically equivalent ROP gadgets.
- Gadgets are automatically extracted from
libc
or from a custom library, if provided. - Gadgets are referenced using symbol anchoring: each gadget is referenced using a random symbol within the provided library and its offset from it. Since symbol addresses are automatically resolved at runtime by the dynamic loader (
ld
), we can guarantee to reach the wanted gadget even if the library is mapped in memory at a non-static address. - ASLR-resilient: works flawlessly with ASLR enabled.
- Data-flow analysis: in case of need of a scratch register where to compute temporary values, only registers that don't hold useful data are used.
- Gadget generalisation through the Xchg graph allows to parametrise gadget instruction operands, giving the possibility to re-use the same gadgets but with different operands. This way we ensure that instructions are correctly obfuscated even in case the number of extracted gadgets is very restricted.
- Dependence on the specific version of
libc
used at compile time.
To avoid this, you can potentially use a library that will be distributed along with the binary. - Support is currently limited to 32bit x86 platform.
- Programs should be built as PIE without PIC (with
-pie
in linking, and without-fpic
in compiling).
ninja
pkg-config
cmake
, version>= 3.00
- external libraries (
libfmt==5.2.1
,tinytoml==0.4
) included in thirdparty/
sudo apt install cmake ninja-build pkg-config
Make sure to be able to clone this repository first and then run:
wget http://releases.llvm.org/7.0.0/llvm-7.0.0.src.tar.xz
tar -xf llvm-7.0.0.src.tar.xz && rm llvm-7.0.0.src.tar.xz
cd llvm-7.0.0.src
pushd tools
wget https://releases.llvm.org/7.0.0/cfe-7.0.0.src.tar.xz
tar -xf cfe-7.0.0.src.tar.xz && rm cfe-7.0.0.src.tar.xz
popd
pushd lib/Target/X86
git clone --recursive git@bitbucket.org:s2lab/ropfuscator.git
patch < ropfuscator/patch/llvm-7.patch
popd
mkdir build && cd build
cmake -DCMAKE_BUILD_TYPE=Debug -DLLVM_TARGETS_TO_BUILD=X86 -DBUILD_SHARED_LIBS=ON -GNinja ..
ninja llc
There are a couple of flags that are worth to be mentioned when configuring the build:
-DCMAKE_BUILD_TYPE=Debug
: just to obtain a debug build (more flexible)-DLLVM_TARGETS_TO_BUILD=X86
: we're interested only in the X86 platform, so we don't want to lose time compiling the backend also for all the other platforms, such as ARM, MIPS, SPARC, etc. This speeds up the compilation process, and make us save up to 4 GB of disk space.-DBUILD_SHARED_LIBS=ON
: shared code is moved in.so
libraries, that can be linked at runtime, thus speeding up the compilation process even more.-GNinja
: specifies to useninja
as build generator. By usingninja
the overall compile time can decrease by more than 50% (it seems that it has better support to multithreading), but most importantly we can invoke a specific command to compile onlyllc
.
Once the project is compiled, we can create a symbolic link to our custom version of llc
, in order to call it in a simpler way, ropf-llc
:
sudo ln -s [BUILD-DIR]/bin/llc $(HOME)/.local/bin/ropf-llc
Make sure that $(HOME)/.local/bin/
is set in your PATH
environment variable.
Since ROPfuscator is a MachineFunctionPass
, we have to recompile llc
(LLVM system compiler) each time we modify the pass.
Luckily we're using ninja-build
, so we don't have to recompile the whole backend; doing this is just a matter of seconds by running:
ninja llc
-
Convert the source code file to obfuscate in LLVM IR:
clang [ -m32 ] -O0 -S -emit-llvm example.c
-m32
: compile in 32-bit mode on 64-bit host (you will need to havegcc-multilib
installed for this)
This will create a new file
example.ll
. -
Compile using our custom LLVM
llc
tool:ropf-llc example.ll [ -march=x86 ]
-march=x86
: compile in 32-bit mode on 64-bit host
The output is an asm
example.s
file. -
Assemble and link:
[ LD_RUN_PATH='$ORIGIN/' ] gcc -pie example1.s -o example [ -m32 ] [ -lc | -L. -l:libcustom.so ]
-
-m32
: compile in 32-bit mode on 64-bit host (you will need to havegcc-multilib
installed for this) -
-lc
: only if you usedlibc
to extract gadgets and symbols during the linking phase. This will enforce the static linker to resolve the symbols we injected using onlylibc
. -
-L. -l:libcustom.so
: only if you used a custom library. -
LD_RUN_PATH
: only if you used a custom library. Enforce the dynamic loader to look for the needed libraries in the specified path first. This will ensure that the loader will load your library first, as soon as it is shipped along with the binary.
Note: we have to use
-pie
to avoid lazy binding (aka PLT) to resolve symbols. This is crucial since we need direct function addresses oflibc
rather than the address of PLT entry, to compute gadget address.gcc
has default compile option-pie
whileclang
doesn't, so be careful if you are usingclang
instead to link the program. Also note that you should not use-fpic
in compiling source file to bitcode. -
To automate the steps above in existing build scripts (such as Makefile
), we provide a shell script ropcc.sh
. It serves both as a compiler and a linker.
The following example compiles foo.c
and bar.c
separately, link the objects with libbaz.so
(obfuscated with config obf.conf
) to generate an obfuscated binary exefile
.
You just need to replace C-compiler with ropcc.sh cc
, and C++-compiler with ropcc.sh c++
, and supply obfuscation configuration (-ropfuscator-config=...
).
Command line options are passed to compiler/linker appropriately.
See shell script (comment) for further details.
$ ropcc.sh cc -c foo.c -o foo.o
$ ropcc.sh cc -c bar.c -o bar.o
$ ropcc.sh cc -ropfuscator-config=obf.conf foo.o bar.o -lbaz -o exefile
While in the build
directory, run:
ninja ropfuscator-examples
The compiled examples will be found in the bin/
directory.
ROPfuscator supports docker build with Dockerfile.
sh docker/build.sh
It will define the following tags:
ropfuscator:prebuild-llvm-7
: just before building ropfuscatorropfuscator:build-llvm-7
: after building ropfuscatorropfuscator:llvm-7
: ropfuscator binary (without build files)
In ropfuscator:llvm-7
image, you can use clang-7
and llc
to run ROPfuscator.
# (If there are multiple source files)
# compile
clang-7 -m32 -c -emit-llvm foo.c
clang-7 -m32 -c -emit-llvm bar.c
# bitcode link
llvm-link-7 -o out.bc foo.bc bar.bc
# obfuscate and build executable
clang-7 -m32 -pie -o out out.bc
# compile, link, obfuscate (if there is only one source file)
clang-7 -m32 -pie main.c