Reverse Engineering Rust Malware
Overview
Rust has become increasingly popular for malware development due to its cross-compilation, memory safety guarantees, and the complexity it introduces for reverse engineers. Rust binaries contain the entire standard library statically linked, producing large binaries with extensive boilerplate code. Key challenges include non-null-terminated strings (Rust uses fat pointers with pointer+length), monomorphization generating duplicated generic code, complex error handling (Result/Option unwrap chains), and unfamiliar calling conventions. Decompiling Rust to C produces unhelpful output compared to C/C++ binaries. Tools like Ghidra scripts for crate extraction, and training focused on Rust-specific patterns (2024-2025) help address these challenges. Notable Rust malware includes BlackCat/ALPHV ransomware, Hive ransomware variants, and Buer Loader.
When to Use
- When performing authorized security testing that involves reverse engineering rust malware
- When analyzing malware samples or attack artifacts in a controlled environment
- When conducting red team exercises or penetration testing engagements
- When building detection capabilities based on offensive technique understanding
Prerequisites
- IDA Pro 8.0+ or Ghidra 11.0+
- Rust toolchain for reference compilation
- Python 3.9+ for helper scripts
- Understanding of Rust memory model (ownership, borrowing)
- Familiarity with Rust string types (String, &str, CString)
Workflow
Step 1: Identify and Parse Rust Binary Metadata
#!/usr/bin/env python3
"""Analyze Rust malware binary metadata and extract crate dependencies."""
import re
import sys
import json
def identify_rust_binary(data):
"""Check if binary is Rust-compiled and extract version info."""
indicators = {
"rust_panic_strings": bool(re.search(rb'panicked at', data)),
"rust_unwrap": bool(re.search(rb'called.*unwrap.*on.*None', data)),
"core_panic": bool(re.search(rb'core::panicking', data)),
"std_rt": bool(re.search(rb'std::rt::lang_start', data)),
"cargo_path": bool(re.search(rb'\.cargo[/\\]registry', data)),
"rustc_version": None,
}
version = re.search(rb'rustc\s+(\d+\.\d+\.\d+)', data)
if version:
indicators["rustc_version"] = version.group(1).decode()
is_rust = sum(1 for v in indicators.values() if v) >= 2
return is_rust, indicators
def extract_crates(data):
"""Extract Rust crate (dependency) names from binary strings."""
crate_pattern = re.compile(
rb'(?:crates\.io-[a-f0-9]+/|\.cargo/registry/src/[^/]+/)'
rb'([\w-]+)-(\d+\.\d+\.\d+)'
)
crates = {}
for match in crate_pattern.finditer(data):
name = match.group(1).decode()
version = match.group(2).decode()
crates[name] = version
# Also check for common malware-relevant crates
suspicious_crates = {
"reqwest": "HTTP client",
"hyper": "HTTP library",
"tokio": "Async runtime",
"aes": "AES encryption",
"chacha20": "ChaCha20 encryption",
"rsa": "RSA encryption",
"ring": "Crypto library",
"base64": "Base64 encoding",
"winapi": "Windows API bindings",
"winreg": "Registry access",
"sysinfo": "System information",
"screenshots": "Screen capture",
"clipboard": "Clipboard access",
"keylogger": "Key logging",
}
capabilities = []
for crate_name, description in suspicious_crates.items():
if crate_name in crates:
capabilities.append({
"crate": crate_name,
"version": crates[crate_name],
"capability": description,
})
return crates, capabilities
def extract_rust_strings(data):
"""Extract strings handling Rust's non-null-terminated format."""
# Rust strings are stored as pointer+length, but string literals
# are often in .rodata as contiguous sequences
strings = []
ascii_pattern = re.compile(rb'[\x20-\x7e]{8,500}')
for match in ascii_pattern.finditer(data):
s = match.group().decode('ascii')
# Filter for malware-relevant strings
keywords = ['http', 'socket', 'encrypt', 'decrypt', 'shell',
'exec', 'cmd', 'upload', 'download', 'persist',
'registry', 'mutex', 'pipe', 'inject']
if any(kw in s.lower() for kw in keywords):
strings.append(s)
return strings
if __name__ == "__main__":
if len(sys.argv) < 2:
print(f"Usage: {sys.argv[0]} <rust_binary>")
sys.exit(1)
with open(sys.argv[1], 'rb') as f:
data = f.read()
is_rust, indicators = identify_rust_binary(data)
print(f"[{'+'if is_rust else '-'}] Rust binary: {is_rust}")
print(json.dumps(indicators, indent=2, default=str))
crates, capabilities = extract_crates(data)
print(f"\n[+] Crates ({len(crates)}):")
for name, ver in sorted(crates.items()):
print(f" {name} v{ver}")
if capabilities:
print(f"\n[!] Suspicious capabilities:")
for cap in capabilities:
print(f" {cap['crate']} -> {cap['capability']}")
strings = extract_rust_strings(data)
if strings:
print(f"\n[+] Suspicious strings ({len(strings)}):")
for s in strings[:20]:
print(f" {s}")
Validation Criteria
- Binary correctly identified as Rust-compiled with version info
- Crate dependencies extracted revealing malware capabilities
- Rust-specific string extraction handles fat pointer format
- Main entry point and core logic functions identified
- Encryption, networking, and persistence code located
References
1---
2name: reverse-engineering-rust-malware
3description: Reverse engineer Rust-compiled malware using IDA Pro and Ghidra with techniques for handling non-null-terminated
4 strings, crate dependency extraction, and Rust-specific control flow analysis.
5domain: cybersecurity
6subdomain: malware-analysis
7tags:
8- rust
9- reverse-engineering
10- malware-analysis
11- ghidra
12- ida-pro
13- binary-analysis
14- rust-malware
15version: '1.0'
16author: mahipal
17license: Apache-2.0
18nist_csf:
19- DE.AE-02
20- RS.AN-03
21- ID.RA-01
22- DE.CM-01
23---
24
25# Reverse Engineering Rust Malware
26
27## Overview
28
29Rust has become increasingly popular for malware development due to its cross-compilation, memory safety guarantees, and the complexity it introduces for reverse engineers. Rust binaries contain the entire standard library statically linked, producing large binaries with extensive boilerplate code. Key challenges include non-null-terminated strings (Rust uses fat pointers with pointer+length), monomorphization generating duplicated generic code, complex error handling (Result/Option unwrap chains), and unfamiliar calling conventions. Decompiling Rust to C produces unhelpful output compared to C/C++ binaries. Tools like Ghidra scripts for crate extraction, and training focused on Rust-specific patterns (2024-2025) help address these challenges. Notable Rust malware includes BlackCat/ALPHV ransomware, Hive ransomware variants, and Buer Loader.
30
31
32## When to Use
33
34- When performing authorized security testing that involves reverse engineering rust malware
35- When analyzing malware samples or attack artifacts in a controlled environment
36- When conducting red team exercises or penetration testing engagements
37- When building detection capabilities based on offensive technique understanding
38
39## Prerequisites
40
41- IDA Pro 8.0+ or Ghidra 11.0+
42- Rust toolchain for reference compilation
43- Python 3.9+ for helper scripts
44- Understanding of Rust memory model (ownership, borrowing)
45- Familiarity with Rust string types (String, &str, CString)
46
47## Workflow
48
49### Step 1: Identify and Parse Rust Binary Metadata
50
51```python
52#!/usr/bin/env python3
53"""Analyze Rust malware binary metadata and extract crate dependencies."""
54import re
55import sys
56import json
57
58
59def identify_rust_binary(data):
60 """Check if binary is Rust-compiled and extract version info."""
61 indicators = {
62 "rust_panic_strings": bool(re.search(rb'panicked at', data)),
63 "rust_unwrap": bool(re.search(rb'called.*unwrap.*on.*None', data)),
64 "core_panic": bool(re.search(rb'core::panicking', data)),
65 "std_rt": bool(re.search(rb'std::rt::lang_start', data)),
66 "cargo_path": bool(re.search(rb'\.cargo[/\\]registry', data)),
67 "rustc_version": None,
68 }
69
70 version = re.search(rb'rustc\s+(\d+\.\d+\.\d+)', data)
71 if version:
72 indicators["rustc_version"] = version.group(1).decode()
73
74 is_rust = sum(1 for v in indicators.values() if v) >= 2
75 return is_rust, indicators
76
77
78def extract_crates(data):
79 """Extract Rust crate (dependency) names from binary strings."""
80 crate_pattern = re.compile(
81 rb'(?:crates\.io-[a-f0-9]+/|\.cargo/registry/src/[^/]+/)'
82 rb'([\w-]+)-(\d+\.\d+\.\d+)'
83 )
84 crates = {}
85 for match in crate_pattern.finditer(data):
86 name = match.group(1).decode()
87 version = match.group(2).decode()
88 crates[name] = version
89
90 # Also check for common malware-relevant crates
91 suspicious_crates = {
92 "reqwest": "HTTP client",
93 "hyper": "HTTP library",
94 "tokio": "Async runtime",
95 "aes": "AES encryption",
96 "chacha20": "ChaCha20 encryption",
97 "rsa": "RSA encryption",
98 "ring": "Crypto library",
99 "base64": "Base64 encoding",
100 "winapi": "Windows API bindings",
101 "winreg": "Registry access",
102 "sysinfo": "System information",
103 "screenshots": "Screen capture",
104 "clipboard": "Clipboard access",
105 "keylogger": "Key logging",
106 }
107
108 capabilities = []
109 for crate_name, description in suspicious_crates.items():
110 if crate_name in crates:
111 capabilities.append({
112 "crate": crate_name,
113 "version": crates[crate_name],
114 "capability": description,
115 })
116
117 return crates, capabilities
118
119
120def extract_rust_strings(data):
121 """Extract strings handling Rust's non-null-terminated format."""
122 # Rust strings are stored as pointer+length, but string literals
123 # are often in .rodata as contiguous sequences
124 strings = []
125 ascii_pattern = re.compile(rb'[\x20-\x7e]{8,500}')
126 for match in ascii_pattern.finditer(data):
127 s = match.group().decode('ascii')
128 # Filter for malware-relevant strings
129 keywords = ['http', 'socket', 'encrypt', 'decrypt', 'shell',
130 'exec', 'cmd', 'upload', 'download', 'persist',
131 'registry', 'mutex', 'pipe', 'inject']
132 if any(kw in s.lower() for kw in keywords):
133 strings.append(s)
134
135 return strings
136
137
138if __name__ == "__main__":
139 if len(sys.argv) < 2:
140 print(f"Usage: {sys.argv[0]} <rust_binary>")
141 sys.exit(1)
142
143 with open(sys.argv[1], 'rb') as f:
144 data = f.read()
145
146 is_rust, indicators = identify_rust_binary(data)
147 print(f"[{'+'if is_rust else '-'}] Rust binary: {is_rust}")
148 print(json.dumps(indicators, indent=2, default=str))
149
150 crates, capabilities = extract_crates(data)
151 print(f"\n[+] Crates ({len(crates)}):")
152 for name, ver in sorted(crates.items()):
153 print(f" {name} v{ver}")
154
155 if capabilities:
156 print(f"\n[!] Suspicious capabilities:")
157 for cap in capabilities:
158 print(f" {cap['crate']} -> {cap['capability']}")
159
160 strings = extract_rust_strings(data)
161 if strings:
162 print(f"\n[+] Suspicious strings ({len(strings)}):")
163 for s in strings[:20]:
164 print(f" {s}")
165```
166
167## Validation Criteria
168
169- Binary correctly identified as Rust-compiled with version info
170- Crate dependencies extracted revealing malware capabilities
171- Rust-specific string extraction handles fat pointer format
172- Main entry point and core logic functions identified
173- Encryption, networking, and persistence code located
174
175## References
176
177- [Binary Defense - Extracting Secrets from Rust Malware](https://binarydefense.com/resources/blog/digging-through-rust-to-find-gold-extracting-secrets-from-rust-malware)
178- [Ghidra Extension for Rust Analysis](https://cir.nii.ac.jp/crid/1050302237609671296)
179- [Fuzzing Labs - Reversing Modern Binaries](https://fuzzinglabs.com/reversing-modern-binaries/)
180- [Bishop Fox - Rust for Malware Development](https://bishopfox.com/blog/rust-for-malware-development)