Files
2026-03-02 21:44:22 +03:00

248 lines
10 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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 = '<tombstoneRef из шага 1>';
```
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'<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}`