PDA

Просмотр полной версии : Чтож вы такое, Оффсеты?


dwa83
18.10.2020, 04:25
Очень часто на форуме встречаются посты, из которых видно, что многие не могут понять, что такое офсеты.
И я подумал, если попробовать "разжевать" данную тему, вопросов будет меньше.
Я не профессионал в программировании(просто хобби), потому, если будут недочёты, прошу извинить и поправить.

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

Для работы с динамически созданными экземплярами данных и нужны указатели.

Предлагаю написать простенькую программу, и на примере её разобрать что к чему.
Я буду использовать C++Builder6.

Создадим новый проект и сохраним в отдельную папку. Так же для удобства создадим два файла в папке проекта с названиями structs.h и structs.cpp. В них напишем наши тестовые структуры, которые и будем рассматривать.

Тут тестовая структура Party, содержащая указатель на цепочку добавляемых элементов, описывающих параметры членов пати PartyMember.

structs.h

#ifndef STRUCTS_H
#define STRUCTS_H

#include <vcl.h>


//структура члена пати
class PartyMember
{
public:
PartyMember(); //конструктор

int level; //уровень
int hp; //жизни
PartyMember * next; //указатель на следующего члена пати, или NULL, если последний
};

// структура пати
class Party
{
public:
Party(); //конструктор
~Party(); //деструктор
void AddRandomMember(); //добавление рандомного члена в пати
void Clear(PartyMember * m);//очистка
void DecHP(PartyMember * m);//уменьшение на 1 ХП всех членов пати
void GetInfo(TListBox * lb);//вывод инфы о всех членах
int count; //количество сопартийцев
PartyMember * chain; //указатель(адрес) на первого члена пати, либо NULL, если их нет

};

#endif //STRUCTS_H


structs.cpp
#include "structs.h"

//конструктор структуры пати
Party::Party()
{
count = 0; //в начале ни одного члена нет
chain = NULL; //обнулим адрес первого члена
}

//деструктор пати
Party::~Party()
{
if (chain != NULL) Clear(chain); //удалим цепочку членов, если она не пуста
count = 0;
}

//добавление члена в конец цепочки
void Party::AddRandomMember()
{
if (chain == NULL) //если в пати ещё нет членов
{
//динамически выделим память, chain будет содержать адрес первого
chain = new PartyMember(); //тут вызовется конструктор члена пати
count++;
return;
}
//если это не первый член, "присоединим" его к последнему
PartyMember * m = chain;
while (m->next != NULL) m = m->next;
m->next = new PartyMember();
count++;
}

//очистка пати
void Party::Clear(PartyMember * m)
{
//рекурсивно удалим членов
if (m == NULL) return;
Clear(m->next);
delete m; //освободим память, занятую текущим членом
m = NULL; //адрес обнулим, чтоб не указывал на уже свободные адреса
}

//уменьшение ХП всех членов в цепочке
void Party::DecHP(PartyMember * m)
{
if (m == NULL) return;
DecHP(m->next);
if (m->hp > 0) m->hp--;
}

void Party::GetInfo(TListBox * lb)
{
lb->Clear();
PartyMember * curent = chain;

int num = 0;
while (curent != NULL)
{
lb->Items->Add("№" + IntToStr(num) + " Level - " + IntToStr(curent->level) + " HP - " + IntToStr(curent->hp)+ " ADDR - " + IntToStr(int(curent)));
curent = curent->next;
num++;
}
}

//конструктор члена пати
PartyMember::PartyMember()
{
level = 10 + 90*(rand()/float(RAND_MAX));
hp = 1000 + 9000*(rand()/float(RAND_MAX));
next = NULL; //адрес следущего члена пока обнулён
}



На форму кинем ListBox, в который будем выводить данные по персонажам. А так же адрес переменной структуры пати.
И одну кнопку, при нажатии на которую у членов пати будут убывать ХП и обновляться информация в листбоксе.
Подключим наш заголовочник в главном модуле, а так же подключим файл реализации к проекту.

//---------------------------------------------------------------------------

