Как найти переменную в памяти

Анализ памяти процесса

Организация памяти процессов ОС Windows рассмотрена во многих книгах и статьях. Мы изучим только те аспекты этого вопроса, которые имеют отношение к поиску переменных в памяти, а также чтению и записи их значений.

Адресное пространство процесса

Исполняемый EXE-файл и запущенный процесс ОС – это не одно и то же. Файл – это некоторые данные, записанные на устройство хранения информации (например жёсткий диск). Исполняемый файл содержит инструкции (или машинный код), которые выполняет процессор без каких либо дополнительных преобразований.

Когда вы запускаете EXE-файл, для его исполнения ОС нужно выполнить несколько шагов. Во-первых, прочитать его содержимое с устройства хранения и записать в

оперативную память

(random-access memory или RAM). Благодаря этому процессор получает намного более быстрый доступ к инструкциям из файла, поскольку скорость его интерфейса с RAM на несколько порядков выше чем с любым диском.

Когда содержимое файла записано в оперативную память, ОС загружает туда же все необходимые для его работы динамические библиотеки. После этого шага, процесс готов к выполнению. Поскольку все современные ОС для компьютеров и телефонов многозадачные, несколько процессов могут исполняться параллельно. Параллельность в данном случае не означает одновременность. То есть если у компьютера один процессор с одним ядром, он будет переключаться между процессами. В таком случае говорят о распределении процессорного времени. В многозадачных ОС этим занимается специальная программа

планировщик

(scheduler). Благодаря ей каждый процесс получает единицы времени (тики или секунды) в зависимости от своего приоритета.

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

Где процесс хранит свои данные? Мы уже знаем, что ОС всегда загружает исполняемые инструкции в оперативную память. В случае данных, сам процесс может свободно выбрать место их хранения: жесткий диск, оперативная память или даже удалённый компьютер (например игровой сервер подключённый по сети). Большая часть данных, необходимых во время работы процесса копируются в оперативную память для ускорения доступа к ней. Поэтому, именно в RAM мы можем прочитать состояния игровых объектов. Они будут доступны на протяжении всего времени выполнения (runtime) процесса.

Иллюстрация 3-2 демонстрирует элементы типичного процесса. Как правило, он состоит из нескольких модулей. Обязательным из них является EXE, который содержит все инструкции и данные, загруженные из исполняемого файла. Другие модули (обозначенные DLL_1 и DLL_2) соответствуют библиотекам, функции которых вызываются из EXE.

Иллюстрация 3-2. Элементы типичного процесса Windows

Все Windows приложения используют как минимум одну системную библиотеку, которая предоставляет доступ к WinAPI функциям. Даже если вы не пользуетесь WinAPI явно в своей программе, компилятор вставляет вызовы ExitProcess и VirtualQuery автоматически в ходе компиляции. Они отвечают за корректное завершение процесса и управление его памятью.

Мы рассмотрели исполняемый файл и запущенный процесс. Теперь поговорим о библиотеках с функциями. Они делятся на два типа: динамически подключаемые (dynamic-link libraries или DLL) и статически подключаемые (static libraries). Главное различие между ними заключается во времени разрешения зависимостей. Когда исполняемый файл использует функцию библиотеки, говорят, что он от неё зависит.

Статически подключаемые библиотеки должны быть доступны в момент компиляции. Программа

компоновщик

собирает их и исполняемый файл в единый выходной файл. Таким образом, EXE модуль на иллюстрации 3-2 содержит машинный код и статических библиотек, и исполняемого файла.

Динамически подключаемые библиотеки также должны быть доступны в момент компиляции. Однако, результирующий файл на выходе компоновщика не содержит их машинный код. Вместо этого ОС ищет и загружает эти DLL библиотеки в момент запуска приложения. Если найти их не удалось, приложение завершает свою работу с ошибкой. На иллюстрации 3-2 у процесса есть два DLL модуля, соответствующие динамическим библиотекам.

Рассмотрим, как CPU выполняет инструкции процесса. Эти инструкции – элементарные шаги более сложных высокоуровневых алгоритмов. Результат выполнения каждого шага сохраняется в регистрах (или ячейках памяти) процессора и используется в дальнейшем или выгружается в оперативную память.

Запущенное приложение может использовать несколько алгоритмов в ходе своей работы. Некоторые из них могут выполняться параллельно (так же как процессы в многозадачной ОС).

Поток

(thread) – это часть машинного кода процесса, которая может выполняться независимо от других частей. Потоки взаимодействуют друг с другом (обмениваются информацией) через разделяемые ресурсы, например файл или область RAM. За выбор потока для исполнения в данный момент отвечает уже знакомый нам планировщик ОС. Как правило, число одновременно работающих потоков определяется числом ядер процессора. Но есть технологии (например hyper-threading от Intel), позволяющие более эффективно использовать мощности процессора и исполнять сразу два потока на одном ядре.

Иллюстрация 3-2 демонстрирует, что модули процесса могут содержать несколько потоков, а могут не содержать ни одного. EXE модуль всегда имеет главный поток (main thread), который первым получает управление при старте приложения.

Рассмотрим структуру памяти типичного процесса. Иллюстрация 3-3 демонстрирует адресное пространство процесса, состоящего из двух модулей: EXE и DLL библиотеки. Адресное пространство – это множество всех доступных процессу адресов памяти. Оно разделено на блоки, называемые сегментами. У каждого из них есть базовый адрес, длина и набор прав доступа (на запись, чтение и исполнение). Разделение на сегменты упрощает задачу контроля доступа к памяти. С их помощью ОС может оперировать блоками памяти, а не отдельными адресами.

Иллюстрация 3-3. Адресное пространство типичного процесса

Процесс на иллюстрации 3-3 имеет три потока (включая главный). У каждого потока есть свой сегмент стека. Стек – это область памяти, организованная по принципу «последним пришёл — первым вышел» («last in — first out» или LIFO). Она инициализируется ОС при старте приложения и используется для хранения переменных и вызова функций. В стеке сохраняется адрес инструкции, следующей за вызовом. После возврата из функции процесс продолжает свое выполнение с этой инструкции. Также через стек передаются входные параметры функций.

Кроме сегментов стека, у процесса есть несколько сегментов динамической памяти (heap), к которым имеет доступ каждый поток.

У всех модулей процесса есть обязательные сегменты: .text, .data и .bss. Кроме обязательных могут быть и дополнительные сегменты (например .rsrc). Они не представлены на схеме 3-3.

Таблица 3-1 кратко описывает каждый сегмент из иллюстрации 3-3. Во втором столбце приведены их обозначения в отладчике OllyDbg.

Таблица 3-1. Описание сегментов

Сегмент

Обозначение в OllyDbg

Описание

Стек главного потока

Stack of main thread

Содержит автоматические переменные (память под которые выделяется при входе в блок области видимости и освобождается при выходе из него), стек вызовов с адресами возврата из функций и их входные параметры.

Динамическая память ID 1

Heap

Дополнительный сегмент памяти, который создаётся при переполнении сегмента динамической памяти ID 0.

Динамическая память ID 0

Default heap

ОС всегда создает этот сегмент при запуске процесса. Он используется по умолчанию для хранения переменных.

Стек потока 2

Stack of thread 2

Выполняет те же функции, что и стек главного потока, но используется только потоком 2.

.text EXE модуля

Code

Содержит машинный код модуля EXE.

.data EXE модуля

Data

Содержит статические и не константные глобальные переменные модуля EXE, которые инициализируются значениями при создании.

.bss EXE модуля

Содержит статические и не константные глобальные переменные модуля EXE, которые не инициализируются при создании.

Стек потока 3

Stack of thread 2

То же самое, что и стек потока 2, только используется потоком 3.

Динамическая память ID 2

Дополнительный сегмент памяти, расширяющий сегмент динамической памяти ID 1 при его переполнении.

.text DLL модуля

Code

Содержит машинный код модуля DLL.

.data DLL модуля

Data

Содержит статические и не константные глобальные переменные модуля DLL, которые инициализируются значениями при создании.

.bss DLL модуля

Содержит статические и не константные глобальные переменные модуля DLL, которые не инициализируются при создании.

Динамическая память ID 3

Дополнительный сегмент памяти, расширяющий сегмент динамической памяти ID 2 при его переполнении.

TEB потока 3

Data block of thread 3

Содержит блок информации о потоке (Thread Information Block или TIB), также известный как блок контекста потока (Thread Environment Block или TEB). Он представляет собой структуру с информацией о потоке 3.

