Я написал следующий код, чтобы попытаться прочитать файл notepad.exe. Мне удается прочитать все по памяти, но я не могу найти текст, который набрал в notepad.exe. Где я неправ?
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Text;
namespace MemoryAcessor;
public partial class MemoryRead
{
const int PROCESS_WM_READ = 0x0010;
[LibraryImport("kernel32.dll")]
public static partial IntPtr OpenProcess(uint dwDesiredAccess, [MarshalAs(UnmanagedType.Bool)] bool bInheritHandle, uint dwProcessId);
[LibraryImport("kernel32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
public static partial bool ReadProcessMemory(IntPtr hProcess, Int64 lpBaseAddress, [Out] byte[] lpBuffer, int dwSize, out IntPtr lpNumberOfBytesRead);
public static void Main()
{
Process process = Process.GetProcessesByName("Notepad")[0];
IntPtr processHandle = OpenProcess(PROCESS_WM_READ, false, (uint)process.Id);
byte[] buffer = new byte[1<<25];
var result = ReadProcessMemory(processHandle, process.MainModule!.BaseAddress, buffer, buffer.Length, out var bytesRead);
Console.WriteLine($"Result: {result} ({bytesRead} bytes)");
Console.WriteLine("====================== = ");
Console.WriteLine(buffer[0..(int)bytesRead]);
Console.WriteLine("====================== = ");
Console.WriteLine(Encoding.UTF8.GetString(buffer[0..(int)bytesRead]));
Console.ReadLine();
}
}
В openend notepad.exe (Win11Pro) введите «Hello World!», а затем запустите приведенный выше код.
Я не могу понять, это потому, что у меня неправильное преобразование (UTF8) или потому, что я читаю не из того модуля?
Чтобы получить текущий текст окна Блокнота, вам лучше перейти по иерархии окон к элементу управления Edit внутри, а затем вызвать для него GetWindowText().
@ h0r53 h0r53, это просто игрушечный пример доступа к памяти другого процесса. Что заставляет меня думать, что код должен работать как есть? Ну, я предполагаю, что ввод в notepad.exe заставляет его сохранить его где-то в своей памяти, и, поскольку я читаю все, я ожидаю найти его там.
Вы можете читать память процесса, но не можете просто начать с базового адреса модуля и ожидать получения энергозависимых данных времени выполнения. Базовый адрес — это буквально начало двоичных данных файла, которые в данном случае будут PE-заголовком. Вам нужно будет перепроектировать двоичный файл, чтобы определить, где на самом деле могут находиться данные. Скорее всего, он хранится где-то в стеке или куче, что будет очень далеко от базового адреса модуля.
@ h0r53 h0r53 Я еще не уверен, что полностью понимаю. Я понимаю, что не найду данные по статусу base_address, но я читаю около 2 МБ данных (=все) с этого адреса и не могу найти их где-либо там. Стек/куча там не находится? Также посмотрите мой последний вопрос: означает ли это, что мне нужно читать из другого модуля (в конце концов, в notepad.exe более 100 модулей). Или мне нужно будет найти в этой памяти адрес памяти, указывающий на место в куче, и разрешить этот адрес? Думаю, это бесполезное занятие, но, по крайней мере, теоретически...
Стек/куча определенно не находится в том же сегменте, что и базовый адрес модуля. Скорее всего, это даже не близко. Базовый адрес модуля обычно включает сегмент .text
, .rodata
и другой статический контент. Однако Windows API VirtualQueryEx
должен иметь возможность идентифицировать адреса стека и кучи процесса. Кроме того, если у вас есть опыт обратного проектирования, вы можете точно определить, где в Блокноте хранятся текстовые буферы. Возможно, вам будет полезно попробовать программу «Cheat Engine» для мониторинга программ Windows и сканирования памяти.
Текст в окне Блокнота будет скрыт в окне, управляемом user32.dll. Вы можете рассмотреть возможность создания совершенно простого приложения WinForms с многострочным текстовым полем, запуска его, а затем изучения вашего приложения с помощью отладчика (Блокнот — это, по сути, окно, текстовое поле и немного кода для добавления функциональности меню).
@ Flydog57 спасибо за совет! Да, это был план Б.
Это должно приблизить вас на шаг к тому, чего вы пытаетесь достичь. Как упоминалось в комментариях, текстовые буферы, используемые Nodepad.exe, не будут находиться по базовому адресу загрузки модуля. Вместо этого весьма вероятно, что эти буферы расположены либо в стеке, либо в куче процесса. Вот модифицированная версия вашего кода, которая пытается определить расположение стека и кучи с помощью Windows API VirtualQueryEx
.
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Text;
namespace MemoryAccessor
{
public partial class MemoryRead
{
const int PROCESS_QUERY_INFORMATION = 0x0400;
const int PROCESS_VM_READ = 0x0010;
[DllImport("kernel32.dll")]
public static extern IntPtr OpenProcess(int dwDesiredAccess, bool bInheritHandle, int dwProcessId);
[DllImport("kernel32.dll", SetLastError = true)]
public static extern bool VirtualQueryEx(IntPtr hProcess, IntPtr lpAddress, out MEMORY_BASIC_INFORMATION lpBuffer, uint dwLength);
[DllImport("kernel32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool ReadProcessMemory(IntPtr hProcess, IntPtr lpBaseAddress, [Out] byte[] lpBuffer, int dwSize, out IntPtr lpNumberOfBytesRead);
[DllImport("kernel32.dll")]
public static extern bool CloseHandle(IntPtr hObject);
[StructLayout(LayoutKind.Sequential)]
public struct MEMORY_BASIC_INFORMATION
{
public IntPtr BaseAddress;
public IntPtr AllocationBase;
public uint AllocationProtect;
public IntPtr RegionSize;
public uint State;
public uint Protect;
public uint Type;
}
public static void Main()
{
Process[] processes = Process.GetProcessesByName("Notepad");
if (processes.Length > 0)
{
Process process = processes[0];
IntPtr processHandle = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, false, process.Id);
if (processHandle != IntPtr.Zero)
{
IntPtr lpAddress = IntPtr.Zero;
MEMORY_BASIC_INFORMATION mbi;
while (VirtualQueryEx(processHandle, lpAddress, out mbi, (uint)Marshal.SizeOf(typeof(MEMORY_BASIC_INFORMATION))) != false)
{
// Check if the region is part of the stack or heap
if (mbi.Protect == 0x04 && mbi.Type == 0x10000) // PAGE_READWRITE and MEM_COMMIT
{
Console.WriteLine($"Base Address: {mbi.BaseAddress}, Region Size: {mbi.RegionSize}");
Console.WriteLine("Placeholder: Read from stack here");
// Here you can read from the stack by using ReadProcessMemory
// For example:
// byte[] buffer = new byte[1024];
// IntPtr bytesRead;
// ReadProcessMemory(processHandle, mbi.BaseAddress, buffer, buffer.Length, out bytesRead);
}
else if (mbi.Protect == 0x04 && mbi.Type == 0x20000) // PAGE_READWRITE and MEM_PRIVATE
{
Console.WriteLine($"Base Address: {mbi.BaseAddress}, Region Size: {mbi.RegionSize}");
Console.WriteLine("Placeholder: Read from heap here");
// Here you can read from the heap by using ReadProcessMemory
// For example:
// byte[] buffer = new byte[1024];
// IntPtr bytesRead;
// ReadProcessMemory(processHandle, mbi.BaseAddress, buffer, buffer.Length, out bytesRead);
}
lpAddress = IntPtr.Add(mbi.BaseAddress, mbi.RegionSize.ToInt32());
}
CloseHandle(processHandle);
}
else
{
Console.WriteLine("Failed to open process.");
}
}
else
{
Console.WriteLine("Process not found.");
}
Console.ReadLine();
}
}
}
Я оставил код-заполнитель для места, где вы хотите попробовать прочитать из стека и кучи.
Альтернативно вы можете попробовать такую программу, как «Cheat Engine», которая отслеживает адресное пространство программы. Это поможет вам быстро найти текст, введенный в Блокнот, и определить, где в памяти находятся эти данные. Вам все равно придется выполнить некоторые расчеты, чтобы определить, как можно надежно сделать то же самое программно.
Спасибо за код, который намекает на интересный материал, который мне нужно прочитать. Мне пришлось изменить продвижение указателя на lpAddress = mbi.BaseAddress + mbi.RegionSize;
, иначе на 64-битной версии он переполнился бы. Однако я до сих пор не могу найти «Привет» ни в одном из них (я распечатал байты в файл и запустил для него команду dngrep). Может ли произойти что-то еще, или теперь мне придется выяснить, что, черт возьми, notepad.exe делает внутри? Например, возможно, он не сохраняет его как случайный массив в памяти, поэтому я не могу нажать CTRL+f для него?
@infinitezero попробуйте использовать Cheat Engine для поиска в адресном пространстве программы текста в Блокноте. Если он сможет его найти, он покажет вам, где в памяти находятся текстовые буферы. Если вы по-прежнему не нашли текст, возможно, Блокнот хранит эти данные во временном файле. Возможно, проверьте AppData
в Windows на наличие файлов процессов Блокнота.
Примечание. Я знаю, что вы сказали, что делаете это в Windows 11, и я сделал это в Windows 10, но вы должны быть в состоянии выполнить шаги, чтобы заставить это работать в Windows 11, просто это может быть немного сложнее, поскольку блокнот может иметь несколько вкладок в Windows 11
Вы можете прочитать текст в блокноте, найдя смещение памяти, в котором блокнот хранит текст (все равно будет работать после перезапуска приложения). Я сделал это с помощью Cheat Engine, вот мои шаги:
Hex
и вставьте скопированный адрес в поле адреса, затем отсканируйте и добавьте результат в список внизу, дважды щелкнув его.Как показано на снимке экрана, адрес, по которому находится указатель, который использует блокнот в Windows 10, — это notepad.exe+31680
, что можно представить на C# следующим образом:
Process process = Process.GetProcessesByName("notepad")[0];
IntPtr.Add(process.MainModule!.BaseAddress, 0x31680); // notepad.exe+31680
Иногда вам может потребоваться использовать приведенный ниже код, если смещение не из основного модуля:
static IntPtr GetModuleBaseAddress(Process process, string moduleName)
{
ProcessModuleCollection modules = process.Modules;
foreach (ProcessModule module in modules)
{
if (module.ModuleName == moduleName)
{
return module.BaseAddress;
}
}
throw new Exception($"Module {moduleName} not found in process {process.ProcessName}");
}
Process process = Process.GetProcessesByName("notepad")[0];
// You can see all modules by looping through `process.Modules`
// Replace notepad.exe with the name of the module. Might be called something like `textinputframework.dll`
IntPtr addressOfTextPointer1 = IntPtr.Add(GetModuleBaseAddress(process, "notepad.exe"), 0x31680);
Проблема в том, что если вы прочитаете то, что находится по адресу, вы получите указатель на другой адрес вместо текста, поэтому вам нужно прочитать указатель, а затем указатель из этого указателя и т. д., пока вы не получите текст.
Вы можете видеть, что notepad.exe+31680 (7FF78F051680)
указывает на 1E5CC990008
, который указывает на 1E5C79E6880
, который указывает на строку Unicode UTF-16 hello world
.
Вот мой полный код, который я использовал для отслеживания указателей и получения текста, который все равно будет работать после перезапуска программы, поскольку исходный указатель всегда будет иметь одно и то же смещение от базового адреса модуля/процесса:
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Text;
public partial class Program
{
// Number of characters to read from the target process
static int charactersToRead = 11;
[LibraryImport("kernel32.dll")]
public static partial IntPtr OpenProcess(uint dwDesiredAccess, [MarshalAs(UnmanagedType.Bool)] bool bInheritHandle, uint dwProcessId);
[LibraryImport("kernel32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
public static partial bool ReadProcessMemory(IntPtr hProcess, long lpBaseAddress, [Out] byte[] lpBuffer, int dwSize, out IntPtr lpNumberOfBytesRead);
public static void Main()
{
try
{
Process process = Process.GetProcessesByName("notepad")[0];
IntPtr processHandle = OpenProcess(0x0010, false, (uint)process.Id);
IntPtr addressOfTextPointer1 = IntPtr.Add(process.MainModule!.BaseAddress, 0x31680);
//IntPtr addressOfTextPointer1 = IntPtr.Add(GetModuleBaseAddress(process, "notepad.exe"), 0x31680);
Console.WriteLine("Pointer 1: " + addressOfTextPointer1.ToString("X"));
long addressOfTextPointer2 = BitConverter.ToInt64(ReadMem(processHandle, addressOfTextPointer1, 8));
Console.WriteLine("Pointer 2: " + addressOfTextPointer2.ToString("X"));
long addressOfText = BitConverter.ToInt64(ReadMem(processHandle, addressOfTextPointer2, 8));
Console.WriteLine("Address of string: " + addressOfText.ToString("X"));
Console.WriteLine("String: " + Encoding.Unicode.GetString(ReadMem(processHandle, addressOfText, charactersToRead * 2)));
Console.ReadLine();
}
catch (Exception e)
{
Console.WriteLine(e.Message);
Console.WriteLine(e.StackTrace);
Console.ReadLine();
}
}
static byte[] ReadMem(nint processHandle, long address, int bufferLength)
{
byte[] buffer = new byte[bufferLength];
ReadProcessMemory(processHandle, address, buffer, buffer.Length, out var bytesRead);
return buffer;
}
// Helper function to get the base address of a module in the target process
static IntPtr GetModuleBaseAddress(Process process, string moduleName)
{
ProcessModuleCollection modules = process.Modules;
foreach (ProcessModule module in modules)
{
if (module.ModuleName == moduleName)
{
return module.BaseAddress;
}
}
throw new Exception($"Module {moduleName} not found in process {process.ProcessName}");
}
}
Выход:
Pointer 1: 7FF78F051680
Pointer 2: 1E5CC990008
Address of string: 1E5C79E6880
String: hello world
Полезные исследования/источники/инструменты, которые я использовал, чтобы во всем этом разобраться. Это может помочь, если вышеуказанные шаги по какой-либо причине не работают:
Вы пытаетесь прочитать несохраненный текст в сеансе Notepad.exe? Это очень хакерски. Вам нужно будет определить, как Блокнот работает внутри, где хранятся временные данные (возможно, в стеке или в кеше) и читать оттуда. Лучше сохранить текст в виде файла и просто прочитать его. Что заставляет вас думать, что ваш код должен работать как есть? Вы читаете фактический двоичный файл исполняемого файла Блокнота.
process.MainModule!.BaseAddress
будет PE-заголовком файла, а не изменчивыми данными в сеансе Блокнота.