reverse-skills-max: initial public release (restored, redacted, hardened)
This commit is contained in:
201
reverse-engineering-dotnet-malware-with-dnspy/LICENSE
Normal file
201
reverse-engineering-dotnet-malware-with-dnspy/LICENSE
Normal 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.
|
||||
348
reverse-engineering-dotnet-malware-with-dnspy/SKILL.md
Normal file
348
reverse-engineering-dotnet-malware-with-dnspy/SKILL.md
Normal 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
|
||||
```
|
||||
@@ -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
|
||||
```
|
||||
218
reverse-engineering-dotnet-malware-with-dnspy/scripts/agent.py
Normal file
218
reverse-engineering-dotnet-malware-with-dnspy/scripts/agent.py
Normal 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)
|
||||
Reference in New Issue
Block a user