Skip to content
KitploitKITPLOIT
ИнструментыБлог
Отправить
ИнструментыБлог
Отправить

Инструменты для хакинга, пентеста и кибербезопасности — ваш арсенал защиты!

Kitploit — это каталог инструментов для хакинга, кибербезопасности и пентестинга. Находите последние обновления проектов для поиска уязвимостей, анализа систем, автоматизации тестирования и усиления вашей безопасности.

··Ленты·Контакты·Конфиденциальность·© 2026 Kitploit

Каталог инструментов

Категории

Все категории
Loading categories
blackbox-fuzzing — Фаззинг IoT-устройств на примере маршрутизатора TL-WR902AC | Kitploit
Инструменты/GitHubGitHub/otsmr/blackbox-fuzzing
Безопасность IoTАнализ уязвимостейЭксплуатацияОбратная инженерияФаззингАнализ Бинарных ФайловСтатьи и ИсследованияОбучение и ОбразованиеАнализ Прошивок
GitHubotsmr/blackbox-fuzzing

blackbox-fuzzing

Фаззинг IoT-устройств на примере маршрутизатора TL-WR902AC

132179 месяцев назадПроверено Kitploit

Популярное

Смотреть все →

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

Изучить все инструменты

Просмотрите нашу коллекцию инструментов

Смотреть все инструменты →
Поделиться
Репозиторий
Сайт

Фаззинг IoT-устройств методом чёрного ящика на примере роутера TL-WR902AC

Это HTML-версия моей курсовой работы, которую можно скачать в формате PDF здесь.

Введение

Фаззинг стал «одним из наиболее эффективных способов» поиска ошибок в программном обеспечении. С этого или подобных утверждений начинаются многие современные статьи, посвящённые фаззингу, [google-scholar]. Основной целью нашей предыдущей курсовой работы на тему «Internet of Vulnerable Things» было найти ошибку, связанную с памятью, и затем написать эксплойт для этой уязвимости. Нам удалось найти уязвимость путём реверс-инжиниринга прошивки, но ошибок, связанных с памятью, найдено не было. Поиск переполнения буфера путём ручного реверс-инжиниринга бинарного файла не только отнимает много времени, но и требует большого опыта. При этом фаззинг нацелен на то, чтобы быть «наиболее эффективным способом» поиска таких уязвимостей, связанных с памятью. Google, например, представил OSS-Fuzz, который непрерывно фаззит программное обеспечение с открытым исходным кодом и уже нашёл более 10 000 уязвимостей в 1 000 проектах [oss-fuzz].

Цель этой курсовой работы — снова найти уязвимость, связанную с памятью, но на этот раз с использованием фаззинга. Целевая уязвимость должна быть эксплуатируемой по сети без знания учётных данных администратора. В этой работе описывается способ достижения этой цели. Для этого работа разделена на две части. Первая часть посвящена тому, как найти подходящую цель, какие инструменты можно использовать и каким требованиям должна удовлетворять хорошая цель для фаззинга. Вторая часть описывает, как разработать и отладить харнесс, способный фаззить конкретную функцию в бинарном файле. Затем разработанный харнесс используется AFL++ для фаззинга целевой функции. Далее даётся краткий обзор и описывается текущее состояние дел в области фаззинга IoT-устройств.

Все файлы, созданные в рамках этой курсовой работы, также полностью опубликованы на GitHub и доступны по следующему URL: otsmr/blackbox-fuzzing.

Современное состояние

Фаззинг IoT-устройств не так прост, как фаззинг проекта с открытым исходным кодом. Часто исходный код является проприетарным, что делает невозможным фаззинг серого ящика, который инструментирует исходный код для достижения наилучшей производительности фаззинга, [afl-persistent]. Кроме того, архитектура ЦП часто не поддерживается фаззерами нативно, что требует эмулятора такого как QEMU [qemu], что также снижает скорость фаззинга [afl-persistent]. Ещё одной проблемой являются аппаратные периферийные устройства, что усложняет разработку общего подхода. В статье «Embedded Fuzzing: A Review of Challenges, Tools, and Solutions» [embedded-fuzzing] даётся обзор различных стратегий фаззинга, таких как аппаратный фаззинг встраиваемых систем. Большинство из этих стратегий требуют исходный код целевой программы, например, при портировании исходного кода фаззеров, таких как AFL, на IoT-устройства на базе ARM для запуска фаззера на аппаратном обеспечении IoT. Запуск фаззера на аппаратном обеспечении устройства также создаёт проблемы с производительностью, поскольку такие устройства часто имеют маломощные ЦП, которые медленнее обычных настольных ЦП. Другой подход, представленный в этой статье, — это фаззинг встраиваемых систем на основе эмуляции, при котором в эмуляторе выполняется либо одна целевая программа для проведения фаззинга на основе покрытия кода, либо вся система целиком.

