You've already forked TrickyStore
mirror of
https://github.com/5ec1cff/TrickyStore.git
synced 2025-09-06 06:37:07 +00:00
Compare commits
74 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 3a515c5fe1 | |||
| 643eb7d909 | |||
| 6f82e4011b | |||
| 479459d863 | |||
| 3ea150212c | |||
| db72d6d3e9 | |||
| 0a52c2b4a0 | |||
| b124f81f9b | |||
| 8f6c2b5dc8 | |||
| f3a150078f | |||
| 89700a22ab | |||
| d44b997f6f | |||
| 7cb2ad0e13 | |||
| 9be33d9528 | |||
| 3ff2c3fa54 | |||
| af76010cb1 | |||
| d4cf6a3450 | |||
| 62ae132432 | |||
| 493c79b1e0 | |||
| 4bbf952483 | |||
| b8dce5ed5f | |||
| 7ce469ea1d | |||
| 1035103e15 | |||
| ee8ef65dc9 | |||
| 55cbe69ad5 | |||
| 4d7257fcc7 | |||
| 95ad2d5a32 | |||
| 11e77147a5 | |||
| a9f3c6f372 | |||
| 31724e1592 | |||
| 932d88933c | |||
| a21ebd634e | |||
| d4f3839ecf | |||
| 979c51009c | |||
| f14f79e90c | |||
| f1bbc0cd0e | |||
| 1fe5bee3d2 | |||
| c620cecc2f | |||
| 5c9fe31ced | |||
| ebdaf87061 | |||
| 49554a8bbd | |||
| 2702d4396a | |||
| 963ff9d277 | |||
| 46f455e4ba | |||
| ef89d7192d | |||
| 2912d53198 | |||
| 52b3fcf5ad | |||
| 37606d6d7e | |||
| 7e7be3f72f | |||
| 1a5a95af23 | |||
| 53ae36774e | |||
| 60f940ca95 | |||
| 2e1eb0f12b | |||
| dbb50ef6a9 | |||
| 994d531304 | |||
| 812d2911b2 | |||
| 2b18146164 | |||
| bc7b75999c | |||
| 83ba59279c | |||
| bd372eb2d3 | |||
| 680a36c499 | |||
| 8de5a34243 | |||
| f97624ac58 | |||
| 8cac6c5a8c | |||
| 2db359090e | |||
| bc0c499050 | |||
| 0bbdbef935 | |||
| 81ee8bd1e4 | |||
| 1fbcfd4654 | |||
| 236384b398 | |||
| d55dc13e87 | |||
| f0eb0f2d92 | |||
| 74fe081ad0 | |||
| f1939a6484 |
@@ -0,0 +1,66 @@
|
||||
name: Build
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ "master" ]
|
||||
pull_request:
|
||||
branches: [ "master" ]
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check out
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: "recursive"
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Setup Java
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
distribution: temurin
|
||||
java-version: 21
|
||||
|
||||
- name: Setup Gradle
|
||||
uses: gradle/actions/setup-gradle@v3
|
||||
with:
|
||||
gradle-home-cache-cleanup: true
|
||||
|
||||
- name: Setup Android SDK
|
||||
uses: android-actions/setup-android@v3
|
||||
with:
|
||||
packages: ''
|
||||
|
||||
- name: Build with Gradle
|
||||
run: |
|
||||
./gradlew zipRelease
|
||||
./gradlew zipDebug
|
||||
|
||||
- name: Prepare artifact
|
||||
if: success()
|
||||
id: prepareArtifact
|
||||
run: |
|
||||
releaseName=`ls module/release/Tricky-Store-v*-release.zip | awk -F '(/|.zip)' '{print $3}'` && echo "releaseName=$releaseName" >> $GITHUB_OUTPUT
|
||||
debugName=`ls module/release/Tricky-Store-v*-debug.zip | awk -F '(/|.zip)' '{print $3}'` && echo "debugName=$debugName" >> $GITHUB_OUTPUT
|
||||
unzip module/release/Tricky-Store-v*-release.zip -d module-release
|
||||
unzip module/release/Tricky-Store-v*-debug.zip -d module-debug
|
||||
|
||||
- name: Upload release
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: ${{ steps.prepareArtifact.outputs.releaseName }}
|
||||
path: "./module-release/*"
|
||||
|
||||
- name: Upload debug
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: ${{ steps.prepareArtifact.outputs.debugName }}
|
||||
path: "./module-debug/*"
|
||||
|
||||
- name: Upload release mappings
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: release-mappings
|
||||
path: "./service/build/outputs/mapping/release"
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
*.iml
|
||||
.gradle
|
||||
/local.properties
|
||||
/.idea
|
||||
.DS_Store
|
||||
/build
|
||||
/captures
|
||||
.externalNativeBuild
|
||||
.cxx
|
||||
local.properties
|
||||
@@ -0,0 +1,3 @@
|
||||
[submodule "module/src/main/cpp/external/LSPlt"]
|
||||
path = module/src/main/cpp/external/LSPlt
|
||||
url = https://github.com/LSPosed/LSPlt
|
||||
@@ -2,14 +2,6 @@
|
||||
|
||||
A trick of keystore. **Android 12 or above is required**.
|
||||
|
||||
This module is used for modifying the certificate chain generated for android key attestation.
|
||||
|
||||
## Stop opening source / 停止开源
|
||||
|
||||
Due to the rampant misuse and the contributions received after open-sourcing being less than expected, this module will be closed-source starting from version 1.1.0.
|
||||
|
||||
考虑到二改泛滥,且开源后获得的贡献少于预期,因此本模块自 1.1.0 版本起闭源发布。
|
||||
|
||||
## Usage
|
||||
|
||||
1. Flash this module and reboot.
|
||||
@@ -48,33 +40,59 @@ format:
|
||||
</AndroidAttestation>
|
||||
```
|
||||
|
||||
## Build Vars Spoofing
|
||||
|
||||
> **Zygisk (or Zygisk Next) is needed for this feature to work.**
|
||||
|
||||
If you still do not pass you can try enabling/disabling Build variable spoofing by creating/deleting the file `/data/adb/tricky_store/spoof_build_vars`.
|
||||
|
||||
Tricky Store will automatically generate example config props inside `/data/adb/tricky_store/spoof_build_vars` once created, on next reboot, then you may manually edit your spoof config.
|
||||
|
||||
Here is an example of a spoof config:
|
||||
|
||||
```
|
||||
MANUFACTURER=Google
|
||||
MODEL=Pixel 8 Pro
|
||||
FINGERPRINT=google/husky_beta/husky:15/AP31.240617.009/12094726:user/release-keys
|
||||
BRAND=google
|
||||
PRODUCT=husky_beta
|
||||
DEVICE=husky
|
||||
RELEASE=15
|
||||
ID=AP31.240617.009
|
||||
INCREMENTAL=12094726
|
||||
TYPE=user
|
||||
TAGS=release-keys
|
||||
SECURITY_PATCH=2024-07-05
|
||||
```
|
||||
|
||||
For Magisk users: if you don't need this feature and zygisk is disabled, please remove or rename the
|
||||
folder `/data/adb/modules/tricky_store/zygisk` manually.
|
||||
|
||||
## Support TEE broken devices
|
||||
|
||||
Tricky Store will hack the leaf certificate by default.
|
||||
On TEE broken devices, this will not work because we can't retrieve the leaf certificate from TEE.
|
||||
In this case, we fallback to use generate key mode automatically.
|
||||
|
||||
You can add a `!` after a package name to force use generate certificate support for this package.
|
||||
Also, you can add a `?` after a package name to force use leaf hack mode for this package.
|
||||
Tricky Store will hack the leaf certificate by default. On TEE broken devices, this will not work because we can't retrieve the leaf certificate from TEE. You can add a `!` after a package name to enable generate certificate support for this package.
|
||||
|
||||
For example:
|
||||
|
||||
```
|
||||
# target.txt
|
||||
# use auto mode for KeyAttestation App
|
||||
# use leaf certificate hacking mode for KeyAttestation App
|
||||
io.github.vvb2060.keyattestation
|
||||
# always use leaf hack mode
|
||||
io.github.vvb2060.mahoshojo?
|
||||
# always use certificate generating mode for gms
|
||||
# use certificate generating mode for gms
|
||||
com.google.android.gms!
|
||||
```
|
||||
|
||||
## TODO
|
||||
|
||||
- Support App Attest Key.
|
||||
- [Support Android 11 and below.](https://github.com/5ec1cff/TrickyStore/issues/25#issuecomment-2250588463)
|
||||
- Support automatic selection mode.
|
||||
|
||||
PR is welcomed.
|
||||
|
||||
## Acknowledgement
|
||||
|
||||
- [PlayIntegrityFix](https://github.com/chiteroman/PlayIntegrityFix)
|
||||
- [FrameworkPatch](https://github.com/chiteroman/FrameworkPatch)
|
||||
- [BootloaderSpoofer](https://github.com/chiteroman/BootloaderSpoofer)
|
||||
- [KeystoreInjection](https://github.com/aviraxp/Zygisk-KeystoreInjection)
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
theme: jekyll-theme-slate
|
||||
@@ -0,0 +1,101 @@
|
||||
import com.android.build.gradle.AppExtension
|
||||
import com.android.build.gradle.LibraryExtension
|
||||
import java.io.ByteArrayOutputStream
|
||||
|
||||
plugins {
|
||||
alias(libs.plugins.agp.app) apply false
|
||||
alias(libs.plugins.jetbrains.kotlin.android) apply false
|
||||
alias(libs.plugins.android.library) apply false
|
||||
}
|
||||
|
||||
fun String.execute(currentWorkingDir: File = file("./")): String {
|
||||
val byteOut = ByteArrayOutputStream()
|
||||
project.exec {
|
||||
workingDir = currentWorkingDir
|
||||
commandLine = split("\\s".toRegex())
|
||||
standardOutput = byteOut
|
||||
}
|
||||
return String(byteOut.toByteArray()).trim()
|
||||
}
|
||||
|
||||
val gitCommitCount = "git rev-list HEAD --count".execute().toInt()
|
||||
val gitCommitHash = "git rev-parse --verify --short HEAD".execute()
|
||||
|
||||
// also the soname
|
||||
val moduleId by extra("tricky_store")
|
||||
val moduleName by extra("Tricky Store")
|
||||
val author by extra("5ec1cff")
|
||||
val description by extra("A trick of keystore")
|
||||
val verName by extra("v1.0.3")
|
||||
val verCode by extra(gitCommitCount)
|
||||
val commitHash by extra(gitCommitHash)
|
||||
val abiList by extra(listOf("arm64-v8a", "x86_64"))
|
||||
|
||||
val androidMinSdkVersion by extra(31)
|
||||
val androidTargetSdkVersion by extra(34)
|
||||
val androidCompileSdkVersion by extra(34)
|
||||
val androidBuildToolsVersion by extra("34.0.0")
|
||||
val androidCompileNdkVersion by extra("27.0.12077973")
|
||||
val androidSourceCompatibility by extra(JavaVersion.VERSION_17)
|
||||
val androidTargetCompatibility by extra(JavaVersion.VERSION_17)
|
||||
|
||||
tasks.register("Delete", Delete::class) {
|
||||
delete(layout.buildDirectory)
|
||||
}
|
||||
|
||||
fun Project.configureBaseExtension() {
|
||||
extensions.findByType(AppExtension::class)?.run {
|
||||
namespace = "io.github.a13e300.tricky_store"
|
||||
compileSdkVersion(androidCompileSdkVersion)
|
||||
ndkVersion = androidCompileNdkVersion
|
||||
buildToolsVersion = androidBuildToolsVersion
|
||||
|
||||
defaultConfig {
|
||||
minSdk = androidMinSdkVersion
|
||||
targetSdk = androidCompileSdkVersion
|
||||
versionCode = verCode
|
||||
versionName = verName
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility = androidSourceCompatibility
|
||||
targetCompatibility = androidTargetCompatibility
|
||||
}
|
||||
}
|
||||
|
||||
extensions.findByType(LibraryExtension::class)?.run {
|
||||
namespace = "io.github.a13e300.tricky_store"
|
||||
compileSdk = androidCompileSdkVersion
|
||||
ndkVersion = androidCompileNdkVersion
|
||||
buildToolsVersion = androidBuildToolsVersion
|
||||
|
||||
defaultConfig {
|
||||
minSdk = androidMinSdkVersion
|
||||
}
|
||||
|
||||
lint {
|
||||
checkReleaseBuilds = false
|
||||
abortOnError = true
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility = androidSourceCompatibility
|
||||
targetCompatibility = androidTargetCompatibility
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
subprojects {
|
||||
plugins.withId("com.android.application") {
|
||||
configureBaseExtension()
|
||||
}
|
||||
plugins.withId("com.android.library") {
|
||||
configureBaseExtension()
|
||||
}
|
||||
plugins.withType(JavaPlugin::class.java) {
|
||||
extensions.configure(JavaPluginExtension::class.java) {
|
||||
sourceCompatibility = androidSourceCompatibility
|
||||
targetCompatibility = androidTargetCompatibility
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
# 1.1.3
|
||||
|
||||
修复若干 bug
|
||||
|
||||
---
|
||||
|
||||
Fix some bugs
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
# Project-wide Gradle settings.
|
||||
# IDE (e.g. Android Studio) users:
|
||||
# Gradle settings configured through the IDE *will override*
|
||||
# any settings specified in this file.
|
||||
# For more details on how to configure your build environment visit
|
||||
# http://www.gradle.org/docs/current/userguide/build_environment.html
|
||||
# Specifies the JVM arguments used for the daemon process.
|
||||
# The setting is particularly useful for tweaking memory settings.
|
||||
org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
|
||||
# When configured, Gradle will run in incubating parallel mode.
|
||||
# This option should only be used with decoupled projects. More details, visit
|
||||
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
|
||||
# org.gradle.parallel=true
|
||||
# AndroidX package structure to make it clearer which packages are bundled with the
|
||||
# Android operating system, and which are packaged with your app"s APK
|
||||
# https://developer.android.com/topic/libraries/support-library/androidx-rn
|
||||
android.useAndroidX=true
|
||||
# Automatically convert third-party libraries to use AndroidX
|
||||
android.enableJetifier=true
|
||||
@@ -0,0 +1,16 @@
|
||||
[versions]
|
||||
agp = "8.5.1"
|
||||
bcpkix-jdk18on = "1.78.1"
|
||||
kotlin = "2.0.0"
|
||||
annotation = "1.8.0"
|
||||
|
||||
[libraries]
|
||||
annotation = { module = "androidx.annotation:annotation", version.ref = "annotation" }
|
||||
bcpkix-jdk18on = { module = "org.bouncycastle:bcpkix-jdk18on", version.ref = "bcpkix-jdk18on" }
|
||||
cxx = { module = "org.lsposed.libcxx:libcxx", version = "27.0.12077973" }
|
||||
|
||||
[plugins]
|
||||
agp-app = { id = "com.android.application", version.ref = "agp" }
|
||||
android-library = { id = "com.android.library", version.ref = "agp" }
|
||||
jetbrains-kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
|
||||
lsplugin-cmaker = { id = "org.lsposed.lsplugin.cmaker", version = "1.2" }
|
||||
Vendored
BIN
Binary file not shown.
+7
@@ -0,0 +1,7 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-bin.zip
|
||||
networkTimeout=10000
|
||||
validateDistributionUrl=true
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
@@ -0,0 +1,249 @@
|
||||
#!/bin/sh
|
||||
|
||||
#
|
||||
# Copyright © 2015-2021 the original authors.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# https://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
|
||||
##############################################################################
|
||||
#
|
||||
# Gradle start up script for POSIX generated by Gradle.
|
||||
#
|
||||
# Important for running:
|
||||
#
|
||||
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
|
||||
# noncompliant, but you have some other compliant shell such as ksh or
|
||||
# bash, then to run this script, type that shell name before the whole
|
||||
# command line, like:
|
||||
#
|
||||
# ksh Gradle
|
||||
#
|
||||
# Busybox and similar reduced shells will NOT work, because this script
|
||||
# requires all of these POSIX shell features:
|
||||
# * functions;
|
||||
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
|
||||
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
|
||||
# * compound commands having a testable exit status, especially «case»;
|
||||
# * various built-in commands including «command», «set», and «ulimit».
|
||||
#
|
||||
# Important for patching:
|
||||
#
|
||||
# (2) This script targets any POSIX shell, so it avoids extensions provided
|
||||
# by Bash, Ksh, etc; in particular arrays are avoided.
|
||||
#
|
||||
# The "traditional" practice of packing multiple parameters into a
|
||||
# space-separated string is a well documented source of bugs and security
|
||||
# problems, so this is (mostly) avoided, by progressively accumulating
|
||||
# options in "$@", and eventually passing that to Java.
|
||||
#
|
||||
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
|
||||
# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
|
||||
# see the in-line comments for details.
|
||||
#
|
||||
# There are tweaks for specific operating systems such as AIX, CygWin,
|
||||
# Darwin, MinGW, and NonStop.
|
||||
#
|
||||
# (3) This script is generated from the Groovy template
|
||||
# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
|
||||
# within the Gradle project.
|
||||
#
|
||||
# You can find Gradle at https://github.com/gradle/gradle/.
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
# Attempt to set APP_HOME
|
||||
|
||||
# Resolve links: $0 may be a link
|
||||
app_path=$0
|
||||
|
||||
# Need this for daisy-chained symlinks.
|
||||
while
|
||||
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
|
||||
[ -h "$app_path" ]
|
||||
do
|
||||
ls=$( ls -ld "$app_path" )
|
||||
link=${ls#*' -> '}
|
||||
case $link in #(
|
||||
/*) app_path=$link ;; #(
|
||||
*) app_path=$APP_HOME$link ;;
|
||||
esac
|
||||
done
|
||||
|
||||
# This is normally unused
|
||||
# shellcheck disable=SC2034
|
||||
APP_BASE_NAME=${0##*/}
|
||||
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
|
||||
APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit
|
||||
|
||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||
MAX_FD=maximum
|
||||
|
||||
warn () {
|
||||
echo "$*"
|
||||
} >&2
|
||||
|
||||
die () {
|
||||
echo
|
||||
echo "$*"
|
||||
echo
|
||||
exit 1
|
||||
} >&2
|
||||
|
||||
# OS specific support (must be 'true' or 'false').
|
||||
cygwin=false
|
||||
msys=false
|
||||
darwin=false
|
||||
nonstop=false
|
||||
case "$( uname )" in #(
|
||||
CYGWIN* ) cygwin=true ;; #(
|
||||
Darwin* ) darwin=true ;; #(
|
||||
MSYS* | MINGW* ) msys=true ;; #(
|
||||
NONSTOP* ) nonstop=true ;;
|
||||
esac
|
||||
|
||||
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
||||
|
||||
|
||||
# Determine the Java command to use to start the JVM.
|
||||
if [ -n "$JAVA_HOME" ] ; then
|
||||
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
||||
# IBM's JDK on AIX uses strange locations for the executables
|
||||
JAVACMD=$JAVA_HOME/jre/sh/java
|
||||
else
|
||||
JAVACMD=$JAVA_HOME/bin/java
|
||||
fi
|
||||
if [ ! -x "$JAVACMD" ] ; then
|
||||
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
else
|
||||
JAVACMD=java
|
||||
if ! command -v java >/dev/null 2>&1
|
||||
then
|
||||
die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
fi
|
||||
|
||||
# Increase the maximum file descriptors if we can.
|
||||
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
|
||||
case $MAX_FD in #(
|
||||
max*)
|
||||
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
|
||||
# shellcheck disable=SC2039,SC3045
|
||||
MAX_FD=$( ulimit -H -n ) ||
|
||||
warn "Could not query maximum file descriptor limit"
|
||||
esac
|
||||
case $MAX_FD in #(
|
||||
'' | soft) :;; #(
|
||||
*)
|
||||
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
|
||||
# shellcheck disable=SC2039,SC3045
|
||||
ulimit -n "$MAX_FD" ||
|
||||
warn "Could not set maximum file descriptor limit to $MAX_FD"
|
||||
esac
|
||||
fi
|
||||
|
||||
# Collect all arguments for the java command, stacking in reverse order:
|
||||
# * args from the command line
|
||||
# * the main class name
|
||||
# * -classpath
|
||||
# * -D...appname settings
|
||||
# * --module-path (only if needed)
|
||||
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
|
||||
|
||||
# For Cygwin or MSYS, switch paths to Windows format before running java
|
||||
if "$cygwin" || "$msys" ; then
|
||||
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
|
||||
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
|
||||
|
||||
JAVACMD=$( cygpath --unix "$JAVACMD" )
|
||||
|
||||
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
||||
for arg do
|
||||
if
|
||||
case $arg in #(
|
||||
-*) false ;; # don't mess with options #(
|
||||
/?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
|
||||
[ -e "$t" ] ;; #(
|
||||
*) false ;;
|
||||
esac
|
||||
then
|
||||
arg=$( cygpath --path --ignore --mixed "$arg" )
|
||||
fi
|
||||
# Roll the args list around exactly as many times as the number of
|
||||
# args, so each arg winds up back in the position where it started, but
|
||||
# possibly modified.
|
||||
#
|
||||
# NB: a `for` loop captures its iteration list before it begins, so
|
||||
# changing the positional parameters here affects neither the number of
|
||||
# iterations, nor the values presented in `arg`.
|
||||
shift # remove old arg
|
||||
set -- "$@" "$arg" # push replacement arg
|
||||
done
|
||||
fi
|
||||
|
||||
|
||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||
|
||||
# Collect all arguments for the java command:
|
||||
# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
|
||||
# and any embedded shellness will be escaped.
|
||||
# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
|
||||
# treated as '${Hostname}' itself on the command line.
|
||||
|
||||
set -- \
|
||||
"-Dorg.gradle.appname=$APP_BASE_NAME" \
|
||||
-classpath "$CLASSPATH" \
|
||||
org.gradle.wrapper.GradleWrapperMain \
|
||||
"$@"
|
||||
|
||||
# Stop when "xargs" is not available.
|
||||
if ! command -v xargs >/dev/null 2>&1
|
||||
then
|
||||
die "xargs is not available"
|
||||
fi
|
||||
|
||||
# Use "xargs" to parse quoted args.
|
||||
#
|
||||
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
|
||||
#
|
||||
# In Bash we could simply go:
|
||||
#
|
||||
# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
|
||||
# set -- "${ARGS[@]}" "$@"
|
||||
#
|
||||
# but POSIX shell has neither arrays nor command substitution, so instead we
|
||||
# post-process each arg (as a line of input to sed) to backslash-escape any
|
||||
# character that might be a shell metacharacter, then use eval to reverse
|
||||
# that process (while maintaining the separation between arguments), and wrap
|
||||
# the whole thing up as a single "set" statement.
|
||||
#
|
||||
# This will of course break if any of these variables contains a newline or
|
||||
# an unmatched quote.
|
||||
#
|
||||
|
||||
eval "set -- $(
|
||||
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
|
||||
xargs -n1 |
|
||||
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
|
||||
tr '\n' ' '
|
||||
)" '"$@"'
|
||||
|
||||
exec "$JAVACMD" "$@"
|
||||
Vendored
+92
@@ -0,0 +1,92 @@
|
||||
@rem
|
||||
@rem Copyright 2015 the original author or authors.
|
||||
@rem
|
||||
@rem Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@rem you may not use this file except in compliance with the License.
|
||||
@rem You may obtain a copy of the License at
|
||||
@rem
|
||||
@rem https://www.apache.org/licenses/LICENSE-2.0
|
||||
@rem
|
||||
@rem Unless required by applicable law or agreed to in writing, software
|
||||
@rem distributed under the License is distributed on an "AS IS" BASIS,
|
||||
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
@rem See the License for the specific language governing permissions and
|
||||
@rem limitations under the License.
|
||||
@rem
|
||||
|
||||
@if "%DEBUG%"=="" @echo off
|
||||
@rem ##########################################################################
|
||||
@rem
|
||||
@rem Gradle startup script for Windows
|
||||
@rem
|
||||
@rem ##########################################################################
|
||||
|
||||
@rem Set local scope for the variables with windows NT shell
|
||||
if "%OS%"=="Windows_NT" setlocal
|
||||
|
||||
set DIRNAME=%~dp0
|
||||
if "%DIRNAME%"=="" set DIRNAME=.
|
||||
@rem This is normally unused
|
||||
set APP_BASE_NAME=%~n0
|
||||
set APP_HOME=%DIRNAME%
|
||||
|
||||
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
|
||||
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
|
||||
|
||||
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
|
||||
|
||||
@rem Find java.exe
|
||||
if defined JAVA_HOME goto findJavaFromJavaHome
|
||||
|
||||
set JAVA_EXE=java.exe
|
||||
%JAVA_EXE% -version >NUL 2>&1
|
||||
if %ERRORLEVEL% equ 0 goto execute
|
||||
|
||||
echo. 1>&2
|
||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
|
||||
echo. 1>&2
|
||||
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
|
||||
echo location of your Java installation. 1>&2
|
||||
|
||||
goto fail
|
||||
|
||||
:findJavaFromJavaHome
|
||||
set JAVA_HOME=%JAVA_HOME:"=%
|
||||
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
||||
|
||||
if exist "%JAVA_EXE%" goto execute
|
||||
|
||||
echo. 1>&2
|
||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
|
||||
echo. 1>&2
|
||||
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
|
||||
echo location of your Java installation. 1>&2
|
||||
|
||||
goto fail
|
||||
|
||||
:execute
|
||||
@rem Setup the command line
|
||||
|
||||
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
||||
|
||||
|
||||
@rem Execute Gradle
|
||||
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
|
||||
|
||||
:end
|
||||
@rem End local scope for the variables with windows NT shell
|
||||
if %ERRORLEVEL% equ 0 goto mainEnd
|
||||
|
||||
:fail
|
||||
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
||||
rem the _cmd.exe /c_ return code!
|
||||
set EXIT_CODE=%ERRORLEVEL%
|
||||
if %EXIT_CODE% equ 0 set EXIT_CODE=1
|
||||
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
|
||||
exit /b %EXIT_CODE%
|
||||
|
||||
:mainEnd
|
||||
if "%OS%"=="Windows_NT" endlocal
|
||||
|
||||
:omega
|
||||
@@ -0,0 +1,4 @@
|
||||
/build
|
||||
/libs
|
||||
/obj
|
||||
/release
|
||||
@@ -0,0 +1,192 @@
|
||||
import android.databinding.tool.ext.capitalizeUS
|
||||
import org.apache.tools.ant.filters.FixCrLfFilter
|
||||
import org.apache.tools.ant.filters.ReplaceTokens
|
||||
import java.security.MessageDigest
|
||||
|
||||
plugins {
|
||||
alias(libs.plugins.agp.app)
|
||||
alias(libs.plugins.lsplugin.cmaker)
|
||||
}
|
||||
|
||||
val moduleId: String by rootProject.extra
|
||||
val moduleName: String by rootProject.extra
|
||||
val verCode: Int by rootProject.extra
|
||||
val verName: String by rootProject.extra
|
||||
val commitHash: String by rootProject.extra
|
||||
val abiList: List<String> by rootProject.extra
|
||||
val androidMinSdkVersion: Int by rootProject.extra
|
||||
val author: String by rootProject.extra
|
||||
val description: String by rootProject.extra
|
||||
val moduleDescription = description
|
||||
|
||||
android {
|
||||
defaultConfig {
|
||||
ndk {
|
||||
abiFilters.addAll(abiList)
|
||||
}
|
||||
}
|
||||
|
||||
buildFeatures {
|
||||
prefab = true
|
||||
}
|
||||
|
||||
externalNativeBuild {
|
||||
cmake {
|
||||
version = "3.28.0+"
|
||||
path("src/main/cpp/CMakeLists.txt")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
cmaker {
|
||||
default {
|
||||
arguments += arrayOf(
|
||||
"-DANDROID_STL=none",
|
||||
"-DANDROID_SUPPORT_FLEXIBLE_PAGE_SIZES=ON",
|
||||
"-DANDROID_ALLOW_UNDEFINED_SYMBOLS=ON",
|
||||
"-DMODULE_NAME=$moduleId",
|
||||
"-DCMAKE_CXX_STANDARD=23",
|
||||
"-DCMAKE_C_STANDARD=23",
|
||||
"-DCMAKE_INTERPROCEDURAL_OPTIMIZATION=ON",
|
||||
"-DCMAKE_VISIBILITY_INLINES_HIDDEN=ON",
|
||||
"-DCMAKE_CXX_VISIBILITY_PRESET=hidden",
|
||||
"-DCMAKE_C_VISIBILITY_PRESET=hidden",
|
||||
)
|
||||
abiFilters(*abiList.toTypedArray())
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(libs.cxx)
|
||||
}
|
||||
|
||||
evaluationDependsOn(":service")
|
||||
|
||||
afterEvaluate {
|
||||
android.applicationVariants.forEach { variant ->
|
||||
val variantLowered = variant.name.lowercase()
|
||||
val variantCapped = variant.name.capitalizeUS()
|
||||
val buildTypeCapped = variant.buildType.name.replaceFirstChar { it.uppercase() }
|
||||
val buildTypeLowered = variant.buildType.name.lowercase()
|
||||
val supportedAbis = abiList.map {
|
||||
when (it) {
|
||||
"arm64-v8a" -> "arm64"
|
||||
"armeabi-v7a" -> "arm"
|
||||
"x86" -> "x86"
|
||||
"x86_64" -> "x64"
|
||||
else -> error("unsupported abi $it")
|
||||
}
|
||||
}.joinToString(" ")
|
||||
|
||||
val moduleDir = layout.buildDirectory.file("outputs/module/$variantLowered")
|
||||
val zipFileName =
|
||||
"$moduleName-$verName-$verCode-$commitHash-$buildTypeLowered.zip".replace(' ', '-')
|
||||
|
||||
val prepareModuleFilesTask = task<Sync>("prepareModuleFiles$variantCapped") {
|
||||
group = "module"
|
||||
dependsOn(
|
||||
"assemble$variantCapped",
|
||||
":service:package$buildTypeCapped"
|
||||
)
|
||||
into(moduleDir)
|
||||
from(rootProject.layout.projectDirectory.file("README.md"))
|
||||
from(layout.projectDirectory.file("template")) {
|
||||
exclude("module.prop", "customize.sh", "post-fs-data.sh", "service.sh", "daemon")
|
||||
filter<FixCrLfFilter>("eol" to FixCrLfFilter.CrLf.newInstance("lf"))
|
||||
}
|
||||
from(layout.projectDirectory.file("template")) {
|
||||
include("module.prop")
|
||||
expand(
|
||||
"moduleId" to moduleId,
|
||||
"moduleName" to moduleName,
|
||||
"versionName" to "$verName ($verCode-$commitHash-$variantLowered)",
|
||||
"versionCode" to verCode,
|
||||
"author" to author,
|
||||
"description" to moduleDescription,
|
||||
)
|
||||
}
|
||||
from(layout.projectDirectory.file("template")) {
|
||||
include("customize.sh", "post-fs-data.sh", "service.sh", "daemon")
|
||||
val tokens = mapOf(
|
||||
"DEBUG" to if (buildTypeLowered == "debug") "true" else "false",
|
||||
"SONAME" to moduleId,
|
||||
"SUPPORTED_ABIS" to supportedAbis,
|
||||
"MIN_SDK" to androidMinSdkVersion.toString()
|
||||
)
|
||||
filter<ReplaceTokens>("tokens" to tokens)
|
||||
filter<FixCrLfFilter>("eol" to FixCrLfFilter.CrLf.newInstance("lf"))
|
||||
}
|
||||
from(project(":service").tasks.getByName("package$buildTypeCapped").outputs) {
|
||||
include("*.apk")
|
||||
rename(".*\\.apk", "service.apk")
|
||||
}
|
||||
from(layout.buildDirectory.file("intermediates/stripped_native_libs/$variantLowered/strip${variantCapped}DebugSymbols/out/lib")) {
|
||||
exclude("**/libbinder.so", "**/libutils.so")
|
||||
into("lib")
|
||||
}
|
||||
|
||||
doLast {
|
||||
fileTree(moduleDir).visit {
|
||||
if (isDirectory) return@visit
|
||||
val md = MessageDigest.getInstance("SHA-256")
|
||||
file.forEachBlock(4096) { bytes, size ->
|
||||
md.update(bytes, 0, size)
|
||||
}
|
||||
file(file.path + ".sha256").writeText(
|
||||
org.apache.commons.codec.binary.Hex.encodeHexString(
|
||||
md.digest()
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val zipTask = task<Zip>("zip$variantCapped") {
|
||||
group = "module"
|
||||
dependsOn(prepareModuleFilesTask)
|
||||
archiveFileName.set(zipFileName)
|
||||
destinationDirectory.set(layout.projectDirectory.file("release").asFile)
|
||||
from(moduleDir)
|
||||
}
|
||||
|
||||
val pushTask = task<Exec>("push$variantCapped") {
|
||||
group = "module"
|
||||
dependsOn(zipTask)
|
||||
commandLine("adb", "push", zipTask.outputs.files.singleFile.path, "/data/local/tmp")
|
||||
}
|
||||
|
||||
val installKsuTask = task<Exec>("installKsu$variantCapped") {
|
||||
group = "module"
|
||||
dependsOn(pushTask)
|
||||
commandLine(
|
||||
"adb", "shell", "su", "-c",
|
||||
"/data/adb/ksud module install /data/local/tmp/$zipFileName"
|
||||
)
|
||||
}
|
||||
|
||||
val installMagiskTask = task<Exec>("installMagisk$variantCapped") {
|
||||
group = "module"
|
||||
dependsOn(pushTask)
|
||||
commandLine(
|
||||
"adb",
|
||||
"shell",
|
||||
"su",
|
||||
"-M",
|
||||
"-c",
|
||||
"magisk --install-module /data/local/tmp/$zipFileName"
|
||||
)
|
||||
}
|
||||
|
||||
task<Exec>("installKsuAndReboot$variantCapped") {
|
||||
group = "module"
|
||||
dependsOn(installKsuTask)
|
||||
commandLine("adb", "reboot")
|
||||
}
|
||||
|
||||
task<Exec>("installMagiskAndReboot$variantCapped") {
|
||||
group = "module"
|
||||
dependsOn(installMagiskTask)
|
||||
commandLine("adb", "reboot")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest/>
|
||||
@@ -0,0 +1,34 @@
|
||||
cmake_minimum_required(VERSION 3.28)
|
||||
project(trick_store)
|
||||
|
||||
find_package(cxx REQUIRED CONFIG)
|
||||
link_libraries(cxx::cxx)
|
||||
|
||||
add_library(elf_util STATIC elf_util/elf_util.cpp)
|
||||
add_library(my_logging STATIC logging/logging.cpp)
|
||||
|
||||
add_subdirectory(external)
|
||||
|
||||
target_include_directories(my_logging PUBLIC logging/include)
|
||||
target_include_directories(elf_util PUBLIC elf_util/include)
|
||||
|
||||
target_link_libraries(my_logging log)
|
||||
target_link_libraries(elf_util lsplt my_logging)
|
||||
|
||||
# libutils stub
|
||||
add_library(utils SHARED binder/stub_utils.cpp)
|
||||
target_include_directories(utils PUBLIC binder/include)
|
||||
|
||||
# libbinder stub
|
||||
add_library(binder SHARED binder/stub_binder.cpp)
|
||||
target_include_directories(binder PUBLIC binder/include)
|
||||
target_link_libraries(binder PRIVATE utils)
|
||||
|
||||
add_executable(libinject.so inject/main.cpp inject/utils.cpp)
|
||||
target_link_libraries(libinject.so PRIVATE lsplt my_logging)
|
||||
|
||||
add_library(${MODULE_NAME} SHARED binder_interceptor.cpp)
|
||||
target_link_libraries(${MODULE_NAME} PRIVATE log binder utils elf_util my_logging)
|
||||
|
||||
add_library(tszygisk SHARED zygisk/main.cpp)
|
||||
target_link_libraries(tszygisk PRIVATE log my_logging)
|
||||
@@ -0,0 +1,113 @@
|
||||
/*
|
||||
* Copyright (C) 2008 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <atomic>
|
||||
#include <stdint.h>
|
||||
#include <binder/Common.h>
|
||||
#include <binder/IBinder.h>
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
namespace android {
|
||||
|
||||
namespace internal {
|
||||
class Stability;
|
||||
}
|
||||
|
||||
class BBinder : public IBinder {
|
||||
public:
|
||||
LIBBINDER_EXPORTED BBinder();
|
||||
|
||||
LIBBINDER_EXPORTED virtual const String16& getInterfaceDescriptor() const;
|
||||
LIBBINDER_EXPORTED virtual bool isBinderAlive() const;
|
||||
LIBBINDER_EXPORTED virtual status_t pingBinder();
|
||||
LIBBINDER_EXPORTED virtual status_t dump(int fd, const Vector<String16>& args);
|
||||
|
||||
// NOLINTNEXTLINE(google-default-arguments)
|
||||
LIBBINDER_EXPORTED virtual status_t transact(uint32_t code, const Parcel& data, Parcel* reply,
|
||||
uint32_t flags = 0) final;
|
||||
|
||||
// NOLINTNEXTLINE(google-default-arguments)
|
||||
LIBBINDER_EXPORTED virtual status_t linkToDeath(const sp<DeathRecipient>& recipient,
|
||||
void* cookie = nullptr, uint32_t flags = 0);
|
||||
|
||||
// NOLINTNEXTLINE(google-default-arguments)
|
||||
LIBBINDER_EXPORTED virtual status_t unlinkToDeath(const wp<DeathRecipient>& recipient,
|
||||
void* cookie = nullptr, uint32_t flags = 0,
|
||||
wp<DeathRecipient>* outRecipient = nullptr);
|
||||
|
||||
LIBBINDER_EXPORTED virtual void* attachObject(const void* objectID, void* object,
|
||||
void* cleanupCookie,
|
||||
object_cleanup_func func) final;
|
||||
LIBBINDER_EXPORTED virtual void* findObject(const void* objectID) const final;
|
||||
LIBBINDER_EXPORTED virtual void* detachObject(const void* objectID) final;
|
||||
LIBBINDER_EXPORTED void withLock(const std::function<void()>& doWithLock);
|
||||
|
||||
LIBBINDER_EXPORTED virtual BBinder* localBinder();
|
||||
protected:
|
||||
LIBBINDER_EXPORTED virtual ~BBinder();
|
||||
|
||||
// NOLINTNEXTLINE(google-default-arguments)
|
||||
LIBBINDER_EXPORTED virtual status_t onTransact(uint32_t code, const Parcel& data, Parcel* reply,
|
||||
uint32_t flags = 0);
|
||||
|
||||
private:
|
||||
BBinder(const BBinder& o);
|
||||
BBinder& operator=(const BBinder& o);
|
||||
|
||||
class RpcServerLink;
|
||||
class Extras;
|
||||
|
||||
std::atomic<Extras*> mExtras;
|
||||
|
||||
friend ::android::internal::Stability;
|
||||
int16_t mStability;
|
||||
bool mParceled;
|
||||
bool mRecordingOn;
|
||||
|
||||
#ifdef __LP64__
|
||||
int32_t mReserved1;
|
||||
#endif
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
class BpRefBase : public virtual RefBase {
|
||||
protected:
|
||||
LIBBINDER_EXPORTED explicit BpRefBase(const sp<IBinder>& o);
|
||||
LIBBINDER_EXPORTED virtual ~BpRefBase();
|
||||
LIBBINDER_EXPORTED virtual void onFirstRef();
|
||||
LIBBINDER_EXPORTED virtual void onLastStrongRef(const void* id);
|
||||
LIBBINDER_EXPORTED virtual bool onIncStrongAttempted(uint32_t flags, const void* id);
|
||||
|
||||
LIBBINDER_EXPORTED inline IBinder* remote() const { return mRemote; }
|
||||
LIBBINDER_EXPORTED inline sp<IBinder> remoteStrong() const {
|
||||
return sp<IBinder>::fromExisting(mRemote);
|
||||
}
|
||||
|
||||
private:
|
||||
BpRefBase(const BpRefBase& o);
|
||||
BpRefBase& operator=(const BpRefBase& o);
|
||||
|
||||
IBinder* const mRemote;
|
||||
RefBase::weakref_type* mRefs;
|
||||
std::atomic<int32_t> mState;
|
||||
};
|
||||
|
||||
} // namespace android
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -0,0 +1,21 @@
|
||||
/*
|
||||
* Copyright (C) 2005 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
// ---------------------------------------------------------------------------
|
||||
namespace android {
|
||||
class BpBinder;
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
#pragma once
|
||||
#define LIBBINDER_EXPORTED __attribute__((__visibility__("default")))
|
||||
@@ -0,0 +1,228 @@
|
||||
/*
|
||||
* Copyright (C) 2008 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <binder/Common.h>
|
||||
#include <binder/unique_fd.h>
|
||||
#include <utils/Errors.h>
|
||||
#include <utils/RefBase.h>
|
||||
#include <utils/String16.h>
|
||||
#include <utils/Vector.h>
|
||||
|
||||
#include <functional>
|
||||
|
||||
// linux/binder.h defines this, but we don't want to include it here in order to
|
||||
// avoid exporting the kernel headers
|
||||
#ifndef B_PACK_CHARS
|
||||
#define B_PACK_CHARS(c1, c2, c3, c4) \
|
||||
((((c1)<<24)) | (((c2)<<16)) | (((c3)<<8)) | (c4))
|
||||
#endif // B_PACK_CHARS
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
namespace android {
|
||||
|
||||
class BBinder;
|
||||
class BpBinder;
|
||||
class IInterface;
|
||||
class Parcel;
|
||||
|
||||
/**
|
||||
* Base class and low-level protocol for a remotable object.
|
||||
* You can derive from this class to create an object for which other
|
||||
* processes can hold references to it. Communication between processes
|
||||
* (method calls, property get and set) is down through a low-level
|
||||
* protocol implemented on top of the transact() API.
|
||||
*/
|
||||
class LIBBINDER_EXPORTED IBinder : public virtual RefBase {
|
||||
public:
|
||||
enum {
|
||||
FIRST_CALL_TRANSACTION = 0x00000001,
|
||||
LAST_CALL_TRANSACTION = 0x00ffffff,
|
||||
|
||||
PING_TRANSACTION = B_PACK_CHARS('_', 'P', 'N', 'G'),
|
||||
START_RECORDING_TRANSACTION = B_PACK_CHARS('_', 'S', 'R', 'D'),
|
||||
STOP_RECORDING_TRANSACTION = B_PACK_CHARS('_', 'E', 'R', 'D'),
|
||||
DUMP_TRANSACTION = B_PACK_CHARS('_', 'D', 'M', 'P'),
|
||||
SHELL_COMMAND_TRANSACTION = B_PACK_CHARS('_', 'C', 'M', 'D'),
|
||||
INTERFACE_TRANSACTION = B_PACK_CHARS('_', 'N', 'T', 'F'),
|
||||
SYSPROPS_TRANSACTION = B_PACK_CHARS('_', 'S', 'P', 'R'),
|
||||
EXTENSION_TRANSACTION = B_PACK_CHARS('_', 'E', 'X', 'T'),
|
||||
DEBUG_PID_TRANSACTION = B_PACK_CHARS('_', 'P', 'I', 'D'),
|
||||
SET_RPC_CLIENT_TRANSACTION = B_PACK_CHARS('_', 'R', 'P', 'C'),
|
||||
|
||||
// See android.os.IBinder.TWEET_TRANSACTION
|
||||
// Most importantly, messages can be anything not exceeding 130 UTF-8
|
||||
// characters, and callees should exclaim "jolly good message old boy!"
|
||||
TWEET_TRANSACTION = B_PACK_CHARS('_', 'T', 'W', 'T'),
|
||||
|
||||
// See android.os.IBinder.LIKE_TRANSACTION
|
||||
// Improve binder self-esteem.
|
||||
LIKE_TRANSACTION = B_PACK_CHARS('_', 'L', 'I', 'K'),
|
||||
|
||||
// Corresponds to TF_ONE_WAY -- an asynchronous call.
|
||||
FLAG_ONEWAY = 0x00000001,
|
||||
|
||||
// Corresponds to TF_CLEAR_BUF -- clear transaction buffers after call
|
||||
// is made
|
||||
FLAG_CLEAR_BUF = 0x00000020,
|
||||
|
||||
// Private userspace flag for transaction which is being requested from
|
||||
// a vendor context.
|
||||
FLAG_PRIVATE_VENDOR = 0x10000000,
|
||||
};
|
||||
|
||||
IBinder();
|
||||
|
||||
/**
|
||||
* Check if this IBinder implements the interface named by
|
||||
* @a descriptor. If it does, the base pointer to it is returned,
|
||||
* which you can safely static_cast<> to the concrete C++ interface.
|
||||
*/
|
||||
virtual sp<IInterface> queryLocalInterface(const String16& descriptor);
|
||||
|
||||
/**
|
||||
* Return the canonical name of the interface provided by this IBinder
|
||||
* object.
|
||||
*/
|
||||
virtual const String16& getInterfaceDescriptor() const = 0;
|
||||
|
||||
/**
|
||||
* Last known alive status, from last call. May be arbitrarily stale.
|
||||
* May be incorrect if a service returns an incorrect status code.
|
||||
*/
|
||||
virtual bool isBinderAlive() const = 0;
|
||||
virtual status_t pingBinder() = 0;
|
||||
virtual status_t dump(int fd, const Vector<String16>& args) = 0;
|
||||
|
||||
// NOLINTNEXTLINE(google-default-arguments)
|
||||
virtual status_t transact( uint32_t code,
|
||||
const Parcel& data,
|
||||
Parcel* reply,
|
||||
uint32_t flags = 0) = 0;
|
||||
|
||||
// DeathRecipient is pure abstract, there is no virtual method
|
||||
// implementation to put in a translation unit in order to silence the
|
||||
// weak vtables warning.
|
||||
#if defined(__clang__)
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wweak-vtables"
|
||||
#endif
|
||||
|
||||
class DeathRecipient : public virtual RefBase
|
||||
{
|
||||
public:
|
||||
virtual void binderDied(const wp<IBinder>& who) = 0;
|
||||
};
|
||||
|
||||
#if defined(__clang__)
|
||||
#pragma clang diagnostic pop
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Register the @a recipient for a notification if this binder
|
||||
* goes away. If this binder object unexpectedly goes away
|
||||
* (typically because its hosting process has been killed),
|
||||
* then DeathRecipient::binderDied() will be called with a reference
|
||||
* to this.
|
||||
*
|
||||
* The @a cookie is optional -- if non-NULL, it should be a
|
||||
* memory address that you own (that is, you know it is unique).
|
||||
*
|
||||
* @note When all references to the binder being linked to are dropped, the
|
||||
* recipient is automatically unlinked. So, you must hold onto a binder in
|
||||
* order to receive death notifications about it.
|
||||
*
|
||||
* @note You will only receive death notifications for remote binders,
|
||||
* as local binders by definition can't die without you dying as well.
|
||||
* Trying to use this function on a local binder will result in an
|
||||
* INVALID_OPERATION code being returned and nothing happening.
|
||||
*
|
||||
* @note This link always holds a weak reference to its recipient.
|
||||
*
|
||||
* @note You will only receive a weak reference to the dead
|
||||
* binder. You should not try to promote this to a strong reference.
|
||||
* (Nor should you need to, as there is nothing useful you can
|
||||
* directly do with it now that it has passed on.)
|
||||
*/
|
||||
// NOLINTNEXTLINE(google-default-arguments)
|
||||
virtual status_t linkToDeath(const sp<DeathRecipient>& recipient,
|
||||
void* cookie = nullptr,
|
||||
uint32_t flags = 0) = 0;
|
||||
|
||||
/**
|
||||
* Remove a previously registered death notification.
|
||||
* The @a recipient will no longer be called if this object
|
||||
* dies. The @a cookie is optional. If non-NULL, you can
|
||||
* supply a NULL @a recipient, and the recipient previously
|
||||
* added with that cookie will be unlinked.
|
||||
*
|
||||
* If the binder is dead, this will return DEAD_OBJECT. Deleting
|
||||
* the object will also unlink all death recipients.
|
||||
*/
|
||||
// NOLINTNEXTLINE(google-default-arguments)
|
||||
virtual status_t unlinkToDeath( const wp<DeathRecipient>& recipient,
|
||||
void* cookie = nullptr,
|
||||
uint32_t flags = 0,
|
||||
wp<DeathRecipient>* outRecipient = nullptr) = 0;
|
||||
|
||||
virtual bool checkSubclass(const void* subclassID) const;
|
||||
|
||||
typedef void (*object_cleanup_func)(const void* id, void* obj, void* cleanupCookie);
|
||||
|
||||
/**
|
||||
* This object is attached for the lifetime of this binder object. When
|
||||
* this binder object is destructed, the cleanup function of all attached
|
||||
* objects are invoked with their respective objectID, object, and
|
||||
* cleanupCookie. Access to these APIs can be made from multiple threads,
|
||||
* but calls from different threads are allowed to be interleaved.
|
||||
*
|
||||
* This returns the object which is already attached. If this returns a
|
||||
* non-null value, it means that attachObject failed (a given objectID can
|
||||
* only be used once).
|
||||
*/
|
||||
[[nodiscard]] virtual void* attachObject(const void* objectID, void* object,
|
||||
void* cleanupCookie, object_cleanup_func func) = 0;
|
||||
/**
|
||||
* Returns object attached with attachObject.
|
||||
*/
|
||||
[[nodiscard]] virtual void* findObject(const void* objectID) const = 0;
|
||||
/**
|
||||
* Returns object attached with attachObject, and detaches it. This does not
|
||||
* delete the object.
|
||||
*/
|
||||
[[nodiscard]] virtual void* detachObject(const void* objectID) = 0;
|
||||
|
||||
/**
|
||||
* Use the lock that this binder contains internally. For instance, this can
|
||||
* be used to modify an attached object without needing to add an additional
|
||||
* lock (though, that attached object must be retrieved before calling this
|
||||
* method). Calling (most) IBinder methods inside this will deadlock.
|
||||
*/
|
||||
void withLock(const std::function<void()>& doWithLock);
|
||||
|
||||
virtual BBinder* localBinder();
|
||||
virtual BpBinder* remoteBinder();
|
||||
|
||||
protected:
|
||||
virtual ~IBinder();
|
||||
|
||||
private:
|
||||
};
|
||||
|
||||
} // namespace android
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -0,0 +1,9 @@
|
||||
#pragma once
|
||||
|
||||
#include "utils/RefBase.h"
|
||||
|
||||
namespace android {
|
||||
class IInterface : public virtual RefBase {
|
||||
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
* Copyright (C) 2005 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <binder/Common.h>
|
||||
#include <binder/Parcel.h>
|
||||
#include <utils/Errors.h>
|
||||
#include <utils/Vector.h>
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
namespace android {
|
||||
|
||||
/**
|
||||
* Kernel binder thread state. All operations here refer to kernel binder. This
|
||||
* object is allocated per-thread.
|
||||
*/
|
||||
class IPCThreadState {
|
||||
public:
|
||||
|
||||
LIBBINDER_EXPORTED static IPCThreadState* self();
|
||||
LIBBINDER_EXPORTED static IPCThreadState* selfOrNull(); // self(), but won't instantiate
|
||||
|
||||
[[nodiscard]] LIBBINDER_EXPORTED pid_t getCallingPid() const;
|
||||
|
||||
/**
|
||||
* Returns the SELinux security identifier of the process which has
|
||||
* made the current binder call. If not in a binder call this will
|
||||
* return nullptr. If this isn't requested with
|
||||
* Binder::setRequestingSid, it will also return nullptr.
|
||||
*
|
||||
* This can't be restored once it's cleared, and it does not return the
|
||||
* context of the current process when not in a binder call.
|
||||
*/
|
||||
[[nodiscard]] LIBBINDER_EXPORTED const char* getCallingSid() const;
|
||||
|
||||
/**
|
||||
* Returns the UID of the process which has made the current binder
|
||||
* call. If not in a binder call, this will return 0.
|
||||
*/
|
||||
[[nodiscard]] LIBBINDER_EXPORTED uid_t getCallingUid() const;
|
||||
};
|
||||
|
||||
} // namespace android
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -0,0 +1,163 @@
|
||||
/*
|
||||
* Copyright (C) 2005 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <binder/Common.h>
|
||||
#include <binder/IInterface.h>
|
||||
#include <utils/Vector.h>
|
||||
#include <utils/String16.h>
|
||||
#include <optional>
|
||||
|
||||
namespace android {
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Service manager for C++ services.
|
||||
*
|
||||
* IInterface is only for legacy ABI compatibility
|
||||
*/
|
||||
class LIBBINDER_EXPORTED IServiceManager : public IInterface {
|
||||
public:
|
||||
// for ABI compatibility
|
||||
virtual const String16 &getInterfaceDescriptor() const;
|
||||
|
||||
IServiceManager();
|
||||
|
||||
virtual ~IServiceManager();
|
||||
|
||||
/**
|
||||
* Must match values in IServiceManager.aidl
|
||||
*/
|
||||
/* Allows services to dump sections according to priorities. */
|
||||
static const int DUMP_FLAG_PRIORITY_CRITICAL = 1 << 0;
|
||||
static const int DUMP_FLAG_PRIORITY_HIGH = 1 << 1;
|
||||
static const int DUMP_FLAG_PRIORITY_NORMAL = 1 << 2;
|
||||
/**
|
||||
* Services are by default registered with a DEFAULT dump priority. DEFAULT priority has the
|
||||
* same priority as NORMAL priority but the services are not called with dump priority
|
||||
* arguments.
|
||||
*/
|
||||
static const int DUMP_FLAG_PRIORITY_DEFAULT = 1 << 3;
|
||||
static const int DUMP_FLAG_PRIORITY_ALL = DUMP_FLAG_PRIORITY_CRITICAL |
|
||||
DUMP_FLAG_PRIORITY_HIGH |
|
||||
DUMP_FLAG_PRIORITY_NORMAL |
|
||||
DUMP_FLAG_PRIORITY_DEFAULT;
|
||||
static const int DUMP_FLAG_PROTO = 1 << 4;
|
||||
|
||||
/**
|
||||
* Retrieve an existing service, blocking for a few seconds if it doesn't yet exist. This
|
||||
* does polling. A more efficient way to make sure you unblock as soon as the service is
|
||||
* available is to use waitForService or to use service notifications.
|
||||
*
|
||||
* Warning: when using this API, typically, you should call it in a loop. It's dangerous to
|
||||
* assume that nullptr could mean that the service is not available. The service could just
|
||||
* be starting. Generally, whether a service exists, this information should be declared
|
||||
* externally (for instance, an Android feature might imply the existence of a service,
|
||||
* a system property, or in the case of services in the VINTF manifest, it can be checked
|
||||
* with isDeclared).
|
||||
*/
|
||||
[[deprecated("this polls for 5s, prefer waitForService or checkService")]]
|
||||
virtual sp<IBinder> getService(const String16 &name) const = 0;
|
||||
|
||||
/**
|
||||
* Retrieve an existing service, non-blocking.
|
||||
*/
|
||||
virtual sp<IBinder> checkService(const String16 &name) const = 0;
|
||||
|
||||
/**
|
||||
* Register a service.
|
||||
*/
|
||||
// NOLINTNEXTLINE(google-default-arguments)
|
||||
virtual status_t addService(const String16 &name, const sp<IBinder> &service,
|
||||
bool allowIsolated = false,
|
||||
int dumpsysFlags = DUMP_FLAG_PRIORITY_DEFAULT) = 0;
|
||||
|
||||
/**
|
||||
* Return list of all existing services.
|
||||
*/
|
||||
// NOLINTNEXTLINE(google-default-arguments)
|
||||
virtual Vector<String16> listServices(int dumpsysFlags = DUMP_FLAG_PRIORITY_ALL) = 0;
|
||||
|
||||
/**
|
||||
* Efficiently wait for a service.
|
||||
*
|
||||
* Returns nullptr only for permission problem or fatal error.
|
||||
*/
|
||||
virtual sp<IBinder> waitForService(const String16 &name) = 0;
|
||||
|
||||
/**
|
||||
* Check if a service is declared (e.g. VINTF manifest).
|
||||
*
|
||||
* If this returns true, waitForService should always be able to return the
|
||||
* service.
|
||||
*/
|
||||
virtual bool isDeclared(const String16 &name) = 0;
|
||||
|
||||
/**
|
||||
* Get all instances of a service as declared in the VINTF manifest
|
||||
*/
|
||||
virtual Vector<String16> getDeclaredInstances(const String16 &interface) = 0;
|
||||
|
||||
/**
|
||||
* If this instance is updatable via an APEX, returns the APEX with which
|
||||
* this can be updated.
|
||||
*/
|
||||
virtual std::optional<String16> updatableViaApex(const String16 &name) = 0;
|
||||
|
||||
/**
|
||||
* Returns all instances which are updatable via the APEX. Instance names are fully qualified
|
||||
* like `pack.age.IFoo/default`.
|
||||
*/
|
||||
virtual Vector<String16> getUpdatableNames(const String16 &apexName) = 0;
|
||||
|
||||
/**
|
||||
* If this instance has declared remote connection information, returns
|
||||
* the ConnectionInfo.
|
||||
*/
|
||||
struct ConnectionInfo {
|
||||
std::string ipAddress;
|
||||
unsigned int port;
|
||||
};
|
||||
|
||||
virtual std::optional<ConnectionInfo> getConnectionInfo(const String16 &name) = 0;
|
||||
|
||||
struct LocalRegistrationCallback : public virtual RefBase {
|
||||
virtual void
|
||||
onServiceRegistration(const String16 &instance, const sp<IBinder> &binder) = 0;
|
||||
|
||||
virtual ~LocalRegistrationCallback() {}
|
||||
};
|
||||
|
||||
virtual status_t registerForNotifications(const String16 &name,
|
||||
const sp<LocalRegistrationCallback> &callback) = 0;
|
||||
|
||||
virtual status_t unregisterForNotifications(const String16 &name,
|
||||
const sp<LocalRegistrationCallback> &callback) = 0;
|
||||
|
||||
struct ServiceDebugInfo {
|
||||
std::string name;
|
||||
int pid;
|
||||
};
|
||||
|
||||
virtual std::vector<ServiceDebugInfo> getServiceDebugInfo() = 0;
|
||||
};
|
||||
|
||||
LIBBINDER_EXPORTED sp<IServiceManager> defaultServiceManager();
|
||||
|
||||
LIBBINDER_EXPORTED void setDefaultServiceManager(const sp<IServiceManager> &sm);
|
||||
} // namespace android
|
||||
@@ -0,0 +1,190 @@
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <limits>
|
||||
#include <map> // for legacy reasons
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <type_traits>
|
||||
#include <variant>
|
||||
#include <vector>
|
||||
|
||||
#include <binder/unique_fd.h>
|
||||
#include <utils/Errors.h>
|
||||
#include <utils/RefBase.h>
|
||||
#include <utils/String16.h>
|
||||
#include <utils/Vector.h>
|
||||
|
||||
#include <binder/Common.h>
|
||||
|
||||
//NOLINTNEXTLINE(google-runtime-int) b/173188702
|
||||
typedef unsigned long long binder_size_t;
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
namespace android {
|
||||
class IBinder;
|
||||
namespace binder {
|
||||
class Status {};
|
||||
}
|
||||
|
||||
class Parcel {
|
||||
public:
|
||||
|
||||
LIBBINDER_EXPORTED Parcel();
|
||||
LIBBINDER_EXPORTED ~Parcel();
|
||||
|
||||
LIBBINDER_EXPORTED const uint8_t* data() const;
|
||||
LIBBINDER_EXPORTED size_t dataSize() const;
|
||||
LIBBINDER_EXPORTED size_t dataAvail() const;
|
||||
LIBBINDER_EXPORTED size_t dataPosition() const;
|
||||
LIBBINDER_EXPORTED size_t dataCapacity() const;
|
||||
LIBBINDER_EXPORTED size_t dataBufferSize() const;
|
||||
|
||||
LIBBINDER_EXPORTED status_t setDataSize(size_t size);
|
||||
|
||||
// this must only be used to set a data position that was previously returned from
|
||||
// dataPosition(). If writes are made, the exact same types of writes must be made (e.g.
|
||||
// auto i = p.dataPosition(); p.writeInt32(0); p.setDataPosition(i); p.writeInt32(1);).
|
||||
// Writing over objects, such as file descriptors and binders, is not supported.
|
||||
LIBBINDER_EXPORTED void setDataPosition(size_t pos) const;
|
||||
LIBBINDER_EXPORTED status_t setDataCapacity(size_t size);
|
||||
|
||||
LIBBINDER_EXPORTED status_t setData(const uint8_t* buffer, size_t len);
|
||||
|
||||
LIBBINDER_EXPORTED status_t appendFrom(const Parcel* parcel, size_t start, size_t len);
|
||||
|
||||
// Verify there are no bytes left to be read on the Parcel.
|
||||
// Returns Status(EX_BAD_PARCELABLE) when the Parcel is not consumed.
|
||||
LIBBINDER_EXPORTED binder::Status enforceNoDataAvail() const;
|
||||
|
||||
// This Api is used by fuzzers to skip dataAvail checks.
|
||||
LIBBINDER_EXPORTED void setEnforceNoDataAvail(bool enforceNoDataAvail);
|
||||
|
||||
LIBBINDER_EXPORTED void freeData();
|
||||
|
||||
LIBBINDER_EXPORTED status_t write(const void* data, size_t len);
|
||||
LIBBINDER_EXPORTED void* writeInplace(size_t len);
|
||||
LIBBINDER_EXPORTED status_t writeUnpadded(const void* data, size_t len);
|
||||
LIBBINDER_EXPORTED status_t writeInt32(int32_t val);
|
||||
LIBBINDER_EXPORTED status_t writeUint32(uint32_t val);
|
||||
LIBBINDER_EXPORTED status_t writeInt64(int64_t val);
|
||||
LIBBINDER_EXPORTED status_t writeUint64(uint64_t val);
|
||||
LIBBINDER_EXPORTED status_t writeFloat(float val);
|
||||
LIBBINDER_EXPORTED status_t writeDouble(double val);
|
||||
LIBBINDER_EXPORTED status_t writeCString(const char* str);
|
||||
LIBBINDER_EXPORTED status_t writeString8(const char* str, size_t len);
|
||||
LIBBINDER_EXPORTED status_t writeStrongBinder(const sp<IBinder>& val);
|
||||
LIBBINDER_EXPORTED status_t writeBool(bool val);
|
||||
LIBBINDER_EXPORTED status_t writeChar(char16_t val);
|
||||
LIBBINDER_EXPORTED status_t writeByte(int8_t val);
|
||||
|
||||
// Like Parcel.java's writeNoException(). Just writes a zero int32.
|
||||
// Currently the native implementation doesn't do any of the StrictMode
|
||||
// stack gathering and serialization that the Java implementation does.
|
||||
LIBBINDER_EXPORTED status_t writeNoException();
|
||||
|
||||
LIBBINDER_EXPORTED status_t read(void* outData, size_t len) const;
|
||||
LIBBINDER_EXPORTED const void* readInplace(size_t len) const;
|
||||
LIBBINDER_EXPORTED int32_t readInt32() const;
|
||||
LIBBINDER_EXPORTED status_t readInt32(int32_t* pArg) const;
|
||||
LIBBINDER_EXPORTED uint32_t readUint32() const;
|
||||
LIBBINDER_EXPORTED status_t readUint32(uint32_t* pArg) const;
|
||||
LIBBINDER_EXPORTED int64_t readInt64() const;
|
||||
LIBBINDER_EXPORTED status_t readInt64(int64_t* pArg) const;
|
||||
LIBBINDER_EXPORTED uint64_t readUint64() const;
|
||||
LIBBINDER_EXPORTED status_t readUint64(uint64_t* pArg) const;
|
||||
LIBBINDER_EXPORTED float readFloat() const;
|
||||
LIBBINDER_EXPORTED status_t readFloat(float* pArg) const;
|
||||
LIBBINDER_EXPORTED double readDouble() const;
|
||||
LIBBINDER_EXPORTED status_t readDouble(double* pArg) const;
|
||||
LIBBINDER_EXPORTED bool readBool() const;
|
||||
LIBBINDER_EXPORTED status_t readBool(bool* pArg) const;
|
||||
LIBBINDER_EXPORTED char16_t readChar() const;
|
||||
LIBBINDER_EXPORTED status_t readChar(char16_t* pArg) const;
|
||||
LIBBINDER_EXPORTED int8_t readByte() const;
|
||||
LIBBINDER_EXPORTED status_t readByte(int8_t* pArg) const;
|
||||
|
||||
LIBBINDER_EXPORTED sp<IBinder> readStrongBinder() const;
|
||||
LIBBINDER_EXPORTED status_t readStrongBinder(sp<IBinder>* val) const;
|
||||
LIBBINDER_EXPORTED status_t readNullableStrongBinder(sp<IBinder>* val) const;
|
||||
|
||||
// Like Parcel.java's readExceptionCode(). Reads the first int32
|
||||
// off of a Parcel's header, returning 0 or the negative error
|
||||
// code on exceptions, but also deals with skipping over rich
|
||||
// response headers. Callers should use this to read & parse the
|
||||
// response headers rather than doing it by hand.
|
||||
LIBBINDER_EXPORTED int32_t readExceptionCode() const;
|
||||
|
||||
// Retrieve a file descriptor from the parcel. This returns the raw fd
|
||||
// in the parcel, which you do not own -- use dup() to get your own copy.
|
||||
LIBBINDER_EXPORTED int readFileDescriptor() const;
|
||||
|
||||
private:
|
||||
// `objects` and `objectsSize` always 0 for RPC Parcels.
|
||||
typedef void (*release_func)(const uint8_t* data, size_t dataSize, const binder_size_t* objects,
|
||||
size_t objectsSize);
|
||||
|
||||
// special int32 value to indicate NonNull or Null parcelables
|
||||
// This is fixed to be only 0 or 1 by contract, do not change.
|
||||
static constexpr int32_t kNonNullParcelableFlag = 1;
|
||||
static constexpr int32_t kNullParcelableFlag = 0;
|
||||
|
||||
// special int32 size representing a null vector, when applicable in Nullable data.
|
||||
// This fixed as -1 by contract, do not change.
|
||||
static constexpr int32_t kNullVectorSize = -1;
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
private:
|
||||
|
||||
status_t mError;
|
||||
uint8_t* mData;
|
||||
size_t mDataSize;
|
||||
size_t mDataCapacity;
|
||||
mutable size_t mDataPos;
|
||||
|
||||
// Fields only needed when parcelling for "kernel Binder".
|
||||
struct KernelFields {
|
||||
KernelFields() {}
|
||||
binder_size_t* mObjects = nullptr;
|
||||
size_t mObjectsSize = 0;
|
||||
size_t mObjectsCapacity = 0;
|
||||
mutable size_t mNextObjectHint = 0;
|
||||
|
||||
mutable size_t mWorkSourceRequestHeaderPosition = 0;
|
||||
mutable bool mRequestHeaderPresent = false;
|
||||
|
||||
mutable bool mObjectsSorted = false;
|
||||
mutable bool mFdsKnown = true;
|
||||
mutable bool mHasFds = false;
|
||||
};
|
||||
// Fields only needed when parcelling for RPC Binder.
|
||||
struct RpcFields {};
|
||||
std::variant<KernelFields, RpcFields> mVariantFields;
|
||||
|
||||
// Pointer to KernelFields in mVariantFields if present.
|
||||
KernelFields* maybeKernelFields() { return std::get_if<KernelFields>(&mVariantFields); }
|
||||
const KernelFields* maybeKernelFields() const {
|
||||
return std::get_if<KernelFields>(&mVariantFields);
|
||||
}
|
||||
// Pointer to RpcFields in mVariantFields if present.
|
||||
RpcFields* maybeRpcFields() { return std::get_if<RpcFields>(&mVariantFields); }
|
||||
const RpcFields* maybeRpcFields() const { return std::get_if<RpcFields>(&mVariantFields); }
|
||||
|
||||
bool mAllowFds;
|
||||
|
||||
// if this parcelable is involved in a secure transaction, force the
|
||||
// data to be overridden with zero when deallocated
|
||||
mutable bool mDeallocZero;
|
||||
|
||||
// Set this to false to skip dataAvail checks.
|
||||
bool mEnforceNoDataAvail;
|
||||
bool mServiceFuzzing;
|
||||
|
||||
release_func mOwner;
|
||||
|
||||
size_t mReserved;
|
||||
};
|
||||
|
||||
} // namespace android
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -0,0 +1,5 @@
|
||||
#pragma once
|
||||
|
||||
namespace android::binder {
|
||||
class unique_fd;
|
||||
}
|
||||
@@ -0,0 +1,241 @@
|
||||
/*
|
||||
* This file is auto-generated. Modifications will be lost.
|
||||
*
|
||||
* See https://android.googlesource.com/platform/bionic/+/master/libc/kernel/
|
||||
* for more information.
|
||||
*/
|
||||
#ifndef _UAPI_LINUX_BINDER_H
|
||||
#define _UAPI_LINUX_BINDER_H
|
||||
|
||||
#include <linux/types.h>
|
||||
#include <linux/ioctl.h>
|
||||
|
||||
#ifndef B_PACK_CHARS
|
||||
#define B_PACK_CHARS(c1, c2, c3, c4) ((((c1) << 24)) | (((c2) << 16)) | (((c3) << 8)) | (c4))
|
||||
#endif
|
||||
#define B_TYPE_LARGE 0x85
|
||||
enum {
|
||||
BINDER_TYPE_BINDER = B_PACK_CHARS('s', 'b', '*', B_TYPE_LARGE),
|
||||
BINDER_TYPE_WEAK_BINDER = B_PACK_CHARS('w', 'b', '*', B_TYPE_LARGE),
|
||||
BINDER_TYPE_HANDLE = B_PACK_CHARS('s', 'h', '*', B_TYPE_LARGE),
|
||||
BINDER_TYPE_WEAK_HANDLE = B_PACK_CHARS('w', 'h', '*', B_TYPE_LARGE),
|
||||
BINDER_TYPE_FD = B_PACK_CHARS('f', 'd', '*', B_TYPE_LARGE),
|
||||
BINDER_TYPE_FDA = B_PACK_CHARS('f', 'd', 'a', B_TYPE_LARGE),
|
||||
BINDER_TYPE_PTR = B_PACK_CHARS('p', 't', '*', B_TYPE_LARGE),
|
||||
};
|
||||
enum flat_binder_object_shifts {
|
||||
FLAT_BINDER_FLAG_SCHED_POLICY_SHIFT = 9,
|
||||
};
|
||||
enum flat_binder_object_flags {
|
||||
FLAT_BINDER_FLAG_PRIORITY_MASK = 0xff,
|
||||
FLAT_BINDER_FLAG_ACCEPTS_FDS = 0x100,
|
||||
FLAT_BINDER_FLAG_SCHED_POLICY_MASK = 3U << FLAT_BINDER_FLAG_SCHED_POLICY_SHIFT,
|
||||
FLAT_BINDER_FLAG_INHERIT_RT = 0x800,
|
||||
FLAT_BINDER_FLAG_TXN_SECURITY_CTX = 0x1000,
|
||||
};
|
||||
#ifdef BINDER_IPC_32BIT
|
||||
typedef __u32 binder_size_t;
|
||||
typedef __u32 binder_uintptr_t;
|
||||
#else
|
||||
typedef __u64 binder_size_t;
|
||||
typedef __u64 binder_uintptr_t;
|
||||
#endif
|
||||
struct binder_object_header {
|
||||
__u32 type;
|
||||
};
|
||||
struct flat_binder_object {
|
||||
struct binder_object_header hdr;
|
||||
__u32 flags;
|
||||
union {
|
||||
binder_uintptr_t binder;
|
||||
__u32 handle;
|
||||
};
|
||||
binder_uintptr_t cookie;
|
||||
};
|
||||
struct binder_fd_object {
|
||||
struct binder_object_header hdr;
|
||||
__u32 pad_flags;
|
||||
union {
|
||||
binder_uintptr_t pad_binder;
|
||||
__u32 fd;
|
||||
};
|
||||
binder_uintptr_t cookie;
|
||||
};
|
||||
struct binder_buffer_object {
|
||||
struct binder_object_header hdr;
|
||||
__u32 flags;
|
||||
binder_uintptr_t buffer;
|
||||
binder_size_t length;
|
||||
binder_size_t parent;
|
||||
binder_size_t parent_offset;
|
||||
};
|
||||
enum {
|
||||
BINDER_BUFFER_FLAG_HAS_PARENT = 0x01,
|
||||
};
|
||||
struct binder_fd_array_object {
|
||||
struct binder_object_header hdr;
|
||||
__u32 pad;
|
||||
binder_size_t num_fds;
|
||||
binder_size_t parent;
|
||||
binder_size_t parent_offset;
|
||||
};
|
||||
struct binder_write_read {
|
||||
binder_size_t write_size;
|
||||
binder_size_t write_consumed;
|
||||
binder_uintptr_t write_buffer;
|
||||
binder_size_t read_size;
|
||||
binder_size_t read_consumed;
|
||||
binder_uintptr_t read_buffer;
|
||||
};
|
||||
struct binder_version {
|
||||
__s32 protocol_version;
|
||||
};
|
||||
#ifdef BINDER_IPC_32BIT
|
||||
#define BINDER_CURRENT_PROTOCOL_VERSION 7
|
||||
#else
|
||||
#define BINDER_CURRENT_PROTOCOL_VERSION 8
|
||||
#endif
|
||||
struct binder_node_debug_info {
|
||||
binder_uintptr_t ptr;
|
||||
binder_uintptr_t cookie;
|
||||
__u32 has_strong_ref;
|
||||
__u32 has_weak_ref;
|
||||
};
|
||||
struct binder_node_info_for_ref {
|
||||
__u32 handle;
|
||||
__u32 strong_count;
|
||||
__u32 weak_count;
|
||||
__u32 reserved1;
|
||||
__u32 reserved2;
|
||||
__u32 reserved3;
|
||||
};
|
||||
struct binder_freeze_info {
|
||||
__u32 pid;
|
||||
__u32 enable;
|
||||
__u32 timeout_ms;
|
||||
};
|
||||
struct binder_frozen_status_info {
|
||||
__u32 pid;
|
||||
__u32 sync_recv;
|
||||
__u32 async_recv;
|
||||
};
|
||||
struct binder_extended_error {
|
||||
__u32 id;
|
||||
__u32 command;
|
||||
__s32 param;
|
||||
};
|
||||
enum {
|
||||
BINDER_WRITE_READ = _IOWR('b', 1, struct binder_write_read),
|
||||
BINDER_SET_IDLE_TIMEOUT = _IOW('b', 3, __s64),
|
||||
BINDER_SET_MAX_THREADS = _IOW('b', 5, __u32),
|
||||
BINDER_SET_IDLE_PRIORITY = _IOW('b', 6, __s32),
|
||||
BINDER_SET_CONTEXT_MGR = _IOW('b', 7, __s32),
|
||||
BINDER_THREAD_EXIT = _IOW('b', 8, __s32),
|
||||
BINDER_VERSION = _IOWR('b', 9, struct binder_version),
|
||||
BINDER_GET_NODE_DEBUG_INFO = _IOWR('b', 11, struct binder_node_debug_info),
|
||||
BINDER_GET_NODE_INFO_FOR_REF = _IOWR('b', 12, struct binder_node_info_for_ref),
|
||||
BINDER_SET_CONTEXT_MGR_EXT = _IOW('b', 13, struct flat_binder_object),
|
||||
BINDER_FREEZE = _IOW('b', 14, struct binder_freeze_info),
|
||||
BINDER_GET_FROZEN_INFO = _IOWR('b', 15, struct binder_frozen_status_info),
|
||||
BINDER_ENABLE_ONEWAY_SPAM_DETECTION = _IOW('b', 16, __u32),
|
||||
BINDER_GET_EXTENDED_ERROR = _IOWR('b', 17, struct binder_extended_error),
|
||||
};
|
||||
enum transaction_flags {
|
||||
TF_ONE_WAY = 0x01,
|
||||
TF_ROOT_OBJECT = 0x04,
|
||||
TF_STATUS_CODE = 0x08,
|
||||
TF_ACCEPT_FDS = 0x10,
|
||||
TF_CLEAR_BUF = 0x20,
|
||||
TF_UPDATE_TXN = 0x40,
|
||||
};
|
||||
struct binder_transaction_data {
|
||||
union {
|
||||
__u32 handle;
|
||||
binder_uintptr_t ptr;
|
||||
} target;
|
||||
binder_uintptr_t cookie;
|
||||
__u32 code;
|
||||
__u32 flags;
|
||||
__kernel_pid_t sender_pid;
|
||||
__kernel_uid32_t sender_euid;
|
||||
binder_size_t data_size;
|
||||
binder_size_t offsets_size;
|
||||
union {
|
||||
struct {
|
||||
binder_uintptr_t buffer;
|
||||
binder_uintptr_t offsets;
|
||||
} ptr;
|
||||
__u8 buf[8];
|
||||
} data;
|
||||
};
|
||||
struct binder_transaction_data_secctx {
|
||||
struct binder_transaction_data transaction_data;
|
||||
binder_uintptr_t secctx;
|
||||
};
|
||||
struct binder_transaction_data_sg {
|
||||
struct binder_transaction_data transaction_data;
|
||||
binder_size_t buffers_size;
|
||||
};
|
||||
struct binder_ptr_cookie {
|
||||
binder_uintptr_t ptr;
|
||||
binder_uintptr_t cookie;
|
||||
};
|
||||
struct binder_handle_cookie {
|
||||
__u32 handle;
|
||||
binder_uintptr_t cookie;
|
||||
} __attribute__((__packed__));
|
||||
struct binder_pri_desc {
|
||||
__s32 priority;
|
||||
__u32 desc;
|
||||
};
|
||||
struct binder_pri_ptr_cookie {
|
||||
__s32 priority;
|
||||
binder_uintptr_t ptr;
|
||||
binder_uintptr_t cookie;
|
||||
};
|
||||
enum binder_driver_return_protocol {
|
||||
BR_ERROR = _IOR('r', 0, __s32),
|
||||
BR_OK = _IO('r', 1),
|
||||
BR_TRANSACTION_SEC_CTX = _IOR('r', 2, struct binder_transaction_data_secctx),
|
||||
BR_TRANSACTION = _IOR('r', 2, struct binder_transaction_data),
|
||||
BR_REPLY = _IOR('r', 3, struct binder_transaction_data),
|
||||
BR_ACQUIRE_RESULT = _IOR('r', 4, __s32),
|
||||
BR_DEAD_REPLY = _IO('r', 5),
|
||||
BR_TRANSACTION_COMPLETE = _IO('r', 6),
|
||||
BR_INCREFS = _IOR('r', 7, struct binder_ptr_cookie),
|
||||
BR_ACQUIRE = _IOR('r', 8, struct binder_ptr_cookie),
|
||||
BR_RELEASE = _IOR('r', 9, struct binder_ptr_cookie),
|
||||
BR_DECREFS = _IOR('r', 10, struct binder_ptr_cookie),
|
||||
BR_ATTEMPT_ACQUIRE = _IOR('r', 11, struct binder_pri_ptr_cookie),
|
||||
BR_NOOP = _IO('r', 12),
|
||||
BR_SPAWN_LOOPER = _IO('r', 13),
|
||||
BR_FINISHED = _IO('r', 14),
|
||||
BR_DEAD_BINDER = _IOR('r', 15, binder_uintptr_t),
|
||||
BR_CLEAR_DEATH_NOTIFICATION_DONE = _IOR('r', 16, binder_uintptr_t),
|
||||
BR_FAILED_REPLY = _IO('r', 17),
|
||||
BR_FROZEN_REPLY = _IO('r', 18),
|
||||
BR_ONEWAY_SPAM_SUSPECT = _IO('r', 19),
|
||||
BR_TRANSACTION_PENDING_FROZEN = _IO('r', 20),
|
||||
};
|
||||
enum binder_driver_command_protocol {
|
||||
BC_TRANSACTION = _IOW('c', 0, struct binder_transaction_data),
|
||||
BC_REPLY = _IOW('c', 1, struct binder_transaction_data),
|
||||
BC_ACQUIRE_RESULT = _IOW('c', 2, __s32),
|
||||
BC_FREE_BUFFER = _IOW('c', 3, binder_uintptr_t),
|
||||
BC_INCREFS = _IOW('c', 4, __u32),
|
||||
BC_ACQUIRE = _IOW('c', 5, __u32),
|
||||
BC_RELEASE = _IOW('c', 6, __u32),
|
||||
BC_DECREFS = _IOW('c', 7, __u32),
|
||||
BC_INCREFS_DONE = _IOW('c', 8, struct binder_ptr_cookie),
|
||||
BC_ACQUIRE_DONE = _IOW('c', 9, struct binder_ptr_cookie),
|
||||
BC_ATTEMPT_ACQUIRE = _IOW('c', 10, struct binder_pri_desc),
|
||||
BC_REGISTER_LOOPER = _IO('c', 11),
|
||||
BC_ENTER_LOOPER = _IO('c', 12),
|
||||
BC_EXIT_LOOPER = _IO('c', 13),
|
||||
BC_REQUEST_DEATH_NOTIFICATION = _IOW('c', 14, struct binder_handle_cookie),
|
||||
BC_CLEAR_DEATH_NOTIFICATION = _IOW('c', 15, struct binder_handle_cookie),
|
||||
BC_DEAD_BINDER_DONE = _IOW('c', 16, binder_uintptr_t),
|
||||
BC_TRANSACTION_SG = _IOW('c', 17, struct binder_transaction_data_sg),
|
||||
BC_REPLY_SG = _IOW('c', 18, struct binder_transaction_data_sg),
|
||||
};
|
||||
#endif
|
||||
@@ -0,0 +1,77 @@
|
||||
/*
|
||||
* Copyright (C) 2007 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cerrno>
|
||||
#include <cstdint>
|
||||
#include <sys/types.h>
|
||||
#include <string>
|
||||
|
||||
namespace android {
|
||||
|
||||
/**
|
||||
* The type used to return success/failure from frameworks APIs.
|
||||
* See the anonymous enum below for valid values.
|
||||
*/
|
||||
typedef int32_t status_t;
|
||||
|
||||
/*
|
||||
* Error codes.
|
||||
* All error codes are negative values.
|
||||
*/
|
||||
|
||||
enum {
|
||||
OK = 0, // Preferred constant for checking success.
|
||||
#ifndef NO_ERROR
|
||||
// Win32 #defines NO_ERROR as well. It has the same value, so there's no
|
||||
// real conflict, though it's a bit awkward.
|
||||
NO_ERROR = OK, // Deprecated synonym for `OK`. Prefer `OK` because it doesn't conflict with Windows.
|
||||
#endif
|
||||
|
||||
UNKNOWN_ERROR = (-2147483647-1), // INT32_MIN value
|
||||
|
||||
NO_MEMORY = -ENOMEM,
|
||||
INVALID_OPERATION = -ENOSYS,
|
||||
BAD_VALUE = -EINVAL,
|
||||
BAD_TYPE = (UNKNOWN_ERROR + 1),
|
||||
NAME_NOT_FOUND = -ENOENT,
|
||||
PERMISSION_DENIED = -EPERM,
|
||||
NO_INIT = -ENODEV,
|
||||
ALREADY_EXISTS = -EEXIST,
|
||||
DEAD_OBJECT = -EPIPE,
|
||||
FAILED_TRANSACTION = (UNKNOWN_ERROR + 2),
|
||||
#if !defined(_WIN32)
|
||||
BAD_INDEX = -EOVERFLOW,
|
||||
NOT_ENOUGH_DATA = -ENODATA,
|
||||
WOULD_BLOCK = -EWOULDBLOCK,
|
||||
TIMED_OUT = -ETIMEDOUT,
|
||||
UNKNOWN_TRANSACTION = -EBADMSG,
|
||||
#else
|
||||
BAD_INDEX = -E2BIG,
|
||||
NOT_ENOUGH_DATA = (UNKNOWN_ERROR + 3),
|
||||
WOULD_BLOCK = (UNKNOWN_ERROR + 4),
|
||||
TIMED_OUT = (UNKNOWN_ERROR + 5),
|
||||
UNKNOWN_TRANSACTION = (UNKNOWN_ERROR + 6),
|
||||
#endif
|
||||
FDS_NOT_ALLOWED = (UNKNOWN_ERROR + 7),
|
||||
UNEXPECTED_NULL = (UNKNOWN_ERROR + 8),
|
||||
};
|
||||
|
||||
// Human readable name of error
|
||||
std::string statusToString(status_t status);
|
||||
|
||||
} // namespace android
|
||||
@@ -0,0 +1,487 @@
|
||||
#ifndef ANDROID_REF_BASE_H
|
||||
#define ANDROID_REF_BASE_H
|
||||
|
||||
#include <atomic>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <type_traits> // for common_type.
|
||||
|
||||
#include <cstdint>
|
||||
#include <sys/types.h>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
|
||||
// LightRefBase used to be declared in this header, so we have to include it
|
||||
// #include <utils/LightRefBase.h>
|
||||
|
||||
#include <utils/StrongPointer.h>
|
||||
// #include <utils/TypeHelpers.h>
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
namespace android {
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
#define COMPARE_WEAK(_op_) \
|
||||
template<typename U> \
|
||||
inline bool operator _op_ (const U* o) const { \
|
||||
return m_ptr _op_ o; \
|
||||
} \
|
||||
/* Needed to handle type inference for nullptr: */ \
|
||||
inline bool operator _op_ (const T* o) const { \
|
||||
return m_ptr _op_ o; \
|
||||
}
|
||||
|
||||
template<template<typename C> class comparator, typename T, typename U>
|
||||
static inline bool _wp_compare_(T* a, U* b) {
|
||||
return comparator<typename std::common_type<T*, U*>::type>()(a, b);
|
||||
}
|
||||
|
||||
// Use std::less and friends to avoid undefined behavior when ordering pointers
|
||||
// to different objects.
|
||||
#define COMPARE_WEAK_FUNCTIONAL(_op_, _compare_) \
|
||||
template<typename U> \
|
||||
inline bool operator _op_ (const U* o) const { \
|
||||
return _wp_compare_<_compare_>(m_ptr, o); \
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
// RefererenceRenamer is pure abstract, there is no virtual method
|
||||
// implementation to put in a translation unit in order to silence the
|
||||
// weak vtables warning.
|
||||
#if defined(__clang__)
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wweak-vtables"
|
||||
#endif
|
||||
|
||||
class ReferenceRenamer {
|
||||
protected:
|
||||
// destructor is purposely not virtual so we avoid code overhead from
|
||||
// subclasses; we have to make it protected to guarantee that it
|
||||
// cannot be called from this base class (and to make strict compilers
|
||||
// happy).
|
||||
~ReferenceRenamer() { }
|
||||
public:
|
||||
virtual void operator()(size_t i) const = 0;
|
||||
};
|
||||
|
||||
#if defined(__clang__)
|
||||
#pragma clang diagnostic pop
|
||||
#endif
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
class RefBase
|
||||
{
|
||||
public:
|
||||
void incStrong(const void* id) const;
|
||||
void incStrongRequireStrong(const void* id) const;
|
||||
void decStrong(const void* id) const;
|
||||
|
||||
void forceIncStrong(const void* id) const;
|
||||
|
||||
class weakref_type
|
||||
{
|
||||
public:
|
||||
RefBase* refBase() const;
|
||||
|
||||
void incWeak(const void* id);
|
||||
void incWeakRequireWeak(const void* id);
|
||||
void decWeak(const void* id);
|
||||
|
||||
// acquires a strong reference if there is already one.
|
||||
bool attemptIncStrong(const void* id);
|
||||
|
||||
// acquires a weak reference if there is already one.
|
||||
// This is not always safe. see ProcessState.cpp and BpBinder.cpp
|
||||
// for proper use.
|
||||
bool attemptIncWeak(const void* id);
|
||||
};
|
||||
|
||||
weakref_type* createWeak(const void* id) const;
|
||||
|
||||
weakref_type* getWeakRefs() const;
|
||||
protected:
|
||||
// When constructing these objects, prefer using sp::make<>. Using a RefBase
|
||||
// object on the stack or with other refcount mechanisms (e.g.
|
||||
// std::shared_ptr) is inherently wrong. RefBase types have an implicit
|
||||
// ownership model and cannot be safely used with other ownership models.
|
||||
|
||||
RefBase();
|
||||
virtual ~RefBase();
|
||||
|
||||
//! Flags for onIncStrongAttempted()
|
||||
enum {
|
||||
FIRST_INC_STRONG = 0x0001
|
||||
};
|
||||
|
||||
// Invoked after creation of initial strong pointer/reference.
|
||||
virtual void onFirstRef();
|
||||
// Invoked when either the last strong reference goes away, or we need to undo
|
||||
// the effect of an unnecessary onIncStrongAttempted.
|
||||
virtual void onLastStrongRef(const void* id);
|
||||
// Only called in OBJECT_LIFETIME_WEAK case. Returns true if OK to promote to
|
||||
// strong reference. May have side effects if it returns true.
|
||||
// The first flags argument is always FIRST_INC_STRONG.
|
||||
// TODO: Remove initial flag argument.
|
||||
virtual bool onIncStrongAttempted(uint32_t flags, const void* id);
|
||||
// Invoked in the OBJECT_LIFETIME_WEAK case when the last reference of either
|
||||
// kind goes away. Unused.
|
||||
// TODO: Remove.
|
||||
virtual void onLastWeakRef(const void* id);
|
||||
|
||||
private:
|
||||
friend class weakref_type;
|
||||
class weakref_impl;
|
||||
|
||||
RefBase(const RefBase& o);
|
||||
RefBase& operator=(const RefBase& o);
|
||||
|
||||
private:
|
||||
weakref_impl* const mRefs;
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
template <typename T>
|
||||
class wp
|
||||
{
|
||||
public:
|
||||
typedef typename RefBase::weakref_type weakref_type;
|
||||
|
||||
inline wp() : m_ptr(nullptr), m_refs(nullptr) { }
|
||||
|
||||
// if nullptr, returns nullptr
|
||||
//
|
||||
// if a weak pointer is already available, this will retrieve it,
|
||||
// otherwise, this will abort
|
||||
static inline wp<T> fromExisting(T* other);
|
||||
|
||||
// for more information about this flag, see above
|
||||
#if defined(ANDROID_UTILS_REF_BASE_DISABLE_IMPLICIT_CONSTRUCTION)
|
||||
wp(std::nullptr_t) : wp() {}
|
||||
#else
|
||||
wp(T* other); // NOLINT(implicit)
|
||||
template <typename U>
|
||||
wp(U* other); // NOLINT(implicit)
|
||||
wp& operator=(T* other);
|
||||
template <typename U>
|
||||
wp& operator=(U* other);
|
||||
#endif
|
||||
|
||||
wp(const wp<T>& other);
|
||||
explicit wp(const sp<T>& other);
|
||||
|
||||
template<typename U> wp(const sp<U>& other); // NOLINT(implicit)
|
||||
template<typename U> wp(const wp<U>& other); // NOLINT(implicit)
|
||||
|
||||
~wp();
|
||||
|
||||
// Assignment
|
||||
|
||||
wp& operator = (const wp<T>& other);
|
||||
wp& operator = (const sp<T>& other);
|
||||
|
||||
template<typename U> wp& operator = (const wp<U>& other);
|
||||
template<typename U> wp& operator = (const sp<U>& other);
|
||||
|
||||
void set_object_and_refs(T* other, weakref_type* refs);
|
||||
|
||||
// promotion to sp
|
||||
|
||||
sp<T> promote() const;
|
||||
|
||||
// Reset
|
||||
|
||||
void clear();
|
||||
|
||||
// Accessors
|
||||
|
||||
inline weakref_type* get_refs() const { return m_refs; }
|
||||
|
||||
inline T* unsafe_get() const { return m_ptr; }
|
||||
|
||||
// Operators
|
||||
|
||||
COMPARE_WEAK(==)
|
||||
COMPARE_WEAK(!=)
|
||||
COMPARE_WEAK_FUNCTIONAL(>, std::greater)
|
||||
COMPARE_WEAK_FUNCTIONAL(<, std::less)
|
||||
COMPARE_WEAK_FUNCTIONAL(<=, std::less_equal)
|
||||
COMPARE_WEAK_FUNCTIONAL(>=, std::greater_equal)
|
||||
|
||||
template<typename U>
|
||||
inline bool operator == (const wp<U>& o) const {
|
||||
return m_refs == o.m_refs; // Implies m_ptr == o.mptr; see invariants below.
|
||||
}
|
||||
|
||||
template<typename U>
|
||||
inline bool operator == (const sp<U>& o) const {
|
||||
// Just comparing m_ptr fields is often dangerous, since wp<> may refer to an older
|
||||
// object at the same address.
|
||||
if (o == nullptr) {
|
||||
return m_ptr == nullptr;
|
||||
} else {
|
||||
return m_refs == o->getWeakRefs(); // Implies m_ptr == o.mptr.
|
||||
}
|
||||
}
|
||||
|
||||
template<typename U>
|
||||
inline bool operator != (const sp<U>& o) const {
|
||||
return !(*this == o);
|
||||
}
|
||||
|
||||
template<typename U>
|
||||
inline bool operator > (const wp<U>& o) const {
|
||||
if (m_ptr == o.m_ptr) {
|
||||
return _wp_compare_<std::greater>(m_refs, o.m_refs);
|
||||
} else {
|
||||
return _wp_compare_<std::greater>(m_ptr, o.m_ptr);
|
||||
}
|
||||
}
|
||||
|
||||
template<typename U>
|
||||
inline bool operator < (const wp<U>& o) const {
|
||||
if (m_ptr == o.m_ptr) {
|
||||
return _wp_compare_<std::less>(m_refs, o.m_refs);
|
||||
} else {
|
||||
return _wp_compare_<std::less>(m_ptr, o.m_ptr);
|
||||
}
|
||||
}
|
||||
template<typename U> inline bool operator != (const wp<U>& o) const { return !operator == (o); }
|
||||
template<typename U> inline bool operator <= (const wp<U>& o) const { return !operator > (o); }
|
||||
template<typename U> inline bool operator >= (const wp<U>& o) const { return !operator < (o); }
|
||||
|
||||
private:
|
||||
template<typename Y> friend class sp;
|
||||
template<typename Y> friend class wp;
|
||||
|
||||
T* m_ptr;
|
||||
weakref_type* m_refs;
|
||||
};
|
||||
|
||||
#undef COMPARE_WEAK
|
||||
#undef COMPARE_WEAK_FUNCTIONAL
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// No user serviceable parts below here.
|
||||
|
||||
// Implementation invariants:
|
||||
// Either
|
||||
// 1) m_ptr and m_refs are both null, or
|
||||
// 2) m_refs == m_ptr->mRefs, or
|
||||
// 3) *m_ptr is no longer live, and m_refs points to the weakref_type object that corresponded
|
||||
// to m_ptr while it was live. *m_refs remains live while a wp<> refers to it.
|
||||
//
|
||||
// The m_refs field in a RefBase object is allocated on construction, unique to that RefBase
|
||||
// object, and never changes. Thus if two wp's have identical m_refs fields, they are either both
|
||||
// null or point to the same object. If two wp's have identical m_ptr fields, they either both
|
||||
// point to the same live object and thus have the same m_ref fields, or at least one of the
|
||||
// objects is no longer live.
|
||||
//
|
||||
// Note that the above comparison operations go out of their way to provide an ordering consistent
|
||||
// with ordinary pointer comparison; otherwise they could ignore m_ptr, and just compare m_refs.
|
||||
|
||||
template <typename T>
|
||||
wp<T> wp<T>::fromExisting(T* other) {
|
||||
if (!other) return nullptr;
|
||||
|
||||
auto refs = other->getWeakRefs();
|
||||
refs->incWeakRequireWeak(other);
|
||||
|
||||
wp<T> ret;
|
||||
ret.m_ptr = other;
|
||||
ret.m_refs = refs;
|
||||
return ret;
|
||||
}
|
||||
|
||||
#if !defined(ANDROID_UTILS_REF_BASE_DISABLE_IMPLICIT_CONSTRUCTION)
|
||||
template<typename T>
|
||||
wp<T>::wp(T* other)
|
||||
: m_ptr(other)
|
||||
{
|
||||
m_refs = other ? m_refs = other->createWeak(this) : nullptr;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
template <typename U>
|
||||
wp<T>::wp(U* other) : m_ptr(other) {
|
||||
m_refs = other ? other->createWeak(this) : nullptr;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
wp<T>& wp<T>::operator=(T* other) {
|
||||
weakref_type* newRefs = other ? other->createWeak(this) : nullptr;
|
||||
if (m_ptr) m_refs->decWeak(this);
|
||||
m_ptr = other;
|
||||
m_refs = newRefs;
|
||||
return *this;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
template <typename U>
|
||||
wp<T>& wp<T>::operator=(U* other) {
|
||||
weakref_type* newRefs = other ? other->createWeak(this) : 0;
|
||||
if (m_ptr) m_refs->decWeak(this);
|
||||
m_ptr = other;
|
||||
m_refs = newRefs;
|
||||
return *this;
|
||||
}
|
||||
#endif
|
||||
|
||||
template<typename T>
|
||||
wp<T>::wp(const wp<T>& other)
|
||||
: m_ptr(other.m_ptr), m_refs(other.m_refs)
|
||||
{
|
||||
if (m_ptr) m_refs->incWeak(this);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
wp<T>::wp(const sp<T>& other)
|
||||
: m_ptr(other.m_ptr)
|
||||
{
|
||||
m_refs = m_ptr ? m_ptr->createWeak(this) : nullptr;
|
||||
}
|
||||
|
||||
template<typename T> template<typename U>
|
||||
wp<T>::wp(const wp<U>& other)
|
||||
: m_ptr(other.m_ptr)
|
||||
{
|
||||
if (m_ptr) {
|
||||
m_refs = other.m_refs;
|
||||
m_refs->incWeak(this);
|
||||
} else {
|
||||
m_refs = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
template<typename T> template<typename U>
|
||||
wp<T>::wp(const sp<U>& other)
|
||||
: m_ptr(other.m_ptr)
|
||||
{
|
||||
m_refs = m_ptr ? m_ptr->createWeak(this) : nullptr;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
wp<T>::~wp()
|
||||
{
|
||||
if (m_ptr) m_refs->decWeak(this);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
wp<T>& wp<T>::operator = (const wp<T>& other)
|
||||
{
|
||||
weakref_type* otherRefs(other.m_refs);
|
||||
T* otherPtr(other.m_ptr);
|
||||
if (otherPtr) otherRefs->incWeak(this);
|
||||
if (m_ptr) m_refs->decWeak(this);
|
||||
m_ptr = otherPtr;
|
||||
m_refs = otherRefs;
|
||||
return *this;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
wp<T>& wp<T>::operator = (const sp<T>& other)
|
||||
{
|
||||
weakref_type* newRefs =
|
||||
other != nullptr ? other->createWeak(this) : nullptr;
|
||||
T* otherPtr(other.m_ptr);
|
||||
if (m_ptr) m_refs->decWeak(this);
|
||||
m_ptr = otherPtr;
|
||||
m_refs = newRefs;
|
||||
return *this;
|
||||
}
|
||||
|
||||
template<typename T> template<typename U>
|
||||
wp<T>& wp<T>::operator = (const wp<U>& other)
|
||||
{
|
||||
weakref_type* otherRefs(other.m_refs);
|
||||
U* otherPtr(other.m_ptr);
|
||||
if (otherPtr) otherRefs->incWeak(this);
|
||||
if (m_ptr) m_refs->decWeak(this);
|
||||
m_ptr = otherPtr;
|
||||
m_refs = otherRefs;
|
||||
return *this;
|
||||
}
|
||||
|
||||
template<typename T> template<typename U>
|
||||
wp<T>& wp<T>::operator = (const sp<U>& other)
|
||||
{
|
||||
weakref_type* newRefs =
|
||||
other != nullptr ? other->createWeak(this) : 0;
|
||||
U* otherPtr(other.m_ptr);
|
||||
if (m_ptr) m_refs->decWeak(this);
|
||||
m_ptr = otherPtr;
|
||||
m_refs = newRefs;
|
||||
return *this;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void wp<T>::set_object_and_refs(T* other, weakref_type* refs)
|
||||
{
|
||||
if (other) refs->incWeak(this);
|
||||
if (m_ptr) m_refs->decWeak(this);
|
||||
m_ptr = other;
|
||||
m_refs = refs;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
sp<T> wp<T>::promote() const
|
||||
{
|
||||
sp<T> result;
|
||||
if (m_ptr && m_refs->attemptIncStrong(&result)) {
|
||||
result.set_pointer(m_ptr);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void wp<T>::clear()
|
||||
{
|
||||
if (m_ptr) {
|
||||
m_refs->decWeak(this);
|
||||
m_refs = 0;
|
||||
m_ptr = 0;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace android
|
||||
|
||||
namespace libutilsinternal {
|
||||
template <typename T, typename = void>
|
||||
struct is_complete_type : std::false_type {};
|
||||
|
||||
template <typename T>
|
||||
struct is_complete_type<T, decltype(void(sizeof(T)))> : std::true_type {};
|
||||
} // namespace libutilsinternal
|
||||
|
||||
namespace std {
|
||||
|
||||
// Define `RefBase` specific versions of `std::make_shared` and
|
||||
// `std::make_unique` to block people from using them. Using them to allocate
|
||||
// `RefBase` objects results in double ownership. Use
|
||||
// `sp<T>::make(...)` instead.
|
||||
//
|
||||
// Note: We exclude incomplete types because `std::is_base_of` is undefined in
|
||||
// that case.
|
||||
|
||||
template <typename T, typename... Args,
|
||||
typename std::enable_if<libutilsinternal::is_complete_type<T>::value, bool>::value = true,
|
||||
typename std::enable_if<std::is_base_of<android::RefBase, T>::value, bool>::value = true>
|
||||
shared_ptr<T> make_shared(Args...) { // SEE COMMENT ABOVE.
|
||||
static_assert(!std::is_base_of<android::RefBase, T>::value, "Must use RefBase with sp<>");
|
||||
}
|
||||
|
||||
template <typename T, typename... Args,
|
||||
typename std::enable_if<libutilsinternal::is_complete_type<T>::value, bool>::value = true,
|
||||
typename std::enable_if<std::is_base_of<android::RefBase, T>::value, bool>::value = true>
|
||||
unique_ptr<T> make_unique(Args...) { // SEE COMMENT ABOVE.
|
||||
static_assert(!std::is_base_of<android::RefBase, T>::value, "Must use RefBase with sp<>");
|
||||
}
|
||||
|
||||
} // namespace std
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
#endif // ANDROID_REF_BASE_H
|
||||
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* Copyright (C) 2005 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef ANDROID_STRING16_H
|
||||
#define ANDROID_STRING16_H
|
||||
|
||||
namespace android {
|
||||
class String16 {
|
||||
public:
|
||||
String16();
|
||||
|
||||
String16(const String16 &o);
|
||||
|
||||
String16(String16 &&o) noexcept;
|
||||
|
||||
explicit String16(const char *o);
|
||||
|
||||
~String16();
|
||||
|
||||
private:
|
||||
const char16_t *mString;
|
||||
};
|
||||
|
||||
|
||||
} // namespace android
|
||||
|
||||
#endif // ANDROID_STRING16_H
|
||||
@@ -0,0 +1,361 @@
|
||||
/*
|
||||
* Copyright (C) 2005 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef ANDROID_STRONG_POINTER_H
|
||||
#define ANDROID_STRONG_POINTER_H
|
||||
|
||||
#include <functional>
|
||||
#include <type_traits> // for common_type.
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
namespace android {
|
||||
|
||||
template<typename T> class wp;
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
template<typename T>
|
||||
class sp {
|
||||
public:
|
||||
inline sp() : m_ptr(nullptr) { }
|
||||
|
||||
// The old way of using sp<> was like this. This is bad because it relies
|
||||
// on implicit conversion to sp<>, which we would like to remove (if an
|
||||
// object is being managed some other way, this is double-ownership). We
|
||||
// want to move away from this:
|
||||
//
|
||||
// sp<Foo> foo = new Foo(...); // DO NOT DO THIS
|
||||
//
|
||||
// Instead, prefer to do this:
|
||||
//
|
||||
// sp<Foo> foo = sp<Foo>::make(...); // DO THIS
|
||||
//
|
||||
// Sometimes, in order to use this, when a constructor is marked as private,
|
||||
// you may need to add this to your class:
|
||||
//
|
||||
// friend class sp<Foo>;
|
||||
template <typename... Args>
|
||||
static inline sp<T> make(Args&&... args);
|
||||
|
||||
// if nullptr, returns nullptr
|
||||
//
|
||||
// if a strong pointer is already available, this will retrieve it,
|
||||
// otherwise, this will abort
|
||||
static inline sp<T> fromExisting(T* other);
|
||||
|
||||
// for more information about this macro and correct RefBase usage, see
|
||||
// the comment at the top of utils/RefBase.h
|
||||
#if defined(ANDROID_UTILS_REF_BASE_DISABLE_IMPLICIT_CONSTRUCTION)
|
||||
sp(std::nullptr_t) : sp() {}
|
||||
#else
|
||||
sp(T* other); // NOLINT(implicit)
|
||||
template <typename U>
|
||||
sp(U* other); // NOLINT(implicit)
|
||||
sp& operator=(T* other);
|
||||
template <typename U>
|
||||
sp& operator=(U* other);
|
||||
#endif
|
||||
|
||||
sp(const sp<T>& other);
|
||||
sp(sp<T>&& other) noexcept;
|
||||
|
||||
template<typename U> sp(const sp<U>& other); // NOLINT(implicit)
|
||||
template<typename U> sp(sp<U>&& other); // NOLINT(implicit)
|
||||
|
||||
// Cast a strong pointer directly from one type to another. Constructors
|
||||
// allow changing types, but only if they are pointer-compatible. This does
|
||||
// a static_cast internally.
|
||||
template <typename U>
|
||||
static inline sp<T> cast(const sp<U>& other);
|
||||
|
||||
~sp();
|
||||
|
||||
// Assignment
|
||||
|
||||
sp& operator = (const sp<T>& other);
|
||||
sp& operator=(sp<T>&& other) noexcept;
|
||||
|
||||
template<typename U> sp& operator = (const sp<U>& other);
|
||||
template<typename U> sp& operator = (sp<U>&& other);
|
||||
|
||||
//! Special optimization for use by ProcessState (and nobody else).
|
||||
void force_set(T* other);
|
||||
|
||||
// Reset
|
||||
|
||||
void clear();
|
||||
|
||||
// Accessors
|
||||
|
||||
inline T& operator* () const { return *m_ptr; }
|
||||
inline T* operator-> () const { return m_ptr; }
|
||||
inline T* get() const { return m_ptr; }
|
||||
inline explicit operator bool () const { return m_ptr != nullptr; }
|
||||
|
||||
// Punt these to the wp<> implementation.
|
||||
template<typename U>
|
||||
inline bool operator == (const wp<U>& o) const {
|
||||
return o == *this;
|
||||
}
|
||||
|
||||
template<typename U>
|
||||
inline bool operator != (const wp<U>& o) const {
|
||||
return o != *this;
|
||||
}
|
||||
|
||||
private:
|
||||
template<typename Y> friend class sp;
|
||||
template<typename Y> friend class wp;
|
||||
void set_pointer(T* ptr);
|
||||
T* m_ptr;
|
||||
};
|
||||
|
||||
#define COMPARE_STRONG(_op_) \
|
||||
template <typename T, typename U> \
|
||||
static inline bool operator _op_(const sp<T>& t, const sp<U>& u) { \
|
||||
return t.get() _op_ u.get(); \
|
||||
} \
|
||||
template <typename T, typename U> \
|
||||
static inline bool operator _op_(const T* t, const sp<U>& u) { \
|
||||
return t _op_ u.get(); \
|
||||
} \
|
||||
template <typename T, typename U> \
|
||||
static inline bool operator _op_(const sp<T>& t, const U* u) { \
|
||||
return t.get() _op_ u; \
|
||||
} \
|
||||
template <typename T> \
|
||||
static inline bool operator _op_(const sp<T>& t, std::nullptr_t) { \
|
||||
return t.get() _op_ nullptr; \
|
||||
} \
|
||||
template <typename T> \
|
||||
static inline bool operator _op_(std::nullptr_t, const sp<T>& t) { \
|
||||
return nullptr _op_ t.get(); \
|
||||
}
|
||||
|
||||
template <template <typename C> class comparator, typename T, typename U>
|
||||
static inline bool _sp_compare_(T* a, U* b) {
|
||||
return comparator<typename std::common_type<T*, U*>::type>()(a, b);
|
||||
}
|
||||
|
||||
#define COMPARE_STRONG_FUNCTIONAL(_op_, _compare_) \
|
||||
template <typename T, typename U> \
|
||||
static inline bool operator _op_(const sp<T>& t, const sp<U>& u) { \
|
||||
return _sp_compare_<_compare_>(t.get(), u.get()); \
|
||||
} \
|
||||
template <typename T, typename U> \
|
||||
static inline bool operator _op_(const T* t, const sp<U>& u) { \
|
||||
return _sp_compare_<_compare_>(t, u.get()); \
|
||||
} \
|
||||
template <typename T, typename U> \
|
||||
static inline bool operator _op_(const sp<T>& t, const U* u) { \
|
||||
return _sp_compare_<_compare_>(t.get(), u); \
|
||||
} \
|
||||
template <typename T> \
|
||||
static inline bool operator _op_(const sp<T>& t, std::nullptr_t) { \
|
||||
return _sp_compare_<_compare_>(t.get(), nullptr); \
|
||||
} \
|
||||
template <typename T> \
|
||||
static inline bool operator _op_(std::nullptr_t, const sp<T>& t) { \
|
||||
return _sp_compare_<_compare_>(nullptr, t.get()); \
|
||||
}
|
||||
|
||||
COMPARE_STRONG(==)
|
||||
COMPARE_STRONG(!=)
|
||||
COMPARE_STRONG_FUNCTIONAL(>, std::greater)
|
||||
COMPARE_STRONG_FUNCTIONAL(<, std::less)
|
||||
COMPARE_STRONG_FUNCTIONAL(<=, std::less_equal)
|
||||
COMPARE_STRONG_FUNCTIONAL(>=, std::greater_equal)
|
||||
|
||||
#undef COMPARE_STRONG
|
||||
#undef COMPARE_STRONG_FUNCTIONAL
|
||||
|
||||
// For code size reasons, we do not want these inlined or templated.
|
||||
void sp_report_race();
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// No user serviceable parts below here.
|
||||
|
||||
// TODO: Ideally we should find a way to increment the reference count before running the
|
||||
// constructor, so that generating an sp<> to this in the constructor is no longer dangerous.
|
||||
template <typename T>
|
||||
template <typename... Args>
|
||||
sp<T> sp<T>::make(Args&&... args) {
|
||||
T* t = new T(std::forward<Args>(args)...);
|
||||
sp<T> result;
|
||||
result.m_ptr = t;
|
||||
t->incStrong(t);
|
||||
return result;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
sp<T> sp<T>::fromExisting(T* other) {
|
||||
if (other) {
|
||||
other->incStrongRequireStrong(other);
|
||||
sp<T> result;
|
||||
result.m_ptr = other;
|
||||
return result;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
#if !defined(ANDROID_UTILS_REF_BASE_DISABLE_IMPLICIT_CONSTRUCTION)
|
||||
template<typename T>
|
||||
sp<T>::sp(T* other)
|
||||
: m_ptr(other) {
|
||||
if (other) {
|
||||
other->incStrong(this);
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
template <typename U>
|
||||
sp<T>::sp(U* other) : m_ptr(other) {
|
||||
if (other) {
|
||||
(static_cast<T*>(other))->incStrong(this);
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
sp<T>& sp<T>::operator=(T* other) {
|
||||
T* oldPtr(*const_cast<T* volatile*>(&m_ptr));
|
||||
if (other) {
|
||||
other->incStrong(this);
|
||||
}
|
||||
if (oldPtr) oldPtr->decStrong(this);
|
||||
if (oldPtr != *const_cast<T* volatile*>(&m_ptr)) sp_report_race();
|
||||
m_ptr = other;
|
||||
return *this;
|
||||
}
|
||||
#endif
|
||||
|
||||
template<typename T>
|
||||
sp<T>::sp(const sp<T>& other)
|
||||
: m_ptr(other.m_ptr) {
|
||||
if (m_ptr)
|
||||
m_ptr->incStrong(this);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
sp<T>::sp(sp<T>&& other) noexcept : m_ptr(other.m_ptr) {
|
||||
other.m_ptr = nullptr;
|
||||
}
|
||||
|
||||
template<typename T> template<typename U>
|
||||
sp<T>::sp(const sp<U>& other)
|
||||
: m_ptr(other.m_ptr) {
|
||||
if (m_ptr)
|
||||
m_ptr->incStrong(this);
|
||||
}
|
||||
|
||||
template<typename T> template<typename U>
|
||||
sp<T>::sp(sp<U>&& other)
|
||||
: m_ptr(other.m_ptr) {
|
||||
other.m_ptr = nullptr;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
template <typename U>
|
||||
sp<T> sp<T>::cast(const sp<U>& other) {
|
||||
return sp<T>::fromExisting(static_cast<T*>(other.get()));
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
sp<T>::~sp() {
|
||||
if (m_ptr)
|
||||
m_ptr->decStrong(this);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
sp<T>& sp<T>::operator =(const sp<T>& other) {
|
||||
// Force m_ptr to be read twice, to heuristically check for data races.
|
||||
T* oldPtr(*const_cast<T* volatile*>(&m_ptr));
|
||||
T* otherPtr(other.m_ptr);
|
||||
if (otherPtr) otherPtr->incStrong(this);
|
||||
if (oldPtr) oldPtr->decStrong(this);
|
||||
if (oldPtr != *const_cast<T* volatile*>(&m_ptr)) sp_report_race();
|
||||
m_ptr = otherPtr;
|
||||
return *this;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
sp<T>& sp<T>::operator=(sp<T>&& other) noexcept {
|
||||
T* oldPtr(*const_cast<T* volatile*>(&m_ptr));
|
||||
if (oldPtr) oldPtr->decStrong(this);
|
||||
if (oldPtr != *const_cast<T* volatile*>(&m_ptr)) sp_report_race();
|
||||
m_ptr = other.m_ptr;
|
||||
other.m_ptr = nullptr;
|
||||
return *this;
|
||||
}
|
||||
|
||||
template<typename T> template<typename U>
|
||||
sp<T>& sp<T>::operator =(const sp<U>& other) {
|
||||
T* oldPtr(*const_cast<T* volatile*>(&m_ptr));
|
||||
T* otherPtr(other.m_ptr);
|
||||
if (otherPtr) otherPtr->incStrong(this);
|
||||
if (oldPtr) oldPtr->decStrong(this);
|
||||
if (oldPtr != *const_cast<T* volatile*>(&m_ptr)) sp_report_race();
|
||||
m_ptr = otherPtr;
|
||||
return *this;
|
||||
}
|
||||
|
||||
template<typename T> template<typename U>
|
||||
sp<T>& sp<T>::operator =(sp<U>&& other) {
|
||||
T* oldPtr(*const_cast<T* volatile*>(&m_ptr));
|
||||
if (m_ptr) m_ptr->decStrong(this);
|
||||
if (oldPtr != *const_cast<T* volatile*>(&m_ptr)) sp_report_race();
|
||||
m_ptr = other.m_ptr;
|
||||
other.m_ptr = nullptr;
|
||||
return *this;
|
||||
}
|
||||
|
||||
#if !defined(ANDROID_UTILS_REF_BASE_DISABLE_IMPLICIT_CONSTRUCTION)
|
||||
template<typename T> template<typename U>
|
||||
sp<T>& sp<T>::operator =(U* other) {
|
||||
T* oldPtr(*const_cast<T* volatile*>(&m_ptr));
|
||||
if (other) (static_cast<T*>(other))->incStrong(this);
|
||||
if (oldPtr) oldPtr->decStrong(this);
|
||||
if (oldPtr != *const_cast<T* volatile*>(&m_ptr)) sp_report_race();
|
||||
m_ptr = other;
|
||||
return *this;
|
||||
}
|
||||
#endif
|
||||
|
||||
template<typename T>
|
||||
void sp<T>::force_set(T* other) {
|
||||
other->forceIncStrong(this);
|
||||
m_ptr = other;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void sp<T>::clear() {
|
||||
T* oldPtr(*const_cast<T* volatile*>(&m_ptr));
|
||||
if (oldPtr) {
|
||||
oldPtr->decStrong(this);
|
||||
if (oldPtr != *const_cast<T* volatile*>(&m_ptr)) sp_report_race();
|
||||
m_ptr = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void sp<T>::set_pointer(T* ptr) {
|
||||
m_ptr = ptr;
|
||||
}
|
||||
|
||||
} // namespace android
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
#endif // ANDROID_STRONG_POINTER_H
|
||||
@@ -0,0 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
// TODO
|
||||
namespace android {
|
||||
template<class TYPE>
|
||||
class Vector;
|
||||
}
|
||||
@@ -0,0 +1,165 @@
|
||||
#include "binder/IBinder.h"
|
||||
#include "binder/Binder.h"
|
||||
#include "binder/BpBinder.h"
|
||||
#include "binder/IPCThreadState.h"
|
||||
#include "binder/Parcel.h"
|
||||
#include "binder/IInterface.h"
|
||||
#include "binder/IServiceManager.h"
|
||||
|
||||
namespace android {
|
||||
// IBinder.h
|
||||
IBinder::IBinder() {}
|
||||
IBinder::~IBinder() {}
|
||||
sp<IInterface> IBinder::queryLocalInterface(const String16& descriptor) { return nullptr; }
|
||||
bool IBinder::checkSubclass(const void* subclassID) const { return false; }
|
||||
void IBinder::withLock(const std::function<void()>& doWithLock) {}
|
||||
|
||||
BBinder* IBinder::localBinder() { return nullptr; }
|
||||
BpBinder* IBinder::remoteBinder() { return nullptr; }
|
||||
|
||||
// Binder.h
|
||||
#ifdef __LP64__
|
||||
static_assert(sizeof(IBinder) == 24);
|
||||
static_assert(sizeof(BBinder) == 40);
|
||||
#else
|
||||
static_assert(sizeof(IBinder) == 12);
|
||||
static_assert(sizeof(BBinder) == 20);
|
||||
#endif
|
||||
BBinder::BBinder() {}
|
||||
|
||||
const String16 &BBinder::getInterfaceDescriptor() const { __builtin_unreachable(); }
|
||||
bool BBinder::isBinderAlive() const { return false; }
|
||||
status_t BBinder::pingBinder() { return 0; }
|
||||
status_t BBinder::dump(int fd, const Vector<String16>& args) { return 0; }
|
||||
|
||||
// NOLINTNEXTLINE(google-default-arguments)
|
||||
status_t BBinder::transact(uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags) { return 0; }
|
||||
|
||||
// NOLINTNEXTLINE(google-default-arguments)
|
||||
status_t BBinder::linkToDeath(const sp<DeathRecipient>& recipient, void* cookie, uint32_t flags) { return 0; }
|
||||
|
||||
// NOLINTNEXTLINE(google-default-arguments)
|
||||
status_t BBinder::unlinkToDeath(const wp<DeathRecipient>& recipient, void* cookie, uint32_t flags, wp<DeathRecipient>* outRecipient) { return 0; }
|
||||
|
||||
void* BBinder::attachObject(const void* objectID, void* object, void* cleanupCookie, object_cleanup_func func) { return nullptr; }
|
||||
void* BBinder::findObject(const void* objectID) const { return nullptr; }
|
||||
void* BBinder::detachObject(const void* objectID) { return nullptr; }
|
||||
void BBinder::withLock(const std::function<void()>& doWithLock) {}
|
||||
|
||||
BBinder* BBinder::localBinder() { return nullptr; }
|
||||
BBinder::~BBinder() {}
|
||||
|
||||
// NOLINTNEXTLINE(google-default-arguments)
|
||||
status_t BBinder::onTransact(uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags) { return 0; }
|
||||
|
||||
// IPCThreadState.h
|
||||
|
||||
IPCThreadState* IPCThreadState::self() { return nullptr; }
|
||||
IPCThreadState* IPCThreadState::selfOrNull() { return nullptr; }
|
||||
|
||||
pid_t IPCThreadState::getCallingPid() const { return 0; }
|
||||
|
||||
const char* IPCThreadState::getCallingSid() const { return nullptr; }
|
||||
|
||||
uid_t IPCThreadState::getCallingUid() const { return 0; }
|
||||
|
||||
// Parcel.h
|
||||
#ifdef __LP64__
|
||||
static_assert(sizeof(Parcel) == 120);
|
||||
#else
|
||||
static_assert(sizeof(Parcel) == 60);
|
||||
#endif
|
||||
Parcel::Parcel() {}
|
||||
Parcel::~Parcel() {}
|
||||
|
||||
const uint8_t* Parcel::data() const { return nullptr; }
|
||||
size_t Parcel::dataSize() const { return 0; }
|
||||
size_t Parcel::dataAvail() const { return 0; }
|
||||
size_t Parcel::dataPosition() const { return 0; }
|
||||
size_t Parcel::dataCapacity() const { return 0; }
|
||||
size_t Parcel::dataBufferSize() const { return 0; }
|
||||
|
||||
status_t Parcel::setDataSize(size_t size) { return 0; }
|
||||
|
||||
// this must only be used to set a data position that was previously returned from
|
||||
// dataPosition(). If writes are made, the exact same types of writes must be made (e.g.
|
||||
// auto i = p.dataPosition(); p.writeInt32(0); p.setDataPosition(i); p.writeInt32(1);).
|
||||
// Writing over objects, such as file descriptors and binders, is not supported.
|
||||
void Parcel::setDataPosition(size_t pos) const { }
|
||||
status_t Parcel::setDataCapacity(size_t size) { return 0; }
|
||||
|
||||
status_t Parcel::setData(const uint8_t* buffer, size_t len) { return 0; }
|
||||
|
||||
status_t Parcel::appendFrom(const Parcel* parcel, size_t start, size_t len) { return 0; }
|
||||
|
||||
// Verify there are no bytes left to be read on the Parcel.
|
||||
// Returns Status(EX_BAD_PARCELABLE) when the Parcel is not consumed.
|
||||
binder::Status Parcel::enforceNoDataAvail() const { return {}; }
|
||||
|
||||
// This Api is used by fuzzers to skip dataAvail checks.
|
||||
void Parcel::setEnforceNoDataAvail(bool enforceNoDataAvail) {}
|
||||
|
||||
void Parcel::freeData() {}
|
||||
|
||||
status_t Parcel::write(const void* data, size_t len) { return 0; }
|
||||
void* Parcel::writeInplace(size_t len) { return nullptr; }
|
||||
status_t Parcel::writeUnpadded(const void* data, size_t len) { return 0; }
|
||||
status_t Parcel::writeInt32(int32_t val) { return 0; }
|
||||
status_t Parcel::writeUint32(uint32_t val) { return 0; }
|
||||
status_t Parcel::writeInt64(int64_t val) { return 0; }
|
||||
status_t Parcel::writeUint64(uint64_t val) { return 0; }
|
||||
status_t Parcel::writeFloat(float val) { return 0; }
|
||||
status_t Parcel::writeDouble(double val) { return 0; }
|
||||
status_t Parcel::writeCString(const char* str) { return 0; }
|
||||
status_t Parcel::writeString8(const char* str, size_t len) { return 0; }
|
||||
status_t Parcel::writeStrongBinder(const sp<IBinder>& val) { return 0; }
|
||||
status_t Parcel::writeBool(bool val) { return 0; }
|
||||
status_t Parcel::writeChar(char16_t val) { return 0; }
|
||||
status_t Parcel::writeByte(int8_t val) { return 0; }
|
||||
|
||||
// Like Parcel.java's writeNoException(). Just writes a zero int32.
|
||||
// Currently the native implementation doesn't do any of the StrictMode
|
||||
// stack gathering and serialization that the Java implementation does.
|
||||
status_t Parcel::writeNoException() { return 0; }
|
||||
|
||||
status_t Parcel::read(void* outData, size_t len) const { return 0; }
|
||||
const void* Parcel::readInplace(size_t len) const { return nullptr; }
|
||||
int32_t Parcel::readInt32() const { return 0; }
|
||||
status_t Parcel::readInt32(int32_t* pArg) const { return 0; }
|
||||
uint32_t Parcel::readUint32() const { return 0; }
|
||||
status_t Parcel::readUint32(uint32_t* pArg) const { return 0; }
|
||||
int64_t Parcel::readInt64() const { return 0; }
|
||||
status_t Parcel::readInt64(int64_t* pArg) const { return 0; }
|
||||
uint64_t Parcel::readUint64() const { return 0; }
|
||||
status_t Parcel::readUint64(uint64_t* pArg) const { return 0; }
|
||||
float Parcel::readFloat() const { return 0; }
|
||||
status_t Parcel::readFloat(float* pArg) const { return 0; }
|
||||
double Parcel::readDouble() const { return 0; }
|
||||
status_t Parcel::readDouble(double* pArg) const { return 0; }
|
||||
bool Parcel::readBool() const { return 0; }
|
||||
status_t Parcel::readBool(bool* pArg) const { return 0; }
|
||||
char16_t Parcel::readChar() const { return 0; }
|
||||
status_t Parcel::readChar(char16_t* pArg) const { return 0; }
|
||||
int8_t Parcel::readByte() const { return 0; }
|
||||
status_t Parcel::readByte(int8_t* pArg) const { return 0; }
|
||||
|
||||
sp<IBinder> Parcel::readStrongBinder() const { return nullptr; }
|
||||
status_t Parcel::readStrongBinder(sp<IBinder>* val) const { return 0; }
|
||||
status_t Parcel::readNullableStrongBinder(sp<IBinder>* val) const { return 0; }
|
||||
|
||||
int32_t Parcel::readExceptionCode() const { return 0; }
|
||||
int Parcel::readFileDescriptor() const { return 0; }
|
||||
|
||||
// IServiceManager.h
|
||||
const String16 &IServiceManager::getInterfaceDescriptor() const {
|
||||
__builtin_unreachable();
|
||||
}
|
||||
|
||||
IServiceManager::IServiceManager() {}
|
||||
|
||||
IServiceManager::~IServiceManager() {}
|
||||
|
||||
sp<IServiceManager> defaultServiceManager() { return nullptr; }
|
||||
|
||||
void setDefaultServiceManager(const sp<IServiceManager> &sm) {}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
#include "utils/StrongPointer.h"
|
||||
#include "utils/RefBase.h"
|
||||
#include "utils/String16.h"
|
||||
|
||||
namespace android {
|
||||
void RefBase::incStrong(const void *id) const {
|
||||
|
||||
}
|
||||
|
||||
void RefBase::incStrongRequireStrong(const void *id) const {
|
||||
|
||||
}
|
||||
|
||||
void RefBase::decStrong(const void *id) const {
|
||||
|
||||
}
|
||||
|
||||
void RefBase::forceIncStrong(const void *id) const {
|
||||
|
||||
}
|
||||
|
||||
RefBase::weakref_type* RefBase::createWeak(const void* id) const {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
RefBase::weakref_type* RefBase::getWeakRefs() const {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
RefBase::RefBase(): mRefs(nullptr) {}
|
||||
RefBase::~RefBase() {}
|
||||
|
||||
void RefBase::onFirstRef() {}
|
||||
void RefBase::onLastStrongRef(const void* id) {}
|
||||
bool RefBase::onIncStrongAttempted(uint32_t flags, const void* id) { return false; }
|
||||
void RefBase::onLastWeakRef(const void* id) {}
|
||||
|
||||
RefBase* RefBase::weakref_type::refBase() const { return nullptr; }
|
||||
|
||||
void RefBase::weakref_type::incWeak(const void* id) {}
|
||||
void RefBase::weakref_type::incWeakRequireWeak(const void* id) {}
|
||||
void RefBase::weakref_type::decWeak(const void* id) {}
|
||||
|
||||
// acquires a strong reference if there is already one.
|
||||
bool RefBase::weakref_type::attemptIncStrong(const void* id) { return false; }
|
||||
|
||||
// acquires a weak reference if there is already one.
|
||||
// This is not always safe. see ProcessState.cpp and BpBinder.cpp
|
||||
// for proper use.
|
||||
bool RefBase::weakref_type::attemptIncWeak(const void* id) { return false; }
|
||||
|
||||
void sp_report_race() {}
|
||||
|
||||
String16::String16() {}
|
||||
|
||||
String16::String16(const String16 &o) {}
|
||||
|
||||
String16::String16(String16 &&o) noexcept {}
|
||||
|
||||
String16::String16(const char *o) {}
|
||||
|
||||
String16::~String16() {}
|
||||
}
|
||||
@@ -0,0 +1,351 @@
|
||||
#include <utils/RefBase.h>
|
||||
#include <binder/IPCThreadState.h>
|
||||
#include <binder/Parcel.h>
|
||||
#include <binder/IBinder.h>
|
||||
#include <binder/Binder.h>
|
||||
#include <utils/StrongPointer.h>
|
||||
#include <binder/Common.h>
|
||||
#include <binder/IServiceManager.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include "kernel/binder.h"
|
||||
|
||||
#include <utility>
|
||||
#include <map>
|
||||
#include <shared_mutex>
|
||||
#include <vector>
|
||||
#include <queue>
|
||||
|
||||
#include "logging.hpp"
|
||||
#include "lsplt.hpp"
|
||||
#include "elf_util.h"
|
||||
|
||||
using namespace SandHook;
|
||||
using namespace android;
|
||||
|
||||
class BinderInterceptor : public BBinder {
|
||||
enum {
|
||||
REGISTER_INTERCEPTOR = 1,
|
||||
UNREGISTER_INTERCEPTOR = 2
|
||||
};
|
||||
enum {
|
||||
PRE_TRANSACT = 1,
|
||||
POST_TRANSACT
|
||||
};
|
||||
enum {
|
||||
SKIP = 1,
|
||||
CONTINUE,
|
||||
OVERRIDE_REPLY,
|
||||
OVERRIDE_DATA
|
||||
};
|
||||
struct InterceptItem {
|
||||
wp<IBinder> target{};
|
||||
sp<IBinder> interceptor;
|
||||
};
|
||||
using RwLock = std::shared_mutex;
|
||||
using WriteGuard = std::unique_lock<RwLock>;
|
||||
using ReadGuard = std::shared_lock<RwLock>;
|
||||
RwLock lock;
|
||||
std::map<wp<IBinder>, InterceptItem> items{};
|
||||
public:
|
||||
status_t onTransact(uint32_t code, const android::Parcel &data, android::Parcel *reply,
|
||||
uint32_t flags) override;
|
||||
|
||||
bool handleIntercept(sp<BBinder> target, uint32_t code, const Parcel &data, Parcel *reply,
|
||||
uint32_t flags, status_t &result);
|
||||
|
||||
bool needIntercept(const wp<BBinder>& target);
|
||||
};
|
||||
|
||||
static sp<BinderInterceptor> gBinderInterceptor = nullptr;
|
||||
|
||||
struct thread_transaction_info {
|
||||
uint32_t code;
|
||||
wp<BBinder> target;
|
||||
};
|
||||
|
||||
thread_local std::queue<thread_transaction_info> ttis;
|
||||
|
||||
class BinderStub : public BBinder {
|
||||
status_t onTransact(uint32_t code, const android::Parcel &data, android::Parcel *reply, uint32_t flags) override {
|
||||
LOGD("BinderStub %d", code);
|
||||
if (!ttis.empty()) {
|
||||
auto tti = ttis.front();
|
||||
ttis.pop();
|
||||
if (tti.target == nullptr && tti.code == 0xdeadbeef && reply) {
|
||||
LOGD("backdoor requested!");
|
||||
reply->writeStrongBinder(gBinderInterceptor);
|
||||
return OK;
|
||||
} else if (tti.target != nullptr) {
|
||||
LOGD("intercepting");
|
||||
auto p = tti.target.promote();
|
||||
if (p) {
|
||||
LOGD("calling interceptor");
|
||||
status_t result;
|
||||
if (!gBinderInterceptor->handleIntercept(p, tti.code, data, reply, flags,
|
||||
result)) {
|
||||
LOGD("calling orig");
|
||||
result = p->transact(tti.code, data, reply, flags);
|
||||
}
|
||||
return result;
|
||||
} else {
|
||||
LOGE("promote failed");
|
||||
}
|
||||
}
|
||||
}
|
||||
return UNKNOWN_TRANSACTION;
|
||||
}
|
||||
};
|
||||
|
||||
static sp<BinderStub> gBinderStub = nullptr;
|
||||
|
||||
// FIXME: when use ioctl hooking, some already blocked ioctl calls will not be hooked
|
||||
int (*old_ioctl)(int fd, int request, ...) = nullptr;
|
||||
int new_ioctl(int fd, int request, ...) {
|
||||
va_list list;
|
||||
va_start(list, request);
|
||||
auto arg = va_arg(list, void*);
|
||||
va_end(list);
|
||||
auto result = old_ioctl(fd, request, arg);
|
||||
// TODO: check fd
|
||||
if (result >= 0 && request == BINDER_WRITE_READ) {
|
||||
auto &bwr = *(struct binder_write_read*) arg;
|
||||
LOGD("read buffer %p size %zu consumed %zu", bwr.read_buffer, bwr.read_size,
|
||||
bwr.read_consumed);
|
||||
if (bwr.read_buffer != 0 && bwr.read_size != 0 && bwr.read_consumed > sizeof(int32_t)) {
|
||||
auto ptr = bwr.read_buffer;
|
||||
auto consumed = bwr.read_consumed;
|
||||
while (consumed > 0) {
|
||||
consumed -= sizeof(uint32_t);
|
||||
if (consumed < 0) {
|
||||
LOGE("consumed < 0");
|
||||
break;
|
||||
}
|
||||
auto cmd = *(uint32_t *) ptr;
|
||||
ptr += sizeof(uint32_t);
|
||||
auto sz = _IOC_SIZE(cmd);
|
||||
LOGD("ioctl cmd %d sz %d", cmd, sz);
|
||||
consumed -= sz;
|
||||
if (consumed < 0) {
|
||||
LOGE("consumed < 0");
|
||||
break;
|
||||
}
|
||||
if (cmd == BR_TRANSACTION_SEC_CTX || cmd == BR_TRANSACTION) {
|
||||
binder_transaction_data_secctx *tr_secctx = nullptr;
|
||||
binder_transaction_data *tr = nullptr;
|
||||
if (cmd == BR_TRANSACTION_SEC_CTX) {
|
||||
LOGD("cmd is BR_TRANSACTION_SEC_CTX");
|
||||
tr_secctx = (binder_transaction_data_secctx *) ptr;
|
||||
tr = &tr_secctx->transaction_data;
|
||||
} else {
|
||||
LOGD("cmd is BR_TRANSACTION");
|
||||
tr = (binder_transaction_data *) ptr;
|
||||
}
|
||||
|
||||
if (tr != nullptr) {
|
||||
auto wt = tr->target.ptr;
|
||||
if (wt != 0) {
|
||||
bool need_intercept = false;
|
||||
thread_transaction_info tti{};
|
||||
if (tr->code == 0xdeadbeef && tr->sender_euid == 0) {
|
||||
tti.code = 0xdeadbeef;
|
||||
tti.target = nullptr;
|
||||
need_intercept = true;
|
||||
} else if (reinterpret_cast<RefBase::weakref_type *>(wt)->attemptIncStrong(
|
||||
nullptr)) {
|
||||
auto b = (BBinder *) tr->cookie;
|
||||
auto wb = wp<BBinder>::fromExisting(b);
|
||||
if (gBinderInterceptor->needIntercept(wb)) {
|
||||
tti.code = tr->code;
|
||||
tti.target = wb;
|
||||
need_intercept = true;
|
||||
LOGD("intercept code=%d target=%p", tr->code, b);
|
||||
}
|
||||
b->decStrong(nullptr);
|
||||
}
|
||||
if (need_intercept) {
|
||||
LOGD("add intercept item!");
|
||||
tr->target.ptr = (uintptr_t) gBinderStub->getWeakRefs();
|
||||
tr->cookie = (uintptr_t) gBinderStub.get();
|
||||
tr->code = 0xdeadbeef;
|
||||
ttis.push(tti);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
LOGE("no transaction data found!");
|
||||
}
|
||||
}
|
||||
ptr += sz;
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
bool BinderInterceptor::needIntercept(const wp<BBinder> &target) {
|
||||
ReadGuard g{lock};
|
||||
return items.find(target) != items.end();
|
||||
}
|
||||
|
||||
status_t
|
||||
BinderInterceptor::onTransact(uint32_t code, const android::Parcel &data, android::Parcel *reply,
|
||||
uint32_t flags) {
|
||||
if (code == REGISTER_INTERCEPTOR) {
|
||||
sp<IBinder> target, interceptor;
|
||||
if (data.readStrongBinder(&target) != OK) {
|
||||
return BAD_VALUE;
|
||||
}
|
||||
if (!target->localBinder()) {
|
||||
return BAD_VALUE;
|
||||
}
|
||||
if (data.readStrongBinder(&interceptor) != OK) {
|
||||
return BAD_VALUE;
|
||||
}
|
||||
{
|
||||
WriteGuard wg{lock};
|
||||
wp<IBinder> t = target;
|
||||
auto it = items.lower_bound(t);
|
||||
if (it == items.end() || it->first != t) {
|
||||
it = items.emplace_hint(it, t, InterceptItem{});
|
||||
it->second.target = t;
|
||||
}
|
||||
// TODO: send callback to old interceptor
|
||||
it->second.interceptor = interceptor;
|
||||
return OK;
|
||||
}
|
||||
} else if (code == UNREGISTER_INTERCEPTOR) {
|
||||
sp<IBinder> target, interceptor;
|
||||
if (data.readStrongBinder(&target) != OK) {
|
||||
return BAD_VALUE;
|
||||
}
|
||||
if (!target->localBinder()) {
|
||||
return BAD_VALUE;
|
||||
}
|
||||
if (data.readStrongBinder(&interceptor) != OK) {
|
||||
return BAD_VALUE;
|
||||
}
|
||||
{
|
||||
WriteGuard wg{lock};
|
||||
wp<IBinder> t = target;
|
||||
auto it = items.find(t);
|
||||
if (it != items.end()) {
|
||||
if (it->second.interceptor != interceptor) {
|
||||
return BAD_VALUE;
|
||||
}
|
||||
items.erase(it);
|
||||
return OK;
|
||||
}
|
||||
return BAD_VALUE;
|
||||
}
|
||||
}
|
||||
return UNKNOWN_TRANSACTION;
|
||||
}
|
||||
|
||||
bool
|
||||
BinderInterceptor::handleIntercept(sp<BBinder> target, uint32_t code, const Parcel &data, Parcel *reply,
|
||||
uint32_t flags, status_t &result) {
|
||||
#define CHECK(expr) ({ auto __result = (expr); if (__result != OK) { LOGE(#expr " = %d", __result); return false; } })
|
||||
sp<IBinder> interceptor;
|
||||
{
|
||||
ReadGuard rg{lock};
|
||||
auto it = items.find(target);
|
||||
if (it == items.end()) {
|
||||
LOGE("no intercept item found!");
|
||||
return false;
|
||||
}
|
||||
interceptor = it->second.interceptor;
|
||||
}
|
||||
LOGD("intercept on binder %p code %d flags %d (reply=%s)", target.get(), code, flags,
|
||||
reply ? "true" : "false");
|
||||
Parcel tmpData, tmpReply, realData;
|
||||
CHECK(tmpData.writeStrongBinder(target));
|
||||
CHECK(tmpData.writeUint32(code));
|
||||
CHECK(tmpData.writeUint32(flags));
|
||||
CHECK(tmpData.writeInt32(IPCThreadState::self()->getCallingUid()));
|
||||
CHECK(tmpData.writeInt32(IPCThreadState::self()->getCallingPid()));
|
||||
CHECK(tmpData.writeUint64(data.dataSize()));
|
||||
CHECK(tmpData.appendFrom(&data, 0, data.dataSize()));
|
||||
CHECK(interceptor->transact(PRE_TRANSACT, tmpData, &tmpReply));
|
||||
int32_t preType;
|
||||
CHECK(tmpReply.readInt32(&preType));
|
||||
LOGD("pre transact type %d", preType);
|
||||
if (preType == SKIP) {
|
||||
return false;
|
||||
} else if (preType == OVERRIDE_REPLY) {
|
||||
result = tmpReply.readInt32();
|
||||
if (reply) {
|
||||
size_t sz = tmpReply.readUint64();
|
||||
CHECK(reply->appendFrom(&tmpReply, tmpReply.dataPosition(), sz));
|
||||
}
|
||||
return true;
|
||||
} else if (preType == OVERRIDE_DATA) {
|
||||
size_t sz = tmpReply.readUint64();
|
||||
CHECK(realData.appendFrom(&tmpReply, tmpReply.dataPosition(), sz));
|
||||
} else {
|
||||
CHECK(realData.appendFrom(&data, 0, data.dataSize()));
|
||||
}
|
||||
result = target->transact(code, realData, reply, flags);
|
||||
|
||||
tmpData.freeData();
|
||||
tmpReply.freeData();
|
||||
|
||||
CHECK(tmpData.writeStrongBinder(target));
|
||||
CHECK(tmpData.writeUint32(code));
|
||||
CHECK(tmpData.writeUint32(flags));
|
||||
CHECK(tmpData.writeInt32(IPCThreadState::self()->getCallingUid()));
|
||||
CHECK(tmpData.writeInt32(IPCThreadState::self()->getCallingPid()));
|
||||
CHECK(tmpData.writeInt32(result));
|
||||
CHECK(tmpData.writeUint64(data.dataSize()));
|
||||
CHECK(tmpData.appendFrom(&data, 0, data.dataSize()));
|
||||
CHECK(tmpData.writeUint64(reply == nullptr ? 0 : reply->dataSize()));
|
||||
LOGD("data size %zu reply size %zu", data.dataSize(), reply == nullptr ? 0 : reply->dataSize());
|
||||
if (reply) {
|
||||
CHECK(tmpData.appendFrom(reply, 0, reply->dataSize()));
|
||||
}
|
||||
CHECK(interceptor->transact(POST_TRANSACT, tmpData, &tmpReply));
|
||||
int32_t postType;
|
||||
CHECK(tmpReply.readInt32(&postType));
|
||||
LOGD("post transact type %d", postType);
|
||||
if (postType == OVERRIDE_REPLY) {
|
||||
result = tmpReply.readInt32();
|
||||
if (reply) {
|
||||
size_t sz = tmpReply.readUint64();
|
||||
reply->freeData();
|
||||
CHECK(reply->appendFrom(&tmpReply, tmpReply.dataPosition(), sz));
|
||||
LOGD("reply size=%zu sz=%zu", reply->dataSize(), sz);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool hookBinder() {
|
||||
auto maps = lsplt::MapInfo::Scan();
|
||||
dev_t dev;
|
||||
ino_t ino;
|
||||
bool found = false;
|
||||
for (auto &m: maps) {
|
||||
if (m.path.ends_with("/libbinder.so")) {
|
||||
dev = m.dev;
|
||||
ino = m.inode;
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found) {
|
||||
LOGE("libbinder not found!");
|
||||
return false;
|
||||
}
|
||||
gBinderInterceptor = sp<BinderInterceptor>::make();
|
||||
gBinderStub = sp<BinderStub>::make();
|
||||
lsplt::RegisterHook(dev, ino, "ioctl", (void *) new_ioctl, (void **) &old_ioctl);
|
||||
if (!lsplt::CommitHook()) {
|
||||
LOGE("hook failed!");
|
||||
return false;
|
||||
}
|
||||
LOGI("hook success!");
|
||||
return true;
|
||||
}
|
||||
|
||||
extern "C" [[gnu::visibility("default")]] [[gnu::used]] bool entry(void *handle) {
|
||||
LOGI("injected, my handle %p", handle);
|
||||
return hookBinder();
|
||||
}
|
||||
@@ -0,0 +1,305 @@
|
||||
/*
|
||||
* This file is part of LSPosed.
|
||||
*
|
||||
* LSPosed is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* LSPosed is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with LSPosed. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* Copyright (C) 2019 Swift Gan
|
||||
* Copyright (C) 2021 LSPosed Contributors
|
||||
*/
|
||||
#include <malloc.h>
|
||||
#include <cstring>
|
||||
#include <sys/mman.h>
|
||||
#include <fcntl.h>
|
||||
#include <unistd.h>
|
||||
#include <cassert>
|
||||
#include <sys/stat.h>
|
||||
#include "logging.hpp"
|
||||
#include "elf_util.h"
|
||||
#include <vector>
|
||||
#include "lsplt.hpp"
|
||||
|
||||
using namespace SandHook;
|
||||
|
||||
template<typename T>
|
||||
inline constexpr auto offsetOf(ElfW(Ehdr) *head, ElfW(Off) off) {
|
||||
return reinterpret_cast<std::conditional_t<std::is_pointer_v<T>, T, T *>>(
|
||||
reinterpret_cast<uintptr_t>(head) + off);
|
||||
}
|
||||
|
||||
ElfImg::ElfImg(std::string_view base_name, void *base, off_t file_offset, size_t size) : elf(
|
||||
base_name), base(base), size(size) {
|
||||
// load elf
|
||||
int fd = open(elf.data(), O_RDONLY);
|
||||
if (fd < 0) {
|
||||
LOGE("failed to open %s", elf.data());
|
||||
return;
|
||||
}
|
||||
|
||||
header = reinterpret_cast<decltype(header)>(mmap(nullptr, size, PROT_READ, MAP_SHARED, fd,
|
||||
file_offset));
|
||||
|
||||
close(fd);
|
||||
|
||||
section_header = offsetOf<decltype(section_header)>(header, header->e_shoff);
|
||||
|
||||
auto shoff = reinterpret_cast<uintptr_t>(section_header);
|
||||
char *section_str = offsetOf<char *>(header, section_header[header->e_shstrndx].sh_offset);
|
||||
|
||||
for (int i = 0; i < header->e_shnum; i++, shoff += header->e_shentsize) {
|
||||
auto *section_h = (ElfW(Shdr) *) shoff;
|
||||
char *sname = section_h->sh_name + section_str;
|
||||
auto entsize = section_h->sh_entsize;
|
||||
switch (section_h->sh_type) {
|
||||
case SHT_DYNSYM: {
|
||||
if (bias == -4396) {
|
||||
dynsym = section_h;
|
||||
dynsym_offset = section_h->sh_offset;
|
||||
dynsym_start = offsetOf<decltype(dynsym_start)>(header, dynsym_offset);
|
||||
dynsym_size = section_h->sh_size;
|
||||
dynsym_count = dynsym_size / entsize;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case SHT_SYMTAB: {
|
||||
if (strcmp(sname, ".symtab") == 0) {
|
||||
symtab = section_h;
|
||||
symtab_offset = section_h->sh_offset;
|
||||
symtab_size = section_h->sh_size;
|
||||
symtab_count = symtab_size / entsize;
|
||||
symtab_start = offsetOf<decltype(symtab_start)>(header, symtab_offset);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case SHT_STRTAB: {
|
||||
if (bias == -4396) {
|
||||
strtab = section_h;
|
||||
symstr_offset = section_h->sh_offset;
|
||||
strtab_start = offsetOf<decltype(strtab_start)>(header, symstr_offset);
|
||||
}
|
||||
if (strcmp(sname, ".strtab") == 0) {
|
||||
symstr_offset_for_symtab = section_h->sh_offset;
|
||||
} else if (strcmp(sname, ".dynstr") == 0) {
|
||||
symstr_offset_for_dynsym = section_h->sh_offset;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case SHT_PROGBITS: {
|
||||
if (strtab == nullptr || dynsym == nullptr) break;
|
||||
if (bias == -4396) {
|
||||
bias = (off_t) section_h->sh_addr - (off_t) section_h->sh_offset;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case SHT_HASH: {
|
||||
auto *d_un = offsetOf<ElfW(Word)>(header, section_h->sh_offset);
|
||||
nbucket_ = d_un[0];
|
||||
bucket_ = d_un + 2;
|
||||
chain_ = bucket_ + nbucket_;
|
||||
break;
|
||||
}
|
||||
case SHT_GNU_HASH: {
|
||||
auto *d_buf = reinterpret_cast<ElfW(Word) *>(((size_t) header) +
|
||||
section_h->sh_offset);
|
||||
gnu_nbucket_ = d_buf[0];
|
||||
gnu_symndx_ = d_buf[1];
|
||||
gnu_bloom_size_ = d_buf[2];
|
||||
gnu_shift2_ = d_buf[3];
|
||||
gnu_bloom_filter_ = reinterpret_cast<decltype(gnu_bloom_filter_)>(d_buf + 4);
|
||||
gnu_bucket_ = reinterpret_cast<decltype(gnu_bucket_)>(gnu_bloom_filter_ +
|
||||
gnu_bloom_size_);
|
||||
gnu_chain_ = gnu_bucket_ + gnu_nbucket_ - gnu_symndx_;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ElfW(Sym) *ElfImg::ElfLookup(std::string_view name, uint32_t hash) const {
|
||||
if (nbucket_ == 0) return 0;
|
||||
|
||||
char *strings = (char *) strtab_start;
|
||||
|
||||
for (auto n = bucket_[hash % nbucket_]; n != 0; n = chain_[n]) {
|
||||
auto *sym = dynsym_start + n;
|
||||
if (name == strings + sym->st_name) {
|
||||
return sym;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
ElfW(Sym) *ElfImg::GnuLookup(std::string_view name, uint32_t hash) const {
|
||||
static constexpr auto bloom_mask_bits = sizeof(ElfW(Addr)) * 8;
|
||||
|
||||
if (gnu_nbucket_ == 0 || gnu_bloom_size_ == 0) return 0;
|
||||
|
||||
auto bloom_word = gnu_bloom_filter_[(hash / bloom_mask_bits) % gnu_bloom_size_];
|
||||
uintptr_t mask = 0
|
||||
| (uintptr_t) 1 << (hash % bloom_mask_bits)
|
||||
| (uintptr_t) 1 << ((hash >> gnu_shift2_) % bloom_mask_bits);
|
||||
if ((mask & bloom_word) == mask) {
|
||||
auto sym_index = gnu_bucket_[hash % gnu_nbucket_];
|
||||
if (sym_index >= gnu_symndx_) {
|
||||
char *strings = (char *) strtab_start;
|
||||
do {
|
||||
auto *sym = dynsym_start + sym_index;
|
||||
if (((gnu_chain_[sym_index] ^ hash) >> 1) == 0
|
||||
&& name == strings + sym->st_name) {
|
||||
return sym;
|
||||
}
|
||||
} while ((gnu_chain_[sym_index++] & 1) == 0);
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void ElfImg::MayInitLinearMap() const {
|
||||
if (symtabs_.empty()) {
|
||||
if (symtab_start != nullptr && symstr_offset_for_symtab != 0) {
|
||||
for (ElfW(Off) i = 0; i < symtab_count; i++) {
|
||||
unsigned int st_type = ELF_ST_TYPE(symtab_start[i].st_info);
|
||||
const char *st_name = offsetOf<const char *>(header, symstr_offset_for_symtab +
|
||||
symtab_start[i].st_name);
|
||||
if ((st_type == STT_FUNC || st_type == STT_OBJECT) && symtab_start[i].st_size) {
|
||||
symtabs_.emplace(st_name, &symtab_start[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const std::string ElfImg::findSymbolNameForAddr(ElfW(Addr) addr) const {
|
||||
if (symtab_start != nullptr && symstr_offset_for_symtab != 0) {
|
||||
auto addr_off = (ElfW(Addr)) (addr + bias - (uintptr_t) base);
|
||||
for (ElfW(Off) i = 0; i < symtab_count; i++) {
|
||||
unsigned int st_type = ELF_ST_TYPE(symtab_start[i].st_info);
|
||||
const char *st_name = offsetOf<const char *>(header, symstr_offset_for_symtab +
|
||||
symtab_start[i].st_name);
|
||||
if ((st_type == STT_FUNC || st_type == STT_OBJECT) && symtab_start[i].st_size) {
|
||||
auto off = symtab_start[i].st_value;
|
||||
auto len = symtab_start[i].st_size;
|
||||
if (off <= addr_off && addr_off < off + len) {
|
||||
LOGD("found in symtab sym %p name %s", off,
|
||||
st_name);
|
||||
char buf[1024];
|
||||
snprintf(buf, sizeof(buf), "%s (0x%lx)+0x%lx/(0x%lx) from symtab %d", st_name,
|
||||
off, addr_off - off, len, i);
|
||||
return buf;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (dynsym_start != nullptr && symstr_offset_for_dynsym != 0) {
|
||||
auto addr_off = (ElfW(Addr)) (addr + bias - (uintptr_t) base);
|
||||
for (ElfW(Off) i = 0; i < dynsym_count; i++) {
|
||||
unsigned int st_type = ELF_ST_TYPE(dynsym_start[i].st_info);
|
||||
const char *st_name = offsetOf<const char *>(header, symstr_offset_for_dynsym +
|
||||
dynsym_start[i].st_name);
|
||||
if ((st_type == STT_FUNC || st_type == STT_OBJECT) && dynsym_start[i].st_size) {
|
||||
auto off = dynsym_start[i].st_value;
|
||||
auto len = dynsym_start[i].st_size;
|
||||
if (off <= addr_off && addr_off < off + len) {
|
||||
LOGD("found in dynsym sym %p name %s", off,
|
||||
st_name);
|
||||
char buf[1024];
|
||||
snprintf(buf, sizeof(buf), "%s (0x%lx)+0x%lx/(0x%lx) from dynsym %d", st_name,
|
||||
off, addr_off - off, len, i);
|
||||
return buf;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return "(not found)";
|
||||
}
|
||||
|
||||
|
||||
ElfW(Sym) *ElfImg::LinearLookup(std::string_view name) const {
|
||||
MayInitLinearMap();
|
||||
if (auto i = symtabs_.find(name); i != symtabs_.end()) {
|
||||
return i->second;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
ElfW(Sym) *ElfImg::PrefixLookupFirst(std::string_view prefix) const {
|
||||
MayInitLinearMap();
|
||||
if (auto i = symtabs_.lower_bound(prefix); i != symtabs_.end() &&
|
||||
i->first.starts_with(prefix)) {
|
||||
LOGD("found prefix %s of %s %p in %s in symtab by linear lookup", prefix.data(),
|
||||
i->first.data(), reinterpret_cast<void *>(i->second->st_value), elf.data());
|
||||
return i->second;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
ElfImg::~ElfImg() {
|
||||
//open elf file local
|
||||
if (buffer) {
|
||||
free(buffer);
|
||||
buffer = nullptr;
|
||||
}
|
||||
//use mmap
|
||||
if (header) {
|
||||
if (munmap(header, size) == -1) PLOGE("munmap");
|
||||
LOGI("unmapped %p %ld", header, size);
|
||||
}
|
||||
}
|
||||
|
||||
ElfW(Sym) *
|
||||
ElfImg::getSym(std::string_view name, uint32_t gnu_hash, uint32_t elf_hash) const {
|
||||
if (auto sym = GnuLookup(name, gnu_hash); sym) {
|
||||
LOGD("found %s %p/%llu in %s in dynsym by gnuhash", name.data(),
|
||||
reinterpret_cast<void *>(sym->st_value), sym->st_size, elf.data());
|
||||
return sym;
|
||||
} else if (sym = ElfLookup(name, elf_hash); sym) {
|
||||
LOGD("found %s %p/%llu in %s in dynsym by elfhash", name.data(),
|
||||
reinterpret_cast<void *>(sym->st_value), sym->st_size, elf.data());
|
||||
return sym;
|
||||
} else if (sym = LinearLookup(name); sym) {
|
||||
LOGD("found %s %p/%llu in %s in symtab by linear lookup", name.data(),
|
||||
reinterpret_cast<void *>(sym->st_value), sym->st_size, elf.data());
|
||||
return sym;
|
||||
} else {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
constexpr inline bool contains(std::string_view a, std::string_view b) {
|
||||
return a.find(b) != std::string_view::npos;
|
||||
}
|
||||
|
||||
ElfInfo ElfInfo::getElfInfoForName(const char *name, const char *pid) {
|
||||
auto maps = lsplt::MapInfo::Scan(pid);
|
||||
std::string path;
|
||||
void *base = nullptr;
|
||||
size_t file_size = 0;
|
||||
for (auto &map: maps) {
|
||||
if (map.path.ends_with(name) && map.offset == 0) {
|
||||
struct stat st{};
|
||||
if (stat(map.path.c_str(), &st) == -1) {
|
||||
PLOGE("stat %s", map.path.c_str());
|
||||
break;
|
||||
}
|
||||
path = map.path;
|
||||
file_size = st.st_size;
|
||||
base = reinterpret_cast<void *>(map.start);
|
||||
LOGD("found path %s size %zu", path.c_str(), file_size);
|
||||
break;
|
||||
}
|
||||
}
|
||||
return {path, base, 0, file_size};
|
||||
}
|
||||
@@ -0,0 +1,174 @@
|
||||
/*
|
||||
* This file is part of LSPosed.
|
||||
*
|
||||
* LSPosed is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* LSPosed is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with LSPosed. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* Copyright (C) 2019 Swift Gan
|
||||
* Copyright (C) 2021 LSPosed Contributors
|
||||
*/
|
||||
#ifndef SANDHOOK_ELF_UTIL_H
|
||||
#define SANDHOOK_ELF_UTIL_H
|
||||
|
||||
#include <string_view>
|
||||
#include <map>
|
||||
#include <linux/elf.h>
|
||||
#include <sys/types.h>
|
||||
#include <string>
|
||||
#include <link.h>
|
||||
|
||||
#define SHT_GNU_HASH 0x6ffffff6
|
||||
|
||||
namespace SandHook {
|
||||
struct ElfInfo {
|
||||
std::string name;
|
||||
void *base;
|
||||
off_t offset;
|
||||
size_t size;
|
||||
|
||||
static ElfInfo getElfInfoForName(const char *name, const char *pid = "self");
|
||||
};
|
||||
class ElfImg {
|
||||
public:
|
||||
|
||||
ElfImg(std::string_view base_name, void *base, off_t file_offset, size_t size);
|
||||
|
||||
ElfImg(ElfInfo info) : ElfImg(info.name, info.base, info.offset, info.size) {}
|
||||
|
||||
std::pair<uintptr_t, size_t> getSymInfo(std::string_view name) const {
|
||||
auto sym = getSym(name, GnuHash(name), ElfHash(name));
|
||||
if (sym && base != nullptr) {
|
||||
auto offset = sym->st_value;
|
||||
return {(uintptr_t) base + offset - bias, sym->st_size};
|
||||
} else {
|
||||
return {0, 0};
|
||||
}
|
||||
}
|
||||
|
||||
template<typename T = void*>
|
||||
requires(std::is_pointer_v<T>)
|
||||
constexpr T getSymbAddress(std::string_view name) const {
|
||||
auto sym = getSym(name, GnuHash(name), ElfHash(name));
|
||||
if (sym && base != nullptr) {
|
||||
auto offset = sym->st_value;
|
||||
return reinterpret_cast<T>(static_cast<ElfW(Addr)>((uintptr_t) base + offset - bias));
|
||||
} else {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
template<typename T = void*>
|
||||
requires(std::is_pointer_v<T>)
|
||||
constexpr T getSymbPrefixFirstOffset(std::string_view prefix) const {
|
||||
auto sym = PrefixLookupFirst(prefix);
|
||||
if (sym && base != nullptr) {
|
||||
auto offset = sym->st_value;
|
||||
return reinterpret_cast<T>(static_cast<ElfW(Addr)>((uintptr_t) base + offset - bias));
|
||||
} else {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
bool isValid() const {
|
||||
return base != nullptr;
|
||||
}
|
||||
|
||||
const std::string name() const {
|
||||
return elf;
|
||||
}
|
||||
|
||||
const void *getBase() const {
|
||||
return base;
|
||||
}
|
||||
|
||||
const std::string findSymbolNameForAddr(ElfW(Addr) addr) const;
|
||||
|
||||
~ElfImg();
|
||||
|
||||
private:
|
||||
ElfW(Sym) *getSym(std::string_view name, uint32_t gnu_hash, uint32_t elf_hash) const;
|
||||
|
||||
ElfW(Sym) *ElfLookup(std::string_view name, uint32_t hash) const;
|
||||
|
||||
ElfW(Sym) *GnuLookup(std::string_view name, uint32_t hash) const;
|
||||
|
||||
ElfW(Sym) *LinearLookup(std::string_view name) const;
|
||||
|
||||
ElfW(Sym) *PrefixLookupFirst(std::string_view prefix) const;
|
||||
|
||||
constexpr static uint32_t ElfHash(std::string_view name);
|
||||
|
||||
constexpr static uint32_t GnuHash(std::string_view name);
|
||||
|
||||
void MayInitLinearMap() const;
|
||||
|
||||
std::string elf;
|
||||
uintptr_t offset_for_zip;
|
||||
void *base = nullptr;
|
||||
char *buffer = nullptr;
|
||||
off_t size = 0;
|
||||
off_t bias = -4396;
|
||||
ElfW(Ehdr) *header = nullptr;
|
||||
ElfW(Shdr) *section_header = nullptr;
|
||||
ElfW(Shdr) *symtab = nullptr;
|
||||
ElfW(Shdr) *strtab = nullptr;
|
||||
ElfW(Shdr) *dynsym = nullptr;
|
||||
ElfW(Sym) *symtab_start = nullptr;
|
||||
ElfW(Sym) *dynsym_start = nullptr;
|
||||
ElfW(Sym) *strtab_start = nullptr;
|
||||
ElfW(Off) symstr_offset = 0;
|
||||
ElfW(Off) symstr_offset_for_symtab = 0;
|
||||
ElfW(Off) symstr_offset_for_dynsym = 0;
|
||||
ElfW(Off) symtab_offset = 0;
|
||||
ElfW(Off) dynsym_offset = 0;
|
||||
ElfW(Off) symtab_count = 0;
|
||||
ElfW(Off) symtab_size = 0;
|
||||
ElfW(Off) dynsym_count = 0;
|
||||
ElfW(Off) dynsym_size = 0;
|
||||
|
||||
uint32_t nbucket_{};
|
||||
uint32_t *bucket_ = nullptr;
|
||||
uint32_t *chain_ = nullptr;
|
||||
|
||||
uint32_t gnu_nbucket_{};
|
||||
uint32_t gnu_symndx_{};
|
||||
uint32_t gnu_bloom_size_;
|
||||
uint32_t gnu_shift2_;
|
||||
uintptr_t *gnu_bloom_filter_;
|
||||
uint32_t *gnu_bucket_;
|
||||
uint32_t *gnu_chain_;
|
||||
|
||||
mutable std::map<std::string_view, ElfW(Sym) *> symtabs_;
|
||||
};
|
||||
|
||||
constexpr uint32_t ElfImg::ElfHash(std::string_view name) {
|
||||
uint32_t h = 0, g;
|
||||
for (unsigned char p: name) {
|
||||
h = (h << 4) + p;
|
||||
g = h & 0xf0000000;
|
||||
h ^= g;
|
||||
h ^= g >> 24;
|
||||
}
|
||||
return h;
|
||||
}
|
||||
|
||||
constexpr uint32_t ElfImg::GnuHash(std::string_view name) {
|
||||
uint32_t h = 5381;
|
||||
for (unsigned char p: name) {
|
||||
h += (h << 5) + p;
|
||||
}
|
||||
return h;
|
||||
}
|
||||
}
|
||||
|
||||
#endif //SANDHOOK_ELF_UTIL_H
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
project(external)
|
||||
|
||||
# lsplt
|
||||
set(SOURCES LSPlt/lsplt/src/main/jni/lsplt.cc LSPlt/lsplt/src/main/jni/elf_util.cc)
|
||||
|
||||
add_library(lsplt STATIC ${SOURCES})
|
||||
target_include_directories(lsplt PUBLIC LSPlt/lsplt/src/main/jni/include)
|
||||
target_include_directories(lsplt PRIVATE LSPlt/lsplt/src/main/jni)
|
||||
target_link_libraries(lsplt PUBLIC my_logging)
|
||||
# end lsplt
|
||||
+1
Submodule module/src/main/cpp/external/LSPlt added at cef80a97a7
@@ -0,0 +1,300 @@
|
||||
#include <sys/ptrace.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/uio.h>
|
||||
#include <sys/auxv.h>
|
||||
#include <elf.h>
|
||||
#include <link.h>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <sys/mman.h>
|
||||
#include <sys/wait.h>
|
||||
#include <cstdlib>
|
||||
#include <cstdio>
|
||||
#include <dlfcn.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/un.h>
|
||||
#include <android/dlext.h>
|
||||
#include <fcntl.h>
|
||||
#include <csignal>
|
||||
#include <sys/system_properties.h>
|
||||
#include <string>
|
||||
#include <cinttypes>
|
||||
|
||||
#include "lsplt.hpp"
|
||||
|
||||
#include "logging.hpp"
|
||||
#include "utils.hpp"
|
||||
|
||||
using namespace std::string_literals;
|
||||
|
||||
// zygote inject
|
||||
|
||||
bool inject_library(int pid, const char *lib_path, const char* entry_name) {
|
||||
LOGI("injecting %s and calling %s in %d", lib_path, entry_name, pid);
|
||||
struct user_regs_struct regs{}, backup{};
|
||||
std::vector<lsplt::MapInfo> map;
|
||||
|
||||
if (ptrace(PTRACE_ATTACH, pid, 0, 0) == -1) {
|
||||
PLOGE("attach");
|
||||
}
|
||||
int status;
|
||||
{
|
||||
wait_for_trace(pid, &status, __WALL);
|
||||
}
|
||||
if (WIFSTOPPED(status) && WSTOPSIG(status) == SIGSTOP) {
|
||||
if (!get_regs(pid, regs)) return false;
|
||||
// The linker has been initialized now, we can do dlopen
|
||||
LOGD("stopped at entry");
|
||||
// backup registers
|
||||
memcpy(&backup, ®s, sizeof(regs));
|
||||
{
|
||||
map = lsplt::MapInfo::Scan(std::to_string(pid));
|
||||
}
|
||||
auto local_map = lsplt::MapInfo::Scan();
|
||||
auto libc_return_addr = find_module_return_addr(map, "libc.so");
|
||||
LOGD("libc return addr %p", libc_return_addr);
|
||||
|
||||
std::vector<uintptr_t> args;
|
||||
uintptr_t str, remote_handle, injector_entry;
|
||||
auto close_addr = find_func_addr(local_map, map, "libc.so", "close");
|
||||
if (!close_addr) return false;
|
||||
|
||||
int lib_fd = -1;
|
||||
|
||||
// prepare fd
|
||||
{
|
||||
set_sockcreate_con("u:object_r:system_file:s0");
|
||||
UniqueFd local_socket = socket(AF_UNIX, SOCK_DGRAM | SOCK_CLOEXEC, 0);
|
||||
if (local_socket == -1) {
|
||||
PLOGE("create local socket");
|
||||
return false;
|
||||
}
|
||||
if (setfilecon(lib_path, "u:object_r:system_file:s0") == -1) {
|
||||
PLOGE("set context of lib");
|
||||
}
|
||||
UniqueFd local_lib_fd = open(lib_path, O_RDONLY | O_CLOEXEC);
|
||||
if (local_lib_fd == -1) {
|
||||
PLOGE("open lib");
|
||||
return false;
|
||||
}
|
||||
auto socket_addr = find_func_addr(local_map, map, "libc.so", "socket");
|
||||
auto bind_addr = find_func_addr(local_map, map, "libc.so", "bind");
|
||||
auto recvmsg_addr = find_func_addr(local_map, map, "libc.so", "recvmsg");
|
||||
auto errno_addr = find_func_addr(local_map, map, "libc.so", "__errno");
|
||||
auto get_remote_errno = [&]() -> int {
|
||||
if (!errno_addr) {
|
||||
LOGE("could not get errno!");
|
||||
return 0;
|
||||
}
|
||||
auto addr = remote_call(pid, regs, (uintptr_t) errno_addr, 0, args);
|
||||
int err = 0;
|
||||
if (!read_proc(pid, addr, &err, sizeof(err))) return 0;
|
||||
return err;
|
||||
};
|
||||
if (!socket_addr || !bind_addr || !recvmsg_addr) return false;
|
||||
args.clear();
|
||||
args.push_back(AF_UNIX);
|
||||
args.push_back(SOCK_DGRAM | SOCK_CLOEXEC);
|
||||
args.push_back(0);
|
||||
int remote_fd = (int) remote_call(pid, regs, (uintptr_t) socket_addr, 0, args);
|
||||
if (remote_fd == -1) {
|
||||
errno = get_remote_errno();
|
||||
PLOGE("remote socket");
|
||||
return false;
|
||||
}
|
||||
|
||||
auto close_remote = [&](int fd) -> void {
|
||||
args.clear();
|
||||
args.push_back(fd);
|
||||
if (remote_call(pid, regs, (uintptr_t) close_addr, 0, args) != 0) {
|
||||
LOGE("remote not closed: %d", fd);
|
||||
}
|
||||
};
|
||||
|
||||
auto magic = generateMagic(16);
|
||||
struct sockaddr_un addr{
|
||||
.sun_family = AF_UNIX,
|
||||
.sun_path = {0}
|
||||
};
|
||||
LOGD("socket name %s", magic.c_str());
|
||||
memcpy(addr.sun_path + 1, magic.c_str(), magic.size());
|
||||
socklen_t len = sizeof(addr.sun_family) + 1 + magic.size();
|
||||
auto remote_addr = push_memory(pid, regs, &addr, sizeof(addr));
|
||||
args.clear();
|
||||
args.push_back(remote_fd);
|
||||
args.push_back(remote_addr);
|
||||
args.push_back(len);
|
||||
auto bind_result = remote_call(pid, regs, (uintptr_t) bind_addr, 0, args);
|
||||
if (bind_result == (uintptr_t) -1) {
|
||||
errno = get_remote_errno();
|
||||
PLOGE("remote bind");
|
||||
close_remote(remote_fd);
|
||||
return false;
|
||||
}
|
||||
|
||||
char cmsgbuf[CMSG_SPACE(sizeof(int))] = {0};
|
||||
auto remote_cmsgbuf = push_memory(pid, regs, &cmsgbuf, sizeof(cmsgbuf));
|
||||
|
||||
struct msghdr hdr{};
|
||||
hdr.msg_control = (void*) remote_cmsgbuf;
|
||||
hdr.msg_controllen = sizeof(cmsgbuf);
|
||||
|
||||
auto remote_hdr = push_memory(pid, regs, &hdr, sizeof(hdr));
|
||||
|
||||
args.clear();
|
||||
args.push_back(remote_fd);
|
||||
args.push_back(remote_hdr);
|
||||
args.push_back(MSG_WAITALL);
|
||||
if (!remote_pre_call(pid, regs, (uintptr_t) recvmsg_addr, 0, args)) {
|
||||
// we can't do anything more
|
||||
LOGE("pre call remote recvmsg");
|
||||
return false;
|
||||
}
|
||||
|
||||
hdr.msg_control = &cmsgbuf;
|
||||
hdr.msg_name = &addr;
|
||||
hdr.msg_namelen = len;
|
||||
{
|
||||
cmsghdr *cmsg = CMSG_FIRSTHDR(&hdr);
|
||||
cmsg->cmsg_len = CMSG_LEN(sizeof(int));
|
||||
cmsg->cmsg_level = SOL_SOCKET;
|
||||
cmsg->cmsg_type = AF_UNIX;
|
||||
*(int *) CMSG_DATA(cmsg) = local_lib_fd;
|
||||
}
|
||||
|
||||
if (sendmsg(local_socket, &hdr, 0) == -1) {
|
||||
PLOGE("send to remote");
|
||||
close_remote(remote_fd);
|
||||
return false;
|
||||
}
|
||||
|
||||
auto recvmsg_result = (ssize_t) remote_post_call(pid, regs, 0);
|
||||
if (recvmsg_result == -1) {
|
||||
errno = get_remote_errno();
|
||||
PLOGE("post call recvmsg");
|
||||
close_remote(remote_fd);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (read_proc(pid, remote_cmsgbuf, &cmsgbuf, sizeof(cmsgbuf)) != sizeof(cmsgbuf)) {
|
||||
LOGE("failed to read proc");
|
||||
close_remote(remote_fd);
|
||||
return false;
|
||||
}
|
||||
|
||||
cmsghdr *cmsg = CMSG_FIRSTHDR(&hdr);
|
||||
if (cmsg == nullptr || cmsg->cmsg_len != CMSG_LEN(sizeof(int)) || cmsg->cmsg_level != SOL_SOCKET || cmsg->cmsg_type != SCM_RIGHTS) {
|
||||
LOGE("remote recv fd failed!");
|
||||
close_remote(remote_fd);
|
||||
return false;
|
||||
}
|
||||
|
||||
lib_fd = *(int*) CMSG_DATA(cmsg);
|
||||
LOGD("remote lib fd: %d", lib_fd);
|
||||
close_remote(remote_fd);
|
||||
}
|
||||
|
||||
// call dlopen
|
||||
{
|
||||
auto dlopen_addr = find_func_addr(local_map, map, "libdl.so", "android_dlopen_ext");
|
||||
if (dlopen_addr == nullptr) return false;
|
||||
android_dlextinfo info{};
|
||||
info.flags = ANDROID_DLEXT_USE_LIBRARY_FD;
|
||||
info.library_fd = lib_fd;
|
||||
uintptr_t remote_info = push_memory(pid, regs, &info, sizeof(info));
|
||||
str = push_string(pid, regs, lib_path);
|
||||
args.clear();
|
||||
args.push_back((long) str);
|
||||
args.push_back((long) RTLD_NOW);
|
||||
args.push_back(remote_info);
|
||||
remote_handle = remote_call(pid, regs, (uintptr_t) dlopen_addr,
|
||||
(uintptr_t) libc_return_addr, args);
|
||||
LOGD("remote handle %p", (void *) remote_handle);
|
||||
if (remote_handle == 0) {
|
||||
LOGE("handle is null");
|
||||
// call dlerror
|
||||
auto dlerror_addr = find_func_addr(local_map, map, "libdl.so", "dlerror");
|
||||
if (dlerror_addr == nullptr) {
|
||||
LOGE("find dlerror");
|
||||
return false;
|
||||
}
|
||||
args.clear();
|
||||
auto dlerror_str_addr = remote_call(pid, regs, (uintptr_t) dlerror_addr,
|
||||
(uintptr_t) libc_return_addr, args);
|
||||
LOGD("dlerror str %p", (void *) dlerror_str_addr);
|
||||
if (dlerror_str_addr == 0) return false;
|
||||
auto strlen_addr = find_func_addr(local_map, map, "libc.so", "strlen");
|
||||
if (strlen_addr == nullptr) {
|
||||
LOGE("find strlen");
|
||||
return false;
|
||||
}
|
||||
args.clear();
|
||||
args.push_back(dlerror_str_addr);
|
||||
auto dlerror_len = remote_call(pid, regs, (uintptr_t) strlen_addr,
|
||||
(uintptr_t) libc_return_addr, args);
|
||||
if (dlerror_len <= 0) {
|
||||
LOGE("dlerror len <= 0");
|
||||
return false;
|
||||
}
|
||||
std::string err;
|
||||
err.resize(dlerror_len + 1, 0);
|
||||
read_proc(pid, (uintptr_t) dlerror_str_addr, err.data(), dlerror_len);
|
||||
LOGE("dlerror info %s", err.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
args.clear();
|
||||
args.push_back(lib_fd);
|
||||
if (remote_call(pid, regs, (uintptr_t) close_addr, 0, args) != 0) {
|
||||
LOGE("remote lib not closed: %d", lib_fd);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// call dlsym(handle, "entry")
|
||||
{
|
||||
auto dlsym_addr = find_func_addr(local_map, map, "libdl.so", "dlsym");
|
||||
if (dlsym_addr == nullptr) return false;
|
||||
args.clear();
|
||||
str = push_string(pid, regs, "entry");
|
||||
args.push_back(remote_handle);
|
||||
args.push_back((long) str);
|
||||
injector_entry = remote_call(pid, regs, (uintptr_t) dlsym_addr,
|
||||
(uintptr_t) libc_return_addr, args);
|
||||
LOGD("injector entry %p", (void *) injector_entry);
|
||||
if (injector_entry == 0) {
|
||||
LOGE("injector entry is null");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// call injector entry(handle)
|
||||
{
|
||||
args.clear();
|
||||
args.push_back(remote_handle);
|
||||
remote_call(pid, regs, injector_entry, (uintptr_t) libc_return_addr, args);
|
||||
}
|
||||
|
||||
LOGD("restore context");
|
||||
// restore registers
|
||||
if (!set_regs(pid, backup)) return false;
|
||||
if (ptrace(PTRACE_DETACH, pid, 0, 0) == -1) {
|
||||
PLOGE("failed to detach");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
} else {
|
||||
LOGE("stopped by other reason: %s", parse_status(status).c_str());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
#ifndef NDEBUG
|
||||
logging::setPrintEnabled(true);
|
||||
#endif
|
||||
auto pid = strtol(argv[1], nullptr, 0);
|
||||
char buf[4096];
|
||||
realpath(argv[2], buf);
|
||||
return !inject_library(pid, buf, argv[3]);
|
||||
}
|
||||
@@ -0,0 +1,696 @@
|
||||
#include <vector>
|
||||
#include <sys/mman.h>
|
||||
#include <sys/sysmacros.h>
|
||||
#include <array>
|
||||
#include <cinttypes>
|
||||
#include <sys/ptrace.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/uio.h>
|
||||
#include <sys/auxv.h>
|
||||
#include <elf.h>
|
||||
#include <link.h>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <sys/mman.h>
|
||||
#include <sys/wait.h>
|
||||
#include <cstdlib>
|
||||
#include <cstdio>
|
||||
#include <dlfcn.h>
|
||||
#include <csignal>
|
||||
#include <cstring>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/syscall.h>
|
||||
#include <cinttypes>
|
||||
#include <sys/xattr.h>
|
||||
#include <random>
|
||||
|
||||
#include "utils.hpp"
|
||||
#include "logging.hpp"
|
||||
#include <sched.h>
|
||||
#include <fcntl.h>
|
||||
|
||||
bool switch_mnt_ns(int pid, int *fd) {
|
||||
int nsfd, old_nsfd = -1;
|
||||
std::string path;
|
||||
if (pid == 0) {
|
||||
if (fd != nullptr) {
|
||||
nsfd = *fd;
|
||||
*fd = -1;
|
||||
} else return false;
|
||||
path = "/proc/self/fd/";
|
||||
path += std::to_string(nsfd);
|
||||
} else {
|
||||
if (fd != nullptr) {
|
||||
old_nsfd = open("/proc/self/ns/mnt", O_RDONLY | O_CLOEXEC);
|
||||
if (old_nsfd == -1) {
|
||||
PLOGE("get old nsfd");
|
||||
return false;
|
||||
}
|
||||
*fd = old_nsfd;
|
||||
}
|
||||
path = std::string("/proc/") + std::to_string(pid) + "/ns/mnt";
|
||||
nsfd = open(path.c_str(), O_RDONLY | O_CLOEXEC);
|
||||
if (nsfd == -1) {
|
||||
PLOGE("open nsfd %s", path.c_str());
|
||||
close(old_nsfd);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (setns(nsfd, CLONE_NEWNS) == -1) {
|
||||
PLOGE("set ns to %s", path.c_str());
|
||||
close(nsfd);
|
||||
close(old_nsfd);
|
||||
return false;
|
||||
}
|
||||
close(nsfd);
|
||||
return true;
|
||||
}
|
||||
|
||||
ssize_t write_proc(int pid, uintptr_t remote_addr, const void *buf, size_t len, bool use_proc_mem) {
|
||||
LOGV("write to %d addr %" PRIxPTR " size %zu use_proc_mem=%d", pid, remote_addr, len, use_proc_mem);
|
||||
if (use_proc_mem) {
|
||||
char path[64];
|
||||
snprintf(path, sizeof(path), "/proc/%d/mem", pid);
|
||||
auto fd = open(path, O_WRONLY | O_CLOEXEC);
|
||||
if (fd == -1) {
|
||||
PLOGE("open proc mem");
|
||||
return -1;
|
||||
}
|
||||
auto l = pwrite(fd, buf, len, remote_addr);
|
||||
if (l == -1) {
|
||||
PLOGE("pwrite");
|
||||
} else if (static_cast<size_t>(l) != len) {
|
||||
LOGW("not fully written: %zu, excepted %zu", l, len);
|
||||
}
|
||||
return l;
|
||||
} else {
|
||||
struct iovec local{
|
||||
.iov_base = (void *) buf,
|
||||
.iov_len = len
|
||||
};
|
||||
struct iovec remote{
|
||||
.iov_base = (void *) remote_addr,
|
||||
.iov_len = len
|
||||
};
|
||||
auto l = process_vm_writev(pid, &local, 1, &remote, 1, 0);
|
||||
if (l == -1) {
|
||||
PLOGE("process_vm_writev");
|
||||
} else if (static_cast<size_t>(l) != len) {
|
||||
LOGW("not fully written: %zu, excepted %zu", l, len);
|
||||
}
|
||||
return l;
|
||||
}
|
||||
}
|
||||
|
||||
ssize_t read_proc(int pid, uintptr_t remote_addr, void *buf, size_t len) {
|
||||
struct iovec local{
|
||||
.iov_base = (void *) buf,
|
||||
.iov_len = len
|
||||
};
|
||||
struct iovec remote{
|
||||
.iov_base = (void *) remote_addr,
|
||||
.iov_len = len
|
||||
};
|
||||
auto l = process_vm_readv(pid, &local, 1, &remote, 1, 0);
|
||||
if (l == -1) {
|
||||
PLOGE("process_vm_readv");
|
||||
} else if (static_cast<size_t>(l) != len) {
|
||||
LOGW("not fully read: %zu, excepted %zu", l, len);
|
||||
}
|
||||
return l;
|
||||
}
|
||||
|
||||
bool get_regs(int pid, struct user_regs_struct ®s) {
|
||||
#if defined(__x86_64__) || defined(__i386__)
|
||||
if (ptrace(PTRACE_GETREGS, pid, 0, ®s) == -1) {
|
||||
PLOGE("getregs");
|
||||
return false;
|
||||
}
|
||||
#elif defined(__aarch64__) || defined(__arm__)
|
||||
struct iovec iov = {
|
||||
.iov_base = ®s,
|
||||
.iov_len = sizeof(struct user_regs_struct),
|
||||
};
|
||||
if (ptrace(PTRACE_GETREGSET, pid, NT_PRSTATUS, &iov) == -1) {
|
||||
PLOGE("getregs");
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
|
||||
bool set_regs(int pid, struct user_regs_struct ®s) {
|
||||
#if defined(__x86_64__) || defined(__i386__)
|
||||
if (ptrace(PTRACE_SETREGS, pid, 0, ®s) == -1) {
|
||||
PLOGE("setregs");
|
||||
return false;
|
||||
}
|
||||
#elif defined(__aarch64__) || defined(__arm__)
|
||||
struct iovec iov = {
|
||||
.iov_base = ®s,
|
||||
.iov_len = sizeof(struct user_regs_struct),
|
||||
};
|
||||
if (ptrace(PTRACE_SETREGSET, pid, NT_PRSTATUS, &iov) == -1) {
|
||||
PLOGE("setregs");
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string get_addr_mem_region(std::vector<lsplt::MapInfo> &info, uintptr_t addr) {
|
||||
for (auto &map: info) {
|
||||
if (map.start <= addr && map.end > addr) {
|
||||
auto s = std::string(map.path);
|
||||
s += ' ';
|
||||
s += map.perms & PROT_READ ? 'r' : '-';
|
||||
s += map.perms & PROT_WRITE ? 'w' : '-';
|
||||
s += map.perms & PROT_EXEC ? 'x' : '-';
|
||||
return s;
|
||||
}
|
||||
}
|
||||
return "<unknown>";
|
||||
}
|
||||
|
||||
|
||||
void *find_module_return_addr(std::vector<lsplt::MapInfo> &info, std::string_view suffix) {
|
||||
for (auto &map: info) {
|
||||
if ((map.perms & PROT_EXEC) == 0 && map.path.ends_with(suffix)) {
|
||||
return (void *) map.start;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void *find_module_base(std::vector<lsplt::MapInfo> &info, std::string_view suffix) {
|
||||
for (auto &map: info) {
|
||||
if (map.offset == 0 && map.path.ends_with(suffix)) {
|
||||
return (void *) map.start;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void *find_func_addr(
|
||||
std::vector<lsplt::MapInfo> &local_info,
|
||||
std::vector<lsplt::MapInfo> &remote_info,
|
||||
std::string_view module,
|
||||
std::string_view func) {
|
||||
auto lib = dlopen(module.data(), RTLD_NOW);
|
||||
if (lib == nullptr) {
|
||||
LOGE("failed to open lib %s: %s", module.data(), dlerror());
|
||||
return nullptr;
|
||||
}
|
||||
auto sym = reinterpret_cast<uint8_t *>(dlsym(lib, func.data()));
|
||||
if (sym == nullptr) {
|
||||
LOGE("failed to find sym %s in %s: %s", func.data(), module.data(), dlerror());
|
||||
dlclose(lib);
|
||||
return nullptr;
|
||||
}
|
||||
LOGD("sym %s: %p", func.data(), sym);
|
||||
dlclose(lib);
|
||||
auto local_base = reinterpret_cast<uint8_t *>(find_module_base(local_info, module));
|
||||
if (local_base == nullptr) {
|
||||
LOGE("failed to find local base for module %s", module.data());
|
||||
return nullptr;
|
||||
}
|
||||
auto remote_base = reinterpret_cast<uint8_t *>(find_module_base(remote_info, module));
|
||||
if (remote_base == nullptr) {
|
||||
LOGE("failed to find remote base for module %s", module.data());
|
||||
return nullptr;
|
||||
}
|
||||
LOGD("found local base %p remote base %p", local_base, remote_base);
|
||||
auto addr = (sym - local_base) + remote_base;
|
||||
LOGD("addr %p", addr);
|
||||
return addr;
|
||||
}
|
||||
|
||||
void align_stack(struct user_regs_struct ®s, uintptr_t preserve) {
|
||||
regs.REG_SP = (regs.REG_SP - preserve) & ~0xf;
|
||||
}
|
||||
|
||||
uintptr_t push_memory(int pid, struct user_regs_struct ®s, void* local_addr, size_t len) {
|
||||
regs.REG_SP -= len;
|
||||
align_stack(regs);
|
||||
auto addr = static_cast<uintptr_t>(regs.REG_SP);
|
||||
if (!write_proc(pid, addr, local_addr, len)) {
|
||||
LOGE("failed to write mem %p+%zu", local_addr, len);
|
||||
}
|
||||
LOGD("pushed mem %" PRIxPTR, addr);
|
||||
return addr;
|
||||
}
|
||||
|
||||
uintptr_t push_string(int pid, struct user_regs_struct ®s, const char *str) {
|
||||
auto len = strlen(str) + 1;
|
||||
regs.REG_SP -= len;
|
||||
align_stack(regs);
|
||||
auto addr = static_cast<uintptr_t>(regs.REG_SP);
|
||||
if (!write_proc(pid, addr, str, len)) {
|
||||
LOGE("failed to write string %s", str);
|
||||
}
|
||||
LOGD("pushed string %" PRIxPTR, addr);
|
||||
return addr;
|
||||
}
|
||||
|
||||
bool remote_pre_call(int pid, struct user_regs_struct ®s, uintptr_t func_addr, uintptr_t return_addr,
|
||||
std::vector<uintptr_t> &args) {
|
||||
align_stack(regs);
|
||||
LOGV("calling remote function %" PRIxPTR " args %zu", func_addr, args.size());
|
||||
for (auto &a: args) {
|
||||
LOGV("arg %p", (void *) a);
|
||||
}
|
||||
#if defined(__x86_64__)
|
||||
if (args.size() >= 1) {
|
||||
regs.rdi = args[0];
|
||||
}
|
||||
if (args.size() >= 2) {
|
||||
regs.rsi = args[1];
|
||||
}
|
||||
if (args.size() >= 3) {
|
||||
regs.rdx = args[2];
|
||||
}
|
||||
if (args.size() >= 4) {
|
||||
regs.rcx = args[3];
|
||||
}
|
||||
if (args.size() >= 5) {
|
||||
regs.r8 = args[4];
|
||||
}
|
||||
if (args.size() >= 6) {
|
||||
regs.r9 = args[5];
|
||||
}
|
||||
if (args.size() > 6) {
|
||||
auto remain = (args.size() - 6) * sizeof(uintptr_t);
|
||||
align_stack(regs, remain);
|
||||
if (!write_proc(pid, (uintptr_t) regs.REG_SP, args.data(), remain)) {
|
||||
LOGE("failed to push arguments");
|
||||
}
|
||||
}
|
||||
regs.REG_SP -= sizeof(uintptr_t);
|
||||
if (!write_proc(pid, (uintptr_t) regs.REG_SP, &return_addr, sizeof(return_addr))) {
|
||||
LOGE("failed to write return addr");
|
||||
}
|
||||
regs.REG_IP = func_addr;
|
||||
#elif defined(__i386__)
|
||||
if (args.size() > 0) {
|
||||
auto remain = (args.size()) * sizeof(uintptr_t);
|
||||
align_stack(regs, remain);
|
||||
if (!write_proc(pid, (uintptr_t) regs.REG_SP, args.data(), remain)) {
|
||||
LOGE("failed to push arguments");
|
||||
}
|
||||
}
|
||||
regs.REG_SP -= sizeof(uintptr_t);
|
||||
if (!write_proc(pid, (uintptr_t) regs.REG_SP, &return_addr, sizeof(return_addr))) {
|
||||
LOGE("failed to write return addr");
|
||||
}
|
||||
regs.REG_IP = func_addr;
|
||||
#elif defined(__aarch64__)
|
||||
for (size_t i = 0; i < args.size() && i < 8; i++) {
|
||||
regs.regs[i] = args[i];
|
||||
}
|
||||
if (args.size() > 8) {
|
||||
auto remain = (args.size() - 8) * sizeof(uintptr_t);
|
||||
align_stack(regs, remain);
|
||||
write_proc(pid, (uintptr_t)regs.REG_SP, args.data(), remain);
|
||||
}
|
||||
regs.regs[30] = return_addr;
|
||||
regs.REG_IP = func_addr;
|
||||
#elif defined(__arm__)
|
||||
for (size_t i = 0; i < args.size() && i < 4; i++) {
|
||||
regs.uregs[i] = args[i];
|
||||
}
|
||||
if (args.size() > 4) {
|
||||
auto remain = (args.size() - 4) * sizeof(uintptr_t);
|
||||
align_stack(regs, remain);
|
||||
write_proc(pid, (uintptr_t)regs.REG_SP, args.data(), remain);
|
||||
}
|
||||
regs.uregs[14] = return_addr;
|
||||
regs.REG_IP = func_addr;
|
||||
constexpr auto CPSR_T_MASK = 1lu << 5;
|
||||
if ((regs.REG_IP & 1) != 0) {
|
||||
regs.REG_IP = regs.REG_IP & ~1;
|
||||
regs.uregs[16] = regs.uregs[16] | CPSR_T_MASK;
|
||||
} else {
|
||||
regs.uregs[16] = regs.uregs[16] & ~CPSR_T_MASK;
|
||||
}
|
||||
#endif
|
||||
if (!set_regs(pid, regs)) {
|
||||
LOGE("failed to set regs");
|
||||
return false;
|
||||
}
|
||||
return ptrace(PTRACE_CONT, pid, 0, 0) == 0;
|
||||
}
|
||||
|
||||
uintptr_t remote_post_call(int pid, struct user_regs_struct ®s, uintptr_t return_addr) {
|
||||
int status;
|
||||
wait_for_trace(pid, &status, __WALL);
|
||||
if (!get_regs(pid, regs)) {
|
||||
LOGE("failed to get regs after call");
|
||||
return 0;
|
||||
}
|
||||
if (WSTOPSIG(status) == SIGSEGV) {
|
||||
if (static_cast<uintptr_t>(regs.REG_IP) != return_addr) {
|
||||
LOGE("wrong return addr %p", (void *) regs.REG_IP);
|
||||
siginfo_t siginfo;
|
||||
if (ptrace(PTRACE_GETSIGINFO, pid, 0, &siginfo) == -1) {
|
||||
PLOGE("failed to get siginfo");
|
||||
} else {
|
||||
LOGE("si_code=%d si_addr=%p", siginfo.si_code, siginfo.si_addr);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
return regs.REG_RET;
|
||||
} else {
|
||||
LOGE("stopped by other reason %s at addr %p", parse_status(status).c_str(), (void*) regs.REG_IP);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
uintptr_t remote_call(int pid, struct user_regs_struct ®s, uintptr_t func_addr, uintptr_t return_addr,
|
||||
std::vector<uintptr_t> &args) {
|
||||
if (!remote_pre_call(pid, regs, func_addr, return_addr, args)) return 0;
|
||||
return remote_post_call(pid, regs, return_addr);
|
||||
}
|
||||
|
||||
int fork_dont_care() {
|
||||
auto pid = fork();
|
||||
if (pid < 0) {
|
||||
PLOGE("fork 1");
|
||||
} else if (pid == 0) {
|
||||
pid = fork();
|
||||
if (pid < 0) {
|
||||
PLOGE("fork 2");
|
||||
} else if (pid > 0) {
|
||||
exit(0);
|
||||
}
|
||||
} else {
|
||||
int status;
|
||||
waitpid(pid, &status, __WALL);
|
||||
}
|
||||
return pid;
|
||||
}
|
||||
|
||||
bool wait_for_trace(int pid, int* status, int flags) {
|
||||
while (true) {
|
||||
auto result = waitpid(pid, status, flags);
|
||||
if (result == -1) {
|
||||
if (errno == EINTR) {
|
||||
continue;
|
||||
} else {
|
||||
PLOGE("wait %d failed", pid);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (!WIFSTOPPED(*status)) {
|
||||
LOGE("process %d not stopped for trace: %s, exit", pid, parse_status(*status).c_str());
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
std::string parse_status(int status) {
|
||||
char buf[64];
|
||||
if (WIFEXITED(status)) {
|
||||
snprintf(buf, sizeof(buf), "0x%x exited with %d", status, WEXITSTATUS(status));
|
||||
} else if (WIFSIGNALED(status)) {
|
||||
snprintf(buf, sizeof(buf), "0x%x signaled with %s(%d)", status, sigabbrev_np(WTERMSIG(status)), WTERMSIG(status));
|
||||
} else if (WIFSTOPPED(status)) {
|
||||
auto stop_sig = WSTOPSIG(status);
|
||||
snprintf(buf, sizeof(buf), "0x%x stopped by signal=%s(%d),event=%s", status, sigabbrev_np(stop_sig), stop_sig, parse_ptrace_event(status));
|
||||
} else {
|
||||
snprintf(buf, sizeof(buf), "0x%x unknown", status);
|
||||
}
|
||||
return buf;
|
||||
}
|
||||
|
||||
std::string get_program(int pid) {
|
||||
std::string path = "/proc/";
|
||||
path += std::to_string(pid);
|
||||
path += "/exe";
|
||||
constexpr const auto SIZE = 256;
|
||||
char buf[SIZE + 1];
|
||||
auto sz = readlink(path.c_str(), buf, SIZE);
|
||||
if (sz == -1) {
|
||||
PLOGE("readlink /proc/%d/exe", pid);
|
||||
return "";
|
||||
}
|
||||
buf[sz] = 0;
|
||||
return buf;
|
||||
}
|
||||
|
||||
std::string parse_exec(int pid) {
|
||||
struct user_regs_struct regs;
|
||||
if (!get_regs(pid, regs)) return "";
|
||||
auto nr = regs.REG_NR;
|
||||
if (nr != SYS_execve) {
|
||||
// LOGI("syscall %ld != %d", nr, SYS_execve);
|
||||
return "";
|
||||
}
|
||||
auto file_name_ptr = regs.REG_SYS_ARG0;
|
||||
char buf[128];
|
||||
auto sz = read_proc(pid, file_name_ptr, buf, sizeof(buf));
|
||||
if (sz < 0) return "";
|
||||
for (auto i = 0; i < sz; i++) {
|
||||
if (buf[i] == 0) {
|
||||
LOGD("exec len %d prog %s", i, buf);
|
||||
return buf;
|
||||
}
|
||||
}
|
||||
// too long
|
||||
return "";
|
||||
}
|
||||
|
||||
bool skip_syscall(int pid) {
|
||||
struct user_regs_struct regs;
|
||||
if (!get_regs(pid, regs)) return false;
|
||||
#if defined(__aarch64__)
|
||||
// https://stackoverflow.com/questions/63620203/ptrace-change-syscall-number-arm64
|
||||
int syscallno = 0xffff;
|
||||
struct iovec iov = {
|
||||
.iov_base = &syscallno,
|
||||
.iov_len = sizeof (int),
|
||||
};
|
||||
if (ptrace(PTRACE_SETREGSET, pid, NT_ARM_SYSTEM_CALL, &iov) == -1) {
|
||||
PLOGE("failed to set syscall");
|
||||
}
|
||||
#elif defined(__arm__)
|
||||
if (ptrace(PTRACE_SET_SYSCALL, pid, 0, 0xffff) == -1) {
|
||||
PLOGE("failed to set syscall");
|
||||
}
|
||||
#else
|
||||
auto orig_nr = regs.REG_NR;
|
||||
regs.REG_NR = 0xffff;
|
||||
if (!set_regs(pid, regs)) return false;
|
||||
regs.REG_NR = orig_nr;
|
||||
#endif
|
||||
if (ptrace(PTRACE_SYSCALL, pid, 0, 0) == -1) {
|
||||
PLOGE("failed to singlestep");
|
||||
return false;
|
||||
}
|
||||
int status;
|
||||
waitpid(pid, &status, __WALL);
|
||||
#if defined(__x86_64__)
|
||||
regs.REG_IP -= 2;
|
||||
regs.rax = orig_nr;
|
||||
#elif defined(__i386__)
|
||||
regs.REG_IP -= 2;
|
||||
regs.eax = orig_nr;
|
||||
#elif defined(__aarch64__)
|
||||
regs.REG_IP -= 4;
|
||||
#elif defined(__arm__)
|
||||
regs.REG_IP -= (regs.REG_IP % 2) ? 2 : 4;
|
||||
#endif
|
||||
return set_regs(pid, regs);
|
||||
}
|
||||
|
||||
void wait_for_syscall(int pid) {
|
||||
int status;
|
||||
for (;;) {
|
||||
if (!wait_for_trace(pid, &status, __WALL)) {
|
||||
LOGE("could not wait for trace");
|
||||
exit(1);
|
||||
}
|
||||
if (WIFSTOPPED(status) && WSTOPSIG(status) == (SIGTRAP | 0x80)) {
|
||||
LOGV("!! stopped at syscall");
|
||||
break;
|
||||
}
|
||||
LOGV("! not syscall: %s", parse_status(status).c_str());
|
||||
if (ptrace(PTRACE_CONT, pid, 0, WSTOPSIG(status)) == -1) {
|
||||
PLOGE("failed to cont");
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool do_syscall(int pid, uintptr_t &ret, int nr, uintptr_t arg0, uintptr_t arg1, uintptr_t arg2, uintptr_t arg3, uintptr_t arg4, uintptr_t arg5) {
|
||||
#if defined(__i386__)
|
||||
LOGE("do_syscall is unsupported on i386!");
|
||||
return false;
|
||||
#else
|
||||
struct user_regs_struct regs, backup_regs;
|
||||
if (ptrace(PTRACE_SYSCALL, pid, 0, 0) == -1) {
|
||||
PLOGE("failed to singlestep");
|
||||
return false;
|
||||
}
|
||||
wait_for_syscall(pid);
|
||||
if (!get_regs(pid, regs)) return false;
|
||||
memcpy(&backup_regs, ®s, sizeof(struct user_regs_struct));
|
||||
// set syscall nr and args
|
||||
#if defined(__aarch64__)
|
||||
// https://stackoverflow.com/questions/63620203/ptrace-change-syscall-number-arm64
|
||||
int syscallno = nr;
|
||||
struct iovec iov = {
|
||||
.iov_base = &syscallno,
|
||||
.iov_len = sizeof (int),
|
||||
};
|
||||
if (ptrace(PTRACE_SETREGSET, pid, NT_ARM_SYSTEM_CALL, &iov) == -1) {
|
||||
PLOGE("failed to set syscall");
|
||||
return false;
|
||||
}
|
||||
regs.regs[0] = arg0;
|
||||
regs.regs[1] = arg1;
|
||||
regs.regs[2] = arg2;
|
||||
regs.regs[3] = arg3;
|
||||
regs.regs[4] = arg4;
|
||||
regs.regs[5] = arg5;
|
||||
#elif defined(__arm__)
|
||||
if (ptrace(PTRACE_SET_SYSCALL, pid, 0, nr) == -1) {
|
||||
PLOGE("failed to set syscall");
|
||||
return false;
|
||||
}
|
||||
regs.uregs[0] = arg0;
|
||||
regs.uregs[1] = arg1;
|
||||
regs.uregs[2] = arg2;
|
||||
regs.uregs[3] = arg3;
|
||||
regs.uregs[4] = arg4;
|
||||
regs.uregs[5] = arg5;
|
||||
#elif defined(__x86_64__)
|
||||
auto orig_nr = regs.REG_NR;
|
||||
regs.REG_NR = nr;
|
||||
regs.rdi = arg0;
|
||||
regs.rsi = arg1;
|
||||
regs.rdx = arg2;
|
||||
regs.r10 = arg3;
|
||||
regs.r8 = arg4;
|
||||
regs.r9 = arg5;
|
||||
#endif
|
||||
if (!set_regs(pid, regs)) return false;
|
||||
if (ptrace(PTRACE_SYSCALL, pid, 0, 0) == -1) {
|
||||
PLOGE("failed to singlestep");
|
||||
return false;
|
||||
}
|
||||
wait_for_syscall(pid);
|
||||
if (!get_regs(pid, regs)) return false;
|
||||
// go back to last instruction
|
||||
// on x86, we should fill the nr register with orig_nr
|
||||
#if defined(__x86_64__)
|
||||
ret = regs.rax;
|
||||
backup_regs.REG_IP -= 2;
|
||||
backup_regs.rax = orig_nr;
|
||||
#elif defined(__aarch64__)
|
||||
ret = regs.regs[0];
|
||||
backup_regs.REG_IP -= 4;
|
||||
#elif defined(__arm__)
|
||||
ret = regs.uregs[0];
|
||||
backup_regs.REG_IP -= (regs.REG_IP % 2) ? 2 : 4;
|
||||
#endif
|
||||
return set_regs(pid, backup_regs);
|
||||
#endif // i386
|
||||
}
|
||||
|
||||
uintptr_t remote_syscall(int pid, uintptr_t &ret, int nr, uintptr_t arg0 = 0, uintptr_t arg1 = 0, uintptr_t arg2 = 0, uintptr_t arg3 = 0, uintptr_t arg4 = 0, uintptr_t arg5 = 0) {
|
||||
if (!do_syscall(pid, ret, nr, arg0, arg1, arg2, arg3, arg4, arg5)) {
|
||||
LOGE("do remote syscall %d failed", nr);
|
||||
return -1;
|
||||
}
|
||||
if (SYSCALL_IS_ERR(ret)) {
|
||||
LOGE("do remote syscall %d return error %d %s", nr, SYSCALL_ERR(ret), strerror(SYSCALL_ERR(ret)));
|
||||
return -1;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
uintptr_t remote_mmap(int pid, uintptr_t addr, size_t size, int prot, int flags, int fd, off_t offset) {
|
||||
uintptr_t result;
|
||||
LOGD("remote mmap %" PRIxPTR " size %zu fd %d off %lu", addr, size, fd, offset);
|
||||
if (!remote_syscall(pid, result, SYS_mmap, addr, size, prot, flags, fd, offset)) {
|
||||
LOGE("do remote mmap failed");
|
||||
return -1;
|
||||
}
|
||||
LOGD("remote mmap get %" PRIxPTR, result);
|
||||
return result;
|
||||
}
|
||||
|
||||
bool remote_munmap(int pid, uintptr_t addr, size_t size) {
|
||||
uintptr_t result = 0;
|
||||
if (remote_syscall(pid, result, SYS_munmap, addr, size)) {
|
||||
LOGE("do remote munmap failed");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
int remote_open(int pid, uintptr_t path_addr, int flags) {
|
||||
uintptr_t result;
|
||||
if (!remote_syscall(pid, result, SYS_openat, AT_FDCWD, path_addr, flags)) {
|
||||
LOGE("remote open failed");
|
||||
return -1;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
bool remote_close(int pid, int fd) {
|
||||
uintptr_t result = 0;
|
||||
if (remote_syscall(pid, result, SYS_close, fd)) {
|
||||
LOGE("remote close failed");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
int wait_for_child(int pid) {
|
||||
int status;
|
||||
for (;;) {
|
||||
if (waitpid(pid, &status, 0) == -1) {
|
||||
if (errno == EINTR) continue;
|
||||
PLOGE("waitpid %d", pid);
|
||||
return -1;
|
||||
} else {
|
||||
return status;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::string generateMagic(size_t len) {
|
||||
constexpr const char chrs[] = "0123456789"
|
||||
"abcdefghijklmnopqrstuvwxyz"
|
||||
"ABCDEFGHIJKLMNOPQRSTUVWXYZ";
|
||||
std::mt19937 rg{std::random_device{}()};
|
||||
std::uniform_int_distribution<std::string::size_type> pick(0, sizeof(chrs) - 2);
|
||||
|
||||
std::string s;
|
||||
s.reserve(len);
|
||||
while(len--) s += chrs[pick(rg)];
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
int setfilecon(const char* path, const char* con) {
|
||||
return syscall(__NR_setxattr, path, XATTR_NAME_SELINUX, con, strlen(con) + 1, 0);
|
||||
}
|
||||
|
||||
bool set_sockcreate_con(const char* con) {
|
||||
auto sz = static_cast<ssize_t>(strlen(con) + 1);
|
||||
UniqueFd fd = open("/proc/thread-self/attr/sockcreate", O_WRONLY | O_CLOEXEC);
|
||||
if (fd == -1 || write(fd, con, sz) != sz) {
|
||||
PLOGE("set socket con");
|
||||
char buf[128];
|
||||
snprintf(buf, sizeof(buf), "/proc/%d/attr/sockcreate", gettid());
|
||||
fd = open(buf, O_WRONLY | O_CLOEXEC);
|
||||
if (fd == -1 || write(fd, con, sz) != sz) {
|
||||
PLOGE("set socket con fallback");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -0,0 +1,166 @@
|
||||
#pragma once
|
||||
#include <string>
|
||||
#include <sys/ptrace.h>
|
||||
#include <map>
|
||||
#include "lsplt.hpp"
|
||||
|
||||
#define LOG_TAG "TrickyStore"
|
||||
|
||||
#define SYSCALL_IS_ERR(e) (((unsigned long) e) > -4096UL)
|
||||
#define SYSCALL_ERR(e) (-(int)(e))
|
||||
|
||||
#if defined(__x86_64__)
|
||||
#define REG_SP rsp
|
||||
#define REG_IP rip
|
||||
#define REG_RET rax
|
||||
#define REG_NR orig_rax
|
||||
#define REG_SYS_ARG0 rdi
|
||||
#elif defined(__i386__)
|
||||
#define REG_SP esp
|
||||
#define REG_IP eip
|
||||
#define REG_RET eax
|
||||
#define REG_NR orig_eax
|
||||
#define REG_SYS_ARG0 ebx
|
||||
#elif defined(__aarch64__)
|
||||
#define REG_SP sp
|
||||
#define REG_IP pc
|
||||
#define REG_RET regs[0]
|
||||
#define REG_NR regs[8]
|
||||
#define REG_SYS_ARG0 regs[0]
|
||||
#elif defined(__arm__)
|
||||
#define REG_SP uregs[13]
|
||||
#define REG_IP uregs[15]
|
||||
#define REG_RET uregs[0]
|
||||
#define REG_NR uregs[7]
|
||||
#define REG_SYS_ARG0 uregs[0]
|
||||
#define user_regs_struct user_regs
|
||||
#define SYS_mmap SYS_mmap2
|
||||
#endif
|
||||
|
||||
ssize_t write_proc(int pid, uintptr_t remote_addr, const void *buf, size_t len, bool use_proc_mem = false);
|
||||
|
||||
ssize_t read_proc(int pid, uintptr_t remote_addr, void *buf, size_t len);
|
||||
|
||||
bool get_regs(int pid, struct user_regs_struct ®s);
|
||||
|
||||
bool set_regs(int pid, struct user_regs_struct ®s);
|
||||
|
||||
std::string get_addr_mem_region(std::vector<lsplt::MapInfo> &info, uintptr_t addr);
|
||||
|
||||
void *find_module_base(std::vector<lsplt::MapInfo> &info, std::string_view suffix);
|
||||
|
||||
void *find_func_addr(
|
||||
std::vector<lsplt::MapInfo> &local_info,
|
||||
std::vector<lsplt::MapInfo> &remote_info,
|
||||
std::string_view module,
|
||||
std::string_view func);
|
||||
|
||||
void align_stack(struct user_regs_struct ®s, uintptr_t preserve = 0);
|
||||
|
||||
uintptr_t push_memory(int pid, struct user_regs_struct ®s, void* local_addr, size_t len);
|
||||
|
||||
uintptr_t push_string(int pid, struct user_regs_struct ®s, const char *str);
|
||||
|
||||
uintptr_t remote_call(int pid, struct user_regs_struct ®s, uintptr_t func_addr, uintptr_t return_addr,
|
||||
std::vector<uintptr_t> &args);
|
||||
|
||||
int fork_dont_care();
|
||||
|
||||
bool wait_for_trace(int pid, int* status, int flags);
|
||||
|
||||
std::string parse_status(int status);
|
||||
|
||||
#define WPTEVENT(x) (x >> 16)
|
||||
|
||||
#define CASE_CONST_RETURN(x) case x: return #x;
|
||||
|
||||
inline const char* parse_ptrace_event(int status) {
|
||||
status = status >> 16;
|
||||
switch (status) {
|
||||
CASE_CONST_RETURN(PTRACE_EVENT_FORK)
|
||||
CASE_CONST_RETURN(PTRACE_EVENT_VFORK)
|
||||
CASE_CONST_RETURN(PTRACE_EVENT_CLONE)
|
||||
CASE_CONST_RETURN(PTRACE_EVENT_EXEC)
|
||||
CASE_CONST_RETURN(PTRACE_EVENT_VFORK_DONE)
|
||||
CASE_CONST_RETURN(PTRACE_EVENT_EXIT)
|
||||
CASE_CONST_RETURN(PTRACE_EVENT_SECCOMP)
|
||||
CASE_CONST_RETURN(PTRACE_EVENT_STOP)
|
||||
default:
|
||||
return "(no event)";
|
||||
}
|
||||
}
|
||||
|
||||
inline const char* sigabbrev_np(int sig) {
|
||||
if (sig > 0 && sig < NSIG) return sys_signame[sig];
|
||||
return "(unknown)";
|
||||
}
|
||||
|
||||
std::string get_program(int pid);
|
||||
void *find_module_return_addr(std::vector<lsplt::MapInfo> &info, std::string_view suffix);
|
||||
|
||||
// pid = 0, fd != nullptr -> set to fd
|
||||
// pid != 0, fd != nullptr -> set to pid ns, give orig ns in fd
|
||||
bool switch_mnt_ns(int pid, int *fd);
|
||||
|
||||
std::vector<std::string> get_cmdline(int pid);
|
||||
|
||||
std::string parse_exec(int pid);
|
||||
|
||||
bool skip_syscall(int pid);
|
||||
bool do_syscall(int pid, uintptr_t &ret, int nr, uintptr_t arg0 = 0, uintptr_t arg1 = 0, uintptr_t arg2 = 0, uintptr_t arg3 = 0, uintptr_t arg4 = 0, uintptr_t arg5 = 0);
|
||||
|
||||
uintptr_t remote_mmap(int pid, uintptr_t addr, size_t size, int prot, int flags, int fd, off_t offset);
|
||||
bool remote_munmap(int pid, uintptr_t addr, size_t size);
|
||||
|
||||
int remote_open(int pid, uintptr_t path_addr, int flags);
|
||||
bool remote_close(int pid, int fd);
|
||||
|
||||
int wait_for_child(int pid);
|
||||
int get_elf_class(std::string_view path);
|
||||
|
||||
bool remote_pre_call(int pid, struct user_regs_struct ®s, uintptr_t func_addr, uintptr_t return_addr,
|
||||
std::vector<uintptr_t> &args);
|
||||
|
||||
uintptr_t remote_post_call(int pid, struct user_regs_struct ®s, uintptr_t return_addr);
|
||||
|
||||
|
||||
// magic.h
|
||||
constexpr const auto kMainMagicLength = 16;
|
||||
std::string generateMagic(size_t len);
|
||||
|
||||
// files.hpp
|
||||
|
||||
int setfilecon(const char* path, const char* con);
|
||||
|
||||
// utils.hpp
|
||||
class UniqueFd {
|
||||
using Fd = int;
|
||||
public:
|
||||
UniqueFd() = default;
|
||||
|
||||
UniqueFd(Fd fd) : fd_(fd) {}
|
||||
|
||||
~UniqueFd() { if (fd_ >= 0) close(fd_); }
|
||||
|
||||
// Disallow copy
|
||||
UniqueFd(const UniqueFd&) = delete;
|
||||
|
||||
UniqueFd& operator=(const UniqueFd&) = delete;
|
||||
|
||||
// Allow move
|
||||
UniqueFd(UniqueFd&& other) { std::swap(fd_, other.fd_); }
|
||||
|
||||
UniqueFd& operator=(UniqueFd&& other) {
|
||||
std::swap(fd_, other.fd_);
|
||||
return *this;
|
||||
}
|
||||
|
||||
// Implict cast to Fd
|
||||
operator const Fd&() const { return fd_; }
|
||||
|
||||
private:
|
||||
Fd fd_ = -1;
|
||||
};
|
||||
|
||||
// socket_utils.hpp
|
||||
bool set_sockcreate_con(const char* con);
|
||||
@@ -0,0 +1,30 @@
|
||||
#pragma once
|
||||
|
||||
#include <android/log.h>
|
||||
#include <cerrno>
|
||||
#include <cstring>
|
||||
#include <string>
|
||||
|
||||
#ifndef LOG_TAG
|
||||
# define LOG_TAG "TrickyStore"
|
||||
#endif
|
||||
|
||||
#ifndef NDEBUG
|
||||
#define LOGD(...) logging::log(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)
|
||||
#define LOGV(...) logging::log(ANDROID_LOG_VERBOSE, LOG_TAG, __VA_ARGS__)
|
||||
#else
|
||||
#define LOGD(...) (void)0
|
||||
#define LOGV(...) (void)0
|
||||
#endif
|
||||
#define LOGI(...) logging::log(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)
|
||||
#define LOGW(...) logging::log(ANDROID_LOG_WARN, LOG_TAG, __VA_ARGS__)
|
||||
#define LOGE(...) logging::log(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)
|
||||
#define LOGF(...) logging::log(ANDROID_LOG_FATAL, LOG_TAG, __VA_ARGS__)
|
||||
#define PLOGE(fmt, args...) LOGE(fmt " failed with %d: %s", ##args, errno, strerror(errno))
|
||||
|
||||
namespace logging {
|
||||
void setPrintEnabled(bool print);
|
||||
|
||||
[[gnu::format(printf, 3, 4)]]
|
||||
void log(int prio, const char *tag, const char *fmt, ...);
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
#include <android/log.h>
|
||||
#include <unistd.h>
|
||||
#include <cstdio>
|
||||
#include <string>
|
||||
|
||||
#include "logging.hpp"
|
||||
|
||||
namespace logging {
|
||||
static bool use_print = false;
|
||||
static char prio_str[] = {
|
||||
'V', 'D', 'I', 'W', 'E', 'F'
|
||||
};
|
||||
|
||||
void setPrintEnabled(bool print) {
|
||||
use_print = print;
|
||||
}
|
||||
|
||||
void log(int prio, const char *tag, const char *fmt, ...) {
|
||||
{
|
||||
va_list ap;
|
||||
va_start(ap, fmt);
|
||||
__android_log_vprint(prio, tag, fmt, ap);
|
||||
va_end(ap);
|
||||
}
|
||||
if (use_print) {
|
||||
char buf[BUFSIZ];
|
||||
va_list ap;
|
||||
va_start(ap, fmt);
|
||||
vsnprintf(buf, sizeof(buf), fmt, ap);
|
||||
va_end(ap);
|
||||
auto prio_char = (prio > ANDROID_LOG_DEFAULT && prio <= ANDROID_LOG_FATAL) ? prio_str[
|
||||
prio - ANDROID_LOG_VERBOSE] : '?';
|
||||
printf("[%c][%d:%d][%s]:%s\n", prio_char, getpid(), gettid(), tag, buf);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,303 @@
|
||||
#include <android/log.h>
|
||||
#include <array>
|
||||
#include <cstdlib>
|
||||
#include <fcntl.h>
|
||||
#include <jni.h>
|
||||
#include <memory>
|
||||
#include <string_view>
|
||||
#include <sys/stat.h>
|
||||
#include <tuple>
|
||||
#include <unistd.h>
|
||||
#include <utility>
|
||||
|
||||
#include "logging.hpp"
|
||||
#include "zygisk.hpp"
|
||||
|
||||
using zygisk::Api;
|
||||
using zygisk::AppSpecializeArgs;
|
||||
using zygisk::ServerSpecializeArgs;
|
||||
using namespace std::string_view_literals;
|
||||
|
||||
template <size_t N> struct FixedString {
|
||||
// NOLINTNEXTLINE(*-explicit-constructor)
|
||||
[[maybe_unused]] consteval inline FixedString(const char (&str)[N]) {
|
||||
std::copy_n(str, N, data);
|
||||
}
|
||||
consteval inline FixedString() = default;
|
||||
char data[N] = {};
|
||||
};
|
||||
|
||||
using PropValue = std::array<char, 127>;
|
||||
|
||||
template<typename T, FixedString Field, bool Version=false>
|
||||
struct Prop {
|
||||
using Type [[maybe_unused]] = T;
|
||||
bool has_value{false};
|
||||
PropValue value {};
|
||||
|
||||
[[maybe_unused]] inline consteval static const char *getField() {
|
||||
return Field.data;
|
||||
}
|
||||
[[maybe_unused]] inline consteval static bool isVersion() {
|
||||
return Version;
|
||||
}
|
||||
};
|
||||
|
||||
static_assert(sizeof(Prop<void, "", false>) % sizeof(void*) == 0);
|
||||
|
||||
using SpoofConfig = std::tuple<
|
||||
Prop<jstring, "MANUFACTURER">,
|
||||
Prop<jstring, "MODEL">,
|
||||
Prop<jstring, "FINGERPRINT">,
|
||||
Prop<jstring, "BRAND">,
|
||||
Prop<jstring, "PRODUCT">,
|
||||
Prop<jstring, "DEVICE">,
|
||||
Prop<jstring, "RELEASE", true>,
|
||||
Prop<jstring, "ID">,
|
||||
Prop<jstring, "INCREMENTAL", true>,
|
||||
Prop<jstring, "TYPE">,
|
||||
Prop<jstring, "TAGS">,
|
||||
Prop<jstring, "SECURITY_PATCH", true>,
|
||||
Prop<jstring, "BOARD">,
|
||||
Prop<jstring, "HARDWARE">,
|
||||
Prop<jint, "DEVICE_INITIAL_SDK_INT", true>
|
||||
>;
|
||||
|
||||
|
||||
ssize_t xread(int fd, void *buffer, size_t count) {
|
||||
ssize_t total = 0;
|
||||
char *buf = (char *)buffer;
|
||||
while (count > 0) {
|
||||
ssize_t ret = read(fd, buf, count);
|
||||
if (ret < 0) return -1;
|
||||
buf += ret;
|
||||
total += ret;
|
||||
count -= ret;
|
||||
}
|
||||
return total;
|
||||
}
|
||||
|
||||
ssize_t xwrite(int fd, const void *buffer, size_t count) {
|
||||
ssize_t total = 0;
|
||||
char *buf = (char *)buffer;
|
||||
while (count > 0) {
|
||||
ssize_t ret = write(fd, buf, count);
|
||||
if (ret < 0) return -1;
|
||||
buf += ret;
|
||||
total += ret;
|
||||
count -= ret;
|
||||
}
|
||||
return total;
|
||||
}
|
||||
|
||||
void trim(std::string_view &str) {
|
||||
str.remove_prefix(std::min(str.find_first_not_of(" \t"), str.size()));
|
||||
str.remove_suffix(std::min(str.size() - str.find_last_not_of(" \t") - 1, str.size()));
|
||||
}
|
||||
|
||||
class TrickyStore : public zygisk::ModuleBase {
|
||||
public:
|
||||
void onLoad(Api *api, JNIEnv *env) override {
|
||||
this->api_ = api;
|
||||
this->env_ = env;
|
||||
}
|
||||
|
||||
void preAppSpecialize(AppSpecializeArgs *args) override {
|
||||
api_->setOption(zygisk::DLCLOSE_MODULE_LIBRARY);
|
||||
if (args->app_data_dir == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto app_data_dir = env_->GetStringUTFChars(args->app_data_dir, nullptr);
|
||||
auto nice_name = env_->GetStringUTFChars(args->nice_name, nullptr);
|
||||
|
||||
std::string_view dir(app_data_dir);
|
||||
std::string_view process(nice_name);
|
||||
|
||||
bool isGms = false, isGmsUnstable = false;
|
||||
isGms = dir.ends_with("/com.google.android.gms");
|
||||
isGmsUnstable = process == "com.google.android.gms.unstable";
|
||||
|
||||
env_->ReleaseStringUTFChars(args->app_data_dir, app_data_dir);
|
||||
env_->ReleaseStringUTFChars(args->nice_name, nice_name);
|
||||
|
||||
if (!isGms) {
|
||||
return;
|
||||
}
|
||||
api_->setOption(zygisk::FORCE_DENYLIST_UNMOUNT);
|
||||
if (!isGmsUnstable) {
|
||||
return;
|
||||
}
|
||||
|
||||
int enabled = 0;
|
||||
SpoofConfig spoofConfig{};
|
||||
auto fd = api_->connectCompanion();
|
||||
if (fd >= 0) [[likely]] {
|
||||
// read enabled
|
||||
xread(fd, &enabled, sizeof(enabled));
|
||||
if (enabled) {
|
||||
xread(fd, &spoofConfig, sizeof(spoofConfig));
|
||||
}
|
||||
close(fd);
|
||||
}
|
||||
if (enabled) {
|
||||
LOGI("spoofing build vars in GMS!");
|
||||
auto buildClass = env_->FindClass("android/os/Build");
|
||||
auto buildVersionClass = env_->FindClass("android/os/Build$VERSION");
|
||||
|
||||
std::apply([this, &buildClass, &buildVersionClass](auto &&... args) {
|
||||
((!args.has_value ||
|
||||
(setField<typename std::remove_cvref_t<decltype(args)>::Type>(
|
||||
std::remove_cvref_t<decltype(args)>::isVersion() ? buildVersionClass
|
||||
: buildClass,
|
||||
std::remove_cvref_t<decltype(args)>::getField(),
|
||||
args.value) &&
|
||||
(LOGI("%s set %s to %s",
|
||||
std::remove_cvref_t<decltype(args)>::isVersion() ? "VERSION" : "Build",
|
||||
std::remove_cvref_t<decltype(args)>::getField(),
|
||||
args.value.data()), true))
|
||||
? void(0)
|
||||
: LOGE("%s failed to set %s to %s",
|
||||
std::remove_cvref_t<decltype(args)>::isVersion() ? "VERSION" : "Build",
|
||||
std::remove_cvref_t<decltype(args)>::getField(),
|
||||
args.value.data())), ...);
|
||||
}, spoofConfig);
|
||||
}
|
||||
}
|
||||
|
||||
void preServerSpecialize(ServerSpecializeArgs *args) override {
|
||||
api_->setOption(zygisk::DLCLOSE_MODULE_LIBRARY);
|
||||
}
|
||||
|
||||
private:
|
||||
Api *api_{nullptr};
|
||||
JNIEnv *env_{nullptr};
|
||||
|
||||
template<typename T>
|
||||
inline bool setField(jclass clazz, const char* field, const PropValue& value);
|
||||
|
||||
template<>
|
||||
inline bool setField<jstring>(jclass clazz, const char* field, const PropValue& value) {
|
||||
auto id = env_->GetStaticFieldID(clazz, field, "Ljava/lang/String;");
|
||||
if (!id) return false;
|
||||
env_->SetStaticObjectField(clazz, id, env_->NewStringUTF(value.data()));
|
||||
return true;
|
||||
}
|
||||
|
||||
template<>
|
||||
inline bool setField<jint>(jclass clazz, const char* field, const PropValue& value) {
|
||||
auto id = env_->GetStaticFieldID(clazz, field, "I");
|
||||
if (!id) return false;
|
||||
char *p = nullptr;
|
||||
jint x = static_cast<jint>(strtol(value.data(), &p, 10));
|
||||
if (p == value.data()) {
|
||||
return false;
|
||||
}
|
||||
env_->SetStaticIntField(clazz, id, x);
|
||||
return true;
|
||||
}
|
||||
|
||||
template<>
|
||||
inline bool setField<jboolean>(jclass clazz, const char* field, const PropValue& value) {
|
||||
auto id = env_->GetStaticFieldID(clazz, field, "Z");
|
||||
if (!id) return false;
|
||||
auto x = std::string_view(value.data());
|
||||
if (x == "1" || x == "true") {
|
||||
env_->SetStaticBooleanField(clazz, id, JNI_TRUE);
|
||||
} else if (x == "0" || x == "false") {
|
||||
env_->SetStaticBooleanField(clazz, id, JNI_FALSE);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
void read_config(FILE *config, SpoofConfig &spoof_config) {
|
||||
char *l = nullptr;
|
||||
struct finally {
|
||||
char *(&l);
|
||||
|
||||
~finally() { free(l); }
|
||||
} finally{l};
|
||||
size_t len = 0;
|
||||
ssize_t n;
|
||||
while ((n = getline(&l, &len, config)) != -1) {
|
||||
if (n <= 1) continue;
|
||||
std::string_view line{l, static_cast<size_t>(n)};
|
||||
if (line.back() == '\n') {
|
||||
line.remove_suffix(1);
|
||||
}
|
||||
auto d = line.find_first_of('=');
|
||||
if (d == std::string_view::npos) {
|
||||
LOGW("Ignore invalid line %.*s", static_cast<int>(line.size()), line.data());
|
||||
continue;
|
||||
}
|
||||
auto key = line.substr(0, d);
|
||||
trim(key);
|
||||
auto value = line.substr(d + 1);
|
||||
trim(value);
|
||||
std::apply([&key, &value](auto &&... args) {
|
||||
((key == std::remove_cvref_t<decltype(args)>::getField() &&
|
||||
(LOGD("Read config: %.*s = %.*s", static_cast<int>(key.size()), key.data(),
|
||||
static_cast<int>(value.size()), value.data()),
|
||||
args.value.size() >= value.size() + 1 ?
|
||||
(args.has_value = true,
|
||||
strlcpy(args.value.data(), value.data(),
|
||||
std::min(args.value.size(), value.size() + 1))) :
|
||||
(LOGW("Config value %.*s for %.*s is too long, ignored",
|
||||
static_cast<int>(value.size()), value.data(),
|
||||
static_cast<int>(key.size()), key.data()), true))) || ...);
|
||||
}, spoof_config);
|
||||
}
|
||||
}
|
||||
|
||||
static void companion_handler(int fd) {
|
||||
constexpr auto kSpoofConfigFile = "/data/adb/tricky_store/spoof_build_vars"sv;
|
||||
constexpr auto kDefaultSpoofConfig =
|
||||
R"EOF(MANUFACTURER=Google
|
||||
MODEL=Pixel
|
||||
FINGERPRINT=google/sailfish/sailfish:10/QPP3.190404.015/5505587:user/release-keys
|
||||
BRAND=google
|
||||
PRODUCT=sailfish
|
||||
DEVICE=sailfish
|
||||
RELEASE=10
|
||||
ID=QPP3.190404.015
|
||||
INCREMENTAL=5505587
|
||||
TYPE=user
|
||||
TAGS=release-keys
|
||||
SECURITY_PATCH=2019-05-05
|
||||
)EOF"sv;
|
||||
struct stat st{};
|
||||
int enabled = stat(kSpoofConfigFile.data(), &st) == 0;
|
||||
xwrite(fd, &enabled, sizeof(enabled));
|
||||
|
||||
if (!enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
int cfd = -1;
|
||||
if (st.st_size == 0) {
|
||||
cfd = open(kSpoofConfigFile.data(), O_RDWR);
|
||||
if (cfd > 0) {
|
||||
xwrite(cfd, kDefaultSpoofConfig.data(), kDefaultSpoofConfig.size());
|
||||
lseek(cfd, 0, SEEK_SET);
|
||||
}
|
||||
} else {
|
||||
cfd = open(kSpoofConfigFile.data(), O_RDONLY);
|
||||
}
|
||||
if (cfd < 0) {
|
||||
LOGE("[companion_handler] Failed to open spoof_build_vars");
|
||||
return;
|
||||
}
|
||||
|
||||
SpoofConfig spoof_config{};
|
||||
std::unique_ptr<FILE, decltype([](auto *f) { fclose(f); })> config{fdopen(cfd, "r")};
|
||||
read_config(config.get(), spoof_config);
|
||||
|
||||
xwrite(fd, &spoof_config, sizeof(spoof_config));
|
||||
}
|
||||
|
||||
// Register our module class and the companion handler function
|
||||
REGISTER_ZYGISK_MODULE(TrickyStore)
|
||||
REGISTER_ZYGISK_COMPANION(companion_handler)
|
||||
@@ -0,0 +1,419 @@
|
||||
/* Copyright 2022-2023 John "topjohnwu" Wu
|
||||
*
|
||||
* Permission to use, copy, modify, and/or distribute this software for any
|
||||
* purpose with or without fee is hereby granted.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||
* REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
* AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||
* INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||
* LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||
* OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
* PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
|
||||
// This is the public API for Zygisk modules.
|
||||
// DO NOT MODIFY ANY CODE IN THIS HEADER.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <jni.h>
|
||||
|
||||
#define ZYGISK_API_VERSION 4
|
||||
|
||||
/*
|
||||
|
||||
***************
|
||||
* Introduction
|
||||
***************
|
||||
|
||||
On Android, all app processes are forked from a special daemon called "Zygote".
|
||||
For each new app process, zygote will fork a new process and perform "specialization".
|
||||
This specialization operation enforces the Android security sandbox on the newly forked
|
||||
process to make sure that 3rd party application code is only loaded after it is being
|
||||
restricted within a sandbox.
|
||||
|
||||
On Android, there is also this special process called "system_server". This single
|
||||
process hosts a significant portion of system services, which controls how the
|
||||
Android operating system and apps interact with each other.
|
||||
|
||||
The Zygisk framework provides a way to allow developers to build modules and run custom
|
||||
code before and after system_server and any app processes' specialization.
|
||||
This enable developers to inject code and alter the behavior of system_server and app processes.
|
||||
|
||||
Please note that modules will only be loaded after zygote has forked the child process.
|
||||
THIS MEANS ALL OF YOUR CODE RUNS IN THE APP/SYSTEM_SERVER PROCESS, NOT THE ZYGOTE DAEMON!
|
||||
|
||||
*********************
|
||||
* Development Guide
|
||||
*********************
|
||||
|
||||
Define a class and inherit zygisk::ModuleBase to implement the functionality of your module.
|
||||
Use the macro REGISTER_ZYGISK_MODULE(className) to register that class to Zygisk.
|
||||
|
||||
Example code:
|
||||
|
||||
static jint (*orig_logger_entry_max)(JNIEnv *env);
|
||||
static jint my_logger_entry_max(JNIEnv *env) { return orig_logger_entry_max(env); }
|
||||
|
||||
class ExampleModule : public zygisk::ModuleBase {
|
||||
public:
|
||||
void onLoad(zygisk::Api *api, JNIEnv *env) override {
|
||||
this->api = api;
|
||||
this->env = env;
|
||||
}
|
||||
void preAppSpecialize(zygisk::AppSpecializeArgs *args) override {
|
||||
JNINativeMethod methods[] = {
|
||||
{ "logger_entry_max_payload_native", "()I", (void*) my_logger_entry_max },
|
||||
};
|
||||
api->hookJniNativeMethods(env, "android/util/Log", methods, 1);
|
||||
*(void **) &orig_logger_entry_max = methods[0].fnPtr;
|
||||
}
|
||||
private:
|
||||
zygisk::Api *api;
|
||||
JNIEnv *env;
|
||||
};
|
||||
|
||||
REGISTER_ZYGISK_MODULE(ExampleModule)
|
||||
|
||||
-----------------------------------------------------------------------------------------
|
||||
|
||||
Since your module class's code runs with either Zygote's privilege in pre[XXX]Specialize,
|
||||
or runs in the sandbox of the target process in post[XXX]Specialize, the code in your class
|
||||
never runs in a true superuser environment.
|
||||
|
||||
If your module require access to superuser permissions, you can create and register
|
||||
a root companion handler function. This function runs in a separate root companion
|
||||
daemon process, and an Unix domain socket is provided to allow you to perform IPC between
|
||||
your target process and the root companion process.
|
||||
|
||||
Example code:
|
||||
|
||||
static void example_handler(int socket) { ... }
|
||||
|
||||
REGISTER_ZYGISK_COMPANION(example_handler)
|
||||
|
||||
*/
|
||||
|
||||
namespace zygisk {
|
||||
|
||||
struct Api;
|
||||
struct AppSpecializeArgs;
|
||||
struct ServerSpecializeArgs;
|
||||
|
||||
class ModuleBase {
|
||||
public:
|
||||
|
||||
// This method is called as soon as the module is loaded into the target process.
|
||||
// A Zygisk API handle will be passed as an argument.
|
||||
virtual void onLoad([[maybe_unused]] Api *api, [[maybe_unused]] JNIEnv *env) {}
|
||||
|
||||
// This method is called before the app process is specialized.
|
||||
// At this point, the process just got forked from zygote, but no app specific specialization
|
||||
// is applied. This means that the process does not have any sandbox restrictions and
|
||||
// still runs with the same privilege of zygote.
|
||||
//
|
||||
// All the arguments that will be sent and used for app specialization is passed as a single
|
||||
// AppSpecializeArgs object. You can read and overwrite these arguments to change how the app
|
||||
// process will be specialized.
|
||||
//
|
||||
// If you need to run some operations as superuser, you can call Api::connectCompanion() to
|
||||
// get a socket to do IPC calls with a root companion process.
|
||||
// See Api::connectCompanion() for more info.
|
||||
virtual void preAppSpecialize([[maybe_unused]] AppSpecializeArgs *args) {}
|
||||
|
||||
// This method is called after the app process is specialized.
|
||||
// At this point, the process has all sandbox restrictions enabled for this application.
|
||||
// This means that this method runs with the same privilege of the app's own code.
|
||||
virtual void postAppSpecialize([[maybe_unused]] const AppSpecializeArgs *args) {}
|
||||
|
||||
// This method is called before the system server process is specialized.
|
||||
// See preAppSpecialize(args) for more info.
|
||||
virtual void preServerSpecialize([[maybe_unused]] ServerSpecializeArgs *args) {}
|
||||
|
||||
// This method is called after the system server process is specialized.
|
||||
// At this point, the process runs with the privilege of system_server.
|
||||
virtual void postServerSpecialize([[maybe_unused]] const ServerSpecializeArgs *args) {}
|
||||
};
|
||||
|
||||
struct AppSpecializeArgs {
|
||||
// Required arguments. These arguments are guaranteed to exist on all Android versions.
|
||||
jint &uid;
|
||||
jint &gid;
|
||||
jintArray &gids;
|
||||
jint &runtime_flags;
|
||||
jobjectArray &rlimits;
|
||||
jint &mount_external;
|
||||
jstring &se_info;
|
||||
jstring &nice_name;
|
||||
jstring &instruction_set;
|
||||
jstring &app_data_dir;
|
||||
|
||||
// Optional arguments. Please check whether the pointer is null before de-referencing
|
||||
jintArray *const fds_to_ignore;
|
||||
jboolean *const is_child_zygote;
|
||||
jboolean *const is_top_app;
|
||||
jobjectArray *const pkg_data_info_list;
|
||||
jobjectArray *const whitelisted_data_info_list;
|
||||
jboolean *const mount_data_dirs;
|
||||
jboolean *const mount_storage_dirs;
|
||||
|
||||
AppSpecializeArgs() = delete;
|
||||
};
|
||||
|
||||
struct ServerSpecializeArgs {
|
||||
jint &uid;
|
||||
jint &gid;
|
||||
jintArray &gids;
|
||||
jint &runtime_flags;
|
||||
jlong &permitted_capabilities;
|
||||
jlong &effective_capabilities;
|
||||
|
||||
ServerSpecializeArgs() = delete;
|
||||
};
|
||||
|
||||
namespace internal {
|
||||
struct api_table;
|
||||
|
||||
template<class T>
|
||||
void entry_impl(api_table *, JNIEnv *);
|
||||
}
|
||||
|
||||
// These values are used in Api::setOption(Option)
|
||||
enum Option : int {
|
||||
// Force Magisk's denylist unmount routines to run on this process.
|
||||
//
|
||||
// Setting this option only makes sense in preAppSpecialize.
|
||||
// The actual unmounting happens during app process specialization.
|
||||
//
|
||||
// Set this option to force all Magisk and modules' files to be unmounted from the
|
||||
// mount namespace of the process, regardless of the denylist enforcement status.
|
||||
FORCE_DENYLIST_UNMOUNT = 0,
|
||||
|
||||
// When this option is set, your module's library will be dlclose-ed after post[XXX]Specialize.
|
||||
// Be aware that after dlclose-ing your module, all of your code will be unmapped from memory.
|
||||
// YOU MUST NOT ENABLE THIS OPTION AFTER HOOKING ANY FUNCTIONS IN THE PROCESS.
|
||||
DLCLOSE_MODULE_LIBRARY = 1,
|
||||
};
|
||||
|
||||
// Bit masks of the return value of Api::getFlags()
|
||||
enum StateFlag : uint32_t {
|
||||
// The user has granted root access to the current process
|
||||
PROCESS_GRANTED_ROOT = (1u << 0),
|
||||
|
||||
// The current process was added on the denylist
|
||||
PROCESS_ON_DENYLIST = (1u << 1),
|
||||
};
|
||||
|
||||
// All API methods will stop working after post[XXX]Specialize as Zygisk will be unloaded
|
||||
// from the specialized process afterwards.
|
||||
struct Api {
|
||||
|
||||
// Connect to a root companion process and get a Unix domain socket for IPC.
|
||||
//
|
||||
// This API only works in the pre[XXX]Specialize methods due to SELinux restrictions.
|
||||
//
|
||||
// The pre[XXX]Specialize methods run with the same privilege of zygote.
|
||||
// If you would like to do some operations with superuser permissions, register a handler
|
||||
// function that would be called in the root process with REGISTER_ZYGISK_COMPANION(func).
|
||||
// Another good use case for a companion process is that if you want to share some resources
|
||||
// across multiple processes, hold the resources in the companion process and pass it over.
|
||||
//
|
||||
// The root companion process is ABI aware; that is, when calling this method from a 32-bit
|
||||
// process, you will be connected to a 32-bit companion process, and vice versa for 64-bit.
|
||||
//
|
||||
// Returns a file descriptor to a socket that is connected to the socket passed to your
|
||||
// module's companion request handler. Returns -1 if the connection attempt failed.
|
||||
int connectCompanion();
|
||||
|
||||
// Get the file descriptor of the root folder of the current module.
|
||||
//
|
||||
// This API only works in the pre[XXX]Specialize methods.
|
||||
// Accessing the directory returned is only possible in the pre[XXX]Specialize methods
|
||||
// or in the root companion process (assuming that you sent the fd over the socket).
|
||||
// Both restrictions are due to SELinux and UID.
|
||||
//
|
||||
// Returns -1 if errors occurred.
|
||||
int getModuleDir();
|
||||
|
||||
// Set various options for your module.
|
||||
// Please note that this method accepts one single option at a time.
|
||||
// Check zygisk::Option for the full list of options available.
|
||||
void setOption(Option opt);
|
||||
|
||||
// Get information about the current process.
|
||||
// Returns bitwise-or'd zygisk::StateFlag values.
|
||||
uint32_t getFlags();
|
||||
|
||||
// Exempt the provided file descriptor from being automatically closed.
|
||||
//
|
||||
// This API only make sense in preAppSpecialize; calling this method in any other situation
|
||||
// is either a no-op (returns true) or an error (returns false).
|
||||
//
|
||||
// When false is returned, the provided file descriptor will eventually be closed by zygote.
|
||||
bool exemptFd(int fd);
|
||||
|
||||
// Hook JNI native methods for a class
|
||||
//
|
||||
// Lookup all registered JNI native methods and replace it with your own methods.
|
||||
// The original function pointer will be saved in each JNINativeMethod's fnPtr.
|
||||
// If no matching class, method name, or signature is found, that specific JNINativeMethod.fnPtr
|
||||
// will be set to nullptr.
|
||||
void hookJniNativeMethods(JNIEnv *env, const char *className, JNINativeMethod *methods,
|
||||
int numMethods);
|
||||
|
||||
// Hook functions in the PLT (Procedure Linkage Table) of ELFs loaded in memory.
|
||||
//
|
||||
// Parsing /proc/[PID]/maps will give you the memory map of a process. As an example:
|
||||
//
|
||||
// <address> <perms> <offset> <dev> <inode> <pathname>
|
||||
// 56b4346000-56b4347000 r-xp 00002000 fe:00 235 /system/bin/app_process64
|
||||
// (More details: https://man7.org/linux/man-pages/man5/proc.5.html)
|
||||
//
|
||||
// The `dev` and `inode` pair uniquely identifies a file being mapped into memory.
|
||||
// For matching ELFs loaded in memory, replace function `symbol` with `newFunc`.
|
||||
// If `oldFunc` is not nullptr, the original function pointer will be saved to `oldFunc`.
|
||||
void
|
||||
pltHookRegister(dev_t dev, ino_t inode, const char *symbol, void *newFunc, void **oldFunc);
|
||||
|
||||
// Commit all the hooks that was previously registered.
|
||||
// Returns false if an error occurred.
|
||||
bool pltHookCommit();
|
||||
|
||||
private:
|
||||
internal::api_table *tbl;
|
||||
|
||||
template<class T>
|
||||
friend void internal::entry_impl(internal::api_table *, JNIEnv *);
|
||||
};
|
||||
|
||||
// Register a class as a Zygisk module
|
||||
|
||||
#define REGISTER_ZYGISK_MODULE(clazz) \
|
||||
void zygisk_module_entry(zygisk::internal::api_table *table, JNIEnv *env) { \
|
||||
zygisk::internal::entry_impl<clazz>(table, env); \
|
||||
}
|
||||
|
||||
// Register a root companion request handler function for your module
|
||||
//
|
||||
// The function runs in a superuser daemon process and handles a root companion request from
|
||||
// your module running in a target process. The function has to accept an integer value,
|
||||
// which is a Unix domain socket that is connected to the target process.
|
||||
// See Api::connectCompanion() for more info.
|
||||
//
|
||||
// NOTE: the function can run concurrently on multiple threads.
|
||||
// Be aware of race conditions if you have globally shared resources.
|
||||
|
||||
#define REGISTER_ZYGISK_COMPANION(func) \
|
||||
void zygisk_companion_entry(int client) { func(client); }
|
||||
|
||||
/*********************************************************
|
||||
* The following is internal ABI implementation detail.
|
||||
* You do not have to understand what it is doing.
|
||||
*********************************************************/
|
||||
|
||||
namespace internal {
|
||||
|
||||
struct module_abi {
|
||||
long api_version;
|
||||
ModuleBase *impl;
|
||||
|
||||
void (*preAppSpecialize)(ModuleBase *, AppSpecializeArgs *);
|
||||
|
||||
void (*postAppSpecialize)(ModuleBase *, const AppSpecializeArgs *);
|
||||
|
||||
void (*preServerSpecialize)(ModuleBase *, ServerSpecializeArgs *);
|
||||
|
||||
void (*postServerSpecialize)(ModuleBase *, const ServerSpecializeArgs *);
|
||||
|
||||
module_abi(ModuleBase *module) : api_version(ZYGISK_API_VERSION), impl(module) {
|
||||
preAppSpecialize = [](auto m, auto args) { m->preAppSpecialize(args); };
|
||||
postAppSpecialize = [](auto m, auto args) { m->postAppSpecialize(args); };
|
||||
preServerSpecialize = [](auto m, auto args) { m->preServerSpecialize(args); };
|
||||
postServerSpecialize = [](auto m, auto args) { m->postServerSpecialize(args); };
|
||||
}
|
||||
};
|
||||
|
||||
struct api_table {
|
||||
// Base
|
||||
void *impl;
|
||||
|
||||
bool (*registerModule)(api_table *, module_abi *);
|
||||
|
||||
void (*hookJniNativeMethods)(JNIEnv *, const char *, JNINativeMethod *, int);
|
||||
|
||||
void (*pltHookRegister)(dev_t, ino_t, const char *, void *, void **);
|
||||
|
||||
bool (*exemptFd)(int);
|
||||
|
||||
bool (*pltHookCommit)();
|
||||
|
||||
int (*connectCompanion)(void * /* impl */);
|
||||
|
||||
void (*setOption)(void * /* impl */, Option);
|
||||
|
||||
int (*getModuleDir)(void * /* impl */);
|
||||
|
||||
uint32_t (*getFlags)(void * /* impl */);
|
||||
};
|
||||
|
||||
template<class T>
|
||||
void entry_impl(api_table *table, JNIEnv *env) {
|
||||
static Api api;
|
||||
api.tbl = table;
|
||||
static T module;
|
||||
ModuleBase *m = &module;
|
||||
static module_abi abi(m);
|
||||
if (!table->registerModule(table, &abi)) return;
|
||||
m->onLoad(&api, env);
|
||||
}
|
||||
|
||||
} // namespace internal
|
||||
|
||||
inline int Api::connectCompanion() {
|
||||
return tbl->connectCompanion ? tbl->connectCompanion(tbl->impl) : -1;
|
||||
}
|
||||
|
||||
inline int Api::getModuleDir() {
|
||||
return tbl->getModuleDir ? tbl->getModuleDir(tbl->impl) : -1;
|
||||
}
|
||||
|
||||
inline void Api::setOption(Option opt) {
|
||||
if (tbl->setOption) tbl->setOption(tbl->impl, opt);
|
||||
}
|
||||
|
||||
inline uint32_t Api::getFlags() {
|
||||
return tbl->getFlags ? tbl->getFlags(tbl->impl) : 0;
|
||||
}
|
||||
|
||||
inline bool Api::exemptFd(int fd) {
|
||||
return tbl->exemptFd != nullptr && tbl->exemptFd(fd);
|
||||
}
|
||||
|
||||
inline void
|
||||
Api::hookJniNativeMethods(JNIEnv *env, const char *className, JNINativeMethod *methods,
|
||||
int numMethods) {
|
||||
if (tbl->hookJniNativeMethods)
|
||||
tbl->hookJniNativeMethods(env, className, methods, numMethods);
|
||||
}
|
||||
|
||||
inline void Api::pltHookRegister(dev_t dev, ino_t inode, const char *symbol, void *newFunc,
|
||||
void **oldFunc) {
|
||||
if (tbl->pltHookRegister) tbl->pltHookRegister(dev, inode, symbol, newFunc, oldFunc);
|
||||
}
|
||||
|
||||
inline bool Api::pltHookCommit() {
|
||||
return tbl->pltHookCommit != nullptr && tbl->pltHookCommit();
|
||||
}
|
||||
|
||||
} // namespace zygisk
|
||||
|
||||
extern "C" {
|
||||
|
||||
[[gnu::visibility("default"), maybe_unused]]
|
||||
void zygisk_module_entry(zygisk::internal::api_table *, JNIEnv *);
|
||||
|
||||
[[gnu::visibility("default"), maybe_unused]]
|
||||
void zygisk_companion_entry(int);
|
||||
|
||||
} // extern "C"
|
||||
@@ -0,0 +1,190 @@
|
||||
#!/sbin/sh
|
||||
|
||||
#################
|
||||
# Initialization
|
||||
#################
|
||||
|
||||
umask 022
|
||||
|
||||
# echo before loading util_functions
|
||||
ui_print() { echo "$1"; }
|
||||
|
||||
require_new_magisk() {
|
||||
ui_print "*******************************"
|
||||
ui_print " Please install Magisk v19.0+! "
|
||||
ui_print "*******************************"
|
||||
exit 1
|
||||
}
|
||||
|
||||
#########################
|
||||
# Load util_functions.sh
|
||||
#########################
|
||||
|
||||
OUTFD=$2
|
||||
ZIPFILE=$3
|
||||
|
||||
mount /data 2>/dev/null
|
||||
|
||||
[ -f /data/adb/magisk/util_functions.sh ] || require_new_magisk
|
||||
. /data/adb/magisk/util_functions.sh
|
||||
[ $MAGISK_VER_CODE -lt 19000 ] && require_new_magisk
|
||||
|
||||
if [ $MAGISK_VER_CODE -ge 20400 ]; then
|
||||
# New Magisk have complete installation logic within util_functions.sh
|
||||
install_module
|
||||
exit 0
|
||||
fi
|
||||
|
||||
#################
|
||||
# Legacy Support
|
||||
#################
|
||||
|
||||
TMPDIR=/dev/tmp
|
||||
PERSISTDIR=/sbin/.magisk/mirror/persist
|
||||
|
||||
is_legacy_script() {
|
||||
unzip -l "$ZIPFILE" install.sh | grep -q install.sh
|
||||
return $?
|
||||
}
|
||||
|
||||
print_modname() {
|
||||
local len
|
||||
len=`echo -n $MODNAME | wc -c`
|
||||
len=$((len + 2))
|
||||
local pounds=`printf "%${len}s" | tr ' ' '*'`
|
||||
ui_print "$pounds"
|
||||
ui_print " $MODNAME "
|
||||
ui_print "$pounds"
|
||||
ui_print "*******************"
|
||||
ui_print " Powered by Magisk "
|
||||
ui_print "*******************"
|
||||
}
|
||||
|
||||
# Override abort as old scripts have some issues
|
||||
abort() {
|
||||
ui_print "$1"
|
||||
$BOOTMODE || recovery_cleanup
|
||||
[ -n $MODPATH ] && rm -rf $MODPATH
|
||||
rm -rf $TMPDIR
|
||||
exit 1
|
||||
}
|
||||
|
||||
rm -rf $TMPDIR 2>/dev/null
|
||||
mkdir -p $TMPDIR
|
||||
|
||||
# Preperation for flashable zips
|
||||
setup_flashable
|
||||
|
||||
# Mount partitions
|
||||
mount_partitions
|
||||
|
||||
# Detect version and architecture
|
||||
api_level_arch_detect
|
||||
|
||||
# Setup busybox and binaries
|
||||
$BOOTMODE && boot_actions || recovery_actions
|
||||
|
||||
##############
|
||||
# Preparation
|
||||
##############
|
||||
|
||||
# Extract prop file
|
||||
unzip -o "$ZIPFILE" module.prop -d $TMPDIR >&2
|
||||
[ ! -f $TMPDIR/module.prop ] && abort "! Unable to extract zip file!"
|
||||
|
||||
$BOOTMODE && MODDIRNAME=modules_update || MODDIRNAME=modules
|
||||
MODULEROOT=$NVBASE/$MODDIRNAME
|
||||
MODID=`grep_prop id $TMPDIR/module.prop`
|
||||
MODPATH=$MODULEROOT/$MODID
|
||||
MODNAME=`grep_prop name $TMPDIR/module.prop`
|
||||
|
||||
# Create mod paths
|
||||
rm -rf $MODPATH 2>/dev/null
|
||||
mkdir -p $MODPATH
|
||||
|
||||
##########
|
||||
# Install
|
||||
##########
|
||||
|
||||
if is_legacy_script; then
|
||||
unzip -oj "$ZIPFILE" module.prop install.sh uninstall.sh 'common/*' -d $TMPDIR >&2
|
||||
|
||||
# Load install script
|
||||
. $TMPDIR/install.sh
|
||||
|
||||
# Callbacks
|
||||
print_modname
|
||||
on_install
|
||||
|
||||
# Custom uninstaller
|
||||
[ -f $TMPDIR/uninstall.sh ] && cp -af $TMPDIR/uninstall.sh $MODPATH/uninstall.sh
|
||||
|
||||
# Skip mount
|
||||
$SKIPMOUNT && touch $MODPATH/skip_mount
|
||||
|
||||
# prop file
|
||||
$PROPFILE && cp -af $TMPDIR/system.prop $MODPATH/system.prop
|
||||
|
||||
# Module info
|
||||
cp -af $TMPDIR/module.prop $MODPATH/module.prop
|
||||
|
||||
# post-fs-data scripts
|
||||
$POSTFSDATA && cp -af $TMPDIR/post-fs-data.sh $MODPATH/post-fs-data.sh
|
||||
|
||||
# service scripts
|
||||
$LATESTARTSERVICE && cp -af $TMPDIR/service.sh $MODPATH/service.sh
|
||||
|
||||
ui_print "- Setting permissions"
|
||||
set_permissions
|
||||
else
|
||||
print_modname
|
||||
|
||||
unzip -o "$ZIPFILE" customize.sh -d $MODPATH >&2
|
||||
|
||||
if ! grep -q '^SKIPUNZIP=1$' $MODPATH/customize.sh 2>/dev/null; then
|
||||
ui_print "- Extracting module files"
|
||||
unzip -o "$ZIPFILE" -x 'META-INF/*' -d $MODPATH >&2
|
||||
|
||||
# Default permissions
|
||||
set_perm_recursive $MODPATH 0 0 0755 0644
|
||||
fi
|
||||
|
||||
# Load customization script
|
||||
[ -f $MODPATH/customize.sh ] && . $MODPATH/customize.sh
|
||||
fi
|
||||
|
||||
# Handle replace folders
|
||||
for TARGET in $REPLACE; do
|
||||
ui_print "- Replace target: $TARGET"
|
||||
mktouch $MODPATH$TARGET/.replace
|
||||
done
|
||||
|
||||
if $BOOTMODE; then
|
||||
# Update info for Magisk Manager
|
||||
mktouch $NVBASE/modules/$MODID/update
|
||||
cp -af $MODPATH/module.prop $NVBASE/modules/$MODID/module.prop
|
||||
fi
|
||||
|
||||
# Copy over custom sepolicy rules
|
||||
if [ -f $MODPATH/sepolicy.rule -a -e $PERSISTDIR ]; then
|
||||
ui_print "- Installing custom sepolicy patch"
|
||||
PERSISTMOD=$PERSISTDIR/magisk/$MODID
|
||||
mkdir -p $PERSISTMOD
|
||||
cp -af $MODPATH/sepolicy.rule $PERSISTMOD/sepolicy.rule
|
||||
fi
|
||||
|
||||
# Remove stuffs that don't belong to modules
|
||||
rm -rf \
|
||||
$MODPATH/system/placeholder $MODPATH/customize.sh \
|
||||
$MODPATH/README.md $MODPATH/.git* 2>/dev/null
|
||||
|
||||
#############
|
||||
# Finalizing
|
||||
#############
|
||||
|
||||
cd /
|
||||
$BOOTMODE || recovery_cleanup
|
||||
rm -rf $TMPDIR
|
||||
|
||||
ui_print "- Done"
|
||||
exit 0
|
||||
@@ -0,0 +1 @@
|
||||
#MAGISK
|
||||
@@ -0,0 +1,107 @@
|
||||
# shellcheck disable=SC2034
|
||||
SKIPUNZIP=1
|
||||
|
||||
DEBUG=@DEBUG@
|
||||
SONAME=@SONAME@
|
||||
SUPPORTED_ABIS="@SUPPORTED_ABIS@"
|
||||
MIN_SDK=@MIN_SDK@
|
||||
|
||||
if [ "$BOOTMODE" ] && [ "$KSU" ]; then
|
||||
ui_print "- Installing from KernelSU app"
|
||||
ui_print "- KernelSU version: $KSU_KERNEL_VER_CODE (kernel) + $KSU_VER_CODE (ksud)"
|
||||
if [ "$(which magisk)" ]; then
|
||||
ui_print "*********************************************************"
|
||||
ui_print "! Multiple root implementation is NOT supported!"
|
||||
ui_print "! Please uninstall Magisk before installing Zygisk Next"
|
||||
abort "*********************************************************"
|
||||
fi
|
||||
elif [ "$BOOTMODE" ] && [ "$MAGISK_VER_CODE" ]; then
|
||||
ui_print "- Installing from Magisk app"
|
||||
else
|
||||
ui_print "*********************************************************"
|
||||
ui_print "! Install from recovery is not supported"
|
||||
ui_print "! Please install from KernelSU or Magisk app"
|
||||
abort "*********************************************************"
|
||||
fi
|
||||
|
||||
VERSION=$(grep_prop version "${TMPDIR}/module.prop")
|
||||
ui_print "- Installing $SONAME $VERSION"
|
||||
|
||||
# check architecture
|
||||
support=false
|
||||
for abi in $SUPPORTED_ABIS
|
||||
do
|
||||
if [ "$ARCH" == "$abi" ]; then
|
||||
support=true
|
||||
fi
|
||||
done
|
||||
if [ "$support" == "false" ]; then
|
||||
abort "! Unsupported platform: $ARCH"
|
||||
else
|
||||
ui_print "- Device platform: $ARCH"
|
||||
fi
|
||||
|
||||
# check android
|
||||
if [ "$API" -lt $MIN_SDK ]; then
|
||||
ui_print "! Unsupported sdk: $API"
|
||||
abort "! Minimal supported sdk is $MIN_SDK"
|
||||
else
|
||||
ui_print "- Device sdk: $API"
|
||||
fi
|
||||
|
||||
ui_print "- Extracting verify.sh"
|
||||
unzip -o "$ZIPFILE" 'verify.sh' -d "$TMPDIR" >&2
|
||||
if [ ! -f "$TMPDIR/verify.sh" ]; then
|
||||
ui_print "*********************************************************"
|
||||
ui_print "! Unable to extract verify.sh!"
|
||||
ui_print "! This zip may be corrupted, please try downloading again"
|
||||
abort "*********************************************************"
|
||||
fi
|
||||
. "$TMPDIR/verify.sh"
|
||||
extract "$ZIPFILE" 'customize.sh' "$TMPDIR/.vunzip"
|
||||
extract "$ZIPFILE" 'verify.sh' "$TMPDIR/.vunzip"
|
||||
|
||||
ui_print "- Extracting module files"
|
||||
extract "$ZIPFILE" 'module.prop' "$MODPATH"
|
||||
extract "$ZIPFILE" 'post-fs-data.sh' "$MODPATH"
|
||||
extract "$ZIPFILE" 'service.sh' "$MODPATH"
|
||||
extract "$ZIPFILE" 'service.apk' "$MODPATH"
|
||||
extract "$ZIPFILE" 'sepolicy.rule' "$MODPATH"
|
||||
extract "$ZIPFILE" 'daemon' "$MODPATH"
|
||||
chmod 755 "$MODPATH/daemon"
|
||||
|
||||
mkdir "$MODPATH/zygisk"
|
||||
|
||||
if [ "$ARCH" = "x64" ]; then
|
||||
ui_print "- Extracting x64 libraries"
|
||||
extract "$ZIPFILE" "lib/x86_64/lib$SONAME.so" "$MODPATH" true
|
||||
extract "$ZIPFILE" "lib/x86_64/libinject.so" "$MODPATH" true
|
||||
extract "$ZIPFILE" "lib/x86_64/libtszygisk.so" "$MODPATH/zygisk" true
|
||||
mv "$MODPATH/zygisk/libtszygisk.so" "$MODPATH/zygisk/x86_64.so"
|
||||
else
|
||||
ui_print "- Extracting arm64 libraries"
|
||||
extract "$ZIPFILE" "lib/arm64-v8a/lib$SONAME.so" "$MODPATH" true
|
||||
extract "$ZIPFILE" "lib/arm64-v8a/libinject.so" "$MODPATH" true
|
||||
extract "$ZIPFILE" "lib/arm64-v8a/libtszygisk.so" "$MODPATH/zygisk" true
|
||||
mv "$MODPATH/zygisk/libtszygisk.so" "$MODPATH/zygisk/arm64-v8a.so"
|
||||
fi
|
||||
|
||||
mv "$MODPATH/libinject.so" "$MODPATH/inject"
|
||||
chmod 755 "$MODPATH/inject"
|
||||
|
||||
CONFIG_DIR=/data/adb/tricky_store
|
||||
if [ ! -d "$CONFIG_DIR" ]; then
|
||||
ui_print "- Creating configuration directory"
|
||||
mkdir -p "$CONFIG_DIR"
|
||||
[ ! -f "$CONFIG_DIR/spoof_build_vars" ] && touch "$CONFIG_DIR/spoof_build_vars"
|
||||
fi
|
||||
if [ ! -f "$CONFIG_DIR/keybox.xml" ]; then
|
||||
ui_print "- Adding default software keybox"
|
||||
extract "$ZIPFILE" 'keybox.xml' "$TMPDIR"
|
||||
mv "$TMPDIR/keybox.xml" "$CONFIG_DIR/keybox.xml"
|
||||
fi
|
||||
if [ ! -f "$CONFIG_DIR/target.txt" ]; then
|
||||
ui_print "- Adding default target scope"
|
||||
extract "$ZIPFILE" 'target.txt' "$TMPDIR"
|
||||
mv "$TMPDIR/target.txt" "$CONFIG_DIR/target.txt"
|
||||
fi
|
||||
@@ -0,0 +1,3 @@
|
||||
#!/system/bin/sh
|
||||
|
||||
exec /system/bin/app_process -Djava.class.path=./service.apk / --nice-name=TrickyStore io.github.a13e300.tricky_store.MainKt
|
||||
@@ -0,0 +1,114 @@
|
||||
<?xml version="1.0"?>
|
||||
<AndroidAttestation>
|
||||
<NumberOfKeyboxes>1</NumberOfKeyboxes>
|
||||
<Keybox DeviceID="sw">
|
||||
<Key algorithm="ecdsa">
|
||||
<PrivateKey format="pem">
|
||||
-----BEGIN EC PRIVATE KEY-----
|
||||
MHcCAQEEICHghkMqFRmEWc82OlD8FMnarfk19SfC39ceTW28QuVEoAoGCCqGSM49
|
||||
AwEHoUQDQgAE6555+EJjWazLKpFMiYbMcK2QZpOCqXMmE/6sy/ghJ0whdJdKKv6l
|
||||
uU1/ZtTgZRBmNbxTt6CjpnFYPts+Ea4QFA==
|
||||
-----END EC PRIVATE KEY-----
|
||||
</PrivateKey>
|
||||
<CertificateChain>
|
||||
<NumberOfCertificates>2</NumberOfCertificates>
|
||||
<Certificate format="pem">
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIICeDCCAh6gAwIBAgICEAEwCgYIKoZIzj0EAwIwgZgxCzAJBgNVBAYTAlVTMRMw
|
||||
EQYDVQQIDApDYWxpZm9ybmlhMRYwFAYDVQQHDA1Nb3VudGFpbiBWaWV3MRUwEwYD
|
||||
VQQKDAxHb29nbGUsIEluYy4xEDAOBgNVBAsMB0FuZHJvaWQxMzAxBgNVBAMMKkFu
|
||||
ZHJvaWQgS2V5c3RvcmUgU29mdHdhcmUgQXR0ZXN0YXRpb24gUm9vdDAeFw0xNjAx
|
||||
MTEwMDQ2MDlaFw0yNjAxMDgwMDQ2MDlaMIGIMQswCQYDVQQGEwJVUzETMBEGA1UE
|
||||
CAwKQ2FsaWZvcm5pYTEVMBMGA1UECgwMR29vZ2xlLCBJbmMuMRAwDgYDVQQLDAdB
|
||||
bmRyb2lkMTswOQYDVQQDDDJBbmRyb2lkIEtleXN0b3JlIFNvZnR3YXJlIEF0dGVz
|
||||
dGF0aW9uIEludGVybWVkaWF0ZTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABOue
|
||||
efhCY1msyyqRTImGzHCtkGaTgqlzJhP+rMv4ISdMIXSXSir+pblNf2bU4GUQZjW8
|
||||
U7ego6ZxWD7bPhGuEBSjZjBkMB0GA1UdDgQWBBQ//KzWGrE6noEguNUlHMVlux6R
|
||||
qTAfBgNVHSMEGDAWgBTIrel3TEXDo88NFhDkeUM6IVowzzASBgNVHRMBAf8ECDAG
|
||||
AQH/AgEAMA4GA1UdDwEB/wQEAwIChDAKBggqhkjOPQQDAgNIADBFAiBLipt77oK8
|
||||
wDOHri/AiZi03cONqycqRZ9pDMfDktQPjgIhAO7aAV229DLp1IQ7YkyUBO86fMy9
|
||||
Xvsiu+f+uXc/WT/7
|
||||
-----END CERTIFICATE-----
|
||||
</Certificate>
|
||||
<Certificate format="pem">
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIICizCCAjKgAwIBAgIJAKIFntEOQ1tXMAoGCCqGSM49BAMCMIGYMQswCQYDVQQG
|
||||
EwJVUzETMBEGA1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNTW91bnRhaW4gVmll
|
||||
dzEVMBMGA1UECgwMR29vZ2xlLCBJbmMuMRAwDgYDVQQLDAdBbmRyb2lkMTMwMQYD
|
||||
VQQDDCpBbmRyb2lkIEtleXN0b3JlIFNvZnR3YXJlIEF0dGVzdGF0aW9uIFJvb3Qw
|
||||
HhcNMTYwMTExMDA0MzUwWhcNMzYwMTA2MDA0MzUwWjCBmDELMAkGA1UEBhMCVVMx
|
||||
EzARBgNVBAgMCkNhbGlmb3JuaWExFjAUBgNVBAcMDU1vdW50YWluIFZpZXcxFTAT
|
||||
BgNVBAoMDEdvb2dsZSwgSW5jLjEQMA4GA1UECwwHQW5kcm9pZDEzMDEGA1UEAwwq
|
||||
QW5kcm9pZCBLZXlzdG9yZSBTb2Z0d2FyZSBBdHRlc3RhdGlvbiBSb290MFkwEwYH
|
||||
KoZIzj0CAQYIKoZIzj0DAQcDQgAE7l1ex+HA220Dpn7mthvsTWpdamguD/9/SQ59
|
||||
dx9EIm29sa/6FsvHrcV30lacqrewLVQBXT5DKyqO107sSHVBpKNjMGEwHQYDVR0O
|
||||
BBYEFMit6XdMRcOjzw0WEOR5QzohWjDPMB8GA1UdIwQYMBaAFMit6XdMRcOjzw0W
|
||||
EOR5QzohWjDPMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgKEMAoGCCqG
|
||||
SM49BAMCA0cAMEQCIDUho++LNEYenNVg8x1YiSBq3KNlQfYNns6KGYxmSGB7AiBN
|
||||
C/NR2TB8fVvaNTQdqEcbY6WFZTytTySn502vQX3xvw==
|
||||
-----END CERTIFICATE-----
|
||||
</Certificate>
|
||||
</CertificateChain>
|
||||
</Key>
|
||||
<Key algorithm="rsa">
|
||||
<PrivateKey format="pem">
|
||||
-----BEGIN RSA PRIVATE KEY-----
|
||||
MIICXQIBAAKBgQDAgyPcVogbuDAgafWwhWHG7r5/BeL1qEIEir6LR752/q7yXPKb
|
||||
KvoyABQWAUKZiaFfz8aBXrNjWDwv0vIL5Jgyg92BSxbX4YVBeuVKvClqOm21wAQI
|
||||
O2jFVsHwIzmRZBmGTVC3TUCuykhMdzVsiVoMJ1q/rEmdXX0jYvKcXgLocQIDAQAB
|
||||
AoGBAL6GCwuZqAKm+xpZQ4p7txUGWwmjbcbpysxr88AsNNfXnpTGYGQo2Ix7f2V3
|
||||
wc3qZAdKvo5yht8fCBHclygmCGjeldMu/Ja20IT/JxpfYN78xwPno45uKbqaPF/C
|
||||
woB2tqiWrx0014gozpvdsfNPnJQEQweBKY4gExZyW728mTpBAkEA4cbZJ2RsCRbs
|
||||
NoJtWUmDdAwh8bB0xKGlmGfGaXlchdPcRkxbkp6Uv7NODcxQFLEPEzQat/3V9gQU
|
||||
0qMmytQcxQJBANpIWZd4XNVjD7D9jFJU+Y5TjhiYOq6ea35qWntdNDdVuSGOvUAy
|
||||
DSg4fXifdvohi8wti2il9kGPu+ylF5qzr70CQFD+/DJklVlhbtZTThVFCTKdk6PY
|
||||
ENvlvbmCKSz3i9i624Agro1X9LcdBThv/p6dsnHKNHejSZnbdvjl7OnA1J0CQBW3
|
||||
TPJ8zv+Ls2vwTZ2DRrCaL3DS9EObDyasfgP36dH3fUuRX9KbKCPwOstdUgDghX/y
|
||||
qAPpPu6W1iNc6VRCvCECQQCQp0XaiXCyzWSWYDJCKMX4KFb/1mW6moXI1g8bi+5x
|
||||
fs0scurgHa2GunZU1M9FrbXx8rMdn4Eiz6XxpVcPmy0l
|
||||
-----END RSA PRIVATE KEY-----
|
||||
</PrivateKey>
|
||||
<CertificateChain>
|
||||
<NumberOfCertificates>2</NumberOfCertificates>
|
||||
<Certificate format="pem">
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIICtjCCAh+gAwIBAgICEAAwDQYJKoZIhvcNAQELBQAwYzELMAkGA1UEBhMCVVMx
|
||||
EzARBgNVBAgMCkNhbGlmb3JuaWExFjAUBgNVBAcMDU1vdW50YWluIFZpZXcxFTAT
|
||||
BgNVBAoMDEdvb2dsZSwgSW5jLjEQMA4GA1UECwwHQW5kcm9pZDAeFw0xNjAxMDQx
|
||||
MjQwNTNaFw0zNTEyMzAxMjQwNTNaMHYxCzAJBgNVBAYTAlVTMRMwEQYDVQQIDApD
|
||||
YWxpZm9ybmlhMRUwEwYDVQQKDAxHb29nbGUsIEluYy4xEDAOBgNVBAsMB0FuZHJv
|
||||
aWQxKTAnBgNVBAMMIEFuZHJvaWQgU29mdHdhcmUgQXR0ZXN0YXRpb24gS2V5MIGf
|
||||
MA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDAgyPcVogbuDAgafWwhWHG7r5/BeL1
|
||||
qEIEir6LR752/q7yXPKbKvoyABQWAUKZiaFfz8aBXrNjWDwv0vIL5Jgyg92BSxbX
|
||||
4YVBeuVKvClqOm21wAQIO2jFVsHwIzmRZBmGTVC3TUCuykhMdzVsiVoMJ1q/rEmd
|
||||
XX0jYvKcXgLocQIDAQABo2YwZDAdBgNVHQ4EFgQU1AwQG/jNY7n3OVK1DhNcpteZ
|
||||
k4YwHwYDVR0jBBgwFoAUKfrxrMxN0kyWQCd1trDpMuUH/i4wEgYDVR0TAQH/BAgw
|
||||
BgEB/wIBADAOBgNVHQ8BAf8EBAMCAoQwDQYJKoZIhvcNAQELBQADgYEAni1IX4xn
|
||||
M9waha2Z11Aj6hTsQ7DhnerCI0YecrUZ3GAi5KVoMWwLVcTmnKItnzpPk2sxixZ4
|
||||
Fg2Iy9mLzICdhPDCJ+NrOPH90ecXcjFZNX2W88V/q52PlmEmT7K+gbsNSQQiis6f
|
||||
9/VCLiVE+iEHElqDtVWtGIL4QBSbnCBjBH8=
|
||||
-----END CERTIFICATE-----
|
||||
</Certificate>
|
||||
<Certificate format="pem">
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIICpzCCAhCgAwIBAgIJAP+U2d2fB8gMMA0GCSqGSIb3DQEBCwUAMGMxCzAJBgNV
|
||||
BAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlhMRYwFAYDVQQHDA1Nb3VudGFpbiBW
|
||||
aWV3MRUwEwYDVQQKDAxHb29nbGUsIEluYy4xEDAOBgNVBAsMB0FuZHJvaWQwHhcN
|
||||
MTYwMTA0MTIzMTA4WhcNMzUxMjMwMTIzMTA4WjBjMQswCQYDVQQGEwJVUzETMBEG
|
||||
A1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNTW91bnRhaW4gVmlldzEVMBMGA1UE
|
||||
CgwMR29vZ2xlLCBJbmMuMRAwDgYDVQQLDAdBbmRyb2lkMIGfMA0GCSqGSIb3DQEB
|
||||
AQUAA4GNADCBiQKBgQCia63rbi5EYe/VDoLmt5TRdSMfd5tjkWP/96r/C3JHTsAs
|
||||
Q+wzfNes7UA+jCigZtX3hwszl94OuE4TQKuvpSe/lWmgMdsGUmX4RFlXYfC78hdL
|
||||
t0GAZMAoDo9Sd47b0ke2RekZyOmLw9vCkT/X11DEHTVm+Vfkl5YLCazOkjWFmwID
|
||||
AQABo2MwYTAdBgNVHQ4EFgQUKfrxrMxN0kyWQCd1trDpMuUH/i4wHwYDVR0jBBgw
|
||||
FoAUKfrxrMxN0kyWQCd1trDpMuUH/i4wDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8B
|
||||
Af8EBAMCAoQwDQYJKoZIhvcNAQELBQADgYEAT3LzNlmNDsG5dFsxWfbwjSVJMJ6j
|
||||
HBwp0kUtILlNX2S06IDHeHqcOd6os/W/L3BfRxBcxebrTQaZYdKumgf/93y4q+uc
|
||||
DyQHXrF/unlx/U1bnt8Uqf7f7XzAiF343ZtkMlbVNZriE/mPzsF83O+kqrJVw4Op
|
||||
Lvtc9mL1J1IXvmM=
|
||||
-----END CERTIFICATE-----
|
||||
</Certificate>
|
||||
</CertificateChain>
|
||||
</Key>
|
||||
</Keybox>
|
||||
</AndroidAttestation>
|
||||
@@ -0,0 +1,7 @@
|
||||
id=${moduleId}
|
||||
name=${moduleName}
|
||||
version=${versionName}
|
||||
versionCode=${versionCode}
|
||||
author=${author}
|
||||
description=${description}
|
||||
#updateJson=
|
||||
@@ -0,0 +1 @@
|
||||
MODDIR=${0%/*}
|
||||
@@ -0,0 +1,4 @@
|
||||
allow keystore system_file unix_dgram_socket *
|
||||
allow system_file keystore unix_dgram_socket *
|
||||
allow keystore system_file file *
|
||||
allow crash_dump keystore process *
|
||||
@@ -0,0 +1,14 @@
|
||||
DEBUG=@DEBUG@
|
||||
|
||||
MODDIR=${0%/*}
|
||||
|
||||
cd $MODDIR
|
||||
|
||||
(
|
||||
while [ true ]; do
|
||||
./daemon
|
||||
if [ $? -ne 0 ]; then
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
) &
|
||||
@@ -0,0 +1,4 @@
|
||||
com.google.android.gms
|
||||
io.github.vvb2060.keyattestation
|
||||
io.github.vvb2060.mahoshojo
|
||||
icu.nullptr.nativetest
|
||||
@@ -0,0 +1,51 @@
|
||||
TMPDIR_FOR_VERIFY="$TMPDIR/.vunzip"
|
||||
mkdir "$TMPDIR_FOR_VERIFY"
|
||||
|
||||
abort_verify() {
|
||||
ui_print "*********************************************************"
|
||||
ui_print "! $1"
|
||||
ui_print "! This zip may be corrupted, please try downloading again"
|
||||
abort "*********************************************************"
|
||||
}
|
||||
|
||||
# extract <zip> <file> <target dir> <junk paths>
|
||||
extract() {
|
||||
zip=$1
|
||||
file=$2
|
||||
dir=$3
|
||||
junk_paths=$4
|
||||
[ -z "$junk_paths" ] && junk_paths=false
|
||||
opts="-o"
|
||||
[ $junk_paths = true ] && opts="-oj"
|
||||
|
||||
file_path=""
|
||||
hash_path=""
|
||||
if [ $junk_paths = true ]; then
|
||||
file_path="$dir/$(basename "$file")"
|
||||
hash_path="$TMPDIR_FOR_VERIFY/$(basename "$file").sha256"
|
||||
else
|
||||
file_path="$dir/$file"
|
||||
hash_path="$TMPDIR_FOR_VERIFY/$file.sha256"
|
||||
fi
|
||||
|
||||
unzip $opts "$zip" "$file" -d "$dir" >&2
|
||||
[ -f "$file_path" ] || abort_verify "$file not exists"
|
||||
|
||||
unzip $opts "$zip" "$file.sha256" -d "$TMPDIR_FOR_VERIFY" >&2
|
||||
[ -f "$hash_path" ] || abort_verify "$file.sha256 not exists"
|
||||
|
||||
(echo "$(cat "$hash_path") $file_path" | sha256sum -c -s -) || abort_verify "Failed to verify $file"
|
||||
ui_print "- Verified $file" >&1
|
||||
}
|
||||
|
||||
file="META-INF/com/google/android/update-binary"
|
||||
file_path="$TMPDIR_FOR_VERIFY/$file"
|
||||
hash_path="$file_path.sha256"
|
||||
unzip -o "$ZIPFILE" "META-INF/com/google/android/*" -d "$TMPDIR_FOR_VERIFY" >&2
|
||||
[ -f "$file_path" ] || abort_verify "$file not exists"
|
||||
if [ -f "$hash_path" ]; then
|
||||
(echo "$(cat "$hash_path") $file_path" | sha256sum -c -s -) || abort_verify "Failed to verify $file"
|
||||
ui_print "- Verified $file" >&1
|
||||
else
|
||||
ui_print "- Download from Magisk app"
|
||||
fi
|
||||
@@ -0,0 +1 @@
|
||||
/build
|
||||
@@ -0,0 +1,124 @@
|
||||
import android.databinding.tool.ext.capitalizeUS
|
||||
import org.jetbrains.kotlin.daemon.common.toHexString
|
||||
import java.security.MessageDigest
|
||||
|
||||
plugins {
|
||||
alias(libs.plugins.jetbrains.kotlin.android)
|
||||
alias(libs.plugins.agp.app)
|
||||
}
|
||||
|
||||
val moduleId: String by rootProject.extra
|
||||
val moduleName: String by rootProject.extra
|
||||
val verCode: Int by rootProject.extra
|
||||
val verName: String by rootProject.extra
|
||||
val commitHash: String by rootProject.extra
|
||||
val author: String by rootProject.extra
|
||||
val description: String by rootProject.extra
|
||||
val moduleDescription = description
|
||||
|
||||
fun calculateChecksum(variantLowered: String): String {
|
||||
return MessageDigest.getInstance("SHA-256").run {
|
||||
update(moduleId.toByteArray(Charsets.UTF_8))
|
||||
update(moduleName.toByteArray(Charsets.UTF_8))
|
||||
update("$verName ($verCode-$commitHash-$variantLowered)".toByteArray(Charsets.UTF_8))
|
||||
update(verCode.toString().toByteArray(Charsets.UTF_8))
|
||||
update(author.toByteArray(Charsets.UTF_8))
|
||||
update(description.toByteArray(Charsets.UTF_8))
|
||||
digest().toHexString()
|
||||
}
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = "io.github.a13e300.tricky_store"
|
||||
compileSdk = 34
|
||||
|
||||
defaultConfig {
|
||||
applicationId = "io.github.a13e300.tricky_store"
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
release {
|
||||
isMinifyEnabled = true
|
||||
isShrinkResources = true
|
||||
proguardFiles(
|
||||
getDefaultProguardFile("proguard-android-optimize.txt"),
|
||||
"proguard-rules.pro"
|
||||
)
|
||||
}
|
||||
forEach {
|
||||
val checksum = calculateChecksum(it.name)
|
||||
it.buildConfigField("String", "CHECKSUM", "\"$checksum\"")
|
||||
}
|
||||
}
|
||||
|
||||
kotlinOptions {
|
||||
jvmTarget = "17"
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
release {
|
||||
signingConfig = signingConfigs["debug"]
|
||||
}
|
||||
}
|
||||
|
||||
packaging {
|
||||
resources {
|
||||
excludes += "**"
|
||||
}
|
||||
}
|
||||
|
||||
lint {
|
||||
checkReleaseBuilds = false
|
||||
abortOnError = true
|
||||
}
|
||||
|
||||
buildFeatures {
|
||||
buildConfig = true
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compileOnly(project(":stub"))
|
||||
compileOnly(libs.annotation)
|
||||
implementation(libs.bcpkix.jdk18on)
|
||||
}
|
||||
|
||||
afterEvaluate {
|
||||
android.applicationVariants.forEach { variant ->
|
||||
val variantLowered = variant.name.lowercase()
|
||||
val variantCapped = variant.name.capitalizeUS()
|
||||
val pushTask = task<Task>("pushService$variantCapped") {
|
||||
group = "Service"
|
||||
dependsOn("assemble$variantCapped")
|
||||
doLast {
|
||||
exec {
|
||||
commandLine(
|
||||
"adb",
|
||||
"push",
|
||||
layout.buildDirectory.file("outputs/apk/$variantLowered/service-$variantLowered.apk")
|
||||
.get().asFile.absolutePath,
|
||||
"/data/local/tmp/service.apk"
|
||||
)
|
||||
}
|
||||
exec {
|
||||
commandLine(
|
||||
"adb",
|
||||
"shell",
|
||||
"su -c 'rm /data/adb/modules/tricky_store/service.apk; mv /data/local/tmp/service.apk /data/adb/modules/tricky_store/'"
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
task<Task>("pushAndRestartService$variantCapped") {
|
||||
group = "Service"
|
||||
dependsOn(pushTask)
|
||||
doLast {
|
||||
exec {
|
||||
commandLine("adb", "shell", "su -c \"setprop ctl.restart keystore2\"")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Vendored
+38
@@ -0,0 +1,38 @@
|
||||
# Add project specific ProGuard rules here.
|
||||
# You can control the set of applied configuration files using the
|
||||
# proguardFiles setting in build.gradle.
|
||||
#
|
||||
# For more details, see
|
||||
# http://developer.android.com/guide/developing/tools/proguard.html
|
||||
|
||||
# If your project uses WebView with JS, uncomment the following
|
||||
# and specify the fully qualified class name to the JavaScript interface
|
||||
# class:
|
||||
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
|
||||
# public *;
|
||||
#}
|
||||
|
||||
# Uncomment this to preserve the line number information for
|
||||
# debugging stack traces.
|
||||
#-keepattributes SourceFile,LineNumberTable
|
||||
|
||||
# If you keep the line number information, uncomment this to
|
||||
# hide the original source file name.
|
||||
#-renamesourcefileattribute SourceFile
|
||||
-keepclasseswithmembers class io.github.a13e300.tricky_store.MainKt {
|
||||
public static void main(java.lang.String[]);
|
||||
}
|
||||
|
||||
-assumenosideeffects class io.github.a13e300.tricky_store.Logger {
|
||||
public static void d(java.lang.String);
|
||||
}
|
||||
|
||||
# keep these or bouncycastle will not work
|
||||
-keep class org.bouncycastle.jcajce.provider.** { *; }
|
||||
-keep class org.bouncycastle.jce.provider.** { *; }
|
||||
-dontwarn javax.naming.**
|
||||
|
||||
-repackageclasses
|
||||
-allowaccessmodification
|
||||
-overloadaggressively
|
||||
-renamesourcefileattribute
|
||||
@@ -0,0 +1,2 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest />
|
||||
@@ -0,0 +1,91 @@
|
||||
package io.github.a13e300.tricky_store
|
||||
|
||||
import android.content.pm.IPackageManager
|
||||
import android.os.FileObserver
|
||||
import android.os.ServiceManager
|
||||
import io.github.a13e300.tricky_store.keystore.CertHack
|
||||
import java.io.File
|
||||
|
||||
object Config {
|
||||
private val hackPackages = mutableSetOf<String>()
|
||||
private val generatePackages = mutableSetOf<String>()
|
||||
|
||||
private fun updateTargetPackages(f: File?) = runCatching {
|
||||
hackPackages.clear()
|
||||
generatePackages.clear()
|
||||
f?.readLines()?.forEach {
|
||||
if (it.isNotBlank() && !it.startsWith("#")) {
|
||||
val n = it.trim()
|
||||
if (n.endsWith("!")) generatePackages.add(n.removeSuffix("!").trim())
|
||||
else hackPackages.add(n)
|
||||
}
|
||||
}
|
||||
Logger.i("update hack packages: $hackPackages, generate packages=$generatePackages")
|
||||
}.onFailure {
|
||||
Logger.e("failed to update target files", it)
|
||||
}
|
||||
|
||||
private fun updateKeyBox(f: File?) = runCatching {
|
||||
CertHack.readFromXml(f?.readText())
|
||||
}.onFailure {
|
||||
Logger.e("failed to update keybox", it)
|
||||
}
|
||||
|
||||
private const val CONFIG_PATH = "/data/adb/tricky_store"
|
||||
private const val TARGET_FILE = "target.txt"
|
||||
private const val KEYBOX_FILE = "keybox.xml"
|
||||
private val root = File(CONFIG_PATH)
|
||||
|
||||
object ConfigObserver : FileObserver(root, CLOSE_WRITE or DELETE or MOVED_FROM or MOVED_TO) {
|
||||
override fun onEvent(event: Int, path: String?) {
|
||||
path ?: return
|
||||
val f = when (event) {
|
||||
CLOSE_WRITE, MOVED_TO -> File(root, path)
|
||||
DELETE, MOVED_FROM -> null
|
||||
else -> return
|
||||
}
|
||||
when (path) {
|
||||
TARGET_FILE -> updateTargetPackages(f)
|
||||
KEYBOX_FILE -> updateKeyBox(f)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun initialize() {
|
||||
root.mkdirs()
|
||||
val scope = File(root, TARGET_FILE)
|
||||
if (scope.exists()) {
|
||||
updateTargetPackages(scope)
|
||||
} else {
|
||||
Logger.e("target.txt file not found, please put it to $scope !")
|
||||
}
|
||||
val keybox = File(root, KEYBOX_FILE)
|
||||
if (!keybox.exists()) {
|
||||
Logger.e("keybox file not found, please put it to $keybox !")
|
||||
} else {
|
||||
updateKeyBox(keybox)
|
||||
}
|
||||
ConfigObserver.startWatching()
|
||||
}
|
||||
|
||||
private var iPm: IPackageManager? = null
|
||||
|
||||
fun getPm(): IPackageManager? {
|
||||
if (iPm == null) {
|
||||
iPm = IPackageManager.Stub.asInterface(ServiceManager.getService("package"))
|
||||
}
|
||||
return iPm
|
||||
}
|
||||
|
||||
fun needHack(callingUid: Int) = kotlin.runCatching {
|
||||
if (hackPackages.isEmpty()) return false
|
||||
val ps = getPm()?.getPackagesForUid(callingUid)
|
||||
ps?.any { it in hackPackages }
|
||||
}.onFailure { Logger.e("failed to get packages", it) }.getOrNull() ?: false
|
||||
|
||||
fun needGenerate(callingUid: Int) = kotlin.runCatching {
|
||||
if (generatePackages.isEmpty()) return false
|
||||
val ps = getPm()?.getPackagesForUid(callingUid)
|
||||
ps?.any { it in generatePackages }
|
||||
}.onFailure { Logger.e("failed to get packages", it) }.getOrNull() ?: false
|
||||
}
|
||||
@@ -0,0 +1,159 @@
|
||||
package io.github.a13e300.tricky_store
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.hardware.security.keymint.SecurityLevel
|
||||
import android.os.IBinder
|
||||
import android.os.Parcel
|
||||
import android.os.ServiceManager
|
||||
import android.system.keystore2.IKeystoreService
|
||||
import android.system.keystore2.KeyDescriptor
|
||||
import android.system.keystore2.KeyEntryResponse
|
||||
import io.github.a13e300.tricky_store.binder.BinderInterceptor
|
||||
import io.github.a13e300.tricky_store.keystore.CertHack
|
||||
import io.github.a13e300.tricky_store.keystore.Utils
|
||||
import kotlin.system.exitProcess
|
||||
|
||||
@SuppressLint("BlockedPrivateApi")
|
||||
object KeystoreInterceptor : BinderInterceptor() {
|
||||
private val getKeyEntryTransaction =
|
||||
getTransactCode(IKeystoreService.Stub::class.java, "getKeyEntry") // 2
|
||||
|
||||
private lateinit var keystore: IBinder
|
||||
|
||||
private var teeInterceptor: SecurityLevelInterceptor? = null
|
||||
private var strongBoxInterceptor: SecurityLevelInterceptor? = null
|
||||
|
||||
override fun onPreTransact(
|
||||
target: IBinder,
|
||||
code: Int,
|
||||
flags: Int,
|
||||
callingUid: Int,
|
||||
callingPid: Int,
|
||||
data: Parcel
|
||||
): Result {
|
||||
if (code == getKeyEntryTransaction) {
|
||||
if (CertHack.canHack()) {
|
||||
Logger.d("intercept pre $target uid=$callingUid pid=$callingPid dataSz=${data.dataSize()}")
|
||||
if (Config.needGenerate(callingUid))
|
||||
kotlin.runCatching {
|
||||
data.enforceInterface(IKeystoreService.DESCRIPTOR)
|
||||
val descriptor =
|
||||
data.readTypedObject(KeyDescriptor.CREATOR) ?: return@runCatching
|
||||
val response =
|
||||
SecurityLevelInterceptor.getKeyResponse(callingUid, descriptor.alias)
|
||||
?: return@runCatching
|
||||
Logger.i("generate key for uid=$callingUid alias=${descriptor.alias}")
|
||||
val p = Parcel.obtain()
|
||||
p.writeNoException()
|
||||
p.writeTypedObject(response, 0)
|
||||
return OverrideReply(0, p)
|
||||
}
|
||||
else if (Config.needHack(callingUid)) return Continue
|
||||
return Skip
|
||||
}
|
||||
}
|
||||
return Skip
|
||||
}
|
||||
|
||||
override fun onPostTransact(
|
||||
target: IBinder,
|
||||
code: Int,
|
||||
flags: Int,
|
||||
callingUid: Int,
|
||||
callingPid: Int,
|
||||
data: Parcel,
|
||||
reply: Parcel?,
|
||||
resultCode: Int
|
||||
): Result {
|
||||
if (target != keystore || code != getKeyEntryTransaction || reply == null) return Skip
|
||||
if (kotlin.runCatching { reply.readException() }.exceptionOrNull() != null) return Skip
|
||||
val p = Parcel.obtain()
|
||||
Logger.d("intercept post $target uid=$callingUid pid=$callingPid dataSz=${data.dataSize()} replySz=${reply.dataSize()}")
|
||||
try {
|
||||
val response = reply.readTypedObject(KeyEntryResponse.CREATOR)
|
||||
val chain = Utils.getCertificateChain(response)
|
||||
if (chain != null) {
|
||||
val newChain = CertHack.hackCertificateChain(chain)
|
||||
Utils.putCertificateChain(response, newChain)
|
||||
Logger.i("hacked cert of uid=$callingUid")
|
||||
p.writeNoException()
|
||||
p.writeTypedObject(response, 0)
|
||||
return OverrideReply(0, p)
|
||||
} else {
|
||||
p.recycle()
|
||||
}
|
||||
} catch (t: Throwable) {
|
||||
Logger.e("failed to hack certificate chain of uid=$callingUid pid=$callingPid!", t)
|
||||
p.recycle()
|
||||
}
|
||||
return Skip
|
||||
}
|
||||
|
||||
private var triedCount = 0
|
||||
private var injected = false
|
||||
|
||||
fun tryRunKeystoreInterceptor(): Boolean {
|
||||
Logger.i("trying to register keystore interceptor ($triedCount) ...")
|
||||
val b = ServiceManager.getService("android.system.keystore2.IKeystoreService/default") ?: return false
|
||||
val bd = getBinderBackdoor(b)
|
||||
if (bd == null) {
|
||||
// no binder hook, try inject
|
||||
if (triedCount >= 3) {
|
||||
Logger.e("tried injection but still has no backdoor, exit")
|
||||
exitProcess(1)
|
||||
}
|
||||
if (!injected) {
|
||||
Logger.i("trying to inject keystore ...")
|
||||
val p = Runtime.getRuntime().exec(
|
||||
arrayOf(
|
||||
"/system/bin/sh",
|
||||
"-c",
|
||||
"exec ./inject `pidof keystore2` libtricky_store.so entry"
|
||||
)
|
||||
)
|
||||
// logD(p.inputStream.readBytes().decodeToString())
|
||||
// logD(p.errorStream.readBytes().decodeToString())
|
||||
if (p.waitFor() != 0) {
|
||||
Logger.e("failed to inject! daemon exit")
|
||||
exitProcess(1)
|
||||
}
|
||||
injected = true
|
||||
}
|
||||
triedCount += 1
|
||||
return false
|
||||
}
|
||||
val ks = IKeystoreService.Stub.asInterface(b)
|
||||
val tee = kotlin.runCatching { ks.getSecurityLevel(SecurityLevel.TRUSTED_ENVIRONMENT) }
|
||||
.getOrNull()
|
||||
val strongBox =
|
||||
kotlin.runCatching { ks.getSecurityLevel(SecurityLevel.STRONGBOX) }.getOrNull()
|
||||
keystore = b
|
||||
Logger.i("register for Keystore $keystore!")
|
||||
registerBinderInterceptor(bd, b, this)
|
||||
keystore.linkToDeath(Killer, 0)
|
||||
if (tee != null) {
|
||||
Logger.i("register for TEE SecurityLevel $tee!")
|
||||
val interceptor = SecurityLevelInterceptor(tee, SecurityLevel.TRUSTED_ENVIRONMENT)
|
||||
registerBinderInterceptor(bd, tee.asBinder(), interceptor)
|
||||
teeInterceptor = interceptor
|
||||
} else {
|
||||
Logger.i("no TEE SecurityLevel found!")
|
||||
}
|
||||
if (strongBox != null) {
|
||||
Logger.i("register for StrongBox SecurityLevel $tee!")
|
||||
val interceptor = SecurityLevelInterceptor(strongBox, SecurityLevel.STRONGBOX)
|
||||
registerBinderInterceptor(bd, strongBox.asBinder(), interceptor)
|
||||
strongBoxInterceptor = interceptor
|
||||
} else {
|
||||
Logger.i("no StrongBox SecurityLevel found!")
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
object Killer : IBinder.DeathRecipient {
|
||||
override fun binderDied() {
|
||||
Logger.d("keystore exit, daemon restart")
|
||||
exitProcess(0)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package io.github.a13e300.tricky_store;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
public class Logger {
|
||||
private static final String TAG = "TrickyStore";
|
||||
public static void d(String msg) {
|
||||
Log.d(TAG, msg);
|
||||
}
|
||||
|
||||
public static void e(String msg) {
|
||||
Log.e(TAG, msg);
|
||||
}
|
||||
|
||||
public static void e(String msg, Throwable t) {
|
||||
Log.e(TAG, msg, t);
|
||||
}
|
||||
|
||||
public static void i(String msg) {
|
||||
Log.i(TAG, msg);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
package io.github.a13e300.tricky_store
|
||||
|
||||
import java.io.File
|
||||
import java.security.MessageDigest
|
||||
import kotlin.system.exitProcess
|
||||
|
||||
fun main(args: Array<String>) {
|
||||
verifySelf()
|
||||
Logger.i("Welcome to TrickyStore!")
|
||||
while (true) {
|
||||
if (!KeystoreInterceptor.tryRunKeystoreInterceptor()) {
|
||||
Thread.sleep(1000)
|
||||
continue
|
||||
}
|
||||
Config.initialize()
|
||||
while (true) {
|
||||
Thread.sleep(1000000)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalStdlibApi::class)
|
||||
fun verifySelf() {
|
||||
val kv = mutableMapOf<String, String>()
|
||||
val prop = File("./module.prop")
|
||||
runCatching {
|
||||
if (prop.canonicalPath != "/data/adb/modules/tricky_store/module.prop") error("wrong directory ${prop.canonicalPath}!")
|
||||
prop.forEachLine(Charsets.UTF_8) {
|
||||
val a = it.split("=", limit = 2)
|
||||
if (a.size != 2) return@forEachLine
|
||||
kv[a[0]] = a[1]
|
||||
}
|
||||
val checksum = MessageDigest.getInstance("SHA-256").run {
|
||||
update(kv["id"]!!.toByteArray(Charsets.UTF_8))
|
||||
update(kv["name"]!!.toByteArray(Charsets.UTF_8))
|
||||
update(kv["version"]!!.toByteArray(Charsets.UTF_8))
|
||||
update(kv["versionCode"]!!.toByteArray(Charsets.UTF_8))
|
||||
update(kv["author"]!!.toByteArray(Charsets.UTF_8))
|
||||
update(kv["description"]!!.toByteArray(Charsets.UTF_8))
|
||||
digest().toHexString()
|
||||
}
|
||||
if (checksum != BuildConfig.CHECKSUM) {
|
||||
Logger.e("unverified module files! ($checksum != ${BuildConfig.CHECKSUM})")
|
||||
prop.writeText(kv.entries.joinToString("\n") { (k, v) ->
|
||||
when (k) {
|
||||
"description" -> "description=×Module files corrupted, please re-download it from github.com/5ec1cff/TrickyStore"
|
||||
"author" -> "author=5ec1cff"
|
||||
else -> "$k=$v"
|
||||
}
|
||||
})
|
||||
File("./remove").createNewFile()
|
||||
exitProcess(1)
|
||||
}
|
||||
Logger.d("verify success!")
|
||||
}.onFailure {
|
||||
Logger.e("error while verifying self", it)
|
||||
exitProcess(1)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,144 @@
|
||||
package io.github.a13e300.tricky_store
|
||||
|
||||
import android.hardware.security.keymint.KeyParameter
|
||||
import android.hardware.security.keymint.KeyParameterValue
|
||||
import android.hardware.security.keymint.Tag
|
||||
import android.os.IBinder
|
||||
import android.os.Parcel
|
||||
import android.system.keystore2.Authorization
|
||||
import android.system.keystore2.IKeystoreSecurityLevel
|
||||
import android.system.keystore2.KeyDescriptor
|
||||
import android.system.keystore2.KeyEntryResponse
|
||||
import android.system.keystore2.KeyMetadata
|
||||
import io.github.a13e300.tricky_store.binder.BinderInterceptor
|
||||
import io.github.a13e300.tricky_store.keystore.CertHack
|
||||
import io.github.a13e300.tricky_store.keystore.CertHack.KeyGenParameters
|
||||
import io.github.a13e300.tricky_store.keystore.Utils
|
||||
import java.security.KeyPair
|
||||
import java.security.cert.Certificate
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
|
||||
class SecurityLevelInterceptor(
|
||||
private val original: IKeystoreSecurityLevel,
|
||||
private val level: Int
|
||||
) : BinderInterceptor() {
|
||||
companion object {
|
||||
private val generateKeyTransaction =
|
||||
getTransactCode(IKeystoreSecurityLevel.Stub::class.java, "generateKey")
|
||||
private val keys = ConcurrentHashMap<Key, Info>()
|
||||
|
||||
fun getKeyResponse(uid: Int, alias: String): KeyEntryResponse? =
|
||||
keys[Key(uid, alias)]?.response
|
||||
}
|
||||
|
||||
data class Key(val uid: Int, val alias: String)
|
||||
data class Info(val keyPair: KeyPair, val response: KeyEntryResponse)
|
||||
|
||||
override fun onPreTransact(
|
||||
target: IBinder,
|
||||
code: Int,
|
||||
flags: Int,
|
||||
callingUid: Int,
|
||||
callingPid: Int,
|
||||
data: Parcel
|
||||
): Result {
|
||||
if (code == generateKeyTransaction && Config.needGenerate(callingUid)) {
|
||||
Logger.i("intercept key gen uid=$callingUid pid=$callingPid")
|
||||
kotlin.runCatching {
|
||||
data.enforceInterface(IKeystoreSecurityLevel.DESCRIPTOR)
|
||||
val keyDescriptor =
|
||||
data.readTypedObject(KeyDescriptor.CREATOR) ?: return@runCatching
|
||||
val attestationKeyDescriptor = data.readTypedObject(KeyDescriptor.CREATOR)
|
||||
val params = data.createTypedArray(KeyParameter.CREATOR)!!
|
||||
// val aFlags = data.readInt()
|
||||
// val entropy = data.createByteArray()
|
||||
val kgp = KeyGenParameters(params)
|
||||
if (kgp.attestationChallenge != null) {
|
||||
if (attestationKeyDescriptor != null) {
|
||||
Logger.e("warn: attestation key not supported now")
|
||||
} else {
|
||||
val pair = CertHack.generateKeyPair(callingUid, keyDescriptor, kgp)
|
||||
?: return@runCatching
|
||||
val response = buildResponse(pair.second, kgp, keyDescriptor)
|
||||
keys[Key(callingUid, keyDescriptor.alias)] = Info(pair.first, response)
|
||||
val p = Parcel.obtain()
|
||||
p.writeNoException()
|
||||
p.writeTypedObject(response.metadata, 0)
|
||||
return OverrideReply(0, p)
|
||||
}
|
||||
}
|
||||
}.onFailure {
|
||||
Logger.e("parse key gen request", it)
|
||||
}
|
||||
}
|
||||
return Skip
|
||||
}
|
||||
|
||||
private fun buildResponse(
|
||||
chain: List<Certificate>,
|
||||
params: KeyGenParameters,
|
||||
descriptor: KeyDescriptor
|
||||
): KeyEntryResponse {
|
||||
val response = KeyEntryResponse()
|
||||
val metadata = KeyMetadata()
|
||||
metadata.keySecurityLevel = level
|
||||
Utils.putCertificateChain(metadata, chain.toTypedArray<Certificate>())
|
||||
val d = KeyDescriptor()
|
||||
d.domain = descriptor.domain
|
||||
d.nspace = descriptor.nspace
|
||||
metadata.key = d
|
||||
val authorizations = ArrayList<Authorization>()
|
||||
var a: Authorization
|
||||
for (i in params.purpose) {
|
||||
a = Authorization()
|
||||
a.keyParameter = KeyParameter()
|
||||
a.keyParameter.tag = Tag.PURPOSE
|
||||
a.keyParameter.value = KeyParameterValue.keyPurpose(i)
|
||||
a.securityLevel = level
|
||||
authorizations.add(a)
|
||||
}
|
||||
for (i in params.digest) {
|
||||
a = Authorization()
|
||||
a.keyParameter = KeyParameter()
|
||||
a.keyParameter.tag = Tag.DIGEST
|
||||
a.keyParameter.value = KeyParameterValue.digest(i)
|
||||
a.securityLevel = level
|
||||
authorizations.add(a)
|
||||
}
|
||||
a = Authorization()
|
||||
a.keyParameter = KeyParameter()
|
||||
a.keyParameter.tag = Tag.ALGORITHM
|
||||
a.keyParameter.value = KeyParameterValue.algorithm(params.algorithm)
|
||||
a.securityLevel = level
|
||||
authorizations.add(a)
|
||||
a = Authorization()
|
||||
a.keyParameter = KeyParameter()
|
||||
a.keyParameter.tag = Tag.KEY_SIZE
|
||||
a.keyParameter.value = KeyParameterValue.integer(params.keySize)
|
||||
a.securityLevel = level
|
||||
authorizations.add(a)
|
||||
a = Authorization()
|
||||
a.keyParameter = KeyParameter()
|
||||
a.keyParameter.tag = Tag.EC_CURVE
|
||||
a.keyParameter.value = KeyParameterValue.ecCurve(params.ecCurve)
|
||||
a.securityLevel = level
|
||||
authorizations.add(a)
|
||||
a = Authorization()
|
||||
a.keyParameter = KeyParameter()
|
||||
a.keyParameter.tag = Tag.NO_AUTH_REQUIRED
|
||||
a.keyParameter.value = KeyParameterValue.boolValue(true) // TODO: copy
|
||||
a.securityLevel = level
|
||||
authorizations.add(a)
|
||||
// TODO: ORIGIN
|
||||
//OS_VERSION
|
||||
//OS_PATCHLEVEL
|
||||
//VENDOR_PATCHLEVEL
|
||||
//BOOT_PATCHLEVEL
|
||||
//CREATION_DATETIME
|
||||
//USER_ID
|
||||
metadata.authorizations = authorizations.toTypedArray<Authorization>()
|
||||
response.metadata = metadata
|
||||
response.iSecurityLevel = original
|
||||
return response
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
package io.github.a13e300.tricky_store.binder
|
||||
|
||||
import android.os.Binder
|
||||
import android.os.IBinder
|
||||
import android.os.Parcel
|
||||
import io.github.a13e300.tricky_store.Logger
|
||||
|
||||
open class BinderInterceptor : Binder() {
|
||||
sealed class Result
|
||||
data object Skip : Result()
|
||||
data object Continue : Result()
|
||||
data class OverrideData(val data: Parcel) : Result()
|
||||
data class OverrideReply(val code: Int = 0, val reply: Parcel) : Result()
|
||||
|
||||
companion object {
|
||||
fun getBinderBackdoor(b: IBinder): IBinder? {
|
||||
val data = Parcel.obtain()
|
||||
val reply = Parcel.obtain()
|
||||
try {
|
||||
if (!b.transact(0xdeadbeef.toInt(), data, reply, 0)) {
|
||||
Logger.d("remote return false!")
|
||||
return null
|
||||
}
|
||||
Logger.d("remote return true!")
|
||||
return reply.readStrongBinder()
|
||||
} catch (t: Throwable) {
|
||||
Logger.e("failed to read binder", t)
|
||||
return null
|
||||
} finally {
|
||||
data.recycle()
|
||||
reply.recycle()
|
||||
}
|
||||
}
|
||||
|
||||
fun registerBinderInterceptor(backdoor: IBinder, target: IBinder, interceptor: BinderInterceptor) {
|
||||
val data = Parcel.obtain()
|
||||
val reply = Parcel.obtain()
|
||||
data.writeStrongBinder(target)
|
||||
data.writeStrongBinder(interceptor)
|
||||
backdoor.transact(1, data, reply, 0)
|
||||
}
|
||||
}
|
||||
|
||||
open fun onPreTransact(target: IBinder, code: Int, flags: Int, callingUid: Int, callingPid: Int, data: Parcel): Result = Skip
|
||||
open fun onPostTransact(target: IBinder, code: Int, flags: Int, callingUid: Int, callingPid: Int, data: Parcel, reply: Parcel?, resultCode: Int): Result = Skip
|
||||
|
||||
override fun onTransact(code: Int, data: Parcel, reply: Parcel?, flags: Int): Boolean {
|
||||
val result = when (code) {
|
||||
1 -> { // PRE_TRANSACT
|
||||
val target = data.readStrongBinder()
|
||||
val theCode = data.readInt()
|
||||
val theFlags = data.readInt()
|
||||
val callingUid = data.readInt()
|
||||
val callingPid = data.readInt()
|
||||
val sz = data.readLong()
|
||||
val theData = Parcel.obtain()
|
||||
try {
|
||||
theData.appendFrom(data, data.dataPosition(), sz.toInt())
|
||||
theData.setDataPosition(0)
|
||||
onPreTransact(target, theCode, theFlags, callingUid, callingPid, theData)
|
||||
} finally {
|
||||
theData.recycle()
|
||||
}
|
||||
}
|
||||
2 -> { // POST_TRANSACT
|
||||
val target = data.readStrongBinder()
|
||||
val theCode = data.readInt()
|
||||
val theFlags = data.readInt()
|
||||
val callingUid = data.readInt()
|
||||
val callingPid = data.readInt()
|
||||
val resultCode = data.readInt()
|
||||
val theData = Parcel.obtain()
|
||||
val theReply = Parcel.obtain()
|
||||
try {
|
||||
val sz = data.readLong().toInt()
|
||||
theData.appendFrom(data, data.dataPosition(), sz)
|
||||
theData.setDataPosition(0)
|
||||
data.setDataPosition(data.dataPosition() + sz)
|
||||
val sz2 = data.readLong().toInt()
|
||||
if (sz2 != 0) {
|
||||
theReply.appendFrom(data, data.dataPosition(), sz2)
|
||||
theReply.setDataPosition(0)
|
||||
}
|
||||
onPostTransact(target, theCode, theFlags, callingUid, callingPid, theData, if (sz2 == 0) null else theReply, resultCode)
|
||||
} finally {
|
||||
theData.recycle()
|
||||
theReply.recycle()
|
||||
}
|
||||
}
|
||||
else -> return super.onTransact(code, data, reply, flags)
|
||||
}
|
||||
when (result) {
|
||||
Skip -> reply!!.writeInt(1)
|
||||
Continue -> reply!!.writeInt(2)
|
||||
is OverrideReply -> {
|
||||
reply!!.writeInt(3)
|
||||
reply.writeInt(result.code)
|
||||
reply.writeLong(result.reply.dataSize().toLong())
|
||||
reply.appendFrom(result.reply, 0, result.reply.dataSize())
|
||||
result.reply.recycle()
|
||||
}
|
||||
is OverrideData -> {
|
||||
reply!!.writeInt(4)
|
||||
reply.writeLong(result.data.dataSize().toLong())
|
||||
reply.appendFrom(result.data, 0, result.data.dataSize())
|
||||
result.data.recycle()
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,560 @@
|
||||
package io.github.a13e300.tricky_store.keystore;
|
||||
|
||||
import android.content.pm.PackageManager;
|
||||
import android.hardware.security.keymint.Algorithm;
|
||||
import android.hardware.security.keymint.EcCurve;
|
||||
import android.hardware.security.keymint.KeyParameter;
|
||||
import android.hardware.security.keymint.Tag;
|
||||
import android.security.keystore.KeyProperties;
|
||||
import android.system.keystore2.KeyDescriptor;
|
||||
import android.util.Pair;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import org.bouncycastle.asn1.ASN1Boolean;
|
||||
import org.bouncycastle.asn1.ASN1Encodable;
|
||||
import org.bouncycastle.asn1.ASN1EncodableVector;
|
||||
import org.bouncycastle.asn1.ASN1Enumerated;
|
||||
import org.bouncycastle.asn1.ASN1Integer;
|
||||
import org.bouncycastle.asn1.ASN1ObjectIdentifier;
|
||||
import org.bouncycastle.asn1.ASN1OctetString;
|
||||
import org.bouncycastle.asn1.ASN1Sequence;
|
||||
import org.bouncycastle.asn1.ASN1TaggedObject;
|
||||
import org.bouncycastle.asn1.DERNull;
|
||||
import org.bouncycastle.asn1.DEROctetString;
|
||||
import org.bouncycastle.asn1.DERSequence;
|
||||
import org.bouncycastle.asn1.DERSet;
|
||||
import org.bouncycastle.asn1.DERTaggedObject;
|
||||
import org.bouncycastle.asn1.x500.X500Name;
|
||||
import org.bouncycastle.asn1.x509.Extension;
|
||||
import org.bouncycastle.asn1.x509.KeyUsage;
|
||||
import org.bouncycastle.cert.X509CertificateHolder;
|
||||
import org.bouncycastle.cert.X509v3CertificateBuilder;
|
||||
import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
|
||||
import org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder;
|
||||
import org.bouncycastle.jce.provider.BouncyCastleProvider;
|
||||
import org.bouncycastle.openssl.PEMKeyPair;
|
||||
import org.bouncycastle.openssl.PEMParser;
|
||||
import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter;
|
||||
import org.bouncycastle.operator.ContentSigner;
|
||||
import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
|
||||
import org.bouncycastle.util.io.pem.PemReader;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.StringReader;
|
||||
import java.math.BigInteger;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.KeyPair;
|
||||
import java.security.KeyPairGenerator;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.Security;
|
||||
import java.security.cert.Certificate;
|
||||
import java.security.cert.CertificateFactory;
|
||||
import java.security.cert.CertificateParsingException;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.security.spec.ECGenParameterSpec;
|
||||
import java.security.spec.RSAKeyGenParameterSpec;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
|
||||
import javax.security.auth.x500.X500Principal;
|
||||
|
||||
import io.github.a13e300.tricky_store.Config;
|
||||
import io.github.a13e300.tricky_store.Logger;
|
||||
import io.github.a13e300.tricky_store.UtilKt;
|
||||
|
||||
public final class CertHack {
|
||||
private static final ASN1ObjectIdentifier OID = new ASN1ObjectIdentifier("1.3.6.1.4.1.11129.2.1.17");
|
||||
|
||||
private static final int ATTESTATION_APPLICATION_ID_PACKAGE_INFOS_INDEX = 0;
|
||||
private static final int ATTESTATION_APPLICATION_ID_SIGNATURE_DIGESTS_INDEX = 1;
|
||||
private static final Map<String, KeyBox> keyboxes = new HashMap<>();
|
||||
private static final int ATTESTATION_PACKAGE_INFO_PACKAGE_NAME_INDEX = 0;
|
||||
|
||||
private static final CertificateFactory certificateFactory;
|
||||
|
||||
static {
|
||||
try {
|
||||
certificateFactory = CertificateFactory.getInstance("X.509");
|
||||
} catch (Throwable t) {
|
||||
Logger.e("", t);
|
||||
throw new RuntimeException(t);
|
||||
}
|
||||
}
|
||||
|
||||
private static final int ATTESTATION_PACKAGE_INFO_VERSION_INDEX = 1;
|
||||
|
||||
public static boolean canHack() {
|
||||
return !keyboxes.isEmpty();
|
||||
}
|
||||
|
||||
private static PEMKeyPair parseKeyPair(String key) throws Throwable {
|
||||
try (PEMParser parser = new PEMParser(new StringReader(UtilKt.trimLine(key)))) {
|
||||
return (PEMKeyPair) parser.readObject();
|
||||
}
|
||||
}
|
||||
|
||||
private static Certificate parseCert(String cert) throws Throwable {
|
||||
try (PemReader reader = new PemReader(new StringReader(UtilKt.trimLine(cert)))) {
|
||||
return certificateFactory.generateCertificate(new ByteArrayInputStream(reader.readPemObject().getContent()));
|
||||
}
|
||||
}
|
||||
|
||||
private static byte[] getByteArrayFromAsn1(ASN1Encodable asn1Encodable) throws CertificateParsingException {
|
||||
if (!(asn1Encodable instanceof DEROctetString derOctectString)) {
|
||||
throw new CertificateParsingException("Expected DEROctetString");
|
||||
}
|
||||
return derOctectString.getOctets();
|
||||
}
|
||||
|
||||
public static void readFromXml(String data) {
|
||||
keyboxes.clear();
|
||||
if (data == null) {
|
||||
Logger.i("clear all keyboxes");
|
||||
return;
|
||||
}
|
||||
XMLParser xmlParser = new XMLParser(data);
|
||||
|
||||
try {
|
||||
int numberOfKeyboxes = Integer.parseInt(Objects.requireNonNull(xmlParser.obtainPath(
|
||||
"AndroidAttestation.NumberOfKeyboxes").get("text")));
|
||||
for (int i = 0; i < numberOfKeyboxes; i++) {
|
||||
String keyboxAlgorithm = xmlParser.obtainPath(
|
||||
"AndroidAttestation.Keybox.Key[" + i + "]").get("algorithm");
|
||||
String privateKey = xmlParser.obtainPath(
|
||||
"AndroidAttestation.Keybox.Key[" + i + "].PrivateKey").get("text");
|
||||
int numberOfCertificates = Integer.parseInt(Objects.requireNonNull(xmlParser.obtainPath(
|
||||
"AndroidAttestation.Keybox.Key[" + i + "].CertificateChain.NumberOfCertificates").get("text")));
|
||||
|
||||
LinkedList<Certificate> certificateChain = new LinkedList<>();
|
||||
|
||||
for (int j = 0; j < numberOfCertificates; j++) {
|
||||
Map<String, String> certData = xmlParser.obtainPath(
|
||||
"AndroidAttestation.Keybox.Key[" + i + "].CertificateChain.Certificate[" + j + "]");
|
||||
certificateChain.add(parseCert(certData.get("text")));
|
||||
}
|
||||
String algo;
|
||||
if (keyboxAlgorithm.toLowerCase().equals("ecdsa")) {
|
||||
algo = KeyProperties.KEY_ALGORITHM_EC;
|
||||
} else {
|
||||
algo = KeyProperties.KEY_ALGORITHM_RSA;
|
||||
}
|
||||
var pemKp = parseKeyPair(privateKey);
|
||||
var kp = new JcaPEMKeyConverter().getKeyPair(pemKp);
|
||||
keyboxes.put(algo, new KeyBox(pemKp, kp, certificateChain));
|
||||
}
|
||||
Logger.i("update " + numberOfKeyboxes + " keyboxes");
|
||||
} catch (Throwable t) {
|
||||
Logger.e("Error loading xml file (keyboxes cleared): " + t);
|
||||
}
|
||||
}
|
||||
|
||||
public static Certificate[] hackCertificateChain(Certificate[] caList) {
|
||||
if (caList == null) throw new UnsupportedOperationException("caList is null!");
|
||||
try {
|
||||
X509Certificate leaf = (X509Certificate) certificateFactory.generateCertificate(new ByteArrayInputStream(caList[0].getEncoded()));
|
||||
byte[] bytes = leaf.getExtensionValue(OID.getId());
|
||||
if (bytes == null) return caList;
|
||||
|
||||
X509CertificateHolder leafHolder = new X509CertificateHolder(leaf.getEncoded());
|
||||
Extension ext = leafHolder.getExtension(OID);
|
||||
ASN1Sequence sequence = ASN1Sequence.getInstance(ext.getExtnValue().getOctets());
|
||||
ASN1Encodable[] encodables = sequence.toArray();
|
||||
ASN1Sequence teeEnforced = (ASN1Sequence) encodables[7];
|
||||
ASN1EncodableVector vector = new ASN1EncodableVector();
|
||||
ASN1Encodable rootOfTrust = null;
|
||||
|
||||
for (ASN1Encodable asn1Encodable : teeEnforced) {
|
||||
ASN1TaggedObject taggedObject = (ASN1TaggedObject) asn1Encodable;
|
||||
if (taggedObject.getTagNo() == 704) {
|
||||
rootOfTrust = taggedObject.getBaseObject().toASN1Primitive();
|
||||
continue;
|
||||
}
|
||||
vector.add(taggedObject);
|
||||
}
|
||||
|
||||
LinkedList<Certificate> certificates;
|
||||
X509v3CertificateBuilder builder;
|
||||
ContentSigner signer;
|
||||
|
||||
var k = keyboxes.get(leaf.getPublicKey().getAlgorithm());
|
||||
if (k == null)
|
||||
throw new UnsupportedOperationException("unsupported algorithm " + leaf.getPublicKey().getAlgorithm());
|
||||
certificates = new LinkedList<>(k.certificates);
|
||||
builder = new X509v3CertificateBuilder(
|
||||
new X509CertificateHolder(
|
||||
certificates.get(0).getEncoded()
|
||||
).getSubject(),
|
||||
leafHolder.getSerialNumber(),
|
||||
leafHolder.getNotBefore(),
|
||||
leafHolder.getNotAfter(),
|
||||
leafHolder.getSubject(),
|
||||
leafHolder.getSubjectPublicKeyInfo()
|
||||
);
|
||||
signer = new JcaContentSignerBuilder(leaf.getSigAlgName())
|
||||
.build(k.keyPair.getPrivate());
|
||||
|
||||
byte[] verifiedBootKey = UtilKt.getBootKey();
|
||||
byte[] verifiedBootHash = null;
|
||||
try {
|
||||
if (!(rootOfTrust instanceof ASN1Sequence r)) {
|
||||
throw new CertificateParsingException("Expected sequence for root of trust, found "
|
||||
+ rootOfTrust.getClass().getName());
|
||||
}
|
||||
verifiedBootHash = getByteArrayFromAsn1(r.getObjectAt(3));
|
||||
} catch (Throwable t) {
|
||||
Logger.e("failed to get verified boot key or hash from original, use randomly generated instead", t);
|
||||
}
|
||||
|
||||
if (verifiedBootHash == null) {
|
||||
verifiedBootHash = UtilKt.getBootHash();
|
||||
}
|
||||
|
||||
ASN1Encodable[] rootOfTrustEnc = {
|
||||
new DEROctetString(verifiedBootKey),
|
||||
ASN1Boolean.TRUE,
|
||||
new ASN1Enumerated(0),
|
||||
new DEROctetString(verifiedBootHash)
|
||||
};
|
||||
|
||||
ASN1Sequence hackedRootOfTrust = new DERSequence(rootOfTrustEnc);
|
||||
ASN1TaggedObject rootOfTrustTagObj = new DERTaggedObject(704, hackedRootOfTrust);
|
||||
vector.add(rootOfTrustTagObj);
|
||||
|
||||
ASN1Sequence hackEnforced = new DERSequence(vector);
|
||||
encodables[7] = hackEnforced;
|
||||
ASN1Sequence hackedSeq = new DERSequence(encodables);
|
||||
|
||||
ASN1OctetString hackedSeqOctets = new DEROctetString(hackedSeq);
|
||||
Extension hackedExt = new Extension(OID, false, hackedSeqOctets);
|
||||
builder.addExtension(hackedExt);
|
||||
|
||||
for (ASN1ObjectIdentifier extensionOID : leafHolder.getExtensions().getExtensionOIDs()) {
|
||||
if (OID.getId().equals(extensionOID.getId())) continue;
|
||||
builder.addExtension(leafHolder.getExtension(extensionOID));
|
||||
}
|
||||
certificates.addFirst(new JcaX509CertificateConverter().getCertificate(builder.build(signer)));
|
||||
|
||||
return certificates.toArray(new Certificate[0]);
|
||||
|
||||
} catch (Throwable t) {
|
||||
Logger.e("", t);
|
||||
}
|
||||
return caList;
|
||||
}
|
||||
|
||||
public static Pair<KeyPair, List<Certificate>> generateKeyPair(int uid, KeyDescriptor descriptor, KeyGenParameters params) {
|
||||
Logger.i("Requested KeyPair with alias: " + descriptor.alias);
|
||||
KeyPair rootKP;
|
||||
X500Name issuer;
|
||||
int size = params.keySize;
|
||||
KeyPair kp = null;
|
||||
KeyBox keyBox = null;
|
||||
try {
|
||||
var algo = params.algorithm;
|
||||
if (algo == Algorithm.EC) {
|
||||
Logger.d("GENERATING EC KEYPAIR OF SIZE " + size);
|
||||
kp = buildECKeyPair(params);
|
||||
keyBox = keyboxes.get(KeyProperties.KEY_ALGORITHM_EC);
|
||||
} else if (algo == Algorithm.RSA) {
|
||||
Logger.d("GENERATING RSA KEYPAIR OF SIZE " + size);
|
||||
kp = buildRSAKeyPair(params);
|
||||
keyBox = keyboxes.get(KeyProperties.KEY_ALGORITHM_RSA);
|
||||
}
|
||||
if (keyBox == null) {
|
||||
Logger.e("UNSUPPORTED ALGORITHM: " + algo);
|
||||
return null;
|
||||
}
|
||||
rootKP = keyBox.keyPair;
|
||||
issuer = new X509CertificateHolder(
|
||||
keyBox.certificates.get(0).getEncoded()
|
||||
).getSubject();
|
||||
|
||||
X509v3CertificateBuilder certBuilder = new JcaX509v3CertificateBuilder(issuer,
|
||||
params.certificateSerial,
|
||||
params.certificateNotBefore,
|
||||
params.certificateNotAfter,
|
||||
params.certificateSubject,
|
||||
kp.getPublic()
|
||||
);
|
||||
|
||||
KeyUsage keyUsage = new KeyUsage(KeyUsage.keyCertSign);
|
||||
certBuilder.addExtension(Extension.keyUsage, true, keyUsage);
|
||||
certBuilder.addExtension(createExtension(params, uid));
|
||||
|
||||
ContentSigner contentSigner;
|
||||
if (algo == Algorithm.EC) {
|
||||
contentSigner = new JcaContentSignerBuilder("SHA256withECDSA").build(rootKP.getPrivate());
|
||||
} else {
|
||||
contentSigner = new JcaContentSignerBuilder("SHA256withRSA").build(rootKP.getPrivate());
|
||||
}
|
||||
X509CertificateHolder certHolder = certBuilder.build(contentSigner);
|
||||
var leaf = new JcaX509CertificateConverter().getCertificate(certHolder);
|
||||
List<Certificate> chain = new ArrayList<>(keyBox.certificates);
|
||||
chain.add(0, leaf);
|
||||
Logger.d("Successfully generated X500 Cert for alias: " + descriptor.alias);
|
||||
return new Pair<>(kp, chain);
|
||||
} catch (Throwable t) {
|
||||
Logger.e("", t);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static KeyPair buildECKeyPair(KeyGenParameters params) throws Exception {
|
||||
Security.removeProvider(BouncyCastleProvider.PROVIDER_NAME);
|
||||
Security.addProvider(new BouncyCastleProvider());
|
||||
ECGenParameterSpec spec = new ECGenParameterSpec(params.ecCurveName);
|
||||
KeyPairGenerator kpg = KeyPairGenerator.getInstance("ECDSA", BouncyCastleProvider.PROVIDER_NAME);
|
||||
kpg.initialize(spec);
|
||||
return kpg.generateKeyPair();
|
||||
}
|
||||
|
||||
private static KeyPair buildRSAKeyPair(KeyGenParameters params) throws Exception {
|
||||
Security.removeProvider(BouncyCastleProvider.PROVIDER_NAME);
|
||||
Security.addProvider(new BouncyCastleProvider());
|
||||
RSAKeyGenParameterSpec spec = new RSAKeyGenParameterSpec(
|
||||
params.keySize, params.rsaPublicExponent);
|
||||
KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA", BouncyCastleProvider.PROVIDER_NAME);
|
||||
kpg.initialize(spec);
|
||||
return kpg.generateKeyPair();
|
||||
}
|
||||
|
||||
private static ASN1Encodable[] fromIntList(List<Integer> list) {
|
||||
ASN1Encodable[] result = new ASN1Encodable[list.size()];
|
||||
for (int i = 0; i < list.size(); i++) {
|
||||
result[i] = new ASN1Integer(list.get(i));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private static Extension createExtension(KeyGenParameters params, int uid) {
|
||||
try {
|
||||
byte[] key = UtilKt.getBootKey();
|
||||
byte[] hash = UtilKt.getBootHash();
|
||||
|
||||
ASN1Encodable[] rootOfTrustEncodables = {new DEROctetString(key), ASN1Boolean.TRUE,
|
||||
new ASN1Enumerated(0), new DEROctetString(hash)};
|
||||
|
||||
ASN1Sequence rootOfTrustSeq = new DERSequence(rootOfTrustEncodables);
|
||||
|
||||
var Apurpose = new DERSet(fromIntList(params.purpose));
|
||||
var Aalgorithm = new ASN1Integer(params.algorithm);
|
||||
var AkeySize = new ASN1Integer(params.keySize);
|
||||
var Adigest = new DERSet(fromIntList(params.digest));
|
||||
var AecCurve = new ASN1Integer(params.ecCurve);
|
||||
var AnoAuthRequired = DERNull.INSTANCE;
|
||||
|
||||
// To be loaded
|
||||
var AosVersion = new ASN1Integer(UtilKt.getOsVersion());
|
||||
var AosPatchLevel = new ASN1Integer(UtilKt.getPatchLevel());
|
||||
|
||||
var AapplicationID = createApplicationId(uid);
|
||||
var AbootPatchlevel = new ASN1Integer(UtilKt.getPatchLevelLong());
|
||||
var AvendorPatchLevel = new ASN1Integer(UtilKt.getPatchLevelLong());
|
||||
|
||||
var AcreationDateTime = new ASN1Integer(System.currentTimeMillis());
|
||||
var Aorigin = new ASN1Integer(0);
|
||||
|
||||
var purpose = new DERTaggedObject(true, 1, Apurpose);
|
||||
var algorithm = new DERTaggedObject(true, 2, Aalgorithm);
|
||||
var keySize = new DERTaggedObject(true, 3, AkeySize);
|
||||
var digest = new DERTaggedObject(true, 5, Adigest);
|
||||
var ecCurve = new DERTaggedObject(true, 10, AecCurve);
|
||||
var noAuthRequired = new DERTaggedObject(true, 503, AnoAuthRequired);
|
||||
var creationDateTime = new DERTaggedObject(true, 701, AcreationDateTime);
|
||||
var origin = new DERTaggedObject(true, 702, Aorigin);
|
||||
var rootOfTrust = new DERTaggedObject(true, 704, rootOfTrustSeq);
|
||||
var osVersion = new DERTaggedObject(true, 705, AosVersion);
|
||||
var osPatchLevel = new DERTaggedObject(true, 706, AosPatchLevel);
|
||||
var applicationID = new DERTaggedObject(true, 709, AapplicationID);
|
||||
var vendorPatchLevel = new DERTaggedObject(true, 718, AvendorPatchLevel);
|
||||
var bootPatchLevel = new DERTaggedObject(true, 719, AbootPatchlevel);
|
||||
|
||||
ASN1Encodable[] teeEnforcedEncodables;
|
||||
|
||||
// Support device properties attestation
|
||||
if (params.brand != null) {
|
||||
var Abrand = new DEROctetString(params.brand);
|
||||
var Adevice = new DEROctetString(params.device);
|
||||
var Aproduct = new DEROctetString(params.product);
|
||||
var Amanufacturer = new DEROctetString(params.manufacturer);
|
||||
var Amodel = new DEROctetString(params.model);
|
||||
var brand = new DERTaggedObject(true, 710, Abrand);
|
||||
var device = new DERTaggedObject(true, 711, Adevice);
|
||||
var product = new DERTaggedObject(true, 712, Aproduct);
|
||||
var manufacturer = new DERTaggedObject(true, 716, Amanufacturer);
|
||||
var model = new DERTaggedObject(true, 717, Amodel);
|
||||
|
||||
teeEnforcedEncodables = new ASN1Encodable[]{purpose, algorithm, keySize, digest, ecCurve,
|
||||
noAuthRequired, origin, rootOfTrust, osVersion, osPatchLevel, vendorPatchLevel,
|
||||
bootPatchLevel, brand, device, product, manufacturer, model};
|
||||
} else {
|
||||
teeEnforcedEncodables = new ASN1Encodable[]{purpose, algorithm, keySize, digest, ecCurve,
|
||||
noAuthRequired, origin, rootOfTrust, osVersion, osPatchLevel, vendorPatchLevel,
|
||||
bootPatchLevel};
|
||||
}
|
||||
|
||||
ASN1Encodable[] softwareEnforced = {applicationID, creationDateTime};
|
||||
|
||||
ASN1OctetString keyDescriptionOctetStr = getAsn1OctetString(teeEnforcedEncodables, softwareEnforced, params);
|
||||
|
||||
return new Extension(new ASN1ObjectIdentifier("1.3.6.1.4.1.11129.2.1.17"), false, keyDescriptionOctetStr);
|
||||
} catch (Throwable t) {
|
||||
Logger.e("", t);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static ASN1OctetString getAsn1OctetString(ASN1Encodable[] teeEnforcedEncodables, ASN1Encodable[] softwareEnforcedEncodables, KeyGenParameters params) throws IOException {
|
||||
ASN1Integer attestationVersion = new ASN1Integer(100);
|
||||
ASN1Enumerated attestationSecurityLevel = new ASN1Enumerated(1);
|
||||
ASN1Integer keymasterVersion = new ASN1Integer(100);
|
||||
ASN1Enumerated keymasterSecurityLevel = new ASN1Enumerated(1);
|
||||
ASN1OctetString attestationChallenge = new DEROctetString(params.attestationChallenge);
|
||||
ASN1OctetString uniqueId = new DEROctetString("".getBytes());
|
||||
ASN1Encodable softwareEnforced = new DERSequence(softwareEnforcedEncodables);
|
||||
ASN1Sequence teeEnforced = new DERSequence(teeEnforcedEncodables);
|
||||
|
||||
ASN1Encodable[] keyDescriptionEncodables = {attestationVersion, attestationSecurityLevel, keymasterVersion,
|
||||
keymasterSecurityLevel, attestationChallenge, uniqueId, softwareEnforced, teeEnforced};
|
||||
|
||||
ASN1Sequence keyDescriptionHackSeq = new DERSequence(keyDescriptionEncodables);
|
||||
|
||||
return new DEROctetString(keyDescriptionHackSeq);
|
||||
}
|
||||
|
||||
private static DEROctetString createApplicationId(int uid) throws Throwable {
|
||||
var pm = Config.INSTANCE.getPm();
|
||||
if (pm == null) {
|
||||
throw new IllegalStateException("createApplicationId: pm not found!");
|
||||
}
|
||||
var packages = pm.getPackagesForUid(uid);
|
||||
var size = packages.length;
|
||||
ASN1Encodable[] packageInfoAA = new ASN1Encodable[size];
|
||||
Set<Digest> signatures = new HashSet<>();
|
||||
var dg = MessageDigest.getInstance("SHA-256");
|
||||
for (int i = 0; i < size; i++) {
|
||||
var name = packages[i];
|
||||
var info = UtilKt.getPackageInfoCompat(pm, name, PackageManager.GET_SIGNATURES, uid / 100000);
|
||||
ASN1Encodable[] arr = new ASN1Encodable[2];
|
||||
arr[ATTESTATION_PACKAGE_INFO_PACKAGE_NAME_INDEX] =
|
||||
new DEROctetString(packages[i].getBytes(StandardCharsets.UTF_8));
|
||||
arr[ATTESTATION_PACKAGE_INFO_VERSION_INDEX] = new ASN1Integer(info.getLongVersionCode());
|
||||
packageInfoAA[i] = new DERSequence(arr);
|
||||
for (var s : info.signatures) {
|
||||
signatures.add(new Digest(dg.digest(s.toByteArray())));
|
||||
}
|
||||
}
|
||||
|
||||
ASN1Encodable[] signaturesAA = new ASN1Encodable[signatures.size()];
|
||||
var i = 0;
|
||||
for (var d : signatures) {
|
||||
signaturesAA[i] = new DEROctetString(d.digest);
|
||||
i++;
|
||||
}
|
||||
|
||||
ASN1Encodable[] applicationIdAA = new ASN1Encodable[2];
|
||||
applicationIdAA[ATTESTATION_APPLICATION_ID_PACKAGE_INFOS_INDEX] =
|
||||
new DERSet(packageInfoAA);
|
||||
applicationIdAA[ATTESTATION_APPLICATION_ID_SIGNATURE_DIGESTS_INDEX] =
|
||||
new DERSet(signaturesAA);
|
||||
|
||||
return new DEROctetString(new DERSequence(applicationIdAA).getEncoded());
|
||||
}
|
||||
|
||||
record Digest(byte[] digest) {
|
||||
@Override
|
||||
public boolean equals(@Nullable Object o) {
|
||||
if (o instanceof Digest d)
|
||||
return Arrays.equals(digest, d.digest);
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Arrays.hashCode(digest);
|
||||
}
|
||||
}
|
||||
|
||||
record KeyBox(PEMKeyPair pemKeyPair, KeyPair keyPair, List<Certificate> certificates) {
|
||||
}
|
||||
|
||||
public static class KeyGenParameters {
|
||||
public int keySize;
|
||||
public int algorithm;
|
||||
public BigInteger certificateSerial;
|
||||
public Date certificateNotBefore;
|
||||
public Date certificateNotAfter;
|
||||
public X500Name certificateSubject;
|
||||
|
||||
public BigInteger rsaPublicExponent;
|
||||
public int ecCurve;
|
||||
public String ecCurveName;
|
||||
|
||||
public List<Integer> purpose = new ArrayList<>();
|
||||
public List<Integer> digest = new ArrayList<>();
|
||||
|
||||
public byte[] attestationChallenge;
|
||||
public byte[] brand;
|
||||
public byte[] device;
|
||||
public byte[] product;
|
||||
public byte[] manufacturer;
|
||||
public byte[] model;
|
||||
|
||||
public KeyGenParameters(KeyParameter[] params) {
|
||||
for (var kp : params) {
|
||||
var p = kp.value;
|
||||
switch (kp.tag) {
|
||||
case Tag.KEY_SIZE -> keySize = p.getInteger();
|
||||
case Tag.ALGORITHM -> algorithm = p.getAlgorithm();
|
||||
case Tag.CERTIFICATE_SERIAL -> certificateSerial = new BigInteger(p.getBlob());
|
||||
case Tag.CERTIFICATE_NOT_BEFORE ->
|
||||
certificateNotBefore = new Date(p.getDateTime());
|
||||
case Tag.CERTIFICATE_NOT_AFTER ->
|
||||
certificateNotAfter = new Date(p.getDateTime());
|
||||
case Tag.CERTIFICATE_SUBJECT ->
|
||||
certificateSubject = new X500Name(new X500Principal(p.getBlob()).getName());
|
||||
case Tag.RSA_PUBLIC_EXPONENT -> rsaPublicExponent = new BigInteger(p.getBlob());
|
||||
case Tag.EC_CURVE -> {
|
||||
ecCurve = p.getEcCurve();
|
||||
ecCurveName = getEcCurveName(ecCurve);
|
||||
}
|
||||
case Tag.PURPOSE -> {
|
||||
purpose.add(p.getKeyPurpose());
|
||||
}
|
||||
case Tag.DIGEST -> {
|
||||
digest.add(p.getDigest());
|
||||
}
|
||||
case Tag.ATTESTATION_CHALLENGE -> attestationChallenge = p.getBlob();
|
||||
case Tag.ATTESTATION_ID_BRAND -> brand = p.getBlob();
|
||||
case Tag.ATTESTATION_ID_DEVICE -> device = p.getBlob();
|
||||
case Tag.ATTESTATION_ID_PRODUCT -> product = p.getBlob();
|
||||
case Tag.ATTESTATION_ID_MANUFACTURER -> manufacturer = p.getBlob();
|
||||
case Tag.ATTESTATION_ID_MODEL -> model = p.getBlob();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static String getEcCurveName(int curve) {
|
||||
String res;
|
||||
switch (curve) {
|
||||
case EcCurve.CURVE_25519 -> res = "CURVE_25519";
|
||||
case EcCurve.P_224 -> res = "secp224r1";
|
||||
case EcCurve.P_256 -> res = "secp256r1";
|
||||
case EcCurve.P_384 -> res = "secp384r1";
|
||||
case EcCurve.P_521 -> res = "secp521r1";
|
||||
default -> throw new IllegalArgumentException("unknown curve");
|
||||
}
|
||||
return res;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
package io.github.a13e300.tricky_store.keystore;
|
||||
|
||||
import android.system.keystore2.KeyEntryResponse;
|
||||
import android.system.keystore2.KeyMetadata;
|
||||
import android.util.Log;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.security.cert.Certificate;
|
||||
import java.security.cert.CertificateException;
|
||||
import java.security.cert.CertificateFactory;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Iterator;
|
||||
|
||||
public class Utils {
|
||||
private final static String TAG = "Utils";
|
||||
static X509Certificate toCertificate(byte[] bytes) {
|
||||
try {
|
||||
final CertificateFactory certFactory = CertificateFactory.getInstance("X.509");
|
||||
return (X509Certificate) certFactory.generateCertificate(
|
||||
new ByteArrayInputStream(bytes));
|
||||
} catch (CertificateException e) {
|
||||
Log.w(TAG, "Couldn't parse certificate in keystore", e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private static Collection<X509Certificate> toCertificates(byte[] bytes) {
|
||||
try {
|
||||
final CertificateFactory certFactory = CertificateFactory.getInstance("X.509");
|
||||
return (Collection<X509Certificate>) certFactory.generateCertificates(
|
||||
new ByteArrayInputStream(bytes));
|
||||
} catch (CertificateException e) {
|
||||
Log.w(TAG, "Couldn't parse certificates in keystore", e);
|
||||
return new ArrayList<>();
|
||||
}
|
||||
}
|
||||
|
||||
public static Certificate[] getCertificateChain(KeyEntryResponse response) {
|
||||
if (response == null || response.metadata.certificate == null) return null;
|
||||
var leaf = toCertificate(response.metadata.certificate);
|
||||
Certificate[] chain;
|
||||
if (response.metadata.certificateChain != null) {
|
||||
var certs = toCertificates(response.metadata.certificateChain);
|
||||
chain = new Certificate[certs.size() + 1];
|
||||
final Iterator<X509Certificate> it = certs.iterator();
|
||||
int i = 1;
|
||||
while (it.hasNext()) {
|
||||
chain[i++] = it.next();
|
||||
}
|
||||
} else {
|
||||
chain = new Certificate[1];
|
||||
}
|
||||
chain[0] = leaf;
|
||||
return chain;
|
||||
}
|
||||
|
||||
public static void putCertificateChain(KeyEntryResponse response, Certificate[] chain) throws Throwable {
|
||||
putCertificateChain(response.metadata, chain);
|
||||
}
|
||||
|
||||
public static void putCertificateChain(KeyMetadata metadata, Certificate[] chain) throws Throwable {
|
||||
if (chain == null || chain.length == 0) return;
|
||||
metadata.certificate = chain[0].getEncoded();
|
||||
var output = new ByteArrayOutputStream();
|
||||
for (int i = 1; i < chain.length; i++) {
|
||||
output.write(chain[i].getEncoded());
|
||||
}
|
||||
metadata.certificateChain = output.toByteArray();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
package io.github.a13e300.tricky_store.keystore;
|
||||
|
||||
import org.xmlpull.v1.XmlPullParser;
|
||||
import org.xmlpull.v1.XmlPullParserException;
|
||||
import org.xmlpull.v1.XmlPullParserFactory;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.StringReader;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public class XMLParser {
|
||||
|
||||
private final String xml;
|
||||
|
||||
public XMLParser(String xml) {
|
||||
this.xml = xml;
|
||||
}
|
||||
|
||||
public Map<String, String> obtainPath(String path) throws Exception {
|
||||
XmlPullParserFactory xmlFactoryObject = XmlPullParserFactory.newInstance();
|
||||
XmlPullParser parser = xmlFactoryObject.newPullParser();
|
||||
parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, false);
|
||||
parser.setInput(new StringReader(xml));
|
||||
|
||||
String[] tags = path.split("\\.");
|
||||
|
||||
return readData(parser, tags, 0, new HashMap<>());
|
||||
}
|
||||
|
||||
private Map<String, String> readData(XmlPullParser parser, String[] tags, int index,
|
||||
Map<String, Integer> tagCounts) throws IOException, XmlPullParserException {
|
||||
while (parser.next() != XmlPullParser.END_DOCUMENT) {
|
||||
if (parser.getEventType() != XmlPullParser.START_TAG) {
|
||||
continue;
|
||||
}
|
||||
|
||||
String name = parser.getName();
|
||||
|
||||
if (name.equals(tags[index].split("\\[")[0])) {
|
||||
|
||||
String[] tagParts = tags[index].split("\\[");
|
||||
if (tagParts.length > 1) {
|
||||
if (tagCounts.getOrDefault(name, 0) < Integer.parseInt(tagParts[1].replace("]", ""))) {
|
||||
tagCounts.put(name, tagCounts.getOrDefault(name, 0) + 1);
|
||||
return readData(parser, tags, index, tagCounts);
|
||||
} else {
|
||||
if (index == tags.length - 1) {
|
||||
return readAttributes(parser);
|
||||
} else {
|
||||
return readData(parser, tags, index + 1, tagCounts);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (index == tags.length - 1) {
|
||||
return readAttributes(parser);
|
||||
} else {
|
||||
return readData(parser, tags, index + 1, tagCounts);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
skip(parser);
|
||||
}
|
||||
}
|
||||
|
||||
throw new XmlPullParserException("Path not found");
|
||||
}
|
||||
|
||||
private Map<String, String> readAttributes(XmlPullParser parser) throws IOException, XmlPullParserException {
|
||||
Map<String, String> attributes = new HashMap<>();
|
||||
for (int i = 0; i < parser.getAttributeCount(); i++) {
|
||||
attributes.put(parser.getAttributeName(i), parser.getAttributeValue(i));
|
||||
}
|
||||
if (parser.next() == XmlPullParser.TEXT) {
|
||||
attributes.put("text", parser.getText());
|
||||
}
|
||||
return attributes;
|
||||
}
|
||||
|
||||
private void skip(XmlPullParser parser) throws XmlPullParserException, IOException {
|
||||
if (parser.getEventType() != XmlPullParser.START_TAG) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
int depth = 1;
|
||||
while (depth != 0) {
|
||||
switch (parser.next()) {
|
||||
case XmlPullParser.END_TAG:
|
||||
depth--;
|
||||
break;
|
||||
case XmlPullParser.START_TAG:
|
||||
depth++;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
package io.github.a13e300.tricky_store
|
||||
|
||||
import android.content.pm.IPackageManager
|
||||
import android.os.Build
|
||||
import android.os.SystemProperties
|
||||
import java.util.concurrent.ThreadLocalRandom
|
||||
|
||||
fun getTransactCode(clazz: Class<*>, method: String) =
|
||||
clazz.getDeclaredField("TRANSACTION_$method").apply { isAccessible = true }
|
||||
.getInt(null) // 2
|
||||
|
||||
val bootHash by lazy {
|
||||
getBootHashFromProp() ?: randomBytes()
|
||||
}
|
||||
|
||||
// TODO: get verified boot keys
|
||||
val bootKey by lazy {
|
||||
randomBytes()
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalStdlibApi::class)
|
||||
fun getBootHashFromProp(): ByteArray? {
|
||||
val b = SystemProperties.get("ro.boot.vbmeta.digest", null) ?: return null
|
||||
if (b.length != 64) return null
|
||||
return b.hexToByteArray()
|
||||
}
|
||||
|
||||
fun randomBytes() = ByteArray(32).also { ThreadLocalRandom.current().nextBytes(it) }
|
||||
|
||||
val patchLevel by lazy {
|
||||
Build.VERSION.SECURITY_PATCH.convertPatchLevel(false)
|
||||
}
|
||||
|
||||
val patchLevelLong by lazy {
|
||||
Build.VERSION.SECURITY_PATCH.convertPatchLevel(true)
|
||||
}
|
||||
|
||||
// FIXME
|
||||
val osVersion by lazy {
|
||||
when (Build.VERSION.SDK_INT) {
|
||||
Build.VERSION_CODES.UPSIDE_DOWN_CAKE -> 140000
|
||||
Build.VERSION_CODES.TIRAMISU -> 130000
|
||||
Build.VERSION_CODES.S_V2 -> 120100
|
||||
Build.VERSION_CODES.S -> 120000
|
||||
else -> 0
|
||||
}
|
||||
}
|
||||
|
||||
fun String.convertPatchLevel(long: Boolean) = kotlin.runCatching {
|
||||
val l = split("-")
|
||||
if (long) l[0].toInt() * 10000 + l[1].toInt() * 100 + l[2].toInt()
|
||||
else l[0].toInt() * 100 + l[1].toInt()
|
||||
}.onFailure { Logger.e("invalid patch level $this !", it) }.getOrDefault(202404)
|
||||
|
||||
fun IPackageManager.getPackageInfoCompat(name: String, flags: Long, userId: Int) =
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
getPackageInfo(name, flags, userId)
|
||||
} else {
|
||||
getPackageInfo(name, flags.toInt(), userId)
|
||||
}
|
||||
|
||||
fun String.trimLine() = trim().split("\n").joinToString("\n") { it.trim() }
|
||||
@@ -0,0 +1,20 @@
|
||||
pluginManagement {
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
gradlePluginPortal()
|
||||
}
|
||||
}
|
||||
|
||||
dependencyResolutionManagement {
|
||||
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
}
|
||||
}
|
||||
|
||||
rootProject.name = "TrickyStore"
|
||||
include(":module")
|
||||
include(":service")
|
||||
include(":stub")
|
||||
@@ -0,0 +1 @@
|
||||
/build
|
||||
@@ -0,0 +1,29 @@
|
||||
plugins {
|
||||
alias(libs.plugins.android.library)
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = "io.github.a13e300.stub"
|
||||
defaultConfig {
|
||||
consumerProguardFiles("consumer-rules.pro")
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
release {
|
||||
isMinifyEnabled = false
|
||||
proguardFiles(
|
||||
getDefaultProguardFile("proguard-android-optimize.txt"),
|
||||
"proguard-rules.pro"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
lint {
|
||||
checkReleaseBuilds = false
|
||||
abortOnError = true
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compileOnly(libs.annotation)
|
||||
}
|
||||
Vendored
+21
@@ -0,0 +1,21 @@
|
||||
# Add project specific ProGuard rules here.
|
||||
# You can control the set of applied configuration files using the
|
||||
# proguardFiles setting in build.gradle.
|
||||
#
|
||||
# For more details, see
|
||||
# http://developer.android.com/guide/developing/tools/proguard.html
|
||||
|
||||
# If your project uses WebView with JS, uncomment the following
|
||||
# and specify the fully qualified class name to the JavaScript interface
|
||||
# class:
|
||||
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
|
||||
# public *;
|
||||
#}
|
||||
|
||||
# Uncomment this to preserve the line number information for
|
||||
# debugging stack traces.
|
||||
#-keepattributes SourceFile,LineNumberTable
|
||||
|
||||
# If you keep the line number information, uncomment this to
|
||||
# hide the original source file name.
|
||||
#-renamesourcefileattribute SourceFile
|
||||
@@ -0,0 +1,2 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest/>
|
||||
@@ -0,0 +1,17 @@
|
||||
package android.content.pm;
|
||||
|
||||
import android.os.IBinder;
|
||||
|
||||
public interface IPackageManager {
|
||||
String[] getPackagesForUid(int uid);
|
||||
|
||||
PackageInfo getPackageInfo(String packageName, long flags, int userId);
|
||||
|
||||
PackageInfo getPackageInfo(String packageName, int flags, int userId);
|
||||
|
||||
class Stub {
|
||||
public static IPackageManager asInterface(IBinder binder) {
|
||||
throw new RuntimeException("");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package android.hardware.security.keymint;
|
||||
|
||||
public @interface Algorithm {
|
||||
int AES = 32;
|
||||
int EC = 3;
|
||||
int HMAC = 128;
|
||||
int RSA = 1;
|
||||
int TRIPLE_DES = 33;
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package android.hardware.security.keymint;
|
||||
|
||||
/* loaded from: classes2.dex */
|
||||
public @interface EcCurve {
|
||||
public static final int CURVE_25519 = 4;
|
||||
public static final int P_224 = 0;
|
||||
public static final int P_256 = 1;
|
||||
public static final int P_384 = 2;
|
||||
public static final int P_521 = 3;
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package android.hardware.security.keymint;
|
||||
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
public class KeyParameter implements Parcelable {
|
||||
public static final Creator<KeyParameter> CREATOR = new Creator<>() {
|
||||
@Override
|
||||
public KeyParameter createFromParcel(Parcel in) {
|
||||
throw new RuntimeException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public KeyParameter[] newArray(int size) {
|
||||
throw new RuntimeException();
|
||||
}
|
||||
};
|
||||
public int tag = 0;
|
||||
public KeyParameterValue value;
|
||||
|
||||
@Override
|
||||
public int describeContents() {
|
||||
throw new RuntimeException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeToParcel(@NonNull Parcel parcel, int i) {
|
||||
throw new RuntimeException();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,239 @@
|
||||
package android.hardware.security.keymint;
|
||||
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
/* loaded from: classes2.dex */
|
||||
public final class KeyParameterValue implements Parcelable {
|
||||
public static final int algorithm = 1;
|
||||
public static final int blob = 14;
|
||||
public static final int blockMode = 2;
|
||||
public static final int boolValue = 10;
|
||||
public static final int dateTime = 13;
|
||||
public static final int digest = 4;
|
||||
public static final int ecCurve = 5;
|
||||
public static final int hardwareAuthenticatorType = 8;
|
||||
public static final int integer = 11;
|
||||
public static final int invalid = 0;
|
||||
public static final int keyPurpose = 7;
|
||||
public static final int longInteger = 12;
|
||||
public static final int origin = 6;
|
||||
public static final int paddingMode = 3;
|
||||
public static final int securityLevel = 9;
|
||||
public static final Creator<KeyParameterValue> CREATOR = new Creator<KeyParameterValue>() {
|
||||
@Override
|
||||
public KeyParameterValue createFromParcel(Parcel in) {
|
||||
throw new RuntimeException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public KeyParameterValue[] newArray(int size) {
|
||||
throw new RuntimeException();
|
||||
}
|
||||
};
|
||||
|
||||
public KeyParameterValue() {
|
||||
throw new RuntimeException();
|
||||
}
|
||||
|
||||
protected KeyParameterValue(Parcel in) {
|
||||
throw new RuntimeException();
|
||||
}
|
||||
|
||||
public static KeyParameterValue invalid(int _value) {
|
||||
throw new RuntimeException();
|
||||
}
|
||||
|
||||
public static KeyParameterValue algorithm(int _value) {
|
||||
throw new RuntimeException();
|
||||
}
|
||||
|
||||
public static KeyParameterValue blockMode(int _value) {
|
||||
throw new RuntimeException();
|
||||
}
|
||||
|
||||
public static KeyParameterValue paddingMode(int _value) {
|
||||
throw new RuntimeException();
|
||||
}
|
||||
|
||||
public static KeyParameterValue digest(int _value) {
|
||||
throw new RuntimeException();
|
||||
}
|
||||
|
||||
public static KeyParameterValue ecCurve(int _value) {
|
||||
throw new RuntimeException();
|
||||
}
|
||||
|
||||
public static KeyParameterValue origin(int _value) {
|
||||
throw new RuntimeException();
|
||||
}
|
||||
|
||||
public static KeyParameterValue keyPurpose(int _value) {
|
||||
throw new RuntimeException();
|
||||
}
|
||||
|
||||
public static KeyParameterValue hardwareAuthenticatorType(int _value) {
|
||||
throw new RuntimeException();
|
||||
}
|
||||
|
||||
public static KeyParameterValue securityLevel(int _value) {
|
||||
throw new RuntimeException();
|
||||
}
|
||||
|
||||
public static KeyParameterValue boolValue(boolean _value) {
|
||||
throw new RuntimeException();
|
||||
}
|
||||
|
||||
public static KeyParameterValue integer(int _value) {
|
||||
throw new RuntimeException();
|
||||
}
|
||||
|
||||
public static KeyParameterValue longInteger(long _value) {
|
||||
throw new RuntimeException();
|
||||
}
|
||||
|
||||
public static KeyParameterValue dateTime(long _value) {
|
||||
throw new RuntimeException();
|
||||
}
|
||||
|
||||
public static KeyParameterValue blob(byte[] _value) {
|
||||
throw new RuntimeException();
|
||||
}
|
||||
|
||||
public int getTag() {
|
||||
throw new RuntimeException();
|
||||
}
|
||||
|
||||
public int getInvalid() {
|
||||
throw new RuntimeException();
|
||||
}
|
||||
|
||||
public void setInvalid(int _value) {
|
||||
throw new RuntimeException();
|
||||
}
|
||||
|
||||
public int getAlgorithm() {
|
||||
throw new RuntimeException();
|
||||
}
|
||||
|
||||
public void setAlgorithm(int _value) {
|
||||
throw new RuntimeException();
|
||||
}
|
||||
|
||||
public int getBlockMode() {
|
||||
throw new RuntimeException();
|
||||
}
|
||||
|
||||
public void setBlockMode(int _value) {
|
||||
throw new RuntimeException();
|
||||
}
|
||||
|
||||
public int getPaddingMode() {
|
||||
throw new RuntimeException();
|
||||
}
|
||||
|
||||
public void setPaddingMode(int _value) {
|
||||
throw new RuntimeException();
|
||||
}
|
||||
|
||||
public int getDigest() {
|
||||
throw new RuntimeException();
|
||||
}
|
||||
|
||||
public void setDigest(int _value) {
|
||||
throw new RuntimeException();
|
||||
}
|
||||
|
||||
public int getEcCurve() {
|
||||
throw new RuntimeException();
|
||||
}
|
||||
|
||||
public void setEcCurve(int _value) {
|
||||
throw new RuntimeException();
|
||||
}
|
||||
|
||||
public int getOrigin() {
|
||||
throw new RuntimeException();
|
||||
}
|
||||
|
||||
public void setOrigin(int _value) {
|
||||
throw new RuntimeException();
|
||||
}
|
||||
|
||||
public int getKeyPurpose() {
|
||||
throw new RuntimeException();
|
||||
}
|
||||
|
||||
public void setKeyPurpose(int _value) {
|
||||
throw new RuntimeException();
|
||||
}
|
||||
|
||||
public int getHardwareAuthenticatorType() {
|
||||
throw new RuntimeException();
|
||||
}
|
||||
|
||||
public void setHardwareAuthenticatorType(int _value) {
|
||||
throw new RuntimeException();
|
||||
}
|
||||
|
||||
public int getSecurityLevel() {
|
||||
throw new RuntimeException();
|
||||
}
|
||||
|
||||
public void setSecurityLevel(int _value) {
|
||||
throw new RuntimeException();
|
||||
}
|
||||
|
||||
public boolean getBoolValue() {
|
||||
throw new RuntimeException();
|
||||
}
|
||||
|
||||
public void setBoolValue(boolean _value) {
|
||||
throw new RuntimeException();
|
||||
}
|
||||
|
||||
public int getInteger() {
|
||||
throw new RuntimeException();
|
||||
}
|
||||
|
||||
public void setInteger(int _value) {
|
||||
throw new RuntimeException();
|
||||
}
|
||||
|
||||
public long getLongInteger() {
|
||||
throw new RuntimeException();
|
||||
}
|
||||
|
||||
public void setLongInteger(long _value) {
|
||||
throw new RuntimeException();
|
||||
}
|
||||
|
||||
public long getDateTime() {
|
||||
throw new RuntimeException();
|
||||
}
|
||||
|
||||
public void setDateTime(long _value) {
|
||||
throw new RuntimeException();
|
||||
}
|
||||
|
||||
public byte[] getBlob() {
|
||||
throw new RuntimeException();
|
||||
}
|
||||
|
||||
public void setBlob(byte[] _value) {
|
||||
throw new RuntimeException();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public int describeContents() {
|
||||
throw new RuntimeException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeToParcel(@NonNull Parcel parcel, int i) {
|
||||
throw new RuntimeException();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package android.hardware.security.keymint;
|
||||
|
||||
public @interface KeyPurpose {
|
||||
int AGREE_KEY = 6;
|
||||
int ATTEST_KEY = 7;
|
||||
int DECRYPT = 1;
|
||||
int ENCRYPT = 0;
|
||||
int SIGN = 2;
|
||||
int VERIFY = 3;
|
||||
int WRAP_KEY = 5;
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package android.hardware.security.keymint;
|
||||
|
||||
public @interface SecurityLevel {
|
||||
int KEYSTORE = 100;
|
||||
int SOFTWARE = 0;
|
||||
int STRONGBOX = 2;
|
||||
int TRUSTED_ENVIRONMENT = 1;
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
package android.hardware.security.keymint;
|
||||
|
||||
public @interface Tag {
|
||||
int ACTIVE_DATETIME = 1610613136;
|
||||
int ALGORITHM = 268435458;
|
||||
int ALLOW_WHILE_ON_BODY = 1879048698;
|
||||
int APPLICATION_DATA = -1879047492;
|
||||
int APPLICATION_ID = -1879047591;
|
||||
int ASSOCIATED_DATA = -1879047192;
|
||||
int ATTESTATION_APPLICATION_ID = -1879047483;
|
||||
int ATTESTATION_CHALLENGE = -1879047484;
|
||||
int ATTESTATION_ID_BRAND = -1879047482;
|
||||
int ATTESTATION_ID_DEVICE = -1879047481;
|
||||
int ATTESTATION_ID_IMEI = -1879047478;
|
||||
int ATTESTATION_ID_MANUFACTURER = -1879047476;
|
||||
int ATTESTATION_ID_MEID = -1879047477;
|
||||
int ATTESTATION_ID_MODEL = -1879047475;
|
||||
int ATTESTATION_ID_PRODUCT = -1879047480;
|
||||
int ATTESTATION_ID_SECOND_IMEI = -1879047469;
|
||||
int ATTESTATION_ID_SERIAL = -1879047479;
|
||||
int AUTH_TIMEOUT = 805306873;
|
||||
int BLOCK_MODE = 536870916;
|
||||
int BOOTLOADER_ONLY = 1879048494;
|
||||
int BOOT_PATCHLEVEL = 805307087;
|
||||
int CALLER_NONCE = 1879048199;
|
||||
int CERTIFICATE_NOT_AFTER = 1610613745;
|
||||
int CERTIFICATE_NOT_BEFORE = 1610613744;
|
||||
int CERTIFICATE_SERIAL = -2147482642;
|
||||
int CERTIFICATE_SUBJECT = -1879047185;
|
||||
int CONFIRMATION_TOKEN = -1879047187;
|
||||
int CREATION_DATETIME = 1610613437;
|
||||
int DEVICE_UNIQUE_ATTESTATION = 1879048912;
|
||||
int DIGEST = 536870917;
|
||||
int EARLY_BOOT_ONLY = 1879048497;
|
||||
int EC_CURVE = 268435466;
|
||||
int HARDWARE_TYPE = 268435760;
|
||||
int IDENTITY_CREDENTIAL_KEY = 1879048913;
|
||||
int INCLUDE_UNIQUE_ID = 1879048394;
|
||||
int INVALID = 0;
|
||||
int KEY_SIZE = 805306371;
|
||||
int MAC_LENGTH = 805307371;
|
||||
int MAX_BOOT_LEVEL = 805307378;
|
||||
int MAX_USES_PER_BOOT = 805306772;
|
||||
int MIN_MAC_LENGTH = 805306376;
|
||||
int MIN_SECONDS_BETWEEN_OPS = 805306771;
|
||||
int NONCE = -1879047191;
|
||||
int NO_AUTH_REQUIRED = 1879048695;
|
||||
int ORIGIN = 268436158;
|
||||
int ORIGINATION_EXPIRE_DATETIME = 1610613137;
|
||||
int OS_PATCHLEVEL = 805307074;
|
||||
int OS_VERSION = 805307073;
|
||||
int PADDING = 536870918;
|
||||
int PURPOSE = 536870913;
|
||||
int RESET_SINCE_ID_ROTATION = 1879049196;
|
||||
int ROLLBACK_RESISTANCE = 1879048495;
|
||||
int ROOT_OF_TRUST = -1879047488;
|
||||
int RSA_OAEP_MGF_DIGEST = 536871115;
|
||||
int RSA_PUBLIC_EXPONENT = 1342177480;
|
||||
int STORAGE_KEY = 1879048914;
|
||||
int TRUSTED_CONFIRMATION_REQUIRED = 1879048700;
|
||||
int TRUSTED_USER_PRESENCE_REQUIRED = 1879048699;
|
||||
int UNIQUE_ID = -1879047485;
|
||||
int UNLOCKED_DEVICE_REQUIRED = 1879048701;
|
||||
int USAGE_COUNT_LIMIT = 805306773;
|
||||
int USAGE_EXPIRE_DATETIME = 1610613138;
|
||||
int USER_AUTH_TYPE = 268435960;
|
||||
int USER_ID = 805306869;
|
||||
int USER_SECURE_ID = -1610612234;
|
||||
int VENDOR_PATCHLEVEL = 805307086;
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package android.os;
|
||||
|
||||
public class ServiceManager {
|
||||
public static IBinder getService(String name) {
|
||||
throw new UnsupportedOperationException("STUB!");
|
||||
}
|
||||
|
||||
public static void addService(String name, IBinder binder) {
|
||||
throw new UnsupportedOperationException("STUB!");
|
||||
}
|
||||
|
||||
public static IBinder checkService(String name) {
|
||||
throw new UnsupportedOperationException("STUB!");
|
||||
}
|
||||
|
||||
public static String[] listServices() {
|
||||
throw new UnsupportedOperationException("STUB!");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package android.os;
|
||||
|
||||
public class SystemProperties {
|
||||
public static String get(String key, String def) {
|
||||
throw new RuntimeException("");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package android.system.keystore2;
|
||||
|
||||
import android.hardware.security.keymint.KeyParameter;
|
||||
|
||||
public class Authorization {
|
||||
public KeyParameter keyParameter;
|
||||
public int securityLevel = 0;
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package android.system.keystore2;
|
||||
|
||||
import android.hardware.security.keymint.KeyParameter;
|
||||
import android.os.IBinder;
|
||||
import android.os.IInterface;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
public interface IKeystoreSecurityLevel extends IInterface {
|
||||
String DESCRIPTOR = "android.system.keystore2.IKeystoreSecurityLevel";
|
||||
|
||||
KeyMetadata generateKey(KeyDescriptor key, @Nullable KeyDescriptor attestationKey,
|
||||
KeyParameter[] params, int flags, byte[] entropy);
|
||||
|
||||
class Stub {
|
||||
public static IKeystoreSecurityLevel asInterface(IBinder b) {
|
||||
throw new RuntimeException();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package android.system.keystore2;
|
||||
|
||||
import android.os.IBinder;
|
||||
|
||||
public interface IKeystoreService {
|
||||
String DESCRIPTOR = "android.system.keystore2.IKeystoreService";
|
||||
|
||||
IKeystoreSecurityLevel getSecurityLevel(int securityLevel);
|
||||
|
||||
class Stub {
|
||||
public static IKeystoreService asInterface(IBinder b) {
|
||||
throw new RuntimeException("");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package android.system.keystore2;
|
||||
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
public class KeyDescriptor implements Parcelable {
|
||||
public String alias;
|
||||
public byte[] blob;
|
||||
public int domain = 0;
|
||||
public long nspace = 0;
|
||||
|
||||
public static final Creator<KeyDescriptor> CREATOR = new Creator<KeyDescriptor>() {
|
||||
@Override
|
||||
public KeyDescriptor createFromParcel(Parcel in) {
|
||||
throw new RuntimeException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public KeyDescriptor[] newArray(int size) {
|
||||
throw new RuntimeException();
|
||||
}
|
||||
};
|
||||
|
||||
@Override
|
||||
public int describeContents() {
|
||||
throw new RuntimeException("");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeToParcel(@NonNull Parcel parcel, int i) {
|
||||
throw new RuntimeException("");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package android.system.keystore2;
|
||||
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
public class KeyEntryResponse implements Parcelable {
|
||||
public IKeystoreSecurityLevel iSecurityLevel;
|
||||
public KeyMetadata metadata;
|
||||
|
||||
public static final Creator<KeyEntryResponse> CREATOR = new Creator<KeyEntryResponse>() {
|
||||
@Override
|
||||
public KeyEntryResponse createFromParcel(Parcel in) {
|
||||
throw new RuntimeException("");
|
||||
}
|
||||
|
||||
@Override
|
||||
public KeyEntryResponse[] newArray(int size) {
|
||||
throw new RuntimeException("");
|
||||
}
|
||||
};
|
||||
|
||||
@Override
|
||||
public int describeContents() {
|
||||
throw new RuntimeException("");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeToParcel(@NonNull Parcel parcel, int i) {
|
||||
throw new RuntimeException("");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package android.system.keystore2;
|
||||
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
public class KeyMetadata implements Parcelable {
|
||||
public Authorization[] authorizations;
|
||||
public byte[] certificate;
|
||||
public byte[] certificateChain;
|
||||
public KeyDescriptor key;
|
||||
public int keySecurityLevel = 0;
|
||||
public long modificationTimeMs = 0;
|
||||
|
||||
public static final Creator<KeyMetadata> CREATOR = new Creator<KeyMetadata>() {
|
||||
@Override
|
||||
public KeyMetadata createFromParcel(Parcel in) {
|
||||
throw new RuntimeException("");
|
||||
}
|
||||
|
||||
@Override
|
||||
public KeyMetadata[] newArray(int size) {
|
||||
throw new RuntimeException("");
|
||||
}
|
||||
};
|
||||
|
||||
@Override
|
||||
public int describeContents() {
|
||||
throw new RuntimeException("");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeToParcel(@NonNull Parcel parcel, int i) {
|
||||
throw new RuntimeException("");
|
||||
}
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
{
|
||||
"versionCode": 128,
|
||||
"version": "v1.1.3",
|
||||
"zipUrl": "https://github.com/5ec1cff/TrickyStore/releases/download/1.1.3/Tricky-Store-v1.1.3-128-820c2b2-release.zip",
|
||||
"changelog": "https://github.com/5ec1cff/TrickyStore/raw/release/changelog.md"
|
||||
}
|
||||
Reference in New Issue
Block a user