Я начал записывать заметки о видео, связанных с безопасностью, которые я смотрю (как способ быстрого воспоминания).
Это может быть более полезным для начинающих.
Порядок заметок здесь не в порядке сложности, а в обратном хронологическом порядке того, как я их пишу (т.е. сначала).
Эта работа лицензирована по международной лицензии Creative Commons Attribution-Noncommercial-Sharealike 4.0.
Написано 12 августа 2017 года
Под влиянием доверия Гинваэля CTF 2017 Livestreams здесь и здесь; И его Google CTF Quals 2017 Livestream здесь
Иногда задача может выполнить сложную задачу, внедрив виртуальную машину. Не всегда необходимо полностью изменить виртуальную машину и работать над решением задачи. Иногда вы можете немного решить, и как только вы узнаете, что происходит, вы можете зацепить виртуальную машину и получить доступ к вещам, которые вам нужно. Кроме того, атаки по боковым каналам на основе времени становятся проще в виртуальных машинах (в основном из-за большего количества выполненных «реальных» инструкций.
Криптографически интересные функции в двоичных файлах могут быть распознаны и быстро переоценивают, просто искав константы и поиск их в Интернете. Для стандартных крипто -функций эти константы достаточны для быстрого догадки при функции. Более простые крипто -функции могут быть распознаны еще легче. Если вы видите много Xors и подобных вещей, и нет легко идентифицируемых констант, это, вероятно, криптография вручную (и, возможно, сломанный).
Иногда при использовании IDA с HexRays представление разборки может быть лучше, чем представление декомпиляции. Это особенно верно, если вы заметите, что, кажется, происходит много осложнений в представлении декомпиляции, но вы замечаете повторяющиеся закономерности в представлении разборки. (Вы можете быстро переключить B/W двух, используя пробел). Например, если внедрена библиотека с большим интеграцией (фиксированного размера), то вид декомпиляции ужасен, но представление разборки легко понять (и легко узнавать из-за повторяющихся инструкций «с несущимися», такими как adc ). Кроме того, при анализе такого анализа использование функции «Групповые узлы» в представлении графика IDA чрезвычайно полезно для быстрого уменьшения сложности вашего графика, как вы понимаете, что делает каждый узел.
Для странных архитектур наличие хорошего эмулятора чрезвычайно полезно. В частности, эмулятор, который может дать вам дамп памяти, может быть использован для быстрого выяснения того, что происходит, и распознавать интересные части, как только у вас есть память из эмулятора. Кроме того, использование эмулятора, реализованного на удобном языке (например, Python), означает, что вы можете запустить вещи именно так, как вам нравится. Например, если есть какая -то интересная часть кода, вы можете запустить несколько раз (например, для грубой силы или что -то в этом роде), то используя эмулятор, вы можете быстро кодировать что -то, что делает только эту часть кода, а не запускать полную программу.
Быть ленивым - это хорошо, когда Ринг. Не тратьте время на то, чтобы реверсировать все, но потратьте достаточно времени на разведку (даже в вызове!), Чтобы иметь возможность сократить время, потраченное на то, чтобы сделать более сложную задачу. В такой ситуации резон - просто быстро взглянуть на разные функции, не тратя слишком много времени на тщательное анализ каждой функции. Вы просто быстро оцениваете, о чем может быть функция (например, «выглядит как крипто -вещь» или «выглядит как управление памятью» и т. Д.)
Для неизвестного оборудования или архитектуры потратьте достаточно времени на поиск в Google, вам может повезти с кучей полезных инструментов или документов, которые могут помочь вам быстрее создавать инструменты. Часто вы найдете реализации Emulator Emulator и т. Д., Которые могут быть полезны в качестве быстрых моментов для начала. В качестве альтернативы, вы можете получить некоторую интересную информацию (например, как хранятся растровые карты, или как хранятся строки, или что -то в этом роде), с помощью которой вы можете написать быстрый скрипт «исправления», а затем использовать обычные инструменты, чтобы увидеть, есть ли интересные вещи.
GIMP (инструмент манипуляции с изображением) имеет очень крутую функциональность Open/Load, чтобы увидеть необработанные данные пикселя. Вы можете использовать это, чтобы быстро найти активы или повторяющиеся структуры в необработанных бинарных данных. Потратьте время на то, чтобы все испортить настройки, чтобы увидеть, можно ли получить дополнительную информацию от нее.
Написано 2 июля 2017 года
Под влиянием дискуссии с @P4N74 и @H3RCUL35 в чате InfoSeciitr #BIN. Мы обсуждали о том, как иногда начинающие пытаются начать с более крупного бинарного состава, особенно когда он раздет.
Чтобы решить проблему RE, либо иметь возможность его, нужно сначала проанализировать заданный двоичный файл, чтобы иметь возможность эффективно использовать его. Поскольку двоичный файл, возможно, может быть разделен и т. Д. (Найдено с использованием file ), нужно знать, с чего начать анализ, чтобы получить опору для наращивания.
Есть несколько стилей анализа, при поиске уязвимостей в двоичных файлах (и из того, что я собрал, разные команды CTF имеют разные предпочтения):
1.1. Транспортировка полного кода в c
Этот вид анализа встречается в некотором роде, но весьма полезен для меньших двоичных файлов. Идея состоит в том, чтобы пройти в реверс -инженере, полная код. Каждая функция открывается в IDA (с использованием представления декомпилятора), а переименование (ярлыка: n) и переиздание (ярлык: Y) используются для быстрого сделания декомпилированного кода гораздо более читабельным. Затем весь код копируется/экспортируется в отдельный .c -файл, который можно собрать, чтобы получить эквивалентный (но не одинаковый) двоичный для оригинала. Затем может быть проведен анализ уровня исходного кода, чтобы найти Vulns и т. Д. Как только точка уязвимости будет обнаружена, тогда эксплойт построен на исходном двоичном языке, следуя по хорошо декомпилированному источнику в IDA, рядом с видом на разборку (используйте вкладку для быстрого переключения между двумя; и используйте пространство, чтобы быстро переключаться между графиком и текстовым представлением для разборки).
1.2. Минимальный анализ декомпиляции
Это делается довольно часто, так как большая часть бинарника относительно бесполезна (с точки зрения злоумышленника). Вам нужно только проанализировать функции, которые подозрительны или могут привести вас к Vuln. Для этого есть некоторые подходы для начала:
1.2.1. Начните с Main
Теперь обычно, для разряженного бинарного файла, даже Main не помечена (IDA 6.9, хотя и отмечает его для вас), но со временем вы научитесь распознавать, как достичь основного с точки зрения (где IDA открывается по умолчанию). Вы прыгаете на это и начинаете анализировать оттуда.
1.2.2. Найдите соответствующие строки
Иногда вы знаете некоторые конкретные строки, которые могут быть выведены и т. Д., Которые, которые, как вы знаете, могут быть полезны (например, «Поздравляю, ваш флаг %s» для вызова). Вы можете перейти к представлению строк (сочетание: Shift+F12), найти строку и работать назад, используя XREFS (ярлык: x). XREF позволяют вам найти путь функций для этой строки, используя XREF для всех функций в этой цепи, пока вы не достигнете основного (или какой -то точки, который вы знаете).
1.2.3. От некоторой случайной функции
Иногда не может быть полезна конкретная строка, и вы не хотите начинать с Main. Таким образом, вместо этого вы быстро переключаетесь через список целых функций, ищет функции, которые выглядят подозрительно (такие как наличие большого количества константов, или множества Xors и т. Д.) или вызывает важные функции (Xrefs of Malloc, Free и т. Д.), И вы начинаете оттуда и перейдите к обеим вперед (следующие функции) и обратно (xrefs функции) и перейдите к обеим вперед (следующие функции) и назад (xrefs функции) и перейдите к обеим вперед (следующие функции.
1.3. Чистая анализ разборки
Иногда вы не можете использовать представление декомпиляции (из-за странной архитектуры, методов антидекомпиляции, или ручной сборки, или декомпиляции, выглядящего слишком излишне сложным). В этом случае совершенно верно смотреть исключительно на представление о разборке. Чрезвычайно полезно (для новых архитектур) включить авто комментарии, в котором показан комментарий, объясняющий каждую инструкцию. Кроме того, функциональные возможности раскраски и групповых узлов чрезвычайно полезны. Даже если вы не используете ни одного из них, регулярно отмечать комментарии в разборке очень помогает. Если я лично делаю это, я предпочитаю записывать комментарии, подобные питону, чтобы я мог быстро перенести вручную в Python (особенно полезно для вызовов, где вам, возможно, придется использовать Z3 и т. Д.).
1.4. Использование платформ, таких как BAP и т. Д.
Этот вид анализа (полу) автоматизирован и обычно более полезен для гораздо более крупного программного обеспечения и редко используется непосредственно в CTF.
Fuzzing может быть эффективной техникой, чтобы быстро добраться до Vuln, не понимая его изначально. Используя раздумку, можно получить много стиля Vulns с низким висящим фруктом, который затем необходимо проанализировать и дать триам, чтобы добраться до фактического Vuln. Смотрите мои заметки о основаниях пузырька и генетического пузырька для получения дополнительной информации.
Динамический анализ может быть использован после поиска Vuln с использованием статического анализа, чтобы помочь быстро построить эксплойты. В качестве альтернативы его можно использовать, чтобы найти саму Vuln. Обычно человек запускает исполняемый файл внутри отладчика и пытается пойти по пути кода, которые запускают ошибку. Размещая точки останова в нужных местах и анализируя состояние регистров/кучи/стека/и т. Д., Можно получить хорошее представление о том, что происходит. Можно также использовать отладчики для быстрого выявления интересных функций. Это можно сделать, например, путем установки временных точек останова на всех функциях изначально; Затем продолжая выполнять 2 прогулки - одна через все неинтересные пути кода; И один через один интересный путь. Первая прогулка переезжает на все неинтересные функции и отключает эти точки останова, тем самым оставляя интересные, появляющиеся как точки останова во второй прогулке.
Мой личный стиль для анализа-начать со статического анализа, обычно с основных (или для неконсольских приложений, от строк) и работать в направлении быстрого поиска функции, которая выглядит странной. Затем я провожу время и выпускаю вперед и отсюда, регулярно записывая комментарии, и постоянно переименовав и переигрываю переменные для улучшения декомпиляции. Как и другие, я использую такие имена, как Apple, Banana, Carrot и т. Д. для, казалось бы, полезных, но, как и еще неизвестные функции/переменные/и т. Д., Чтобы облегчить анализ (отслеживание Func_123456 Стиль имен слишком сложно). Я также регулярно использую представление структур в IDA, чтобы определить структуры (и перечисления), чтобы сделать декомпиляцию еще приятнее. Как только я найду Vuln, я обычно переходите к написанию сценария с помощью pwntools (и использую его, чтобы вызвать gdb.attach() ). Таким образом, я могу получить большой контроль над тем, что происходит. Внутри GDB я обычно использую простой GDB, хотя я добавил команду peda , которая при необходимости загружает педа.
Мой стиль определенно развивается, поскольку я стал более комфортно с моими инструментами, а также с пользовательскими инструментами, которые я написал, чтобы ускорить ситуацию. Я был бы рад услышать о других стилях анализа, а также о возможных изменениях в моем стиле, которые могут помочь мне быстрее. Для любых комментариев/критики/похвалы, которые у вас есть, как всегда, меня можно связаться в Twitter @jay_f0xtr0t.
Написано 4 июня 2017 года
Под влиянием этого удивительного живого потока Гинваэль Колдвонд, где он обсуждает основы ROP, и дает несколько советов и хитростей
Ориентированное на возврат программирования (ROP) является одним из классических методов эксплуатации, который используется для обхода защиты NX (не исполняемая память). Microsoft включила NX как DEP (предотвращение выполнения данных). Даже Linux и т. Д., Имейте это эффективное, что означает, что с этой защитой вы больше не можете поместить Sellcode на кучу/стек и заставить его выполнить, просто прыгая к нему. Итак, теперь, чтобы иметь возможность выполнять код, вы прыгаете в ранее существовавший код (основной двоичный файл или его библиотеки-LIBC, LDD и т. Д. На Linux; Kernel32, NTDLL и т. Д. В Windows). ROP возникает, повторно используя фрагменты этого кода, который уже существует, и выяснив способ объединить эти фрагменты в то, что вы хотите сделать (что, конечно, взломать планету !!!).
Первоначально ROP начинал с RET2LIBC, а затем стал более продвинутым с течением времени, используя еще много небольших кусочков кода. Некоторые могут сказать, что ROP теперь «мертв» из -за дополнительной защиты, чтобы смягчить его, но его все еще можно использовать во многих сценариях (и определенно необходимо для многих CTF).
Самая важная часть ROP, - это гаджеты. Гаджеты - это «полезные кусочки кода для ROP». Обычно это означает кусочки кода, которые заканчиваются ret (но другие виды гаджетов также могут быть полезны; такие, как те, которые заканчиваются pop eax; jmp eax и т. Д.). Мы объединяем эти гаджеты, чтобы сформировать эксплойт, который известен как цепь ROP .
Одним из наиболее важных предположений ROP является то, что у вас есть управление над стеком (то есть указатель стека указывает на буфер, который вы управляете). Если это не так, то вам нужно будет применять другие уловки (например, стек -поворот), чтобы получить этот контроль, прежде чем построить цепь ROP.
Как вы извлеките гаджеты? Используйте загружаемые инструменты (такие как Ropgadget) или онлайн -инструмент (например, Ropshell) или напишите свои собственные инструменты (иногда может быть более полезным для более сложных задач, так как вы можете настроить его на конкретную задачу, если это необходимо). По сути, нам просто нужны адреса, на которые мы можем прыгнуть для этих гаджетов. Именно здесь может быть проблема с ASLR и т. Д. (В этом случае вы получаете утечку адреса, прежде чем перейти к фактически выполнять ROP).
Итак, как мы используем эти гаджеты, чтобы сделать Ropchain? Сначала мы ищем «базовые гаджеты». Это гаджеты, которые могут выполнять для нас простые задачи (например, pop ecx; ret , которые можно использовать для загрузки значения в ECX путем размещения гаджета, за которым следует загрузка значения, за которым следует остальная цепь, которая возвращается после загрузки значения). Наиболее полезными базовыми гаджетами обычно являются «установить регистр», «Значение регистрации хранить по адресу, указанное по регистрации» и т. Д.
Мы можем создать из этих примитивных функций, чтобы получить функциональность более высокого уровня (аналогично моей пост под названием «Абстракция эксплуатации»). Например, используя гаджеты SET-Register и Store-Value-At-Address, мы можем придумать функцию «Poke», которая позволяет нам устанавливать любой конкретный адрес с определенным значением. Используя это, мы можем построить функцию «poke-string», которая позволяет нам хранить какую-либо конкретную строку в любом конкретном месте в памяти. Теперь, когда у нас есть Poke-String, мы в основном практически готовы, поскольку мы можем создавать любые структуры, которые мы хотим в памяти, а также можем вызвать любые функции, которые мы хотим с нужными параметрами, которые мы хотим (поскольку мы можем установить регистрацию, и можем поставить значения в стек).
Одна из наиболее важных причин построения из этих примитивов более низкого порядка до более крупных функций, которые выполняют более сложные вещи, - это снизить шансы на ошибки (что является обычным явлением в ROP в противном случае).
Есть более сложные идеи, методы и советы для ROP, но это, возможно, тема для отдельной заметки, для другого времени :)
PS: Gyn имеет пост блога о эксплуатации, ориентированной на возврат, которая может быть прочитана.
Написано 27 мая 2017 года; Расширен 29 мая 2017 года
Под влиянием этого удивительного живого потока Гинваэль Колдвонд, где он рассказывает о основной теории генетического пузырька и начинает строить базовый генетический пузырь. Затем он продолжает завершить реализацию в этом живом потоке.
«Продвинутый» пузырь (по сравнению со слепым пузырьком, описанный в моей «Основаниях пузырьков»). Это также изменяет/мутает байты и т. Д., Но он делает это немного умнее, чем слепой «тупой» раздумчик.
Зачем нам генетический фьюзмер?
Некоторые программы могут быть «неприятными» по отношению к глупым пумпам, поскольку возможно, что уязвимость может потребовать целой кучи условий, чтобы быть удовлетворенными, чтобы быть достигнутыми. В глупом раздумке у нас есть очень низкая вероятность этого, так как у него нет ни малейшего представления о том, делает ли он какой -либо прогресс или нет. В качестве конкретного примера, если у нас есть код if a: if b: if c: if d: crash! (Давайте назовем это кодом Crasher), тогда в этом случае нам нужно 4 условия, чтобы быть удовлетворенным для сбоя программы. Тем не менее, тупой пузырь может быть не в состоянии преодолеть a , только потому, что существует очень низкая вероятность того, что все 4 мутации a , b , c , d , одновременно происходят. На самом деле, даже если он прогрессирует, выполняя только a , следующая мутация может вернуться к !a только потому, что она ничего не знает о программе.
Подождите, когда появляется такая программа «плохой случай»?
Это довольно распространено в анализаторах формата файла, чтобы взять один пример. Чтобы достичь некоторых конкретных путей кода, это может потребоваться пройти несколько проверок: «Это значение должно быть таким, и это значение должно быть таким, и какое -то другое значение должно быть чем -то другим» и так далее. Кроме того, почти ни одно реальное программное обеспечение не является «несложным», и в большинстве программ есть много возможных путей кода, некоторые из которых можно получить только после того, как многие вещи в штате будут настроены правильно. Таким образом, многие из этих путей кода этих программ в основном недоступны для глупых раздушек. Кроме того, иногда некоторые пути могут быть совершенно недоступными (а не просто безумно невероятными) из -за недостаточного количества мутаций, выполненных вообще. Если у кого -то из этих путей есть ошибки, тупой пузырь никогда не сможет их найти.
Итак, как мы делаем лучше, чем тупые раздушки?
Рассмотрим график потока управления (CFG) вышеупомянутого кода аварийного управления. Если случайно тупой раздут внезапно получил a , то он тоже не узнает, что достиг нового узла, но он продолжит игнорировать это, отбросив образец. С другой стороны, то, что делают AFL (и другие генетические или «умные» пумпуры), они признают это новой информацией («недавно достигнутый путь») и хранят этот образец в качестве новой первоначальной точки в корпусе. Это означает, что теперь Fuzzer может начать с блока a и двигаться дальше. Конечно, иногда это может вернуться к !a от образца a , но большую часть времени это не будет, и вместо этого может достичь b -блока. Это снова новый узел, поэтому добавляет новый образец в корпус. Это продолжается, позволяя проверять все больше и больше возможных путей, и, наконец, достигает crash! Анкет
Почему это работает?
Добавляя мутированные образцы в корпус, которые больше исследуют график (то есть охватывают детали, не изучаемые ранее), мы можем достичь ранее недоступных областей и, таким образом, можем размыть такие области. Поскольку мы можем раздувать такие области, мы могли бы раскрыть ошибки в этих регионах.
Почему это называется генетическим пузырьком?
Этот вид «умного» пушина похож на генетические алгоритмы. Мутация и кроссовер образцов вызывает новые образцы. Мы сохраняем образцы, которые лучше подходят для тестируемых условий. В этом случае условие: «Сколько узлов на графике оно достигло?». Те, которые проходят больше, можно сохранить. Это не совсем похоже на генетические алгографии, но это вариация (поскольку мы храним все образцы, которые проходят невыполненную территорию, и мы не делаем кроссовер), но достаточно похожи, чтобы получить одно и то же имя. В основном, выбор из ранее существовавшей популяции, за которой следует мутация, за которой следует фитнес-тестирование (будь то новые области) и повторяйте.
Подождите, так что мы просто отслеживаем недоступные узлы?
Нет, не совсем. AFL отслеживает обход к крае на графике, а не в узлах. Кроме того, он не просто говорит «Эдж путешествовал или нет», он отслеживает, сколько раз было проходит преимущество. Если край пройдет 0, 1, 2, 4, 8, 16, ... время, это считается «новым путем» и приводит к дополнению в корпус. Это делается, потому что, если смотреть на края, а не узлы, является лучшим способом различения между состояниями применения, а использование экспоненциально увеличивающегося количества обходов от края дает больше информации (обходной крайний раз, один раз отличается дважды, дважды пересекается, но пересечение 10 не слишком отличается от 11 раз).
Итак, что и все вам нужно в генетическом пузыре?
Нам нужно 2 вещи, первая часть называется Tracer (или отслеживание инструментов). Это в основном говорит вам, какие инструкции были выполнены в приложении. AFL делает это простым способом, прыгая между этапами компиляции. После генерации сборки, но перед сборкой программы она ищет основные блоки (поиск окончания, проверяя на инструкции по прыжкам/ветвью) и добавляет код в каждый блок, который отмечает блок/край как выполненное (вероятно, в некоторую теневую память или что -то в этом роде). Если у нас нет исходного кода, мы можем использовать другие методы для отслеживания (например, PIN -код, отладчик и т. Д.). Оказывается, даже Асан может дать информацию о покрытии (см. Документы для этого).
Во второй части мы затем используем информацию о покрытии, предоставленную Tracer, чтобы отслеживать новые пути по мере их появления, и добавляем эти сгенерированные образцы в корпус для случайного отбора в будущем.
Есть несколько механизмов, чтобы сделать трассировку. Они могут быть на основе программного обеспечения или на основе аппаратного обеспечения. Для оборудования на основе, например, существуют некоторые функции процессора Intel, где дается буфер в памяти, он записывает информацию всех основных блоков, проходящих в этот буфер. Это функция ядра, поэтому ядро должно поддерживать его и предоставить его как API (что делает Linux). Для программного обеспечения мы можем сделать это, добавив в код или используя отладчик (используя временные точки останова, или через одиночную ступеню), или использовать атмосферные способности трассировщика и использовать крючки, или используйте крючки, или эмуляторы, или целую кучу других способов.
Другой способ дифференцировать механизмы-это либо трассировка черной коробки (где вы можете использовать только немодифицированный бинарный), либо программное трассировку белой коробки (где у вас есть доступ к исходному коду, и изменить сам код, чтобы добавить в код отслеживания).
AFL использует программные приборы во время компиляции в качестве метода для отслеживания (или эмуляции QEMU). Honggfuzz поддерживает как программные, так и аппаратные методы отслеживания. Другие умные пузырьки могут быть разными. Тот, который Gyn Builds использует трассировку/покрытие, предоставляемое Address Danitizer (ASAN).
Некоторые пумпы используют «скорость скорости» (то есть увеличение скорости пушина), например, путем создания Forkserver или других подобных идей. Может быть, стоит изучить их в какой -то момент :)
Написано 20 апреля 2017 года
Под влиянием этого удивительного живого потока Gynvael Coldwind, где он говорит о том, о чем идет речь, а также строит базовый пузырь с нуля!
Что такое пузырь, в первую очередь? И почему мы его используем?
Учтите, что у нас есть библиотека/программа, которая принимает входные данные. Вход может быть структурирован каким -то образом (скажем, PDF, или PNG, или XML, и т. Д., Но это не должно быть никакого «стандартного» формата). С точки зрения безопасности, интересно, если есть граница безопасности между входом и процессом / библиотекой / программой, и мы можем передать некоторый «специальный ввод», что вызывает непреднамеренное поведение за пределами этой границы. Фуззер - это один из таких способов сделать это. Это происходит путем «мутирующих» вещей в входе (тем самым , возможно, его повреждено), чтобы привести либо к нормальному выполнению (включая безопасные ошибки), либо к сбою. Это может произойти из -за того, что логика корпуса края плохо обрабатывается.
Сбой - самый простой способ для условий ошибок. Там могут быть и другие. Например, использование ASAN (Addressainzer) и т. Д. Может также выявить больше вещей, что может быть проблемой безопасности. Например, один переполнение буфера с одним байтом может не вызвать сбой самостоятельно, но, используя Asan, мы могли бы поймать даже это с помощью пузырька.
Другое возможное использование для Fuzzer заключается в том, что входы, генерируемые путем Fuzzing One Program, также могут использоваться в другой библиотеке/программе и посмотреть, есть ли различия. Например, некоторые математические библиотеки были замечены. Это обычно не приводит к проблемам безопасности, поэтому мы не будем сосредоточиться на этом.
Как работает пузырь?
Fuzzer-это в основном петля Re-Execute Repeat, которая исследует пространство состояния приложения, чтобы попытаться «случайным образом» найти состояния аварии / безопасности Vuln. Он не находит эксплойт, просто vuln. Основной частью Fuzzer является сам мутатор. Подробнее об этом позже.
Выходы из фьюзмера?
В Fuzzer отладчик (иногда) прикреплен к заявлению, чтобы получить какой -то отчет из аварии, чтобы иметь возможность проанализировать его позже как Security Vuln против доброкачественного (но, возможно, важного) аварии.
Как определить, какие области программ лучше всего подходят для Fuzz?
При пухке мы обычно хотим сосредоточиться на одном кусочке или небольшом наборе программы. Обычно это делается в основном для сокращения объема выполнения. Обычно мы концентрируемся только на анализе и обработке. Опять же, граница безопасности очень важна при принятии решения о том, какие части имеют значение для нас.
Типы пупзров?
Входные образцы, приведенные Fuzzer, называются корпусом . В Oldschool Fuzzers (он же «слепые»/«тупые» пухлеры) была необходимость для большого корпуса. Новые (например, «генетические» фьюззоры, например, AFL) не обязательно нуждаются в таком большом корпусе, поскольку они сами исследуют государство.
Как полезны истешение?
Фуззеры в основном полезны для «низких висящих фруктов». Он не найдет сложные логические ошибки, но он может найти легко найти ошибки (которые на самом деле иногда легко пропустить во время ручного анализа). Хотя я мог бы сказать ввод на протяжении всей этой ноты и обычно ссылаюсь на входной файл , это не должно быть просто. Fuzzers могут обрабатывать входы, которые могут быть stdin или входным файлом или сетевым розетком или многими другими. Однако без особой потери общности мы можем думать об этом как просто файл.
Как написать (базовый) раздумчик?
Опять же, это просто должна быть петля, заполняемая мутат-закупкой. Мы должны часто вызывать цель ( subprocess.Popen ). Мы также должны быть в состоянии передать вход в программу (например, файлы) и обнаруживать сбои ( SIGSEGV и т. Д. Приводят исключения, которые можно поймать). Теперь нам просто нужно написать мутатор для входного файла и продолжать вызывать цель в мутированных файлах.
Мутаторы? Что?!?
Там может быть несколько возможных мутаторов. Простыми (т.е. простыми в реализации) могут быть мутирующие биты, мутирующие байты или мутантные значения «магии». Чтобы увеличить вероятность сбоя, вместо того, чтобы изменить только 1 бит или что -то в этом роде, мы можем изменить несколько (может быть, какой -то параметризованный процент из них?). Мы также можем (вместо случайных мутаций), изменить байты/слова/dwords/etc на некоторые «магические» значения. Значения магии могут быть 0 , 0xff , 0xffff , 0xffffffff , 0x80000000 (32-битный INT_MIN ), 0x7fffffff (32-битный INT_MAX ) и т. Д. В основном выбирают те, которые являются общими для возникновения проблем безопасности (потому что они могут вызвать некоторые краевые случаи). Мы можем написать более умные мутаторы, если мы знаем больше информации о программе (например, для целых чисел на основе строк мы могли бы написать что -то, что меняет целочисленную строку на "65536" или -1 и т. Д.). Мутаторы, основанные на чанке, могут перемещать кусочки (в основном, реорганизация ввода). Аддитивные/аппликативные мутаторы также работают (например, вызывая больший вход в буфер). Трюнкторы также могут работать (например, иногда EOF может быть плохо обрабатываться). По сути, попробуйте целую кучу творческих способов пошевать. Чем больше опыта в отношении программы (и эксплуатации в целом), могут быть возможны более полезные мутаторы.
Но что это за «генетический» пушистый?
Это, вероятно, дискуссия на более позднее время. Тем не менее, пара ссылок на некоторые современные (открытый исходный код) фьюззеры являются AFL и Honggfuzz.
Написано 7 апреля 2017 года
Под влиянием хорошего вызова в PicoCTF 2017 (имя вызова не раздано, поскольку конкурс все еще продолжается)
Предупреждение: эта заметка может показаться простой/очевидной для некоторых читателей, но это требует сказать, поскольку наслоение не было кристально ясным для меня до недавнего времени.
Конечно, при программировании все мы используем абстракции, будь то классы и объекты, или функции, или мета-функции, или полиморфизм, или монады, или функторы, или все это джаз. Тем не менее, можем ли мы действительно иметь такую вещь во время эксплуатации? Очевидно, что мы можем использовать ошибки, сделанные при реализации вышеупомянутых абстракций, но здесь я говорю о чем -то другое.
В нескольких CTF, всякий раз, когда я писал эксплойт ранее, это был специальный сценарий эксплойта, который бросает оболочку. Я использую удивительные pwntools в качестве рамки (для подключения к сервису, конвертации вещей, а также Dynelf и т. Д.), Но это все. Каждый эксплойт имел тенденцию быть специальным способом работы по цели произвольного выполнения кода. Тем не менее, эта текущая задача, а также размышления о моей предыдущей заметке о «продвинутой» эксплуатации формирования форматирования заставили меня понять, что я могу последовательно уложить свои подвиги и пройти через различные слои абстракции, чтобы наконец достичь необходимой цели.
Например, давайте рассмотрим уязвимость как логическую ошибку, которая позволяет нам выполнять чтение/запись 4 байтов, где -то в небольшом диапазоне после буфера. Мы хотим злоупотреблять этим вплоть до получения выполнения кода и, наконец, флага.
В этом сценарии я бы считал, что эта абстракция является short-distance-write-anything . С этим сам, очевидно, мы не можем многое сделать. Тем не менее, я делаю небольшую функцию Python vuln(offset, val) . Однако, поскольку сразу после буфера могут быть некоторые данные/метадаты, которые могут быть полезны, мы можем злоупотреблять этим, чтобы построить как read-anywhere , так write-anything-anywhere примитивах. Это означает, что я пишу короткие функции Python, которые вызывают ранее определенную функцию vuln() . Эти функции get_mem(addr) и set_mem(addr, val) выполняются просто (в этом текущем примере), просто используя функцию vuln() для перезаписи указателя, который затем может быть связан с в другом месте в бинаре.
Теперь, после того, как у нас появятся эти абстракции get_mem() и set_mem() , я создаю абстракцию против ASLR, в основном протекая 2 адреса из gat to get_mem() и сравнивая с базой данных LIBC (спасибо @niklasb за создание базы данных). Отвращения от них дают мне надежно, что libc_base , что позволяет мне заменить любую функцию в GET на другой от LIBC.
По сути, это дало мне контроль над EIP (в тот момент, когда я могу «запустить» одну из этих функций , когда я хочу). Теперь все, что остается для меня, - это позвонить на спусковой крючок с правильными параметрами. Поэтому я настроил параметры как отдельную абстракцию, а затем вызовут trigger() , и у меня есть доступ к оболочке в системе.
TL; DR: можно построить небольшие примитивы эксплуатации (которые не имеют слишком большой власти), и, объединив их и создав иерархию более сильных примитивов, мы можем получить полное исполнение.
Написано 6 апреля 2017 года
Под влиянием этого удивительного живого потока Гинваэль Колдвонд, где он говорит об эксплуатации формата строк
Простой формат строки эксплойтов:
Вы можете использовать %p чтобы увидеть, что находится на стеке. Если сама строка формата находится в стеке, то можно поместить адрес (скажем, foo ) на стек, а затем попытаться использовать его, используя позицию n$ (например, AAAA %7$p может вернуть AAAA 0x41414141 , если 7 является позицией в стеке). We can then use this to build a read-where primitive, using the %s format specifier instead (for example, AAAA %7$s would return the value at the address 0x41414141, continuing the previous example). We can also use the %n format specifier to make it into a write-what-where primitive. Usually instead, we use %hhn (a glibc extension, iirc), which lets us write one byte at a time.
We use the above primitives to initially beat ASLR (if any) and then overwrite an entry in the GOT (say exit() or fflush() or ...) to then raise it to an arbitrary-eip-control primitive, which basically gives us arbitrary-code-execution .
Possible difficulties (that make it "advanced" exploitation):
If we have partial ASLR , then we can still use format strings and beat it, but this becomes much harder if we only have one-shot exploit (ie, our exploit needs to run instantaneously, and the addresses are randomized on each run, say). The way we would beat this is to use addresses that are already in the memory, and overwrite them partially (since ASLR affects only higher order bits). This way, we can gain reliability during execution.
If we have a read only .GOT section, then the "standard" attack of overwriting the GOT will not work. In this case, we look for alternative areas that can be overwritten (preferably function pointers). Some such areas are: __malloc_hook (see man page for the same), stdin 's vtable pointer to write or flush , etc. In such a scenario, having access to the libc sources is extremely useful. As for overwriting the __malloc_hook , it works even if the application doesn't call malloc , since it is calling printf (or similar), and internally, if we pass a width specifier greater than 64k (say %70000c ), then it will call malloc, and thus whatever address was specified at the global variable __malloc_hook .
If we have our format string buffer not on the stack , then we can still gain a write-what-where primitive, though it is a little more complex. First off, we need to stop using the position specifiers n$ , since if this is used, then printf internally copies the stack (which we will be modifying as we go along). Now, we find two pointers that point ahead into the stack itself, and use those to overwrite the lower order bytes of two further ahead pointing pointers on the stack, so that they now point to x+0 and x+2 where x is some location further ahead on the stack. Using these two overwrites, we are able to completely control the 4 bytes at x , and this becomes our where in the primitive. Now we just have to ignore more positions on the format string until we come to this point, and we have a write-what-where primitive.
Written on 1st April 2017
Influenced by this amazing live stream by Gynvael Coldwind, where he explains about race conditions
If a memory region (or file or any other resource) is accessed twice with the assumption that it would remain same, but due to switching of threads, we are able to change the value, we have a race condition.
Most common kind is a TOCTTOU (Time-of-check to Time-of-use), where a variable (or file or any other resource) is first checked for some value, and if a certain condition for it passes, then it is used. In this case, we can attack it by continuously "spamming" this check in one thread, and in another thread, continuously "flipping" it so that due to randomness, we might be able to get a flip in the middle of the "window-of-opportunity" which is the (short) timeframe between the check and the use.
Usually the window-of-opportunity might be very small. We can use multiple tricks in order to increase this window of opportunity by a factor of 3x or even up to ~100x. We do this by controlling how the value is being cached, or paged. If a value (let's say a long int ) is not aligned to a cache line, then 2 cache lines might need to be accessed and this causes a delay for the same instruction to execute. Alternatively, breaking alignment on a page, (ie, placing it across a page boundary) can cause a much larger time to access. This might give us higher chance of the race condition being triggered.
Smarter ways exist to improve this race condition situation (such as clearing TLB etc, but these might not even be necessary sometimes).
Race conditions can be used, in (possibly) their extreme case, to get ring0 code execution (which is "higher than root", since it is kernel mode execution).
It is possible to find race conditions "automatically" by building tools/plugins on top of architecture emulators. For further details, http://vexillium.org/pub/005.html
Written on 31st Mar 2017
Influenced by this amazing live stream by Gynvael Coldwind, where he is experimenting on the heap
Use-after-free:
Let us say we have a bunch of pointers to a place in heap, and it is freed without making sure that all of those pointers are updated. This would leave a few dangling pointers into free'd space. This is exploitable by usually making another allocation of different type into the same region, such that you control different areas, and then you can abuse this to gain (possibly) arbitrary code execution.
Double-free:
Free up a memory region, and the free it again. If you can do this, you can take control by controlling the internal structures used by malloc. This can get complicated, compared to use-after-free, so preferably use that one if possible.
Classic buffer overflow on the heap (heap-overflow):
If you can write beyond the allocated memory, then you can start to write into the malloc's internal structures of the next malloc'd block, and by controlling what internal values get overwritten, you can usually gain a read-what-where primitive, that can usually be abused to gain higher levels of access (usually arbitrary code execution, via the GOT PLT , or __fini_array__ or similar).