Compare commits

...

34 Commits

Author SHA1 Message Date
Hen Ry
fe8997efae Fix 2021-01-10 20:17:20 -08:00
Arbri çoçka
23455c722c fix in Values-sq 2021-01-10 20:16:57 -08:00
topjohnwu
5ce29c30d2 Fix sepolicy copying 2021-01-10 20:16:02 -08:00
topjohnwu
70d67728fd Add global toggle for ptrace monitor 2021-01-10 19:27:54 -08:00
topjohnwu
e546884b08 Remove isolated process handling in ptrace
Impossible to achieve only through ptrace
2021-01-10 17:18:42 -08:00
topjohnwu
b36e6d987d Reorganize MagiskHide code
Prepare for zygote injection hiding
2021-01-10 17:11:00 -08:00
topjohnwu
53c3dd5e8b Auto track JNI method hooks 2021-01-10 05:07:17 -08:00
topjohnwu
da723b207a Allow 3rd party code to load pre-specializing
Magisk's policy is to never allow 3rd party code to be loaded in the
zygote daemon process so we have 100% control over injection and hiding.
However, this makes it impossible for 3rd party modules to run anything
before process specialization, which includes the ability to modify the
arguments being sent to these original nativeForkAndXXX methods.

The trick here is to fork before calling the original nativeForkAndXXX
methods, and hook `fork` in libandroid_runtime.so to skip the next
invocation; basically, we're moving the responsibility of process
forking to our own hands.
2021-01-10 01:25:30 -08:00
topjohnwu
e050f77198 Don't hook SystemProperties#set
Doesn't seem necessary
2021-01-09 20:39:59 -08:00
topjohnwu
540b4b7ea9 Update pre/post hooks implementation 2021-01-09 17:41:25 -08:00
topjohnwu
bbef22daf7 More macro magic to automate more code 2021-01-09 04:28:26 -08:00
topjohnwu
9ed110c91b Add JNI hooks to critical methods 2021-01-08 05:25:44 -08:00
topjohnwu
a30d510eb1 Use xHook to hook functions in PLT 2021-01-08 00:53:24 -08:00
topjohnwu
ef98eaed8f Proper injection entry and unloading 2021-01-06 23:59:05 -08:00
topjohnwu
2a257f327c Sanitize /proc/PID/environ 2021-01-06 23:41:37 -08:00
topjohnwu
4060c2107c Add preliminary zygote code injection support
Prototyping the injection setup and a clean "self unloading" mechanism.
2021-01-06 22:21:17 -08:00
topjohnwu
cd23d27048 Fix remote_write implementation 2021-01-06 21:56:29 -08:00
topjohnwu
18b86e4fd2 Update Android.mk for test binary
Make Android Studio happy
2021-01-05 00:01:02 -08:00
topjohnwu
5f2e22a259 Support remote function call with ptrace
End up not used for anything, but keep it for good
2021-01-02 21:29:45 -08:00
topjohnwu
4e97b18977 Move libsystemproperties to external 2020-12-31 15:06:19 -08:00
topjohnwu
f9bde347bc Convert indentation to spaces
The tab war is lost
2020-12-30 22:11:24 -08:00
Billy Laws
947a7d6a2f Support rootwait cmdline parameter on legacy SAR
On devices where the primary storage is slow to probe it makes sense to
wait forever for the system partition to mount, this emulates the
kernel's behaviour when waiting for rootfs on SAR if the rootwait
parameter is supplied.

This issue was encountered with some SD cards on the Nintendo Switch.
2020-12-30 16:43:28 -08:00
Björn Engel
872ab2e99b Change translation for next
Nächste sounds a little bit strange.
2020-12-30 16:41:22 -08:00
kubalav
90b8813bb7 Fixed typo 2020-12-30 16:41:01 -08:00
Arbri çoçka
88d0f63294 Fix text in strings_sq 2020-12-30 16:40:47 -08:00
topjohnwu
79fa0d3a90 Hide selection improvements 2020-12-30 16:40:22 -08:00
topjohnwu
8e61080a4a Preparation for hiding isolated processes 2020-12-30 15:55:53 -08:00
topjohnwu
3f9a64417b Disable gradle daemon on Windows CI 2020-12-29 02:46:57 -08:00
topjohnwu
eb959379e8 Prevent resource ID clash 2020-12-29 02:39:47 -08:00
topjohnwu
41a644afb9 Open source stub APK loader
Close #3537
2020-12-29 01:44:02 -08:00
topjohnwu
6b42db943d Better bug report details 2020-12-28 17:03:20 -08:00
topjohnwu
1c325459eb Only run CI when it matters 2020-12-28 16:38:25 -08:00
John Wu
6d88d8ad95 Add issue templates 2020-12-28 16:26:10 -08:00
topjohnwu
246997f273 Update links 2020-12-28 15:58:53 -08:00
148 changed files with 12796 additions and 11127 deletions

28
.github/ISSUE_TEMPLATE/bug_report.md vendored Normal file
View File

@@ -0,0 +1,28 @@
---
name: Bug report
about: Create a report to help us improve
title: ''
labels: ''
assignees: ''
---
## READ BEFORE OPENING ISSUES
All bug reports require you to **USE CANARY BUILDS**. Please include the version name and version code in the bug report.
If you experience a bootloop, attach a `dmesg` (kernel logs) when the device refuse to boot. This may very likely require a custom kernel on some devices as `last_kmsg` or `pstore ramoops` are usually not enabled by default. In addition, please also upload the result of `cat /proc/mounts` when your device is working correctly **WITHOUT ROOT**.
If you experience issues during installation, in recovery, upload the recovery logs, or in Magisk Manager, upload the install logs. Please also upload the `boot.img` or `recovery.img` that you are using for patching.
If you experience a crash of Magisk Manager, dump the full `logcat` **when the crash happens**. **DO NOT** upload `magisk.log`.
If you experience other issues related to Magisk, upload `magisk.log`, and preferably also include a boot `logcat` (start dumping `logcat` when the device boots up)
**DO NOT** open issues regarding root detection.
**DO NOT** ask for instructions.
**DO NOT** report issues if you have any modules installed.
Without following the rules above, your issue will be closed without explanation.

View File

@@ -3,6 +3,13 @@ name: Magisk Build
on:
push:
branches: [ master ]
paths:
- 'app/**'
- 'native/**'
- 'stub/**'
- 'buildSrc/**'
- 'build.py'
- 'gradle.properties'
pull_request:
branches: [ master ]
workflow_dispatch:
@@ -43,6 +50,7 @@ jobs:
echo "ANDROID_SDK_ROOT=$sdk_root" >> $env:GITHUB_ENV
echo "ANDROID_HOME=$sdk_root" >> $env:GITHUB_ENV
echo "MAGISK_NDK_VERSION=$ndk_ver" >> $env:GITHUB_ENV
echo "GRADLE_OPTS=-Dorg.gradle.daemon=false" >> $env:GITHUB_ENV
- name: Set up GitHub env (Unix)
if: runner.os != 'Windows'
@@ -57,7 +65,6 @@ jobs:
path: |
~/.gradle/caches
~/.gradle/wrapper
!~/.gradle/caches/**/*.lock
key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle.kts') }}
restore-keys: ${{ runner.os }}-gradle-

3
.gitmodules vendored
View File

@@ -25,6 +25,9 @@
[submodule "pcre"]
path = native/jni/external/pcre
url = https://android.googlesource.com/platform/external/pcre
[submodule "xhook"]
path = native/jni/external/xhook
url = https://github.com/iqiyi/xHook.git
[submodule "termux-elf-cleaner"]
path = tools/termux-elf-cleaner
url = https://github.com/termux/termux-elf-cleaner.git

View File

