Почему не работает Buffer Overflow в C?

Пытаюсь воспроизвести Buffer Overflow. Для этого использую этот туториал: phrack.org/issues/49/14.html
Но никак не могу даже сделать простейший пример (в том гайде он идет под названием example3.c). Для начала хочу сказать, что всё делаю под виртуальной машиной (убунту 13.04) с такими параметрами:
$ uname -a
Linux ubuntu-VirtualBox 3.8.0-19-generic #29-Ubuntu SMP Wed Apr 17 18:19:42 UTC 2013 i686 i686 i686 GNU/Linux

Также, перед тем как что-либо делать выключил ASLR:
echo 0 | sudo tee /proc/sys/kernel/randomize_va_space

Компилирую, выключив защиту стека:
gcc -fno-stack-protector -z execstack -o example3 example3.c

Вот собственно код этого примера (example3.c):
void function(int a, int b, int c) {
   char buffer1[5];
   char buffer2[10];
   int *ret;

   ret = buffer1 + 12;
   (*ret) += 8;
}

void main() {
  int x;

  x = 0;
  function(1,2,3);
  x = 1;
  printf("%d\n",x);
}


Из main вызывается функция, в которой перезаписывается return адресс, т.е. в конечном итоге мы должны перепрыгнуть инструкцию x=1; и на экран должно вывестить "0". Логика примера проста: перед buffer1 (выше по стеку) находится return адрес. Так как buffer1[5] занимает 8 байт (2 слова по 4 байта) и так как SFP занимает еще 4 байта (находится перед buffer1), мы находим этот return адрес, прибавив 12 к адресу buffer1:
ret = buffer1 + 12;
Далее к значению получившегося адреса прибавляем 8 (инструкция x=1), чтобы эту инструкцию не выполнять.
Всё бы хорошо, но на экран всё-таки выводится "1" (без Segmentation Fault). Тогда я решил посмотреть asm код, который генерирует компилятор:
gcc -S -o example3.s example3.c
Получилось следущее:
example3.s
.file	"example3.c"
.text
.globl	function
.type	function, @function
function:
.LFB0:
	.cfi_startproc
	pushl	%ebp
	.cfi_def_cfa_offset 8
	.cfi_offset 5, -8
	movl	%esp, %ebp
	.cfi_def_cfa_register 5
	subl	$40, %esp
	movl	%gs:20, %eax
	movl	%eax, -12(%ebp)
	xorl	%eax, %eax
	leal	-22(%ebp), %eax
	addl	$18, %eax
	movl	%eax, -28(%ebp)
	movl	-28(%ebp), %eax
	movl	(%eax), %eax
	leal	8(%eax), %edx
	movl	-28(%ebp), %eax
	movl	%edx, (%eax)
	movl	-12(%ebp), %eax
	xorl	%gs:20, %eax
	je	.L2
	call	__stack_chk_fail
.L2:
	leave
	.cfi_restore 5
	.cfi_def_cfa 4, 4
	ret
	.cfi_endproc
.LFE0:
	.size	function, .-function
	.section	.rodata
.LC0:
	.string	"%d\n"
	.text
	.globl	main
	.type	main, @function
main:
.LFB1:
	.cfi_startproc
	pushl	%ebp
	.cfi_def_cfa_offset 8
	.cfi_offset 5, -8
	movl	%esp, %ebp
	.cfi_def_cfa_register 5
	andl	$-16, %esp
	subl	$32, %esp
	movl	$0, 28(%esp)
	movl	$3, 8(%esp)
	movl	$2, 4(%esp)
	movl	$1, (%esp)
	call	function
	movl	$1, 28(%esp)
	movl	28(%esp), %eax
	movl	%eax, 4(%esp)
	movl	$.LC0, (%esp)
	call	printf
	leave
	.cfi_restore 5
	.cfi_def_cfa 4, 4
	ret
	.cfi_endproc
.LFE1:
	.size	main, .-main
	.ident	"GCC: (Ubuntu/Linaro 4.7.3-1ubuntu1) 4.7.3"
	.section	.note.GNU-stack,"",@progbits


В самой функции function, резервируется 40 байт вместо 20 как в примере:
pushl	%ebp
movl	%esp, %ebp
subl	$40, %esp

Как в примере получилось 20 мне понятно: (buffer1[5] = 8) + (buffer2[10] = 12) = 20. Но как у меня получилось 40 не совсем ясно. Тогда я попробовал в функции оставить просто buffer1[5] и увидел что резервируется 24 байта (откуда это число мне тоже не понятно). Ну да ладно, 24 + 4 = 28, т.е.
ret = buffer1 + 28;
Но увы, это тоже не работает. В связи с этим у меня 2 вопроса: почему мои значения отличаются от тех что из примера (или "почему не работает?!") и как сделать так, чтобы можно было воспроизвести хотябы простейшую buffer overflow атаку ?
  • Вопрос задан
  • 2768 просмотров
Пригласить эксперта
Ответы на вопрос 2
jcmvbkbc
@jcmvbkbc
"I'm here to consult you" © Dogbert
Вы потеряли -fno-stack-protector когда компилировали в ассемблер, в коде как раз присутствуют проверки целостности стека, а на стеке резервируется место для канарейки.
Ответ написан
@DancingOnWater
По ходу это как-то связано с 64битностью системы.

P.S. хотя я не понимаю, почему мы должны перепрыгнуть инструкцию. Область инструкций и область данных - две совершенные разные области в памяти

Блин туплю. На стеке внутри функции 1указатель + 10 чаров + 5 чаров + 3инта + указатель адреса выхода.

В случае 32-битной системы тебе нужно отмотать 3 инта(*4 байта = 12) и попасть на указатель выхода. В 32 битной системе его размерность тоже 4 байта (и вот здесь я пока не понимаю, почему мы меняем на +8 в примере), в 64 битной его размер = 8.
Черт, совсем забыл про разную длину инструкций в x86
Ответ написан
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Похожие вопросы