ReadProcessMemory: Почему я не вижу содержимого notepad.exe?

Я написал следующий код, чтобы попытаться прочитать файл 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) или потому, что я читаю не из того модуля?

Вы пытаетесь прочитать несохраненный текст в сеансе Notepad.exe? Это очень хакерски. Вам нужно будет определить, как Блокнот работает внутри, где хранятся временные данные (возможно, в стеке или в кеше) и читать оттуда. Лучше сохранить текст в виде файла и просто прочитать его. Что заставляет вас думать, что ваш код должен работать как есть? Вы читаете фактический двоичный файл исполняемого файла Блокнота. process.MainModule!.BaseAddress будет PE-заголовком файла, а не изменчивыми данными в сеансе Блокнота.

h0r53 15.04.2024 21:39

Чтобы получить текущий текст окна Блокнота, вам лучше перейти по иерархии окон к элементу управления Edit внутри, а затем вызвать для него GetWindowText().

Seva Alekseyev 15.04.2024 21:42

@ h0r53 h0r53, это просто игрушечный пример доступа к памяти другого процесса. Что заставляет меня думать, что код должен работать как есть? Ну, я предполагаю, что ввод в notepad.exe заставляет его сохранить его где-то в своей памяти, и, поскольку я читаю все, я ожидаю найти его там.

infinitezero 15.04.2024 21:47

Вы можете читать память процесса, но не можете просто начать с базового адреса модуля и ожидать получения энергозависимых данных времени выполнения. Базовый адрес — это буквально начало двоичных данных файла, которые в данном случае будут PE-заголовком. Вам нужно будет перепроектировать двоичный файл, чтобы определить, где на самом деле могут находиться данные. Скорее всего, он хранится где-то в стеке или куче, что будет очень далеко от базового адреса модуля.

h0r53 15.04.2024 21:53

@ h0r53 h0r53 Я еще не уверен, что полностью понимаю. Я понимаю, что не найду данные по статусу base_address, но я читаю около 2 МБ данных (=все) с этого адреса и не могу найти их где-либо там. Стек/куча там не находится? Также посмотрите мой последний вопрос: означает ли это, что мне нужно читать из другого модуля (в конце концов, в notepad.exe более 100 модулей). Или мне нужно будет найти в этой памяти адрес памяти, указывающий на место в куче, и разрешить этот адрес? Думаю, это бесполезное занятие, но, по крайней мере, теоретически...

infinitezero 15.04.2024 22:02

Стек/куча определенно не находится в том же сегменте, что и базовый адрес модуля. Скорее всего, это даже не близко. Базовый адрес модуля обычно включает сегмент .text, .rodata и другой статический контент. Однако Windows API VirtualQueryEx должен иметь возможность идентифицировать адреса стека и кучи процесса. Кроме того, если у вас есть опыт обратного проектирования, вы можете точно определить, где в Блокноте хранятся текстовые буферы. Возможно, вам будет полезно попробовать программу «Cheat Engine» для мониторинга программ Windows и сканирования памяти.

h0r53 15.04.2024 22:27

Текст в окне Блокнота будет скрыт в окне, управляемом user32.dll. Вы можете рассмотреть возможность создания совершенно простого приложения WinForms с многострочным текстовым полем, запуска его, а затем изучения вашего приложения с помощью отладчика (Блокнот — это, по сути, окно, текстовое поле и немного кода для добавления функциональности меню).

Flydog57 16.04.2024 01:36

@ Flydog57 спасибо за совет! Да, это был план Б.

infinitezero 16.04.2024 07:38
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
8
54
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ответ принят как подходящий

Это должно приблизить вас на шаг к тому, чего вы пытаетесь достичь. Как упоминалось в комментариях, текстовые буферы, используемые 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 15.04.2024 23:37

@infinitezero попробуйте использовать Cheat Engine для поиска в адресном пространстве программы текста в Блокноте. Если он сможет его найти, он покажет вам, где в памяти находятся текстовые буферы. Если вы по-прежнему не нашли текст, возможно, Блокнот хранит эти данные во временном файле. Возможно, проверьте AppData в Windows на наличие файлов процессов Блокнота.

h0r53 15.04.2024 23:45

Примечание. Я знаю, что вы сказали, что делаете это в Windows 11, и я сделал это в Windows 10, но вы должны быть в состоянии выполнить шаги, чтобы заставить это работать в Windows 11, просто это может быть немного сложнее, поскольку блокнот может иметь несколько вкладок в Windows 11

Вы можете прочитать текст в блокноте, найдя смещение памяти, в котором блокнот хранит текст (все равно будет работать после перезапуска приложения). Я сделал это с помощью Cheat Engine, вот мои шаги:

  1. Начните с поиска типа строкового значения и значения, которое вы ввели в блокнот с помощью Cheat Engine. Здесь вы можете видеть, что я получил один результат, который показывает адрес, где в данный момент хранится текст. Дважды щелкните этот результат, чтобы добавить его в список внизу чит-движка.

alt text


  1. Дважды щелкните адрес и скопируйте его.

alt text


  1. Измените тип значения на 8 байт (для процессов x64), установите флажок Hex и вставьте скопированный адрес в поле адреса, затем отсканируйте и добавьте результат в список внизу, дважды щелкнув его.

alt text


  1. Повторяйте шаги 2 и 3, пока один из адресов в результатах не станет зеленым, что означает, что вы нашли смещение, указывающее на текст. Затем вы можете использовать это смещение для чтения текста из памяти даже после перезапуска программы.

alt text

Как показано на снимке экрана, адрес, по которому находится указатель, который использует блокнот в 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);

Проблема в том, что если вы прочитаете то, что находится по адресу, вы получите указатель на другой адрес вместо текста, поэтому вам нужно прочитать указатель, а затем указатель из этого указателя и т. д., пока вы не получите текст.

alt text

Вы можете видеть, что 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

Полезные исследования/источники/инструменты, которые я использовал, чтобы во всем этом разобраться. Это может помочь, если вышеуказанные шаги по какой-либо причине не работают:

Другие вопросы по теме

Похожие вопросы

Приложение MAUI аварийно завершает работу без отладки, InvalidCastException для TextColor
Метод концентратора SignalR не вызывается в Blazor
Рабочей службе C# .NET Core необходимо войти в текстовый файл и таблицу базы данных в зависимости от настроек
Ошибка 500.30 после изменения целевой платформы с x64 на x86 в проекте API веб-сервера в Visual Studio 2022
.NET Standard 2.0, эквивалент Int128Converter
Как обновить фотографию профиля пользователя с помощью UPN (имя участника-пользователя) с помощью API Graph в C#
Получение списка планов Планировщика через Microsoft Graph — в сообщении об исключении говорится, что требуется недокументированный фильтр
Как установить правила форматирования для оператора распространения C# в Visual Studio?
Есть ли разница между всегда отбрасыванием возвращаемой задачи и созданием асинхронного недействительного метода?
WinUI 3 — ComboBox не работает должным образом во всплывающем окне