Это поможет скомпилировать пример из другого вопроса в сборку, чтобы вы могли понять, как устроен стек для вашего компилятора и процессора. +8
в примере может быть неправильным числом для вашей среды. Вам нужно определить, где адрес возврата хранится в стеке относительно массива, хранящегося в стеке.
Кстати, пример работал для меня. Я скомпилировал на Win XP с Cygwin, gcc версии 4.3.4. Когда я говорю, что это «работает», я имею в виду, что он запускал код в функции bad()
, хотя эта функция никогда не вызывалась кодом.
$ gcc -Wall -Wextra buffer-overflow.c && ./a.exe
Oh shit really bad~!
Segmentation fault (core dumped)
Код действительно не является примером переполнения буфера, это пример того, что может случиться с плохим при использовании переполнения буфера.
Я не очень хорошо разбираюсь в сборке x86, но вот моя интерпретация того, как работает этот эксплойт.
$ gcc -S buffer-overflow.c && cat buffer-overflow.s
_foo:
pushl %ebp ;2
movl %esp, %ebp ;3
subl $16, %esp ;4
movl LC1, %eax ;5
movl %eax, -4(%ebp) ;6
leal -4(%ebp), %eax ;7
leal 8(%eax), %edx ;8
movl $_bad, %eax ;9
movl %eax, (%edx) ;10
leave
ret
_main:
...
call _foo ;1
...
Когда main
вызывает foo
(1), инструкция call
помещает в стек адрес внутри main, чтобы вернуться к нему после завершения вызова foo
. Загрузка в стек включает в себя уменьшение ESP и сохранение там значения.
Однажды в foo
старое базовое значение указателя также помещается в стек (2). Это будет восстановлено, когда foo
вернется. Указатель стека сохраняется в качестве базового указателя для этого кадра стека (3). Указатель стека уменьшается на 16 (4), что создает пространство в этом кадре стека для локальных переменных.
Адрес литерала "WOW \ 0" копируется в локальную переменную overme
в стеке (5,6) - мне это кажется странным, если бы не копирование 4 символов в пространство, выделенное на стек? В любом случае, место, куда копируется WOW (или указатель на него), находится на 4 байта ниже текущего базового указателя. Таким образом, стек содержит это значение, затем старый базовый указатель, а затем адрес возврата.
Адрес overme
помещается в EAX (7), а целочисленный указатель создается на 8 байт за этим адресом (8). Адрес функции bad
помещается в EAX (9), а затем этот адрес сохраняется в памяти, на которую указывает целочисленный указатель (10).
Стек выглядит так:
// 4 bytes on each row
ESP: (unused)
: (unused)
: (unused)
: &"WOW\0"
: old EBP from main
: return PC, overwritten with &bad
Когда вы компилируете с оптимизацией, все интересные вещи оптимизируются как «бесполезный код» (который он есть).
$ gcc -S -O2 buffer-overflow.c && cat buffer-overflow.s
_foo:
pushl %ebp
movl %esp, %ebp
popl %ebp
ret