Перейти к содержанию

Как я Cradle of Rome 2 лечил


Рекомендуемые сообщения

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

Для тех кто в нее играл (а таких, наверное, довольно много), вкратце опишу что она собой представляет, и какая задача передо мной стояла. А представляет она из себя обычный казуальный Match3 с двумя экранами: карта Рима, на которой можно строить различные строения и собственно match3 игра. Из отличий от остальных казуальных игр - на match3 экране есть бонусы, они становятся доступными после постройки определенных строений. У каждого бонуса есть энергия. Она пополняется, если соединить фишки соответствующие этому бонусу. Кроме того есть режим турнира, в котором нужно очистить ировое поле за заданное време. Вот на этом то режиме араб и застрял.

Итак, тренер должен:

1) устанавливать заданное количество ресурсов,

2) автоматически пополнять энергию у бонусов,

3) останавливать время в режиме турнира.

littlegame.png

С чего бы начать? С поиска какого-нибудь простого значения с помощью Art of Money. Идеально для поиска подходит количество ресурсов (неважно каких). Они скорее всего хранятся в двойном беззнаковом слове. Их легко изменить в игре, чтобы отсеять мусор. Благодаря повериям казуальщиков, это достаточно большое число, и мусора будет не слишком много.

Ищу и отсеиваю. Отсеиваю и ищу. После второй же итерации, остаются 3 ячейки памяти, в которых ничего не меняется.

artmoney.png

Теперь в дело вступает OllyDbg. Устанвливаю точку останова на чтение всех трех найденных ячеек. На какой первой остановится, там и здорово. Подкидываю золотишка. Остановилось. Здорово. Смотрю на код.

ollydbgcondstop.png

А в коде происходит следующее: сначала считывают текущее количество золота(eax) из массива(edx) по заданному индексу (ecx). Затем прибавляют аргумент функции ([ebp+0xc]), и полученное значение записывают обратно. Смотрю откуда берется массив. Массив берется из [ecx+0x7F0]. Смотрим что такое ecx (кроме того что регистр, конечно :-)). А ecx у нас берется из локальной переменной (ebp-78), в которую он сохраняется в самом начеле функции. Значит функция - член класса. Ecx - указатель на this. Для лучшего понимая происходящего пишу псевдокод:

void Game::AddResources (DWORD increment, DWORD resourceIndex)
{
DWORD currentResource;
currentResource = this->resources[resourceIndex];
currentResource += increment;
this->resources[resourceIndex] = currentResource;
}

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

ollydbgafterstack.png

Тут небольшая последовательность присвоений.

mov edx, [0xA5ABB0]
mov eax, [edx]
mov [ebp-0xC4], eax
mov ecx, [ebp-0xC4]

И получается отлично! Указатель на игру хранится в глобальной переменной, а значит я в любой момент времени могу его вычислить. Опять пишу псевдокод, что делать когда у меня будет указатель.

DWORD* GlobalPointer = (DWORD*) 0xA5ABB0;
BYTE* GamePointer = GlobalPointer[0];// это чтобы со смещением не путаться
DWORD* ResourcesPointer = GamePointer[0x7F0];
ResourcesPointer[resourcesId] = newResource;

Добавляю поочередно ресурсы. Смотрю на изменение значений в массиве (дамп OllyDbg). Составляю табличку смещений массива:


int MONEY_OFFSET = 1; //Золото
int WOOD_OFFSET = 2; //Ресурсы
int int FOOD_OFFSET = 3; //Еда
int SCORE_OFFSET = 4; //Очки

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


int LIFES_OFFSET = 5; // жизни
int LEVEL_OFFSET = 6; // уровень

Обращаю внимание на ряд единичек и нолей. Они обязательно должны что-то означать. Создаю новый профиль в игре, начинаю заново. И точно, означают. Доступен или не доступен бонус. Значит где-то здесь есть и заряд бонусов. Есть. Такой же ряд байт, минимум 0 (разряжен), максимум 0xC4 (заряжен). Странный выбор максимума, кстати.

Пишу таблицу для бонусов.


int HAMMER = 0x0;
int BOMB = 0x1;
int FIREBALL = 0x2;
int HOURGLASS = 0x3;
int LIGHTNING = 0x4;
int MEGABOMB = 0x5;
int MIXER = 0x6;
int STAR = 0x7; //Всякие бонусы
int BONUSAMOUNT_OFFSET = 0x4C; //Смещение для заряда
int BONUSENABLED_OFFSET = 0x7A; //Смещение для индикатора доступности

