WingCloudHexExplorer/Be.Windows.Forms.HexBox/ProcessByteProvider.cs

418 lines
14 KiB
C#

using System;
using System.Diagnostics;
using System.IO;
using System.Runtime.InteropServices;
namespace Be.Windows.Forms
{
/// <summary>
///
/// </summary>
public sealed class ProcessByteProvider : IByteProvider, IDisposable
{
private const int PROCESS_VM_READ = 0x0010;
private const int PROCESS_VM_WRITE = 0x0020;
private const int IMAGE_SIZEOF_FILE_HEADER = 20;
private const ushort SizeOfImageOffset = 56;
/// <summary>
///
/// </summary>
public long Length => _stream.Length;
#pragma warning disable CS0067
/// <summary>
///
/// </summary>
[Obsolete("由于操作进程,暂不支持改变镜像大小", true)]
public event EventHandler LengthChanged;
#pragma warning restore CS0067
/// <summary>
///
/// </summary>
public event EventHandler Changed;
/// <summary>
///
/// </summary>
public IntPtr BaseAddr { get; set; }
/// <summary>
///
/// </summary>
public Process Process { get; set; }
private MemoryStream _stream;
private DataMap _dataMap;
private long _totalLength;
private readonly bool _readOnly;
private const int COPY_BLOCK_SIZE = 4096;
private readonly IntPtr handle;
/// <summary>
/// Gets a value, if the file is opened in read-only mode.
/// </summary>
public bool ReadOnly => _readOnly;
/// <summary>
/// 目前文件流
/// </summary>
public Stream Stream { get { return _stream; } }
/// <summary>
///
/// </summary>
/// <param name="processid"></param>
/// <param name="writeable"></param>
public ProcessByteProvider(int processid,bool writeable=false)
{
_readOnly = !writeable;
if ((handle = OpenProcess(writeable ? PROCESS_VM_READ | PROCESS_VM_WRITE : PROCESS_VM_READ, false, processid)) != IntPtr.Zero)
{
Process process = Process.GetProcessById(processid);
Process = process;
BaseAddr = process.MainModule.BaseAddress;
IntPtr ptr = Marshal.AllocHGlobal(sizeof(int));
ReadProcessMemory(process.Handle, BaseAddr + 0x3c, ptr, sizeof(int), IntPtr.Zero);
int offset = Marshal.ReadInt32(ptr);
ReadProcessMemory(process.Handle, BaseAddr + offset+
IMAGE_SIZEOF_FILE_HEADER+4+SizeOfImageOffset, ptr, sizeof(int), IntPtr.Zero);
int sizeofimage = Marshal.ReadInt32(ptr);
Marshal.FreeHGlobal(ptr);
ptr = Marshal.AllocHGlobal(sizeofimage);
ReadProcessMemory(process.Handle, BaseAddr, ptr, sizeofimage, IntPtr.Zero);
byte[] buffer = new byte[sizeofimage];
for (int i = 0; i < sizeofimage; i++)
{
buffer[i] = Marshal.ReadByte(ptr + i);
}
_stream = new MemoryStream(buffer);
Marshal.FreeHGlobal(ptr);
ReInitialize();
}
}
/// <summary>
///
/// </summary>
/// <param name="process"></param>
/// <param name="writeable"></param>
public ProcessByteProvider(Process process, bool writeable = false)
{
_readOnly = !writeable;
if (OpenProcess(writeable ? PROCESS_VM_READ | PROCESS_VM_WRITE : PROCESS_VM_READ, false, process.Id) != IntPtr.Zero)
{
Process = process;
BaseAddr = process.MainModule.BaseAddress;
int sizeofimage = process.MainModule.ModuleMemorySize;
IntPtr ptr = Marshal.AllocHGlobal(sizeofimage);
ReadProcessMemory(process.Handle, BaseAddr, ptr, sizeofimage, IntPtr.Zero);
byte[] buffer = new byte[sizeofimage];
for (int i = 0; i < sizeofimage; i++)
{
buffer[i] = Marshal.ReadByte(ptr + i);
}
_stream = new MemoryStream(buffer);
Marshal.FreeHGlobal(ptr);
ReInitialize();
}
else
{
throw new UnauthorizedAccessException("无法打开进程,拒绝访问");
}
}
/// <summary>
///
/// </summary>
~ProcessByteProvider()
{
Dispose();
}
#region WinAPI
[DllImport("kernel32.dll", EntryPoint = "ReadProcessMemory",
CallingConvention = CallingConvention.Winapi, SetLastError =true, CharSet = CharSet.Unicode)]
private static extern bool ReadProcessMemory(IntPtr hProcess, IntPtr lpBaseAddress, IntPtr lpBuffer, int nSize, IntPtr lpNumberOfBytesRead);
[DllImport("kernel32.dll", EntryPoint = "WriteProcessMemory",
CallingConvention = CallingConvention.Winapi, SetLastError = true, CharSet = CharSet.Unicode)]
private static extern bool WriteProcessMemory(IntPtr hProcess, IntPtr lpBaseAddress, byte[] lpBuffer, int nSize, IntPtr lpNumberOfBytesWritten);
[DllImport("kernel32.dll", EntryPoint = "OpenProcess",
CallingConvention = CallingConvention.Winapi, SetLastError = true, CharSet = CharSet.Unicode)]
private static extern IntPtr OpenProcess(int dwDesiredAccess, bool bInheritHandle, int dwProcessId);
[DllImport("kernel32.dll",CallingConvention = CallingConvention.Winapi, SetLastError = true)]
private static extern void CloseHandle(IntPtr hObject);
#endregion
private void ReInitialize()
{
_dataMap = new DataMap();
_dataMap.AddFirst(new FileDataBlock(0, _stream.Length));
_totalLength = _stream.Length;
}
/// <summary>
///
/// </summary>
public void Dispose()
{
if (_stream != null)
{
_stream.Close();
_stream = null;
}
_dataMap = null;
CloseHandle(handle);
}
private byte ReadByteFromImage(long fileOffset)
{
// Move to the correct position and read the byte.
if (_stream.Position != fileOffset)
{
_stream.Position = fileOffset;
}
return (byte)_stream.ReadByte();
}
/// <summary>
///
/// </summary>
/// <param name="index"></param>
/// <returns></returns>
public byte ReadByte(long index)
{
DataBlock block = GetDataBlock(index, out long blockOffset);
if (block is FileDataBlock fileBlock)
{
return ReadByteFromImage(fileBlock.FileOffset + index - blockOffset);
}
else
{
MemoryDataBlock memoryBlock = block as MemoryDataBlock;
return memoryBlock.Data[index - blockOffset];
}
}
private DataBlock GetDataBlock(long findOffset, out long blockOffset)
{
if (findOffset < 0 || findOffset > _totalLength)
{
throw new ArgumentOutOfRangeException("findOffset");
}
// Iterate over the blocks until the block containing the required offset is encountered.
blockOffset = 0;
for (DataBlock block = _dataMap.FirstBlock; block != null; block = block.NextBlock)
{
if ((blockOffset <= findOffset && blockOffset + block.Length > findOffset) || block.NextBlock == null)
{
return block;
}
blockOffset += block.Length;
}
return null;
}
/// <summary>
///
/// </summary>
/// <param name="index"></param>
/// <param name="value"></param>
public void WriteByte(long index, byte value)
{
try
{
// Find the block affected.
DataBlock block = GetDataBlock(index, out long blockOffset);
// If the byte is already in a memory block, modify it.
if (block is MemoryDataBlock memoryBlock)
{
memoryBlock.Data[index - blockOffset] = value;
return;
}
FileDataBlock fileBlock = (FileDataBlock)block;
// If the byte changing is the first byte in the block and the previous block is a memory block, extend that.
if (blockOffset == index && block.PreviousBlock != null)
{
if (block.PreviousBlock is MemoryDataBlock previousMemoryBlock)
{
previousMemoryBlock.AddByteToEnd(value);
fileBlock.RemoveBytesFromStart(1);
if (fileBlock.Length == 0)
{
_dataMap.Remove(fileBlock);
}
return;
}
}
// If the byte changing is the last byte in the block and the next block is a memory block, extend that.
if (blockOffset + fileBlock.Length - 1 == index && block.NextBlock != null)
{
if (block.NextBlock is MemoryDataBlock nextMemoryBlock)
{
nextMemoryBlock.AddByteToStart(value);
fileBlock.RemoveBytesFromEnd(1);
if (fileBlock.Length == 0)
{
_dataMap.Remove(fileBlock);
}
return;
}
}
// Split the block into a prefix and a suffix and place a memory block in-between.
FileDataBlock prefixBlock = null;
if (index > blockOffset)
{
prefixBlock = new FileDataBlock(fileBlock.FileOffset, index - blockOffset);
}
FileDataBlock suffixBlock = null;
if (index < blockOffset + fileBlock.Length - 1)
{
suffixBlock = new FileDataBlock(
fileBlock.FileOffset + index - blockOffset + 1,
fileBlock.Length - (index - blockOffset + 1));
}
block = _dataMap.Replace(block, new MemoryDataBlock(value));
if (prefixBlock != null)
{
_dataMap.AddBefore(block, prefixBlock);
}
if (suffixBlock != null)
{
_dataMap.AddAfter(block, suffixBlock);
}
}
finally
{
Changed?.Invoke(this, EventArgs.Empty);
}
}
/// <summary>
///
/// </summary>
/// <param name="index"></param>
/// <param name="bs"></param>
[Obsolete("由于操作进程,暂不支持改变镜像大小", true)]
public void InsertBytes(long index, byte[] bs)
{
return;
}
/// <summary>
///
/// </summary>
/// <param name="index"></param>
/// <param name="length"></param>
public void DeleteBytes(long index, long length)
{
for (int i = 0; i < length; i++)
{
WriteByte(index + i, 0);
}
}
/// <summary>
///
/// </summary>
/// <returns></returns>
public bool HasChanges()
{
long offset = 0;
for (DataBlock block = _dataMap.FirstBlock; block != null; block = block.NextBlock)
{
if (!(block is FileDataBlock fileBlock))
{
return true;
}
if (fileBlock.FileOffset != offset)
{
return true;
}
offset += fileBlock.Length;
}
return (offset != _stream.Length);
}
/// <summary>
///
/// </summary>
/// <param name="stream"></param>
public void ApplyChanges(Stream stream = null)
{
var mstream = stream ?? _stream;
if (stream == null && _readOnly)
throw new OperationCanceledException("Image is in read-only mode");
int dataOffset = 0;
IntPtr handle = Process.Handle;
for (DataBlock block = _dataMap.FirstBlock; block != null; block = block.NextBlock)
{
if (block is MemoryDataBlock memoryBlock)
{
mstream.Position = dataOffset;
for (int memoryOffset = 0; memoryOffset < memoryBlock.Length; memoryOffset += COPY_BLOCK_SIZE)
{
mstream.Write(memoryBlock.Data, memoryOffset, (int)Math.Min(COPY_BLOCK_SIZE, memoryBlock.Length - memoryOffset));
}
WriteProcessMemory(handle, BaseAddr + dataOffset, memoryBlock.Data, (int)memoryBlock.Length, IntPtr.Zero);
}
dataOffset += (int)block.Length;
}
if (stream == null)
ReInitialize();
}
/// <summary>
///
/// </summary>
/// <returns></returns>
public bool SupportsWriteByte()
{
return !_readOnly;
}
/// <summary>
///
/// </summary>
/// <returns></returns>
public bool SupportsInsertBytes()
{
return false;
}
/// <summary>
///
/// </summary>
/// <returns></returns>
public bool SupportsDeleteBytes()
{
return !_readOnly;
}
}
}