Содержание статьи
Авторы таска подготовили нам три файла:
- ELF-бинарь
gsoapNote
; - ELF-бинарь
libc-2.
;27. so - XML-файл
ns.
.wsdl
www
SOAP — это протокол на основе XML, который используется для удаленного вызова процедур (Remote Procedure Call). Файл ns.
(Web Services Description Language) описывает доступ к вызываемым процедурам. Наша задача — получить удаленное исполнение кода в SOAP-сервисе gsoapNote
.
Авторы таска намекают, что gsoapNote
запускается на тачке с Linux, где загружена библиотека libc-2.
. Поэтому сразу заставляем отладчик GDB загружать конкретно этот бинарь при старте сервиса. В .
добавим
user@ubuntu: cat .gdbinit
...
set exec-wrapper env 'LD_PRELOAD=./libc-2.27.so'
Извлечем из ns.
информацию c помощью утилиты SOAPUI.
![](https://static.xakep.ru/images/2ede3f5ef67c63d5dca6b9e161ad95ac/25546/soap-ui-first-request.png)
SOAPUI автоматически формирует XML-шаблон запроса RPC. Мы сразу видим, на каком сетевом интерфейсе и порте стартует сервер — localhost:
, а также имя RPC-метода — handleCommand(
. SOAPUI ничего не знает об аргументах, поэтому на их месте стоит знак вопроса.
Запускаем gsoapNote
и через SOAPUI отправляем неполноценный шаблон. Получаем осмысленный ответ от сервера, закодированный в Base64:
<resultCode>2</resultCode>
Наверно, двойка — это оценка нашего запроса, который не понравился gsoapNote
!
Взглянем на бинарные митигации.
![](https://static.xakep.ru/images/2ede3f5ef67c63d5dca6b9e161ad95ac/25585/checksec.png)
Из плохих новостей — стековые канарейки защищают gsoapNote
от переполнения буфера на стеке (2 — Canary found), NX делает некоторые страницы памяти неисполняемыми (3 — NX enabled).
Хороших новостей гораздо больше: строка RELRO
говорит о том, что мы можем переписывать адреса функций из shared-библиотек (1 — Partial
) и эти адреса не будут рандомизироваться (4 — No
), а это хорошее подспорье для перехвата управления. Ну и самая хорошая новость — в бинарнике есть символы (5 — 817
), значит, у нас будут хотя бы сигнатуры функций, а это очень облегчит реверс.
Реверс-инжиниринг
handleCommand
Не забываем, что gsoapNote
собран с символами, поэтому сразу грузим его в «Иду» и прыгаем к функции handleCommand(
.
info
Очень удобное расположение окон в «Иде» я подсмотрел у ребят с OALabs: окна с дизассемблерным и декомпилированным листингами разделяют воркспейс пополам и синхронизируются между собой.
Декомпилированный листинг не то чтобы ужасный, но понять, что происходит, сложно. Цикл for
, куча вложенных if
, функция executeCommand(
принимает девять аргументов...
![](https://static.xakep.ru/images/2ede3f5ef67c63d5dca6b9e161ad95ac/25583/handle-command.png)
Давай будем рассматривать листинг как картину импрессионистов — отойдем на пару шагов назад и поищем общие паттерны.
Во‑первых, сразу в нескольких местах видим, что если в if
условие не выполняется, то локальной переменной v11
присваивается значение 2
и пропускается куча кода. А двойка — это как раз тот result code, который пришел в ответ на шаблон SOAPUI. Все сходится.
![](https://static.xakep.ru/images/2ede3f5ef67c63d5dca6b9e161ad95ac/25582/handle-command-ret-code-2.png)
Очень много кода выполняется внутри цикла for
. Обратим наше внимание на него.
![](https://static.xakep.ru/images/2ede3f5ef67c63d5dca6b9e161ad95ac/25581/handle-command-for-cycle.png)
Перед циклом вызывается функция xmlDocGetRootElement(
, возвращенная структура используется в цикле for
. Разыменовывается оффсет +24 для инициализации счетчика и оффсет +48 для итерации.
Вместо исследования внутренностей функции xmlDocGetRootElement(
посмотрим примеры исходного кода с ее использованием. В этом нам поможет grep.app. Этот сайт покажет примеры исходного кода качественных репозиториев с интересующей нас функцией.
![](https://static.xakep.ru/images/2ede3f5ef67c63d5dca6b9e161ad95ac/25580/grep-app.png)
По ссылке прыгаем в репозиторий проекта (я выбрал lastpass) и вникаем в исходный код:
// lastpass xml.c source code...#include <libxml/parser.h>#include <libxml/tree.h>...xmlNode *root;root = xmlDocGetRootElement(doc);...
Ага... значит, xmlDocGetRootElement(
возвращает указатель на структуру типа xmlNode
, а сама структура определена в libxml
. Гуглим libxml
и находим устройство структуры xmlNode. Особенно нас интересуют оффсеты, которые мы встретили в декомпиленном листинге.
![](https://static.xakep.ru/images/2ede3f5ef67c63d5dca6b9e161ad95ac/25579/xmlnode-definition.png)
Перенесем это знание в «Иду». Создаем структуру xmlNode
по тому же принципу.
![](https://static.xakep.ru/images/2ede3f5ef67c63d5dca6b9e161ad95ac/25545/xmlnode-ida-struct.png)
info
Необязательно восстанавливать структуру полностью один в один. Делаем это до нужного нам оффсета +48 (
.
Теперь возвращаемся в декомпилированный листинг и присваиваем локальной переменной v16
тип указателя на xmlNode
.
И все стало гораздо лучше! То, что происходит в цикле for
, теперь как на ладони! Наш gsoapNote
парсит XML: достает рутовую ноду, инициализирует счетчик его потомком xmlNode->
и обходит соседние ноды этого потомка при помощи xmlNode->
. И strcmp(
теперь обрел смысл: сравнивается имя ноды curXmlNode->
с захардкоженной строкой "array"
.
![](https://static.xakep.ru/images/2ede3f5ef67c63d5dca6b9e161ad95ac/25573/handle-command-xmlnode-defined.png)
Обрати внимание на функцию parseArray(
, она принимает текущую XML-ноду и зануленный двадцатибайтовый массив. Зануляется он, скорее всего, потому, что в него будет записан результат парсинга, не зря же функция parseArray(
имеет такое название.
Еще обратим внимание на то, как код возврата parseArray(
влияет на поток выполнения: если ноль, то вызывается функция с любопытным названием executeCommand(
, в противном случае обрабатываем следующую ноду.
Определенно нам надо попасть в executeCommand(
, но для этого нужно распарсить массив с нулевым кодом возврата.
parseArray
Функция принимает два аргумента: a1
и a2
. Мы уже выяснили, что первый — указатель на xmlNode
, а второй — зануленный байтовый массив. Давай посмотрим, что происходит с этим массивом. Для этого в декомпилированном листинге смотрим кросс‑референсы на a2
.
![](https://static.xakep.ru/images/2ede3f5ef67c63d5dca6b9e161ad95ac/25577/20-bytes-array-xrefs.png)
Сразу подмечаем оффсеты: +0, +8, +16, +24. Каждый следующий оффсет больше предыдущего на размер QWORD (8 байт). Это не 0x20-байтовый массив, а массив из четырех QWORD. К 85-й строке вся структура инициализирована.
По оффсетам +0 и +16 будут указатели на строки, по +8 и +24 будут int.
Предположим, что в эту структуру из четырех QWORD заносится результат парсинга. Назовем ее PARSE_RESULT
.
![](https://static.xakep.ru/images/2ede3f5ef67c63d5dca6b9e161ad95ac/25576/parse-result-ida-struct.png)
Присвоим аргументу a1
тип xmlNode
, а аргументу a2
тип PARSE_RESULT
и снова взглянем на parseArray(
. Внутри опять видим много однообразного кода парсинга XML. Поскольку мы удачно определили структуры входных аргументов, читаем декомпиленный листинг практически как исходный код.
![](https://static.xakep.ru/images/2ede3f5ef67c63d5dca6b9e161ad95ac/25575/parsearray-integer.png)
Анализируя parseArray(
дальше, мы получаем практически целостную картину того, что нужно вставить вместо многозначительного знака вопроса. Не буду утомлять тебя дальнейшим разбором, а просто покажу, как выглядит ожидаемый XML.
![](https://static.xakep.ru/images/2ede3f5ef67c63d5dca6b9e161ad95ac/25572/parse-array-test-soapui.png)
info
Значение ноды <
должно быть равно количеству нод внутри <
минус один.
Правильное определение используемых структур позволило достать много информации из статического анализа gsoapNote
. Давай продолжать исследование в динамике. Отправим XML на рисунке выше, поставим брейк‑пойнт на выходе из parseArray(
(по адресу 0x403BE2) и посмотрим, что лежит в PARSE_RESULT
после парсинга XML из запроса выше:
#
pwndbg>
0x7ffffffd5a90:
0x7ffffffd5aa0:
pwndbg>
0x6562a0:
pwndbg>
0x7ffffffd5a98:
pwndbg>
0x6575b0:
pwndbg>
0x7ffffffd5aa8:
#
#
pwndbg>
rax
С удовольствием наблюдаем, как контролируемые нами данные оседают в памяти. Все поля структуры PARSE_RESULT
инициализированы, значит, выполнился почти весь код функции parseArray(
, но код возврата -1, а это не дает нам двигаться дальше...
Давай разбираться. Можно, конечно, пошагово выполнить код в отладчике, но это долго. Мы сделаем это быстро, одним выстрелом — подсветим выполненный код с помощью DynamoRIO.
DynamoRIO — это фреймворк для разработки инструментов динамического анализа. Нам понадобится встроенный в него инструмент drcov, который покажет нам все выполненные инструкции.
info
Если вдруг захочешь решать эту проблему в отладчике, то советую использовать кастомную команду для GDB step before. Вводишь в консоль sb
и брякаешься перед инструкцией call
. Это ускорит процесс отладки.
Запускаем gsoapNote
под инструментацией drcov
следующим образом:
<path to DynamoRIO>/bin64/drrun -t drcov — gsoapNote
Снова отсылаем XML через SOAPUI и завершаем процесс.
Наш drrun
сгенерировал файл drcov.
, в котором содержатся адреса выполненных инструкций. Для просмотра этого файла будем использовать плагин lighthouse для IDA.
info
На момент написания статьи последняя версия DynamoRIO — 9.0, c drcov
версии 3, но lighthouse не может распарсить файл третьей версии, поэтому я использую версию 8.0 с drcov
версии 2.
![](https://static.xakep.ru/images/2ede3f5ef67c63d5dca6b9e161ad95ac/25571/parse-array-drcov.png)
Зеленым цветом выделяются выполненные инструкции. На скрине выше приведен момент выхода с кодом возврата -1. И произошло это потому, что значение внутри <
не равно длине строки <
. В SOAPUI корректируем XML, пролетаем parseArray(
и попадаем в executeCommand(
. Успех.
![](https://static.xakep.ru/images/2ede3f5ef67c63d5dca6b9e161ad95ac/25544/dynamorio-answers-questions.png)
Продолжение доступно только участникам
Вариант 1. Присоединись к сообществу «Xakep.ru», чтобы читать все материалы на сайте
Членство в сообществе в течение указанного срока откроет тебе доступ ко ВСЕМ материалам «Хакера», позволит скачивать выпуски в PDF, отключит рекламу на сайте и увеличит личную накопительную скидку! Подробнее
Вариант 2. Открой один материал
Заинтересовала статья, но нет возможности стать членом клуба «Xakep.ru»? Тогда этот вариант для тебя! Обрати внимание: этот способ подходит только для статей, опубликованных более двух месяцев назад.
Я уже участник «Xakep.ru»