Updates v4.0.11 ➞ v4.1


Language changes

These are the syntax/semantic updates which may break current programs.

  • bool is a keyword (C23 6.7.2).
  • nullptr is a keyword (C23 6.4.4.6).
  • enum : { … } — C23/clang fixed-underlying-type enums. Only the anonymous form (colon straight after enum)
  • GCC statement and label attributes — __attribute__((...)) stmt; and attributes on labels now keep their attributes instead of being dropped.
  • An attribute between a type specifier and a typedef name — typedef int __attribute__((mode(__pointer__))) intptr_t; now parses as a redefinition rather than a bogus specifier list.
  • Unary + performs the integer promotion (C11 6.5.3.3). sizeof(+c) on a char is now 4, was 1.
  • _Complex actually works.
  • _Noreturn is parsed for real.
  • __builtin_bswap<N> is declared unsigned, as gcc and clang declare it.
  • __int128 is refused where the target has none — i386, armv7 and MSVC, per the new sizeof_int128 in the machine model.
  • 128-bit constants print correctly. Emitted as (unsigned __int128)(0xHI ULL << 64 | 0xLO ULL) rather than a bare decimal literal, which C silently truncates.

Opaques

Opaque values can now be constructed along three independent axes.

  • First the hardness family (the kind):
    • identity
    • modular
    • mba
    • structure_state
    • scalar_state
    • environment
    • plugin
  • Second, the scope of analysis needed to defeat them (the resilience):
    • trivial
    • local
    • global
    • interprocedural.
  • Third, the size of the opaque expression.

The default for resilience is every level except trivial. Trival contains invariants a C compiler may fold to a constant at -O2; these are kept only for educational purposes.

See InitOpaque.

  • The default set of opaque invariants are set with the --Transform=InitOpaque) transform using the two options:
    • --InitOpaqueInvariantKinds
    • --InitOpaqueInvariantResilience
  • The new --InitOpaqueMaxSize option sets the default composition size for transformations that do not set their own. Defaults to 1.
  • The --InitOpaqueStructs option is a deprecated spelling of --InitOpaqueInvariantKinds, kept for compatibility; it maps the old kind names onto the new ones. The input kind was removed. Default none.
  • The kind, resilience, and size can be set for all transformations <T> that need them (AddOpaque, Flatten, Merge, Virtualize, InitBranchFuns and AntiBranchAnalysis):
    • --<T>OpaqueInvariantKinds
    • --<T>OpaqueInvariantResilience
    • --<T>OpaqueMaxSize
    For example, for AddOpaque we get the three options --AddOpaqueInvariantKinds, --AddOpaqueInvariantResilience, and --AddOpaqueMaxSize. Each default to what InitOpaque set up.
  • The option --AddOpaqueStructs is now deprecated. It maps onto the new kinds:
    • list/arraystructure_state
    • envenvironment
    • input was removed.
    Its default is the kinds given to InitOpaque. This option will be removed in a future version.
  • The new --Transform=InitOpaque --InitOpaqueList=true option prints the whole opaque-invariant database. This requires the --DeityMode=password to be set. This allows the exact name of an opaque predicate to be passed to --<T>Select.
  • A new per-transform --<Transform>Select option (e.g. --AddOpaqueSelect=name) draws opaque invariants by exact name, bypassing the kind and resilience selection. This requires the --DeityMode=password to be set.
  • The new --InitOpaqueObfuscate option, on by default, applies light obfuscation to the initialization of the opaque state variables.
  • The new --UpdateOpaqueObfuscate option, on by default, applies light obfuscation to the opaque state updates.

DeityMode

  • The new --DeityMode=password option gives access to additional Tigress features.

AntiBranchAnalysis

  • The option --AntiBranchAnalysisBranchFunFlatten has been renamed --AntiBranchAnalysisFlatten.
  • The new --AntiBranchAnalysisFraction option controls how many of the unconditional branches are encoded, defaulting to %100.
  • In --AntiBranchAnalysisKinds, the new kind goto2push2 replaces goto L with push L; leal; jmp.

CleanUp

See CleanUp.

  • The new --CleanUpRenameKinds option chooses the style of the new identifiers:
    • short names (the default)
    • English words (english, english2)
    • identifiers drawn from the Exebench benchmark suite (exebench, exebench2)
    • a word followed by a programming abbreviation in camel case (englishAbbr, exebenchAbbr).
  • --CleanUpKinds has two new kinds:
  • noLineNumbers drops the #line directives we emit before globals
  • noNestedBlocks flattens blocks-within-blocks.

