Добавить вектор прерывания в Windows x64

Тема в разделе "WASM.BEGINNERS", создана пользователем HoShiMin, 17 дек 2016.

  1. HoShiMin

    HoShiMin Well-Known Member

    Публикаций:
    5
    Регистрация:
    17 дек 2016
    Сообщения:
    1.477
    Адрес:
    Россия, Нижний Новгород
    Возвращаясь к старым экспериментам с пробросом портов в юзермод и выполнением произвольного кода в ядре, решил сократить издержки переключения в ядро и обратно.

    Текущая система такая: в IOCTL передаю в драйвер указатели на юзермодные код и аргументы, драйвер готовит окружение - отключает SMEP и SMAP, сохраняет состояние FPU и прыгает по юзермодному адресу, выполняет то, что ему передали, затем возвращает окружение обратно и возвращает управление в юзермод.

    Эта система видится мне слишком накладной в плане скорости работы, поэтому решил попробовать повесить исполнялку кода на незанятое прерывание вместо DeviceIoControl'a. И с добавлением обработчика возникли трудности.

    Код (C):
    1.  
    2. // Обработчик:
    3. VOID InterruptHandler() {
    4.     DbgBreakPoint(); // Сюда мы не попадаем, либо попадаем, но int 3 не обрабатывается (???)
    5.     DbgPrint("Hello from Ring0"); // В консоль ничего не выводит
    6.     _IRET(); // Функция-заглушка: add rsp, 8 -> iret
    7. }
    8.  
    9. VOID SetupInterruptHandler() {
    10.     IDTR IDTRegister;
    11.     IdtGdtTrOperation(SIDT, &IDTRegister);
    12.  
    13.     DbgPrint("IDTR.Base  = 0x%X\r\nIDTR.Limit = %d", IDTRegister.Base, IDTRegister.Limit);
    14.    
    15.     PIDTENTRY IDTEntry = IDTRegister.Base; // Указатель на начало IDT
    16.  
    17.     // Идём по таблице прерываний:
    18.     ULONG Counter = 0;
    19.     ULONG Offset = 0;
    20.     while (Offset < IDTRegister.Limit) {
    21.         // Если нашли незанятое прерывание:
    22.         PVOID IdtOffset = ExtractOffsetFromIdtEntry(IDTEntry); // Собираем адрес обработчика из кусочков в нормальный вид
    23.         if (IdtOffset == 0) {
    24.             ZeroMemory(IDTEntry, sizeof(IDTENTRY));
    25.             IDTEntry->TargetSelector        = 0x10;
    26.             IDTEntry->TargetOffsetLow       = (unsigned)((SIZE_T)InterruptHandler);
    27.             IDTEntry->TargetOffsetMiddle    = (unsigned)((SIZE_T)InterruptHandler >> 16);
    28.             IDTEntry->TargetOffsetHigh      = (unsigned)((SIZE_T)InterruptHandler >> 32);
    29.             IDTEntry->Present = 1;
    30.             IDTEntry->Type = 0x0E; // 64-Bit Interrupt Gate
    31.  
    32.             REGISTERS_STATE RegistersState;
    33.             RegistersState.RAX = 0x1EE7C0DE;
    34.             RegistersState.RCX = 0xBADC0DE;
    35.             RegistersState.RDX = 0x10101010;
    36.             _INT((BYTE)Counter, &RegistersState); // Пробуем вызвать наш обработчик
    37.  
    38.             break;
    39.         }
    40.  
    41.         IDTEntry = (PIDTENTRY)((PBYTE)IDTEntry + sizeof(IDTENTRY));
    42.         Offset += sizeof(IDTENTRY);
    43.         Counter++;
    44.     }
    45. }
    46.  
    Проверяю на 64х-битной Win10 1607, в IDT свободны два последних прерывания (0xFE и 0xFF). Вызов прерывания выбивает exception, который ловится внешним try..except'ом в обработчике IOCTL'ов, после чего процесс, пославший IOCTL, намертво зависает и не убивается ни ProcessHacker'ом, ни чем-то ещё (драйвер, соответственно, тоже выгрузиться не может).

    Что делаю не так?
     
  2. HoShiMin

    HoShiMin Well-Known Member

    Публикаций:
    5
    Регистрация:
    17 дек 2016
    Сообщения:
    1.477
    Адрес:
    Россия, Нижний Новгород
    Написал обработчик на асме, чтобы обойтись без костыльного _IRET'a, заработало: в RAX передаю адрес сишного обработчика, и из асмового обработчика по RAX'у прыгаем на код в драйвере. Но если поставить внутри обработчика int 3 (не отладчиком, а в коде) - на выходе из прерывания падаем в SYSTEM_SERVICE_EXCEPTION.

    Следовательно, вопрос: можно ли внутри прерываний вызывать другие прерывания?
    И самое главное, ради чего всё затевалось - как теперь пробросить прерывание в юзермод? Выставил в IDT DPL = 3, не работает. Что ещё нужно?
     
  3. Indy_

    Indy_ Well-Known Member

    Публикаций:
    4
    Регистрация:
    29 апр 2011
    Сообщения:
    4.775
    > можно ли внутри прерываний вызывать другие прерывания?

    Да, но int3 это не прерывание, а исключение. Необходимо находится в трап-фрейме, что бы всё ровно работало.
     
  4. HoShiMin

    HoShiMin Well-Known Member

    Публикаций:
    5
    Регистрация:
    17 дек 2016
    Сообщения:
    1.477
    Адрес:
    Россия, Нижний Новгород
    Хм, ладно, благодарю. А как вытащить прерывание в юзермод, не подскажете?
     
  5. Indy_

    Indy_ Well-Known Member

    Публикаций:
    4
    Регистрация:
    29 апр 2011
    Сообщения:
    4.775
    В юзермод всегда выполняется возврат, тоесть юзер окружение уже имеется. Тогда выгружается трап фрейм - выполняется возврат. Если же юзер контекста нет, то его нужно создать(поток) или использовать чужой - выполниться в контексте другого треда.
     
  6. Indy_

    Indy_ Well-Known Member

    Публикаций:
    4
    Регистрация:
    29 апр 2011
    Сообщения:
    4.775
    HoShiMin

    Посмотрите KDR, это лучший способ обработки ловушек. Я не давно переписал парсер его, это нужно было для фикса кривого дебаг плага для кряклаба. Но я пока не отладил даже, пока нет такой возможности. Там x86 если есть отладчик и желание, то могу пересобрать норм семпл для теста https://yadi.sk/d/rdE6cV6U33Qhn4
     
  7. HoShiMin

    HoShiMin Well-Known Member

    Публикаций:
    5
    Регистрация:
    17 дек 2016
    Сообщения:
    1.477
    Адрес:
    Россия, Нижний Новгород
    Пока в обработке ловушек нет необходимости. Идея была - сделать прерывание, доступное из юзермода, чтобы исполнять лежащий в юзермоде кодес, предназначенный для ядра. Грубо говоря, пишем на любом компилируемом в машинный код ЯПе юзермодную программку, в ней определяем функцию, которую хотели бы выполнить в ядре (например, что-то вытащить из MSR), затем из этой программки зовём прерывание, передав в регистрах адрес нашей функции и аргументы для неё. Обработчик уходит в ядро и оттуда прыгает на переданный адрес, выполняет с нулевыми привилегиями функцию из юзермодного пространства и возвращается обратно в юзермод.

    При вызове из драйвера всё работает, обработчик вызывается, всё хорошо, но если пробуем вызвать прерывание из юзермода, получаем это:
    20-12-2016 13-07-57.png

    Обработчик пока что никуда не прыгает (в регистрах можем передавать любой мусор), вызываю так (да простят меня гуру за дэльфи):
    Код (Delphi):
    1.  
    2. procedure Call;
    3. asm
    4.   mov rax, $1EE7C0DE
    5.   mov rcx, $C0FFEE
    6.   mov rdx, $10101010
    7.   int $FF
    8. end;
    9.  
    10. begin
    11.   Call;
    12.   WriteLn('Final');
    13.   ReadLn;
    14. end.
    15.  
    Сам обработчик:
    Код (ASM):
    1.  
    2. proc _InternalInterruptHandler ; RAX - адрес функции; RCX, RDX - аргументы
    3.   if defined x64
    4.     ;call rax
    5.   else
    6.     ;call eax
    7.   end if
    8.   leave
    9.   iret
    10. endp
    11.  
    Потому и вопрос - как пробросить это прерывание в юзермод?

    Похожий функционал уже сделан через DeviceIoControl (все аргументы и адреса идут в буферах), но хотелось бы повысить скорость работы и минимизировать издержки на проверки контекстов, которые делает сама система. Поэтому решил повесить тот же самый функционал на свободное прерывание. Других способов легально (без эксплойтов) выполнить юзермодный код в ядре не придумал, но если такие есть - будет интересно рассмотреть все варианты.
     
    Последнее редактирование: 20 дек 2016
  8. Indy_

    Indy_ Well-Known Member

    Публикаций:
    4
    Регистрация:
    29 апр 2011
    Сообщения:
    4.775
    NoShiMin

    > При вызове из драйвера всё работает, обработчик вызывается, всё хорошо, но если пробуем вызвать прерывание из юзермода

    Если не вызывается, значит не правильно настроен дескриптор - не проходят проверки безопасности.

    > Других способов легально (без эксплойтов) выполнить юзермодный код в ядре не придумал

    Вы ничего в таком обработчике делать не сможите, придётся как то сформировать трап фрейм. Может лучше использовать системные механизмы, к примеру добавить свой сервис в SST ?
     
  9. HoShiMin

    HoShiMin Well-Known Member

    Публикаций:
    5
    Регистрация:
    17 дек 2016
    Сообщения:
    1.477
    Адрес:
    Россия, Нижний Новгород
    Так действительно лучше, но понятия не имею, как получить указатель на SDT. В применении к х64, простой extern PVOID KeServiceDescriptorTable говорит, что символ не найден.
     
  10. HoShiMin

    HoShiMin Well-Known Member

    Публикаций:
    5
    Регистрация:
    17 дек 2016
    Сообщения:
    1.477
    Адрес:
    Россия, Нижний Новгород
    Это связано с отсутствующей обработкой исключений, которая должна поддерживаться в ядерных API?
    Здесь у меня небольшое непонимание - у кого какой DPL должен стоять. В IDT DPL = 3, в GDT (у сегмента кода 0x10) DPL = 0. Но и обработчик лежит в ядерной памяти и ничего не знает про юзермодный код.
     
  11. Indy_

    Indy_ Well-Known Member

    Публикаций:
    4
    Регистрация:
    29 апр 2011
    Сообщения:
    4.775
    > Это связано с отсутствующей обработкой исключений, которая должна поддерживаться в ядерных API?

    Исключения штатными способами обработаны не будут, так как у вас замаскированы прерывания. Это в общем случае. В целом же нельзя обращаться к системе, сервис к примеру вызвать.
     
  12. HoShiMin

    HoShiMin Well-Known Member

    Публикаций:
    5
    Регистрация:
    17 дек 2016
    Сообщения:
    1.477
    Адрес:
    Россия, Нижний Новгород
    Хм, да, похоже, что способ с прерываниями действительно бессмысленный. Остаётся разбирать SST.
     
  13. Indy_

    Indy_ Well-Known Member

    Публикаций:
    4
    Регистрация:
    29 апр 2011
    Сообщения:
    4.775
    Не бессмысленный, просто вы не сможите реализовать. Есть два пути - выделить код формирования фрейма(пролог вектора) автоматикой, что вы не можите сделать, либо использовать системные механизмы. Сервисы не единственный способ, так я уже говорил про кдр. Можно есчо где то переписать указатель на свой, который будет косвенно вызываться, можно кучу примеров привести, любая ссылка на хэндлер, которая вызывается через вызов юзер механизма.