Init. commit

This commit is contained in:
Caplag
2026-03-02 21:44:22 +03:00
committed by Ivan Z
commit 9511b38280
38 changed files with 4397 additions and 0 deletions

View File

@@ -0,0 +1,247 @@
# 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}`