Туториал по написанию эксплойтов часть 7: Юникод - от 0×00410041 до calc.exe
Наконец ... проведя пару недель работая над юникодом и юникодными экслойтами, я счастлив, что могу опубликовать эту статью, относящуюся к серии написания эксплойтов: написание эксплойтов для стека основанного на переполнении буфера Юникода.
Бывало что вы сталкивались (а может и нет
) с ситуацией, когда вы выполнили переполнение стека буфера, перезаписывая или RET адрес или SEH записи, но вместо того, чтобы получать 0×41414141 в EIP, вы получили 0×00410041.
Иногда, когда данные используются в функции, применяются некоторые манипуляции. Иногда данные преобразуются в верхний регистр, в нижний регистр, и т. д. В некоторых случаях данные конвертируется в Юникод. Когда вы видите, 0×00410041 в EIP, во многих случаях, это, вероятно, означает, что ваша полезная нагрузка была преобразована в Юникод, прежде чем она попала в стек.
В течение долгого времени, люди полагали, что этот тип перезаписи не может быть использован. Это могло привести к отказу в обслуживании, но не к выполнению кода.
В 2002 году Крис Анлей написал документ, показывающий, что это утверждение ложно. Так родился термин "Венецианский Шеллкод".
В январе 2003 года, статья «Phrack(http://www.phrack.org/issues.html?issue=61&id=11#article)» была написана автором по имени Обскоу (Obscou), в которой была продемонстрирована техника того, как превратить эти знания в рабочий шеллкод, и примерно через месяц, Дэйв Аител выпустил скрипт для автоматизации этого процесса.
В 2004 году FX продемонстрировали новый скрипт, который позволит оптимизировать эту технику еще дальше.
Наконец, некоторое время спустя, SkyLined выпустил свой знаменитый альфа-2 кодировщик для паблика, который также позволяет создавать юникод-совместимый шелл-код. Мы еще поговорим об этих методах и инструментах позже.
2009 год – и вот мой туториал. Он не содержит ничего нового, но призван собрать весь имеющийся материал воедино в этом документе.
Для того чтобы перейти от поиска 0×00410041 к созданию рабочих эксплойтов, существует несколько вещей, которые необходимо уточнить в первую очередь. Важно понимать, что такое юникод, почему данные преобразуются в юникод, как происходит преобразование, что влияет на процесс преобразования, и как преобразования влияют на процесс создания эксплойта.
Что такое Юникод и почему разработчик решает преобразовать данные в юникод?
Википедия гласит: «Юникод является стандартом кодирования символов, который позволяет компьютерам представлять и манипулировать текстом, выраженном в большинстве мировых письменных системах, неизменно.
Разработанный в тандеме со стандартом универсального набора символов и опубликованный в виде книги, как стандарт Юникода, последняя версия Юникода состоит из репертуара более 107000 символов охватывающих 90 скриптов, набора кода для визуальной ссылки, методологии кодирования и набора стандартных кодировок символов, перечисление характера свойств, таких как верхний и нижний регистры, набор справочных файлов данных компьютера, а также ряд сопутствующих товаров, таких как характер свойств, правила нормализации, декомпозиция, сортировка, передача и двунаправленный порядок отображения (для правильного отображения текста, содержащего скрипты как справа-налево, таких как арабский или иврит, так и слева-направо). ».
Вкратце, Юникод позволяет визуально представить и / или манипулировать текстом в большинстве систем по всему миру, в согласованном порядке. Таким образом, приложения можно использовать по всему миру, не беспокоясь о том, как текст будет выглядеть, когда отображается на компьютере - практически на любом компьютере - в другой части мира.
Большинство из вас должны быть более или менее знакомы с ASCII. В сущности, используется 7 бит для представления 128 символов, часто сохраняя их в 8 бит, или в один байта за символ. Первый символ начинается с 00 и последний представлен 7F в шестнадцатеричной системе. (Вы можете увидеть полную ASCII таблицу, пройдя по адресу http://www.asciitable.com/).
Юникод бывает разным. Тем временем как есть много различных форм Юникода, UTF-16 является одним из самых популярных. Это не удивительно, так как он состоит из 16 бит, и разбивается в различные блоки/зоны (подробнее на http://czyborra.com/unicode/characters.html). (К вашему сведению, расширение было определено для того, чтобы обеспечить 32 бита). Просто помните: символы, необходимые для сегодняшних живых языков, все еще должны быть помещены в оригинальный Юникод плана 0 (a.k.a. Basic Multilangual Plane = BMP). Это означает, что наиболее простые языковые символы, как те, которые используются для написания данной статьи, представленные в Юникоде, начинаются с 00 (следуемый другим байтом, который соответствует шестнадцатеричному значению оригинального ASCII символа).
Вы можете найти большой обзор различных кодов символов Юникода здесь (http://unicode.org/charts/index.html).
Пример: ASCII символов 'A' = 41 (шестнадцатеричное значение), репрезентацией Основного Латинского Юникода является 0041.
Существует еще очень много страниц кода, а некоторые из них не начинаются с 00. Это важно помнить, тоже.
Пока все хорошо - иметь единый способ представления символов это хорошо ... но почему же многие вещи все еще в ASCII? Ну, большинство приложений, которые работают со строками, используют нулевой байт null-терменированной строки. Так что, если вы постараетесь наполнить юникод данные в ASCII-строку, то строка будет закончена сразу же ... Вот почему, например, простые текстовые приложения (такие как, SMTP, POP3 и т.д.) по-прежнему используют ASCII для настройки связи. (Хорошо, полезная нагрузка может быть закодирована и может использовать юникод, но транспоризация в приложении использует ASCII).
Если вы преобразуете ASCII текст в Юникод (кодовую страницу ANSI), то результат будет выглядеть так, как если бы "00" добавляется перед каждым байтом. Таким образом, AAAA (41 41 41 41) будет выглядеть так 0041 0041 0041 0041. Конечно, это всего лишь результат преобразования данных в широкий символ данных. Результат любого преобразования юникода зависит от кодировки, которая была использована.
Давайте посмотрим на функцию MultiByteToWideChar (которая отображает строку символов в wide-character unicode символов):
Как вы можете видеть, кодировка важна. Некоторые возможные значения:
CP_ACP (ANSI кодировка, которая используется в Windows, также известный как UTF-16), CP_OEMCP (OEM кодировка), CP_UTF7 (UTF-7 кодировка), CP_UTF8 (UTF-8 кодировка) и т.д.
Параметр lpMultiBytestr содержит строку символов, которые должны быть преобразованы, и lpWideCharStr содержит указатель на буфер, который будет получать переведенную (юникод) строку.
Так что это неправильно утверждать, что юникод = 00 + оригинальный байт. Это зависит от кодировки.
Вы можете посмотреть код страницы, который используется в вашей системе, взглянув на "Язык и региональные стандарты". В моей системе, это выглядит так:
Документ в FX показывает хорошую таблицу ASCII символов (в шестнадцатеричном виде), а также различные представления юникода в шестнадцатеричном виде (ANSI, OEM, UTF-7 и UTF-8). Вы заметите, что с ASCII 0×80, некоторые из ANSI представлений не содержат нулевых байтов больше (но они преобразуются в 0xc200XXXX или 0xc300XXXX), некоторые из OEM преобразований совершенно разные, и так далее.
Поэтому важно помнить, что только ASCII символы между 01h и 7FH имеют представительства в ANSI юникоде, где нулевые байты добавляются точно. Нам понадобятся эти знания в дальнейшем.
Разработчик может решить использовать эту функцию по назначению, по очевидным причинам (как указано выше). Но иногда разработчик может даже не знать, какое расширение юникода будет использоваться " под колпаком ", когда приложение построено/скомпелированно. Собственно говоря, Win32 API часто переводять строки в Юникод прежде чем начать работать с ними. В некоторых случаях (например, с Visual Studio), используемый API зависит от того устанавливается макрос _ЮНИКОД во время сборки или нет. Если макрос установлен, подпрограммы и типы отображаются на объектах, которые могут иметь дело с юникодом. API функций могут измениться. Например, вызов CreateProcess меняется на CreateProcessW (Unicode) или CreateProcessA (ANSI), основанный на статусе макроса.
Что является результатом преобразования в Юникод/влияние на построение эксплоита?
Когда входная строка преобразуется в ANSI юникод, ко всем символам от 0 × 00 и 0x7f, присоединятся спереди нулевой байт. Кроме того, множество символов выше 0x7F переводятся в 2 байта, и эти 2 байта могут не содержать исходный байт.
Это сводит на нет все, что мы изучали об эксплойтах и шеллкоде до сих пор.
Во всех предыдущих туториалах, мы пытались переписать EIP с 4 байтами (без учета намеренной частичной перезаписи).
С Юникодом, вы контролируете только 2 байта из этих 4 байтов (2 других, скорее всего, будет нулями ... таким образом, вы контролируете эти нули тоже)
Кроме этого, множество доступных инструкций (используются для прыжков, для шеллкода и т. д.) становятся ограниченными. В конце концов, нулевой байт помещается перед большинством байтов. И на вершине всего, другие байты (> 0x7F) просто преобразуются в нечто совсем другое. Статья Phrack (см. главу 2) объясняет, какие инструкции могут быть использованы, а какие нет.
Даже такие простые вещи, такие как куча nop-ов (0×90) становится проблемой. Первый nop может работать. Второй nop (в связи с выравниванием) станет инструкцией 0090 (или 009000) ... и это уже больше не nop.
Таким образом, это звучит так, как будто придется преодолевать много препятствий. Неудивительно, что сначала люди думали, что написать эксплойт было невозможно ...
Читайте документы
Я кратко объяснил, что произошло в последующие месяцы и годы после публикации «Создание произвольных шеллкодов в строке с юникод расширением ".
Когда вы возвращаетесь к чтению и попыткам понять все эти документы и методы (см. URL в начале этого туториала), становится ясно, что это отличный материал. К сожалению, мне потребовалось некоторое время, чтобы понять и скомпоновать все вместе. Хорошо, некоторые понятия хорошо объясняются в этих документах ... но они показывают вам только часть картины. И я не мог найти хорошие ресурсы, в которых все это было скомпоновано вместе.
К сожалению, и несмотря на мои усилия и на тот факт, что я задавал много вопросов (по электронной почте, в Twitter, в списках рассылки и т.д.), я не получил какую-либо весомую помощь от других людей.
Так что либо не так много людей хотели объяснить мне это (возможно, они забыли, что они не родились с этими навыками ... они тоже должны были учиться этому так или иначе), они были слишком заняты, чтобы отвечать на мой хромой вопрос, или просто не могли объяснить мне, или они просто игнорировали меня, потому что ...? Я не знаю.
В любом случае ... в конце концов, несколько добрых людей нашли время, чтобы дать мне исчерпывающий ответ (а не просто ссылаясь на некие PDF документы снова и снова). Спасибо, ребята. Если вы читаете это, и если вы хотите, чтобы ваше имя было здесь, дайте мне знать.
Возвращаясь к этим PDF файлам ... хорошо, эти документы и инструменты довольно хороши. Но каждый раз, когда я читал один из этих документов, я начинал думать: «Хорошо, это здорово ... теперь как я могу применить это? Как преобразовать эту концепцию в рабочий эксплойт ".
Пожалуйста, сделайте мне одолжение, и найдите время, чтобы прочитать эти документы самостоятельно. Если вам удастся полностью понять, как построить юникод эксплойты исключительно на основе этих документов, от А до Z, то это здорово ..., то вы можете пропустить остаток этого туториала (или продолжить читать и смеяться надо мной, потому что я с трудом понимал это в своё время...)
Но если вы хотите узнать, как склеить все эти файлы в PDF формате и инструменты вместе и пройти лишнюю милю, необходимую для преобразования этих файлов в эксплойты, то читайте дальше.
Некоторые из вас могут быть знакомы с юникод эксплойтами, связанными с ошибоками браузера и heap spray. Несмотря на то, что количество ошибок в браузере значительно возросло за последние несколько лет (и число эксплойтов и ресурсов увеличиваются), я не собираюсь обсуждать технику эксплойта сегодня. Мое основное внимание будет сосоредоточено на том, чтобы объяснить стек на основе переполнения, которое подлежит юникод преобразованию. Некоторые части этого документа будут служить руководством при атаке браузеров, а также (особенно часть этого документа, посвященная шеллкоду).
Можем ли мы создать эксплойт, когда наш буфер преобразован в юникод?
Прежде всего
Прежде всего, вы узнаете, что не существует всеобъемлющего шаблона для создания юникод эксплойтов. Каждый эксплойт может (и, вероятно, будет) быть разным, требовать разного подхода и может потребовать много работы и усилий. Вам придется играть со смещениями, регистрами, инструкциями, написать свою собственную линию венецианского шеллкода, и т.д. ... Так что пример, который я буду использовать сегодня, может не быть полезным во всех конкретных случаях. Пример, который я буду использовать, является только примером о том, как использовать различные техники, в основном демонстрирующие пути создания собственных строк кода и скомпоновать все вместе так, чтобы получить эксплойт, который делает то, что вы хотите, чтобы он делал.
EIP является 0×00410041. И что теперь?
В предыдущих туториалах мы обсуждали 2 вида эксплойтов: директива RET перезаписи или SEH перезаписи. Эти 2 типа перезаписи, конечно, остаются в силе с юникод эксплойтами. В типичной стеке на основе переполнения, вы либо перезаписывыете RET с 4 байтами (но из-за юникода, только 2 байта находятся под вашим контролем), либо перезаписывыете структурированные поля обработчика исключений записи (следующий SEH и SE Handler), каждый с 4 байтами, опять же, из которых только 2 находятся под вашим контролем.
Как мы можем по-прежнему злоупотреблять этим, чтобы заставить EIP делать то, что мы хотим, чтобы он делал? Ответ прост: перезаписать 2 байта в EIP чем-то полезным.
Директива RET: перезапись EIP чем-то полезным
Глобальная идея "прыжок в ваш шелл-код", когда владеешь EIP, является все той же, является ли это ASCII или юникод переполнение буфера. В случае прямого RET перезаписи, вы должны будете найти указатель на инструкцию (или серию инструкций), который приведет вас к вашему шелл-коду, и вам нужно перезаписать EIP с этим указателем. Таким образом, вы должны найти регистр, который указывает на ваш буфер (даже если он содержит нулевые байты между каждым символом - не нужно беспокоиться по этому поводу), и вам нужно перейти («перепрыгнуть») к этому регистру.
Единственная проблема заключается в том, что вы не можете просто взять любой адрес. Адрес, который вам нужно поискать должен быть "отформатированным" таким образом, что, если к символам спереди добавляются 00, адрес должен остаться действительным.
Таким образом, по существу, у нас есть только 2 варианта:
1. найти адрес, который указывает на вашу jump/call/… инструкцию, и это выглядит так: 0x00nn00mm. Так что если вы перезапишите RET с 0xnn, 0xmm он станет 00nn00mm
или, наоборот, если вы не можете найти такой адрес:
2. найти адрес, который также отформатирован 0x00nn00mm, и близок к call/jump/… инструкции, которую вы хотите выполнить. Убедитесь, что инструкции между адресом и фактическими call/jump адресами не повредит ваш стек/регистры, и использует этот адрес.
Как мы можем найти такие адреса?
FX написал хороший плагин для OllyDbg (http://www.openrce.org/downloads/details/120/OllyUni) (так называемый OllyUNI (http://www.phenoelit-us.org/win/)), и мой собственный плагин для ImmDbg pvefindaddr (http://www.corelan.be:8800/index.php/my-free-tools/security/pvefindaddr-py-immunity-debugger-pycommand/) поможет вам с этой задачей, а также:
Допустим, вам нужно перейти к eax. Скачайте pvefindaddr.py и поместите его в папку pyCommand вашего ImmDbg установочного. Затем откройте уязвимое приложение в ImmDbg и запустите
Это выдаст вам список всех адресов в "jump eax". Эти адреса будут отображаться не только в логе, но они также будут записаны в текстовый файл с именем j.txt.
Откройте этот файл и задайте поиск "Unicode".
Вы можете найти два типа записей: записи, которые говорят: "Возможно Юникод совместимый" и записи, которые говорят "Юникод совместимый".
Если вы сможете найти "Юникод совместимые" адреса, то эти адреса будут в 0x00nn00mm форме. (Таким образом, вы должны быть в состоянии использовать один из этих адресов без дальнейших исследований).
Если вы нашли " Возможно Юникод совместимые" адреса, то вы надо следить за этими адресами. Они будут в 0x00nn0mmm форме. Так что если вы взгляните в инструкции между 0x00nn00mm и 0x00nn0mmm, и вы видите, что эти инструкции не будут вредить приложению flow/registers/…, то вы можете использовать 0x00nn00mm адрес (и это будет встречатся на всем пути, пока не достигнет call/jump инструкции в 0x00nn0mmm). В сущности, вы будете прыгать более или менее близко/рядом к реальной инструкции, и будете надеяться, что инструкции между вашим местоположением и реальный прыжком не убьют вас.
OllyUNI в основном будет делать то же самое. Он будет искать Юникод дружественные адреса. Собственно говоря, он будет искать все call/jump reg/… инструкции (так что вам придется пройти через лог и смотреть сможете ли вы найти адрес, который прыгает на нужный регистр).
В принципе, мы ищем адреса, которые содержат нулевые байты в нужном месте. Если EIP содержит 0x00nn00mm, то вы должны найти адрес с тем же форматом. Если EIP содержит 0xnn00mm00, то вы должны найти адрес с этим форматом.
В прошлом мы всегда старались избегать нулевых байтов, так как он действует как ограничитель строки. На этот раз нам нужны адреса с нулевыми байтами. Нам не нужно беспокоиться об ограничении строки, потому что мы не собираемся ставить нулевые байты в строку, которая передается в приложение. Юникод преобразование будет вставлять нулевые байты для нас автоматически.
Давайте предположим, что вы нашли адрес, который сделает прыжок. Скажем, адрес 0x005E0018. Этот адрес не содержит символы, которые имеют шестигранную > 7f значение. Так что адрес должен работать.
Я полагаю, что вы поняли, после какого количества байт вы перезапишите сохраненный EIP. (Вы можете использовать метасплоит шаблон для этого, но вам придется смотреть на байты до и после перезаписи EIP, для того чтобы получить не менее 4 символов). Я покажу пример того, как сделать соответствие позже в этом туториале.
Предположим, что вы перезаписываете EIP после отправки 500 А. И вы хотите перезаписать EIP с "jump eax (в 0x005e0018)" (потому что EAX указывает на А), тогда ваш скрипт должен выглядеть следующим образом:
Таким образом, вместо того, чтобы перезаписывать EIP с паком ('V', 0x005E0018), вы перезаписываете EIP с 5E 18. Юникод добавляет нулевые байты перед 5E, и между 5E и 18, так EIP будет перезаписан с 005e0018.
(string-to-widechar преобразоване позаботилось о правильном добавлении нулей, которые мы хотели добавить. Шаг 1 выполнен.)
SEH основанный: владение EIP + short jump? (или нет?)
Что делать, если уязвимость основана на SEH? Из части 3 и 3б туториала, мы знаем, что мы должны переписать SE Handler с указателем на pop pop ret, и переписать nSEH с коротким прыжком (short jump).
С юникодом, вам все равно придется переписывать SE Handler с указателем на pop pop ret. Опять же, pvefindaddr поможет нам:
Опять же, это выведет запись в лог, а также в файл под названием ppr2.txt. Откройте файл и поищите "Unicode" еще раз. Если вы сможете найти вход, который не содержит байты > 7f, то вы можете попробовать переписать SE Handler с этим адресом. Опять же, оставьте нулевые байты (они будут добавлены автоматически в связи с юникод преобразованием). В nseh, положите \xcc\xcc (2 точки остановки, 2 байта. Опять же, нулевые байты будут добавлены), и посмотрите, что произойдет.
Если все пойдет хорошо, pop pop ret выполнена, вы будете перенаправлены на первую точку остановки.
В не юникод эксплойтах, вам придется заменять эти точки остановки в nseh с short jump-ом и делать jump через адрес SE Handler на шеллкод. Но я могу заверить вас, что прописывая short jump в юникоде, только с 2 байтами, отделенных нулевыми байтами ... не рассчитывайте на это. Он не будет работать.
Так что на этом и заканчивается.
SEH основанный: jump
Окей. Это не конец. Я просто шучу. Мы можем сделать это, при определенных условиях. Я объясню, как это работает на примере, который приводится внизу этого поста, поэтому сейчас я буду придерживаться теории. Все станет ясно, когда вы взгляните на пример, так что не волнуйтесь. (и если вы не понимаете, просто спросите. Я буду более чем счастлив объяснить вам).
Теория: вместо того чтобы писать код, чтобы сделать короткий прыжок (short jump) (0xeb, 0 × 06), возможно, мы можем запустить эксплойт безвредного кода, так, что он просто пройдет по перезаписанным nseh и seh, и закончится сразу в том месте, где мы должны перезаписали SEH, выполняя код, который мы поместили после того, как перезаписали SE структуру. Собственно говоря это то, чего мы хотели достичь в первую очередь, перепрыгивая через nSEH и SEH.
Для того чтобы сделать это, нам нужно 2 вещи:
- Несколько инструкций, которые при исполнении не будут причинять никакого вреда. Нам нужно поместить эти инструкции в nSEH и
- юникод совместимый адрес используемый для перезаписи SE Handler не должен, когда выполняется как инструкция, причинять никакого вреда тоже.
Сбивает с толку? Не паникуйте. Я объясню это дальше подробно в примере, приведенном ниже в этом блоге.
Ну так что, мы можем только поставить 0x00nn00nn в EIP?
Да, и нет. Если вы посмотрите на таблицу юникод перевода, вы можете иметь некоторые другие опции, рядом с очевидным форматом 0x00nn00nn.
Ascii значения, представленные в шестнадцатеричном виде > 0x7f переводятся по-разному. В большинстве из этих случаев (см. таблицу на стр. 15 - 17), перевод превращает юникод версию во что-то другое.
Например 0 × 82 становится 1A20. Так что, если вы сможете найти адрес в формате 0x00nn201A, то вы можете использовать тот факт, что 0 × 82, будет автоматически пересчитано в 201A.
Единственная проблема, с которой вы можете столкнуться, это если вы строите эксплойт на основе SEH, это может привести к проблеме, потому что после pop pop ret, адресные байты выполняются как инструкции. Пока инструкции действуют как nops, или не вызывают больших изменений, все идет прекрасно. Я думаю, что вам просто надо проверить все доступные "юникод совместимые" адреса и убедиться самим, есть ли адрес, который будет работать. Опять же, вы можете использовать pvefindaddr (Immdbg плагин) для поиска нужного pop pop ret адреса, который являются юникод совместимым.
Адреса, которые вы будете искать могут начинаться или заканчиваться:
ac20 (=80 ASCII), 1a20 (=82 ASCII), 9201 (=83 ASCII), 1e20 (=84 ASCII), и так далее (просто посмотрите на таблицу перевода.). Успех не гарантирован, но это стоит попробовать.
Готов запустить шеллкод ... Но готов ли шеллкод?
Хорошо, теперь мы знаем, что положить в EIP. Но если вы посмотрите на свой ASCII шеллкод: он также будет содержать нулевые байты, и, если он использует инструкции (коды операций) выше 0x7F, инструкции, возможно, даже изменились. Как мы можем сделать это? Есть ли способ для преобразования ASCII шелл-кода (такие же, как те, которые создаются с метасплоит) в юникод совместимый шелл-код? Или мы должны написать наш собственный материал? Мы собираемся выяснить это.
Шеллкод: Техника 1: Найти ASCII эквивалент и перепрыгнуть к ней
В большинстве случаев, ASCII строка, которая подавалась в приложение преобразуется в юникод после того, как она была поставлена в стек или в память. Это означает, что это возможно найти ASCII версию вашего шеллкода где-то. Так что, если вы сможете указать EIP, чтобы он перепрыгнул к этому месту, это может сработать.
Если ASCII версия не достижима напрямую (прыжком в регистр), но вы контролируете содержимое одного из регистров, то вы можете перейти в этот регистр, и поместить некоторые основные jump-коды в том месте, которое сделает прыжок на ASCII версию. Мы поговорим об этом jump-коде позже.
Хороший пример эксплойта, который использует эту технику можно найти здесь.
Шеллкод: Техника 2: Пишем свой юникод-совместимый шеллкод с нуля
Правильно. Это возможно, нелегко, но возможно ... но есть лучшие способы. (см. техника 3)
Шеллкод: Техника 3: Использование декодера
Хорошо, мы знаем, что шеллкод генерируемый в метасплоит (или написанный вами) не будет работать. Если шеллкод не был написан специально для юникода, он не будет работать. (нулевые байты вставляются, опкоды изменились, и т.д.).
К счастью, пару умных людей придумали несколько инструментов (на основе концепции венецианского шелл-кода), которые решат этот вопрос. (Dave Aitel, FX и SkyLined).
По сути, все сводится к следующему: Вам нужно закодировать ASCII шеллкод в юникод-совместимый код, перед именем декодера (также юникод-совместимого). Затем, когда декодер выполнен, он раскодирует исходный код и выполнит его.
Существуют 2 основных способа сделать это: либо воспроизведя исходный код в отдельном месте памяти, и затем прыгать на то место, или путем изменения кода "в линии", и затем запустить воспроизведенный шеллкод. Вы можете прочитать все об этих инструментах (и принципах, на которых они основаны) в соответствующих документах, упомянутых в начале этого блога. Первый метод требует 2 вещи: один из реестров должен указывать на начало декодер+шеллкод, и один регистр должен указывать на область памяти, куда можно записать (и где это лучше, чтобы записать новый собранный шеллкод). Второй метод требует, чтобв только один из регистров указывал на начало декодер+шеллкод, и оригинальный шеллкод будет собран на месте.
Можем ли мы использовать эти инструменты для создания рабочих шеллкодов, и если да, то как мы должны их использовать? Давайте выясним.
1. makeunicode2.py (Dave Aitel)
Этот скрипт является частью CANVAS, коммерческий инструмент от Immunity. Так как я не имеют лицензии, я не был в состоянии проверить это (и, следовательно, я не могу объяснить, как им пользоваться).
2. vense.pl (FX)
На основании объяснений FX в 2004 его Blackhat презентации (http://www.blackhat.com/presentations/win-usa-04/bh-win-04-fx.pdf), этот удивительный Perl скрипт, кажется, производит усовершенствованную версию, которую можно получить makeunicode2.py
Выходной файл этого скрипта является строкой байт, содержащей декодер и оригинальный шеллкод все-в-одном. Таким образом, вместо того, чтобы размещать метасплоит сгенерированный шелл-код в буфер, вам необходимо поместить выходной файл vense.pl в буфер.
Для того, чтобы иметь возможность использовать декодер, вы должны быть в состоянии создать регистры следующим образом: один регистр должен указывать непосредственно на начало место расположения буфера, где ваш шеллкод (vense.pl сгенерированный шелл-код) будет помещен .
(В следующей главе я объясню, как изменить значения в регистрах, так чтобы вы могли указать один регистр в любое место куда вы хотите.). Далее, вам нужно иметь второй регистр, который указывает на ячейку памяти, доступную для записи и исполнения (RWX), и где лучше всего делать записи данных (без повреждения чего-нибудь еще).
Предположим, что регистр, который будет создан для того, чтобы указывать на начало vense-генерируемых шеллкодов, является eax, и edi указывает на местоположение для записи:
редактируем vense.pl и устанавливаем $basereg и $writable параметры до необходимого значения.
Далее, прокрутите вниз, и поищите $secondstage
Удалите содержимое этой переменной и замените его на свой собственный (сгенерированный метасплойтом) Perl шеллкод. (Это ASCII шеллкода, который запустится после того, как декодер сделает свое дело.)
Сохраните файл и запустите скрипт.
Выходной файл будет показывать:
- Оригинал шеллкода
- Новый шеллкод (тот, который включает в себя декодер).
Теперь используйте этот «новый» шеллкод в вашем эксплойте и убедитесь, eax указывает на начало этого шеллкода. Вам, скорее всего, придется настроить регистры (если вам повезло).
Когда регистры созданы, просто запустите "jump eax" и декодер извлечет оригинальный шелл-код и запустит его. Опять же, в следующей главе я вам покажу, как установить/настроить регистры и сделать прыжок (jump) с использованием юникод-совместимого кода. Примечание 1: вновь созданный шифровщик+шеллкод будет работать только когда он преобразуется в юникод, а затем запустится. Таким образом, вы не сможете использовать этот тип шеллкода в не-юникод эксплойте.
Примечание 2: несмотря на то, что алгоритм, используемый в этом скрипте является улучшением по сравнению с makeunicode2.py, вам все равно в конечном итоге закончите с довольно длинным шеллкодом. Так что вам нужно собственное пространство буфера (или короткие, не сложные шелл-коды) для того, чтобы использовать эту технику.
3. альфа2 (SkyLined)
Знаменитый альфа-2 кодировщик (также адаптированный в другие инструменты, такие как метасплойт, и куча других инструментов) будет принимать ваши оригинальный шеллкод, заворачивать его в декодер (очень похоже на то, что vense.pl делает), но преимуществом здесь является
- Вам нужно всего лишь регистр, который указывает на начало этого шеллкода. Вам не нужен дополнительный регистр, доступный для записи/исполнения.
- Декодер развернет исходный код на месте. Декодер самомодифицирующийся, и общее количество необходимого размера буфера меньше.
(в документации говорится, что "декодер изменяет свой собственный код, чтобы избежать ограничений буквенно-цифрового кода. Он создает декодер цикл, который декодирует оригинальный шеллкод из закодированных данных. Он перезаписывает закодированные данные с декодированным шелл-кодом и передает выполнение закончит. Чтобы это сделать, ему нужно прочитать, записать и выполнить разрешение на памяти, где он работает, и ему нужно знать его место в памяти (это baseaddress)
Вот как это работает:
1. генерируется необработанный шеллкод с msf полезной нагрузкой
2. необработанный шеллкод преобразовывается в юникод строку, используя альфа-2:
(Я удалил большую часть выходных данных. Просто создайте свой собственный шелл-код и скопируйте/вставьте выходные данные в ваш эксплойт скрипт)
Расположите выходные данные alpha2 преобразования в $shellcode переменную в вашем эксплойте. Опять же, убедитесь, что регистр (eax в моем примере) указывает на первый символ этого шеллкода, и убедитесь, что стоит переход на eax (jmp eax) (после создания регистра, если это было необходимо)
Если вы не можете подготовить/использовать регистр в качестве базового адреса, то альфа-2 также поддерживает метод, который попытается рассчитать свой базовый адрес с помощью SEH. И вместо указания регистра, просто укажите SEH. Таким образом, вы можете просто запустить код (даже если он не указывает непосредственно на один из регистров), и он все еще будет способен декодировать и запустить оригинальный шелл-код).
4. Метасплойт
Я попытался создать юникод совместимый шелл-код с помощью метасплойта, но изначально он не работает так, как я ожидал…
(поставьте все на одну строку)
(в основном, прежде кодируйте с alpha_mixed, а затем с unicode_upper). Выходной будет юникод совместимым шеллкодом для Perl
Результат: Метасплоит также может сделать это.
5. UniShellGenerator Back Khoa Internetwork Security (http://security.bkis.vn/)
Этот инструмент продемонстрирован в этой презентации. К сожалению, я не смог найти копию этого инструмента нигде, и человек/люди, которые писали инструмент не ответил мне также ...
Присоединение одиного к другому: подготовка регистров и переход к шеллкоду
Для того, чтобы бы выполнить шеллкод, вы должны достигнуть шеллкода. Является ли это ASCII версией шеллкода, или юникод версией (декодер), вам нужно, прежде всего, попасть туда. Для того, чтобы сделать это, вам часто будет необходимо создавать регистры определенным образом, используя собственный венецианский шеллкод и/или написать код, который будет осуществлять прыжок на данный реестр.
Написание этих строк кода требует немного творчества, требует, чтобы вы думали о регистрах, и будет требовать, чтобы вы могли писать некоторые основные ассемблер инструкции.
Написание jumpcode основывается исключительно на принципах венецианского шеллкода. Это означает, что
- У вас есть только ограниченный набор команд
- вам нужно следить за нулевыми байтами. Когда код помещается в стек, будут вставлены нулевые байты. Так что инструкции должны работать, когда нулевые байты добавляются
- вам нужно думать об опкоде выравнивания
Пример 1.
Допустим, вы нашли ASCII версию вашего шеллкода, без изменений, при 0 × 33445566, и вы заметили, что вы также контролируете eax. Вы перезаписываете EIP с прыжком в eax, и теперь, идея заключается в том, чтобы написать несколько строк кода в eax, который совершит прыжок на 0 × 33445566.
Если бы это не являлось юникодом, мы могли бы сделать это, используя следующие инструкции:
=> Мы бы расположили следующий код в eax: \xbb\x66\x55\x44\x33\xff\xe3, и мы бы перезаписали EIP с “jump eax”.
Но это юникод. Таким образом, очевидно, что это не будет работать.
Как мы можем достичь того же с юникодом совместимыми инструкциями?
Давайте взглянем на инструкцию mov в первую очередь. “mov ebx” = 0xbb, сопровождается тем, что вы хотите поместить в ebx. Этот параметр должен быть в 00nn00mm формате (так, когда нулевые байты вставлены, они будут вставлены туда где уже есть нули (или там, где мы ожидаем нули), не вызывая вопросов в инструкции). Например, вы можете сделать mov ebx, 33005500. Опкод для этого будет такой
bb00550033 #mov ebx,33005500h
Так байты для записи в eax (в нашем примере) следующие \xbb\x55\x33. Юникод вставит нулевые байты, в результате выдавая \xbb\x00\x55\x00\x33, что на самом деле является инструкцией, которая нам нужна.
Та же самая техника применяется для добавления и суб инструкции.
Вы можете использовать inc, dec инструкции также, чтобы изменить регистры или перенести позиции в стеке.
Статья Phrack о Построение IA32 "Unicode-Proof" Shellcodes показывает всю последовательность присоединения любого адреса в данный регистр, показывая, что именно я имею в виду. Возвращаясь к нашему примеру, мы хотим положить 0 × 33445566 в eax. Вот как это делается:
Наконец ... проведя пару недель работая над юникодом и юникодными экслойтами, я счастлив, что могу опубликовать эту статью, относящуюся к серии написания эксплойтов: написание эксплойтов для стека основанного на переполнении буфера Юникода.
Бывало что вы сталкивались (а может и нет
Иногда, когда данные используются в функции, применяются некоторые манипуляции. Иногда данные преобразуются в верхний регистр, в нижний регистр, и т. д. В некоторых случаях данные конвертируется в Юникод. Когда вы видите, 0×00410041 в EIP, во многих случаях, это, вероятно, означает, что ваша полезная нагрузка была преобразована в Юникод, прежде чем она попала в стек.
В течение долгого времени, люди полагали, что этот тип перезаписи не может быть использован. Это могло привести к отказу в обслуживании, но не к выполнению кода.
В 2002 году Крис Анлей написал документ, показывающий, что это утверждение ложно. Так родился термин "Венецианский Шеллкод".
В январе 2003 года, статья «Phrack(http://www.phrack.org/issues.html?issue=61&id=11#article)» была написана автором по имени Обскоу (Obscou), в которой была продемонстрирована техника того, как превратить эти знания в рабочий шеллкод, и примерно через месяц, Дэйв Аител выпустил скрипт для автоматизации этого процесса.
В 2004 году FX продемонстрировали новый скрипт, который позволит оптимизировать эту технику еще дальше.
Наконец, некоторое время спустя, SkyLined выпустил свой знаменитый альфа-2 кодировщик для паблика, который также позволяет создавать юникод-совместимый шелл-код. Мы еще поговорим об этих методах и инструментах позже.
2009 год – и вот мой туториал. Он не содержит ничего нового, но призван собрать весь имеющийся материал воедино в этом документе.
Для того чтобы перейти от поиска 0×00410041 к созданию рабочих эксплойтов, существует несколько вещей, которые необходимо уточнить в первую очередь. Важно понимать, что такое юникод, почему данные преобразуются в юникод, как происходит преобразование, что влияет на процесс преобразования, и как преобразования влияют на процесс создания эксплойта.
Что такое Юникод и почему разработчик решает преобразовать данные в юникод?
Википедия гласит: «Юникод является стандартом кодирования символов, который позволяет компьютерам представлять и манипулировать текстом, выраженном в большинстве мировых письменных системах, неизменно.
Разработанный в тандеме со стандартом универсального набора символов и опубликованный в виде книги, как стандарт Юникода, последняя версия Юникода состоит из репертуара более 107000 символов охватывающих 90 скриптов, набора кода для визуальной ссылки, методологии кодирования и набора стандартных кодировок символов, перечисление характера свойств, таких как верхний и нижний регистры, набор справочных файлов данных компьютера, а также ряд сопутствующих товаров, таких как характер свойств, правила нормализации, декомпозиция, сортировка, передача и двунаправленный порядок отображения (для правильного отображения текста, содержащего скрипты как справа-налево, таких как арабский или иврит, так и слева-направо). ».
Вкратце, Юникод позволяет визуально представить и / или манипулировать текстом в большинстве систем по всему миру, в согласованном порядке. Таким образом, приложения можно использовать по всему миру, не беспокоясь о том, как текст будет выглядеть, когда отображается на компьютере - практически на любом компьютере - в другой части мира.
Большинство из вас должны быть более или менее знакомы с ASCII. В сущности, используется 7 бит для представления 128 символов, часто сохраняя их в 8 бит, или в один байта за символ. Первый символ начинается с 00 и последний представлен 7F в шестнадцатеричной системе. (Вы можете увидеть полную ASCII таблицу, пройдя по адресу http://www.asciitable.com/).
Юникод бывает разным. Тем временем как есть много различных форм Юникода, UTF-16 является одним из самых популярных. Это не удивительно, так как он состоит из 16 бит, и разбивается в различные блоки/зоны (подробнее на http://czyborra.com/unicode/characters.html). (К вашему сведению, расширение было определено для того, чтобы обеспечить 32 бита). Просто помните: символы, необходимые для сегодняшних живых языков, все еще должны быть помещены в оригинальный Юникод плана 0 (a.k.a. Basic Multilangual Plane = BMP). Это означает, что наиболее простые языковые символы, как те, которые используются для написания данной статьи, представленные в Юникоде, начинаются с 00 (следуемый другим байтом, который соответствует шестнадцатеричному значению оригинального ASCII символа).
Вы можете найти большой обзор различных кодов символов Юникода здесь (http://unicode.org/charts/index.html).
Пример: ASCII символов 'A' = 41 (шестнадцатеричное значение), репрезентацией Основного Латинского Юникода является 0041.
Существует еще очень много страниц кода, а некоторые из них не начинаются с 00. Это важно помнить, тоже.
Пока все хорошо - иметь единый способ представления символов это хорошо ... но почему же многие вещи все еще в ASCII? Ну, большинство приложений, которые работают со строками, используют нулевой байт null-терменированной строки. Так что, если вы постараетесь наполнить юникод данные в ASCII-строку, то строка будет закончена сразу же ... Вот почему, например, простые текстовые приложения (такие как, SMTP, POP3 и т.д.) по-прежнему используют ASCII для настройки связи. (Хорошо, полезная нагрузка может быть закодирована и может использовать юникод, но транспоризация в приложении использует ASCII).
Если вы преобразуете ASCII текст в Юникод (кодовую страницу ANSI), то результат будет выглядеть так, как если бы "00" добавляется перед каждым байтом. Таким образом, AAAA (41 41 41 41) будет выглядеть так 0041 0041 0041 0041. Конечно, это всего лишь результат преобразования данных в широкий символ данных. Результат любого преобразования юникода зависит от кодировки, которая была использована.
Давайте посмотрим на функцию MultiByteToWideChar (которая отображает строку символов в wide-character unicode символов):
Код:
int MultiByteToWideChar(
UINT CodePage,
DWORD dwFlags,
LPCSTR lpMultiByteStr,
int cbMultiByte,
LPWSTR lpWideCharStr,
int cchWideChar
);
Как вы можете видеть, кодировка важна. Некоторые возможные значения:
CP_ACP (ANSI кодировка, которая используется в Windows, также известный как UTF-16), CP_OEMCP (OEM кодировка), CP_UTF7 (UTF-7 кодировка), CP_UTF8 (UTF-8 кодировка) и т.д.
Параметр lpMultiBytestr содержит строку символов, которые должны быть преобразованы, и lpWideCharStr содержит указатель на буфер, который будет получать переведенную (юникод) строку.
Так что это неправильно утверждать, что юникод = 00 + оригинальный байт. Это зависит от кодировки.
Вы можете посмотреть код страницы, который используется в вашей системе, взглянув на "Язык и региональные стандарты". В моей системе, это выглядит так:
Документ в FX показывает хорошую таблицу ASCII символов (в шестнадцатеричном виде), а также различные представления юникода в шестнадцатеричном виде (ANSI, OEM, UTF-7 и UTF-8). Вы заметите, что с ASCII 0×80, некоторые из ANSI представлений не содержат нулевых байтов больше (но они преобразуются в 0xc200XXXX или 0xc300XXXX), некоторые из OEM преобразований совершенно разные, и так далее.
Поэтому важно помнить, что только ASCII символы между 01h и 7FH имеют представительства в ANSI юникоде, где нулевые байты добавляются точно. Нам понадобятся эти знания в дальнейшем.
Разработчик может решить использовать эту функцию по назначению, по очевидным причинам (как указано выше). Но иногда разработчик может даже не знать, какое расширение юникода будет использоваться " под колпаком ", когда приложение построено/скомпелированно. Собственно говоря, Win32 API часто переводять строки в Юникод прежде чем начать работать с ними. В некоторых случаях (например, с Visual Studio), используемый API зависит от того устанавливается макрос _ЮНИКОД во время сборки или нет. Если макрос установлен, подпрограммы и типы отображаются на объектах, которые могут иметь дело с юникодом. API функций могут измениться. Например, вызов CreateProcess меняется на CreateProcessW (Unicode) или CreateProcessA (ANSI), основанный на статусе макроса.
Что является результатом преобразования в Юникод/влияние на построение эксплоита?
Когда входная строка преобразуется в ANSI юникод, ко всем символам от 0 × 00 и 0x7f, присоединятся спереди нулевой байт. Кроме того, множество символов выше 0x7F переводятся в 2 байта, и эти 2 байта могут не содержать исходный байт.
Это сводит на нет все, что мы изучали об эксплойтах и шеллкоде до сих пор.
Во всех предыдущих туториалах, мы пытались переписать EIP с 4 байтами (без учета намеренной частичной перезаписи).
С Юникодом, вы контролируете только 2 байта из этих 4 байтов (2 других, скорее всего, будет нулями ... таким образом, вы контролируете эти нули тоже)
Кроме этого, множество доступных инструкций (используются для прыжков, для шеллкода и т. д.) становятся ограниченными. В конце концов, нулевой байт помещается перед большинством байтов. И на вершине всего, другие байты (> 0x7F) просто преобразуются в нечто совсем другое. Статья Phrack (см. главу 2) объясняет, какие инструкции могут быть использованы, а какие нет.
Даже такие простые вещи, такие как куча nop-ов (0×90) становится проблемой. Первый nop может работать. Второй nop (в связи с выравниванием) станет инструкцией 0090 (или 009000) ... и это уже больше не nop.
Таким образом, это звучит так, как будто придется преодолевать много препятствий. Неудивительно, что сначала люди думали, что написать эксплойт было невозможно ...
Читайте документы
Я кратко объяснил, что произошло в последующие месяцы и годы после публикации «Создание произвольных шеллкодов в строке с юникод расширением ".
Когда вы возвращаетесь к чтению и попыткам понять все эти документы и методы (см. URL в начале этого туториала), становится ясно, что это отличный материал. К сожалению, мне потребовалось некоторое время, чтобы понять и скомпоновать все вместе. Хорошо, некоторые понятия хорошо объясняются в этих документах ... но они показывают вам только часть картины. И я не мог найти хорошие ресурсы, в которых все это было скомпоновано вместе.
К сожалению, и несмотря на мои усилия и на тот факт, что я задавал много вопросов (по электронной почте, в Twitter, в списках рассылки и т.д.), я не получил какую-либо весомую помощь от других людей.
Так что либо не так много людей хотели объяснить мне это (возможно, они забыли, что они не родились с этими навыками ... они тоже должны были учиться этому так или иначе), они были слишком заняты, чтобы отвечать на мой хромой вопрос, или просто не могли объяснить мне, или они просто игнорировали меня, потому что ...? Я не знаю.
В любом случае ... в конце концов, несколько добрых людей нашли время, чтобы дать мне исчерпывающий ответ (а не просто ссылаясь на некие PDF документы снова и снова). Спасибо, ребята. Если вы читаете это, и если вы хотите, чтобы ваше имя было здесь, дайте мне знать.
Возвращаясь к этим PDF файлам ... хорошо, эти документы и инструменты довольно хороши. Но каждый раз, когда я читал один из этих документов, я начинал думать: «Хорошо, это здорово ... теперь как я могу применить это? Как преобразовать эту концепцию в рабочий эксплойт ".
Пожалуйста, сделайте мне одолжение, и найдите время, чтобы прочитать эти документы самостоятельно. Если вам удастся полностью понять, как построить юникод эксплойты исключительно на основе этих документов, от А до Z, то это здорово ..., то вы можете пропустить остаток этого туториала (или продолжить читать и смеяться надо мной, потому что я с трудом понимал это в своё время...)
Но если вы хотите узнать, как склеить все эти файлы в PDF формате и инструменты вместе и пройти лишнюю милю, необходимую для преобразования этих файлов в эксплойты, то читайте дальше.
Некоторые из вас могут быть знакомы с юникод эксплойтами, связанными с ошибоками браузера и heap spray. Несмотря на то, что количество ошибок в браузере значительно возросло за последние несколько лет (и число эксплойтов и ресурсов увеличиваются), я не собираюсь обсуждать технику эксплойта сегодня. Мое основное внимание будет сосоредоточено на том, чтобы объяснить стек на основе переполнения, которое подлежит юникод преобразованию. Некоторые части этого документа будут служить руководством при атаке браузеров, а также (особенно часть этого документа, посвященная шеллкоду).
Можем ли мы создать эксплойт, когда наш буфер преобразован в юникод?
Прежде всего
Прежде всего, вы узнаете, что не существует всеобъемлющего шаблона для создания юникод эксплойтов. Каждый эксплойт может (и, вероятно, будет) быть разным, требовать разного подхода и может потребовать много работы и усилий. Вам придется играть со смещениями, регистрами, инструкциями, написать свою собственную линию венецианского шеллкода, и т.д. ... Так что пример, который я буду использовать сегодня, может не быть полезным во всех конкретных случаях. Пример, который я буду использовать, является только примером о том, как использовать различные техники, в основном демонстрирующие пути создания собственных строк кода и скомпоновать все вместе так, чтобы получить эксплойт, который делает то, что вы хотите, чтобы он делал.
EIP является 0×00410041. И что теперь?
В предыдущих туториалах мы обсуждали 2 вида эксплойтов: директива RET перезаписи или SEH перезаписи. Эти 2 типа перезаписи, конечно, остаются в силе с юникод эксплойтами. В типичной стеке на основе переполнения, вы либо перезаписывыете RET с 4 байтами (но из-за юникода, только 2 байта находятся под вашим контролем), либо перезаписывыете структурированные поля обработчика исключений записи (следующий SEH и SE Handler), каждый с 4 байтами, опять же, из которых только 2 находятся под вашим контролем.
Как мы можем по-прежнему злоупотреблять этим, чтобы заставить EIP делать то, что мы хотим, чтобы он делал? Ответ прост: перезаписать 2 байта в EIP чем-то полезным.
Директива RET: перезапись EIP чем-то полезным
Глобальная идея "прыжок в ваш шелл-код", когда владеешь EIP, является все той же, является ли это ASCII или юникод переполнение буфера. В случае прямого RET перезаписи, вы должны будете найти указатель на инструкцию (или серию инструкций), который приведет вас к вашему шелл-коду, и вам нужно перезаписать EIP с этим указателем. Таким образом, вы должны найти регистр, который указывает на ваш буфер (даже если он содержит нулевые байты между каждым символом - не нужно беспокоиться по этому поводу), и вам нужно перейти («перепрыгнуть») к этому регистру.
Единственная проблема заключается в том, что вы не можете просто взять любой адрес. Адрес, который вам нужно поискать должен быть "отформатированным" таким образом, что, если к символам спереди добавляются 00, адрес должен остаться действительным.
Таким образом, по существу, у нас есть только 2 варианта:
1. найти адрес, который указывает на вашу jump/call/… инструкцию, и это выглядит так: 0x00nn00mm. Так что если вы перезапишите RET с 0xnn, 0xmm он станет 00nn00mm
или, наоборот, если вы не можете найти такой адрес:
2. найти адрес, который также отформатирован 0x00nn00mm, и близок к call/jump/… инструкции, которую вы хотите выполнить. Убедитесь, что инструкции между адресом и фактическими call/jump адресами не повредит ваш стек/регистры, и использует этот адрес.
Как мы можем найти такие адреса?
FX написал хороший плагин для OllyDbg (http://www.openrce.org/downloads/details/120/OllyUni) (так называемый OllyUNI (http://www.phenoelit-us.org/win/)), и мой собственный плагин для ImmDbg pvefindaddr (http://www.corelan.be:8800/index.php/my-free-tools/security/pvefindaddr-py-immunity-debugger-pycommand/) поможет вам с этой задачей, а также:
Допустим, вам нужно перейти к eax. Скачайте pvefindaddr.py и поместите его в папку pyCommand вашего ImmDbg установочного. Затем откройте уязвимое приложение в ImmDbg и запустите
Код:
!pvefindaddr j eax
Это выдаст вам список всех адресов в "jump eax". Эти адреса будут отображаться не только в логе, но они также будут записаны в текстовый файл с именем j.txt.
Откройте этот файл и задайте поиск "Unicode".
Вы можете найти два типа записей: записи, которые говорят: "Возможно Юникод совместимый" и записи, которые говорят "Юникод совместимый".
Если вы сможете найти "Юникод совместимые" адреса, то эти адреса будут в 0x00nn00mm форме. (Таким образом, вы должны быть в состоянии использовать один из этих адресов без дальнейших исследований).
Если вы нашли " Возможно Юникод совместимые" адреса, то вы надо следить за этими адресами. Они будут в 0x00nn0mmm форме. Так что если вы взгляните в инструкции между 0x00nn00mm и 0x00nn0mmm, и вы видите, что эти инструкции не будут вредить приложению flow/registers/…, то вы можете использовать 0x00nn00mm адрес (и это будет встречатся на всем пути, пока не достигнет call/jump инструкции в 0x00nn0mmm). В сущности, вы будете прыгать более или менее близко/рядом к реальной инструкции, и будете надеяться, что инструкции между вашим местоположением и реальный прыжком не убьют вас.
OllyUNI в основном будет делать то же самое. Он будет искать Юникод дружественные адреса. Собственно говоря, он будет искать все call/jump reg/… инструкции (так что вам придется пройти через лог и смотреть сможете ли вы найти адрес, который прыгает на нужный регистр).
В принципе, мы ищем адреса, которые содержат нулевые байты в нужном месте. Если EIP содержит 0x00nn00mm, то вы должны найти адрес с тем же форматом. Если EIP содержит 0xnn00mm00, то вы должны найти адрес с этим форматом.
В прошлом мы всегда старались избегать нулевых байтов, так как он действует как ограничитель строки. На этот раз нам нужны адреса с нулевыми байтами. Нам не нужно беспокоиться об ограничении строки, потому что мы не собираемся ставить нулевые байты в строку, которая передается в приложение. Юникод преобразование будет вставлять нулевые байты для нас автоматически.
Давайте предположим, что вы нашли адрес, который сделает прыжок. Скажем, адрес 0x005E0018. Этот адрес не содержит символы, которые имеют шестигранную > 7f значение. Так что адрес должен работать.
Я полагаю, что вы поняли, после какого количества байт вы перезапишите сохраненный EIP. (Вы можете использовать метасплоит шаблон для этого, но вам придется смотреть на байты до и после перезаписи EIP, для того чтобы получить не менее 4 символов). Я покажу пример того, как сделать соответствие позже в этом туториале.
Предположим, что вы перезаписываете EIP после отправки 500 А. И вы хотите перезаписать EIP с "jump eax (в 0x005e0018)" (потому что EAX указывает на А), тогда ваш скрипт должен выглядеть следующим образом:
Код:
my $junk="A" x 500;
my $ret="\x18\x5e";
my $payload=$junk.$ret;
Таким образом, вместо того, чтобы перезаписывать EIP с паком ('V', 0x005E0018), вы перезаписываете EIP с 5E 18. Юникод добавляет нулевые байты перед 5E, и между 5E и 18, так EIP будет перезаписан с 005e0018.
(string-to-widechar преобразоване позаботилось о правильном добавлении нулей, которые мы хотели добавить. Шаг 1 выполнен.)
SEH основанный: владение EIP + short jump? (или нет?)
Что делать, если уязвимость основана на SEH? Из части 3 и 3б туториала, мы знаем, что мы должны переписать SE Handler с указателем на pop pop ret, и переписать nSEH с коротким прыжком (short jump).
С юникодом, вам все равно придется переписывать SE Handler с указателем на pop pop ret. Опять же, pvefindaddr поможет нам:
Код:
!pvefindaddr p2
Опять же, это выведет запись в лог, а также в файл под названием ppr2.txt. Откройте файл и поищите "Unicode" еще раз. Если вы сможете найти вход, который не содержит байты > 7f, то вы можете попробовать переписать SE Handler с этим адресом. Опять же, оставьте нулевые байты (они будут добавлены автоматически в связи с юникод преобразованием). В nseh, положите \xcc\xcc (2 точки остановки, 2 байта. Опять же, нулевые байты будут добавлены), и посмотрите, что произойдет.
Если все пойдет хорошо, pop pop ret выполнена, вы будете перенаправлены на первую точку остановки.
В не юникод эксплойтах, вам придется заменять эти точки остановки в nseh с short jump-ом и делать jump через адрес SE Handler на шеллкод. Но я могу заверить вас, что прописывая short jump в юникоде, только с 2 байтами, отделенных нулевыми байтами ... не рассчитывайте на это. Он не будет работать.
Так что на этом и заканчивается.
SEH основанный: jump
Окей. Это не конец. Я просто шучу. Мы можем сделать это, при определенных условиях. Я объясню, как это работает на примере, который приводится внизу этого поста, поэтому сейчас я буду придерживаться теории. Все станет ясно, когда вы взгляните на пример, так что не волнуйтесь. (и если вы не понимаете, просто спросите. Я буду более чем счастлив объяснить вам).
Теория: вместо того чтобы писать код, чтобы сделать короткий прыжок (short jump) (0xeb, 0 × 06), возможно, мы можем запустить эксплойт безвредного кода, так, что он просто пройдет по перезаписанным nseh и seh, и закончится сразу в том месте, где мы должны перезаписали SEH, выполняя код, который мы поместили после того, как перезаписали SE структуру. Собственно говоря это то, чего мы хотели достичь в первую очередь, перепрыгивая через nSEH и SEH.
Для того чтобы сделать это, нам нужно 2 вещи:
- Несколько инструкций, которые при исполнении не будут причинять никакого вреда. Нам нужно поместить эти инструкции в nSEH и
- юникод совместимый адрес используемый для перезаписи SE Handler не должен, когда выполняется как инструкция, причинять никакого вреда тоже.
Сбивает с толку? Не паникуйте. Я объясню это дальше подробно в примере, приведенном ниже в этом блоге.
Ну так что, мы можем только поставить 0x00nn00nn в EIP?
Да, и нет. Если вы посмотрите на таблицу юникод перевода, вы можете иметь некоторые другие опции, рядом с очевидным форматом 0x00nn00nn.
Ascii значения, представленные в шестнадцатеричном виде > 0x7f переводятся по-разному. В большинстве из этих случаев (см. таблицу на стр. 15 - 17), перевод превращает юникод версию во что-то другое.
Например 0 × 82 становится 1A20. Так что, если вы сможете найти адрес в формате 0x00nn201A, то вы можете использовать тот факт, что 0 × 82, будет автоматически пересчитано в 201A.
Единственная проблема, с которой вы можете столкнуться, это если вы строите эксплойт на основе SEH, это может привести к проблеме, потому что после pop pop ret, адресные байты выполняются как инструкции. Пока инструкции действуют как nops, или не вызывают больших изменений, все идет прекрасно. Я думаю, что вам просто надо проверить все доступные "юникод совместимые" адреса и убедиться самим, есть ли адрес, который будет работать. Опять же, вы можете использовать pvefindaddr (Immdbg плагин) для поиска нужного pop pop ret адреса, который являются юникод совместимым.
Адреса, которые вы будете искать могут начинаться или заканчиваться:
ac20 (=80 ASCII), 1a20 (=82 ASCII), 9201 (=83 ASCII), 1e20 (=84 ASCII), и так далее (просто посмотрите на таблицу перевода.). Успех не гарантирован, но это стоит попробовать.
Готов запустить шеллкод ... Но готов ли шеллкод?
Хорошо, теперь мы знаем, что положить в EIP. Но если вы посмотрите на свой ASCII шеллкод: он также будет содержать нулевые байты, и, если он использует инструкции (коды операций) выше 0x7F, инструкции, возможно, даже изменились. Как мы можем сделать это? Есть ли способ для преобразования ASCII шелл-кода (такие же, как те, которые создаются с метасплоит) в юникод совместимый шелл-код? Или мы должны написать наш собственный материал? Мы собираемся выяснить это.
Шеллкод: Техника 1: Найти ASCII эквивалент и перепрыгнуть к ней
В большинстве случаев, ASCII строка, которая подавалась в приложение преобразуется в юникод после того, как она была поставлена в стек или в память. Это означает, что это возможно найти ASCII версию вашего шеллкода где-то. Так что, если вы сможете указать EIP, чтобы он перепрыгнул к этому месту, это может сработать.
Если ASCII версия не достижима напрямую (прыжком в регистр), но вы контролируете содержимое одного из регистров, то вы можете перейти в этот регистр, и поместить некоторые основные jump-коды в том месте, которое сделает прыжок на ASCII версию. Мы поговорим об этом jump-коде позже.
Хороший пример эксплойта, который использует эту технику можно найти здесь.
Шеллкод: Техника 2: Пишем свой юникод-совместимый шеллкод с нуля
Правильно. Это возможно, нелегко, но возможно ... но есть лучшие способы. (см. техника 3)
Шеллкод: Техника 3: Использование декодера
Хорошо, мы знаем, что шеллкод генерируемый в метасплоит (или написанный вами) не будет работать. Если шеллкод не был написан специально для юникода, он не будет работать. (нулевые байты вставляются, опкоды изменились, и т.д.).
К счастью, пару умных людей придумали несколько инструментов (на основе концепции венецианского шелл-кода), которые решат этот вопрос. (Dave Aitel, FX и SkyLined).
По сути, все сводится к следующему: Вам нужно закодировать ASCII шеллкод в юникод-совместимый код, перед именем декодера (также юникод-совместимого). Затем, когда декодер выполнен, он раскодирует исходный код и выполнит его.
Существуют 2 основных способа сделать это: либо воспроизведя исходный код в отдельном месте памяти, и затем прыгать на то место, или путем изменения кода "в линии", и затем запустить воспроизведенный шеллкод. Вы можете прочитать все об этих инструментах (и принципах, на которых они основаны) в соответствующих документах, упомянутых в начале этого блога. Первый метод требует 2 вещи: один из реестров должен указывать на начало декодер+шеллкод, и один регистр должен указывать на область памяти, куда можно записать (и где это лучше, чтобы записать новый собранный шеллкод). Второй метод требует, чтобв только один из регистров указывал на начало декодер+шеллкод, и оригинальный шеллкод будет собран на месте.
Можем ли мы использовать эти инструменты для создания рабочих шеллкодов, и если да, то как мы должны их использовать? Давайте выясним.
1. makeunicode2.py (Dave Aitel)
Этот скрипт является частью CANVAS, коммерческий инструмент от Immunity. Так как я не имеют лицензии, я не был в состоянии проверить это (и, следовательно, я не могу объяснить, как им пользоваться).
2. vense.pl (FX)
На основании объяснений FX в 2004 его Blackhat презентации (http://www.blackhat.com/presentations/win-usa-04/bh-win-04-fx.pdf), этот удивительный Perl скрипт, кажется, производит усовершенствованную версию, которую можно получить makeunicode2.py
Выходной файл этого скрипта является строкой байт, содержащей декодер и оригинальный шеллкод все-в-одном. Таким образом, вместо того, чтобы размещать метасплоит сгенерированный шелл-код в буфер, вам необходимо поместить выходной файл vense.pl в буфер.
Для того, чтобы иметь возможность использовать декодер, вы должны быть в состоянии создать регистры следующим образом: один регистр должен указывать непосредственно на начало место расположения буфера, где ваш шеллкод (vense.pl сгенерированный шелл-код) будет помещен .
(В следующей главе я объясню, как изменить значения в регистрах, так чтобы вы могли указать один регистр в любое место куда вы хотите.). Далее, вам нужно иметь второй регистр, который указывает на ячейку памяти, доступную для записи и исполнения (RWX), и где лучше всего делать записи данных (без повреждения чего-нибудь еще).
Предположим, что регистр, который будет создан для того, чтобы указывать на начало vense-генерируемых шеллкодов, является eax, и edi указывает на местоположение для записи:
редактируем vense.pl и устанавливаем $basereg и $writable параметры до необходимого значения.
Далее, прокрутите вниз, и поищите $secondstage
Удалите содержимое этой переменной и замените его на свой собственный (сгенерированный метасплойтом) Perl шеллкод. (Это ASCII шеллкода, который запустится после того, как декодер сделает свое дело.)
Сохраните файл и запустите скрипт.
Выходной файл будет показывать:
- Оригинал шеллкода
- Новый шеллкод (тот, который включает в себя декодер).
Теперь используйте этот «новый» шеллкод в вашем эксплойте и убедитесь, eax указывает на начало этого шеллкода. Вам, скорее всего, придется настроить регистры (если вам повезло).
Когда регистры созданы, просто запустите "jump eax" и декодер извлечет оригинальный шелл-код и запустит его. Опять же, в следующей главе я вам покажу, как установить/настроить регистры и сделать прыжок (jump) с использованием юникод-совместимого кода. Примечание 1: вновь созданный шифровщик+шеллкод будет работать только когда он преобразуется в юникод, а затем запустится. Таким образом, вы не сможете использовать этот тип шеллкода в не-юникод эксплойте.
Примечание 2: несмотря на то, что алгоритм, используемый в этом скрипте является улучшением по сравнению с makeunicode2.py, вам все равно в конечном итоге закончите с довольно длинным шеллкодом. Так что вам нужно собственное пространство буфера (или короткие, не сложные шелл-коды) для того, чтобы использовать эту технику.
3. альфа2 (SkyLined)
Знаменитый альфа-2 кодировщик (также адаптированный в другие инструменты, такие как метасплойт, и куча других инструментов) будет принимать ваши оригинальный шеллкод, заворачивать его в декодер (очень похоже на то, что vense.pl делает), но преимуществом здесь является
- Вам нужно всего лишь регистр, который указывает на начало этого шеллкода. Вам не нужен дополнительный регистр, доступный для записи/исполнения.
- Декодер развернет исходный код на месте. Декодер самомодифицирующийся, и общее количество необходимого размера буфера меньше.
(в документации говорится, что "декодер изменяет свой собственный код, чтобы избежать ограничений буквенно-цифрового кода. Он создает декодер цикл, который декодирует оригинальный шеллкод из закодированных данных. Он перезаписывает закодированные данные с декодированным шелл-кодом и передает выполнение закончит. Чтобы это сделать, ему нужно прочитать, записать и выполнить разрешение на памяти, где он работает, и ему нужно знать его место в памяти (это baseaddress)
Вот как это работает:
1. генерируется необработанный шеллкод с msf полезной нагрузкой
2. необработанный шеллкод преобразовывается в юникод строку, используя альфа-2:
Код:
root@bt4:/# cd pentest
root@bt4:/pentest# cd exploits/
root@bt4:/pentest/exploits# cd framework3
./msfpayload windows/exec CMD=calc R > /pentest/exploits/runcalc.raw
root@bt4:/pentest/exploits/framework3# cd ..
root@bt4:/pentest/exploits# cd alpha2
./alpha2 eax --unicode --uppercase < /pentest/exploits/runcalc.raw
PPYAIAIAIAIAQATAXAZAPA3QAD...0LJA
(Я удалил большую часть выходных данных. Просто создайте свой собственный шелл-код и скопируйте/вставьте выходные данные в ваш эксплойт скрипт)
Расположите выходные данные alpha2 преобразования в $shellcode переменную в вашем эксплойте. Опять же, убедитесь, что регистр (eax в моем примере) указывает на первый символ этого шеллкода, и убедитесь, что стоит переход на eax (jmp eax) (после создания регистра, если это было необходимо)
Если вы не можете подготовить/использовать регистр в качестве базового адреса, то альфа-2 также поддерживает метод, который попытается рассчитать свой базовый адрес с помощью SEH. И вместо указания регистра, просто укажите SEH. Таким образом, вы можете просто запустить код (даже если он не указывает непосредственно на один из регистров), и он все еще будет способен декодировать и запустить оригинальный шелл-код).
4. Метасплойт
Я попытался создать юникод совместимый шелл-код с помощью метасплойта, но изначально он не работает так, как я ожидал…
Код:
root@krypt02:/pentest/exploits/framework3#
./msfpayload windows/exec CMD=calc R |
./msfencode -e x86/unicode_upper BufferRegister=EAX -t perl
[-] x86/unicode_upper failed: BadChar; 0 to 1
[-] No encoders succeeded.
(Залил сюда https://metasploit.com/redmine/issues/430)
Стивен Фювер предложил такое решение этой проблемы:
./msfpayload windows/exec CMD=calc R |
./msfencode -e x86/alpha_mixed -t raw |
./msfencode -e x86/unicode_upper BufferRegister=EAX -t perl
(поставьте все на одну строку)
(в основном, прежде кодируйте с alpha_mixed, а затем с unicode_upper). Выходной будет юникод совместимым шеллкодом для Perl
Результат: Метасплоит также может сделать это.
5. UniShellGenerator Back Khoa Internetwork Security (http://security.bkis.vn/)
Этот инструмент продемонстрирован в этой презентации. К сожалению, я не смог найти копию этого инструмента нигде, и человек/люди, которые писали инструмент не ответил мне также ...
Присоединение одиного к другому: подготовка регистров и переход к шеллкоду
Для того, чтобы бы выполнить шеллкод, вы должны достигнуть шеллкода. Является ли это ASCII версией шеллкода, или юникод версией (декодер), вам нужно, прежде всего, попасть туда. Для того, чтобы сделать это, вам часто будет необходимо создавать регистры определенным образом, используя собственный венецианский шеллкод и/или написать код, который будет осуществлять прыжок на данный реестр.
Написание этих строк кода требует немного творчества, требует, чтобы вы думали о регистрах, и будет требовать, чтобы вы могли писать некоторые основные ассемблер инструкции.
Написание jumpcode основывается исключительно на принципах венецианского шеллкода. Это означает, что
- У вас есть только ограниченный набор команд
- вам нужно следить за нулевыми байтами. Когда код помещается в стек, будут вставлены нулевые байты. Так что инструкции должны работать, когда нулевые байты добавляются
- вам нужно думать об опкоде выравнивания
Пример 1.
Допустим, вы нашли ASCII версию вашего шеллкода, без изменений, при 0 × 33445566, и вы заметили, что вы также контролируете eax. Вы перезаписываете EIP с прыжком в eax, и теперь, идея заключается в том, чтобы написать несколько строк кода в eax, который совершит прыжок на 0 × 33445566.
Если бы это не являлось юникодом, мы могли бы сделать это, используя следующие инструкции:
Код:
bb66554433 #mov ebx,33445566h
ffe3 #jmp ebx
=> Мы бы расположили следующий код в eax: \xbb\x66\x55\x44\x33\xff\xe3, и мы бы перезаписали EIP с “jump eax”.
Но это юникод. Таким образом, очевидно, что это не будет работать.
Как мы можем достичь того же с юникодом совместимыми инструкциями?
Давайте взглянем на инструкцию mov в первую очередь. “mov ebx” = 0xbb, сопровождается тем, что вы хотите поместить в ebx. Этот параметр должен быть в 00nn00mm формате (так, когда нулевые байты вставлены, они будут вставлены туда где уже есть нули (или там, где мы ожидаем нули), не вызывая вопросов в инструкции). Например, вы можете сделать mov ebx, 33005500. Опкод для этого будет такой
bb00550033 #mov ebx,33005500h
Так байты для записи в eax (в нашем примере) следующие \xbb\x55\x33. Юникод вставит нулевые байты, в результате выдавая \xbb\x00\x55\x00\x33, что на самом деле является инструкцией, которая нам нужна.
Та же самая техника применяется для добавления и суб инструкции.
Вы можете использовать inc, dec инструкции также, чтобы изменить регистры или перенести позиции в стеке.
Статья Phrack о Построение IA32 "Unicode-Proof" Shellcodes показывает всю последовательность присоединения любого адреса в данный регистр, показывая, что именно я имею в виду. Возвращаясь к нашему примеру, мы хотим положить 0 × 33445566 в eax. Вот как это делается:
Код:
mov eax,0xAA004400 ; set EAX to 0xAA004400
push eax
dec esp
pop eax ; EAX = 0x004400