Как написать эксплойт переполнения буфера в GCC, windows XP, x86?

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("%dn",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 <main+0>:    push   %ebp
0x004012ef <main+1>:    mov    %esp,%ebp
0x004012f1 <main+3>:    sub    x18,%esp
0x004012f4 <main+6>:    and    xfffffff0,%esp
0x004012f7 <main+9>:    mov    x0,%eax
0x004012fc <main+14>:   add    xf,%eax
0x004012ff <main+17>:   add    xf,%eax
0x00401302 <main+20>:   shr    x4,%eax
0x00401305 <main+23>:   shl    x4,%eax
0x00401308 <main+26>:   mov    %eax,0xfffffff8(%ebp)
0x0040130b <main+29>:   mov    0xfffffff8(%ebp),%eax
0x0040130e <main+32>:   call   0x401b00 <_alloca>
0x00401313 <main+37>:   call   0x4017b0 <__main>
0x00401318 <main+42>:   movl   x0,0xfffffffc(%ebp)
0x0040131f <main+49>:   movl   x3,0x8(%esp)
0x00401327 <main+57>:   movl   x2,0x4(%esp)
0x0040132f <main+65>:   movl   x1,(%esp)
0x00401336 <main+72>:   call   0x4012d0 <function>
0x0040133b <main+77>:   movl   x1,0xfffffffc(%ebp)
0x00401342 <main+84>:   mov    0xfffffffc(%ebp),%eax
0x00401345 <main+87>:   mov    %eax,0x4(%esp)
0x00401349 <main+91>:   movl   x403000,(%esp)
0x00401350 <main+98>:   call   0x401b60 <printf>
0x00401355 <main+103>:  leave
0x00401356 <main+104>:  ret
0x00401357 <main+105>:  nop
0x00401358 <main+106>:  add    %al,(%eax)
0x0040135a <main+108>:  add    %al,(%eax)
0x0040135c <main+110>:  add    %al,(%eax)
0x0040135e <main+112>:  add    %al,(%eax)
End of assembler dump.

Dump of assembler code for function function:
0x004012d0 <function+0>:        push   %ebp
0x004012d1 <function+1>:        mov    %esp,%ebp
0x004012d3 <function+3>:        sub    x38,%esp
0x004012d6 <function+6>:        lea    0xffffffe8(%ebp),%eax
0x004012d9 <function+9>:        add    xc,%eax
0x004012dc <function+12>:       mov    %eax,0xffffffd4(%ebp)
0x004012df <function+15>:       mov    0xffffffd4(%ebp),%edx
0x004012e2 <function+18>:       mov    0xffffffd4(%ebp),%eax
0x004012e5 <function+21>:       movzbl (%eax),%eax
0x004012e8 <function+24>:       add    x5,%al
0x004012ea <function+26>:       mov    %al,(%edx)
0x004012ec <function+28>:       leave
0x004012ed <function+29>:       ret

в моем случае, расстояние должно быть - = 5,верно?Но, кажется, нет. рабочий..

почему function должен 56 байт для локальных переменных?( sub x38,%esp )

6 ответов


As joveha указал, значение EIP, сохраненное в стеке (обратный адрес) с помощью call инструкция должна быть увеличена на 7 байт (0x00401342 - 0x0040133b = 7) для того, чтобы пропустить x = 1; инструкции (movl x1,0xfffffffc(%ebp)).

вы правы, что 56 байт зарезервированы для локальных переменных (sub x38,%esp), поэтому недостающая часть - сколько байтов прошло buffer1 в стеке находится сохраненный EIP.


A бит тестового кода и встроенной сборки говорит мне, что магическое значение 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 для определения этого значение.

(скомпилировано w/-g включить отладочные символы)

(gdb) break function
...
(gdb) run
...
(gdb) p $ebp
 = (void *) 0x22ff28
(gdb) p &buffer1
 = (char (*)[5]) 0x22ff10
(gdb) quit

(0x22ff28 + 4) - 0x22ff10 = 28

(значение ebp + размер слова) - адрес buffer1 = количество байтов


кроме Разбивая Стек Для Удовольствия И Прибыли, я бы предложил прочитать некоторые из статей, которые я упомянул в мой ответ на ваш предыдущий вопрос и / или другие материалы по этому вопросу. Имея хорошее понимание того, как именно этот тип эксплойта работает, вы должны помочь вам писать более безопасный код.


трудно предсказать, что buffer1 + 12 действительно указывает на. Ваш компилятор может поставить buffer1 и buffer2 в любом месте на стеке он желает, даже идя так далеко, чтобы не сэкономить место для buffer2 на всех. Единственный способ действительно знать, где buffer1 goes-это посмотреть на вывод ассемблера вашего компилятора, и есть хороший шанс, что он будет прыгать с разными настройками оптимизации или разными версиями одного и того же компилятора.


Я еще не тестирую код на своей машине, но вы приняли во внимание выравнивание памяти? Попробуйте разобрать код с помощью gcc. Я думаю, что код сборки может дать вам дальнейшее понимание кода. :-)


этот код печатает 1 также на OpenBSD и FreeBSD и дает ошибку сегментации в Linux.

этот вид эксплойта сильно зависит как от набора инструкций конкретной машины, так и от соглашений о вызовах компилятора и операционной системы. Все о макете стека определяется реализацией, а не языком C. В статье предполагается Linux на x86, но похоже, что вы используете Windows, и ваша система может быть 64-разрядной, хотя вы можете переключить gcc на 32-бит с помощью -m32.

параметры, которые вам придется настроить, - это 12, что является смещением от кончика стека до обратного адреса, и 8, сколько байтов main вы хотите перепрыгнуть. Как говорится в статье, вы можете использовать gdb для проверки разборки функции, чтобы увидеть (a), как далеко стек выталкивается при вызове function и (b) смещения байтов инструкций в main.


часть + 8 байтов - это то, насколько он хочет сохранить EIP с увеличением. EIP был сохранен, чтобы программа могла вернуться к последнему заданию после function готово-теперь он хочет пропустить его, добавив 8 байтов к сохраненному EIP.

поэтому все, что он пытается, это "пропустить"

x = 1;

в вашем случае сохраненный EIP будет указывать на 0x0040133b первая инструкция После function возвращает. Чтобы пропустить назначение, вам нужно сделать сохраненную точку EIP 0x00401342. Это 7 байт.

это действительно "беспорядок с RET EIP", а не пример переполнения буфера.

и что касается 56 байтов для локальных переменных, это может быть все, что ваш компилятор придумает, например, заполнение, канарейки стека и т. д.

Edit:

это показывает, как трудно сделать примеры переполнения буфера в C. смещение 12 от buffer1 предполагает определенный стиль заполнения и параметры компиляции. GCC с радостью вставит стек канарейки в настоящее время (который становится локальной переменной, которая "защищает" сохраненный EIP), если вы не скажете ему не делать этого. Кроме того, новый адрес, на который он хочет перейти (инструкция start для printf вызов) действительно должен быть разрешен вручную из сборки. В его случае, на его Мачи, с его ОС, с его компилятором, в тот день.... это было 8.


вы компилируете программу на C с помощью компилятора C++. Переименовать hw.cpp-hw.c и вы найдете, что он будет компилироваться.