TEB потока 2

Data block of thread 2

Содержит TEB структуру потока 2.

TEB главного потока

Data block of main thread

Содержит TEB структуру главного потока.

PEB

Process Environment Block

Содержит блок контекста процесса (Process Environment Block или PEB). Эта структура данных с информацией о процессе в целом.

Пользовательские данные

User Share Data

Содержит данные, которые доступны и совместно используются текущим процессом и другим.

Память ядра

Kernel memory

Область памяти, зарезервированная для нужд ОС.

Предположим, что на иллюстрации 3-3 приведено адресное пространство процесса игрового приложения. В этом случае состояние игровых объектов может находится в сегментах, отмеченных красным цветом.

ОС назначает базовые адреса этих сегментов в момент старта приложения. Эти адреса могут отличаться от запуска к запуску. Кроме того, последовательность сегментов в памяти может также меняться. В то же время некоторые из сегментов, отмеченных синим цветом на иллюстрации 3-3 (например PEB, User Share Data и Kernel memory), имеют неизменный адрес при каждом старте приложения.

Отладчик OllyDbg позволяет прочитать структуру памяти (memory map) запущенного процесса. Иллюстрации 3-4 и 3-5 демонстрируют вывод OllyDbg для приложения, адресное пространство которого приведено на схеме 3-3.

Структура памяти процесса в OllyDbg

Иллюстрация 3-4. Структура памяти процесса в OllyDbg

Структура памяти процесса в OllyDbg

Иллюстрация 3-5. Структура памяти процесса в OllyDbg (продолжение)

Таблица 3-2 демонстрирует соответствие между схемой 3-3 и сегментами настоящего процесса из иллюстраций 3-4 и 3-5.

Таблица 3-2. Сегменты процесса

Базовый адрес

Сегмент

Обозначение в OllyDbg

001ED000

Стек главного потока

Stack of main thread

004F0000

Динамическая память ID 1

Heap

00530000

Динамическая память ID 0

Default heap

00ACF000
00D3E000
0227F000

Стеки вспомогательных потоков

Stack of thread N

00D50000-00D6E000

Сегменты EXE модуля «ConsoleApplication1»

02280000-0BB40000
0F230000-2BC70000

Дополнительные сегменты динамической памяти

0F0B0000-0F217000

Сегменты DLL модуля «ucrtbased»

7EFAF000
7EFD7000
7EFDA000

TEB вспомогательных потоков

Data block of thread N

7EFDD000

TEB главного потока

Data block of main thread

7EFDE000

PEB главного потока

Process Environment Block

7FFE0000

Пользовательские данные

User shared data

80000000

Память ядра

Kernel memory

Возможно, вы обратили внимание, что OllyDbg не может автоматически идентифицировать все сегменты динамической памяти. С этой задачей лучше справляются отладчик WinDbg и инструмент HeapMemView.

Поиск переменной в памяти

Внутриигровые боты читают состояния объектов из памяти процесса игрового приложения. Эти состояния могут храниться в нескольких переменных, находящихся в разных сегментах. Базовые адреса этих сегментов и смещение переменных внутри них могут меняться от запуска к запуску. Это означает, что абсолютные адреса переменных непостоянны. К сожалению, бот может читать данные из памяти только по абсолютным адресам. Следовательно, он должен уметь искать нужные ему переменные самостоятельно.

Термин «абсолютный адрес» неточен, если мы говорим о

модели сегментации памяти x86

. x86 – это архитектура процессора, впервые реализованная компанией Intel. Сегодня практически все настольные компьютеры имеют процессоры этой архитектуры. Правильный термин, который следует употреблять – «линейный адрес». Он вычисляется по следующей формуле:

линейный адрес = базовый адрес сегмента + смещение в сегменте

В этой главе мы продолжим использовать термин «абсолютный адрес», поскольку он интуитивно понятен.

Задачу поиска переменной в памяти процесса можно разделить на три этапа. В результате получится следующий алгоритм:

  1. 1.

    Найти сегмент, который содержит искомую переменную.

  2. 2.

    Определить базовый адрес сегмента.

  3. 3.

    Определить смещение переменной внутри сегмента.

Очень высока вероятность того, что переменная будет храниться в одном и том же сегменте при каждом старте приложения. Это правило не выполняется для сегментов динамической памяти, что связано с особенностью её организации. Если мы установили, что переменная не находится в сегменте динамической памяти, первый шаг алгоритма может быть выполнен вручную. Полученный результат можно закодировать в боте без каких-либо дополнительных условий и проверок. В противном случае бот должен искать сегмент самостоятельно.

Второй шаг алгоритма бот должен всегда выполнять сам. Как мы упоминали ранее, адреса сегментов меняются при старте приложения.

Последний шаг алгоритма – найти смещение переменной в сегменте. Нет никаких гарантий, что оно не будет меняться при каждом старте приложения. Однако, смещение может оставаться тем же в некоторых случаях. Это зависит от типа сегмента, как демонстрирует таблица 3-3. Таким образом, в некоторых случаях мы можем выполнить третий шаг алгоритма вручную и закодировать результат в боте.

Таблица 3-3. Смещение переменных в различных типах сегментов

Смещение переменной не меняется при перезапуске приложения.

В большинстве случаев смещение переменной не меняется. Но оно зависит от порядка выполнения инструкций (control flow). Если этот порядок меняется, смещение, скорее всего, тоже изменится.

Смещение переменной меняется при перезапуске приложения.

Поиск переменной в 32-битном приложении

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

Приложение ColorPix является 32-битным. Скриншот его окна приведён на иллюстрации 3-6. Попробуем найти в памяти переменную, которая соответствует координате X выделенного на экране пикселя. На иллюстрации 3-6 она подчеркнута красной линией.

Иллюстрация 3-6. Окно приложения ColorPix

В ходе дальнейших действий вы не должны закрывать уже запущенное приложение ColorPix. Иначе, вам придется начать поиск переменной сначала.

Для начала найдём сегмент памяти, в котором хранится переменная. Эту задачу можно разделить на два этапа:

  1. 1.

    Найти абсолютный адрес переменной с помощью сканера памяти Cheat Engine.

  2. 2.

    Сравнить найденный адрес с базовыми адресами всех сегментов. Таким образом мы узнаем сегмент, в котором хранится переменная.

Чтобы найти переменную с помощью Cheat Engine, выполните следующие действия:

  1. 1.

    Запустите 32-битную версию сканера с правами администратора.

  2. 2.

    Выберите пункт главного меню «File» ➤ «Open Process». Вы увидите диалог со списком запущенных процессов (см. иллюстрацию 3-7).

Диалог выбора процесса Cheat Engine

Иллюстрация 3-7. Диалог выбора процесса Cheat Engine

  1. 1.

    Выберите процесс с именем «ColorPixel.exe» и нажмите кнопку «Open». В результате имя этого процесса отобразится в верхней части окна Cheat Engine.

  2. 2.

    Введите значение координаты X, которое вы видите в данный момент в окне ColorPixel, в поле «Value» окна Cheat Engine.

  3. 3.

    Нажмите кнопку «First Scan», чтобы найти абсолютный адрес указанного значения координаты X в памяти процесса ColorPixel.

Когда вы нажимаете кнопку «First Scan», значение в поле «Value» окна Cheat Engine, должно соответствовать тому, что отображает ColorPixel. Координата X изменится, если вы переместите курсор мыши по экрану, поэтому нажать на кнопку будет затруднительно. Воспользуйтесь комбинацией клавиш Shift+Tab, чтобы переключиться на неё и Enter, чтобы нажать.

В левой части окна Cheat Engine вы увидите результаты поиска, как на иллюстрации 3-8.

Результаты поиска Cheat Engine

Иллюстрация 3-8. Результаты поиска в окне Cheat Engine

Если в момент сканирования процесса несколько переменных имеют то же самое значение что и координата X, найденных переменных будет больше чем две. В этом случае вам надо отфильтровать ошибочные результаты. Для этого выполните следующие шаги:

  1. 1.

    Переместите курсор мыши, чтобы значение координаты X в окне ColorPixel изменилось.

  2. 2.

    Введите новую координату X в поле «Value» окна Cheat Engine.

  3. 3.

    Нажмите кнопку «Next Scan».

После этого в окне результатов должны остаться только две переменные, как на иллюстрации 3-8. В моём случае их абсолютные адреса равны 0018FF38 и 0025246C. У вас они могут отличаться, но это не существенно для нашего примера.

