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

Поиск указателя для игры Never Winter Nigth


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

Давно у нас не было статей и вот пришло время поучаствовать с автором во обмане игры. Статью я откопал случайно, она 2002 года, она покажет как искать цепочку указателей.

Есть некоторые моменты касательно реверсинга. Например, как и когда и какие бряки нужно ставить применительно к СофтАйс. Тоже самое можно сделать и в OllyDbg. Главное понять технику.

Указатель будет вот такого вида:

@(@(@(@(@(@(@(@(862360)+4)+4)+10074))+0C2*4)+4)+A8)

надеюсь без труда вы сможете это представить скобками как мы делаем:

[[[[[[[[862360]+4]+4]+10074]]+0C2*4]+4]+A8] = здоровью (8 пар скобок!)

Статья на мой взгляд написана "кусками", но кое-что понять можно. Для тех кто с СофтАйсом не работал, то выражение к примеру

mov eax,[eax][esi]*4 

эквивалентно для OllyDbg:

mov eax,[eax+esi*4]

Итак статья.

Автор: FreeExec

Выбирать особо было не из чего, т.к. на машине стояла только одна игра Newer Winter Nigths. И решено было писать тренер к ней.

Продолжение статьи...

Трейнер это программа, которая позволяет изменять значения в игре, например жизни. Сам я читеров не люблю и втихомолку сильно бью *(. Но так я думал до поры до времени, пока не припёрло к стенке, т.е. к монстру. И набросал я быстренько (ну это как посмотреть) свой трейнер.

С помощью ArtMoney я нашел тот блок памяти, в котором хранятся очки жизни. Было бы всё просто, если бы не одно НО: при каждой новой загрузке это место меняется, это происходит по тому, что программа выделяет память динамически и раз на раз не приходится. Ну, раз программа находит нужное место то и я его смогу найти.)

Примечание: ArtMoney найдёт не одно значение, у меня их было 10. Меняйте эти значения и смотрите за остальными, некоторые ячейки тоже поменяют своё значение на введённое вами. Та ячейка, которая изменяет собой больше ячеек и есть искомая.