ImplicitFlow

  • A new implicit-flow kind callChain for --InitImplicitFlowKinds copies a variable by passing it along a chain of calls.
  • The new --InitImplicitCallChainLength option sets the length of the call chain generated for --InitImplicitFlowKinds=callChain, defaulting to 800.
  • The new --InitImplicitFlowCallChainName option sets the base name of the generated call-chain functions, with the number appended, defaulting to callChain.

LowerType

See LowerType.

  • --Transform=LowerType is a new transformation. Its float2fixed converter rewrites a function's float and double computation as fixed-point integer arithmetic. This allows integer obfuscators like EncodeArithmetic and EncodeData to transform code they previously could not see at all.

EncodeExternal

  • --EncodeExternalKind=embed can now include source code from more common libraries:
    • random (PRNG)
    • checksum
    • parse
    • bignum
    • crude bit-trick approximations of the math functions
    • crypto.

128-bit integers

  • --Allow128BitInts is a new top-level option saying whether the target has 128-bit integers ( __int128). You normally do not set it: it is read from the machine model that --Environment selects.
  • A function carrying a 128-bit integer constant cannot be jitted, since MyJit has no 128-bit register, and Tigress now reports this instead of truncating the constant to 64 bits and silently changing what the function computes. When the constant comes from a value codec rather than from your own source, --Allow128BitInts=false makes the codec pick a formulation that stays in 64 bits. See Jit.

Virtualize

See Virtualize.

  • The default for --VirtualizeDynamicBytecode, which makes the virtualized bytecode self-modifying, is now false instead of hard.

EncodeData

See EncodeData.

  • The Residue Number Coding codec (--EncodeDataCodecs=rnc) is now correct at every integer type; before 4.1 it silently computed wrong values at unsigned short, unsigned int, long and unsigned long. Its one remaining limitation is now documented: the codec is exact only while the value being reconstructed stays below half the product of its moduli, which a single operation on in-range values always satisfies, but a value accumulated in a loop eventually will not. Prefer poly1 or xor for such variables; both are exact for every value of every integer type.

EncodeLiterals

  • The new --EncodeLiteralsStringKinds option chooses how string literals are encoded (one is picked at random per run when several are given):
    • fsm (the default) rebuilds each string at runtime with a generated decoder function.
    • chunk encodes each string in small pieces, so no readable text remains in the generated source or the compiled binary.

Major Bug Fixes

1

Fixed a spurious Invalid combination of type specifiers error when a typedef redefined an existing type name with an attribute in between, as in typedef int __attribute__((mode(__pointer__))) intptr_t;.

2

Tigress no longer reports Invalid combination of type specifiers on the typedefs in system headers such as bits/floatn-common.h.

3

The JitDynamic transform now works on Darwin/ARM64, where the transformed program previously died with a bus error at the first block decode because Apple Silicon forbids a page that is writable and executable at the same time.

4

Generated code no longer uses the MSVC-only __inline specifier, which GCC did not understand and which left merged functions such as base64_decode_ctx_init as undefined references at link time.

5

Fixed the type checking of MBA plugin functions for comparison operators, which wrongly rejected valid declarations such as long mba_Eq_2(long a, long y) with a Wrong argument types error.

6

Fixed Merge passing an untyped 0 as the return-value argument of a merged void function, which broke compilation when EncodeLiterals was applied afterwards and turned that 0 into an opaque integer.

7

Fixed a failure when applying Jit to a program that had already been processed by Merge.

8

The Virtualize transform now handles !p for every pointer type, not just void *.

9

Fixed the ordering of ordinary labels around case and default labels in a switch, which came out reversed and could redirect a goto to the wrong place.

10

Fixed a crash caused by printing the wrong label for a goto when the target statement carried several labels, for example a label following a default: label in a switch.

11

Code generated by the Checksum transform now compiles under GCC 15 without the pointer type errors that previously required -fpermissive.

12

Fixed the ordering of generated declarations for functions carrying the two-argument __attribute__((__malloc__(deallocator, 1))), whose deallocator could be declared after its allocator and make GCC reject the output with 'free_buffer' undeclared here (not in a function).

13

