Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions apksigner-pkcs11.sh
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
# - https://developers.yubico.com/PIV/Guides/Android_code_signing.html
# - https://geoffreymetais.github.io/code/key-signing/
# - https://medium.com/swlh/android-app-signing-with-a-yubikey-fdfc3d730965
# for macOS: brew install opensc pkcs11-helper

# Environment variables that can be overridden:
# - ANDROID_HOME -> the path to the Android SDK (by default $HOME/Library/Android/sdk or $HOME/Android/Sdk will be used)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ import org.osservatorionessuno.qf.AcquisitionRunner
import java.io.File

private const val TAG = "ScanScreen"
private const val BETA_COUNTDOWN_SECONDS = 10

@Composable
fun ScanScreen() {
val coroutineScope = rememberCoroutineScope()
Expand All @@ -60,7 +62,7 @@ fun ScanScreen() {

val isBetaVersion = context.packageName.contains("beta", ignoreCase = true)
var showBetaWarningDialog by remember { mutableStateOf(false) }
var betaCountdown by remember { mutableStateOf(10) }
var betaCountdown by remember { mutableStateOf(BETA_COUNTDOWN_SECONDS) }
var betaButtonEnabled by remember { mutableStateOf(false) }

val isLandscape = LocalConfiguration.current.orientation == Configuration.ORIENTATION_LANDSCAPE
Expand Down Expand Up @@ -338,8 +340,6 @@ fun ScanScreen() {
AppState.AdbConnected -> {
if (isBetaVersion) {
showBetaWarningDialog = true
betaCountdown = 10
betaButtonEnabled = false
return@Button
}
startAcquisition()
Expand Down Expand Up @@ -395,20 +395,16 @@ fun ScanScreen() {
if (showBetaWarningDialog) {
LaunchedEffect(showBetaWarningDialog) {
if (showBetaWarningDialog) {
betaCountdown = 10
betaCountdown = BETA_COUNTDOWN_SECONDS
betaButtonEnabled = false
for (i in 10 downTo 1) {
for (i in betaCountdown.toInt() downTo 1) {
delay(1000)
betaCountdown = i - 1
}
betaButtonEnabled = true
}
}

LaunchedEffect(betaCountdown) {
// This ensures recomposition happens when countdown changes
}


AlertDialog(
onDismissRequest = { showBetaWarningDialog = false },
title = {
Expand Down
34 changes: 27 additions & 7 deletions app/src/main/java/org/osservatorionessuno/cadb/AdbSync.kt
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package org.osservatorionessuno.cadb

import android.util.Log
import io.github.muntashirakon.adb.LocalServices
import java.io.EOFException
import java.io.File
Expand All @@ -18,6 +19,14 @@ class AdbSync(
private val manager: AdbConnectionManager,
private val progress: ((Long) -> Unit)? = null,
) {
private companion object {
private const val TAG = "AdbSync"
// Unix file type bits (st_mode & 0xF000)
private const val S_IFMT = 0xF000
private const val S_IFDIR = 0x4000
private const val S_IFREG = 0x8000
}

/**
* Pull a remote file to a local destination.
*
Expand Down Expand Up @@ -99,7 +108,9 @@ class AdbSync(
var remaining = length
while (remaining > 0) {
val read = input.read(buffer, off, remaining)
if (read < 0) throw EOFException()
if (read < 0) {
throw EOFException("EOF while reading from input stream: $remaining bytes remaining")
}
off += read
remaining -= read
}
Expand Down Expand Up @@ -213,12 +224,21 @@ class AdbSync(
val localPath = File(localDest, path)
localPath.parentFile?.mkdirs()

if (mode and 0x4000 != 0) {
pullFolder(remoteDir + path, localPath)
} else {
pull(remoteDir + path, localPath)
// set last modified time accordingly
localPath.setLastModified(mtime.toLong() * 1000)
val type = mode and S_IFMT
when (type) {
S_IFDIR -> {
pullFolder(remoteDir + path + "/", localPath)
}
S_IFREG -> {
pull(remoteDir + path, localPath)
// set last modified time accordingly
localPath.setLastModified(mtime.toLong() * 1000)
}
else -> {
// Skip non-regular files (symlinks, sockets, fifos, device nodes).
// Some pseudo-files (e.g. under /proc, /sys) may never reach EOF when read.
Log.d(TAG, "Skipping non-regular entry: ${remoteDir}${path} (mode=${Integer.toHexString(mode)})")
}
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,7 @@ class AcquisitionRunner(
Log.i(TAG, "Module ${module.name} finished")
} catch (t: Throwable) {
Log.e(TAG, "Module ${module.name} failed", t)
// TODO: display error message to the user
}
completedCount++
listener?.onModuleComplete(module.name, completedCount, total)
Expand Down
6 changes: 5 additions & 1 deletion app/src/main/java/org/osservatorionessuno/qf/modules/Logs.kt
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ class Logs : Module {
val dest = File(outDir, "logs")
dest.mkdirs()

runCatching {
val result = runCatching {
for (target in targets) {
if (target.endsWith("/")) {
sync.pullFolder(target, dest)
Expand All @@ -46,5 +46,9 @@ class Logs : Module {
}
Log.i(TAG, "Pulled logs to: ${dest.absolutePath}")
}
if (result.isFailure) {
// TODO: write this feedback to the acquisition report in some way
Log.e(TAG, "Failed to pull logs", result.exceptionOrNull())
}
}
}
70 changes: 66 additions & 4 deletions app/src/main/java/org/osservatorionessuno/qf/modules/Packages.kt
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
package org.osservatorionessuno.qf.modules

import android.content.Context
import android.util.Log
import org.osservatorionessuno.qf.Module
import org.osservatorionessuno.qf.Utils
import org.osservatorionessuno.cadb.AdbShell
import org.osservatorionessuno.cadb.AdbSync
import org.osservatorionessuno.cadb.AdbConnectionManager
import org.osservatorionessuno.libmvt.android.parsers.APKParser
import org.osservatorionessuno.libmvt.android.parsers.CertificateParser

import java.io.File
import org.json.JSONArray
import org.json.JSONObject
Expand All @@ -15,6 +20,7 @@ import org.json.JSONObject
*/
class Packages : Module {
override val name: String = "packages"
private val TAG = "PackagesModule"

// Data class to hold package info
data class Package(
Expand All @@ -34,8 +40,24 @@ class Packages : Module {
var sha1: String,
var sha256: String,
var sha512: String,
// TODO: implement certificate validation checks
var suspicious: Boolean,
var certificates: List<CertificateParser.CertificateInfo>,
var infiles: List<String>
) {
private fun certToJsonObject(c: CertificateParser.CertificateInfo): JSONObject {
return JSONObject().apply {
put("Md5", c.checksums.md5)
put("Sha1", c.checksums.sha1)
put("Sha256", c.checksums.sha256)
put("ValidFrom", c.notBefore)
put("ValidTo", c.notAfter)
put("Issuer", c.issuer)
put("Subject", c.subject)
put("SignatureAlgorithm", c.algorithm)
put("SerialNumber", c.serialNumber)
}
}

fun toJsonObject(): JSONObject {
return JSONObject().apply {
put("path", path)
Expand All @@ -44,6 +66,9 @@ class Packages : Module {
put("sha1", sha1)
put("sha256", sha256)
put("sha512", sha512)
put("suspicious", suspicious)
put("certificates", JSONArray().apply { certificates.forEach { put(certToJsonObject(it)) } })
put("infiles", infiles)
}
}

Expand All @@ -68,7 +93,20 @@ class Packages : Module {
return Triple(packageName, installer, uid)
}

fun getPackageFiles(shell: AdbShell, packageName: String): List<PackageFile> {
fun getPathToLocalCopy(apksPath: String, packageName: String, filePath: String): String {
val fileName = APKParser.extractFileName(filePath)

var localPath = File(apksPath, "${packageName}${fileName}.apk")
var counter = 0

while (localPath.exists()) {
counter++
localPath = File(apksPath, "${packageName}${fileName}_$counter.apk")
}
return localPath.absolutePath
}

fun getPackageFiles(shell: AdbShell, sync: AdbSync, apksDir: File, packageName: String): List<PackageFile> {
val files = mutableListOf<PackageFile>()
val output = try {
shell.exec("pm path $packageName")
Expand All @@ -87,6 +125,9 @@ class Packages : Module {
sha1 = "",
sha256 = "",
sha512 = "",
suspicious = false,
certificates = emptyList(),
infiles = emptyList(),
)

runCatching {
Expand All @@ -106,6 +147,23 @@ class Packages : Module {
packageFile.sha512 = sha512Out.trim().split(Regex("\\s+"), 2).getOrElse(0) { "" }
}

val apkInfo = APKParser.parseAPK(File(packagePath))
packageFile.suspicious = apkInfo.suspicious
packageFile.certificates = apkInfo.certificates
packageFile.infiles = apkInfo.files

if (packageFile.suspicious) {
val localPath = getPathToLocalCopy(apksDir.absolutePath, packageName, packageFile.path)
Log.i(TAG, "copying $packagePath to $localPath")
val result = runCatching {
sync.pull(packagePath, File(localPath))
}
if (result.isFailure) {
// TODO: write this feedback to the acquisition report in some way
Log.e(TAG, "Failed to copy $packagePath to $localPath", result.exceptionOrNull())
}
}

files.add(packageFile)
}
return files
Expand All @@ -118,6 +176,7 @@ class Packages : Module {
progress: ((Long) -> Unit)?
) {
val shell = AdbShell(manager, progress = progress)
val sync = AdbSync(manager, progress = progress)

var withInstaller = true

Expand Down Expand Up @@ -146,6 +205,9 @@ class Packages : Module {
}
}

// Create the apks directory
val apksDir = File(outDir, "apks")
apksDir.mkdirs()
val packages = mutableListOf<Package>()

output.lineSequence().forEach { line ->
Expand All @@ -159,7 +221,7 @@ class Packages : Module {
name = packageName,
installer = installer,
uid = uid,
files = getPackageFiles(shell, packageName)
files = getPackageFiles(shell, sync, apksDir, packageName)
)
)
}
Expand Down Expand Up @@ -208,7 +270,7 @@ class Packages : Module {
put("uid", pkg.uid)
put("disabled", pkg.disabled)
put("system", pkg.system)
put("thirdParty", pkg.thirdParty)
put("third_party", pkg.thirdParty)
}
jsonArray.put(obj)
}
Expand Down
8 changes: 5 additions & 3 deletions app/src/main/java/org/osservatorionessuno/qf/modules/Temp.kt
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,13 @@ class Temp : Module {
val dest = File(outDir, "tmp")
dest.mkdirs()

try {
val result = runCatching {
sync.pullFolder("/data/local/tmp/", dest)
Log.i(TAG, "Pulled temp to: ${dest.absolutePath}")
} catch (e: Exception) {
Log.e(TAG, "Failed to pull temp", e)
}
if (result.isFailure) {
// TODO: write this feedback to the acquisition report in some way
Log.e(TAG, "Failed to pull temp", result.exceptionOrNull())
}
}
}
Loading