Позбавляємо зору антічит систему багатокористувацьких ігор

  1. Зміст статті Вбудованою системою стеження за процесом і навколишнім середовищем, з метою протидії...
  2. безмовний доглядач
  3. Проти системи
  4. INFO
  5. Пошук даних за бінарним паттерну
  6. патчер
  7. INFO
  8. Незряче око Warden'а
  9. SOURCE
  10. Proof of concept
  11. Як щодо інших проектів Blizzard?
  12. Повна свобода дій

Зміст статті

Вбудованою системою стеження за процесом і навколишнім середовищем, з метою протидії різним неавторизованих модифікацій коду, вже нікого не здивувати: практично кожен більш-менш популярний багатокористувацький ігровий проект має щось подібне. У цій статті ми проаналізуємо використовувану розробниками з Blizzard клієнтську захист, а також реалізуємо один з ефективних способів її обходу.

WARNING

Автор і редакція нагадують, що вся інформація опублікована виключно в освітніх цілях, описані в статті дії можуть суперечити ліцензійною угодою Blizzard Entertaiment.

Warden (перекладається з англійської як доглядач, наглядач) - саме так вирішили назвати захисну систему розробники найпопулярніших в своїх жанрах ігор з Blizzard. Система, будучи фактично частиною Battle.net, використовується в таких проектах, як World of Warcraft, StarCraft II і Diablo 3. Тільки лише за офіційними даними за весь час були забанені десятки тисяч акаунтів Battle.net, і чимала частина при цьому - заслуга Warden .

безмовний доглядач

Для початку, мабуть, варто з'ясувати, що собою являє Warden. Система складається з двох частин: серверної і клієнтської, і, само собою, ми будемо мати справу тільки з клієнтською частиною. Як вже було сказано раніше, Warden не є невід'ємною частиною ігрового коду. Код клієнтської частини подгружается динамічно з Battle.net у вигляді образів, що віддалено нагадують по своїй структурі Portable Executable, які потім відображаються по випадковим адресами в адресному просторі ігрового процесу. Варто також відзначити, що велика частина коду клієнтської частини Warden обфусцірована і може змінюватися від однієї ігрової сесії до іншої.

Warden є пасивний механізм захисту, і все, чим займається клієнтська частина Warden, - це збір інформації, яка згодом вирушає серверної частини. В цілому приблизний алгоритм роботи клієнтської частини Warden виглядає наступним чином:

  1. Отримання списку відносних адрес для сканування.
  2. Зчитування необхідної кількості байт по кожному з адрес.
  3. Розрахунок хешів.
  4. Компонування пакета з хешамі і відправка його на сервер.

Процедура повторюється з певною періодичністю кілька разів на хвилину. Якщо серверна частина виявляє невідповідність хешів еталонних значень, вважається, що використовуються заборонені модифікації коду. Будь-яких негайних дій при цьому не робиться - аккаунт просто позначається як такий, що порушує правила, а про те, що ти «попався» можна буде дізнатися лише через деякий час, коли обліковий запис вже буде заблокована. Цілком аккаунт Battle.net (який може містити безліч прикріплених ліцензій) при цьому не блокується - тільки обліковий запис гри.

net (який може містити безліч прикріплених ліцензій) при цьому не блокується - тільки обліковий запис гри

Заблокована ігрова обліковий запис

Проти системи

Нейтралізувати Warden, просто відключивши його або заблокувавши його роботу, не вдасться: система влаштована таким чином, що серверна частина в будь-якому випадку повинна отримувати від клієнтської відповідні пакети, які, в свою чергу, повинні містити інформацію про сканування. Отже, вихід тільки один - не потрапляти. Домогтися цього можна як мінімум трьома способами:

  1. Обходити стороною свідомо небезпечні адреси при внесенні модифікацій в код.
  2. Використовувати непряме впровадження, перехоплюючи один з методів DirectX - Device.EndScene ().
  3. Ховати всі скоєні модифікації на льоту (при скануванні).

Перший варіант буде працювати до пори до часу і за великим рахунком обходом як таким не є. Другий варіант (перехоплення EndScene ()) дійсно непогано працює, функція викликається після завершення побудови кожного виведеного на екран кадру і перехоплюється, наприклад цілком легальними програмами відеозахвату, що не дає Warden можливості однозначно трактувати зміни в коді функції як заборонені модифікації. Проте варіант більше підходить для пошукових роботів і успішно ними експлуатується протягом уже декількох років. Третій варіант ідеально підходить для статичних модифікацій (як, наприклад, включення відтворення всієї карти в Star Craft - maphack), крім того, його реалізація сама по собі цікавіше і більш технологічні. Саме останній варіант докладно і розглянемо далі.

