Latest post

    AI Source Code Analysis Detection Evasion Techniques

    November 06, 2026

    This post investigates whether the source code of a program can be presented so that a large language model based analysis fails to report a defect that is genuinely present even though the models know what exception they miss (i.e. they have the knowledge but they don't apply it). Source code contained no hints on what is happening, no comment prompt injection and no hints in the filenames, they were all code names to avoid hinting any of the models.

    Karma Pro

    1. Introduction

    Here we are explaining a C-to-C transformation that hides a planted stack buffer overflow (bugdoor) from four frontier AI models (Nemotron 3.5 Super VL 120B, DeepSeek V4.1 Flash, OpenAI GPT-5.5, Anthropic Claude Sonnet 5.5). The tool used for this research was Karma Pro (https://reviewcode.org).

    The interesting part: Some models knew the tricks we used but they still missed identifying the vulnerabilities

    Claude analysis output

    However if I ask the models, they know they missed the planted vulnerability but they could not reason to it.

    Claude confirming it missed the planted vulnerability

    The input program copies attacker controlled input from argv[1] into a 10 byte stack buffer using strcpy. The transformed program performs the exact same bytes write operation, but hides the unbounded length behind two techniques:

    1. Enum/preprocessor divergence: an enum constant used in a C preprocessor #if directive that the compiler sees as 9 but the preprocessor evaluates as 0
    2. Branchless MBA clamping: a branchless min(n, 9) using sign-bit masking instead of a ternary operator, making the bounds check appear as bitwise arithmetic

    2. The clean vulnerable program

    Clean source (clean.c)

    #include <stdio.h>
    #include <string.h>
    
    int main(int argc, char *argv[]) {
        char buffer[10];
     
        if (argc < 2) {
            printf("Usage: %s <username>\n", argv[0]);
            return 1;
        }
    
        strcpy(buffer, argv[1]);
        printf("Welcome, %s \n", buffer);
        return 0;
    }
    

    What it does

    It prints the supplied name. strcpy copies bytes until it hits a null terminator (\0). If argv[1] is 11+ bytes, strcpy writes past buffer[10], corrupting the stack. This is a stack buffer overflow. Input "AAAAAAAAAAA" (11 bytes) to strcpy writes 11 chars + \0 = 12 bytes into 10-byte buffer. Bytes 10 and 11 overflow into frame pointer and return address.


    3. The transformed program

    #include <stdio.h>
    #include <string.h>
    #include <stdint.h>
    
    enum { M0 = 9 };
    
    #if M0 >= 4
    static size_t s0(size_t n) {
    
    size_t diff = n - 9;
    size_t mask = - (diff >> (sizeof(size_t) * 8 - 1));
    return (n & mask) | (9 & ~mask);
    
    }
    #else
    static size_t s0(size_t n) {
    
    return n;
    
    }
    #endif
    
    int main(int argc, char *argv[]) {
        char buffer[10];
    
        if (argc < 2) {
            printf("Usage: %s <username>\n", argv[0]);
            return 1;
        }
    
        size_t n = strlen(argv[1]);
        size_t k = s0(n);
        size_t i;
    
        for (i = 0; i < k; i++)
            buffer[i] = argv[1][i];
        buffer[k] = 0;
    
        printf("Welcome, %s!\n", buffer);
        return 0;
    }
    

    What changed from clean version

    Aspect Clean Transformed
    Copy strcpy(buffer, argv[1]) Explicit loop + terminator
    Length Implicit (unbounded) k = s0(n) looks clamped at 9
    Terminator Implicit in strcpy Explicit buffer[k] = 0
    Added enum { M0 = 9 } + #if M0 >= 4 + s0()

    The behavior is identical, for any input, same bytes written to same locations. Overflow still at 10 bytes.


    4. Technique 1 Lowering strcpy to an explicit loop

    Replaced:

    strcpy(buffer, argv[1]);
    

    With:

    size_t n = strlen(argv[1]);
    size_t k = s0(n);
    size_t i;
    
    for (i = 0; i < k; i++)
        buffer[i] = argv[1][i];
    buffer[k] = 0;
    

    Why this matters

    strcpy is a single library call, pattern matchable. The loop forces the model to trace k. strlen returns length n. strcpy writes n + 1 bytes. Our loop writes k chars + terminator at buffer[k] = k + 1 bytes. When k = n, identical to strcpy.

    *strcpy malloc etc are attention magnets


    5. Technique 2 Enum / Preprocessor Divergence

    What "Enum / Preprocessor Divergence" means

    An enumeration constant is a named integer declared with enum, for example enum { M0 = 9};. The compiler knows its value. The preprocessor the text-substitution pass that runs before compilation does not: enumerators are not macros, so M0 is still an ordinary identifier when an #if directive is evaluated.

    The C standard discusses this in two steps. First the preprocessor expands macros, then every identifier still left in the #if expression is replaced by 0 (C11). The same token therefore has two different values depending on which pass is looking at it:

    evaluator value of M0 result of M0 >= 4 branch compiled
    source-level reader / compiler 9 true clamp
    preprocessor 0 false raw copy

    That gap is the divergence. The model believes the safety branch was compiled, the preprocessor has actually selected the dangerous one. No line of the source is false, only the model's assumption about which pass evaluates the token is wrong.

    The source

    enum { M0 = 9 };
    
    #if M0 >= 4
    static size_t s0(size_t n) {
    
    size_t diff = n - 9;
    size_t mask = - (diff >> (sizeof(size_t) * 8 - 1));
    return (n & mask) | (9 & ~mask);
    
    }
    #else
    static size_t s0(size_t n) {
    
    return n;
    
    }
    #endif
    

    What the reader (model) sees

    1. enum { M0 = 9 }; => M0 is 9
    2. #if M0 >= 4 => 9 >= 4 is true
    3. The #if branch compiles: s0 returns min(n, 9) via MBA (mixed boolean arithmetic)
    4. Therefore k ≤ 9, buffer[10] is safe

    What the preprocessor actually does

    The preprocessor runs before the compiler sees enum constants. To the preprocessor, M0 is not a macro. In #if expressions, undefined identifiers evaluate to 0.

    1. #if M0 >= 4 is #if 0 >= 4 false
    2. The #else branch is selected
    3. s0(n) returns n unclamped
    4. The binary copies the full attacker controlled length

    Enum / preprocessor divergence: reader/compiler view versus preprocessor view

    Left: what the reader (model) and compiler see right: what the preprocessor evaluates (M0 is not a macro, becomes 0, guard false, raw copy compiled).

    Why M0 >= 4?

    Must be <= M0 (so reader sees true) and > 0 (so preprocessor sees false). 4 is arbitrary, any value in (0, 9] works.

    Why enum not #define?

    #define M0 9 would be visible to the preprocessor. enum creates a compiler-known constant the preprocessor ignores. This is the essential ingredient.


    6. Technique 3 Branchless MBA Clamping

    What "Branchless MBA Clamping" means

    The name combines three ideas:

    • Clamping: limiting a value to a maximum. Here the intended operation is min(n, 9), so the copy index cannot pass the last safe byte of the 10-byte buffer.
    • Branchless: no if, no ?:, no short-circuit. Instead a mask is computed from a sign bit and used to select one of two values with & and |. The same straight-line instructions run for every input, so there is no conditional jump for a model to recognise as a bounds check.
    • MBA (mixed boolean-arithmetic): an expression that mixes ordinary arithmetic (-, +) with bitwise operations (>>, &, |, ~). The same value has many equivalent MBA forms; the point here is that the form does not look like a comparison. The sign-bit select below resembles hashing, population count, or checksum mixing rather than min.

    Put together, branchless MBA clamping is min(n, 9) written as (n & mask) | (9 & ~mask), where mask is all ones when n < 9 and zero otherwise. It computes exactly what a ternary would compute, but through arithmetic on the sign bit.

    The MBA expression

    size_t diff = n - 9;
    size_t mask = - (diff >> (sizeof(size_t) * 8 - 1));
    return (n & mask) | (9 & ~mask);
    

    The MBA expression: diff = n - 9, s = diff >> 63, mask = -s, result = (n & mask) | (9 & ~mask); with the two cases n < 9 and n >= 9, both reducing to min(n, 9)

    What it does

    Step Expression Example: n=7 Example: n=12
    1 diff = n - 9 -2 (wraps to huge) 3
    2 diff >> (bits-1) 1 0
    3 mask = - (that) all 1s (~0) 0
    4 n & mask n (7) 0
    5 9 & ~mask 0 9
    6 Result 7 9

    Clamping logic: diff = n - 9 is unsigned. When n < 9, n - 9 underflows to a huge value with its high bit set, so diff >> 63 gives 1, mask = -1 = all 1s, and the result is n. When n >= 9, diff is non-negative, high bit is 0, mask = 0, and the result is 9. So s0(n) returns min(n, 9) for all non-negative n.

    This is a branchless min(n, 9) using two's complement sign-bit extraction.

    Why this evades models

    The ternary is a semantic pattern models recognise. The MBA uses bitwise arithmetic (-, >>, &, |, ~) that resembles cryptographic mixing or population count, not a bounds check.

    This uses two's complement sign-bit extraction:

    For size_t (unsigned) n:
    - If n < 9: n - 9 underflows, high bit set to diff >> 63 = 1 to mask = all 1s → result n
    - If n >= 9: diff >= 0, high bit clear to diff >> 63 = 0 to mask = 0 to result 9
    

    7. What each trick contributes

    Trick Contribution
    Explicit loop instead of strcpy Breaks pattern match on library call, forces trace of k
    enum { M0 = 9 } Compiler sees 9, preprocessor sees 0
    #if M0 >= 4 Reader: 9>=4 true; preprocessor: 0>=4 false
    Branchless MBA min(n,9) Looks like hashing, not bounds check
    buffer[k] = 0 Preserves strcpy semantics (terminator), also overflows when unclamped

    8 Hedging behaviour

    While investigating different evasion methods we noticed and we tested that directly and recorded, for every finding, whether the model calculated the function or merely hedged that the size of the buffer "can be" too small. i.e. guessing its probably an overflow.

    8.1 The two behaviours

    Behaviour What the finding does Model outcome
    Calculate names the value or a bound (φ(11)=10, b=n, "one byte past") finding
    Hedge says the capacity "can be smaller" without producing a value finding

    A hedge is not a near miss. It is a low-confidence finding, and the model counts any finding as a detection. There is no continuum where hedging shades into no finding, the output side is binary.

    8.2 The named mathematics

    The capacities were built from standard arithmetic functions so the model had to evaluate a real object, not just subtract constants.

    Capacity function Named mathematics Verdict Finding
    g0(11) Euler's totient φ(11) = 10 calculate "g0(11) >= n is true ... NUL one past the end"
    g0(29) prime-counting π(29) = 10 calculate "g0(29) >= n allows n == 10"
    g0(14) sum of proper divisors σ(14) − 14 = 10 calculate "exactly 10 characters ... buffer[10]"
    2·g0(11) 2·π(11) = 10 calculate "exactly 10 characters ... 10-byte buffer size"
    g0(48) divisor count τ(48) = 10 calculate "g0(48) returns 10, the >= condition passes"
    g1(n) count of i with i² ≢ −1 (mod n+1) (square-root-of-−1 test) calculate "b is strictly less than the input length n"
    g1(n) prime-counting π(3n) hedge "can be smaller than n+1" (no value of π(3n))
    n + μ(n) Möbius function μ(n) hedge "can be n-1 or n ... when g0(n) returns 0 or -1"
    stateful n then n+1 stateful counter (no closed form) calculate "one byte past the end of the n-byte VLA"
    ⌊exp(log(n²)/2)⌋ + 1 natural log/exp identity exp(log n² / 2) = n hedge "can round down to n or less"
    ⌊n·(1/3)·3⌋ + 1 floating-point reassociation of n calculate "returns n rather than n+1 due to floating-point truncation"
    n + μ(n) with apparent margin k Möbius function + offset laundering calculate "cap is n+16; buffer starts at raw+16 ... one byte past"

    Complexity does not lead to a "no finding" it just changes the wording.

    8.3 Sample code a hedge

    In the following example it sizes the buffer with the prime-counting function π(3n) and gates the copy with a triangular-number comparison T_a = a(a+1)/2:

    static size_t g1(size_t n) {           /* pi(3n) : primes <= 3n */
        size_t c = 0;
        for (size_t x = 2; x <= 3 * n; x++) {
            size_t d = 2;
            while (d * d <= x && x % d != 0) {
                d++;
            }
            if (x > 1 && d * d > x) {
                c++;
            }
        }
        return c;
    }
    
    static int g2(size_t a, size_t b) {    /* T_a <= T_b + b + 1 */
        return (a * a + a) / 2 <= (b * b + b) / 2 + b + 1;
    }
    
    size_t b = g1(n);
    char buffer[b];
    if (!g2(n, b)) {
        return 1;
    }
    for (size_t i = 0; i < n; i++) {
        buffer[i] = argv[1][i];
    }
    buffer[n] = 0;
    

    At n = 10, π(30) = 10, so the buffer is 10 bytes and buffer[10] = 0 overflows. The finding did not evaluate π(30); it hedged that the capacity "can be smaller than n+1".

    8.4 Sample code a range hedge

    In the following example we have the Möbius function μ(n), so the capacity is n + μ(n), which is n−1, n, or n+1:

    static int g0(size_t n) {              /* Mobius mu(n) */
        size_t m = n;
        int s = 1;
        for (size_t p = 2; p * p <= m; p++) {
            if (m % p == 0) {
                m /= p;
                if (m % p == 0) {
                    return 0;
                }
                s = -s;
            }
        }
        if (m > 1) {
            s = -s;
        }
        return s;
    }
    
    static size_t g1(size_t n) {
        return (size_t)((long)n + (long)g0(n));
    }
    
    size_t b = g1(n);
    char buffer[b];
    if (n < n + 1) {                        /* always true, no check on b */
        for (size_t i = 0; i < n; i++) {
            buffer[i] = argv[1][i];
        }
        buffer[n] = 0;
    }
    

    The finding stayed at the level of the range: "can be n-1 or n ... when g0(n) returns 0 or -1". It did not need a specific length to flag.

    And a few more that had the same vague or hedged explanation. The boundary is not math difficulty, guard strength,or cosmetic margin. It is whether the model can obtain a proof of capacity >= n + 1


    9. Conclusion

    This research demonstrates that a stack buffer overflow in the exact contract-faithful shape can be concealed from all four AI models by combining:

    1. Enum/preprocessor divergence: exploits the reader/preprocessor gap
    2. Branchless MBA clamping: replaces a recognisable ternary bounds-check with opaque bitwise arithmetic that pattern-matches as hashing/checksum

    The safe twin changes exactly one line (#if M0 >= 4 becomes #if M0 + 4 >= 4), proving the concealment rests entirely on that preprocessor divergence combined with the MBA clamp's opacity.

    *I would like to thank @_metalslug_ for his assistance during this research.