Init. commit
This commit is contained in:
247
WitheredFlower-forensic/README.md
Normal file
247
WitheredFlower-forensic/README.md
Normal 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}`
|
||||
Reference in New Issue
Block a user