reverse-skills-max: initial public release (restored, redacted, hardened)
This commit is contained in:
@@ -0,0 +1,9 @@
|
||||
Apache License 2.0 — see the repository root LICENSE file for the full text.
|
||||
|
||||
Copyright the AnalystAIPack contributors.
|
||||
|
||||
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
|
||||
@@ -0,0 +1,96 @@
|
||||
---
|
||||
name: analyzing-dotnet-malware-internals
|
||||
description: 'Reverses .NET/managed malware: decompiling MSIL back to C#, defeating common
|
||||
.NET protectors and string encryptors, and tracing reflection-based loaders to recover the
|
||||
real payload. Activates for requests to analyze a .NET sample, decompile MSIL, or unpack a
|
||||
managed loader.'
|
||||
domain: cybersecurity
|
||||
subdomain: reverse-engineering
|
||||
tags:
|
||||
- reverse-engineering
|
||||
- dotnet
|
||||
- msil
|
||||
- decompilation
|
||||
- deobfuscation
|
||||
- managed-code
|
||||
version: 1.0.0
|
||||
author: analyst-ai-pack
|
||||
license: Apache-2.0
|
||||
mitre_attack:
|
||||
- T1027
|
||||
- T1620
|
||||
- T1055
|
||||
d3fend:
|
||||
- D3-FCR
|
||||
- D3-DA
|
||||
references:
|
||||
- 'ECMA-335 Common Language Infrastructure (CIL/MSIL) — https://ecma-international.org/publications-and-standards/standards/ecma-335/'
|
||||
- 'ILSpy decompiler — https://github.com/icsharpcode/ILSpy'
|
||||
---
|
||||
|
||||
# Analyzing .NET Malware Internals
|
||||
|
||||
## When to Use
|
||||
|
||||
- A sample is a managed (.NET) assembly — confirmed by a CLR header / `mscoree` import or
|
||||
`BSJB` metadata signature.
|
||||
- You need readable C# from MSIL and want to defeat .NET-specific obfuscation.
|
||||
- A loader uses reflection (`Assembly.Load`) to run an in-memory payload you must recover.
|
||||
|
||||
**Do not use** native disassembly workflows (Ghidra for x86) as the primary tool — managed
|
||||
code decompiles far more cleanly with a .NET decompiler.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- ILSpy / dnSpyEx for decompilation and (with dnSpyEx) managed debugging.
|
||||
- de4dot or equivalent for known protectors; familiarity with common .NET obfuscators.
|
||||
|
||||
## Workflow
|
||||
|
||||
### Step 1: Confirm it is managed
|
||||
|
||||
Check for the CLR runtime header and the `BSJB` metadata magic:
|
||||
|
||||
```bash
|
||||
python scripts/analyst.py identify sample.exe
|
||||
```
|
||||
|
||||
### Step 2: Decompile
|
||||
|
||||
Open in ILSpy/dnSpyEx and review the entry point, `Main`, and module initializer
|
||||
(`<Module>.cctor`), which protectors often abuse.
|
||||
|
||||
### Step 3: Handle obfuscation
|
||||
|
||||
Recognize and undo common schemes:
|
||||
|
||||
- **String encryption** — a decryptor method called everywhere; run/trace it to recover
|
||||
plaintext (de4dot can often static-decrypt).
|
||||
- **Control-flow flattening** — follow the dispatcher state machine.
|
||||
- **Proxy methods / renaming** — rely on decompiler analysis rather than names.
|
||||
|
||||
### Step 4: Trace reflection loaders
|
||||
|
||||
Find `Assembly.Load(byte[])` / `Activator.CreateInstance`; dump the byte array argument at
|
||||
runtime (managed debugger breakpoint) to recover the real second-stage assembly, then recurse.
|
||||
|
||||
### Step 5: Analyze the payload
|
||||
|
||||
Decompile the recovered stage; extract C2, configuration, and capabilities for the report.
|
||||
|
||||
## Validation
|
||||
|
||||
- Decompiled C# is coherent (named or recovered) and the entry path is traced.
|
||||
- Encrypted strings are recovered to plaintext.
|
||||
- The reflection-loaded stage is dumped and itself decompiles.
|
||||
|
||||
## Pitfalls
|
||||
|
||||
- Treating the loader as the payload — managed malware is frequently multi-stage.
|
||||
- Ignoring the module initializer where protectors install hooks.
|
||||
- Static-decrypting strings when the scheme is runtime-keyed; debug and dump instead.
|
||||
|
||||
## References
|
||||
|
||||
- See [`references/api-reference.md`](references/api-reference.md) for the CLR identifier.
|
||||
- ECMA-335 and ILSpy (linked in frontmatter).
|
||||
@@ -0,0 +1,39 @@
|
||||
# API Reference: Reverse Engineering .NET Malware
|
||||
|
||||
## Overview
|
||||
|
||||
`scripts/analyst.py` parses a PE to detect the CLR runtime header (COM descriptor data directory
|
||||
index 14) and the `BSJB` metadata-root signature, confirming whether a sample is managed. It
|
||||
parses bytes only and never executes the sample.
|
||||
|
||||
## Dependencies
|
||||
|
||||
| Package | Version | Purpose |
|
||||
|---------|---------|---------|
|
||||
| (stdlib) | — | struct parsing, file IO |
|
||||
|
||||
## External tools
|
||||
|
||||
| Tool | Purpose |
|
||||
|------|---------|
|
||||
| ILSpy / dnSpyEx | Decompile MSIL to C#; managed debugging |
|
||||
| de4dot | Static deobfuscation of known protectors |
|
||||
|
||||
## Core functions
|
||||
|
||||
### `identify(path)`
|
||||
Returns whether the sample is managed, whether the `BSJB` metadata signature is present, and
|
||||
diagnostic notes (mscoree/_CorExeMain references).
|
||||
|
||||
## Usage
|
||||
|
||||
```bash
|
||||
python scripts/analyst.py identify sample.exe
|
||||
```
|
||||
|
||||
## Sources
|
||||
|
||||
- ECMA-335 Common Language Infrastructure (CIL/MSIL metadata, BSJB root) —
|
||||
https://ecma-international.org/publications-and-standards/standards/ecma-335/
|
||||
- PE COM descriptor data directory (index 14) — PE/COFF specification
|
||||
- ILSpy decompiler — https://github.com/icsharpcode/ILSpy
|
||||
@@ -0,0 +1,64 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Identify a .NET/managed assembly and surface basic CLR metadata signals.
|
||||
|
||||
Parses the PE far enough to detect the COM descriptor (CLR) data directory and searches for
|
||||
the ``BSJB`` metadata-root signature. Static parsing only; the sample is never executed.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import struct
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
def identify(path: str) -> dict:
|
||||
data = Path(path).read_bytes()
|
||||
result = {"managed": False, "bsjb_metadata": False, "notes": []}
|
||||
|
||||
if data[:2] != b"MZ":
|
||||
result["notes"].append("not a PE (no MZ)")
|
||||
return result
|
||||
|
||||
e_lfanew = struct.unpack_from("<I", data, 0x3C)[0]
|
||||
if data[e_lfanew:e_lfanew + 4] != b"PE\x00\x00":
|
||||
result["notes"].append("no PE signature")
|
||||
return result
|
||||
|
||||
coff = e_lfanew + 4
|
||||
opt = coff + 20
|
||||
magic = struct.unpack_from("<H", data, opt)[0]
|
||||
pe32_plus = magic == 0x20B
|
||||
# data directories start after the standard+windows optional header fields
|
||||
dd_offset = opt + (112 if pe32_plus else 96)
|
||||
# COM descriptor is data directory index 14
|
||||
com_dir = dd_offset + 14 * 8
|
||||
rva, size = struct.unpack_from("<II", data, com_dir)
|
||||
if rva and size:
|
||||
result["managed"] = True
|
||||
result["notes"].append("CLR runtime header (COM descriptor) present")
|
||||
|
||||
if b"BSJB" in data:
|
||||
result["bsjb_metadata"] = True
|
||||
result["notes"].append("BSJB metadata root signature found")
|
||||
|
||||
if b"mscoree.dll" in data or b"_CorExeMain" in data:
|
||||
result["notes"].append("mscoree / _CorExeMain reference")
|
||||
|
||||
result["managed"] = result["managed"] or result["bsjb_metadata"]
|
||||
return result
|
||||
|
||||
|
||||
def main(argv: list[str] | None = None) -> int:
|
||||
parser = argparse.ArgumentParser(description="Identify managed .NET assembly")
|
||||
sub = parser.add_subparsers(dest="cmd", required=True)
|
||||
p = sub.add_parser("identify")
|
||||
p.add_argument("file")
|
||||
args = parser.parse_args(argv)
|
||||
print(json.dumps(identify(args.file), indent=2))
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
Reference in New Issue
Block a user