#include <vcl.h>
#pragma hdrstop

#include "Unit1.h"

#include "structs.h"



//---------------------------------------------------------------------------
#pragma package(smart_init)
#pragma resource "*.dfm"
TForm1 *Form1;




Party party;
...

Тут же в главном модуле объявим глобальную переменную paty.

В событии создания формы мы должны добавить туда к примеру 3 члена, и вывести инфо о них.
void __fastcall TForm1::FormCreate(TObject *Sender)
{
party.AddRandomMember();
party.AddRandomMember();
party.AddRandomMember();
party.GetInfo(ListBox1);
ListBox1->Items->Add("party ADDR = " + IntToStr(int(&party)));
}

А в событии кнопки вызовем функцию уменьшения хп и опять же обновим инфо в листбоксе.
void __fastcall TForm1::Button1Click(TObject *Sender)
{
party.DecHP(party.chain);
party.GetInfo(ListBox1);
ListBox1->Items->Add("party ADDR = " + IntToStr(int(&party)));
}

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

Теперь давайте запустим её, подцепимся к ней с помощью Cheat Engine и с помощью темы [Ссылки могут видеть только зарегистрированные и активированные пользователи] попытаемся найти базовый адрес(и не только).
Сначала найдём ХП последнего члена в списке(если не с первого раза нашли, кнопкой уменьшаем ХП и отсеиваем, пока не останется одно значение).

В поле Description лучше сразу писать те значения, которые прибавляются к значениям регистров в окошке MoreInformation
[Ссылки могут видеть только зарегистрированные и активированные пользователи]
И далее как в вышеуказаной теме ищем адреса, пока не доберёмся до базового адреса(в данном случае базового адреса модуля Project1.exe, который кстати меняется после перезапуска).

Получится примерно такая картина.
[Ссылки могут видеть только зарегистрированные и активированные пользователи]
В итоге мы добравшись до базового адреса нашли эту самую цепочку офсетов(смещений), которая ведёт до значения ХП третьего члена пати.
BA + 48C4 + 8 + 8 + 4 = HP
Но что это за значения, и откуда они взялись? Давайте разбираться.

Сначала переведём значение адреса party ADDR (последняя строчка листбокса нашей программки) в HEX и получим 004048C0.
А как мы помним, в программе это адрес нашей глобальной переменной Paty.
Но последняя строчка списке адресов Cheat Engine имеет значение 004048C4.
Ага, почти тоже самое, но на 4 больше. Смотрим на структуру нашей Paty и видим, первое поле int count; 4 байта - это значение лежит в начале нашей структуры(на объявления функций не смотрим), то-есть по адресу 004048C0.
А за ней на 4 байта дальше(по адресу 004048C4) находится PartyMember * chain.
Так как наша переменная party - глобальная, её адрес всегда будет один и тот же, и смещение от базового адреса до неё будет одно и то же. Делаем вывод, что + 48C4 - это и есть оффсет(смещение) до структуры party от базового адреса, но не до начала, а до второго её поля. A там лежит адрес структуры первого члена.

Далее по всей логике стройкой выше идёт смещение +8 от начала структуры первого члена. Смотрим в код и видим, что от начала структуры члена пати на 8 байт у нас описано PartyMember * next - указатель на начало структуры следущего члена. То-есть по этим последовательным смещениям мы идём по цепочке адресов членов пати, но последняя строчка со смещением +04. А в структуре на 4 от начала у нас описано int hp;

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

Чтоб было нагладнее, можно проилюстрировать наш примёр.
[Ссылки могут видеть только зарегистрированные и активированные пользователи]

monahus
15.11.2020, 12:26
Мне казалось, что я хорошо понимаю что такое офсеты. Но почитав сей пост перестал понимать.

ScythLab
17.11.2020, 23:40
Но почитав сей пост перестал понимать.А тема весьма интересная и годная. По крайней мере если ты действительно понимаешь о чем пишет автор, то все понятно :), но вот насколько доходчив этот материал для неподготовленных читателей мне тяжело судить