Статья [Reverse-Engineering] MIDNIGHT - CS:GO

  • colby57
  • UNEXPECTED_KERNEL_MODE_TRAP_M (1000007f)
  • 155
  • 2
  • 239
Шалом. После той статейки с неверлузом я много чего успел пореверсить и урвать для себя много полезных трюков и фишек, миднайт - является одной из тех полезных находок, с которой я хотел бы с вами поделиться.
В этой статье я в основном буду делать акцент на инжект, так как для меня он оказался очень интересным и запутанным, а так же инициализации чита в игре.
Я на процентов 80-90 уверен, что инжект будет отличаться в зависимости от выбранного продукта, а под реверс попал только продукт CS:GO.
Приятного чтения =)


Лаунчер

Архитектура защиты состояла в детекте хуков сциллыхайд + VMProtect, с использованием библиотеки asmjit x86, коннект к серверу и ответ от сервера принимались через send/recv функции, я не стал анализировать связь между клиентом и сервером в лаунчере, ибо связь была зашифрована. Шифрование напоминало мне что-то по типу шифрований Tiger-192/OCX

midnight_launcher.png


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

В обходе антиотладки лаунчера тоже не было огромного смысла, здесь политика против реверсеров не такая радикальная, как теперь у неверлуза после моей статьи =) Не стал выяснять на какие Nt/Zw функции лаунчер палит хуки, так как антиаттач заключается только в детекте хуков, поэтому ничего не мешает выключить сциллухайд и аттачнуться.
Однако меня зацепил один момент, когда миднайт просто вылетал без всяких срабатываний на бряк NtTerminateProcess, узнал как он это делает я только после анализа инжекта, где там и расскажу как он закрывался, обходя бряк =)


Загрузчик чита + Инжект

Вот и подошли к моей любимой части миднайта, именно здесь я дико тупил и не замечал многих вещей.
Лаунчер создает через CreateProcessW системный explorer.exe и в него пихает через мануал мап модуль, который будет ждать запуска кс
Так как я хотел выяснить как именно проходит инжект, то дампить и разбирать большой по размерам системный explorer было самоубийством ещё тем, ещё и модуль по итогу сам окажется накрытым вмпротектором.

1624376716602.png


Не долго думая я решил создать самое простое консольное приложение, в которое лаунчер миднайта будет записывать модуль для инжекта.
Хукаем CreateProcessW и вместо оригинального lpApplicationName мы будем возвращать путь к нашему файлу в который и будет записан модуль.

Прототип функции CreateProcessW:
C++:
BOOL CreateProcessW(
  LPCWSTR               lpApplicationName,
  LPWSTR                lpCommandLine,
  LPSECURITY_ATTRIBUTES lpProcessAttributes,
  LPSECURITY_ATTRIBUTES lpThreadAttributes,
  BOOL                  bInheritHandles,
  DWORD                 dwCreationFlags,
  LPVOID                lpEnvironment,
  LPCWSTR               lpCurrentDirectory,
  LPSTARTUPINFOW        lpStartupInfo,
  LPPROCESS_INFORMATION lpProcessInformation
);

Теперь лаунчер все записывает в наше консольное приложение, находим регион памяти в котором сидит наш модуль и дампим его.
После полного восстановления PE формата в этом дампе я смог запустить его, на тот момент у меня на руках уже был отдельный модуль загрузчика миднайта, который я мог запускать спокойно без лаунчера, опять же с одним НО.
Забыл сказать, что explorer.exe запускался с параметрами запуска, где был указан наш тот самый токен .dat файл. Поэтому и наш восстановленный дамп загрузчика не мог запуститься без этого, для этого создаем ярлык с параметрами запуска где будет указан наш токен. Теперь ничего не мешает использовать его отдельно от лаунчера, однако вместе с этим были и свои "побочные эффекты", видите ли, загрузчик после того как проходит прогресс бар пытается найти тот самый explorer.exe и закрыть, но так как его нет, то загрузчик находит оригинальный системный explorer.exe и закрывает его =D и когда я дебажил, то неплохо так перепугался, увидев что мой рабочий стол стал абсолютно пустым.

Screenshot_2.png


