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

Гайд [Сурс] Kernel MAC Spoofer — In-place подмена в NDIS и структурах драйверов

Sloppy
Начинающий
Начинающий
Статус
Оффлайн
Регистрация
13 Фев 2026
Сообщения
617
Реакции
16
Здарова, реверсеры. Пока большинство юзает паблик-спуферы, которые просто меняют одну ветку в реестре, античиты вроде EAC и BattlEye давно научились слать OID-запросы напрямую в минипорт. Обычный софт тут пасует, поэтому выкладываю базу для нормального спуфа на уровне ядра.

В чем суть метода
Мы не полагаемся на статические оффсеты (которые залетают в детект после каждого патча винды). Вместо этого драйвер сканирует
Код:
Expand Collapse Copy
ndis.sys
и расширения устройств (DeviceExtension) целевых сетевых драйверов на наличие байтов оригинального MAC-адреса. Как только паттерн найден — делаем замену прямо в памяти. Это убивает сразу двух зайцев:
Код:
Expand Collapse Copy
OID_802_3_PERMANENT_ADDRESS
и
Код:
Expand Collapse Copy
OID_802_3_CURRENT_ADDRESS
начинают возвращать то, что нам нужно.

Логика работы:
  1. Парсим реальные MAC-адреса из веток реестра сетевых классов.
  2. Генерируем рандомные локальные MAC-адреса (unicast/locally-administered).
  3. Проходимся по списку драйверов (Intel, Realtek, Broadcom и т.д.).
  4. Скан секций памяти на паттерны оригинального MAC.
  5. In-place замена байтов в структурах
    Код:
    Expand Collapse Copy
    NDIS_MINIPORT_BLOCK
    и кастомных структурах вендоров.

  • ndis.sys / ndisuio.sys (Ядро)
  • e1iexpress / e1dexpress / e1rexpress / igc (Intel)
  • rt640x64 / rt68cx21 / rtwlane (Realtek)
  • bnxt / b57nd60a (Broadcom)
  • mlx4eth63 (Mellanox)
  • vmxnet3ndis6 / netvsc (Виртуалки)

Техническое ядро для реализации скана и подмены:
Код:
Expand Collapse Copy
//    - NDIS OID_802_3_PERMANENT_ADDRESS → reads from miniport block
//    - NDIS OID_802_3_CURRENT_ADDRESS → reads from miniport block
//    - Registry NetworkAddress → used as override at driver init
//
//  NDIS_MINIPORT_BLOCK stores CurrentAddress and PermanentAddress.
//  We don't rely on exact offsets — we scan for the original MAC bytes
//  across the entire miniport block and all NIC driver device extensions.
//
//  approach:
//    1. Read real MACs from NIC class registry keys
//    2. Generate locally-administered random MACs
//    3. Scan ndis.sys + all NIC driver device extensions for original
//       MAC bytes (both in NDIS_MINIPORT_BLOCK and hardware-specific structs)
//    4. Replace in-place (immediate effect on all OID queries)
 
constexpr ULONG MAX_NICS = 8;
 
inline UCHAR g_OMac[MAX_NICS][6] = {};
inline UCHAR g_SMac[MAX_NICS][6] = {};
inline ULONG g_NicCnt = 0;
 
// Read 6-byte MAC from a registry hex string (with or without delimiters)
inline BOOLEAN ParseMacHex(const char* str, UCHAR out[6])
{
    ULONG parsed = 0;
    for (ULONG c = 0; str[c] && parsed < 6; ) {
        while (str[c] == '-' || str[c] == ':') c++;
        if (!str[c]) break;
        UCHAR hi = 0, lo = 0;
        char ch = str[c];
        if (ch >= '0' && ch <= '9') hi = (UCHAR)(ch - '0');
        else if (ch >= 'A' && ch <= 'F') hi = (UCHAR)(ch - 'A' + 10);
        else if (ch >= 'a' && ch <= 'f') hi = (UCHAR)(ch - 'a' + 10);
        c++;
        ch = str[c];
        if (ch >= '0' && ch <= '9') lo = (UCHAR)(ch - '0');
        else if (ch >= 'A' && ch <= 'F') lo = (UCHAR)(ch - 'A' + 10);
        else if (ch >= 'a' && ch <= 'f') lo = (UCHAR)(ch - 'a' + 10);
        c++;
        out[parsed++] = (hi << 4) | lo;
    }
    // Reject zero MACs and broadcast MACs
    if (parsed != 6) return FALSE;
    BOOLEAN allZero = TRUE, allFF = TRUE;
    for (int i = 0; i < 6; i++) { if (out[i]) allZero = FALSE; if (out[i] != 0xFF) allFF = FALSE; }
    return !allZero && !allFF;
}
 
