• [ Регистрация ]Открытая и бесплатная
  • Tg admin@ALPHV_Admin (обязательно подтверждение в ЛС форума)

Статья Статья Прикручиваем "Глаз Бога" к Maltego

Pelotres

Новорег
Пользователь
Регистрация
31.08.2021
Сообщения
2
Розыгрыши
0
Реакции
0
Pelotres не предоставил(а) никакой дополнительной информации.
Всем привет!

С чего вдруг решил написать статью на эту тему? Мне кажется, что много кто использует “Глаз Бога” для быстрого сбора информации. Т.е. бот имеет свою популярность. Maltego, в свою очередь, тоже крайне популярный инструмент среди специалистов разных направлений: OSINTеры, хакеры. пентестеры и т.д. Много нашего брата пользуется этим инструментом. Но главное, что мальтего позволяет себя расширять и автоматизировать, добавляя новые источники данных, а мимо этого пройти не могу. Кто следит за моими статьями, знает: хлебом не корми, дай что-нибудь к чему-нибудь прикрутить. А тут такая удачная автоматизация, жмакнул пару кнопок и в автомате подгрузил данные о негодяе. Ну или о годяе, смотря чем заняты. Заодно пробежимся по ботам для телеги, но акцентировать внимание постараюсь на каких-то нужных нам и интересных моментах.

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

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




Это полностью автоматический ответ. Возможные имена и даты рождения, собраны как фразы.

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

Схема взаимодействия​

У нас есть несколько вариантов организации работы. Как минимум, мы можем выбрать, запустить отдельный сервер кастомных трансформаций или локально. Следующим встает вопрос как взаимодействовать с нашим ботом? Запускать его на каждую отдельную трансформацию или накапливать данные? Вариаций и моментов много, поэтому на данном этапе будем упрощать.

Для демонстрации я пошел путем поднятия бота для чата с Глаз Бога, как отдельного сервиса на каком-нибудь из портов локальной машины. Схема работы будет выглядеть так:




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

Maltego Local Transforms​

Первым делом, начнем с добавления новой трансформации в Мальтего. Идем на вкладку “Transforms” и жмем “New Local Transforms”




Следующим шагом нужно указать основные данные трансформации:




“Transform ID” нужно указать уникальным в рамках только вашей системы и он может быть каким угодно. Мы его будем использовать на этапе автоматизации через Machines.

Тип входящей сущности я выбрал “Phone Number”, так как в этой трансоформации данные будем собирать по номеру телефона. Это входящая сущность для нашего Python-скрипта. Таким образом мы указываем Maltego, что эта трансформация будет показываться только тогда когда используется сущность Phone Number.




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

Python: Скопировать в буфер обмена
Код:
def create_entities(cls, request: MaltegoMsg, response: MaltegoTransform):
        maltego_phone_number = request.getProperty('phonenumber')

Остальные поля, в нашем случае, не имеют существенного значения. Заполняем и жмем нехт.

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




В целом, параметры интуитивно понятные. Если у вас в системе установлено несколько версий Python, укажите путь к той под которую написан скрипт. Обычно это требуется в Linux-системах, на Windows достаточно указать просто “python.exe”.

В параметрах я указал project.py - это запускающий файл проекта. Ниже будет понятно что и зачем. Параметр “local” указывает проекту, как запускать трансформацию. Альтернативные варианты это “runserver” и “list”, но это уже факультатив, а не основная тема))). Крайний параметр, это написанный нами Python-класс трансформации, который будет выполнять всю необходимую работу.

Если что-то пошло не так, после добавления, можно будет изменить эти параметры. Для этого надо вызывать “Transforms Manager”:




В появившемся окне легко можно найти нашу трансформацию и внести соответствующие правки:




На данном этапе настройка Transforms закончена. Можно переходить к написанию кода самой трансформации.

Python Custom Transforms​

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

Код: Скопировать в буфер обмена
pip install maltego-trx

Посмотреть пакет на гите: Для просмотра ссылки Войди или Зарегистрируйся

Далее, нам нужно создать проект maltego-trx командой:

Код: Скопировать в буфер обмена
maltego-trx start godeye

для виртуальной среды:

Код: Скопировать в буфер обмена
my-venv/bin/maltego-trx start godeye

После того, как мастер создаст проект трансформации Мальтего. Причем, через старт мы создаем проект с примерами. В дальнейшем лучше использовать команду “init”. Сейчас же, нам будет предоставлена такая структура папок:




Красным выделил файлы с примерами, которые нам сейчас абсолютно не нужны. Спокойно удаляем их и создаем файл с нашей трансформацией. Название указываем тоже, что установили в Мальтего при создании локальной трасформации. В моем случае это “godeye”. Внутри объявляем одноименный класс, наследник DiscoverableTransform и добавляем необходимые импорты:

