Гайд: Правильное вмешательство в код, Или как не сломать программу

Автор DarkSim, 2012 Дек. 22, 14:20

« назад - далее »

0 Пользователи и 1 гость просматривают эту тему.

Ключевые слова [SEO] программированиекодвмешательство

DarkSim

Давно уже хотел написать о данной теме, цель статьи - изложить проблемы и ограничения связанные с вмешательством в код чужой программы (все будет на примере main.exe).

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

-> Будем считать, что main.exe уже открыт в OllyDBG

1. Как правильно войти в чужую функцию и выйти из нее
- Вот это наверное одна из первых проблем, которую я встретил, сложность в том, что нужно определить точные рамки кода, который мы будем заменять своим и определить адрес выхода, что бы код работал дальше.

Рассмотрим участок кода main.exe, где он накладывает текст на вещи (в данном примере 12:15, он же Jewel Of Chaos):
Спойлер
## здесь был скриншот ##
[свернуть]

в общих чертах функция делает простую проверку на вроде:

else if( ItemID == ITEM(12, 15) )
{
... (выполняются действия)
}
else if( ... )

т.е. сверяет указатель вещи с доступным и выполняет назначенные для него условия (накладывает текст соответствующий Jewel Of Chaos), если указатель не подходит - переходит к следующей проверке и т.д.

Наша первая задача в данном случае это определить границы условия, что внедрить свой код безопасно, не повредив предыдущие и последующие проверки, делается это просто - смотрим внимательно на код и выясняем в какой момент он начинается повторяться, можно отталкиваться от проверки else if( ItemID == ITEM(12,15) ):

Спойлер
## здесь был скриншот ##
[свернуть]
Вот именно тот участок кода, который повторяется, рыба нашей мечты, следующая наша задача определить размер участка кода, делается это еще проще - выделяем наш участок и жмем комбинацию CTR+E, после чего перед нами появиться Hex Edit окно, в нем нам нужно выделить все байты и Olly сама выдаст нужную цифру:

Спойлер
## здесь был скриншот ##
[свернуть]
Наша цифра: 57 (Hex) или 87 в Dec, подробнее о переводе цифр из Hex в Dec я писал в этом гайде.

И так, мы определили, что наш участок кода занимает 87 байт, что делать дальше?
- Нам необходимо создать свою функцию, которая будет осуществлять перехват этого участка кода и возвращаться обратно:

1.1. Создаем произвольную функцию типа void, декларируем ее как naked

void __declspec(naked) MySpecialFunction()
{
//...
}

1.2. Удаляем оригинальный участок кода и ставим прыжок на нашу функцию
- Практически каждый использует свои "классы" для этого, поэтому я просто покажу как это примерно делается:

ЦитироватьSetRange((LPVOID)0x005C751D, 87, ASM::NOP);
SetJmp((LPVOID)0x005C751D, MySpecialFunction);

SetRange - Выполняет заливку байтов по радиусу
SetJmp - Вставляет прыжок на наша функцию

Адрес начала нашего участка
Количество байт участка (87)
В данном случае мы заменяем все текущие байты участка на NOP (0x90), на пустые байты
Название нашей naked функции

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

Для того, что-бы вернуться в программу нам нужно определить начало следующего условия:
Спойлер
## здесь был скриншот ##
[свернуть]

1 - Адрес следующего условия, на которых программа уйдет, если ItemID не совпадет с Jewel Of Chaos
2 - Начала следующего условия

Собственно не сложно придти к выводу, что выходить из нашей функции мы будем на адрес 005C7574, сделаем мы это так:
DWORD Buff_Dword; //-> Объявляем вне функции (позже расскажу почему) наш буффер для хранения DWORD значений

void __declspec(naked) MySpecialFunction()
{
//... Выполняем нужные нам операции
_asm
{
mov Buff_Dword, 0x005C7574 //-> Ложим в Buff_Dword значение 0x005C7574
jmp Buff_Dword //-> Прыгаем на Buff_Dword
}
}

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

2. Ограничения, проблемы, костыли и прочее
- Все бы казалось хорошо, мы вошли и вышли из участка, нечего не сломали и т.д., но вот когда мы начнем добавлять свои условия, проверки, дополнения в этот участок - столкнемся с рядом геморойных проблем, которые не дадут нам работать всей доступной мощью C++, связанно это в первую очередь из-за безопасной декларации naked, которая имеет свои грандиозные заебы, сейчас я приведу список накопленных мною проблем и костыли к ним:

