Black Hat Rust, Rust, Программирование

#6 Black Hat Rust. Наша первая программа на Rust: взломщик хэшей SHA-1.

Здравствуйте, дорогие друзья.

Настал момент закатать рукава, и написать нашу первую программу на Rust.

$ cargo new sha1_cracker

Создадим новый проект в папке sha1_cracker .

How a hashing function works

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

Реальность совсем иная. Давайте представим сценарий, в котором мы только что взломали такой веб-сайт и теперь хотим восстановить учетные данные пользователей, чтобы получить доступ к их учетным записям. Вот где полезен “взломщик хэшей”. Взломщик хэшей — это программа, которая перепробует множество различных хэшей, чтобы найти исходный пароль.

Эта простая программа поможет нам изучить основы Rust.

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

Чтение аргументов командной строки так же просто, как:

Где use std::env импортирует модуль env из стандартной библиотеки, а env::args() вызывает метод args из этого модуля и возвращает итератор, который может быть “собран” в Vec<String>, вектор объектов String.

Затем легко проверить количество аргументов и отобразить сообщение об ошибке, если оно не соответствует ожидаемому.

Как Вы, возможно, заметили, синтаксис println! с восклицательным знаком странный. Действительно, println! — это не классическая функция, а макрос.

println! это макрос, а не функция, потому что Rust не поддерживает (пока?) вариативные обобщения.

Обработка ошибок

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

Среди дюжины языков программирования, с которыми у меня есть опыт работы, Rust, без всяких сомнений, является моим любимым языком обработки ошибок из-за его ясности, безопасности и лаконичности. Поскольку это также хорошо документировано и не является темой данной статьи, вот, безусловно, один из самых актуальных ресурсов по этому поводу: https://nick.groenen.me/posts/rust-error-handling

Чтение файлов

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

Crates

Теперь, когда базовая структура нашей программы готова, нам действительно нужно вычислить хэши SHA-1. К счастью для нас, несколько талантливых разработчиков уже разработали этот сложный фрагмент кода и выложили его в сеть, готовым к использованию в виде внешней библиотеки. В rust мы называем эти библиотеки, или пакеты, crates. Их можно просмотреть онлайн по адресу https://crates.io.

Они управляются с помощью Cargo: менеджер пакетов Rust. Перед использованием crate в нашей программе, нам необходимо объявить его версию в файле манифеста Cargo: ‘Cargo.toml“.

Затем мы можем импортировать его в наш SHA-1 cracker:

Ужас! Наша первая программа завершена. Мы можем протестировать ее, запустив:

$ cargo run -- wordlist.txt 7c6a61c68ef8b9b6b061b28c348bc1ed7921cb53

Пожалуйста, обратите внимание, что в реальном контексте мы можем использовать оптимизированные хэш-крекеры, такие как hashcat или John the Ripper.

RAII

Деталь, возможно, привлекла внимание самых дотошных из Вас: мы открываем файл списка слов, но никогда его не закрываем!

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

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

Волшебство, не так ли? Благодаря этому утечка ресурсов в Rust чрезвычайно затруднена.

Ok(())

Возможно, Вы также заметили, что последняя строка нашей основной функции не содержит ключевого слова return. Это потому, что Rust — язык, ориентированный на выражения. Выражения принимают значение, а их противоположности, операторы, — это инструкции, которые что-то делают и заканчиваются точкой с запятой ( ; ).

Таким образом, если наша программа достигнет последней строки функции main, функция main примет значение Ok(()), что означает успех: все прошло по плану. Эквивалентом было бы:

return Ok(());

но не:

Ok(());

потому что здесь Ok(()); является оператором из-за точки с запятой, и функция main больше не вычисляет ожидаемый тип возвращаемого значения: Result.

На этом все. Всем хорошего дня!

Black Hat Rust

Цикл статей по курсу Black Hat Rust.