Информация [InfinityHook] Как Vanguard перехватывает системные вызовы через ETW... и причем здесь HAL.

Привет. Много своих статей удалил я с этого форума к сожалению. Сегодня я решил написать статью на тему, которая в народе называется Infinity-hook, в РУ-сегменте она довольно таки мало документирована, соответственно найти действительно качественные материалы задача нетривиальная (собственно, в РУ-сегменте я такие статьи и не обнаружил). Поэтому данная статья описывает работу хука, через реализацию которого можно перехватывать syscall-ы. Сам же этот хук уже с года 19 работает в античите от Riot - Vanguard. Начнем.

В целом, реализация данного хука в частности заложена в агрессивные античиты вроде VGK, за весь свой опыт работы с антивирусами, подобной реализации нигде я не замечал. Все современные антивирусы до сих пор по большей части полагаются на стандартные и документированные механизмы вроде минифильтров файловой системы, да и обычные каллбеков,.

Предыстория про infinity-hook.​

Непосредственно самую первую реализацию хука обнаружил Sirocco в процессе анализа компонентов Windows, реализация хука работала через ETW функцию, а если конкретно вернутся в 19 год, то реализация работала через механизм трассировки событий Windows (ETW), а если еще точнее - через системный регистратор WMI_LOGGER_CONTEXT. Реализация хука максимально тривиальна, я реализовал диаграмму, которую вы можете изучить, после чего я объясню, как он работает в теории:
1789321842862.png
Теперь теоритически. Когда в системе активируется трассировка системных вызовов, то ядро должно фиксировать точное время каждого сискола. За это отвечает функция PerfInfoLogSysCallEntry.
  1. Юзермодное приложение вызывает сискол. После чего управление переходит в обработчик KiSystemServiceStart;​
  2. Активный логгер получает управление через PerfInfoLogSysCallEntry;​
  3. Непосредственно внутри этой функции ядро извлекает указатель на структуру WMI_LOGGER_CONTEXT которая управляет конкретной сессией трассировки;​
  4. Чтобы писать в лог точное время вызова, ядру необходимо знать текущий такт процессора, соответственно в данной структуре присутствовало поле GetCpuClock, который являлся обычным указателем на функцию, которая возвращает текущее время. Соответственно после логирования времени управление возвращалось к диспетчеру, и выполнялся оригинальный сискол.​
Когда управление передавалось в драйвер античита, он анализировал стек вызовов. На архитектуре x64 параметры сискола и его номер можно извлечь прямо из контекста потока, соответственно если программа вызывала NtOpenProcess, управление переходило на обработчик драйвера, если перехват данной функции вообще существовал.

Что поменялось с 2019 года?​

Поменялось многое. Microsoft прикрыли лавочку с этим способом тем, что перевели структуры ETW под контроль PG и изменили сам алгоритм вызова GetCpuClock внутри PerfInfoLogSysCallEntry. В современных сборках Windows 10/11 из структуры WMI_LOGGER_CONTEXT был убран прямой голый указатель на функцию замера времени. Теперь поле GetCpuClock хранит перечисление и числовой индекс. Если показать иллюстрационно в коде то выглядит примерно так:
C++:
switch (LoggerContext->GetCpuClock) {
    case 1: Time = rdtsc(); break;
    case 2: Time = KeQueryPerformanceCounter(); break;
    // и тд
}

В замен же этой реализации пришла более современная и удобная реализация этого же самого хука, только работает он немного иначе.

Немного про таблицу.​

Продолжением идеи данного хука стало исследование разработчика Эйдана Хури и Ника Патерсона. Суть нового метода заключается в том, что внутри архитектуры логгеров остались другие, альтернативные функции обратного вызова, которые Microsoft либо забыла защитить, либо не смогла перевести на индексы из-за совместимости.

Реализация более современной версии уходит в интерфейс HAL. Кстаати, интересно то, что таблица, которую мы будем разбирать - HalPrivateDispatchTable. Поле из данной таблицы использовалась еще и для коммуникации с драйвером из юзермода, реализация же коммуникации происходила через NtConvertBetweenAuxiliaryCounterAndPerformanceCounter, разработчики читов подменяли запись в таблице HalPrivateDispatchTable, которая указывает на процедуру xKdEnumerateDebuggingDevices. Из юзермода вызывалась обертка из ntdll, а передаваемые аргументы сквозным потоком проваливались в ядро, предоставляя разработчику идеальный и очень быстрый канал для чтения или записи памяти, быстрее он относительно способа с реестром, например.
1789323136679.png

