Гайд по созданию OSINT инструментов и инструментов проникновения на Python

Кобра: создаем OSINT инструмент на Python, часть 1

Итак, каждый программист желает все автоматизировать — и не только программист. В этой статье мы рассмотрим создание OSINT-инструмента на Python.

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

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

Гайд по созданию OSINT инструментов и инструментов проникновения на Python

В этой статье мы создадим инструмент для:

  • Получения информации об IP адресе
  • Получения информации об номере телефона
  • Получение информации, есть ли IP в черных листах DNS
  • Парсер всех ссылок с сайта
  • Сканер портов
  • SYN-сканер портов
  • Сканер сессий Meterpreter для Windows 7/10
  • Virus Total API
  • Сканер SQL инъекций
  • Сканер XSS уязвимостей
  • Генератор фейкового User-Agent
  • Скрипт для изменения mac-адреса

Данная статья ориентирована на продвинутых разработчиков, которые уже умеют писать и читать python-код.

Для всего этого нам нужен будет Python >3.9, Linux и пакетный менеджер pip.

Весь исходный код доступен по ссылке. Репозиторий локализирован на английский язык, в директории /docs/ru есть markdown-версия статьи.

Создаем рабочее окружение

Никогда вам не советую устанавливать пакеты в саму систему. Для того чтобы работать над каждым проектом отдельно (дабы не возникало ошибок с пакетами и библиотеками) надо создавать виртуальные окружения.

Для того, чтобы создать виртуальное окружение, нам потребуется всего лишь одна команда:

python3 -m venv <название виртуального окружения> # Например, создание виртуального окружения venv python3 -m venv venv

После ввода данной команды у вас в текущем проекте будет создана новая директория — это и будет виртуальное окружение.

Но это еще не все — нам предстоит активировать его командой:

source venv/bin/activate # Или, если у вас fish: source venv/bin/activate.fish

И вот теперь мы можем спокойно работать с проектом и устанавливать пакеты. Для деактивации и выхода из окружения можно использовать команду deactivate.

Установка зависимостей

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

sudo pacman -S whois # Arch sudo apt install whois # Debian/Ubuntu sudo dnf install whois # Fedora sudo epm install whois # Alt

После активации нашего окружения, можно установить python-пакеты. Список нужных нам можно получить по ссылке в файле requirements.txt. Просто скачиваете его в проект и выполняете следующию команду:

pip3 install -r requirements.txt

Этим мы установили нужные нам зависимости. Теперь можно начать разработку!

Архитектура проекта

Давайте создадим архитектуру нашего инструмента:

  • Директория core — базовый функционал для самого инструмента
  • Директория modules — главная папка всего проекта, здесь мы будем хранить модули для работы.anonymity — модуль анонимности, безопасности. Небольшой, чаще всего вспомогательный. Есть пока два компонента — генерация фейкового юзер-агента (fakeuseragent.py) и изменение MAC-адреса устройства (machanger.py)network_base — вспомогательно-информационный модуль. Содержит информацию о сети, IP и портах. Один из самых больших модулей. Содержит следующие файлы: адрес из гео-координат, домен из IP, IP из домена, небольшие программы для работы с IPv4, ваш публичный IP, параметры сети, скрипт для пинга адреса, какой сервис на порту и т.д.osint — модуль, как не странно, для OSINT и поиска информации. Здесь уже содержится прокси-файл для взаимодействия с модулем network_base, а также скрипты для получения информации об ip и номере телефона.scanners — модуль для сканеров, как понятно из названия. Здесь есть сканер IP на наличие в черных списках DNS, парсер ссылок с сайта, сканер портов, поиск SQL-Injection и XSS уяизвимостей.
  • Файл main.py — главный файл кода в корневом каталоге проекта.

Итак, в репозитории директория core является хранилищем трех файлов — это

  • highlight_schemes.py — цветовые схемы Pygments.
  • logger.py — файл с классом логгера, дебаггера объектов.
  • style.py — файл с цветами, для того чтобы делать вывод красивее.

