Как да пропуснете ред, извършвайки препълване на буфер в C

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

Също така съм конфигурирал опциите среда за рандомизиране и 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 Никоя ОС няма никаква стратегия, която да ви попречи да промените адреса за връщане на функцията, от която се връщате ... това би зависило от архитектура на машина, която има стек за извиквания само за четене. Но във всеки случай вървите в грешна посока ... адресът за връщане идва преди буфера, а не след. И вие добавяте към char, но адресът за връщане не е char, така че това може или не може да добави 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 с радост ще вмъкне stack canary днес, освен ако не му кажете да не го прави.

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], за да презапишете адреса за връщане. Повечето съвременни реализации обаче използват техники като stack canary за защита срещу това.

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; (*ret) += 8; - и числата 12 и 8 и са правилни за пропускане на x=1; на адрес 0x00000000004002fb - person Percy; 12.03.2011
comment
@tony колко байта указатели се преместват от един регистър в друг. Много си много объркан. - person Jim Balter; 12.03.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;
 }
person hlynur    schedule 12.03.2011