Запускаем SoftICE, и ставил брейкпоинт (в дальнейшем БР) на доступ к памяти в ячейку, которую мы нашли. (У меня по адресу 10AEFC70, поэту команда для SoftICE'а выглядит так: bpmd 10AEFC70. Но обратите внимание что под окном кода должно быть написано, что вы в модуле nwmain.text, иначе у вас не чего не выйдет). Жмём F5, когда выскочит SoftICE.

Это должна быть инструкция по адресу 607C87 MOV AX, [ECX+A8]. Т.е. какое-то значение (ECX)+А8 ссылается на жизнь. Следующим шагом нужно отыскать то место где регистр ECX получает своё значение. Чуть ниже этой инструкции стоит команда RET- это выход из функции. Выполняем пошагово (F8) пока не выйдем из функции, и не уведем следующее:


5D05E3 call [edx+00000009C]
5D05E1 mov ecx, esi

Ставим БР по адресу 5D05E1 командой "bpx 5D05E1" или двойным щелчком на строке и нажимаем F5 несколько раз, пока ESI не будет равен найденному значению минус A8 (10AEFC70-A8=10AEFBC8). Двигаем код в окне вверх (CTRL+вверх), ища, где ESI получает своё значение, должны дойти вот досюда:


005D0073 call [edx+00030]
005D0076 mov esi,eax
005D0071 mov ecx,ebx

Значит, функция возвращает в регистре EAX наше число, но не стоит сразу ковырять внутренности функции, поставив БР по адресу 005D0071, мы увидим, что EBX=10AEFBC8. Чуть выше видим:

005D0049 mov ebx,[esp+00050]

Снова ставим БР на участок памяти ESP+50 "bpmb esp+20 w"(w означает, что отслеживать только запись по этому адресу). Наберём в командной строке "? ESP+50" чтобы получить точный адрес, затем "d ESp+50" чтобы видеть этот участок. Жмём F5 столько раз, пока на этом участке не появится наше значение в перевёрнутом виде, т.е. C8FBAE10 (руки бы оторвать тому, кто это придумал %). Как дальше разворачивались дела я не помню, но вы должны выйти на:

CALL 005CF000 (например, по адресу 5D8D6A, 5D8866)

После выполнения этой функции EAX содержит наше число. Значит способ получения этого числа в этой функции. Вот содержимое этой функции (примечание: все числа после точки с запетой приведены конкретно для моего примера, у вас они могут быть другие):


mov eax,ecx
mov ecx,[000862360] ; ECX=A6BA590 (7)
mov edx,[eax][00030]
mov ecx,[ecx][00004] ; ECX=[A6BA594]=79A6058 (6)
push edx
call .0005BB7E0

5BB7E0:
mov eax,[esp][00004]
mov ecx,[ecx][00004] ; ECX=[79A605C]=7A58008 (5)
push eax
call .0005C2B10

5C2B10:
push ecx
mov edx,[esp][00008]
mov ecx,[ecx][000010074] ; ECX=[7A6807C]=79F1CB8 (4)
lea eax,[esp][00000]
push eax
push edx
mov d,[esp][00008],000000000
call .0004259D0

4259D0:
mov eax,[esp][00004] ; EAX=20C2
push esi
mov esi,eax
shr esi,01F ; ESI=0
and esi,001
mov edx,eax
and eax,000000FFF ; EAX=0C2
shl esi,00C ; ESI=0
add esi,eax ; ESI=0C2
mov eax,[ecx] ; EAX=[79F1CB8]=7A1EFF8 (3)
mov eax,[eax][esi]*4 ; EAX=[7A1F300]=7BD1E08 (2)
and edx,07FFFFFFF
test eax,eax
pop esi
je .000425A04
cmp [eax],edx
je .000425A13
mov eax,[eax][00008]
test eax,eax
jne .0004259F9
mov ecx,[esp][00008]
mov d,[ecx],000000000
mov al,001
retn 00008
mov edx,[eax][00004] ; EDX=10AEFBC8 (1)
5CF000:

Цифрами в скобках обозначены ключевые моменты кода. Мы видим, чтобы получить нужный адрес надо прочитать DWORD по адресу EAX+4. В свою очередь этот EAX=EAX+ESI*4, где ESI=0C2. И так далее. Чтобы проверить правильность наших рассуждений пишем в командной строке SoftICE'а "? @(10AEFBC8+A8)", программа должна написать текущее количество жизни (первое число в шестнадцатеричное, а вот второе десятичное). Теперь надо заменить число 10AEFBC8 на то, что мы получили, т.е. на @(7BD1E08+4). Соединяем все вместе - должно получится "? @(@(7BD1E08+4)+A8)". Аналогичным способом движемся выше до строки обозначенной (7), там адрес задан конкретно. Он будет всегда один и тем же. В итоге у меня получилось так: "? @(@(@(@(@(@(@(@(862360)+4)+4)+10074))+0C2*4)+4)+A8)". Вот в принципе и всё что касается нахождения нашей жизни ;) Можно уже юзать это совместно с SoftICE'ом.

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

Чтобы иметь возможность изменять данные в чужом процессе (программе) нужно знать её идентификатор процесса. Его можно получить используя функцию NtQuerySystemInformation(5, адрес_буфера, размер_буфера, 0), из библиотеки ntdll.dll. Чем больше процессов запущено, тем больше нужен буфер. У меня он составляет 7 Кб. Буфер состоит из массива структур ProcessInfo. По 0 смещению задан размер структуры DWORD. А со смещением 3C ссылка на имя процесса в формате UniCODE. По смещению 44 находится идентификатор процесса. Затем открываем этот процесс функцией OpenProcess(1F0FFF,0,идентификатор_процесса), она возвратит нам хендл на этот процесс. С помощью функции ReadProcessMemory(Handle, адресс_в_процессе_ откуда_читать, адресс_буфера_ куда_записать, сколько_прочитать, 0) сначала читаем по адресу 862360, прибавляем к этому значению 4 и снова читаем по полученному адресу, и т.д. Запись в память производится с помощью WriteProcessMemory(Handle, адрес_куда_записывать, адрес_буфера, сколько_записать, 0).

Процесса заморозки можно добиться, используя вызов по таймеру. Создаём таймер - SetTimer(hWnd-идентификатор_окна, уникальный_номер, время_в_милисек, 0 или адрес_процедуры_ которая_будет_вызываться). Если последний аргумент равен 0, то процедуре обработки окна будет приходить сообщение WM_TIMER, если таймеров много то отличить их можно по уникальному номеру, который был задан при создании таймера, он находится в переменной wParam. Не забудьте удалить таймер при выходе KillTimer(hWnd, handle).

Ну, вот в принципе и всё, осталось нарисовать морду будущей проги и описать несколько функций.

P.S. Функция NtQuerySystemInformation доступна только NT системам в Win9x нужно юзать CreateToolhelp32Snapshot, Process32First, Process32Next чтобы получить идентификатор нужного процесса.

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

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

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

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