ETW.​

Архитектура ETW включает в себя 4 элемента:
  1. Поставщик событий - компонент который создает события ETW. Им может быть, как сама операционная система, так и отдельное приложение или драйвер. Например, компонент системы может сообщить о создании процесса, загрузке образа или другом интересующем событии, задача протокола это сформировать событие и передать его инфраструктуре ETW;
  2. Сеанс трассировки - механизм, через который происходит непосредственный сбор событий. Он связывает поставщиков с системой, которая эти события получает. При запуске сеанса определяется, какие поставщики будут активны и какие события от них необходимо собирать. Полученные данные могут временно находиться в буферах, а затем передаваться потребителю или записываться в журнал;
  3. Потребитель - компонент, который получает события из сеанса трассировки и обрабатывает их. На данном этапе собранная информация начинает использоваться по назначению, например, антивирусный процесс может получить событие о создани нового процесса, извлечь из него идентификатор процесса, путь к исполняемому файлу и другие данные, после чего передать информацию своему механизму анализа;
  4. Контроллер - компонент отвечающий за управление сеансом трассировки. Через него можно запустить или остановить сеанс, добавить или отключить поставщиков, а также изменить параметры сбора событий.
1789323286691.png

Современная реализация хука.​

Если говорить простым языком, HAL_PRIVATE_DISPATCH (в кругах чит-дева известная как HalPrivateDispatchTable) - это системный мост, спрятанный глубоко внутри HAL. Главная задача отвечать за взаимодействие ядра с аппаратным физическим железом причем для таких специфических функций, которые Microsoft решила не выносить в стандартные, общедоступные HAL-интерфейсы. Сама структура является просто массивом голых указателей на функции, каждый элемент этой таблицы за строго определенную аппаратную фичу, которая конфигурируется под нужды конкретной платформы. Сама таблица выглядит так:
C++:
struct HAL_PRIVATE_DISPATCH
{
  unsigned int Version;
  BUS_HANDLER *(*HalHandlerForBus)(INTERFACE_TYPE, unsigned int);
  BUS_HANDLER *(*HalHandlerForConfigSpace)(BUS_DATA_TYPE, unsigned int);
  void (*HalLocateHiberRanges)(void *);
  int (*HalRegisterBusHandler)(INTERFACE_TYPE, BUS_DATA_TYPE, unsigned int, INTERFACE_TYPE, unsigned int, unsigned int, int (*)(BUS_HANDLER *), BUS_HANDLER **);
  void (*HalSetWakeEnable)(unsigned __int8);
  int (*HalSetWakeAlarm)(unsigned __int64, unsigned __int64);
  unsigned __int8 (*HalPciTranslateBusAddress)(INTERFACE_TYPE, unsigned int, LARGE_INTEGER, unsigned int *, LARGE_INTEGER *);
  int (*HalPciAssignSlotResources)(UNICODE_STRING *, UNICODE_STRING *, DRIVER_OBJECT *, DEVICE_OBJECT *, INTERFACE_TYPE, unsigned int, unsigned int, CM_RESOURCE_LIST **);
  void (*HalHaltSystem)();
  unsigned __int8 (*HalFindBusAddressTranslation)(LARGE_INTEGER, unsigned int *, LARGE_INTEGER *, unsigned __int64 *, unsigned __int8);
  unsigned __int8 (*HalResetDisplay)();
  int (*HalAllocateMapRegisters)(_ADAPTER_OBJECT *, unsigned int, unsigned int, MAP_REGISTER_ENTRY *);
  int (*KdSetupPciDeviceForDebugging)(void *, DEBUG_DEVICE_DESCRIPTOR *);
  int (*KdReleasePciDeviceForDebugging)(DEBUG_DEVICE_DESCRIPTOR *);
  void *(*KdGetAcpiTablePhase0)(LOADER_PARAMETER_BLOCK *, unsigned int);
  void (*KdCheckPowerButton)();
  unsigned __int8 (*HalVectorToIDTEntry)(unsigned int);
  void *(*KdMapPhysicalMemory64)(LARGE_INTEGER, unsigned int, unsigned __int8);
  void (*KdUnmapVirtualAddress)(void *, unsigned int, unsigned __int8);
  unsigned int (*KdGetPciDataByOffset)(unsigned int, unsigned int, void *, unsigned int, unsigned int);
  unsigned int (*KdSetPciDataByOffset)(unsigned int, unsigned int, void *, unsigned int, unsigned int);
  unsigned int (*HalGetInterruptVectorOverride)(INTERFACE_TYPE, unsigned int, unsigned int, unsigned int, unsigned __int8 *, unsigned __int64 *);
  int (*HalGetVectorInputOverride)(unsigned int, GROUP_AFFINITY *, unsigned int *, KINTERRUPT_POLARITY *, INTERRUPT_REMAPPING_INFO *);
  int (*HalLoadMicrocode)(void *);
  int (*HalUnloadMicrocode)();
  int (*HalPostMicrocodeUpdate)();
  int (*HalAllocateMessageTargetOverride)(DEVICE_OBJECT *, GROUP_AFFINITY *, unsigned int, KINTERRUPT_MODE, unsigned __int8, unsigned int *, unsigned __int8 *, unsigned int *);
  void (*HalFreeMessageTargetOverride)(DEVICE_OBJECT *, unsigned int, GROUP_AFFINITY *);
  int (*HalDpReplaceBegin)(HAL_DP_REPLACE_PARAMETERS *, void **);
  void (*HalDpReplaceTarget)(void *);
  int (*HalDpReplaceControl)(unsigned int, void *);
  void (*HalDpReplaceEnd)(void *);
  void (*HalPrepareForBugcheck)(unsigned int);
  unsigned __int8 (*HalQueryWakeTime)(unsigned __int64 *, unsigned __int64 *);
  void (*HalReportIdleStateUsage)(unsigned __int8, KAFFINITY_EX *);
  void (*HalTscSynchronization)(unsigned __int8, unsigned int *);
  int (*HalWheaInitProcessorGenericSection)(WHEA_ERROR_RECORD_SECTION_DESCRIPTOR *, WHEA_PROCESSOR_GENERIC_ERROR_SECTION *);
  void (*HalStopLegacyUsbInterrupts)(SYSTEM_POWER_STATE);
  int (*HalReadWheaPhysicalMemory)(LARGE_INTEGER, unsigned int, void *);
  int (*HalWriteWheaPhysicalMemory)(LARGE_INTEGER, unsigned int, void *);
  int (*HalDpMaskLevelTriggeredInterrupts)();
  int (*HalDpUnmaskLevelTriggeredInterrupts)();
  int (*HalDpGetInterruptReplayState)(void *, void **);
  int (*HalDpReplayInterrupts)(void *);
  unsigned __int8 (*HalQueryIoPortAccessSupported)();
  int (*KdSetupIntegratedDeviceForDebugging)(void *, DEBUG_DEVICE_DESCRIPTOR *);
  int (*KdReleaseIntegratedDeviceForDebugging)(DEBUG_DEVICE_DESCRIPTOR *);
  void (*HalGetEnlightenmentInformation)(HAL_INTEL_ENLIGHTENMENT_INFORMATION *);
  void *(*HalAllocateEarlyPages)(LOADER_PARAMETER_BLOCK *, unsigned int, unsigned __int64 *, unsigned int);
  void *(*HalMapEarlyPages)(unsigned __int64, unsigned int, unsigned int);
  void *Dummy1;
  void *Dummy2;
  void (*HalNotifyProcessorFreeze)(unsigned __int8, unsigned __int8);
  int (*HalPrepareProcessorForIdle)(unsigned int);
  void (*HalRegisterLogRoutine)(HAL_LOG_REGISTER_CONTEXT *);
  void (*HalResumeProcessorFromIdle)();
  void *Dummy;
  unsigned int (*HalVectorToIDTEntryEx)(unsigned int);
  int (*HalSecondaryInterruptQueryPrimaryInformation)(INTERRUPT_VECTOR_DATA *, unsigned int *);
  int (*HalMaskInterrupt)(unsigned int, unsigned int);
  int (*HalUnmaskInterrupt)(unsigned int, unsigned int);
  unsigned __int8 (*HalIsInterruptTypeSecondary)(unsigned int, unsigned int);
  int (*HalAllocateGsivForSecondaryInterrupt)(char *, unsigned __int16, unsigned int *);
  int (*HalAddInterruptRemapping)(unsigned int, unsigned int, PCI_BUSMASTER_DESCRIPTOR *, unsigned __int8, INTERRUPT_VECTOR_DATA *, unsigned int);
  void (*HalRemoveInterruptRemapping)(unsigned int, unsigned int, PCI_BUSMASTER_DESCRIPTOR *, unsigned __int8, INTERRUPT_VECTOR_DATA *, unsigned int);
  void (*HalSaveAndDisableHvEnlightenment)();
  void (*HalRestoreHvEnlightenment)();
  void (*HalFlushIoBuffersExternalCache)(MDL *, unsigned __int8);
  void (*HalFlushExternalCache)(unsigned __int8);
  int (*HalPciEarlyRestore)(_SYSTEM_POWER_STATE);
  int (*HalGetProcessorId)(unsigned int, unsigned int *, unsigned int *);
  int (*HalAllocatePmcCounterSet)(unsigned int, _KPROFILE_SOURCE *, unsigned int, struct _HAL_PMC_COUNTERS **);
  void (*HalCollectPmcCounters)(struct HAL_PMC_COUNTERS *, unsigned __int64 *);
  void (*HalFreePmcCounterSet)(struct HAL_PMC_COUNTERS *);
  int (*HalProcessorHalt)(unsigned int, void *, int (*)(void *));
  unsigned __int64 (*HalTimerQueryCycleCounter)(unsigned __int64 *);
  void *Dummy3;
  void (*HalPciMarkHiberPhase)();
  int (*HalQueryProcessorRestartEntryPoint)(LARGE_INTEGER *);
  int (*HalRequestInterrupt)(unsigned int);
  int (*HalEnumerateUnmaskedInterrupts)(unsigned __int8 (*)(void *, HAL_UNMASKED_INTERRUPT_INFORMATION *), void *, HAL_UNMASKED_INTERRUPT_INFORMATION *);
  void (*HalFlushAndInvalidatePageExternalCache)(LARGE_INTEGER);
  int (*KdEnumerateDebuggingDevices)(void *, DEBUG_DEVICE_DESCRIPTOR *, KD_CALLBACK_ACTION (*)(DEBUG_DEVICE_DESCRIPTOR *));
  void (*HalFlushIoRectangleExternalCache)(_MDL *, unsigned int, unsigned int, unsigned int, unsigned int, unsigned __int8);
  void (*HalPowerEarlyRestore)(unsigned int);
  int (*HalQueryCapsuleCapabilities)(void *, unsigned int, unsigned __int64 *, unsigned int *);
  int (*HalUpdateCapsule)(void *, unsigned int, LARGE_INTEGER);
  unsigned __int8 (*HalPciMultiStageResumeCapable)();
  void (*HalDmaFreeCrashDumpRegisters)(unsigned int);
  unsigned __int8 (*HalAcpiAoacCapable)();
  int (*HalInterruptSetDestination)(INTERRUPT_VECTOR_DATA *, GROUP_AFFINITY *, unsigned int *);
  void (*HalGetClockConfiguration)(HAL_CLOCK_TIMER_CONFIGURATION *);
  void (*HalClockTimerActivate)(unsigned __int8);
  void (*HalClockTimerInitialize)();
  void (*HalClockTimerStop)();
  int (*HalClockTimerArm)(_HAL_CLOCK_TIMER_MODE, unsigned __int64, unsigned __int64 *);
  unsigned __int8 (*HalTimerOnlyClockInterruptPending)();
  void *(*HalAcpiGetMultiNode)();
  void (*(*HalPowerSetRebootHandler)(void (*)(unsigned int, volatile int *)))(unsigned int, volatile int *);
  void (*HalIommuRegisterDispatchTable)(HAL_IOMMU_DISPATCH *);
  void (*HalTimerWatchdogStart)();
  void (*HalTimerWatchdogResetCountdown)();
  void (*HalTimerWatchdogStop)();
  unsigned __int8 (*HalTimerWatchdogGeneratedLastReset)();
  int (*HalTimerWatchdogTriggerSystemReset)(unsigned __int8);
  int (*HalInterruptVectorDataToGsiv)(INTERRUPT_VECTOR_DATA *, unsigned int *);
  int (*HalInterruptGetHighestPriorityInterrupt)(unsigned int *, unsigned __int8 *);
  int (*HalProcessorOn)(unsigned int);
  int (*HalProcessorOff)();
  int (*HalProcessorFreeze)();
  int (*HalDmaLinkDeviceObjectByToken)(unsigned __int64, DEVICE_OBJECT *);
  int (*HalDmaCheckAdapterToken)(unsigned __int64);
  void *Dummy4;
  int (*HalTimerConvertPerformanceCounterToAuxiliaryCounter)(unsigned __int64, unsigned __int64 *, unsigned __int64 *);
  int (*HalTimerConvertAuxiliaryCounterToPerformanceCounter)(unsigned __int64, unsigned __int64 *, unsigned __int64 *);
  int (*HalTimerQueryAuxiliaryCounterFrequency)(unsigned __int64 *);
  int (*HalConnectThermalInterrupt)(unsigned __int8 (*)(KINTERRUPT *, void *));
  unsigned __int8 (*HalIsEFIRuntimeActive)();
  unsigned __int8 (*HalTimerQueryAndResetRtcErrors)(unsigned __int8);
  void (*HalAcpiLateRestore)();
  int (*KdWatchdogDelayExpiration)(unsigned __int64 *);
  int (*HalGetProcessorStats)(HAL_PROCESSOR_STAT_TYPE, unsigned int, unsigned int, unsigned __int64 *);
  unsigned __int64 (*HalTimerWatchdogQueryDueTime)(unsigned __int8);
  int (*HalConnectSyntheticInterrupt)(unsigned __int8 (*)(KINTERRUPT *, void *));
  void (*HalPreprocessNmi)(unsigned int);
  int (*HalEnumerateEnvironmentVariablesWithFilter)(unsigned int, unsigned __int8 (*)(const _GUID *, const wchar_t *), void *, unsigned int *);
  int (*HalCaptureLastBranchRecordStack)(unsigned int, HAL_LBR_ENTRY *, unsigned int *);
  unsigned __int8 (*HalClearLastBranchRecordStack)();
  int (*HalConfigureLastBranchRecord)(unsigned int, unsigned int);
  unsigned __int8 (*HalGetLastBranchInformation)(unsigned int *, unsigned int *);
  void (*HalResumeLastBranchRecord)(unsigned __int8);
  int (*HalStartLastBranchRecord)(unsigned int, unsigned int *);
  int (*HalStopLastBranchRecord)(unsigned int);
  int (*HalIommuBlockDevice)(void *);
  int (*HalIommuUnblockDevice)(EXT_IOMMU_DEVICE_ID *, void **);
  int (*HalGetIommuInterface)(unsigned int, DMA_IOMMU_INTERFACE *);
  int (*HalRequestGenericErrorRecovery)(void *, unsigned int *);
  int (*HalTimerQueryHostPerformanceCounter)(unsigned __int64 *);
  int (*HalTopologyQueryProcessorRelationships)(unsigned int, unsigned int, unsigned __int8 *, unsigned __int8 *, unsigned __int8 *, unsigned int *, unsigned int *);
  void (*HalInitPlatformDebugTriggers)();
  void (*HalRunPlatformDebugTriggers)(unsigned __int8);
  void *(*HalTimerGetReferencePage)();
  int (*HalGetHiddenProcessorPowerInterface)(HIDDEN_PROCESSOR_POWER_INTERFACE *);
  unsigned int (*HalGetHiddenProcessorPackageId)(unsigned int);
  unsigned int (*HalGetHiddenPackageProcessorCount)(unsigned int);
  int (*HalGetHiddenProcessorApicIdByIndex)(unsigned int, unsigned int *);
  int (*HalRegisterHiddenProcessorIdleState)(unsigned int, unsigned __int64);
  void (*HalIommuReportIommuFault)(unsigned __int64, FAULT_INFORMATION *);
  unsigned __int8 (*HalIommuDmaRemappingCapable)(EXT_IOMMU_DEVICE_ID *, unsigned int *);
};
Непосредственно получать доступ к данной таблице возможно только из под драйвера даже учитывая то, что ETW доступно даже из юзермода. Поиск адреса данной таблицы выглядит следующим образом:
C++:
UNICODE_STRING target = RTL_CONSTANT_STRING( L"HalPrivateDispatchTable" );
HalPrivateDispatchTable = reinterpret_cast< PHAL_PRIVATE_DISPATCH_TABLE >( MmGetSystemRoutineAddress( &target ) );
if ( !HalPrivateDispatchTable )
    return STATUS_RESOURCE_UNAVAILABLE;
