- Статус
- Оффлайн
- Регистрация
- 13 Фев 2026
- Сообщения
- 209
- Реакции
- 5
Народ, кто сейчас ковыряет обходы для жестких античитов, забирайте подгон по TPM-спуфингу. Опять пришлось дампить вручную и хукать диспетчер, потому что стандартные решения через реестр или простые макросы отлетают в детект быстрее, чем успеваешь загрузиться в катку.
Суть метода в подмене IOCTL запросов напрямую через системный драйвер TPM. Это позволяет отдавать античиту (EAC/BE/Ricochet) сгенерированные рандомные данные вместо реальных серийников железа. За идею с хуком диспетчера респект Сэмюэлю Тулаху, я лишь допилил нормальную обработку IOCTL под текущие реалии.
Технические нюансы реализации:
Что по детектам?
Сам по себе этот код — только база для того, чтобы античит не видел ваши реальные ID. Если планируете использовать это на основе, не забывайте про UEFI-маппинг и чистку следов в AZURE/Registry. Без нормального спуфера по остальным параметрам (MAC, GUID, Disk) один только TPM вас не спасет от пермача по железу.
Кто уже тестил этот движок в батл-роялях или других экстракшен-шутерах? Отпишитесь, как проходит проверка attestation, нет ли проблем с "лягушкой" или внезапными киками в лобби? Кидайте свои правки, если кто допиливал обработку под специфические версии Windows.
Суть метода в подмене IOCTL запросов напрямую через системный драйвер TPM. Это позволяет отдавать античиту (EAC/BE/Ricochet) сгенерированные рандомные данные вместо реальных серийников железа. За идею с хуком диспетчера респект Сэмюэлю Тулаху, я лишь допилил нормальную обработку IOCTL под текущие реалии.
Технические нюансы реализации:
- Хук диспетчера: Перехватываем IRP_MJ_DEVICE_CONTROL и фильтруем через IOCTL_TPM_SUBMIT_COMMAND.
- Рандомизация: Используется seed-система для генерации RSA ключей и digest-значений, чтобы каждый запуск системы выглядел уникальным для сервера античита.
- Обработка данных: Полностью подменяем ответы для PCR_Read, Quote, ReadPublic и прочих критических вызовов.
Код:
typedef struct _TPM_DATA_READ_PUBLIC
{
TPM2_RESPONSE_HEADER Header;
struct
{
struct
{
struct
{
TPM2B_PUBLIC_KEY_RSA rsa;
} unique;
} publicArea;
} OutPublic;
} TPM_DATA_READ_PUBLIC;
typedef struct _TPM_DATA_GET_RANDOM
{
TPM2_RESPONSE_HEADER Header;
TPM2B_DIGEST RandomBytes;
} TPM_DATA_GET_RANDOM;
typedef struct _TPM_DATA_READ_CLOCK
{
TPM2_RESPONSE_HEADER Header;
TPMS_TIME_INFO CurrentTime;
} TPM_DATA_READ_CLOCK;
typedef struct _TPM_DATA_PCR_READ
{
TPM2_RESPONSE_HEADER Header;
struct
{
UINT32 count;
TPM2B_DIGEST digests [ 8 ];
} pcrValues;
} TPM_DATA_PCR_READ;
typedef struct _TPM_DATA_NV_READ
{
TPM2_RESPONSE_HEADER Header;
TPM2B_MAX_NV_BUFFER Data;
} TPM_DATA_NV_READ;
typedef struct _TPM_DATA_CREATE_PRIMARY
{
TPM2_RESPONSE_HEADER Header;
TPM_HANDLE ObjectHandle;
struct
{
struct
{
struct
{
TPM2B_PUBLIC_KEY_RSA rsa;
} unique;
} publicArea;
} OutPublic;
} TPM_DATA_CREATE_PRIMARY;
#pragma pack(pop)
namespace tpm
{
static PDRIVER_DISPATCH originalDispatch = nullptr;
static TPM2B_PUBLIC_KEY_RSA generatedKey = { 0 };
static unsigned int tpm_seed = 0;
static UINT32 SwapU32 ( UINT32 v )
{
return ( ( v >> 24 ) & 0x000000FFu ) |
( ( v >> 8 ) & 0x0000FF00u ) |
( ( v << 8 ) & 0x00FF0000u ) |
( ( v << 24 ) & 0xFF000000u );
}
static UINT16 SwapU16 ( UINT16 v )
{
return ( ( v >> 8 ) & 0x00FFu ) | ( ( v << 8 ) & 0xFF00u );
}
static unsigned int NextSeed ( )
{
tpm_seed ^= tpm_seed << 13;
tpm_seed ^= tpm_seed >> 17;
tpm_seed ^= tpm_seed << 5;
return tpm_seed;
}
static void GenerateKey ( )
{
UINT32 s = emulated_utils::hwid_seed;
for ( UINT16 i = 0; i < MAX_RSA_KEY_BYTES; i++ ) {
s ^= s << 13;
s ^= s >> 17;
s ^= s << 5;
generatedKey.buffer [ i ] = static_cast< UCHAR >( ( s >> 16 ) & 0xFF );
}
generatedKey.size = MAX_RSA_KEY_BYTES;
}
static void FillRandom ( UCHAR* buf , UINT16 len )
{
for ( UINT16 i = 0; i < len; i++ )
buf [ i ] = static_cast< UCHAR > ( NextSeed ( ) >> 16 );
}
static void FillRandomBytes ( UCHAR* buf , SIZE_T len )
{
while ( len ) {
UINT16 chunk = static_cast< UINT16 >( len > 65535 ? 65535 : len );
FillRandom ( buf , chunk );
buf += chunk;
len -= chunk;
}
}
static NTSTATUS HandleQuote ( PDEVICE_OBJECT device , PIRP irp , PVOID context )
{
if ( !context )
return STATUS_SUCCESS;
emulated_utils::IOC_REQUEST req = *static_cast< emulated_utils::PIOC_REQUEST >( context );
ExFreePool ( context );
if ( req.Buffer && MmIsAddressValid ( req.Buffer ) ) {
TPM2_RESPONSE_HEADER* hdr = static_cast< TPM2_RESPONSE_HEADER* >( req.Buffer );
const UINT32 rc = SwapU32 ( static_cast< UINT32 >( hdr->responseCode ) );
if ( rc == TPM_RC_SUCCESS ) {
const UINT32 paramSize = SwapU32 ( hdr->paramSize );
const SIZE_T hdrSz = sizeof ( TPM2_RESPONSE_HEADER );
if ( paramSize > hdrSz && paramSize <= 65536 ) {
PUCHAR base = static_cast< PUCHAR >( req.Buffer );
if ( MmIsAddressValid ( base + paramSize - 1 ) ) {
TPM2B_ATTEST* quoted = reinterpret_cast< TPM2B_ATTEST* >( base + hdrSz );
UINT16 asz = SwapU16 ( quoted->size );
if ( asz > sizeof ( quoted->attestationData ) )
asz = static_cast< UINT16 >( sizeof ( quoted->attestationData ) );
if ( asz )
FillRandom ( quoted->attestationData , asz );
PUCHAR sigPtr = base + hdrSz + 2u + static_cast< SIZE_T >( asz );
if ( sigPtr < base + paramSize ) {
const SIZE_T sigLen = static_cast< SIZE_T >( base + paramSize - sigPtr );
if ( sigLen )
FillRandomBytes ( sigPtr , sigLen );
}
}
}
}
}
if ( req.OldRoutine )
return req.OldRoutine ( device , irp , req.OldContext );
return STATUS_SUCCESS;
}
static NTSTATUS HandleReadPublic ( PDEVICE_OBJECT device , PIRP irp , PVOID context )
{
if ( !context )
return STATUS_SUCCESS;
emulated_utils::IOC_REQUEST req = *static_cast< emulated_utils::PIOC_REQUEST > ( context );
ExFreePool ( context );
if ( req.Buffer && MmIsAddressValid ( req.Buffer ) ) {
TPM_DATA_READ_PUBLIC* data = static_cast< TPM_DATA_READ_PUBLIC* >( req.Buffer );
const UINT32 respSize = SwapU32 ( data->Header.paramSize );
const size_t keyCopy = MAX_RSA_KEY_BYTES;
const size_t minSize = offsetof ( TPM_DATA_READ_PUBLIC , OutPublic.publicArea.unique.rsa.buffer ) + keyCopy;
if ( respSize >= minSize )
RtlCopyMemory ( data->OutPublic.publicArea.unique.rsa.buffer , generatedKey.buffer , keyCopy );
}
if ( req.OldRoutine )
return req.OldRoutine ( device , irp , req.OldContext );
return STATUS_SUCCESS;
}
static NTSTATUS HandleGetRandom ( PDEVICE_OBJECT device , PIRP irp , PVOID context )
{
if ( !context )
return STATUS_SUCCESS;
emulated_utils::IOC_REQUEST req = *static_cast< emulated_utils::PIOC_REQUEST >( context );
ExFreePool ( context );
if ( req.Buffer && MmIsAddressValid ( req.Buffer ) ) {
TPM_DATA_GET_RANDOM* data = static_cast< TPM_DATA_GET_RANDOM* >( req.Buffer );
UINT16 len = SwapU16 ( data->RandomBytes.size );
if ( len > 0 && len <= sizeof ( data->RandomBytes.buffer ) )
FillRandom ( data->RandomBytes.buffer , len );
}
if ( req.OldRoutine )
return req.OldRoutine ( device , irp , req.OldContext );
return STATUS_SUCCESS;
}
static NTSTATUS HandleReadClock ( PDEVICE_OBJECT device , PIRP irp , PVOID context )
{
if ( !context )
return STATUS_SUCCESS;
emulated_utils::IOC_REQUEST req = *static_cast< emulated_utils::PIOC_REQUEST >( context );
ExFreePool ( context );
if ( req.Buffer && MmIsAddressValid ( req.Buffer ) ) {
TPM_DATA_READ_CLOCK* data = static_cast< TPM_DATA_READ_CLOCK* >( req.Buffer );
unsigned int seed = NextSeed ( );
data->CurrentTime.clockInfo.resetCount = ( seed >> 8 ) & 0xFFFF;
data->CurrentTime.clockInfo.restartCount = ( seed >> 4 ) & 0xFFFF;
}
if ( req.OldRoutine )
return req.OldRoutine ( device , irp , req.OldContext );
return STATUS_SUCCESS;
}
static NTSTATUS HandleGetCapability ( PDEVICE_OBJECT device , PIRP irp , PVOID context )
{
if ( !context )
return STATUS_SUCCESS;
emulated_utils::IOC_REQUEST req = *static_cast< emulated_utils::PIOC_REQUEST >( context );
ExFreePool ( context );
if ( req.OldRoutine )
return req.OldRoutine ( device , irp , req.OldContext );
return STATUS_SUCCESS;
}
static NTSTATUS HandlePCRRead ( PDEVICE_OBJECT device , PIRP irp , PVOID context )
{
if ( !context )
return STATUS_SUCCESS;
emulated_utils::IOC_REQUEST req = *static_cast< emulated_utils::PIOC_REQUEST >( context );
ExFreePool ( context );
if ( req.Buffer && MmIsAddressValid ( req.Buffer ) ) {
TPM_DATA_PCR_READ* data = static_cast< TPM_DATA_PCR_READ* >( req.Buffer );
UINT32 count = SwapU32 ( data->pcrValues.count );
if ( count > 8 ) count = 8;
for ( UINT32 i = 0; i < count; i++ )
{
UINT16 sz = SwapU16 ( data->pcrValues.digests [ i ].size );
if ( sz == 0 || sz > sizeof ( data->pcrValues.digests [ i ].buffer ) ) continue;
FillRandom ( data->pcrValues.digests [ i ].buffer , sz );
}
}
if ( req.OldRoutine )
return req.OldRoutine ( device , irp , req.OldContext );
return STATUS_SUCCESS;
}
static NTSTATUS HandleCreatePrimary ( PDEVICE_OBJECT device , PIRP irp , PVOID context )
{
if ( !context )
return STATUS_SUCCESS;
emulated_utils::IOC_REQUEST req = *static_cast< emulated_utils::PIOC_REQUEST >( context );
ExFreePool ( context );
if ( req.Buffer && MmIsAddressValid ( req.Buffer ) ) {
TPM_DATA_CREATE_PRIMARY* data = static_cast< TPM_DATA_CREATE_PRIMARY* >( req.Buffer );
RtlCopyMemory ( data->OutPublic.publicArea.unique.rsa.buffer , generatedKey.buffer , MAX_RSA_KEY_BYTES );
}
if ( req.OldRoutine )
return req.OldRoutine ( device , irp , req.OldContext );
return STATUS_SUCCESS;
}
static NTSTATUS HandleNVRead ( PDEVICE_OBJECT device , PIRP irp , PVOID context )
{
if ( !context )
return STATUS_SUCCESS;
emulated_utils::IOC_REQUEST req = *static_cast< emulated_utils::PIOC_REQUEST >( context );
ExFreePool ( context );
if ( req.Buffer && MmIsAddressValid ( req.Buffer ) ) {
TPM_DATA_NV_READ* data = static_cast< TPM_DATA_NV_READ* >( req.Buffer );
UINT16 sz = SwapU16 ( data->Data.size );
if ( sz > 0 && sz <= sizeof ( data->Data.buffer ) )
FillRandom ( data->Data.buffer , sz );
}
if ( req.OldRoutine )
return req.OldRoutine ( device , irp , req.OldContext );
return STATUS_SUCCESS;
}
static NTSTATUS Dispatch ( PDEVICE_OBJECT device , PIRP irp )
{
const PIO_STACK_LOCATION ioc = IoGetCurrentIrpStackLocation ( irp );
if ( ioc->MajorFunction == IRP_MJ_DEVICE_CONTROL &&
ioc->Parameters.DeviceIoControl.IoControlCode == IOCTL_TPM_SUBMIT_COMMAND )
{
PVOID buf = irp->AssociatedIrp.SystemBuffer;
if ( buf && MmIsAddressValid ( buf ) &&
ioc->Parameters.DeviceIoControl.InputBufferLength >= sizeof ( TPM2_COMMAND_HEADER ) )
{
const TPM2_COMMAND_HEADER* header = static_cast< const TPM2_COMMAND_HEADER* >( buf );
const TPM_CC command = SwapU32 ( header->commandCode );
switch ( command )
{
case TPM_CC_ReadPublic:
emulated_utils::change_ioc ( ioc , irp , HandleReadPublic );
break;
case TPM_CC_GetRandom:
emulated_utils::change_ioc ( ioc , irp , HandleGetRandom );
break;
case TPM_CC_ReadClock:
emulated_utils::change_ioc ( ioc , irp , HandleReadClock );
break;
case TPM_CC_GetCapability:
emulated_utils::change_ioc ( ioc , irp , HandleGetCapability );
break;
case TPM_CC_PCR_Read:
emulated_utils::change_ioc ( ioc , irp , HandlePCRRead );
break;
case TPM_CC_CreatePrimary:
emulated_utils::change_ioc ( ioc , irp , HandleCreatePrimary );
break;
case TPM_CC_NV_Read:
emulated_utils::change_ioc ( ioc , irp , HandleNVRead );
break;
case TPM_CC_Quote:
emulated_utils::change_ioc ( ioc , irp , HandleQuote );
break;
default:
break;
}
}
}
return originalDispatch ( device , irp );
}
NTSTATUS spoof ( )
{
GenerateKey ( );
tpm_seed = emulated_utils::hwid_seed;
UNICODE_STRING driverName;
RtlInitUnicodeString ( &driverName , encrypt ( L"\\Driver\\TPM" ) );
PDRIVER_OBJECT driverObject = nullptr;
if ( !NT_SUCCESS ( ObReferenceObjectByName (
&driverName , OBJ_CASE_INSENSITIVE , nullptr , 0 ,
*IoDriverObjectType , KernelMode , nullptr ,
reinterpret_cast< PVOID* >( &driverObject ) ) ) )
return STATUS_UNSUCCESSFUL;
originalDispatch = driverObject->MajorFunction [ IRP_MJ_DEVICE_CONTROL ];
driverObject->MajorFunction [ IRP_MJ_DEVICE_CONTROL ] = &Dispatch;
ObDereferenceObject ( driverObject );
return STATUS_SUCCESS;
}
}
Что по детектам?
Сам по себе этот код — только база для того, чтобы античит не видел ваши реальные ID. Если планируете использовать это на основе, не забывайте про UEFI-маппинг и чистку следов в AZURE/Registry. Без нормального спуфера по остальным параметрам (MAC, GUID, Disk) один только TPM вас не спасет от пермача по железу.
Слил вам оффсеты и логику, потому что сам юзаю похожий софт. Юзайте на свой страх и риск, желательно на виртуалках или в связке с нормальным Kernel-драйвером, который умеет скрывать свои следы в памяти. И да, нормальную генерацию серийников я тут вырезал — она сложнее, чем кажется на первый взгляд, придется немного додумать самому.
Кто уже тестил этот движок в батл-роялях или других экстракшен-шутерах? Отпишитесь, как проходит проверка attestation, нет ли проблем с "лягушкой" или внезапными киками в лобби? Кидайте свои правки, если кто допиливал обработку под специфические версии Windows.