Python: Скопировать в буфер обмена
Код:
from maltego_trx.maltego import MaltegoMsg, MaltegoTransform
from maltego_trx.transform import DiscoverableTransform

class godeye(DiscoverableTransform):

Пора бы и оживить класс, дав ему хоть какую-то функциональность. Пока будем создавать новую сущность “Person” с тестовыми данными. Просто чтобы увидеть, как все работает.

Нам потребуется создать метод класса create_entities:

Python: Скопировать в буфер обмена
Код:
    @classmethod
    def create_entities(cls, request: MaltegoMsg, response: MaltegoTransform):
 
        maltego_phone_number = request.getProperty('phonenumber')
 
        f =  open('log.log', 'a')
        f.write(maltego_phone_number + '\n')
        f.close()
 
        person_entity = response.addEntity('maltego.Person', 'Person')
 
        person_entity.addProperty("firstname", value="Example")
        person_entity.addProperty('lastname', value="Person")
        person_entity.addProperty('surname', value="XSS")
        person_entity.addProperty('new_property', value='non-existent property')
 
        email_entity = response.addEntity('maltego.EmailAddress', 'petrihn1984@xss.is')
 
        vk_entity = response.addEntity('maltego.affiliation.VKontakte', 'Example VK')
        vk_entity.addProperty('profile', value='https://vk.com/help')
        vk_entity.addProperty('profileurl', value='https://vk.com/help')
        vk_entity.addProperty('affiliation.profile-url', value='https://vk.com/help')
 
        phrase_entity = response.addEntity('maltego.Phrase', 'Test phrase')

Метод класса принимает два параметра:

request - объект с данными о входящей сущности, экземпляр класса MaltegoMsg. Помните, при создании локальной трансформации, выбирали тип входящей сущности? Мы выбрали “Phone number”, соответственно, на входе и получаем её. Из нее мы можем спокойно получить одноименное свойство. Для примера, получаю номер телефона и записываю его в импровезированный лог. Данные мы получаем через getProperty(), либо через “request.Value”.

response - это ответ для Мальтего, экземпляр класса MaltegoTransform. Благодаря ему, мы можем как-то взаимодействовать с Мальтего, создавая новые сущности и наполняя их данными.

При помощи addEntity мы создаем новые сущности выбранного нами типа, в том числе несуществующие, которым будет присвоен тип maltego.Unknown. Но в целом, если нет желания создать кастомный тип, то можно пользоваться и несуществующим. Причем, спокойно задавая свойства "на лету", в том чилсе иконку, что продемонстрирую ниже. Если вы не знаете, как правильно называется та или иная существующая сущность, самый простой способ это создать её в Maltego и открыть свойства. Например, Вконтакте:




Заполняется сущность при помощи “addProperty”. Для примера, тестовая трансформация создает четыре разных объекта: персону, мыло, ссылку на ВК и текст (фразу).




Что касается названия свойств, которые добавляются, общую информацию можно посмотреть Для просмотра ссылки Войди или Зарегистрируйся. Если выкинет на ошибку 403, смените IP. Там есть примеры стандартных сущностей Мальтего и их свойств. Но, к сожалению, справка не идеальна… иногда нужно включать голову. Например, чтобы правильно вставить ссылку на профиль Вконтакте, потребовалось поискать. Оказалось, что свойство совпало со свойством ссылки на профиль в Твиттер:




С другой стороны, если бы была идеальная справка, я бы никогда не узнал, что можно спокойно добавлять новые свойства. Любое несуществующее свойство, которое вы добавите к сущности, автоматически подтянется как новое к существующему объекту:




Таким образом, можно наполнять объект свойствами для дальнейшей обработки в трансформациях.

Что касается записи номера телефона в файл, файл выглядит следующим образом:




Возможно, придется нормализовывать данные…

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

Создание клиента Telegram​

Начнем с лайта, создадим своего клиента для телеги, который будет работать через MTProto. Альтернатив никаких не вижу. Мне не попадалось какого-то API для “Глаза Бога” (по крайней мере официального) и альтернатив чата в телеге от имени пользователя, тоже не знаю. Разве что создать web-сессию и заниматься рукоблудием в виде автоматизации в браузере, но зачем?

Начать стоит с посещения этой ссылки: Для просмотра ссылки Войди или Зарегистрируйся




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




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




Мы можем написать все взаимодействие через обычные запросы, но лучше возьмем привычное готовое решение в виде Telethon.




Установка и тестовые скрипты расположены здесь: Для просмотра ссылки Войди или Зарегистрируйся




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

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

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

К скрипту выше добавим несколько импортов и объявим класс, который будет обрабатывать запросы:

Python: Скопировать в буфер обмена
Код:
import http.server
import socketserver
import json
import re

...

