Возвращаясь к старым экспериментам с пробросом портов в юзермод и выполнением произвольного кода в ядре, решил сократить издержки переключения в ядро и обратно. Текущая система такая: в IOCTL передаю в драйвер указатели на юзермодные код и аргументы, драйвер готовит окружение - отключает SMEP и SMAP, сохраняет состояние FPU и прыгает по юзермодному адресу, выполняет то, что ему передали, затем возвращает окружение обратно и возвращает управление в юзермод. Эта система видится мне слишком накладной в плане скорости работы, поэтому решил попробовать повесить исполнялку кода на незанятое прерывание вместо DeviceIoControl'a. И с добавлением обработчика возникли трудности. Код (C): // Обработчик: VOID InterruptHandler() { DbgBreakPoint(); // Сюда мы не попадаем, либо попадаем, но int 3 не обрабатывается (???) DbgPrint("Hello from Ring0"); // В консоль ничего не выводит _IRET(); // Функция-заглушка: add rsp, 8 -> iret } VOID SetupInterruptHandler() { IDTR IDTRegister; IdtGdtTrOperation(SIDT, &IDTRegister); DbgPrint("IDTR.Base = 0x%X\r\nIDTR.Limit = %d", IDTRegister.Base, IDTRegister.Limit); PIDTENTRY IDTEntry = IDTRegister.Base; // Указатель на начало IDT // Идём по таблице прерываний: ULONG Counter = 0; ULONG Offset = 0; while (Offset < IDTRegister.Limit) { // Если нашли незанятое прерывание: PVOID IdtOffset = ExtractOffsetFromIdtEntry(IDTEntry); // Собираем адрес обработчика из кусочков в нормальный вид if (IdtOffset == 0) { ZeroMemory(IDTEntry, sizeof(IDTENTRY)); IDTEntry->TargetSelector = 0x10; IDTEntry->TargetOffsetLow = (unsigned)((SIZE_T)InterruptHandler); IDTEntry->TargetOffsetMiddle = (unsigned)((SIZE_T)InterruptHandler >> 16); IDTEntry->TargetOffsetHigh = (unsigned)((SIZE_T)InterruptHandler >> 32); IDTEntry->Present = 1; IDTEntry->Type = 0x0E; // 64-Bit Interrupt Gate REGISTERS_STATE RegistersState; RegistersState.RAX = 0x1EE7C0DE; RegistersState.RCX = 0xBADC0DE; RegistersState.RDX = 0x10101010; _INT((BYTE)Counter, &RegistersState); // Пробуем вызвать наш обработчик break; } IDTEntry = (PIDTENTRY)((PBYTE)IDTEntry + sizeof(IDTENTRY)); Offset += sizeof(IDTENTRY); Counter++; } } Проверяю на 64х-битной Win10 1607, в IDT свободны два последних прерывания (0xFE и 0xFF). Вызов прерывания выбивает exception, который ловится внешним try..except'ом в обработчике IOCTL'ов, после чего процесс, пославший IOCTL, намертво зависает и не убивается ни ProcessHacker'ом, ни чем-то ещё (драйвер, соответственно, тоже выгрузиться не может). Что делаю не так?
Написал обработчик на асме, чтобы обойтись без костыльного _IRET'a, заработало: в RAX передаю адрес сишного обработчика, и из асмового обработчика по RAX'у прыгаем на код в драйвере. Но если поставить внутри обработчика int 3 (не отладчиком, а в коде) - на выходе из прерывания падаем в SYSTEM_SERVICE_EXCEPTION. Следовательно, вопрос: можно ли внутри прерываний вызывать другие прерывания? И самое главное, ради чего всё затевалось - как теперь пробросить прерывание в юзермод? Выставил в IDT DPL = 3, не работает. Что ещё нужно?
> можно ли внутри прерываний вызывать другие прерывания? Да, но int3 это не прерывание, а исключение. Необходимо находится в трап-фрейме, что бы всё ровно работало.
В юзермод всегда выполняется возврат, тоесть юзер окружение уже имеется. Тогда выгружается трап фрейм - выполняется возврат. Если же юзер контекста нет, то его нужно создать(поток) или использовать чужой - выполниться в контексте другого треда.
HoShiMin Посмотрите KDR, это лучший способ обработки ловушек. Я не давно переписал парсер его, это нужно было для фикса кривого дебаг плага для кряклаба. Но я пока не отладил даже, пока нет такой возможности. Там x86 если есть отладчик и желание, то могу пересобрать норм семпл для теста https://yadi.sk/d/rdE6cV6U33Qhn4
Пока в обработке ловушек нет необходимости. Идея была - сделать прерывание, доступное из юзермода, чтобы исполнять лежащий в юзермоде кодес, предназначенный для ядра. Грубо говоря, пишем на любом компилируемом в машинный код ЯПе юзермодную программку, в ней определяем функцию, которую хотели бы выполнить в ядре (например, что-то вытащить из MSR), затем из этой программки зовём прерывание, передав в регистрах адрес нашей функции и аргументы для неё. Обработчик уходит в ядро и оттуда прыгает на переданный адрес, выполняет с нулевыми привилегиями функцию из юзермодного пространства и возвращается обратно в юзермод. При вызове из драйвера всё работает, обработчик вызывается, всё хорошо, но если пробуем вызвать прерывание из юзермода, получаем это: Обработчик пока что никуда не прыгает (в регистрах можем передавать любой мусор), вызываю так (да простят меня гуру за дэльфи): Код (Delphi): procedure Call; asm mov rax, $1EE7C0DE mov rcx, $C0FFEE mov rdx, $10101010 int $FF end; begin Call; WriteLn('Final'); ReadLn; end. Сам обработчик: Код (ASM): proc _InternalInterruptHandler ; RAX - адрес функции; RCX, RDX - аргументы if defined x64 ;call rax else ;call eax end if leave iret endp Потому и вопрос - как пробросить это прерывание в юзермод? Похожий функционал уже сделан через DeviceIoControl (все аргументы и адреса идут в буферах), но хотелось бы повысить скорость работы и минимизировать издержки на проверки контекстов, которые делает сама система. Поэтому решил повесить тот же самый функционал на свободное прерывание. Других способов легально (без эксплойтов) выполнить юзермодный код в ядре не придумал, но если такие есть - будет интересно рассмотреть все варианты.
NoShiMin > При вызове из драйвера всё работает, обработчик вызывается, всё хорошо, но если пробуем вызвать прерывание из юзермода Если не вызывается, значит не правильно настроен дескриптор - не проходят проверки безопасности. > Других способов легально (без эксплойтов) выполнить юзермодный код в ядре не придумал Вы ничего в таком обработчике делать не сможите, придётся как то сформировать трап фрейм. Может лучше использовать системные механизмы, к примеру добавить свой сервис в SST ?
Так действительно лучше, но понятия не имею, как получить указатель на SDT. В применении к х64, простой extern PVOID KeServiceDescriptorTable говорит, что символ не найден.
Это связано с отсутствующей обработкой исключений, которая должна поддерживаться в ядерных API? Здесь у меня небольшое непонимание - у кого какой DPL должен стоять. В IDT DPL = 3, в GDT (у сегмента кода 0x10) DPL = 0. Но и обработчик лежит в ядерной памяти и ничего не знает про юзермодный код.
> Это связано с отсутствующей обработкой исключений, которая должна поддерживаться в ядерных API? Исключения штатными способами обработаны не будут, так как у вас замаскированы прерывания. Это в общем случае. В целом же нельзя обращаться к системе, сервис к примеру вызвать.
Не бессмысленный, просто вы не сможите реализовать. Есть два пути - выделить код формирования фрейма(пролог вектора) автоматикой, что вы не можите сделать, либо использовать системные механизмы. Сервисы не единственный способ, так я уже говорил про кдр. Можно есчо где то переписать указатель на свой, который будет косвенно вызываться, можно кучу примеров привести, любая ссылка на хэндлер, которая вызывается через вызов юзер механизма.