К сожалению, эти файлы мы не будем затрагивать — это совершенно другая тема. Если вы хотите, следующая статья будет об создании продвинутых CLI-программ на Python. А пока продолжим.

В репозитории modules есть огромное количество файлов и каталогов, я разберу здесь большую часть.

Создание модулей

Займемся созданием модулей. Начнем с модуля анонимности

Генерация фейкового User-Agent

Для этого мы будем использовать библиотеку fake-useragent.

from fake_useragent import UserAgent def generate_useragent() -> str: fua = UserAgent() return str(fua.random)

Здесь всего лишь одна функция — генерации случайного юзер-агента.

Изменение MAC-адреса

Для этого нам не нужны будут дополнительные библиотеки. Но вам возможно нужно будет установить набор инструментов net-tools. Этот набор содержит в себе утилиту ifconfig, аналог ipconfig в Windows, который нам нужен будет для управления сетями.

import re import subprocess from random import choice def ifconfig(): """Возвращаем вывод команды ifconfig""" output = subprocess.check_output(["sudo", 'ifconfig']) return str(str(output.decode('utf-8')).replace(r'\n', '\n')) def change_mac(interface: str, new_mac_address: str) -> None: """Функция для изменения MAC-адреса при помощи утилиты ifconfig Аргументы: + interface: str - название интерфейса (ex. wlan0, wlp1s0, wlp3s0, eth) + new_mac_address - значение нового mac-адреса""" print(f'[+] Выключение {interface}') subprocess.call(["doas", "ifconfig", interface, "down"]) print(f'[+] Замена MAC-адреса {interface} на {new_mac_address}') subprocess.call(["doas", 'ifconfig', interface, 'hw', 'ether', new_mac_address]) print(f'[+] Включение {interface}') subprocess.call(["doas", "ifconfig", interface, "up"]) def get_random_mac_address() -> str: """Функция генерации и получения случайного mac-адреса. У нас есть словарь с числами от 0 до 9, и латинских букв от a до f, а также начало нового mac-адреса (00). После мы проходимся в итерационном цикле и добавляем 5 раз еще по паре символов, а после возвращаем Возвращает: + str - новый mac-адрес""" characters = list("1234567890abcdef") random_mac_address = "00" for i in range(5): random_mac_address += ':' + choice(characters) + choice(characters) return random_mac_address def get_current_mac(interface: str): """Получаем текущий MAC-адрес при помощи утилиты ifconfig""" output = subprocess.check_output(["doas", "ifconfig", interface]) return re.search(r"\w\w:\w\w:\w\w:\w\w:\w\w:\w\w", str(output)).group(0) def main(interface: str): new_mac_address = get_random_mac_address() try: current_mac = get_current_mac(interface) except subprocess.CalledProcessError as ex: print(f'[!] Произошла ошибка. Скорее всего {interface} не существует') print(f' !-> {ex}') return print(f'[+] Текущий MAC-адрес: {current_mac}') print(f'[+] Замена MAC-адреса на {new_mac_address}') change_mac(interface, new_mac_address)

Модуль network_base

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

Обязательно советую использовать этот код, ибо он требуется для получения информации об IP. Можете просто скопировать код и изучить его, он обильно документирован.

Там вы увидите множество файлов — все они пригодятся.

Модуль OSINT

Теперь займемся самим поиском информации по открытым источникам. Займемся файлом ip.py — он поведует нам некоторую информацию об IP адресе.