Fixed Virtualize with --VirtualizeDynamicBytecode=true emitting the bytecode array twice, the second copy all zeros, which made the obfuscated program loop forever.

14

Fixed crashes, illegal instructions and wrong results in programs transformed with SelfModify on Darwin/ARM.

15

The Virtualize transform no longer generates casts from an integer to a pointer of a different size, which produced -Wint-to-pointer-cast warnings.

16

Fixed incompatible-pointer-type errors when compiling code from Virtualize that assigns to and reads from the virtual program counter.

17

On Darwin, preprocessing now passes -U__BLOCKS__, so system headers that declare Clang block-pointer parameters with ^, such as scandir_b in <dirent.h>, no longer cause a syntax error regardless of whether tigress.h was included first.

18

The #define _Noreturn workaround has been removed from tigress.h, so the C11 _Noreturn specifier is now parsed properly and its noreturn semantics survive into the obfuscated output.

19

Including <stdatomic.h> and using the C11 _Atomic type specifier no longer causes a parse error.

20

Fixed Merge failing with Cannot find the new copy of local variable on a function containing a variable-length array such as int arr[V], both on its own and combined with Flatten.

21

Fixed InitFunctions discarding the bounds of initialized arrays, so an array declared with a constant bound is no longer shrunk to a single element.

22

Fixed a syntax error on the C23 / clang fixed-underlying-type enum enum : <type> { ... }, which blocked any source that transitively included a modern macOS SDK header such as <mach/vm_types.h>.

23

The wrapper no longer prints an empty Using compiler: when --Compiler is omitted, and reports the compiler version consistently when --Compiler and --Environment are resolved from each other.

24

AddOpaque with a math opaque kind no longer inserts a predicate that reads uninitialized local variables, which was undefined behaviour and could change what the obfuscated program computed.

25

Fixed Optimize deleting a goto whose target is the next statement without moving that statement's labels or re-pointing the branches that named it, which left a branch pointing at a label that no longer existed.

26

Fixed Optimize rebuilding sizeof(expr) as _Alignof(expr), which gives the wrong value for anything whose size differs from its alignment.

27

--OptimizeDumpInterferenceGraph=true now writes the graph it promises, instead of doing nothing while --Verbosity=3 wrote the file whether or not it was asked for.

28

--EncodeDataCodecs=rnc no longer computes silently wrong values, and is now correct at every integer type; it was wrong at unsigned short, unsigned int, long and unsigned long.

29

Fixed a generated bit mask being built from a 32-bit int and then shifted by up to 63 places, undefined behaviour that put garbage values into the obfuscated program.

30

--CleanUpKinds=noExterns now also drops the system-header type definitions and static-inline helpers that came with the declarations it removes, so re-adding the #include files no longer causes redefinition errors.

31

Unary + now performs the C11 integer promotion, so sizeof(+c) on a char is sizeof(int); without it an obfuscated program could print different values from the original.

32

The __builtin_bswap<N> family is now declared unsigned, as GCC and clang declare it, instead of signed.

33

Long double floating constants no longer print a treating long double constant ... as double constant warning to the terminal, even at --Verbosity=0.

34

Fixed a segmentation fault in the prologue of a jitted function containing an indirect call under --JitObfuscateArguments, caused by a garbage frame size.

35

Added the C23 nullptr keyword, without which no program compiled against a C23 standard library could be obfuscated at all.

36

Fixed Virtualize aborting with findField: '_unsigned___int128' not found on a function whose body contains a 128-bit value.

37

Fixed an embedded library's unsigned char coming out signed, which made every EncodeExternal-embedded crypto hash produce the wrong digest.

38

Fixed struct-typed library globals being emitted in the wrong order, which crashed Tigress on every embedded library with global tables, such as libMath and libCrypto.

39

Fixed --VirtualizeAddOpaqueToBogusFuns=true corrupting the generated interpreter's dispatch table for the pointer-based dispatch methods, which made the obfuscated program die on a wild computed goto.

40

Jit now reports that it cannot represent a 128-bit integer constant, instead of silently truncating it to 64 bits and changing what the function computes.

41

Fixed --EncodeExternalKind=embed truncating to 32 bits the result of an embedded function called without a visible prototype, which crashed at run time with no diagnostic.

42

Fixed 128-bit integer constants being printed as bare decimal literals, which a C compiler silently truncates modulo 2^64; this made the rnc codec decode to the wrong value and could leave the obfuscated program looping forever.