Skip to content

Commit

Permalink
refine banned list
Browse files Browse the repository at this point in the history
  • Loading branch information
TinyHai committed Apr 10, 2024
1 parent 1635906 commit a24b0d7
Show file tree
Hide file tree
Showing 23 changed files with 854 additions and 407 deletions.
6 changes: 4 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
- 禁止卸载应用
- 禁止清除应用数据
- 添加按需禁止功能(默认关闭)
- 适配系统双开应用
- 适配Android 5.0 - Android 14(仅在Android 14测试)
- 更新版本号
12 changes: 12 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,14 @@
# BanUninstall

一个禁止卸载和禁止清除用户数据的Xposed模块

A Xposed Module that prevents apps be uninstall or apps' data be cleared

### Compatibility
Android 5.0 - Android 14

I have tested with my phone on Android 14, and it works well. I can't ensure it works on your phone.

**So before you use it, you must test it with an irrelevant app by yourself.**

**I am not responsible for any data loss if you test it using an important app.**
6 changes: 3 additions & 3 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,8 @@ android {
applicationId = "cn.tinyhai.ban_uninstall"
minSdk = 21
targetSdk = 34
versionCode = 1
versionName = "1.0.0"
versionCode = 2
versionName = "1.1.0"

testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
}
Expand Down Expand Up @@ -131,7 +131,7 @@ dependencies {
implementation(libs.coil.compose)
implementation(libs.compose.destinations.core)
implementation(libs.compose.destinations.bottomsheet)
implementation("com.github.TinyHai:ComposeDragDrop:jitpack-SNAPSHOT")
implementation("com.github.TinyHai:ComposeDragDrop:dev-SNAPSHOT")
ksp(libs.compose.destinations.ksp)
debugImplementation(libs.androidx.compose.ui.tooling)
}
8 changes: 7 additions & 1 deletion app/src/main/java/cn/tinyhai/ban_uninstall/MainActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import androidx.activity.viewModels
import cn.tinyhai.ban_uninstall.transact.client.TransactClient
import cn.tinyhai.ban_uninstall.ui.theme.AppTheme
import cn.tinyhai.ban_uninstall.vm.MainViewModel
import cn.tinyhai.compose.dragdrop.AnimatedDragDropBox
import com.ramcosta.composedestinations.DestinationsNavHost
import com.ramcosta.composedestinations.generated.NavGraphs

Expand All @@ -22,7 +23,12 @@ class MainActivity : ComponentActivity() {
enableEdgeToEdge()
setContent {
AppTheme {
DestinationsNavHost(NavGraphs.root)
AnimatedDragDropBox(
scale = 1.5f,
alpha = 0.6f
) {
DestinationsNavHost(NavGraphs.root)
}
}
}
}
Expand Down
30 changes: 13 additions & 17 deletions app/src/main/java/cn/tinyhai/ban_uninstall/XposedInit.kt
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
package cn.tinyhai.ban_uninstall

import cn.tinyhai.ban_uninstall.hook.*
import cn.tinyhai.ban_uninstall.hook.HookSelf
import cn.tinyhai.ban_uninstall.hook.HookSystem
import cn.tinyhai.ban_uninstall.hook.Hooker
import cn.tinyhai.ban_uninstall.hook.OneshotHooker
import de.robv.android.xposed.IXposedHookLoadPackage
import de.robv.android.xposed.callbacks.XC_LoadPackage

Expand All @@ -17,22 +20,15 @@ class XposedInit : IXposedHookLoadPackage {
}

override fun handleLoadPackage(lpparam: XC_LoadPackage.LoadPackageParam) {
val packageName = lpparam.packageName
val processName = lpparam.processName
hookers.filterIsInstance<OneshotHooker>().filter {
it.targetPackageName == packageName
}.filter {
it.targetProcessName == null || it.targetProcessName == processName
}.forEach {
it.startOneshotHook(lpparam)
}
hookers.filterIsInstance<UnhookableHooker>().filter {
it.targetPackageName == packageName
}.filter {
it.targetProcessName == null || it.targetProcessName == processName
}.forEach {
it.init(lpparam)
it.startHook(lpparam)
for (hooker in hookers) {
if (!hooker.isInterest(lpparam)) {
continue
}
hooker.init(lpparam)
if (hooker is OneshotHooker) {
hooker.startOneshotHook(lpparam)
}
hooker.startHook(lpparam)
}
}
}
185 changes: 185 additions & 0 deletions app/src/main/java/cn/tinyhai/ban_uninstall/hook/Compat.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
package cn.tinyhai.ban_uninstall.hook