inline void CollectNicMacs()
{
    g_NicCnt = 0;
    const WCHAR* base = L"\\Registry\\Machine\\SYSTEM\\CurrentControlSet\\Control"
        L"\\Class\\{4D36E972-E325-11CE-BFC1-08002BE10318}";
 
    for (ULONG idx = 0; idx < 32 && g_NicCnt < MAX_NICS; idx++)
    {
        WCHAR instPath[300];
        RtlStringCchPrintfW(instPath, 300, L"%ws\\%04lu", base, idx);
 
        // Read DriverDesc to skip virtual NICs
        HANDLE hk = nullptr; UNICODE_STRING key; OBJECT_ATTRIBUTES oa;
        RtlInitUnicodeString(&key, instPath);
        InitializeObjectAttributes(&oa, &key, OBJ_CASE_INSENSITIVE|OBJ_KERNEL_HANDLE, nullptr, nullptr);
        if (!NT_SUCCESS(ZwOpenKey(&hk, KEY_READ, &oa))) continue;
 
        WCHAR desc[128] = {};
        UCHAR tmp[512] = {}; ULONG rlen = 0; UNICODE_STRING vn;
        RtlInitUnicodeString(&vn, L"DriverDesc");
        if (NT_SUCCESS(ZwQueryValueKey(hk, &vn, KeyValueFullInformation, tmp, sizeof(tmp), &rlen))) {
            auto info = (PKEY_VALUE_FULL_INFORMATION)tmp;
            if (info->Type == REG_SZ && info->DataLength > 0)
                RtlCopyMemory(desc, (PUCHAR)info + info->DataOffset, min(info->DataLength, sizeof(desc)-2));
        }
        ZwClose(hk);
 
        if (wcsstr(desc, L"Virtual") || wcsstr(desc, L"VPN") || wcsstr(desc, L"Loopback") ||
            wcsstr(desc, L"TAP") || wcsstr(desc, L"WAN") || wcsstr(desc, L"Tunnel") ||
            wcsstr(desc, L"Bluetooth") || desc[0] == 0)
            continue;
 
        // Read original MAC
        char macStr[32] = {};
        UCHAR origMac[6] = {};
        BOOLEAN gotMac = FALSE;
 
        // Source 1: "NetworkAddress" (current override or original)
        if (!gotMac && NT_SUCCESS(RegGetSzA(instPath, L"NetworkAddress", macStr, 32)))
            gotMac = ParseMacHex(macStr, origMac);
 
        // Source 2: "OriginalNetworkAddress"
        if (!gotMac) {
            RtlZeroMemory(macStr, sizeof(macStr));
            if (NT_SUCCESS(RegGetSzA(instPath, L"OriginalNetworkAddress", macStr, 32)))
                gotMac = ParseMacHex(macStr, origMac);
        }
 
        if (!gotMac) continue;
 
        RtlCopyMemory(g_OMac[g_NicCnt], origMac, 6);
 
        // Generate spoofed MAC (locally administered, unicast)
        for (int b = 0; b < 6; b++) g_SMac[g_NicCnt][b] = RandByte();
        g_SMac[g_NicCnt][0] &= 0xFE;  // unicast
        g_SMac[g_NicCnt][0] |= 0x02;   // locally-administered
 
        // Registry Layer: set NetworkAddress for this adapter
        WCHAR newMac[16];
        RtlStringCchPrintfW(newMac, 16, L"%02X%02X%02X%02X%02X%02X",
            g_SMac[g_NicCnt][0], g_SMac[g_NicCnt][1], g_SMac[g_NicCnt][2],
            g_SMac[g_NicCnt][3], g_SMac[g_NicCnt][4], g_SMac[g_NicCnt][5]);
        RegSetSz(instPath, L"NetworkAddress", newMac);
 
        g_NicCnt++;
    }
}
 
