WitheredFlower Forensic
На входе нам дается evidence.zip, внутри которого расположены: backup.ab и папка Logs.
Подготовка
Извлекаем backup.ab через abe.jar (его скачиваем с интернета)
После этого основные пути:
- логи:
.\Logs\... - данные приложений:
.\ab_extracted\...
Таск 1. BRAND / MODEL / BUILD_ID
- Открываем
Logs/ADBReport/device_info.txt. - Поиск по файлу:
ro.product.manufacturerro.product.model
- Открываем
Logs/DumpSysReport/package.txt. - Поиск по файлу:
buildFingerprint=. - Из
buildFingerprintберёмBUILD_ID(часть видаBP2A...).
Флаг: caplag{SAMSUNG/SM-A346E/BP2A.250605.031.A3}
Таск 2. Время последней загрузки + Realtime
- Открываем
Logs/DumpSysReport/meminfo.txt, в первой строке видим:Realtime: 479801602ms - Открываем
Logs/DumpSysReport/sensorservice.txt, берём:Captured at: 04:00:17.895- дату из строк
wall=02-10 ...(месяц-день)
- Год берём из системных логов с ISO-датой (например,
Logs/ADBReport/device_state.txt), здесь это2026. - Считаем в 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:37realtime_DDHHMMSS = 05:13:16:41
Флаг: caplag{2026-02-04/14:43:37_05:13:16:41}
Таск 3. Самый большой файл в Download + SHA-256
- Открываем папку
ab_extracted/shared/0/Download. - Сортируем файлы по размеру по убыванию.
- Самый большой файл:
Telegram.apk. - Считаем SHA-256 вручную через 7-Zip:
7-Zip -> CRC SHA -> SHA-256- копируем значение
Флаг: caplag{4d3400b9330b2a7d93683fd03ddda41aa144c65f74eb27e3a1786b21397ec7d1}
Таск 4. Самый ранний Wi-Fi BSSID + адрес
- Открываем
Logs/DumpSysReport/wifi.txt. - Видим, что чаще всего встречается BSSID 00:1C:10:BC:B4:03, он и есть самый ранний
- Открываем сайт 3wifi.dev, вбиваем в поиск этот MAC адрес, находим коорды 38.939159, -77.163895
- По картам находим адрес дома (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
- Открываем поиск по адресу на сайте https://icare.fairfaxcounty.gov/ffxcare/search/commonsearch.aspx?mode=address
- Вбиваем все данные, получаем pdf документ со всей информацией о доме.
- В PDF находим данны о стоимости, площади бассейна и т.д.
Флаг: caplag{1965_887000_720}
Таск 6. Package name + device name
- Открываем
Logs/ADBReport/packages_thirdparty.txt, ищемprojectflower:- получаем package name:
com.projectflower.agency
- получаем package name:
- Открываем
Logs/DumpSysReport/wifi.txt, ищемwifi_p2p_device_name:- получаем device name:
a-272-8
- получаем device name:
Флаг: caplag{com.projectflower.agency_a-272-8}
Таск 7. Время первой установки ProjectFlower
-
Подтверждаем
appIdдля нужного пакета:Logs/DumpSysReport/package.txt- по поиску
Package [com.projectflower.agency]иappId=10336
-
Открываем
Logs/ADBReport/device_state.txt, ищемPackage add: uid=10336. -
Среди найденных строк выбираем самое раннее время по timestamp в начале строки.
Самый ранний timestamp: 2026-02-10T02:50:38.948756.
Формат: 2026-02-10/02:50:38
Флаг: caplag{2026-02-10/02:50:38}
Таск 8. Основной sync endpoint
- Идём в
ab_extracted/apps/com.projectflower.agency/a/. - Открываем
base.apkархиватором (или копируем какbase.zipи распаковываем). - Читаем файл
assets/protocol/kappa.cfg.
Берём:
endpoint_primary=wss://relay-core.projectflower.agency/syncport_primary=443
Флаг: caplag{wss://relay-core.projectflower.agency/sync:443}
Таск 9. PIN + SecretCode агента T-4A1KD
PIN
- Открываем
.\ab_extracted\apps\com.projectflower.agency\a\base.apkв jadx-gui. - Переходим в класс
com.projectflower.agency.security.PinManager(или делаем поиск по строкеpin_hashи открываем класс-обработчик PIN). - Открываем метод
initializeDefaultsIfMissingи/илиhashPin. - В
initializeDefaultsIfMissingвидно, какое 6-значное значение передаётся как дефолтный PIN при инициализации. - В
hashPinподтверждаем алгоритм:PBKDF2WithHmacSHA256,12000раундов. - Проверочный state лежит в
.\ab_extracted\apps\com.projectflower.agency\sp\pf_secure_state.xml(pin_salt,pin_hash), что совпадает с логикой из кода. - Из кода получаем PIN:
473029.
SecretCode
- Открываем
.\ab_extracted\apps\com.projectflower.agency\db\flowerline.dbв DB Browser for SQLite. - Выполняем SQL:
SELECT id, keyPartA, keyPartB FROM chat_threads;
SELECT chatId, bodyCipher, iv FROM messages WHERE senderId='T-4A1KD' AND isEncrypted=1;
- Для найденного
chatIdсобираем материал ключа:key_material = keyPartA + "::" + keyPartB + "::orchid/signal/v4"
- Расшифровываем
bodyCipher(AES-GCM, nonce =iv, AAD пустой) и получаем plaintext.
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
- В DB Browser находим удалённое сообщение через ссылку
tombstoneRef:
SELECT id, chatId, senderId, tombstoneRef
FROM messages
WHERE senderId='Petal' AND tombstoneRef IS NOT NULL;
- По найденному
tombstoneRefдостаём шифртекст:
SELECT id, messageId, cipherText, iv
FROM tombstones
WHERE id = '<tombstoneRef из шага 1>';
-
Достаём параметры для ключа:
fragmentиз.\apk_unpack\assets\protocol\kappa.cfgcursor_seedиз.\ab_extracted\apps\com.projectflower.agency\sp\pf_ops_seed.xml
-
Формируем ключ:
key = SHA256("PLF-omega-" + fragment + "kappa-19" + seed[:8])
-
Расшифровываем AES-GCM (
cipherText,iv, AAD пустой), получаем строку формата:messageId|chatId|original_body
-
Считаем SHA-256 только от
original_body.
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'<string name="cursor_seed">([^<]+)</string>', 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}