С этим понятно. Перехожу в режим турнира. Смотрю на память в дампе, и нахожу бонусы для турнира.


int TOURNBONUSAMOUNT_OFFSET = 0x164; //Смещение для заряда в турнире
int TOURNBONUSENABLED_OFFSET = 0x192; //Смещение для индикатора доступности в турнире

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

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

ollydbgtimestop.png

Заменяю JNZ на JMP и получаю результат! Таймер стоит, побочных эффектов не обнаружено. Конечно, патчить код не так здорово как данные, но разбирать дальше что происходит нет ни желания, ни времени. И так ведь работает :-).

  • Плюс 1
Ссылка на комментарий
Поделиться на другие сайты

....Смотрю откуда берется массив. Массив берется из [ecx+0x7F0]. Смотрим что такое ecx (кроме того что регистр, конечно :-)). А ecx у нас берется из локальной переменной (ebp-78), в которую он сохраняется в самом начеле функции. Значит функция - член класса. Ecx - указатель на this. Для лучшего понимая происходящего пишу псевдокод:

void Game::AddResources (DWORD increment, DWORD resourceIndex)
{
DWORD currentResource;
currentResource = this->resources[resourceIndex];
currentResource += increment;
this->resources[resourceIndex] = currentResource;
}

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

1. Почему ты считаешь что это "объект", а не структура? Какие отличия объекта от структуры? (каверзный вопрос с прицелом на второй)

2. У тебя есть утверждение из цитаты выше:

А ecx у нас берется из локальной переменной (ebp-78), в которую он сохраняется в самом начеле функции. Значит функция - член класса. Ecx - указатель на this.

Как ты узнаёшь что функция является членом класса? Точнее как ты пришёл к такому выводу?

Если ты определял это по тому что функция работает с ecx ведь это ещё не значит что эта функция член класса (кстати о каком классе идёт речь, да и потом может быть там класса вообще нет а ecx указатель на структуру)...

...Другое дело писать псевдокод по метаданным (правда я эту часть не исследовал, знаю что эти метаданные вшиваются компилятором). Просто подразумевается что ты по каким-то соображением построил псевдокод "Game::AddResources"

void Game::AddResources (...)

Но про метаданные ты ничего не упоминаешь и никто не понимает как ты пришёл к выводу что функция является членом класса. Что ты работаешь именно с объектом...

На С++ можно написать функции которые никак не связаны с классом и экземпляром класса и эти функции без метаданных нельзя относить к членам класса.

Если я не прав, прошу поправить :)

Ссылка на комментарий
Поделиться на другие сайты

1. Почему ты считаешь что это "объект", а не структура? Какие отличия объекта от структуры? (каверзный вопрос с прицелом на второй)

В основном, конечно по вызову. Регистр ECX используется в функции без инициализации. Инициируется перед вызовом функции. Сейчас еще посмотрел откуда берется эта структура/объект.


push 8F0h
call sub_6BAD9B
add esp, 8
mov [ebp+var_14], eax

Функция sub_6BAD9B вызывается не один раз из разных мест. В дереве вызовов функции sub_6BAD9B находится функция HeapAlloc. В функцию sub_6BAD9B каждый раз передаются разные числа (и я уверен что это размер).

Я очень верю что это какой-то свой оператор new.

Потом идет функция которая инициализирует ту память которую мы выделили. И я очень верю что это конструктор.

Еще человек использует у себя boost, и я очень не верю что он все пишет на структурах.

Но что это функция-член, я конечно подумал когда увидел что в нее передается указатель на что-то в регистре ECX. Как то не встречал чтобы параметры передавались таки образом если не класс

Ссылка на комментарий
Поделиться на другие сайты

хм... интересно.

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

Если дизассемблерный код функции работает с указателем на ecx это ещё не значит, что ecx указатель на объект (но я не уверен, хотя всем известно что компилятор C++ по ecx передаёт указатель на объект). Если у объекта есть конструктор, то и у структуры данных может быть своя функция похожая на конструктор... По поводу "boost" я к не в курсе что это за зверь.

