Автоматизация действий игрока всегда была серьёзной проблемой таких MMORPG, как World of Warcraft и Runescape. Подобный взлом игр сильно отличается от традиционных читов, например, в шутерах.

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

Ботоводство


Последние несколько месяцев аккаунт sch0u круглосуточно играл на сервере world 67, выполняя такие монотонные задачи, как убийство мобов и сбор ресурсов. На первый взгляд, этот аккаунт похож на любого другого игрока, но есть одно важное отличие: это бот.


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

Поэтому единственное, что я смог понять — это важность перемещений мыши, но так ли это на самом деле?

Эвристики!


Для подтверждения этой теории я начал с изучения клиента Runescape и почти сразу заметил глобальную переменную hhk, задаваемую вскоре после запуска.

const auto module_handle = GetModuleHandleA(0);
hhk = SetWindowsHookExA(WH_MOUSE_LL, rs::mouse_hook_handler, module_handle, 0);

Этот код устанавливает на мышь низкоуровневый перехватчик, прицепляемый к системной цепочке перехватчиков. Это позволяет приложениям под Windows перехватывать все события мыши, даже если они не связаны с конкретным приложением. Низкоуровневые перехватчики (хуки) часто используются кейлоггерами, но могут применяться и в мирных целях, например, в эвристиках, как вышеупомянутый перехватчик мыши.

По сути, обработчик мыши в Runescape довольно прост (псевдокод для красоты был подправлен вручную):

LRESULT __fastcall rs::mouse_hook_handler(int code, WPARAM wParam, LPARAM lParam)
{
  if ( rs::client::singleton )
  {
      // Call the internal logging handler
      rs::mouse_hook_handler_internal(rs::client::singleton->window_ctx, wParam, lParam);
  }
  // Pass the information to the next hook on the system
  return CallNextHookEx(hhk, code, wParam, lParam);
}

void __fastcall rs::mouse_hook_handler_internal(rs::window_ctx *window_ctx, __int64 wparam, _DWORD *lparam)
{
  // If the mouse event happens outside of the Runescape window, don't log it.
  if (!window_ctx->event_inside_of_window(lparam))
  {
    return;
  }

  switch (wparam)
  {
    case WM_MOUSEMOVE:
      rs::heuristics::log_movement(lparam);
      break;
    
    case WM_LBUTTONDOWN:
    case WM_LBUTTONDBLCLK:
    case WM_RBUTTONDOWN:
    case WM_RBUTTONDBLCLK:
    case WM_MBUTTONDOWN:
    case WM_MBUTTONDBLCLK:
      rs::heuristics::log_button(lparam);
      break;
  }
}

для снижения нагрузки на канал эти функции rs::heuristics::log_* используют простые алгоритмы для пропуска данных событий, напоминающих ранее зафиксированные события.

Позже эти данные событий парсятся функцией rs::heuristics::process, которая вызывается в каждом кадре основного цикла рендеринга.

void __fastcall rs::heuristics::process(rs::heuristic_engine *heuristic_engine)
{
  // Don't process any data if the player is not in a world
  auto client = heuristic_engine->client;
  if (client->state != STATE_IN_GAME)
  {
    return;
  }

  // Make sure the connection object is properly initialised
  auto connection = client->network->connection;
  if (!connection || connection->server->mode != SERVER_INITIALISED)
  {
    return;
  }

  // The following functions parse and pack the event data, and is later sent
  // by a different component related to networking that has a queue system for
  // packets.

  // Process data gathered by internal handlers
  rs::heuristics::process_source(&heuristic_engine->event_client_source);

  // Process data gathered by the low level mouse hook
  rs::heuristics::process_source(&heuristic_engine->event_hook_source);
}

Away from keyboard?


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

Запретив игре вызывать функцию rs::heuristics::process, я поначалу ничего не заметил, однако спустя ровно пять минут меня разлогинил сервер. Очевидно, Runescape определяет неактивность игрока исключительно по передаваемым клиентом на сервер данным эвристик, даже если вы вполне нормально можете играть. Это вызвало новый вопрос: если сервер думает, что я не играю, думает ли он, что я играю ботом?.

После этого я потратил ещё несколько дней на реверс-инжиниринг сетевого слоя игры, благодаря чему мой бот теперь может делать почти что угодно при помощи одних сетевых пакетов.

Для подтверждения своей теории я круглосуточно и семь дней в неделю занимался ботоводством, даже не двигая мышью. Проведя так тысячи часов, я могу со всей уверенностью сказать, что или система обнаружения ботов использует только данные событий эвристик, отправляемых серверу клиентом, или работает только когда игрок не «afk». Любой игрок, которому удаётся играть, не двигая курсором, должен быть немедленно забанен, то есть разработчикам стоит обратить внимание на эту недоработку.