Бинарные уязвимости/Переполнение стека: различия между версиями

Материал из SecSem Wiki
Перейти к навигации Перейти к поиску
(Новая страница: «=Адресное пространство процесса= Файл:address_space_1.png|500px|right|thumb|Пример содержимого адресно…»)
 
м (Stack buffer overflow)
Строка 23: Строка 23:
 
=Stack buffer overflow=
 
=Stack buffer overflow=
  
Переполнение буфера в стеке происходит, когда программа должным образом не проверяет размер буфера, выделенного на стеке, при записи в него. Например, так делают известные функции '''gets''', '''strcpy'''. Рассмотрим пример функции с таким типом уязвимости:
+
Переполнение буфера в стеке происходит, когда программа должным образом не проверяет размер буфера, выделенного на стеке, при записи в него. Например, так делают известные функции '''gets''', '''strcpy'''. Рассмотрим пример кода, где есть уязвимая функция '''foo'''
  
 
<syntaxhighlight lang="c">
 
<syntaxhighlight lang="c">
void foo(char* arg){
+
// 3.c
 +
#include <stdio.h>
 +
#include <string.h>
 +
#include <unistd.h>
 +
 
 +
void print_passwrd(void) {
 +
    system("cat /etc/passwd");
 +
}
 +
 
 +
void root(){
 +
    system("/bin/sh");
 +
}
 +
 
 +
void foo(char* c){
 
     int is_admin = 0;
 
     int is_admin = 0;
 
     char buffer[256];
 
     char buffer[256];
  
     strcpy(buffer, arg);
+
     strcpy(buffer, c);
 
     printf("Hello, %s\n", buffer);
 
     printf("Hello, %s\n", buffer);
  
Строка 37: Строка 50:
 
     }
 
     }
 
}
 
}
</syntaxhighlight>
 
  
[[Файл:Foo stask.JPG|300px|border|Стековый кадр функции foo]]
+
int main(int argc, char *argv[]) {
 +
    if (argc < 2) {
 +
        return -1;
 +
    }
 +
 
 +
    foo(argv[1]);
  
 +
    return 0;
 +
}
 +
</syntaxhighlight>
  
 
==Устройство стека==
 
==Устройство стека==
  
В данной функции определяется локальная переменная '''is_admin''' и  локальный массив '''buffer''', размещающиеся на стеке. Функция '''strcpy''' копирует '''arg''', переданный во втором аргументе, в '''buffer''' до тех пор пока не встретит в ней нулевой байт. При этом сам нулевой байт также будет скопирован. Чтобы понять, почему это может быть опасно, нужно рассмотреть содержимое стекового кадра функции '''foo'''. Предположим, что мы скомпилировали данный код в 64-битную программу без оптимизации и соглашение о вызовах функции - '''cdecl'''. Тогда стековый кадр будет выглядеть так:
+
В функции '''foo''' определяется локальная переменная '''is_admin''' и  локальный массив '''buffer''', размещающиеся на стеке. Функция '''strcpy''' копирует '''arg''', переданный во втором аргументе, в '''buffer''' до тех пор пока не встретит в ней нулевой байт. При этом сам нулевой байт также будет скопирован. Чтобы понять, почему это может быть опасно, нужно рассмотреть содержимое стекового кадра функции '''foo'''. Предположим, что мы скомпилировали данный код в 64-битную программу без оптимизации и соглашение о вызовах функции - '''cdecl'''. Тогда стековый кадр будет выглядеть так:
  
[[Файл:stack_frame.png|400px|border|Стековый кадр функции foo]]
+
[[Файл:Foo stask-3.JPG|300px|border|Стековый кадр функции foo]]
  
На данной схеме стрелочкой показано направление роста стека в адресном пространстве (от старших к младшим адресам). Далее последовательно в стеке располагаются:<br />
+
На данной схеме стек растет вниз в сторону младших адресов. Далее последовательно в стеке располагаются:<br />
 
- аргумент функции '''foo'''<br />
 
- аргумент функции '''foo'''<br />
- адрес возврата - адрес, на который перейдет управление после окончания исполнения функции '''foo'''<br />
+
- адрес возврата - адрес внутри '''main''', на который перейдет управление после окончания исполнения функции '''foo'''<br />
- значение регистра '''ebp''', являющееся указателем стекового кадра вызывающей '''foo''' функции<br />
+
- значение регистра '''ebp''', являющееся указателем стекового кадра вызывающей '''foo''' функции (в данном случае '''main''')<br />
- опциональный паддинг (обычно используется, что локальные аргументы были выравнены по 16 байт)<br />
+
- локальный массив '''buffer'''
- локальный массив '''local'''
 
  
Функция '''strcpy''' осуществляет копирование в сторону противоположную росту стека. Таким образом при достаточном размере копируемой строки она может перетереть данные, хранящиеся после '''local''': '''ebp''', адрес возврата, аргументы и стековый кадр другой функции. Самым интересным с точки зрения эксплуатации является изменения адреса возврата, т.к. это значение полностью определяет, какой участок кода будет выполняться после завершения данной функции.
+
Функция '''strcpy''' осуществляет копирование в сторону противоположную росту стека (в сторону старших адресов). Таким образом при достаточном размере копируемой строки она может перетереть данные, хранящиеся после '''buffer''': '''ebp''', адрес возврата, аргументы и стековый кадр другой функции.
  
 
==Эксплуатация==
 
