uawikipc.ru

Диспетчеризація системних служб

На процесорах x86, що передують процесорам Pentium II, Windows використовує інструкцію 0x2e (46 в десятковому вираженні), яка призводить до системного переривання. Windows заповнює запис 46 в IDT покажчиком на диспетчер системних служб.

Відео: 7 Системні служби

Системне переривання змушує виконується потік перейти в режим ядра і увійти в диспетчер системних служб. У регістр процесора EAX передається числовий аргумент, який показує номер запитуваної системної служби. Регістр EDX вказує на список параметрів, який викликає програма передає системної службі. Для повернення в призначений для користувача режим диспетчер системної служби використовує інструкцію iret (interrupt return - повернення з переривання).

На процесорі x86 Pentium II і вище Windows використовує інструкцію sysenter, яку компанія Intel визначила спеціально для швидкодіючих диспетчерів системних служб. Для підтримки інструкції Windows зберігає під час завантаження адреса процедури диспетчера системних служб, що знаходиться в ядрі, в машинно-залежний регістр (machine-specific register, MSR), пов`язаний з інструкцією.

Виконання інструкції призводить до переходу в режим ядра і до виконання коду диспетчера системних служб. Номер системної служби передається в регістр процесора EAX, а регістр EDX вказує на список аргументів викликає програми. Для повернення в призначений для користувача режим диспетчер системних служб зазвичай виконує інструкцію sysexit.

У деяких випадках, наприклад, при встановленому на процесорі прапорі покрокового виконання, диспетчер системних служб використовує замість неї інструкцію iret, тому що sysexit не дозволяє повертатися в призначений для користувача режим зі зміненим регістром EFLAGS, що необхідно в тому випадку, якщо інструкція sysenter була виконана, коли прапор trap був встановлений в результаті трасування, що проводиться отладчиком в призначеному для користувача режимі або при пропуску системного виклику.

ПРИМІТКА. Оскільки деякі старі програми могли бути жорстко запрограмовані на використання інструкції int 0x2e для самостійного використання системного виклику (непідтримуваної операції), 32-розрядної версії Windows зберігає цей механізм готовим до використання на системах, що підтримують інструкцію sysenter, як і раніше реєструючи обробник.

В архітектурі x64 Windows використовує інструкцію syscall, передаючи номер системного виклику в регістрі EAX, і будь-які параметри, поза тими чотирьох, в стеку.

Відео: Збільшуємо швидкість в iOS 7 і моя ненависть до ДАІ !!!

В архітектурі IA64 Windows використовує інструкцію epc (привілейований режим введення - EnterPrivilegedMode). Перші 8 аргументів системного виклику передаються в регістрах, а решта 8 передаються в стеці.

Позиціонування диспетчера системних служб

Як вже зазначалось, виклики 32-розрядної системи здійснюються через переривання, з чого випливає, що обробник повинен бути зареєстрований в IDT або через спеціальну інструкцію sysenter, яка під час завантаження використовує для збереження адреси обробника регістр MSR. На деяких 32-розрядних системах AMD Windows використовує замість неї інструкцію syscall, яка схожа на 64-розрядну інструкцію syscall. Визначити місце розташування відповідної процедури для будь-якого методу можна наступним чином:

  • Для перегляду обробника на 32-розрядних системах з версією системного виклику диспетчера за допомогою переривання 2E потрібно набрати в отладчике ядра команду! Idt 2e.

lkd>! idt 2e

Dumping IDT:

2e: 8208c8ee nt! KiSystemService

  • Для перегляду обробника на системах з версією sysenter потрібно скористатися командою відладчика rdmsr для читання даних з MSR-регістра 0x176, в якому зберігається адреса обробника:

lkd> rdmsr 176

msr [176] = 00000000`8208c9c0

lkd> ln 00000000`8208c9c0

(8208c9c0) nt! KiFastCallEntry

При використанні 64-розрядної машини можна подивитися на 64-розрядний диспетчер виклику служб, повторюючи цей крок, але використовуючи замість колишнього MSR-регістр 0xC0000082, який використовується для виклику версії syscall для 64-розрядного коду. Ви побачите, що він відповідає

nt! KiSystemCall64:

lkd> rdmsr c0000082

msr [c0000082] = fffff800`01a71ec0

lkd> ln fffff800`01a71ec0

(Fffff800`01a71ec0) nt! KiSystemCall64

  • Можна аналізувати код процедуру KiSystemService або процедуру KiSystemCall64 за допомогою команди u. У підсумку на 32-розрядної системі ви помітите такі інструкції:

nt! KiSystemService + 0x7b:

8208c969 897d04 mov dword ptr [ebp + 4], edi

8208c96c fb sti

8208c96d e9dd000000 jmp nt! KiFastCallEntry + 0x8f (8208ca4f)

