DEV Community

lich0
lich0

Posted on

SLLVM - A unique OLLVM-based obfuscator

https://github.com/lich4/sllvm

Supported Systems and Architectures

  • Host Systems: Planned support for macOS/Linux; no current plans for Windows.
  • Target Systems: Planned support for macOS/iOS/Android.
  • Architectures: Both Host and Target support X64 and ARM64.
  • Languages: Supports C, C++, Objective-C, Objective-C++, Swift, Rust, Golang, etc.

Obfuscation Capabilities

  • Data Encryption
    • CE Constant Encryption (String obfuscation).
  • Control Flow Obfuscation
    • FLA Control Flow Flattening.
    • BCF Bogus Control Flow.
    • ECF Novel Control Flow obfuscation.
  • Function-Level Obfuscation
    • FW Function Wrapper (Function nesting).
    • FCC Function Calling Convention obfuscation.
  • Instruction-Level Obfuscation
    • IBR Indirect Branch.
    • ICALL Indirect Call.
    • IGV Indirect Global Variable reference.
    • SVC Supervisor Call (System call) obfuscation.
    • SPLIT Instruction Splitting.
  • 其他
    • INLINE Function Inlining.
    • SEC Anti-debugging/Security protection.

SLLVM Features

SLLVM was developed to address limitations in the OLLVM lineage (Hikari, Hikari-LLVM15, Pluto, Polaris-Obfuscator, goron, Arkari, o-mvll, etc.):

  • Release Compatibility: Obfuscation is not reverted by the compiler during Release builds.
  • Anti-Analysis: Resists "Read-Only Data Segment" attacks in IDA Pro; if data segments are marked read-only, obfuscation remains intact.
  • Hardening: Removes common OLLVM signatures to resist specialized scripts, symbolic execution (Angr), and AI-based deobfuscation.
  • Stability: Avoids compilation hangs and memory exhaustion often seen in other variants when processing large header-only libraries.

Configuration

In many real-world projects, fully obfuscating the entire codebase is often impractical due to the following reasons:

  • Project Scale and Dependencies: Large projects or those utilizing numerous header-only libraries may end up obfuscating unnecessary code, resulting in excessively large binary sizes.
  • Compilation Overhead: For massive projects with complex dependencies, applying flattening (or other methods) to unnecessary code can lead to extremely long compilation times or even system hangs.
  • Performance Impact: Obfuscating complex algorithms can significantly increase runtime latency; typically, control flow flattening adds over 10% to execution time.
  • Compliance Issues: Excessive obfuscation may violate the submission policies of platforms like the AppStore or GooglePlay.

In practice, it is often necessary to apply different levels of obfuscation based on the importance of specific modules or functions. This requires a configuration strategy to specify which targets receive which type of obfuscation. Traditional open-source OLLVM implementations usually manage strategies through the following methods:

  • Command-line Arguments: Specifying parameters for specific modules (e.g., -llvm -fla), which is compatible with all compiler front-ends supporting LLVM flags.
  • Environment Variables: Defining obfuscation parameters via the system environment.
  • Function Annotations: Using attributes like __attribute((__annotate__(("fla")))) (or the modern syntax [[clang::annotate("fla")]]); however, this is restricted to C/C++ and is not supported by Objective-C or other languages.
  • Marker Functions: Designating specific functions as markers, as shown below; this method provides support for Objective-C.

All of the aforementioned methods have their limitations—they either require intrusive code changes, fail to provide control at the function level, or are restricted to specific languages. This project utilizes a configuration file (sllvm.json) to define which functions and modules should be obfuscated, ensuring compatibility with the majority of compiler frontends and programming languages.

Primary/Secondary Fields

  • log_level Global logging level. String type, optional. Defaults to no logging. Possible values: info | debug
  • src_root Source file root path. String type, optional. Defaults to the current directory; typically does not need to be specified.
  • policies A list of strategies, divided into Module-level and Function-level policies. Module-level policies contain module and policy fields but no func field. Function-level policies include module, func, and policy fields.
    • module A regular expression used to match module paths.
    • func regular expression used to match function names. C++ functions are demangled before matching.
    • policy String type; must correspond to a key defined in policy_map.
  • policy_map A strategy index referenced by policies. Each strategy name maps to a dictionary containing the following fields:
    • base The name of a base policy to inherit from (module or function level). String type, optional. Value must be a key in policy_map.
    • dump Types of intermediate code to output (saved in the sllvm_dump directory). Applies to module or function levels. String array, defaults to empty. Possible values: ir, mmd, asm.
    • enable_std Enables obfuscation for C++ standard library functions. Module-level policy. Boolean type, optional. Disabled by default to minimize compatibility issues.

