Running Tigress on C++ Code

Below is a proof-of-concept recipe showing how to obfuscate a C++ program (SuperTux) with Tigress, provided to us by Bart Coppens. At some future date we hope to integrate the LLVM C backend with Tigress to automate this process.

We need five steps to transform the C++ code into C, and then obfuscate with Tigress:

1

Building the right LLVM tool chain;

2

Building the (patched/hacked) LLVM C Backend;

3

Compiling Supertux with the LLVM tool chain from (1) to create an LLVM Intermediate Representation of Supertux as a whole;

4

Running the patched LLVM C Backend on the LLVM IR from (3) to create a single C file;

5

Some gentle post-processing of the single C file of (4)

Build LLVM 17

From here on we will assume that everything is done in the directory /projects.

If you're using Docker, do the following:

docker pull debian:12 ; 
docker run -v ${PWD}/projects:/projects -ti debian:12 bash 
sed -i 's/deb$/deb deb-src/' /etc/apt/sources.list.d/debian.sources 
apt-get update 
apt-get build-dep supertux 
apt-get install vim python3.11-venv
cd /projects 
git clone https://url.usb.m.mimecastprotect.com/s/MexdCoAWMofr6Q610i1fgCYOGkE?domain=github.com
cd llvm-project
git switch release/17.x
mkdir llvm/build
cd llvm/build
cmake -DCMAKE_BUILD_TYPE=Release \
      -DLLVM_ENABLE_DUMP=ON \
      -DLLVM_BUILD_LLVM_DYLIB=ON \
      -DLLVM_ENABLE_PROJECTS=clang \
      -DLLVM_ENABLE_ASSERTIONS=ON ..

If you ever need to debug things, add -DMAKE_BUILD_TYPE=Debug or -DMAKE_BUILD_TYPE=RelWithDebInfo. Then, build:

make -jnumber of CPU cores

Build the patched LLVM C Backend

cd /projects
git clone https://url.usb.m.mimecastprotect.com/s/h9m8Cp9WNpfnp7pJBSDh7CXcChH?domain=github.com
cd llvm-cbe
git switch development
mkdir build
cd build

export PATH=/projects/llvm-project/llvm/build/bin/:$PATH

cmake ..

make