Переходим к процессу инжекта, он выполняет почти те же самые действия что и простой мануал мап, однако меня поставил в тупик вызов VirtualAllocEx 2 раза в процессе CS:GO, выделяя два региона памяти. Не сильно меня это волновало, так как хотел побыстрее узнать что записывается в сам процесс игры, хукаем NtWriteProcessMemory. Когда хук сработал и в консоль вывело baseadress региона памяти с 132 записанными байтами, то я ринулся в дебаггер смотреть шо там, какого было моё удивление когда я увидел лишь пару бесполезных инструкции, на самом деле все эти инструкции означали путь к тому самому токен файлу, без которого не было бы инжекта. Тогда встает вопрос, где сам чит миднайта, если NtWriteProcessMemory вызвался только один раз? Долго думать не пришлось, я хукнул GetProcAddress, да бы узнать какие адреса функции берет миднайт для мануал мапа.

Список приложу ниже:

C++:
        [ + ] GetProcAddress Event Called
                [ + ] hModule -> 773E0000
                [ + ] lpProcName -> RtlGetVersion

        [ + ] GetProcAddress Event Called
                [ + ] hModule -> 773E0000
                [ + ] lpProcName -> NtQuerySystemInformation

        [ + ] GetProcAddress Event Called
                [ + ] hModule -> 773E0000
                [ + ] lpProcName -> RtlDosApplyFileIsolationRedirection_Ustr

        [ + ] GetProcAddress Event Called
                [ + ] hModule -> 773E0000
                [ + ] lpProcName -> RtlInitUnicodeString

        [ + ] GetProcAddress Event Called
                [ + ] hModule -> 773E0000
                [ + ] lpProcName -> RtlFreeUnicodeString

        [ + ] GetProcAddress Event Called
                [ + ] hModule -> 773E0000
                [ + ] lpProcName -> RtlHashUnicodeString

        [ + ] GetProcAddress Event Called
                [ + ] hModule -> 773E0000
                [ + ] lpProcName -> RtlUpcaseUnicodeChar

        [ + ] GetProcAddress Event Called
                [ + ] hModule -> 773E0000
                [ + ] lpProcName -> NtQueryInformationProcess

        [ + ] GetProcAddress Event Called
                [ + ] hModule -> 773E0000
                [ + ] lpProcName -> NtSetInformationProcess

        [ + ] GetProcAddress Event Called
                [ + ] hModule -> 773E0000
                [ + ] lpProcName -> NtQueryInformationThread

        [ + ] GetProcAddress Event Called
                [ + ] hModule -> 773E0000
                [ + ] lpProcName -> NtDuplicateObject

        [ + ] GetProcAddress Event Called
                [ + ] hModule -> 773E0000
                [ + ] lpProcName -> NtQueryObject

        [ + ] GetProcAddress Event Called
                [ + ] hModule -> 773E0000
                [ + ] lpProcName -> NtQuerySection

        [ + ] GetProcAddress Event Called
                [ + ] hModule -> 773E0000
                [ + ] lpProcName -> RtlCreateActivationContext

        [ + ] GetProcAddress Event Called
                [ + ] hModule -> 773E0000
                [ + ] lpProcName -> NtQueryVirtualMemory

        [ + ] GetProcAddress Event Called
                [ + ] hModule -> 773E0000
                [ + ] lpProcName -> NtCreateThreadEx

        [ + ] GetProcAddress Event Called
                [ + ] hModule -> 773E0000
                [ + ] lpProcName -> NtLockVirtualMemory

        [ + ] GetProcAddress Event Called
                [ + ] hModule -> 773E0000
                [ + ] lpProcName -> NtSuspendProcess

        [ + ] GetProcAddress Event Called
                [ + ] hModule -> 773E0000
                [ + ] lpProcName -> NtResumeProcess

        [ + ] GetProcAddress Event Called
                [ + ] hModule -> 773E0000
                [ + ] lpProcName -> RtlImageNtHeader

        [ + ] GetProcAddress Event Called
                [ + ] hModule -> 773E0000
                [ + ] lpProcName -> NtLoadDriver (Нахуя ему это, если там нет драйвера =D)

        [ + ] GetProcAddress Event Called
                [ + ] hModule -> 773E0000
                [ + ] lpProcName -> NtUnloadDriver

        [ + ] GetProcAddress Event Called
                [ + ] hModule -> 773E0000
                [ + ] lpProcName -> RtlDosPathNameToNtPathName_U

        [ + ] GetProcAddress Event Called
                [ + ] hModule -> 773E0000
                [ + ] lpProcName -> NtOpenEvent

        [ + ] GetProcAddress Event Called
                [ + ] hModule -> 773E0000
                [ + ] lpProcName -> NtCreateEvent

        [ + ] GetProcAddress Event Called
                [ + ] hModule -> 773E0000
                [ + ] lpProcName -> NtQueueApcThread

        [ + ] GetProcAddress Event Called
                [ + ] hModule -> 773E0000
                [ + ] lpProcName -> RtlEncodeSystemPointer

        [ + ] GetProcAddress Event Called
                [ + ] hModule -> 773E0000
                [ + ] lpProcName -> RtlQueueApcWow64Thread

        [ + ] GetProcAddress Event Called
                [ + ] hModule -> 773E0000
                [ + ] lpProcName -> NtWow64QueryInformationProcess64

        [ + ] GetProcAddress Event Called
                [ + ] hModule -> 773E0000
                [ + ] lpProcName -> NtWow64ReadVirtualMemory64

        [ + ] GetProcAddress Event Called
                [ + ] hModule -> 773E0000
                [ + ] lpProcName -> NtWow64WriteVirtualMemory64

        [ + ] GetProcAddress Event Called
                [ + ] hModule -> 74BD0000
                [ + ] lpProcName -> Wow64GetThreadContext

        [ + ] GetProcAddress Event Called
                [ + ] hModule -> 74BD0000
                [ + ] lpProcName -> Wow64SetThreadContext

        [ + ] GetProcAddress Event Called
                [ + ] hModule -> 74BD0000
                [ + ] lpProcName -> Wow64SuspendThread

        [ + ] GetProcAddress Event Called
                [ + ] hModule -> 74BD0000
                [ + ] lpProcName -> GetProcessDEPPolicy

        [ + ] GetProcAddress Event Called
                [ + ] hModule -> 74BD0000
                [ + ] lpProcName -> QueryFullProcessImageNameW

        [ + ] GetProcAddress Event Called
                [ + ] hModule -> 74BD0000
                [ + ] lpProcName -> GetThreadId