В случае же с более современной реализации. В системной функции EtwpLogKernelEvent есть несколько интересных мест.
C++:
if()
{
  v33 = EtwpReserveTraceBuffer(v14, v15 + 0x10, &v59, &v55, a6);
  //
  // ...
  //
  goto LABEL_19;
}
if ( )
{
  v34 = EtwpReserveWithPebsIndex(v14, 0x524, v15, &v59, &v55, a6);
}
else
{
  //...
  v34 = EtwpReserveWithPmcCounters(v14, a5, v15, &v59, &v55, a6);
}
При определенных условиях инициализации ETW-логгера ядро начинает выполнять цепочку проверок и резервирования буферов. Внутри этого процесса вызывается функция EtwpReserveWithPmcCounters, которая уже внутри себя вызывает HalpCollectPmcCounters:
Поиск HalPmcCounters.png

Ядро обращается к этому полю непосредственно по той таблице. Сама же таблица лежит в секции .data, соответственно PG на нее абсолютно никак не реагирует. Да и исторически вышло так, что Microsoft довольно таки часто работали с данной таблицей, соответственно под крыло PG решили данную таблица не добавлять.

При соответствующей настройке трассировки выполнение может доходить до перехватчика, когда ядро собирает аппаратные счетчики производительности процессора для ETW-лога. Сразу добавлю, что на этапе вызова HalpCollectPmcCounters в функции фильтрации нет прямой информации о том, какой именно сискол сейчас выполняется. На руках есть только контекст счетчиков, поэтому для определения конкретного сискол уже приходится смотреть на стек вызовов.