If you get a build error here about undefined reference to `typeinfo for llvm::cl::GenericOptionValue', add the following at the end of ../tools/llvm-cbe/CMakeLists.txt:

if (NOT ${LLVM_ENABLE_RTTI})
   target_compile_options(llvm-cbe PRIVATE "-fno-rtti")
   target_link_options(llvm-cbe PRIVATE "-fno-rtti")
endif()

Compiling single LLVM IR file for Supertux with the right LLVM tool chain

First, set up a python venv for wllvm. This is a tool that allows for creating objects and binaries with LLVM IR embedded inside them. This can be used for link-time optimization but also for our purposes:

cd /projects
python3 -m venv venv-wllvm 
. venv-wllvm/bin/activate
pip3 install wllvm

Next, set up the aliases and exports such that wllvm works correctly. This still depends on the active venv and the PATH being set correctly from earlier:

export LLVM_COMPILER=clang
export LLVM_COMPILER_PATH=/projects/llvm-project/llvm/build/bin/
alias wllvm="wllvm -g"

Next, get Supertux. In this example, we built on a Debian 12 machine, which at the time had supertux-0.6.3. Get the dependencies to build it:

sudo apt-get build-dep supertux

cd /projects
apt-get source supertux
cd supertux-0.6.3/

mkdir build
cd build

Next, configure Supertux to build with wllvm (the other build options were based on those from Debian), and then build it:

CC=wllvm CXX=wllvm++ CFLAGS=-g CXXFLAGS=-g \
     cmake -DBUILD_SHARED_LIBS=OFF \
           -DCMAKE_BUILD_RPATH_USE_ORIGIN=ON \
           -DENABLE_BOOST_STATIC_LIBS=OFF \
           -DUSE_SYSTEM_PHYSFS=ON \
           -DIS_SUPERTUX_RELEASE=ON ..
make -j number of CPU cores

This might output a few warnings, but you should now have a supertux2 binary. This binary will also contain info to get our LLVM IR file from, which we extract as follows:

extract-bc --manifest supertux2 

This results in supertux2.bc, which is the LLVM bitcode file with the IR in. As the C backend starts from textual disassembly of that IR, we first disassemble it:

llvm-dis supertux2.bc 

This results in supertux2.ll. Finally, we prepare for running supertux later on:

make install

This will install the data files in /usr/local. However, if we build our merged file later on in the build directory and run it from there, it will expect those data files in the build directory. One possibility is to change the PREFIX of cmake where it installs, but instead we just do the following:

make install
mkdir -p /projects/supertux-0.6.3/build/share/games
cp -a /usr/local/share/games/supertux2 /projects/supertux-0.6.3/build/share/games/

Run the patched LLVM C Backend to create a C file

Do the following:

/projects/llvm-cbe/build/tools/llvm-cbe/llvm-cbe supertux2.ll

This results in supertux2.cbe.c.

Some post-processing and then building and linking the final file

The C backend helpfully adds #line statements so that the lines of the C file get mapped back to the original source files. This is probably useful when debugging a crash that would also be in the original code, but definitely not useful in debugging the case where it is the transpiled code that is broken. So first create a file without the line statements:

grep -v '#line' supertux2.cbe.c > supertux2.cbe_noline.c

Create an object file:

gcc -g -Wno-discarded-qualifiers -std=c99 -o supertux2_cbe.o -c supertux2.cbe_noline.c

Now, here we arrive at a fragile issue: the exception handling hack. If the above code would not compile due to errors related to 'cbe_get_landingpad' that should return 'l_unnamed_1' (or something similar), edit the supertux2.cbe_noline.c and change

struct cbe_landingpad cbe_get_landingpad() {
    struct cbe_landingpad r; 
    r.field1 = 1; 
    r.field0 = cbe_exception.o; 
    return r; 
}
void __attribute__((noreturn)) 
   __cbe_resume(struct cbe_landingpad l) __attribute__((noreturn)) {
    longjmp(ExceptionStates[--jmp_idx].buf, 1); 
}

into

struct l_unnamed_1 cbe_get_landingpad() { 
   struct l_unnamed_1 r; 
   r.field1 = 1; 
   r.field0 = cbe_exception.o; 
   return r; 
}
void __attribute__((noreturn)) 
   __cbe_resume(struct l_unnamed_1 l) { 
   longjmp(ExceptionStates[--jmp_idx].buf, 1); 
}

Again, it might instead be something like l_unnamed_1 that you then have to use instead. It might be that you have to remove the second __attribute__((noreturn)) regardless. Similarly, if it complains about setjmp/longjmp, edit the file as follows. Search for and delete the following 4 lines:

#define longjmp _longjmp
#define setjmp _setjmp
uint32_t _setjmp(void* _147370) __ATTRIBUTELIST__((nothrow, returns_twice));
__noreturn void longjmp(void* _147371, uint32_t _147372) __ATTRIBUTELIST__((nothrow));

Note that the numbered argument names might differ.

Finally, link the file:

g++ -o supertux2_cbe supertux2_cbe.o \
    -L/projects/supertux-0.6.3/build-wllvm-14.x/SDL_ttf/lib  \
   -Wl,-rpath,"\$ORIGIN/SDL_ttf/lib:" libsupertux2_lib.a \
   /usr/lib/x86_64-linux-gnu/libboost_filesystem.so.1.74.0 \
   /usr/lib/x86_64-linux-gnu/libboost_locale.so.1.74.0 \
   /usr/lib/x86_64-linux-gnu/libboost_chrono.so.1.74.0 \
   /usr/lib/x86_64-linux-gnu/libboost_thread.so.1.74.0 \
   /usr/lib/x86_64-linux-gnu/libboost_atomic.so.1.74.0 \
   -lSDL2 -lSDL2_image -lSDL2 -lSDL2_image SDL_ttf//lib/libSDL2_ttf.a \
   -lfreetype -lharfbuzz -lfribidi -lraqm \
   squirrel/ex/lib/libsquirrel_static.a \
   squirrel/ex/lib/libsqstdlib_static.a \
   tinygettext//lib/libtinygettext.a \
   libsexp.a libsavepng.a -lpng \
   libpartio_zip_lib.a \
   -lz -lopenal -lvorbisfile -lvorbis -logg \
   /usr/lib/x86_64-linux-gnu/libboost_system.so.1.74.0 \
   /usr/lib/x86_64-linux-gnu/libboost_date_time.so.1.74.0 \
   -lphysfs -lGL -lGLU \
   /usr/lib/x86_64-linux-gnu/libGLEW.so \
   -lcurl -latomic

This link statement is basically adapted from what you would get when linking the original Supertux binary, which you can find out with 'make VERBOSE=1'

Now you should be able to start ./supertux2_cbe and it should run. Now, you can finally apply Tigress:

tigress \
   --Verbosity=1 \
   --Environment=x86_64:Linux:Gcc:12.2 \
   --Transform=Virtualize \
      --Functions=_ZN13ScreenManager9loop_iterEv \
      --VirtualizeDispatch=direct \
   --Transform=Flatten \
      --Functions=_ZN13ScreenManager4drawER10CompositorRNS_9FPS_StatsE \
   --Transform=EncodeArithmetic \
      --Functions=main,_ZN4Main3runEiPPc \
   --out=supertux2.cbe_noline_tigress_3_3_1_actuallyobfuscated.c \
   supertux2.cbe_noline_atomicandmorehacks.c