@@ -15,11 +15,11 @@ Here are some feature highlights:
## Downloads
[![](https://img.shields.io/badge/Magisk%20Manager-v8.0.3-green)](https://github.com/topjohnwu/Magisk/releases/download/manager-v8.0.3/MagiskManager-v8.0.3.apk)
[![](https://img.shields.io/badge/Magisk%20Manager-v8.0.4-green)](https://github.com/topjohnwu/Magisk/releases/download/manager-v8.0.4/MagiskManager-v8.0.4.apk)
[![](https://img.shields.io/badge/Magisk%20Manager-Canary-red)](https://raw.githubusercontent.com/topjohnwu/magisk_files/canary/app-debug.apk)
<br>
[![](https://img.shields.io/badge/Magisk-v20.4-blue)](https://github.com/topjohnwu/Magisk/releases/tag/v20.4)
[![](https://img.shields.io/badge/Magisk%20Beta-v21.1-blue)](https://github.com/topjohnwu/Magisk/releases/tag/v21.1)
[![](https://img.shields.io/badge/Magisk%20Beta-v21.2-blue)](https://github.com/topjohnwu/Magisk/releases/tag/v21.2)
## Useful Links

View File

@@ -25,6 +25,7 @@ object Const {
fun atLeast_20_2() = Info.env.magiskVersionCode >= 20200 || isCanary()
fun atLeast_20_4() = Info.env.magiskVersionCode >= 20400 || isCanary()
fun atLeast_21_0() = Info.env.magiskVersionCode >= 21000 || isCanary()
fun atLeast_21_2() = Info.env.magiskVersionCode >= 21200 || isCanary()
fun isCanary() = Info.env.magiskVersionCode % 100 != 0
}
@@ -36,7 +37,6 @@ object Const {
// notifications
const val MAGISK_UPDATE_NOTIFICATION_ID = 4
const val APK_UPDATE_NOTIFICATION_ID = 5
const val HIDE_MANAGER_NOTIFICATION_ID = 8
const val UPDATE_NOTIFICATION_CHANNEL = "update"
const val PROGRESS_NOTIFICATION_CHANNEL = "progress"
const val CHECK_MAGISK_UPDATE_WORKER_ID = "magisk_update"

View File

@@ -27,13 +27,13 @@ class LocalModule(path: String) : Module() {
val dir = "$PERSIST/$id"
if (enable) {
disableFile.delete()
if (Const.Version.isCanary())
if (Const.Version.atLeast_21_2())
Shell.su("copy_sepolicy_rules").submit()
else
Shell.su("mkdir -p $dir", "cp -af $ruleFile $dir").submit()
} else {
!disableFile.createNewFile()
if (Const.Version.isCanary())
if (Const.Version.atLeast_21_2())
Shell.su("copy_sepolicy_rules").submit()
else
Shell.su("rm -rf $dir").submit()
@@ -45,13 +45,13 @@ class LocalModule(path: String) : Module() {
set(remove) {
if (remove) {
removeFile.createNewFile()
if (Const.Version.isCanary())
if (Const.Version.atLeast_21_2())
Shell.su("copy_sepolicy_rules").submit()
else
Shell.su("rm -rf $PERSIST/$id").submit()
} else {
!removeFile.delete()
if (Const.Version.isCanary())
if (Const.Version.atLeast_21_2())
Shell.su("copy_sepolicy_rules").submit()
else
Shell.su("cp -af $ruleFile $PERSIST/$id").submit()

View File

@@ -7,9 +7,11 @@ import android.content.Context
import android.content.ContextWrapper
import android.content.Intent
import android.content.pm.ApplicationInfo
import android.content.pm.ComponentInfo
import android.content.pm.PackageManager
import android.content.pm.PackageManager.*
import android.content.pm.ServiceInfo
import android.content.pm.ServiceInfo.FLAG_ISOLATED_PROCESS
import android.content.pm.ServiceInfo.FLAG_USE_APP_ZYGOTE
import android.content.res.Configuration
import android.content.res.Resources
import android.database.Cursor
@@ -57,32 +59,10 @@ import java.lang.reflect.Array as JArray
val packageName: String get() = get<Context>().packageName
val ApplicationInfo.processes: List<String> @SuppressLint("InlinedApi") get() {
val pm = get<PackageManager>()
val appProcessName = processName ?: packageName
val baseFlag = MATCH_DISABLED_COMPONENTS or MATCH_UNINSTALLED_PACKAGES
val packageInfo = try {
val request = GET_ACTIVITIES or GET_SERVICES or GET_RECEIVERS or GET_PROVIDERS
pm.getPackageInfo(packageName, baseFlag or request)
} catch (e: NameNotFoundException) { // EdXposed hooked, issue#3276
return listOf(appProcessName)
} catch (e: Exception) {
// Exceed binder data transfer limit, fetch each component type separately
pm.getPackageInfo(packageName, baseFlag).apply {
runCatching { activities = pm.getPackageInfo(packageName, GET_ACTIVITIES).activities }
runCatching { services = pm.getPackageInfo(packageName, GET_SERVICES).services }
runCatching { receivers = pm.getPackageInfo(packageName, GET_RECEIVERS).receivers }
runCatching { providers = pm.getPackageInfo(packageName, GET_PROVIDERS).providers }
}
}
fun Array<out ComponentInfo>.processNames() = map { it.processName ?: appProcessName }
return with(packageInfo) {
activities?.processNames().orEmpty() +
services?.processNames().orEmpty() +
receivers?.processNames().orEmpty() +
providers?.processNames().orEmpty()
}
}
val ServiceInfo.isIsolated get() = (flags and FLAG_ISOLATED_PROCESS) != 0
@get:SuppressLint("InlinedApi")
val ServiceInfo.useAppZygote get() = (flags and FLAG_USE_APP_ZYGOTE) != 0
fun Context.rawResource(id: Int) = resources.openRawResource(id)

View File

@@ -1,47 +0,0 @@
package com.topjohnwu.magisk.ui.hide
import android.content.pm.ApplicationInfo
import android.content.pm.PackageManager
import android.graphics.drawable.Drawable
import com.topjohnwu.magisk.core.utils.currentLocale
import com.topjohnwu.magisk.ktx.getLabel
class HideTarget(line: String) {
val packageName: String
val process: String
init {
val split = line.split(Regex("\\|"), 2)
packageName = split[0]
process = split.getOrElse(1) { packageName }
}
}
class HideAppInfo(info: ApplicationInfo, pm: PackageManager)
: ApplicationInfo(info), Comparable<HideAppInfo> {
val label = info.getLabel(pm)
val iconImage: Drawable = info.loadIcon(pm)
override fun compareTo(other: HideAppInfo) = comparator.compare(this, other)
companion object {
private val comparator = compareBy<HideAppInfo>(
{ it.label.toLowerCase(currentLocale) },
{ it.packageName }
)
}
}
data class HideProcessInfo(
val name: String,
val packageName: String,
val isHidden: Boolean
)
class HideAppTarget(
val info: HideAppInfo,
val processes: List<HideProcessInfo>
) : Comparable<HideAppTarget> {
override fun compareTo(other: HideAppTarget) = compareValuesBy(this, other) { it.info }
}

View File

@@ -0,0 +1,105 @@
package com.topjohnwu.magisk.ui.hide
import android.annotation.SuppressLint
import android.content.pm.ApplicationInfo
import android.content.pm.ComponentInfo
import android.content.pm.PackageManager
import android.content.pm.PackageManager.*
import android.content.pm.ServiceInfo
import android.graphics.drawable.Drawable
import com.topjohnwu.magisk.core.utils.currentLocale
import com.topjohnwu.magisk.ktx.getLabel
import com.topjohnwu.magisk.ktx.isIsolated
import com.topjohnwu.magisk.ktx.useAppZygote
class CmdlineHiddenItem(line: String) {
val packageName: String
val process: String
init {
val split = line.split(Regex("\\|"), 2)
packageName = split[0]
process = split.getOrElse(1) { packageName }
}
}
const val ISOLATED_MAGIC = "isolated"
@SuppressLint("InlinedApi")
class HideAppInfo(info: ApplicationInfo, pm: PackageManager, hideList: List<CmdlineHiddenItem>)
: ApplicationInfo(info), Comparable<HideAppInfo> {
val label = info.getLabel(pm)
val iconImage: Drawable = info.loadIcon(pm)
val processes = fetchProcesses(pm, hideList)
override fun compareTo(other: HideAppInfo) = comparator.compare(this, other)
private fun fetchProcesses(
pm: PackageManager,
hideList: List<CmdlineHiddenItem>
): List<HideProcessInfo> {
// Fetch full PackageInfo
val baseFlag = MATCH_DISABLED_COMPONENTS or MATCH_UNINSTALLED_PACKAGES
val packageInfo = try {
val request = GET_ACTIVITIES or GET_SERVICES or GET_RECEIVERS or GET_PROVIDERS
pm.getPackageInfo(packageName, baseFlag or request)
} catch (e: NameNotFoundException) {
// EdXposed hooked, issue#3276
return emptyList()
} catch (e: Exception) {
// Exceed binder data transfer limit, fetch each component type separately
pm.getPackageInfo(packageName, baseFlag).apply {
runCatching { activities = pm.getPackageInfo(packageName, baseFlag or GET_ACTIVITIES).activities }
runCatching { services = pm.getPackageInfo(packageName, baseFlag or GET_SERVICES).services }
runCatching { receivers = pm.getPackageInfo(packageName, baseFlag or GET_RECEIVERS).receivers }
runCatching { providers = pm.getPackageInfo(packageName, baseFlag or GET_PROVIDERS).providers }
}
}
val hidden = hideList.filter { it.packageName == packageName || it.packageName == ISOLATED_MAGIC }
fun createProcess(name: String, pkg: String = packageName): HideProcessInfo {
return HideProcessInfo(name, pkg, hidden.any { it.process == name })
}
var haveAppZygote = false
fun Array<out ComponentInfo>.processes() = map { createProcess(it.processName) }
fun Array<ServiceInfo>.processes() = map {
if (it.isIsolated) {
if (it.useAppZygote) {
haveAppZygote = true
// Using app zygote, don't need to track the process
null
} else {
createProcess("${it.processName}:${it.name}", ISOLATED_MAGIC)
}
} else {
createProcess(it.processName)
}
}
return with(packageInfo) {
activities?.processes().orEmpty() +
services?.processes().orEmpty() +
receivers?.processes().orEmpty() +
providers?.processes().orEmpty() +
listOf(if (haveAppZygote) createProcess("${processName}_zygote") else null)
}.filterNotNull().distinctBy { it.name }.sortedBy { it.name }
}
companion object {
private val comparator = compareBy<HideAppInfo>(
{ it.label.toLowerCase(currentLocale) },
{ it.packageName }
)
}
}
data class HideProcessInfo(
val name: String,
val packageName: String,
var isHidden: Boolean
) {
val isIsolated get() = name == ISOLATED_MAGIC
val isAppZygote get() = name.endsWith("_zygote")
}

View File

@@ -12,14 +12,13 @@ import com.topjohnwu.magisk.utils.set
import com.topjohnwu.superuser.Shell
import kotlin.math.roundToInt
class HideItem(
app: HideAppTarget
) : ObservableItem<HideItem>(), Comparable<HideItem> {
class HideRvItem(
val info: HideAppInfo
) : ObservableItem<HideRvItem>(), Comparable<HideRvItem> {
override val layoutRes = R.layout.item_hide_md2
override val layoutRes get() = R.layout.item_hide_md2
val info = app.info
val processes = app.processes.map { HideProcessItem(it) }
val processes = info.processes.map { HideProcessRvItem(it) }
@get:Bindable
var isExpanded = false
@@ -41,11 +40,10 @@ class HideItem(
if (value == true) {
processes
.filterNot { it.isHidden }
.filter { isExpanded || it.process.name == it.process.packageName }
.filter { isExpanded || it.defaultSelection }
} else {
processes
.filter { it.isHidden }
.filter { isExpanded || it.process.name == it.process.packageName }
}.forEach { it.toggle() }
}
@@ -69,14 +67,19 @@ class HideItem(
else -> null
}
} else {
processes.find { it.isHidden && it.process.name == it.process.packageName } != null
val defaultProcesses = processes.filter { it.defaultSelection }
when (defaultProcesses.count { it.isHidden }) {
0 -> false
defaultProcesses.size -> true
else -> null
}
}
}
override fun compareTo(other: HideItem) = comparator.compare(this, other)
override fun compareTo(other: HideRvItem) = comparator.compare(this, other)
companion object {
private val comparator = compareBy<HideItem>(
private val comparator = compareBy<HideRvItem>(
{ it.itemsChecked == 0 },
{ it.info }
)
@@ -84,16 +87,17 @@ class HideItem(
}
class HideProcessItem(
class HideProcessRvItem(
val process: HideProcessInfo
) : ObservableItem<HideProcessItem>() {
) : ObservableItem<HideProcessRvItem>() {
override val layoutRes = R.layout.item_hide_process_md2
override val layoutRes get() = R.layout.item_hide_process_md2
@get:Bindable
var isHidden = process.isHidden
set(value) = set(value, field, { field = it }, BR.hidden) {
val arg = if (isHidden) "add" else "rm"
var isHidden
get() = process.isHidden
set(value) = set(value, process.isHidden, { process.isHidden = it }, BR.hidden) {
val arg = if (it) "add" else "rm"
val (name, pkg) = process
Shell.su("magiskhide --$arg $pkg $name").submit()
}
@@ -102,7 +106,10 @@ class HideProcessItem(
isHidden = !isHidden
}
override fun contentSameAs(other: HideProcessItem) = process == other.process
override fun itemSameAs(other: HideProcessItem) = process.name == other.process.name
val defaultSelection get() =
process.isIsolated || process.isAppZygote || process.name == process.packageName
override fun contentSameAs(other: HideProcessRvItem) = process == other.process
override fun itemSameAs(other: HideProcessRvItem) = process.name == other.process.name
}

View File

@@ -3,6 +3,7 @@ package com.topjohnwu.magisk.ui.hide
import android.annotation.SuppressLint
import android.content.pm.ApplicationInfo
import android.content.pm.PackageManager
import android.content.pm.PackageManager.MATCH_UNINSTALLED_PACKAGES
import android.os.Process
import androidx.databinding.Bindable
import androidx.lifecycle.viewModelScope
@@ -14,7 +15,6 @@ import com.topjohnwu.magisk.arch.itemBindingOf
import com.topjohnwu.magisk.core.Config
import com.topjohnwu.magisk.ktx.get
import com.topjohnwu.magisk.ktx.packageName
import com.topjohnwu.magisk.ktx.processes
import com.topjohnwu.magisk.utils.Utils
import com.topjohnwu.magisk.utils.set
import com.topjohnwu.superuser.Shell
@@ -45,11 +45,11 @@ class HideViewModel : BaseViewModel(), Queryable {
submitQuery()
}
val items = filterableListOf<HideItem>()
val itemBinding = itemBindingOf<HideItem> {
val items = filterableListOf<HideRvItem>()
val itemBinding = itemBindingOf<HideRvItem> {
it.bindExtra(BR.viewModel, this)
}
val itemInternalBinding = itemBindingOf<HideProcessItem> {
val itemInternalBinding = itemBindingOf<HideProcessRvItem> {
it.bindExtra(BR.viewModel, this)
}
@@ -62,14 +62,13 @@ class HideViewModel : BaseViewModel(), Queryable {
state = State.LOADING
val (apps, diff) = withContext(Dispatchers.Default) {
val pm = get<PackageManager>()
val hides = Shell.su("magiskhide --ls").exec().out.map { HideTarget(it) }
val apps = pm.getInstalledApplications(PackageManager.MATCH_UNINSTALLED_PACKAGES)
val hideList = Shell.su("magiskhide --ls").exec().out.map { CmdlineHiddenItem(it) }
val apps = pm.getInstalledApplications(MATCH_UNINSTALLED_PACKAGES)
.asSequence()
.filter { it.enabled && !blacklist.contains(it.packageName) }
.map { HideAppInfo(it, pm) }
.map { createTarget(it, hides) }
.map { HideAppInfo(it, pm, hideList) }
.filter { it.processes.isNotEmpty() }
.map { HideItem(it) }
.map { HideRvItem(it) }
.toList()
.sorted()
apps to items.calculateDiff(apps)
@@ -80,18 +79,6 @@ class HideViewModel : BaseViewModel(), Queryable {
// ---
private fun createTarget(info: HideAppInfo, hideList: List<HideTarget>): HideAppTarget {
val pkg = info.packageName
val hidden = hideList.filter { it.packageName == pkg }
val processNames = info.processes.distinct()
val processes = processNames.map { name ->
HideProcessInfo(name, pkg, hidden.any { name == it.process })
}
return HideAppTarget(info, processes)
}
// ---
override fun query() {
items.filter {
fun showHidden() = it.itemsChecked != 0

View File

@@ -9,7 +9,7 @@
<variable
name="item"
type="com.topjohnwu.magisk.ui.hide.HideItem" />
type="com.topjohnwu.magisk.ui.hide.HideRvItem" />
<variable
name="viewModel"

View File

@@ -7,7 +7,7 @@
<variable
name="item"
type="com.topjohnwu.magisk.ui.hide.HideProcessItem" />
type="com.topjohnwu.magisk.ui.hide.HideProcessRvItem" />
<variable
name="viewModel"

View File

@@ -1,3 +1,7 @@
## v8.0.5
- Fix sepolicy rule copying
## v8.0.4
- A lot of stability changes and minor bug fixes

View File

@@ -15,7 +15,7 @@
<string name="no_connection">Keine Verbindung verfügbar</string>
<string name="app_changelog">Änderungen</string>
<string name="manager">Manager</string>
<string name="loading">Lädt</string>
<string name="loading">Laden</string>
<string name="update">Update</string>
<string name="not_available">N/A</string>
<string name="hide">Verstecken</string>
@@ -40,7 +40,7 @@
<string name="recovery_mode">Recovery Modus</string>
<string name="install_options_title">Optionen</string>
<string name="install_method_title">Methode</string>
<string name="install_next">Nächste</string>
<string name="install_next">Nächster Schritt</string>
<string name="install_start">Los geht\'s</string>
<string name="manager_download_install">Tippen zum Herunterladen und Installieren</string>
<string name="download_zip_only">Nur Zip herunterladen</string>

View File

@@ -44,7 +44,7 @@
<string name="install_next">Ďalej</string>
<string name="install_start">Poďme na to</string>
<string name="manager_download_install">Stlačte pre stiahnutie a inštaláciu</string>
<string name="download_zip_only">Len tiahnuť zip</string>
<string name="download_zip_only">Len stiahnuť zip</string>
<string name="direct_install">Priama inštalácia (Odporúča sa)</string>
<string name="install_inactive_slot">Inštalovať na neaktívny slot (Po OTA)</string>
<string name="install_inactive_slot_msg">Vaše zariadenie bude po reštarte PRINÚTENÉ nabootovať do aktuálne neaktívneho slotu!\nTúto voľbu použite iba po skončení OTA.\nPokračovať?</string>

View File

@@ -28,11 +28,11 @@
<string name="home_support_content">Magisk është, dhe gjithmonë do të jetë, falas dhe me burim të hapur. Megjithatë mund të na tregoni se ju interesoni duke dërguar një donacion të vogël.</string>
<string name="home_status_normal">Normale</string>
<string name="home_status_stub">Stub</string>
<string name="home_installed_version">Instaluar</string>
<string name="home_latest_version"> fundit</string>
<string name="home_installed_version">E instaluar</string>
<string name="home_latest_version">e fundit</string>
<string name="invalid_update_channel">Kanal i pavlefshëm i azhurnimit</string>
<string name="uninstall_magisk_title">Çinstalo Magisk</string>
<string name="uninstall_magisk_msg">Të gjitha modulet do të çaktivizohen/hiqen! \n Rrënja do të hiqet! \n Të dhënat tuaja potencialisht të koduara nëse jo tashmë!</string>
<string name="uninstall_magisk_msg">Të gjitha modulet do të çaktivizohen/hiqen! \n Rrënja do të hiqet! \n\n Të dhënat tuaja potencialisht të koduara nëse jo tashmë!</string>
<string name="home_check_safetynet">Kontrolloni Rrjetin e Sigurisë</string>
<!--Install-->
@@ -95,7 +95,7 @@
<string name="safetynet_res_invalid">Përgjigjja është e pavlefshme </string>
<string name="safetynet_attest_success">Suksese!</string>
<string name="safetynet_attest_failure">Vërtetimi dështoi!</string>
<string name="safetynet_attest_loading">Thjesht një sekondë…</string>
<string name="safetynet_attest_loading">Prit një sekondë…</string>
<string name="safetynet_attest_restart">Provo përsëri</string>
<!--MagiskHide-->
@@ -111,7 +111,7 @@
<string name="reboot_userspace">Ristartim normal</string>
<string name="reboot_recovery">Ristartoni në recovery</string>
<string name="reboot_bootloader">Ristartoni në bootloader</string>
<string name="reboot_download">Ristartoni për në download</string>
<string name="reboot_download">Ristartoni në download</string>
<string name="reboot_edl">Ristartoni në EDL</string>
<string name="module_version_author">%1$s nga %2$s</string>
<string name="module_state_remove">Hiq</string>

View File

@@ -1,5 +1,6 @@
# Release Notes
- [v21.2](21200.md)
- [v21.1](21100.md)
- [v21.0](21000.md)
- [v20.4](20400.md)

View File

@@ -27,6 +27,6 @@ android.injected.testOnly=false
kapt.incremental.apt=true
# Magisk
magisk.versionCode=21200
magisk.versionCode=21201
magisk.ndkVersion=21d
magisk.fullNdkVersion=21.3.6528147

View File

@@ -8,7 +8,7 @@ ifdef B_MAGISK
include $(CLEAR_VARS)
LOCAL_MODULE := magisk
LOCAL_STATIC_LIBRARIES := libnanopb libsystemproperties libutils
LOCAL_STATIC_LIBRARIES := libnanopb libsystemproperties libutils libxhook
LOCAL_C_INCLUDES := jni/include
LOCAL_SRC_FILES := \
@@ -30,7 +30,10 @@ LOCAL_SRC_FILES := \
su/su.cpp \
su/connect.cpp \
su/pts.cpp \
su/su_daemon.cpp
su/su_daemon.cpp \
inject/entry.cpp \
inject/utils.cpp \
inject/hook.cpp
LOCAL_LDLIBS := -llog
include $(BUILD_EXECUTABLE)
@@ -140,15 +143,16 @@ include $(BUILD_EXECUTABLE)
endif
ifdef B_TEST
ifneq (,$(wildcard jni/test.cpp))
include $(CLEAR_VARS)
LOCAL_MODULE := test
LOCAL_STATIC_LIBRARIES := libutils
LOCAL_C_INCLUDES := jni/include
LOCAL_SRC_FILES := test.cpp
LOCAL_LDFLAGS := -static
include $(BUILD_EXECUTABLE)
endif
endif
ifdef B_BB
@@ -161,5 +165,4 @@ endif
# Libraries
########################
include jni/utils/Android.mk
include jni/systemproperties/Android.mk
include jni/external/Android.mk

View File

@@ -8,21 +8,26 @@
#include <selinux.hpp>
#include <utils.hpp>
using namespace std::literals;
using namespace std;
using main_fun = int (*)(int, char *[]);
static main_fun applet_main[] = { su_client_main, resetprop_main, magiskhide_main, nullptr };
[[noreturn]] static void call_applet(int argc, char **argv) {
static int call_applet(int argc, char *argv[]) {
// Applets
string_view base = basename(argv[0]);
for (int i = 0; applet_names[i]; ++i) {
if (strcmp(basename(argv[0]), applet_names[i]) == 0) {
exit((*applet_main[i])(argc, argv));
if (base == applet_names[i]) {
return (*applet_main[i])(argc, argv);
}
}
fprintf(stderr, "%s: applet not found\n", basename(argv[0]));
exit(1);
if (str_starts(base, "app_process")) {
return app_process_main(argc, argv);
}
fprintf(stderr, "%s: applet not found\n", base.data());
return 1;
}
int main(int argc, char *argv[]) {
@@ -41,6 +46,6 @@ int main(int argc, char *argv[]) {
}
}
call_applet(argc, argv);
return call_applet(argc, argv);
}

View File

@@ -189,21 +189,22 @@ static int magisk_log(int prio, const char *fmt, va_list ap) {
return vfprintf(local_log_file.get(), buf, args);
}
static void android_logging() {
#define mlog(prio) [](auto fmt, auto ap){ return magisk_log(ANDROID_LOG_##prio, fmt, ap); }
static void magisk_logging() {
auto in_mem_file = make_stream_fp<byte_stream>(log_buf, log_buf_len);
log_file.reset(in_mem_file.release(), [](FILE *) {
free(log_buf);
log_buf = nullptr;
});
log_cb.d = [](auto fmt, auto ap){ return magisk_log(ANDROID_LOG_DEBUG, fmt, ap); };
log_cb.i = [](auto fmt, auto ap){ return magisk_log(ANDROID_LOG_INFO, fmt, ap); };
log_cb.w = [](auto fmt, auto ap){ return magisk_log(ANDROID_LOG_WARN, fmt, ap); };
log_cb.e = [](auto fmt, auto ap){ return magisk_log(ANDROID_LOG_ERROR, fmt, ap); };
log_cb.d = mlog(DEBUG);
log_cb.i = mlog(INFO);
log_cb.w = mlog(WARN);
log_cb.e = mlog(ERROR);
log_cb.ex = nop_ex;
}
static void daemon_entry(int ppid) {
android_logging();
magisk_logging();
int fd = xopen("/dev/null", O_WRONLY);
xdup2(fd, STDOUT_FILENO);

View File

@@ -353,4 +353,23 @@ LOCAL_SRC_FILES := \
pcre/dist2/src/pcre2_xclass.c
include $(BUILD_STATIC_LIBRARY)
include $(LOCAL_PATH)/mincrypt/Android.mk
# libxhook.a
include $(CLEAR_VARS)
LOCAL_MODULE:= libxhook
LOCAL_C_INCLUDES := $(LOCAL_PATH)/xhook/libxhook/jni
LOCAL_EXPORT_C_INCLUDES := $(LOCAL_C_INCLUDES)
LOCAL_CFLAGS := -Wall -Wextra -Werror -fvisibility=hidden
LOCAL_CONLYFLAGS := -std=c11
LOCAL_SRC_FILES := \
xhook/libxhook/jni/xh_log.c \
xhook/libxhook/jni/xh_version.c \
xhook/libxhook/jni/xh_jni.c \
xhook/libxhook/jni/xhook.c \
xhook/libxhook/jni/xh_core.c \
xhook/libxhook/jni/xh_util.c \
xhook/libxhook/jni/xh_elf.c
include $(BUILD_STATIC_LIBRARY)
CWD := $(LOCAL_PATH)
include $(CWD)/systemproperties/Android.mk
include $(CWD)/mincrypt/Android.mk

1
native/jni/external/xhook vendored Submodule

View File

@@ -36,3 +36,4 @@ int magiskhide_main(int argc, char *argv[]);
int magiskpolicy_main(int argc, char *argv[]);
int su_client_main(int argc, char *argv[]);
int resetprop_main(int argc, char *argv[]);
int app_process_main(int argc, char *argv[]);

View File

@@ -160,6 +160,8 @@ void load_kernel_info(cmdline *cmd) {
cmd->skip_initramfs = true;
} else if (key == "androidboot.force_normal_boot") {
cmd->force_normal_boot = value[0] == '1';
} else if (key == "rootwait") {
cmd->rootwait = true;
} else if (key == "androidboot.android_dt_dir") {
strcpy(cmd->dt_dir, value);
} else if (key == "androidboot.hardware") {
@@ -174,6 +176,7 @@ void load_kernel_info(cmdline *cmd) {
LOGD("Kernel cmdline info:\n");
LOGD("skip_initramfs=[%d]\n", cmd->skip_initramfs);
LOGD("force_normal_boot=[%d]\n", cmd->force_normal_boot);
LOGD("rootwait=[%d]\n", cmd->rootwait);
LOGD("slot=[%s]\n", cmd->slot);
LOGD("dt_dir=[%s]\n", cmd->dt_dir);
LOGD("fstab_suffix=[%s]\n", cmd->fstab_suffix);

View File

@@ -5,6 +5,7 @@
struct cmdline {
bool skip_initramfs;
bool force_normal_boot;
bool rootwait;
char slot[3];
char dt_dir[64];
char fstab_suffix[32];

View File

@@ -309,6 +309,8 @@ void SARBase::backup_files() {
void SARBase::mount_system_root() {
LOGD("Early mount system_root\n");
strcpy(blk_info.block_dev, "/dev/root");
do {
// Try legacy SAR dm-verity
strcpy(blk_info.partname, "vroot");
auto dev = setup_block(false);
@@ -326,6 +328,9 @@ void SARBase::mount_system_root() {
if (dev >= 0)
goto mount_root;
// Poll forever if rootwait was given in cmdline
} while (cmd->rootwait);
// We don't really know what to do at this point...
LOGE("Cannot find root partition, abort\n");
exit(1);

View File

@@ -68,7 +68,7 @@ static void load_overlay_rc(const char *overlay) {
// Do not allow overwrite init.rc
unlinkat(dfd, "init.rc", 0);
for (dirent *entry; (entry = xreaddir(dir.get()));) {
if (strend(entry->d_name, ".rc") == 0) {
if (str_ends(entry->d_name, ".rc")) {
LOGD("Found rc script [%s]\n", entry->d_name);
int rc = xopenat(dfd, entry->d_name, O_RDONLY | O_CLOEXEC);
rc_list.push_back(fd_full_read(rc));

151
native/jni/inject/entry.cpp Normal file
View File

@@ -0,0 +1,151 @@
#include <libgen.h>
#include <dlfcn.h>
#include <sys/mount.h>
#include <sys/sendfile.h>
#include <sys/prctl.h>
#include <android/log.h>
#include <atomic>
#include <utils.hpp>
#include "inject.hpp"
using namespace std;
static void *self_handle = nullptr;
static atomic<int> active_threads = -1;
#define alog(prio) [](auto fmt, auto ap){ \
return __android_log_vprint(ANDROID_LOG_##prio, "Magisk", fmt, ap); }
static void inject_logging() {
log_cb.d = alog(DEBUG);
log_cb.i = alog(INFO);
log_cb.w = alog(WARN);
log_cb.e = alog(ERROR);
log_cb.ex = nop_ex;
}
__attribute__((destructor))
static void inject_cleanup() {
if (active_threads < 0)
return;
// Setup 1ms
timespec ts = { .tv_sec = 0, .tv_nsec = 1000000L };
// Check flag in busy loop
while (active_threads)
nanosleep(&ts, nullptr);
// Wait another 1ms to make sure all threads left our code
nanosleep(&ts, nullptr);
}
void self_unload() {
LOGD("hook: Request to self unload\n");
// If unhook failed, do not unload or else it will cause SIGSEGV
if (!unhook_functions())
return;
new_daemon_thread(reinterpret_cast<void *(*)(void *)>(&dlclose), self_handle);
active_threads--;
}
static void *unload_first_stage(void *) {
// Setup 1ms
timespec ts = { .tv_sec = 0, .tv_nsec = 1000000L };
while (getenv(INJECT_ENV_1))
nanosleep(&ts, nullptr);
// Wait another 1ms to make sure all threads left our code
nanosleep(&ts, nullptr);
unmap_all(INJECT_LIB_1);
active_threads--;
return nullptr;
}
// Make sure /proc/self/environ does not reveal our secrets
// Copy all env to a contiguous memory and set the memory region as MM_ENV
static void sanitize_environ() {
static string env;
for (int i = 0; environ[i]; ++i) {
if (str_starts(environ[i], INJECT_ENV_1 "="))
continue;
env += environ[i];
env += '\0';
}
for (int i = 0; i < 2; ++i) {
bool success = true;
success &= (0 <= prctl(PR_SET_MM, PR_SET_MM_ENV_START, env.data(), 0, 0));
success &= (0 <= prctl(PR_SET_MM, PR_SET_MM_ENV_END, env.data() + env.size(), 0, 0));
if (success)
break;
}
}
__attribute__((constructor))
static void inject_init() {
inject_logging();
if (getenv(INJECT_ENV_2)) {
LOGD("zygote: inject 2nd stage\n");
active_threads = 1;
unsetenv(INJECT_ENV_2);
// Get our own handle
self_handle = dlopen(INJECT_LIB_2, RTLD_LAZY);
dlclose(self_handle);
hook_functions();
// Some cleanup
sanitize_environ();
active_threads++;
new_daemon_thread(&unload_first_stage);
} else if (char *env = getenv(INJECT_ENV_1)) {
LOGD("zygote: inject 1st stage\n");
if (env[0] == '1')
unsetenv("LD_PRELOAD");
else
setenv("LD_PRELOAD", env, 1); // Restore original LD_PRELOAD
// Setup second stage
setenv(INJECT_ENV_2, "1", 1);
cp_afc(INJECT_LIB_1, INJECT_LIB_2);
dlopen(INJECT_LIB_2, RTLD_LAZY);
unsetenv(INJECT_ENV_1);
}
}
int app_process_main(int argc, char *argv[]) {
inject_logging();
char buf[4096];
if (realpath("/proc/self/exe", buf) == nullptr)
return 1;
int in = xopen(buf, O_RDONLY);
int out = xopen(INJECT_LIB_1, O_CREAT | O_WRONLY | O_TRUNC, 0777);
sendfile(out, in, nullptr, INT_MAX);
close(in);
close(out);
if (char *ld = getenv("LD_PRELOAD")) {
char env[128];
sprintf(env, "%s:" INJECT_LIB_1, ld);
setenv("LD_PRELOAD", env, 1);
setenv(INJECT_ENV_1, ld, 1); // Backup original LD_PRELOAD
} else {
setenv("LD_PRELOAD", INJECT_LIB_1, 1);
setenv(INJECT_ENV_1, "1", 1);
}
// Execute real app_process
xumount2(buf, MNT_DETACH);
execve(buf, argv, environ);
return 1;
}

247
native/jni/inject/hook.cpp Normal file
View File

@@ -0,0 +1,247 @@
#include <jni.h>
#include <xhook.h>
#include <utils.hpp>
#include <flags.hpp>
#include "inject.hpp"
using namespace std;
#define DCL_HOOK_FUNC(ret, func, ...) \
static ret (*old_##func)(__VA_ARGS__); \
static ret new_##func(__VA_ARGS__)
#define DCL_JNI_FUNC(name) \
static const JNINativeMethod *name##_orig = nullptr; \
extern const JNINativeMethod name##_methods[]; \
extern const int name##_methods_num;
// For some reason static vectors won't work, use pointers instead
static vector<tuple<const char *, const char *, void **>> *xhook_list;
static vector<JNINativeMethod> *jni_list;
static JavaVM *g_jvm;
static int prev_fork_pid = -1;
namespace {
struct HookContext {
int pid;
bool unload;
};
// JNI method declarations
DCL_JNI_FUNC(nativeForkAndSpecialize)
DCL_JNI_FUNC(nativeSpecializeAppProcess)
DCL_JNI_FUNC(nativeForkSystemServer)
}
#define HOOK_JNI(method) \
if (newMethods[i].name == #method##sv) { \
auto orig = new JNINativeMethod(); \
memcpy(orig, &newMethods[i], sizeof(JNINativeMethod)); \
method##_orig = orig; \
jni_list->push_back(newMethods[i]); \
for (int j = 0; j < method##_methods_num; ++j) { \
if (strcmp(newMethods[i].signature, method##_methods[j].signature) == 0) { \
newMethods[i] = method##_methods[j]; \
LOGI("hook: replaced #" #method "\n"); \
++hooked; \
break; \
} \
} \
continue; \
}
DCL_HOOK_FUNC(int, jniRegisterNativeMethods,
JNIEnv *env, const char *className, const JNINativeMethod *methods, int numMethods) {
LOGD("hook: jniRegisterNativeMethods %s", className);
unique_ptr<JNINativeMethod[]> newMethods;
int hooked = 0;
if (g_jvm == nullptr) {
// Save for later unhooking
env->GetJavaVM(&g_jvm);
}
if (className == "com/android/internal/os/Zygote"sv) {
newMethods = make_unique<JNINativeMethod[]>(numMethods);
memcpy(newMethods.get(), methods, sizeof(JNINativeMethod) * numMethods);
for (int i = 0; i < numMethods && hooked < 3; ++i) {
HOOK_JNI(nativeForkAndSpecialize);
HOOK_JNI(nativeSpecializeAppProcess);
HOOK_JNI(nativeForkSystemServer);
}
}
return old_jniRegisterNativeMethods(env, className, newMethods.get() ?: methods, numMethods);
}
DCL_HOOK_FUNC(int, fork) {
if (prev_fork_pid < 0)
return old_fork();
// Skip an actual fork and return the previous fork result
int pid = prev_fork_pid;
prev_fork_pid = -1;
return pid;
}
static int sigmask(int how, int signum) {
sigset_t set;
sigemptyset(&set);
sigaddset(&set, signum);
return sigprocmask(how, &set, nullptr);
}
static int pre_specialize_fork() {
// First block SIGCHLD, unblock after original fork is done
sigmask(SIG_BLOCK, SIGCHLD);
prev_fork_pid = old_fork();
return prev_fork_pid;
}
// -----------------------------------------------------------------
static void nativeForkAndSpecialize_pre(HookContext *ctx,
JNIEnv *env, jclass clazz, jint &uid, jint &gid, jintArray &gids, jint &runtime_flags,
jobjectArray &rlimits, jint &mount_external, jstring &se_info, jstring &nice_name,
jintArray &fds_to_close, jintArray &fds_to_ignore, /* These 2 arguments are unique to fork */
jboolean &is_child_zygote, jstring &instruction_set, jstring &app_data_dir,
jboolean &is_top_app, jobjectArray &pkg_data_info_list,
jobjectArray &whitelisted_data_info_list, jboolean &mount_data_dirs,
jboolean &mount_storage_dirs) {
// Do our own fork before loading any 3rd party code
ctx->pid = pre_specialize_fork();
if (ctx->pid != 0)
return;
// TODO: check if we need to do hiding
// Demonstrate self unload in child process
ctx->unload = true;
LOGD("hook: %s\n", __FUNCTION__);
}
static void nativeForkAndSpecialize_post(HookContext *ctx, JNIEnv *env, jclass clazz) {
// Unblock SIGCHLD in case the original method didn't
sigmask(SIG_UNBLOCK, SIGCHLD);
if (ctx->pid != 0)
return;
LOGD("hook: %s\n", __FUNCTION__);
if (ctx->unload)
self_unload();
}
// -----------------------------------------------------------------
static void nativeSpecializeAppProcess_pre(HookContext *ctx,
JNIEnv *env, jclass clazz, jint &uid, jint &gid, jintArray &gids, jint &runtime_flags,
jobjectArray &rlimits, jint &mount_external, jstring &se_info, jstring &nice_name,
jboolean &is_child_zygote, jstring &instruction_set, jstring &app_data_dir,
jboolean &is_top_app, jobjectArray &pkg_data_info_list,
jobjectArray &whitelisted_data_info_list, jboolean &mount_data_dirs,
jboolean &mount_storage_dirs) {
LOGD("hook: %s\n", __FUNCTION__);
}
static void nativeSpecializeAppProcess_post(HookContext *ctx, JNIEnv *env, jclass clazz) {
LOGD("hook: %s\n", __FUNCTION__);
}
// -----------------------------------------------------------------
static void nativeForkSystemServer_pre(HookContext *ctx,
JNIEnv *env, jclass clazz, uid_t &uid, gid_t &gid, jintArray &gids, jint &runtime_flags,
jobjectArray &rlimits, jlong &permitted_capabilities, jlong &effective_capabilities) {
// Do our own fork before loading any 3rd party code
ctx->pid = pre_specialize_fork();
if (ctx->pid != 0)
return;
LOGD("hook: %s\n", __FUNCTION__);
}
static void nativeForkSystemServer_post(HookContext *ctx, JNIEnv *env, jclass clazz) {
// Unblock SIGCHLD in case the original method didn't
sigmask(SIG_UNBLOCK, SIGCHLD);
if (ctx->pid != 0)
return;
LOGD("hook: %s\n", __FUNCTION__);
}
// -----------------------------------------------------------------
static bool hook_refresh() {
if (xhook_refresh(0) == 0) {
xhook_clear();
LOGI("hook: xhook success\n");
return true;
} else {
LOGE("hook: xhook failed\n");
return false;
}
}
static int hook_register(const char *path, const char *symbol, void *new_func, void **old_func) {
int ret = xhook_register(path, symbol, new_func, old_func);
if (ret != 0) {
LOGE("hook: Failed to register hook \"%s\"\n", symbol);
return ret;
}
xhook_list->emplace_back(path, symbol, old_func);
return 0;
}
#define XHOOK_REGISTER(PATH_REGEX, NAME) \
hook_register(PATH_REGEX, #NAME, (void*) new_##NAME, (void **) &old_##NAME)
void hook_functions() {
#ifdef MAGISK_DEBUG
xhook_enable_debug(1);
xhook_enable_sigsegv_protection(0);
#endif
xhook_list = new remove_pointer_t<decltype(xhook_list)>();
jni_list = new remove_pointer_t<decltype(jni_list)>();
XHOOK_REGISTER(".*\\libandroid_runtime.so$", jniRegisterNativeMethods);
XHOOK_REGISTER(".*\\libandroid_runtime.so$", fork);
hook_refresh();
}
bool unhook_functions() {
JNIEnv* env;
if (g_jvm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK)
return false;
// Unhook JNI methods
if (!jni_list->empty() && old_jniRegisterNativeMethods(env,
"com/android/internal/os/Zygote",
jni_list->data(), jni_list->size()) != 0) {
LOGE("hook: Failed to register JNI hook\n");
return false;
}
delete jni_list;
// Unhook xhook
for (auto &[path, sym, old_func] : *xhook_list) {
if (xhook_register(path, sym, *old_func, nullptr) != 0) {
LOGE("hook: Failed to register hook \"%s\"\n", sym);
return false;
}
}
delete xhook_list;
return hook_refresh();
}
#include "jni_hooks.hpp"

View File

@@ -0,0 +1,22 @@
#pragma once
#include <stdint.h>
#include <jni.h>
#define INJECT_LIB_1 "/dev/tmp/magisk.1.so"
#define INJECT_LIB_2 "/dev/tmp/magisk.2.so"
#define INJECT_ENV_1 "MAGISK_INJ_1"
#define INJECT_ENV_2 "MAGISK_INJ_2"
// Unmap all pages matching the name
void unmap_all(const char *name);
// Get library name and base address that contains the function
uintptr_t get_function_lib(uintptr_t addr, char *lib);
// Get library base address with name
uintptr_t get_remote_lib(int pid, const char *lib);
void self_unload();
void hook_functions();
bool unhook_functions();

View File

@@ -0,0 +1,366 @@
/*
* Original code: https://github.com/RikkaApps/Riru/blob/master/riru/src/main/cpp/jni_native_method.cpp
* The code is modified and sublicensed to GPLv3 for incorporating into Magisk.
*
* Copyright (c) 2018-2021, RikkaW
* Copyright (c) 2021, John 'topjohnwu' Wu
*/
#define ENABLE_LEGACY_DP 0 // Nobody should use outdated developer preview...
// All possible missing arguments
static union {
struct {
jintArray fds_to_ignore;
jboolean is_child_zygote;
jboolean is_top_app;
jobjectArray pkg_data_info_list;
jobjectArray whitelisted_data_info_list;
jboolean mount_data_dirs;
jboolean mount_storage_dirs;
};
size_t args_buf[8]; // Easy access to wipe all variables at once
};
#define DCL_JNI(ret, name, sig, ...) \
const static char name##_sig[] = sig; \
static ret name(__VA_ARGS__)
// -----------------------------------------------------------------
#define pre_fork() \
HookContext ctx{}; \
memset(args_buf, 0, sizeof(args_buf)); \
nativeForkAndSpecialize_pre(&ctx, env, clazz, uid, gid, gids, runtime_flags, \
rlimits, mount_external, se_info, nice_name, fds_to_close, fds_to_ignore, is_child_zygote, \
instruction_set, app_data_dir, is_top_app, pkg_data_info_list, whitelisted_data_info_list, \
mount_data_dirs, mount_storage_dirs)
#define orig_fork(ver, ...) \
reinterpret_cast<decltype(&nativeForkAndSpecialize_##ver)> \
(nativeForkAndSpecialize_orig->fnPtr)(__VA_ARGS__)
#define post_fork() \
nativeForkAndSpecialize_post(&ctx, env, clazz); \
return ctx.pid
#define DCL_FORK_AND_SPECIALIZE(ver, sig, ...) \
DCL_JNI(jint, nativeForkAndSpecialize_##ver, sig, __VA_ARGS__)
DCL_FORK_AND_SPECIALIZE(m,
"(II[II[[IILjava/lang/String;Ljava/lang/String;[ILjava/lang/String;Ljava/lang/String;)I",
JNIEnv *env, jclass clazz, jint uid, jint gid, jintArray gids, jint runtime_flags,
jobjectArray rlimits, jint mount_external, jstring se_info, jstring nice_name,
jintArray fds_to_close, jstring instruction_set, jstring app_data_dir) {
pre_fork();
orig_fork(m, env, clazz, uid, gid, gids, runtime_flags, rlimits, mount_external,
se_info, nice_name, fds_to_close, instruction_set, app_data_dir);
post_fork();
}
DCL_FORK_AND_SPECIALIZE(o,
"(II[II[[IILjava/lang/String;Ljava/lang/String;[I[ILjava/lang/String;Ljava/lang/String;)I",
JNIEnv *env, jclass clazz, jint uid, jint gid, jintArray gids, jint runtime_flags,
jobjectArray rlimits, jint mount_external, jstring se_info, jstring nice_name,
jintArray fds_to_close, jintArray fds_to_ignore, jstring instruction_set, jstring app_data_dir) {
pre_fork();
orig_fork(o, env, clazz, uid, gid, gids, runtime_flags, rlimits, mount_external,
se_info, nice_name, fds_to_close, fds_to_ignore, instruction_set, app_data_dir);
post_fork();
}
DCL_FORK_AND_SPECIALIZE(p,
"(II[II[[IILjava/lang/String;Ljava/lang/String;[I[IZLjava/lang/String;Ljava/lang/String;)I",
JNIEnv *env, jclass clazz, jint uid, jint gid, jintArray gids, jint runtime_flags,
jobjectArray rlimits, jint mount_external, jstring se_info, jstring nice_name,
jintArray fds_to_close, jintArray fds_to_ignore, jboolean is_child_zygote,
jstring instruction_set, jstring app_data_dir) {
pre_fork();
orig_fork(p, env, clazz, uid, gid, gids, runtime_flags, rlimits, mount_external, se_info,
nice_name, fds_to_close, fds_to_ignore, is_child_zygote, instruction_set, app_data_dir);
post_fork();
}
DCL_FORK_AND_SPECIALIZE(q_alt,
"(II[II[[IILjava/lang/String;Ljava/lang/String;[I[IZLjava/lang/String;Ljava/lang/String;Z)I",
JNIEnv *env, jclass clazz, jint uid, jint gid, jintArray gids, jint runtime_flags,
jobjectArray rlimits, jint mount_external, jstring se_info, jstring nice_name,
jintArray fds_to_close, jintArray fds_to_ignore, jboolean is_child_zygote,
jstring instruction_set, jstring app_data_dir, jboolean is_top_app) {
pre_fork();
orig_fork(q_alt, env, clazz, uid, gid, gids, runtime_flags, rlimits, mount_external, se_info,
nice_name, fds_to_close, fds_to_ignore, is_child_zygote, instruction_set, app_data_dir, is_top_app);
post_fork();
}
#if ENABLE_LEGACY_DP
DCL_FORK_AND_SPECIALIZE(r_dp2,
"(II[II[[IILjava/lang/String;Ljava/lang/String;[I[IZLjava/lang/String;Ljava/lang/String;Z[Ljava/lang/String;)I",
JNIEnv *env, jclass clazz, jint uid, jint gid, jintArray gids, jint runtime_flags,
jobjectArray rlimits, jint mount_external, jstring se_info, jstring nice_name,
jintArray fds_to_close, jintArray fds_to_ignore, jboolean is_child_zygote,
jstring instruction_set, jstring app_data_dir, jboolean is_top_app, jobjectArray pkg_data_info_list) {
pre_fork();
orig_fork(r_dp2, env, clazz, uid, gid, gids, runtime_flags, rlimits, mount_external, se_info,
nice_name, fds_to_close, fds_to_ignore, is_child_zygote, instruction_set, app_data_dir,
is_top_app, pkg_data_info_list);
post_fork();
}
DCL_FORK_AND_SPECIALIZE(r_dp3,
"(II[II[[IILjava/lang/String;Ljava/lang/String;[I[IZLjava/lang/String;Ljava/lang/String;Z[Ljava/lang/String;Z)I",
JNIEnv *env, jclass clazz, jint uid, jint gid, jintArray gids, jint runtime_flags,
jobjectArray rlimits, jint mount_external, jstring se_info, jstring nice_name,
jintArray fds_to_close, jintArray fds_to_ignore, jboolean is_child_zygote,
jstring instruction_set, jstring app_data_dir, jboolean is_top_app, jobjectArray pkg_data_info_list,
jboolean mount_storage_dirs) {
pre_fork();
orig_fork(r_dp3, env, clazz, uid, gid, gids, runtime_flags, rlimits, mount_external,
se_info, nice_name, fds_to_close, fds_to_ignore, is_child_zygote, instruction_set,
app_data_dir, is_top_app, pkg_data_info_list, mount_storage_dirs);
post_fork();
}
#endif // ENABLE_LEGACY_DP
DCL_FORK_AND_SPECIALIZE(r,
"(II[II[[IILjava/lang/String;Ljava/lang/String;[I[IZLjava/lang/String;Ljava/lang/String;Z[Ljava/lang/String;[Ljava/lang/String;ZZ)I",
JNIEnv *env, jclass clazz, jint uid, jint gid, jintArray gids, jint runtime_flags,
jobjectArray rlimits, jint mount_external, jstring se_info, jstring nice_name,
jintArray fds_to_close, jintArray fds_to_ignore, jboolean is_child_zygote,
jstring instruction_set, jstring app_data_dir, jboolean is_top_app, jobjectArray pkg_data_info_list,
jobjectArray whitelisted_data_info_list, jboolean mount_data_dirs, jboolean mount_storage_dirs) {
pre_fork();
orig_fork(r, env, clazz, uid, gid, gids, runtime_flags, rlimits, mount_external, se_info,
nice_name, fds_to_close, fds_to_ignore, is_child_zygote, instruction_set, app_data_dir, is_top_app,
pkg_data_info_list, whitelisted_data_info_list, mount_data_dirs, mount_storage_dirs);
post_fork();
}
DCL_FORK_AND_SPECIALIZE(samsung_m,
"(II[II[[IILjava/lang/String;IILjava/lang/String;[ILjava/lang/String;Ljava/lang/String;)I",
JNIEnv *env, jclass clazz, jint uid, jint gid, jintArray gids, jint runtime_flags,
jobjectArray rlimits, jint mount_external, jstring se_info, jint category, jint accessInfo,
jstring nice_name, jintArray fds_to_close, jstring instruction_set, jstring app_data_dir) {
pre_fork();
orig_fork(samsung_m, env, clazz, uid, gid, gids, runtime_flags, rlimits, mount_external,
se_info, category, accessInfo, nice_name, fds_to_close, instruction_set, app_data_dir);
post_fork();
}
DCL_FORK_AND_SPECIALIZE(samsung_n,
"(II[II[[IILjava/lang/String;IILjava/lang/String;[ILjava/lang/String;Ljava/lang/String;I)I",
JNIEnv *env, jclass clazz, jint uid, jint gid, jintArray gids, jint runtime_flags,
jobjectArray rlimits, jint mount_external, jstring se_info, jint category, jint accessInfo,
jstring nice_name, jintArray fds_to_close, jstring instruction_set, jstring app_data_dir, jint a1) {
pre_fork();
orig_fork(samsung_n, env, clazz, uid, gid, gids, runtime_flags, rlimits, mount_external,
se_info, category, accessInfo, nice_name, fds_to_close, instruction_set, app_data_dir, a1);
post_fork();
}
DCL_FORK_AND_SPECIALIZE(samsung_o,
"(II[II[[IILjava/lang/String;IILjava/lang/String;[I[ILjava/lang/String;Ljava/lang/String;)I",
JNIEnv *env, jclass clazz, jint uid, jint gid, jintArray gids, jint runtime_flags,
jobjectArray rlimits, jint mount_external, jstring se_info, jint category, jint accessInfo,
jstring nice_name, jintArray fds_to_close, jintArray fds_to_ignore, jstring instruction_set,
jstring app_data_dir) {
pre_fork();
orig_fork(samsung_o, env, clazz, uid, gid, gids, runtime_flags, rlimits, mount_external,
se_info, category, accessInfo, nice_name, fds_to_close, fds_to_ignore,
instruction_set, app_data_dir);
post_fork();
}
DCL_FORK_AND_SPECIALIZE(samsung_p,
"(II[II[[IILjava/lang/String;IILjava/lang/String;[I[IZLjava/lang/String;Ljava/lang/String;)I",
JNIEnv *env, jclass clazz, jint uid, jint gid, jintArray gids, jint runtime_flags,
jobjectArray rlimits, jint mount_external, jstring se_info, jint category, jint accessInfo,
jstring nice_name, jintArray fds_to_close, jintArray fds_to_ignore, jboolean is_child_zygote,
jstring instruction_set, jstring app_data_dir) {
pre_fork();
orig_fork(samsung_p, env, clazz, uid, gid, gids, runtime_flags, rlimits, mount_external,
se_info, category, accessInfo, nice_name, fds_to_close, fds_to_ignore, is_child_zygote,
instruction_set, app_data_dir);
post_fork();
}
#define DEF_FORK(ver) { \
"nativeForkAndSpecialize", \
nativeForkAndSpecialize_##ver##_sig, \
(void *) &nativeForkAndSpecialize_##ver \
}
// -----------------------------------------------------------------
#define pre_spec() \
HookContext ctx{}; \
memset(args_buf, 0, sizeof(args_buf)); \
nativeSpecializeAppProcess_pre(&ctx, \
env, clazz, uid, gid, gids, runtime_flags, rlimits, mount_external, se_info, nice_name, \
is_child_zygote, instruction_set, app_data_dir, is_top_app, pkg_data_info_list, \
whitelisted_data_info_list, mount_data_dirs, mount_storage_dirs)
#define orig_spec(ver, ...) \
reinterpret_cast<decltype(&nativeSpecializeAppProcess_##ver)> \
(nativeSpecializeAppProcess_orig->fnPtr)(__VA_ARGS__)
#define post_spec() \
nativeSpecializeAppProcess_post(&ctx, env, clazz)
#define DCL_SPECIALIZE_APP(ver, sig, ...) \
DCL_JNI(void, nativeSpecializeAppProcess_##ver, sig, __VA_ARGS__)
DCL_SPECIALIZE_APP(q,
"(II[II[[IILjava/lang/String;Ljava/lang/String;ZLjava/lang/String;Ljava/lang/String;)V",
JNIEnv *env, jclass clazz, jint uid, jint gid, jintArray gids, jint runtime_flags,
jobjectArray rlimits, jint mount_external, jstring se_info, jstring nice_name,
jboolean is_child_zygote, jstring instruction_set, jstring app_data_dir) {
pre_spec();
orig_spec(q, env, clazz, uid, gid, gids, runtime_flags, rlimits, mount_external, se_info,
nice_name, is_child_zygote, instruction_set, app_data_dir);
post_spec();
}
DCL_SPECIALIZE_APP(q_alt,
"(II[II[[IILjava/lang/String;Ljava/lang/String;ZLjava/lang/String;Ljava/lang/String;Z)V",
JNIEnv *env, jclass clazz, jint uid, jint gid, jintArray gids, jint runtime_flags,
jobjectArray rlimits, jint mount_external, jstring se_info, jstring nice_name,
jboolean is_child_zygote, jstring instruction_set, jstring app_data_dir,
jboolean is_top_app) {
pre_spec();
orig_spec(q_alt, env, clazz, uid, gid, gids, runtime_flags, rlimits, mount_external, se_info,
nice_name, is_child_zygote, instruction_set, app_data_dir, is_top_app);
post_spec();
}
#if ENABLE_LEGACY_DP
DCL_SPECIALIZE_APP(r_dp2,
"(II[II[[IILjava/lang/String;Ljava/lang/String;ZLjava/lang/String;Ljava/lang/String;Z[Ljava/lang/String;)V",
JNIEnv *env, jclass clazz, jint uid, jint gid, jintArray gids, jint runtime_flags,
jobjectArray rlimits, jint mount_external, jstring se_info, jstring nice_name,
jboolean is_child_zygote, jstring instruction_set, jstring app_data_dir,
jboolean is_top_app, jobjectArray pkg_data_info_list) {
pre_spec();
orig_spec(r_dp2, env, clazz, uid, gid, gids, runtime_flags, rlimits, mount_external, se_info,
nice_name, is_child_zygote, instruction_set, app_data_dir, is_top_app, pkg_data_info_list);
post_spec();
}
DCL_SPECIALIZE_APP(r_dp3,
"(II[II[[IILjava/lang/String;Ljava/lang/String;ZLjava/lang/String;Ljava/lang/String;Z[Ljava/lang/String;Z)V",
JNIEnv *env, jclass clazz, jint uid, jint gid, jintArray gids, jint runtime_flags,
jobjectArray rlimits, jint mount_external, jstring se_info, jstring nice_name,
jboolean is_child_zygote, jstring instruction_set, jstring app_data_dir,
jboolean is_top_app, jobjectArray pkg_data_info_list, jboolean mount_storage_dirs) {
pre_spec();
orig_spec(r_dp3, env, clazz, uid, gid, gids, runtime_flags, rlimits, mount_external, se_info,
nice_name, is_child_zygote, instruction_set, app_data_dir, is_top_app, pkg_data_info_list,
mount_storage_dirs);
post_spec();
}
#endif // ENABLE_LEGACY_DP
DCL_SPECIALIZE_APP(r,
"(II[II[[IILjava/lang/String;Ljava/lang/String;ZLjava/lang/String;Ljava/lang/String;Z[Ljava/lang/String;[Ljava/lang/String;ZZ)V",
JNIEnv *env, jclass clazz, jint uid, jint gid, jintArray gids, jint runtime_flags,
jobjectArray rlimits, jint mount_external, jstring se_info, jstring nice_name,
jboolean is_child_zygote, jstring instruction_set, jstring app_data_dir,
jboolean is_top_app, jobjectArray pkg_data_info_list, jobjectArray whitelisted_data_info_list,
jboolean mount_data_dirs, jboolean mount_storage_dirs) {
pre_spec();
orig_spec(r, env, clazz, uid, gid, gids, runtime_flags, rlimits, mount_external, se_info, nice_name,
is_child_zygote, instruction_set, app_data_dir, is_top_app, pkg_data_info_list,
whitelisted_data_info_list, mount_data_dirs, mount_storage_dirs);
post_spec();
}
DCL_SPECIALIZE_APP(samsung_q,
"(II[II[[IILjava/lang/String;IILjava/lang/String;ZLjava/lang/String;Ljava/lang/String;)V",
JNIEnv *env, jclass clazz, jint uid, jint gid, jintArray gids, jint runtime_flags,
jobjectArray rlimits, jint mount_external, jstring se_info, jint space, jint accessInfo,
jstring nice_name, jboolean is_child_zygote, jstring instruction_set, jstring app_data_dir) {
pre_spec();
orig_spec(samsung_q, env, clazz, uid, gid, gids, runtime_flags, rlimits, mount_external,
se_info, space, accessInfo, nice_name, is_child_zygote, instruction_set, app_data_dir);
post_spec();
}
#define DEF_SPEC(ver) { \
"nativeSpecializeAppProcess", \
nativeSpecializeAppProcess_##ver##_sig, \
(void *) &nativeSpecializeAppProcess_##ver \
}
// -----------------------------------------------------------------
#define pre_server() \
HookContext ctx{}; \
memset(args_buf, 0, sizeof(args_buf)); \
nativeForkSystemServer_pre(&ctx, env, clazz, uid, gid, gids, runtime_flags, \
rlimits, permitted_capabilities, effective_capabilities)
#define orig_server(ver, ...) \
reinterpret_cast<decltype(&nativeForkSystemServer_##ver)> \
(nativeForkSystemServer_orig->fnPtr)(__VA_ARGS__)
#define post_server() \
nativeForkSystemServer_post(&ctx, env, clazz); \
return ctx.pid
#define DCL_FORK_SERVER(ver, sig, ...) \
DCL_JNI(jint, nativeForkSystemServer_##ver, sig, __VA_ARGS__)
DCL_FORK_SERVER(m, "(II[II[[IJJ)I",
JNIEnv *env, jclass clazz, uid_t uid, gid_t gid, jintArray gids, jint runtime_flags,
jobjectArray rlimits, jlong permitted_capabilities, jlong effective_capabilities) {
pre_server();
orig_server(m, env, clazz, uid, gid, gids, runtime_flags, rlimits, permitted_capabilities,
effective_capabilities);
post_server();
}
DCL_FORK_SERVER(samsung_q, "(II[IIII[[IJJ)I",
JNIEnv *env, jclass clazz, uid_t uid, gid_t gid, jintArray gids, jint runtime_flags,
jint space, jint accessInfo, jobjectArray rlimits, jlong permitted_capabilities,
jlong effective_capabilities) {
pre_server();
orig_server(samsung_q, env, clazz, uid, gid, gids, runtime_flags, space, accessInfo, rlimits,
permitted_capabilities, effective_capabilities);
post_server();
}
#define DEF_SERVER(ver) { \
"nativeForkSystemServer", \
nativeForkSystemServer_##ver##_sig, \
(void *) &nativeForkSystemServer_##ver \
}
namespace {
const JNINativeMethod nativeForkAndSpecialize_methods[] = {
DEF_FORK(m), DEF_FORK(o), DEF_FORK(p),
DEF_FORK(q_alt), DEF_FORK(r),
DEF_FORK(samsung_m), DEF_FORK(samsung_n),
DEF_FORK(samsung_o), DEF_FORK(samsung_p),
#if ENABLE_LEGACY_DP
DEF_FORK(r_dp2), DEF_FORK(r_dp3)
#endif
};
const int nativeForkAndSpecialize_methods_num = std::size(nativeForkAndSpecialize_methods);
const JNINativeMethod nativeSpecializeAppProcess_methods[] = {
DEF_SPEC(q), DEF_SPEC(q_alt),
DEF_SPEC(r), DEF_SPEC(samsung_q),
#if ENABLE_LEGACY_DP
DEF_SPEC(r_dp2), DEF_SPEC(r_dp3)
#endif
};
const int nativeSpecializeAppProcess_methods_num = std::size(
nativeSpecializeAppProcess_methods);
const JNINativeMethod nativeForkSystemServer_methods[] = {
DEF_SERVER(m), DEF_SERVER(samsung_q)
};
const int nativeForkSystemServer_methods_num = std::size(nativeForkSystemServer_methods);
}

View File

@@ -0,0 +1,241 @@
/*
* Original code: https://github.com/Chainfire/injectvm-binderjack/blob/master/app/src/main/jni/libinject/inject.cpp
* The code is heavily modified and sublicensed to GPLv3 for incorporating into Magisk.
*
* Copyright (c) 2015, Simone 'evilsocket' Margaritelli
* Copyright (c) 2015-2019, Jorrit 'Chainfire' Jongma
* Copyright (c) 2021, John 'topjohnwu' Wu
*
* See original LICENSE file from the original project for additional details:
* https://github.com/Chainfire/injectvm-binderjack/blob/master/LICENSE
*/
/*
* NOTE:
* The code in this file was originally planned to be used for some features,
* but it turned out to be unsuitable for the task. However, this shall remain
* in our arsenal in case it may be used in the future.
*/
#include <elf.h>
#include <sys/mount.h>
#include <sys/ptrace.h>
#include <sys/wait.h>
#include <utils.hpp>
#include "inject.hpp"
#include "ptrace.hpp"
using namespace std;
#if defined(__arm__)
#define CPSR_T_MASK (1u << 5)
#define PARAMS_IN_REGS 4
#elif defined(__aarch64__)
#define CPSR_T_MASK (1u << 5)
#define PARAMS_IN_REGS 8
#define pt_regs user_pt_regs
#define uregs regs
#define ARM_pc pc
#define ARM_sp sp
#define ARM_cpsr pstate
#define ARM_lr regs[30]
#define ARM_r0 regs[0]
#endif
bool _remote_read(int pid, uintptr_t addr, void *buf, size_t len) {
for (size_t i = 0; i < len; i += sizeof(long)) {
long data = xptrace(PTRACE_PEEKTEXT, pid, reinterpret_cast<void*>(addr + i));
if (data < 0)
return false;
memcpy(static_cast<uint8_t *>(buf) + i, &data, std::min(len - i, sizeof(data)));
}
return true;
}
bool _remote_write(int pid, uintptr_t addr, const void *buf, size_t len) {
for (size_t i = 0; i < len; i += sizeof(long)) {
long data = 0;
memcpy(&data, static_cast<const uint8_t *>(buf) + i, std::min(len - i, sizeof(data)));
if (xptrace(PTRACE_POKETEXT, pid, reinterpret_cast<void*>(addr + i), data) < 0)
return false;
}
return true;
}
// Get remote registers
#define remote_getregs(regs) _remote_getregs(pid, regs)
static void _remote_getregs(int pid, pt_regs *regs) {
#if defined(__LP64__)
uintptr_t regset = NT_PRSTATUS;
iovec iov{};
iov.iov_base = regs;
iov.iov_len = sizeof(*regs);
xptrace(PTRACE_GETREGSET, pid, reinterpret_cast<void*>(regset), &iov);
#else
xptrace(PTRACE_GETREGS, pid, nullptr, regs);
#endif
}
// Set remote registers
#define remote_setregs(regs) _remote_setregs(pid, regs)
static void _remote_setregs(int pid, pt_regs *regs) {
#if defined(__LP64__)
uintptr_t regset = NT_PRSTATUS;
iovec iov{};
iov.iov_base = regs;
iov.iov_len = sizeof(*regs);
xptrace(PTRACE_SETREGSET, pid, reinterpret_cast<void*>(regset), &iov);
#else
xptrace(PTRACE_SETREGS, pid, nullptr, regs);
#endif
}
uintptr_t remote_call_abi(int pid, uintptr_t func_addr, int nargs, va_list va) {
pt_regs regs, regs_bak;
// Get registers and save a backup
remote_getregs(&regs);
memcpy(&regs_bak, &regs, sizeof(regs));
// ABI dependent: Setup stack and registers to perform the call
#if defined(__arm__) || defined(__aarch64__)
// Fill R0-Rx with the first 4 (32-bit) or 8 (64-bit) parameters
for (int i = 0; (i < nargs) && (i < PARAMS_IN_REGS); ++i) {
regs.uregs[i] = va_arg(va, uintptr_t);
}
// Push remaining parameters onto stack
if (nargs > PARAMS_IN_REGS) {
regs.ARM_sp -= sizeof(uintptr_t) * (nargs - PARAMS_IN_REGS);
uintptr_t stack = regs.ARM_sp;
for (int i = PARAMS_IN_REGS; i < nargs; ++i) {
uintptr_t arg = va_arg(va, uintptr_t);
remote_write(stack, &arg, sizeof(uintptr_t));
stack += sizeof(uintptr_t);
}
}
// Set return address
regs.ARM_lr = 0;
// Set function address to call
regs.ARM_pc = func_addr;
// Setup the current processor status register
if (regs.ARM_pc & 1u) {
// thumb
regs.ARM_pc &= (~1u);
regs.ARM_cpsr |= CPSR_T_MASK;
} else {
// arm
regs.ARM_cpsr &= ~CPSR_T_MASK;
}
#elif defined(__i386__)
// Push all params onto stack
regs.esp -= sizeof(uintptr_t) * nargs;
uintptr_t stack = regs.esp;
for (int i = 0; i < nargs; ++i) {
uintptr_t arg = va_arg(va, uintptr_t);
remote_write(stack, &arg, sizeof(uintptr_t));
stack += sizeof(uintptr_t);
}
// Push return address onto stack
uintptr_t ret_addr = 0;
regs.esp -= sizeof(uintptr_t);
remote_write(regs.esp, &ret_addr, sizeof(uintptr_t));
// Set function address to call
regs.eip = func_addr;
#elif defined(__x86_64__)
// Align, rsp - 8 must be a multiple of 16 at function entry point
uintptr_t space = sizeof(uintptr_t);
if (nargs > 6)
space += sizeof(uintptr_t) * (nargs - 6);
while (((regs.rsp - space - 8) & 0xF) != 0)
regs.rsp--;
// Fill [RDI, RSI, RDX, RCX, R8, R9] with the first 6 parameters
for (int i = 0; (i < nargs) && (i < 6); ++i) {
uintptr_t arg = va_arg(va, uintptr_t);
switch (i) {
case 0: regs.rdi = arg; break;
case 1: regs.rsi = arg; break;
case 2: regs.rdx = arg; break;
case 3: regs.rcx = arg; break;
case 4: regs.r8 = arg; break;
case 5: regs.r9 = arg; break;
}
}
// Push remaining parameters onto stack
if (nargs > 6) {
regs.rsp -= sizeof(uintptr_t) * (nargs - 6);
uintptr_t stack = regs.rsp;
for(int i = 6; i < nargs; ++i) {
uintptr_t arg = va_arg(va, uintptr_t);
remote_write(stack, &arg, sizeof(uintptr_t));
stack += sizeof(uintptr_t);
}
}
// Push return address onto stack
uintptr_t ret_addr = 0;
regs.rsp -= sizeof(uintptr_t);
remote_write(regs.rsp, &ret_addr, sizeof(uintptr_t));
// Set function address to call
regs.rip = func_addr;
// may be needed
regs.rax = 0;
regs.orig_rax = 0;
#else
#error Unsupported ABI
#endif
// Resume process to do the call
remote_setregs(&regs);
xptrace(PTRACE_CONT, pid);
// Catch SIGSEGV caused by the 0 return address
int status;
while (waitpid(pid, &status, __WALL | __WNOTHREAD) == pid) {
if (WIFSTOPPED(status) && (WSTOPSIG(status) == SIGSEGV))
break;
xptrace(PTRACE_CONT, pid);
}
// Get registers again for return value
remote_getregs(&regs);
// Restore registers
remote_setregs(&regs_bak);
#if defined(__arm__) || defined(__aarch64__)
return regs.ARM_r0;
#elif defined(__i386__)
return regs.eax;
#elif defined(__x86_64__)
return regs.rax;
#endif
}
uintptr_t remote_call_vararg(int pid, uintptr_t addr, int nargs, ...) {
char lib_name[4096];
auto local = get_function_lib(addr, lib_name);
if (local == 0)
return 0;
auto remote = get_remote_lib(pid, lib_name);
if (remote == 0)
return 0;
addr = addr - local + remote;
va_list va;
va_start(va, nargs);
auto result = remote_call_abi(pid, addr, nargs, va);
va_end(va);
return result;
}

View File

@@ -0,0 +1,27 @@
#pragma once
#include <stdint.h>
// Write bytes to the remote process at addr
bool _remote_write(int pid, uintptr_t addr, const void *buf, size_t len);
#define remote_write(...) _remote_write(pid, __VA_ARGS__)
// Read bytes from the remote process at addr
bool _remote_read(int pid, uintptr_t addr, void *buf, size_t len);
#define remote_read(...) _remote_read(pid, __VA_ARGS__)
// Call a remote function
// Arguments are expected to be only integer-like or pointer types
// as other more complex C ABIs are not implemented.
uintptr_t remote_call_abi(int pid, uintptr_t func_addr, int nargs, va_list va);
// Find remote offset and invoke function
uintptr_t remote_call_vararg(int pid, uintptr_t addr, int nargs, ...);
// C++ wrapper for auto argument counting and casting function pointers
template<class FuncPtr, class ...Args>
static uintptr_t _remote_call(int pid, FuncPtr sym, Args && ...args) {
auto addr = reinterpret_cast<uintptr_t>(sym);
return remote_call_vararg(pid, addr, sizeof...(args), std::forward<Args>(args)...);
}
#define remote_call(...) _remote_call(pid, __VA_ARGS__)

101
native/jni/inject/utils.cpp Normal file
View File

@@ -0,0 +1,101 @@
#include <utils.hpp>
#include "inject.hpp"
using namespace std;
namespace {
struct map_info {
uintptr_t start;
uintptr_t end;
int perms;
char *path;
map_info() : start(0), end(0), perms(0), path(nullptr) {}
enum {
EXEC = (1 << 0),
WRITE = (1 << 1),
READ = (1 << 2),
};
};
} // namespace
template<typename Func>
static void parse_maps(int pid, Func fn) {
char file[32];
// format: start-end perms offset dev inode path
sprintf(file, "/proc/%d/maps", pid);
file_readline(true, file, [=](string_view l) -> bool {
char *pos = (char *) l.data();
map_info info;
// Parse address hex strings
info.start = strtoul(pos, &pos, 16);
info.end = strtoul(++pos, &pos, 16);
// Parse permissions
if (*(++pos) != '-')
info.perms |= map_info::READ;
if (*(++pos) != '-')
info.perms |= map_info::WRITE;
if (*(++pos) != '-')
info.perms |= map_info::EXEC;
pos += 3;
// Skip everything except path
int path_off;
sscanf(pos, "%*s %*s %*s %n%*s", &path_off);
pos += path_off;
info.path = pos;
return fn(info);
});
}
void unmap_all(const char *name) {
vector<map_info> unmaps;
parse_maps(getpid(), [=, &unmaps](map_info &info) -> bool {
if (strcmp(info.path, name) == 0)
unmaps.emplace_back(info);
return true;
});
for (map_info &info : unmaps) {
void *addr = reinterpret_cast<void *>(info.start);
size_t size = info.end - info.start;
munmap(addr, size);
if (info.perms & map_info::READ) {
// Make sure readable pages are still readable
xmmap(addr, size, PROT_READ, MAP_ANONYMOUS | MAP_PRIVATE, 0, 0);
}
}
}
uintptr_t get_function_lib(uintptr_t addr, char *lib) {
uintptr_t base = 0;
parse_maps(getpid(), [=, &base](map_info &info) -> bool {
if (addr >= info.start && addr < info.end) {
if (lib)
strcpy(lib, info.path);
base = info.start;
return false;
}
return true;
});
return base;
}
uintptr_t get_remote_lib(int pid, const char *lib) {
uintptr_t base = 0;
parse_maps(pid, [=, &base](map_info &info) -> bool {
if (strcmp(info.path, lib) == 0 && (info.perms & map_info::EXEC)) {
base = info.start;
return false;
}
return true;
});
return base;
}

View File

@@ -30,7 +30,7 @@ static const char *late_prop_val[] =
{ "green", nullptr };
void hide_sensitive_props() {
LOGI("hide_policy: Hiding sensitive props\n");
LOGI("hide: Hiding sensitive props\n");
for (int i = 0; prop_key[i]; ++i) {
auto value = getprop(prop_key[i]);
@@ -63,7 +63,7 @@ void hide_sensitive_props() {
}
void hide_late_sensitive_props() {
LOGI("hide_policy: Hiding sensitive props (late)\n");
LOGI("hide: Hiding sensitive props (late)\n");
for (int i = 0; late_prop_key[i]; ++i) {
auto value = getprop(late_prop_key[i]);
@@ -74,9 +74,10 @@ void hide_late_sensitive_props() {
static void lazy_unmount(const char* mountpoint) {
if (umount2(mountpoint, MNT_DETACH) != -1)
LOGD("hide_policy: Unmounted (%s)\n", mountpoint);
LOGD("hide: Unmounted (%s)\n", mountpoint);
}
#if ENABLE_PTRACE_MONITOR
void hide_daemon(int pid) {
if (fork_dont_care() == 0) {
hide_unmount(pid);
@@ -85,15 +86,16 @@ void hide_daemon(int pid) {
_exit(0);
}
}
#endif
#define TMPFS_MNT(dir) (mentry->mnt_type == "tmpfs"sv && \
strncmp(mentry->mnt_dir, "/" #dir, sizeof("/" #dir) - 1) == 0)
void hide_unmount(int pid) {
if (switch_mnt_ns(pid))
if (pid > 0 && switch_mnt_ns(pid))
return;
LOGD("hide_policy: handling PID=[%d]\n", pid);
LOGD("hide: handling PID=[%d]\n", pid);
char val;
int fd = xopen(SELINUX_ENFORCE, O_RDONLY);

View File

@@ -1,11 +1,9 @@
#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <dirent.h>
#include <string.h>
#include <set>
#include <magisk.hpp>
#include <utils.hpp>
@@ -15,11 +13,46 @@
using namespace std;
static pthread_t proc_monitor_thread;
static bool hide_state = false;
static set<pair<string, string>> hide_set; /* set of <pkg, process> pair */
map<int, vector<string_view>> uid_proc_map; /* uid -> list of process */
// This locks the 2 variables above
static pthread_mutex_t hide_state_lock = PTHREAD_MUTEX_INITIALIZER;
// Locks the variables above
pthread_mutex_t hide_state_lock = PTHREAD_MUTEX_INITIALIZER;
#if ENABLE_PTRACE_MONITOR
static pthread_t monitor_thread;
#endif
void update_uid_map() {
mutex_guard lock(hide_state_lock);
uid_proc_map.clear();
string data_path(APP_DATA_DIR);
size_t len = data_path.length();
auto dir = open_dir(APP_DATA_DIR);
bool first_iter = true;
for (dirent *entry; (entry = xreaddir(dir.get()));) {
data_path.resize(len);
data_path += '/';
data_path += entry->d_name; // multiuser user id
data_path += '/';
size_t user_len = data_path.length();
struct stat st;
for (auto &hide : hide_set) {
if (hide.first == ISOLATED_MAGIC) {
if (!first_iter) continue;
// Setup isolated processes
uid_proc_map[-1].emplace_back(hide.second);
}
data_path.resize(user_len);
data_path += hide.first;
if (stat(data_path.data(), &st))
continue;
uid_proc_map[st.st_uid].emplace_back(hide.second);
}
first_iter = false;
}
}
// Leave /proc fd opened as we're going to read from it repeatedly
static DIR *procfp;
@@ -43,28 +76,27 @@ bool hide_enabled() {
return hide_state;
}
void set_hide_state(bool state) {
mutex_guard g(hide_state_lock);
hide_state = state;
}
template <bool str_op(string_view, string_view)>
static bool proc_name_match(int pid, const char *name) {
char buf[4019];
sprintf(buf, "/proc/%d/cmdline", pid);
if (FILE *f = fopen(buf, "re")) {
fgets(buf, sizeof(buf), f);
fclose(f);
if (strcmp(buf, name) == 0)
if (auto fp = open_file(buf, "re")) {
fgets(buf, sizeof(buf), fp.get());
if (str_op(buf, name)) {
LOGD("hide: kill PID=[%d] (%s)\n", pid, buf);
return true;
}
}
return false;
}
static void kill_process(const char *name, bool multi = false) {
static inline bool str_eql(string_view s, string_view ss) { return s == ss; }
static void kill_process(const char *name, bool multi = false,
bool (*filter)(int, const char *) = proc_name_match<&str_eql>) {
crawl_procfs([=](int pid) -> bool {
if (proc_name_match(pid, name)) {
if (kill(pid, SIGTERM) == 0)
LOGD("hide_utils: killed PID=[%d] (%s)\n", pid, name);
if (filter(pid, name)) {
kill(pid, SIGTERM);
return multi;
}
return true;
@@ -72,6 +104,8 @@ static void kill_process(const char *name, bool multi = false) {
}
static bool validate(const char *s) {
if (strcmp(s, ISOLATED_MAGIC) == 0)
return true;
bool dot = false;
for (char c; (c = *s); ++s) {
if ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') ||
@@ -87,7 +121,18 @@ static bool validate(const char *s) {
return dot;
}
static int add_list(const char *pkg, const char *proc = "") {
static void add_hide_set(const char *pkg, const char *proc) {
LOGI("hide_list add: [%s/%s]\n", pkg, proc);
hide_set.emplace(pkg, proc);
if (strcmp(pkg, ISOLATED_MAGIC) == 0) {
// Kill all matching isolated processes
kill_process(proc, true, proc_name_match<&str_starts>);
} else {
kill_process(proc);
}
}
static int add_list(const char *pkg, const char *proc) {
if (proc[0] == '\0')
proc = pkg;
@@ -105,15 +150,12 @@ static int add_list(const char *pkg, const char *proc = "") {
char *err = db_exec(sql);
db_err_cmd(err, return DAEMON_ERROR);
LOGI("hide_list add: [%s/%s]\n", pkg, proc);
// Critical region
{
mutex_guard lock(monitor_lock);
hide_set.emplace(pkg, proc);
// Critical region
mutex_guard lock(hide_state_lock);
add_hide_set(pkg, proc);
}
kill_process(proc);
return DAEMON_SUCCESS;
}
@@ -123,27 +165,28 @@ int add_list(int client) {
int ret = add_list(pkg, proc);
free(pkg);
free(proc);
if (ret == DAEMON_SUCCESS)
update_uid_map();
return ret;
}
static int rm_list(const char *pkg, const char *proc = "") {
static int rm_list(const char *pkg, const char *proc) {
bool remove = false;
{
// Critical region
mutex_guard lock(monitor_lock);
bool remove = false;
mutex_guard lock(hide_state_lock);
for (auto it = hide_set.begin(); it != hide_set.end();) {
if (it->first == pkg && (proc[0] == '\0' || it->second == proc)) {
remove = true;
LOGI("hide_list rm: [%s]\n", it->second.data());
LOGI("hide_list rm: [%s/%s]\n", it->first.data(), it->second.data());
it = hide_set.erase(it);
} else {
++it;
}
}
}
if (!remove)
return HIDE_ITEM_NOT_EXIST;
}
char sql[4096];
if (proc[0] == '\0')
@@ -167,10 +210,11 @@ int rm_list(int client) {
return ret;
}
static void init_list(const char *pkg, const char *proc) {
LOGI("hide_list init: [%s/%s]\n", pkg, proc);
hide_set.emplace(pkg, proc);
kill_process(proc);
static bool str_ends_safe(string_view s, string_view ss) {
// Never kill webview zygote
if (s == "webview_zygote")
return false;
return str_ends(s, ss);
}
#define SNET_PROC "com.google.android.gms.unstable"
@@ -178,28 +222,29 @@ static void init_list(const char *pkg, const char *proc) {
#define MICROG_PKG "org.microg.gms.droidguard"
static bool init_list() {
LOGD("hide_list: initialize\n");
LOGD("hide: initialize\n");
char *err = db_exec("SELECT * FROM hidelist", [](db_row &row) -> bool {
init_list(row["package_name"].data(), row["process"].data());
add_hide_set(row["package_name"].data(), row["process"].data());
return true;
});
db_err_cmd(err, return false);
// If Android Q+, also kill blastula pool
// If Android Q+, also kill blastula pool and all app zygotes
if (SDK_INT >= 29) {
kill_process("usap32", true);
kill_process("usap64", true);
kill_process("_zygote", true, proc_name_match<&str_ends_safe>);
}
// Add SafetyNet by default
init_list(GMS_PKG, SNET_PROC);
init_list(MICROG_PKG, SNET_PROC);
add_hide_set(GMS_PKG, SNET_PROC);
add_hide_set(MICROG_PKG, SNET_PROC);
// We also need to hide the default GMS process if MAGISKTMP != /sbin
// The snet process communicates with the main process and get additional info
if (MAGISKTMP != "/sbin")
init_list(GMS_PKG, GMS_PKG);
add_hide_set(GMS_PKG, GMS_PKG);
update_uid_map();
return true;
@@ -237,10 +282,7 @@ int launch_magiskhide() {
if (procfp == nullptr && (procfp = opendir("/proc")) == nullptr)
return DAEMON_ERROR;
LOGI("* Starting MagiskHide\n");
// Initialize the mutex lock
pthread_mutex_init(&monitor_lock, nullptr);
LOGI("* Enable MagiskHide\n");
// Initialize the hide list
if (!init_list())
@@ -250,10 +292,11 @@ int launch_magiskhide() {
if (DAEMON_STATE >= STATE_BOOT_COMPLETE || DAEMON_STATE == STATE_NONE)
hide_late_sensitive_props();
#if ENABLE_PTRACE_MONITOR
// Start monitoring
void *(*start)(void*) = [](void*) -> void* { proc_monitor(); return nullptr; };
if (xpthread_create(&proc_monitor_thread, nullptr, start, nullptr))
if (new_daemon_thread(&proc_monitor))
return DAEMON_ERROR;
#endif
hide_state = true;
update_hide_config();
@@ -264,8 +307,12 @@ int stop_magiskhide() {
mutex_guard g(hide_state_lock);
if (hide_state) {
LOGI("* Stopping MagiskHide\n");
pthread_kill(proc_monitor_thread, SIGTERMTHRD);
LOGI("* Disable MagiskHide\n");
uid_proc_map.clear();
hide_set.clear();
#if ENABLE_PTRACE_MONITOR
pthread_kill(monitor_thread, SIGTERMTHRD);
#endif
}
hide_state = false;
@@ -275,7 +322,9 @@ int stop_magiskhide() {
void auto_start_magiskhide() {
if (hide_enabled()) {
pthread_kill(proc_monitor_thread, SIGALRM);
#if ENABLE_PTRACE_MONITOR
pthread_kill(monitor_thread, SIGALRM);
#endif
hide_late_sensitive_props();
} else if (SDK_INT >= 19) {
db_settings dbs;
@@ -285,8 +334,10 @@ void auto_start_magiskhide() {
}
}
#if ENABLE_PTRACE_MONITOR
void test_proc_monitor() {
if (procfp == nullptr && (procfp = opendir("/proc")) == nullptr)
exit(1);
proc_monitor();
}
#endif

View File

@@ -105,7 +105,7 @@ int magiskhide_main(int argc, char *argv[]) {
execvp(argv[2], argv + 2);
exit(1);
}
#if 0
#if 0 && ENABLE_PTRACE_MONITOR
else if (opt == "test"sv)
test_proc_monitor();
#endif

View File

@@ -5,14 +5,17 @@
#include <pthread.h>
#include <unistd.h>
#include <dirent.h>
#include <string>
#include <string_view>
#include <functional>
#include <map>
#include <set>
#include <daemon.hpp>
#define SIGTERMTHRD SIGUSR1
#define ISOLATED_MAGIC "isolated"
// Global toggle for ptrace monitor
#define ENABLE_PTRACE_MONITOR 1
// CLI entries
int launch_magiskhide();
@@ -20,26 +23,27 @@ int stop_magiskhide();
int add_list(int client);
int rm_list(int client);
void ls_list(int client);
[[noreturn]] void test_proc_monitor();
#if ENABLE_PTRACE_MONITOR
// Process monitoring
[[noreturn]] void proc_monitor();
void update_uid_map();
[[noreturn]] void test_proc_monitor();
#endif
// Utility functions
void crawl_procfs(const std::function<bool (int)> &fn);
void crawl_procfs(DIR *dir, const std::function<bool (int)> &fn);
bool hide_enabled();
void set_hide_state(bool state);
void update_uid_map();
// Hide policies
void hide_daemon(int pid);
void hide_unmount(int pid = getpid());
void hide_unmount(int pid = -1);
void hide_sensitive_props();
void hide_late_sensitive_props();
extern pthread_mutex_t monitor_lock;
extern std::set<std::pair<std::string, std::string>> hide_set;
extern pthread_mutex_t hide_state_lock;
extern std::map<int, std::vector<std::string_view>> uid_proc_map;
enum {
LAUNCH_MAGISKHIDE,

View File

@@ -1,6 +1,3 @@
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <signal.h>
@@ -21,18 +18,11 @@ using namespace std;
static int inotify_fd = -1;
static void term_thread(int sig = SIGTERMTHRD);
static void new_zygote(int pid);
/**********************
* All data structures
**********************/
set<pair<string, string>> hide_set; /* set of <pkg, process> pair */
static map<int, struct stat> zygote_map; /* zygote pid -> mnt ns */
static map<int, vector<string_view>> uid_proc_map; /* uid -> list of process */
pthread_mutex_t monitor_lock;
/******************
* Data structures
******************/
#define PID_MAX 32768
struct pid_set {
@@ -44,7 +34,10 @@ private:
};
// true if pid is monitored
pid_set attaches;
static pid_set attaches;
// zygote pid -> mnt ns
static map<int, struct stat> zygote_map;
/********
* Utils
@@ -61,51 +54,17 @@ static int parse_ppid(int pid) {
int ppid;
sprintf(path, "/proc/%d/stat", pid);
FILE *stat = fopen(path, "re");
if (stat == nullptr)
auto stat = open_file(path, "re");
if (!stat)
return -1;
// PID COMM STATE PPID .....
fscanf(stat, "%*d %*s %*c %d", &ppid);
fclose(stat);
fscanf(stat.get(), "%*d %*s %*c %d", &ppid);
return ppid;
}
static inline long xptrace(int request, pid_t pid, void *addr, void *data) {
long ret = ptrace(request, pid, addr, data);
if (ret < 0)
PLOGE("ptrace %d", pid);
return ret;
}
static inline long xptrace(int request, pid_t pid, void *addr = nullptr, intptr_t data = 0) {
return xptrace(request, pid, addr, reinterpret_cast<void *>(data));
}
void update_uid_map() {
mutex_guard lock(monitor_lock);
uid_proc_map.clear();
string data_path(APP_DATA_DIR);
size_t len = data_path.length();
auto dir = open_dir(APP_DATA_DIR);
for (dirent *entry; (entry = xreaddir(dir.get()));) {
data_path.resize(len);
data_path += '/';
data_path += entry->d_name;
data_path += '/';
size_t user_len = data_path.length();
struct stat st;
for (auto &hide : hide_set) {
data_path.resize(user_len);
data_path += hide.first;
if (stat(data_path.data(), &st))
continue;
uid_proc_map[st.st_uid].emplace_back(hide.second);
}
}
}
static bool is_zygote_done() {
#ifdef __LP64__
return zygote_map.size() >= 2;
@@ -139,7 +98,7 @@ static void check_zygote() {
static void setup_inotify() {
inotify_fd = xinotify_init1(IN_CLOEXEC);
if (inotify_fd < 0)
term_thread();
return;
// Setup inotify asynchronous I/O
fcntl(inotify_fd, F_SETFL, O_ASYNC);
@@ -167,8 +126,8 @@ static void setup_inotify() {
************************/
static void inotify_event(int) {
/* Make sure we can actually read stuffs
* or else the whole thread will be blocked.*/
// Make sure we can actually read stuffs
// or else the whole thread will be blocked.
struct pollfd pfd = {
.fd = inotify_fd,
.events = POLLIN,
@@ -187,13 +146,8 @@ static void inotify_event(int) {
// Workaround for the lack of pthread_cancel
static void term_thread(int) {
LOGD("proc_monitor: cleaning up\n");
uid_proc_map.clear();
zygote_map.clear();
hide_set.clear();
attaches.reset();
// Misc
set_hide_state(false);
pthread_mutex_destroy(&monitor_lock);
close(inotify_fd);
inotify_fd = -1;
LOGD("proc_monitor: terminate\n");
@@ -224,7 +178,7 @@ static bool check_pid(int pid) {
sprintf(path, "/proc/%d", pid);
if (stat(path, &st)) {
// Process killed unexpectedly, ignore
// Process died unexpectedly, ignore
detach_pid(pid);
return true;
}
@@ -237,7 +191,7 @@ static bool check_pid(int pid) {
if (auto f = open_file(path, "re")) {
fgets(cmdline, sizeof(cmdline), f.get());
} else {
// Process killed unexpectedly, ignore
// Process died unexpectedly, ignore
detach_pid(pid);
return true;
}
@@ -247,36 +201,44 @@ static bool check_pid(int pid) {
return false;
int uid = st.st_uid;
auto it = uid_proc_map.find(uid);
if (it != uid_proc_map.end()) {
// Start accessing uid_proc_map
mutex_guard lock(hide_state_lock);
auto it = uid_proc_map.end();
if (uid % 100000 > 90000) {
// No way to handle isolated process
goto not_target;
}
it = uid_proc_map.find(uid);
if (it == uid_proc_map.end())
goto not_target;
for (auto &s : it->second) {
if (s == cmdline) {
// Double check whether ns is separated
if (s != cmdline)
continue;
// Check if ns is separated (could be app zygote)
read_ns(pid, &st);
bool mnt_ns = true;
for (auto &zit : zygote_map) {
if (zit.second.st_ino == st.st_ino &&
zit.second.st_dev == st.st_dev) {
mnt_ns = false;
break;
// ns not separated, abort
goto not_target;
}
}
// For some reason ns is not separated, abort
if (!mnt_ns)
break;
// Finally this is our target!
// Detach from ptrace but should still remain stopped.
// The hide daemon will resume the process.
PTRACE_LOG("target found\n");
LOGI("proc_monitor: [%s] PID=[%d] UID=[%d]\n", cmdline, pid, uid);
detach_pid(pid, SIGSTOP);
hide_daemon(pid);
return true;
}
}
}
PTRACE_LOG("[%s] not our target\n", cmdline);
not_target:
PTRACE_LOG("[%s] is not our target\n", cmdline);
detach_pid(pid);
return true;
}
@@ -323,8 +285,7 @@ static void new_zygote(int pid) {
xptrace(PTRACE_CONT, pid);
}
#define WEVENT(s) (((s) & 0xffff0000) >> 16)
#define DETACH_AND_CONT { detach = true; continue; }
#define DETACH_AND_CONT { detach_pid(pid); continue; }
void proc_monitor() {
// Unblock some signals
@@ -354,9 +315,7 @@ void proc_monitor() {
setitimer(ITIMER_REAL, &interval, nullptr);
}
int status;
for (;;) {
for (int status;;) {
const int pid = waitpid(-1, &status, __WALL | __WNOTHREAD);
if (pid < 0) {
if (errno == ECHILD) {
@@ -370,38 +329,35 @@ void proc_monitor() {
}
continue;
}
bool detach = false;
run_finally f([&] {
if (detach)
// Non of our business now
detach_pid(pid);
});
if (!WIFSTOPPED(status) /* Ignore if not ptrace-stop */)
DETACH_AND_CONT;
if (WSTOPSIG(status) == SIGTRAP && WEVENT(status)) {
int event = WEVENT(status);
int signal = WSTOPSIG(status);
if (signal == SIGTRAP && event) {
unsigned long msg;
xptrace(PTRACE_GETEVENTMSG, pid, nullptr, &msg);
if (zygote_map.count(pid)) {
// Zygote event
switch (WEVENT(status)) {
switch (event) {
case PTRACE_EVENT_FORK:
case PTRACE_EVENT_VFORK:
PTRACE_LOG("zygote forked: [%d]\n", msg);
PTRACE_LOG("zygote forked: [%lu]\n", msg);
attaches[msg] = true;
break;
case PTRACE_EVENT_EXIT:
PTRACE_LOG("zygote exited with status: [%d]\n", msg);
PTRACE_LOG("zygote exited with status: [%lu]\n", msg);
[[fallthrough]];
default:
zygote_map.erase(pid);
DETACH_AND_CONT;
}
} else {
switch (WEVENT(status)) {
switch (event) {
case PTRACE_EVENT_CLONE:
PTRACE_LOG("create new threads: [%d]\n", msg);
PTRACE_LOG("create new threads: [%lu]\n", msg);
if (attaches[pid] && check_pid(pid))
continue;
break;
@@ -414,7 +370,7 @@ void proc_monitor() {
}
}
xptrace(PTRACE_CONT, pid);
} else if (WSTOPSIG(status) == SIGSTOP) {
} else if (signal == SIGSTOP) {
if (!attaches[pid]) {
// Double check if this is actually a process
attaches[pid] = is_process(pid);
@@ -432,8 +388,8 @@ void proc_monitor() {
}
} else {
// Not caused by us, resend signal
xptrace(PTRACE_CONT, pid, nullptr, WSTOPSIG(status));
PTRACE_LOG("signal [%d]\n", WSTOPSIG(status));
xptrace(PTRACE_CONT, pid, nullptr, signal);
PTRACE_LOG("signal [%d]\n", signal);
}
}
}

View File

@@ -58,12 +58,6 @@ int gen_rand_str(char *buf, int len, bool varlen) {
return len - 1;
}
int strend(const char *s1, const char *s2) {
size_t l1 = strlen(s1);
size_t l2 = strlen(s2);
return strcmp(s1 + l1 - l2, s2);
}
int exec_command(exec_t &exec) {
int pipefd[] = {-1, -1};
int outfd = -1;
@@ -119,23 +113,30 @@ int exec_command_sync(exec_t &exec) {
return WEXITSTATUS(status);
}
int new_daemon_thread(thread_entry entry, void *arg, const pthread_attr_t *attr) {
int new_daemon_thread(thread_entry entry, void *arg) {
pthread_t thread;
int ret = xpthread_create(&thread, attr, entry, arg);
if (ret == 0)
pthread_detach(thread);
return ret;
pthread_attr_t attr;
pthread_attr_init(&attr);
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
return xpthread_create(&thread, &attr, entry, arg);
}
static void *proxy_routine(void *fp) {
int new_daemon_thread(void(*entry)()) {
thread_entry proxy = [](void *entry) -> void * {
reinterpret_cast<void(*)()>(entry)();
return nullptr;
};
return new_daemon_thread(proxy, (void *) entry);
}
int new_daemon_thread(std::function<void()> &&entry) {
thread_entry proxy = [](void *fp) -> void * {
auto fn = reinterpret_cast<std::function<void()>*>(fp);
(*fn)();
delete fn;
return nullptr;
}
int new_daemon_thread(std::function<void()> &&entry) {
return new_daemon_thread(proxy_routine, new std::function<void()>(std::move(entry)));
};
return new_daemon_thread(proxy, new std::function<void()>(std::move(entry)));
}
static char *argv0;
@@ -151,12 +152,6 @@ void set_nice_name(const char *name) {
prctl(PR_SET_NAME, name);
}
bool ends_with(const std::string_view &s1, const std::string_view &s2) {
unsigned l1 = s1.length();
unsigned l2 = s2.length();
return l1 < l2 ? false : s1.compare(l1 - l2, l2, s2) == 0;
}
/*
* Bionic's atoi runs through strtol().
* Use our own implementation for faster conversion.

View File

@@ -8,9 +8,6 @@
#define UID_ROOT 0
#define UID_SHELL 2000
#define str_contains(s, ss) ((ss) != nullptr && (s).find(ss) != std::string::npos)
#define str_starts(s, ss) ((ss) != nullptr && (s).compare(0, strlen(ss), ss) == 0)
class mutex_guard {
public:
explicit mutex_guard(pthread_mutex_t &m): mutex(&m) {
@@ -62,13 +59,22 @@ static inline int parse_int(const std::string &s) { return parse_int(s.data());
static inline int parse_int(std::string_view s) { return parse_int(s.data()); }
using thread_entry = void *(*)(void *);
int new_daemon_thread(thread_entry entry, void *arg = nullptr, const pthread_attr_t *attr = nullptr);
int new_daemon_thread(thread_entry entry, void *arg = nullptr);
int new_daemon_thread(void(*entry)());
int new_daemon_thread(std::function<void()> &&entry);
bool ends_with(const std::string_view &s1, const std::string_view &s2);
static inline bool str_contains(std::string_view s, std::string_view ss) {
return s.find(ss) != std::string::npos;
}
static inline bool str_starts(std::string_view s, std::string_view ss) {
return s.rfind(ss, 0) == 0;
}
static inline bool str_ends(std::string_view s, std::string_view ss) {
return s.size() >= ss.size() && s.compare(s.size() - ss.size(), std::string::npos, ss) == 0;
}
int fork_dont_care();
int fork_no_orphan();
int strend(const char *s1, const char *s2);
void init_argv0(int argc, char **argv);
void set_nice_name(const char *name);
uint32_t binary_gcd(uint32_t u, uint32_t v);

View File

@@ -9,6 +9,7 @@
#include <sys/mount.h>
#include <sys/mman.h>
#include <sys/sendfile.h>
#include <sys/ptrace.h>
#include <utils.hpp>
@@ -470,3 +471,10 @@ int xmknod(const char *pathname, mode_t mode, dev_t dev) {
}
return ret;
}
long xptrace(int request, pid_t pid, void *addr, void *data) {
long ret = ptrace(request, pid, addr, data);
if (ret < 0)
PLOGE("ptrace %d", pid);
return ret;
}

View File

@@ -60,4 +60,8 @@ int xpoll(struct pollfd *fds, nfds_t nfds, int timeout);
int xinotify_init1(int flags);
char *xrealpath(const char *path, char *resolved_path);
int xmknod(const char *pathname, mode_t mode, dev_t dev);
long xptrace(int request, pid_t pid, void *addr = nullptr, void *data = nullptr);
static inline long xptrace(int request, pid_t pid, void *addr, uintptr_t data) {
return xptrace(request, pid, addr, reinterpret_cast<void *>(data));
}
#define WEVENT(s) (((s) & 0xffff0000) >> 16)

View File

@@ -3,12 +3,13 @@ plugins {
}
android {
val canary = !Config["appVersion"].orEmpty().contains(".")
val canary = !Config.appVersion.contains(".")
defaultConfig {
applicationId = "com.topjohnwu.magisk"
versionCode = 1
versionName = Config.appVersion
buildConfigField("int", "STUB_VERSION", "15")
buildConfigField("String", "DEV_CHANNEL", Config["DEV_CHANNEL"] ?: "null")
buildConfigField("boolean", "CANARY", if (canary) "true" else "false")
}
@@ -16,11 +17,15 @@ android {
buildTypes {
getByName("release") {
isMinifyEnabled = true
isShrinkResources = true
isShrinkResources = false
proguardFiles("proguard-rules.pro")
}
}
aaptOptions {
additionalParameters("--package-id", "0x80")
}
dependenciesInfo {
includeInApk = false
includeInBundle = false

View File

@@ -5,25 +5,82 @@
package="com.topjohnwu.magisk">
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.VIBRATE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.USE_BIOMETRIC" />
<uses-permission android:name="android.permission.USE_FINGERPRINT" />
<uses-permission
android:name="android.permission.WRITE_EXTERNAL_STORAGE"
android:maxSdkVersion="29" />
<application
android:allowBackup="true"
tools:ignore="AllowBackup">
android:appComponentFactory=".DelegateComponentFactory"
android:name="a.e"
android:allowBackup="false"
tools:ignore="UnusedAttribute,GoogleAppIndexingWarning" >
<activity android:name=".MainActivity">
<!-- Splash -->
<activity
android:name="a.c">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.APPLICATION_PREFERENCES" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
<!-- Main -->
<activity android:name="a.b" />
<!-- Superuser -->
<activity
android:name="a.m"
android:directBootAware="true"
android:excludeFromRecents="true"
android:exported="false"
tools:ignore="AppLinkUrlError">
<intent-filter>
<action android:name="android.intent.action.VIEW"/>
<category android:name="android.intent.category.DEFAULT"/>
</intent-filter>
</activity>
<!-- Receiver -->
<receiver
android:name="a.h"
android:directBootAware="true">
<intent-filter>
<action android:name="android.intent.action.REBOOT" />
<action android:name="android.intent.action.LOCALE_CHANGED" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.PACKAGE_REPLACED" />
<action android:name="android.intent.action.PACKAGE_FULLY_REMOVED" />
<data android:scheme="package" />
</intent-filter>
</receiver>
<!-- DownloadService -->
<service android:name="a.j" />
<!-- FileProvider -->
<provider
android:name=".FileProvider"
android:name="a.p"
android:authorities="${applicationId}.provider"
android:directBootAware="true"
android:exported="false"
android:grantUriPermissions="true">
</provider>
<!-- Hardcode GMS version -->
<meta-data
android:name="com.google.android.gms.version"
android:value="12451000" />
</application>
</manifest>

View File

@@ -0,0 +1,6 @@
package a;
import com.topjohnwu.magisk.DownloadActivity;
public class c extends DownloadActivity {
}

View File

@@ -0,0 +1,6 @@
package a;
import com.topjohnwu.magisk.DelegateApplication;
public class e extends DelegateApplication {
}

View File

@@ -0,0 +1,6 @@
package a;
import com.topjohnwu.magisk.dummy.DummyReceiver;
public class h extends DummyReceiver {
}

View File

@@ -0,0 +1,6 @@
package a;
import com.topjohnwu.magisk.FileProvider;
public class p extends FileProvider {
}

View File

@@ -0,0 +1,41 @@
package com.topjohnwu.magisk;
import android.app.Application;
import android.content.Context;
import android.content.ContextWrapper;
import android.content.res.Configuration;
import android.os.Build;
import java.lang.reflect.Method;
public class DelegateApplication extends Application {
private Application delegate;
static boolean dynLoad = false;
@Override
protected void attachBaseContext(Context base) {
super.attachBaseContext(base);
// Only dynamic load full APK if hidden and possible
dynLoad = Build.VERSION.SDK_INT >= 28 &&
!base.getPackageName().equals(BuildConfig.APPLICATION_ID);
if (!dynLoad)
return;
delegate = InjectAPK.setup(this);
if (delegate != null) try {
// Call attachBaseContext without ContextImpl to show it is being wrapped
Method m = ContextWrapper.class.getDeclaredMethod("attachBaseContext", Context.class);
m.setAccessible(true);
m.invoke(delegate, this);
} catch (Exception ignored) { /* Impossible */ }
}
@Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
if (delegate != null)
delegate.onConfigurationChanged(newConfig);
}
}

View File

@@ -0,0 +1,79 @@
package com.topjohnwu.magisk;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.app.AppComponentFactory;
import android.app.Application;
import android.app.Service;
import android.content.BroadcastReceiver;
import android.content.ContentProvider;
import android.content.Intent;
import com.topjohnwu.magisk.dummy.DummyProvider;
import com.topjohnwu.magisk.dummy.DummyReceiver;
import com.topjohnwu.magisk.dummy.DummyService;
@SuppressLint("NewApi")
public class DelegateComponentFactory extends AppComponentFactory {
ClassLoader loader;
AppComponentFactory delegate;
interface DummyFactory<T> {
T create();
}
public DelegateComponentFactory() {
InjectAPK.factory = this;
}
@Override
public Application instantiateApplication(ClassLoader cl, String className) {
if (loader == null) loader = cl;
return new DelegateApplication();
}
@Override
public Activity instantiateActivity(ClassLoader cl, String className, Intent intent)
throws ClassNotFoundException, IllegalAccessException, InstantiationException {
if (delegate != null)
return delegate.instantiateActivity(loader, className, intent);
return create(className, DownloadActivity::new);
}
@Override
public BroadcastReceiver instantiateReceiver(ClassLoader cl, String className, Intent intent)
throws ClassNotFoundException, IllegalAccessException, InstantiationException {
if (delegate != null)
return delegate.instantiateReceiver(loader, className, intent);
return create(className, DummyReceiver::new);
}
@Override
public Service instantiateService(ClassLoader cl, String className, Intent intent)
throws ClassNotFoundException, IllegalAccessException, InstantiationException {
if (delegate != null)
return delegate.instantiateService(loader, className, intent);
return create(className, DummyService::new);
}
@Override
public ContentProvider instantiateProvider(ClassLoader cl, String className)
throws ClassNotFoundException, IllegalAccessException, InstantiationException {
if (loader == null) loader = cl;
if (delegate != null)
return delegate.instantiateProvider(loader, className);
return create(className, DummyProvider::new);
}
/**
* Create the class or dummy implementation if creation failed
*/
private <T> T create(String name, DummyFactory<T> factory) {
try {
return (T) loader.loadClass(name).newInstance();
} catch (Exception ignored) {
return factory.create();
}
}
}

View File

@@ -4,9 +4,11 @@ import android.app.Activity;
import android.app.AlertDialog;
import android.app.ProgressDialog;
import android.content.Context;
import android.os.AsyncTask;
import android.os.Bundle;
import android.util.Log;
import android.view.ContextThemeWrapper;
import android.widget.Toast;
import com.topjohnwu.magisk.net.Networking;
import com.topjohnwu.magisk.net.Request;
@@ -20,11 +22,13 @@ import java.io.File;
import static android.R.string.no;
import static android.R.string.ok;
import static android.R.string.yes;
import static com.topjohnwu.magisk.DelegateApplication.dynLoad;
import static com.topjohnwu.magisk.R.string.dling;
import static com.topjohnwu.magisk.R.string.no_internet_msg;
import static com.topjohnwu.magisk.R.string.relaunch_app;
import static com.topjohnwu.magisk.R.string.upgrade_msg;
public class MainActivity extends Activity {
public class DownloadActivity extends Activity {
private static final String APP_NAME = "Magisk Manager";
private static final String CDN_URL = "https://cdn.jsdelivr.net/gh/topjohnwu/magisk_files@%s/%s";
@@ -107,11 +111,23 @@ public class MainActivity extends Activity {
private void dlAPK() {
dialog = ProgressDialog.show(themed, getString(dling), getString(dling) + " " + APP_NAME, true);
// Download and upgrade the app
File apk = new File(getCacheDir(), "manager.apk");
request(apkLink).getAsFile(apk, file -> {
File apk = dynLoad ? DynAPK.current(this) : new File(getCacheDir(), "manager.apk");
request(apkLink).setExecutor(AsyncTask.THREAD_POOL_EXECUTOR).getAsFile(apk, file -> {
if (dynLoad) {
InjectAPK.setup(this);
runOnUiThread(() -> {
dialog.dismiss();
Toast.makeText(themed, relaunch_app, Toast.LENGTH_LONG).show();
finish();
});
} else {
runOnUiThread(() -> {
dialog.dismiss();
APKInstall.install(this, file);
finish();
});
}
});
}
}

View File

@@ -0,0 +1,78 @@
package com.topjohnwu.magisk;
import android.app.AppComponentFactory;
import android.app.Application;
import android.content.ContentResolver;
import android.content.Context;
import android.net.Uri;
import android.util.Log;
import com.topjohnwu.magisk.utils.DynamicClassLoader;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.HashMap;
public class InjectAPK {
static DelegateComponentFactory factory;
static Application setup(Context context) {
File apk = DynAPK.current(context);
File update = DynAPK.update(context);
if (update.exists())
update.renameTo(apk);
Application delegate = null;
if (!apk.exists()) {
// Try copying APK
Uri uri = new Uri.Builder().scheme("content")
.authority("com.topjohnwu.magisk.provider")
.encodedPath("apk_file").build();
ContentResolver resolver = context.getContentResolver();
try (InputStream is = resolver.openInputStream(uri)) {
if (is != null) {
try (OutputStream out = new FileOutputStream(apk)) {
byte[] buf = new byte[4096];
for (int read; (read = is.read(buf)) >= 0;) {
out.write(buf, 0, read);
}
}
}
} catch (Exception e) {
Log.e(InjectAPK.class.getSimpleName(), "", e);
}
}
if (apk.exists()) {
ClassLoader cl = new DynamicClassLoader(apk, factory.loader);
try {
// Create the delegate AppComponentFactory
AppComponentFactory df = (AppComponentFactory)
cl.loadClass("androidx.core.app.CoreComponentFactory").newInstance();
// Create the delegate Application
delegate = (Application) cl.loadClass("a.e").getConstructor(Object.class)
.newInstance(DynAPK.pack(dynData()));
// If everything went well, set our loader and delegate
factory.delegate = df;
factory.loader = cl;
} catch (Exception e) {
Log.e(InjectAPK.class.getSimpleName(), "", e);
apk.delete();
}
}
return delegate;
}
private static DynAPK.Data dynData() {
DynAPK.Data data = new DynAPK.Data();
data.version = BuildConfig.STUB_VERSION;
// Public source code does not do component name obfuscation
data.classToComponent = new HashMap<>();
return data;
}
}

View File

@@ -0,0 +1,13 @@
package com.topjohnwu.magisk.dummy;
import android.app.Activity;
import android.os.Bundle;
public class DummyActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
finish();
}
}

View File

@@ -0,0 +1,38 @@
package com.topjohnwu.magisk.dummy;
import android.content.ContentProvider;
import android.content.ContentValues;
import android.database.Cursor;
import android.net.Uri;
public class DummyProvider extends ContentProvider {
@Override
public boolean onCreate() {
return false;
}
@Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
return null;
}
@Override
public String getType(Uri uri) {
return null;
}
@Override
public Uri insert(Uri uri, ContentValues values) {
return null;
}
@Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
return 0;
}
@Override
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
return 0;
}
}

View File

@@ -0,0 +1,10 @@
package com.topjohnwu.magisk.dummy;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
public class DummyReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {}
}

View File

@@ -0,0 +1,13 @@
package com.topjohnwu.magisk.dummy;
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
public class DummyService extends Service {
@Override
public IBinder onBind(Intent intent) {
stopSelf();
return null;
}
}