Мы нашли абсолютные адреса двух переменных, хранящих значение координаты X. Теперь определим сегменты, в которых они находятся. Для этой цели воспользуемся отладчиком OllyDbg. Для поиска сегментов выполните следующие шаги:

  1. 1.

    Запустите отладчик OllyDbg с правами администратора. Путь к нему по умолчанию:
    C:Program Files (x86)odbg201ollydbg.exe.

  2. 2.

    Выберите пункт главного меню «File» ➤ «Attach». Вы увидите диалог со списком запущенных 32-битных процессов (см. иллюстрацию 3-9).

Диалог выбора процесса OllyDbg

Иллюстрация 3-9. Диалог выбора процесса в отладчике OllyDbg

  1. 1.

    Выберите процесс «ColorPix» в списке и нажмите кнопку «Attach». Когда отладчик подключится к нему, вы увидите состояние «Paused» в правом нижнем углу окна OllyDbg.

  2. 2.

    Нажмите комбинацию клавиш Alt+M, чтобы открыть окно, отображающее структуру памяти процесса ColorPix. Это окно «Memory Map» приведено на иллюстрации 3-10.

Иллюстрация 3-10. Окно «Memory Map» со структурой памяти процесса

Переменная с абсолютным адресом 0018FF38 хранится в сегменте стека главного процесса («Stack of main thread»), который занимает адреса с 0017F000 по 00190000.

OllyDbg отображает только адрес начала сегмента и его размер. Чтобы вычислить конечный адрес, вы должны сложить два эти числа. Результат будет равен адресу начала следующего сегмента.

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

Последний шаг поиска – расчёт смещения переменной в сегменте стека. Стек в архитектуре x86 растёт вниз. Это означает, что он начинается с больших адресов и расширяется в сторону меньших. Следовательно, базовый адрес стека равен его верхней границе (в нашем случае это 00190000). Нижняя границе стека может меняться по ходу его увеличения.

Смещение переменной равно разности базового адреса сегмента, в котором она находится, и её абсолютного адреса. В нашем случае мы получим:

Для сегментов динамической памяти, .bss и .data это вычисление выглядело бы иначе. Все они растут вверх (в сторону больших адресов), поэтому их базовый адрес соответствует нижней границе.

Теперь у нас есть вся необходимая информация, чтобы найти и прочитать координату X в любом запущенном процессе ColorPix. Алгоритм бота, который бы это делал, выглядит следующим образом:

  1. 1.

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

  2. 2.

    Вычесть смещение переменной (всегда равное C8) из базового адреса сегмента стека. В результате получим её абсолютный адрес.

  3. 3.

    Прочитать значение переменной из памяти процесса ColorPix по её абсолютному адресу.

Корректность первого шага алгоритма мы можем проверить вручную с помощью отладчика OllyDbg. Он позволяет прочитать информацию сегмента TEB в удобном виде. Для этого дважды щелкните по сегменту, который называется «Data block of main thread», в окне «Memory Map» отладчика. Вы увидите окно как на иллюстрации 3-11.

Иллюстрация 3-11. Окно OllyDbg с информацией TEB

Базовый адрес сегмента стека 00190000 указан во второй строчке открывшегося окна. Учтите, что этот адрес может меняться при каждом запуске приложения.

Поиск переменной в 64-битном приложении

Применим наш алгоритм поиска переменной для 64-битного приложения.

Отладчик OllyDbg не поддерживает 64-битные приложения, поэтому вместо него воспользуемся WinDbg.

Resource Monitor (монитор ресурсов) Windows 7 будет нашим приложением для анализа. Он распространяется вместе с ОС и доступен сразу после её установки. Разрядность Resource Monitor совпадает с разрядностью Windows. Чтобы запустить приложение, откройте меню Пуск (Start) Windows и введите следующую команду в строку поиска:

Иллюстрации 3-12 демонстрирует окно Resource Monitor.

Иллюстрация 3-12. Окно приложения Resource Monitor

Найдём переменную, хранящую размер свободной памяти системы. На иллюстрации её значение подчёркнуто красной линией.

Прежде всего найдём сегмент, содержащий искомую переменную. Для этого воспользуемся 64-битной версией сканера Cheat Engine. Интерфейс его 64 и 34-битных версий одинаков, поэтому вам нужно выполнить те же действия, что и при анализе приложения ColorPixel.

В моем случае сканер нашёл две переменные с адресами 00432FEC и 00433010. Определим сегменты, в которых они хранятся. Чтобы прочитать структуру памяти процесса с помощью отладчика WinDbg, выполните следующие действия:

  1. 1.

    Запустите 64-битную версию WinDbg с правами администратора. Путь к нему по умолчанию:
    C:Program Files (x86)Windows ­Kits8.1Debuggersx64windbg.exe.

  2. 2.

    Выберите пункт главного меню «File» ➤ «Attach to a Process…». Откроется окно диалога со списком запущенных 64-разрядных процессов, как на иллюстрации 3-13.

Диалог выбора процесса WinDbg

Иллюстрация 3-13. Диалог выбора процесса в отладчике WinDbg

  1. 1.

    Выберите в списке процесс «perfmon.exe» и нажмите кнопку «OK».

  2. 2.

    В командной строке отладчика, расположенной в нижней части окна «Command», введите текст !address и нажмите Enter. Структура памяти процесса отобразится в окне «Command», как на иллюстрации 3-14.

Структура памяти в WinDbg

Иллюстрация 3-14. Вывод структуры памяти процесса в окне «Command»

Обе переменные с абсолютными адресами 00432FEC и 00433010 находятся в сегменте динамической памяти с ID 2. Границы этого сегмента: с 003E0000 по 00447000. Смещение первой переменной в сегменте равно 52FEC:

00432FEC — 003E0000 = 52FEC

Для бота алгоритм поиска переменной, хранящей размер свободной памяти ОС в приложении Resource Monitor, выглядит следующим образом:

  1. 1.

    Прочитать базовый адрес сегмента динамической памяти с ID 2. Чтобы получить доступ к этим сегментам, надо воспользоваться следующими WinAPI функциями:

  2. 2.

    Добавить смещение переменной (в моем случае равное 52FEC) к базовому адресу сегмента. В результате получится её абсолютный адрес.

  3. 3.

    Прочитать значение переменной из памяти процесса.

Как вы помните, смещение переменной в сегменте динамической памяти обычно меняется при перезапуске приложения. В случае если приложение достаточно простое (как рассматриваемый нами Resource Monitor), порядок выделения динамической памяти может быть одним и тем же при каждом старте программы.

Попробуйте перезапустить Resource Monitor и найти переменную еще раз. Вы получите то же самое её смещение в сегменте, равное 52FEC.

Мы рассмотрели адресное пространство Windows процесса. Затем составили алгоритм поиска переменной в памяти и применили его к 32 и 64-разрядному приложениям. В ходе этого мы познакомились с функциями отладчиков OllyDbg и WinDbg для анализа структуры памяти процесса.

Организация памяти процесса

Организация памяти процессов ОС Windows рассмотрена во многих книгах и статьях. Мы изучим только те аспекты этого вопроса, которые имеют отношение к поиску переменных в памяти, а также чтению и записи их значений.

Адресное пространство процесса

Исполняемый EXE-файл и запущенный процесс ОС – это не одно и то же. Файл – это некоторые данные, записанные на устройство хранения информации (например жёсткий диск). Исполняемый файл содержит инструкции (или машинный код), которые выполняет процессор без каких либо дополнительных преобразований.

Когда вы запускаете EXE-файл, для его исполнения ОС нужно выполнить несколько шагов. Во-первых, прочитать его содержимое с устройства хранения и записать в

оперативную память

(random-access memory или RAM). Благодаря этому процессор получает намного более быстрый доступ к инструкциям из файла, поскольку скорость его интерфейса с RAM на несколько порядков выше чем с любым диском.

Когда содержимое файла записано в оперативную память, ОС загружает туда же все необходимые для его работы динамические библиотеки. После этого шага, процесс готов к выполнению. Поскольку все современные ОС для компьютеров и телефонов многозадачные, несколько процессов могут исполняться параллельно. Параллельность в данном случае не означает одновременность. То есть если у компьютера один процессор с одним ядром, он будет переключаться между процессами. В таком случае говорят о распределении процессорного времени. В многозадачных ОС этим занимается специальная программа

планировщик

(scheduler). Благодаря ей каждый процесс получает единицы времени (тики или секунды) в зависимости от своего приоритета.

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

