Pull to refresh

Comments 61

UFO just landed and posted this here
Данный подход сейчас не всегда легок в реализации, чем рисование поверх игры.
Продолжайте, очень интересно посмотреть на эмуляцию поведения.
А вот у меня есть вопросик из соседней области: а как бы вывести свои какие-то данные прямо в игровом окне (или поверх) в полноэкранном режиме? Например какую-то картинку. Ибо в полноэкранном режиме может быть только одно окно. Может как-то после того, как игра отрисует свою сцену, можно к сцене добавить, например, какой-то полигончик с текстуркой?
Насколько я знаю софт который использует OSD(on-screen display)/Screen overlay работает или напрямую с видеобуфером, или инжектится в вызовы direct3d.
если речь идет о вмешательстве в игровой процесс, то незаметного способа в полноэкранном режиме нет. Незаметный же способ в общем случае — обычно достают всю информацию о сцене через ReadProcessMemory, и необходимую информацию (например положение остальных игроков) рисуют на втором окне (отдельное приложение), которое располагается поверх основного, имеет прозрачную форму, находится в топе, не имеет границ и фокуса. При этом основное окно, чаще всего можно сделать тоже borderless, приближаясь по картинке к fullscreen.
Проблема-то как раз в полноэкранном режиме. Если режим не полноэкранный — положить окно сверху не проблема. А вот окно в полноэкранном режиме все равно будет выше.
В принципе можно, но будет кривовато. Для этого перехватываем например Present, который отвечает за вывод буфера на экран:
STDMETHOD(Present)(THIS_ CONST RECT* pSourceRect,CONST RECT* pDestRect,HWND hDestWindowOverride,CONST RGNDATA* pDirtyRegion) PURE;
Затем вызываем BeginScene, рисуем, вызываем EndScene и наконец вызываем оригинальный Present.
Интересно, к чему вся эта интрига с ручным рассчетом адресов и ассемблером, если можно взять Detours или madCodeHook и вешать хуки просто по именам функций или методов интерфейсов.
Для это можно модифицировать сам исполняемый файл (это очень легко сделать, но и легко определить и получить бан) или внедрить DLL (это тоже определяется очень просто), но это все не для нас
Мой знакомый однажды в своих целях спрятал все текстуры в LA2, чтобы можно было видеть игроков сквозь стены и ограды (от них остались только остовные деревья). Поделился с людьми — обвинили в кейлоггерстве или вроде того, расстроился и больше никогда ни с кем не делился =\
Так тут серия статей намечается чтобы вы сами могли себе сделать. Свой код в кейлоггерстве не обвинишь.
Насколько я помню, текстуры легко отключались переключением рендерера в wireframe режим, вполне штатный для движка. Другое дело, что для правки конфига нужно было знать приватный RSA ключ, который, впрочем, на 99% фришек был дефолтный.
Кто может помочь восстановить алгоритм одной небольшой функции из сишной библиотеки? Самому к сожалению не хватает знаний ASM/C чтобы разобрать псевдокод полученный в Ida. Извините за оффтоп.
Попробуйте плагин Hex-Rays
Его и использовал, получил
псевдокод
//----- (00000CBC) --------------------------------------------------------
int __fastcall kEncode(int a1, int a2, int a3, int a4)
{
  int v4; // r3@2

  if ( a1 == 1 )
    v4 = crc((const char *)a2, (const char *)a3, (char *)a4);
  else
    v4 = 1;
  return v4;
}