class GodEyeHTTPRequestHandler(http.server.SimpleHTTPRequestHandler):
 
    def do_POST(self):
        if self.path == '/getPersonByPhone':
            content_len = int(self.headers.get('Content-length'))
            post_body = self.rfile.read(content_len)
            post_data_str = post_body.decode('utf-8')
            phone = post_data_str.split('=')[1]

            client.send_message(godeye_login, phone)
 
            while True:
                messages = client.get_messages(godeye_login, 1)

                if f'Номер: {phone}' in messages[0].message:
                    self.send_response(200)
                    self.send_header("Content-type", "application/json")
                    self.end_headers()
                    data = self.parseDataByPhone(messages[0].message)
                    self.wfile.write(data.encode('utf-8'))
                    break

Обрабатываем только POST-запросы и, пока, только один путь “/getPersonByPhone”. Чтобы избежать асинхронных вызовов, пошел банальным путем. Отправляем запрос в Глаз Бога и начинаем чекать последнее сообщение в чате, так как чату надо время на сбор информации:




Если находим строчку вида “Номер 79…”, значит пора забирать данные. При верном ответе бота, мы всегда найдем эту строку с нашим номером телефона. Если что-то пошло не так, сервер вернет ошибку по таймауту.

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

Python: Скопировать в буфер обмена
Код:
def parseDataByPhone(self, message):
        data = {}
 
        phones_metches = re.findall(r'(?<=Номер:\s).*', message)
        country_matches = re.findall(r'(?<=Страна:\s).*', message)
        region_matches = re.findall(r'(?<=Регион:\s).*', message)
        operator_matches = re.findall(r'(?<=Оператор:\s).*', message)

        possible_names = re.findall(r'(?<=Возможные имена:\n.\s\s).*(?=\n\n)', message)
        possible_fio = re.findall(r'(?<=Возможное ФИО:\s).*', message)
        birth_dates = re.findall(r'(?<=Дата рождения:\s).*', message)

        vk_matches = re.findall(r'(?<=Вконтакте:\s).*', message)
        ok_matches = re.findall(r'(?<=Одноклассники:\s).*', message)
        tg_matches = re.findall(r'(?<=Telegram:\s).*', message)
        whatsapp_matches = re.findall(r'(?<=Whatsapp:\s).*', message)
        viber_matches = re.findall(r'(?<=Viber:\s).*', message)
 
        if len(phones_metches):
            data["phone"] = phones_metches[0]
 
        if len(country_matches):
            data["country"] = country_matches[0]
 
        if len(region_matches):
            data["region"] = region_matches[0]
 
        if len(operator_matches):
            data["operator"] = operator_matches[0]
 
        if len(possible_names):
            data["possible_names"] = possible_names[0]
 
        if len(possible_fio):
            data["possible_fio"] = possible_fio[0]
 
        if len(birth_dates):
            data["birth_dates"] = birth_dates[0]
 
        if len(vk_matches):
            data["vk"] = vk_matches[0]
 
        if len(ok_matches):
            data["ok"] = ok_matches[0]
 
        if len(tg_matches):
            data["tg"] = tg_matches[0]
 
        if len(whatsapp_matches):
            data["whatsapp"] = whatsapp_matches[0]
 
        if len(viber_matches):
            data["viber"] = viber_matches[0]
 
        return json.dumps(data)

Банально, просто, но работает. Остается только запустить прослушивание порта сервером и телеграм клиент. Вне класса пишем:

Python: Скопировать в буфер обмена
Код:
PORT = 8887
Handler = GodEyeHTTPRequestHandler

with socketserver.TCPServer(("", PORT), Handler) as httpd:
    print('Serving port', PORT)
    httpd.serve_forever()

client.run_until_disconnected()

Это некий минимум с которым наш сервер будет обрабатывать POST-запрос с номером телефона и возвращать основные данные. Теперь можно переписать нашу трансформацию, чтобы хоть как-то её оживить:

Python: Скопировать в буфер обмена
Код:
from maltego_trx.maltego import MaltegoMsg, MaltegoTransform
from maltego_trx.transform import DiscoverableTransform
import requests

class godeye(DiscoverableTransform):
 
    @classmethod
    def create_entities(cls, request: MaltegoMsg, response: MaltegoTransform):

        maltego_phone_number = request.getProperty('phonenumber')
        maltego_phone_number = str(maltego_phone_number).replace(' ', '').replace('+', '')

        data = {
            "phone": maltego_phone_number
        }

        url = 'http://127.0.0.1:8887/getPersonByPhone'

        response_godeye = requests.post(url=url, data=data)
        message = response_godeye.json()
        country = message['country']
 
        phrase_entity = response.addEntity('maltego.Phrase', country)

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




Можной зайти в Мальтего, чтобы протестировать трансформацию. Добавляем нужный телефон, запускаем трансформацию и получаем первый результат:




Но, если вы работаете на Windows, высока вероятность получить такую печальку:





Где-то поплыла кодировка. Смотрите в какой кодировке у вас файлы проекта. Кроме того, пишут, что проблема может возникать на стыке Python-Java. Есть множество вариантов решений, в каждом случае срабатывает свое. Как одно из решений, прописать в начало файла трансформации:

Python: Скопировать в буфер обмена
Код:
#!/usr/bin/env python
# -*- coding: iso-8859-5 -*-

Есть еще вариант установить переменную окружения указывающую базовую кодировку для Python, выполнив в терминале Windows:


Python: Скопировать в буфер обмена
set PYTHONIOENCODING=...

У меня сработал вариант с добавлением к каждой строке перекодирования:

Python: Скопировать в буфер обмена
"Корявая строка".encode('utf8').decode('cp1251')

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

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

Python: Скопировать в буфер обмена
Код:
from maltego_trx.maltego import MaltegoMsg, MaltegoTransform
from maltego_trx.transform import DiscoverableTransform
import requests
import json
import re
 
class godeye(DiscoverableTransform):
 
    @classmethod
    def create_entities(cls, request: MaltegoMsg, response: MaltegoTransform):

        maltego_phone_number = request.getProperty('phonenumber')
        maltego_phone_number = str(maltego_phone_number).replace(' ', '').replace('+', '')

        data = {
            "phone": maltego_phone_number
        }

        url = 'http://127.0.0.1:8887/getPersonByPhone'

        response_godeye = requests.post(url=url, data=data)
        text_content = response_godeye.text
        message = json.loads(text_content)

        phone_info = []

        if 'country' in message:
            phone_info.append(message['country'])

        if 'region' in message:
            phone_info.append(message['region'])

        if 'operator' in message:
            phone_info.append(message['operator'])

        if len(phone_info):
            phone_entity = response.addEntity('maltego.Phrase', f"{', '.join(phone_info)}" )

        if 'possible_fio' in message:
            for person in message['possible_fio']:
                person_entity = response.addEntity('maltego.Person', person)
                person_names = person.split(' ')
                person_entity.addProperty("firstname", value=person_names[0])
                if 'birth_dates' in message:
                    person_entity.addDisplayInformation(f"Возможные даты рождения: <ul><li>{'</li><li>'.join(message['birth_dates'])}</li></ul>")
     
                if person_names[1]:
                    person_entity.addProperty('lastname', value=person_names[1])
     
                if person_names[2]:
                    person_entity.addProperty('surname', value=person_names[2])

        if 'email' in message:
            for email in message['email']:
                email_entity = response.addEntity('maltego.EmailAddress', email)
 
        if 'possible_names' in message:
            possible_names_entity = response.addEntity('maltego.Phrase', f"Потенциальные имена: {message['possible_names']}" )

        if 'birth_dates' in message:
            birth_dates_entity = response.addEntity('maltego.Phrase', f"Даты рождения: {', '.join(message['birth_dates'])}" )

        if 'whatsapp' in message and message['whatsapp'] == 'найдено':
            whatsapp_entity = response.addEntity('maltego.Unknown', 'Whatsapp: 79166485790')
            whatsapp_entity.setIconURL('https://faq.whatsapp.com/images/presma/whatsapp/whatsapp_logo_green.png')

        if 'viber' in message and message['viber'] == 'найдено':
            viber_entity = response.addEntity('maltego.Unknown', 'Viber: 79166485790')
            viber_entity.setIconURL('https://www.viber.com/app/uploads/viber-logo.png')

        if 'tiktok' in message:
            cls.soclinks_add(message['tiktok'],'TikTok', response)

        if 'vk' in message:
            cls.soclinks_add(message['vk'],'VKontakte', response)

        if 'ok' in message:
            cls.soclinks_add(message['ok'],'OK', response)  


        if 'tg' in message:
            for tg in message['tg']:
                tg_entity = response.addEntity('maltego.affiliation.Telegram', tg)
                tg_entity.addProperty('affiliation.uid', value=tg)
                tg_entity.addProperty('affiliation.profile-url', value=f"https:/t.me/{tg}")

    @classmethod
    def soclinks_add(self, soclink_list, socnetwork, maltego):
        for soclink in soclink_list:
            found = re.search(r'(.*)\s\((http.*)\)', soclink, re.DOTALL)
            if found != None:
                ok_entity = maltego.addEntity(f'maltego.affiliation.{socnetwork}', found.group(1))
                ok_entity.addProperty('affiliation.profile-url', value=found.group(2))

Что здесь происходит? Сначала нормализуем номер телефона, удалив все пробелы и плюс. После делаем пост-запрос к нашему серверу, который писали выше. Получив ответ, мы поэтапно прохдим и проверяем все свойства. Если свойство есть, то добавляем соответствующий объект в проект Мальтего. Я не стал мудорствовать, происходит простой первичный сбор информации.

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