Где процесс хранит свои данные? Мы уже знаем, что ОС всегда загружает исполняемые инструкции в оперативную память. В случае данных, сам процесс может свободно выбрать место их хранения: жёсткий диск, оперативная память или даже удалённый компьютер (например игровой сервер подключённый по сети). Большая часть данных, необходимых во время работы процесса копируются в оперативную память для ускорения доступа к ней. Поэтому, именно в RAM мы можем прочитать состояния игровых объектов. Они будут доступны на протяжении всего времени выполнения (runtime) процесса.

Иллюстрация 3-2 демонстрирует элементы типичного процесса. Как правило, он состоит из нескольких модулей. Обязательным из них является EXE, который содержит все инструкции и данные, загруженные из исполняемого файла. Другие модули (обозначенные DLL_1 и DLL_2) соответствуют библиотекам, функции которых вызываются из EXE.

{caption: «Иллюстрация 3-2. Элементы типичного процесса Windows»}

Схема процесса

Все Windows приложения используют как минимум одну системную библиотеку, которая предоставляет доступ к WinAPI-функциям. Даже если вы не пользуетесь WinAPI явно в своей программе, компилятор вставляет вызовы ExitProcess и VirtualQuery автоматически в ходе компиляции. Они отвечают за корректное завершение процесса и управление его памятью.

Мы рассмотрели исполняемый файл и запущенный процесс. Теперь поговорим о библиотеках с функциями. Они делятся на два типа: динамически подключаемые (dynamic-link libraries или DLL) и статически подключаемые (static libraries). Главное различие между ними заключается во времени разрешения зависимостей. Когда исполняемый файл использует функцию библиотеки, говорят, что он от неё зависит.

Статически подключаемые библиотеки должны быть доступны в момент компиляции. Программа

компоновщик

собирает их и исполняемый файл в единый выходной файл. Таким образом, EXE-модуль на иллюстрации 3-2 содержит машинный код и статических библиотек, и исполняемого файла.

Динамически подключаемые библиотеки также должны быть доступны в момент компиляции. Однако, результирующий файл на выходе компоновщика не содержит их машинный код. Вместо этого ОС ищет и загружает эти DLL библиотеки в момент запуска приложения. Если найти их не удалось, приложение завершает свою работу с ошибкой. На иллюстрации 3-2 у процесса есть два модуля DLL, соответствующие динамическим библиотекам.

Рассмотрим, как CPU выполняет инструкции процесса. Эти инструкции – элементарные шаги более сложных высокоуровневых алгоритмов. Результат выполнения каждого шага сохраняется в регистрах (или ячейках памяти) процессора и используется в дальнейшем или выгружается в оперативную память.

Запущенное приложение может использовать несколько алгоритмов в ходе своей работы. Некоторые из них могут выполняться параллельно (так же как процессы в многозадачной ОС).

Поток

(thread) – это часть машинного кода процесса, которая может выполняться независимо от других частей. Потоки взаимодействуют друг с другом (обмениваются информацией) через разделяемые ресурсы, например файл или область RAM. За выбор потока для исполнения в данный момент отвечает уже знакомый нам планировщик ОС. Как правило, число одновременно работающих потоков определяется числом ядер процессора. Но есть технологии (например hyper-threading от Intel), позволяющие более эффективно использовать мощности процессора и исполнять сразу два потока на одном ядре.

Иллюстрация 3-2 демонстрирует, что модули процесса могут содержать несколько потоков, а могут не содержать ни одного. EXE-модуль всегда имеет главный поток (main thread), который первым получает управление при старте приложения.

Рассмотрим структуру памяти типичного процесса. Иллюстрация 3-3 демонстрирует адресное пространство процесса, состоящего из EXE-модуля и DLL-библиотеки. Адресное пространство – это множество всех доступных процессу адресов памяти. Оно разделено на блоки, называемые сегментами. У каждого из них есть базовый адрес, длина и набор прав доступа (на запись, чтение и исполнение). Разделение на сегменты упрощает задачу контроля доступа к памяти. С их помощью ОС может оперировать блоками памяти, а не отдельными адресами.

{caption: «Иллюстрация 3-3. Адресное пространство типичного процесса»}

Схема памяти процесса

Процесс на иллюстрации 3-3 имеет три потока (включая главный). У каждого потока есть свой сегмент стека. Стек – это область памяти, организованная по принципу «последним пришёл — первым вышел» («last in — first out» или LIFO). Она инициализируется ОС при старте приложения и используется для хранения переменных и вызова функций. В стеке сохраняется адрес инструкции, следующей за вызовом. После возврата из функции процесс продолжает своё выполнение с этой инструкции. Также через стек передаются входные параметры функций.

Кроме сегментов стека, у процесса есть несколько сегментов динамической памяти (heap), к которым имеет доступ каждый поток.

У всех модулей процесса есть обязательные сегменты: .text, .data и .bss. Кроме обязательных могут быть и дополнительные сегменты (например .rsrc). Они не представлены на схеме 3-3.

Таблица 3-1 кратко описывает каждый сегмент из иллюстрации 3-3. Во втором столбце приведены их обозначения в отладчике OllyDbg.

{caption: «Таблица 3-1. Описание сегментов», width: «100%», column-widths: «15%,15%,*»}

Сегмент

Обозначение в OllyDbg

Описание

Стек главного потока

Stack of main thread

Содержит автоматические переменные (память под которые выделяется при входе в блок области видимости и освобождается при выходе из него), стек вызовов с адресами возврата из функций и их входные параметры.

Динамическая память ID 1

Heap

Дополнительный сегмент памяти, который создаётся при переполнении сегмента динамической памяти ID 0.

Динамическая память ID 0

Default heap

ОС всегда создаёт этот сегмент при запуске процесса. Он используется по умолчанию для хранения переменных.

Стек потока 2

Stack of thread 2

Выполняет те же функции, что и стек главного потока, но используется только потоком 2.

.text EXE модуля

Code

Содержит машинный код модуля EXE.

.data EXE модуля

Data

Содержит статические и не константные глобальные переменные модуля EXE, которые инициализируются значениями при создании.

.bss EXE модуля

Содержит статические и не константные глобальные переменные модуля EXE, которые не инициализируются при создании.

Стек потока 3

Stack of thread 2

То же самое, что и стек потока 2, только используется потоком 3.

Динамическая память ID 2

Дополнительный сегмент памяти, расширяющий сегмент динамической памяти ID 1 при его переполнении.

.text модуля DLL

Code

Содержит машинный код модуля DLL.

.data модуля DLL

Data

Содержит статические и не константные глобальные переменные модуля DLL, которые инициализируются значениями при создании.

.bss модуля DLL

Содержит статические и не константные глобальные переменные модуля DLL, которые не инициализируются при создании.

Динамическая память ID 3

Дополнительный сегмент памяти, расширяющий сегмент динамической памяти ID 2 при его переполнении.

TEB потока 3

Data block of thread 3

Содержит блок информации о потоке (Thread Information Block или TIB), также известный как блок контекста потока (Thread Environment Block или TEB). Он представляет собой структуру с информацией о потоке 3.

TEB потока 2

Data block of thread 2

Содержит TEB структуру потока 2.

TEB главного потока

Data block of main thread

Содержит TEB структуру главного потока.

PEB

Process Environment Block

Содержит блок контекста процесса (Process Environment Block или PEB). Эта структура данных с информацией о процессе в целом.

Пользовательские данные

User Share Data

Содержит данные, которые доступны и совместно используются текущим процессом и другим.

Память ядра

Kernel memory

Область памяти, зарезервированная для нужд ОС.

Предположим, что на иллюстрации 3-3 приведено адресное пространство процесса игрового приложения. В этом случае состояние игровых объектов может находится в сегментах, отмеченных красным цветом.

ОС назначает базовые адреса этих сегментов в момент старта приложения. Эти адреса могут отличаться от запуска к запуску. Кроме того, последовательность сегментов в памяти может также меняться. В то же время некоторые из сегментов, отмеченных синим цветом на иллюстрации 3-3 (например PEB, User Share Data и Kernel memory), имеют неизменный адрес при каждом старте приложения.

Отладчик OllyDbg позволяет прочитать структуру памяти (memory map) запущенного процесса. Иллюстрации 3-4 и 3-5 демонстрируют вывод OllyDbg для приложения, адресное пространство которого приведено на схеме 3-3.

{caption: «Иллюстрация 3-4. Структура памяти процесса в OllyDbg»}