import android.content.pm.IPackageDataObserver
import android.content.pm.IPackageDeleteObserver2
import android.content.pm.VersionedPackage
import android.os.Build
import androidx.annotation.RequiresApi
import cn.tinyhai.ban_uninstall.hook.callback.beforeMethodWithPost
import cn.tinyhai.ban_uninstall.transact.server.TransactService
import cn.tinyhai.ban_uninstall.utils.LogUtils
import cn.tinyhai.ban_uninstall.utils.XSharedPrefs
import de.robv.android.xposed.XC_MethodHook.Unhook
import de.robv.android.xposed.XposedHelpers
import de.robv.android.xposed.callbacks.XC_LoadPackage

abstract class HookCompat(private val helper: HookerHelper) : HookerHelper by helper {
abstract fun createHooks(lp: XC_LoadPackage.LoadPackageParam): List<Unhook>
}

private const val PMS_CLASS_NAME = "com.android.server.pm.PackageManagerService"
private const val PMI_CLASS_NAME = "$PMS_CLASS_NAME\$IPackageManagerImpl"

class PMSUninstallHookCompat(helper: HookerHelper) : HookCompat(helper) {

override fun createHooks(lp: XC_LoadPackage.LoadPackageParam): List<Unhook> {
val unhooks = mutableListOf<Unhook>()
val pmsClass = XposedHelpers.findClass(PMS_CLASS_NAME, lp.classLoader)
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
createHooksBelowO(unhooks, pmsClass)
}

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
createHooksAboveOrEqualO(unhooks, pmsClass)
}

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
createHooksAboveOrEqualR(unhooks, pmsClass)
}

return unhooks
}

private fun createHooksBelowO(unhooks: MutableList<Unhook>, pmsClass: Class<*>) {
unhooks.findAndAddHook(pmsClass, "deletePackage", beforeMethodWithPost { param, post ->
val packageName = param.args[0] as String
val observer2 = param.args[1] as? IPackageDeleteObserver2
val userId = param.args[2] as Int
val isUseBannedList = XSharedPrefs.isUseBannedList
LogUtils.log("${param.method.name}(packageName: $packageName, observer2: ${observer2.hashCode()} userId: $userId)")
when {
isUseBannedList && TransactService.contains(
packageName,
userId
) -> {
LogUtils.log("(in bannedList) -> return early")
observer2?.let {
post {
LogUtils.log("invoke observer2#onPackageDeleted($packageName, -1, null)")
it.onPackageDeleted(packageName, -1, null)
}
}
param.args[0] = null
}

!isUseBannedList -> {
LogUtils.log("(all) -> return early")
observer2?.let {
post {
LogUtils.log("invoke observer2#onPackageDeleted($packageName, -1, null)")
it.onPackageDeleted(packageName, -1, null)
}
}
param.args[0] = null
}

else -> {}
}
})
}

@RequiresApi(Build.VERSION_CODES.O)
private val pmsCallback = beforeMethodWithPost { param, post ->
val versionedPackage = param.args[0] as VersionedPackage
val packageName = versionedPackage.packageName
val observer2 = param.args[1] as? IPackageDeleteObserver2
val userId = param.args[2] as Int
val isUseBannedList = XSharedPrefs.isUseBannedList
LogUtils.log("${param.method.name}(packageName: $packageName, observer2: ${observer2.hashCode()} userId: $userId)")
when {
isUseBannedList && TransactService.contains(
packageName,
userId
) -> {
LogUtils.log("(in bannedList) -> return early")
observer2?.let {
post {
LogUtils.log("invoke observer2#onPackageDeleted($packageName, -1, null)")
it.onPackageDeleted(packageName, -1, null)
}
}
param.result = null
}

!isUseBannedList -> {
LogUtils.log("(all) -> return early")
observer2?.let {
post {
LogUtils.log("invoke observer2#onPackageDeleted($packageName, -1, null)")
it.onPackageDeleted(packageName, -1, null)
}
}
param.result = null
}

else -> {}
}
}

