This commit is contained in:
toneillcodes
2026-05-21 22:45:41 -04:00
2 changed files with 203 additions and 1 deletions
+42 -1
View File
@@ -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.
+161
View File
@@ -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
)