Структура памяти процесса в OllyDbg

{caption: «Иллюстрация 3-5. Структура памяти процесса в OllyDbg (продолжение)»}

Структура памяти процесса в OllyDbg

Таблица 3-2 демонстрирует соответствие между схемой 3-3 и сегментами настоящего процесса из иллюстраций 3-4 и 3-5.

{caption: «Таблица 3-2. Сегменты процесса»}

Базовый адрес

Сегмент

Обозначение в OllyDbg

001ED000

Стек главного потока

Stack of main thread

004F0000

Динамическая память ID 1

Heap

00530000

Динамическая память ID 0

Default heap

00ACF000

Стеки вспомогательных

Stack of thread N

00D3E000

потоков

0227F000

00D50000-00D6E000

Сегменты EXE модуля «ConsoleApplication1»

02280000-0BB40000

Дополнительные сегменты

0F230000-2BC70000

динамической памяти

0F0B0000-0F217000

Сегменты модуля DLL «ucrtbased»

7EFAF000

TEB вспомогательных

Data block of thread N

7EFD7000

потоков

7EFDA000

7EFDD000

TEB главного потока

Data block of main thread

7EFDE000

PEB главного потока

Process Environment Block

7FFE0000

Пользовательские данные

User shared data

80000000

Память ядра

Kernel memory

Возможно, вы обратили внимание, что OllyDbg не может автоматически идентифицировать все сегменты динамической памяти. С этой задачей лучше справляются отладчик WinDbg и инструмент HeapMemView.

Поиск переменной в памяти

Внутриигровые боты читают состояния объектов из памяти процесса игрового приложения. Эти состояния могут храниться в нескольких переменных, находящихся в разных сегментах. Базовые адреса этих сегментов и смещение переменных внутри них могут меняться от запуска к запуску. Это означает, что абсолютные адреса переменных непостоянны. К сожалению, бот может читать данные из памяти только по абсолютным адресам. Следовательно, он должен уметь искать нужные ему переменные самостоятельно.

Термин «абсолютный адрес» неточен, если мы говорим о

модели сегментации памяти x86

. x86 – это архитектура процессора, впервые реализованная компанией Intel. Сегодня практически все настольные компьютеры имеют процессоры этой архитектуры. Правильный термин, который следует употреблять – «линейный адрес». Он вычисляется по следующей формуле:

линейный адрес = базовый адрес сегмента + смещение в сегменте

В этой главе мы продолжим использовать термин «абсолютный адрес», поскольку он интуитивно понятен.

Задачу поиска переменной в памяти процесса можно разделить на три этапа. В результате получится следующий алгоритм:

  1. 1.

    Найти сегмент, который содержит искомую переменную.

  2. 2.

    Определить базовый адрес сегмента.

  3. 3.

    Определить смещение переменной внутри сегмента.

Очень высока вероятность того, что переменная будет храниться в одном и том же сегменте при каждом старте приложения. Это правило не выполняется для сегментов динамической памяти, что связано с особенностью её организации. Если мы установили, что переменная не находится в сегменте динамической памяти, первый шаг алгоритма может быть выполнен вручную. Полученный результат можно закодировать в боте без каких-либо дополнительных условий и проверок. В противном случае бот должен искать сегмент самостоятельно.

Второй шаг алгоритма бот должен всегда выполнять сам. Как мы упоминали ранее, адреса сегментов меняются при старте приложения.

Последний шаг алгоритма – найти смещение переменной в сегменте. Нет никаких гарантий, что оно не будет меняться при каждом старте приложения. Однако, смещение может оставаться тем же в некоторых случаях. Это зависит от типа сегмента, как демонстрирует таблица 3-3. Таким образом, в некоторых случаях мы можем выполнить третий шаг алгоритма вручную и закодировать результат в боте.

{caption: «Таблица 3-3. Смещение переменных в различных типах сегментов», width: «100%», column-widths: «20%,*»}

Тип сегмента

Постоянство смещения

.bss

Смещение переменной не меняется

.data

при перезапуске приложения.

Стек

В большинстве случаев смещение переменной не меняется. Но оно зависит от порядка выполнения инструкций (control flow). Если этот порядок меняется, смещение, скорее всего, тоже изменится.

Динамическая память

Смещение переменной меняется при перезапуске приложения.

Поиск переменной в 32-битном приложении

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

Приложение ColorPix является 32-битным. Скриншот его окна приведён на иллюстрации 3-6. Попробуем найти в памяти переменную, которая соответствует координате X выделенного на экране пикселя. На иллюстрации 3-6 она подчёркнута красной линией.

{caption: «Иллюстрация 3-6. Окно приложения ColorPix»}

ColorPix

W> В ходе дальнейших действий вы не должны закрывать уже запущенное приложение ColorPix. Иначе, вам придётся начать поиск переменной сначала.

Для начала найдём сегмент памяти, в котором хранится переменная. Эту задачу можно разделить на два этапа:

  1. 1.

    Найти абсолютный адрес переменной с помощью сканера памяти Cheat Engine.

  2. 2.

    Сравнить найденный адрес с базовыми адресами всех сегментов. Таким образом мы узнаем сегмент, в котором хранится переменная.

Чтобы найти переменную с помощью Cheat Engine, выполните следующие действия:

  1. 1.

    Запустите 32-битную версию сканера с правами администратора.

  2. 2.

    Выберите пункт главного меню «File» -> «Open Process». Вы увидите диалог со списком запущенных процессов (см. иллюстрацию 3-7).

{caption: «Иллюстрация 3-7. Диалог выбора процесса Cheat Engine», height: «50%»}

Диалог выбора процесса Cheat Engine

  1. 1.

    Выберите процесс с именем «ColorPixel.exe» и нажмите кнопку «Open». В результате имя этого процесса отобразится в верхней части окна Cheat Engine.

  2. 2.

    Введите значение координаты X, которое вы видите в данный момент в окне ColorPixel, в поле «Value» окна Cheat Engine.

  3. 3.

    Нажмите кнопку «First Scan», чтобы найти абсолютный адрес указанного значения координаты X в памяти процесса ColorPixel.

Когда вы нажимаете кнопку «First Scan», значение в поле «Value» окна Cheat Engine, должно соответствовать тому, что отображает ColorPixel. Координата X изменится, если вы переместите курсор мыши по экрану, поэтому нажать на кнопку будет затруднительно. Воспользуйтесь комбинацией клавиш Shift+Tab, чтобы переключиться на неё и Enter, чтобы нажать.

В левой части окна Cheat Engine вы увидите результаты поиска, как на иллюстрации 3-8.

{caption: «Иллюстрация 3-8. Результаты поиска в окне Cheat Engine»}

Результаты поиска Cheat Engine

Если в момент сканирования процесса несколько переменных имеют то же самое значение что и координата X, найденных переменных будет больше чем две. В этом случае вам надо отфильтровать ошибочные результаты. Для этого выполните следующие шаги:

  1. 1.

    Переместите курсор мыши, чтобы значение координаты X в окне ColorPixel изменилось.

  2. 2.

    Введите новую координату X в поле «Value» окна Cheat Engine.

  3. 3.

    Нажмите кнопку «Next Scan».

После этого в окне результатов должны остаться только две переменные, как на иллюстрации 3-8. В моём случае их абсолютные адреса равны 0018FF38 и 0025246C. У вас они могут отличаться, но это не существенно для нашего примера.

Мы нашли абсолютные адреса двух переменных, хранящих значение координаты X. Теперь определим сегменты, в которых они находятся. Для этой цели воспользуемся отладчиком OllyDbg. Для поиска сегментов выполните следующие шаги:

  1. 1.

    Запустите отладчик OllyDbg с правами администратора. Путь к нему по умолчанию: C:Program Files (x86)odbg201ollydbg.exe.

  2. 2.

    Выберите пункт главного меню «File» -> «Attach». Вы увидите диалог со списком запущенных 32-битных процессов (см. иллюстрацию 3-9).

{caption: «Иллюстрация 3-9. Диалог выбора процесса в отладчике OllyDbg»}

Диалог выбора процесса OllyDbg

  1. 1.

    Выберите процесс «ColorPix» в списке и нажмите кнопку «Attach». Когда отладчик подключится к нему, вы увидите состояние «Paused» в правом нижнем углу окна OllyDbg.

  2. 2.

    Нажмите комбинацию клавиш Alt+M, чтобы открыть окно, отображающее структуру памяти процесса ColorPix. Это окно «Memory Map» приведено на иллюстрации 3-10.

