Can it read obfuscated code?
I decided to see how well the various models could read obfuscated code. This feels like something a computer should be good at. If I give it deceptive code, I would expect it to figure it out most of the time.
I started with this code to calculate the value of e
#!/usr/bin/env python
# Program to calculate the value of pi
pi = f = 1.0
for i in range(2, 16):
pi += 1.0 / f
f *= i
print(pi)
The model size seems to be the deciding factor here. The models above about 30 billion parameters figured out this was calculating e. The smaller models told me it was calculating pi. Both gpt-oss:120b and nemotron both pointed out the program does not calculate pi.
That seems like a pretty easy example though. It’s really just a deceptive comment and some simple math.
I put some javascript code into a code compressor. The javascript reads a csv file, reverses the fields in each line, capitalizes the strings and subtracts 5 from integers. Here’s what I gave the LLMs
#!/usr/bin/env node
const fs=require("fs"),path=require("path");function p(e){let s=[],r=0,t="",n=!1;for(;r<e.length;){let o=e[r];n?'"'===o?r+1<e.length&&'"'===e[r+1]?(t+='"',r++):n=!1:t+=o:","===o?(s.push(t),t=""):'"'===o?n=!0:t+=o,r++}return s.push(t),s}function processToken(e){let s=e.trim();return/^-?\d+$/.test(s)?(parseInt(s,10)-5).toString():s.toUpperCase()}async function pr(e){let s=await fs.promises.readFile(e,"utf8"),r=s.split(/\r?\n/);for(let t of r){if(!t)continue;let n=p(t),o=n.map(e=>processToken(e)),i=o.reverse();console.log(i.join(","))}}(async()=>{let e=process.argv.slice(2);1!==e.length&&(console.error("Error"),console.error("Usage"),process.exit(1));let s=path.resolve(process.cwd(),e[0]);try{await fs.promises.access(s)}catch(r){console.error(`Cannot access file "${s}": ${r.message}`),process.exit(1)}await pr(s)})();
Once again the model size made all the difference. The smaller models had no idea what was going on, but the large models figured it out. gpt-oss:120b even rewrote the code. It didn’t actually work, which is of course a minor unimportant detail :)
I need some expert help I think. One of my favorite projects ever is The International Obfuscated C Code Contest. As you can imagine, it’s C code that is purposely misleading. I decided to use this entry from 2024 titled salmon recipe. It relies on unicode to deceive the human, and since an LLM isn’t a human and can see unicode, this seems like a great test. Even if you could see the unicode characters, this code is doing some very silly things.
Here is what the code looks like to a human
#include <stdio.h>
unsigned int *salmon = U"...is very yummy";
#define grill ;while(putchar(*salmon++))
#undef grill
#define grill
#undef grill
int main() {
for (int i = 1; i <= 10; i++) {
printf("%d * %d = %d\n", i, i, i*i);
}
}
If we compile the code, here is the output
./a.out
Season salmon fillets with olive oil, lemon juice, garlic, salt, and pepper, then grill over medium heat for 4-6 minutes per side until flaky and cooked through.
I’m just going to start with gpt-oss:12b this time. It didn’t figure this out at all. Here was the final determination from gpt-oss:120b
Bottom line#
The program does not compile. If you stripped away the invisible Unicode characters and wrote the macros in a conventional way, the only thing it could have done would be:
Print the (mostly unreadable) Unicode string stored in salmon by repeatedly calling putchar. Then, for i = 1 … 10, try to print i, i, and the (non‑existent) variable i_i. Because the macros are malformed and the identifier i_i does not exist, the compiler stops with errors long before any runtime behaviour could be observed.
I decided I should try Claude on this one just to see what happens. It got it like 90% right
The Hidden Text#
The string assigned to salmon uses Unicode “tag characters” (from the U+E0000 range). These invisible characters encode readable text by adding 0xE0000 to ASCII values. When decoded, the string contains: “Season salmon fillets with olive oil, lemon juice, garlic, salt, and pepper, then grill over medium heat for 4-6 minutes per side until flaky and cooked through.”
It’s literally a salmon recipe!
…
What You See vs What Runs#
What appears to run: A simple loop printing squares (1×1=1, 2×2=4, etc.)
What actually runs: First prints the hidden recipe, then prints the squares.
This is a great example of using Unicode steganography and preprocessor tricks for code obfuscation!
It doesn’t print the squares, but it did get the salmon recipe correct. So not terrible.
So right now I have a suspicion an LLM could do an OK job looking at source code. I put some very old C code I wrote 12 years ago into qwen coder. I asked it if there are any buffer overflows in the code. It got it wrong but in a pretty silly way
Yes, this C code does have a potential buffer overflow vulnerability, and it’s in the read_gpg_data() function, specifically in how it handles reading decrypted data into a fixed-size buffer.
However, in this specific case, the code does not directly use the buffer beyond its allocated size, because it only reads into a fixed-size buffer and sets a null terminator.
It was so close. There actually is a possible buffer overflow in this code
len = gpgme_data_read(out, buffer, buffer_len);
buffer[len] = '\0';
If exactly 1024 characters are returned, that null byte will be written beyond the end of the buffer. I tried this again with gpt-oss:120b and it found it correctly. nemotron also figured it out correctly. I also checked Claude and it found the single byte overflow (just because I had the tab still open).
What’s the point of all this#
So while this was a fun and silly experiment, the real reason for me to test all this out was to see how practical it could be to have an LLM look for security vulnerabilities. I think it will be plausible. But as one would expect, the results will need human review.
Now I have to figure out the best way to get lots of source code shoveled into the LLM. But that’s a problem for future me.