Files
kbe 6300bebe33 Fix API documentation and examples; add comprehensive documentation suite
- 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.
2026-07-22 22:26:07 +02:00

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!");
}
}