Function-level Policy

  • enable_ce Enables CE obfuscation.
    • ce_size_min Minimum string length.
    • ce_size_max Maximum string length.
    • ce_algo Encryption/decryption algorithm.
    • ce_mode_stack Enables stack-based string decryption.
  • enable_fla Enables FLA obfuscation.
    • fla_prob Obfuscation probability for BasicBlocks.
    • fla_force_reg Increases obfuscation strength.
    • fla_use_igv Increases obfuscation strength.
    • fla_use_dyn Increases obfuscation strength.
    • fla_use_rcf Increases obfuscation strength.
    • fla_blk_size Increases obfuscation strength.
    • fla_invoke_op Compatibility for exception handling.
  • enable_bcf Enables BCF obfuscation.
    • bcf_prob Obfuscation probability for BasicBlocks.
    • bcf_complex Expression complexity.
    • bcf_use_var Uses variables to construct expressions.
  • enable_ecf Enables ECF obfuscation.
    • ecf_prob Obfuscation probability for BasicBlocks.
  • enable_fw Enables FW obfuscation.
    • fw_loop_min Minimum number of function nesting layers.
    • fw_loop_max Maximum number of function nesting layers.
    • fw_exclude Sub-functions to exclude.
  • enable_fcc Enables FCC obfuscation.
    • fcc_num Number of randomized calling conventions (Module-level).
    • fcc_type Type of customized calling convention (Module-level).
    • fcc_narg_reg Maximum number of parameters passed via registers (Module-level).
  • enable_ibr Enables IBR obfuscation.
    • ibr_prob Obfuscation probability for BasicBlocks.
    • ibr_use_igv Increases obfuscation strength.
    • ibr_use_dyn Increases obfuscation strength.
  • enable_icall Enables ICALL obfuscation.
    • icall_use_igv Increases obfuscation strength (Enabled by default).
    • icall_use_dyn Increases obfuscation strength.
  • enable_igv Enables IGV obfuscation.
    • igv_use_dyn Increases obfuscation strength.
  • enable_svc Enables SVC obfuscation.
    • svc_usr_ir Increases obfuscation strength.
  • enable_split Enables SPLIT obfuscation.
    • split_maxsize Maximum number of instructions per split segment.
  • enable_inline Enables INLINE obfuscation.
  • enable_sec Enables SEC obfuscation.
    • sec_ad_prob Insertion probability for function anti-debugging logic.
    • sec_usr_ir Increases obfuscation strength.

A typical SLLVM configuration file sllvm.json is as follows::

{
    "log_level": "info",
    "policy_map": {
        "mod_pol": {               
            "dump": ["ir"],
        },
        "func_pol": {               
            "enable_ce": true,
            "ce_size_min": 5,
            "ce_size_max": 128,
            "ce_algo": 0
        }
    },
    "policies": [
        {
            "desc": "Module-level policy",
            "module": ".*",
            "policy": "mod_pol"
        },
        {
            "desc": "Function-level policy",
            "module": ".*",
            "func": ".*",
            "policy": "func_pol"
        }
    ]
}
Enter fullscreen mode Exit fullscreen mode

Supported Obfuscation Methods

String Encryption

Currently, SLLVM's CE supports arm64/arm64e, Objective-C constant strings, and stack-based decryption via ce_mode_stack. Unlike Hikari, which performs module-level obfuscation, SLLVM operates at the function level. This allows users to obfuscate all strings within specific functions, which is particularly useful for handling header-onlylibraries.

Key differences from Hikari:

  • Supports encryption algorithms beyond simple XOR.
  • Supports stack-based decryption(ce_mode_stack)

ce_algo

Used to configure the encryption/decryption algorithm. SLLVM currently supports 30 algorithms with complexity ranging from XOR to AES-level. Setting this value to 100 will select a random algorithm.

ce_mode_stack

This setting controls whether strings are decrypted on the stack. If ce_mode_stack is disabled, SLLVM utilizes the same approach as Hikari. The table below compares Hikari's method, SLLVM's S-mode (stack), and C++ template metaprogramming-based string obfuscation:

Hikari SLLVMS-Mode C++ Template Metaprogramming
Encryption Location Static Area Static Area Static Area / Immediate
Decryption Timing Function Prologue Function Prologue Before Reference
Decryption Location Static Area Stack Area Stack Area
Requires Source Change N N Y
Complex Algorithms Supported Supported Not Suitable

Notes:

  • Decryption Timing: Hikari decrypts once at the function prologue. While some other OLLVM-based projects decrypt in an initialization function, that method has the disadvantage of plaintext appearing in the static area immediately after decryption.
  • Language Support: C++ template methods are limited to C++. While languages like Rust have third-party libraries for similar results, all such methods require manual changes to the source code.

Important: Most OLLVM variants do not implement stack-based decryption because string constants are static data, making it difficult for the IR layer to determine if a string might "escape." In SLLVM, enabling ce_mode_stack demotes strings from static data to stack data, which requires specific handling.

Showcase

int main(int argc, char** argv) {
    printf("hello sllvm\n");
    return 0;
}
Enter fullscreen mode Exit fullscreen mode

CE Static Area

CE Static Area