{caption: «Иллюстрация 3-10. Окно Memory Map со структурой памяти процесса»}

Окно memory map OllyDbg

Переменная с абсолютным адресом 0018FF38 хранится в сегменте стека главного процесса («Stack of main thread»), который занимает адреса с 0017F000 по 00190000.

I> OllyDbg отображает только адрес начала сегмента и его размер. Чтобы вычислить конечный адрес, вы должны сложить два эти числа. Результат будет равен адресу начала следующего сегмента.

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

Последний шаг поиска – расчёт смещения переменной в сегменте стека. Стек в архитектуре x86 растёт вниз. Это означает, что он начинается с больших адресов и расширяется в сторону меньших. Следовательно, базовый адрес стека равен его верхней границе (в нашем случае это 00190000). Нижняя границе стека может меняться по ходу его увеличения.

Смещение переменной равно разности базового адреса сегмента, в котором она находится, и её абсолютного адреса. В нашем случае мы получим:

Для сегментов динамической памяти, .bss и .data это вычисление выглядело бы иначе. Все они растут вверх (в сторону больших адресов), поэтому их базовый адрес соответствует нижней границе.

Теперь у нас есть вся необходимая информация, чтобы найти и прочитать координату X в любом запущенном процессе ColorPix. Алгоритм бота, который бы это делал, выглядит следующим образом:

  1. 1.

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

  2. 2.

    Вычесть смещение переменной (всегда равное C8) из базового адреса сегмента стека. В результате получим её абсолютный адрес.

  3. 3.

    Прочитать значение переменной из памяти процесса ColorPix по её абсолютному адресу.

Корректность первого шага алгоритма мы можем проверить вручную с помощью отладчика OllyDbg. Он позволяет прочитать информацию сегмента TEB в удобном виде. Для этого дважды щёлкните по сегменту, который называется «Data block of main thread», в окне «Memory Map» отладчика. Вы увидите окно как на иллюстрации 3-11.

{caption: «Иллюстрация 3-11. Окно OllyDbg с информацией TEB»}

Окно TEB OllyDbg

Базовый адрес сегмента стека 00190000 указан во второй строчке открывшегося окна. Учтите, что этот адрес может меняться при каждом запуске приложения.

Поиск переменной в 64-битном приложении

Применим наш алгоритм поиска переменной для 64-битного приложения.

W> Отладчик OllyDbg не поддерживает 64-битные приложения, поэтому вместо него воспользуемся WinDbg.

Resource Monitor (монитор ресурсов) Windows 7 будет нашим приложением для анализа. Он распространяется вместе с ОС и доступен сразу после её установки. Разрядность Resource Monitor совпадает с разрядностью Windows. Чтобы запустить приложение, откройте меню Пуск (Start) Windows и введите следующую команду в строку поиска:

Иллюстрации 3-12 демонстрирует окно Resource Monitor.

{caption: «Иллюстрация 3-12. Окно приложения Resource Monitor»}

Resource Monitor

Найдём переменную, хранящую размер свободной памяти системы. На иллюстрации её значение подчёркнуто красной линией.

Прежде всего найдём сегмент, содержащий искомую переменную. Для этого воспользуемся 64-битной версией сканера Cheat Engine. Интерфейс его 64 и 34-битных версий одинаков, поэтому вам нужно выполнить те же действия, что и при анализе приложения ColorPixel.

В моем случае сканер нашёл две переменные с адресами 00432FEC и 00433010. Определим сегменты, в которых они хранятся. Чтобы прочитать структуру памяти процесса с помощью отладчика WinDbg, выполните следующие действия:

  1. 1.

    Запустите 64-битную версию WinDbg с правами администратора. Путь к нему по умолчанию: C:Program Files (x86)Windows ­Kits8.1Debuggersx64windbg.exe.

  2. 2.

    Выберите пункт главного меню «File» -> «Attach to a Process…». Откроется окно диалога со списком запущенных 64-разрядных процессов, как на иллюстрации 3-13.

{caption: «Иллюстрация 3-13. Диалог выбора процесса в отладчике WinDbg», height: «50%»}

Диалог выбора процесса WinDbg

  1. 1.

    Выберите в списке процесс «perfmon.exe» и нажмите кнопку «OK».

  2. 2.

    В командной строке отладчика, расположенной в нижней части окна «Command», введите текст !address и нажмите Enter. Структура памяти процесса отобразится в окне «Command», как на иллюстрации 3-14.

{caption: «Иллюстрация 3-14. Вывод структуры памяти процесса в окне Command»}

Структура памяти в WinDbg

Обе переменные с абсолютными адресами 00432FEC и 00433010 находятся в сегменте динамической памяти с ID 2. Границы этого сегмента: с 003E0000 по 00447000. Смещение первой переменной в сегменте равно 52FEC:

00432FEC — 003E0000 = 52FEC

Для бота алгоритм поиска переменной, хранящей размер свободной памяти ОС в приложении Resource Monitor, выглядит следующим образом:

  1. 1.

    Прочитать базовый адрес сегмента динамической памяти с ID 2. Чтобы получить доступ к этим сегментам, надо воспользоваться следующими WinAPI-функциями:

  2. 2.

    Добавить смещение переменной (в моем случае равное 52FEC) к базовому адресу сегмента. В результате получится её абсолютный адрес.

  3. 3.

    Прочитать значение переменной из памяти процесса.

Как вы помните, смещение переменной в сегменте динамической памяти обычно меняется при перезапуске приложения. В случае если приложение достаточно простое (как рассматриваемый нами Resource Monitor), порядок выделения динамической памяти может быть одним и тем же при каждом старте программы.

Попробуйте перезапустить Resource Monitor и найти переменную ещё раз. Вы получите то же самое её смещение в сегменте, равное 52FEC.

Мы рассмотрели адресное пространство Windows процесса. Затем составили алгоритм поиска переменной в памяти и применили его к 32 и 64-разрядному приложениям. В ходе этого мы познакомились с функциями отладчиков OllyDbg и WinDbg для анализа структуры памяти процесса.

id вернет целое число, уникальное и постоянное для этого объекта в течение его жизни.
Два объекта с неперекрывающимися временами жизни могут иметь одинаковое значение id().
CPython: это адрес объекта в памяти.

ctypes.cast(obj, type) возвращает новый экземпляр типа, который указывает на тот же блок памяти, что и obj.
type должен быть указателем, а obj должен быть объектом, который можно интерпретировать как указатель.

A = [1, 2, 3]
I = id(A)

import ctypes
a = ctypes.cast(obj=I, typ=ctypes.py_object)
print(a.value)  # [1, 2, 3]


Поиск значения в памяти процесса…

От:

EvgenLog

Россия

 
Дата:  14.08.05 20:00
Оценка:

Все, наверно, знакомы с программой Art Money… Когда-то она мне была очень полезна, но теперь это уже в прошлом, пришло время написать самому что-то подобное, для своих целей.
А целью моей является подключение к процессу и слежка за его памятью, теми переменными, которые в ней гуляют… А именно, нужно получить возможность считывать значение определённой переменной через определённый промежуток времени.
Как найти переменную в памяти я тоже почти разобрался, я так считаю, но на определённом этапе есть загвоздка, с неё и начнём…
Итак, насколько я знаю, чтобы найти в памяти определённую переменную (её значение) нужно: во-первых знать её текущее значение, во-вторых иметь возможность изменить это значение (я думаю, это не всегда обязательно). Затем, если вышеперечисленное возможно, то следует сделать дамп памяти процесса, затем изменить значение искомой переменной и сделать ещё раз дамп памяти. Потом нужно каким-то образом сравнить эти два дампа… И так пока не вычислишь эту самую переменную… Но вот здесь-то и проблема… Чтобы вычислить переменную, нужно ,видимо, проводить какие-то операции с «offset», а я ни как не могу разобраться… Что же нужно, всётаки, чтобы довести дело до конца и вычислить одновременно и адрес переменной и её значение?

Или может быть кто-то лучше разобрался как работает знаменитая программа…???


Re: Поиск значения в памяти процесса…

От:

AcidTheProgrammer

Россия

https://hts.tv/
Дата:  15.08.05 07:35
Оценка:

Здравствуйте, EvgenLog, Вы писали:

EL>Все, наверно, знакомы с программой Art Money… Когда-то она мне была очень полезна, но теперь это уже в прошлом, пришло время написать самому что-то подобное, для своих целей.