Все вышеупомянутые подходы нацелены непосредственно на бинарный файл с использованием эмулятора или инструментирования исходного кода. Эти подходы требуют настройки фаззинга, которая часто должна быть специально разработана для конкретного IoT-устройства, и их трудно обобщить. Для этого исследователи создали программу IoTFuzzer, которая представляет собой автоматизированный фреймворк для фаззинга, нацеленный на «поиск уязвимостей повреждения памяти без доступа к образам их прошивки [iotfuzzer]». IoTFuzzers основан на наблюдении, что большинство IoT-устройств имеют мобильное приложение для управления ими, и такие приложения содержат информацию о протоколе, используемом для связи с устройством. Затем программа определяет и повторно использует специфичную для программы логику для мутации тестовых примеров с целью эффективного тестирования IoT-устройств [iotfuzzer].

Общие сведения

Харнесс

Харнесс описывает последовательность вызовов API, обрабатывающих входные данные, предоставленные фаззером. В отличие от обычного приложения, которому часто не нужен харнесс, библиотека, реализующая переиспользуемые функции, должна вызываться с правильными параметрами и в правильной последовательности, чтобы состояние между несколькими совместно используемыми вызовами функций можно было сохранить. Случайный фаззинг библиотеки без построения конечного автомата вряд ли будет успешным и, напротив, приведёт к множеству ложноположительных сбоев, если зависимости библиотеки не соблюдаются. Это может произойти, например, когда фаззер пропускает проверку размера буфера, что приводит к ложному переполнению буфера.

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

Корпус

Термин «корпус» описывает допустимые входные образцы или тестовые примеры и служит базовым ориентиром для генерации новых входных данных в процессе фаззинга. В Код 10 это был бы, например, HTTP-запрос. Затем фаззеры используют этот корпус для создания мутированных или разнообразных тестовых примеров, что способствует обнаружению уязвимостей программного обеспечения путём исследования различных сценариев ввода.

Поиск перспективной цели

Самая трудоёмкая часть фаззинга чёрного ящика — поиск потенциально уязвимой функции в прошивке. Первый шаг — найти интересные бинарные файлы, которые, например, доступны по сети, используют небезопасные функции или не имеют таких функций безопасности, как стековая канарейка, которая является защитой от переполнения буфера. В нашей предыдущей работе ([iovt]) уже было описано, как извлечь прошивку из целевого роутера и как найти потенциально опасный бинарный файл. Для этого использовался инструмент EMBA [emba]. EMBA ранжирует все бинарные файлы, найденные в прошивке, по количеству небезопасных функций, таких как strcpy, наличию сетевого доступа и таких средств защиты, как стековая канарейка или NX-бит, которые становятся интересными при эксплуатации переполнения буфера; результат можно увидеть в Код 1.

```txt [+] STRCPY - top 10 results: 235 : libcmm.so : common linux file: no | No RELRO | No Canary | NX disabled | No Symbols | No Networking | 77 : wscd : common linux file: no | No RELRO | No Canary | NX disabled | No Symbols | Networking | [snip] 28 : httpd : common linux file: yes | RELRO | No Canary | NX enabled | No Symbols | Networking | 27 : cli : common linux file: no | No RELRO | No Canary | NX disabled | No Symbols | No Networking | ```

Код 1: Результат EMBA по небезопасным использованиям функции strcpy.

Поскольку цель этой статьи — найти уязвимость памяти, которую можно эксплуатировать по сети без знания учётных данных администратора, уязвимая функция должна быть вызываемой по сети и должна напрямую взаимодействовать с предоставленными пользовательскими данными. Но наличие сетевого взаимодействия не означает, что бинарный файл также напрямую доступен по сети. Чтобы выяснить, какие бинарные файлы прослушивают порты, мы можем использовать UART root shell, который уже был установлен в [iovt].

```txt ~ # netstat -tulpn Active Internet connections (only servers) Proto Recv-Q Send-Q Local Address Foreign Address State PID/Program name tcp 0 0 127.0.0.1:20002 0.0.0.0:* LISTEN 1045/tmpd tcp 0 0 0.0.0.0:1900 0.0.0.0:* LISTEN 1034/upnpd tcp 0 0 0.0.0.0:80 0.0.0.0:* LISTEN 1027/httpd tcp 0 0 0.0.0.0:22 0.0.0.0:* LISTEN 1224/dropbear udp 0 0 0.0.0.0:20002 0.0.0.0:* 1048/tdpd [...] ```

Код 2: Использование UART root-оболочки для выполнения netstat

Реверс-инжиниринг бинарного файла

Первый бинарный файл, который выглядит многообещающим, — wscd. В нём больше всего небезопасных вызовов strcpy (не считая библиотеки libcmm.so), а также имеется сетевое взаимодействие, которое в случае wscd означает, что он подключается к устройству UPnP и не прослушивает конкретный порт. Как будет показано далее, в нём есть функция, которую легко фаззить, и именно поэтому этот бинарный файл был выбран в качестве примера в данной статье для объяснения общей процедуры. Перед реверс-инжинирингом мы можем использовать UART root-оболочку, чтобы выяснить, запущен ли бинарный файл и как он был запущен.

