reverse-skills-max: initial public release (restored, redacted, hardened)

This commit is contained in:
baradok609hecl1pq6qi
2026-10-20 02:17:15 +03:00
commit da24c2bd20
2227 changed files with 443931 additions and 0 deletions

View File

@@ -0,0 +1,201 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to the Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by the Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding any notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. Please do not remove or change
the license header comment from a contributed file except when
necessary.
Copyright 2026 mukul975
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

View File

@@ -0,0 +1,348 @@
---
name: reverse-engineering-dotnet-malware-with-dnspy
description: 'Reverse engineers .NET malware using dnSpy decompiler and debugger to analyze C#/VB.NET source code, identify
obfuscation techniques, extract configurations, and understand malicious functionality including stealers, RATs, and loaders.
Activates for requests involving .NET malware analysis, C# malware decompilation, managed code reverse engineering, or .NET
obfuscation analysis.
'
domain: cybersecurity
subdomain: malware-analysis
tags:
- malware
- dotnet
- reverse-engineering
- dnSpy
- decompilation
version: 1.0.0
author: mahipal
license: Apache-2.0
nist_csf:
- DE.AE-02
- RS.AN-03
- ID.RA-01
- DE.CM-01
---
# Reverse Engineering .NET Malware with dnSpy
## When to Use
- A malware sample is identified as a .NET assembly (C#, VB.NET, F#) requiring decompilation
- Analyzing .NET-based malware families (AgentTesla, AsyncRAT, RedLine Stealer, Quasar RAT)
- Deobfuscating .NET code protected by ConfuserEx, SmartAssembly, or custom obfuscators
- Extracting hardcoded C2 configurations, encryption keys, and credentials from managed assemblies
- Debugging .NET malware at runtime to observe decryption routines and dynamic behavior
**Do not use** for native (unmanaged) PE binaries; use Ghidra or IDA for native code analysis.
## Prerequisites
- dnSpy or dnSpyEx installed (https://github.com/dnSpyEx/dnSpy - community maintained fork)
- de4dot for automated .NET deobfuscation (`https://github.com/de4dot/de4dot`)
- ILSpy as an alternative decompiler for cross-validation
- .NET SDK installed for recompiling modified assemblies during analysis
- Isolated Windows VM for running dnSpy debugger on live malware
- Detect It Easy (DIE) for identifying the .NET obfuscator used
## Workflow
### Step 1: Identify .NET Assembly and Obfuscator
Verify the sample is a .NET binary and detect protection:
```bash
# Check if file is .NET assembly
file suspect.exe
# Output should contain "PE32 executable" with .NET metadata
# Detect obfuscator with Detect It Easy
diec suspect.exe
# Python-based .NET detection
python3 << 'PYEOF'
import pefile
pe = pefile.PE("suspect.exe")
# Check for .NET COM descriptor
if hasattr(pe, 'DIRECTORY_ENTRY_COM_DESCRIPTOR'):
print("[*] .NET assembly detected")
print(f" Runtime version: {pe.DIRECTORY_ENTRY_COM_DESCRIPTOR}")
else:
# Check for mscoree.dll import (alternative detection)
for entry in pe.DIRECTORY_ENTRY_IMPORT:
if entry.dll.decode().lower() == "mscoree.dll":
print("[*] .NET assembly detected (mscoree.dll import)")
break
else:
print("[!] Not a .NET assembly")
# Check section names for .NET indicators
for section in pe.sections:
name = section.Name.decode().rstrip('\x00')
if name in ['.text', '.rsrc', '.reloc']:
print(f" Section: {name} (typical .NET)")
PYEOF
```
### Step 2: Deobfuscate with de4dot
Remove common .NET obfuscation before manual analysis:
```bash
# Run de4dot to identify and remove obfuscation
de4dot suspect.exe -o suspect_cleaned.exe
# Force specific deobfuscator
de4dot suspect.exe -p cf # ConfuserEx
de4dot suspect.exe -p sa # SmartAssembly
de4dot suspect.exe -p dr # Dotfuscator
de4dot suspect.exe -p rv # Reactor
de4dot suspect.exe -p bl # Babel.NET
# Verbose output for debugging
de4dot -v suspect.exe -o suspect_cleaned.exe
# Handle multi-file assemblies
de4dot suspect.exe suspect_helper.dll -o cleaned/
```
```
Common .NET Obfuscators:
━━━━━━━━━━━━━━━━━━━━━━━
ConfuserEx: String encryption, control flow, anti-debug, anti-tamper
SmartAssembly: String encoding, flow obfuscation, pruning
Dotfuscator: Renaming, string encryption, control flow
.NET Reactor: Native code generation, necrobit, anti-debug
Babel.NET: String encryption, resource encryption, code virtualization
Crypto Obfuscator: String encryption, anti-debug, watermarking
Custom: Malware-specific obfuscation (manual de4dot configuration needed)
```
### Step 3: Open in dnSpy and Analyze Code
Load the deobfuscated assembly in dnSpy for source-level analysis:
```
dnSpy Analysis Workflow:
━━━━━━━━━━━━━━━━━━━━━━━
1. File -> Open -> Select cleaned assembly
2. Navigate to the entry point:
- Assembly Explorer -> <namespace> -> Program class -> Main method
- Or: Right-click assembly -> Go to Entry Point
3. Key areas to examine:
- Entry point (Main) for initialization and execution flow
- Form classes for UI-based malware (RATs, stealers)
- Network/HTTP classes for C2 communication
- Crypto/encryption classes for data protection
- Resource access for embedded payloads
- Timer/Thread classes for persistence and scheduling
4. Navigation shortcuts:
Ctrl+G - Go to token/address
Ctrl+Shift+K - Search assemblies
F12 - Go to definition
Ctrl+R - Analyze (find usages)
F5 - Start debugging
F9 - Toggle breakpoint
```
### Step 4: Extract Configuration and C2 Data
Locate hardcoded configuration in the decompiled source:
```csharp
// Common .NET malware configuration patterns:
// Pattern 1: Static class with hardcoded values
public static class Config {
public static string Host = "185.220.101.42";
public static int Port = 4782;
public static string Key = "GhOsT_RaT_2025";
public static string Mutex = "AsyncMutex_6SI8OkPnk";
public static bool Install = true;
public static string InstallFolder = "%AppData%";
}
// Pattern 2: Encrypted strings decrypted at runtime
public static string Decrypt(string input) {
byte[] data = Convert.FromBase64String(input);
byte[] key = Encoding.UTF8.GetBytes("SecretKey123");
for (int i = 0; i < data.Length; i++) {
data[i] ^= key[i % key.Length];
}
return Encoding.UTF8.GetString(data);
}
// Pattern 3: Resource-embedded configuration
byte[] configData = Properties.Resources.config;
string config = AES.Decrypt(configData, derivedKey);
```
```python
# Python script to extract .NET resource strings
import subprocess
import re
import base64
# Use monodis (Mono) or ildasm (.NET SDK) to dump IL
result = subprocess.run(
["monodis", "--output=il_dump.il", "suspect_cleaned.exe"],
capture_output=True, text=True
)
# Search for string literals in IL dump
with open("il_dump.il", errors="ignore") as f:
il_code = f.read()
# Find ldstr (load string) instructions
strings = re.findall(r'ldstr\s+"([^"]+)"', il_code)
for s in strings:
# Check for Base64 encoded strings
try:
decoded = base64.b64decode(s).decode('utf-8', errors='ignore')
if len(decoded) > 3 and decoded.isprintable():
print(f" Base64: {s[:40]}... -> {decoded[:100]}")
except:
pass
# Check for URLs/IPs
if re.match(r'https?://', s) or re.match(r'\d+\.\d+\.\d+\.\d+', s):
print(f" Network: {s}")
```
### Step 5: Debug with dnSpy
Set breakpoints and debug the malware to observe runtime behavior:
```
dnSpy Debugging Workflow:
━━━━━━━━━━━━━━━━━━━━━━━
1. Set breakpoints on key methods:
- String decryption functions (to capture decrypted values)
- Network connection methods (to capture C2 URLs)
- File write operations (to see what is dropped)
- Registry modification methods (to see persistence)
2. Debug -> Start Debugging (F5)
- Select the assembly to debug
- Set command-line arguments if needed
- Configure exception handling (break on all CLR exceptions)
3. At each breakpoint:
- Inspect local variables (Locals window)
- Evaluate expressions (Immediate window)
- View call stack to understand execution context
- Step over (F10) / Step into (F11) / Step out (Shift+F11)
4. Capture decrypted strings:
- Set breakpoint after decryption function returns
- Read the return value from the Locals window
- Document all decrypted configuration values
```
### Step 6: Document Findings
Compile analysis results into a structured report:
```
Analysis documentation should include:
- .NET assembly metadata (CLR version, target framework, compilation info)
- Obfuscator identified and deobfuscation method used
- Complete C2 configuration (hosts, ports, encryption keys, mutex names)
- Malware capabilities (keylogging, screen capture, file theft, etc.)
- Persistence mechanisms (registry, scheduled tasks, startup folder)
- Anti-analysis techniques (VM detection, debugger detection, sandbox evasion)
- Extracted IOCs (C2 IPs/domains, file hashes, mutex names, registry keys)
- YARA rule based on unique code patterns or strings
```
## Key Concepts
| Term | Definition |
|------|------------|
| **CIL/MSIL** | Common Intermediate Language; the bytecode format .NET assemblies compile to, which can be decompiled back to high-level C#/VB.NET |
| **Metadata Token** | Unique identifier for .NET types, methods, and fields within the assembly metadata tables; used for navigation in dnSpy |
| **de4dot** | Open-source .NET deobfuscator that identifies and removes protection from many commercial and malware-specific obfuscators |
| **ConfuserEx** | Popular open-source .NET obfuscator frequently used by malware authors for string encryption and control flow obfuscation |
| **String Encryption** | Obfuscation technique replacing string literals with encrypted data and runtime decryption calls to hide IOCs from static analysis |
| **Resource Embedding** | Storing configuration, payloads, or additional assemblies in .NET embedded resources, often encrypted with a key derived from assembly metadata |
| **Assembly.Load** | .NET method loading assemblies from byte arrays in memory, enabling fileless execution of embedded payloads |
## Tools & Systems
- **dnSpy/dnSpyEx**: Open-source .NET assembly editor, decompiler, and debugger supporting C# and VB.NET decompilation
- **de4dot**: Automated .NET deobfuscator supporting ConfuserEx, SmartAssembly, Dotfuscator, Reactor, and many other protectors
- **ILSpy**: Open-source .NET decompiler providing C#, VB.NET, and IL views of assembly code
- **dotPeek**: JetBrains' free .NET decompiler with symbol server and cross-reference navigation
- **Detect It Easy (DIE)**: Multi-format file analyzer identifying .NET framework version, obfuscator, and compiler information
## Common Scenarios
### Scenario: Analyzing an AgentTesla Information Stealer
**Context**: A phishing email delivers a .NET executable identified as AgentTesla. The sample needs analysis to determine what credentials it steals, how it exfiltrates data, and its C2 configuration.
**Approach**:
1. Run Detect It Easy to identify the obfuscator (commonly ConfuserEx or custom)
2. Deobfuscate with de4dot to restore readable class/method names and decrypt strings
3. Open in dnSpy and navigate to the entry point to understand initialization
4. Locate the credential harvesting modules (browser, email, FTP, VPN password theft classes)
5. Find the exfiltration method (SMTP email, FTP upload, HTTP POST, Telegram bot API)
6. Extract C2 configuration (SMTP server, credentials, recipient email, or HTTP URL)
7. Set debugger breakpoints on the decryption function to capture all decrypted strings at once
**Pitfalls**:
- Analyzing without de4dot first (ConfuserEx makes manual analysis extremely difficult)
- Not checking for multi-stage loading (initial .NET executable may load additional assemblies from resources)
- Missing configuration stored in .NET resources rather than hardcoded strings
- Running the debugger without network isolation (AgentTesla will attempt to exfiltrate immediately)
## Output Format
```
.NET MALWARE ANALYSIS REPORT
================================
Sample: invoice_scanner.exe
SHA-256: e3b0c44298fc1c149afbf4c8996fb924...
Type: .NET Assembly (C#)
Framework: .NET Framework 4.8
Obfuscator: ConfuserEx v1.6
Deobfuscated: Yes (de4dot -p cf)
CLASSIFICATION
Family: AgentTesla v3
Type: Information Stealer / Keylogger
Compile Date: 2025-09-10
C2 CONFIGURATION
Exfil Method: SMTP (Email)
SMTP Server: smtp.yandex[.]com:587
SMTP User: exfil.account@yandex[.]com
SMTP Pass: Str0ngP@ssw0rd2025
Recipient: operator@protonmail[.]com
Interval: 30 minutes
Encryption: AES-256 with key "AgentTesla_2025_key"
CAPABILITIES
[*] Browser credential theft (Chrome, Firefox, Edge, Opera)
[*] Email client passwords (Outlook, Thunderbird)
[*] FTP client credentials (FileZilla, WinSCP)
[*] VPN credentials (NordVPN, OpenVPN)
[*] Keylogging (SetWindowsHookEx)
[*] Screenshot capture (every 30 seconds)
[*] Clipboard monitoring
PERSISTENCE
Method: Registry Run key + Scheduled Task
Registry: HKCU\Software\Microsoft\Windows\CurrentVersion\Run\WindowsUpdate
Task: \Microsoft\Windows\WindowsUpdate\Updater
EXTRACTED IOCs
SMTP Server: smtp.yandex[.]com
Exfil Email: exfil.account@yandex[.]com
Recipient: operator@protonmail[.]com
Mutex: AgentTesla_2025_Q3_MUTEX
Install Path: %AppData%\Microsoft\Windows\svchost.exe
```

View File

@@ -0,0 +1,68 @@
# API Reference: .NET Malware Reverse Engineering with dnSpy Agent
## Overview
Analyzes .NET malware: validates CLR headers, detects obfuscators (ConfuserEx, SmartAssembly), deobfuscates with de4dot, extracts strings/IOCs, and parses .NET metadata via monodis.
## Dependencies
| Package | Version | Purpose |
|---------|---------|---------|
| hashlib | stdlib | Sample hash computation |
| struct | stdlib | PE/CLR header parsing |
| re | stdlib | String pattern extraction |
## External Tools (Optional)
| Tool | Purpose |
|------|---------|
| diec (Detect It Easy) | Obfuscator identification |
| de4dot | Automated .NET deobfuscation |
| monodis | .NET assembly metadata extraction |
## Core Functions
### `detect_dotnet_assembly(filepath)`
Validates PE file has CLR header (COM descriptor directory entry).
- **Checks**: MZ signature, PE signature, optional header magic, CLR RVA
- **Returns**: `dict` with `is_dotnet`, `clr_header_rva`
### `detect_obfuscator(filepath)`
Runs Detect It Easy to identify ConfuserEx, SmartAssembly, .NET Reactor, Dotfuscator, Babel, Eazfuscator, Crypto Obfuscator.
- **Returns**: `dict` with `detected` list
### `deobfuscate_with_de4dot(filepath, output_path)`
Runs de4dot to remove obfuscation, producing a cleaner assembly.
- **Timeout**: 120 seconds
- **Returns**: `dict` with `success`, `output_path`
### `extract_strings(filepath, min_length)`
Extracts ASCII and Unicode strings, classifies into URLs, IPs, emails, registry keys, base64, and suspicious keywords (keylog, stealer, webhook, etc.).
- **Returns**: `dict[str, list[str]]` - categorized indicator lists
### `analyze_dotnet_metadata(filepath)`
Uses monodis to extract assembly info, type definitions, and method counts.
- **Returns**: `dict` with `type_count`, `method_count`, `types`
### `analyze_dotnet_malware(filepath, output_dir)`
Full pipeline: hashes -> .NET check -> obfuscator detection -> deobfuscation -> strings -> metadata.
## Obfuscators Detected
| Obfuscator | Indicator |
|------------|-----------|
| ConfuserEx | Most common open-source .NET obfuscator |
| SmartAssembly | Commercial obfuscator by Redgate |
| .NET Reactor | Code protection with native stub |
| Dotfuscator | Microsoft-provided obfuscator |
| Eazfuscator | Commercial string/flow obfuscation |
## Suspicious String Keywords
`keylog`, `screenshot`, `clipboard`, `password`, `credential`, `smtp`, `telegram`, `discord`, `webhook`, `stealer`, `inject`, `hook`, `persist`, `startup`
## Usage
```bash
python agent.py suspect.exe
```

View File

@@ -0,0 +1,218 @@
#!/usr/bin/env python3
""".NET malware reverse engineering agent using subprocess wrappers for dnSpy/de4dot."""
import subprocess
import os
import sys
import re
import hashlib
import struct
def compute_hashes(filepath):
"""Compute hashes for sample identification."""
with open(filepath, "rb") as f:
data = f.read()
return {
"md5": hashlib.md5(data).hexdigest(),
"sha256": hashlib.sha256(data).hexdigest(),
"size": len(data),
}
def detect_dotnet_assembly(filepath):
"""Check if file is a .NET assembly by looking for CLI header."""
with open(filepath, "rb") as f:
data = f.read(512)
if data[:2] != b"MZ":
return {"is_dotnet": False, "reason": "Not a PE file"}
try:
pe_offset = struct.unpack_from("<I", data, 0x3C)[0]
if pe_offset + 4 > len(data):
return {"is_dotnet": False, "reason": "Invalid PE header"}
if data[pe_offset:pe_offset + 4] != b"PE\x00\x00":
return {"is_dotnet": False, "reason": "Invalid PE signature"}
opt_offset = pe_offset + 24
magic = struct.unpack_from("<H", data, opt_offset)[0]
if magic == 0x10B:
clr_offset = opt_offset + 208
elif magic == 0x20B:
clr_offset = opt_offset + 224
else:
return {"is_dotnet": False, "reason": "Unknown PE format"}
if clr_offset + 8 <= len(data):
clr_rva, clr_size = struct.unpack_from("<II", data, clr_offset)
if clr_rva > 0 and clr_size > 0:
return {"is_dotnet": True, "clr_header_rva": clr_rva, "clr_size": clr_size}
return {"is_dotnet": False, "reason": "No CLR header"}
except (struct.error, IndexError):
return {"is_dotnet": False, "reason": "Parse error"}
def detect_obfuscator(filepath):
"""Detect .NET obfuscator using Detect It Easy."""
try:
result = subprocess.run(
["diec", filepath], capture_output=True, text=True, timeout=30
)
output = result.stdout
obfuscators = {
"ConfuserEx": "confuser" in output.lower(),
"SmartAssembly": "smartassembly" in output.lower(),
".NET Reactor": "reactor" in output.lower(),
"Dotfuscator": "dotfuscator" in output.lower(),
"Babel": "babel" in output.lower(),
"Eazfuscator": "eazfuscator" in output.lower(),
"Crypto Obfuscator": "crypto" in output.lower() and "obfuscator" in output.lower(),
}
detected = [name for name, found in obfuscators.items() if found]
return {"detected": detected, "raw_output": output.strip()}
except FileNotFoundError:
return {"detected": [], "raw_output": "diec not installed"}
def deobfuscate_with_de4dot(filepath, output_path):
"""Run de4dot to deobfuscate .NET assembly."""
try:
result = subprocess.run(
["de4dot", filepath, "-o", output_path],
capture_output=True, text=True, timeout=120
)
return {
"success": result.returncode == 0,
"output_path": output_path,
"stdout": result.stdout[-500:] if result.stdout else "",
}
except FileNotFoundError:
return {"success": False, "error": "de4dot not installed"}
def extract_strings(filepath, min_length=8):
"""Extract strings and classify for IOCs."""
with open(filepath, "rb") as f:
data = f.read()
unicode_strings = re.findall(
rb"(?:[\x20-\x7e]\x00){%d,}" % min_length, data
)
ascii_strings = re.findall(
rb"[\x20-\x7e]{%d,}" % min_length, data
)
all_strings = set()
for s in ascii_strings:
all_strings.add(s.decode("ascii", errors="ignore"))
for s in unicode_strings:
all_strings.add(s.decode("utf-16-le", errors="ignore"))
indicators = {
"urls": [], "ips": [], "emails": [],
"registry_keys": [], "file_paths": [],
"base64_strings": [], "suspicious_strings": [],
}
suspicious_keywords = [
"keylog", "screenshot", "clipboard", "password", "credential",
"smtp", "telegram", "discord", "webhook", "stealer",
"inject", "hook", "persist", "startup",
]
for s in all_strings:
if re.search(r"https?://", s):
indicators["urls"].append(s)
if re.search(r"\b(\d{1,3}\.){3}\d{1,3}\b", s):
indicators["ips"].append(s)
if re.search(r"[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}", s):
indicators["emails"].append(s)
if re.search(r"HKLM|HKCU|SOFTWARE\\", s, re.IGNORECASE):
indicators["registry_keys"].append(s)
if re.search(r"[A-Za-z0-9+/]{40,}={0,2}$", s):
indicators["base64_strings"].append(s[:100])
for kw in suspicious_keywords:
if kw in s.lower():
indicators["suspicious_strings"].append(s[:100])
break
for key in indicators:
indicators[key] = list(set(indicators[key]))[:20]
return indicators
def analyze_dotnet_metadata(filepath):
"""Extract .NET metadata using monodis or ilspy CLI if available."""
metadata = {}
try:
result = subprocess.run(
["monodis", "--assembly", filepath],
capture_output=True, text=True, timeout=15
)
if result.returncode == 0:
metadata["assembly_info"] = result.stdout.strip()
except FileNotFoundError:
pass
try:
result = subprocess.run(
["monodis", "--typedef", filepath],
capture_output=True, text=True, timeout=15
)
if result.returncode == 0:
types = re.findall(r"(\S+)\s+flags", result.stdout)
metadata["type_count"] = len(types)
metadata["types"] = types[:30]
except FileNotFoundError:
pass
try:
result = subprocess.run(
["monodis", "--method", filepath],
capture_output=True, text=True, timeout=15
)
if result.returncode == 0:
methods = re.findall(r"(\S+)\s+\(", result.stdout)
metadata["method_count"] = len(methods)
except FileNotFoundError:
pass
return metadata
def analyze_dotnet_malware(filepath, output_dir="/tmp/dotnet_analysis"):
"""Full .NET malware analysis pipeline."""
os.makedirs(output_dir, exist_ok=True)
report = {"file": filepath}
report["hashes"] = compute_hashes(filepath)
report["dotnet_check"] = detect_dotnet_assembly(filepath)
if not report["dotnet_check"].get("is_dotnet"):
report["error"] = "Not a .NET assembly"
return report
report["obfuscator"] = detect_obfuscator(filepath)
deobf_path = os.path.join(output_dir, "deobfuscated.exe")
report["deobfuscation"] = deobfuscate_with_de4dot(filepath, deobf_path)
analysis_target = deobf_path if report["deobfuscation"].get("success") else filepath
report["strings"] = extract_strings(analysis_target)
report["metadata"] = analyze_dotnet_metadata(analysis_target)
return report
def print_report(report):
print(".NET Malware Analysis Report")
print("=" * 50)
print(f"File: {report['file']}")
print(f"SHA-256: {report['hashes']['sha256']}")
print(f".NET Assembly: {report['dotnet_check'].get('is_dotnet', False)}")
obf = report.get("obfuscator", {})
if obf.get("detected"):
print(f"Obfuscator: {', '.join(obf['detected'])}")
deobf = report.get("deobfuscation", {})
print(f"Deobfuscation: {'Success' if deobf.get('success') else 'Failed/Skipped'}")
meta = report.get("metadata", {})
if meta:
print(f"Types: {meta.get('type_count', 'N/A')}, Methods: {meta.get('method_count', 'N/A')}")
strings = report.get("strings", {})
if strings:
print("\nExtracted Indicators:")
for cat, values in strings.items():
if values:
print(f" {cat}: {len(values)}")
for v in values[:3]:
print(f" - {v[:80]}")
if __name__ == "__main__":
if len(sys.argv) < 2:
print("Usage: python agent.py <dotnet_executable>")
sys.exit(1)
result = analyze_dotnet_malware(sys.argv[1])
print_report(result)