//----- (00000D0C) --------------------------------------------------------
int __fastcall crc(const char *a1, const char *a2, char *a3)
{
  size_t v3; // r3@10
  char *v5; // [sp+4h] [bp-28h]@1
  const char *v6; // [sp+8h] [bp-24h]@1
  const char *v7; // [sp+Ch] [bp-20h]@1
  signed int v8; // [sp+10h] [bp-1Ch]@1
  size_t v9; // [sp+14h] [bp-18h]@1
  unsigned int v10; // [sp+18h] [bp-14h]@12
  char *s; // [sp+1Ch] [bp-10h]@1
  signed int v12; // [sp+20h] [bp-Ch]@1
  unsigned int n; // [sp+24h] [bp-8h]@12

  v7 = a1;
  v6 = a2;
  v5 = a3;
  v8 = -1;
  v9 = -1;
  s = 0;
  v12 = 0;
  while ( v12 <= 2 )
  {
    if ( (signed int)v9 <= 0 )
    {
      ++v12;
      if ( v12 == 1 )
      {
        s = (char *)v7;
        v9 = strlen(v7);
      }
      else if ( v12 == 2 )
      {
        s = (char *)v6;
        v9 = strlen(v6);
      }
    }
    else
    {
      s += v9;
    }
    if ( v12 <= 2 )
    {
      v3 = v9;
      if ( (signed int)v9 >= 20480 )
        v3 = 20480;
      n = v3;
      memcpy(byte_4004, s, v3);
      v10 = n;
      while ( v10 )
      {
        --v10;
        if ( !(v10 & 1) )
          byte_4004[v10] = ~byte_4004[v10];
      }
      v8 = make_crc(v8, (int)byte_4004, n);
      v9 -= n;
    }
  }
  sprintf(v5, (const char *)&unk_25FC, v8);
  return 0;
}

//----- (00000EE4) --------------------------------------------------------
int make_crc32_table()
{
  unsigned int v1; // [sp+4h] [bp-10h]@2
  signed int i; // [sp+8h] [bp-Ch]@1
  signed int j; // [sp+Ch] [bp-8h]@2

  for ( i = 0; i <= 255; ++i )
  {
    v1 = i;
    for ( j = 0; j <= 7; ++j )
    {
      if ( v1 & 1 )
        v1 = (v1 >> 1) ^ 0xEDB88320;
      else
        v1 >>= 1;
    }
    crc32_table[i] = v1;
  }
  return 0;
}
// EE4: using guessed type int make_crc32_table(void);

//----- (00000FC4) --------------------------------------------------------
int __fastcall make_crc(int a1, int a2, unsigned int a3)
{
  unsigned int v4; // [sp+4h] [bp-20h]@1
  int v5; // [sp+8h] [bp-1Ch]@1
  int v6; // [sp+Ch] [bp-18h]@1
  unsigned int i; // [sp+14h] [bp-10h]@3

  v6 = a1;
  v5 = a2;
  v4 = a3;
  if ( !initcrc )
  {
    initcrc = 1;
    make_crc32_table();
  }
  for ( i = 0; i < v4; ++i )
    v6 = crc32_table[(unsigned __int8)(*(_BYTE *)(v5 + i) ^ v6)] ^ ((unsigned int)v6 >> 8);
  return ~v6;
}
// EE4: using guessed type int make_crc32_table(void);


Но не все в нем понимаю, например что значат ~ перед переменными, что за byte_4004(просто еще одна переменная?) и тд.
byte_4004 — глобальный массив (похоже, размера 20480 байт), ~ — битовое «не» в С.
А почему вам так не нравится подход со сканированием пикселей и имитацией нажатий? Грамотная реализация, особенно если заменить сканирование экрана на прослушку трафика (я в свое время через pcap делал), вычисляется только анализом поведения. Напротив, взаимодействие с процессом вычисляется защитой на раз-два, зачастую даже без хуков на уровне драйвера ядра.
Просто потому, что пиксели — это гадание на кофейной гуще (особенно если в игре есть погода и время суток), трафик — несомненно информативен, но read only, а имитация занимает полностью ваш ПК и что бы работать на нем Вам понадобиться виртуальная машина, которая съест дополнительно 2Гб RAM и 40Гб HDD, а если два окна или 3? Даже при грамотной реализации, это все трудоемко и не надежно. А вот вычислить защитой можно все на самом деле и здесь главное уметь хорошо прятать — о чем я тоже буду писать
Позвольте уточнить, трафик read-only только в случае с pcap.
Как минимум, трафик можно редактировать посредством прокси(из готовых решений — тот же WPF-или-как-там-его-и-его-аналоги). Защита от подобных программ далеко не всегда работает, взять тот же Hackshield в Archeage. Кроме того, это — довольно универсальный способ.
Кроме того, зачастую довольно удобно управлять поведением игры при помощи упомянутого dll-inject'а и перехватом нужных функций. Это, кстати говоря, решает проблему с окнами — dll много памяти не скушает, всю работу, по факту, выполняет клиент. Кроме того, если у клиента нет проверки на checksum, а защита не расчитана на подобные экзерсисы, можно поправить таблицу импорта и подгружать dll уже на старте приложения, а не перетыкать ее инжектором.
Еще довольно неплох в плане подхода micromacro — есть базовые функции для работы с памятью, а вся логика строится на lua/xml(код/путевые точки). Главный минус — вряд ли будет работать с адекватной защитой.
Со сканированием пикселей — согласен, сам такой подход не пробовал, но для комплексных задач представляется довольно сложным в реализации и несколько ограниченным в функционале.

