Encode Literals

Obfuscate integer and/or string literals (such as 42 or "42"). Use --EncodeLiteralsKinds to choose what to encode — integer, string, or both — then pick how with Encoding integers and Encoding strings below.

OptionArgumentsDescription
--Transform EncodeLiterals Replace literal integers and strings with less obvious expressions.
--EncodeLiteralsKinds integer, string, * Specify the types of literals to encode Default=integer,string.
  • integer = Replace literal integers with opaque expressions
  • string = Replace literal strings with calls to a function that generates them
  • * = Same as integer,string
 

Encoding integers

OptionArgumentsDescription
--Transform EncodeLiterals Replace literal integers and strings with less obvious expressions.
--EncodeLiteralsMaxLevel INTSPEC Deprecated (no effect). Formerly the recursion depth of the retired split integer kind. Accepted for compatibility but ignored. Default=100.
--EncodeLiteralsMaxTransforms INTSPEC Deprecated (no effect). Formerly the number of EncodeArithmetic transformations applied by the retired split integer kind. Accepted for compatibility but ignored. Default=100.
--EncodeLiteralsIntegerKinds opaque, split Specify how to encode integer literals. Default=opaque.
  • opaque = Replace each literal with an opaque expression that decomposes it so the literal never appears (the value-tree strategy).
  • split = Deprecated -- a leaky byte-split + EncodeArithmetic that exposed the literal's bytes. Now a compatibility alias for opaque.

For example, this function:

int foo(void) {
   int x = 42;
   return x;
}

becomes, with --EncodeLiteralsIntegerKinds=opaque (the default), an expression that still evaluates to 42 but is built from opaque program state, so the value is no longer visible. The literal is decomposed into random sub-values that are recombined and each encoded from opaque state, so 42 itself never appears anywhere in the output. This kind requires a --Transform=InitOpaque step before EncodeLiterals to set that state up:

int foo(void) {
   int x;
   /* 42 = a + b for random a, b; each of a, b is built from opaque state,
      so neither 42 nor a recognisable part of it is written literally */
   x =  + ;
   return x;
}


Regions

You can apply this transformation to a part of a function. First, include tigress.h as usual, then enclose the regions you want to transform using the ENCODE_INTEGER_BEGIN and ENCODE_INTEGER_END macros:


void foo () {  
   ...

   ENCODE_INTEGER_BEGIN(obfuscateThis);

   int x = 1234567;

   ENCODE_INTEGER_END(obfuscateThis);

   ...

   ENCODE_INTEGER_BEGIN(obfuscateThat);

   int y = 7654321;

   ENCODE_INTEGER_END(obfuscateThat);

   ...
}

The arguments to the macros (obfuscateThis and obfuscateThat) are tags that you specify in the -Regions=... option in your tigress command:

tigress ... \
   --Transform=EncodeLiterals \
      --Functions=foo \
      --Regions=obfuscateThis,obfuscateThat \
   ...

 

Choosing the opaque value encoder

When --EncodeLiteralsIntegerKinds=opaque, each literal is replaced by an opaque expression built from the opaque invariants set up at --Transform=InitOpaque. The same selection options the other opaque consumers carry (AddOpaque, Flatten, Merge) apply here too, letting you steer which invariants are drawn, at what resilience, and how many are composed. By default the literal is built by the value-tree strategy — a recursive decomposition whose leaves encode only random sub-values, so the literal never appears in the output. In deity mode, --EncodeLiteralsSelect pins the strategy or invariant by name (see --InitOpaqueList): --EncodeLiteralsSelect=value_tree names the default explicitly, whereas selecting a single bare encoder (e.g. mba_nonlinear) emits the literal as an additive term, so choose one only deliberately.

OptionArgumentsDescription
--EncodeLiteralsInvariantKinds identity, modular, mba, structure_state, scalar_state, environment, plugin, * Comma-separated list of the kinds of opaque invariant the integer opaque kind may draw from when building an opaque expression for a literal, named by the hardness family they rest on. Constrained to the kinds set up by --InitOpaqueInvariantKinds. Only meaningful with --EncodeLiteralsIntegerKinds=opaque. From version 4.1 Default=The kinds specified at InitOpaque..
  • identity = Universally-true arithmetic identities.
  • modular = Residue and modular-arithmetic facts.
  • mba = Mixed boolean-arithmetic.
  • structure_state = A data structure that maintains an invariant as the program runs and is then queried structurally.
  • scalar_state = Evolving scalar state.
  • environment = Opaque expressions from entropy. Requires --InitEntropy.
  • plugin = Invariants supplied by a user plugin.
  • * = Same as identity,modular,mba,structure_state,scalar_state,environment,plugin