Python: Скопировать в буфер обмена
Код:
        if 'tiktok' in message:
            for soclink in message['tiktok']:
                found = re.search(r'(.*)\s\((http.*)\)', soclink, re.DOTALL)
                if found != None:
                    vk_entity = response.addEntity('maltego.affiliation.TikTok', found.group(1))
                    vk_entity.addProperty('affiliation.profile-url', value=found.group(2))

        if 'vk' in message:
            for soclink in message['vk']:
                found = re.search(r'(.*)\s\((http.*)\)', soclink, re.DOTALL)
                if found != None:
                    vk_entity = response.addEntity('maltego.affiliation.VKontakte', found.group(1))
                    vk_entity.addProperty('affiliation.profile-url', value=found.group(2))

        if 'ok' in message:
            for soclink in message['ok']:
                found = re.search(r'(.*)\s\((http.*)\)', soclink, re.DOTALL)
                if found != None:
                    ok_entity = response.addEntity('maltego.affiliation.OK', found.group(1))
                    ok_entity.addProperty('affiliation.profile-url', value=found.group(2))

С функцией компактнее. Можно создать объект, в котором хранить название свойства для “message”, а также правильное название объекта в Мальтего. Тогда код свернется еще лучше, но нам пока нужна самая суть.

О Whatsapp и Viber, Мальтего ничего не слышал. В коде я выкрутился простым способом, создав сущность с типом “Unknown” и присвоив новой сущности иконку:

Python: Скопировать в буфер обмена
Код:
if 'whatsapp' in message and message['whatsapp'] == 'найдено':
            whatsapp_entity = response.addEntity('maltego.Unknown', 'Whatsapp: 79166485790')
            whatsapp_entity.setIconURL('https://faq.whatsapp.com/images/presma/whatsapp/whatsapp_logo_green.png')

Конечно, правильнее было бы создать новый тип сущности и использовать именно его:




Теперь можно создать сущность с типом “xss.is.Whatsapp”, которая будет базироваться на базе телефонного номера, а значит копирует все его основные свойства.

Python: Скопировать в буфер обмена
Код:
        if 'whatsapp' in message and message['whatsapp'] == 'найдено':
            whatsapp_entity = response.addEntity('xss.is.Whatsapp', 'Whatsapp: 79166485790')
            whatsapp_entity.addProperty('phonenumber', value=maltego_phone_number)

Обратите внимание на использование метода “addDisplayInformation”. При помощи него мы можем вывести дополнительную информацию о сущности. В нашем случае, я дублирую даты рождения каждого в описание добавляемого человека, ведь мы изначально не можем знать какая дата верная для каждого найденного ФИО. Причем, мы спокойно можем подпихивать HTML-разметку, которую Мальтего прекрасно отрабатывает.




Что будет, если мы забыли запустить сервер? Или если наша подписка окончена? Предлагаю отключить сервер с ботом и обернуть работу с сервером в обработчик ошибок. На исключение добавим оповещение пользователя перез addUIMessage:

Python: Скопировать в буфер обмена
Код:
        try:
            response_godeye = requests.post(url=url, data=data)
        except:
            response.addUIMessage('Проверь сервер, что-то не так...', UIM_TYPES['fatal'])
            return response.returnOutput()

Запускаем и видим полноценное окно оповещения:.




Только не забудьте добавить импорт UIM_TYPES:

Python: Скопировать в буфер обмена
from maltego_trx.maltego import UIM_TYPES

Список вариантов оповещения:




Возможно вы обратили внимание, что в коде появился метод returnOutput(). Я его опускал, хотя по-хорошему, он должен завершать работу трансформации. Данный метод генерирует итоговый XML, который должен уйти в Мальтего. Если все идет хорошо, то можно не заморачиваться, метод будет выполнен не явно. Но прерывая выполнение, стоит вызвать этот метод.

Ограничение комьюнити версии​

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




К счастью, мы можем спокойно обойти это ограничение. Во-первых, нам поможет фильтрация Мальтего. Если вы уже создали какую-то сущность, Maltego не позволит программно повторно создать новую сущность. Таким образом, перезапуская трансформацию можно вытащить все нужные данные из Глаза Бога. Но стоит чуток поменять код сервера, чтобы каждый раз не повторять один и тот же запрос. Самый простой способ, это вынести проверку последнго сообщения в отдельную функцию. Речь про эту проверку:

Python: Скопировать в буфер обмена
Код:
            while True:
                messages = client.get_messages(godeye_login, 1)
                message = messages[0].message

                if f'Номер: {phone}' in message:
                    self.send_response(200)
                    self.send_header("Content-type", "text/plain")
                    self.end_headers()
                    data = self.parseDataByPhone(message)
                    print(data)
                    self.wfile.write(data.encode('utf-8'))
                    break

В более "правильном" варианте, код может выглядеть как-то так:

