- PVSM.RU - https://www.pvsm.ru -

Нашумевшие события последних месяцев наглядно показывают, насколько актуальной является проблема критических уязвимостей в программном обеспечении. Массовые атаки на пользователей с помощью вирусов-шифровальщиков WannaCry [1] и Petya [2]осуществлялись путем удалённой эксплуатации уязвимостей нулевого дня в сетевых сервисах Windows – SMBv1 и SMBv2. Нахождение и эксплуатация уязвимостей для удаленного выполнения кода – задачка явно не из легких. Однако лучшим из лучших специалистов по информационной безопасности всё по зубам!
В одном из заданий очного тура NeoQuest-2017 [3] необходимо было найти уязвимости в доступной по сети программе-интерпретаторе команд, и с помощью их эксплуатации выполнить код на удаленном сервере. Для доказательства взлома нужно было прочитать содержимое файлового каталога на сервере – там, по условию, размещался ключ задания. Участникам был доступен бинарный файл интерпретатора. Итак, лёд тронулся!
Для начала запустим бинарник [4] и посмотрим, что он собой представляет. Становится ясно, что исполняемый файл интерпретатора формата PE и предназначен для архитектуры Intel x64. Также ясно, что он скомпилирован с поддержкой DEP/ASLR [5], но без CFG [6]. Сторонние библиотеки также не подгружаются.

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

С поверхностным анализом закончено, пора реверсить – тур-то очный, время поджимает!
Первая промежуточная задача, которую необходимо решить – точное определение поверхности атаки [7]. Уточним количество и формат команд, загрузив бинарник в IDA Pro. Функция main легко находится путем поиска выводимых на экран строк.
Анализ функции main позволяет утверждать следующее:
1. 1. Сначала адрес массива array на стеке функции main заносится в переменную array_base. В цикле ячейки массива инициализируются нулями. Условие выхода из цикла позволяет узнать размер массива – 100 (0x64) целочисленных ячеек.

2. 2. Происходит вывод на экран приветствия и считывание команды пользователя. Присутствие строк «cls» и «whoami» говорит о вызове функции system (в дальнейшем это нам сильно пригодится).

3. Инициализируются переменные-регулярные выражения, для проверки корректности синтаксиса. Видно, что недокументированные команды отсутствуют. Кроме того, становится ясно множество допустимых параметров каждой инструкции.

4. Последовательно в бесконечном цикле производится проверка введенной команды на соответствие регулярным выражениям. Если соответствие какой-либо команде найдено – вычисляются аргументы у команды и вызывается её обработчик.


В результате анализа кода функции main выяснено, что недокументированных команд нет. Возможно влиять на следующие параметры:
В силу наличия защитных механизмов ASLR и DEP необходимо найти и реализовать 2 уязвимости:
ROP (Return Oriented Programming) – современная техника эксплуатации, направленная на обход защитного механизма DEP. Суть этой техники заключается в переиспользовании маленьких фрагментов (гаджетов) исполняемой памяти перед инструкциями ret. Выстроенные в цепочку, адреса гаджетов последовательно снимаются со стека, выполняя команды в гаджете вплоть до команды ret. Она, в свою очередь, снимает со стека адрес следующего гаджета, и т.д.
Теперь мы знаем, что целевой массив имеет константный размер и расположен на стеке функции main. Попробуем прочитать ячейку с индексом, выходящим за границы массива.

Сначала при величинах, больших размера массива, мы наблюдаем нормальное поведение – выход с сообщением об ошибке. Однако при значениях индекса больших, чем 2147483647, программа то падает, то выдает какие-то значения.

Похоже, что индексы, которые трактуются как отрицательные числа, проходят проверку границ и выдают содержимое памяти по отрицательному индексу вне границ массива. Уязвимость найдена!
Почему так происходит? Уязвимость кроется в неверной обработке индекса в командах set/get. Численный индекс в массиве считывается как параметр команды set и переводится из строковой в численную форму с помощью вызова stoul. Эта функция возвращает беззнаковое целое.

Однако при передаче параметра в функции SET/GET это же значение ошибочно приводится к знаковому целому – оно сравнивается с размером массива и влияет на результат команды знакового сравнения jl. Команда GET выдает значение ячейки памяти, отсчитанное от начала массива.

Эту возможность можно использовать для чтения из памяти какого-либо исполняемого адреса. Поскольку массив расположен на стеке, чтением ячеек массива с отрицательными индексами мы можем просматривать содержимое стека до расположения в нем массива. Поскольку архитектура – x64, адреса в памяти занимают 8 байт. Чтение же из массива осуществляется по 4 байта. Поэтому для чтения адреса из памяти необходимо напечатать две подряд идущие ячейки.
С помощью отладчика экспериментально находим такие ячейки памяти перед буфером, где лежит исполняемый адрес – например, 4294967282 == -14 и 4294967283 == -13. Их значения мы далее используем при построении ROP-цепи.

Кроме того, в дальнейшем при эксплуатации нам понадобится адрес самого буфера на стеке. Как его найти? Взглянем на начало функции main и увидим, что переменная array_base хранит указатель на начало буфера.

На стеке эта переменная расположена по адресу rsp+0x358-0x328, а сам буфер начинается с адреса rsp+0x358-0x198.


