Skip to content

Recovering Stack Strings and Decoded Strings in Malware

Why malware hides its strings, how stack strings, tight strings and decoded strings work, and how emulation recovers them without running the file.

Published on 4 min read

TL;DR. Plain string extraction only finds text that sits in the file as-is. Malware routinely hides its interesting strings — C2 addresses, mutex names, commands — by building them on the stack or decoding them at run time. Emulating the code that builds them recovers most of them without running the sample. The PE Parser does this in its Code analysis tab, in the spirit of Mandiant's FLOSS.

What strings sees, and what it doesn't

A strings pass reads the file looking for runs of printable characters (ASCII, UTF-16). It is fast and finds a lot: import names, error messages, paths, URLs left in clear. That is exactly why authors hide the strings that matter. Three techniques cover most of what you will meet:

  • Stack strings. The code writes the characters one by one into a local buffer: mov byte [ebp-0x20], 'h', mov byte [ebp-0x1f], 't'… The text only exists in memory, for the life of the function. In the file you see a series of instructions, not a string.
  • Tight strings. A stack string that is itself encoded, then decoded by a short loop in the same function — typically a XOR with a constant key — just before use.
  • Decoded strings. The strings are stored encrypted in the data section, and a decoding function turns them into text on demand. It is called from many places, each time with a different encrypted blob, and returns or writes the clear text.

All three defeat static extraction, and all three are visible to anyone who can follow what the code does.

How emulation recovers them

An emulator is a CPU written in software: it reads each instruction, updates simulated registers and memory, and moves on. Nothing reaches the real processor or the operating system — calls to Windows functions are answered by stubs that return plausible values (a fresh buffer for an allocation, a success code for the rest). The PE Parser's analysis works in three passes:

  1. Find the functions. Starting from the entry point, exports, TLS callbacks and every direct call, it follows the code to discover functions and the calls between them.
  2. Stack and tight strings. Each function is emulated from its start for a bounded number of instructions; printable text that appears in its stack frame is collected, before and after any decoding loop.
  3. Decoded strings. Functions that look like decoders — a XOR inside a loop, small, called from several places — are emulated from each call site, with the arguments the caller prepared. Text that appears in memory written by the decoder is collected, together with the address of the decoder.

Only text that a plain strings pass could not see is reported, so the list stays short and relevant.

Using it in the PE Parser

  • Open a 32- or 64-bit native file and go to the Code analysis tab. The analysis runs in the browser in a few hundred milliseconds for most files.
  • Recovered strings are listed with their kind (decoded, stack, tight loop) and the function that built them. Click the address to open the disassembly there.
  • Strings that still look encoded (mostly symbols) are hidden by default; a checkbox shows them.
  • Network indicators among the recovered strings — URLs, domains, IP addresses — flow into the IOCs tab and its exports (exporting IOCs).
  • To analyse a whole collection, use Code analysis in the toolbar: every native file is processed and a Code column shows how many capabilities and strings each one has.

The same tab lists capabilities found in the code: anti-debugging checks, process injection, API hashing, crypto constants (MD5, SHA, CRC32, TEA, RC4, AES), keylogging hooks and more, each mapped to MITRE ATT&CK and the Malware Behavior Catalog, with the functions where they were found.

Limits

  • Packed files. If the code is packed, you are emulating the unpacking stub, not the program. Unpack first — the PE Parser does UPX itself (detecting packed executables).
  • Instruction coverage. The emulator handles ordinary integer x86 / x64 code. Decoders that rely on floating point or SIMD instructions are skipped.
  • Keys from outside. A decoder whose key comes from the network, the registry or a file read at run time cannot be resolved statically.
  • Budgets. Each emulation run is capped so a hostile file cannot stall the page; very large programs may be analysed partially, and the tab says so.

When these limits bite, the sandbox is the next step: run the sample in an isolated environment and dump its memory. For the large majority of commodity malware, though, emulation recovers the strings you need in seconds — without the sample ever executing.

Related articles

Step-by-step static triage of Windows executables with the free PE Parser: load files or a ZIP, read indicators, compare builds and export results.
Find and copy suspicious EXE, DLL and SYS files from a Windows host or image without running them: PowerShell, Velociraptor, disk images and pitfalls.
Static signs that a Windows executable is packed or protected — section names, entropy, W+X sections, entry point, imports, overlay — and their pitfalls.