Непосредственно Vanguard в подобном случае используют технику Stack Walking(ake раскрутка стека). Как только управление переходит в обработчик, драйвер берет текущее значение регистра RSP и начинает сканировать стек потока назад, задача же в том, чтобы найти фрейм который принадлежит системному обработчику сисколов KiSystemServiceStart или же ее аналогам. Структура стека же выглядит так:
1789324173490.png

Пример базового обработчика, который раскручивает стек, сверяет идентификаторы событий и находит фрейм нужной системной функции, выглядит так:
C++:
void process_syscall(stack<64>& sp)
{
const auto target_fn = reinterpret_cast(sp.at(9));
if (*target_fn == o__nt_query_system_information)
*target_fn = &hkd__nt_query_system_information;
}
Проверяем конкретный фрейм стека, и если он принадлежит, например, оригинальной NtQuerySystemInformation, мы перезаписываем его адресом нашей функции-обработчика. В качестве тестового примера внутри хука можно модифицировать структуру SystemKernelDebuggerInformation, чтобы сбрасывать флаги KernelDebuggerEnabled и KernelDebuggerNotPresent, или подменять SystemCodeIntegrityInformation, имитируя включенную целостность кода и выключенный тестовый режим подписи драйверов. И так далее. Демонстрация работы хука:
1789325226539.png

