PDA

Просмотр полной версии : [Руководство] [Редактирование клиента] Убираем котов


Smertig
04.03.2014, 21:45
Доброго времени суток всем читателям.
В этой статье я попытаюсь наиболее понятным языком рассказать об основах редактирования клиента на примере удаления котов из него. Результат тут ([Ссылки могут видеть только зарегистрированные и активированные пользователи]).

Основные инструменты и требования:

Клиент PW (куда без него)
Cheat Engine (далее сокращение - СЕ)
CFF Explorer
Olly Debugger
Опыт использования СЕ
Pandora Box или небольшой опыт в OOG
Знать, чем отличается int (dword) и byte
Желание понять ассемблер


Статья разбита на три части для /dgs восприятия:

1. Вступление, или составляем план.
2. Ищем код, отвечающий за появление кота
3. Редактируем код под себя - удаляем котов

Часть 1.
С чего начать? Такой вопрос возникает всегда. Давайте рассуждать. Нужно удалить кота из клиента. Как? Как кот вообще попадает в клиент? Когда мы подходим к коту (или к персонажу) на достаточное расстояние (рассматривать только ситуации, когда мы изначально не видели кота), он появляется на экране, следовательно сервер присылает пакет с информацией об игроке и клиент прорисовывает его. Как клиент отличает кота от игрока? Информация в пакете, правильно. С этим разобрались, проверим потом.
Пакет попал в клиент, дальше клиент его должен обработать и нарисовать нам кота (или игрока обычного). Значит нам нужно найти место в памяти, где клиент проверяет, пришёл пакет с котом или игроком, и немножко поменять это место.
План есть, приступим!

Часть 2.
Начнём с определения флага "кота" в пакете. Тут первая проблема - кто не умеет пользоваться пандорой, тот вряд ли быстро разберется в этом. Поэтому тут я сразу выложу готовый результат:
Пакет 0x04 контейнера 0х00
[Ссылки могут видеть только зарегистрированные и активированные пользователи]
[Ссылки могут видеть только зарегистрированные и активированные пользователи]
Порядок байт обратный в маске, поэтому "00 10 00 00" == 0x00001000 или 0x1000 просто. Следовательно клиент должен как-то проверять маску. Поищем же.
При получении и обработке пакета, идёт хоть одно обращение к базовому адресу клиента (ведь клиент должен записать полученного игрока куда нужно). Запускаем клиент в два окна, заходим за двух персов, улетаем куда-нибудь в пустынное место (чтобы не было других игроков) и расходимся так, чтобы с первого перса не было видно второго перса. Открываем СЕ, подключаемся к первому процессу пв. [Ссылки могут видеть только зарегистрированные и активированные пользователи] добавить адрес, вводим BA нашего клиента. [Ссылки могут видеть только зарегистрированные и активированные пользователи] ПКМ по нему - Find Out What accesses this address и ждём. Сразу [Ссылки могут видеть только зарегистрированные и активированные пользователи] появляться сотни адресов и клиент начнёт виснуть. Нужно дождаться, пока адреса перестанут появляться, при этом можно побегать в клиенте за первого, понажимать что-нибудь, но при этом поблизости никого не должно быть и никто не должен появиться. Когда адреса перестанут появляться, [Ссылки могут видеть только зарегистрированные и активированные пользователи] последний адрес в этом списке, подойдите вторым окном к первому так, чтобы появиться в зоне видимости. В списке адресов тут же [Ссылки могут видеть только зарегистрированные и активированные пользователи] несколько новых адресов (или не несколько, смотря сколько ждали). Сразу жмите Stop. Лично у меня появилось около сотни лишних, но нас интересуют только те, что вызвались первыми и по одному разу (скрин выше). Скопируйте эти адреса в блокнот (на случай случайного закрытия окошка).
Итак, у нас есть адреса функций, который вызвались при появлении другого игрока. И еще мы знаем, что в одной из них должна быть проверка флага. Если кликнуть по адресу в окошке - Show disassembler, мы перейдём к самой функции. Мы знаем, что где-то должна идти проверка маски. Маска - 4 байта. Ассемблер любит сравнивать командами test или cmp. В нём вместо переменных регистры:
eax
ebx
ecx и другие.
Это 4-х байтовые регистры. Почитать можно тут ([Ссылки могут видеть только зарегистрированные и активированные пользователи] D1%80%D0%BE%D1%86%D0%B5%D1%81%D1%81%D0%BE%D1%80%D0 %B0). Видим примерно следующее:
[Ссылки могут видеть только зарегистрированные и активированные пользователи]
Это означает следующее. Если процессор засунет нашу маску в регистр eax, то получится следующее:
eax = 00 00 10 00
ax = 10 00
ah = 10
al = 00
где ah, al - "четвертинки регистра", а ax - половинка. Для ebx аналогично, только не "а", а "b". Следовательно процессору выгоднее всего при сравнении маски пользоваться сравнением ah (bh или другие) с 10. После сравнения обычно идёт условный прыжок JE (Jump if Equal). А перед сравнением должно идти:
mov #, * (# - регистр eax, ebx, ecx или другой, а * - какой-либо адрес)
Следовательно мы ищем среди этих функций конструкцию:
mov eax, *адрес*
test ah, 10
je #адрес#
Повторюсь, вместо eax может быть любой другой регистр. Помимо этой конструкции, там должно быть много
test **, **
je **
так как проверка маски идёт не один раз.