И мое внимание привлекла функция NtWow64WriteVirtualMemory64, как оказалось позже - именно через него загружался модуль миднайта и сразу же все вопросы по VirtualAllocEx у меня отпали.

Инициализация чита

Тут я не буду слишком многословен, перед тем как инициализироваться чит отправляет через send 5 пакетов с зашифрованным буффером, который мне было уже как-то лень пытаться дешифровать, если эти 5 пакетов не были отправлены - чит не инициализируется. И во время использования софта он неоднократно будет вызывать send, чтобы отправить зашифрованный статический буффер, почему статический? Потому что миднайт не меняет его содержимое, и спустя даже несколько запусков буффер будет передавать одно и тоже послание серверу "00001400000000028569e8ba3d329f6765fd865cbe5e1634".

Итак, чтобы инициализировать полноценно чит у юзера должно быть: токен файл, которому нельзя менять название или содержимое через какой-нибудь нотпад, перед инжектом записывает в выделенный регион памяти; связь с сервером миднайта при запуске кс

Вывод
На этом у меня всё, было очень интересно реверсить этот продукт, ведь я особо не ревершу никак читы или методы инжекта, но тут попался очень интересный материал. Хочу сказать спасибо кодеру миднайта (Blick1337) за полученный опыт. На этом у меня всё, подписывайтесь на мой блог-канал в телеге =) Colby5Covington / Back-Engineering

Бонус
1624377830530.png
 
  • 30
  • 8
Контакты для связи отсутствуют.
Забыл кстати поведать о необычном выходе из программы, обходя бряк, там он реализуется с помощью NtQueryInformationProcess и NtSetInformationProcess
из enum ProcessInformationClass юзается ProcessDefaultHardErrorMode, так делал загрузчик миднайта после успешного инжекта
ой если ты не крякаешь то зачем туториал десткий сад бое
 
Активность
Пока что здесь никого нет
Сверху Снизу