Программист мог хорошо взяться за оптимизацию и отказаться где-то от ООП заменив объект простыми структурами.

Интересно узнать следующее.

1) Как будет передаваться указатель на объект в функцию член в дизассемблере.

2) Как будет передаваться указатель на обычную структуру в функцию не принадлежащую никакому классу.

3) Будут ли какие-то отличия

Я бы и сам бы проверил написав тестируемый код. Да вот интересно, кто как думает, а то обычно по таким вещам (связанные с ООП и дизассемблированием) только мне одному доводится заморачиваться.

Ссылка на комментарий
Поделиться на другие сайты

хм... интересно.

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

Если дизассемблерный код функции работает с указателем на ecx это ещё не значит, что ecx указатель на объект (но я не уверен, хотя всем известно что компилятор C++ по ecx передаёт указатель на объект). Если у объекта есть конструктор, то и у структуры данных может быть своя функция похожая на конструктор... По поводу "boost" я к не в курсе что это за зверь.

Программист мог хорошо взяться за оптимизацию и отказаться где-то от ООП заменив объект простыми структурами.

Интересно узнать следующее.

1) Как будет передаваться указатель на объект в функцию член в дизассемблере.

2) Как будет передаваться указатель на обычную структуру в функцию не принадлежащую никакому классу.

3) Будут ли какие-то отличия

Я бы и сам бы проверил написав тестируемый код. Да вот интересно, кто как думает, а то обычно по таким вещам (связанные с ООП и дизассемблированием) только мне одному доводится заморачиваться.

1) Если вызывается что-нибудь типа


someObject->member();
//то это будет
move ecx, someObj
call member
//ну это если наследования нет и всего такого прочего

2) А если что нибудь типа


someClass* someObject;
function(someObject);
//то это будет
push someObject
call function

Между структурами и классами различий не будет, потому как если я ничего не путаю, то структура - это тот же класс, только у нее поля по умолчанию public.

Ссылка на комментарий
Поделиться на другие сайты

Я бы и сам бы проверил написав тестируемый код. Да вот интересно, кто как думает, а то обычно по таким вещам (связанные с ООП и дизассемблированием) только мне одному доводится заморачиваться.

а вот и код:



//Определение классов и структур
struct SomeStruct
{
int classData;
void structMethod();
};

class SomeClass
{
int structData;
public:
void classMethod();
};

void SomeStruct::structMethod()
{
}

void SomeClass::classMethod()
{
}

void StructFunction(SomeStruct* testStruct)
{
}

void ClassFunction(SomeClass* testObject)
{
};

//Дизассемблер
testStruct->structMethod();
0040168A 8B 4D F4 mov ecx,dword ptr [ebp-0Ch]
0040168D E8 4E FE FF FF call SomeStruct::structMethod (4014E0h)
testObject->classMethod();
00401692 8B 4D E8 mov ecx,dword ptr [ebp-18h]
00401695 E8 76 FE FF FF call SomeClass::classMethod (401510h)
StructFunction(testStruct);
0040169A 8B 45 F4 mov eax,dword ptr [ebp-0Ch]
0040169D 50 push eax
0040169E E8 9D FE FF FF call StructFunction (401540h)
004016A3 83 C4 04 add esp,4
ClassFunction(testObject);
004016A6 8B 45 E8 mov eax,dword ptr [ebp-18h]
004016A9 50 push eax
004016AA E8 C1 FE FF FF call ClassFunction (401570h)
004016AF 83 C4 04 add esp,4

Как видим, экземпляры классов и структур не отличаются ни в дизассемблере, ни в исходнике. (не считая ключевого слова public в определении класса).

это в MSVS 2008 без оптимизации. в других компиляторах с другими параметрами может быть и по-другому

  • Плюс 1
Ссылка на комментарий
Поделиться на другие сайты

Спасибо за интересное исследование :) Добавил +1 к посту выше.

P.S. Просто напоминаю. Повторюсь о том что писал в личном сообщении. Если картинки с хостинга пропадут, то эту статью сложно будет понять. Картинки на хостинге вряд ли хранятся неограниченный срок. Дело конечно твоё и решать тебе ;)

Ссылка на комментарий
Поделиться на другие сайты

×
×
  • Создать...

Важная информация

Находясь на нашем сайте, Вы автоматически соглашаетесь соблюдать наши Условия использования.