```txt $ ps PID USER VSZ STAT COMMAND 962 admin 1096 S wscd -i ra0 -m 1 -w /var/tmp/wsc_upnp/ 1018 admin 1080 S wscd_5G -i rai0 -m 1 -w /var/tmp/wsc_upnp_5G/ ```

Код 3: Использование команды ps для отображения всех запущенных программ.

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

```txt $ chroot root /qemu-mipsel-static /usr/bin/wscd Usage: wscd [-i infName] [-a ipaddress] [-p port] [-f descDoc] [-w webRootDir] -m UPnPOpMode -D [-d debugLevel] -h -i: Interface name this daemon will run wsc protocol(if not set, will use the default interface name - ra0) e.g.: ra0 -w: Filesystem path where descDoc and web files related to the device are stored e.g.: /etc/xml/ -m: UPnP system operation mode 1: Enable UPnP Device service(Support Enrolle or Proxy functions) 2: Enable UPnP Control Point service(Support Registratr function) 3: Enable both UPnP device service and Control Point services. [...] ```

Код 4: Опции бинарного файла wscd.

Как показано в Коде 4, wscd запускается с "Enabled UPnP Device service", что выглядит многообещающе. После проверки того, что бинарный файл действительно запущен на роутере, его можно проанализировать с помощью Ghidra для поиска подозрительных функций. Для фаззинга особенно интересны парсерные функции, поскольку они обычно сложны, и часто входные данные содержат поля длины, как TCP-пакет содержит длину полезной нагрузки.

Рисунок 1: Использование Ghidra для поиска парсерных функций.

Ещё одно преимущество парсерных функций в том, что они часто не взаимодействуют с другими частями кода и не имеют пользовательского взаимодействия по сети. Таким образом, парсерную функцию можно вызывать напрямую с входными данными, не изменяя бинарный файл и не переопределяя другие функции, поэтому её можно фаззить.

Прежде чем начинать фаззить функцию, следует проверить, вызывается ли она вообще, поскольку функция представляет интерес только тогда, когда она вызывается с контролируемыми пользователем входными данными. Для этого с помощью Ghidra можно искать ссылки на целевую функцию. В случае функции parser_parse есть несколько способов. Поскольку мы знаем, как запускается программа, вызовы можно свести к единому дереву вызовов функций, как показано в Коде 5.

```c main() if ((WscUPnPOpMode & 1) != 0) // Argument -m 1 WscUPnPDevStart() UpnpDownloadXmlDoc() -> my_http_Download() -> http_Download() if (http_MakeMessage()) http_RequestAndResponse() http_RecvMessage() ```

Код 5: Дерево вызовов функции parser_parse

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

Другие потенциально уязвимые функции

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

Бинарный файл httpd является серверной частью веб-интерфейса администратора. Этот бинарный файл доступен через сеть на порту 80. Одной из интересных функций в httpd является функция httpd_parser_main. При беглом просмотре реализации парсера с помощью Ghidra было выявлено несколько подозрительных фрагментов кода. Одной из подозрительных частей является разбор Content-Type. Далее приведён базовый HTTP-запрос.```txt POST / HTTP/1.1\r\n Content-Type: multipart/form-data; boundary=X;\r\n Host: example.com\r\n \r\n \r\n DATA\r\n

root@kitploit:~
Ниже приведён фрагмент из функции `httpd_parser_main`, которая разбирает `Content-Type` из
предоставленного пользователем HTTP-запроса.


<div id="c6"></div>```c
// user_input_ptr points to
//  "Content-Type: multipart/form-data; boundary=X;\r\nHost: example.com\r\n..."
cursor = strstr(user_input_ptr,"multipart/form-data");

if (user_input_ptr == cursor) {
 cursor = strstr(user_input_ptr,"boundary=");
 user_input_ptr = cursor + 9;

 // user_input_ptr points now to "X;\r\nHost: example.com\r\n..."

 if (cursor != (char *)0x0) {

  do {
    while (cursor = user_input_ptr, *cursor == " ") {
      user_input_ptr = cursor + 1;
    }
    user_input_ptr = cursor + 1;
  } while (*cursor == "\t");

  // cursor points now to "X;\r\nHost: example.com\r\n..."

  // strchr returns a pointer to the first occurrence of ";" in the user request.
  // If ";" is not found, the function returns a null pointer.
  user_input_ptr = strchr(cursor, ";");
  if (user_input_ptr != (char *)0x0) {
    // The character ";" is replaced by an null byte to terminate the string
    *user_input_ptr = "\0";
    // cursor points now to "X\0\r\nHost: example.com\r\n..."
  }

  // DAT_00444050 global array from 0x00444050 to 0x0044414f (255 Bytes)
  strcpy(&DAT_00444050, cursor);
  // DAT_00444050 contains now "X"
 }
}

Code 6: Дерево вызовов функции parser_parse