1. Забудем про объявления переменных внутри функции naked
- Внутри функций с декларацией naked нельзя объявлять новые переменные, иначе говоря это не правильно:
void __declspec(naked) MySpecialFunction()
{
DWORD Buff_Dword; //-> Ошибка
// ----
_asm
{
mov Buff_Dword, 0x005C7574 //-> Ложим в Buff_Dword значение 0x005C7574
jmp Buff_Dword //-> Прыгаем на Buff_Dword
}
}

А это правильно:
DWORD Buff_Dword; //-> Не ошибка

void __declspec(naked) MySpecialFunction()
{
_asm
{
mov Buff_Dword, 0x005C7574 //-> Ложим в Buff_Dword значение 0x005C7574
jmp Buff_Dword //-> Прыгаем на Buff_Dword
}
}

Если мы объявим внутри нашей функции хоть одну переменную любого типа - можно ждать беды.

2. Нельзя просто так взять и использовать операнд из main.exe
- Часто для того, что-бы получить какие либо указатели или переменные из функций требуется использовать операнды, которые уже используются main.exe, в этом случае у нас есть 2 варианта:

1. Найти якобы свободный операнд и надеяться на удачу
2. Восстанавливать операнды после использования

Очевидно, что второй вариант подходит больше, если вы не любитель "Main.exe завершил свою работу, отправить отчет?"

Наш участок:
Спойлер
## здесь был скриншот ##
[свернуть]

Предположим нам нужно получить указатель, по которому функция сверяет вещь
cmp word ptr ds:[edi], 180fВот тут мы видим, что наш указатель храниться в EDI и для его использования будет достаточно WORD размера (но мы все равно будем использовать DWORD, ибо EDI вмещает в себя его размер).

Наши действия для получения указателя:

DWORD Buff_Dword;
DWORD ItemPointer; //-> Объявляем наш буффер для хранения указателя

void __declspec(naked) MySpecialFunction()
{
_asm
{
mov Buff_Dword, esi //-> Копируем содержимое esi в Buff_Dword
mov esi, dword ptr ds:[edi] //-> Копируем содержимое edi в esi
mov ItemPointer, esi //-> Копируем содержимое esi в ItemPointer
mov esi, Buff_Dword //-> Восстанавливаем содержимое esi, после того как использовали его
}
//... (операции и проверки с ItemPointer)
_asm
{
mov Buff_Dword, 0x005C7574 //-> Ложим в Buff_Dword значение 0x005C7574
jmp Buff_Dword //-> Прыгаем на Buff_Dword
}
}

3. Дискриминация циклов в naked
- Циклы в naked функциях по какой-то не ясной причине портят код, звучит конечно параноидально, но так и есть, на своем опыте проверял, пока я так и не понял с чем это связанно и можно ли придумать костыль этому, как только узнаю опишу по подробнее.

Как пример:
- Хукаю я текст для вещей, хочу добавить цикл for(...) для удобства, в итоге на некоторых вещах просто текст пропадает или отображается не верно, убираю цикл - все опять хорошо, проверил это и с while, do - тоже самое и дело тут точно не в том, что выполняет мой цикл.

----

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

P.S.: 1.03.28 GMO, если кто вдруг проверить захочет

Mr.Kernighan

__asm
{
Mov Ecx, 10
my_loop:
; Манипуляции
LoopNz my_loop
}
; Счетчиком для Loop/ов служит регистр Ecx
; Метка "my_loop" это явный адрес (после компилирования естественно) а пока в виде символьного выражения куда будет происходить смещение
; Стандартная инструкция LoopNz с меткой my_loop (таких loop/ов есть разное количество с определенными условиями)

__asm
{
Mov Ebx, 0
my_loop:
; Манипуляции
Cmp Ebx, 100H
Inc Ebx; Или Add Ebx, X = любое число
JLE my_loop
; Другие манипуляции или выход из функции, вообщем все что душе угодно;)
}
Если регистры несут какую-то информацию, восстанови их после использования прежде временно сохранив информацию где-то в буферах до цикла. Вообщем как-то так с циклами.

пс. Такой метод внедрения в функции и процедуры называется Сплайсинг, можешь где-то обозначить это в название темы. =)

Похожие темы (5)