Сага о викинге Эрланге. Высоконаучный брутфорс на практикеНиколай Байбородин / www.dreamboss.ru / На дворе XXI век. Хакер, разбирающий дампы вручную — такой же анахренизм (© Не помню, кто), как и хакер, пытающийся сбрутить пасс в один поток. Напомню, что в прошлом номере мы начали знакомство с викингом Эрлангом. Сегодня тебя ждет продолжение рассказа о языке программирования, рожденном для многопоточной работы в мультипроцессорной монолитной или распределенной среде. Erlang: Стать подмастерьемС языком Erlang я тебя познакомил, да вот беда, не успел рассказать, как программировать с его помощью. Чтобы постичь Дао Erlang программиста, тебе придется познать четыре великих секрета:
Конкурентное программированиеДавай вспомним матчасть — а именно, клиент-серверную архитектуру. На всякий случай, я со своим атрофированным чувством прекрасного попытался изобразить эту шведскую семейку на картинке («Три плюс один»). Модель клиент-сервер отличается от прочих тем, что в ней есть некий центральный узел (такой весь на распальцовке сервачок), к которому подключены несколько дремучих провинциалов-неудачников. Что им нужно от сервера? Очевидно, эти кровососы вовсю юзают один из его ресурсов. По максимуму упростив модель, всю выполняемую сервером работу можно представить в виде некоторой функции F. Теперь представь, что у нашего сервера есть несколько возможных состояний — State (упал под DDOSом, обрабатывает спам-лист, брутит пассы). Наш сервачок постоянно тревожат клиенты своими запросами (Query). Серверу не позавидуешь, потому что пока он жив, он обязан отвечать на все поступившие запросы (Replay), поменяв свое текущее состояние со State на State 1. Все это можно сжато и емко выразить с помощью синтаксиса Erlang:
А теперь перейдем от теории к практике — напишем свой первый сервер:
Страшно? А никто и не обещал, что сбрутить пассы через распределенку будет легко. Это тебе не формочки в дельфях клепать. Но на самом деле, разобраться вполне реально. Клиент осуществляет RPC-запрос (строки с 13 по 17), отправляя сообщение серверу (строка 14) и ожидая от него ответ (строки с 15 по 17). Сервер получает посланный клиентом запрос (строка 23), формирует ответ и, меняя свое состояние, прокручивает ответ через хвостовую рекурсию (строка 26). Почему я выбрал именно этот пример с клиент-серверной архитектурой? Потому что он неплохо иллюстрирует хорошо распараллеливаемый алгоритм — для обработки каждого запроса запускается свой поток. Теперь допустим, что наш сервер нужно обвесить разными рюшечками, не предполагающими параллельной обработки: например, написать функцию, которая будет сбрасывать сбрученный пароль в текстовик. Это будет уже последовательная операция (пока пароль не найден — в файл лить нечего) и глупо пихать ее в один модуль с распараллеленным алгоритмом. Логичнее создать новый модуль, подключив к нему те, что поддаются распараллеливанию:
А что произойдет, если наш сервер, в ожидании хэша рутовского пасса, словит нечто неперевариваемое? К примеру, горе-хакер решит сбрутить CRC, приняв его за вожделенный пароль от админки местечкового порнохранилища. Каким-то образом наша чудо-цифродробилка должна отреагировать на такую внештатную ситуацию. Об этом — буквально в следующем абзаце. Сапер ошибается дваждыПользуясь случаем, хочу передать пламенный привет сишным кодерам, отстрелившим себе не только обе ноги, но и то, что так мешало им танцевать полечку на костях функциональной парадигмы программирования. Среди парней, что паяют компьютерное железо в промышленных масштабах в ходу термин «fault-tolerance». Это же понятие можно встретить в идеологии Erlang’a. Сравни два фрагмента кода:
И второй вариант:
Надеюсь, ты уже успел заметить ключевые отличия первого фрагмента от второго, встретив там до боли знакомое словечко catch. Но не будем торопиться и рассмотрим обработку ошибок в Erlang по шагам.
Получается интересная штуковина: RPC как бы подчиняется транзакционной семантике, которая в свою очередь определяется состоянием сервера — если состояние сервера изменилось, значит, все ОК. Если нет — случился страшный писец и нужно срочно принимать меры по спасению вселенной от последствий роковой ошибки. Приведенная схема прекрасно работает в том случае, если нужно урезонить клиентов, отправляющих серверу невменяемые запросы. А теперь представь такую ситуацию: клиент отправляет серверу корректный запрос и вполне на законных основаниях ожидает от него ответ. Тем временем наш сервер лежит без чувств, сраженный безжалостным DDOSом. Чтобы такое ожидание не длилось вечно, нужно предусмотреть возможность отстрела ошибок и на стороне клиента:
Думаю, решение очевидно. За исключением одного маленького, но принципиального вопроса — как долго ждать ответ от сервера, прежде чем начинать паниковать? Ткнув пальцем в небо, я написал: десять тысяч миллисекунд. При грамотном подходе (если ты хочешь показать кому-то всю свою невозможную крутость) клиент вовсе не должен проверять работоспособность сервера. Не барское это дело! Для такого случая, согласно идеологии Erlang, нужно создать отдельный процесс (перевожу на русский — написать отдельный программный модуль), называемый гипервизором (Hypervisor). Задача гипервизора состоит в том, чтобы с заданной периодичностью проверять состояние сервера и сообщать о нем клиентам. Чужие секретыТы уже достаточно много знаешь о языке программирования Erlang. Не исключено, что у тебя уже появились свои собственные идеи, как можно было бы организовать систему распределенного брутфорса. Но один вопрос по-прежнему не раскрыт, и возможно, не дает тебе спокойно заснуть с любимым журналом под подушкой. Собственно, это вопрос о том, каким образом можно реализовать криптографические инструменты средствами Erlang. Не волновайся, как говаривала добрая бабушка в одном бездарном российском блокбастере. Среди разнообразных библиотек, поставляемых вместе с самим языком, криптографических инструментов присутствует достаточно. Как решать с помощью этих инструментов конкретную задачу, ты и сам прекрасно разберешься, покурив хорошенько документацию. Я, в силу ограниченного журнального пространства, покажу тебе лишь общее направление на простом примере использования алгоритма MD5. На всякий случай напомню, что алгоритм MD5 для своего аргумента вычисляет уникальное 32-разрядное число (обычно представляемое в шестнадцатеричной нотации). Для вычисления MD5 в Erlang есть специальная встроенная функция, возвращающая хэш в виде структуры чисел:
Однако, это не совсем то, что мы привыкли видеть, и не совсем то, что мы ожидаем получить от функции md5()! Чаще всего результат работы алгоритма MD5 фигурирует в виде шестнадцатеричной строки. Поэтому после вычисления значения хэш-функции есть смысл воспользоваться функцией конвертирования binary_to_list():
Следующий шаг — преобразование каждого целого числа в списке в его шестнадцатеричный эквивалент. Как бы ты это сделал, не имея под рукой компьютера? С помощью разложения на множители по модулю 16! Реализация этого алгоритма на любом языке — задача для школьника. Такое преобразование нужно выполнить для каждого элемента списка. Я бы тебе порекомендовал воспользоваться для этого стандартной функцией lists:map, пропустив через нее преобразование каждого десятичного числа к его шестнадцатеричной нотации:
В результате должен получиться список целых чисел в шестнадцатеричной нотации. Последнее, что осталось сделать — привести список к одному значению. Весь алгоритм в сборе будет примерно таким:
И — результат работы:
Finita la comediaВозможно, кто-то разочаруется, так и не обнаружив в статье готового решения. Но, согласись, это было бы, мягко говоря, не по тру-хакерски. Мое дело — дать тебе удочку, а с рыбой ты как-нибудь сам справишься. За эти две статьи, посвященные языку программирования Erlang, мы успели познакомиться с основами синтаксиса языка, его основополагающими идеями, основными приемами, техникой создания процессов и даже затронули криптографическую библиотеку. Тебе осталось собрать кусочки мозаики. Вот за этим занятием разреши тебя и оставить. А если будут вопросы — пиши, не стесняйся. Только не забудь вложить в письмо пустой конверт и пятьдесят американских рублей, на канцелярские, так сказать, расходы. Ариведерчи! Главное в Erlang — его модель легковесных процессов. Пере- фразируя для Erlang слоган текущего дня — «Everything is an object» («Все является объектом»), можно сказать: «Everything is a process» («Все является процессом»). Процессы дешевы, а их создание занимает не больше ресурсов, чем вызов функции. Единственным способом взаимодействия процессов является асинхронный обмен сообщениями. Процесс при желании может или установить связь (link) с другими процессами (по выбору), или получить сообщение об их смерти с указанием причины, или разделить их участь. Процесс имеет свой «почтовый ящик», откуда может выборочно читать сообщения. В этом очень помогает сопоставление по шаблону; код проверки «ящика» чем-то похож на программу awk. Нужное выгребается и обрабатывается, остальное остает- ся или выбрасывается. Мощность связки «Процессы+сообщения» не меньше, чем у «Объекты+Интерфейсы+Наследование». Но зачастую она приводит к более компактным и понятным решениям. Устранить конкуренцию также просто, как и создать. Нет необходимости блокировать доступ к состоянию процесса для синхронизации взаимодействия, и это сильно облегчает жизнь. Для конкурентного ресурса обычно просто создается процесс-монитор, через который осуществляется взаимодействие с ресурсом. Links
|