Секьюрити-ресёрч драйвера AmdTools64.sys ("AMD Tools Driver" 1.7.16.219) —
AMD-подписанного кернел-драйвера, которого нет ни в одном публичном
дистрибутиве AMD. Драйвер наружу торит богатым набором METHOD_BUFFERED
IOCTL-ов, через которые можно из юзермода мапить, читать и писать произвольную
физическую память с attacker-controlled адресом. Итог — полностью рабочий
BYOVD-примитив ("bring your own vulnerable driver").
В рекомендованном Microsoft блоклисте уязвимых драйверов - семпла пока нет.
| Поле | Значение |
|---|---|
| Файл | AmdTools64.sys |
| Версия | 1.7.16.219 |
| Оригинальное имя | amdtools64.sys |
| SHA-256 | 891007BC9F3B55AE168FA60CCBB831CCDB5DCE4DA4A591D000806D5A2C6115C3 |
| Подпись | "Advanced Micro Devices Inc." (Sectigo RSA Code Signing CA, кросс-сертификат через Microsoft Code Verification Root) |
| Таймстамп сборки | 2022-06-22 (PE-заголовок) |
| PDB-путь | C:\Project\AmdToolsDriver\src\AMD Special Tools Driver\x64\Release\amdtools64.pdb |
Драйвер — PnP-шный. В AddDevice он создаёт девайс через
IoCreateDeviceSecure с SDDL D:P(A;;GA;;;SY)(A;;GA;;;BA) (доступ только у
SYSTEM и админов) и регистрирует device-интерфейс с GUID
{1232175B-1C34-41FD-B101-342D47B828AC}. То есть для загрузки драйвера хватает
прав админа, а дальше перечисленные ниже IOCTL-ы сносят границу
админ→кернел в щепки.
Структура запроса (METHOD_BUFFERED, вход и выход >= 0x21 байт):
struct MmapRequest {
UINT64 PhysicalAddress; // +0x00 in: физический базовый адрес (никем не проверяется)
UINT32 Length; // +0x08 in: размер в байтах (тоже без проверок)
UINT32 Status; // +0x0C out: статус драйвера
PVOID PoolCtx; // +0x10 out: аллокация на стороне драйвера (отдать в 0xFFF028A8 для анмапа)
PVOID UserVa; // +0x18 out: юзермод-маппинг этой физической памяти
UINT8 CacheType; // +0x20 in: 0 = NonCached, 1 = Cached, 2 = WriteCombined
};Хендлер делает MmMapIoSpace → IoAllocateMdl →
MmBuildMdlForNonPagedPool → MmMapLockedPagesSpecifyCache(Mdl, UserMode, ...)
и возвращает получившийся юзермод-адрес. Маппинг читается и пишется, так
что один вызов = полный read/write по ядру. 0xFFF028A8 освобождает ранее
выданный маппинг (PoolCtx).
У обоих одинаковый запрос на 0x10C байт (вход и выход >= 0x10C):
struct PhysBlockRequest {
UINT64 PhysicalAddress; // +0x00 in: 64-битный физический адрес (без валидации)
UINT16 Width; // +0x08 in: сколько байт, 1..0x100
UINT16 Status; // +0x0A out: 0 = ок, 5 = MmMapIoSpace обломался, 6 = кривой width
UINT8 Data[0x100]; // +0x0C in (запись) / out (чтение)
};0xFFF0281C(хендлер0x1400026e8):MmMapIoSpace→ скопироватьWidthбайт из замапленного вData→MmUnmapIoSpace— произвольное чтение.0xFFF02820(хендлер0x140003050):MmMapIoSpace→ скопироватьWidthбайт изDataв замапленное →MmUnmapIoSpace— произвольная запись.
MmMapIoSpace вызывается с MmCached; учтите, что MmMapIoSpace(0, ...)
фейлится, так что физический адрес 0 — единственный «неюзабельный».
| IOCTL | Размер | Что делает |
|---|---|---|
0xFFF02888 |
0x20 | MmMapIoSpace(Phys, Len) → копия в пул → мап пула в юзермод; возвращает PoolCtx + UserVa. Len вообще без проверок. 0xFFF0288C освобождает. |
0xFFF02824 |
0x2122 | Поиск паттерна по физпамяти: мапит [Base, Base+WindowSize), тыкается с шагом Stride через RtlCompareMemory против Pattern (ChunkSize <= 0x100), при мэтче копирует до ReadLength (<= 0x2000) байт назад и возвращает адрес совпадения. |
| IOCTL | Размер | Что делает |
|---|---|---|
0xFFF02894 |
0xC | Запись в регистр SMN (AMD System Management Network): база из MSR 0xC0010058, замаскированная 0xFFFFFFF00000, плюс полностью юзер-контролируемый офсет; byte/word/dword (WRITE_REGISTER_*) из запроса. |
0xFFF02890 |
0xC | Чтение SMN-регистра (та же адресация). |
0xFFF02884 |
8 | Запись в регистр APIC: мапит базу APIC из MSR 0x1B (при необходимости сначала включая APIC через WRMSR), офсет WORD[req] < 0x20, значение DWORD[req+4], возвращает старое значение. |
0xFFF02880 |
8 | Чтение регистра APIC (та же адресация). |
0xFFF02830 |
0xA | Запись в PCI configuration space через HalSetBusDataByOffset: {Bus, Func, Dev, Width(1/2/4), Offset, Status, Value}. В связке с BAR-маппингом ниже это даёт непрямой произвольный физический маппинг (переписали BAR → замапили его). |
0xFFF0282C |
0xA | Чтение PCI configuration space через HalGetBusDataByOffset. |
0xFFF02834 / 0xFFF0283C |
0x16 | Читают BAR произвольного PCI-девайса (Bus/Dev/Func/Offset) и мапят его в юзермод; 0xFFF02840 / 0xFFF02838 — парные анмапы. |
| IOCTL | Размер | Что делает |
|---|---|---|
0xFFF028AC |
0x38 | MmAllocateContiguousMemorySpecifyCache + MDL + юзермод-маппинг аллокации; 0xFFF028B0 освобождает. |
0xFFF02868 / 0xFFF0289C |
0x2E | MmAllocatePagesForMdl (диапазон физики задаёт атакующий) + юзермод-маппинг; 0xFFF0286C / 0xFFF028A0 освобождают. |
Один диспетчер на 0x140005968; 0xFFF028C0 переключает на
батч/command-list процессор. Адреса хендлеров — из билда 1.7.16.219.
| IOCTL | Хендлер | Мин. in/out | Функция |
|---|---|---|---|
0xFFF02800 |
инлайн | 4 | вернуть версию 0x010710DA |
0xFFF02804 |
0x1400027BC |
0x14 | команда SMU/MP1 |
0xFFF02808 |
0x140003124 |
0x14 | команда SMU/MP1 |
0xFFF0280C |
0x140002BD0 |
— | команда SMU/MP1 |
0xFFF02810 |
0x140002BD0 |
— | команда SMU/MP1 |
0xFFF02814 |
0x140002990 |
8 | команда SMU/MP1 |
0xFFF02818 |
0x1400032F0 |
8 | команда SMU/MP1 |
0xFFF0281C |
0x1400026E8 |
0x10C | чтение физпамяти (<= 0x100 Б) |
0xFFF02820 |
0x140003050 |
0x10C | запись в физпамять (<= 0x100 Б) |
0xFFF02824 |
0x140002A38 |
0x2122 | поиск паттерна по физике + блочное чтение |
0xFFF02828 |
0x140001A44 |
8 | команда SMU/MP1 |
0xFFF0282C |
0x140002818 |
0xA | чтение PCI config |
0xFFF02830 |
0x140003188 |
0xA | запись PCI config |
0xFFF02834 |
0x1400022FC |
0x16 | мап PCI BAR в юзермод |
0xFFF02838 |
0x140002D70 |
0x16 | анмап |
0xFFF0283C |
0x140001CAC |
0x16 | мап PCI BAR (через копию в пул) |
0xFFF02840 |
0x140002C34 |
0x16 | анмап |
0xFFF02844 |
0x140002504 |
6 | команда SMU/MP1 |
0xFFF02848 |
0x1400029C0 |
3 | команда SMU/MP1 |
0xFFF0284C |
0x140003320 |
3 | команда SMU/MP1 |
0xFFF02850 |
0x14000269C |
2 | команда SMU/MP1 |
0xFFF02854 |
0x140003000 |
2 | команда SMU/MP1 |
0xFFF02858 |
0x140001C94 |
1 | команда SMU/MP1 |
0xFFF0285C |
0x140006504 |
8 | команда SMU/MP1 |
0xFFF02860 |
0x14000261C |
0xC | команда SMU/MP1 |
0xFFF02864 |
0x140002EF0 |
0xC | команда SMU/MP1 |
0xFFF02868 |
0x14000187C |
0x2E | MmAllocatePagesForMdl + мап в юзермод |
0xFFF0286C |
0x140001BC0 |
0x2E | освобождение |
0xFFF02870 |
0x140002668 |
0xC | команда SMU/MP1 |
0xFFF02874 |
0x140002F78 |
0xC | команда SMU/MP1 |
0xFFF0287C |
0x140002DF8 |
— | команда SMU/MP1 |
0xFFF02880 |
0x140002534 |
8 | чтение регистра APIC |
0xFFF02884 |
0x140002E00 |
8 | запись регистра APIC |
0xFFF02888 |
0x140001EF0 |
0x20 | снапшот диапазона физики в юзермод |
0xFFF0288C |
0x140002CA8 |
0x20 | освобождение снапшота |
0xFFF02890 |
0x1400028B0 |
0xC | чтение SMN-регистра |
0xFFF02894 |
0x140003210 |
0xC | запись SMN-регистра |
0xFFF02898 |
0x140001C44 |
0xC | команда SMU/MP1 |
0xFFF0289C |
0x140001960 |
0x2E | MmAllocatePagesForMdl + мап в юзермод |
0xFFF028A0 |
0x140001BC0 |
0x2E | освобождение |
0xFFF028A4 |
0x1400020E4 |
0x21 | мап произвольной физической памяти (RW) |
0xFFF028A8 |
0x140002D1C |
0x21 | анмап |
0xFFF028AC |
0x140001668 |
0x38 | аллокация contiguous memory + мап в юзермод |
0xFFF028B0 |
0x140001B30 |
0x38 | освобождение |
0xFFF028B8 |
0x140001C28 |
8 | команда SMU/MP1 |
0xFFF028BC |
0x140002BB0 |
0x10 | команда SMU/MP1 |
0xFFF028C0 |
0x1400057E0 |
— | батч/command-list процессор |
0xFFF028C4 |
0x140002BD0 |
2/0x78 | команда SMU/MP1 |
0xFFF02900 |
0x140001368 |
0x118/0x14 | форвард запрос в другой девайс |
0xFFF02940 |
0x140002A14 |
0x18 | форвард запрос в другой девайс |
Пункты с пометкой «команда SMU/MP1» прокидывают мелкие фиксированные
пейлоады в mailbox AMD SMU — глубоко их не аудировал; некоторые из них
тоже заканчиваются доступом через MmMapIoSpace.
Драйвер создаёт root-enumerated PnP-девноду, открывает
device-интерфейс, мапит физику 0x53000000 (Length = 0x200000,
MmNonCached) в юзермод и дампит первые 64 байта.
cl /EHsc /W4 poc_amdtools64.cpp cfgmgr32.lib (MSVC x64)
poc_amdtools64.exe C:\absolute\path\to\AmdTools64.sys
Сквозная и при этом crash-safe демонстрация произвольной записи:
- ставит драйвер и биндит его к девноде (см. «Загрузка драйвера» ниже);
- аллоцирует AWE-страницу (
AllocateUserPhysicalPages, нуженSeLockMemoryPrivilege) — физический фрейм, принадлежащий процессу, так что тест гарантированно не покорраптит чужой кернел/девайс-памяти; - кидает в фрейм случайный 32-байтный маркер через свой юзермод-маппинг и
проверяет, что read-примитив драйвера (
0xFFF0281C) видит его по адресуPFN << 12; - пишет пейлоад
"AmdTools64 0xFFF02820 phys write"поphys + 0x800через0xFFF02820и показывает, что байты появились в собственном виртуальном адресе процесса; - бонусом дергает
Width == 4(DWORD) путь хендлера; - останавливает и удаляет сервис.
x86_64-w64-mingw32-g++ -O2 -static -o poc_amdtools64_write.exe \
poc_amdtools64_write.cpp -lcfgmgr32 -lnewdev -lpsapi
poc_amdtools64_write.exe C:\absolute\path\to\AmdTools64.sys
Проверенный вывод (Windows 10 21H2 x64, KVM/QEMU VM):
[+] AWE frame mapped: PFN 0x1B1BAC -> physical 0x1B1BAC000
[+] read-back via 0xFFF0281C matches the marker
[*] writing 32 bytes via IOCTL 0xFFF02820 to physical 0x1B1BAC800...
[+] driver accepted the write (status = 0)
[+] PROOF: bytes appeared in OUR user-mode page @0000000001fb0800:
AmdTools64 0xFFF02820 phys write
[+] DWORD write path verified (0x443342341 @phys 0x1B1BAC810)
Драйвер — чисто PnP-шный (девайс создаётся только в AddDevice), а современная
Windows отказывается биндить голую root-девноду к сервису без драйвер-пакета
(«No compatible drivers found»; UpdateDriverForPlugAndPlayDevices фейлится с
0xE000022F, потому что у пакета нет каталога). PoC использует технику
INF-сквоттинга, которая работает без единого своего подписанного пакета:
- создаём сервис (
CreateServiceW); - создаём root-девноду
ROOT\AMDTOOLS64\0000(CM_Create_DevNodeW); - вешаем на него hardware ID, совпадающий с уже staged драйвер-пакетом —
например
ACPI\QEMU0001из virtiopvpanic.inf(CM_Add_IDW) — и дергаемCM_Setup_DevNode: PnP ставит тот пакет и создаёт class node; - переписываем значение
Service(и вEnum\ROOT\AMDTOOLS64\0000, и вControl\Class\{...}\00NN) наAmdTools64; - disable/enable девайса — PnP грузит уже
AmdTools64.sys, иAddDeviceрегистрирует device-интерфейс.
Нужны админские права — стандартная BYOVD-модель угрозы. Для
AWE-верификации в poc_amdtools64_write.cpp аккаунту дополнительно нужно
право SeLockMemoryPrivilege («Блокировка страниц в памяти»).
MmMapIoSpace(0, ...)фейлится, а диапазоны, накрывающие не-RAM дыры (VGA-диапазон, MMIO, за верхом RAM), отваливаются сSTATUS_NO_MEMORY(0xC0000017) — актуально, когда скриптуешь read/search IOCTL-ы.MmMapIoSpaceсMmCachedфейлится на диапазонах где-то между 64 КиБ и 1 МиБ (поиск через0xFFF02824проверенно работает с окнами 64 КиБ и падает с окнами 1 МиБ); путьMmNonCachedу0xFFF02888тянет как минимум 64 МиБ.- Маппинг, который
0xFFF028A4отдаёт в юзермод, — writable, так что примитив «чтения» по факту является read/write-примитивом.
| Файл | Описание |
|---|---|
AmdTools64.sys |
семпл уязвимого драйвера (AMD-подписанный) |
poc_amdtools64.cpp |
read-PoC через 0xFFF028A4 (мап физики → юзермод) |
poc_amdtools64_write.cpp |
write-PoC через 0xFFF02820 (AWE-верификация, обкатано на VM) |
Зарепорчено в AMD PSIRT в рамках coordinated disclosure. На момент написания инфа не паблилась. Не используйте этот код против систем, которыми не владеете или на которых нет явного разрешения на тестирование.