void function(int a, int b, int c) { char buffer1[5]; char buffer2[10]; int *ret; ret = buffer1 + 12; (*ret) += 8;//why is it 8?? } void main() { int x; x = 0; function(1,2,3); x = 1; printf("%d\n",x); }
Вышеупомянутая демонстрация отсюда:
http://insecure.org/stf/smashstack.html
Но это не работает здесь:
D:\test>gcc -Wall -Wextra hw.cpp && a.exe hw.cpp: In function `void function(int, int, int)': hw.cpp:6: warning: unused variable 'buffer2' hw.cpp: At global scope: hw.cpp:4: warning: unused parameter 'a' hw.cpp:4: warning: unused parameter 'b' hw.cpp:4: warning: unused parameter 'c' 1
И я не понимаю, почему это 8, хотя автор думает:
Немного математики говорит нам, что расстояние составляет 8 байтов.
Мои gdb выводят, как названо:
Dump of assembler code for function main: 0x004012ee
: push %ebp 0x004012ef : mov %esp,%ebp 0x004012f1 : sub $0x18,%esp 0x004012f4 : and $0xfffffff0,%esp 0x004012f7 : mov $0x0,%eax 0x004012fc : add $0xf,%eax 0x004012ff : add $0xf,%eax 0x00401302 : shr $0x4,%eax 0x00401305 : shl $0x4,%eax 0x00401308 : mov %eax,0xfffffff8(%ebp) 0x0040130b : mov 0xfffffff8(%ebp),%eax 0x0040130e : call 0x401b00 <_alloca> 0x00401313 : call 0x4017b0 <__main> 0x00401318 : movl $0x0,0xfffffffc(%ebp) 0x0040131f : movl $0x3,0x8(%esp) 0x00401327 : movl $0x2,0x4(%esp) 0x0040132f : movl $0x1,(%esp) 0x00401336 : call 0x4012d0 0x0040133b : movl $0x1,0xfffffffc(%ebp) 0x00401342 : mov 0xfffffffc(%ebp),%eax 0x00401345 : mov %eax,0x4(%esp) 0x00401349 : movl $0x403000,(%esp) 0x00401350 : call 0x401b60 0x00401355 : leave 0x00401356 : ret 0x00401357 : nop 0x00401358 : add %al,(%eax) 0x0040135a : add %al,(%eax) 0x0040135c : add %al,(%eax) 0x0040135e : add %al,(%eax) End of assembler dump. Dump of assembler code for function function: 0x004012d0 : push %ebp 0x004012d1 : mov %esp,%ebp 0x004012d3 : sub $0x38,%esp 0x004012d6 : lea 0xffffffe8(%ebp),%eax 0x004012d9 : add $0xc,%eax 0x004012dc : mov %eax,0xffffffd4(%ebp) 0x004012df : mov 0xffffffd4(%ebp),%edx 0x004012e2 : mov 0xffffffd4(%ebp),%eax 0x004012e5 : movzbl (%eax),%eax 0x004012e8 : add $0x5,%al 0x004012ea : mov %al,(%edx) 0x004012ec : leave 0x004012ed : ret В моем случае расстояние должно быть - = 5, правильно? Но это кажется не работой..
Почему
function
потребности 56 байтов для локальных переменных? (sub $0x38,%esp
)
Как joveha указал , значение EIP, сохраненное в стеке (адрес возврата) при вызове
инструкция должна быть увеличена на 7 байтов ( 0x00401342
- 0x0040133b
= 7 ), чтобы пропустить x = 1 ;
инструкция ( movl $ 0x1,0xfffffffc (% ebp)
).
Вы правы, 56 байтов зарезервированы для локальных переменных ( sub $ 0x38,% esp
), поэтому недостающий фрагмент - это сколько байтов в стеке после buffer1
сохраненный EIP.
Небольшой тестовый код и встроенный ассемблер говорят мне, что магическое значение для моего теста составляет 28 . Я не могу дать однозначного ответа, почему это 28, но я предполагаю, что компилятор добавляет отступы и / или канарейки стека .
Следующий код был скомпилирован с использованием GCC 3.4.5 (MinGW) и протестирован в Windows XP SP3 (x86).
unsigned long get_ebp() {
__asm__("pop %ebp\n\t"
"movl %ebp,%eax\n\t"
"push %ebp\n\t");
}
void function(int a, int b, int c) {
char buffer1[5];
char buffer2[10];
int *ret;
/* distance in bytes from buffer1 to return address on the stack */
printf("test %d\n", ((get_ebp() + 4) - (unsigned long)&buffer1));
ret = (int *)(buffer1 + 28);
(*ret) += 7;
}
void main() {
int x;
x = 0;
function(1,2,3);
x = 1;
printf("%d\n",x);
}
Я мог бы с таким же успехом использовать gdb для определения этого значения.
(скомпилировано с -g
для включения символов отладки)
(gdb) break function
...
(gdb) run
...
(gdb) p $ebp
$1 = (void *) 0x22ff28
(gdb) p &buffer1
$2 = (char (*)[5]) 0x22ff10
(gdb) quit
( 0x22ff28
+ 4) - 0x22ff10
= 28
(значение ebp + размер слова) - адрес буфера1 = количество байтов
В дополнение к Smashing The Stack For Fun And Profit , я бы посоветовал прочитать некоторые из статей, которые я упомянул в мой ответ на ваш предыдущий вопрос и / или другой материал по теме. Хорошее понимание того, как именно работает этот тип эксплойтов, должно помочь вам писать более безопасный код .
Вы компилируете программу C с помощью компилятора C ++. Переименуйте hw.cpp в hw.c, и вы обнаружите, что он скомпилирован.
Трудно предсказать, на что действительно указывает buffer1 + 12
. Ваш компилятор может поместить buffer1
и buffer2
в любое место стека по своему усмотрению, даже пойти дальше и не сохранить место для buffer2
вообще. Единственный способ узнать, куда именно помещается buffer1
- это посмотреть на ассемблерный вывод вашего компилятора, и велика вероятность, что он будет скакать при разных настройках оптимизации или в разных версиях одного и того же компилятора.
Я еще не тестировал код на своей машине, но вы учли выравнивание памяти? Попробуйте разобрать код с помощью gcc. Я думаю, что ассемблерный код может дать вам более полное представление о коде. : -)
Этот код выводит 1 и на OpenBSD и FreeBSD, и дает ошибку сегментации на Linux.
Этот вид эксплойта сильно зависит как от набора инструкций конкретной машины, так и от соглашений о вызовах компилятора и операционной системы. Все, что касается расположения стека, определяется реализацией, а не языком Си. В статье предполагается Linux на x86, но похоже, что вы используете Windows, и ваша система может быть 64-битной, хотя вы можете переключить gcc на 32-битную с помощью -m32
.
Параметры, которые вам придется подправить, это 12 - смещение от вершины стека до адреса возврата, и 8 - сколько байт main
вы хотите перепрыгнуть. Как говорится в статье, вы можете использовать gdb для просмотра разборки функции, чтобы увидеть (а) насколько далеко отодвигается стек при вызове function
и (б) байтовые смещения инструкций в main
.
Часть +8 байт - это то, на сколько он хочет увеличить сохраненный EIP. EIP был сохранен, чтобы программа могла вернуться к последнему заданию после выполнения функции
- теперь он хочет пропустить его, добавив 8 байт к сохраненному EIP.
Таким образом, он пытается "пропустить"
x = 1;
В вашем случае сохраненный EIP будет указывать на 0x0040133b
, первую инструкцию после возврата function
. Чтобы пропустить присваивание, нужно сделать так, чтобы сохраненный EIP указывал на 0x00401342
. Это 7 байт.
Это действительно пример "путаницы с RET EIP", а не пример переполнения буфера.
А что касается 56 байт для локальных переменных, то это может быть что угодно, что придумает ваш компилятор, например, паддинг, стековые канавки и т.д.
Edit:
Это показывает, насколько сложно сделать примеры переполнения буфера в C. Смещение 12 от buffer1
предполагает определенный стиль набивки и параметры компиляции. GCC с радостью вставит стековую канарейку (которая становится локальной переменной, "защищающей" сохраненный EIP), если вы не запретите ему это делать. Кроме того, новый адрес, на который он хочет перейти (инструкция начала вызова printf
), действительно должен быть определен вручную из ассемблера. В его случае, на его машине, с его ОС, с его компилятором, в тот день.... это было 8.