Шалом. После той статейки с неверлузом я много чего успел пореверсить и урвать для себя много полезных трюков и фишек, миднайт - является одной из тех полезных находок, с которой я хотел бы с вами поделиться.
В этой статье я в основном буду делать акцент на инжект, так как для меня он оказался очень интересным и запутанным, а так же инициализации чита в игре.
Я на процентов 80-90 уверен, что инжект будет отличаться в зависимости от выбранного продукта, а под реверс попал только продукт CS:GO.
Приятного чтения =)
Лаунчер
Архитектура защиты состояла в детекте хуков сциллыхайд + VMProtect, с использованием библиотеки asmjit x86, коннект к серверу и ответ от сервера принимались через send/recv функции, я не стал анализировать связь между клиентом и сервером в лаунчере, ибо связь была зашифрована. Шифрование напоминало мне что-то по типу шифрований Tiger-192/OCX
Как и в случае с неверлузом в реверсе лаунчера миднайта не было особо смысла, потому что он в дальнейшем почти никак не будет влиять на инжект, кроме одного НО, единственное на что стоит обратить внимание во время захода в лаунчер - это создание .dat файла, который будет выступать у нас на протяжении всего инжекта в качестве токена юзера, да бы испытать шок у реверсеров почему восстановленный дамп чита даёт сбой, на то будет играть несколько факторов.
В обходе антиотладки лаунчера тоже не было огромного смысла, здесь политика против реверсеров не такая радикальная, как теперь у неверлуза после моей статьи =) Не стал выяснять на какие Nt/Zw функции лаунчер палит хуки, так как антиаттач заключается только в детекте хуков, поэтому ничего не мешает выключить сциллухайд и аттачнуться.
Однако меня зацепил один момент, когда миднайт просто вылетал без всяких срабатываний на бряк NtTerminateProcess, узнал как он это делает я только после анализа инжекта, где там и расскажу как он закрывался, обходя бряк =)
Загрузчик чита + Инжект
Вот и подошли к моей любимой части миднайта, именно здесь я дико тупил и не замечал многих вещей.
Лаунчер создает через CreateProcessW системный explorer.exe и в него пихает через мануал мап модуль, который будет ждать запуска кс
Так как я хотел выяснить как именно проходит инжект, то дампить и разбирать большой по размерам системный explorer было самоубийством ещё тем, ещё и модуль по итогу сам окажется накрытым вмпротектором.
Не долго думая я решил создать самое простое консольное приложение, в которое лаунчер миднайта будет записывать модуль для инжекта.
Хукаем CreateProcessW и вместо оригинального lpApplicationName мы будем возвращать путь к нашему файлу в который и будет записан модуль.
Прототип функции CreateProcessW:
Теперь лаунчер все записывает в наше консольное приложение, находим регион памяти в котором сидит наш модуль и дампим его.
После полного восстановления PE формата в этом дампе я смог запустить его, на тот момент у меня на руках уже был отдельный модуль загрузчика миднайта, который я мог запускать спокойно без лаунчера, опять же с одним НО.
Забыл сказать, что explorer.exe запускался с параметрами запуска, где был указан наш тот самый токен .dat файл. Поэтому и наш восстановленный дамп загрузчика не мог запуститься без этого, для этого создаем ярлык с параметрами запуска где будет указан наш токен. Теперь ничего не мешает использовать его отдельно от лаунчера, однако вместе с этим были и свои "побочные эффекты", видите ли, загрузчик после того как проходит прогресс бар пытается найти тот самый explorer.exe и закрыть, но так как его нет, то загрузчик находит оригинальный системный explorer.exe и закрывает его =D и когда я дебажил, то неплохо так перепугался, увидев что мой рабочий стол стал абсолютно пустым.
Переходим к процессу инжекта, он выполняет почти те же самые действия что и простой мануал мап, однако меня поставил в тупик вызов VirtualAllocEx 2 раза в процессе CS:GO, выделяя два региона памяти. Не сильно меня это волновало, так как хотел побыстрее узнать что записывается в сам процесс игры, хукаем NtWriteProcessMemory. Когда хук сработал и в консоль вывело baseadress региона памяти с 132 записанными байтами, то я ринулся в дебаггер смотреть шо там, какого было моё удивление когда я увидел лишь пару бесполезных инструкции, на самом деле все эти инструкции означали путь к тому самому токен файлу, без которого не было бы инжекта. Тогда встает вопрос, где сам чит миднайта, если NtWriteProcessMemory вызвался только один раз? Долго думать не пришлось, я хукнул GetProcAddress, да бы узнать какие адреса функции берет миднайт для мануал мапа.
Список приложу ниже:
И мое внимание привлекла функция NtWow64WriteVirtualMemory64, как оказалось позже - именно через него загружался модуль миднайта и сразу же все вопросы по VirtualAllocEx у меня отпали.
Инициализация чита
Тут я не буду слишком многословен, перед тем как инициализироваться чит отправляет через send 5 пакетов с зашифрованным буффером, который мне было уже как-то лень пытаться дешифровать, если эти 5 пакетов не были отправлены - чит не инициализируется. И во время использования софта он неоднократно будет вызывать send, чтобы отправить зашифрованный статический буффер, почему статический? Потому что миднайт не меняет его содержимое, и спустя даже несколько запусков буффер будет передавать одно и тоже послание серверу "00001400000000028569e8ba3d329f6765fd865cbe5e1634".
Итак, чтобы инициализировать полноценно чит у юзера должно быть: токен файл, которому нельзя менять название или содержимое через какой-нибудь нотпад, перед инжектом записывает в выделенный регион памяти; связь с сервером миднайта при запуске кс
Вывод
На этом у меня всё, было очень интересно реверсить этот продукт, ведь я особо не ревершу никак читы или методы инжекта, но тут попался очень интересный материал. Хочу сказать спасибо кодеру миднайта (Blick1337) за полученный опыт. На этом у меня всё, подписывайтесь на мой блог-канал в телеге =) Colby5Covington / Back-Engineering
Бонус
В этой статье я в основном буду делать акцент на инжект, так как для меня он оказался очень интересным и запутанным, а так же инициализации чита в игре.
Я на процентов 80-90 уверен, что инжект будет отличаться в зависимости от выбранного продукта, а под реверс попал только продукт CS:GO.
Приятного чтения =)
Лаунчер
Архитектура защиты состояла в детекте хуков сциллыхайд + VMProtect, с использованием библиотеки asmjit x86, коннект к серверу и ответ от сервера принимались через send/recv функции, я не стал анализировать связь между клиентом и сервером в лаунчере, ибо связь была зашифрована. Шифрование напоминало мне что-то по типу шифрований Tiger-192/OCX
Как и в случае с неверлузом в реверсе лаунчера миднайта не было особо смысла, потому что он в дальнейшем почти никак не будет влиять на инжект, кроме одного НО, единственное на что стоит обратить внимание во время захода в лаунчер - это создание .dat файла, который будет выступать у нас на протяжении всего инжекта в качестве токена юзера, да бы испытать шок у реверсеров почему восстановленный дамп чита даёт сбой, на то будет играть несколько факторов.
В обходе антиотладки лаунчера тоже не было огромного смысла, здесь политика против реверсеров не такая радикальная, как теперь у неверлуза после моей статьи =) Не стал выяснять на какие Nt/Zw функции лаунчер палит хуки, так как антиаттач заключается только в детекте хуков, поэтому ничего не мешает выключить сциллухайд и аттачнуться.
Однако меня зацепил один момент, когда миднайт просто вылетал без всяких срабатываний на бряк NtTerminateProcess, узнал как он это делает я только после анализа инжекта, где там и расскажу как он закрывался, обходя бряк =)
Загрузчик чита + Инжект
Вот и подошли к моей любимой части миднайта, именно здесь я дико тупил и не замечал многих вещей.
Лаунчер создает через CreateProcessW системный explorer.exe и в него пихает через мануал мап модуль, который будет ждать запуска кс
Так как я хотел выяснить как именно проходит инжект, то дампить и разбирать большой по размерам системный explorer было самоубийством ещё тем, ещё и модуль по итогу сам окажется накрытым вмпротектором.
Не долго думая я решил создать самое простое консольное приложение, в которое лаунчер миднайта будет записывать модуль для инжекта.
Хукаем 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 и когда я дебажил, то неплохо так перепугался, увидев что мой рабочий стол стал абсолютно пустым.
Переходим к процессу инжекта, он выполняет почти те же самые действия что и простой мануал мап, однако меня поставил в тупик вызов 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
Бонус