Оскільки реальні операції диспетчеризації системних викликів є загальними, незалежно від механізму, використовуваного для виходу на обробник, старий обробник, заснований на застосуванні переривання, для виконання тих же загальних завдань просто викликається в середині новішого обробника, заснованого на застосуванні інструкції sysenter.

Єдині відрізняються частини оброблювачів пов`язані з генерацією фрейма системного переривання і установкою значень конкретних регістрів.

Під час завантаження 32-розрядна Windows визначає тип процесора, на якому вона виконується, і встановлює відповідний використовуваний код системного виклику шляхом збереження покажчика на правильний код в структурі SharedUserData. Код системної служби для NtReadFile в призначеному для користувача режимі має наступний вигляд:

0: 000> u ntdll! NtReadFile



ntdll! ZwReadFile:

77020074 b802010000 mov eax, 102h

77020079 ba0003fe7f mov edx, offset SharedUserData! SystemCallStub

(7ffe0300)

7702007e ff12 call dword ptr [edx]

77020080 c22400 ret 24h

77020083 90 nop

Номер системної служби - 0x102 (258 в десятковому форматі), а інструкція call виконує встановлений ядром код диспетчера системної служби, чий покажчик знаходиться за адресою 0x7ffe0300. (Це відповідає елементу SystemCallStub структури KUSER_SHARED_DATA, який починається з адреси 0x7FFE0000.)

Оскільки наступний висновок узятий з Intel Core 2 Duo, він містить покажчик на sysenter:

0: 000> dd SharedUserData! SystemCallStub l 1

7ffe0300 77020f30

0: 000> u 77020f30

ntdll! KiFastSystemCall:

77020f30 8bd4 mov edx, esp

77020f32 0f34 sysenter

Так як у 64-розрядних систем є тільки один механізм для здійснення системних викликів, точки входу системної служби в Ntdll.dll, як показано тут, безпосередньо використовують інструкцію syscall:

ntdll! NtReadFile:

00000000`77f9fc60 4c8bd1 mov r10, rcx

00000000`77f9fc63 b810200000 mov eax, 0x102

00000000`77f9fc68 0f05 syscall

00000000`77f9fc6a c3 ret

Kernel-Mode System Service Dispatching

Для виявлення інформації про системну службі в таблиці диспетчера системної служби ядро використовує номер системного виклику. На 32-розрядних системах ця таблиця схожа на таблицю диспетчера переривань, за винятком того, що кожен запис містить покажчик на системну службу, а не на процедуру обробки переривання.

Відео: Four Horsemen - Feature Documentary - Official Version

На 64-розрядних системах таблиця реалізована трохи інакше, вона містить не покажчики на системні служби, а зміщення відносно самої таблиці. Цей механізм адресації краще підходить наявного в системі x64 бінарного інтерфейсу прикладних програм - application binary interface (ABI) і формату кодування інструкцій.

ПРИМІТКА. Залежно від використовуваного пакету оновлень номера системних служб можуть змінюватися - компанія Microsoft час від часу додає або видаляє системні служби, і номера системних служб генеруються автоматично, як частина компіляції ядра.

виключення-системних-служб

Винятки системних служб.

Диспетчер системних служб, KiSystemService, копіює аргументи викликає програми з стека потоку призначеного для користувача режиму в свій стек режиму ядра (щоб користувач не зміг змінити аргументи при зверненні до них ядра), а потім виконує системну службу. Ядро отримує уявлення про те, скільки байт стека потрібно копіювати, завдяки використанню другої таблиці, яка називається таблицею аргументів і є байтовим масивом (на відміну від масиву покажчиків на кшталт таблиці диспетчеризації). Кожен запис дає опис кількості байт для копіювання.

На 64-розрядних системах Windows кодує цю інформацію в саму таблицю служби за допомогою процесу, який називається ущільненням таблиці системних викликів. Якщо аргументи, що передаються системної службі, вказують на буфери в просторі користувача, ці буфери повинні бути перевірені на доступність, перш ніж код режиму ядра зможе копіювати дані в ці буфери або з них. Ця перевірка здійснюється тільки тоді, коли попередній режим (previous mode) потоку встановлений на призначений для користувача режим.

Попередній режим є значенням (режим ядра або призначений для користувача режим), яке ядро зберігає в потоці, коли в ньому виконується оброблювач системного переривання і ідентифікується рівень привілеїв входить виключення, системного переривання або системного виклику. Як оптимізації, якщо системний виклик надходить від драйвера або від самого ядра, перевірка і захоплення параметрів пропускаються, і всі параметри вважаються вказують на допустимі буфери режиму ядра (також дозволяється доступ до даних в режимі ядра).

Оскільки системні виклики можуть також здійснюватися кодом, виконуваних в режимі ядра, давайте розглянемо спосіб їх реалізації. Так як код для кожного системного виклику виконується в режимі ядра, а зухвала програма вже виконується в режимі ядра, можна прийти до висновку, що будь-якого переривання або операції sysenter не потрібно: центральний процесор вже знаходиться на потрібному рівні привілеїв, і драйвери, як і ядро, повинні тільки мати можливість безпосереднього виклику необхідної функції.

У разі виконує системи саме це і відбувається: ядро має доступ до всіх своїх власними процедурами і може просто викликати їх точно так же, як викликає стандартні процедури. Але зовні драйвери можуть отримати доступ до цих системних викликів тільки тоді, коли ці виклики експортовані подібно іншим стандартним API-функцій режиму ядра. Фактично, експортовано досить багато системних викликів.

Але такий спосіб доступу для драйверів не передбачається. Замість цього драйвери повинні використовувати Zw-версії цих викликів, тобто замість NtCreateFile вони повинні використовувати ZwCreateFile. Ці Zw-версії повинні бути також вручну експортовані ядром, їх небагато, але на них є повна документація і підтримка.

Через розглянутого раніше поняття попереднього режиму Zw-версії офіційно доступні тільки для драйверів. Оскільки значення попереднього режиму оновлюється тільки при кожному створенні ядром фрейма системного переривання, в зв`язку з простим API-викликом воно змінюватися не буде, оскільки ніякого фрейма системного виклику згенеровано не буде.

