• XSS.stack #1 – первый литературный журнал от юзеров форума

Статья Radare2 с самого начала. Учимся использовать опенсорсный фреймворк для анализа приложений в Linux

lukas

(L3) cache
Пользователь
Регистрация
11.10.2018
Сообщения
282
Реакции
691
Если ты никогда не пользовался Radare2, то наверняка о нем слышал: это опенсорсный набор инструментов для исследования программ. В него входит мощный дизассемблер и отладчик, позволяющий погрузиться в систему глубже, чем стандартный GDB. В этой статье мы подробно поговорим о философии Radare2 и начнем работать с ним.

Radare2​

Как известно, Radare2 представляет собой глобально измененный форк первого Radare. В соответствии с законами мира Unix это набор узкоспециализированных утилит командной строки. Их можно применять как порознь, так и вместе: одна утилита использует результаты работы другой.

В набор входит более десятка утилит. Вот некоторые из них:
  • Radare2 — собственно главная тулза, она способна открывать большое количество разных источников ввода‑вывода подобно обычным файлам: диски, сетевые соединения, драйверы, процессы под отладкой. Позволяет при помощи командной строки перемещаться по файлу и заниматься дизассемблированием, поиском, анализом, изменением и сравнением данных с возможностью визуализации. Любой процесс поддается скриптингу на многих языках, среди которых JavaScript, Ruby, Python и Lua.
  • Rax2 — анализатор выражений. Умеет делать базовые преобразования между разными форматами: строками, значениями с плавающей запятой, шестнадцатеричными числами и прочими. Также поддерживает изменение порядка следования байтов.
  • Rafind2 — позволяет воспользоваться библиотекой r_search для поиска строк или байтов по маске.
  • Rabin2 — показывает всю информацию о двоичном файле: сведения о секциях, заголовках, импортированных данных и прочее. Понимает много форматов исполняемых файлов: PE, ELF, Mach-O, Java и другие. С помощью плагинов этот список можно расширить, после чего Rabin2 будет отображать символы импорта и экспорта, библиотечные зависимости, строки, адреса точек входа и тип архитектуры.
  • Rarun2 — позволяет настроить среду для выполнения или отладки приложений. Позволяет менять настройки окружения, не затрагивая работу ОС. Особенно часто используется для переопределения потока вывода отлаживаемой с помощью Radare2 программы.
  • Rasm2 — встроенный ассемблер/дизассемблер. Первоначально инструмент был разработан для внесения изменений в бинарные файлы. Его основная функция — получить байты, соответствующие заданному опкоду машинной инструкции. Rasm2 поддерживает плагины, что позволяет ему работать с файлами для разных архитектур.
  • Radiff2 — тулза для сравнения двоичных файлов, подобно тому, как это делает diff с текстовыми файлами. Radiff2 использует разные алгоритмы для поиска и сравнения содержимого файлов.
  • Ragg2 — компилирует программы, написанные на собственном языке высокого уровня, в крошечные бинарники для архитектур x86, x86-64 и ARM. Позволяет организовать базовый блок для создания перемещаемых фрагментов кода, которые будут использоваться для внедрения в целевые процессы при эксплуатации. Хотя по умолчанию Ragg2 компилирует свой собственный язык Ragg2, также можно скомпилировать код на языке C, используя шелл‑коды GCC или Clang.
  • Rahash2 — c помощью большого числа алгоритмов подсчитывает контрольную сумму любых файлов, дисков или строк. Тулза также способна кодировать и декодировать данные, используя простые операции, такие как Base64, XOR и прочие.
  • Rodeco — написанный на Rust декомпилятор с ассемблера, получаемого от Rasm2, на псевдо-C. К сожалению, до сих пор нежизнеспособен: результаты его работы крайне скромные. Поэтому утилита не включена в стандартную поставку фреймворка.
Так как Radare2 — Unix-ориентированный фреймворк, все утилиты в базовом варианте исполняются в командной строке. Однако с Radare2 можно работать не только в консольной среде: к услугам хакера есть несколько графических оболочек, объединяющих все инструменты фреймворка в мощную интерактивную среду. Среди них особую популярность завоевали iaito и Сutter. Они очень похожи, поскольку второй — форк первого.

Инструменты, входящие в Radare2, можно использовать в самых разных ОС и исследовать код для разных процессорных архитектур. Поддерживаются x86-64, ARM, MIPS, PowerPC, SPARC, Z80, MOS 6502 и байт‑код разных виртуальных машин. Анализировать можно PE32, PE32+, ELF, ROM-файлы игровых приставок и многое другое.

Хотя сами утилиты Radare2 где только не работают, интерактивные среды поддерживаются лишь в основных десктопных ОС. Однако даже консольные утилиты Radare2 удобны в работе, если ты умеешь с ними обращаться. Также управлять Radare2 можно из веб‑оболочки.

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

Установка​