Значит, необходимо отступить (0x328-0x198)/4 = 100 четырехбайтовых ячеек от начала массива, чтобы прочесть переменную array_base. Искомые смещения: 4294967196, 4294967197.

Благодаря данной уязвимости мы раскрыли исполняемый адрес в памяти процесса, а также выяснили адрес искомого буфера для размещения параметров ROP-цепи. Теперь необходимо найти способ перехвата потока управления программы.
До сего момента мы не исследовали функцию интерпретатора load. Посмотрим на неё повнимательнее. Очень скоро здесь обнаруживается классическое переполнение буфера на стеке. Введенная с клавиатуры строка — аргумент команды load — выделяется и передается как первый параметр функции-обработчика LOAD. Вторым параметром идет адрес искомого буфера.

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

Передав достаточно большое количество символов на вход команде load, возможно переписать адрес возврата на стеке и с выходом из интерпретатора передать управление по контролируемому нами адресу. С учетом размера и расположения буфера на стеке, адрес первичной передачи управления необходимо размещать со смещением 4 * (100 + 2) байта (дополнительные 8 байт переписывают сохраненное на стеке значение регистра rbp).
Поскольку переполненный буфер расположен на стеке, а не в куче, то ROP-цепь будем располагать там же целиком – начиная с смещения 4 * (100 + 2) во входном параметре команды load.

Теперь все ингредиенты на месте. Пришло время собирать из них боевой эксплойт!
Для получения ключа задания необходимо прочитать и вывести на экран содержимое локального каталога на сервере. Ранее при анализе нами было замечено, что перед выводом приветствия содержимое экрана очищается, и в приветствии присутствует имя пользователя.

Очевидно, что функция, которая это делает и принимает в качестве параметров строки «cls» и «whoami», ведет себя подобно библиотечной функции system. Необходимо вызвать эту функцию, но в качестве выполняемой команды взять «dir» – вывод на экран содержимого локального каталога на сервере.
Построим ROP-цепь, позволяющую это сделать. Последовательность операций следующая:

При размещении адресов в буфере необходимо вспомнить о наличии ASLR, и вычислять их динамически, с использованием считанных ранее адресов исполняемой памяти и начала буфера. Кроме того, их запись в буфер осуществляется в соответствие с нотацией Little Endian, то есть, от младших байтов к старшим.
Теперь необходимо найти смещения нужных гаджетов в исполняемом файле интерпретатора. Первый необходим для помещения в rcx адреса строки «dir», второй — для вызова функции system_func. По смещениям 0x24c00 и 0x16ce6 соответственно в секции кода исполняемого файла интерпретатора находятся нужные участки кода.


Строку «dir» мы поместим после гаджетов. Её адрес, таким образом, будет на 4*(100 + 2) + 6*4 байт больше адреса буфера.
Ниже приведен наш вариант скрипта для генерации содержимого буфера:
from __future__ import print_function
import sys
n = 102
f=open("buf.txt", "w")
base_l = 0x2f150000 - 0x0 # get 4294967282
base_h = 0x7ff6 # get 4294967283
buffer_l = 0x0afcf6e0 # get 4294967196
buffer_h = 0xe8 # get 4294967197
def rev(x):
return ((x << 24) & 0xff000000 | (x << 8) & 0x00ff0000 | (x >> 8) & 0x0000ff00 | (x >> 24) & 0x000000ff)
arr = [
base_l + 0x24c00,
base_h, # pop rcx ; ret
buffer_l + n*0x4 + 6*0x4,
buffer_h, # buffer ptr - in rdi
base_l + 0x16ce6,
base_h # &system in our binary
]
for i in range(0,n): # dumb
print("%08x" % rev(0xdeadbeef), end='', file=f)
for i in arr:
print("%08x" % rev(i), end='', file=f)
# "dir"
print("%08x" % 0x64697200, end='', file=f)
f.close()
Всё готово, пора идти за ключом!

Искомый ключ: fb520eb552747437c09f2770a9a282ea.
В NeoQUEST [3]мы собираем самые разнообразные задания, требующие знаний из разных направлений ИБ и способные показать, чем чревато небрежное отношение к безопасности: слабые пароли, плохая реализация сервера (уязвимость в ней мы искали методом фаззинга и подробно описали в этой статье [8]), нестойкие шифры. А на примере данного задания хорошо видно, как небрежности в программировании могут нанести критический урон безопасности информации.
Автор: NWOcs
Источник [9]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/vulnerability/261987
Ссылки в тексте:
[1] WannaCry: http://malware.wikia.com/wiki/WannaCry
[2] Petya : https://www.avast.ru/c-petya
[3] очного тура NeoQuest-2017: http://neoquest.ru/timeline.php?year=2017&part=2
[4] запустим бинарник: https://yadi.sk/d/xH09BX973Lp6mp
[5] DEP/ASLR: https://blogs.technet.microsoft.com/srd/2010/12/08/on-the-effectiveness-of-dep-and-aslr/
[6] CFG: https://msdn.microsoft.com/ru-ru/library/windows/desktop/mt637065%28v=vs.85%29.aspx
[7] поверхности атаки: https://www.owasp.org/index.php/Attack_Surface_Analysis_Cheat_Sheet
[8] статье: https://habrahabr.ru/company/neobit/blog/321912/
[9] Источник: https://habrahabr.ru/post/335046/
Нажмите здесь для печати.