From 87b565a1931ac922dfc18a2a8a03871b6f52dc4b Mon Sep 17 00:00:00 2001
From: giulio <66009328+lsd-cat@users.noreply.github.com>
Date: Thu, 4 Sep 2025 03:04:43 +0200
Subject: [PATCH 1/3] Add screen to show per-scan detail
---
app/src/main/AndroidManifest.xml | 4 +
.../bugbane/ScanDetailActivity.kt | 76 +++++++++
.../screens/AcquisitionDetailScreen.kt | 21 ++-
.../bugbane/screens/ScanDetailScreen.kt | 157 ++++++++++++++++++
app/src/main/res/values-it/strings.xml | 11 ++
app/src/main/res/values/strings.xml | 11 ++
6 files changed, 278 insertions(+), 2 deletions(-)
create mode 100644 app/src/main/java/org/osservatorionessuno/bugbane/ScanDetailActivity.kt
create mode 100644 app/src/main/java/org/osservatorionessuno/bugbane/screens/ScanDetailScreen.kt
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index ffd7410..eb530c9 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -43,6 +43,10 @@
android:name=".AcquisitionActivity"
android:exported="false"
android:theme="@style/Theme.Theme" />
+
+ Box(
+ modifier = Modifier
+ .fillMaxSize()
+ .padding(padding)
+ ) {
+ ScanDetailScreen(acquisitionDir, scanFile)
+ }
+ }
+}
diff --git a/app/src/main/java/org/osservatorionessuno/bugbane/screens/AcquisitionDetailScreen.kt b/app/src/main/java/org/osservatorionessuno/bugbane/screens/AcquisitionDetailScreen.kt
index 1c773e4..cdada12 100644
--- a/app/src/main/java/org/osservatorionessuno/bugbane/screens/AcquisitionDetailScreen.kt
+++ b/app/src/main/java/org/osservatorionessuno/bugbane/screens/AcquisitionDetailScreen.kt
@@ -8,6 +8,7 @@ import android.content.res.Configuration
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.foundation.border
+import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
@@ -24,6 +25,7 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.osservatorionessuno.bugbane.analysis.AcquisitionScanner
+import org.osservatorionessuno.bugbane.ScanDetailActivity
import org.json.JSONObject
import java.io.File
import java.io.FileInputStream
@@ -221,6 +223,7 @@ fun AcquisitionDetailScreen(acquisitionDir: File) {
style = MaterialTheme.typography.bodyLarge.copy(fontWeight = FontWeight.Medium)
)
ScanList(
+ acquisitionDir = acquisitionDir,
scans = scans,
dateFormat = dateFormat,
modifier = Modifier
@@ -281,6 +284,7 @@ fun AcquisitionDetailScreen(acquisitionDir: File) {
style = MaterialTheme.typography.bodyLarge.copy(fontWeight = FontWeight.Medium)
)
ScanList(
+ acquisitionDir = acquisitionDir,
scans = scans,
dateFormat = dateFormat,
modifier = Modifier
@@ -363,10 +367,12 @@ fun AcquisitionDetailScreen(acquisitionDir: File) {
@Composable
private fun ScanList(
+ acquisitionDir: File,
scans: List,
dateFormat: DateFormat,
modifier: Modifier = Modifier
) {
+ val context = LocalContext.current
Box(
modifier = modifier
.border(1.dp, MaterialTheme.colorScheme.outline)
@@ -392,7 +398,17 @@ private fun ScanList(
}
}
items(scans) { scan ->
- Row(modifier = Modifier.fillMaxWidth()) {
+ Row(
+ modifier = Modifier
+ .fillMaxWidth()
+ .clickable {
+ val intent = Intent(context, ScanDetailActivity::class.java).apply {
+ putExtra(ScanDetailActivity.EXTRA_ACQUISITION_PATH, acquisitionDir.absolutePath)
+ putExtra(ScanDetailActivity.EXTRA_SCAN_PATH, scan.file.absolutePath)
+ }
+ context.startActivity(intent)
+ }
+ ) {
Text(
dateFormat.format(Date.from(scan.started)),
modifier = Modifier.weight(1f)
@@ -412,6 +428,7 @@ private fun ScanList(
}
private data class ScanSummary(
+ val file: File,
val started: Instant,
val indicatorsHash: String,
val matchCount: Int,
@@ -436,7 +453,7 @@ private fun loadScans(acquisitionDir: File): List {
sha256(hashes.joinToString(""))
}
val results = obj.optJSONArray("results")?.length() ?: 0
- ScanSummary(started, hash, results)
+ ScanSummary(file, started, hash, results)
} catch (_: Exception) {
null
}
diff --git a/app/src/main/java/org/osservatorionessuno/bugbane/screens/ScanDetailScreen.kt b/app/src/main/java/org/osservatorionessuno/bugbane/screens/ScanDetailScreen.kt
new file mode 100644
index 0000000..721045c
--- /dev/null
+++ b/app/src/main/java/org/osservatorionessuno/bugbane/screens/ScanDetailScreen.kt
@@ -0,0 +1,157 @@
+package org.osservatorionessuno.bugbane.screens
+
+import androidx.compose.foundation.border
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.layout.*
+import androidx.compose.foundation.lazy.LazyColumn
+import androidx.compose.foundation.lazy.items
+import androidx.compose.material3.*
+import androidx.compose.runtime.*
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.text.style.TextOverflow
+import androidx.compose.ui.unit.dp
+import org.json.JSONObject
+import java.io.File
+import java.text.DateFormat
+import java.time.Instant
+import java.util.Date
+import org.osservatorionessuno.bugbane.R
+
+@Composable
+fun ScanDetailScreen(acquisitionDir: File, scanFile: File) {
+ val dateFormat = remember { DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.SHORT) }
+ var acquisitionMeta by remember { mutableStateOf(null) }
+ var scanMeta by remember { mutableStateOf(null) }
+ var results by remember { mutableStateOf(listOf()) }
+ var selected by remember { mutableStateOf(null) }
+
+ LaunchedEffect(acquisitionDir, scanFile) {
+ val metaFile = File(acquisitionDir, "acquisition.json")
+ if (metaFile.exists()) {
+ try { acquisitionMeta = JSONObject(metaFile.readText()) } catch (_: Throwable) {}
+ }
+ try {
+ val obj = JSONObject(scanFile.readText())
+ scanMeta = obj
+ val arr = obj.optJSONArray("results")
+ val tmp = mutableListOf()
+ if (arr != null) {
+ for (i in 0 until arr.length()) {
+ val o = arr.getJSONObject(i)
+ tmp += ScanResult(
+ o.optString("file"),
+ o.optString("type"),
+ o.optString("ioc"),
+ o.optString("context")
+ )
+ }
+ }
+ results = tmp
+ } catch (_: Exception) {
+ }
+ }
+
+ Column(
+ modifier = Modifier
+ .fillMaxSize()
+ .padding(16.dp),
+ verticalArrangement = Arrangement.spacedBy(12.dp)
+ ) {
+ Text(
+ stringResource(R.string.scan_details_acquisition_name, acquisitionDir.name),
+ style = MaterialTheme.typography.bodyLarge
+ )
+ acquisitionMeta?.let {
+ val uuid = it.optString("uuid")
+ val completed = it.optString("completed").let { s ->
+ try { dateFormat.format(Date.from(Instant.parse(s))) } catch (_: Exception) { s }
+ }
+ Text(
+ stringResource(R.string.scan_details_acquisition_uuid, uuid),
+ style = MaterialTheme.typography.bodyLarge
+ )
+ Text(
+ stringResource(R.string.scan_details_acquisition_completed, completed),
+ style = MaterialTheme.typography.bodyLarge
+ )
+ }
+ scanMeta?.let {
+ val completed = it.optString("completed").let { s ->
+ try { dateFormat.format(Date.from(Instant.parse(s))) } catch (_: Exception) { s }
+ }
+ Text(
+ stringResource(R.string.scan_details_analysis_completed, completed),
+ style = MaterialTheme.typography.bodyLarge
+ )
+ }
+ ResultList(results = results, onSelect = { selected = it }, modifier = Modifier.weight(1f))
+ }
+
+ selected?.let { res ->
+ AlertDialog(
+ onDismissRequest = { selected = null },
+ confirmButton = {
+ TextButton(onClick = { selected = null }) {
+ Text(stringResource(R.string.acquisition_passphrase_close))
+ }
+ },
+ title = { Text(stringResource(R.string.scan_details_context_dialog_title)) },
+ text = { Text(res.context) }
+ )
+ }
+}
+
+data class ScanResult(
+ val file: String,
+ val type: String,
+ val ioc: String,
+ val context: String,
+)
+
+@Composable
+private fun ResultList(
+ results: List,
+ onSelect: (ScanResult) -> Unit,
+ modifier: Modifier = Modifier
+) {
+ Box(
+ modifier = modifier
+ .border(1.dp, MaterialTheme.colorScheme.outline)
+ .fillMaxSize()
+ ) {
+ LazyColumn(modifier = Modifier.fillMaxSize().padding(8.dp)) {
+ item {
+ Row(modifier = Modifier.fillMaxWidth()) {
+ Text(
+ stringResource(R.string.scan_details_artifact),
+ modifier = Modifier.weight(1f),
+ fontWeight = FontWeight.SemiBold
+ )
+ Text(
+ stringResource(R.string.scan_details_type),
+ modifier = Modifier.weight(1f),
+ fontWeight = FontWeight.SemiBold
+ )
+ Text(
+ stringResource(R.string.scan_details_ioc),
+ modifier = Modifier.weight(1f),
+ fontWeight = FontWeight.SemiBold
+ )
+ }
+ }
+ items(results) { r ->
+ Row(
+ modifier = Modifier
+ .fillMaxWidth()
+ .clickable { onSelect(r) }
+ ) {
+ Text(r.file, modifier = Modifier.weight(1f), maxLines = 1, overflow = TextOverflow.Ellipsis)
+ Text(r.type, modifier = Modifier.weight(1f), maxLines = 1, overflow = TextOverflow.Ellipsis)
+ Text(r.ioc, modifier = Modifier.weight(1f), maxLines = 1, overflow = TextOverflow.Ellipsis)
+ }
+ }
+ }
+ }
+}
diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml
index 14f607d..68a9927 100644
--- a/app/src/main/res/values-it/strings.xml
+++ b/app/src/main/res/values-it/strings.xml
@@ -75,4 +75,15 @@
Archivio di acquisizione. Passphrase: %1$s
Copia nella clipboard
Chiudi
+
+
+ Dettagli analisi
+ Artefatto
+ Tipo
+ IOC
+ Contesto
+ Nome acquisizione: %1$s
+ UUID acquisizione: %1$s
+ Acquisizione completata: %1$s
+ Analisi completata: %1$s
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index db49733..813e419 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -106,4 +106,15 @@
Acquisition archive. Passphrase: %1$s
Copy to clipboard
Close
+
+
+ Scan Details
+ Artifact
+ Type
+ IOC
+ Context
+ Acquisition name: %1$s
+ Acquisition UUID: %1$s
+ Acquisition completed: %1$s
+ Analysis completed: %1$s
From cb14efb520845734803790c069fdfc498a943cb9 Mon Sep 17 00:00:00 2001
From: giulio <66009328+lsd-cat@users.noreply.github.com>
Date: Thu, 4 Sep 2025 04:19:14 +0200
Subject: [PATCH 2/3] Make nomenclature consistent (scan->analysis)
---
...lActivity.kt => AnalysisDetailActivity.kt} | 2 +-
.../screens/AcquisitionDetailScreen.kt | 10 +++----
...etailScreen.kt => AnalysisDetailScreen.kt} | 16 +++++-----
app/src/main/res/values-it/strings.xml | 26 ++++++++--------
app/src/main/res/values/strings.xml | 30 +++++++++----------
5 files changed, 42 insertions(+), 42 deletions(-)
rename app/src/main/java/org/osservatorionessuno/bugbane/{ScanDetailActivity.kt => AnalysisDetailActivity.kt} (96%)
rename app/src/main/java/org/osservatorionessuno/bugbane/screens/{ScanDetailScreen.kt => AnalysisDetailScreen.kt} (88%)
diff --git a/app/src/main/java/org/osservatorionessuno/bugbane/ScanDetailActivity.kt b/app/src/main/java/org/osservatorionessuno/bugbane/AnalysisDetailActivity.kt
similarity index 96%
rename from app/src/main/java/org/osservatorionessuno/bugbane/ScanDetailActivity.kt
rename to app/src/main/java/org/osservatorionessuno/bugbane/AnalysisDetailActivity.kt
index 8995840..0e0c3c5 100644
--- a/app/src/main/java/org/osservatorionessuno/bugbane/ScanDetailActivity.kt
+++ b/app/src/main/java/org/osservatorionessuno/bugbane/AnalysisDetailActivity.kt
@@ -48,7 +48,7 @@ fun ScanDetailContent(acquisitionDir: File, scanFile: File) {
Scaffold(
topBar = {
TopAppBar(
- title = { Text(stringResource(R.string.scan_details_title)) },
+ title = { Text(stringResource(R.string.analysis_details_title)) },
navigationIcon = {
IconButton(onClick = { (context as? ComponentActivity)?.finish() }) {
Icon(
diff --git a/app/src/main/java/org/osservatorionessuno/bugbane/screens/AcquisitionDetailScreen.kt b/app/src/main/java/org/osservatorionessuno/bugbane/screens/AcquisitionDetailScreen.kt
index cdada12..11393ed 100644
--- a/app/src/main/java/org/osservatorionessuno/bugbane/screens/AcquisitionDetailScreen.kt
+++ b/app/src/main/java/org/osservatorionessuno/bugbane/screens/AcquisitionDetailScreen.kt
@@ -219,7 +219,7 @@ fun AcquisitionDetailScreen(acquisitionDir: File) {
verticalArrangement = Arrangement.spacedBy(12.dp)
) {
Text(
- text = stringResource(org.osservatorionessuno.bugbane.R.string.acquisition_details_scans),
+ text = stringResource(org.osservatorionessuno.bugbane.R.string.acquisition_details_analyses),
style = MaterialTheme.typography.bodyLarge.copy(fontWeight = FontWeight.Medium)
)
ScanList(
@@ -280,7 +280,7 @@ fun AcquisitionDetailScreen(acquisitionDir: File) {
Text(stringResource(org.osservatorionessuno.bugbane.R.string.acquisition_details_rescan))
}
Text(
- text = stringResource(org.osservatorionessuno.bugbane.R.string.acquisition_details_scans),
+ text = stringResource(org.osservatorionessuno.bugbane.R.string.acquisition_details_analyses),
style = MaterialTheme.typography.bodyLarge.copy(fontWeight = FontWeight.Medium)
)
ScanList(
@@ -381,17 +381,17 @@ private fun ScanList(
item {
Row(modifier = Modifier.fillMaxWidth()) {
Text(
- stringResource(org.osservatorionessuno.bugbane.R.string.acquisition_scans_date),
+ stringResource(org.osservatorionessuno.bugbane.R.string.acquisition_analyses_date),
modifier = Modifier.weight(1f),
fontWeight = FontWeight.SemiBold
)
Text(
- stringResource(org.osservatorionessuno.bugbane.R.string.acquisition_scans_indicators),
+ stringResource(org.osservatorionessuno.bugbane.R.string.acquisition_analyses_indicators),
modifier = Modifier.weight(1f),
fontWeight = FontWeight.SemiBold
)
Text(
- stringResource(org.osservatorionessuno.bugbane.R.string.acquisition_scans_matches),
+ stringResource(org.osservatorionessuno.bugbane.R.string.acquisition_analyses_matches),
modifier = Modifier.weight(1f),
fontWeight = FontWeight.SemiBold
)
diff --git a/app/src/main/java/org/osservatorionessuno/bugbane/screens/ScanDetailScreen.kt b/app/src/main/java/org/osservatorionessuno/bugbane/screens/AnalysisDetailScreen.kt
similarity index 88%
rename from app/src/main/java/org/osservatorionessuno/bugbane/screens/ScanDetailScreen.kt
rename to app/src/main/java/org/osservatorionessuno/bugbane/screens/AnalysisDetailScreen.kt
index 721045c..1cea0ec 100644
--- a/app/src/main/java/org/osservatorionessuno/bugbane/screens/ScanDetailScreen.kt
+++ b/app/src/main/java/org/osservatorionessuno/bugbane/screens/AnalysisDetailScreen.kt
@@ -60,7 +60,7 @@ fun ScanDetailScreen(acquisitionDir: File, scanFile: File) {
verticalArrangement = Arrangement.spacedBy(12.dp)
) {
Text(
- stringResource(R.string.scan_details_acquisition_name, acquisitionDir.name),
+ stringResource(R.string.analysis_details_acquisition_name, acquisitionDir.name),
style = MaterialTheme.typography.bodyLarge
)
acquisitionMeta?.let {
@@ -69,11 +69,11 @@ fun ScanDetailScreen(acquisitionDir: File, scanFile: File) {
try { dateFormat.format(Date.from(Instant.parse(s))) } catch (_: Exception) { s }
}
Text(
- stringResource(R.string.scan_details_acquisition_uuid, uuid),
+ stringResource(R.string.analysis_details_acquisition_uuid, uuid),
style = MaterialTheme.typography.bodyLarge
)
Text(
- stringResource(R.string.scan_details_acquisition_completed, completed),
+ stringResource(R.string.analysis_details_acquisition_completed, completed),
style = MaterialTheme.typography.bodyLarge
)
}
@@ -82,7 +82,7 @@ fun ScanDetailScreen(acquisitionDir: File, scanFile: File) {
try { dateFormat.format(Date.from(Instant.parse(s))) } catch (_: Exception) { s }
}
Text(
- stringResource(R.string.scan_details_analysis_completed, completed),
+ stringResource(R.string.analysis_details_analysis_completed, completed),
style = MaterialTheme.typography.bodyLarge
)
}
@@ -97,7 +97,7 @@ fun ScanDetailScreen(acquisitionDir: File, scanFile: File) {
Text(stringResource(R.string.acquisition_passphrase_close))
}
},
- title = { Text(stringResource(R.string.scan_details_context_dialog_title)) },
+ title = { Text(stringResource(R.string.analysis_details_context_dialog_title)) },
text = { Text(res.context) }
)
}
@@ -125,17 +125,17 @@ private fun ResultList(
item {
Row(modifier = Modifier.fillMaxWidth()) {
Text(
- stringResource(R.string.scan_details_artifact),
+ stringResource(R.string.analysis_details_artifact),
modifier = Modifier.weight(1f),
fontWeight = FontWeight.SemiBold
)
Text(
- stringResource(R.string.scan_details_type),
+ stringResource(R.string.analysis_details_type),
modifier = Modifier.weight(1f),
fontWeight = FontWeight.SemiBold
)
Text(
- stringResource(R.string.scan_details_ioc),
+ stringResource(R.string.analysis_details_ioc),
modifier = Modifier.weight(1f),
fontWeight = FontWeight.SemiBold
)
diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml
index 68a9927..81449c2 100644
--- a/app/src/main/res/values-it/strings.xml
+++ b/app/src/main/res/values-it/strings.xml
@@ -64,10 +64,10 @@
Completato: %1$s
File
Visualizza i file
- Analisi
- Data
- Indicatori
- Corrispondenza
+ Analisi
+ Data
+ Indicatori
+ Corrispondenza
Esporta
Condividi
Esegui analisi
@@ -77,13 +77,13 @@
Chiudi
- Dettagli analisi
- Artefatto
- Tipo
- IOC
- Contesto
- Nome acquisizione: %1$s
- UUID acquisizione: %1$s
- Acquisizione completata: %1$s
- Analisi completata: %1$s
+ Dettagli analisi
+ Artefatto
+ Tipo
+ IOC
+ Contesto
+ Nome acquisizione: %1$s
+ UUID acquisizione: %1$s
+ Acquisizione completata: %1$s
+ Analisi completata: %1$s
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 813e419..1b08cab 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -44,7 +44,7 @@
Start Acquisition
Acquiring…
Enable Permissions
- After each acquisition, it’s recommended to disable Developer Options until the next session.
+ After each acquisition, it's recommended to disable Developer Options until the next session.
Disable
Close
Stop Acquisition
@@ -53,7 +53,7 @@
Configure your preferences and app settings here.
General Settings
Disable Developer Options
- It’s recommended to disable Developer Options after each session and re-enable them only when needed.
+ It's recommended to disable Developer Options after each session and re-enable them only when needed.
Indicators
Last fetch: %1$s
Last update: %1$s
@@ -93,10 +93,10 @@
Completed: %1$s
Files
View files
- Analyses
- Date
- Indicators
- Matches
+ Analyses
+ Date
+ Indicators
+ Matches
Export
Share
Run Analysis
@@ -108,13 +108,13 @@
Close
- Scan Details
- Artifact
- Type
- IOC
- Context
- Acquisition name: %1$s
- Acquisition UUID: %1$s
- Acquisition completed: %1$s
- Analysis completed: %1$s
+ Analysis Details
+ Artifact
+ Type
+ IOC
+ Context
+ Acquisition name: %1$s
+ Acquisition UUID: %1$s
+ Acquisition completed: %1$s
+ Analysis completed: %1$s
From 0b31b00b0c0e06093a34f3bb4bb7db10d5fc8c80 Mon Sep 17 00:00:00 2001
From: giulio <66009328+lsd-cat@users.noreply.github.com>
Date: Thu, 4 Sep 2025 04:30:00 +0200
Subject: [PATCH 3/3] Fix quote escape in strings.xml
---
app/src/main/res/values/strings.xml | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 1b08cab..d37981b 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -44,7 +44,7 @@
Start Acquisition
Acquiring…
Enable Permissions
- After each acquisition, it's recommended to disable Developer Options until the next session.
+ After each acquisition, it\'s recommended to disable Developer Options until the next session.
Disable
Close
Stop Acquisition
@@ -53,7 +53,7 @@
Configure your preferences and app settings here.
General Settings
Disable Developer Options
- It's recommended to disable Developer Options after each session and re-enable them only when needed.
+ It\'s recommended to disable Developer Options after each session and re-enable them only when needed.
Indicators
Last fetch: %1$s
Last update: %1$s