Как пропустить строку при переполнении буфера в C

Я хочу пропустить строку в 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;  
}

person Percy    schedule 12.03.2011    source источник
comment
Я не совсем уверен, что должен помогать вам отлаживать ваши стратегии атаки, но вы должны выгрузить x/i10 стек $esp и добавить это к вашему объяснению. Также покажите, что происходит, когда вы переходите к инструкциям с самого начала function.   -  person Alex Brown    schedule 12.03.2011
comment
Я предлагаю вам игнорировать вывод вашей программы и просто выполнить его в отладчике пошагово, по одной инструкции сборки за раз. Вы увидите, что он на самом деле делает (регистрация значений, стек) на каждом этапе, и это скажет вам, почему он не делает то, что вы ожидаете.   -  person Gabe    schedule 12.03.2011
comment
Пожалуйста, объясните, почему вы хотите это сделать.   -  person Jim Balter    schedule 12.03.2011
comment
В старые добрые времена авторы вредоносных программ перед завтраком писали о переполнении буфера. В настоящее время они публикуют вопросы на Stackoverflow. Что грядет мир, я тоже спрашиваю?   -  person David Heffernan    schedule 12.03.2011
comment
@jim: по словам моего профессора, это старая проблема, и у большинства ОС есть стратегии, позволяющие избежать этой атаки, я всего лишь студент, пытающийся понять, как указатели перемещаются по регистру.   -  person Percy    schedule 12.03.2011
comment
@tony Ни у одной ОС нет стратегии, позволяющей помешать вам изменить адрес возврата функции, из которой вы возвращаетесь ... это будет зависеть от архитектуры машины, имеющей стек вызовов только для чтения. Но в любом случае вы идете не в ту сторону... адрес возврата приходит до буфера, а не после. И вы добавляете к символу, но адрес возврата не является символом, так что это может добавить или не добавить 8 к адресу.   -  person Jim Balter    schedule 12.03.2011


Ответы (4)


Вы, должно быть, читаете статью Разрушение стека ради удовольствия и прибыли. Я читал ту же статью и обнаружил ту же проблему, что не пропускал эту инструкцию. После нескольких часов отладки в 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();
}
person caltuntas    schedule 25.03.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

Если у вас возникли трудности с пониманием того, как работает стек вызовов, прочтите стек вызовов и пролог функции могут помочь статьи Википедии. Это показывает сложность создания примеров "переполнения буфера" в 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], чтобы перезаписать адрес возврата. Однако в большинстве современных реализаций для защиты от этого используются такие методы, как стековые канарейки.

person jschmier    schedule 06.04.2011
comment
исправление: ret = (int *)(buffer + 24); должно быть ret = (int *)(buffer + 6); C умножает 24 на 4, чтобы получить совместимый указатель, который дает нам 96. но смещение равно 24, поэтому нам нужно 6 вместо 24 (6*4=24). также вы можете использовать позицию переданных аргументов (в данном случае a, b, c) для достижения требуемой позиции в стеке вместо объявления буфера. @jschmier - person DevX; 13.05.2018
comment
(не уверен, что отметил тебя) - person DevX; 13.05.2018

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

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

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

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

person Johannes Rudolph    schedule 12.03.2011
comment
Я не уверен, сколько байтов указатели перемещаются из одного регистра в другой, поэтому я не знаю, если эти строки - ret = буфер + 12; (*возврат) += 8; - и числа 12 и 8 и правильно пропустить x=1; по адресу 0x00000000004002fb - person Percy; 12.03.2011
comment
@tony сколько байтов указатели перемещаются из одного регистра в другой Вы очень-очень запутались. - person Jim Balter; 12.03.2011

Вы не можете сделать это таким образом. Вот классический пример кода переполнения буфера. Посмотрите, что произойдет, если вы наберете 5, а затем 6 символов с клавиатуры. Если вы выберете больше (должно быть 16 символов), вы перезапишете базовый указатель, затем функция вернет адрес, и вы получите ошибку сегментации. Что вы хотите сделать, так это выяснить, какие 4 символа перезаписывают адрес возврата. и заставить программу выполнить ваш код. Погуглите вокруг стека 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;
 }
person hlynur    schedule 12.03.2011