Skip to content

Commit

Permalink
Retrieve security logs
Browse files Browse the repository at this point in the history
  • Loading branch information
BinTianqi committed Sep 1, 2024
1 parent 03a4242 commit 4799a15
Show file tree
Hide file tree
Showing 8 changed files with 148 additions and 57 deletions.
10 changes: 9 additions & 1 deletion app/src/main/java/com/bintianqi/owndroid/Receiver.kt
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import android.widget.Toast
import com.bintianqi.owndroid.dpm.getDPM
import com.bintianqi.owndroid.dpm.getReceiver
import com.bintianqi.owndroid.dpm.handleNetworkLogs
import com.bintianqi.owndroid.dpm.handleSecurityLogs
import com.bintianqi.owndroid.dpm.isDeviceAdmin
import com.bintianqi.owndroid.dpm.isDeviceOwner
import com.bintianqi.owndroid.dpm.isProfileOwner
Expand Down Expand Up @@ -54,12 +55,19 @@ class Receiver : DeviceAdminReceiver() {

override fun onNetworkLogsAvailable(context: Context, intent: Intent, batchToken: Long, networkLogsCount: Int) {
super.onNetworkLogsAvailable(context, intent, batchToken, networkLogsCount)
if(VERSION.SDK_INT >= 28) {
if(VERSION.SDK_INT >= 26) {
CoroutineScope(Dispatchers.IO).launch {
handleNetworkLogs(context, batchToken)
}
}
}

override fun onSecurityLogsAvailable(context: Context, intent: Intent) {
super.onSecurityLogsAvailable(context, intent)
if(VERSION.SDK_INT >= 24) {
handleSecurityLogs(context)
}
}
}

val installAppDone = MutableStateFlow(false)
Expand Down
18 changes: 11 additions & 7 deletions app/src/main/java/com/bintianqi/owndroid/Utils.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,11 @@ package com.bintianqi.owndroid

import android.Manifest
import android.app.admin.DevicePolicyManager
import android.content.*
import android.content.ClipData
import android.content.ClipboardManager
import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import android.net.Uri
import android.os.Build.VERSION
Expand All @@ -17,7 +21,6 @@ import java.io.File
import java.io.FileNotFoundException
import java.io.IOException
import java.io.InputStream
import java.nio.file.Files
import java.util.Locale

lateinit var getFile: ActivityResultLauncher<Intent>
Expand Down Expand Up @@ -76,12 +79,13 @@ fun writeClipBoard(context: Context, string: String):Boolean{
}

lateinit var requestPermission: ActivityResultLauncher<String>
lateinit var saveNetworkLogs: ActivityResultLauncher<Intent>
lateinit var exportFile: ActivityResultLauncher<Intent>
val exportFilePath = MutableStateFlow<String?>(null)

fun registerActivityResult(context: ComponentActivity){
getFile = context.registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { activityResult ->
activityResult.data.let {
if(it==null){
if(it == null){
Toast.makeText(context.applicationContext, R.string.file_not_exist, Toast.LENGTH_SHORT).show()
}else{
fileUriFlow.value = it.data
Expand All @@ -96,13 +100,13 @@ fun registerActivityResult(context: ComponentActivity){
}
}
requestPermission = context.registerForActivityResult(ActivityResultContracts.RequestPermission()) { permissionGranted.value = it }
saveNetworkLogs = context.registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
exportFile = context.registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
val intentData = result.data ?: return@registerForActivityResult
val uriData = intentData.data ?: return@registerForActivityResult
val path = exportFilePath.value ?: return@registerForActivityResult
context.contentResolver.openOutputStream(uriData).use { outStream ->
if(outStream != null) {
val logFile = context.filesDir.resolve("NetworkLogs.json")
logFile.inputStream().use { inStream ->
File(path).inputStream().use { inStream ->
inStream.copyTo(outStream)
}
Toast.makeText(context.applicationContext, R.string.success, Toast.LENGTH_SHORT).show()
Expand Down
55 changes: 43 additions & 12 deletions app/src/main/java/com/bintianqi/owndroid/dpm/DPM.kt
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ import android.content.Intent
import android.content.pm.IPackageInstaller
import android.content.pm.PackageInstaller
import android.content.pm.PackageManager
import android.os.Build
import android.os.Build.VERSION
import androidx.activity.result.ActivityResultLauncher
import androidx.annotation.DrawableRes
Expand Down Expand Up @@ -320,41 +319,39 @@ fun permissionList(): List<PermissionItem>{
return list
}

@RequiresApi(Build.VERSION_CODES.O)
@RequiresApi(26)
@OptIn(ExperimentalSerializationApi::class)
fun handleNetworkLogs(context: Context, batchToken: Long) {
val events = context.getDPM().retrieveNetworkLogs(context.getReceiver(), batchToken) ?: return
val eventsList = mutableListOf<NetworkEventItem>()
val networkEvents = context.getDPM().retrieveNetworkLogs(context.getReceiver(), batchToken) ?: return
val file = context.filesDir.toPath().resolve("NetworkLogs.json")
if(file.notExists()) file.writeText("[]")
val json = Json { ignoreUnknownKeys = true; explicitNulls = false }
var jsonObj: MutableList<NetworkEventItem>
var events: MutableList<NetworkEventItem>
file.inputStream().use {
jsonObj = json.decodeFromStream(it)
events = json.decodeFromStream(it)
}
events.forEach { event ->
networkEvents.forEach { event ->
try {
val dnsEvent = event as DnsEvent
val addresses = mutableListOf<String?>()
dnsEvent.inetAddresses.forEach { inetAddresses ->
addresses += inetAddresses.hostAddress
}
eventsList += NetworkEventItem(
events += NetworkEventItem(
id = if(VERSION.SDK_INT >= 28) event.id else null, packageName = event.packageName
, timestamp = event.timestamp, type = "dns", hostName = dnsEvent.hostname,
hostAddresses = addresses, totalResolvedAddressCount = dnsEvent.totalResolvedAddressCount
)
} catch(e: Exception) {
val connectEvent = event as ConnectEvent
eventsList += NetworkEventItem(
events += NetworkEventItem(
id = if(VERSION.SDK_INT >= 28) event.id else null, packageName = event.packageName, timestamp = event.timestamp, type = "connect",
hostAddress = connectEvent.inetAddress.hostAddress, port = connectEvent.port
)
}
}
jsonObj.addAll(eventsList)
file.outputStream().use {
json.encodeToStream(jsonObj, it)
json.encodeToStream(events, it)
}
}

Expand All @@ -364,9 +361,43 @@ data class NetworkEventItem(
@SerialName("package_name") val packageName: String,
val timestamp: Long,
val type: String,
val port: Int? = null,
@SerialName("address") val hostAddress: String? = null,
val port: Int? = null,
@SerialName("host_name") val hostName: String? = null,
@SerialName("count") val totalResolvedAddressCount: Int? = null,
@SerialName("addresses") val hostAddresses: List<String?>? = null
)

@OptIn(ExperimentalSerializationApi::class)
@RequiresApi(24)
fun handleSecurityLogs(context: Context) {
val file = context.filesDir.resolve("SecurityLogs.json")
val json = Json { ignoreUnknownKeys = true; explicitNulls = false }
if(!file.exists()) file.writeText("[]")
val securityEvents = context.getDPM().retrieveSecurityLogs(context.getReceiver())
securityEvents ?: return
val logs: MutableList<SecurityEventItem>
file.inputStream().use {
logs = json.decodeFromStream(it)
}
securityEvents.forEach {
logs += SecurityEventItem(
id = if(VERSION.SDK_INT >= 28) it.id else null,
tag = it.tag, timeNanos = it.timeNanos,
logLevel = if(VERSION.SDK_INT >= 28) it.logLevel else null,
data = it.data.toString()
)
}
file.outputStream().use {
json.encodeToStream(logs, it)
}
}

@Serializable
data class SecurityEventItem(
val id: Long?,
val tag: Int,
@SerialName("time_nanos") val timeNanos: Long,
@SerialName("log_level") val logLevel: Int?,
val data: String
)
25 changes: 15 additions & 10 deletions app/src/main/java/com/bintianqi/owndroid/dpm/Network.kt
Original file line number Diff line number Diff line change
Expand Up @@ -99,8 +99,9 @@ import androidx.navigation.compose.composable
import androidx.navigation.compose.currentBackStackEntryAsState
import androidx.navigation.compose.rememberNavController
import com.bintianqi.owndroid.R
import com.bintianqi.owndroid.exportFile
import com.bintianqi.owndroid.exportFilePath
import com.bintianqi.owndroid.formatFileSize
import com.bintianqi.owndroid.saveNetworkLogs
import com.bintianqi.owndroid.selectedPackage
import com.bintianqi.owndroid.toText
import com.bintianqi.owndroid.ui.Animations
Expand Down Expand Up @@ -169,6 +170,8 @@ private fun Home(navCtrl:NavHostController, scrollState: ScrollState, wifiMacDia
val receiver = context.getReceiver()
val deviceOwner = context.isDeviceOwner
val profileOwner = context.isProfileOwner
val sharedPref = context.getSharedPreferences("data", Context.MODE_PRIVATE)
val dhizuku = sharedPref.getBoolean("dhizuku", false)
Column(modifier = Modifier.fillMaxSize().verticalScroll(scrollState)) {
Text(
text = stringResource(R.string.network),
Expand Down Expand Up @@ -196,7 +199,7 @@ private fun Home(navCtrl:NavHostController, scrollState: ScrollState, wifiMacDia
if(deviceOwner) {
SubPageItem(R.string.recommended_global_proxy, "", R.drawable.vpn_key_fill0) { navCtrl.navigate("RecommendedGlobalProxy") }
}
if(VERSION.SDK_INT >= 26&&(deviceOwner || (profileOwner && dpm.isManagedProfile(receiver)))) {
if(VERSION.SDK_INT >= 26 && !dhizuku && (deviceOwner || (profileOwner && dpm.isManagedProfile(receiver)))) {
SubPageItem(R.string.retrieve_net_logs, "", R.drawable.description_fill0) { navCtrl.navigate("NetworkLog") }
}
if(VERSION.SDK_INT >= 31 && (deviceOwner || profileOwner)) {
Expand Down Expand Up @@ -629,7 +632,6 @@ private fun NetworkLog() {
val receiver = context.getReceiver()
val logFile = context.filesDir.resolve("NetworkLogs.json")
var fileSize by remember { mutableLongStateOf(0) }
var fileExists by remember { mutableStateOf(logFile.exists()) }
LaunchedEffect(Unit) {
fileSize = logFile.length()
}
Expand All @@ -638,26 +640,29 @@ private fun NetworkLog() {
Text(text = stringResource(R.string.retrieve_net_logs), style = typography.headlineLarge)
Spacer(Modifier.padding(vertical = 5.dp))
SwitchItem(R.string.enable, "", null, { dpm.isNetworkLoggingEnabled(receiver) }, { dpm.setNetworkLoggingEnabled(receiver,it) }, padding = false)
if(fileExists) {
Text(stringResource(R.string.retrieved_logs_are, formatFileSize(fileSize)))
Text(stringResource(R.string.log_file_size_is, formatFileSize(fileSize)))
Row(horizontalArrangement = Arrangement.SpaceBetween, modifier = Modifier.fillMaxWidth()) {
Button(
onClick = {
val intent = Intent(Intent.ACTION_CREATE_DOCUMENT)
intent.addCategory(Intent.CATEGORY_OPENABLE)
intent.setType("application/json")
intent.putExtra(Intent.EXTRA_TITLE, "NetworkLogs.json")
saveNetworkLogs.launch(intent)
exportFilePath.value = logFile.path
exportFile.launch(intent)
},
modifier = Modifier.fillMaxWidth()
enabled = fileSize > 0,
modifier = Modifier.fillMaxWidth(0.49F)
) {
Text(stringResource(R.string.export_logs))
}
Button(
onClick = {
Toast.makeText(context, if(logFile.delete()) R.string.success else R.string.failed, Toast.LENGTH_SHORT).show()
fileExists = logFile.exists()
logFile.delete()
fileSize = logFile.length()
},
modifier = Modifier.fillMaxWidth()
enabled = fileSize > 0,
modifier = Modifier.fillMaxWidth(0.96F)
) {
Text(stringResource(R.string.delete_logs))
}
Expand Down
Loading

0 comments on commit 4799a15

Please sign in to comment.