@RequiresApi(Build.VERSION_CODES.O)
private fun createHooksAboveOrEqualO(unhooks: MutableList<Unhook>, pmsClass: Class<*>) {
unhooks.findAndAddHook(pmsClass, "deletePackageVersioned", pmsCallback)
}

@RequiresApi(Build.VERSION_CODES.O)
private fun createHooksAboveOrEqualR(unhooks: MutableList<Unhook>, pmsClass: Class<*>) {
unhooks.findAndAddHook(pmsClass, "deleteExistingPackageAsUser", pmsCallback)
}
}

class PMSClearDataHookCompat(helper: HookerHelper) : HookCompat(helper) {
override fun createHooks(lp: XC_LoadPackage.LoadPackageParam): List<Unhook> {
val unhooks = mutableListOf<Unhook>()
val pmsClass = XposedHelpers.findClass(PMS_CLASS_NAME, lp.classLoader)
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) {
createHooksBelowTIRAMISU(unhooks, pmsClass)
}
val pmiClass =
XposedHelpers.findClass(PMI_CLASS_NAME, lp.classLoader)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
createHooksAboveOrEqualTIRAMISU(unhooks, pmiClass)
}
return unhooks
}

private val callback = beforeMethodWithPost { param, post ->
val packageName = param.args[0] as String
val observer = param.args[1] as? IPackageDataObserver
val userId = param.args[2] as Int
LogUtils.log("${param.method.name}(packageName: $packageName, observer: ${observer.hashCode()}, userId: $userId)")
val isUseBannedList = XSharedPrefs.isUseBannedList
when {
isUseBannedList && TransactService.contains(packageName, userId) -> {
LogUtils.log("(in bannedList) -> return early")
observer?.let {
post {
LogUtils.log("invoke observer#onRemoveCompleted($packageName, false)")
it.onRemoveCompleted(packageName, false)
}
}
param.result = null
}

!isUseBannedList -> {
LogUtils.log("(all) -> return early")
observer?.let {
post {
LogUtils.log("invoke observer#onRemoveCompleted($packageName, false)")
it.onRemoveCompleted(packageName, false)
}
}
param.result = null
}

else -> {}
}
}

private fun createHooksBelowTIRAMISU(unhooks: MutableList<Unhook>, pmsClass: Class<*>) {
unhooks.findAndAddHook(pmsClass, "clearApplicationUserData", callback)
}

private fun createHooksAboveOrEqualTIRAMISU(unhooks: MutableList<Unhook>, pmiClass: Class<*>) {
unhooks.findAndAddHook(pmiClass, "clearApplicationUserData", callback)
}
}
7 changes: 6 additions & 1 deletion app/src/main/java/cn/tinyhai/ban_uninstall/hook/HookSelf.kt
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,11 @@ class HookSelf : BaseOneshotHooker() {
get() = "HookSelf"

override fun createOneshotHook(lp: XC_LoadPackage.LoadPackageParam) {
val userId = lp.appInfo.uid / 100_000
if (userId > 0) {
LogUtils.log("skip dual app")
return
}
val xpTag = try {
val tag = XposedBridge::class.java.getDeclaredField("TAG").get(null)?.toString()
when (tag) {
Expand Down Expand Up @@ -47,7 +52,7 @@ class HookSelf : BaseOneshotHooker() {
dir.mkdir()
}
val prev = Os.umask("777".toInt(8))
LogUtils.log(prev.toString(8))
LogUtils.log("prev umask: ${prev.toString(8).padStart(4, '0')}")
Os.chmod(dir.absolutePath, "715".toInt(8))
Os.umask(prev)
LogUtils.log("set prefs dir world readable success")
Expand Down
Loading

0 comments on commit a24b0d7

Please sign in to comment.