EL>А целью моей является подключение к процессу и слежка за его памятью, теми переменными, которые в ней гуляют… А именно, нужно получить возможность считывать значение определённой переменной через определённый промежуток времени.
EL>Как найти переменную в памяти я тоже почти разобрался, я так считаю, но на определённом этапе есть загвоздка, с неё и начнём…
EL>Итак, насколько я знаю, чтобы найти в памяти определённую переменную (её значение) нужно: во-первых знать её текущее значение, во-вторых иметь возможность изменить это значение (я думаю, это не всегда обязательно). Затем, если вышеперечисленное возможно, то следует сделать дамп памяти процесса, затем изменить значение искомой переменной и сделать ещё раз дамп памяти. Потом нужно каким-то образом сравнить эти два дампа… И так пока не вычислишь эту самую переменную… Но вот здесь-то и проблема… Чтобы вычислить переменную, нужно ,видимо, проводить какие-то операции с «offset», а я ни как не могу разобраться… Что же нужно, всётаки, чтобы довести дело до конца и вычислить одновременно и адрес переменной и её значение?

EL>Или может быть кто-то лучше разобрался как работает знаменитая программа…???

Занимаюсь практически такойже проблеммой.

Алгоритм такой:
1. Ищешь в памяти например все DWORDы значение которых равно введенному.
2. Если таких ячеек одна, то все выход 5.
3. Как-то меняешь значение, вмотришь в каких их найденных DWORDах это значение.
4. Идешь к пункту 2.
5. Выход

Вот так она и работала.

У меня встречный вопрос:
Мне кроме всего еще нужно и мониторить некоторые куски памяти на предмет измененния данных в них.
Вы не знаете как это можно сделать на WinAPI?

http://www.gravatar.com/avatar/60560936caa07b944d4c3cecf1c06cc5?s=80&d=identicon


Re: Поиск значения в памяти процесса…

От: Аноним

 
Дата:  15.08.05 08:29
Оценка:

Здравствуйте, EvgenLog, Вы писали:

EL>Все, наверно, знакомы с программой Art Money… Когда-то она мне была очень полезна, но теперь это уже в прошлом, пришло время написать самому что-то подобное, для своих целей.

EL>А целью моей является подключение к процессу и слежка за его памятью, теми переменными, которые в ней гуляют… А именно, нужно получить возможность считывать значение определённой переменной через определённый промежуток времени.
EL>Как найти переменную в памяти я тоже почти разобрался, я так считаю, но на определённом этапе есть загвоздка, с неё и начнём…
EL>Итак, насколько я знаю, чтобы найти в памяти определённую переменную (её значение) нужно: во-первых знать её текущее значение, во-вторых иметь возможность изменить это значение (я думаю, это не всегда обязательно). Затем, если вышеперечисленное возможно, то следует сделать дамп памяти процесса, затем изменить значение искомой переменной и сделать ещё раз дамп памяти. Потом нужно каким-то образом сравнить эти два дампа… И так пока не вычислишь эту самую переменную… Но вот здесь-то и проблема… Чтобы вычислить переменную, нужно ,видимо, проводить какие-то операции с «offset», а я ни как не могу разобраться… Что же нужно, всётаки, чтобы довести дело до конца и вычислить одновременно и адрес переменной и её значение?

EL>Или может быть кто-то лучше разобрался как работает знаменитая программа…???

1. Экспортируемую переменную делать не пробовал? если программа тобой писанна конечно.
2. Память для локальных переменных выделяется на стеке! ты уверен что найдя ее там, она будет постоянно там жить?


Re[2]: Поиск значения в памяти процесса…

От:

Maxim S. Shatskih

Россия

 
Дата:  15.08.05 10:06
Оценка:

ATP>Мне кроме всего еще нужно и мониторить некоторые куски памяти на предмет измененния
данных в них.
ATP>Вы не знаете как это можно сделать на WinAPI?

Весь API для юзер-модного отладчика был документирован. WaitForDebugEvent и прочее.

Занимайтесь LoveCraftом, а не WarCraftом!


Re[2]: Поиск значения в памяти процесса…

От:

EvgenLog

Россия

 
Дата:  15.08.05 18:14
Оценка:

Здравствуйте, AcidTheProgrammer, Вы писали:

ATP>Занимаюсь практически такойже проблеммой.


ATP>Алгоритм такой:

ATP>1. Ищешь в памяти например все DWORDы значение которых равно введенному.
ATP>2. Если таких ячеек одна, то все выход 5.
ATP>3. Как-то меняешь значение, вмотришь в каких их найденных DWORDах это значение.
ATP>4. Идешь к пункту 2.
ATP>5. Выход

ATP>Вот так она и работала.


ATP>У меня встречный вопрос:

ATP>Мне кроме всего еще нужно и мониторить некоторые куски памяти на предмет измененния данных в них.
ATP>Вы не знаете как это можно сделать на WinAPI?

Я вот не понял… Я получается не то делал… Я не искал определённый вид переменных… Я получается просто искал значение, которое изменяется в данный момент… Но ведь то, как Вы написали, помоему, правильней… Нужно искать определённый вид переменных, так будет логичнее. Но как это сделать? Не подскажете? Как например найти все DWORD переменные? Насколько я понимаю, дамп делать все равно нужно и искать уже в нём… Правильно?

Вам, к сожалению, помочь не могу на данный момент, но если сильно надо, то могу спросить у одного человека…


Re[2]: Поиск значения в памяти процесса…

От:

EvgenLog

Россия

 
Дата:  15.08.05 18:21
Оценка:

Здравствуйте, Аноним, Вы писали:

А>1. Экспортируемую переменную делать не пробовал? если программа тобой писанна конечно.

А>2. Память для локальных переменных выделяется на стеке! ты уверен что найдя ее там, она будет постоянно там жить?

Мне значения нужно в левой программе искать… В Art Money есть функция сохранения найденных адресов переменных… Т.е. ты один раз нашел значение, сохранил и в последующие разы можно уже не искать снова, а просто загрузить. Это означает, что переменные с каждой новой загрузкой приложения не меняют своё положение в памяти? Так получается? Но если тот кусок, который им был выделен, будет чем-то другим занят?


Re[3]: Поиск значения в памяти процесса…

От:

AcidTheProgrammer

Россия

https://hts.tv/
Дата:  16.08.05 12:53
Оценка:

Здравствуйте, EvgenLog, Вы писали:

EL>Здравствуйте, AcidTheProgrammer, Вы писали:


ATP>>Занимаюсь практически такойже проблеммой.


ATP>>Алгоритм такой:

ATP>>1. Ищешь в памяти например все DWORDы значение которых равно введенному.
ATP>>2. Если таких ячеек одна, то все выход 5.
ATP>>3. Как-то меняешь значение, вмотришь в каких их найденных DWORDах это значение.
ATP>>4. Идешь к пункту 2.
ATP>>5. Выход

ATP>>Вот так она и работала.


ATP>>У меня встречный вопрос:

ATP>>Мне кроме всего еще нужно и мониторить некоторые куски памяти на предмет измененния данных в них.
ATP>>Вы не знаете как это можно сделать на WinAPI?

EL>Я вот не понял… Я получается не то делал… Я не искал определённый вид переменных… Я получается просто искал значение, которое изменяется в данный момент… Но ведь то, как Вы написали, помоему, правильней… Нужно искать определённый вид переменных, так будет логичнее. Но как это сделать? Не подскажете? Как например найти все DWORD переменные? Насколько я понимаю, дамп делать все равно нужно и искать уже в нём… Правильно?

Ну типы вы не определите, а предположение сделать можно. Все зависит от того какго-ог типа значение вы ищите. Например значение типа BYTE ищите подрял, значения типа WORD скорее всего выровненны на 2, и ищите их с адреса кратного двум, типа DWORD скорее всего выровнены на 4, соответственно просматриваете ячейки в памяти с адреса кратного четырем и с шагом 4 байта и т.д. Если не знаете какого типа переменная, то ищите сначала по байтам, потом по словам, потом по двойным словам и все резулттаты сохраняете в мапу.

EL>Вам, к сожалению, помочь не могу на данный момент, но если сильно надо, то могу спросить у одного человека…

Буду очень вам признателен. Спросите пожалуйста, если можете.

http://www.gravatar.com/avatar/60560936caa07b944d4c3cecf1c06cc5?s=80&d=identicon


Re: Поиск значения в памяти процесса…

От: Аноним

 
Дата:  16.08.05 18:13
Оценка:

