Skip to content

Commit

Permalink
Add support for dumping protected memory, some fixes
Browse files Browse the repository at this point in the history
  • Loading branch information
Alexx999 committed May 19, 2019
1 parent 4287ac6 commit dc6439d
Showing 1 changed file with 200 additions and 14 deletions.
214 changes: 200 additions & 14 deletions Dumper/Program.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
Expand All @@ -11,9 +12,10 @@ namespace Dumper
[Flags]
public enum ProcessAccessFlags : uint
{
All = 0x001F0FFF,
All = 0x001FFFFF,
Terminate = 0x00000001,
CreateThread = 0x00000002,
SetSessionId = 0x00000004,
VirtualMemoryOperation = 0x00000008,
VirtualMemoryRead = 0x00000010,
VirtualMemoryWrite = 0x00000020,
Expand All @@ -22,10 +24,78 @@ public enum ProcessAccessFlags : uint
SetQuota = 0x00000100,
SetInformation = 0x00000200,
QueryInformation = 0x00000400,
SuspendResume = 0x00000800,
QueryLimitedInformation = 0x00001000,
SetLimitedInformation = 0x00002000,
Synchronize = 0x00100000
}

[Flags]
public enum MemoryProtection : uint
{
Execute = 0x10,
ExecuteRead = 0x20,
ExecuteReadWrite = 0x40,
ExecuteWriteCopy = 0x80,
NoAccess = 0x01,
ReadOnly = 0x02,
ReadWrite = 0x04,
WriteCopy = 0x08,
Guard = 0x100,
NoCache = 0x200,
WriteCombine = 0x400
}

public enum MemoryState : uint
{
Commit = 0x1000,
Free = 0x10000,
Reserve = 0x2000
}

public enum MemoryType : uint
{
Image = 0x1000000,
Mapped = 0x40000,
Private = 0x20000
}

[StructLayout(LayoutKind.Sequential)]
public struct MemoryBasicInformation32
{
public int BaseAddress;
public int AllocationBase;
public MemoryProtection AllocationProtect;
public int RegionSize;
public MemoryState State;
public MemoryProtection Protect;
public MemoryType Type;
}

[StructLayout(LayoutKind.Sequential)]
public struct MemoryBasicInformation64
{
public long BaseAddress;
public long AllocationBase;
public MemoryProtection AllocationProtect;
public long RegionSize;
public MemoryState State;
public MemoryProtection Protect;
public MemoryType Type;
}

[StructLayout(LayoutKind.Sequential)]
public struct MemoryBasicInformation
{
public IntPtr BaseAddress;
public IntPtr AllocationBase;
public MemoryProtection AllocationProtect;
public UIntPtr RegionSize;
public MemoryState State;
public MemoryProtection Protect;
public MemoryType Type;
}

