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

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


Лаунчер

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

Посмотреть вложение 19629

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

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


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

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

Посмотреть вложение 19631

Не долго думая я решил создать самое простое консольное приложение, в которое лаунчер миднайта будет записывать модуль для инжекта.
Хукаем 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 и когда я дебажил, то неплохо так перепугался, увидев что мой рабочий стол стал абсолютно пустым.

Посмотреть вложение 19630

Переходим к процессу инжекта, он выполняет почти те же самые действия что и простой мануал мап, однако меня поставил в тупик вызов 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

Бонус
Посмотреть вложение 19632
бля, топ
 
Активность
Пока что здесь никого нет
Сверху Снизу