В этой статье будет рассказано про уязвимость, найденную в операционных системах Windows NT/2000/XP, причём в не NT системах нет данной уязвимости. Речь пойдёт о реализации виртуальной DOS машины в этих операционных системах. Как же старое
16 битное приложение для DOS, запущенное в Windows NT, сообщает ей что именно нужно делать - читать файлы, выводить на консоль символы, изменять графический режим?
Всё это происходит в ядре системы. Оказывается,
разработчики Windows NT/2000/XP придумали для этого интересный способ - они используют некорректную инструкцию, она вызывает исключение #UD - Faulf , а после этой инструкции расположена дополнительная информация, сам код инструкции идёт как сигнатура - то что это именно вызов сервиса, а далее что именно требуется, остальные входные параметры находятся в регистрах процессора. Теперь подробнее про инструкцию процессора, которую они решили использовать. Инструкция LES -
непривилегированная инструкция пользователя, предназначена для загрузки дальнего FAR указателя в регистры ES (сегментная часть, или селектор) и первый параметр команды (смещение). Вторым параметром команды обязательно должна быть память - оттуда и считывается дальний указатель. Итак формат команды:
LES R16/32,Mem32/48
Если не считать возможных префиксов перед, тогда код инструкции будет таким:
C4 xx rrr mmm ...
C4 - это первый байт данной команды, всегда такой, если C4
- значит это LES. Далее идёт важный байтик (mod/reg/mem), xx - его старшие два бита, rrr - средние три бита и mmm - младшие три бита,
в этом байте заданы аргументы инструкции. Не будем тут сильно углубляться в структуру команд Intel, в данном случае нужно знать только, что если старшие два бита xx равны 11,
значит второй аргумент команды регистр, если же они не равны 11, значит второй аргумент память. Но у инструкции LES не может быть второго регистрового аргумента! Что будет если указать для неё во втором байте старшие два бита равные 11? А будет некорректная инструкция и при попытке исполнения получим #UD - Faulf. Итак, разработчики решили поступить именно так и второй байт равен тоже C4 - сразу видно люди любят красоту, даже тут где мало кто может её заметить. Инструкция с кодом C4 C4.
C4 C4 ; красота то какая
Это дизассемблируется так (некорректно, но всё таки):
LES AX,SP
C4h = 11000100b => 11 - > второй операнд регистр, 000 - первый операнд регистр с номером 0, 100 - второй операнд регистр с номером 4 => итог - первый это AX, второй это SP.
Так вот, после байтов C4 C4 следует ещё один байт (наверно всегда один), он как раз и есть номер сервиса, параметры вызова находятся в регистрах процессора и обработчик исключения получит их значения, выполнит необходимые действия и снова совершит возврат и процесс, но уже конечно пропустит эти байты и управление будет передано дальше. Посмотрим примеры кода взятого из DOS среды, рождённой в Windows XP.
push si
xor si,si
db 0C4h,0C4h,17h
pop si
;####
xor si,si
db 0C4h,0C4h,17h
test ah,8
jz short ...
;####
push si
push dx
mov si,0FFFFh
db 0C4h,0C4h,17h
;####
Фрагмент взят из дампа памяти, тут ясно видно, что это явно какой-то сервис 17h, причём он получает параметр в регистре SI.
mov ah,002h
db 0C4h,0C4h,16h
lab_00002C09:
mov bx,[0001Ah]
cmp bx,[0001Ch]
jnz short lab_00002C2A
mov ax,cs:[009B8h]
cmp ax,cs:[009C0h]
jnb short lab_00002C25
inc word ptr cs:[009BCh]
jmp short lab_00002C09
lab_00002C25:
mov ah,001h
db 0C4h,0C4h,16h
lab_00002C2A:
pop ax
retn
Тоже пример, но уже сервис 16h и ясно видно, что на этот раз параметр в AH. И таких мест очень много, это действительно не мусор и всё так как я написал, если проходить данные места под отладчиком, то всё работает и создаётся впечатление, что это какие-то специальные инструкции процессора, он послушно их обрабатывает и пропускает ровно сколько байтов, сколько надо, но это только так кажется - это эмулируется операционной системой.
db 0C4h,0C4h,0FEh
push ax
xor ax,ax
db 0C4h,0C4h,9h
pop ax
Вот ещё пример. Наверно всегда вызов выглядит так C4 C4 i8 , то есть плохая инструкция и ещё один байт - номер сервиса.
push ax
mov ax,013FEh
db 0C4h,0C4h,42h
pop ax
clc
dec word ptr cs:[00804h]
retn
;####
push ax
mov ax,00001h
db 0C4h,0C4h,59h
pop ax
mov ah,080h
stc
retf 00002h
Таких примеров можно привести много. Как видно номер сервиса - байт после C4 C4
- тпринимает различные значения, но наверно не все возможные, я например некоторые так и не нашёл. Например нулевое значение так и не было найдено во всём мегабайте DOS образа. Интересно, что же будет если сделать такой вызов !?
mov di,0041Dh
mov cx,00004h
mov ax,0BEEFh
db 0C4h,0C4h,5Fh
jc short lab_00002646
И вот я взял да и написал меленький .COM файлик следующего содержания:
C4 C4 00 C3
И запустил его - консоль умерла. Теперь прибиваем консоль и запускаем что-нибудь 16 битное, чтобы NTVDM.EXE запустился. И теперь снова выполняем маленький файлик
- NTVDM.EXE вывела сообщение: Заголовок окна: 16 bit MS-DOS Subsystem, а в самом окне: NTVDM has encountered a System Error. Acsess is denied. Независимо от ответа на это окошко консоль продолжает висеть, но легко прибивается, или сама исчезает со временем. Запомним, что сама консоль совсем мёртвая и ни на что не реагирует, хотя окно
можно закрыть. При этом занятость процессора не меняется. Немного позже мне пришла хорошая идея - перед исполнением плохой инструкции с неверным номером сервиса нужно развернуть консоль на весь экран, причём вызовом чего-нибудь из этой же среды, например надёжно работает смена режима консоли на графический режим, ну вот например старенький 13h от прерывания 10h. Пишем следующий код:
mov ax,13h
int 10h
db 0C4h,0C4h,0
Теперь всё это в .COM файл и получим вот что:
B8 13 00 CD 10 C4 C4 00
Это просто превзошло все ожидания !!! Запускаем и...
Сначала консоль разворачивается на весь экран, а после блокируется, просто умирает и не реагирует уже совсем ни на что!
Даже CTRL-ALT-DEL не спасёт, впрочем не поможет и следующее: ALT-TAB, ALT-BLANK, ALT-ENTER, WINKEY, и так далее... Только лишь на вершине экрана уже в графическом режиме видны куски мусора, процессор не висит, остальные приложения нормально продолжают работать - у меня медиа плейер играл в это время. Но пользователь ничего не может сделать! Эта вещь работает везде - под админом, под пользователем с ограниченными правами и под гостем тоже. Такое состояние продолжается примерно минут пять и больше, потом операционная система сама понимает что что-то не так и пытается снять задачу, что тоже получается не сразу.
Тоже интересное поведение наблюдается если сначала запустить например Volkov Commaned, только не в полноэкранном режиме, а так чтобы консолька была отдельным окном не на весь экран. Потом исполняем из него первый файлик (достаточно только C4 C4 00), при этом консоль тоже умирает, но остаётся маленькой, теперь её можно развернуть на весь экран с помощью ALT-ENTER, но вот свернуть обратно не получится - консоль умерла и занимает весь экран, только мигающий курсор внизу экрана будет нервировать вас ещё минут пять, все нажатия на клавиши бесполезны, но не советую много чего нажимать, так как когда операционная система будет прибивать это процесс она выполнит все нажатия что были совершены во время блокировки. Как же это можно использовать ? Подумайте сами и я уверен, что найдёте интересное применение. Данная вещь проверялась на Windows NT, Windows 2000, Windows XP. И приводила в шок всех кто это видел, подвесить Windows NT это конечно надо постараться, но чтобы так подвесить нужно сильно-сильно постараться. Эту уязвимость я нашёл в первый раз четыре года назад в Windows NT с шестым сервис паком, позже я проверил это на Windows 2000 и Windows XP
и всё работало, наверно ошибку упорно не хотят замечать уже несколько лет подряд, я также нигде не видел упоминания об этом свойстве систем Windows NT. В общем используйте как только сможете придумать, возможно в этом направлении можно что-то ещё раскопать ...
Best regards from Chemiker.