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