CE Stack Area

CE Stack Area

Control Flow Flattening

Transforms control flow from sequential execution into a switch-case loop structure. Key differences from Hikari include:

  • Signature Weakening: Significantly reduces common FLA patterns, such as state variables, in-degrees, dispatcher blocks, and single-loop structures.
  • Exception Handling: While Hikari cannot handle functions with exception handling, SLLVM allows you to choose a specific handling method via the fla_invoke_op parameter.

展示

int main(int argc, char** argv) {
    if (argc <= 0) {
        printf("not possible\n");
    } else if (argc == 1) {
        printf("no args\n");
    } else {
        printf("%d args\n", argc - 1);
    }
    return 0;
}
Enter fullscreen mode Exit fullscreen mode

FLA Full

FLA Full

Bogus Control Flow

Inserts "always-false" conditional branches into the sequential control flow. Key differences from Hikari include:

  • Release Stability: The obfuscation is not reverted or optimized away by the compiler during Release builds.
  • Resilience to Static Analysis: Resists "read-only data segment" attacks; the obfuscation remains intact even if the data segment is set to read-only in IDA Pro (this feature relies on bcf_use_var).

Showcase

int main(int argc, char** argv) {
    if (argc <= 0) {
        printf("not possible\n");
    } else if (argc == 1) {
        printf("no args\n");
    } else {
        printf("%d args\n", argc - 1);
    }
    return 0;
}
Enter fullscreen mode Exit fullscreen mode

BCF Constant

BCF Constant

BCF Variable

BCF Variable

Novel Control Flow

A brand-new obfuscation approach designed to counter tracing and symbolic execution by tools like Angr.

Function Wrapper

Performs nesting on sub-functions directly called by the designated function. Key difference from Hikari:

  • The obfuscation is not reverted or optimized away by the compiler during Release builds.

Calling Convention Obfuscation

Converts standard C calling conventions into randomized ones, altering the registers used for parameters and return values. Currently, this is partially implemented for ARM64.

  • fcc_num Specifies the number of randomized calling conventions.
  • fcc_type Specifies the type of customized calling convention, with the following values:
    • 0 Uses only registers X0~X8
    • 1 Uses only integer registers.
    • 2 Uses only floating-point registers.
    • 10 Uses any available registers.
  • fcc_narg_reg Specifies the maximum number of parameters passed via registers; remaining parameters are passed via the stack.

Showcase

static int test(int a0, int a1, int a2, int a3, int a4) {
    printf("a0=%d\n", a0);
    printf("a1=%d\n", a1);
    printf("a2=%d\n", a2);
    printf("a3=%d\n", a3);
    printf("a4=%d\n", a4);
    return a0 + a1 + a2 + a3 + a4;
}

int main(int argc, char** argv) {
    test(argv[0][0], argv[0][1], argv[0][2], argv[0][3], argv[0][4]);
    return 0;
}
Enter fullscreen mode Exit fullscreen mode

FCC usingX8(X8,X1,X6,...)

FCC_BASE

FCC usingD26(D26,D2,X15,...)

FCC_FULL

Indirect Branch

Key differences from Hikari:

  • The obfuscation is not reverted by the compiler when compiling in Release mode.
  • ibr_use_igv is similar to Hikari's indibran-enc-jump-target. When combined with ibr_use_dyn, it further enhances the obfuscation strength.

Showcase

int main(int argc, char** argv) {
    if (argc <= 0) {
        printf("not possible\n");
    } else if (argc == 1) {
        printf("no args\n");
    } else {
        printf("%d args\n", argc - 1);
    }
    return 0;
}
Enter fullscreen mode Exit fullscreen mode

IBR Full

IBR Full

System Call Obfuscation

Converts standard system call functions into direct SVC (Supervisor Call) instructions.

Showcase

int main(int argc, char** argv) {
    int r = access("/tmp/1.txt", 0);
    printf("r=%d\n", r);
    return 0;
}
Enter fullscreen mode Exit fullscreen mode

SVC Base
SVC基础

SVC Custom1

SVC Custom1

Instruction Splitting

Splits a function's instructions and scatters them across random addresses throughout the entire module.

Showcase

void test(int argc) {
    if (argc <= 0) {
        printf("not possible\n");
    } else if (argc == 1) {
        printf("no arg\n");
    } else {
        printf("%d args\n", argc - 1);
    }
}

int main(int argc, const char** argv) {
    if (argc <= 0) {
        printf("not possible\n");
    } else if (argc == 1) {
        printf("no arg\n");
    } else if (argc == 2) {
        printf("1 arg\n");
    } else {
        printf("%d args\n", argc - 1);
    }
    return 0;
}
Enter fullscreen mode Exit fullscreen mode

SPLIT Full

SPLIT Full

Function Inlining

Inlines all sub-functions called by a specified function into the current function's body.

Security Protection

Inserts anti-debugging logic directly into the specified functions.

Top comments (0)