Python: Скопировать в буфер обмена
Код:
    def do_POST(self):
        if self.path == '/getPersonByPhone':
            content_len = int(self.headers.get('Content-length'))
            post_body = self.rfile.read(content_len)
            post_data_str = post_body.decode('utf-8')
            phone = post_data_str.split('=')[1]

            if self.checkLastMessageHasPhone(phone):
                self.sendResponseFromMessage()
                return

            client.send_message(godeye_login, phone)
 
            while True:
                if self.checkLastMessageHasPhone(phone):
                    self.sendResponseFromMessage()
                    break

    def sendResponseFromMessage(self):
        messages = client.get_messages(godeye_login, 1)
        message = messages[0].message
        self.send_response(200)
        self.send_header("Content-type", "text/plain")
        self.end_headers()
        data = self.parseDataByPhone(message)
        print(data)
        self.wfile.write(data.encode('utf-8'))

    def checkLastMessageHasPhone(self, phone):
        messages = client.get_messages(godeye_login, 1)
        message = messages[0].message

        if f'Номер: {phone}' in message:
            return True
        return False

Получается, перед тем, как сделать новый запрос в Глаз Бога, мы смотрим последнее сообщение. Если это тот же номер, то просто заново парсим сообщение и возвращаем результат пользователю. В ином случае, делаем запрос и ждем ответ. Почти как и было, но теперь без повторных запросов. Минус только один - не получится обновить данные в чате, пока не выполнишь другой запрос. Зато не задолбаем ГБ до лимита.

Машины и MSL​

Чтобы минимизировать рутину, Maltego предоставляет простой механизм автоматизированных машин со встроенным языком Maltego Scripting Language. Это достаточно скудный язык, который не позволит выстроить те же логические ветвления. Жаль, ведь тогда мы могли бы обойти ограничение в 12 сущностей с трансформации, запуская её пока не прекратится добавление новых сущностей. Но все же, MSL позволяет значительно облегчить жизнь осинтера. Если коротко о возможностях языка:

  1. Запуск трансформацией: линейно, одна за другой, параллельно или гибридный режим, когда используются оба варианта.
  2. Возможность фильтрации сущностей которые обрабатывают трансформации: по типу сущности, по значению, по свойствам, по входящим и исходящим взаимосвязям, можно предоставить пользователю возможность выбора.
  3. Логгирование и информирование о состоянии машины
  4. Оперирование сущностями, например, удаление
  5. Возможность выполнять машину по заданному интервалу и прочие мелочи, типа сохранения графа, экспорта картинок и т.п.

Сложно сказать, чем руководствуются разработчики, в том числе Maltego. Практически невозможно найти полноценную спецификацию или справку по MSL. Возможно, это часть бизнеса и они рассказывают все на каких-то платных курсах. Черт знает каким образом я нашел старую ссылку на Для просмотра ссылки Войди или Зарегистрируйся, которая уже не работала, но через Веб-архив удалось вытащить эту Для просмотра ссылки Войди или Зарегистрируйся. Прикрепить не могу из-за размера. В ней подробно рассматриваются все возможности MSL.

Но продолжим. Связь с Глазом Бога уже готова, самое время расширить возможности и обогатить информацию. Нам потребуется новый запрос в ГБ, а именно, возьмем возможные ФИО, а также даты рождения и по ним попытаемся получить больше данных. Для этого мы можем создать еще одну трансформацию и, после выполнения первой, в скрипте машины выполнить вторую. Либо воспользоваться уже существующей, но добавив входящий параметр при запуске. Пойду по первому пути. Но помните, что в данном случае, это скорее пример, поэтому не стоит удивляться сомнительным решениям.

Сначала нам нужно определиться с тем, что делать с датами рождения. То, что мы добавляем в DisplayInformation, мы не можем обратно извлечь из сущности. По крайней мере, у меня нет способа этого сделать. Поэтому продублировал добавление почт к Person, но уже в виде нового параметра. Тоже делаю со страной, так как при запросе в ГБ надо будет кликать по кнопке с выбором страны.

Python: Скопировать в буфер обмена
Код:
                if 'birth_dates' in message:
                    person_entity.addDisplayInformation(f"Возможные даты рождения: <ul><li>{'</li><li>'.join(message['birth_dates'])}</li></ul>")
                    birth_dates_str = re.sub('\(.*?\)|\s', '', ', '.join(message['birth_dates']))
                    person_entity.addProperty('possible_birth_dates', birth_dates_str)

                if 'country' in message:
                    person_entity.addProperty('country',message['country'])

Чтобы не городить огород, рядом с нашим файлом трансформации “godeye.py” создадим “GreetPerson.py”:

Python: Скопировать в буфер обмена
Код:
from maltego_trx.maltego import MaltegoMsg, MaltegoTransform, UIM_TYPES
from maltego_trx.transform import DiscoverableTransform
import requests
import json

