# WitheredFlower Forensic На входе нам дается `evidence.zip`, внутри которого расположены: `backup.ab` и папка `Logs`. ## Подготовка Извлекаем `backup.ab` через `abe.jar` (его скачиваем с интернета) После этого основные пути: - логи: `.\Logs\...` - данные приложений: `.\ab_extracted\...` ### Таск 1. BRAND / MODEL / BUILD_ID 1. Открываем `Logs/ADBReport/device_info.txt`. 2. Поиск по файлу: - `ro.product.manufacturer` - `ro.product.model` 3. Открываем `Logs/DumpSysReport/package.txt`. 4. Поиск по файлу: `buildFingerprint=`. 5. Из `buildFingerprint` берём `BUILD_ID` (часть вида `BP2A...`). Флаг: `caplag{SAMSUNG/SM-A346E/BP2A.250605.031.A3}` ### Таск 2. Время последней загрузки + Realtime 1. Открываем `Logs/DumpSysReport/meminfo.txt`, в первой строке видим: `Realtime: 479801602` ms 2. Открываем `Logs/DumpSysReport/sensorservice.txt`, берём: - `Captured at: 04:00:17.895` - дату из строк `wall=02-10 ...` (месяц-день) 3. Год берём из системных логов с ISO-датой (например, `Logs/ADBReport/device_state.txt`), здесь это `2026`. 4. Считаем в Python: ```python from datetime import datetime, timedelta capture = datetime.fromisoformat("2026-02-10 04:00:17.895") realtime_ms = 479_801_602 realtime_sec = realtime_ms // 1000 boot_raw = capture - timedelta(seconds=realtime_sec) boot_rounded = (boot_raw + timedelta(milliseconds=500)).replace(microsecond=0) days = realtime_sec // 86400 rem = realtime_sec % 86400 hours = rem // 3600 minutes = (rem % 3600) // 60 seconds = rem % 60 print("boot_raw:", boot_raw) print("boot_rounded:", boot_rounded.strftime("%Y-%m-%d %H:%M:%S")) print("realtime_DDHHMMSS:", f"{days:02d}:{hours:02d}:{minutes:02d}:{seconds:02d}") ``` Получаем: - `boot_rounded = 2026-02-04 14:43:37` - `realtime_DDHHMMSS = 05:13:16:41` Флаг: `caplag{2026-02-04/14:43:37_05:13:16:41}` ## Таск 3. Самый большой файл в Download + SHA-256 1. Открываем папку `ab_extracted/shared/0/Download`. 2. Сортируем файлы по размеру по убыванию. 3. Самый большой файл: `Telegram.apk`. 4. Считаем SHA-256 вручную через 7-Zip: - `7-Zip -> CRC SHA -> SHA-256` - копируем значение Флаг: `caplag{4d3400b9330b2a7d93683fd03ddda41aa144c65f74eb27e3a1786b21397ec7d1}` ## Таск 4. Самый ранний Wi-Fi BSSID + адрес 1. Открываем `Logs/DumpSysReport/wifi.txt`. 2. Видим, что чаще всего встречается BSSID 00:1C:10:BC:B4:03, он и есть самый ранний 3. Открываем сайт 3wifi.dev, вбиваем в поиск этот MAC адрес, находим коорды 38.939159, -77.163895 4. По картам находим адрес дома (1305 BALLANTRAE FARM DRIVE MC LEAN VA 22101) Флаг: `caplag{00:1C:10:BC:B4:03_1305BALLANTRAEFARMDRIVEMCLEANVA22101}` ## Таск 5. Год постройки, стоимость земли на 2017 год, площадь бассейна в sq ft. По описанию таска нам нужно найти информацию о соседнем доме с таким же номером, 1305 Ballantrae Ct, McLean, VA 22101 1. Открываем поиск по адресу на сайте https://icare.fairfaxcounty.gov/ffxcare/search/commonsearch.aspx?mode=address 2. Вбиваем все данные, получаем pdf документ со всей информацией о доме. 3. В PDF находим данны о стоимости, площади бассейна и т.д. Флаг: `caplag{1965_887000_720}` ## Таск 6. Package name + device name 1. Открываем `Logs/ADBReport/packages_thirdparty.txt`, ищем `projectflower`: - получаем package name: `com.projectflower.agency` 2. Открываем `Logs/DumpSysReport/wifi.txt`, ищем `wifi_p2p_device_name`: - получаем device name: `a-272-8` Флаг: `caplag{com.projectflower.agency_a-272-8}` ## Таск 7. Время первой установки ProjectFlower 1. Подтверждаем `appId` для нужного пакета: - `Logs/DumpSysReport/package.txt` - по поиску `Package [com.projectflower.agency]` и `appId=10336` 2. Открываем `Logs/ADBReport/device_state.txt`, ищем `Package add: uid=10336`. 3. Среди найденных строк выбираем самое раннее время по timestamp в начале строки. Самый ранний timestamp: `2026-02-10T02:50:38.948756`. Формат: `2026-02-10/02:50:38` Флаг: `caplag{2026-02-10/02:50:38}` ## Таск 8. Основной sync endpoint 1. Идём в `ab_extracted/apps/com.projectflower.agency/a/`. 2. Открываем `base.apk` архиватором (или копируем как `base.zip` и распаковываем). 3. Читаем файл `assets/protocol/kappa.cfg`. Берём: - `endpoint_primary=wss://relay-core.projectflower.agency/sync` - `port_primary=443` Флаг: `caplag{wss://relay-core.projectflower.agency/sync:443}` ## Таск 9. PIN + SecretCode агента T-4A1KD ### PIN 1. Открываем `.\ab_extracted\apps\com.projectflower.agency\a\base.apk` в jadx-gui. 2. Переходим в класс `com.projectflower.agency.security.PinManager` (или делаем поиск по строке `pin_hash` и открываем класс-обработчик PIN). 3. Открываем метод `initializeDefaultsIfMissing` и/или `hashPin`. 4. В `initializeDefaultsIfMissing` видно, какое 6-значное значение передаётся как дефолтный PIN при инициализации. 5. В `hashPin` подтверждаем алгоритм: `PBKDF2WithHmacSHA256`, `12000` раундов. 6. Проверочный state лежит в `.\ab_extracted\apps\com.projectflower.agency\sp\pf_secure_state.xml` (`pin_salt`, `pin_hash`), что совпадает с логикой из кода. 7. Из кода получаем PIN: `473029`. ### SecretCode 1. Открываем `.\ab_extracted\apps\com.projectflower.agency\db\flowerline.db` в DB Browser for SQLite. 2. Выполняем SQL: ```sql SELECT id, keyPartA, keyPartB FROM chat_threads; SELECT chatId, bodyCipher, iv FROM messages WHERE senderId='T-4A1KD' AND isEncrypted=1; ``` 3. Для найденного `chatId` собираем материал ключа: - `key_material = keyPartA + "::" + keyPartB + "::orchid/signal/v4"` 4. Расшифровываем `bodyCipher` (AES-GCM, nonce = `iv`, AAD пустой) и получаем plaintext. ```python import base64, hashlib, sqlite3 from pathlib import Path from cryptography.hazmat.primitives.ciphers.aead import AESGCM db = Path(r".\ab_extracted\apps\com.projectflower.agency\db\flowerline.db") con = sqlite3.connect(db) con.row_factory = sqlite3.Row cur = con.cursor() cur.execute("SELECT chatId, bodyCipher, iv FROM messages WHERE senderId='T-4A1KD' AND isEncrypted=1 LIMIT 1") msg = cur.fetchone() cur.execute("SELECT keyPartA, keyPartB FROM chat_threads WHERE id=?", (msg["chatId"],)) thr = cur.fetchone() key_material = f'{thr["keyPartA"]}::{thr["keyPartB"]}::orchid/signal/v4' key = hashlib.sha256(key_material.encode()).digest() pt = AESGCM(key).decrypt(base64.b64decode(msg["iv"]), base64.b64decode(msg["bodyCipher"]), None) print(pt.decode()) ``` Получаем: `Code: 19XQF` Флаг: `caplag{473029_19XQF}` ## Таск 10. Восстановление удалённого сообщения + SHA-256 1. В DB Browser находим удалённое сообщение через ссылку `tombstoneRef`: ```sql SELECT id, chatId, senderId, tombstoneRef FROM messages WHERE senderId='Petal' AND tombstoneRef IS NOT NULL; ``` 2. По найденному `tombstoneRef` достаём шифртекст: ```sql SELECT id, messageId, cipherText, iv FROM tombstones WHERE id = ''; ``` 3. Достаём параметры для ключа: - `fragment` из `.\apk_unpack\assets\protocol\kappa.cfg` - `cursor_seed` из `.\ab_extracted\apps\com.projectflower.agency\sp\pf_ops_seed.xml` 4. Формируем ключ: - `key = SHA256("PLF-omega-" + fragment + "kappa-19" + seed[:8])` 5. Расшифровываем AES-GCM (`cipherText`, `iv`, AAD пустой), получаем строку формата: - `messageId|chatId|original_body` 6. Считаем SHA-256 только от `original_body`. ```python import base64, hashlib, re, sqlite3 from pathlib import Path from zipfile import ZipFile from cryptography.hazmat.primitives.ciphers.aead import AESGCM base = Path(r".") db = base / "ab_extracted" / "apps" / "com.projectflower.agency" / "db" / "flowerline.db" seed_xml = (base / "ab_extracted" / "apps" / "com.projectflower.agency" / "sp" / "pf_ops_seed.xml").read_text(encoding="utf-8") cfg_path = base / "apk_unpack" / "assets" / "protocol" / "kappa.cfg" if cfg_path.exists(): cfg = cfg_path.read_text(encoding="utf-8") else: apk = base / "ab_extracted" / "apps" / "com.projectflower.agency" / "a" / "base.apk" with ZipFile(apk, "r") as z: cfg = z.read("assets/protocol/kappa.cfg").decode("utf-8") seed = re.search(r'([^<]+)', seed_xml).group(1) fragment = re.search(r"^fragment=(.+)$", cfg, re.M).group(1).strip() con = sqlite3.connect(db) con.row_factory = sqlite3.Row cur = con.cursor() cur.execute("SELECT tombstoneRef FROM messages WHERE senderId='Petal' AND tombstoneRef IS NOT NULL ORDER BY createdAt LIMIT 1") ref = cur.fetchone()["tombstoneRef"] cur.execute("SELECT cipherText, iv FROM tombstones WHERE id=?", (ref,)) t = cur.fetchone() key = hashlib.sha256(f"PLF-omega-{fragment}kappa-19{seed[:8]}".encode()).digest() pt = AESGCM(key).decrypt(base64.b64decode(t["iv"]), base64.b64decode(t["cipherText"]), None).decode() original_body = pt.split("|", 2)[2] print(hashlib.sha256(original_body.encode()).hexdigest()) ``` Флаг: `caplag{8eddf96d2de82df880e42163a792338c067b65f07ab1d167f9ffec9fe12ceaea}`