==Эксплуатация==
 +
 +
Цель: пройти проверку внутри функции '''foo''' значения переменной '''is_admin''' и получить вызов функции '''print_passwrd'''
 +
 +
Заметим, что значение локальной переменной  '''is_admin''' лежит до '''buffer'''. Значит при достаточно длинном размере копируемой строк значение '''is_admin''' может быть перетерто желаемым значением (0x1337). Чтобы правильно вычислить размер строки, обратимся к дисассемблированному коду функции '''foo'''.

Версия 16:42, 1 декабря 2023

Адресное пространство процесса

Пример содержимого адресного пространства процесса на x86

Адресное пространство процесса на x86/amd64 - это совокупность виртуальных адресов, доступная программе. Размер адресного пространства на x86 без дополнительных способов расширения - 4 Гб, он разделённый на kernel-space (2/1 Гб) и user-space (2/3 Гб). На x86-64 размер адресного пространства 2**48 - старшие 16 бит адреса все равны или 0, или 1. Такие адреса называются каноничными, все другие - неканоничными. В случае попытки обращения к неканоничному адресу возникает general protection exception (#GP). В случае x86-64 каноничность адресов можно использовать при проведении анализа содержимого памяти (так адрес из адресного пространства ядра будет иметь префикс 0xFFF, а из пользовательского - 0x000).

В *nix в user-space части адресного пространства содержится:
- запускаемый исполняемый файл
- динамические *.so библиотеки
- mmap() области (анонимные аллокации и отмапленные файлы)
- стек
- куча
- отмапленные из ядра области (vsyscall/vvar/vdso)
- различные служебные структуры

В pwndbg/gef/peda содержимое адресного пространства можно посмотреть с помощью команды vmmap:

Вывод команды vmmap в pwndbg

В gdb можно использовать команду info proc map, а без отладчика содержимое можно посмотреть через файловую систему /proc с помощью команды cat /proc/<self>/maps.


Stack buffer overflow

Переполнение буфера в стеке происходит, когда программа должным образом не проверяет размер буфера, выделенного на стеке, при записи в него. Например, так делают известные функции gets, strcpy. Рассмотрим пример кода, где есть уязвимая функция foo

// 3.c
#include <stdio.h>
#include <string.h>
#include <unistd.h>

void print_passwrd(void) {
    system("cat /etc/passwd");
}

void root(){
    system("/bin/sh");
}

void foo(char* c){
    int is_admin = 0;
    char buffer[256];

    strcpy(buffer, c);
    printf("Hello, %s\n", buffer);

    if (is_admin == 0x1337) {
        print_passwrd();
    }
}

int main(int argc, char *argv[]) {
    if (argc < 2) {
        return -1;
    }

    foo(argv[1]);

    return 0;
}

Устройство стека

В функции foo определяется локальная переменная is_admin и локальный массив buffer, размещающиеся на стеке. Функция strcpy копирует arg, переданный во втором аргументе, в buffer до тех пор пока не встретит в ней нулевой байт. При этом сам нулевой байт также будет скопирован. Чтобы понять, почему это может быть опасно, нужно рассмотреть содержимое стекового кадра функции foo. Предположим, что мы скомпилировали данный код в 64-битную программу без оптимизации и соглашение о вызовах функции - cdecl. Тогда стековый кадр будет выглядеть так:

Стековый кадр функции foo

На данной схеме стек растет вниз в сторону младших адресов. Далее последовательно в стеке располагаются:
- аргумент функции foo
- адрес возврата - адрес внутри main, на который перейдет управление после окончания исполнения функции foo
- значение регистра ebp, являющееся указателем стекового кадра вызывающей foo функции (в данном случае main)
- локальный массив buffer

Функция strcpy осуществляет копирование в сторону противоположную росту стека (в сторону старших адресов). Таким образом при достаточном размере копируемой строки она может перетереть данные, хранящиеся после buffer: ebp, адрес возврата, аргументы и стековый кадр другой функции.

Эксплуатация

Цель: пройти проверку внутри функции foo значения переменной is_admin и получить вызов функции print_passwrd

Заметим, что значение локальной переменной is_admin лежит до buffer. Значит при достаточно длинном размере копируемой строк значение is_admin может быть перетерто желаемым значением (0x1337). Чтобы правильно вычислить размер строки, обратимся к дисассемблированному коду функции foo.