Мне повезло, эта функция была пятой по счёту (чуть пролистать еще вниз) и выглядела вот [Ссылки могут видеть только зарегистрированные и активированные пользователи]
?"]Небольшое отступление в ассемблер:
mov - команда копирования. Есть несколько видов:
1) mov *, #
Копирует из регистра # в регистр *.
Пример: mov eax, ebx - делает eax равным ebx
2) mov *, [#]
Читает значение по адресу # и копирует его в *
Пример: mov eax, [ebx]
3) mov , #
Вместо того, что лежит по адресу * ставит туда #
Пример: mov [eax], ebx


Итак, мы видим много раз повторяющуюся конструкцию mov eax, [ebp+16] (16 - это в hex ([Ссылки могут видеть только зарегистрированные и активированные пользователи] 0%B0%D1%82%D0%B5%D1%80%D0%B8%D1%87%D0%BD%D0%B0%D1% 8F_%D1%81%D0%B8%D1%81%D1%82%D0%B5%D0%BC%D0%B0_%D1% 81%D1%87%D0%B8%D1%81%D0%BB%D0%B5%D0%BD%D0%B8%D1%8F )'e, dec - 22). А теперь вернемся к нашим пакетам. Как [Ссылки могут видеть только зарегистрированные и активированные пользователи] ровно на 22-м месте стоит маска. Значит мы на верном пути! Записываем в блокнот адрес начала функции (у [Ссылки могут видеть только зарегистрированные и активированные пользователи] 00453D40)

Проверить, реально ли мы нашли то место, можно [Ссылки могут видеть только зарегистрированные и активированные пользователи] (далее бряк) в любом месте функции (ф5 в СЕ), вызвать функцию (для этого вторым окном отойти и подойти снова). Выполнение [Ссылки могут видеть только зарегистрированные и активированные пользователи] там, где вы поставили бряк. Копируем ebp, внизу в памяти жмём на любой байт, Ctrl-G, [Ссылки могут видеть только зарегистрированные и активированные пользователи] адрес и видим наш [Ссылки могут видеть только зарегистрированные и активированные пользователи] Кстати, чтобы продолжить выполнение после остановки на бряке, снимите заодно его (ф5) и жмите ф9 для продолжения выполнения команд.

Часть 3.
Мало кто до сюда доживает. Но ведь здесь происходит самое интересное!
Нам известна функция, известно, что пакет (а точнее его кусок с информацией об одном персонаже) хранится по адресу ebp. Я предлагаю модифицировать код так, чтобы "у" координата обнулялась, если персонаж == кот. Приступим! (первое окно можно закрыть, бегающего твина лучше оставить)
Чтобы писать свой код в клиенте, нам нужно место. В бой вступает CFF Explorer. Запускаем, открываем наш ехешник, жмём слева Section Headers и видим [Ссылки могут видеть только зарегистрированные и активированные пользователи] Дальше [Ссылки могут видеть только зарегистрированные и активированные пользователи] пкм по пустому месту, Add Section (Empty space), размер 1000 - хватит, ок. Внизу [Ссылки могут видеть только зарегистрированные и активированные пользователи] еще одна строчка (я назвал её .zhyk - начинаться должно с точки). [Ссылки могут видеть только зарегистрированные и активированные пользователи] адрес нам понадобится. Сохраняем под новым именем. Итак, место для наших каляк-маляк у нас есть.
Теперь открываем Olly Debugger (далее оля), [Ссылки могут видеть только зарегистрированные и активированные пользователи] в нём новый ехе'шник, переходим по адресу функции (Ctrl-G), найденному в конце 2-й части. У меня 00453D40. Если олька ругается [Ссылки могут видеть только зарегистрированные и активированные пользователи] просто допишите пару нулей перед адресом. Теперь нужно реализовать следующую схему:
[Ссылки могут видеть только зарегистрированные и активированные пользователи]

Поэтому выбираем какое-нибудь место в функции (после push'ов всяких), я выбрал [Ссылки могут видеть только зарегистрированные и активированные пользователи] Переписываем в блокнот все эти инструкции (дабл-клик, копипаст). Вместо первой инструкции пишем
JMP 00C75000
где С75000 = адрес, который выписали из CFF Explorer'а + 0x400000 (почему именно 0х400000? это адрес образа файла при копировании его в память)Смотрим, какие инструкции мы [Ссылки могут видеть только зарегистрированные и активированные пользователи] этим джампом.
Не хватает:
LEA EDI,DWORD PTR SS:[EBP+4]
MOV DWORD PTR DS:[ESI+6E8],EAX
Запомним этот факт.
Запишем [Ссылки могут видеть только зарегистрированные и активированные пользователи] адрес (нам ведь сюда нужно вернуться).
Теперь переходим на нашу память. Жмём Ctrl-G и тот адрес, на который мы прописали джамп. Ммм.. [Ссылки могут видеть только зарегистрированные и активированные пользователи]!
Пишем следующий код (построчно подряд, получится [Ссылки могут видеть только зарегистрированные и активированные пользователи])
PUSH EAX
MOV EAX,DWORD PTR SS:[EBP+16]
TEST AH,10
JE SHORT *наш адрес (который CFF+400000) плюс 10* (у меня получилось JE SHORT 00C75010)
MOV DWORD PTR SS:[EBP+8],0
POP EAX
LEA EDI,DWORD PTR SS:[EBP+4]
MOV DWORD PTR DS:[ESI+6E8],EAX
JMP 00453D5B
Эти строчки нужны для того, чтобы не потерять регистр eax и смысл начального кода. Так как мы его в дальнейшем будем использовать, то мы его сохраняем (командой push, в стеке), дальше используем его для своих делишек, потом вытаскиваем обратно. Две строчки после pop eax - это те строки, которые мы потеряли, когда прописали в основном коде jmp.
Этот джамп возвращает нас на тот адрес, который мы записали ранее ([Ссылки могут видеть только зарегистрированные и активированные пользователи]).

Рассмотрим оставшиеся 4 строчки отдельно.
MOV EAX,DWORD PTR SS:[EBP+16]
кратко mov eax, [ebp+16] - записываем в регистр (кому удобнее - переменную) eax значение по адресу ebp+16 (наша маска)
TEST AH,10
см. ниже
JE SHORT 00C75010 (мой адрес)
проверка на "кота" и если "не кот", то перепрыгнуть на команду pop eax если в test'е выше байты не совпали (ah AND 10 == 0), тогда происходит джамп на адрес команды pop eax
MOV DWORD PTR SS:[EBP+8],0
кратко mov [ebp+8], 0 - если предыдущий джамп не произошёл, то есть перс оказался котом, тогда мы устанавливаем y=0 (или z, везде по разному называется, хотя на самом деле у китайцев высота это у)
[Ссылки могут видеть только зарегистрированные и активированные пользователи]

Теперь переходим обратно по адресу начальной функции (где писали jmp), выделяем наш код, подсвеченный красным, пкм, Copy to executable -> All modifications - [Ссылки могут видеть только зарегистрированные и активированные пользователи] Далее Copy all. В появившемся окошке пкм - [Ссылки могут видеть только зарегистрированные и активированные пользователи] Сохраняем. Готово!

Эпилог
Планировалась короткая статья, но получилось что-то длинное /nyan
Давно, когда я был новичком, я перечитывал статьи TBX1n'а, Dinmaite'а, FreePvP, dwa83 и многих других десятки раз и каждый раз вычитывал что-то новое. А теперь я сам владею тем, чем могу поделиться с людьми в своих статьях. Надеюсь, что донёс до тебя, читатель, хоть какие-нибудь полезные мысли. Информация - это наиважнейший ресурс.

Сделано по просьбе ([Ссылки могут видеть только зарегистрированные и активированные пользователи]).

Флуд буду тереть нещадно, о неточностях в лс, о важных уточнениях в комментарии.

P.S. Планирую написать ещё пару статей на тему редактирования клиента, только подкиньте идеи.

UPDATE: Картинки все сломались, восстанавливать долго, извините уж :)

VeTaL_UA
05.03.2014, 01:19
Статья хорошая, да.
MOV EAX,DWORD PTR SS:[EBP+16]
кратко mov eax, [ebp+16] - записываем в регистр (кому удобнее - переменную) eax значение по адресу ebp+16 (наша маска)
А что, если я тебе скажу, что эта команда записывает в аккумулятор четвёртый по счёту элемент стека(ну если от ebp отсчитывать)? :)
MOV DWORD PTR SS:[EBP+8],0
А эта пишет во второй элемент стека 0 :)