Уязвимость в этом коде — вызов функции strcpy и предположение о том, что Content-Type заканчивается точкой с запятой. Поскольку strcpy копирует буфер до следующего нулевого байта, а как показано в Code 6, нулевой байт добавляется только при обнаружении точки с запятой. Если удалить точку с запятой, следующий нулевой байт окажется в конце входного буфера, например, в конце HTTP-запроса. Таким образом, глобальная переменная DAT_00444050 может быть переполнена, что затем перезапишет данные за пределами адреса 0x0044414f. Сложность заключается не только в поиске интересной глобальной переменной за этим адресом, которую можно было бы перезаписать, но и в том, что из-за strcpy нельзя использовать нулевые байты. Но если здесь есть одна такая ошибка, вероятно, найдутся и другие.

Бинарный файл tdpd используется мобильным приложением и доступен через UDP в локальной сети. tdpd имеет почти те же функции, что и tmpd, которые в основном просто никогда не вызываются. Главная функция только ожидает сообщения через UDP-порт и всегда отвечает базовой информацией о маршрутизаторе, например, именем или моделью. Взаимодействие с вводимыми пользователем данными практически отсутствует, поэтому они не интересны для фаззинга.

Ещё одна интересная пара бинарных файлов — upnpd и ushare. Оба бинарных файла обрабатывают UPnP- сообщения, для чего им необходимо разбирать XML. Поскольку в бинарном файле можно найти строку авторского права, можно предположить, что эти программы были разработаны не TP-Link.```sh $ strings usr/bin/ushare | grep "(C)" Benjamin Zores (C) 2005-2007, for GeeXboX Team.

root@kitploit:~
Оба бинарных файла загружают разделяемые библиотеки `libupnp.so` и `libixml.so`, которые имеют те же
функции, что и проект с открытым исходным кодом `pupnp` [\[pupnp\]](https://github.com/pupnp/pupnp/). Поскольку
основное внимание в этой статье уделяется фаззингу чёрного ящика, эти бинарные файлы игнорируются. Но фаззинг серого ящика этой
библиотеки может быть перспективным, потому что в 2021 году в `libixml.so` была обнаружена утечка памяти
[\[pupnp-mem-leak\]](https://github.com/pupnp/pupnp/issues/249).

Бинарный файл **tmpd** является серверной частью мобильного приложения. Самое интересное, что маршрутизатор и
мобильное приложение общаются через собственный бинарный протокол. Ниже показано сообщение от
клиента к серверу.

<div id="c7"></div>```txt
00000000  01 00 05 00 00 08 00 00  00 00 00 17 50 7b 6e fe  |............P{n.|
00000010  01 01 02 00 00 00 00 00                           |........        |

Код 7: Сообщение от мобильного приложения к маршрутизатору.

Чтобы понять бинарный протокол, бинарный tmpd был реверснут с помощью Ghidra. С этой информацией сообщение из Кода 7 можно разбить на следующее:```txt 01 00 05 00 : Version 00 08 00 00 : Size (8 Bytes) 00 00 00 17 : Datatype 50 7b 6e fe : Checksum (CRC32) 01 01 : Options 02 00 : Function id 00 00 00 00 : Function parameters

root@kitploit:~
<p class="text-align: center">Код 8: Разбор пользовательского бинарного протокола.</p>

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


<figure id="f2">
  <p><img src="https://assets.kitploit.com/production/public/readmes/48851/43cb089d7f85d30ba036234ff0fdb29b6a54dd4a3382b2bd68e065f9f6c75369.png" style="width:90.0%" /></p>
  <figcaption>
    <p style="text-align: center">Рисунок 2: Восстановленная функция из tmpd, которая разбирает идентификатор функции и их параметры.</p>
  </figcaption>
</figure>

[Рисунок 2](#f2) показывает часть декомпилированной функции разбора пользовательского протокола. В строке 16
извлекается идентификатор функции, а затем в строке 29 вызывается соответствующая функция. Подозрительным
является то, что функция вызывается с параметрами, извлечёнными без какой-либо проверки из
управляемого пользователем входного буфера. Теперь мы могли бы попытаться найти функцию в таблице переходов, показанной на
[Рисунке 3](#f3), где это может быть опасным, например, когда параметр используется для индексации буфера
или интерпретируется как строка. Вместо ручного реверс-инжиниринга и поиска среди более чем 100 функций,
что заняло бы много времени, можно использовать фаззер, который сделает это автоматически.

<figure id="f3">
<p><img src="https://assets.kitploit.com/production/public/readmes/48851/708cf59aee459b840ca6dbcac32948d2b80fb426c3d4cc745157eac4f9b7c28e.png"
style="width:90.0%" /></p>
<figcaption><p style="text-align: center">Рисунок 3: Восстановленная функция из tmpd, которая разбирает идентификатор функции
и его параметры.</p></figcaption>
</figure>

К сожалению, бинарный файл `tmpd` доступен по сети только локально, как показано в [Коде
2](#c2). Чтобы подключиться к этому бинарному файлу, приложение сначала подключается к маршрутизатору через SSH в режиме
`direct-tcpip`, который просто пересылает пакеты локальному процессу. И SSH-соединение
защищено учётными данными администратора. Но, как описано в
[\[iovt\]](https://raw.githubusercontent.com/otsmr/internet-of-vulnerable-things/main/Internet_of_Vulnerable_Things.pdf),
SSH-соединение легко скомпрометировать, поскольку серверный хост-ключ никогда не проверяется
приложением. Отбрасывая все пакеты, направленные в интернет, можно обмануть администратора, заставив его войти в
маршрутизатор, пока выполняется атака «человек посередине» для кражи учётных данных.

## Фаззинг с помощью AFL++ и QEMU

В этом разделе разрабатывается харнес, нацеленный на одну из ранее найденных функций. После
разработки харнеса для фаззинга целевой функции используется современный фаззер AFL++
[\[aflpp\]](https://github.com/AFLplusplus/AFLplusplus). Поскольку
бинарные файлы скомпилированы для архитектуры `mipsel`, для запуска бинарного файла используется эмулятор QEMU.
Основная конфигурация фаззинга, используемая в этой статье, вдохновлена записью в блоге «Firmware
Fuzzing 101» Адама Ван Проойена [\[b101\]](https://www.mayhem.security/blog/firmware-fuzzing-101).

### Среда фаззинга 

Для простого создания воспроизводимой среды фаззинга лучше всего подходит Docker. Мы создали
Dockerfile, который устанавливает все необходимые инструменты, например кросс-компилятор для архитектуры процессора `mipsel`
или `gdb-multiarch`, который можно использовать для отладки харнеса.

Кроме того, AFLplusplus загружается и компилируется вместе с QEMU, который собирается в версии
с небольшими изменениями, позволяющими запускать неинструментированные бинарные файлы под afl-fuzz.```docker
FROM debian:latest

RUN apt update && apt install -y \
      curl \
      vim \
      gcc-mipsel-linux-gnu \
      openssh-server \
      qemu-user-static \
      gdb-multiarch
# Qemu statics are installed at /usr/bin/qemu-mipsel-static

# Compiling AFL++
RUN apt install -y git make build-essential clang ninja-build pkg-config libglib2.0-dev libpixman-1-dev
RUN git clone https://github.com/AFLplusplus/AFLplusplus /AFLplusplus
WORKDIR /AFLplusplus
RUN make all
WORKDIR /AFLplusplus/qemu_mode
RUN CPU_TARGET=mipsel ./build_qemu_support.sh

RUN echo "#!/bin/bash\n\nsleep infinity" >> /entry.sh
RUN chmod +x /entry.sh

WORKDIR /share
ENTRYPOINT [ "/entry.sh" ]

Dockerfile, который устанавливает необходимые инструменты.

Затем образ можно собрать с помощью docker build.```sh docker build -t fuzz .

root@kitploit:~
Когда образ собран, его можно легко использовать с помощью `docker run`, который затем запускает контейнер.```sh
docker run -d --rm -v $PWD/:/share --name fuzz fuzz

Использование опции -d запустит контейнер в фоновом режиме. С помощью docker exec внутри контейнера можно запустить несколько оболочек, что удобно для запуска исполняемого файла в одном сеансе с помощью QEMU, а в другом сеансе — gdb-multiarch.```sh docker exec -it fuzz /bin/bash

root@kitploit:~
### Переопределение главной функции

В предыдущем разделе была выявлена мощная цель для фаззинга. Проблема в том, что при выполнении
бинаря мы никогда не достигнем вызова функции, потому что функция `parser_parse` вызывается только
в том случае, если через сокет получен TCP-пакет. Это было бы плохо не только для производительности,
но и сложно в настройке. Именно поэтому точка входа фаззера должна находиться в другом месте, чем
обычная главная функция. Для этого можно использовать переменную окружения `LD_PRELOAD`, которая
позволяет внедрить харнесс, имеющий доступ к внутренним функциям. Как описывается на странице
руководства `ld.so`, которая отвечает за связывание общих библиотек, необходимых исполняемому файлу
во время выполнения, `LD_PRELOAD` можно использовать "для выборочного переопределения функций в
других общих объектах
[\[man-pages\]](https://www.man7.org/linux/man-pages/man8/ld.so.8.html)."

Функция `__uClibc_main` лучше всего подходит для этой цели. Чтобы переопределить эту функцию, необходимо
создать C-файл, содержащий функцию с тем же именем.```c
void __uClibc_main(void *main, int argc, char** argv) {
    // Harness code, e.g. call the function parser_append
    printf("My custom __uClibc_main was called!");
}

Затем C-файл можно кросс-компилировать в разделяемый объект для архитектуры mipsel с помощью mipsel-linux-gnu-gcc. Опция -fPIC включает «позиционно-независимый код», что означает, что машинный код не зависит от размещения по конкретному адресу за счёт использования относительной адресации вместо абсолютной.```txt $ mipsel-linux-gnu-gcc parser_parse_hook.c -o parser_parse_hook.o -shared -fPIC

root@kitploit:~
Вновь созданную разделяемую библиотеку затем можно загрузить, добавив переменную окружения `LD_PRELOAD` к команде QEMU.```txt
$ chroot root /qemu-mipsel-static -E LD_PRELOAD=/parser_parse_hook.o /usr/bin/wscd
My custom __uClibc_main was called!

С помощью команды chroot можно изменить текущий и корневой каталоги для указанной команды. Это полезно, поскольку исполняемый файл wscd открывает другие файлы, например общие библиотеки из прошивки. Мы можем увидеть это поведение, добавив аргумент -strace к QEMU.```txt chroot root /qemu-mipsel-static -E LD_PRELOAD=/parser_parse_hook.o -strace /usr/bin/wscd /corpus/notify.txt 38180 mmap(NULL,4096,PROT_READ|PROT_WRITE,MAP_PRIVATE|MAP_ANONYMOUS|0x4000000,-1,0) = 0x7f7e7000 38180 stat("/etc/ld.so.cache",0x7ffffa48) = -1 errno=2 (No such file or directory) 38180 open("/parser_parse_hook.o",O_RDONLY) = 3 38180 fstat(3,0x7ffff920) = 0 38180 close(3) = 0 38180 munmap(0x7f7e6000,4096) = 0 38180 open("/lib/libpthread.so.0",O_RDONLY) = 3 38180 open("/lib/libc.so.0",O_RDONLY) = 3 [...]

root@kitploit:~
Как мы видим, исполняемый файл открывает несколько библиотек в папке `/lib/` в прошивке, а не на хосте.

### Разработка и отладка харнесса

После создания окружения мы можем приступить к разработке харнесса. Как описано в разделе предыстории, харнесс является связующим звеном между фаззером и целевой функцией. Харнесс загружает входные данные фаззинга, которые AFL++ сохраняет в файл. Используя путь к файлу в качестве параметра, харнесс затем вызывает цель фаззинга; в данном случае это `parser_append`. Функции можно вызывать по адресу.

<div id="c10"></div>```c
void __uClibc_main(void *main, int argc, char** argv)
{
  // Verify that a filename is provided
  if (argc != 2) exit(1);

  // Create function pointer to the fuzz target
  int (*parser_request_init)(void *, int) = (void *) 0x00412564;
  int (*parser_append)(void *, void *, int) = (void *) 0x00412e98;

  // Open the fuzz input file
  int fd = open(argv[1], O_RDONLY);
  char fuzz_buf[2048 + 1];
  int fuzz_buf_len = read(fd, fuzz_buf, sizeof(fuzz_buf) - 1);
  if (fuzz_buf_len < 0) exit(1);
  fuzz_buf[fuzz_buf_len] = 0;

  // Call the target functions
  uint8_t parsed_data[220]; 
  parser_request_init(parsed_data, 8);
  int status = parser_append(parsed_data, fuzz_buf, fuzz_buf_len);
  printf("Response is %d\n", status);
  exit(0);
}

Код 10: Код обвязки с фаззинг-целью `parser_append` в бинарном файле wscd.

Как показано в Код 10, функция parser_parse вызывается не напрямую, а с помощью функции parser_append. Перед вызовом этой функции необходимо вызвать функцию инициализации parser_request_init, которая инициализирует выходную структуру функции parser_parse.

В то время как в случае parser_parse обвязку довольно легко настроить, другие цели требуют более сложных обвязок, например функции httpd_parser_main. К примеру, перед вызовом цели необходимо вызвать функцию http_init_main, которая приводит к ошибке SIGSEGV. Чтобы выяснить, где возникает это нарушение сегментации, полезно отладить код с помощью отладчика вроде gdb. Для этого QEMU можно запустить с опцией -g, которая запускает gdb-server на указанном порту.```sh chroot root /qemu-mipsel-static -strace -g 1234 -E LD_PRELOAD="/httpd_parser_main.o" /usr/bin/httpd corpus/httpd/simple.txt

root@kitploit:~
Поскольку бинарный файл имеет архитектуру `mipsel`, необходимо использовать `gdb-multiarch`. После запуска gdb
можно загрузить следующий init-скрипт с помощью `sources <path to script>`.```sh
set solib-absolute-prefix /share/root/
file /share/root/usr/bin/httpd
target remote :1234
# break bevor fuzz target is called
# break __uClibc_main
break http_parser_main
display/4i $pc

Из-за chroot сценарий сначала изменил абсолютный путь префикса, чтобы, когда бинарный файл загружает разделяемую библиотеку, gdb смог найти файл. Затем задаётся целевой файл, потому что gdb-сервер QEMU не поддерживает передачу файлов, поэтому gdb пытается загружать файлы с диска. После настройки gdb сценарий подключается к gdb-серверу с помощью target remote и создаёт точку останова в начале целевой функции. С помощью display вывод просто улучшается, так что при пошаговом выполнении будут показаны следующие четыре строки ассемблера. Используя si, можно выполнить одну инструкцию за шаг, что полезно, когда харнесс получает ошибку сегментации с использованием корпуса по умолчанию, который всегда должен работать. Как показано в Code 11, бинарный файл вызывает ошибку сегментации в функции fprintf.

```sh (gdb) si 0x004059b0 in http_parser_makeHeader () 1: x/4i \$pc => 0x4059b0 : jalr t9 0x4059b4 : addiu a1,a1,16248 0x4059b8 : li v0,200 0x4059bc : lw gp,16(sp) (gdb) ni 0x7f56a8ac in fprintf () from /share/root/lib/libc.so.0 1: x/4i \$pc => 0x7f56a8ac \: bal 0x7f56db80 0x7f56a8b0 \: nop 0x7f56a8b4 \: lw ra,36(sp) 0x7f56a8b8 \: jr ra 0x7f56a8bc \: addiu sp,sp,40 (gdb) n Single stepping until exit from function fprintf, which has no line number information.

Program received signal SIGSEGV, Segmentation fault.

root@kitploit:~
<p style="text-align: center">Код 11: Ошибка сегментации в printf.</p>

Чтобы изучить ошибку, можно использовать Ghidra, чтобы выяснить, с какими параметрами вызывается функция.```c
fprintf(
 *(FILE **)(iVar1 + 0x101c),
 "HTTP/1.1 %d %s\r\n",
 *(undefined4 *)(&DAT_0042ee68 + (uint)(byte)(&DAT_00414570)[statuscode & 0x3f] * 8),
 (&PTR_DAT_0042ee6c)[(uint)(byte)(&DAT_00414570)[statuscode & 0x3f] * 2]
);

SIGSEGV, вероятно, вызван тем, что первый параметр — это не файловый дескриптор, а нулевой указатель. Где iVar1 — это всего лишь ссылка на входные данные функции httpd_parser_main. Это означает, что входные данные для фаззинга должны содержать файловый дескриптор в позиции 0x101c. Таким образом, входные данные должны быть приведены к следующей структуре.```c typedef struct { int _a; // 4 Bytes int _b; // 4 Bytes int socket; // 4 Bytes int ip; // 4 Bytes int mac; // 4 Bytes unsigned char body[0x1008]; // 0x101c - 4*5 = 0x1008 Bytes FILE * fd_out; // expected to be a valid file descriptor } HttpMainT;

root@kitploit:~
Поскольку `fd_out` должен быть всего лишь допустимым указателем на файловый дескриптор, его легко можно установить в `stdout`.
Повторное выполнение `httpd_parser_main` теперь выдаст допустимый HTTP-вывод.```c
$ chroot root /qemu-mipsel-static -E LD_PRELOAD=/httpd_parser_main.o \
    /usr/bin/httpd /httpd_corpus.txt

bind: No such file or directory
[ dm_shmInit ] 086:  shmget to exitst shared memory failed. Could not create shared memory.
rdp_getObj is called with: 4274932gdpr_getSystemGDPREntry Error
gdpr_getNewSystemGDPREntry OK
#Msg: getsockname error
HTTP/1.1 200 OK
Content-Type: text/html; charset=utf-8
Content-Length: 24257
Set-Cookie: JSESSIONID=deleted; Expires=Thu, 01 Jan 1970 00:00:01 GMT; Path=/; HttpOnly
Connection: close

<!DOCTYPE html>
[...]

The harness works now and can be used to fuzz the function using AFL++ which will be explained in the next section.

Генерация данных корпуса

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

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

Что касается функций, разбирающих сетевые данные, такие входные данные можно создать с помощью Wireshark, записывая различные пакеты.

Для функции httpd_parse_main было создано четыре разных корпуса. Каждый нацелен на разные пути в бинарном файле. Одним из примеров является запрос входа, содержащий имя пользователя и пароль. Для этого корпуса харнес пришлось изменить, поскольку TP-Link использует (слабую) криптографию для «защиты» пароля. Для этого пароль шифруется в браузере с помощью AES, а затем расшифровывается на бэкенде. Причём пароль генерируется в браузере, а затем шифруется с помощью RSA. После этого зашифрованные данные подписываются. Поскольку фаззер не может создать подпись или зашифровать данные, некоторые функции были переопределены и теперь просто декодируют данные из base64. Для этого данные сначала извлекались в открытом виде из браузера с помощью отладчика, показанного на Рисунке 4.

Рисунок 4: Извлечение данных перед шифрованием.

В цели функция rsa_tmp_decrypt_bypart была затем переопределена, чтобы заменить логику расшифровки данных на простое декодирование из base64.```c // Replacing the logic with b64_decode int rsa_tmp_decrypt_bypart(uint8_t *input, int input_len, uint8_t *output) { // other params just key data int (*b64_decode)(uint8_t *, int, uint8_t *, int) = (void *) 0x0040bf00; b64_decode(output, 0x1000, input, input_len); int * seqnumber = (int *) 0x00444db0; *seqnumber = 0x3ac28e29-input_len+12; return 0; // says it was okay }

root@kitploit:~
<p style="text-align: center">Код 12: функция rsa_tmp_decrypt_bypart теперь просто декодирует base64, а не расшифровывает данные.</p>

При выполнении корпуса целевая функция всегда возвращает HTML-документ с ошибкой "408
Request Timeout". С помощью Ghidra и GDB проблему удалось идентифицировать. Ошибка всегда возникает
после вызова функции `http_stream_fgets`. Проблемной строкой была проверка символа
перевода строки `\n`.```c
if (((cVar1 == '\n') && (param_3 < pcVar4)) && (pcVar4[-1] == '\r')) {

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

Фаззинг цели

В предыдущем разделе мы разработали несколько харнессов и запускали их с помощью QEMU. В этом разделе QEMU заменяется на AFL++, который получает сгенерированные корпусы в качестве стартового ввода для фаззинга целевой функции. В разделе «Среда фаззинга» был создан docker-образ, который уже загружает AFL++ с GitHub, а затем использует предоставленный AFL++ скрипт для сборки пропатченной версии QEMU. Теперь AFL++ можно запустить следующей командой, которая принимает различные параметры, например -Q, указывающий AFL++ использовать пропатченную версию QEMU.```sh QEMU_LD_PREFIX=/share/root AFL_PRELOAD=/share/root/httpd_parser_main.o
/AFLplusplus/afl-fuzz -Q
-i /share/root/corpus/httpd/ -o /share/afl-out/httpd/
-- /share/root/usr/bin/httpd @@

root@kitploit:~
<p style="text-align: center">Код 13: Фаззинг бинарного файла <code>httpd</code> с помощью обвязки
и <code>afl-fuzz</code>.</p>

В отличие от ранее, команда `chroot` больше не нужна и заменяется переменной
`QEMU_LD_PREFIX`. Эта переменная указывает QEMU, где искать разделяемые объекты. Кроме того,
переменная `LD_PRELOAD` заменяется специфической для AFL версией `AFL_PRELOAD`. Последний аргумент в команде —
это два символа `@`. AFL++ заменит их на путь к файлу, содержащему ввод для фаззинга.
При запуске AFL++ показывает прогресс с помощью терминального интерфейса, показанного на [Рисунке 5](#f5).

<div id="f5"></div>
<figure>
<p><img src="https://assets.kitploit.com/production/public/readmes/48851/ac4b12fcf84c2041c9edc2a0871c5bb89b576d976b2eba0a2d0f843e7e708795.png" style="width:95.0%" /></p>
<figcaption><p style="text-align: center">Рисунок 5: Экран состояния AFL++.</p></figcaption>
</figure>

Экран состояния `AFL++` предоставляет важную информацию о текущем процессе фаззинга. В документации
`AFL++` есть хороший обзор терминов, используемых на экране состояния
[\[afl-screen\]](https://aflplus.plus/docs/status_screen/).  При отладке корпуса с помощью
следующих переменных окружения пользовательский интерфейс можно отключить, а с помощью `AFL_DEBUG` включить
детальное журналирование, которое показывает текущий ввод фаззера и `stdout` целевой программы.```sh
export AFL_DEBUG=1 && export AFL_NO_UI=1
unset AFL_DEBUG && unset AFL_NO_UI

Как показано на Рисунке 5, фаззинг бинарного файла может занять довольно много времени. Согласно документации, «следует ожидать, что он будет выполняться днями или неделями», а «некоторым заданиям будет разрешено выполняться месяцами». Чтобы сократить необходимое время, скорость выполнения должна быть выше 100 execs/сек. Когда, например, целевой httpd_main_parser фаззился, скорость выполнения в начале составляла около 30/сек. Чтобы повысить скорость, в целевом бинарном файле искали подозрительные функции, которые, вероятно, являются причиной замедления. Одной из подозрительных функций была rsa_gdpr_generate_key, поскольку известно, что генерация RSA-ключа занимает много времени. После перезаписи функции скорость улучшилась до 600 выполнений в секунду.

Один из индикаторов, помогающий понять, когда остановить фаззинг, — счётчик циклов. AFL++ подсвечивает число зелёным, когда «фаззер уже довольно долго не видит никакой активности», что помогает принять решение остановить фаззер.

Но, пожалуй, самое интересное число — «total crashes». Оно показывает, когда программа падает из-за текущих входных данных фаззера, и это, вероятно, ошибка, связанная с памятью. Чтобы убедиться, что это реальная ошибка, можно снова использовать gdb для определения места ошибки.

Заключение

Фаззинг может быть наиболее эффективным способом поиска уязвимостей безопасности. В этой курсовой работе были подвергнуты фаззингу три разные функции, но ни одной не найдено. Хотя сама настройка фаззинга «чёрного ящика» не так уж сложна и трудоёмка, поиск подходящей цели и разработка рабочего харнесса — вот что сложно и трудоёмко. Большую часть времени приходится отлаживать харнесс, а затем логика, лежащая в основе бинарного файла, должна быть подвергнута реверс-инжинирингу, что снова занимает много времени.

Скачать инструмент