10 Commits

Author SHA1 Message Date
5ec1cff 26b274cf58 fix typo 2025-06-21 15:15:32 +08:00
5ec1cff 140e212ebc 1.3.0 2025-06-18 20:17:54 +08:00
5ec1cff 1d92796ed1 fix 1.2.1 url 2025-02-07 14:04:39 +08:00
5ec1cff 50e3ce31f6 1.2.1 2025-02-07 13:40:54 +08:00
5ec1cff 86b7eb1a8b Update update.json 2024-10-28 22:04:11 +08:00
5ec1cff 29a22a461c 1.2.0 2024-10-28 22:00:12 +08:00
5ec1cff df6d697e0f 1.1.3 2024-08-26 00:08:09 +08:00
5ec1cff edc6cdab08 1.1.2 2024-08-23 14:52:24 +08:00
5ec1cff fe9cc9681f 1.1.1 2024-08-21 15:23:58 +08:00
5ec1cff 9b28661fee release 2024-08-18 23:24:47 +08:00
95 changed files with 220 additions and 8239 deletions
-66
View File
@@ -1,66 +0,0 @@
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
View File
@@ -1,10 +0,0 @@
*.iml
.gradle
/local.properties
/.idea
.DS_Store
/build
/captures
.externalNativeBuild
.cxx
local.properties
-6
View File
@@ -1,6 +0,0 @@
[submodule "module/src/main/cpp/external/LSPlt"]
path = module/src/main/cpp/external/LSPlt
url = https://github.com/LSPosed/LSPlt
[submodule "module/src/main/cpp/external/glaze"]
path = module/src/main/cpp/external/glaze
url = https://github.com/stephenberry/glaze
+49 -43
View File
@@ -1,14 +1,24 @@
# Tricky Store
A trick of keystore. **Android 12 or above is required**.
A trick of keystore. **Android 10 or above is required**.
This module is used for modifying the certificate chain generated for android key attestation.
[中文 README](README.zh-CN.md)
## 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.
## Usage
1. Flash this module and reboot.
2. Put keybox.xml to /data/adb/tricky_store/keybox.xml .
3. Put target packages to /data/adb/tricky_store/target.txt (Optional).
2. For more than DEVICE integrity, put an unrevoked hardware keybox.xml at `/data/adb/tricky_store/keybox.xml` (Optional).
3. Customize target packages at `/data/adb/tricky_store/target.txt` (Optional).
4. Enjoy!
**All configuration files will take effect immediately.**
## keybox.xml
format:
@@ -38,62 +48,58 @@ format:
</AndroidAttestation>
```
## Build Vars Spoofing
> **Zygisk (or Zygisk Next) is needed for this feature to work.**
If you can not pass strong integrity, you can try to enable build vars spoofing
by creating a file `/data/adb/tricky_store/spoof_build_vars`.
After doing that, TrickyStore will automatically generate an example config json
inside `/data/adb/tricky_store/spoof_build_vars` on next reboot. Then you can manually
edit your spoof config.
Here is an example of spoof config:
```json
{
"manufacturer": "Google",
"model": "Pixel",
"fingerprint": "google/sailfish/sailfish:8.1.0/OPM1.171019.011/4448085:user/release-keys",
"brand": "google",
"product": "sailfish",
"device": "sailfish",
"release": "8.1.0",
"id": "OPM1.171019.011",
"incremental": "4448085",
"security_patch": "2017-12-05",
"type": "user",
"tags": "release-keys"
}
```
## Support TEE broken devices
TrickyStore will hack leaf certificate by default.
On TEE broken devices, this will not work because we can't retrieve leaf certificate from TEE.
You can add a `!` after package name to enable certificate generate support 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.
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.
For example:
```
# target.txt
# use leaf certificate hacking mode for KeyAttestation App
# use auto mode for KeyAttestation App
io.github.vvb2060.keyattestation
# use certificate generating mode for gms
# always use leaf hack mode
io.github.vvb2060.mahoshojo?
# always use certificate generating mode for gms
com.google.android.gms!
```
## Known issue
## Customize security patch level (1.2.1+)
- App Attest Key is not supported.
- StrongBox is not supported.
Create the file `/data/adb/tricky_store/security_patch.txt`.
Simple:
```
# Hack os/vendor/boot security patch level
20241101
```
Advanced:
```
# os security patch level is 202411
system=202411
# do not hack boot patch level
boot=no
# vendor patch level is 20241101 (another format)
vendor=2024-11-01
# default value
# all=20241101
# keep consistent with system prop
# system=prop
```
Note: this feature will only hack the result of KeyAttestation, it will not do resetprop, you need do it yourself.
## 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)
- [LSPosed](https://github.com/LSPosed/LSPosed)
- [Glaze](https://github.com/stephenberry/glaze)
+105
View File
@@ -0,0 +1,105 @@
# Tricky Store
**支持 Android 10 及以上版本**.
该模块用于修改 Android Keystore 生成的 Android KeyAttestation 证书链。
[中文 README](README.zh-CN.md)
## 停止开源
考虑到二改泛滥,且开源后获得的贡献少于预期,因此本模块自 1.1.0 版本起闭源发布。
## 用法
1. 刷入模块并重启。
2. For more than DEVICE integrity, put an unrevoked hardware keybox.xml at `/data/adb/tricky_store/keybox.xml` (可选)。
3.`/data/adb/tricky_store/target.txt` 自定义修改生效的应用包名(可选) 。
4. 大功告成!
**所有配置会立即生效**
## keybox.xml
format:
```xml
<?xml version="1.0"?>
<AndroidAttestation>
<NumberOfKeyboxes>1</NumberOfKeyboxes>
<Keybox DeviceID="...">
<Key algorithm="ecdsa|rsa">
<PrivateKey format="pem">
-----BEGIN EC PRIVATE KEY-----
...
-----END EC PRIVATE KEY-----
</PrivateKey>
<CertificateChain>
<NumberOfCertificates>...</NumberOfCertificates>
<Certificate format="pem">
-----BEGIN CERTIFICATE-----
...
-----END CERTIFICATE-----
</Certificate>
... more certificates
</CertificateChain>
</Key>...
</Keybox>
</AndroidAttestation>
```
## 支持 TEE 损坏的设备
TrickyStore 默认采用修改来自 TEE 的叶证书的方式。
这在 TEE 损坏的设备上无法工作,因为 TEE 无法提供证书链。
因此,TrickyStore 会自动切换到生成证书链模式。
在 target.txt 中,在包名后添加一个 `!` 可以强制使用生成证书链模式。
添加 `?` 到包名后可强制使用修改证书链模式。如无后缀则自动选择。
例子
```
# target.txt
# 对 KeyAttestation App 使用自动模式
io.github.vvb2060.keyattestation
# 对 momo 使用修改证书链模式
io.github.vvb2060.mahoshojo?
# 对 gms 使用生成证书链模式
com.google.android.gms!
```
## 自定义安全补丁级别(1.2.1+)
配置文件 `/data/adb/tricky_store/security_patch.txt`
简易:
```
# 修改 os/vendor/boot 的安全补丁级别
20241101
```
高级:
```
# os 安全补丁级别为 202411
system=202411
# 不要修改 boot 安全补丁级别
boot=no
# vendor 安全补丁级别 20241101 (使用了另一种格式)
vendor=2024-11-01
# 默认值
# all=20241101
# system 安全补丁级别与系统属性一致
# system=prop
```
注意:该功能仅修改 KeyAttestation 返回的结果,不会重置系统属性。
## Acknowledgement
- [FrameworkPatch](https://github.com/chiteroman/FrameworkPatch)
- [BootloaderSpoofer](https://github.com/chiteroman/BootloaderSpoofer)
- [KeystoreInjection](https://github.com/aviraxp/Zygisk-KeystoreInjection)
- [LSPosed](https://github.com/LSPosed/LSPosed)
+1
View File
@@ -0,0 +1 @@
theme: jekyll-theme-slate
-99
View File
@@ -1,99 +0,0 @@
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 verName by extra("v1.0.2")
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
}
}
}
+59
View File
@@ -0,0 +1,59 @@
# 1.3.0
- 支持 KeyMint 4.0 新增的 moduleHash 字段
- 支持 Android 16
- 修复偶发注入失败的问题
- 将 Play 商店加入默认作用列表
- 修复大量证书链生成问题
---
- Support for the new moduleHash field introduced in KeyMint 4.0
- Compatibility with Android 16
- Fixed occasional injection failures
- Added Play Store to the default scope list
- Resolved numerous certificate chain generation issues
# 1.2.1
支持自定义安全补丁级别(请参见 README.md)
---
Support customizing security patch level (please refer to README.md)
# 1.2.0
修复注入失败的问题
修复安装失败的问题
修复 cert hack 下报错的问题
---
Fixed the injection failure issue
Fixed the installation failure issue
Fixed the error issue under cert hack
# 1.2.0-RC2
修改叶证书模式同时会修改安全等级与信任根为非软件
修复缺失的 osVersion 字段
---
Leaf hack mode will also change the security level and root of trust to non-software based
Fix missing osVersion field
# 1.2.0-RC1
初步支持 Android 10-11 (感谢 @N-X-T
自动模式会检测是否支持硬件加密
修复模块损坏问题
修复证书签名算法选择的问题
---
Add initial support for Android 10-11 (Thanks @N-X-T )
Auto mode will detect if hardware encryption is supported
Fix issue that module may be corrupted
Fix issue with certificate signature algorithm selection
-19
View File
@@ -1,19 +0,0 @@
# 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
-16
View File
@@ -1,16 +0,0 @@
[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" }
Binary file not shown.
-7
View File
@@ -1,7 +0,0 @@
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
Vendored
-249
View File
@@ -1,249 +0,0 @@
#!/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
View File
@@ -1,92 +0,0 @@
@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
-4
View File
@@ -1,4 +0,0 @@
/build
/libs
/obj
/release
-207
View File
@@ -1,207 +0,0 @@
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 releaseFlags = arrayOf(
"-O3", "-flto",
"-Wno-unused", "-Wno-unused-parameter",
"-Wl,--exclude-libs,ALL", "-Wl,-icf=all,--lto-O3", "-Wl,-s,-x,--gc-sections"
)
android {
defaultConfig {
ndk {
abiFilters.addAll(abiList)
}
}
buildFeatures {
prefab = true
}
externalNativeBuild {
cmake {
version = "3.28.0+"
path("src/main/cpp/CMakeLists.txt")
}
}
}
cmaker {
default {
val cmakeArgs = arrayOf(
"-DANDROID_STL=none",
"-DMODULE_NAME=$moduleId",
)
arguments += cmakeArgs
abiFilters("arm64-v8a", "x86_64")
}
buildTypes {
when (it.name) {
"release" -> {
cppFlags += releaseFlags
cFlags += releaseFlags
}
}
val commonFlags = arrayOf(
// Silent noisy warnings
"-Wno-reorder-ctor",
"-Wno-overloaded-virtual",
"-Wno-unused-function",
"-Wno-unused-but-set-variable",
"-Wno-unused-private-field",
"-Wno-missing-braces",
"-Wno-delete-non-abstract-non-virtual-dtor",
"-Wno-unused-variable",
"-Wno-sometimes-uninitialized",
"-Wno-logical-op-parentheses",
"-Wno-shift-count-overflow",
"-Wno-deprecated-declarations",
"-Wno-infinite-recursion",
"-Wno-format",
"-Wno-deprecated-volatile",
)
cppFlags += commonFlags
cFlags += commonFlags
}
}
dependencies {
compileOnly(libs.cxx)
}
androidComponents.onVariants { variant ->
afterEvaluate {
val variantLowered = variant.name.lowercase()
val variantCapped = variant.name.capitalizeUS()
val buildTypeLowered = variant.buildType?.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:assemble$variantCapped")
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
)
}
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").layout.buildDirectory.file("outputs/apk/$variantLowered/service-$variantLowered.apk")) {
rename { "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")
}
}
}
-2
View File
@@ -1,2 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest/>
-52
View File
@@ -1,52 +0,0 @@
cmake_minimum_required(VERSION 3.28)
project(sample)
if (CCACHE)
set(CMAKE_CXX_COMPILER_LAUNCHER ${CCACHE})
set(CMAKE_C_COMPILER_LAUNCHER ${CCACHE})
endif ()
find_package(cxx REQUIRED CONFIG)
link_libraries(cxx::cxx)
find_program(CCACHE ccache)
set(LINKER_FLAGS "-ffixed-x18 -Wl,--hash-style=both")
set(CMAKE_CXX_STANDARD 23)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fno-exceptions -fno-rtti")
set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} ${LINKER_FLAGS}")
set(CMAKE_MODULE_LINKER_FLAGS "${CMAKE_MODULE_LINKER_FLAGS} ${LINKER_FLAGS}")
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_compile_options(utils PRIVATE -fvisibility=default -fno-visibility-inlines-hidden)
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)
target_compile_options(libinject.so PRIVATE -fvisibility=hidden -fvisibility-inlines-hidden)
add_library(${MODULE_NAME} SHARED binder_interceptor.cpp)
target_link_libraries(${MODULE_NAME} PRIVATE log binder utils elf_util my_logging)
target_compile_options(${MODULE_NAME} PRIVATE -fvisibility=hidden -fvisibility-inlines-hidden)
add_library(tszygisk SHARED zygisk/main.cpp)
target_link_libraries(tszygisk PRIVATE log my_logging glaze::glaze)
@@ -1,113 +0,0 @@
/*
* 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
// ---------------------------------------------------------------------------
@@ -1,21 +0,0 @@
/*
* 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;
}
@@ -1,2 +0,0 @@
#pragma once
#define LIBBINDER_EXPORTED __attribute__((__visibility__("default")))
@@ -1,228 +0,0 @@
/*
* 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
// ---------------------------------------------------------------------------
@@ -1,9 +0,0 @@
#pragma once
#include "utils/RefBase.h"
namespace android {
class IInterface : public virtual RefBase {
};
}
@@ -1,59 +0,0 @@
/*
* 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
// ---------------------------------------------------------------------------
@@ -1,163 +0,0 @@
/*
* 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
@@ -1,190 +0,0 @@
#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
// ---------------------------------------------------------------------------
@@ -1,5 +0,0 @@
#pragma once
namespace android::binder {
class unique_fd;
}
@@ -1,241 +0,0 @@
/*
* 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
@@ -1,77 +0,0 @@
/*
* 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
@@ -1,487 +0,0 @@
#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
@@ -1,40 +0,0 @@
/*
* 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
@@ -1,361 +0,0 @@
/*
* 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
@@ -1,7 +0,0 @@
#pragma once
// TODO
namespace android {
template<class TYPE>
class Vector;
}
-165
View File
@@ -1,165 +0,0 @@
#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) {}
}
-63
View File
@@ -1,63 +0,0 @@
#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() {}
}
-351
View File
@@ -1,351 +0,0 @@
#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();
}
-305
View File
@@ -1,305 +0,0 @@
/*
* 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};
}
@@ -1,174 +0,0 @@
/*
* 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
-12
View File
@@ -1,12 +0,0 @@
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
add_subdirectory(glaze)
-300
View File
@@ -1,300 +0,0 @@
#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, &regs, 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]);
}
-696
View File
@@ -1,696 +0,0 @@
#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 &regs) {
#if defined(__x86_64__) || defined(__i386__)
if (ptrace(PTRACE_GETREGS, pid, 0, &regs) == -1) {
PLOGE("getregs");
return false;
}
#elif defined(__aarch64__) || defined(__arm__)
struct iovec iov = {
.iov_base = &regs,
.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 &regs) {
#if defined(__x86_64__) || defined(__i386__)
if (ptrace(PTRACE_SETREGS, pid, 0, &regs) == -1) {
PLOGE("setregs");
return false;
}
#elif defined(__aarch64__) || defined(__arm__)
struct iovec iov = {
.iov_base = &regs,
.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 &regs, uintptr_t preserve) {
regs.REG_SP = (regs.REG_SP - preserve) & ~0xf;
}
uintptr_t push_memory(int pid, struct user_regs_struct &regs, 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 &regs, 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 &regs, 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 &regs, 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 &regs, 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, &regs, 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;
}
-166
View File
@@ -1,166 +0,0 @@
#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 &regs);
bool set_regs(int pid, struct user_regs_struct &regs);
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 &regs, uintptr_t preserve = 0);
uintptr_t push_memory(int pid, struct user_regs_struct &regs, void* local_addr, size_t len);
uintptr_t push_string(int pid, struct user_regs_struct &regs, const char *str);
uintptr_t remote_call(int pid, struct user_regs_struct &regs, 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 &regs, uintptr_t func_addr, uintptr_t return_addr,
std::vector<uintptr_t> &args);
uintptr_t remote_post_call(int pid, struct user_regs_struct &regs, 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);
@@ -1,30 +0,0 @@
#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(...)
#define LOGV(...)
#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, ...);
}
-36
View File
@@ -1,36 +0,0 @@
#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);
}
}
}
-193
View File
@@ -1,193 +0,0 @@
#include <cstdlib>
#include <unistd.h>
#include <fcntl.h>
#include <android/log.h>
#include <string_view>
#include <utility>
#include "glaze/glaze.hpp"
#include "logging.hpp"
#include "zygisk.hpp"
using zygisk::Api;
using zygisk::AppSpecializeArgs;
using zygisk::ServerSpecializeArgs;
using namespace std::string_view_literals;
struct spoof_config {
std::string manufacturer{"Google"};
std::string model{"Pixel"};
std::string fingerprint{"google/sailfish/sailfish:8.1.0/OPM1.171019.011/4448085:user/release-keys"};
std::string brand{"google"};
std::string product{"sailfish"};
std::string device{"sailfish"};
std::string release{"8.1.0"};
std::string id{"OPM1.171019.011"};
std::string incremental{"4448085"};
std::string security_patch{"2017-12-05"};
std::string type{"user"};
std::string tags{"release-keys"};
};
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, 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;
}
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);
int enabled = 0;
spoof_config spoofConfig{};
{
auto fd = api_->connectCompanion();
if (fd >= 0) [[likely]] {
// read enabled
xread(fd, &enabled, sizeof(enabled));
if (enabled) {
size_t bufferSize = 0;
std::string buffer;
// read size first
xread(fd, &bufferSize, sizeof(bufferSize));
// resize and receive
buffer.resize(bufferSize);
xread(fd, buffer.data(), bufferSize);
// parse
if (glz::read_json(spoofConfig, buffer)) [[unlikely]] {
LOGE("[preAppSpecialize] spoofConfig parse error");
}
}
close(fd);
}
}
if (!enabled) return;
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 process(nice_name);
std::string_view dir(app_data_dir);
if (dir.ends_with("/com.google.android.gms") &&
process == "com.google.android.gms.unstable") {
LOGI("spoofing build vars in GMS!");
auto buildClass = env_->FindClass("android/os/Build");
auto buildVersionClass = env_->FindClass("android/os/Build$VERSION");
setField(buildClass, "MANUFACTURER", std::move(spoofConfig.manufacturer));
setField(buildClass, "MODEL", std::move(spoofConfig.model));
setField(buildClass, "FINGERPRINT", std::move(spoofConfig.fingerprint));
setField(buildClass, "BRAND", std::move(spoofConfig.brand));
setField(buildClass, "PRODUCT", std::move(spoofConfig.product));
setField(buildClass, "DEVICE", std::move(spoofConfig.device));
setField(buildVersionClass, "RELEASE", std::move(spoofConfig.release));
setField(buildClass, "ID", std::move(spoofConfig.id));
setField(buildVersionClass, "INCREMENTAL", std::move(spoofConfig.incremental));
setField(buildVersionClass, "SECURITY_PATCH", std::move(spoofConfig.security_patch));
setField(buildClass, "TYPE", std::move(spoofConfig.type));
setField(buildClass, "TAGS", std::move(spoofConfig.tags));
}
env_->ReleaseStringUTFChars(args->nice_name, nice_name);
env_->ReleaseStringUTFChars(args->app_data_dir, app_data_dir);
}
void preServerSpecialize(ServerSpecializeArgs *args) override {
api_->setOption(zygisk::DLCLOSE_MODULE_LIBRARY);
}
private:
Api *api_;
JNIEnv *env_;
inline void setField(jclass clazz, const char* field, std::string&& value) {
auto id = env_->GetStaticFieldID(clazz, field, "Ljava/lang/String;");
env_->SetStaticObjectField(clazz, id, env_->NewStringUTF(value.c_str()));
}
};
static inline void write_spoof_configs(const struct spoof_config& spoofConfig) {
std::string buffer{};
if (glz::write<glz::opts{.prettify = true}>(spoofConfig, buffer)) [[unlikely]] {
// This should NEVER happen, but it's not the reason we don't handle the case
LOGE("[write_spoof_configs] Failed to parse json to std::string");
return;
}
// Remove old one first
std::filesystem::remove("/data/adb/tricky_store/spoof_build_vars"sv);
FILE* file = fopen("/data/adb/tricky_store/spoof_build_vars", "w");
if (!file) [[unlikely]] {
LOGE("[write_spoof_configs] Failed to open spoof_build_vars");
return;
}
if (fprintf(file, "%s", buffer.c_str()) < 0) [[unlikely]] {
LOGE("[write_spoof_configs] Failed to write spoof_build_vars");
fclose(file);
return;
}
fclose(file);
LOGI("[write_spoof_configs] write done!");
}
static void companion_handler(int fd) {
int enabled = access("/data/adb/tricky_store/spoof_build_vars", F_OK) == 0;
xwrite(fd, &enabled, sizeof(enabled));
if (!enabled) {
return;
}
spoof_config spoofConfig{};
auto ec = glz::read_file_json(spoofConfig, "/data/adb/tricky_store/spoof_build_vars"sv, std::string{});
if (ec) [[unlikely]] {
LOGW("[companion_handler] Failed to parse spoof_build_vars, writing and using default spoof config...");
write_spoof_configs(spoofConfig);
}
std::string buffer = glz::write_json(spoofConfig).value_or("");
size_t bufferSize = buffer.size();
// Send buffer size first
xwrite(fd, &bufferSize, sizeof(bufferSize));
// client resize string stl and receive buffer
xwrite(fd, buffer.data(), bufferSize);
}
// Register our module class and the companion handler function
REGISTER_ZYGISK_MODULE(TrickyStore)
REGISTER_ZYGISK_COMPANION(companion_handler)
-419
View File
@@ -1,419 +0,0 @@
/* 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"
@@ -1,190 +0,0 @@
#!/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
@@ -1 +0,0 @@
#MAGISK
-101
View File
@@ -1,101 +0,0 @@
# 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"
if [ ! -f "$CONFIG_DIR/target.txt" ]; then
ui_print "- Adding default scope"
extract "$ZIPFILE" 'target.txt' "$TMPDIR"
mv "$TMPDIR/target.txt" "$CONFIG_DIR/target.txt"
fi
fi
-3
View File
@@ -1,3 +0,0 @@
#!/system/bin/sh
exec /system/bin/app_process -Djava.class.path=./service.apk / --nice-name=TrickyStore io.github.a13e300.tricky_store.MainKt
-7
View File
@@ -1,7 +0,0 @@
id=${moduleId}
name=${moduleName}
version=${versionName}
versionCode=${versionCode}
author=5ec1cff
description=A trick of keystore
#updateJson=
-1
View File
@@ -1 +0,0 @@
MODDIR=${0%/*}
-4
View File
@@ -1,4 +0,0 @@
allow keystore system_file unix_dgram_socket *
allow system_file keystore unix_dgram_socket *
allow keystore system_file file *
allow crash_dump keystore process *
-14
View File
@@ -1,14 +0,0 @@
DEBUG=@DEBUG@
MODDIR=${0%/*}
cd $MODDIR
(
while [ true ]; do
./daemon
if [ $? -ne 0 ]; then
exit 1
fi
done
) &
-4
View File
@@ -1,4 +0,0 @@
com.google.android.gms
io.github.vvb2060.keyattestation
io.github.vvb2060.mahoshojo
icu.nullptr.nativetest
-51
View File
@@ -1,51 +0,0 @@
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
-1
View File
@@ -1 +0,0 @@
/build
-92
View File
@@ -1,92 +0,0 @@
import android.databinding.tool.ext.capitalizeUS
plugins {
alias(libs.plugins.jetbrains.kotlin.android)
alias(libs.plugins.agp.app)
}
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"
)
}
}
kotlinOptions {
jvmTarget = "17"
}
buildTypes {
release {
signingConfig = signingConfigs["debug"]
}
}
packaging {
resources {
excludes += "**"
}
}
lint {
checkReleaseBuilds = false
abortOnError = true
}
}
dependencies {
compileOnly(project(":stub"))
compileOnly(libs.annotation)
implementation(libs.bcpkix.jdk18on)
}
androidComponents.onVariants { variant ->
afterEvaluate {
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\"")
}
}
}
}
}
-38
View File
@@ -1,38 +0,0 @@
# 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
-2
View File
@@ -1,2 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest />
@@ -1,91 +0,0 @@
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
}
@@ -1,159 +0,0 @@
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)
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)
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)
}
}
}
@@ -1,23 +0,0 @@
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);
}
}
@@ -1,15 +0,0 @@
package io.github.a13e300.tricky_store
fun main(args: Array<String>) {
Logger.i("Welcome to TrickyStore!")
while (true) {
if (!KeystoreInterceptor.tryRunKeystoreInterceptor()) {
Thread.sleep(1000)
continue
}
Config.initialize()
while (true) {
Thread.sleep(1000000)
}
}
}
@@ -1,141 +0,0 @@
package io.github.a13e300.tricky_store
import android.hardware.security.keymint.KeyParameter
import android.hardware.security.keymint.KeyParameterValue
import android.hardware.security.keymint.SecurityLevel
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) : 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()
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 = SecurityLevel.TRUSTED_ENVIRONMENT
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 = SecurityLevel.TRUSTED_ENVIRONMENT
authorizations.add(a)
}
a = Authorization()
a.keyParameter = KeyParameter()
a.keyParameter.tag = Tag.ALGORITHM
a.keyParameter.value = KeyParameterValue.algorithm(params.algorithm)
a.securityLevel = SecurityLevel.TRUSTED_ENVIRONMENT
authorizations.add(a)
a = Authorization()
a.keyParameter = KeyParameter()
a.keyParameter.tag = Tag.KEY_SIZE
a.keyParameter.value = KeyParameterValue.integer(params.keySize)
a.securityLevel = SecurityLevel.TRUSTED_ENVIRONMENT
authorizations.add(a)
a = Authorization()
a.keyParameter = KeyParameter()
a.keyParameter.tag = Tag.EC_CURVE
a.keyParameter.value = KeyParameterValue.ecCurve(params.ecCurve)
a.securityLevel = SecurityLevel.TRUSTED_ENVIRONMENT
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 = SecurityLevel.TRUSTED_ENVIRONMENT
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
}
}
@@ -1,111 +0,0 @@
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
}
}
@@ -1,526 +0,0 @@
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 holder = new X509CertificateHolder(leaf.getEncoded());
Extension ext = holder.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(),
holder.getSerialNumber(),
holder.getNotBefore(),
holder.getNotAfter(),
holder.getSubject(),
k.pemKeyPair.getPublicKeyInfo()
);
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 : holder.getExtensions().getExtensionOIDs()) {
if (OID.getId().equals(extensionOID.getId())) continue;
builder.addExtension(holder.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());
// TODO hex3l: add applicationID to attestation
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 = {purpose, algorithm, keySize, digest, ecCurve,
noAuthRequired, creationDateTime, origin, rootOfTrust, osVersion, osPatchLevel, applicationID, vendorPatchLevel, bootPatchLevel};
ASN1OctetString keyDescriptionOctetStr = getAsn1OctetString(teeEnforcedEncodables, 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, 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());
ASN1Sequence softwareEnforced = new DERSequence();
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 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();
}
}
}
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;
}
}
}
@@ -1,74 +0,0 @@
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();
}
}
@@ -1,96 +0,0 @@
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;
}
}
}
}
@@ -1,62 +0,0 @@
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() }
-22
View File
@@ -1,22 +0,0 @@
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")
-1
View File
@@ -1 +0,0 @@
/build
-29
View File
@@ -1,29 +0,0 @@
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)
}
View File
-21
View File
@@ -1,21 +0,0 @@
# 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
-2
View File
@@ -1,2 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest/>
@@ -1,17 +0,0 @@
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("");
}
}
}
@@ -1,9 +0,0 @@
package android.hardware.security.keymint;
public @interface Algorithm {
int AES = 32;
int EC = 3;
int HMAC = 128;
int RSA = 1;
int TRIPLE_DES = 33;
}
@@ -1,10 +0,0 @@
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;
}
@@ -1,32 +0,0 @@
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();
}
}
@@ -1,239 +0,0 @@
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();
}
}
@@ -1,11 +0,0 @@
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;
}
@@ -1,8 +0,0 @@
package android.hardware.security.keymint;
public @interface SecurityLevel {
int KEYSTORE = 100;
int SOFTWARE = 0;
int STRONGBOX = 2;
int TRUSTED_ENVIRONMENT = 1;
}
@@ -1,70 +0,0 @@
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;
}
@@ -1,19 +0,0 @@
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!");
}
}
@@ -1,7 +0,0 @@
package android.os;
public class SystemProperties {
public static String get(String key, String def) {
throw new RuntimeException("");
}
}
@@ -1,8 +0,0 @@
package android.system.keystore2;
import android.hardware.security.keymint.KeyParameter;
public class Authorization {
public KeyParameter keyParameter;
public int securityLevel = 0;
}
@@ -1,20 +0,0 @@
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();
}
}
}
@@ -1,15 +0,0 @@
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("");
}
}
}
@@ -1,35 +0,0 @@
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("");
}
}
@@ -1,33 +0,0 @@
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("");
}
}
@@ -1,37 +0,0 @@
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("");
}
}
+6
View File
@@ -0,0 +1,6 @@
{
"versionCode": 180,
"version": "v1.3.0",
"zipUrl": "https://github.com/5ec1cff/TrickyStore/releases/download/1.3.0/Tricky-Store-v1.3.0-180-8acfa57-release.zip",
"changelog": "https://github.com/5ec1cff/TrickyStore/raw/release/changelog.md"
}