Sirioga
05.03.2014, 04:34
А что, если я тебе скажу, что эта команда записывает в аккумулятор четвёртый по счёту элемент стека(ну если от ebp отсчитывать)? :)

А эта пишет во второй элемент стека 0 :)

А что, от этого оно не будет работать что ли?

Smertig
05.03.2014, 06:41
А что, от этого оно не будет работать что ли?

Я допустил грубую ошибку. Я забыл, что ebp - указатель на стек. push eax помещает в стек значение eax, следовательно ebp -= 4, и дальнейшее обнуление [ebp+8] обнуляет по факту [ebp+4] (если считать относительно начального ebp). Странно, что эта ошибка не всплыла (обнулялась координата x). Буду с пк - поправлю статью.

апдейт: А флаг вообще из левого места тогда читается. Странно, что это работает. Либо я спросонья запутался и всё в порядке. Обязательно проверю.

Паника отменяется. Push eax суёт eax в "другой" стек, за адрес которого отвечает esp, а не ebp. Почитав гугл, не особо понял разницу esp и ebp как стековых указателей.

BritishColonist
06.03.2014, 23:16
не особо понял разницу esp и ebp
ESP отвечает за адрес вершины стека; вручную (умышленно, разработчиком взламываемого ПО) модифицируется редко, но часто команды sub esp и add esp встречаются в прологах/эпилогах функций.