В отличие от прошлой статьи, где я использовал Ubuntu, организовывать эксперименты сегодня я буду в Debian Linux.
Перед установкой Radare2 в Debian мне потребовалось установить gnu make, по умолчанию его в дистрибутиве не было. Лучше сразу установить build-essential:
Код:
sudo apt-get install build-essential
Хотя многие дистрибутивы Linux содержат готовые к инсталляции пакеты Radare2, версия из менеджера пакетов может быть не самой новой, поэтому можешь скачать последнюю версию с GitHub (заметим, что и обновлять в таком случае дальше придется вручную):
Код:
git clone https://github.com/radareorg/radare2.git
Запустим установку:
Код:
cd radare2
sudo sys/install.sh
Внутри подкаталога binr находятся подпапки со всеми перечисленными выше утилитами.
Содержимое подкаталога binr

Заодно установим vim, если его нет в системе:
Код:
sudo apt install vim
Хотя, если предпочитаешь nano или другой редактор, ты волен ставить, что тебе больше нравится.

Объект анализа​

Итак, представим, что наш Linux находится в далекой‑предалекой галактике и нам, героям, нужно взломать сервак злодея‑работорговца Джаббы Хатта. Программа на этом сервере написана на C, потому что Джабба предпочитает олдскул.

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

Находясь в каталоге с исходником нашего сервера, выполним компиляцию и сборку:
Код:
gcc server.c -o server64.elf
На выходе получаем исполняемый ELF-файл, над которым мы будем работать на протяжении статьи.

Запуск​

Мы не знаем, что делает утянутый у Джаббы исполняемый файл, поэтому вначале просто запустим его и посмотрим. Неизвестные файлы лучше всего выполнять в изолированной среде, ведь в реальной жизни что только не приходится отлаживать. Поэтому я работаю в виртуальной машине, которая не содержит никаких ценных данных, что рекомендую и тебе.
Итак, из каталога HackThisServer запустим наш сервер:
Код:
./server64.elf
Ничего видимого не происходит: вероятно, программа ждет обращения.
В качестве клиента для подключения к запущенному серверу воспользуемся утилитой netcat, задав в параметрах адрес целевой машины и номер порта. В другом окне терминала введи (номер порта мы узнаем чуть позже, пока прими данный по умолчанию):
Код:
nc localhost 14884
В результате нас встретит экран авторизации.
Система канализации Джаббы

Попробуем ввести xakep. Сервер ответит: «These are not the droids you are looking for!». При этом сеанс netcat будет завершен.

Исследование: статический анализ​

Использование Radare2 из командной строки похоже на работу с отладчиком GDB. Чтобы лучше усвоить команды R2, предлагаю первое время использовать именно консольный вариант, не прибегая к помощи визуальных оболочек. Давай посмотрим, как из командной строки проводить статическое исследование и динамический анализ бинарника.

Этап первый — подготовительный​

Оставаясь в каталоге с ELF-файлом, натравим на него дизассемблер:
Код:
r2 server64.elf
Все последующие команды будут выполняться над указанным файлом, поэтому его имя больше вводить не понадобится.
В приглашении для ввода Radare2 показывает адрес точки входа программы.
Запустим процедуру анализа:
Код:
aaa
В процессе анализа Radare2 выявляет точку входа, импортированные функции, пытается определить вызовы функций, их параметры, восстанавливает переменные. Если приложение написано на ООП‑языках, определяет методы, а если на C++, находит и парсит таблицу виртуальных функций. IDA Pro запускает подобную процедуру анализа автоматически сразу после открытия файла.
Radare2 после анализа

В начале анализа хорошим решением будет посмотреть на список импортированных функций, так мы сможем понять, чем вообще занимается приложение:
Код:
ii
Список импортированных функций

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

К слову, о командах. R2 предлагает свою справочную систему по каждой команде в духе справки man. Достаточно ввести первую букву команды, затем знак вопроса, и ты получишь список всех команд, начинающихся с введенной буквы, а к каждой — краткое описание действий.

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

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

Команда iz показывает читаемые строки, находящиеся в секции .data. А команда izz выводит строки во всем бинарнике. Как мы знаем, программист может запросто создать свою нестандартную секцию и запихнуть пароль в нее.
Вывод команды izz

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

Подходящих строк несметное количество. Все равно что искать иголку в стоге сена. Так что пока ничего интересного найти не удалось.

Давай выведем список функций в этой программе. Для этого служит команда afl.
Функции, составляющие программу

В выводе есть функции с говорящими названиями: main, authenticate, check_username, check_password, start_server и другие. Что ж, начнем с первой.

Этап второй — внутренности защитного механизма​

Заглянем для начала в функцию main, чтобы определить, чем программа занимается, что вызывает, возвращает и так далее. Для этого в R2 есть команда s (от английского seek — искать). В качестве параметра она может принимать адрес, по которому надо перейти, или название функции, в которую будет сделан переход. Если посмотришь справку по этой команде, найдешь еще много других параметров.

После перехода в строке ввода команды мы обнаружим обновленное смещение. Это значит, что нужная функция найдена и переход на нее удачно выполнен. Но простого перехода мало, нам надо посмотреть результат дизассемблирования этой функции, для чего служит команда pdf. Если вызвать ее без параметров (как в нашем случае), она отобразит текущую функцию — ту, на начальный адрес которой указывает курсор.
Дизассемблированная функция main в консоли