INFO

Всупереч загальній думці, ніякого витоку особистої інформації (у версії на момент написання статті) не відбувається: скануються лише деякі ділянки адресного простору ігрового процесу.

Очевидно, що для приховування вироблених модифікацій необхідно впровадитися в скануючий код Warden. Як відомо, цей код не присутній в процесі зі старту, до того ж при завантаженні отримує випадковий адресу. На перший раз його можна виявити за допомогою відладчика, просто встановивши breakpoint на читання будь-якого з сканованих адрес (від будь-якого старого, давно детектируемого хака). Наприклад, для останнього (на момент написання статті) билда World of Warcraft, встановивши breakpoint за відносним базі основного способу адресою 0x0045A6F0, ми потрапляємо в наступну ділянку коду:

Серце сканера Warden masm push esi push edi cld mov edx, dword ptr ss: [esp + 14h] mov esi, dword ptr ss: [esp + 10h] mov eax, dword ptr ss: [esp + 0Ch] mov ecx, edx mov edi, eax shr ecx, 2 je short; Тут дані потрапляють до тимчасового буфер, з якого буде розраховуватися хеш. ; Досить підставляти замість змінених байт оригінальні rep movs dword ptr es: [edi], dword ptr ds: [esi] mov cl, 3 and ecx, edx je short rep movs byte ptr es: [edi], byte ptr ds: [esi] pop edi pop esi ret

Досвідченим шляхом було встановлено, що виявлений код не піддається поліморфним змін, на відміну від усього іншого модуля, до того ж змінювався він за останні роки лише одного разу, що робить його ідеальною мішенню для впровадження. Але так як цей код - частина завантажується динамічно модуля, то необхідно буде також перехопити момент його появи в процесі, щоб внести зміни до першого виконання. У випадку з WoW завантажувач є частиною коду гри і знаходиться прямо в Wow.exe (для 32-бітової версії), його можна знайти, перелопативши кілометри лістингів в дизассемблера, або піти більш хитрим шляхом. Пам'ять під завантажуються образи модулів Warden виділяється функціейVirtualAlloc (), лог викликів, із зазначенням місця, звідки був зроблений виклик, буде містити адресу, що належить завантажувачу.

c ++ void VA_hook_ (DWORD dwCallAddr, DWORD dwMemBlock, DWORD dwSize) {if (dwMemBlock && dwSize> 0x2000) {Logger :: OutLog ( "Allocated block:%. 8x -% .8x, called from:%. 8x \ r \ n ", dwMemBlock, dwMemBlock + dwSize, dwCallAddr); }}

При цьому немає потреби перебирати всі записи: після логіна і входу на ігровий реалм необхідний модуль Warden буде вже завантажений, можна просто зробити пошук по всьому адресного простору процесу бінарного патерну, відповідного раніше знайденої процедурі сканування даних:

