Как записать, что переполнение буфера использует в 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("%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 )

8
задан MSalters 30 March 2010 в 13:33
поделиться

6 ответов

Как 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 , я бы посоветовал прочитать некоторые из статей, которые я упомянул в мой ответ на ваш предыдущий вопрос и / или другой материал по теме. Хорошее понимание того, как именно работает этот тип эксплойтов, должно помочь вам писать более безопасный код .

2
ответ дан 5 December 2019 в 21:17
поделиться

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

0
ответ дан 5 December 2019 в 21:17
поделиться

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

2
ответ дан 5 December 2019 в 21:17
поделиться

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

1
ответ дан 5 December 2019 в 21:17
поделиться

Этот код выводит 1 и на OpenBSD и FreeBSD, и дает ошибку сегментации на Linux.

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

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

1
ответ дан 5 December 2019 в 21:17
поделиться

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

Таким образом, он пытается "пропустить"

x = 1;

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

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

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

Edit:

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

1
ответ дан 5 December 2019 в 21:17
поделиться
Другие вопросы по тегам:

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