Skip to content

Commit 8c8a7d8

Browse files
committed
Document streaming acquisition archives
1 parent fc9fe65 commit 8c8a7d8

2 files changed

Lines changed: 136 additions & 1 deletion

File tree

README.md

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ docker build --build-arg VERSION=1.8.3 -t androidqf .
9696
## How to use
9797

9898
> [!TIP]
99-
> We have not yet provided extensive official documentation of the output bundle androidqf produces. You might consider referring to the third-party androidqf documentation that has been developed by our friends at [SocialTIC](https://socialtic.org), in conjunction with their mobile forensics guide: [SocialTIC Forensics - AndroidQF output file dictionary](https://forensics.socialtic.org/en/references/01-reference-androidqf-dictionary/01-reference-androidqf-dictionary.html)
99+
> See [Acquisition archives](docs/acquisition-archives.md) for the archive format, integrity hashes, encryption and large-file handling. For a dictionary of collected files, see the third-party [SocialTIC AndroidQF output file dictionary](https://forensics.socialtic.org/en/references/01-reference-androidqf-dictionary/01-reference-androidqf-dictionary.html).
100100
101101
Before launching androidqf you need to have the target Android device connected to your computer via USB, and you will need to have enabled USB debugging. Please refer to the [official documentation](https://developer.android.com/studio/debug/dev-options#enable) on how to do this, but also be mindful that Android phones from different manufacturers might require different navigation steps than the defaults.
102102

@@ -122,6 +122,11 @@ The following data can be extracted:
122122
| A copy of the files available in temp folders. | | `tmp/*` |
123123
| A bug report containing system and app-specific logs, with no private data included. | | `bugreport.zip` |
124124

125+
Every acquisition also contains `acquisition.json`, `command.log` when log output
126+
was produced, and `hashes.csv`. The hash list records the SHA-256 digest of each
127+
preceding plaintext archive entry and does not include itself. Failed device
128+
pulls are not committed as archive entries. See [Acquisition
129+
archives](docs/acquisition-archives.md) for details.
125130

126131
### About optional data collection
127132

@@ -188,6 +193,12 @@ Alternatively, androidqf allows to encrypt each acquisition with a provided [age
188193

189194
androidqf streams each acquisition into a zip archive. If you place a file called `key.txt` in the current working directory, androidqf will encrypt the zip stream with age and write `<UUID>.zip.age`; otherwise, it writes an unencrypted `<UUID>.zip`. androidqf also checks for `key.txt` in the same folder as the executable; if both files exist, the current working directory takes precedence.
190195

196+
Encrypted acquisitions do not create a plaintext acquisition archive. Device
197+
files that must be validated before they are added to an encrypted archive are
198+
temporarily staged with authenticated ChaCha20-Poly1305 encryption. See
199+
[Acquisition archives](docs/acquisition-archives.md#encrypted-acquisitions) for
200+
the complete data flow and large-APK behavior.
201+
191202
Once you have retrieved an encrypted acquisition file, you can decrypt it with age like so:
192203

193204
```

docs/acquisition-archives.md

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
# Acquisition archives
2+
3+
androidqf writes each acquisition directly to a ZIP archive instead of first
4+
building an acquisition directory. This keeps storage use bounded and allows
5+
encryption to protect evidence from the moment it reaches the host.
6+
7+
## Output files
8+
9+
The `-output` option selects the output directory. If it is omitted, androidqf
10+
uses the current working directory. The output filename is based on the
11+
acquisition UUID:
12+
13+
| Configuration | Output |
14+
|---|---|
15+
| No `key.txt` found | `<UUID>.zip` |
16+
| Valid age recipient in `key.txt` | `<UUID>.zip.age` |
17+
18+
androidqf looks for `key.txt` in the current working directory and then beside
19+
the executable. A key in the current working directory takes precedence.
20+
21+
The archive contains the collected module outputs documented in the main
22+
[README](../README.md#how-to-use), plus these acquisition-level entries:
23+
24+
| Entry | Purpose |
25+
|---|---|
26+
| `acquisition.json` | Acquisition UUID, timestamps, androidqf version and device information. |
27+
| `command.log` | Debug-level command and acquisition log, when log output was produced. |
28+
| `hashes.csv` | SHA-256 integrity records for preceding plaintext archive entries. |
29+
30+
`hashes.csv` has two CSV fields per row: the ZIP entry name and its lowercase
31+
SHA-256 digest. The digest covers the plaintext, uncompressed entry content.
32+
The file does not contain a hash record for itself.
33+
34+
## Streaming and failed pulls
35+
36+
Module output is written to the archive as it is collected. Data that can be
37+
produced reliably as one stream, such as a backup or bug report, is streamed
38+
directly into its ZIP entry.
39+
40+
Files that may disappear or fail partway through an ADB pull are staged and
41+
validated before androidqf creates their ZIP entries. Consequently, a failed
42+
pull does not leave an empty or partial entry that appears valid in
43+
`hashes.csv`.
44+
45+
Archive completion writes `acquisition.json`, `command.log` and `hashes.csv`,
46+
then closes the ZIP and, when enabled, the age encryption stream. A failure in
47+
any of these operations is reported as a failed acquisition; androidqf does not
48+
print the acquisition-success message for an archive that could not be
49+
finalized.
50+
51+
## Encrypted acquisitions
52+
53+
When `key.txt` is present, the ZIP stream is passed directly through age into
54+
`<UUID>.zip.age`:
55+
56+
```text
57+
device data -> ZIP writer -> age encryption -> <UUID>.zip.age
58+
```
59+
60+
No plaintext ZIP archive or acquisition directory is created. To decrypt an
61+
acquisition:
62+
63+
```bash
64+
age --decrypt -i /path/to/privatekey.txt \
65+
-o <UUID>.zip <UUID>.zip.age
66+
```
67+
68+
The decrypted ZIP can then be opened with standard ZIP tooling.
69+
70+
### Validated encrypted staging
71+
72+
A ZIP entry cannot be removed after it has been created. For device files that
73+
must be completely pulled or inspected before entry creation, androidqf uses a
74+
temporary encrypted staging file during encrypted acquisitions.
75+
76+
Temporary staging uses MinIO SIO's DARE format with authenticated
77+
ChaCha20-Poly1305 encryption. A new random 256-bit key is generated for each
78+
staged file and retained only in process memory. DARE provides authenticated
79+
random access, allowing APK signature verification without writing a plaintext
80+
APK to disk or loading the entire APK into memory.
81+
82+
```text
83+
ADB pull
84+
-> ChaCha20-Poly1305 DARE temporary file
85+
-> authenticated seekable plaintext view in memory
86+
-> APK certificate verification
87+
-> final age-encrypted ZIP stream
88+
```
89+
90+
The staging file is deleted and its in-memory key is cleared after use. DARE is
91+
an internal temporary format only; the delivered acquisition remains a
92+
standard age-encrypted ZIP stream.
93+
94+
During an unencrypted acquisition, files requiring validation are staged in an
95+
ordinary temporary file before being committed to the ZIP. A process or host
96+
crash may leave such a plaintext temporary file behind. Use age encryption
97+
when plaintext-at-rest protection is required.
98+
99+
## APK handling
100+
101+
androidqf uses an in-memory buffer of up to 500 MB for APK processing. APKs
102+
within that limit are certificate-checked from memory for both encrypted and
103+
unencrypted acquisitions.
104+
105+
Larger APKs use seekable staging so certificate verification still occurs:
106+
107+
| Acquisition | APK larger than 500 MB |
108+
|---|---|
109+
| Encrypted | Authenticated ChaCha20-Poly1305 DARE staging; no plaintext temporary APK. |
110+
| Unencrypted | Plaintext temporary staging. |
111+
112+
The certificate metadata is recorded in `packages.json` in both cases. If the
113+
operator selects removal of APKs signed by a trusted certificate, that choice
114+
also applies to APKs larger than 500 MB. A trusted APK selected for removal is
115+
not added to the archive.
116+
117+
## Operational considerations
118+
119+
- Keep sufficient free space for the output archive and, when staging is
120+
required, one additional copy of the largest staged file.
121+
- Treat a finalization error as a failed acquisition. The output may be
122+
incomplete or unreadable and should not be treated as finalized evidence.
123+
- Preserve the age identity separately from the acquisition host when the
124+
operator should not be able to decrypt collected evidence under duress.

0 commit comments

Comments
 (0)