За способ, описанный в статье, отдельное спасибо(особенно — за шарп), интересный подход. Еще вместо расчета офсета вручную можно было бы применить GetProcAddress, но это работает только для экспортированных функций.

И по поводу следующих статей — однозначно писать!
Очень напомнило, как когда-то на 2-ом курсе в качестве курсовика по ассемблеру написали с однокурсником бота, который тоже работает засчёт перехвата функций.
Как внедриться в чужой процесс брали здесь: rsdn.ru/article/baseserv/IntercetionAPI.xml#EIEAC
А вот описанный в той же статье метод перехвата не подошёл, поэтому DirectX многопоточен. Так что преподаватель подсказал сделать как в Detours (гуглить: file:huntusenixnt99.pdf).
Вот видосики, где бот играет, может и про него статью напишем: www.youtube.com/user/thbotproject/videos
Сайт бота: thbot.kt15.ru/

Теперь насчёт технических деталей: мы тоже перехватывали, руками вытаскивая offset из vtable. А потом, когда всё уже сделали, поняли, что можно было C-шное API перехватывать.
Посмотрел видосики… Нео и Агент Смит нервно курят, глядя на эту анимешную девочку, как она проскакивает между пуль…
Спасибо) Вот только тактики нашему боту не хватает, он думает про текущий момент и всё.

А про Вашего бота, конечно же, жду продолжение, ведь бот для MMORPG — это в разы интереснее!
У шариков линейное движение, вы можете знать их траектории, вычислив их скорость по 2м точкам например и найти места где они не пролетают с учетом времени движения до этих мест и сразу двигаться к ним
Если думает только про текущий момент — как так круто получилось реализовать безумные увороты и сбор бонусов?
В каждый момент есть позиция, к которой бот стремится добраться. Когда на экране нет бонусов и врагов, то это заданная позиция. Когда нет бонусов и есть враги, то это позиция напротив врага, а когда есть бонусы, то это позиция одного из них.
Бот пытается в неё добраться, но при этом анализирует все пульки в небольшом радиусе вокруг себя и избегает их. Примерно так.
UFO just landed and posted this here
Это всё конечно здорово, но в очередной раз про x86 и DX9.
А современный мир использует x64 и DX11.
DX11 — я тоже описал, а вот x64 очень легко получить из x86, пару бессонных ночей и готово
Про пару магических чисел сказали, а об остальных умолчали :) Почему 5 и 7 при чтении?
У Вас есть 2 варианта узнать =)
  1. Почитать документацию по АСМу
  2. Дождаться следующей части