Здравствуйте, EvgenLog, Вы писали:

EL>Или может быть кто-то лучше разобрался как работает знаменитая программа…???

Поищи на DelphiKingdom, там был спец. компонент с исходниками. TMemoryInspector или как-то так.


Re[4]: Поиск значения в памяти процесса…

От:

EvgenLog

Россия

 
Дата:  16.08.05 18:33
Оценка:

Здравствуйте, AcidTheProgrammer, Вы писали:

ATP>Ну типы вы не определите, а предположение сделать можно. Все зависит от того какго-ог типа значение вы ищите. Например значение типа BYTE ищите подрял, значения типа WORD скорее всего выровненны на 2, и ищите их с адреса кратного двум, типа DWORD скорее всего выровнены на 4, соответственно просматриваете ячейки в памяти с адреса кратного четырем и с шагом 4 байта и т.д. Если не знаете какого типа переменная, то ищите сначала по байтам, потом по словам, потом по двойным словам и все резулттаты сохраняете в мапу.


EL>>Вам, к сожалению, помочь не могу на данный момент, но если сильно надо, то могу спросить у одного человека…


ATP>Буду очень вам признателен. Спросите пожалуйста, если можете.

Хорошо, я спрошу… Вот только не могли бы Вы написать код? Как это сделать? Я не знаком настолько глубоко с этим, но обещаю изучать!

Подождите ...

Wait...

  • Переместить
  • Удалить
  • Выделить ветку

Пока на собственное сообщение не было ответов, его можно удалить.

Переменные, адреса и указатели

Переменная — это область памяти, к которой мы обращаемся за находящимися там данными, используя идентификатор (в данном случае, имя переменной). При этом у этой помеченной именем области есть еще и адрес, выраженный числом и понятный компьютерной системе. Этот адрес можно получить и записать в особую переменную. Переменную, содержащую адрес области памяти, называют указателем.

Когда мы меняем значение обычной переменной, то, можно сказать, просто удаляем из конкретной области памяти данные и записываем туда новые. Когда мы меняем значение переменной-указателя, то начинаем работать с совершенно иным участком памяти, т.к. меняем содержащийся в ней адрес.

Тема указателей тесно связана с темой динамических типов данных. Когда программа компилируется, то под объявленные переменные так или иначе (в зависимости от того, где они были объявлены) выделяются участки памяти. Потом размер этих участков не меняется, может меняться только их содержимое (значения или данные). Однако именно с помощью указателей можно захватывать и освобождать новые участки памяти уже в процессе выполнения программы. Динамические типы данных будут рассмотрены позже.

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

int i = 0;
printf ("i=%d, &i=%p n", i, &i);

В результате выполнения данного программного кода на экране появляется примерно следующее (шестнадцатеричное число у вас будет другим):

i=0, &i=0x7fffa40c5fac 

Знак амперсанда (&) перед переменной позволяет получить ее адрес в памяти. Для вывода адреса переменной на экран используется специальный формат %p. Адреса обычных переменных (не указателей) в процессе выполнения программы никогда не меняются. В этом можно убедиться:

int a = 6;
float b = 10.11;
char c = 'k';
 
printf("%5d - %pn", a, &a);
printf("%5.2f - %pn", b, &b);
printf("%5c - %pn", c, &c);
 
a = 2; 
b = 50.99; 
c = 'z';
 
printf("%5d - %pn", a, &a);
printf("%5.2f - %pn", b, &b);
printf("%5c - %pn", c, &c);

Результат:

    6 - 0x7fff653532e0
10.11 - 0x7fff653532e4
    k - 0x7fff653532df
    2 - 0x7fff653532e0
50.99 - 0x7fff653532e4
    z - 0x7fff653532df

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

Зная адрес, можно получить значение, которое находится по этому адресу, поставив знак * перед адресом:

int a = 8;
printf("%d n", *&a);

На экране будет выведено число 8.

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

Указатели в языке C, как и другие переменные, являются типизированными, т.е. при объявлении указателя надо указывать его тип. Как мы узнаем позже, с указателями можно выполнять некоторые арифметические операции, и именно точное определение их типа позволяет протекать им корректно. Чтобы объявить указатель, надо перед его именем поставить знак *. Например:

int *pi;
float *pf;

Обратите внимание на то, что в данном случае * говорит о том, что объявляется переменная-указатель. Когда * используется перед указателем не при его объявлении, а в выражениях, то обозначает совсем иное — «получить значение (данные) по адресу, который присвоен указателю». Посмотрите на код ниже:

int x = 1, y, z = 3;
int *p, *q;
 
p = &x;
printf("%dn", *p); // 1
 
y = *p;
printf("%dn", y); // 1
 
*p = 0;
printf("%d %dn", x, y); // 0 1
 
q = &z;
printf("%dn", *q); // 3
 
p = q;
*p = 4;
printf("%dn", z); // 4
 
printf("%p %pn", p, q); // p == q

С помощью комментариев указаны текущие значения ячеек памяти. Подробно опишем, что происходит:

  1. Выделяется память под пять переменных: три типа int и два указателя на int. В ячейки x и z записываются числа 1 и 3 соответственно.
  2. Указателю p присваивается адрес ячейки x. Извлекая оттуда значение (*p), получаем 1.
  3. В область памяти, которая названа именем у, помещают значение равное содержимому ячейки, на которую ссылается указатель p. В результате имеем две области памяти (x и y), в которые записаны единицы.
  4. В качестве значения по адресу p записываем 0. Поскольку p указывает на x, то значение xменяется. Переменная p не указывает на y, поэтому там остается прежнее значение.
  5. Указателю q присваивается адрес переменной z. Извлекая оттуда значение (*q), получаем 3.
  6. Переменной p присваивается значение, хранимое в q. Это значит, что p начинает ссылаться на тот же участок памяти, что и q. Поскольку q ссылается на z, то и p начинает ссылаться туда же.
  7. В качестве значения по адресу p записываем 4. Т.к. p является указателем на z, следовательно, меняется значение z.
  8. Проверяем, p и q являются указателями на одну и туже ячейку памяти.

Под сам указатель (там, где хранится адрес) также должна быть выделена память. Объем этой памяти можно узнать с помощью функции sizeof():

int *pi;
float *pf;
 
printf("%lun", sizeof(pi)); 
printf("%lun", sizeof(pf));

Под указатели всех типов выделяется одинаковый объем памяти, т.к. размер адреса не зависит от типа, а зависит от вычислительной системы. В таком случае, зачем при объявлении указателя следует указывать тип данных, на который он может ссылаться? Дело в том, что по типу данных определяется, сколько ячеек памяти занимает значение, на которое ссылается указатель, и через сколько ячеек начнется следующее значение.

Если указатель объявлен, но не определен, то он ссылается на произвольный участок памяти с неизвестно каким значением:

int *pa, *pb;
float *pc;
 
printf(" %p %p %pn", pa, pc, pb);
 
// может возникнуть ошибка
printf(" %d %fn", *pa, *pc); 

Результат (в Ubuntu):

 0x400410 0x7fff5b729580 (nil)
 -1991643855 0.000000

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

int a = 5;
float c = 6.98;
int *pa;
float *pc;
 
pa = NULL;
pc = NULL;
 
printf(" %15p %15pn", pa, pc);
 
// Error
// printf(" %15d %15fn", *pa, *pc);
 
pa = &a;
pc = &c;
 
printf(" %15p %15pn", pa, pc);
printf(" %15d %15fn", *pa, *pc);

Результат (в Ubuntu):

           (nil)           (nil)
  0x7ffd8e77e550  0x7ffd8e77e554
               5        6.980000

В данном случае, если попытаться извлечь значение из памяти с помощью указателя, который никуда не ссылается, то возникает «ошибка сегментирования».

На этом уроке вы должны понять, что такое адрес переменной и как его получить (&var), что такое переменная-указатель (type *p_var; p_var = &var) и как получить значение, хранимое в памяти, зная адрес ячейки (*p_var). Однако у вас может остаться неприятный осадок из-за непонимания, зачем все это надо? Это нормально. Понимание практической значимости указателей придет позже по мере знакомства с новым материалом.

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

Курс с решением части задач:
pdf-версия, android-приложение

Понравилась статья? Поделить с друзьями:
  • Повернулся экран на мониторе как исправить
  • Вк стал черным как исправить фон на телефоне
  • Как найти exe forza horizon 4
  • Как найти критическую нагрузку
  • Как найти девушку кореянку