Бинарные уязвимости/Переполнение стека: различия между версиями
Dzeni (обсуждение | вклад) м (→Эксплуатация) |
Dzeni (обсуждение | вклад) м (→Эксплуатация) |
||
Строка 82: | Строка 82: | ||
Заметим, что значение локальной переменной '''is_admin''' лежит до '''buffer'''. Значит, при достаточно длинном размере копируемой строки, значение '''is_admin''' может быть перетерто желаемым значением (0x1337). Чтобы правильно вычислить размер строки, обратимся к дисассемблированному коду функции '''foo'''. | Заметим, что значение локальной переменной '''is_admin''' лежит до '''buffer'''. Значит, при достаточно длинном размере копируемой строки, значение '''is_admin''' может быть перетерто желаемым значением (0x1337). Чтобы правильно вычислить размер строки, обратимся к дисассемблированному коду функции '''foo'''. | ||
− | <syntaxhighlight lang=" | + | <syntaxhighlight lang="Asymptote"> |
pwndbg> disassemble foo | pwndbg> disassemble foo | ||
Dump of assembler code for function foo: | Dump of assembler code for function foo: |
Текущая версия на 15:53, 1 декабря 2023
Содержание
Адресное пространство процесса
Адресное пространство процесса на 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:
В 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
- адрес возврата - адрес внутри main, на который перейдет управление после окончания исполнения функции foo
- значение регистра ebp, являющееся указателем стекового кадра вызывающей foo функции (в данном случае main)
- локальный массив buffer
Функция strcpy осуществляет копирование в сторону противоположную росту стека (в сторону старших адресов). Таким образом при достаточном размере копируемой строки она может перетереть данные, хранящиеся после buffer: ebp, адрес возврата, аргументы и стековый кадр другой функции.
Эксплуатация
Цель: пройти проверку внутри функции foo значения переменной is_admin и получить вызов функции print_passwrd.
Заметим, что значение локальной переменной is_admin лежит до buffer. Значит, при достаточно длинном размере копируемой строки, значение is_admin может быть перетерто желаемым значением (0x1337). Чтобы правильно вычислить размер строки, обратимся к дисассемблированному коду функции foo.
pwndbg> disassemble foo
Dump of assembler code for function foo:
0x0000000000401172 <+0>: push rbp
0x0000000000401173 <+1>: mov rbp,rsp
0x0000000000401176 <+4>: sub rsp,0x120
0x000000000040117d <+11>: mov QWORD PTR [rbp-0x118],rdi
0x0000000000401184 <+18>: mov DWORD PTR [rbp-0x4],0x0
0x000000000040118b <+25>: mov rdx,QWORD PTR [rbp-0x118]
0x0000000000401192 <+32>: lea rax,[rbp-0x110]
0x0000000000401199 <+39>: mov rsi,rdx
0x000000000040119c <+42>: mov rdi,rax
0x000000000040119f <+45>: call 0x401030 <strcpy@plt>
0x00000000004011a4 <+50>: lea rax,[rbp-0x110]
0x00000000004011ab <+57>: mov rsi,rax
0x00000000004011ae <+60>: mov edi,0x40201c
0x00000000004011b3 <+65>: mov eax,0x0
0x00000000004011b8 <+70>: call 0x401050 <printf@plt>
0x00000000004011bd <+75>: cmp DWORD PTR [rbp-0x4],0x1337
0x00000000004011c4 <+82>: jne 0x4011cb <foo+89>
0x00000000004011c6 <+84>: call 0x401146 <print_passwrd>
0x00000000004011cb <+89>: nop
0x00000000004011cc <+90>: leave
0x00000000004011cd <+91>: ret
End of assembler dump.