EBP же - Base Pointer, часто содержит в себе указатели на что-нибудь, но лично я ни разу не видел особой специфики работы с этим регистром (совсем необязательно он содержит указатель на стек, лично я вообще его иногда использую как те же eax/edx/ecx).

UPD:
Кстати, добавлю про перехват управления в коде, в частности про 0xE9.
Если хотим пропатчить клиент с целью перехода с адреса X в адрес Y, то в адрес X записывается байт 0xE9 (jmp), а в следующие 4 байта, т.е. по адресу памяти X+1 запишется число Y-(X+5). Т.е. адрес назначения минус текущий адрес плюс размер команды jmp с операндами (5 байт).
Это я к тому, что можно и без дополнительных программ вычислить этот адрес, даже не проверяя через Olly.

P.S. Думал на днях запилить гайд по перехвату управления. Но даже не знаю, стоит ли игра свеч, ибо прошлые мои гайды особого успеха не поимели (пишу громоздко и максимально подробно). Интересно кому-нибудь?

Smertig
07.03.2014, 00:03
ESP отвечает за адрес вершины стека; вручную (умышленно, разработчиком взламываемого ПО) модифицируется редко, но часто команды sub esp и add esp встречаются в прологах/эпилогах функций.

EBP же - Base Pointer, часто содержит в себе указатели на что-нибудь, но лично я ни разу не видел особой специфики работы с этим регистром (совсем необязательно он содержит указатель на стек, лично я вообще его иногда использую как те же eax/edx/ecx).
Вот за это спасибо.

UPD:
Кстати, добавлю про перехват управления в коде, в частности про 0xE9.
Если хотим пропатчить клиент с целью перехода с адреса X в адрес Y, то в адрес X записывается байт 0xE9 (jmp), а в следующие 4 байта, т.е. по адресу памяти X+1 запишется число Y-(X+5). Т.е. адрес назначения минус текущий адрес плюс размер команды jmp с операндами (5 байт).
Это я к тому, что можно и без дополнительных программ вычислить этот адрес, даже не проверяя через Olly.
Проще говоря - относительная адресация. Она очень удобна, когда джамп локальный и при перемещении куска кода ничего не нужно исправлять.

P.S. Думал на днях запилить гайд по перехвату управления. Но даже не знаю, стоит ли игра свеч, ибо прошлые мои гайды особого успеха не поимели (пишу громоздко и максимально подробно). Интересно кому-нибудь?
Гайды имели успех, и этим ([Ссылки могут видеть только зарегистрированные и активированные пользователи]) пользовался, а эти ([Ссылки могут видеть только зарегистрированные и активированные пользователи]) две ([Ссылки могут видеть только зарегистрированные и активированные пользователи]) темы вдохновили на написание своего яп. Пили гайд, обязательно

VeTaL_UA
08.03.2014, 00:07
EBP же - Base Pointer, часто содержит в себе указатели на что-нибудь, но лично я ни разу не видел особой специфики работы с этим регистром
Там должен быть указатель на начало сегмента стека, используется "по назначению" в случае необходимости работы с элементом стека, не вынимая все предыдущие, что мы, кстати, тут и видим :)

mafiosi
21.08.2014, 18:11
прошу ,перезалей все скрины,очень интересная тема,но без наглядных примеров я не понимаю что к чему