183 lines
6.2 KiB
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)
|
|
}
|