Асм — настолько огромный мир, что читать можно очень долго и не узнать все :)
Видимо автор таким образом пытается определить сколько места в начале функции потребуется для записи инструкций хука, что сделано крайне некошерно
Давайте будем конструктивными, как бы Вы сделали?
Для таких целей используют дизассемблер длин, иначе нет никакой горантии что пролог функции не будет повреждён. Вы ведь не имеете 100% представления сколько инструкций будут перезаписаны
Абсолютно с Вами согласен, но написание своего Length Disassembler'а заслуживает отдельной статьи, а так как мы перехватываем известную функцию на известной архитектуре, с которой я естественно предварительно познакомил свой дизасемблер, то возникает резонный вопрос: «А зачем?»
Никто не гарантирует что пролог будет во всех бинарниках одинаковым и будет начинатся именно с конкретной инструкции, это нигде не документировано и не факт что вы не нарвётесь на уже установленный хук. Конечно оно может работать и так, но это всёравно будет не корректная реализация(см. реализацию движков перехватчиков). Что касается готового дизассемблера длин, то на ASM\С\С++ с этим проблем нету, как на дела обстаят на .net я не в курсе.
Ну я думаю, если кому это понадобится, тот запросто соберет для себя DLL и подключит к своему проекту или портирует в .net. Задача моей статьи показать как это реализовать в черновом варианте и не более чем. Но за замечание спасибо, для универсальности кода, это очень хорошее замечание.
Очень интересно! Как раз на днях задавался таким вопросом, как написать бота для мморпг. Жду продолжения. Попробую реализовать нечто похожее по примерам.
Спасибо большое за хорошую статью. Мне всегда была интересна эта тема, но до стремления изучить её так и не дошло. Благодаря вашим статьям мне удастся изучить принципы и методы в этой области. Буду с нетерпением ждать продолжения.

Спасибо.
Warden в состоянии определить подмену адресов в IAT, будьте осторожны с этим!
Вообще статьи как таковой нет, вопрос «писать или нет» вместился бы в комментарий.
Наносимый «вред» для разработчика игры измеряется, например, в количестве установок бота * средние потери. Допустим, Blizzard как-то терпит последних 3.5 линуксоида с вайном (вот уж где поле для внедрений… подход с внедрением своего графического слоя у форка вайн ака winex прожил пару лет), но любой среднестатистический ботик, подгружающий дллку, будет убит через n инсталлов, да еще и улучшит ситуацию с детектированием последующих версий :) Делать «для себя и красиво» можно, да, даже если оно будет стоить месяца работы, работать на трех машинах в связке и анализировать сразу и трафик, и графику, и еще и менять трафик…

Линкану www.unknowncheats.me/forum/anti-cheat-bypass/60881-gameguard-d3d-vtable-hooks-get-detected.html, даже самая занюханная защита уже умеет сканировать память процесса в поиске очевидных дыр, с учетом проверки окружения (dll, менее важно — драйвера), вот так с ходу не верится, что в 2015 остались недетектируемые способы напакостить в win-процессе.
С другой стороны, способов сделать неотключаемую защиту не осталось еще раньше… Хотя, конечно же, снятие защиты требует совсем других познаний, нежели написание бота.
недетектируемые способы напакостить в win-процессе
Через hardware breakpoint-ы, например?
Плюсую, я писал чит для Dota 2 который не обнаруживается VAC именно с HB. Кстати, сам процесс разработки был гораздо более сложен и интересен, чем то, что предлагает автор. Хуки vtable уже давно обнаруживаются. Хуки с EndScene тоже. Мне ещё пришлось написать внедрение shared-библиотек в рантайме, и всё это было под Linux (это тоже заслуживает отдельной статьи). В общем, я бы с радостью поделился, но не могу, ибо чит приватный и писался не для себя :D
Очень интересно было бы почитать про используемые подходы. Сам чит тут вряд ли кому-то нужен.

