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

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

Начинающий
Начинающий
Статус
Оффлайн
Регистрация
13 Фев 2026
Сообщения
130
Реакции
4
Народ, кто сейчас ковыряет свои лоадеры или пишет кастомный спуфер под античиты, обратите внимание — наткнулся на интересный подход к 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 палится по сигнатуре? Кто допиливал — кидайте свои правки или методы обхода проверок целостности реестра.
 
Назад
Сверху Снизу