Strings: The First Thing Attackers Read
Welcome to Level 5. Before you can judge any defense, you have to see how shipped code is actually attacked. We start with the single cheapest move a reverse engineer makes, the one they run before they read a single line of logic: they pull every readable string out of the file at once.
Why strings come first
Programs are full of text that has to stay text to work: error messages, URLs, file paths, config keys, prompts, and sometimes secrets that never should have been there. A single tool scans the whole file, finds every run of printable characters, and prints them as a list. On a real binary this is the classic strings command; on a script it is barely more than opening the file. In seconds an attacker has a map of what your program talks about.
Think of it like flipping a stranger's phone face-up and reading every notification on the lock screen. You have not unlocked anything, but the previews alone tell you who they bank with, who they message, and what they were just doing. String extraction is that lock-screen glance, aimed at your program.
The reason this triage is so productive is leverage. It costs one command and needs no understanding of the code's structure, yet it routinely surfaces API endpoints, feature-flag names, license-check messages, debug logs, and the exact wording to search for to jump straight to the interesting function. Attackers do it first precisely because the payoff-to-effort ratio is the best of any technique in the field.
What a string dump gives away
- Endpoints and hosts: any URL or domain the code contacts, which reveals the backend before a single request is traced.
- Control messages: text like invalid license or trial expired points a searcher at the exact check to neutralize.
- Structure hints: SQL fragments, JSON keys, and format strings expose the shape of the data the program handles.
- Accidental secrets: tokens, passwords, and keys left in source appear in the dump with everything else, undisguised.
None of that required understanding the program. It fell out of a flat scan for printable text, which is why leaving meaningful strings in the clear is the most common and most costly mistake in shipped code.
Splitting does not hide
A tempting non-fix is to split a sensitive string into pieces and glue them back together at runtime. It fools a naive human skim, but the joined value still has to exist to be used, and the fragments themselves still show up in the string dump. Reconstructing them is trivial, as the challenge below demonstrates: the parts are right there, in order.
The four fragments below are exactly what a string dump would show for a program that tried to hide a key by splitting it. Your job is to do what an attacker does in their head in about two seconds: join them in order and print the recovered key.
That is the entire attack on split strings. It took one line. Splitting raised the cost of recovery from zero to almost zero, which is the same as doing nothing.
What actually raises the cost
Real string protection stops the string from ever appearing in the file as text. The bytes stored are an encoded form, and the readable value is produced only while the program runs, from a key the file does not simply hand over. A flat scan for printable text then finds nothing usable, so the attacker cannot triage by strings at all and is forced to the far more expensive work of running or reading the actual logic.
url = "https://api.example.com/v1/verify"
msg = "invalid license key"
if checkKey() == false {
print msg
}// stored bytes decode only at runtime
url = D(0x2f, 0xa1, 0x77, ...)
msg = D(0x51, 0x0c, 0x9e, ...)
if checkKey() == false {
print msg
}One honest caveat that Level 5 keeps coming back to: hiding the string in the file does not hide it from a running program. The moment the code needs the URL, the decoded URL exists in memory, and an attacker who runs the code can read it there. String encoding defeats the cheap static scan completely, which is genuinely worth doing, but it is a static-facing defense. It buys nothing against the run-and-dump attack you will meet later in this level. Knowing which attack a defense answers is the whole point of thinking clearly about protection.
String extraction is the reverse engineer's first move because it is nearly free and often decisive. Keeping meaningful text out of the file as plaintext is a baseline, not a luxury. It shuts down the cheapest attack entirely, while honestly buying nothing against an attacker who is willing to run your code.
Frequently asked questions
What is the strings command?
A standard utility that scans a file and prints every run of printable characters above a minimum length. It is the classic first triage step on an unknown binary, and the same idea applies to scripts by simply reading the file. It requires no understanding of the code and often reveals endpoints, messages, and accidentally embedded secrets.
Does encoding my strings make my program secure?
It makes the cheap static scan come up empty, which is genuinely valuable and should be considered a baseline. It does not make the program secure on its own, because a running program has to decode its strings to use them, and an attacker who runs the code can read the decoded values in memory. String encoding answers the static attack, not the dynamic one.
Should I ever put a secret directly in my code?
No. Any secret in the source is one string dump away from disclosure, and encoding only raises the cost of a static read, not a dynamic one. Real secrets belong on a server behind authentication, not shipped in code that runs in someone else's hands.