adb-toolkit/backend_appinspect.go

183 lines
6.2 KiB
Go

package main
import (
"fmt"
"strings"
)
type AppInspection struct {
PackageName string `json:"packageName"`
VersionName string `json:"versionName"`
VersionCode string `json:"versionCode"`
TargetSDK string `json:"targetSdk"`
MinSDK string `json:"minSdk"`
InstallPath string `json:"installPath"`
DataDir string `json:"dataDir"`
Installer string `json:"installer"`
FirstInstall string `json:"firstInstall"`
LastUpdated string `json:"lastUpdated"`
IsSystem bool `json:"isSystem"`
IsEnabled bool `json:"isEnabled"`
IsDebuggable bool `json:"isDebuggable"`
UID string `json:"uid"`
Permissions []string `json:"permissions"`
Activities []string `json:"activities"`
Services []string `json:"services"`
Receivers []string `json:"receivers"`
Providers []string `json:"providers"`
NativeLibs []string `json:"nativeLibs"`
SharedLibs []string `json:"sharedLibs"`
CertSubject string `json:"certSubject"`
CertIssuer string `json:"certIssuer"`
CertExpiry string `json:"certExpiry"`
CertSHA256 string `json:"certSha256"`
ManifestDump string `json:"manifestDump"`
}
// InspectApp returns deep information about an installed package.
func (a *App) InspectApp(packageName string) (AppInspection, error) {
if err := validatePackageName(packageName); err != nil {
return AppInspection{}, err
}
result := AppInspection{PackageName: packageName}
// Full package dump
dump, err := a.runAdbShellTimeout(30*1e9, "dumpsys", "package", packageName)
if err != nil {
return result, fmt.Errorf("failed to dump package: %w", err)
}
result.ManifestDump = dump
// Parse key fields from dump
for _, line := range strings.Split(dump, "\n") {
line = strings.TrimSpace(line)
switch {
case strings.HasPrefix(line, "versionName="):
result.VersionName = strings.TrimPrefix(line, "versionName=")
case strings.HasPrefix(line, "versionCode="):
parts := strings.Fields(line)
if len(parts) > 0 {
result.VersionCode = strings.TrimPrefix(parts[0], "versionCode=")
}
case strings.HasPrefix(line, "targetSdk="):
result.TargetSDK = strings.TrimPrefix(line, "targetSdk=")
case strings.HasPrefix(line, "minSdk="):
result.MinSDK = strings.TrimPrefix(line, "minSdk=")
case strings.HasPrefix(line, "codePath="):
result.InstallPath = strings.TrimPrefix(line, "codePath=")
case strings.HasPrefix(line, "dataDir="):
result.DataDir = strings.TrimPrefix(line, "dataDir=")
case strings.HasPrefix(line, "installerPackageName="):
result.Installer = strings.TrimPrefix(line, "installerPackageName=")
case strings.HasPrefix(line, "firstInstallTime="):
result.FirstInstall = strings.TrimPrefix(line, "firstInstallTime=")
case strings.HasPrefix(line, "lastUpdateTime="):
result.LastUpdated = strings.TrimPrefix(line, "lastUpdateTime=")
case strings.HasPrefix(line, "pkgFlags="):
flags := line
result.IsSystem = strings.Contains(flags, "SYSTEM")
result.IsDebuggable = strings.Contains(flags, "DEBUGGABLE")
case strings.HasPrefix(line, "userId="):
result.UID = strings.TrimPrefix(line, "userId=")
case strings.HasPrefix(line, "enabledState="):
result.IsEnabled = strings.Contains(line, "ENABLED") && !strings.Contains(line, "DISABLED")
}
}
// Granted permissions
perms, _ := a.runAdbShell("dumpsys", "package", packageName)
inPerms := false
for _, line := range strings.Split(perms, "\n") {
trimmed := strings.TrimSpace(line)
if strings.Contains(trimmed, "granted=true") || strings.Contains(trimmed, "install permissions:") {
inPerms = true
}
if inPerms && strings.HasPrefix(trimmed, "android.permission.") {
perm := strings.Split(trimmed, ":")[0]
result.Permissions = append(result.Permissions, perm)
}
if inPerms && trimmed == "" {
inPerms = false
}
}
// Activities, services, receivers, providers via pm dump
pmDump, _ := a.runAdbShell("pm", "dump", packageName)
section := ""
for _, line := range strings.Split(pmDump, "\n") {
trimmed := strings.TrimSpace(line)
switch {
case strings.Contains(trimmed, "Activity Resolver Table"):
section = "activities"
case strings.Contains(trimmed, "Service Resolver Table"):
section = "services"
case strings.Contains(trimmed, "Receiver Resolver Table"):
section = "receivers"
case strings.Contains(trimmed, "Provider Resolver Table"):
section = "providers"
case strings.Contains(trimmed, "Key Set Manager"):
section = ""
}
if section != "" && strings.Contains(trimmed, packageName+"/") {
parts := strings.Fields(trimmed)
for _, p := range parts {
if strings.Contains(p, packageName+"/") {
className := strings.Split(p, "/")[1]
switch section {
case "activities":
result.Activities = appendUnique(result.Activities, className)
case "services":
result.Services = appendUnique(result.Services, className)
case "receivers":
result.Receivers = appendUnique(result.Receivers, className)
case "providers":
result.Providers = appendUnique(result.Providers, className)
}
}
}
}
}
// Native libraries
apkPath := strings.TrimPrefix(strings.TrimSpace(result.InstallPath), "package:")
if apkPath != "" {
libOut, _ := a.runAdbShell("unzip", "-l", apkPath+"/base.apk")
for _, line := range strings.Split(libOut, "\n") {
if strings.Contains(line, "lib/") && strings.HasSuffix(line, ".so") {
parts := strings.Fields(line)
if len(parts) > 0 {
result.NativeLibs = appendUnique(result.NativeLibs, parts[len(parts)-1])
}
}
}
}
// Certificate info via apksigner or keytool
certOut, _ := a.runAdbShell("pm", "dump", packageName)
for _, line := range strings.Split(certOut, "\n") {
trimmed := strings.TrimSpace(line)
if strings.Contains(trimmed, "Signing certificates:") {
// next lines have cert info
}
if strings.HasPrefix(trimmed, "Subject:") {
result.CertSubject = strings.TrimPrefix(trimmed, "Subject: ")
}
if strings.HasPrefix(trimmed, "Issuer:") {
result.CertIssuer = strings.TrimPrefix(trimmed, "Issuer: ")
}
}
return result, nil
}
func appendUnique(slice []string, item string) []string {
for _, s := range slice {
if s == item {
return slice
}
}
return append(slice, item)
}