Recipes

Below are some simple recipes that will make it easy for you to get started using Tigress. See them as a starting point for you to explore all of Tigress' features, not as recommendations for particular situations. Before using any kind of software protection technique you must start with a detailed security analysis:

  • What assets are you trying to protect?
  • What is the value of those assets?
  • For how long do you need to protect those assets?
  • What is your performance budget?
  • How does obfuscation fit into your overall security architecture?
  • What are you adversary's goals and motivation?
  • What are your adversary's tools and skill sets?

Only when you have answered these questions, and have determined that obfuscation needs to be an integral part of your architecture, can you start putting together Tigress scripts. Over time we will add more recipes here. If you have found a particular combination of Tigress transformations useful, please let us know, and we will add your recipe to the collection!

In the examples below we will be using these programs as input: test.c and test-jit.c.

Recipe #1: Opaque Predicates, Branch Functions, and Encoded Arithmetic

Here is a simple recipe that should have a relatively low performance impact:

tigress --Seed=42 --Statistics=0 --Verbosity=0 --Environment=x86_64:Darwin:Clang:5.1  \
     --Transform=InitEntropy \
        --Functions=init_tigress \
        --InitEntropyKinds=vars \
     --Transform=InitOpaque \
        --Functions=init_tigress \
        --InitOpaqueStructs=list,array,env  \
     --Transform=InitBranchFuns \
        --InitBranchFunsCount=1 \
     --Transform=AddOpaque \
        --Functions=fac,fib \
        --AddOpaqueStructs=list \
        --AddOpaqueKinds=true \
     --Transform=AntiBranchAnalysis \
       --Functions=fac,fib \
       --AntiBranchAnalysisKinds=branchFuns \
       --AntiBranchAnalysisObfuscateBranchFunCall=false \
       --AntiBranchAnalysisBranchFunFlatten=true \
     --Transform=EncodeArithmetic \
        --Functions=fac,fib  \
    test.c --out=output1.c 

Here is the resulting code. There are a few things to note here:

  • Most Tigress scripts require you to start off with a sequence of Init-transformations. In this case we have InitEntropy, InitOpaque, and InitBranchFuns. This is admittedly annoying, but necessary, so that Tigress can create the types, variables, and functions necessary to transform the program. For example, InitBranchFuns creates one or more branch functions that the AntiBranchAnalysis transformation can use later on.
  • It is convenient, but not necessary, to create a special init_tigress function into which the Init-transformations can put initialization code. This function can then also be obfuscated to hide the initializations!
  • --AntiBranchAnalysisBranchFunFlatten=true will flatten the code prior to inserting calls to branch functions. This is because branch functions operate on direct jumps (gotos), and most codes don't have many of those. After the flattening transformation, however, there are many jumps to encode.
  • If we instead set --Seed=0 (which means the internal random number generator will be seeded with a new value every time Tigress is run), every time you run this script you will get a differently obfuscated program.
  • The generated code starts off with a lot of seemingly random declarations. This is because Tigress is a whole program obfuscator: it starts by including all the .h files (including all system files) into one .c file that then becomes the input to the obfuscation process. If your program consists of multiple C-files you first need to merge them together using tigress-merge.

Recipe #2: Virtualization and Self-Modification

This transformation shows off a combination of two more powerful transformation, virtualization and self-modifying code, that together is likely to have serious performance impact:

tigress --Seed=42 --Statistics=0 --Verbosity=0 --Environment=x86_64:Darwin:Clang:9.0  \
     --Transform=InitEntropy \
        --Functions=init_tigress \
        --InitEntropyKinds=vars \
     --Transform=InitOpaque \
        --Functions=init_tigress \
        --InitOpaqueStructs=list,array,env  \
     --Transform=Virtualize \
        --Skip=false \
        --VirtualizeDispatch=ifnest \
        --Functions=fac,fib \
     --Transform=SelfModify \
       --Skip=false \
       --Functions=fac,fib \
       --SelfModifySubExpressions=false \
       --SelfModifyBogusInstructions=10 \
    test.c --out=output2.c 

Here is the resulting code. Here are a few things to note:

  • The --Skip-false option is handy to turn transformations on-and-off to try out the impact of different transformations.
  • For more diversity, try different values for --VirtualizeDispatch.
  • To compile this program you need to use gcc -segprot __TEXT rwx rwx output2.c -o output2.exe (on Darwin). See the SelfModify page for more information.

Recipe #3: Virtualization and Dynamic Obfuscation

In this recipe we first virtualize and then add dynamic obfusction; i.e. the program will decode and re-encode itself as it is running. In this particular case we decode/encode using the XTEA cipher:

tigress --Seed=42 --Statistics=0 --Verbosity=0 --Environment=x86_64:Darwin:Clang:5.1  \
     --Transform=InitEntropy \
        --Functions=init_tigress \
        --InitEntropyKinds=vars \
     --Transform=InitOpaque \
        --Functions=init_tigress \
        --InitOpaqueStructs=list,array,env  \
     --Transform=Virtualize \
        --Skip=false \
        --VirtualizeDispatch=direct \
        --Functions=fib \
     --Transform=JitDynamic \
        --Skip=false \
        --Functions=fib \
        --JitDynamicCodecs=xtea \
        --JitDynamicBlockFraction=%100 \
     --Transform=Measure \
        --Functions=fib \
        --MeasureTimes=100 \
    test-jit.c --out=output3.c 

Here is the resulting code. Here are a few things to note:

  • We use the Measure transformation along with differen values of Skip to measure the performance impact. On my machine virtualization alone takes 0.000192 seconds, dynamic obfuscation alone takes 0.077977 seconds, and together, the program slows down to 2.125580 seconds!
  • Setting --JitDynamicBlockFraction=1 (which means only one basic block will be encoded) reduces the time to 1.800674 seconds.

Recipe #4: Merge, Virtualization, and Encode Literals

In this recipe we first merge all functions together. This includes init_tigress which has important secrets in it, in particular the initalization of opaque structures:

tigress --Seed=42 --Statistics=0 --Verbosity=1 --Environment=x86_64:Darwin:Clang:9.0  \
     --Transform=InitEntropy \
        --Functions=init_tigress \
        --InitEntropyKinds=vars \
     --Transform=InitOpaque \
        --Functions=init_tigress \
        --InitOpaqueStructs=list,array,env  \
     --Transform=Merge \
        --MergeFlatten=false \
        --MergeName=MERGED \
        --Functions=fib,fac,init_tigress \
     --Transform=Virtualize \
        --VirtualizeDispatch=direct \
        --Functions=MERGED \
     --Transform=EncodeLiterals \
        --Functions=main \
    test.c --out=output4.c 

Here is the resulting code. Here are a few things to note:

  • There is no need to flatten the code in the merging process since we are going to virtualize the resulting code anyway. So we set --MergeFlatten=false to save some on performance.
  • Have a look at main: it now consists of 3 calls to MERGED rather than calls to the individual functions.
  • Merging adds an extra argument so that we can know which function we're calling. This is a dead giveaway so we use the EncodeLiterals transformation to add some opaque expressions to the calls to MERGED in main
  • Another possibility is add extra bogus arguments, using --Transform=RndArgs --Functions=MERGED --RndArgsBogusNo=10, which results in this code.