Я думаю, вы неправильно понимаете цель Cheat Engine.
CE позволяет изменять значения, которые надежно хранятся в памяти. Например, в куче или в статических данных программы.
Например, объекты C ++ размещаются детерминированным образом и, следовательно, они никогда не перемещаются. Вот почему на них может ссылаться указатель, который остается постоянным в течение всего времени жизни объекта. Этот объект когда-то принадлежит другому. Если вы нашли указатель на объект-владелец, вы нашли то, что называется базовый указатель .
Например:
class Object
{
bool dummy;
int someField;
Object* child;
};
Теперь представьте, что у вас есть вложенное дерево 3 Object
. Это означает, что у вас есть корень Object
(n ° 1), чей child
- это другой Object
(n ° 2), чей child
- это другой Object
(n ° 3). Представьте, что вы делаете что-то вроде этого:
int main(int argc, char** argv)
{
Object root; // n°1
root.child = new Object(); // n°2
root.child->child = new Object(); // n°3
return 0;
}
Вы заинтересованы в том, чтобы возиться со значением n ° 3 someField
. Вы знаете, что адрес someField
, относительно Object
, равен +sizeof(bool) = 1
.
Так что (void*)&(object n°3) + 1
- это указатель на someField
, который вы хотите.
Теперь, как найти указатель на объект № 3?
Зная, что относительный адрес child
равен +sizeof(bool)+sizeof(int) = 5
. Мы знаем, что указатель на объект n ° 3 равен (void*)&(object n°2) +
5.
То же самое касается адреса объекта № 2, я оставлю это как упражнение.
А как насчет объекта № 1? Это не выделено в куче. Это в стеке. Дерьмо. Поэтому мы должны найти другой способ найти адрес, где хранится объект n ° 1.
Локальные переменные хранятся в стеке. В сборке они идентифицируются по их смещению относительно регистра EBP
(или ESP
, если функция не меняет стек).
EBP
- верхняя часть стека, а ESP
- нижняя часть стека.
В этом примере:
function foo()
{
int a;
double b;
}
Когда вызывается foo, стек будет увеличен настолько, чтобы вместить a и b, то есть sizeof (int) + sizeof (double) или 12 байтов. a будет храниться в EBP - sizeof(int) = EBP - 4
(так же, как ESP + 8
), а b будет храниться в EBP - sizeof(int) - sizeof(double) = EBP - 12
(так же, как ESP
). Внимание! Компилятор может изменить этот порядок, поэтому порядок объявления ваших переменных не обязательно одинаков в памяти. Оптимизация также может полностью изменить это. Но давай все будем в порядке, хорошо?
Итак, вернемся к нашему основному примеру. Какие локальные переменные у нас есть? только корень. Поэтому корень будет расположен на EBP - 9
напрямую. Но это, ТОЛЬКО когда main - это функция поверх стека вызовов. Без отладчика вы не сможете этого сделать.
Предположим, что наш EBP
равен 0028FF28, когда вызывается main (взято из только что скомпилированной программы на C).
корень тогда в (0x0028FF28 - 9) = 0x0028FF1F;
указатель на root.child
находится в (0x0028FF1F + 5) = (0x0028FF24);
Следовательно, root.child
расположен в * 0x0028FF24.
Указатель на root.child->child
находится в (* 0x0028FF24 + 5) = (скажем, 10000)
Тогда root.child->child
находится на * 10000.
Наконец, root.child->child.someField
находится на * 10000 + 3.
Подводя итог: вам просто нужно найти статический адрес root, чтобы найти остальные.
root не находится в куче или любой другой долговременной памяти, но находится в стеке main, который длится почти всю программу, так что он почти как постоянный.
CE помогает найти статический адрес путем сканирования всего пространства памяти процесса
Имея все это в виду, вы должны быть в состоянии вычислить относительный адрес hp
в стеке и найти статический указатель на него (main очень, очень, очень вероятно, будет иметь статический адрес кадра каждый раз, когда вы запустить программу). Если вам нужна помощь, используйте отладчик! Я рекомендую Immunity Debugger.