Подробнее описание реализации.​

Если разбирать пошагово реализацию, то сам хук реализуется так:
  1. Находим адрес таблицы HalPrivateDispatchTable через MmGetSystemRoutineAddress;
  2. Конфигурируем сессию логгера ядра через ZwTraceControl. Нам понадобятся как минимум коды функций EtwStartLoggerCode, EtwStopLoggerCode и EtwUpdateLoggerCode для управления сессией NT Kernel Logger;
  3. Дополнительно настраиваем блоки информации трассировки через ZwSetSystemInformation с классом SystemPerformanceTraceInformation. Это нужно, чтобы проинициализировать списки счетчиков EventTraceProfileCounterListInformation, без которых подсистема PMC просто не начнет собирать данные;
  4. Подменяем указатель оригинальной функции HalpCollectPmcCounters в таблице HAL на свой обработчик.
Когда все шаги выполнены, управление при каждом сисколе падает в функцию фильтрации. Подробнее реализацию хука я советую глянуть на гитхабе у самого Ника - https://github.com/everdox/infinityhook

Всем пока.
 

Вложения

  • 1789324130985.png
    1789324130985.png
    35.4 KB · Просмотры: 7
  • 1789324155741.png
    1789324155741.png
    35.4 KB · Просмотры: 7
  • 1789325218165.png
    1789325218165.png
    488.7 KB · Просмотры: 6
Последнее редактирование: