Исследуем игру «Сапёр» (продолжение)
Продолжаем наши исследования игры «Сапёр» от Microsoft.
Данная статья является продолжением моей первой статьи. Многое связано с тем, что было описано в статье №1.
О чем будет идти речь в данной статье:
1) Взлом, основанный на переполнении буфера
2) Взлом игровых мин( и прочие шалости, связанные с этим )
3) Исследование архитектуры игры.
I
Итак, приступим. Первым делом открываем саму игру, следом за ней CE( Cheat Engine ). Аттачимся к процессу игры:
и затем ищем значение открытых клеток в данный момент:
далее ищем инструкцию( инструкции ), которые как-либо взаимодействуют с найденным адресом. Для этого добавляем найденный адрес в AddressList, кликаем правой кнопкой мыши и выбираем «Find out what accesses this address». Выпадает окно. Мы переходим в игру и жмем на какую-нибудь клетку:
обнаружится, что с адресом взаимодействуют 3 инструкции( первая и третья схожи, но адреса различны ). В прошлый раз, как некоторые помнят, нас интересовали как раз те инструкции, которые писали в память напрямую( ASM-командой «mov» ), т.е первая и третья, но в этот раз мы сосредоточим внимание на второй инструкции. Данная инструкция, так же, как и остальные, тоже пишет в память, но немного другим образом. Она инкрементирует некоторое значение в памяти столько раз, сколько клеток поля было открыто( в данном случае это одна клетка ).
Ок, эта инструкция вполне подойдет для того, чтобы переполнить буфер, в котором хранится количество открытых клеток поля в данный момент. Если посмотрим в дебаггере, как работает инструкция, то сразу обратим внимание на такую последовательность команд:
сначала может показаться, что эта цепочка как-то связана с проверкой «нутра» клетки, т.е определение, ткнул ли игрок на клетку с миной. Скажу, что ничего подобного здесь не наблюдается. Вы можете сами это проверить, поставив BreakPoint на первое сравнение, например. Тогда при клике на обычную клетку, он сработает, но зато не сработает при клике на клетку с миной. Из этого следует, что определение внутренности клетки определяется где-то в другом месте.
В таком случае, интересной здесь является лишь команда инкремента:
inc [rcx+18]
Пробуем изменить данную команду так, чтобы при открытии новой клетки поля прибавлялась не единица, а, например, максимальное целое число типа INTEGER, т.е 4294967295 ( HEX = FFFFFFFF ):
Затем переходим в игру и начинаем «саперить»( для наглядности можно начать новую игру и выбрать поле побольше ):
Как можно наблюдать, переполнение буфера работает в нашу пользу, но не в полной мере. При первом же клике по полю, сразу же открылось более половины всех клеток( в данном случае это более 128 клеток, как видно ). Играя дальше мы все равно сможем наткнуться на мину и «взорваться». При проигрыше игры и последующей демонстрации всех мин, мы заметим, что вместе с той половиной отсеявшихся клеток, отсеялась и половина всех мин. Это, конечно же облегчает прохождение игры, но не полностью — чуть более, чем на половину.
II
Теперь рассмотрим каким же образом можно «вручную» взаимодействовать с минами. Для этого перезапускаем игру( закрыть игру, затем открыть ). И снова аттачимся к ее процессу из CE. Для того, чтобы найти инструкцию, взаимодействующую с количеством мин в игре, необходимо сперва найти адрес в памяти, по которому хранится целое значение количества мин. Общий алгоритм:
Поиск адреса памяти с количеством мин -> Поиск и исследование инструкций, работающих со значением -> отладка
1) Ищем текущее количество мин в памяти. Для этого необходимо проделать алгоритм по поиску значения схожий с тем, который использовался для поиска текущего количества открытых клеток поля игры. Найти адрес достаточно просто в 2-3 поиска: сначала выбираем уровень сложности «Новичок»(10 мин) и ищем число 10 в памяти, затем меняем уровень сложности, например, на «Профессионал»( 99 мин ) и ищем в памяти число 99. Повторив это несколько раз, получится примерно следующее:
Один адрес. Добавляем его в AddressList и смотри, что с ним взаимодействует. Для этого перезапустим( F2 ) игру:
Ага, выпало сразу 4 инструкции. Интерес для нас представляет первая( пишет значение в память ). Третья инструкция нам не интересна, так как она не пишет значение из памяти, а читает его. Исследуем первую инструкцию в дизассемблере:
Хм, весьма интересно… здесь происходит инициализация многих значений. Есть теория, что мы угодили в функцию, срабатывающей при самом старте игры, которая занимается инициализацией всех внутриигровых значений. Выделим текущую функцию( правый клик по инструкции, в меню выбираем «Select current function» ):
Функция довольно небольшая. Если прокрутить немного ниже, то это можно заметить. Ок, попробуем поставить бряк в начало функции:
Затем переходим в игру и начинаем новую игру( F2 ). Сработает бряк, следовательно данная функция выполняется при начале новой игры. Теория подтверждена! Теперь возвращаемся к инструкции «mov», связанной с количеством мин. Инструкцию можно отдебажить, изменив её второй параметр — значение, записываемое по адресу в то место, где находится текущее количество мин:
В данном случае произошла замена числа мин, соответствующее текущему уровню сложности, на четверку. Иными словами, мы изменили количество мин на 4. Переходим в игру, начинаем новую игру:
Как видим, количество мин правда изменилось! Играем дальше:
Все классно :) Теперь мины — не проблема.