Как пропустить строку, делающую переполнение буфера в C - PullRequest
12 голосов
/ 12 марта 2011

Я хочу пропустить строку в C, строку x=1; в главном разделе, используя bufferoverflow;однако я не знаю, почему я не могу пропустить адрес с 4002f4 на следующий адрес 4002fb, несмотря на то, что я считаю 7 байтов от <main+35> до <main+42>.

Я также настроил параметры среды randomniZation и execstack в среде Debian и AMD, но я все еще получаю x=1;.Что не так с этой процедурой?

Я использовал dba для отладки стека и адресов памяти:

0x00000000004002ef <main+30>:    callq  0x4002a4 **<function>**  
**0x00000000004002f4** <main+35>:    movl   $0x1,-0x4(%rbp)  
**0x00000000004002fb** <main+42>:    mov    -0x4(%rbp),%esi  
0x00000000004002fe <main+45>:    mov    $0x4629c4,%edi  

void function(int a, int b, int c)  
{
  char buffer[5];
  int *ret;

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

int main()
{
   int x = 0; 
   function(1, 2, 3);
   x = 1;
   printf("x = %i \n", x);  
   return 0;  
}

Ответы [ 4 ]

4 голосов
/ 06 апреля 2011

Чтобы изменить обратный адрес в пределах function(), чтобы пропустить x = 1 в main(), вам нужно две части информации.

1. Расположение адреса возврата в кадре стека.

Я использовал gdb для определения этого значения. Я устанавливаю точку останова на function() (break function), выполняю код до точки останова (run), извлекаю местоположение в памяти текущего кадра стека ( p $rbp или info reg), а затем получить местоположение в памяти buffer (p &buffer). Используя полученные значения, можно определить местоположение обратного адреса.

(скомпилированный с GCC -g флаг, включающий символы отладки и выполненный в 64-битной среде)

(gdb) break function
...
(gdb) run
...
(gdb) p $rbp
$1 = (void *) 0x7fffffffe270
(gdb) p &buffer
$2 = (char (*)[5]) 0x7fffffffe260
(gdb) quit

(адрес указателя кадра + размер слова) - адрес буфера = количество байтов от локальной переменной буфера до адреса возврата
(0x7fffffffe270 + 8) - 0x7fffffffe260 = 24

Если вы испытываете затруднения в понимании того, как работает стек вызовов, может помочь чтение прологов стек *1043* и Статьи Википедии. Это показывает сложность создания примеров «переполнения буфера» на языке C. Смещение 24 от buffer предполагает определенный стиль заполнения и параметры компиляции. GCC с радостью вставит стека канареек в наше время, если вы не скажете этого.

2. Количество байтов, добавляемых к адресу возврата, чтобы пропустить x = 1.

В вашем случае указатель сохраненной инструкции будет указывать на 0x00000000004002f4 (<main+35>), первая инструкция после функции возвращается. Чтобы пропустить присваивание, необходимо указать указатель сохраненной инструкции на 0x00000000004002fb (<main+42>).

Ваш расчет, что это 7 байтов, является правильным (0x4002fb - 0x4002fb = 7 ).

Я использовал gdb для дизассемблирования приложения (disas main) и также проверил расчет для моего случая. Это значение лучше всего определить вручную, изучив разборку.


Обратите внимание, что я использовал 64-битную среду Ubuntu 10.10 для тестирования следующего кода.

#include <stdio.h>

void function(int a, int b, int c)  
{
    char buffer[5];
    int *ret;

    ret = (int *)(buffer + 24);
    (*ret) += 7; 
}

int main()
{
     int x = 0; 
     function(1, 2, 3);
     x = 1;
     printf("x = %i \n", x);  
     return 0;  
}

выход

x = 0


Это действительно просто изменение адреса возврата function(), а не фактическое переполнение буфера. При фактическом переполнении буфера вы бы переполняли buffer[5], чтобы перезаписать адрес возврата. Тем не менее, большинство современных реализаций используют такие методы, как стеки канареек для защиты от этого.

4 голосов
/ 26 марта 2011

Вы, должно быть, читаете статью Smashing the Stack для удовольствия и получения прибыли . Я читал ту же статью и обнаружил ту же проблему: она не пропускала эту инструкцию. После нескольких часов отладки в IDA я изменил код, как показано ниже, и он печатает x = 0 и b = 5.

#include <stdio.h>

void function(int a, int b) {
     int c=0;
     int* pointer;

     pointer =&c+2;
     (*pointer)+=8;
}

void main() {
  int x =0;
  function(1,2);
  x = 3;
  int b =5;
  printf("x=%d\n, b=%d\n",x,b);
  getch();
}
2 голосов
/ 12 марта 2011

То, что вы здесь делаете, похоже, не сильно подходит для классической атаки переполнения буфера. Идея атаки с переполнением буфера заключается в том, чтобы изменить адрес возврата функции. Разборка вашей программы покажет вам, откуда берется инструкция ret (при условии, что x86). Это то, что вам нужно изменить, чтобы указать на main+42.

Я предполагаю, что вы хотите явно спровоцировать переполнение буфера здесь, обычно вам нужно спровоцировать его, манипулируя входами 'function'.

Просто объявив buffer[5], что вы перемещаете указатель стека в неправильном направлении (проверьте это, посмотрев на сгенерированную сборку), обратный адрес находится где-то глубже в стеке (он был помещен туда инструкцией вызова ). В x86 стеки растут вниз, то есть к более низким адресам.

Я подойду к этому, объявив int* и переместив его вверх, пока я не окажусь на указанном адресе, куда был возвращен обратный адрес, а затем изменим это значение, указав main+42, и разрешим функции ret .

1 голос
/ 12 марта 2011

Вы не можете сделать это таким образом.Вот классический пример кода переполнения буфера.Посмотрите, что произойдет, если вы наберете 5, а затем 6 символов с клавиатуры.Если вы выберете больше (16 символов), вы перезапишете базовый указатель, затем адрес возврата функции, и вы получите ошибку сегментации.Что вы хотите сделать, это выяснить, какие 4 символа перезаписывают возвращаемый адрес.и заставить программу выполнить ваш код.Google вокруг стека Linux, структура памяти.

 void ff(){
     int a=0; char b[5];
     scanf("%s",b);
     printf("b:%x a:%x\n" ,b ,&a);
     printf("b:'%s' a:%d\n" ,b ,a);
 }

 int main() {
     ff();
     return 0;
 }
...