inline void ScanDriverNics(PCWSTR driverPath)
{
    UNICODE_STRING name; RtlInitUnicodeString(&name, driverPath);
    PDRIVER_OBJECT drv = nullptr;
    if (!NT_SUCCESS(ObReferenceObjectByName(&name, OBJ_CASE_INSENSITIVE,
        nullptr, 0, *IoDriverObjectType, KernelMode, nullptr, (PVOID*)&drv)))
        return;
    if (!drv) return;
 
    PDEVICE_OBJECT dev = drv->DeviceObject;
    while (dev)
    {
        if (!MmIsAddressValid(dev)) break;
        if (dev->DeviceExtension && MmIsAddressValid(dev->DeviceExtension))
        {
            for (ULONG n = 0; n < g_NicCnt; n++)
            {
                // Replace 6-byte MAC pattern in extension memory
                // Covers CurrentAddress, PermanentAddress, and any cached copies
                MemReplace(dev->DeviceExtension, 0x4000,
                    g_OMac[n], 6, g_SMac[n], 6, '\0');
            }
        }
 
        // Walk lower device stack too
        PDEVICE_OBJECT lower = IoGetLowerDeviceObject(dev);
        ULONG depth = 0;
        while (lower && depth < 10) {
            if (!MmIsAddressValid(lower)) break;
            if (lower->DeviceExtension && MmIsAddressValid(lower->DeviceExtension)) {
                for (ULONG n = 0; n < g_NicCnt; n++)
                    MemReplace(lower->DeviceExtension, 0x4000,
                        g_OMac[n], 6, g_SMac[n], 6, '\0');
            }
            PDEVICE_OBJECT next = IoGetLowerDeviceObject(lower);
            ObDereferenceObject(lower);
            lower = next; depth++;
        }
        if (lower) ObDereferenceObject(lower);
 
        dev = dev->NextDevice;
    }
    ObDereferenceObject(drv);
}
 
inline void SpoofNics()
{
    CollectNicMacs();
    if (!g_NicCnt) return;
 
 
    const WCHAR* drivers[] = {
        L"\\Driver\\ndis",          // NDIS core — NDIS_MINIPORT_BLOCK structures
        L"\\Driver\\ndisuio",       // NDIS user I/O
        L"\\Driver\\e1iexpress",    // Intel 1GbE
        L"\\Driver\\e1dexpress",    // Intel desktop NIC
        L"\\Driver\\e1rexpress",    // Intel server NIC
        L"\\Driver\\igc",           // Intel I225/I226
        L"\\Driver\\rt640x64",      // Realtek RTL8111/8168
        L"\\Driver\\rt68cx21",      // Realtek 2.5GbE
        L"\\Driver\\rtwlane",       // Realtek WiFi
        L"\\Driver\\bnxt",          // Broadcom NetXtreme
        L"\\Driver\\b57nd60a",      // Broadcom 57xx
        L"\\Driver\\mlx4eth63",     // Mellanox
        L"\\Driver\\vmxnet3ndis6",  // VMware (for dev/testing)
        L"\\Driver\\netvsc",        // Hyper-V
    };
    for (ULONG i = 0; i < ARRAYSIZE(drivers); i++)
        ScanDriverNics(drivers[i]);
}

Нюансы и траблшутинг
Метод жесткий, но требует аккуратности. Если криво пропатчите память — словите BSOD с кодом
Код:
Expand Collapse Copy
KMODE_EXCEPTION_NOT_HANDLED
Перед использованием на основе убедитесь, что выключили Secure Boot и нормально скрыли сам драйвер-маппер, иначе прилетит по голове не за MAC, а за наличие неподписанного кода в системе. Сурс — отличная база для тех, кто пишет свой приватный спуфер и хочет закрыть вопрос с сетевыми картами раз и навсегда.

Кто будет допиливать под DMA — обратите внимание на типы памяти, иногда девайсы кэшируют мак в своих регистрах, там логика чуть сложнее.
 
Назад
Сверху Снизу