Skip to main content

Взлом игры “Сапер”

  • Эта публикация - перевод статьи. Ее автор - Osanda Malith Jayathissa. Оригинал доступен по ссылке ниже:

Недавно я опубликовал в Твиттере скриншот, где я выиграл игру Сапер, получив минное поле из памяти. Я сделал это без причины, просто для удовольствия, так как был счастлив, что наконец-то выиграл эту игру. Я играл в Сапер еще в 2002 году в Windows XP, и я никогда не выигрывал игру, я даже не понимал, как эта она работает – до сегодняшнего дня, когда я наконец-то понял, как она действительно работает.

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

После публикации этого скриншота я увидел пост в блоге @DidierStevens, где он показывает видео о взломе Сапера с помощью Mimikatz из @gentilkiwi. Было бы неплохо включить такую ​​функциональность в Mimkatz. Давайте попробуем закодировать простой грязный хак для Minesweeper в XP.

Недавно я опубликовал в Твиттере скриншот, где я выиграл игру Сапер, получив минное поле из памяти. Я написал это без причины, просто для удовольствия, так как был счастлив, что наконец-то выиграл эту игру. Я играл в эту игру еще в 2002 году в Windows XP, и я никогда не выигрывал эту игру, я даже не понимал, как эта игра работает, до сегодняшнего дня, когда я прочитал, как она действительно работает.

В Windows XP вы можете найти бинарный файл в %systemroot%\system32\winmine.exe

Если у вас нет Windows XP, вы все равно можете скачать оригинальный бинарный файл отсюда.

Статический анализ

Во-первых, давайте посмотрим, включен ли ASLR в двоичном файле. Характеристики DLL получили значение 0x8000, которое равно IMAGE_DLLCHARACTERISTICS_TERMINAL_SERVER_AWARE. Подтверждено, что этот PE был скомпилирован без защиты ASLR. Мы можем жестко закодировать адреса.

Глядя на IAT, мы можем определить, что программа использует интерфейс графического устройства Microsoft Windows (GDI) через функции, импортированные из gdi32.dll. Это очевидно, так как это игра. Мы также можем увидеть API-интерфейсы реестра, используемые из файла advapi32.dll. Таким образом, мы можем подозревать, что приложение обращается к реестру.

Взлом счета

Баллы хранятся в реестре, а значения считываются из реестра. Если вы проверите все операции импорта и найдете ссылки на API RegQueryValueExW и столкнетесь с точкой останова, вы сможете найти местоположение реестра. Другой простой способ - подключить API.

После того, как точка останова достигнута, можно увидеть параметры в стеке и открыть местоположение в реестре.

HKEY_CURRENT_USER\software\Microsoft\winmine

Вы можете изменить имена, оценки и другие параметры, такие как цвет, сложность, высота, ширина и т. п. Значение «Name1» соответствует значению «Time1» в шестнадцатеричном формате.

Взлом шахтного поля

Давайте достигнем точки останова в API BeginPaint.

Давайте войдем в функцию

01001C4C |. E8 720E0000 CALL Winmine_.01002AC3

Затем снова войдите в функцию.

01002AE6 |. 56 PUSH ESI ; /Arg101002AE7 |. E8 BBFBFFFF CALL Winmine_.010026A7 ; \Winmine_.010026A7Вы увидите, что API 'BitBlt' используется для рисования блоков друг за другом.

Если мы проверим регистры, EBX содержит поле mine 0x010056360, а регистр ESI используется для увеличения каждого байта.

Если мы сбросим регистр EBX, тогда сможем определить начальную точку с 0x01005340.

Это добавляет 0x20 к EBX, который мы можем определить в конце каждого поля.

0100271D |. 83C3 20 |ADD EBX,20

Кодирование грязного взлома

Во-первых, мы должны открыть процесс игры. Для этого мы будем использовать FindWindow. Получив дескриптор окна, мы передадим его в «GetWindowThreadProcessId», и как только мы получим «dwProcessId», мы можем передать это значение в API «OpenProcess». Вы также можете использовать метод CreateToolhelp32Snapshot, который обсуждается здесь.

	HWND window = FindWindow(NULL, L"Minesweeper");
	if (window == NULL)
		return wprintf(L"[-] Failed to find Minesweeper process");

	GetWindowThreadProcessId(window, &dwProcessId);
	HANDLE process = OpenProcess(PROCESS_VM_READ, FALSE, dwProcessId);

Далее мы выделим буфер для хранения наших данных с минных полей.

	LPBYTE buffer = (LPBYTE)malloc(size);
	if (buffer == NULL)
		return wprintf(L"[-] Failed to allocated memory");

Наконец, мы напишем бесконечный цикл для чтения памяти из начального адреса памяти с помощью API ReadProcessMemory, чтобы каждый раз, когда мы щелкали поле, поля обновлялись. Мы можем использовать 0x20 в качестве конца каждой строки в поле.

while (true) {
		BOOL ret = ReadProcessMemory(process, (LPVOID)start, buffer, size,
			&dwRead);

		if (ret == NULL) return wprintf(L"[-] Failed to read memory");

		for (size_t i = 0, j = 0; i < size; i++, j++) {
			if (j == 0x20) {
				puts("");
				j = 0;
			}
			printf("%c", buffer[i]);
		}
		Sleep(1500);

		system("cls");
	}

Мы просто выбросили поле памяти из памяти, используя API ReadProcessMemory.

Вот полный исходный код: https://github.com/OsandaMalith/GameHacking/blob/master/Minesweeper/Hack.c

#include "stdafx.h"

#include <stdio.h>
#include <tchar.h>
#include <windows.h>

#define start 0x1005340
#define end 0x10056A0

int _tmain(int argc, _TCHAR* argv[]) {
	DWORD dwProcessId = 0;
	DWORD dwRead = 0;
	HWND window = FindWindow(NULL, L"Minesweeper");
	if (window == NULL)
		return wprintf(L"[-] Failed to find Minesweeper process");

	GetWindowThreadProcessId(window, &dwProcessId);
	HANDLE process =  OpenProcess(PROCESS_VM_READ, FALSE, dwProcessId);

	DWORD size = end - start;

	LPBYTE buffer = (LPBYTE)malloc(size);
	if (buffer == NULL)
		return wprintf(L"[-] Failed to allocated memory");

	while (true) {
		wprintf(L"[+] Minesweeper Dirty hack\n");
		wprintf(L"[+] Author: @OsandaMalith\n");
		wprintf(L"[+] Website: https://osandamalith.com\n\n");

		BOOL ret = ReadProcessMemory(process, (LPVOID)start, buffer, size,
			&dwRead);
		if (ret == NULL) return wprintf(L"[-] Failed to read memory");
		
		BYTE field = NULL;


		for (size_t i = 0, j = 0; i < size; i++, j++) {
			if (j == 0x20) {
				puts("");
				j = 0;
			}
			printf("%c", buffer[i]);
		}
		Sleep(1500);

		system("cls");
	}
	return 0;
}

Вы можете изменить байтовые значения и напечатать что-то вроде этого, но я оставлю это для вас 😊

Вы можете проверить, что моя подруга Офир Харпаз хорошо исправила программу, в которой она сделала, если al == 0x8f, которая является моей, замените ее на флаг, который 0x0E внутри пещеры кода. https://github.com/ophirharpaz/Patched-Minesweeper

Я надеюсь, что этот пост будет полезен для вас, чтобы начать взламывать простые игры 😉