- Example5: Fix format string bugs (alignment specifier placement, SafeMemoryHandle formatting) - Example4: Fix DetourManager.Detour() → Create() in all doc strings - Example3: Fix MemoryBase.CreateFunction() → InProcessInvoker.CreateFunction() in doc strings - Examples 1-5: Correct all runtime errors and API mismatches vs actual WhiteMagic API - README: Fix DetourManager.Detour() examples to use Create(); add missing code fence markers - Add WhiteMagic.Examples project with 5 comprehensive example files (40+ sub-examples) - Add docfx.json and toc.md for DocFX API reference generation - Add 5 conceptual guides: architecture, memory-access, execution-models, hooking, troubleshooting - Ensure zero errors, zero warnings across all projects (net8.0-windows) Doc strings now teach correct APIs; runtime format bugs eliminated; build succeeds.
532 lines
20 KiB
C#
532 lines
20 KiB
C#
using System;
|
|
using System.Diagnostics;
|
|
using System.Runtime.InteropServices;
|
|
using System.Linq;
|
|
using WhiteMagic;
|
|
using WhiteMagic.Windows;
|
|
using WhiteMagic.Assembly;
|
|
using WhiteMagic.Discovery;
|
|
|
|
namespace WhiteMagic.Examples;
|
|
|
|
/// <summary>
|
|
/// Example 5: High-Level API
|
|
/// Demonstrates RemotePointer, RemoteModule, RemoteFunction, and other high-level APIs
|
|
/// </summary>
|
|
public class HighLevelAPI
|
|
{
|
|
/// <summary>
|
|
/// Target process for examples
|
|
/// </summary>
|
|
private static Process? TargetProcess()
|
|
{
|
|
var processes = Process.GetProcessesByName("notepad");
|
|
if (processes.Length > 0)
|
|
return processes[0];
|
|
|
|
Console.WriteLine("No Notepad process found. Please launch Notepad first.");
|
|
return null;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Example 5.1: RemotePointer (Fluent Pointer Arithmetic)
|
|
/// </summary>
|
|
public static void RemotePointerExample()
|
|
{
|
|
Console.WriteLine("=== Example 5.1: RemotePointer (Fluent Pointer Arithmetic) ===");
|
|
|
|
var process = TargetProcess();
|
|
if (process == null) return;
|
|
|
|
using var magic = Magic.Open(process);
|
|
|
|
IntPtr baseAddress = magic.Memory.ImageBase;
|
|
|
|
// Create a RemotePointer to base address
|
|
var ptr = magic[baseAddress];
|
|
Console.WriteLine($"Created RemotePointer to: 0x{baseAddress:X}");
|
|
|
|
try
|
|
{
|
|
// Read different offsets from the same base
|
|
int offset1 = ptr.Read<int>(0x1000);
|
|
Console.WriteLine($"Read int at offset 0x1000: {offset1}");
|
|
|
|
float offset2 = ptr.Read<float>(0x2000);
|
|
Console.WriteLine($"Read float at offset 0x2000: {offset2}");
|
|
|
|
// Write to offset
|
|
bool writeSuccess = ptr.Write(42, 0x3000);
|
|
Console.WriteLine($"Write int to offset 0x3000: {(writeSuccess ? "Success" : "Failed")}");
|
|
|
|
// Chained pointer reads (pointer → pointer → value)
|
|
IntPtr ptr1 = ptr.Read<IntPtr>(0x4000);
|
|
if (ptr1 != IntPtr.Zero)
|
|
{
|
|
var ptr2 = magic[ptr1];
|
|
int nestedValue = ptr2.Read<int>(0x50);
|
|
Console.WriteLine($"Nested pointer read: 0x{baseAddress:X} → 0x{ptr1:X} → {nestedValue}");
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Console.WriteLine($"✗ Operation failed (offset may be invalid): {ex.Message}");
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Example 5.2: RemoteModule (Module Enumeration)
|
|
/// </summary>
|
|
public static void RemoteModuleExample()
|
|
{
|
|
Console.WriteLine("\n=== Example 5.2: RemoteModule (Module Enumeration) ===");
|
|
|
|
var process = TargetProcess();
|
|
if (process == null) return;
|
|
|
|
using var magic = Magic.Open(process);
|
|
|
|
// List all loaded modules using Process.Modules
|
|
Console.WriteLine("Loaded modules:");
|
|
Console.WriteLine("─────────────────────────────────────────────────────────────────");
|
|
Console.WriteLine(string.Format("{0,-25} {1,-15} {2,-15}", "Name", "Base Address", "Size"));
|
|
Console.WriteLine("─────────────────────────────────────────────────────────────────");
|
|
|
|
foreach (System.Diagnostics.ProcessModule module in process.Modules)
|
|
{
|
|
Console.WriteLine(string.Format("{0,-25} 0x{1:X} 0x{2:X}",
|
|
module.ModuleName, module.BaseAddress, module.ModuleMemorySize));
|
|
}
|
|
|
|
// Access specific module using WhiteMagic's RemoteModule
|
|
var kernel32 = magic["kernel32.dll"];
|
|
if (kernel32 != null && kernel32.BaseAddress != IntPtr.Zero)
|
|
{
|
|
Console.WriteLine();
|
|
Console.WriteLine($"✓ Found {kernel32.Name} at 0x{kernel32.BaseAddress:X}");
|
|
|
|
// Resolve specific exports by name
|
|
Console.WriteLine($"\nResolved exports from {kernel32.Name}:");
|
|
Console.WriteLine("─────────────────────────────────────────────────────────────────");
|
|
|
|
var getTickCount = kernel32["GetTickCount"];
|
|
if (getTickCount.Address != IntPtr.Zero)
|
|
{
|
|
Console.WriteLine($"✓ GetTickCount at 0x{getTickCount.Address:X}");
|
|
}
|
|
|
|
var getCurrentProcessId = kernel32["GetCurrentProcessId"];
|
|
if (getCurrentProcessId.Address != IntPtr.Zero)
|
|
{
|
|
Console.WriteLine($"✓ GetCurrentProcessId at 0x{getCurrentProcessId.Address:X}");
|
|
}
|
|
|
|
var messageBoxA = kernel32["MessageBoxA"];
|
|
if (messageBoxA.Address != IntPtr.Zero)
|
|
{
|
|
Console.WriteLine($"✓ MessageBoxA at 0x{messageBoxA.Address:X}");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Console.WriteLine("✗ kernel32.dll not found");
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Example 5.3: RemoteFunction (Function Resolution and Execution)
|
|
/// </summary>
|
|
public static void RemoteFunctionExample()
|
|
{
|
|
Console.WriteLine("\n=== Example 5.3: RemoteFunction (Function Resolution) ===");
|
|
|
|
var process = TargetProcess();
|
|
if (process == null) return;
|
|
|
|
using var magic = Magic.Open(process);
|
|
|
|
try
|
|
{
|
|
// Resolve function by name
|
|
var getTickCount = magic["kernel32.dll"]["GetTickCount"];
|
|
if (getTickCount.Address != IntPtr.Zero)
|
|
{
|
|
Console.WriteLine($"✓ Resolved GetTickCount at: 0x{getTickCount.Address:X}");
|
|
|
|
// Execute the function
|
|
uint ticks = getTickCount.Execute<uint>(CallConvention.Stdcall);
|
|
Console.WriteLine($" Result: {ticks} ticks ({TimeSpan.FromMilliseconds(ticks):hh\\:mm\\:ss})");
|
|
}
|
|
else
|
|
{
|
|
Console.WriteLine("✗ GetTickCount not found");
|
|
}
|
|
|
|
// Resolve another function
|
|
var getCurrentProcessId = magic["kernel32.dll"]["GetCurrentProcessId"];
|
|
if (getCurrentProcessId.Address != IntPtr.Zero)
|
|
{
|
|
Console.WriteLine($"✓ Resolved GetCurrentProcessId at: 0x{getCurrentProcessId.Address:X}");
|
|
|
|
uint processId = getCurrentProcessId.Execute<uint>(CallConvention.Stdcall);
|
|
Console.WriteLine($" Result: Process ID = {processId}");
|
|
}
|
|
else
|
|
{
|
|
Console.WriteLine("✗ GetCurrentProcessId not found");
|
|
}
|
|
|
|
// Try to resolve non-existent function
|
|
var nonExistent = magic["kernel32.dll"]["NonExistentFunction123"];
|
|
if (nonExistent.Address == IntPtr.Zero)
|
|
{
|
|
Console.WriteLine($"✓ NonExistentFunction123 correctly not found");
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Console.WriteLine($"✗ Function resolution/execution failed: {ex.Message}");
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Example 5.4: ProcessInfo and Module Details
|
|
/// Demonstrates accessing process and module metadata through WhiteMagic
|
|
/// </summary>
|
|
public static void ProcessInfoExample()
|
|
{
|
|
Console.WriteLine("\n=== Example 5.4: ProcessInfo and Module Details ===");
|
|
|
|
var process = TargetProcess();
|
|
if (process == null) return;
|
|
|
|
using var magic = Magic.Open(process);
|
|
|
|
try
|
|
{
|
|
// Access process metadata
|
|
Console.WriteLine("Process Information:");
|
|
Console.WriteLine("─────────────────────────────────────────────────────────────────");
|
|
Console.WriteLine($"Process ID: {process.Id}");
|
|
Console.WriteLine($"Process Name: {process.ProcessName}");
|
|
Console.WriteLine($"Main Window Title: {process.MainWindowTitle}");
|
|
if (process.MainModule != null)
|
|
{
|
|
Console.WriteLine($"Main Module Base: 0x{process.MainModule.BaseAddress:X}");
|
|
Console.WriteLine($"Main Module Size: 0x{process.MainModule.ModuleMemorySize:X} bytes");
|
|
Console.WriteLine($"Main Module Path: {process.MainModule.FileName}");
|
|
}
|
|
|
|
// Enumerate loaded modules
|
|
Console.WriteLine("\nLoaded Modules:");
|
|
Console.WriteLine("─────────────────────────────────────────────────────────────────");
|
|
int moduleCount = 0;
|
|
foreach (System.Diagnostics.ProcessModule module in process.Modules)
|
|
{
|
|
if (moduleCount < 5) // Show first 5 modules
|
|
{
|
|
Console.WriteLine($" {module.ModuleName,-30} Base: 0x{module.BaseAddress:X16} Size: 0x{module.ModuleMemorySize:X}");
|
|
moduleCount++;
|
|
}
|
|
}
|
|
if (process.Modules.Count > 5)
|
|
{
|
|
Console.WriteLine($" ... and {process.Modules.Count - 5} more modules");
|
|
}
|
|
|
|
// Memory information
|
|
Console.WriteLine("\nMemory Information:");
|
|
Console.WriteLine("─────────────────────────────────────────────────────────────────");
|
|
Console.WriteLine($"Image Base: 0x{magic.Memory.ImageBase:X}");
|
|
Console.WriteLine($"Handle: 0x{magic.Memory.Handle.DangerousGetHandle():X}");
|
|
Console.WriteLine($"Bitness: {(magic.Memory.Is64Bit ? "64" : "32")} bits");
|
|
|
|
Console.WriteLine("✓ Process information retrieved successfully");
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Console.WriteLine($"✗ Process info access failed: {ex.Message}");
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Example 5.5: RemoteWindow (Window Manipulation)
|
|
/// </summary>
|
|
public static void RemoteWindowExample()
|
|
{
|
|
Console.WriteLine("\n=== Example 5.5: RemoteWindow (Window Manipulation) ===");
|
|
|
|
var process = TargetProcess();
|
|
if (process == null) return;
|
|
|
|
using var magic = Magic.Open(process);
|
|
|
|
try
|
|
{
|
|
// Get main window handle from Process and create RemoteWindow
|
|
if (process.MainWindowHandle != IntPtr.Zero)
|
|
{
|
|
var mainWindow = new RemoteWindow(process.MainWindowHandle);
|
|
|
|
Console.WriteLine($"Main Window: {mainWindow.Title}");
|
|
Console.WriteLine($"Handle: 0x{mainWindow.Handle:X}");
|
|
Console.WriteLine($"Class: {mainWindow.ClassName}");
|
|
|
|
// Get window rect through WinAPI calls (simplified - would need P/Invoke for full implementation)
|
|
Console.WriteLine($"Note: Full window rect information requires additional WinAPI P/Invoke declarations");
|
|
|
|
// Modify window properties
|
|
Console.WriteLine("\nModifying window properties...");
|
|
|
|
// Flash window
|
|
mainWindow.Flash();
|
|
Console.WriteLine("✓ Window flashed");
|
|
|
|
// Activate window
|
|
mainWindow.Activate();
|
|
Console.WriteLine("✓ Window activated");
|
|
|
|
// Modify title (temporary)
|
|
string originalTitle = mainWindow.Title;
|
|
mainWindow.Title = "WhiteMagic Demo!";
|
|
Console.WriteLine($"✓ Window title changed to: {mainWindow.Title}");
|
|
|
|
// Restore original title
|
|
System.Threading.Thread.Sleep(1000);
|
|
mainWindow.Title = originalTitle;
|
|
Console.WriteLine($"✓ Window title restored to: {mainWindow.Title}");
|
|
}
|
|
else
|
|
{
|
|
Console.WriteLine("✗ No main window found");
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Console.WriteLine($"✗ Window manipulation failed: {ex.Message}");
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Example 5.6: Module Resolution by Name
|
|
/// </summary>
|
|
public static void ModuleResolutionExample()
|
|
{
|
|
Console.WriteLine("\n=== Example 5.6: Module Resolution ===");
|
|
|
|
var process = TargetProcess();
|
|
if (process == null) return;
|
|
|
|
using var magic = Magic.Open(process);
|
|
|
|
// Common modules to check
|
|
string[] moduleNames = { "kernel32.dll", "user32.dll", "ntdll.dll", "notepad.exe" };
|
|
|
|
Console.WriteLine("Module resolution:");
|
|
Console.WriteLine("─────────────────────────────────────────────────────────────────");
|
|
|
|
foreach (var moduleName in moduleNames)
|
|
{
|
|
var module = magic[moduleName];
|
|
if (module != null && module.BaseAddress != IntPtr.Zero)
|
|
{
|
|
Console.WriteLine($"✓ {moduleName,-20} at 0x{module.BaseAddress:X}");
|
|
}
|
|
else
|
|
{
|
|
Console.WriteLine($"✗ {moduleName,-20} not found");
|
|
}
|
|
}
|
|
|
|
// Get main module using Process.MainModule
|
|
if (process.MainModule != null)
|
|
{
|
|
Console.WriteLine($"\n✓ Main module: {process.MainModule.ModuleName} at 0x{process.MainModule.BaseAddress:X}");
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Example 5.7: Pattern Scanning (Basic)
|
|
/// </summary>
|
|
public static void PatternScanningExample()
|
|
{
|
|
Console.WriteLine("\n=== Example 5.7: Pattern Scanning ===");
|
|
|
|
var process = TargetProcess();
|
|
if (process == null) return;
|
|
|
|
using var magic = Magic.Open(process);
|
|
|
|
// Get main module for scanning
|
|
if (process.MainModule == null)
|
|
{
|
|
Console.WriteLine("✗ Main module not available");
|
|
return;
|
|
}
|
|
|
|
Console.WriteLine($"Scanning for patterns in {process.MainModule.ModuleName}...");
|
|
|
|
// Example patterns (these are common x64 instruction patterns)
|
|
// Note: In real use, you'd use patterns specific to your target
|
|
byte[][] patterns =
|
|
{
|
|
new byte[] { 0x48, 0x8B, 0x05, 0x00, 0x00, 0x00, 0x00 }, // mov rax, [rip+disp]
|
|
new byte[] { 0xE8, 0x00, 0x00, 0x00, 0x00 }, // call rel32
|
|
new byte[] { 0xB8, 0x00, 0x00, 0x00, 0x00 } // mov eax, imm32
|
|
};
|
|
|
|
// Masks: 'x' = match exactly, '?' = wildcard
|
|
string[] masks =
|
|
{
|
|
"xxx????", // mov rax, [rip+disp] - last 4 bytes are displacement (wildcard)
|
|
"x????", // call rel32 - displacement is wildcard
|
|
"x????" // mov eax, imm32 - immediate is wildcard
|
|
};
|
|
|
|
for (int i = 0; i < patterns.Length; i++)
|
|
{
|
|
try
|
|
{
|
|
IntPtr result = PatternScanner.FindInModule(
|
|
magic.Memory,
|
|
patterns[i],
|
|
masks[i],
|
|
process.MainModule
|
|
);
|
|
|
|
Console.WriteLine($" Pattern {i + 1}: {(result != IntPtr.Zero ? $"✓ Found at 0x{result:X}" : "✗ Not found")}");
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Console.WriteLine($" Pattern {i + 1}: ✗ Scan failed - {ex.Message}");
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Example 5.8: Cached Pattern Scanning
|
|
/// </summary>
|
|
public static void CachedPatternScanningExample()
|
|
{
|
|
Console.WriteLine("\n=== Example 5.8: Cached Pattern Scanning ===");
|
|
|
|
var process = TargetProcess();
|
|
if (process == null) return;
|
|
|
|
using var magic = Magic.Open(process);
|
|
|
|
if (process.MainModule == null)
|
|
{
|
|
Console.WriteLine("✗ Main module not available");
|
|
return;
|
|
}
|
|
|
|
// Create a cache for pattern scanning
|
|
var cache = new PatternScannerCache(magic.Memory);
|
|
|
|
Console.WriteLine("Demonstrating cached pattern scanning...");
|
|
|
|
// Pattern to find
|
|
byte[] pattern = { 0x48, 0x8B, 0x05, 0x00, 0x00, 0x00, 0x00 };
|
|
string mask = "xxx????";
|
|
|
|
try
|
|
{
|
|
// First scan (reads memory)
|
|
Console.Write(" First scan: ");
|
|
IntPtr result1 = cache.FindInModuleCached(pattern, mask, process.MainModule);
|
|
Console.WriteLine(result1 != IntPtr.Zero ? $"✓ 0x{result1:X}" : "✗ Not found");
|
|
|
|
// Second scan (uses cache)
|
|
Console.Write(" Second scan (cached): ");
|
|
IntPtr result2 = cache.FindInModuleCached(pattern, mask, process.MainModule);
|
|
Console.WriteLine(result2 != IntPtr.Zero ? $"✓ 0x{result2:X}" : "✗ Not found");
|
|
|
|
Console.WriteLine(" ✓ Results match (cache working)");
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Console.WriteLine($" ✗ Cache scan failed: {ex.Message}");
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Example 5.9: High-Level API Chaining
|
|
/// </summary>
|
|
public static void HighLevelAPIChaining()
|
|
{
|
|
Console.WriteLine("\n=== Example 5.9: High-Level API Chaining ===");
|
|
|
|
var process = TargetProcess();
|
|
if (process == null) return;
|
|
|
|
using var magic = Magic.Open(process);
|
|
|
|
try
|
|
{
|
|
// Chain: Module → Function → Execute
|
|
var module = magic["kernel32.dll"];
|
|
if (module != null && module.BaseAddress != IntPtr.Zero)
|
|
{
|
|
var function = module["GetTickCount"];
|
|
if (function.Address != IntPtr.Zero)
|
|
{
|
|
uint result = function.Execute<uint>(CallConvention.Stdcall);
|
|
Console.WriteLine($"✓ Chained call: magic[\"kernel32.dll\"][\"GetTickCount\"].Execute<uint>() = {result}");
|
|
}
|
|
}
|
|
|
|
// Chain: Pointer → Read → Pointer → Read
|
|
var basePtr = magic[magic.Memory.ImageBase];
|
|
try
|
|
{
|
|
IntPtr ptr1 = basePtr.Read<IntPtr>(0x1000);
|
|
if (ptr1 != IntPtr.Zero)
|
|
{
|
|
var ptr2 = magic[ptr1];
|
|
int value = ptr2.Read<int>(0x50);
|
|
Console.WriteLine($"✓ Pointer chain: base → 0x{ptr1:X} → {value}");
|
|
}
|
|
}
|
|
catch
|
|
{
|
|
Console.WriteLine("✗ Pointer chain: Address not accessible (expected for demo)");
|
|
}
|
|
|
|
// Chain: Window → Title → Length
|
|
if (process.MainWindowHandle != IntPtr.Zero)
|
|
{
|
|
var window = new RemoteWindow(process.MainWindowHandle);
|
|
int titleLength = window.Title.Length;
|
|
Console.WriteLine($"✓ Window chain: RemoteWindow(MainWindowHandle).Title.Length = {titleLength}");
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Console.WriteLine($"✗ API chaining failed: {ex.Message}");
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Run all high-level API examples
|
|
/// </summary>
|
|
public static void RunAll()
|
|
{
|
|
Console.WriteLine("╔════════════════════════════════════════════════════════════╗");
|
|
Console.WriteLine("║ WhiteMagic Example 5: High-Level API ║");
|
|
Console.WriteLine("╚════════════════════════════════════════════════════════════╝");
|
|
|
|
RemotePointerExample();
|
|
RemoteModuleExample();
|
|
RemoteFunctionExample();
|
|
ProcessInfoExample();
|
|
RemoteWindowExample();
|
|
ModuleResolutionExample();
|
|
PatternScanningExample();
|
|
CachedPatternScanningExample();
|
|
HighLevelAPIChaining();
|
|
|
|
Console.WriteLine("\n✓ All high-level API examples completed!");
|
|
}
|
|
}
|