Взламываем игру-головоломку «Сапер»

в 17:13, , рубрики: reverse engineering, игры, Песочница, метки: , ,
Intro

Одним поздним вечером, начитавшись статей об исследовании различного ПО и насмотревшись различного рода видео про взломы игр и прочее, у меня вдруг тоже возникло желание повозиться под дебагерром с чем-нибудь интересным. Крякингом я занимаюсь сравнительно давно, поэтому практический опыт имеется. Поначалу я, как и многие, просто искал различные CrackME в сети и взламывал их с целью обучения, затем перешел на взломы платных приложений(поиск/подбор ключей) и написание различного рода KeyGen`ов. В данный момент «набиваю руку» и пытаюсь оттачивать мастерство взлома.
Ну да ладно, это лирическое отступление от сути. Теперь определимся с некоторыми деталями.

В данной статье главным объектом внимания для нас будет компьютерная игра «Сапер». Взламывать мы будем различные «штучки» одной игры для достижения единой цели.
Исследование и последующая отладка приложения происходят под Windows 7 x64 (реализация игры «Сапер» отличается в различных версиях OS Windows).

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

Окей, c деталями разобрались, приступим же к делу!

Наверняка, почти каждый, кто использует Windows, когда-либо имел дело с игрой «Сапер». В «семерке» игра выглядит так:

image

Это стандартное поле( в данном случае 16x16 клеток ). Мы будем ломать игру на среднем уровне сложности, т.е на уровне «Любитель». В общем-то для уровней сложности «Новичок» и «Профессионал» эта статья также будет актуальна, ничего кроме времени, количества мин и размеров поля не изменится.

1 этап

Итак, открываем наш CheatEngine( в дальнейшем я буду использовать сокращение CE ) и аттачимся к процессу игры:

image

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

1) Вводим в поле «Value» первоначальное количество открытых клеток, т.е ноль, нажимаем на «First Scan»
2) Переходим в игру и кликаем на случайной клетке, после переходим в CE и в поле «Scan Type» выбираем «Increased Value»(значение увеличилось), жмеж Next Scan

Делаем похожие действия, пока не найдем то самое, заветное значение:

image

Мы отыскали адрес, в котором хранится другой адрес, который, в свою очередь, хранит статическое значение.
Добавляем значение в «AddressList». Теперь если немного поиграть в игру, можно будет заметить, что значение меняется.

Теперь ищем asm-инструкцию, которая как-то взаимодействует с данным значением( меняет, читает ):

image

Открываем игру и еще немного играем, затем видим такую картину:

image

Ага, очень интересно :) Особенный интерес представляют 1 и 3 инструкции, так как они во-первых пишут в память, а во вторых они схожи и в общем счете выполняются аж 3 раза! Вот мы и переходим в дебаггер, выделив 1 или 3 инструкцию и нажав на кнопку «Show Disassembler».

Так, так, так, все становится интереснее и интереснее! Особый интерес представляет вот эта цепочка с заверщающим сравнением( cmp ):

image

Если повесим «бряк» на сравнение «cmp edx,eax», то при возврате в игру и попытке кликнуть на клетку, бряк сработает. При чем, как при клике на клетку с миной, так и при клике на обычную клетку. А что это значит? А значит это то, что где-то здесь происходит «распознавание» того, что находится под в закрытой клетке: пустота, мина или цифра. Пробуем изменить это значение на какое-либо бессмысленно сравнение, например, на такое:

image

Здесь, как некоторые поняли, главная задача — активация процессорного флага «Z», которая происходит в случае, если оба операнда инструкции CMP эквивалентны.
Возвращаемся в игру и кликаем по какой-либо закрытой клетке. В итоге:

image

Хах, прикольно! Оказывается, это была проверка " на выигрывание игры ", которая происходит при каждом клике по клетке поля, что в общем-то логично. Уже неплохо, но мы ведь хотим именно играть, минуя все мины, а не тупо выигрывать игру при открытии первой же клетки поля, верно? Так что, продолжаем наше исследование.

2 этап

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

image

Попытаемся отыскать границы функции( блока инструкций ), в которой мы находимся в данный момент( где находится инструкция ):

image

Ага, и что же мы видим:

image

Нас визуально перекинуло в начало текущей функции. Это нам пока что еще ни о чем важном не говорит, но если поразмыслить, обдумать, все что имеем, то можем прийти к выводу, что данная функция может быть как-либо связана с графикой игры, например. К такому выводу можно прийти, исходя из 1 этапа, где мы отыскали проверку игры «на выигрывание», что, скорее всего, сказывается на отрисовке поля. Ладно, проверим эту теорию. Спускаемся от начала функции немного вниз, где вскоре обнаружим весьма интересную инструкцию:

image

Это первое сравнение в данной функции… Хм, попробуем поставить бряк на инструкцию, затем переходим в игру:

image

Мы не можем перейти в игру. Почему? Да потому что наша теория подтвердилась! Эта функция и правда связана с графикой игры. При каждой активации окна и прочем взаимодействии с игровым интерфейсом вызывается эта функция, а в ней установлен BreakPoint на некотором сравнении => активировать окно и «Сапёрить» мы не сможем, пока не снимем бряк. Снимаем его. Есть вероятность, что данное сравнение играет ключевую роль в последующем поведении всей функции. Попробуем изменить данное сравнение на бессмысленно сравнение, чтобы флаг процессора «Z» активировался, как это было сделано в первом этапе:

image

Размер новой инструкции( 2 байта ) в 2 раза меньше, чем тот, что был изначально( 4 байта ), следовательно, добавились инструкции «nop» в 1 байт, чтобы «Занопалось» оставшееся пространство в 2 байта. Переходим в игру и пытаемся играть. Тыкаем по клеткам, натыкаемся на мину, и… ничего не произошло! Хм, не удивительно. Ладно, попробуем не включать, а выключать флаг процессора «Z». Для этого надо заменить сравнение на такое, чтобы два сравниваемых элемента никогда не были равными. Для этого восстанавливаем изначальную инструкцию:

cmp dword ptr [rax+38],01

на сравнение содержимого указателя, например, с отрицательным числом( с -1 ):

image

Теперь возвращаемся в игру и начинаем «саперить». При клике на некоторые области, они отрисовываются с некоторым запозданием, или их «нутро» отрисовывается лишь со второго раза. Оно и верно, ведь мы нагло влезли в графическую функцию и беспощадно отдебажили её :). Зато при клике на «опасные области» с минами ничего не происходит вообще!
Результат:

image

Профит :) Мы хакнули игру «Сапер» из стандартного комплекта игр от Microsoft.

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

Автор: Asen

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js