Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 24 additions & 3 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,24 @@ on:
branches: [master]

jobs:
detect-secrets:
name: Detect Secrets
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Baseline check
run: |
pip install --quiet detect-secrets
detect-secrets scan --baseline .secrets.baseline

build:
name: Build (${{ matrix.configuration }})
runs-on: windows-latest
needs: detect-secrets
strategy:
fail-fast: false
matrix:
configuration: [Debug, Release]

steps:
- uses: actions/checkout@v4
Expand All @@ -22,12 +38,17 @@ jobs:
run: dotnet restore EarlyWorm.sln

- name: Build
run: dotnet build EarlyWorm.sln --no-restore --configuration Release
run: dotnet build EarlyWorm.sln --no-restore --configuration ${{ matrix.configuration }}

test:
name: Test
name: Test (${{ matrix.configuration }})
runs-on: windows-latest
needs: build
strategy:
fail-fast: false
matrix:
configuration: [Debug, Release]

steps:
- uses: actions/checkout@v4

Expand All @@ -40,4 +61,4 @@ jobs:
run: dotnet restore EarlyWorm.sln

- name: Test
run: dotnet test "EarlyWorm.Tests/EarlyWorm.Tests.csproj" --no-restore --configuration Release --verbosity normal
run: dotnet test "EarlyWorm.Tests/EarlyWorm.Tests.csproj" --no-restore --configuration ${{ matrix.configuration }} --verbosity normal
7 changes: 7 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,13 @@ repos:
hooks:
- id: gitleaks

# Baseline-tracked keyword/pattern scanning — catches secrets not caught by gitleaks
- repo: https://github.com/Yelp/detect-secrets
rev: v1.5.0
hooks:
- id: detect-secrets
args: ["--baseline", ".secrets.baseline"]

