mirror of
https://github.com/toneillcodes/windows-process-injection
synced 2026-06-21 14:11:25 +00:00
Merge branch 'main' of https://github.com/toneillcodes/windows-process-injection
This commit is contained in:
@@ -9,6 +9,47 @@ Module Stomping is a process injection technique where a legitimate, image-backe
|
||||
| **Tactical Goal** | **Evade Memory Scanners:** Bypasses detections that flag `RX` memory regions not backed by a file on disk (`MEM_PRIVATE`). |
|
||||
| **Stealth** | **Moderate.** While it solves the "unbacked memory" problem, it introduces another IoC "Module Mismatch" (the memory content no longer matches the file on disk). |
|
||||
|
||||
## DLL Scanner
|
||||
[find-stompable-dlls.py](find-stompable-dlls.py)
|
||||
```
|
||||
PS C:\Users\Administrator\Desktop\Tools > python .\find-stompable-dlls.py 0x80000
|
||||
[*] Scanning Target Directory: 'C:\Windows\System32'
|
||||
[*] Filtering for Files : > 1.0MB
|
||||
[*] Required .text Space : 0x80000 bytes
|
||||
------------------------------------------------------------------------------------------
|
||||
DLL Name | File Size (MB) | Size of .text | Virtual Address
|
||||
------------------------------------------------------------------------------------------
|
||||
aadtb.dll | 1.48 | 0xff60c | 0x1000
|
||||
ActiveSyncProvider.dll | 1.73 | 0x14abe2 | 0x1000
|
||||
aeinv.dll | 1.60 | 0x13495a | 0x1000
|
||||
aemarebackup.dll | 1.23 | 0xf8a64 | 0x1000
|
||||
aepic.dll | 1.25 | 0xf2afa | 0x1000
|
||||
APMon.dll | 1.57 | 0xe395c | 0x1000
|
||||
appraiser.dll | 3.18 | 0x26b133 | 0x1000
|
||||
AppVEntSubsystemController.dll | 1.22 | 0xc941c | 0x1000
|
||||
AppVEntSubsystems64.dll | 1.63 | 0x10565c | 0x1000
|
||||
AppVEntVirtualization.dll | 1.56 | 0x108d1c | 0x1000
|
||||
AppVIntegration.dll | 1.37 | 0xd648c | 0x1000
|
||||
AppXDeploymentClient.dll | 1.45 | 0xe89d0 | 0x1000
|
||||
AppXDeploymentExtensions.desktop.dll | 2.68 | 0x1ab07c | 0x1000
|
||||
AppXDeploymentExtensions.onecore.dll | 3.44 | 0x23493c | 0x1000
|
||||
...
|
||||
PrintConfig.dll | 3.66 | 0x1c497c | 0x1000
|
||||
PS5UI.DLL | 1.19 | 0xa993c | 0x1000
|
||||
UNIDRVUI.DLL | 1.27 | 0xb840c | 0x1000
|
||||
cimwin32.dll | 1.75 | 0x12f3dc | 0x1000
|
||||
DMWmiBridgeProv.dll | 3.75 | 0x244a1c | 0x1000
|
||||
Microsoft.Uev.AgentWmi.dll | 1.09 | 0xc3d6c | 0x1000
|
||||
NetPeerDistCim.dll | 1.39 | 0xffe3a | 0x1000
|
||||
wbemcore.dll | 1.77 | 0xee98c | 0x1000
|
||||
Microsoft.Windows.Appx.PackageManager.Commands.Core.dll | 2.68 | 0x2acd98 | 0x2000
|
||||
AuthFWSnapIn.Resources.dll | 2.98 | 0x2f9674 | 0x2000
|
||||
Microsoft.Windows.ServerManager.Plugins.Ipam.resources.dll | 2.72 | 0x2b6d04 | 0x2000
|
||||
------------------------------------------------------------------------------------------
|
||||
[*] Found 433 potential candidates.
|
||||
PS C:\Users\Administrator\Desktop\Tools >
|
||||
```
|
||||
|
||||
## Execution Steps
|
||||
The `local-stomp.cpp` example follows this execution logic:
|
||||
|
||||
@@ -37,4 +78,4 @@ The use of `VirtualProtect` on an image-backed region is a high-confidence heuri
|
||||
## Indicators of Compromise (IoC)
|
||||
* **Memory/Disk Mismatch:** Significant byte differences between the loaded module and its corresponding `C:\Windows\System32\` file.
|
||||
* **Suspicious Call Trace:** Thread execution starting from the middle of a DLL's code section rather than a legitimate exported function.
|
||||
* **API Pattern:** The sequence of `LoadLibrary` -> `VirtualProtect(RW)` -> `VirtualProtect(RX)` is a classic signature of memory manipulation.
|
||||
* **API Pattern:** The sequence of `LoadLibrary` -> `VirtualProtect(RW)` -> `VirtualProtect(RX)` is a classic signature of memory manipulation.
|
||||
|
||||
@@ -0,0 +1,161 @@
|
||||
import os
|
||||
import argparse
|
||||
import pefile
|
||||
|
||||
def auto_int(x):
|
||||
"""Helper to accept both decimal integers and hex strings (0x...) from CLI."""
|
||||
return int(x, 0)
|
||||
|
||||
def parse_flat_file(file_path, list_type_label):
|
||||
"""
|
||||
Parses a flat text file containing only clean DLL filenames (one per line).
|
||||
Normalizes them to lowercase for robust, case-insensitive comparison.
|
||||
"""
|
||||
names_set = set()
|
||||
if not file_path:
|
||||
return names_set
|
||||
|
||||
print(f"[*] Loading {list_type_label} from: '{file_path}'")
|
||||
try:
|
||||
with open(file_path, 'r', encoding='utf-8', errors='ignore') as f:
|
||||
for line in f:
|
||||
dll_name = line.strip().lower()
|
||||
|
||||
# Ignore blank lines or markdown-style separator lines
|
||||
if not dll_name or dll_name.startswith('---') or dll_name.startswith('[+]'):
|
||||
continue
|
||||
|
||||
names_set.add(dll_name)
|
||||
|
||||
print(f"[+] Loaded {len(names_set)} modules into {list_type_label} filter.")
|
||||
except Exception as e:
|
||||
print(f"[-] Warning: Failed to read {list_type_label} file: {e}")
|
||||
|
||||
return names_set
|
||||
|
||||
def find_phantom_dll_candidates(target_dir, min_file_size_mb, min_text_section_size, excluded_dlls, included_dlls):
|
||||
"""
|
||||
Scans a directory for DLLs matching structural criteria.
|
||||
Supports exclusion filters (blacklists) and targeted scope filters (whitelists).
|
||||
"""
|
||||
min_file_size_bytes = min_file_size_mb * 1024 * 1024
|
||||
candidates = []
|
||||
|
||||
print(f"[*] Scanning Target Directory: '{target_dir}'")
|
||||
print(f"[*] Filtering for Files : > {min_file_size_mb}MB")
|
||||
print(f"[*] Required .text Space : {hex(min_text_section_size)} bytes")
|
||||
|
||||
if included_dlls:
|
||||
print(f"[*] Targeted Include Filter : Active ({len(included_dlls)} specific targets allowed)")
|
||||
if excluded_dlls:
|
||||
print(f"[*] Exclusion Filter : Active ({len(excluded_dlls)} modules blacklisted)")
|
||||
|
||||
print("-" * 140)
|
||||
print(f"{'Full File Path':<85} | {'File Size (MB)':<15} | {'Size of .text':<15} | {'Virtual Address':<15}")
|
||||
print("-" * 140)
|
||||
|
||||
for root, _, files in os.walk(target_dir):
|
||||
for file in files:
|
||||
file_lower = file.lower()
|
||||
if not file_lower.endswith('.dll'):
|
||||
continue
|
||||
|
||||
# Gate 1: If an include list is provided, ignore everything else
|
||||
if included_dlls and (file_lower not in included_dlls):
|
||||
continue
|
||||
|
||||
# Gate 2: If an exclude list is provided, drop any matching items
|
||||
if excluded_dlls and (file_lower in excluded_dlls):
|
||||
continue
|
||||
|
||||
file_path = os.path.join(root, file)
|
||||
|
||||
try:
|
||||
file_size = os.path.getsize(file_path)
|
||||
if file_size < min_file_size_bytes:
|
||||
continue
|
||||
|
||||
pe = pefile.PE(file_path, fast_load=True)
|
||||
|
||||
for section in pe.sections:
|
||||
section_name = section.Name.decode('utf-8', errors='ignore').strip('\x00')
|
||||
|
||||
if section_name == '.text':
|
||||
v_size = section.Misc_VirtualSize
|
||||
|
||||
if v_size >= min_text_section_size:
|
||||
file_size_mb = file_size / (1024 * 1024)
|
||||
print(f"{file_path:<85} | {file_size_mb:<15.2f} | {hex(v_size):<15} | {hex(section.VirtualAddress):<15}")
|
||||
candidates.append({
|
||||
'path': file_path,
|
||||
'file_size': file_size,
|
||||
'text_virtual_size': v_size
|
||||
})
|
||||
break
|
||||
|
||||
except (pefile.PEFormatError, PermissionError, FileNotFoundError):
|
||||
continue
|
||||
|
||||
print("-" * 140)
|
||||
print(f"[*] Found {len(candidates)} potential candidates matching the criteria.")
|
||||
return candidates
|
||||
|
||||
if __name__ == "__main__":
|
||||
parser = argparse.ArgumentParser(description="Find DLL candidates with targeted tracking and exclusion parameters.")
|
||||
|
||||
# Space required
|
||||
parser.add_argument(
|
||||
"size",
|
||||
type=auto_int,
|
||||
help="The total required size of the .text section (e.g., 0x80000)"
|
||||
)
|
||||
|
||||
# Directory to scan
|
||||
parser.add_argument(
|
||||
"-d", "--dir",
|
||||
type=str,
|
||||
default=r"C:\Windows\System32",
|
||||
help="Target directory to scan (default: C:\\Windows\\System32)"
|
||||
)
|
||||
|
||||
# Minimum size of image
|
||||
parser.add_argument(
|
||||
"-m", "--min-size",
|
||||
type=float,
|
||||
default=1.0,
|
||||
help="Minimum file size on disk in MB (default: 1.0)"
|
||||
)
|
||||
|
||||
# Exclude list
|
||||
parser.add_argument(
|
||||
"-x", "--exclude",
|
||||
type=str,
|
||||
default=None,
|
||||
help="Path to a text file containing specific DLLs to EXCLUDE"
|
||||
)
|
||||
|
||||
# Include list
|
||||
parser.add_argument(
|
||||
"-i", "--include",
|
||||
type=str,
|
||||
default=None,
|
||||
help="Path to a text file containing specific DLLs to INCLUDE"
|
||||
)
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
if not os.path.isdir(args.dir):
|
||||
print(f"[-] Error: '{args.dir}' is not a valid directory.")
|
||||
exit(1)
|
||||
|
||||
# Parse both filters independently
|
||||
excluded_modules = parse_flat_file(args.exclude, "EXCLUDE_MODULES")
|
||||
included_modules = parse_flat_file(args.include, "INCLUDE_MODULES")
|
||||
|
||||
find_phantom_dll_candidates(
|
||||
target_dir=args.dir,
|
||||
min_file_size_mb=args.min_size,
|
||||
min_text_section_size=args.size,
|
||||
excluded_dlls=excluded_modules,
|
||||
included_dlls=included_modules
|
||||
)
|
||||
Reference in New Issue
Block a user