В начале функции формируется параметр: IP-адрес, переданный пользователем в командной строке, или, при отсутствии такового, локальный адрес хоста (127.0.0.1), который далее через регистр RDI передается в функцию start_server. Стоп! При чем здесь регистр RDI? Ведь на 64-битной платформе первый целочисленный параметр передается через регистр RCX?

Но это относится только к Windows, а в Linux действует другое соглашение — System V AMD64 ABI. Оно гласит, что первые шесть целочисленных параметров или указателей передаются в следующих регистрах: RDI, RSI, RDX, RCX, R8, R9. При этом первые восемь значений с плавающей запятой передаются в регистрах XMM0 — XMM7. Все остальное, если имеет место, передается через стек.
Дизассемблированная функция start_server

Происходящее в start_server нас не особо волнует, поскольку эта функция не принимает участия в защитном механизме, а только открывает порт для прослушивания. Это подтверждает передаваемый ей параметр, содержащий IP-адрес хоста. Однако нам важно разобраться в ней, чтобы узнать номер открываемого для прослушивания порта.

Итак, поверхностно взглянем на кишки этой функции. Аргумент (IP-адрес) копируется в переменную var_28h, далее создается структура сокета со значениями по умолчанию.

После вызова системной функции sym.imp.socket ее результат — сокет помещается в переменную socket. Выделяется блок памяти, после чего вызывается функция sym.imp.inet_addr, которая преобразует переданный в десятичном формате адрес в четырехкомпонентный IP-адрес, а тот сохраняется в структуру in_addr.

Далее происходит вызов sym.imp.htons, эта функция устанавливает порядок преобразования байтов хоста (на x86-64 обратный порядок) в сетевой порядок (прямой). Именно эта функция получает в параметре число 0x3A24. R2 неправильно определил формат аргумента, поставив в комментарии знак доллара. Эта функция принимает целочисленный параметр, преобразуем:
Код:
? 0x3A24
Результат можешь наблюдать на скриншоте.
Значения, соответствующие предложенному набору байтов

В первой строке результата отображается нужное нам число — 14 884, то есть номер открываемого для прослушивания порта. Все приведенные выше значения помещаются в структуру сокета, выделенную вначале, а последняя передается в системную функцию sym.imp.bind. Она связывает локальный адрес с сокетом. В конце start_server вызывается библиотечная функция listen, которая открывает сокет для прослушивания входящих сообщений. На выходе start_server выдает дескриптор сокета.

После возвращения в main дескриптор сокета помещается в переменную var_ch. Далее по перекрестной ссылке видно, что начинается тело цикла:
Код:
CODE XREF from main @ 0x1a8b(x)
Если проследить, откуда происходит переход на эту строку, окажется, что он выполняется из самого конца функции оператором безусловного перехода jmp.

В следующих четырех строчках происходит подготовка параметров для их передачи функции accept, полученный ранее дескриптор сокета как раз и передается в нее. Библиотечная функция accept ожидает подключения к переданному в параметре сокету и в случае успеха возвращает ненулевое значение — дескриптор сокета, с которым произошло соединение. Он сохраняется в переменную fildes.

Далее вызывается функция welcome. Параметром ей передается дескриптор сокета. Ее задача — вывод экрана приглашения для авторизации. А дескриптор сокета служит для указания, куда вывести данные (ведь это может быть удаленный комп). Еще ниже следует функция authenticate, ей тоже передается параметр — значение переменной fildes.

С функции authenticate начинается механизм аутентификации. Это следует из того, что сразу после ее вызова идет условный оператор test, который проверяет возвращенное значение: если оно равно нулю, выполняется переход в место вызова функции reject, которая выводит инфу о неправильно введенном имени пользователя и не тех дроидах. Возьмем на заметку: при корректных пользовательских данных функция authenticate возвращает отличное от нуля значение. Перейдем наконец в ее тело:
Код:
s sym.authenticate
pdf
Дизассемблированная функция authenticate

Кроме аргумента (дескриптора сокета), в функции есть пять локальных переменных. Их типы данных отражены в комментариях, заботливо оставленных R2. Аргумент из регистра аккуратно кладется в переменную fd. Промотаем функцию вниз. В ее начале ничего интересного нет, но спустя несколько строчек наш взгляд привлекает вызов функции с говорящим названием check_username, которой в качестве единственного аргумента передается значение переменной fd.

Промежуточные итоги​

Итак, мы рассмотрели все утилиты фреймворка Radare2, установили в Debian все нужные для предстоящих экспериментов пакеты. На примере подготовленной мной крякми мы начали изучать статический анализ и освоили несколько основных приемов.

Впереди нас ждет много интересного. Нам предстоит научиться с помощью Radare2 находить данные для логина, модифицировать сервер так, чтобы он работал, как нам надо, и, наконец, мы научимся извлекать данные и превращать их в файлы с полезной нагрузкой. Кроме того, используя Radare2, мы проведем динамический анализ. И все это — в консольном режиме!

Автор @yurembo aka Юрий Язев
Источник xakep.ru
 


Напишите ответ...
  • Вставить:
Прикрепить файлы
Верх