import modules.network_base.ipv4_local_cli as ipv4_cli import modules.network_base.ipv4_local_getmac as ipv4_gm import modules.network_base.ipv4_local_sock as ipv4_sock import modules.network_base.router_ip as getaway import modules.network_base.network_params as net_param import modules.network_base.ip_from_domain as ip_domain import modules.network_base.domain_from_ip as domain_ip import modules.network_base.service_on_port as serv_port import modules.network_base.my_public_ip as public_ip import modules.network_base.ping_address as ping_addr import modules.network_base.geolocation_ip as geo_ip import modules.network_base.addr_from_geo as addr_geo def url_info(url): """Информация об URL. URL автоматически ретранслируется в IP""" # Пинг адреса print(f'Ping domain of address: {ping_addr.ping_addr(ip_domain.ip_from_domain(f"{url}"))}') # Координаты print(f"Coords by IP address: {geo_ip.geo_ip(domain_ip.domain_ip(ip_domain.ip_from_domain(f'{url}')))}") # Физический адрес по координатам print(f"Physical address by coords: " f"{addr_geo.get_addr(geo_ip.geo_ip(domain_ip.domain_ip(ip_domain.ip_from_domain(f'{url}'))))}") # IP из домена print(f"IP-address of domain {url}: {ip_domain.ip_from_domain(f'{url}')}") # Домен из IP print(f"Domain name {url} by IP: {domain_ip.domain_ip(ip_domain.ip_from_domain(f'{url}'))}") def ip_info(ip: str) -> str: """Информация об IP""" print(f'Ping domain or address: {ping_addr.ping_addr(ip)}') print(f"Coords by IP: {geo_ip.geo_ip(ip)}") print(f"Geo Addr Coords by IP: {addr_geo.get_addr(geo_ip.geo_ip(domain_ip.domain_ip(ip)))}") print(f"Domain name: {domain_ip.domain_ip(ip)}") def check_network(): """Проверяем сетевые параметры""" # Локальный IP print(f'Local IP (cli): {ipv4_cli.local_ipv4()}') print(f'Local IP (gm): {ipv4_gm.local_ipv4()}') print(f'Local IP (sock): {ipv4_sock.local_ipv4()}') # IP шлюза print(f'IP router: {getaway.router_ip()}') # Параметры сети print(f'Network interface parameters:\n{net_param.network_param()}') # Название сервиса работающего на 80 порту print(f'Name of service work on port 80: {serv_port.type_port(80)}') # Публичный IP print(f'Public IP: {public_ip.public_ip()}')

Следующий шаг — компонент netlib.py. Он как раз и будет неким посредником между модулем OSINT и модулем network_base.

Следующий компонент — phone.py, который отвечает за небольшой поиск информации об номере телефона.

import requests def get_info_phonenumber(phonenumber, fua): """Получение информации о номере телефона Аргументы: + phonenumber - номер телефона + fua - фейковый юзер-агент""" result = '' try: # Делаем запрос к API url = f"https://htmlweb.ru/geo/api.php?json&telcod={phonenumber}" headers = { 'User-Agent': fua } info_data = requests.get(url, headers=headers).json() except Exception as ex: return ex result += f'Country: {info_data["country"]["name"]}\n' result += f'Region: {info_data["region"]["name"]}\n' result += f'Subregion: {info_data["region"]["okrug"]}\n' result += f'Operator: {info_data["0"]["oper"]}\n' result += f'Location: {info_data["country"]["location"]}\n' return result

После этого можно заняться следующим компонентом — whois_information.py:

#!venv/bin/python3 import socket import time from ipaddress import IPv4Address, AddressValueError from datetime import datetime import ipwhois import whois def ipwhois_info(ip: str): """Информация о IP по IPWhois + ip: str - IP адрес""" results = ipwhois.IPWhois(ip).lookup_whois() print(results) print("\n") def whois_info(ip): """Информация о IP по WhoIs + ip: str - IP адрес""" results = whois.whois(ip) print(results) def ianna(ip): """Информация о IP через whois.iana.org + ip: str - IP адрес""" s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.connect(("whois.iana.org", 43)) s.send((ip + "\r\n").encode()) response = b"" while True: data = s.recv(4096) response += data if not data: break s.close() whois = '' for resp in response.decode().splitlines(): if resp.startswith('%') or not resp.strip(): continue elif resp.startswith('whois'): whois = resp.split(":")[1].strip() break return whois if whois else False def get_whois(ip, whois): """Получения информации о IP""" s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.connect((whois, 43)) s.send((ip + "\r\n").encode()) response = b"" while True: data = s.recv(4096) response += data if not data: break s.close() whois_ip = dict() num = 0 for ln in response.decode().splitlines(): if ln.strip().startswith("%") or not ln.strip(): continue else: if ln.strip().split(": ")[0].strip() in ['created', 'last-modified']: dt = datetime.fromisoformat(ln.strip().split(": ")[1].strip()).strftime("%Y-%m-%d %H:%M:%S") whois_ip.update({f'{ln.strip().split(": ")[0].strip()}_{num}': dt}) num += 1 else: whois_ip.update({ln.strip().split(": ")[0].strip(): ln.strip().split(": ")[1].strip()}) return whois_ip if whois_ip else False def validate_request(ip): """Проверка IP + ip - IP адрес""" try: IPv4Address(ip) if whois := ianna(ip): time.sleep(1) if info := get_whois(ip, whois): print(info) else: print("Не была получена информация") else: if info := get_whois(ip, 'whois.ripe.net'): print(info) else: print("Не была получена информация") except AddressValueError: print("IP адрес не валидный") except ConnectionResetError as ex: print(ex)

После этого давайте займемся предпоследним компонентом модуля OSINT — virus_total.py для анализа бинарников на вирусы, трояны и другие малвари.

Для начала вам потребуется авторизоваться на сайте Virus Total. После активации аккаунта, откройте меню пользователя и перейдите в секцию API Key. Ниже вы увидете сам API ключ:

Не забудьте про ограничения бесплатного API-ключа:

  • Частота запросов — 4 поиска / минута
  • Дневная квота — 500 поисков / день
  • Месячная квота — 15.5 K поисков / месяц

После просто скопируйте и вставьте в код. Есть разные практики хранить важные ключи в коде. Давайте рассмотрим три способа:

  • Первый и простой способ. Просто вставить ключ прямо в коде

APIKEY='123456abcdef' # Здесь ваш код # ...

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

  • Создать файл настроек/конфигов, например settings.py и в нем хранить все важные ключи, а после импортировать в проект. Намного лучше чем 1 способ, но проблемы с безопасностью также остаются.
  • Переменные окружения. Безопасный способ, мы храним API не в коде, а в переменных окружения. Этот способ мы и будем использовать.

Вам будет надо установить пакет python-dotenv:

pip install python-dotenv

И после создайте в корневом каталоге файл .env, и в него вставьте следующий код:

VIRUS_TOTAL_KEY = "ваш ключ"

После этого давайте немного изменим концепцию, и создадим микро-антивирус. Он будет сперва проверять хеш файла в базе сигнатурах. Да, просто сопоставлять, и это не будет стоять рядом даже с самыми плохими антивирусами — ведь стоит добавить в бинарник малваря хотя-бы один байт, даже если это будет 0, то уже наш антивирус ничего не заподозрит. Но если малварь и пройдет сигнатурный анализ, то дальше его ждет Virus Total!

И еще, мы не будем сами посылать запросы, и парсить результат, мы будем использовать библиотеку vt-py.

А также для локальной проверки файлов нам нужны будут сигнатуры. Собрал я их в каталоге res, доступный по ссылке.