class GodeyePerson(DiscoverableTransform):
 
    @classmethod
    def create_entities(cls, request: MaltegoMsg, response: MaltegoTransform):
        fullname = request.getProperty('fullname')
        possible_birth_dates_str = request.getProperty('possible_birth_dates')
        country = request.getProperty('country')

        birth_dates_arr = possible_birth_dates_str.split(',')

        for birth_date in birth_dates_arr:

            data = {
                "request": f"{fullname}, {birth_date}",
                "country": country
            }

            url = 'http://127.0.0.1:8887/getDataByFullname'
            try:
                response_godeye = requests.post(url=url, data=data)
            except:
                response.addUIMessage('Проверь сервер, что-то не так...', UIM_TYPES['fatal'])
                return response.returnOutput()

            text_content = response_godeye.text
            message = json.loads(text_content)

            if 'phones' in message:
                for phone in message['phones']:
                    phone_entity = response.addEntity('maltego.PhoneNumber', phone)
                    phone_entity.addProperty('phonenumber', value=phone)

            if 'zodiak' in message:
                zodiak_entity = response.addEntity('maltego.Phrase', message['zodiak'] )
                zodiak_entity.setWeight(1)

            return response.returnOutput()

Берем полное имя, берем даты рожденья и в разных компоновках опрашиваем Глаз Бога. Разбивку дат оставил на стороне Мальтего, чтобы не вылететь по таймауту если дат окажется сильно много. Причем, если трансформация таки вылетает, то стоит покопаться в настройках Мальтего:




Самое время дописать нужный функционал в сервер. По сути, нам нужно пройти несколько шагов:
  1. Отправить запрос в ГБ
  2. Дождаться появления выбора страны
  3. Кликнуть на страну
  4. Дождаться ответа сервера: либо данные, либо сообщение об их отсутствии
  5. Распарсить и вернуть ответ.
Причем, после введения больших штрафов за персональные данные, нам остается парсить совсем немного данных… Самое обидно, что не имеет значения к какой стране относится человек. В любом случае, информации стало очень бедно.




Знак зодиака нам вряд ли нужен… разве что хотите навести порчу или еще какими колдавствами страдаете. Поэтому, взять мы можем только номера телефонов для дальнейшего обогащения информации по ним. Можно еще добавить персоны, так как в этом случае у нас уже будет более верное совпадение по ФИО, дате и городу. В результате, у меня получился вот такой кусок кода:

Python: Скопировать в буфер обмена
Код:
class GodEyeHTTPRequestHandler(http.server.SimpleHTTPRequestHandler):

    def do_POST(self):
        content_len = int(self.headers.get('Content-length'))
        post_body = self.rfile.read(content_len)
        post_data_json = json.loads(post_body)
 
        if self.path == '/getDataByFullname':
            query = post_data_json["query"]
            country = post_data_json["country"]
            client.send_message(godeye_login, query)

            while True:
                time.sleep(0.3)
                messages = client.get_messages(godeye_login, 1)
                message = messages[0].message

                if "Выберите страну" in message:
                    button_num = self.searchCountryButtonIndex(messages[0].buttons, country)                     
                    messages[0].click(button_num)         

                    while True:
                        time.sleep(0.3)
                        messages = client.get_messages(godeye_login, 1)
                        message = messages[0].message

                        if f'Личности:' in message:
                            self.sendResponseByPerson(self, message)
                            return

        elif self.path == '/getPersonByPhone':
                ...

Первое изменение - переделал получение тела, теперь данные передаются через JSON. Добавил таймер, который перед каждой проверкой последнего сообщения ждет 300 миллисекунд. Это нужно чтобы не задергивать чат, все же ГБ не так быстро реагирует на сообщения. Дальше проходим по описанным выше шагам. Когда нам предлагают выбрать страну, перед тем как сделать клик, нужно узнать индекс инлайн-кнопки. Вот код функции:

Python: Скопировать в буфер обмена
Код:
    def searchCountryButtonIndex(self, buttons, country):
        count = 0
        for row in buttons:
            for btn in row:
                if country in btn.text:
                    return count
                count = count + 1

        return 35

Фишка в том, что в телеграм инлайн кнопки располагаются в виде многострочных колонок, но чтобы выполнить клик по кнопке, нам нужен индекс кнопки без строки и колонки.. Поэтому, мы проходим по каждой строке и проверяем свойство “text”. Как только мы нашли, можем сделать “click” по нужному сообщению, передав вместо “n” индекс кнопки.

Остается только распарсить ответ ГБ. Для этого у нас следующие функции:

Python: Скопировать в буфер обмена
Код:
def sendResponseByPerson(self, message):
        self.send_response(200)
        self.send_header("Content-type", "text/plain")
        self.end_headers()
        data = self.parseDataByPerson(message)
        self.wfile.write(data.encode('utf-8'))
 
    def parseDataByPerson(self, message):
        data = {}
        godeye_obj_params = {
            "phone": {
                "rgx": r'(?<=Телефон:\s).*',
                "spliting": ',',
                "options": re.NOFLAG
            },
            "persons": {
                "rgx": r'(?<=Личности:\s).*',
                "spliting": ',',
                "options": re.NOFLAG
            },
            "birth_date": {
                "rgx": r'(?<=День рождения:\s).*?,',
                "options": re.NOFLAG
            },
            "city": {
                "rgx": r'(?<=Город:\s).*',
                "options": re.NOFLAG
            },
            "zodiak": {
                "rgx": r'(?<=\().*(?=\))',
                "options": re.NOFLAG
            }
        }
 
        for key in godeye_obj_params.keys():
            parser = godeye_obj_params[key]
            matches = re.findall(parser['rgx'], message, parser["options"])
            if len(matches):
                if 'spliting' in parser:
                    data[key] = matches[0].split(parser['spliting'])
                else:
                    data[key] = matches[0]

        return json.dumps(data)

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

Возвращаемся к MSL и святой автоматизации. Жмакаем на “New Machine” и заполняем простую форму:




Далее, выбираем Macro. Можно и Blank, но пусть мальтего создаст базовую структуру:






В нашем случае, код будет очень простым и предсказуемым:




Код: Скопировать в буфер обмена
Код:
//Welcome to Maltego Machines!

//Each machine starts with a statement like this
machine("petrinh1988.GodEye",
        displayName:"GodEye",
        author:"petrinh1988@xss.is",
        description: "") {

    start {
        status("Starting godeye")
        log("Parse godeye by phone",showEntities:true)
        run("petrinh1988.godeye")
        status("Starting godeye")
        log("Parse godeye by phone",showEntities:true)
        run("petrinh1988.godeye")
        status("Starting godeye")
        log("Parse godeye by phone",showEntities:true)
        run("petrinh1988.godeye")
        status("Starting godeye")
        log("Parse godeye by phone",showEntities:true)
        run("petrinh1988.godeye")
        userFilter(title:"Parse GodEye by Person",heading:"Persons for parsing",description:"Please select the person entities you want to analyse by",proceedButtonText:"Next >")
        status("Starting GodeyePerson")
        log("Parse godeye by person",showEntities:true)
        run("petrinh1988.GodeyePerson")
        status("End parsing")
    }
}

В сущности, мы только сообщаем пользователю стадии и последовательно запускаем несколько трансформаций. Причем, четыре раза одну и туже, чтобы вытащить из ГБ максимум данных, обходя ограничение бесплатной версии Мальтего. Из интересного только пользовательская фильтрация. В данном случае она позволяет избежать 100%-го безумия и хотя бы поубирать явно левых людей.

Что дальше?​

И так, можно считать, что мы скрестили Maltego и “Глаз Бога”. Да, это не полноценная интеграция, но сами видите, что есть огромное количество нюансов, которые нужно учитывать под каждый проект отдельно. Но зато, закрыта большая часть возможных вопросов. Создали своего бота на базе Telethon, разобрали как можно без асинхронности производить полноценный обмен данными, в том числе кликать по кнопке с выбором страны.

Мы создали несколько несложных собственных трансформаций, но внутри них я постарался разобрать максимум полезных разработчику инструментов. Автоматизации на базе Машин и MSL коснулись совсем немного, но думаю PDF-инструкция поможет разобраться и писать более мощные автоматизации. Если потребуется помощь, пишите, разберу более подробно на примерах.

Следующим шагом, может быть например, запуск трансформации через Docker или “runserver”. Либо, более плотная интеграция с Глазом Бога. Например, я полностью проигнорировал найденные фотографии. Хотя по сути, их можно спокойно получить из Телеграм и наложить в Мальтего. Можно пойти в сторону обогащения данных, например, прикрутить API парсера выдачи и находить упоминания личности в интернет. Можно прикрутить API соцсетей и парсить друзей и прочую информацию. Можно собрать посты из соцсетей пользователя и скормить его через API какой-нибудь нейронке для дальнейшего анализа интересов, политических или потребительских предпочтений т.п.

Не стесняйтесь поделиться мнением по поводу статьи, была ли информация полезной для вас? Может есть какие-то пожелания или замечания?

И конечно, если считаете нужным, что надо больше материалов по созданию собственных трансформаций и обогащению данных, дайте мне об этом знать.
 
Друг мой... уы профиль у тебя закрыт и я не могу оценить весь объём гения... уверен, что его трудно объять. Если максимально просто и на пальцах, то вот там - на главной странице мигает - конкурс статей... их не видел, но есть вероятность что и нет смысла. Это лучшее - что я видел про скрещивание и симбиоз. Спасибо тебе, до слёз.
 
Воооу!
 
Activity
So far there's no one here