class Program
{
[DllImport("kernel32.dll", SetLastError = true)]
Expand All @@ -34,12 +104,24 @@ public static extern SafeProcessHandle OpenProcess(
bool bInheritHandle,
int processId
);


[DllImport("kernel32.dll")]
[DllImport("ntdll.dll", SetLastError = false)]
public static extern IntPtr NtSuspendProcess(IntPtr ProcessHandle);


[DllImport("ntdll.dll", SetLastError = false)]
public static extern IntPtr NtResumeProcess(IntPtr ProcessHandle);


[DllImport("kernel32.dll", SetLastError = true)]
public static extern bool ReadProcessMemory(IntPtr hProcess,
IntPtr lpBaseAddress, IntPtr lpBuffer, IntPtr dwSize, out IntPtr lpNumberOfBytesRead);

[DllImport("kernel32.dll", SetLastError = true)]
static extern int VirtualQueryEx(IntPtr hProcess, IntPtr lpAddress, out MemoryBasicInformation lpBuffer, int dwLength);

[DllImport("kernel32.dll", SetLastError = true)]
static extern bool VirtualProtectEx(IntPtr hProcess, IntPtr lpAddress, UIntPtr dwSize, MemoryProtection flNewProtect, out MemoryProtection lpflOldProtect);

public static bool TryParse(string str, out long value)
{
Expand All @@ -58,9 +140,9 @@ public static bool TryParseHex(string str, out long value)

static void Main(string[] args)
{
if (args.Length != 3)
if (args.Length != 3 && args.Length != 4)
{
Console.WriteLine("Wrong argument count.\nUsage:\ndumper.exe <debugged process id or name> <memory_start_addr> <memory_length>");
Console.WriteLine("Wrong argument count.\nUsage:\ndumper.exe <debugged process id or name> <memory_start_addr> <memory_length> (-unprotect)");
return;
}

Expand All @@ -76,6 +158,8 @@ static void Main(string[] args)
return;
}

var unprotect = args.Length > 3 && args[3] == "-unprotect";

if (!int.TryParse(args[0], out var processId))
{
var processName = args[0];
Expand All @@ -93,7 +177,13 @@ static void Main(string[] args)
processId = process.Single().Id;
}

using (var process = OpenProcess(ProcessAccessFlags.VirtualMemoryRead, false, processId))
var rights = ProcessAccessFlags.VirtualMemoryRead;
if (unprotect)
{
rights |= ProcessAccessFlags.SuspendResume | ProcessAccessFlags.QueryInformation | ProcessAccessFlags.VirtualMemoryOperation;
}

using (var process = OpenProcess(rights, false, processId))
{
if (process.IsInvalid)
{
Expand All @@ -107,30 +197,119 @@ static void Main(string[] args)

Console.WriteLine($"Saving contents of process {processId} to {outFileName}");

List<MemBlock> unprotected = null;

if (unprotect)
{
NtSuspendProcess(process.DangerousGetHandle());

try
{
Console.WriteLine("Unprotecting");
unprotected = Unprotect(process, new IntPtr(address), new IntPtr(length));
}
catch (Exception e)
{
Console.WriteLine($"Unprotecting failed with exception {e}");
return;
}
}

try
{
Dump(process, outFileName, new IntPtr(address), new IntPtr(length));
Dump(process, outFileName, new IntPtr(address), new IntPtr(length), unprotect);
Console.WriteLine("Done");
}
catch (Exception e)
{
Console.WriteLine($"Writing file failed with exception {e}");
}


if (unprotect)
{
Protect(process, unprotected);
NtResumeProcess(process.DangerousGetHandle());
}
}
}

private static void Protect(SafeProcessHandle process, List<MemBlock> unprotected)
{
foreach (var block in unprotected)
{
VirtualProtectEx(process.DangerousGetHandle(), block.Address, block.Size, block.OriginalProtection, out _);
}
}

private static List<MemBlock> Unprotect(SafeProcessHandle process, IntPtr address, IntPtr length)
{
var page = Environment.SystemPageSize;

var result = new List<MemBlock>();

var baseAddr = (address.ToInt64() / page) * page;

var totalLen = 0UL;

var currAddr = baseAddr;

while (true)
{
if (VirtualQueryEx(process.DangerousGetHandle(), new IntPtr(currAddr), out var info,
Marshal.SizeOf(typeof(MemoryBasicInformation))) == 0)
{
throw new Exception($"Unable to query memory at 0x{currAddr:X16}, error 0x{Marshal.GetLastWin32Error():X8}");
}

totalLen += info.RegionSize.ToUInt64();

if (!IsReadable(info.Protect))
{
if (!VirtualProtectEx(process.DangerousGetHandle(), info.BaseAddress, info.RegionSize, MemoryProtection.ReadOnly, out var origProt))
{
throw new Exception($"Unable to unprotect memory at 0x{info.BaseAddress.ToInt64():X16}, error 0x{Marshal.GetLastWin32Error():X8}");
}
result.Add(new MemBlock { Address = info.BaseAddress, Size = info.RegionSize, OriginalProtection = origProt });
}


if (totalLen >= (ulong) length.ToInt64())
{
break;
}
}

return result;
}

private static bool IsReadable(MemoryProtection protect)
{
return protect.HasFlag(MemoryProtection.ReadOnly) || protect.HasFlag(MemoryProtection.ReadWrite) ||
protect.HasFlag(MemoryProtection.ExecuteRead) || protect.HasFlag(MemoryProtection.ExecuteReadWrite);
}

private static void Dump(SafeProcessHandle process, string outFileName, IntPtr address, IntPtr length)
private static void Dump(SafeProcessHandle process, string outFileName, IntPtr address, IntPtr length, bool unprotect)
{
using (var file = File.Create(outFileName))
using (var mmf = MemoryMappedFile.CreateFromFile(file, null, length.ToInt64(), MemoryMappedFileAccess.ReadWrite, null, HandleInheritability.None, false))
using (var accessor = mmf.CreateViewAccessor(0, 0, MemoryMappedFileAccess.Write))
{
var buffer = (SafeBuffer)accessor.SafeMemoryMappedViewHandle;
var ptr = buffer.DangerousGetHandle();
if (!ReadProcessMemory(process.DangerousGetHandle(), address, ptr, length, out var read))
IntPtr read;
using (var mmf = MemoryMappedFile.CreateFromFile(file, null, length.ToInt64(), MemoryMappedFileAccess.ReadWrite, null, HandleInheritability.None, true))
using (var accessor = mmf.CreateViewAccessor(0, 0, MemoryMappedFileAccess.Write))
{
Console.WriteLine($"Reading process memory failed with error {Marshal.GetLastWin32Error()}");
var buffer = (SafeBuffer) accessor.SafeMemoryMappedViewHandle;
var ptr = buffer.DangerousGetHandle();
if (!ReadProcessMemory(process.DangerousGetHandle(), address, ptr, length, out read))
{
var error = Marshal.GetLastWin32Error();
Console.WriteLine($"Reading process memory failed with error 0x{error:8X}");
if (error == 299 && !unprotect)
{
Console.WriteLine("You can try -unprotect option");
}
}
}

if (read != length)
{
Console.WriteLine($"Data was read partially - {read.ToInt64()} bytes out of {length.ToInt64()} bytes requested");
Expand All @@ -151,4 +330,11 @@ private static string GetNextFreeName(string outFileName, string ext)
return currName + ext;
}
}

internal struct MemBlock
{
public IntPtr Address { get; set; }
public MemoryProtection OriginalProtection { get; set; }
public UIntPtr Size { get; set; }
}
}

0 comments on commit dc6439d

Please sign in to comment.