import os import json import requests from dotenv import load_dotenv import os from time import perf_counter from hashlib import sha256, sha1, md5 from colorama import Fore, Style import vt import json # загружаем переменные окружения load_dotenv() # получаем API-ключ VIRUS_TOTAL_API_KEY = os.getenv("VIRUS_TOTAL_KEY") # Сигнатуры signature_resources = ['res/signatures.txt', 'res/signatures2.txt', 'res/signatures3.txt', 'res/signatures4.txt'] # для своих требований, туда можно поместить хэши зловредов, которых нету в обычных списках сигнатур. А также описание и название. signature_resource_info = 'res/signatures_info.json' def add_signature(new_signature: dict) -> None: """Добавление сигнатуры в базу""" with open(signature_resource_info, 'r') as file: signatures_info = json.load(file) for signature in new_signatures: signatures_info['name'] = new_signatures['name'] signatures_info['desc'] = new_signatures['desc'] signatures_info['date'] = new_signatures['date'] with open(signature_resource_info, 'a') as file: json.dump(signatures_info, file, indent=4) return None def rewrite_signatures(signatures: dict) -> bool: """Перезапись БД сигнатур""" try: with open(signature_resource_info, 'w') as file: json.dump(signatures, file, indent=4) except Exception as e: return False else: return True def get_info_signature(signature: str) -> str: """Получение информации об сигнатуре из БД""" with open(signature_resource_info, 'r') as file: signatures_info = json.load(file) try: signature_info = f'''Сигнатура: {signature} Название угрозы: {signatures_info[signature]["name"]} Описание угрозы: {signatures_info[signature]["desc"]} Дата обнаружения угроза: {signatures_info[signature]["date"]}''' except IndexError: signature_info = f'По сигнатуре {signature} еще нету информации в наших базах данных' finally: return signature_info def scan_file(filename: str, delete_file: bool, signature_resources: list, VT_API_KEY: str) -> None: """Сканируем файл + filename: str - путь до файла + delete_file: bool - удалять ли файл + signature_resources: list - список ресурсов сигнатур + VT_API_KEY: str - API ключ Virus Total""" result = f'Сканируем {filename} на наличие угроз...\n' try: print('Load singatures...') start = perf_counter() shahash = sha256() sha1hash = sha1() md5hash = md5() # Получаем хэши with open(filename, 'rb') as file: while True: data = file.read() if not data: break shahash.update(data) # sha256 sha1hash.update(data) # sha1 md5hash.update(data) #md5 result1 = shahash.hexdigest() result2 = sha1hash.hexdigest() result3 = md5hash.hexdigest() result += f'Проверяем наличие хешей "{result1}", "{result2}", "{result3}" (sha256, sha1, md5) в сигнатурах...\n' # Читаем сигнатуры with open(signature_resources[0], 'r') as r: signatures = list(r.read().split('\n')) with open(signature_resources[1], 'r') as r: for sign_hash2 in list(r.read().replace(';', '').split('\n')): signatures.append(sign_hash2) with open(signature_resources[2], 'r') as r: for sign_hash3 in list(r.read().replace(';', '').split('\n')): signatures.append(sign_hash3) with open(signature_resources[3], 'r') as r: for sign_hash4 in list(r.read().replace(';', '').split('\n')): signatures.append(sign_hash4) result += f'''Сканирование {filename} на Virus Total...\n''' print('Loading Virus Total...') client = vt.Client(VT_API_KEY) # анализ файла with open(filename, "rb") as f: analysis = client.scan_file(f, wait_for_completion=True) file = client.get_object(f"/files/{result1}") stats = file.last_analysis_stats result += f'VirusTotal: {filename}\t size: {file.size} bytes\n' result += f'Безвредный: {stats["harmless"]}\n' result += f'Неподдерживаемый тип: {stats["type-unsupported"]}\n' result += f'Подозрительный: {stats["suspicious"]}\n' result += f'Отказ: {stats["failure"]}\n' result += f'Злонамеренный: {stats["malicious"]}\n' result += f'Безопасный: {stats["undetected"]}\n' print('Get final info...') if result1 in signatures or result2 in signatures or result3 in signatures: end = perf_counter() total = end - start result += f'Найдена угроза в файле {filename} (поиск по сигнатурам)\n' # получаем информацию о файле, если он есть в сигнатурах if result in signatures: info = get_info_signature(result) result += f'Информация: {info}\n' elif result2 in signatures: info = get_info_signature(result2) result += f'Информация: {info}\n' elif result3 in signatures: info = get_info_signature(result3) result += f'Информация: {info}\n' # удаление файла if delete_file: os.remove(filename) result += f'[!] Файл {filename} удален!\n' result += f'Время работы: {(total):.07f}s\n' else: end = perf_counter() total = end - start result += 'Угроз не найдено\n' result += f'Время работы: {(total):.07f}s\n' except FileNotFoundError: result += f'[!] Файл не найден\n' except PermissionError: result += f'[!] Ошибка прав доступа к файлу\n' print('End') return result res = scan_file('<любой файл>', False, signature_resources, VIRUS_TOTAL_API_KEY) print(res)

