Привет, %username%! Привет, %username%! И так, продолжим написание нашего бота. Из прошлых статей, мы научились находить адрес перехватываемой функции для DirectX 9 и 11, а так же исполнять произвольный ассемблерный код в главном потоке игры. Естественно, эти все операции, могут быть замечены защитой игры и вы будете наказаны. Но сегодня, я покажу как спрятать этот код от защиты, в том числе и от такого монстра, которого все боятся, как Warden. Как я и говорил, я не ботовод потому, что меня не поймали. Жду вас под катом!
Disclaimer: Автор не несет ответственности за применение вами знаний полученных в данной статье или ущерб в результате их использования. Вся информация здесь изложена только в познавательных целях. Особенно для компаний разрабатывающих MMORPG, что бы помочь им бороться с ботоводами. И, естественно, автор статьи не ботовод, не читер и никогда ими не был.
Для тех кто пропустил прошлые статьи вот содержание, а кто все прочел идем дальше:
Содержание
- Часть 0 — Поиск точки внедрения кода
- Часть 1 — Внедрение и исполнение стороннего кода
- Часть 2 — Прячем код от посторонних глаз
- Часть 3 — Под прицелом World of Warcraft 5.4.x (Структуры)
- Часть 4 — Под прицелом World of Warcraft 5.4.x (Перемещение)
- Часть 5 — Под прицелом World of Warcraft 5.4.x (Кастуем фаерболл)
1. Рассуждения на тему защиты игр
Хотелось бы разогнать тучи таинственности над античитами, протекторами и прочей сопутствующей с игрой защитой. Потому как вариантов защиты не так уж и много и я перечислю основные:
1. Проверка файлов игры на оригинальность
Здесь все просто, игра запрашивает с сервера контрольные суммы файлов игры и сверяет с теми, что получатся в результате проверки. В данном случае можно просто модифицировать алгоритм проверки в исполняемом файле. Но все намного сложнее, если алгоритм проверки, тоже подгружается с сервера, как в случае с Warden.
2. Анализ подключенных DLL
В данном случае вы рискуете, только используя нечто популярное, т.к. информация об этом, скорее всего уже есть у разработчиков и как в случае с Valve Anti Cheat, неизвестные библиотеки отправляются прямиком на сервер для анализа, опять таки, если вызовет подозрение. Так что ваш бан может быть всего лишь отсрочен на неопределенный срок.
3. Анализ VMT импортируемых библиотек на перехваты
Здесь вообще все сложно, вы можете получить бан даже за TeamViwer или Fraps, если разработчики игры так захотят и ничего вы не докажете. И как сказал Alexey2005 в Часть 0 — Поиск точки внедрения кода
Чем параноидальнее защита, тем больше у неё ложных срабатываний. Потому что в процесс постоянно ломится и внедряется огромная куча всего — всякие перехватчики клавиатурных нажатий, антивирусные сканеры, всевозможные «управляющие центры» для видео и звука, компоненты настроек устройств ввода вроде мыши и т.д.
4. Защита игровой памяти от чтения/записи
Данным функционалом обладает набирающая популярность в Steam защита Easy Anti Cheat. В Windows запускается сервис EasyAntiCheat, который защищает память игры от чтения и записи. Если же сервис не запущен, то игра отказывается соединяться с сервером и хотелось бы услышать размышления хабрасообщества на этот счет.
5. Проверка определенных участков памяти игры
Этим, опять же, занимается не без известная Warden. Хотя если честно, чем она только не занимается. Warden подгружает с сервера модули проверки памяти и хэш таблицу и сравнивает значения с таблицей. Этим способом легко определить читеров, которые модифицируют память игры для получения преимуществ, типа повышенной скорости, хождение по воздуху и стенам и прочее.
Итог
Из всего выше описанного, становится понятно, что используя популярные программы и методы мы очень рискуем попасть в бан и что бы этого избежать, нам необходимо сделать наше внедрение тяжело определяемым. Чем мы сейчас и займемся. Для начала напишем метод GetFakeCommand и ObfuscateAsm:
internal static string GetFakeCommand()
{
var list = new List<string> { "mov edx, edx", "mov edi, edi", "xchg ebp, ebp", "mov esp, esp", "xchg esp, esp", "xchg edx, edx", "mov edi, edi" };
int num = Random.Int(0, list.Count - 1);
return list[num];
}
internal static IEnumerable<string> ObfuscateAsm(IList<string> asmLines)
{
for (var i = asmLines.Count - 1; i >= 0; i--)
{
for (var k = Random.Int(1, 4); k >= 1; k--)
{
asmLines.Insert(i, GetFakeCommand());
}
}
for (var j = Random.Int(1, 4); j >= 1; j--)
{
asmLines.Add(GetFakeCommand());
}
return asmLines;
}
Как видно, GetFakeCommand возвращает ассемблерную команду, которая не меняет состояния регистров и флагов и этот список можно расширить при необходимости в несколько раз, а ObfuscateAsm — понатыкает эти команды в случайных местах нашей подпрограммы. И так модифицируем код подмены адреса из прошлой статьи в месте получения HookAddress, я не буду дублировать весь код, а только покажу измененную часть:
//Открыли процесс и главный поток
var RandomOffset = (uint)Random.Int(0, 60);
var HookAddress = Memory.AllocateMemory(6000 + Random.Int(1, 2000)) + RandomOffset;
//Зарезервировали память и сформировали подпрограмму
Memory.Asm = new ManagedFasm(Memory.ProcessHandle);
Memory.Asm.Clear();
foreach (var str in ObfuscateAsm(asmLine))
{
Memory.Asm.AddLine(str);
}
Этот же финт можно проделать и с памятью для аргументов argumentAddress1 и argumentAddress2. Обязательно запоминаем значение RandomOffset для случая освобождения памяти HookAddress.
Memory.FreeMemory(HookAddress - RandomOffset);
Memory.FreeMemory(argumentAddress1 - RandomOffsetArgs1);
Memory.FreeMemory(argumentAddress2 - RandomOffsetArgs2);
И изменим InjectAndExecute метод и как обещал, реализуем очередь для многопоточного вызова метода:
public byte[] InjectAndExecute(IEnumerable<string> asm, bool returnValue = false, int returnLength = 0)
{
lock (Locker)
{
var offset = 0;
var randomValue = (uint)Random.Int(0, 60);
//Наша очередь может хранить 80/4 = 20 значений
while (Memory.Read<int>(argumentAddress1 + offset) != 0 || Memory.Read<int>(argumentAddress2 + offset) != 0)
{
offset += 4;
if (offset >= 80)
{
offset = 0;
}
}
Memory.Asm.Clear();
foreach (var str in asm)
{
for (var i = Random.Int(0, 3); i >= 1; i--)
{
Memory.Asm.AddLine(ProtectHook());
}
Memory.Asm.AddLine(str);
}
dwAddress = Memory.AllocateMemory(Memory.Asm.Assemble().Length + Random.Int(60, 80)) + randomValue;
Memory.Asm.Inject(dwAddress);
Memory.Write<uint>(argumentAddress1, dwAddress + offset);
}
while (Memory.Read<int>(argumentAddress1 + offset) > 0)
{
Thread.Sleep(1);
}
byte[] result = new byte[0];
if (returnValue)
{
result = Memory.ReadBytes(Memory.Read<uint>(argumentAddress2 + offset), returnLength);
}
Memory.Write<int>(argumentAddress2 + offset, 0);
Memory.FreeMemory(dwAddress - randomValue);
return result;
}
В первом while цикле мы двигаемся по нашей очереди и ищем свободное место по смещению offset одновременно в двух зарезервированных адресах для аргументов, если не нашили, то начинаем с начала. Для маскировки добавили фейковых команд и рандомные смещение и размер для выделенной памяти. Пока это все, жду ваших комментариев, советов и интересных решений.
Автор: 3axap4eHko