--EncodeLiteralsInvariantResilience trivial, local, global, interprocedural, inter_process, * Comma-separated list of the resilience levels the integer opaque kind may draw from -- the scope of analysis an attacker must run to decide the invariant. Constrained to the levels set up by --InitOpaqueInvariantResilience. Only meaningful with --EncodeLiteralsIntegerKinds=opaque. From version 4.1 Default=The levels specified at InitOpaque..
  • trivial = One expression decides it, AND a stock compiler at -O2 folds it to a constant.
  • local = One expression decides it, but -O2 does not fold it.
  • global = Deciding it needs analysis of the whole procedure.
  • interprocedural = Deciding it needs whole-program analysis.
  • inter_process = Deciding it needs reasoning about concurrent interleavings. Reserved -- no invariant is at this level yet.
  • * = Every level, trivial included.
--EncodeLiteralsMaxSize INTSPEC The largest number of sub-opaques one opaque value may be composed of when the integer opaque kind builds an expression for a literal. With the default of 1 the value is a single opaque; a larger value composes several independent opaques. The actual size is drawn at random from 1 to the given value. Only meaningful with --EncodeLiteralsIntegerKinds=opaque. Default=1.
--EncodeLiteralsSelect (Deity mode only.) Comma-separated list of exact invariant names the integer opaque kind must draw from, bypassing the kind and resilience selection entirely. Names come from --InitOpaqueList. Silently inert unless the deity key is supplied with --DeityMode. The named invariants must have been set up by InitOpaque, so pair this with a permissive --InitOpaqueInvariantKinds/--InitOpaqueInvariantResilience. The special name value_tree selects the default recursive value-tree strategy (which never emits the literal); a single bare invariant name (e.g. mba_nonlinear) instead emits the literal as an additive term, so use one only deliberately. From version 4.1 Default=value_tree.
--EncodeLiteralsStructs Deprecated spelling of --EncodeLiteralsInvariantKinds. Kept for compatibility. Replace --EncodeLiteralsStructs=list,array with --EncodeLiteralsInvariantKinds=structure_state. Replace --EncodeLiteralsStructs=env with --EncodeLiteralsInvariantKinds=environment. Default=The kinds specified in --InitOpaqueInvariantKinds..
 

Encoding strings

OptionArgumentsDescription
--EncodeLiteralsEncoderName string The name of the generated encoder function (only for encoded strings). Default=None.
--EncodeLiteralsStringKinds fsm, chunk, * How to encode string literals. When you list several, one is chosen at random each run. Default=fsm.
  • fsm = Rebuild each string at runtime with a generated decoder function.
  • chunk = Encode each string in small pieces, so no readable text remains in the generated source or the compiled binary.
  • * = Same as fsm,chunk

For example, this function:

char *foo(void) {
   return "hi";
}

becomes, with --EncodeLiteralsStringKinds=fsm (the default), a call to a generated function that writes the string one character at a time:

char litStr[3];

void stringEncoder(int n, char str[]) {
   int i = 0;
   switch (n) {
   case 0:
      str[i] = 'h';
      i++;
      str[i] = 'i';
      i++;
      str[i] = '\0';
      i++;
      break;
   }
}

char *foo(void) {
   stringEncoder(0, litStr);
   return litStr;
}

With --EncodeLiteralsStringKinds=chunk the characters are encoded too, so no readable text is left anywhere in the output — here the "hi" bytes come out of an exclusive-or:

char litStr[3];

void stringDecoder(int n, char str[]) {
   int i = 0;
   unsigned int c;
   switch (n) {
   case 0:
      c = 1544710088U ^ 1544687264U;   /* the "hi" bytes */
      str[i] = (char)((c >> 0) & 255);
      i++;
      str[i] = (char)((c >> 8) & 255);
      i++;
      str[i] = '\0';
      i++;
      break;
   }
}

char *foo(void) {
   stringDecoder(0, litStr);
   return litStr;
}

 

Issues

The generated string decoder is deliberately simple. To make it less conspicuous, transform it further — for example with Virtualize (to hide the string contents) and RndArgs (to hide the interface). This matters especially with chunk: without a following transform, a compiler's optimizer may fold the decoding back to the original string.