Может быть получится не нарушая условий написать статью?
Тут дело в том, что приватные читы не привлекают внимания и их не фиксят годами (иногда вообще не фиксят). А как только я напишу статью, появятся желающие повторить это => метод привлечёт внимание => ставит под угрозу чит. И да, читы запрещены и к тому же вредят коммюнити.
о чем и речь :)
как только чит из «неизвестного, но теоретически детектируемого» перерастает в «известный, надо детектировать» — становится плохо
Просвятите, пожалуйста, в чем сложность прочитать debug регистры? Многие упаковщики с легкостью детектят это и успешно снимают железные брейкпоинты.
В том, что, фигурально выражаясь, кто раньше встал — того и тапки. В ring3 эти регистры нельзя прочитать напрямую (будет #GP), поэтому в Win32 для usermode-приложения есть только один способ — через структуру CONTEXT, которую можно получить несколькими путями. Самый очевидный — GetThreadContext(), чтобы задетектить отладку, и SetThreadContext(), чтобы ее снять. Второй способ — спровоцировать SEH-исключение, посмотреть CONTEXT *ContextRecord в _except_handler() и поменять при необходимости.

В первом случае достаточно сесть поверх этих двух функций, и игнорировать Dr#. Во втором надежнее всего будет сесть поверх KiDispatchException и просто возвращать копию контекста вместо самого контекста, с которой потом поступать по усмотрению.

Что касается обнаружения самого факта отладки — если не садиться внутри кода приложения, а только на экспорты, то анализ времени выполнения ничего не даст — приложение принципиально не может гарантировать, сколько будет выполняться тот или иной вызов DirectX. Есть еще всякие экзотические способы типа недокументированного изменения EAX после вызова OutputDebugString, но это тоже, в принципе, просто обходится.
А что насчет анализа времени выполнения GetThreadContext()?..
А сколько она должна выполняться на самом деле, например? С учетом того, что поток в любой момент может переключиться и что операционная система, в принципе, не обязана совсем честно обслуживать все потоки, с учетом того, что кому-то может придти в голову сожрать всю оперативку и начать свопиться, или вообще в системе есть антивирус… Мало ли по какой причине функции могут выполняться неодинаковое время :) Эта защита надежно работает только против трассировки процессов — когда между двумя вызовами должно пройти, скажем, порядка 20 мс, а проходит 5 сек — значит, кто-то сидит и вдумчиво жмет F10 в софтайсе.
Можно же позвать системный вызов NtGetContextThread через sysenter, и тогда перехватывать его необходимо еще в ядре. Еще мне помнится, некоторые программы сами себя отлаживают, тем самым не давая прицепить к себе еще один отладчик.
Насколько мне извесно процесс не может подключиться как отладчик сам к себе, для этого требуется дополнительный процесс
Ну так и на KiDispatchException иначе, чем через ядро, не сядешь — потому что в SEH chain встраиваться хоть и можно, но бессмысленно, т.к. у процесса есть контроль над своим SEH chain, и при желании процесс может посмотреть, кто там есть чужой и всех оттуда попросить. С другой стороны — разве сейчас для кого-то проблема сделать простейший драйвер-перехватчик?

И, как выше верно заметили, сам себя отладить не получится. К счастью, в нашем случае необязательно отлаживать процесс официально — т.к. мы уже в ядре, нам не нужны привилегии отладчика чтобы поменять контекст потока, добавить туда отладочные регистры и словить брекпоинт.
Чем параноидальнее защита, тем больше у неё ложных срабатываний. Потому что в процесс постоянно ломится и внедряется огромная куча всего — всякие перехватчики клавиатурных нажатий, антивирусные сканеры, всевозможные «управляющие центры» для видео и звука, компоненты настроек устройств ввода вроде мыши и т.д.
Запустив процесс в OllyDebug, легко увидеть, сколько в адресное пространство процесса грузится явно посторонних DLL. Особенно много всякой фирменной мелочёвки грузится внутрь процесса при игре на ноутах. Каталогизировать сигнатуры всего этого хлама не так-то просто даже для такой большой компании, как Blizzard.
В итоге разработчики защит оказываются меж двух огней: сделай мало проверок — и ботоводы будут творить что хотят. Сделай слишком много проверок — и техподдержку завалят жалобами, что игра не запускается, да ещё и на игровых форумах основательно польют грязью.
В итоге всегда остаётся достаточное количество дыр, через которые можно пролезть. И если вы пишете бот для себя, то его могут никогда не обнаружить, т.к. целенаправленно ловят лишь массовые программы, а одиночки оказываются в позиции Неуловимого Джо.
Почему сразу не на С#? (Я писал бота без ассемблера и его не палили!)
Почему нужно именно вставлять ассембелерный код в вставки в С#?
Увеличена скорость роботы бота? Контроль пакетов или как понять?
В чем подвох? :)
Sign up to leave a comment.

Articles