Вот и все — при запуске этого скрипта мы можем спокойно анализировать файлы на Virus Total.

Модуль scanners

Модуль всеразличных сканеров и парсеров. ПОка есть 6 — поиск IP в черных списках DNS, парсер ссылок, сканер портов, сканер на SQL-Injection и XSS уязвимости. А также есть еще детектор ARP-спуфинга и сканер сессий meterpreter для Windows.

Займемся детектором ARP спуфинга. ARP-spoofing — разновидность сетевой атаки типа MITM, применяемая в сетях с использованием протокола ARP. В основном применяется в сетях Ethernet. Атака основана на недостатках протокола ARP.

Нам нужен будет модуль scapy.

import requests from pprint import pprint from bs4 import BeautifulSoup as bs from urllib.parse import urljoin def get_all_forms(url, fua): """Вводится `url`, это возвращает все формы из HTML-контента fua - фейковый юзер агент""" headers = { 'User-Agent': fua } soup = bs(requests.get(url, headers=headers).content, "html.parser") return soup.find_all("form") def get_form_details(form): """Данная функция получает все возможные данные из HTML-формы""" details = {} # Получаем действие формы action = form.attrs.get("action", "").lower() # Получаем все методы формы (POST, GET, etc.) method = form.attrs.get("method", "get").lower() # Получаем все инпуты inputs = [] for input_tag in form.find_all("input"): input_type = input_tag.attrs.get("type", "text") input_name = input_tag.attrs.get("name") inputs.append({"type": input_type, "name": input_name}) # Добавляем в детали details["action"] = action details["method"] = method details["inputs"] = inputs return details def submit_form(form_details, url, value): """ Отправляем данные формы Возвращает HTTP ответ """ target_url = urljoin(url, form_details["action"]) inputs = form_details["inputs"] data = {} for input in inputs: # заменить весь текст и значения поиска на `value` if input["type"] == "text" or input["type"] == "search": input["value"] = value input_name = input.get("name") input_value = input.get("value") if input_name and input_value: data[input_name] = input_value print(f"[+] Отправка вредоносной полезной нагрузки на {target_url}") print(f"[+] Данные: {data}") if form_details["method"] == "post": return requests.post(target_url, data=data) else: # GET request return requests.get(target_url, params=data) def scan_xss(url, fua): """ Получив `url`, он выводит все формы, уязвимые для XSS, и возвращает True, если кто-то из них уязвим, False в противном случае """ forms = get_all_forms(url, fua) print(f"[+] Найдено {len(forms)} форм на {url}.") js_script = "<Script>alert('Detected')</script>" is_vulnerable = False for form in forms: form_details = get_form_details(form) content = submit_form(form_details, url, js_script).content.decode() if js_script in content: print(f"[+] XSS найдена на {url}") print("[*] Детали формы:") pprint(form_details) is_vulnerable = True return is_vulnerable if __name__ == "__main__": url = "https://xss-game.appspot.com/level1/frame" print(scan_xss(url, '<юзер агент>'))

Следующий шаг — сканер meterpreter сессий для Windows 7 и 10.

Начать дискуссию