При безпосередньому виклику таких функцій, як NtCreateFile, ядро зберігає значення попереднього режиму, яке показує, що воно відноситься до призначеного для користувача режиму, виявляє, що передана адреса відноситься до адреси режиму ядра, і не виконує виклик, правильно вважаючи, що додатки для користувача режиму не повинні передавати покажчики режиму ядра. Але насправді ж це не так, тоді як же ядро має розібратися в правильному попередньому режимі? Відповідь полягає в Zw-виклики.

Ці експортовані API-функції насправді не є простими псевдонімами або оболонками Nt-версій. Замість цього вони є своєрідними «батутами» для стрибка до відповідних системним Nt-викликам, які використовують той же самий механізм диспетчеризації системних викликів.

Замість генерування переривання або використання інструкції sysenter, які не відрізнялися б швидкістю роботи і (або) не підтримувалися б, вони створюють штучний стек переривання (стек, який центральний процесор згенерував б після переривання) і безпосередньо викликають процедуру KiSystemService, фактично імітуючи переривання центрального процесора.

Оброблювач виконує ті ж операції, що і при надходженні цього виклику з призначеного для користувача режиму, за винятком того, що він виявляє фактичний рівень привілеїв, з яким надійшов виклик, і встановлює для попереднього режиму значення режиму ядра (kernel). Тепер функція NtCreateFile бачить, що виклик надійшов з ядра, і більше не відмовляє. Далі показано, як виглядають батути режиму ядра на 32-розрядної і на 64-розрядної системах. Номер системного виклику виділено жирним шрифтом.

lkd> u nt! ZwReadFile

nt! ZwReadFile:

8207f118 b802010000 mov eax, 102h

8207f11d 8d542404 lea edx, [esp + 4]

8207f121 9c pushfd

8207f122 6a08 push 8

8207f124 e8c5d70000 call nt! KiSystemService (8208c8ee)

8207f129 c22400 ret 24h

lkd> uf nt! ZwReadFile

nt! ZwReadFile:

fffff800`01a7a520 488bc4 mov rax, rsp

fffff800`01a7a523 fa cli

fffff800`01a7a524 4883ec10 sub rsp, 10h

fffff800`01a7a528 50 push rax

fffff800`01a7a529 9c pushfq

fffff800`01a7a52a 6a10 push 10h

fffff800`01a7a52c 488d05bd310000 lea rax, [nt! KiServiceLinkage

(Fffff800`01a7d6f0)]

fffff800`01a7a533 50 push rax

fffff800`01a7a534 b803000000 mov eax, 3

fffff800`01a7a539 e902690000 jmp nt! KiServiceInternal (fffff800`01a80e40)

У Windows є дві таблиці системних служб, і драйвери сторонніх розробників не можуть розширити таблиці або вставити нові для додавання своїх власних викликів служб. На 32-розрядних версіях Windows і на версіях IA64 диспетчер системних служб визначає місце розташування таблиць через покажчик в структурі потоків ядра, а на x64-версіях він знаходить їх по їх глобальним адресами

перетворення-номера

Перетворення номера системної служби до системної службі.

Диспетчер системних служб визначає, в якій таблиці міститься запитана служба, інтерпретуючи дворозрядне поле в 32-розрядному номері системної служби в якості індексу таблиці. Молодші 12 розрядів номера системної служби служать в якості індексу в таблиці, зазначеної індексом таблиці.

Поділитися в соц мережах:
Схожі
» » Диспетчеризація системних служб