# C# formatting — enforces .editorconfig rules via dotnet format
- repo: local
hooks:
Expand Down
127 changes: 127 additions & 0 deletions .secrets.baseline
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
{
"version": "1.5.0",
"plugins_used": [
{
"name": "ArtifactoryDetector"
},
{
"name": "AWSKeyDetector"
},
{
"name": "AzureStorageKeyDetector"
},
{
"name": "Base64HighEntropyString",
"limit": 4.5
},
{
"name": "BasicAuthDetector"
},
{
"name": "CloudantDetector"
},
{
"name": "DiscordBotTokenDetector"
},
{
"name": "GitHubTokenDetector"
},
{
"name": "GitLabTokenDetector"
},
{
"name": "HexHighEntropyString",
"limit": 3.0
},
{
"name": "IbmCloudIamDetector"
},
{
"name": "IbmCosHmacDetector"
},
{
"name": "IPPublicDetector"
},
{
"name": "JwtTokenDetector"
},
{
"name": "KeywordDetector",
"keyword_exclude": ""
},
{
"name": "MailchimpDetector"
},
{
"name": "NpmDetector"
},
{
"name": "OpenAIDetector"
},
{
"name": "PrivateKeyDetector"
},
{
"name": "PypiTokenDetector"
},
{
"name": "SendGridDetector"
},
{
"name": "SlackDetector"
},
{
"name": "SoftlayerDetector"
},
{
"name": "SquareOAuthDetector"
},
{
"name": "StripeDetector"
},
{
"name": "TelegramBotTokenDetector"
},
{
"name": "TwilioKeyDetector"
}
],
"filters_used": [
{
"path": "detect_secrets.filters.allowlist.is_line_allowlisted"
},
{
"path": "detect_secrets.filters.common.is_ignored_due_to_verification_policies",
"min_level": 2
},
{
"path": "detect_secrets.filters.heuristic.is_indirect_reference"
},
{
"path": "detect_secrets.filters.heuristic.is_likely_id_string"
},
{
"path": "detect_secrets.filters.heuristic.is_lock_file"
},
{
"path": "detect_secrets.filters.heuristic.is_not_alphanumeric_string"
},
{
"path": "detect_secrets.filters.heuristic.is_potential_uuid"
},
{
"path": "detect_secrets.filters.heuristic.is_prefixed_with_dollar_sign"
},
{
"path": "detect_secrets.filters.heuristic.is_sequential_string"
},
{
"path": "detect_secrets.filters.heuristic.is_swagger_file"
},
{
"path": "detect_secrets.filters.heuristic.is_templated_secret"
}
],
"results": {},
"generated_at": "2026-05-19T11:01:53Z"
}
11 changes: 11 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,17 @@ EarlyWorm/
| [#8](https://github.com/incendiary/EarlyWorm/issues/8) | ✅ Closed | Enable branch protection and make repository public |
| [#9](https://github.com/incendiary/EarlyWorm/issues/9) | ✅ Closed | Remove residual PDiddyVoke paths from git index |
| [#10](https://github.com/incendiary/EarlyWorm/issues/10) | ✅ Closed | Consider integration test harness — manual smoke test checklist added to README |
| [#11](https://github.com/incendiary/EarlyWorm/issues/11) | ✅ Closed | Add xUnit tests for Win32 imports and DInvoke resolution |
| [#12](https://github.com/incendiary/EarlyWorm/issues/12) | ✅ Closed | Fix build: make Imports class public for test project access |
| [#13](https://github.com/incendiary/EarlyWorm/issues/13) | ✅ Done | detect-secrets baseline and pre-commit hook |
| [#14](https://github.com/incendiary/EarlyWorm/issues/14) | ✅ Done | CI matrix for Debug and Release configurations |
| [#15](https://github.com/incendiary/EarlyWorm/issues/15) | ✅ Done | [PInvoke vs DInvoke technique comparison](TECHNIQUES.md) |

---

## Techniques

See [TECHNIQUES.md](TECHNIQUES.md) for a comparison of PInvoke vs DInvoke injection approaches — IAT visibility, EDR hook bypass, and OPSEC tradeoffs.

---

Expand Down
60 changes: 60 additions & 0 deletions TECHNIQUES.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
# Injection Techniques: PInvoke vs DInvoke

This project uses **DInvoke** for Win32 API resolution. This document explains the practical difference between PInvoke and DInvoke for offensive tooling and why DInvoke is preferred here.

---

## PInvoke (Platform Invocation Services)

PInvoke is the standard .NET mechanism for calling native Win32 functions:

```csharp
[DllImport("kernel32.dll")]
public static extern IntPtr VirtualAllocEx(...);
```

**How it works:** The CLR resolves the function at load time and writes the DLL name + function name into the compiled assembly's Import Address Table (IAT). When the process starts, the OS loader populates the IAT with resolved addresses.

**OPSEC concerns:**
- The IAT is statically visible in the PE header — `VirtualAllocEx`, `WriteProcessMemory`, `CreateRemoteThread` in the IAT are high-confidence injection indicators for EDRs and static scanners.
- Many EDRs instrument IAT entries at process load time by patching the resolved addresses to point to their own hooks.

---

## DInvoke (Dynamic Invocation)

DInvoke resolves function pointers at runtime without creating IAT entries:

```csharp
var ptr = DInvoke.DynamicInvoke.Generic.GetLibraryAddress("kernel32.dll", "VirtualAllocEx");
```

**How it works:** `GetLibraryAddress` walks the PEB's loaded module list to find the target DLL in memory, then parses its export directory to locate the function — no static IAT entry, no `LoadLibrary`/`GetProcAddress` call that EDRs commonly hook.

**OPSEC advantages:**
- No IAT entries for sensitive APIs → harder for static analysis to flag.
- Bypasses some userland API hooks if the hook is applied at the IAT level rather than the function prologue.
- Function resolution happens at call-time, not load-time, reducing the static footprint.

**Limitations:**
- Does not bypass inline hooks (prologue patching) on the underlying ntdll/kernel32 functions.
- Slightly more complex code; type-casting delegates requires care.
- Reflection-based approaches can still be detected behaviourally.

---

## Summary

| Aspect | PInvoke | DInvoke |
|--------|---------|---------|
| IAT visibility | ✗ Visible to static analysis | ✓ No IAT entry |
| EDR hook bypass | ✗ IAT hooks apply | ✓ Bypasses IAT-level hooks |
| Inline hook bypass | ✗ No | ✗ No |
| Code complexity | Low | Medium |
| Detection surface | Higher | Lower |

**For authorized red team research**, DInvoke reduces the static artefact footprint and better reflects the techniques used by real-world adversaries, making it a more representative test of defensive coverage.

---

*This document is for educational purposes. All use of this tool requires explicit written authorization from the target system owner.*
Loading