c ++ Scanner :: TPattern WardenPattern ( "\ x56 \ x57 \ xFC \ x8B \ x54 \ x24 \ x14 \ x8B \ x74 \ x24 \ x10 \ x8B \ x44 \ x24 \ x0C \ x8B \ xCA \ x8B \ xF8 \ xC1 \ xE9 \ x02 \ x74 \ x02 \ xF3 \ xA5 "," x26 "); DWORD WardenProc = (DWORD) Scanner :: ScanMem (& WardenPattern); if (WardenProc) {Logger :: OutLog ( "Warden :: Scan proc: 0x% .8X \ r \ n", WardenProc); } Else Logger :: OutLog ( "Warden :: Scan proc not found \ r \ n"); c ++ Scanner :: TPattern WardenPattern ( \ x56 \ x57 \ xFC \ x8B \ x54 \ x24 \ x14 \ x8B \ x74 \ x24 \ x10 \ x8B \ x44 \ x24 \ x0C \ x8B \ xCA \ x8B \ xF8 \ xC1 \ xE9 \ x02 \ x74 \ x02 \ xF3 \ xA5 , x26 );  DWORD WardenProc = (DWORD) Scanner :: ScanMem (& WardenPattern);  if (WardenProc) {Logger :: OutLog ( Warden :: Scan proc: 0x% Пошук завантажувача

Таким чином ми визначимо точне місце перебування необхідного нам коду Warden, а лог викликів VirtualAlloc () дозволить визначити, звідки саме була запрошена пам'ять під цей код, вказавши тим самим на завантажувач модулів Warden. Проаналізувавши в дизассемблера код завантажувача, можна знайти підходяще місце для перехоплення. Для цього потрібно знайти відповідний момент, коли всі секції образу, отриманого з Мережі, будуть успішно відображені в АП процесу, після цього можна буде впроваджувати перехоплення, що модифікує код Warden. Відповідним ділянкою може бути виклик VirtualProtect (), який встановлює фінальні права доступу до секцій:

masm lea ecx, [ebp + flOldProtect] push ecx; lpflOldProtect push dword ptr [esi + 8]; flNewProtect push eax; dwSize push ebx; lpAddress call ds: VirtualProtect test byte ptr [esi + 8], 0F0h jz short loc_A5BE9C push [ebp + dwSize]; dwSize push ebx; lpBaseAddress call ds: GetCurrentProcess push eax; hProcess call ds: FlushInstructionCache masm lea ecx, [ebp + flOldProtect] push ecx;  lpflOldProtect push dword ptr [esi + 8];  flNewProtect push eax;  dwSize push ebx;  lpAddress call ds: VirtualProtect test byte ptr [esi + 8], 0F0h jz short loc_A5BE9C push [ebp + dwSize];  dwSize push ebx;  lpBaseAddress call ds: GetCurrentProcess push eax;  hProcess call ds: FlushInstructionCache   Завантажувач модулів Warden Завантажувач модулів Warden

Код функції-трампліну, перехід на яку встановлений замість call ds: VirtualProtect, може виглядати наступним чином:

c ++ // Викликається для кожної секції __declspec (naked) void WardenLoader_hook (LPVOID lpAddress, SIZE_T dwSize, DWORD flNewProtect) {__asm ​​{push ebp mov ebp, esp pushad} if (flNewProtect == PAGE_EXECUTE_READ) // Для секції, що містить виконуваний код WardenModulePatch (lpAddress, dwSize); // патч код Warden __asm ​​{popad pop ebp jmp dword ptr [VirtualProtect]}}

Пошук даних за бінарним паттерну

Щоб робити модифікують код патчі не залежними від версії ігор і не перебивати раз по раз зміщення, буде потрібно можливість пошуку за допомогою бінарного паттерну (шаблоном). У цьому випадку на основі коду, що вимагає зміни, створюється патерн, що містить достатньо інформації для того, щоб при збігу можна було впевнено сказати, що знайшлося саме те, що потрібно. Існує маса різних можливостей реалізації пошуку за шаблоном. У пропонованому мною вирішенні пошук проводиться за шаблоном виду: xA? B (де A і B - натуральні числа, x - точний збіг байт, кількість яких зазначено наступними символами,? - пропускалися байти).

приклад:

c ++ // ініціалізувавши патерн // Перший параметр - дані для порівняння, другий - шаблон порівняння Scanner :: TPattern SamplePattern ( "\ x56 \ x57 \ xFC \ x00 \ x00 \ x90", "x3? 2x1"); / * Цей патерн відповідає даним, у яких перші три байта збігаються з 0x56, 0x57, 0xFC, потім йдуть два довільних байта, а останній збігається з 0x90 * / // Пошук по заданому паттерну в обмеженій області, з початком pMemBase і розміром dwSize DWORD dwProc = (DWORD) Scanner :: FindPattern (pMemBase, dwSize, & SamplePattern);

Повні вихідні можна подивитися в доданому проекті.

патчер

Для того щоб була можливість приховувати свої дії від очей Warden, необхідно запам'ятовувати абсолютно всі вироблені в пам'яті процесу зміни і мати доступ до оригінальних даними, які існували до внесення змін. Будь-які зміни (перехоплення, підміни та інше) повинні проводитися одним і тим же засобом, який має гарантувати виконання викладених вимог:

c ++ / * pAddr - покажчик на місце виробленої модифікації pData - дані для заміни dwDataSize - розмір даних * / BOOL Patcher :: MakePatch (PBYTE pAddr, PBYTE pData, DWORD dwDataSize) {BOOL fRes = false; DWORD dwOldp; if (VirtualProtect (pAddr, dwDataSize, PAGE_EXECUTE_READWRITE, & dwOldp)) {// Запам'ятовуємо оригінальні байти pPatchStruc = & Patches [dwPatches]; // Останній елемент pPatchStruc-> addr = dwAddr; pPatchStruc-> len = dwSize; memcpy (pPatchStruc-> org, (PVOID) dwAddr, dwSize); // Записуємо нові memcpy (pAddr, pData, dwDataSize); dwPatches ++ fRes = true; } Return fRes; }

Список структур, що містить інформацію по всім досконалим в процесі змін, може належати об'єкту з глобальної областю видимості. Зміна коду тепер може проводитися приблизно наступним чином:

c ++ bool PatchVirutalProtect () {bool bRetval = false; PBYTE bCode = (PBYTE) "\ xE8 \ x90 \ x90 \ x90 \ x90 \ x90"; // call rel32 DWORD pProc = (DWORD) GetProcAddress (GetModuleHandleA ( "KernelBase.DLL"), "VirtualProtect"); * ((PDWORD) (bCode + 1)) = (DWORD) & VP_hook - ((DWORD) pProc + 5); if (Patcher :: Instance () -> MakePatch ((PBYTE) pProc, bCode, 5)) {Logger :: OutLog ( "VirtualProtect patched at:% x \ r \ n", pProc); bRetval = true; } Else Logger :: OutLog ( "VirtualProtect patch failed \ r \ n"); return bRetval; }

Використання централізованого Патчер не доставляє додатковий клопіт, при цьому крім простого доступу до оригінальних даними ми отримуємо можливість відкотити будь-яка зміна, повернувши все до первісного стану, що іноді буває дуже корисно.

INFO

Якщо тебе зацікавила представлена ​​в статті тематика і ти хочеш покопати ще глибше, то можу порекомендувати, можливо, кращий спеціалізований форум .

Незряче око Warden'а

Тепер, коли є вся необхідна інформація і інструменти, залишилося підмінити сканує процедуру Warden своєї, яка замість модифікованих даних буде підставляти оригінальні. Хеші в такому випадку будуть ідентичні тим, що зберігаються на сервері, і зміни коду залишаться непоміченими.

Щоб в поле зору Warden не потрапило жодного зміненого байта, при кожному виклику сканування необхідно шукати перетин множин сканованих адрес з адресами пропатченних даних. Так як патчі, швидше за все, не будуть йти один за іншим (це не має сенсу) - буде максимум одна те для одного ськана і дані можна буде брати зі структури, пов'язаної з якимось одним конкретним патчем. Всі можливі варіанти перетинів зводяться до одного подвійному умові: або адреса початку безлічі сканованих байт входить в безліч адрес патча, або навпаки. Таким чином, ми повинні перебирати всі патчі, перевіряючи задана умова:

c ++ // потрапляють скановані адреси під будь-якої патч? for (unsigned int i = 0; i <dwPatches; i ++) // перебираємо всі патчі if ((PatchList [i] .addr - dwAddr <dwSize) || (dwAddr - PatchList [i] .addr <PatchList [i]. len)) // Знаходимо перетин {pCurrentPatch = & (PatchList [i]); break; }

Отримавши пов'язану з поточним скануванням структуру з інформацією про патч, підмінити дані не важко:

c ++ if (! pCurrentPatch) {// Сканується непропатченних область - копіюємо безпосередньо memcpy (pOutBuff, (PVOID) dwAddr, dwSize); } Else {// побайтовой обробка for (unsigned int i = 0; i <dwSize; i ++) {unsigned int delta = dwAddr + i - pCurrentPatch-> addr; byte * pCurrent; // Чи був байт за цією адресою пропатчений? if (delta <pCurrentPatch-> len) pCurrent = pCurrentPatch-> org + delta; else pCurrent = (PBYTE) (dwAddr + i); pOutBuff [i] = * pCurrent; }}

Використовуючи наведений код замість оригінальної процедури сканування, ми можемо контролювати активність Warden, не даючи йому можливості виявити будь-які внесені в код зміни, навіть в тому випадку, якщо Warden спробує перевірити на цілісність самого себе.

Лог активності Warden

SOURCE

У статті наведено полегшений і неповний вихідний код, автор створив повноцінний робочий проект, який додається до статті. Чи не полінуйся і зазирни в вихідні, цілком можливо, що ти знайдеш там щось корисне для себе.

Proof of concept

В якості демонстрації працездатності обходу з витяганням якоїсь практичної користі було прийнято рішення провести модифікацію коду World of Warcraft за відносним зміщення 0x008C9A3E, яке перевіряється сканером Warden. Процедура, що відповідає цьому зміщення, відповідальна за перевірку прав на виконання Lua-скрипта (багато з функцій WoW API заблоковані для користувача і можуть бути використані тільки рідним призначеним для користувача інтерфейсом). Ділянка коду в області цього зміщення виглядає наступним чином:

masm mov ebp, esp mov edx, dword ptr ss: [ebp + 8] mov eax, dword ptr ds: [17A5B10] xor ecx, ecx push esi cmp dword ptr ds: [15FBAA8], ecx je short 01309A84 cmp edx, 22

Саме зміщення відповідає умовному переходу після порівняння глобальної змінної, що містить ідентифікатор рівня доступу для поточного контексту, з нулем (нуль відповідає найвищим прав). Замінивши умовний перехід безумовним, отримуємо можливість використовувати будь-які функції WoW API, створюючи складні і «розумні» скрипти, що автоматизують багато ігрові дії (найпримітивніший приклад використання: забіндити всю ротацію спеллов на одну кнопку, з перевіркою кулдаун і так далі, що зробити спочатку неможливо ). Спрощений код установки патча виглядає приблизно так:

c ++ PBYTE bCode = (PBYTE) "\ xEB"; // JMP SHORT Scanner :: TPattern Pattern ( "\ x33 \ xC9 \ x56 \ x39 \ x0D \ xFF \ xFF \ xFF \ xFF \ x74 \ x44 \ x83 \ xFA \ x22", "x5? 4x5"); DWORD dwProc = (DWORD) Scanner :: ScanMem (& Pattern); if (dwProc) {DWORD dwProcChangeOffset = dwProc + 9; if (Patcher :: Instance () -> MakePatch ((PBYTE) dwProcChangeOffset, bCode, 1);}

Після установки патча стають доступні прямо з макросів «захищені» функції WoW API, а в балці активності Warden ми можемо спостерігати запобігли спроби просканувати пропатченних область. Переконатися в цьому ти можеш, скомпілювавши і випробувавши додаються до статті вихідні.

Як щодо інших проектів Blizzard?

У статті було розглянуто варіант перехоплення коду завантажувача для WoW, у інших проектів цей код знаходиться в обфусцірованной бібліотеці battle.net.dll, по якій в принципі неможливо створити не залежить від версії бібліотеки патерн для пошуку коду завантажувача. У цьому випадку, як один з варіантів, можна перехоплювати всі виклики VirtualProtect (), вчинені з battle.net.dll, обробляючи їх приблизно в такий спосіб:

c ++ void VP_hook_internal (DWORD dwCallAddr, DWORD dwMemBlock, DWORD dwSize, DWORD flNewProtect) {// Виклик був проведений з battle.net.dll if (dwCallAddr - WardenLoaderHack :: dwBNetBase <WardenLoaderHack :: dwBNetImageSize) {// Секція коду if (dwMemBlock && flNewProtect == PAGE_EXECUTE_READ) {MEMORY_BASIC_INFORMATION Mem; // Шукаємо початок блоку пам'яті if (VirtualQuery ((PVOID) dwMemBlock, & Mem, sizeof (MEMORY_BASIC_INFORMATION))) {// Перші чотири байти - сигнатура модуля Warden if (* (PDWORD) Mem.AllocationBase == '2LLB') {Logger :: OutLog ( "Warden image found at:%. 8X, code section:%. 8X \ r \ n", Mem.AllocationBase, dwMemBlock); // патч код Warden WardenModulePatch (dwMemBlock, dwSize); }}}}}

Повна свобода дій

Можливість безкарно вносити будь-які зміни в ігровий клієнт відкриває найширші перспективи для подальших досліджень. Насправді в іграх Blizzard можна створити абсолютно все, що тільки можна собі уявити або захотіти. Про одних лише можливості розблокованих скриптів Lua в WoW можна було б написати окрему статтю. Адже навіть прості скрипти можуть позбавити гравця від рутинних дій або знизити залежність від реакції і уважності, дозволивши приділяти трохи більше часу іншим речам. При цьому можливості вільних модифікацій клієнта не обмежуються простий розблокуванням тих чи інших можливостей. Загалом, дерзай!

У пропонованому мною вирішенні пошук проводиться за шаблоном виду: xA?
Де A і B - натуральні числа, x - точний збіг байт, кількість яких зазначено наступними символами,?
X56 \ x57 \ xFC \ x00 \ x00 \ x90", "x3?
X33 \ xC9 \ x56 \ x39 \ x0D \ xFF \ xFF \ xFF \ xFF \ x74 \ x44 \ x83 \ xFA \ x22", "x5?
Як щодо інших проектів Blizzard?

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

Или позвоните нам по телефонам: (048) 823-25-64

Организация (обязательно) *

Адрес доставки

Объем

Как с вами связаться:

Имя

Телефон (обязательно) *

Мобильный телефон

Ваш E-Mail

Дополнительная информация: