-
Автор темы
- #1
Весенний шалом.
ПРЕДИСЛОВИЕ
Думаю у меня будет ещё одна статья связанная с лоадером, и на этом мой реверс всяких других лоадеров прекратиться, потому что надоело постяонно видеть схожие трюки, которые я видел до этого.
Поэтому буду думать, что реверсить дальше. Спасибо Senior_Gamania за то, что откопал мне этот лоадер :)
ЛОАДЕР
Лоадер полностью на х64 и оснащен VMProtect, также помимо фулл-пресета ( виртуализации важных функции, антидебаг, крц и т.д. ) там решили абузить SDK VMP,
это означает, что помимо самописных трюков я встречу и трюки VMP ( так и было )
1. ИНИЦИАЛИЗАЦИЯ ЗАЩИТЫ
Перед тем как рисовать меню лоадера вызывается инит защиты со всякими трюками, о которых я поведаю чуть позже
Вся фишка в том, что пролог функции чист, но для выполнения основного кода лоадер прыгает в ВМ, чтобы выполнить всякие трюки, о которых я щас и поведаю
На ВМ сейчас не будем тратить время, так как там до вызова первого трюка ничего такого не исполнялось, на что можно было бы обратить внимание, однако и там будут свои приколы :)
1.1. Шелл-код для попытки сломать аттач к процессу
После выхода из ВМ мы попадаем на наш первый трюк, где в связке шеллкода и лейзиимпорта мы получаем две пропатченные ntdll.dll функции
А именно - DbgUiRemoteBreakin && DbgBreakPoint. Они заменяются непосредственно на вызов FatalExit ( другое название ExitProcess, только для х64 )
Вначале лоадер берет адреса трёх функции через
Как контрить LazyImport я рассказывал в своей предыдущей теме, прочитать можно тут - https://yougame.biz/threads/247347
Тут коротко говоря эти функции вызываются через два регистра: RBX && RDI, ставим на них бряки и выходим на эти WinAPI-функции
Следующим этапом идет сам вызов функции с шеллкодом, который принимает два параметра: Адрес в который будут записывать и адрес, откуда будут брать байты
Сурс-код функции DbgShellcodePatch
Возможно я тут где-то намудрил, но не суть важно :) В итоге в лоадере получается такой шеллкод
С помощью последней инструкции
Подменяется адрес возврата на тот, что сидит в rax, то есть FatalExit, эта инструкция также используется для подмены адреса возврата, чтобы вызвать нужный импорт в VMP. Однако это уже совсем другая история :)
Почему же патчат именно DbgUiRemoteBreakin? Давно натыкался на статью в Хабре, этому трюку аж 9 лет уже, и вот скрин с объяснением
Данный трюк более не актуален для людей, которые часто обновляют x64dbg, потому что дебаггер не использует эту функцию во время отладки
Если мне приходилось в прошлом году сидеть и ломать точно такой же трюк у лоадера интериума патчами, то тут уже сами разрабы дебаггера избавили меня от "неебического" анти-аттача :)
Точно не могу сказать с какой версии дебаггера такой трюк зафиксили, поэтому +- на ком-то этот трюк может и сработать
После этого трюка мы уходим снова в ВМ протектора и спустя некоторое время попадаем на новый трюк
1.2. Детект запускаемых процессов и драйверов
Этот трюк не раз будет появляться, он появится во время попытки авторизации, но сейчас нас интересует текущий вызов.
По всем канонам паблик методов антидебага нас встречает связка из нескольких вызовов:
Увы, но детект происходит через функцию strstr в цикле, берется один условный системный процесс и сравнивается с 3 процессами: idaq, processhacker, cheatengine
Вам ничего не мешает хукнуть strstr в vcruntime140 и возвращать 0 в некоторых случаях.
Если при переборе обнаружили процесс, то в dil присваивается 1 и в последующем при проверке вызывается функция, открывающая хендл процесса и закрывает сам процесс
Выглядит это всё дело так:
Переходим к детекту драйверов, тут вызываются несколько функции ( в том числе и strstr ), а именно:
K32EnumDeviceDrivers
K32GetDeviceDriverBaseNameA
Опять же при детекте задается 1 регистру bl и после сравнения вас кидает на закрытие сервиса найденного драйвера.
К слову ищет он: kprocesshacker.sys, sbie (Sandboxie), gwdrv (GlassWire), npf.sys, uupacket.sys, kdhack64 и 360antih
Вот сурс-код закрытия сервиса:
Закончили с детектами, переходим к следующему трюку
1.3. Защита системных вызовов
На этом этапе защиты я столкнулся уже непосредственно с самими системными вызовами, из ВМ перебираются абсолютно все ntdll-функции, имеющие свой системный номер и вызывается функция, которая принимает два параметра: поинтер на строку нужной функции, и её системный номер
Далее мы возвращаемся снова в ВМ, где очень часто будет читаться наш поинтер на строку, в общем, после выхода из ВМ протектора мы попадаем к инициализации самого шеллкода.
Аллоцируется память через malloc, затем заполняется некий шеллкод, который ну уж очень сильно палился в открытом виде.
Заготовка для шеллкода выглядит так:
Если переводить с байт-кода на ассемблер, то мы получаем вот такие 4 инструкции
Почему 0х0? Я не зря назвал это заготовкой для системного номера, в дальнейшем когда выполнение программы вернётся в ВМ именно там начнется запись шеллкода, вместо 0х0 в ВМе будет записываться системный номер нужной нтдлл функции, который мы получили еще на первой функции
Вот небольшой список тех функции, чей системный номер он брал, пока я мониторил поведение функции
Конечно не все эти функции будут использоваться в дальнейшем, и это далеко не все функции, у которых брались системные номера. Была ещё одна проблема, эти сисколлы зачастую бывали разбросаны по разным регионам памяти ( возможно из-за malloc, я точно не знаю ), а вылавливать их бряками было бы полным самоубийством.
Был вариант поставить поставить патч на инструкцию
Вместо syscall мы бы записали ud2, т.е. 0F 0B и на выходе мы бы получили у всех сисколлов такой вид:
Тем самым мы бы получили эксепшн и вышли бы моментально на тот системный вызов, который выполнялся, например при авторизации или инжекте
Опять же проблема заключается в том, что тут сильно абузят SDK протектора, когда я анализировал чтение секции у протектора для создания хэша, то в вызове функции VMProtectIsValidImageCRC насчитал где-то 5 калькуляторов.
Изначально я подумывал написать тамполайн хук, который бы сверял адрес, который берет для чтения, так к примеру при чтении адреса с "mov word ptr ds:[rax+0x30], 0x50F" вместо 0B ставили 05, как и должно быть по оригиналу.
Но протектор мешается ещё тем, что в функции VM остальные 4 калькулятора тоже проходятся катком по друг другу, т.е. сверяя байты друг у друга. Т.е. мой тамполайн хук тут тяжело очень реализовать. Пришлось бы писать целую систему из проверок
И, кстати, вот какой бы получился сисколл при моем патче
1.4. Промапленный ntdll.dll и парсер системных номеров
После того как я зафиксил полностью импорты в лоадере я обнаружил два интересных импорта, а именно fopen_s и fread.
На первый взгляд кажется, шо они не так важны и можно забить на них. Только вот эти два импорта вызываются в виртуальной машине протектора, а как мы знаем в лоадере вантапа пытаются самое важное виртуализировать.
Суммируем эти два фактора и получаем вполне логичное основание хукнуть fopen_s.
Ставим хук и получаем два вызова, один читает у нас файл hosts, а другой читает уже сам ntdll.dll
Пройдясь по регистру я нашел адрес, в котором сидел полностью промапленный ntdll.dll
Дабы полностью удостовериться, что это именно он, я решил пройтись по файлу и найти таблицу системных номеров
И к счастью я её нашёл :)
Спустя ещё некоторое время я подумал, каков вообще смысл его было читать? Неужели сисколлы берутся именно отсюда?
Именно так всё и было, но с одним НО, чтение сисколлов производится в виртуальной машине протектора. +- я уже понимал логику получения системного номера
Как раз во время трассировки я обнаружил вместе с чтением в стеке и NtLoadDriver.
С первым комментарием я чуть-чуть ошибся, но не важно :)
ПРЕДИСЛОВИЕ
Думаю у меня будет ещё одна статья связанная с лоадером, и на этом мой реверс всяких других лоадеров прекратиться, потому что надоело постяонно видеть схожие трюки, которые я видел до этого.
Поэтому буду думать, что реверсить дальше. Спасибо Senior_Gamania за то, что откопал мне этот лоадер :)
ЛОАДЕР
Лоадер полностью на х64 и оснащен VMProtect, также помимо фулл-пресета ( виртуализации важных функции, антидебаг, крц и т.д. ) там решили абузить SDK VMP,
это означает, что помимо самописных трюков я встречу и трюки VMP ( так и было )
1. ИНИЦИАЛИЗАЦИЯ ЗАЩИТЫ
Перед тем как рисовать меню лоадера вызывается инит защиты со всякими трюками, о которых я поведаю чуть позже
Вся фишка в том, что пролог функции чист, но для выполнения основного кода лоадер прыгает в ВМ, чтобы выполнить всякие трюки, о которых я щас и поведаю
На ВМ сейчас не будем тратить время, так как там до вызова первого трюка ничего такого не исполнялось, на что можно было бы обратить внимание, однако и там будут свои приколы :)
1.1. Шелл-код для попытки сломать аттач к процессу
После выхода из ВМ мы попадаем на наш первый трюк, где в связке шеллкода и лейзиимпорта мы получаем две пропатченные ntdll.dll функции
А именно - DbgUiRemoteBreakin && DbgBreakPoint. Они заменяются непосредственно на вызов FatalExit ( другое название ExitProcess, только для х64 )
Вначале лоадер берет адреса трёх функции через
C++:
[Вызов осуществляется с помощью LazyImporter] GetProcAddress(
GetModuleHandleA( "kernel32.dll" );
"ExitProcess"
);
[Вызов осуществляется с помощью LazyImporter] GetProcAddress(
GetModuleHandleA( "ntdll.dll" );
"DbgUiRemoteBreakin"
);
[Вызов осуществляется с помощью LazyImporter] GetProcAddress(
GetModuleHandleA( "ntdll.dll" );
"DbgBreakPoint"
);
Тут коротко говоря эти функции вызываются через два регистра: RBX && RDI, ставим на них бряки и выходим на эти WinAPI-функции
Следующим этапом идет сам вызов функции с шеллкодом, который принимает два параметра: Адрес в который будут записывать и адрес, откуда будут брать байты
Сурс-код функции DbgShellcodePatch
C++:
BOOL __fastcall* DbgShellcodePatch(
void* first_addr,
void* second_addr
)
{
DWORD old_protect;
std::uint8_t shellcode[ ] = {
0x50,
0x48, 0xB8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x48, 0x87, 0x04, 0x24,
0xC3
};
*reinterpret_cast< void** > ( shellcode + 0x4 ) = second_addr;
VirtualProtect(
first_addr,
15,
PAGE_EXECUTE_READWRITE,
&old_protect
);
memcpy( first_addr, shellcode, 15 );
VirtualProtect(
first_addr,
15,
old_protect,
&old_protect
);
return TRUE;
}
С помощью последней инструкции
Форматирование (BB-код):
xchg qword ptr ss:[rsp], rax
Почему же патчат именно DbgUiRemoteBreakin? Давно натыкался на статью в Хабре, этому трюку аж 9 лет уже, и вот скрин с объяснением
Данный трюк более не актуален для людей, которые часто обновляют x64dbg, потому что дебаггер не использует эту функцию во время отладки
Если мне приходилось в прошлом году сидеть и ломать точно такой же трюк у лоадера интериума патчами, то тут уже сами разрабы дебаггера избавили меня от "неебического" анти-аттача :)
Точно не могу сказать с какой версии дебаггера такой трюк зафиксили, поэтому +- на ком-то этот трюк может и сработать
После этого трюка мы уходим снова в ВМ протектора и спустя некоторое время попадаем на новый трюк
1.2. Детект запускаемых процессов и драйверов
Этот трюк не раз будет появляться, он появится во время попытки авторизации, но сейчас нас интересует текущий вызов.
По всем канонам паблик методов антидебага нас встречает связка из нескольких вызовов:
C++:
HANDLE hSnapshot = CreateToolhelp32Snapshot( 2, 0 );
Process32First( hSnapshot, &processEntry32Struct );
Process32Next( hSnapshot, &processEntry32Struct );
Вам ничего не мешает хукнуть strstr в vcruntime140 и возвращать 0 в некоторых случаях.
Если при переборе обнаружили процесс, то в dil присваивается 1 и в последующем при проверке вызывается функция, открывающая хендл процесса и закрывает сам процесс
Выглядит это всё дело так:
C++:
HANDLE hProcess = OpenProcess( 0x1FFFFF, 0, ProcessId );
TerminateProcess( hProcess, 0 );
CloseHandle( hProcess );
K32EnumDeviceDrivers
K32GetDeviceDriverBaseNameA
Опять же при детекте задается 1 регистру bl и после сравнения вас кидает на закрытие сервиса найденного драйвера.
К слову ищет он: kprocesshacker.sys, sbie (Sandboxie), gwdrv (GlassWire), npf.sys, uupacket.sys, kdhack64 и 360antih
Вот сурс-код закрытия сервиса:
C++:
BOOL __fastcall ShutdownService( const char* DriverName )
{
SC_HANDLE hManager = OpenSCManagerA( 0, 0, 2, );
struct _SERVICE_STATUS ServiceStatus;
if ( hManager )
{
SC_HANDLE hDriverService = OpenServiceA( hManager, DriverName, 0xF01FF );
if ( hDriverService )
{
ControlService( hDriverService, 1, &ServiceStatus );
}
CloseServiceHandle( hManager );
CloseServiceHandle( hDriverService );
return TRUE;
}
return FALSE;
}
1.3. Защита системных вызовов
На этом этапе защиты я столкнулся уже непосредственно с самими системными вызовами, из ВМ перебираются абсолютно все ntdll-функции, имеющие свой системный номер и вызывается функция, которая принимает два параметра: поинтер на строку нужной функции, и её системный номер
Далее мы возвращаемся снова в ВМ, где очень часто будет читаться наш поинтер на строку, в общем, после выхода из ВМ протектора мы попадаем к инициализации самого шеллкода.
Аллоцируется память через malloc, затем заполняется некий шеллкод, который ну уж очень сильно палился в открытом виде.
Заготовка для шеллкода выглядит так:
Если переводить с байт-кода на ассемблер, то мы получаем вот такие 4 инструкции
Форматирование (BB-код):
mov r10, rcx
mov eax, 0x0
syscall
ret
Вот небольшой список тех функции, чей системный номер он брал, пока я мониторил поведение функции
Код:
NtAcceptConnectPort
NtAccessCheck
NtAccessCheckAndAuditAlarm
NtAccessCheckByType
NtAccessCheckByTypeAndAuditAlarm
NtAccessCheckByTypeResultList
NtAccessCheckByTypeResultListAndAuditAlarmByHandl
NtAcquireCrossVmMutant
NtAcquireProcessActivityReference
NtAddAtom
NtAddAtomEx
NtAddBootEntry
NtAddDriverEntry
NtAdjustGroupsToken
NtAdjustPrivilegesToken
NtAdjustTokenClaimsAndDeviceGroups
NtAlertResumeThread
NtAlertThread
NtAlertThreadByThreadId
NtAllocateLocallyUniqueId
NtAllocateReserveObject
NtAllocateUserPhysicalPages
NtAllocateUserPhysicalPagesEx
NtAllocateUuids
NtAllocateVirtualMemory
NtAllocateVirtualMemoryEx
NtAlpcAcceptConnectPort
NtAlpcCancelMessage
NtAlpcConnectPort
NtAlpcConnectPortEx
NtAlpcCreatePort
NtAlpcCreatePortSection
NtAlpcCreateResourceReserve
NtAlpcCreateSectionView
NtAlpcCreateSecurityContext
NtAlpcDeletePortSection
NtAlpcDeleteResourceReserve
NtAlpcDeleteSectionView.NtAlpcDeleteSecurityContext
NtAlpcDisconnectPort
NtAlpcImpersonateClientContainerOfPort
NtAlpcImpersonateClientOfPort
NtAlpcOpenSenderProcess
NtAlpcOpenSenderThread
NtAlpcQueryInformation
NtAlpcQueryInformationMessage
NtAlpcRevokeSecurityContext
NtAlpcSendWaitReceivePort
NtAlpcSetInformation
NtApphelpCacheControl
NtAreMappedFilesTheSame
NtAssignProcessToJobObject
NtAssociateWaitCompletionPacket
NtCallEnclave
NtCallbackReturn
NtCancelIoFile
NtCancelIoFileEx
NtCancelSynchronousIoFile
NtCancelTimer
NtCancelTimer2
NtCancelWaitCompletionPacket
NtClearEvent
NtClose
NtCloseObjectAuditAlarm
NtCommitComplete
NtCommitEnlistment
NtCommitRegistryTransaction
NtCommitTransaction
NtCompactKeys
NtCompareObjects
NtCompareSigningLevels
NtCompareTokens
NtCompleteConnectPort
NtCompressKey
NtConnectPort
NtContinue
NtContinueEx
NtConvertBetweenAuxiliaryCounterAndPerformanceCounter
NtCreateCrossVmEvent
NtCreateCrossVmMutant
NtCreateDebugObject
NtCreateDirectoryObject
NtCreateDirectoryObjectEx
NtCreateEnclave
NtCreateEnlistment
NtCreateEvent
NtCreateEventPair
NtCreateFile
NtCreateIRTimer
NtCreateIoCompletion
NtCreateJobObject
NtCreateJobSet
NtCreateKey
NtCreateKeyTransacted
NtCreateKeyedEvent
NtCreateLowBoxToken
NtCreateMailslotFile
NtCreateMutant
NtCreateNamedPipeFile
NtCreatePagingFile
NtCreatePartition
NtCreatePort
NtCreatePrivateNamespace
NtCreateProcess
NtCreateProcessEx
NtCreateProfile
NtCreateProfileEx
NtCreateRegistryTransaction
NtCreateResourceManager
NtCreateSection
NtCreateSectionEx
NtCreateSemaphore
NtCreateSymbolicLinkObject
NtCreateThread
NtCreateThreadEx
NtCreateTimer
NtCreateTimer2
NtCreateToken
NtCreateTokenEx
NtCreateTransaction
NtCreateTransactionManager
NtCreateUserProcess
NtCreateWaitCompletionPacket
NtCreateWaitablePort
NtCreateWnfStateName
NtCreateWorkerFactory
NtDebugActiveProcess
NtDebugContinue
NtDelayExecution
NtDeleteAtom
NtDeleteBootEntry
NtDeleteDriverEntry
NtDeleteFile
NtDeleteKey
NtAcceptConnectPort
Был вариант поставить поставить патч на инструкцию
Форматирование (BB-код):
mov word ptr ds:[rax+0x30], 0x50F
Форматирование (BB-код):
mov r10, rcx
mov eax, syscall_number
ud2
ret
Опять же проблема заключается в том, что тут сильно абузят SDK протектора, когда я анализировал чтение секции у протектора для создания хэша, то в вызове функции VMProtectIsValidImageCRC насчитал где-то 5 калькуляторов.
Изначально я подумывал написать тамполайн хук, который бы сверял адрес, который берет для чтения, так к примеру при чтении адреса с "mov word ptr ds:[rax+0x30], 0x50F" вместо 0B ставили 05, как и должно быть по оригиналу.
Но протектор мешается ещё тем, что в функции VM остальные 4 калькулятора тоже проходятся катком по друг другу, т.е. сверяя байты друг у друга. Т.е. мой тамполайн хук тут тяжело очень реализовать. Пришлось бы писать целую систему из проверок
И, кстати, вот какой бы получился сисколл при моем патче
1.4. Промапленный ntdll.dll и парсер системных номеров
После того как я зафиксил полностью импорты в лоадере я обнаружил два интересных импорта, а именно fopen_s и fread.
На первый взгляд кажется, шо они не так важны и можно забить на них. Только вот эти два импорта вызываются в виртуальной машине протектора, а как мы знаем в лоадере вантапа пытаются самое важное виртуализировать.
Суммируем эти два фактора и получаем вполне логичное основание хукнуть fopen_s.
Ставим хук и получаем два вызова, один читает у нас файл hosts, а другой читает уже сам ntdll.dll
Пройдясь по регистру я нашел адрес, в котором сидел полностью промапленный ntdll.dll
Дабы полностью удостовериться, что это именно он, я решил пройтись по файлу и найти таблицу системных номеров
И к счастью я её нашёл :)
Спустя ещё некоторое время я подумал, каков вообще смысл его было читать? Неужели сисколлы берутся именно отсюда?
Именно так всё и было, но с одним НО, чтение сисколлов производится в виртуальной машине протектора. +- я уже понимал логику получения системного номера
Как раз во время трассировки я обнаружил вместе с чтением в стеке и NtLoadDriver.
С первым комментарием я чуть-чуть ошибся, но не важно :)
Итоги
На этом мой реверс лоадер Onetap полностью приостанавливается из-за нехватки времени на полный реверс, хотелось бы разобрать и рассказать про трюки во время авторизации, где опять же происходит злоупотребление сдк протектора, но сейчас на вантап у меня не осталось времени. Довольно интересный лоадер, жалко, что у меня не было подписки, потому что во время авторизации ни один сисколл в аллоцированной памяти не выполняется, возможно они бы выполнялись во время инжекта в игру. Спасибо за прочтение данной статьи :)
Ещё конечно же хотелось бы поблагодарить мою команду, а именно: Arting, nelfo57, Dark_Bull, easton
Хотим ещё раз напомнить, что у нас есть пока что маленькое коммьюнити реверсеров на нашем дискорд-сервере, поэтому залетайте туда :D
Ссылка на Team Enterial | Community:
На этом мой реверс лоадер Onetap полностью приостанавливается из-за нехватки времени на полный реверс, хотелось бы разобрать и рассказать про трюки во время авторизации, где опять же происходит злоупотребление сдк протектора, но сейчас на вантап у меня не осталось времени. Довольно интересный лоадер, жалко, что у меня не было подписки, потому что во время авторизации ни один сисколл в аллоцированной памяти не выполняется, возможно они бы выполнялись во время инжекта в игру. Спасибо за прочтение данной статьи :)
Ещё конечно же хотелось бы поблагодарить мою команду, а именно: Arting, nelfo57, Dark_Bull, easton
Хотим ещё раз напомнить, что у нас есть пока что маленькое коммьюнити реверсеров на нашем дискорд-сервере, поэтому залетайте туда :D
Ссылка на Team Enterial | Community:
Пожалуйста, авторизуйтесь для просмотра ссылки.
Последнее редактирование: