Подписывайтесь на наш Telegram и не пропускайте важные новости! Перейти

Исходник [Сурс] Анти-чит байпас — Комплексный спуф WMI и реестра EDID для монитора (C++)

  • Автор темы Автор темы hex_cat
  • Дата начала Дата начала
Sloppy
Начинающий
Начинающий
Статус
Оффлайн
Регистрация
13 Фев 2026
Сообщения
505
Реакции
12
Народ, кто сейчас ковыряет свои лоадеры или пишет кастомный спуфер под античиты, обратите внимание — наткнулся на интересный подход к WMI и EDID. В последнее время в P2C софте часто вижу одну и ту же ошибку: разрабы либо хукают только WMI, либо чистят только реестр, а топовые античиты (EAC/BattlEye/VGK) сверяют оба источника. Если данные не матчатся, сами знаете — отлет по железу гарантирован.

Слил вам рабочий пример, как это закрыть комплексно. Суть в том, что мы перехватываем \Driver\monitor и лезем в IRP_MJ_SYSTEM_CONTROL, чтобы на лету править ответы WMI, плюс патчим HKLM\SYSTEM\CurrentControlSet\Enum\DISPLAY для EDID.

Технические нюансы:
  1. Хуки: Хукаем IRP_MN_QUERY_ALL_DATA для манипуляции WmiMonitorID.
  2. Рандомизация: Используется seed, чтобы значения были консистентными для системы, иначе при каждом запросе будет новый HWID, что сразу вызовет подозрения у аналитики.
  3. EDID: После патча чекаем контрольную сумму (checksum), иначе винда просто отвалится или будет ругаться на драйвер монитора.
  4. Кэширование: Реализовано кэширование по устройствам, чтобы не спамить в логи и не перегружать память.

Код:
Expand Collapse Copy
#define WNODE_FLAG_FIXED_INSTANCE_SIZE 0x00000010
 
typedef struct _WNODE_HEADER {
    ULONG   BufferSize;
    ULONG   ProviderId;
    union {
        ULONG64 HistoricalContext;
        struct { ULONG Version; ULONG Linkage; };
    };
    union {
        ULONG         CountLost;
        HANDLE        KernelHandle;
        LARGE_INTEGER TimeStamp;
    };
    GUID  Guid;
    ULONG ClientContext;
    ULONG Flags;
} WNODE_HEADER, *PWNODE_HEADER;
 
typedef struct {
    ULONG OffsetInstanceData;
    ULONG LengthInstanceData;
} OFFSETINSTANCEDATAANDLENGTH, *POFFSETINSTANCEDATAANDLENGTH;
 
typedef struct _WNODE_ALL_DATA {
    WNODE_HEADER WnodeHeader;
    ULONG        DataBlockOffset;
    ULONG        InstanceCount;
    ULONG        OffsetInstanceNameOffsets;
    union {
        ULONG                        FixedInstanceSize;
        OFFSETINSTANCEDATAANDLENGTH  OffsetInstanceDataAndLength [ 1 ];
    };
} WNODE_ALL_DATA, *PWNODE_ALL_DATA;
 
typedef struct _WmiMonitorID {
    USHORT ProductCodeID       [ 16 ];
    USHORT SerialNumberID      [ 16 ];
    USHORT ManufacturerName    [ 16 ];
    UCHAR  WeekOfManufacture;
    USHORT YearOfManufacture;
    USHORT UserFriendlyNameLength;
    USHORT UserFriendlyName    [ 1 ];
} WmiMonitorID, *PWmiMonitorID;
 
PDRIVER_DISPATCH g_OriginalMonitorDispatch = nullptr;
 
#define MAX_MONITOR_HOOKS 16
static struct {
    PDRIVER_OBJECT   drv;
    PDRIVER_DISPATCH original;
} g_MonitorHooks [ MAX_MONITOR_HOOKS ] = {};
static ULONG g_MonitorHookCount = 0;
 
static char g_SpoofedSerial [ 14 ] = { 0 };
static char g_SpoofedVendor [ 4  ] = { 0 };
static char g_SpoofedName   [ 14 ] = { 0 };
static bool g_MonitorStringsInit = false;
 
namespace monitor
{
 
    static ULONG MonLCG ( ULONG& seed )
    {
        // Use xorshift32 for better distribution
        seed ^= seed << 13;
        seed ^= seed >> 17;
        seed ^= seed << 5;
        return seed;
    }
 
    static void InitMonitorStrings ( )
    {
        if ( g_MonitorStringsInit ) return;
 
        static const char* vendors [ ] = {
            encrypt ( "AUS" ), encrypt ( "SAM" ), encrypt ( "DEL" ), encrypt ( "LEN" ),
            encrypt ( "AOC" ), encrypt ( "BNQ" ), encrypt ( "HWP" ), encrypt ( "GSM" )
        };
        static const char* models [ ] = {
            encrypt ( "VG279QM" ),  encrypt ( "VG27AQ" ),   encrypt ( "VG248QG" ),
            encrypt ( "S24D330" ),  encrypt ( "S27F350" ),   encrypt ( "C27F390" ),
            encrypt ( "P2419H" ),   encrypt ( "U2722D" ),    encrypt ( "S2721DGF" ),
            encrypt ( "27GL850" ),  encrypt ( "27GP850" ),   encrypt ( "24GL600F" ),
            encrypt ( "Q27G2S" ),   encrypt ( "CQ27G2S" ),   encrypt ( "AG274QZ" ),
            encrypt ( "HP24mh" ),   encrypt ( "HP27h" ),     encrypt ( "E24d" ),
            encrypt ( "27UK850" ),  encrypt ( "27GL83A" ),   encrypt ( "32GN650" )
        };
        static const char alphanum [ ] = {
            'A','B','C','D','E','F','G','H','I','J','K','L','M',
            'N','O','P','Q','R','S','T','U','V','W','X','Y','Z',
            '0','1','2','3','4','5','6','7','8','9', 0
        };
        static const char digits [ ] = { '0','1','2','3','4','5','6','7','8','9', 0 };
 
        ULONG seed = emulated_settings::hwid_seed ^ 0xDEADBEEF;
 
        ULONG vi = ( MonLCG ( seed ) >> 16 ) % ( sizeof ( vendors ) / sizeof ( vendors [ 0 ] ) );
        ULONG mi = ( MonLCG ( seed ) >> 16 ) % ( sizeof ( models  ) / sizeof ( models  [ 0 ] ) );
 
        RtlCopyMemory ( g_SpoofedVendor , vendors [ vi ] , 3 );
        g_SpoofedVendor [ 3 ] = 0;
 
        RtlCopyMemory ( g_SpoofedName , models [ mi ] , strlen ( models [ mi ] ) + 1 );
 
        for ( int i = 0; i < 4; i++ )
            g_SpoofedSerial [ i ] = alphanum [ ( MonLCG ( seed ) >> 16 ) % 26 ];
        for ( int i = 4; i < 6; i++ )
            g_SpoofedSerial [ i ] = digits [ ( MonLCG ( seed ) >> 16 ) % 10 ];
        for ( int i = 6; i < 9; i++ )
            g_SpoofedSerial [ i ] = alphanum [ ( MonLCG ( seed ) >> 16 ) % 36 ];
        for ( int i = 9; i < 11; i++ )
            g_SpoofedSerial [ i ] = digits [ ( MonLCG ( seed ) >> 16 ) % 10 ];
        for ( int i = 11; i < 13; i++ )
            g_SpoofedSerial [ i ] = alphanum [ ( MonLCG ( seed ) >> 16 ) % 26 ];
        g_SpoofedSerial [ 13 ] = 0;
 
        g_MonitorStringsInit = true;
 
        logging::output ( encrypt ( "monitor: vendor=%s model=%s serial=%s" ) ,
            g_SpoofedVendor , g_SpoofedName , g_SpoofedSerial );
    }
 
    static void WmiStringToAnsi ( PUSHORT src , ULONG maxChars , char* out , ULONG outSize )
    {
        WCHAR wbuf [ 33 ] = { 0 };
        maxChars = min ( maxChars , 32 );
        for ( ULONG i = 0; i < maxChars; i++ )
        {
            if ( !src [ i ] ) break;
            wbuf [ i ] = ( WCHAR )src [ i ];
        }
        UNICODE_STRING us; ANSI_STRING as;
        RtlInitUnicodeString ( &us , wbuf );
        as.Buffer = out; as.MaximumLength = ( USHORT )outSize;
        RtlUnicodeStringToAnsiString ( &as , &us , FALSE );
    }
 
    static void AnsiToWmiString ( const char* src , PUSHORT dst , ULONG maxChars )
    {
        for ( ULONG i = 0; i < maxChars; i++ )
            dst [ i ] = src [ i ] ? ( USHORT )( UCHAR )src [ i ] : 0;
    }
 
    static void PatchEdidDescriptor ( PUCHAR edid , ULONG edidLen , UCHAR type , const char* newStr )
    {
        if ( edidLen < 128 ) return;
 
        for ( ULONG off = 54; off <= 108; off += 18 )
        {
            if ( edid [ off ] != 0x00 || edid [ off + 1 ] != 0x00 ) continue;
            if ( edid [ off + 2 ] != 0x00 )                         continue;
            if ( edid [ off + 3 ] != type )                         continue;
 
            PUCHAR data = edid + off + 5;
            RtlZeroMemory ( data , 13 );
            SIZE_T len = strlen ( newStr );
            if ( len > 12 ) len = 12;
            RtlCopyMemory ( data , newStr , len );
            data [ len ] = 0x0A;
        }
 
        UCHAR sum = 0;
        for ( ULONG i = 0; i < 127; i++ ) sum += edid [ i ];
        edid [ 127 ] = ( UCHAR )( 0x100 - sum );
    }
 
    static void PatchRegistryEdid ( )
    {
        UNICODE_STRING    displayPath;
        OBJECT_ATTRIBUTES oa;
        HANDLE            hDisplay = nullptr;
 
        RtlInitUnicodeString ( &displayPath , encrypt ( L"\\Registry\\Machine\\SYSTEM\\CurrentControlSet\\Enum\\DISPLAY" ) );
        InitializeObjectAttributes ( &oa , &displayPath , OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE , nullptr , nullptr );
 
        if ( !NT_SUCCESS ( ZwOpenKey ( &hDisplay , KEY_READ , &oa ) ) )
            return;
 
        ULONG modelIdx = 0;
        while ( true )
        {
            ULONG    modelLen = 0;
            NTSTATUS s = ZwEnumerateKey ( hDisplay , modelIdx , KeyBasicInformation , nullptr , 0 , &modelLen );
            if ( s == STATUS_NO_MORE_ENTRIES ) break;
            if ( !modelLen ) { modelIdx++; continue; }
 
            PKEY_BASIC_INFORMATION modelInfo = ( PKEY_BASIC_INFORMATION )ExAllocatePoolWithTag ( NonPagedPool , modelLen , 'TIDE' );
            if ( !modelInfo ) { modelIdx++; continue; }
 
            s = ZwEnumerateKey ( hDisplay , modelIdx++ , KeyBasicInformation , modelInfo , modelLen , &modelLen );
            if ( s == STATUS_NO_MORE_ENTRIES ) { ExFreePoolWithTag ( modelInfo , 'TIDE' ); break; }
            if ( !NT_SUCCESS ( s ) ) { ExFreePoolWithTag ( modelInfo , 'TIDE' ); continue; }
 
            UNICODE_STRING modelName = { ( USHORT )modelInfo->NameLength , ( USHORT )modelInfo->NameLength , modelInfo->Name };
 
            HANDLE hModel = nullptr;
            InitializeObjectAttributes ( &oa , &modelName , OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE , hDisplay , nullptr );
            if ( !NT_SUCCESS ( ZwOpenKey ( &hModel , KEY_READ , &oa ) ) ) { ExFreePoolWithTag ( modelInfo , 'TIDE' ); continue; }
 
            CHAR modelAnsi [ 64 ] = { 0 };
            ANSI_STRING modelAs; UNICODE_STRING modelUs = modelName;
            modelAs.Buffer = modelAnsi; modelAs.MaximumLength = sizeof ( modelAnsi );
            RtlUnicodeStringToAnsiString ( &modelAs , &modelUs , FALSE );
            ExFreePoolWithTag ( modelInfo , 'TIDE' );
 
            ULONG instIdx = 0;
            while ( true )
            {
                ULONG  instLen = 0;
                s = ZwEnumerateKey ( hModel , instIdx , KeyBasicInformation , nullptr , 0 , &instLen );
                if ( s == STATUS_NO_MORE_ENTRIES ) break;
                if ( !instLen ) { instIdx++; continue; }
 
                PKEY_BASIC_INFORMATION instInfo = ( PKEY_BASIC_INFORMATION )ExAllocatePoolWithTag ( NonPagedPool , instLen , 'TIDE' );
                if ( !instInfo ) { instIdx++; continue; }
 
                s = ZwEnumerateKey ( hModel , instIdx++ , KeyBasicInformation , instInfo , instLen , &instLen );
                if ( s == STATUS_NO_MORE_ENTRIES ) { ExFreePoolWithTag ( instInfo , 'TIDE' ); break; }
                if ( !NT_SUCCESS ( s ) ) { ExFreePoolWithTag ( instInfo , 'TIDE' ); continue; }
 
                UNICODE_STRING instName = { ( USHORT )instInfo->NameLength , ( USHORT )instInfo->NameLength , instInfo->Name };
 
                HANDLE hInst = nullptr;
                InitializeObjectAttributes ( &oa , &instName , OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE , hModel , nullptr );
                if ( !NT_SUCCESS ( ZwOpenKey ( &hInst , KEY_READ , &oa ) ) ) { ExFreePoolWithTag ( instInfo , 'TIDE' ); continue; }
                ExFreePoolWithTag ( instInfo , 'TIDE' );
 
                UNICODE_STRING devParams;
                RtlInitUnicodeString ( &devParams , encrypt ( L"Device Parameters" ) );
                HANDLE hDevParams = nullptr;
                InitializeObjectAttributes ( &oa , &devParams , OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE , hInst , nullptr );
 
                NTSTATUS openStatus = ZwOpenKey ( &hDevParams , KEY_ALL_ACCESS , &oa );
                if ( !NT_SUCCESS ( openStatus ) )
                {
                    ULONG disposition = 0;
                    openStatus = ZwCreateKey ( &hDevParams , KEY_ALL_ACCESS , &oa , 0 , nullptr , REG_OPTION_NON_VOLATILE , &disposition );
                }
                if ( !NT_SUCCESS ( openStatus ) ) continue;
 
                UNICODE_STRING edidVal;
                RtlInitUnicodeString ( &edidVal , encrypt ( L"EDID" ) );
 
                ZwClose ( hInst );
 
                ULONG edidInfoSize = 0;
                ZwQueryValueKey ( hDevParams , &edidVal , KeyValueFullInformation , nullptr , 0 , &edidInfoSize );
 
                if ( edidInfoSize > 0 )
                {
                    PKEY_VALUE_FULL_INFORMATION edidInfo = ( PKEY_VALUE_FULL_INFORMATION )
                        ExAllocatePoolWithTag ( NonPagedPool , edidInfoSize , 'TIDE' );
 
                    if ( edidInfo )
                    {
                        ULONG needed = 0;
                        if ( NT_SUCCESS ( ZwQueryValueKey ( hDevParams , &edidVal , KeyValueFullInformation ,
                            edidInfo , edidInfoSize , &needed ) ) )
                        {
                            PUCHAR edidData = ( PUCHAR )edidInfo + edidInfo->DataOffset;
                            ULONG  edidLen = edidInfo->DataLength;
 
                            ULONG seed = emulated_settings::hwid_seed ^ ( modelIdx * 0x1337 ) ^ ( instIdx * 0x7331 );
                            static const char alphanum [ ] = {
                                'A','B','C','D','E','F','G','H','I','J','K','L','M',
                                'N','O','P','Q','R','S','T','U','V','W','X','Y','Z',
                                '0','1','2','3','4','5','6','7','8','9', 0
                            };
                            static const char digits [ ] = { '0','1','2','3','4','5','6','7','8','9', 0 };
                            char devSerial [ 14 ] = { 0 };
                            for ( int c = 0; c < 4;  c++ ) { seed = xorshift32(seed); devSerial [ c ] = alphanum [ ( seed >> 16 ) % 26 ]; }
                            for ( int c = 4; c < 6;  c++ ) { seed = xorshift32(seed); devSerial [ c ] = digits   [ ( seed >> 16 ) % 10 ]; }
                            for ( int c = 6; c < 9;  c++ ) { seed = xorshift32(seed); devSerial [ c ] = alphanum [ ( seed >> 16 ) % 36 ]; }
                            for ( int c = 9; c < 11; c++ ) { seed = xorshift32(seed); devSerial [ c ] = digits   [ ( seed >> 16 ) % 10 ]; }
                            for ( int c = 11; c < 13; c++ ) { seed = xorshift32(seed); devSerial [ c ] = alphanum [ ( seed >> 16 ) % 26 ]; }
 
                            PatchEdidDescriptor ( edidData , edidLen , 0xFF , devSerial );
                            PatchEdidDescriptor ( edidData , edidLen , 0xFC , g_SpoofedName );
                            PatchEdidDescriptor ( edidData , edidLen , 0xFE , g_SpoofedVendor );
 
                            ZwSetValueKey ( hDevParams , &edidVal , 0 , REG_BINARY , edidData , edidLen );
                            logging::output ( encrypt ( "monitor edid: %s -> serial=%s" ) , modelAnsi , devSerial );
                        }
                        ExFreePoolWithTag ( edidInfo , 'TIDE' );
                    }
                }
 
                ZwClose ( hDevParams );
            }
 
            ZwClose ( hModel );
        }
 
        ZwClose ( hDisplay );
    }
 
    NTSTATUS HandleMonitor ( PDEVICE_OBJECT device , PIRP Irp )
    {
        PIO_STACK_LOCATION ioc = IoGetCurrentIrpStackLocation ( Irp );
 
        if ( ioc->MinorFunction != IRP_MN_QUERY_ALL_DATA )
            return g_OriginalMonitorDispatch ( device , Irp );
 
        PVOID wmiBuffer = ioc->Parameters.WMI.Buffer;
        ULONG wmiSize = ioc->Parameters.WMI.BufferSize;
 
        NTSTATUS status = g_OriginalMonitorDispatch ( device , Irp );
 
        if ( NT_SUCCESS ( status ) && wmiBuffer && wmiSize >= sizeof ( WNODE_ALL_DATA ) )
        {
            PWNODE_ALL_DATA allData = ( PWNODE_ALL_DATA )wmiBuffer;
            if ( !MmIsAddressValid ( allData ) || !allData->InstanceCount )
                return status;
 
            BOOLEAN fixed = ( allData->WnodeHeader.Flags & WNODE_FLAG_FIXED_INSTANCE_SIZE ) != 0;
            PUCHAR  base = ( PUCHAR )allData + allData->DataBlockOffset;
 
            for ( ULONG i = 0; i < allData->InstanceCount; i++ )
            {
                PWmiMonitorID mon = nullptr;
 
                if ( fixed )
                    mon = ( PWmiMonitorID )( base + i * allData->FixedInstanceSize );
                else
                {
                    OFFSETINSTANCEDATAANDLENGTH* inst = &allData->OffsetInstanceDataAndLength [ i ];
                    if ( !inst->OffsetInstanceData ) continue;
                    mon = ( PWmiMonitorID )( ( PUCHAR )allData + inst->OffsetInstanceData );
                }
 
                if ( !MmIsAddressValid ( mon ) ) continue;
 
                static PDEVICE_OBJECT g_CachedDevice [ 16 ] = {};
                static char           g_CachedSerial [ 16 ] [ 12 ] = {};
                static bool           g_CachedInit   [ 16 ] = {};
 
                ULONG cacheIdx = 0xFF;
                for ( ULONG k = 0; k < 16; k++ )
                {
                    if ( g_CachedInit [ k ] && g_CachedDevice [ k ] == device ) { cacheIdx = k; break; }
                    if ( !g_CachedInit [ k ] && cacheIdx == 0xFF ) cacheIdx = k;
                }
                if ( cacheIdx == 0xFF ) cacheIdx = 0;
 
                if ( !g_CachedInit [ cacheIdx ] )
                {
                    const char alphanum [ ] = {
                        'A','B','C','D','E','F','G','H','I','J','K','L','M',
                        'N','O','P','Q','R','S','T','U','V','W','X','Y','Z',
                        '0','1','2','3','4','5','6','7','8','9', 0
                    };
                    const char digits [ ] = { '0','1','2','3','4','5','6','7','8','9', 0 };
 
                    ULONG seed = emulated_settings::hwid_seed ^ ( ( ULONG )( ULONG_PTR )device * 0x9E3779B9 );
                    char* s = g_CachedSerial [ cacheIdx ];
                    for ( int c = 0; c < 3;  c++ ) { seed = xorshift32(seed); s [ c ] = alphanum [ ( seed >> 16 ) % 26 ]; }
                    for ( int c = 3; c < 5;  c++ ) { seed = xorshift32(seed); s [ c ] = digits   [ ( seed >> 16 ) % 10 ]; }
                    for ( int c = 5; c < 8;  c++ ) { seed = xorshift32(seed); s [ c ] = alphanum [ ( seed >> 16 ) % 36 ]; }
                    for ( int c = 8; c < 10; c++ ) { seed = xorshift32(seed); s [ c ] = digits   [ ( seed >> 16 ) % 10 ]; }
                    s [ 10 ] = 0;
 
                    g_CachedDevice [ cacheIdx ] = device;
                    g_CachedInit   [ cacheIdx ] = true;
                }
 
                const char* spoofSerial = g_CachedSerial [ cacheIdx ];
 
                AnsiToWmiString ( spoofSerial , mon->SerialNumberID , 16 );
                AnsiToWmiString ( g_SpoofedVendor , mon->ManufacturerName , 16 );
                AnsiToWmiString ( g_SpoofedName , mon->ProductCodeID , 16 );
 
                if ( mon->UserFriendlyNameLength > 0 )
                {
                    ULONG nameLen = min ( ( ULONG )strlen ( g_SpoofedName ) , 13UL );
                    AnsiToWmiString ( g_SpoofedName , mon->UserFriendlyName , nameLen );
                    mon->UserFriendlyNameLength = ( USHORT )nameLen;
                }
 
                logging::output ( encrypt ( "monitor %lu: after  -> serial=%s name=%s" ) , i , spoofSerial , g_SpoofedName );
            }
        }
 
        return status;
    }
 
    void spoof ( )
    {
        InitMonitorStrings ( );
        PatchRegistryEdid ( );
 
        UNICODE_STRING name;
        RtlInitUnicodeString ( &name , encrypt ( L"\\Driver\\monitor" ) );
 
        PDRIVER_OBJECT drv = nullptr;
        NTSTATUS status = ObReferenceObjectByName ( &name , OBJ_CASE_INSENSITIVE , nullptr , 0 ,
            *IoDriverObjectType , KernelMode , nullptr , ( void** )&drv );
 
        if ( !NT_SUCCESS ( status ) || !drv )
        {
            logging::output ( encrypt ( "monitor: driver not found (0x%X)" ) , status );
            return;
        }
 
        g_OriginalMonitorDispatch = drv->MajorFunction [ IRP_MJ_SYSTEM_CONTROL ];
        drv->MajorFunction [ IRP_MJ_SYSTEM_CONTROL ] = HandleMonitor;
 
        logging::output ( encrypt ( "monitor: hooked (orig: %p -> new: %p)" ) ,
            g_OriginalMonitorDispatch , HandleMonitor );
 
        ObDereferenceObject ( drv );
    }
 
}

Что по детектам?
Сам драйвер монитора — это база, но не забывайте про обфускацию (Nuitka/пайплайны для C++), если юзаете это в юзер-мод лоадере. Этот код работает на уровне ядра, так что если ваш драйвер не подписан или загружается через уязвимый маппер (типа kdmapper), следите за тем, чтобы не было конфликтов с античитом.

Если кто будет интегрировать в свой проект — проверяйте оффсеты под актуальные версии билдов винды, иногда WmiMonitorID может отличаться из-за апдейтов.

Братва, делитесь опытом, кто уже тестил этот метод под BattlEye? Есть ли прилеты по мануалу из-за того, что хук на \Driver\monitor палится по сигнатуре? Кто допиливал — кидайте свои правки или методы обхода проверок целостности реестра.
 
пока без детектов на бт но у кого лоадер на минифлтерах может ругаться лучше тестить на старых билдах
 
Назад
Сверху Снизу