You've already forked TrickyStore
mirror of
https://github.com/5ec1cff/TrickyStore.git
synced 2025-09-06 06:37:07 +00:00
Compare commits
10 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 26b274cf58 | |||
| 140e212ebc | |||
| 1d92796ed1 | |||
| 50e3ce31f6 | |||
| 86b7eb1a8b | |||
| 29a22a461c | |||
| df6d697e0f | |||
| edc6cdab08 | |||
| fe9cc9681f | |||
| 9b28661fee |
-10
@@ -1,10 +0,0 @@
|
|||||||
*.iml
|
|
||||||
.gradle
|
|
||||||
/local.properties
|
|
||||||
/.idea
|
|
||||||
.DS_Store
|
|
||||||
/build
|
|
||||||
/captures
|
|
||||||
.externalNativeBuild
|
|
||||||
.cxx
|
|
||||||
local.properties
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
[submodule "module/src/main/cpp/external/libcxx"]
|
|
||||||
path = module/src/main/cpp/external/libcxx
|
|
||||||
url = https://github.com/topjohnwu/libcxx
|
|
||||||
[submodule "module/src/main/cpp/external/dobby"]
|
|
||||||
path = module/src/main/cpp/external/dobby
|
|
||||||
url = https://github.com/LSPosed/dobby
|
|
||||||
@@ -1,14 +1,24 @@
|
|||||||
# Tricky Store
|
# 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
|
## Usage
|
||||||
|
|
||||||
1. Flash this module and reboot.
|
1. Flash this module and reboot.
|
||||||
2. Put keybox.xml to /data/adb/tricky_store/keybox.xml .
|
2. For more than DEVICE integrity, put an unrevoked hardware keybox.xml at `/data/adb/tricky_store/keybox.xml` (Optional).
|
||||||
3. Put target packages to /data/adb/tricky_store/target.txt (Optional).
|
3. Customize target packages at `/data/adb/tricky_store/target.txt` (Optional).
|
||||||
4. Enjoy!
|
4. Enjoy!
|
||||||
|
|
||||||
|
**All configuration files will take effect immediately.**
|
||||||
|
|
||||||
## keybox.xml
|
## keybox.xml
|
||||||
|
|
||||||
format:
|
format:
|
||||||
@@ -38,32 +48,57 @@ format:
|
|||||||
</AndroidAttestation>
|
</AndroidAttestation>
|
||||||
```
|
```
|
||||||
|
|
||||||
## Build Vars Spoofing
|
|
||||||
|
|
||||||
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`.
|
|
||||||
|
|
||||||
Zygisk (or Zygisk Next) is needed for this feature to work.
|
|
||||||
|
|
||||||
## Support TEE broken devices
|
## Support TEE broken devices
|
||||||
|
|
||||||
TrickyStore will hack leaf certificate by default.
|
Tricky Store will hack the leaf certificate by default.
|
||||||
On TEE broken devices, this will not work because we can't retrieve leaf certificate from TEE.
|
On TEE broken devices, this will not work because we can't retrieve the leaf certificate from TEE.
|
||||||
You can add a `!` after package name to enable certificate generate support for this package.
|
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:
|
For example:
|
||||||
|
|
||||||
```
|
```
|
||||||
# target.txt
|
# target.txt
|
||||||
# use leaf certificate hacking mode for KeyAttestation App
|
# use auto mode for KeyAttestation App
|
||||||
io.github.vvb2060.keyattestation
|
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!
|
com.google.android.gms!
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Customize security patch level (1.2.1+)
|
||||||
|
|
||||||
|
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
|
## Acknowledgement
|
||||||
|
|
||||||
- [PlayIntegrityFix](https://github.com/chiteroman/PlayIntegrityFix)
|
|
||||||
- [FrameworkPatch](https://github.com/chiteroman/FrameworkPatch)
|
- [FrameworkPatch](https://github.com/chiteroman/FrameworkPatch)
|
||||||
- [BootloaderSpoofer](https://github.com/chiteroman/BootloaderSpoofer)
|
- [BootloaderSpoofer](https://github.com/chiteroman/BootloaderSpoofer)
|
||||||
- [KeystoreInjection](https://github.com/aviraxp/Zygisk-KeystoreInjection)
|
- [KeystoreInjection](https://github.com/aviraxp/Zygisk-KeystoreInjection)
|
||||||
|
|||||||
+105
@@ -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)
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
theme: jekyll-theme-slate
|
||||||
@@ -1,98 +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")
|
|
||||||
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.11902837")
|
|
||||||
val androidSourceCompatibility by extra(JavaVersion.VERSION_17)
|
|
||||||
val androidTargetCompatibility by extra(JavaVersion.VERSION_17)
|
|
||||||
|
|
||||||
tasks.register("Delete", Delete::class) {
|
|
||||||
delete(rootProject.buildDir)
|
|
||||||
}
|
|
||||||
|
|
||||||
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 {
|
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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
|
||||||
@@ -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
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
[versions]
|
|
||||||
agp = "8.5.0"
|
|
||||||
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" }
|
|
||||||
|
|
||||||
[plugins]
|
|
||||||
agp-app = { id = "com.android.application", version.ref = "agp" }
|
|
||||||
jetbrains-kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
|
|
||||||
android-library = { id = "com.android.library", version.ref = "agp" }
|
|
||||||
Vendored
BIN
Binary file not shown.
-6
@@ -1,6 +0,0 @@
|
|||||||
#Sun Dec 31 12:28:57 CST 2023
|
|
||||||
distributionBase=GRADLE_USER_HOME
|
|
||||||
distributionPath=wrapper/dists
|
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip
|
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
|
||||||
zipStorePath=wrapper/dists
|
|
||||||
@@ -1,185 +0,0 @@
|
|||||||
#!/usr/bin/env sh
|
|
||||||
|
|
||||||
#
|
|
||||||
# Copyright 2015 the original author or 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 UN*X
|
|
||||||
##
|
|
||||||
##############################################################################
|
|
||||||
|
|
||||||
# Attempt to set APP_HOME
|
|
||||||
# Resolve links: $0 may be a link
|
|
||||||
PRG="$0"
|
|
||||||
# Need this for relative symlinks.
|
|
||||||
while [ -h "$PRG" ] ; do
|
|
||||||
ls=`ls -ld "$PRG"`
|
|
||||||
link=`expr "$ls" : '.*-> \(.*\)$'`
|
|
||||||
if expr "$link" : '/.*' > /dev/null; then
|
|
||||||
PRG="$link"
|
|
||||||
else
|
|
||||||
PRG=`dirname "$PRG"`"/$link"
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
SAVED="`pwd`"
|
|
||||||
cd "`dirname \"$PRG\"`/" >/dev/null
|
|
||||||
APP_HOME="`pwd -P`"
|
|
||||||
cd "$SAVED" >/dev/null
|
|
||||||
|
|
||||||
APP_NAME="Gradle"
|
|
||||||
APP_BASE_NAME=`basename "$0"`
|
|
||||||
|
|
||||||
# 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"'
|
|
||||||
|
|
||||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
|
||||||
MAX_FD="maximum"
|
|
||||||
|
|
||||||
warn () {
|
|
||||||
echo "$*"
|
|
||||||
}
|
|
||||||
|
|
||||||
die () {
|
|
||||||
echo
|
|
||||||
echo "$*"
|
|
||||||
echo
|
|
||||||
exit 1
|
|
||||||
}
|
|
||||||
|
|
||||||
# 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
|
|
||||||
;;
|
|
||||||
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"
|
|
||||||
which java >/dev/null 2>&1 || 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
|
|
||||||
|
|
||||||
# Increase the maximum file descriptors if we can.
|
|
||||||
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
|
|
||||||
MAX_FD_LIMIT=`ulimit -H -n`
|
|
||||||
if [ $? -eq 0 ] ; then
|
|
||||||
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
|
|
||||||
MAX_FD="$MAX_FD_LIMIT"
|
|
||||||
fi
|
|
||||||
ulimit -n $MAX_FD
|
|
||||||
if [ $? -ne 0 ] ; then
|
|
||||||
warn "Could not set maximum file descriptor limit: $MAX_FD"
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
# For Darwin, add options to specify how the application appears in the dock
|
|
||||||
if $darwin; then
|
|
||||||
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
|
|
||||||
fi
|
|
||||||
|
|
||||||
# For Cygwin or MSYS, switch paths to Windows format before running java
|
|
||||||
if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
|
|
||||||
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
|
|
||||||
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
|
|
||||||
|
|
||||||
JAVACMD=`cygpath --unix "$JAVACMD"`
|
|
||||||
|
|
||||||
# We build the pattern for arguments to be converted via cygpath
|
|
||||||
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
|
|
||||||
SEP=""
|
|
||||||
for dir in $ROOTDIRSRAW ; do
|
|
||||||
ROOTDIRS="$ROOTDIRS$SEP$dir"
|
|
||||||
SEP="|"
|
|
||||||
done
|
|
||||||
OURCYGPATTERN="(^($ROOTDIRS))"
|
|
||||||
# Add a user-defined pattern to the cygpath arguments
|
|
||||||
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
|
|
||||||
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
|
|
||||||
fi
|
|
||||||
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
|
||||||
i=0
|
|
||||||
for arg in "$@" ; do
|
|
||||||
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
|
|
||||||
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
|
|
||||||
|
|
||||||
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
|
|
||||||
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
|
|
||||||
else
|
|
||||||
eval `echo args$i`="\"$arg\""
|
|
||||||
fi
|
|
||||||
i=`expr $i + 1`
|
|
||||||
done
|
|
||||||
case $i in
|
|
||||||
0) set -- ;;
|
|
||||||
1) set -- "$args0" ;;
|
|
||||||
2) set -- "$args0" "$args1" ;;
|
|
||||||
3) set -- "$args0" "$args1" "$args2" ;;
|
|
||||||
4) set -- "$args0" "$args1" "$args2" "$args3" ;;
|
|
||||||
5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
|
|
||||||
6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
|
|
||||||
7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
|
|
||||||
8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
|
|
||||||
9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
|
|
||||||
esac
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Escape application args
|
|
||||||
save () {
|
|
||||||
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
|
|
||||||
echo " "
|
|
||||||
}
|
|
||||||
APP_ARGS=`save "$@"`
|
|
||||||
|
|
||||||
# Collect all arguments for the java command, following the shell quoting and substitution rules
|
|
||||||
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
|
|
||||||
|
|
||||||
exec "$JAVACMD" "$@"
|
|
||||||
Vendored
-89
@@ -1,89 +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=.
|
|
||||||
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%" == "0" goto execute
|
|
||||||
|
|
||||||
echo.
|
|
||||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
|
||||||
echo.
|
|
||||||
echo Please set the JAVA_HOME variable in your environment to match the
|
|
||||||
echo location of your Java installation.
|
|
||||||
|
|
||||||
goto fail
|
|
||||||
|
|
||||||
:findJavaFromJavaHome
|
|
||||||
set JAVA_HOME=%JAVA_HOME:"=%
|
|
||||||
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
|
||||||
|
|
||||||
if exist "%JAVA_EXE%" goto execute
|
|
||||||
|
|
||||||
echo.
|
|
||||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
|
|
||||||
echo.
|
|
||||||
echo Please set the JAVA_HOME variable in your environment to match the
|
|
||||||
echo location of your Java installation.
|
|
||||||
|
|
||||||
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%"=="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!
|
|
||||||
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
|
|
||||||
exit /b 1
|
|
||||||
|
|
||||||
:mainEnd
|
|
||||||
if "%OS%"=="Windows_NT" endlocal
|
|
||||||
|
|
||||||
:omega
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
/build
|
|
||||||
/libs
|
|
||||||
/obj
|
|
||||||
/release
|
|
||||||
@@ -1,174 +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)
|
|
||||||
}
|
|
||||||
|
|
||||||
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(
|
|
||||||
"-Oz", "-flto",
|
|
||||||
"-Wno-unused", "-Wno-unused-parameter",
|
|
||||||
"-Wl,--exclude-libs,ALL", "-Wl,--gc-sections",
|
|
||||||
)
|
|
||||||
|
|
||||||
android {
|
|
||||||
defaultConfig {
|
|
||||||
ndk {
|
|
||||||
abiFilters.addAll(abiList)
|
|
||||||
}
|
|
||||||
externalNativeBuild {
|
|
||||||
cmake {
|
|
||||||
cppFlags("-std=c++20")
|
|
||||||
arguments(
|
|
||||||
"-DANDROID_STL=none",
|
|
||||||
"-DMODULE_NAME=$moduleId"
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
externalNativeBuild {
|
|
||||||
cmake {
|
|
||||||
path("src/main/cpp/CMakeLists.txt")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
buildTypes {
|
|
||||||
release {
|
|
||||||
externalNativeBuild.cmake {
|
|
||||||
cFlags += releaseFlags
|
|
||||||
cppFlags += releaseFlags
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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")
|
|
||||||
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")
|
|
||||||
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")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<manifest/>
|
|
||||||
@@ -1,47 +0,0 @@
|
|||||||
cmake_minimum_required(VERSION 3.22.1)
|
|
||||||
project(sample)
|
|
||||||
|
|
||||||
set(LINKER_FLAGS "-ffixed-x18 -Wl,--hash-style=both")
|
|
||||||
|
|
||||||
set(CXX_FLAGS "${CXX_FLAGS} -fno-exceptions -fno-rtti")
|
|
||||||
|
|
||||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${CXX_FLAGS}")
|
|
||||||
|
|
||||||
set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} ${LINKER_FLAGS}")
|
|
||||||
set(CMAKE_MODULE_LINKER_FLAGS "${CMAKE_MODULE_LINKER_FLAGS} ${LINKER_FLAGS}")
|
|
||||||
|
|
||||||
add_subdirectory(external)
|
|
||||||
link_libraries(cxx)
|
|
||||||
|
|
||||||
add_library(elf_util STATIC elf_util/elf_util.cpp)
|
|
||||||
add_library(lspmparser STATIC lspmparser/lsplt.cpp)
|
|
||||||
add_library(my_logging STATIC logging/logging.cpp)
|
|
||||||
|
|
||||||
target_include_directories(my_logging PUBLIC logging/include)
|
|
||||||
target_include_directories(elf_util PUBLIC elf_util/include)
|
|
||||||
target_include_directories(lspmparser PUBLIC lspmparser/include)
|
|
||||||
|
|
||||||
target_link_libraries(my_logging cxx log)
|
|
||||||
target_link_libraries(elf_util cxx lspmparser my_logging)
|
|
||||||
target_link_libraries(lspmparser cxx)
|
|
||||||
|
|
||||||
# libutils stub
|
|
||||||
add_library(utils SHARED binder/stub_utils.cpp)
|
|
||||||
target_include_directories(utils PUBLIC binder/include)
|
|
||||||
|
|
||||||
# libbinder stub
|
|
||||||
add_library(binder SHARED binder/stub_binder.cpp)
|
|
||||||
target_include_directories(binder PUBLIC binder/include)
|
|
||||||
target_link_libraries(binder utils)
|
|
||||||
|
|
||||||
add_executable(libinject.so inject/main.cpp inject/utils.cpp)
|
|
||||||
target_link_libraries(libinject.so lspmparser 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} log binder utils dobby 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 log my_logging)
|
|
||||||
target_compile_options(tszygisk PRIVATE -fvisibility=hidden -fvisibility-inlines-hidden)
|
|
||||||
@@ -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,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,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,6 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
// TODO
|
|
||||||
namespace android {
|
|
||||||
class String16 {};
|
|
||||||
}
|
|
||||||
@@ -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;
|
|
||||||
}
|
|
||||||
@@ -1,151 +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"
|
|
||||||
|
|
||||||
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 { return {}; }
|
|
||||||
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; }
|
|
||||||
}
|
|
||||||
@@ -1,52 +0,0 @@
|
|||||||
#include "utils/StrongPointer.h"
|
|
||||||
#include "utils/RefBase.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() {}
|
|
||||||
}
|
|
||||||
@@ -1,277 +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 <utility>
|
|
||||||
#include <map>
|
|
||||||
#include <shared_mutex>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
#include "logging.hpp"
|
|
||||||
#include "dobby.h"
|
|
||||||
#include "elf_util.h"
|
|
||||||
|
|
||||||
#include "hook_util/hook_helper.hpp"
|
|
||||||
|
|
||||||
using namespace SandHook;
|
|
||||||
using namespace android;
|
|
||||||
using namespace hook_helper::literals;
|
|
||||||
|
|
||||||
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(BBinder *thiz, uint32_t code, const Parcel &data, Parcel *reply,
|
|
||||||
uint32_t flags, status_t &result);
|
|
||||||
};
|
|
||||||
|
|
||||||
static sp<BinderInterceptor> gBinderInterceptor = nullptr;
|
|
||||||
|
|
||||||
CREATE_MEM_HOOK_STUB_ENTRY(
|
|
||||||
"_ZN7android7BBinder8transactEjRKNS_6ParcelEPS1_j",
|
|
||||||
status_t, BBinder_Transact,
|
|
||||||
(BBinder * thiz, uint32_t code, const Parcel &data, Parcel *reply, uint32_t flags),
|
|
||||||
{
|
|
||||||
LOGD("transact: binder=%p code=%d", thiz, code);
|
|
||||||
if (IPCThreadState::self()->getCallingUid() == 0 && reply != nullptr &&
|
|
||||||
thiz != gBinderInterceptor) [[unlikely]] {
|
|
||||||
if (code == 0xdeadbeef) {
|
|
||||||
LOGD("request binder interceptor");
|
|
||||||
reply->writeStrongBinder(gBinderInterceptor);
|
|
||||||
return OK;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
status_t result;
|
|
||||||
if (gBinderInterceptor->handleIntercept(thiz, code, data, reply,
|
|
||||||
flags, result)) {
|
|
||||||
LOGD("transact intercepted: binder=%p code=%d result=%d", thiz, code, result);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
result = backup(thiz, code, data, reply, flags);
|
|
||||||
LOGD("transact: binder=%p code=%d result=%d", thiz, code, result);
|
|
||||||
return result;
|
|
||||||
});
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
class HookHandler : public hook_helper::HookHandler {
|
|
||||||
public:
|
|
||||||
ElfImg img;
|
|
||||||
|
|
||||||
explicit HookHandler(ElfInfo info) : img{std::move(info)} {}
|
|
||||||
|
|
||||||
bool isValid() const {
|
|
||||||
return img.isValid();
|
|
||||||
}
|
|
||||||
|
|
||||||
void *get_symbol(const char *name) const override {
|
|
||||||
auto addr = img.getSymbAddress(name);
|
|
||||||
if (!addr) {
|
|
||||||
LOGE("%s: symbol not found", name);
|
|
||||||
}
|
|
||||||
return addr;
|
|
||||||
}
|
|
||||||
|
|
||||||
void *get_symbol_prefix(const char *prefix) const override {
|
|
||||||
auto addr = img.getSymbPrefixFirstOffset(prefix);
|
|
||||||
if (!addr) {
|
|
||||||
LOGE("%s: prefix symbol not found", prefix);
|
|
||||||
}
|
|
||||||
return addr;
|
|
||||||
}
|
|
||||||
|
|
||||||
void *hook(void *original, void *replacement) const override {
|
|
||||||
void *result = nullptr;
|
|
||||||
if (DobbyHook(original, (dobby_dummy_func_t) replacement, (dobby_dummy_func_t *) &result) ==
|
|
||||||
0) {
|
|
||||||
return result;
|
|
||||||
} else {
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
std::pair<uintptr_t, size_t> get_symbol_info(const char *name) const override {
|
|
||||||
auto p = img.getSymInfo(name);
|
|
||||||
if (!p.first) {
|
|
||||||
LOGE("%s: info not found", name);
|
|
||||||
}
|
|
||||||
return p;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
bool
|
|
||||||
BinderInterceptor::handleIntercept(BBinder *thiz, 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};
|
|
||||||
wp<IBinder> target = wp<IBinder>::fromExisting(thiz);
|
|
||||||
auto it = items.find(target);
|
|
||||||
if (it == items.end()) return false;
|
|
||||||
interceptor = it->second.interceptor;
|
|
||||||
}
|
|
||||||
LOGD("intercept on binder %p code %d flags %d (reply=%s)", thiz, code, flags,
|
|
||||||
reply ? "true" : "false");
|
|
||||||
sp<IBinder> target = sp<IBinder>::fromExisting(thiz);
|
|
||||||
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 = BBinder_Transact.backup(thiz, 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() {
|
|
||||||
HookHandler handler{ElfInfo::getElfInfoForName("libbinder.so")};
|
|
||||||
if (!handler.isValid()) {
|
|
||||||
LOGE("libbinder not found!");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (!hook_helper::HookSym(handler, BBinder_Transact)) {
|
|
||||||
LOGE("hook failed!");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
LOGI("hook success!");
|
|
||||||
gBinderInterceptor = sp<BinderInterceptor>::make();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
extern "C" [[gnu::visibility("default")]] [[gnu::used]] bool entry(void *handle) {
|
|
||||||
LOGI("my handle %p", handle);
|
|
||||||
return hookBinder();
|
|
||||||
}
|
|
||||||
@@ -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
|
|
||||||
-109
@@ -1,109 +0,0 @@
|
|||||||
project(external)
|
|
||||||
|
|
||||||
|
|
||||||
# dobby
|
|
||||||
macro(SET_OPTION option value)
|
|
||||||
set(${option} ${value} CACHE INTERNAL "" FORCE)
|
|
||||||
endmacro()
|
|
||||||
|
|
||||||
SET_OPTION(DOBBY_GENERATE_SHARED OFF)
|
|
||||||
SET_OPTION(Plugin.SymbolResolver OFF)
|
|
||||||
|
|
||||||
add_subdirectory(Dobby)
|
|
||||||
target_link_libraries(dobby cxx)
|
|
||||||
# end dobby
|
|
||||||
|
|
||||||
# cxx
|
|
||||||
set(LIBCXX_SOURCES
|
|
||||||
algorithm.cpp
|
|
||||||
# any.cpp
|
|
||||||
atomic.cpp
|
|
||||||
barrier.cpp
|
|
||||||
# bind.cpp
|
|
||||||
charconv.cpp
|
|
||||||
chrono.cpp
|
|
||||||
condition_variable.cpp
|
|
||||||
condition_variable_destructor.cpp
|
|
||||||
# debug.cpp
|
|
||||||
exception.cpp
|
|
||||||
# filesystem/directory_iterator.cpp
|
|
||||||
# filesystem/int128_builtins.cpp
|
|
||||||
# filesystem/operations.cpp
|
|
||||||
functional.cpp
|
|
||||||
future.cpp
|
|
||||||
hash.cpp
|
|
||||||
# ios.cpp
|
|
||||||
# ios.instantiations.cpp
|
|
||||||
# iostream.cpp
|
|
||||||
# locale.cpp
|
|
||||||
memory.cpp
|
|
||||||
mutex.cpp
|
|
||||||
mutex_destructor.cpp
|
|
||||||
new.cpp
|
|
||||||
optional.cpp
|
|
||||||
random.cpp
|
|
||||||
# regex.cpp
|
|
||||||
# ryu/d2fixed.cpp
|
|
||||||
# ryu/d2s.cpp
|
|
||||||
# ryu/f2s.cpp
|
|
||||||
shared_mutex.cpp
|
|
||||||
stdexcept.cpp
|
|
||||||
string.cpp
|
|
||||||
# strstream.cpp
|
|
||||||
system_error.cpp
|
|
||||||
thread.cpp
|
|
||||||
# typeinfo.cpp
|
|
||||||
utility.cpp
|
|
||||||
valarray.cpp
|
|
||||||
variant.cpp
|
|
||||||
vector.cpp
|
|
||||||
)
|
|
||||||
|
|
||||||
list(TRANSFORM LIBCXX_SOURCES PREPEND libcxx/src/)
|
|
||||||
|
|
||||||
set(LIBCXX_EXPORT_FLAGS
|
|
||||||
-DLIBCXX_BUILDING_LIBCXXABI
|
|
||||||
-D_LIBCPP_NO_EXCEPTIONS
|
|
||||||
-D_LIBCPP_NO_RTTI
|
|
||||||
-D_LIBCPP_BUILDING_LIBRARY
|
|
||||||
-D_LIBCPP_DISABLE_VISIBILITY_ANNOTATIONS
|
|
||||||
-D_LIBCXXABI_NO_EXCEPTIONS
|
|
||||||
-D_LIBCPP_HAS_NO_LOCALIZATION
|
|
||||||
)
|
|
||||||
set(LIBCXX_FLAGS
|
|
||||||
-fvisibility-global-new-delete-hidden
|
|
||||||
-fvisibility=hidden
|
|
||||||
-fvisibility-inlines-hidden
|
|
||||||
)
|
|
||||||
set(LIBCXX_EXPORT_INCLUDES libcxx/include)
|
|
||||||
set(LIBCXX_INCLUDES libcxx/src)
|
|
||||||
|
|
||||||
set(LIBCXXABI_SOURCES
|
|
||||||
abort_message.cpp
|
|
||||||
cxa_aux_runtime.cpp
|
|
||||||
cxa_default_handlers.cpp
|
|
||||||
cxa_exception_storage.cpp
|
|
||||||
cxa_guard.cpp
|
|
||||||
cxa_handlers.cpp
|
|
||||||
cxa_noexception.cpp
|
|
||||||
cxa_thread_atexit.cpp
|
|
||||||
cxa_vector.cpp
|
|
||||||
cxa_virtual.cpp
|
|
||||||
stdlib_exception.cpp
|
|
||||||
stdlib_new_delete.cpp
|
|
||||||
stdlib_stdexcept.cpp
|
|
||||||
stdlib_typeinfo.cpp
|
|
||||||
)
|
|
||||||
list(TRANSFORM LIBCXXABI_SOURCES PREPEND libcxx/src/abi/)
|
|
||||||
set(LIBCXXABI_FLAGS
|
|
||||||
-Wno-macro-redefined
|
|
||||||
-Wno-unknown-attributes
|
|
||||||
-DHAS_THREAD_LOCAL)
|
|
||||||
set(LIBCXXABI_INCLUDES libcxx/include/abi)
|
|
||||||
|
|
||||||
add_library(cxx STATIC ${LIBCXX_SOURCES} ${LIBCXXABI_SOURCES})
|
|
||||||
target_compile_options(cxx PUBLIC ${LIBCXX_EXPORT_FLAGS})
|
|
||||||
target_compile_options(cxx PRIVATE ${LIBCXX_FLAGS} ${LIBCXXABI_FLAGS} -ffunction-sections -fdata-sections)
|
|
||||||
target_include_directories(cxx PUBLIC ${LIBCXX_EXPORT_INCLUDES})
|
|
||||||
target_include_directories(cxx PRIVATE ${LIBCXX_INCLUDES} ${LIBCXXABI_INCLUDES})
|
|
||||||
# end cxx
|
|
||||||
Vendored
-1
Submodule module/src/main/cpp/external/dobby deleted from 6813ca76dd
Vendored
-1
Submodule module/src/main/cpp/external/libcxx deleted from 12c8f4e93f
@@ -1,246 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include <concepts>
|
|
||||||
#include <android/log.h>
|
|
||||||
|
|
||||||
#include "type_traits.hpp"
|
|
||||||
|
|
||||||
#if defined(__LP64__)
|
|
||||||
#define LP_SELECT(lp32, lp64) lp64
|
|
||||||
#else
|
|
||||||
#define LP_SELECT(lp32, lp64) lp32
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#define CONCATENATE(a, b) a##b
|
|
||||||
|
|
||||||
#define CREATE_HOOK_STUB_ENTRY(SYM, RET, FUNC, PARAMS, DEF) \
|
|
||||||
inline static struct : public hook_helper::Hooker<RET PARAMS, decltype(CONCATENATE(SYM, _tstr))>{ \
|
|
||||||
inline static RET replace PARAMS DEF} FUNC
|
|
||||||
|
|
||||||
#define CREATE_MEM_HOOK_STUB_ENTRY(SYM, RET, FUNC, PARAMS, DEF) \
|
|
||||||
static struct : public hook_helper::MemHooker<RET PARAMS, \
|
|
||||||
decltype(CONCATENATE(SYM, _tstr))>{ \
|
|
||||||
inline static RET replace PARAMS DEF} FUNC
|
|
||||||
|
|
||||||
#define RETRIEVE_FUNC_SYMBOL(name, ...) \
|
|
||||||
(name##Sym = reinterpret_cast<name##Type>(hook_helper::Dlsym(handler, __VA_ARGS__)))
|
|
||||||
|
|
||||||
#define RETRIEVE_FUNC_SYMBOL_OR_FAIL(name, ...) \
|
|
||||||
RETRIEVE_FUNC_SYMBOL(name, __VA_ARGS__); if (!name##Sym) return false;
|
|
||||||
|
|
||||||
#define RETRIEVE_MEM_FUNC_SYMBOL(name, ...) \
|
|
||||||
(name##Sym = reinterpret_cast<name##Type::FunType>(hook_helper::Dlsym(handler, __VA_ARGS__)))
|
|
||||||
|
|
||||||
#define RETRIEVE_MEM_FUNC_SYMBOL_OR_FAIL(name, ...) \
|
|
||||||
RETRIEVE_MEM_FUNC_SYMBOL(name, __VA_ARGS__); if (!name##Sym) return false;
|
|
||||||
|
|
||||||
#define RETRIEVE_FIELD_SYMBOL(name, ...) \
|
|
||||||
(name = reinterpret_cast<decltype(name)>(hook_helper::Dlsym(handler, __VA_ARGS__)))
|
|
||||||
|
|
||||||
#define CREATE_FUNC_SYMBOL_ENTRY(ret, func, ...) \
|
|
||||||
typedef ret (*func##Type)(__VA_ARGS__); \
|
|
||||||
inline static ret (*func##Sym)(__VA_ARGS__); \
|
|
||||||
inline static ret func(__VA_ARGS__)
|
|
||||||
|
|
||||||
#define CREATE_MEM_FUNC_SYMBOL_ENTRY(ret, func, thiz, ...) \
|
|
||||||
using func##Type = hook_helper::MemberFunction<ret(__VA_ARGS__)>; \
|
|
||||||
inline static func##Type func##Sym; \
|
|
||||||
inline static ret func(thiz, ##__VA_ARGS__)
|
|
||||||
|
|
||||||
namespace hook_helper {
|
|
||||||
struct HookHandler {
|
|
||||||
virtual void *get_symbol(const char *name) const = 0;
|
|
||||||
|
|
||||||
virtual void *get_symbol_prefix(const char *prefix) const = 0;
|
|
||||||
|
|
||||||
// return backup
|
|
||||||
virtual void *hook(void *original, void *replacement) const = 0;
|
|
||||||
|
|
||||||
virtual std::pair<uintptr_t, size_t> get_symbol_info(const char *name) const = 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
inline namespace literals {
|
|
||||||
template<char... chars>
|
|
||||||
struct tstring : public std::integer_sequence<char, chars...> {
|
|
||||||
inline constexpr static const char *c_str() { return str_; }
|
|
||||||
|
|
||||||
inline constexpr operator std::string_view() const {
|
|
||||||
return {c_str(), sizeof...(chars)};
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
inline static constexpr char str_[]{chars..., '\0'};
|
|
||||||
};
|
|
||||||
|
|
||||||
template<typename T, T... chars>
|
|
||||||
inline constexpr tstring<chars...> operator ""_tstr() {
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
|
|
||||||
template<char... as, char... bs>
|
|
||||||
inline constexpr tstring<as..., bs...>
|
|
||||||
operator+(const tstring<as...> &, const tstring<bs...> &) {
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
|
|
||||||
template<char... as>
|
|
||||||
inline constexpr auto operator+(const std::string &a, const tstring<as...> &) {
|
|
||||||
char b[]{as..., '\0'};
|
|
||||||
return a + b;
|
|
||||||
}
|
|
||||||
|
|
||||||
template<char... as>
|
|
||||||
inline constexpr auto operator+(const tstring<as...> &, const std::string &b) {
|
|
||||||
char a[]{as..., '\0'};
|
|
||||||
return a + b;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
inline void *Dlsym(const HookHandler &handle, const char *name, bool match_prefix = false) {
|
|
||||||
if (auto match = handle.get_symbol(name); match) {
|
|
||||||
return match;
|
|
||||||
} else if (match_prefix) {
|
|
||||||
return handle.get_symbol_prefix(name);
|
|
||||||
}
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
template<typename Class, typename Return, typename T, typename... Args>
|
|
||||||
requires(std::is_same_v<T, void> ||
|
|
||||||
std::is_same_v<Class, T>) inline static auto
|
|
||||||
memfun_cast(Return (*func)(T *, Args...)) {
|
|
||||||
union {
|
|
||||||
Return (Class::*f)(Args...);
|
|
||||||
|
|
||||||
struct {
|
|
||||||
decltype(func) p;
|
|
||||||
std::ptrdiff_t adj;
|
|
||||||
} data;
|
|
||||||
} u{.data = {func, 0}};
|
|
||||||
static_assert(sizeof(u.f) == sizeof(u.data), "Try different T");
|
|
||||||
return u.f;
|
|
||||||
}
|
|
||||||
|
|
||||||
template<typename Class, typename Return, typename... Args>
|
|
||||||
inline static auto
|
|
||||||
memfun_addr(Return (Class::*func)(Args...)) {
|
|
||||||
union {
|
|
||||||
Return (Class::*f)(Args...);
|
|
||||||
|
|
||||||
struct {
|
|
||||||
void *p;
|
|
||||||
std::ptrdiff_t adj;
|
|
||||||
} data;
|
|
||||||
} u{.f = func};
|
|
||||||
static_assert(sizeof(u.f) == sizeof(u.data), "Try different T");
|
|
||||||
return u.data.p;
|
|
||||||
}
|
|
||||||
|
|
||||||
template<std::same_as<void> T, typename Return, typename... Args>
|
|
||||||
inline auto memfun_cast(Return (*func)(T *, Args...)) {
|
|
||||||
return memfun_cast<T>(func);
|
|
||||||
}
|
|
||||||
|
|
||||||
template<typename, typename = void>
|
|
||||||
class MemberFunction;
|
|
||||||
|
|
||||||
template<typename This, typename Return, typename... Args>
|
|
||||||
class MemberFunction<Return(Args...), This> {
|
|
||||||
using SelfType = MemberFunction<Return(This *, Args...), This>;
|
|
||||||
using ThisType = std::conditional_t<std::is_same_v<This, void>, SelfType, This>;
|
|
||||||
using MemFunType = Return (ThisType::*)(Args...);
|
|
||||||
|
|
||||||
public:
|
|
||||||
using FunType = Return (*)(This *, Args...);
|
|
||||||
|
|
||||||
private:
|
|
||||||
MemFunType f_ = nullptr;
|
|
||||||
|
|
||||||
public:
|
|
||||||
MemberFunction() = default;
|
|
||||||
|
|
||||||
MemberFunction(FunType f) : f_(memfun_cast<ThisType>(f)) {}
|
|
||||||
|
|
||||||
MemberFunction(MemFunType f) : f_(f) {}
|
|
||||||
|
|
||||||
Return operator()(This *thiz, Args... args) {
|
|
||||||
return (reinterpret_cast<ThisType *>(thiz)->*f_)(std::forward<Args>(args)...);
|
|
||||||
}
|
|
||||||
|
|
||||||
inline operator bool() { return f_ != nullptr; }
|
|
||||||
|
|
||||||
inline void *addr() {
|
|
||||||
return memfun_addr(f_);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// deduction guide
|
|
||||||
template<typename This, typename Return, typename... Args>
|
|
||||||
MemberFunction(Return (*f)(This *, Args...)) -> MemberFunction<Return(Args...), This>;
|
|
||||||
|
|
||||||
template<typename This, typename Return, typename... Args>
|
|
||||||
MemberFunction(Return (This::*f)(Args...)) -> MemberFunction<Return(Args...), This>;
|
|
||||||
|
|
||||||
template<typename, typename>
|
|
||||||
struct Hooker;
|
|
||||||
|
|
||||||
template<typename Ret, typename... Args, char... cs>
|
|
||||||
struct Hooker<Ret(Args...), tstring<cs...>> {
|
|
||||||
inline static Ret (*backup)(Args...) = nullptr;
|
|
||||||
|
|
||||||
inline static constexpr std::string_view sym = tstring<cs...>{};
|
|
||||||
};
|
|
||||||
|
|
||||||
template<typename, typename>
|
|
||||||
struct MemHooker;
|
|
||||||
template<typename Ret, typename This, typename... Args, char... cs>
|
|
||||||
struct MemHooker<Ret(This, Args...), tstring<cs...>> {
|
|
||||||
inline static MemberFunction<Ret(Args...)> backup;
|
|
||||||
inline static constexpr std::string_view sym = tstring<cs...>{};
|
|
||||||
};
|
|
||||||
|
|
||||||
template<typename T>
|
|
||||||
concept HookerType = requires(T a) {
|
|
||||||
a.backup;
|
|
||||||
a.replace;
|
|
||||||
};
|
|
||||||
|
|
||||||
template<HookerType T>
|
|
||||||
inline static bool HookSymNoHandle(const HookHandler &handler, void *original, T &arg) {
|
|
||||||
if (original) {
|
|
||||||
if constexpr (is_instance_v<decltype(arg.backup), MemberFunction>) {
|
|
||||||
void *backup = handler.hook(original, reinterpret_cast<void *>(arg.replace));
|
|
||||||
arg.backup = reinterpret_cast<typename decltype(arg.backup)::FunType>(backup);
|
|
||||||
} else {
|
|
||||||
arg.backup = reinterpret_cast<decltype(arg.backup)>(
|
|
||||||
handler.hook(original, reinterpret_cast<void *>(arg.replace)));
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
} else {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
template<HookerType T>
|
|
||||||
inline static bool HookSym(const HookHandler &handler, T &arg) {
|
|
||||||
auto original = handler.get_symbol(arg.sym.data());
|
|
||||||
return HookSymNoHandle(handler, original, arg);
|
|
||||||
}
|
|
||||||
|
|
||||||
template<HookerType T, HookerType... Args>
|
|
||||||
inline static bool HookSyms(const HookHandler &handle, T &first, Args &...rest) {
|
|
||||||
if (!(HookSym(handle, first) || ... || HookSym(handle, rest))) {
|
|
||||||
__android_log_print(ANDROID_LOG_ERROR,
|
|
||||||
#ifdef LOG_TAG
|
|
||||||
LOG_TAG,
|
|
||||||
#else
|
|
||||||
"HookHelper",
|
|
||||||
#endif
|
|
||||||
"Hook Fails: %*s", static_cast<int>(first.sym.size()),
|
|
||||||
first.sym.data());
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace hook_helper
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include <type_traits>
|
|
||||||
|
|
||||||
namespace hook_helper {
|
|
||||||
template<class, template<class, class...> class>
|
|
||||||
struct is_instance : public std::false_type {
|
|
||||||
};
|
|
||||||
|
|
||||||
template<class... Ts, template<class, class...> class U>
|
|
||||||
struct is_instance<U<Ts...>, U> : public std::true_type {
|
|
||||||
};
|
|
||||||
|
|
||||||
template<class T, template<class, class...> class U>
|
|
||||||
inline constexpr bool is_instance_v = is_instance<T, U>::value;
|
|
||||||
}
|
|
||||||
@@ -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, ®s, sizeof(regs));
|
|
||||||
{
|
|
||||||
map = lsplt::MapInfo::Scan(std::to_string(pid));
|
|
||||||
}
|
|
||||||
auto local_map = lsplt::MapInfo::Scan();
|
|
||||||
auto libc_return_addr = find_module_return_addr(map, "libc.so");
|
|
||||||
LOGD("libc return addr %p", libc_return_addr);
|
|
||||||
|
|
||||||
std::vector<uintptr_t> args;
|
|
||||||
uintptr_t str, remote_handle, injector_entry;
|
|
||||||
auto close_addr = find_func_addr(local_map, map, "libc.so", "close");
|
|
||||||
if (!close_addr) return false;
|
|
||||||
|
|
||||||
int lib_fd = -1;
|
|
||||||
|
|
||||||
// prepare fd
|
|
||||||
{
|
|
||||||
set_sockcreate_con("u:object_r:system_file:s0");
|
|
||||||
UniqueFd local_socket = socket(AF_UNIX, SOCK_DGRAM | SOCK_CLOEXEC, 0);
|
|
||||||
if (local_socket == -1) {
|
|
||||||
PLOGE("create local socket");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (setfilecon(lib_path, "u:object_r:system_file:s0") == -1) {
|
|
||||||
PLOGE("set context of lib");
|
|
||||||
}
|
|
||||||
UniqueFd local_lib_fd = open(lib_path, O_RDONLY | O_CLOEXEC);
|
|
||||||
if (local_lib_fd == -1) {
|
|
||||||
PLOGE("open lib");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
auto socket_addr = find_func_addr(local_map, map, "libc.so", "socket");
|
|
||||||
auto bind_addr = find_func_addr(local_map, map, "libc.so", "bind");
|
|
||||||
auto recvmsg_addr = find_func_addr(local_map, map, "libc.so", "recvmsg");
|
|
||||||
auto errno_addr = find_func_addr(local_map, map, "libc.so", "__errno");
|
|
||||||
auto get_remote_errno = [&]() -> int {
|
|
||||||
if (!errno_addr) {
|
|
||||||
LOGE("could not get errno!");
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
auto addr = remote_call(pid, regs, (uintptr_t) errno_addr, 0, args);
|
|
||||||
int err = 0;
|
|
||||||
if (!read_proc(pid, addr, &err, sizeof(err))) return 0;
|
|
||||||
return err;
|
|
||||||
};
|
|
||||||
if (!socket_addr || !bind_addr || !recvmsg_addr) return false;
|
|
||||||
args.clear();
|
|
||||||
args.push_back(AF_UNIX);
|
|
||||||
args.push_back(SOCK_DGRAM | SOCK_CLOEXEC);
|
|
||||||
args.push_back(0);
|
|
||||||
int remote_fd = (int) remote_call(pid, regs, (uintptr_t) socket_addr, 0, args);
|
|
||||||
if (remote_fd == -1) {
|
|
||||||
errno = get_remote_errno();
|
|
||||||
PLOGE("remote socket");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto close_remote = [&](int fd) -> void {
|
|
||||||
args.clear();
|
|
||||||
args.push_back(fd);
|
|
||||||
if (remote_call(pid, regs, (uintptr_t) close_addr, 0, args) != 0) {
|
|
||||||
LOGE("remote not closed: %d", fd);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
auto magic = generateMagic(16);
|
|
||||||
struct sockaddr_un addr{
|
|
||||||
.sun_family = AF_UNIX,
|
|
||||||
.sun_path = {0}
|
|
||||||
};
|
|
||||||
LOGD("socket name %s", magic.c_str());
|
|
||||||
memcpy(addr.sun_path + 1, magic.c_str(), magic.size());
|
|
||||||
socklen_t len = sizeof(addr.sun_family) + 1 + magic.size();
|
|
||||||
auto remote_addr = push_memory(pid, regs, &addr, sizeof(addr));
|
|
||||||
args.clear();
|
|
||||||
args.push_back(remote_fd);
|
|
||||||
args.push_back(remote_addr);
|
|
||||||
args.push_back(len);
|
|
||||||
auto bind_result = remote_call(pid, regs, (uintptr_t) bind_addr, 0, args);
|
|
||||||
if (bind_result == (uintptr_t) -1) {
|
|
||||||
errno = get_remote_errno();
|
|
||||||
PLOGE("remote bind");
|
|
||||||
close_remote(remote_fd);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
char cmsgbuf[CMSG_SPACE(sizeof(int))] = {0};
|
|
||||||
auto remote_cmsgbuf = push_memory(pid, regs, &cmsgbuf, sizeof(cmsgbuf));
|
|
||||||
|
|
||||||
struct msghdr hdr{};
|
|
||||||
hdr.msg_control = (void*) remote_cmsgbuf;
|
|
||||||
hdr.msg_controllen = sizeof(cmsgbuf);
|
|
||||||
|
|
||||||
auto remote_hdr = push_memory(pid, regs, &hdr, sizeof(hdr));
|
|
||||||
|
|
||||||
args.clear();
|
|
||||||
args.push_back(remote_fd);
|
|
||||||
args.push_back(remote_hdr);
|
|
||||||
args.push_back(MSG_WAITALL);
|
|
||||||
if (!remote_pre_call(pid, regs, (uintptr_t) recvmsg_addr, 0, args)) {
|
|
||||||
// we can't do anything more
|
|
||||||
LOGE("pre call remote recvmsg");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
hdr.msg_control = &cmsgbuf;
|
|
||||||
hdr.msg_name = &addr;
|
|
||||||
hdr.msg_namelen = len;
|
|
||||||
{
|
|
||||||
cmsghdr *cmsg = CMSG_FIRSTHDR(&hdr);
|
|
||||||
cmsg->cmsg_len = CMSG_LEN(sizeof(int));
|
|
||||||
cmsg->cmsg_level = SOL_SOCKET;
|
|
||||||
cmsg->cmsg_type = AF_UNIX;
|
|
||||||
*(int *) CMSG_DATA(cmsg) = local_lib_fd;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (sendmsg(local_socket, &hdr, 0) == -1) {
|
|
||||||
PLOGE("send to remote");
|
|
||||||
close_remote(remote_fd);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto recvmsg_result = (ssize_t) remote_post_call(pid, regs, 0);
|
|
||||||
if (recvmsg_result == -1) {
|
|
||||||
errno = get_remote_errno();
|
|
||||||
PLOGE("post call recvmsg");
|
|
||||||
close_remote(remote_fd);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (read_proc(pid, remote_cmsgbuf, &cmsgbuf, sizeof(cmsgbuf)) != sizeof(cmsgbuf)) {
|
|
||||||
LOGE("failed to read proc");
|
|
||||||
close_remote(remote_fd);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
cmsghdr *cmsg = CMSG_FIRSTHDR(&hdr);
|
|
||||||
if (cmsg == nullptr || cmsg->cmsg_len != CMSG_LEN(sizeof(int)) || cmsg->cmsg_level != SOL_SOCKET || cmsg->cmsg_type != SCM_RIGHTS) {
|
|
||||||
LOGE("remote recv fd failed!");
|
|
||||||
close_remote(remote_fd);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
lib_fd = *(int*) CMSG_DATA(cmsg);
|
|
||||||
LOGD("remote lib fd: %d", lib_fd);
|
|
||||||
close_remote(remote_fd);
|
|
||||||
}
|
|
||||||
|
|
||||||
// call dlopen
|
|
||||||
{
|
|
||||||
auto dlopen_addr = find_func_addr(local_map, map, "libdl.so", "android_dlopen_ext");
|
|
||||||
if (dlopen_addr == nullptr) return false;
|
|
||||||
android_dlextinfo info{};
|
|
||||||
info.flags = ANDROID_DLEXT_USE_LIBRARY_FD;
|
|
||||||
info.library_fd = lib_fd;
|
|
||||||
uintptr_t remote_info = push_memory(pid, regs, &info, sizeof(info));
|
|
||||||
str = push_string(pid, regs, lib_path);
|
|
||||||
args.clear();
|
|
||||||
args.push_back((long) str);
|
|
||||||
args.push_back((long) RTLD_NOW);
|
|
||||||
args.push_back(remote_info);
|
|
||||||
remote_handle = remote_call(pid, regs, (uintptr_t) dlopen_addr,
|
|
||||||
(uintptr_t) libc_return_addr, args);
|
|
||||||
LOGD("remote handle %p", (void *) remote_handle);
|
|
||||||
if (remote_handle == 0) {
|
|
||||||
LOGE("handle is null");
|
|
||||||
// call dlerror
|
|
||||||
auto dlerror_addr = find_func_addr(local_map, map, "libdl.so", "dlerror");
|
|
||||||
if (dlerror_addr == nullptr) {
|
|
||||||
LOGE("find dlerror");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
args.clear();
|
|
||||||
auto dlerror_str_addr = remote_call(pid, regs, (uintptr_t) dlerror_addr,
|
|
||||||
(uintptr_t) libc_return_addr, args);
|
|
||||||
LOGD("dlerror str %p", (void *) dlerror_str_addr);
|
|
||||||
if (dlerror_str_addr == 0) return false;
|
|
||||||
auto strlen_addr = find_func_addr(local_map, map, "libc.so", "strlen");
|
|
||||||
if (strlen_addr == nullptr) {
|
|
||||||
LOGE("find strlen");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
args.clear();
|
|
||||||
args.push_back(dlerror_str_addr);
|
|
||||||
auto dlerror_len = remote_call(pid, regs, (uintptr_t) strlen_addr,
|
|
||||||
(uintptr_t) libc_return_addr, args);
|
|
||||||
if (dlerror_len <= 0) {
|
|
||||||
LOGE("dlerror len <= 0");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
std::string err;
|
|
||||||
err.resize(dlerror_len + 1, 0);
|
|
||||||
read_proc(pid, (uintptr_t) dlerror_str_addr, err.data(), dlerror_len);
|
|
||||||
LOGE("dlerror info %s", err.c_str());
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
args.clear();
|
|
||||||
args.push_back(lib_fd);
|
|
||||||
if (remote_call(pid, regs, (uintptr_t) close_addr, 0, args) != 0) {
|
|
||||||
LOGE("remote lib not closed: %d", lib_fd);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// call dlsym(handle, "entry")
|
|
||||||
{
|
|
||||||
auto dlsym_addr = find_func_addr(local_map, map, "libdl.so", "dlsym");
|
|
||||||
if (dlsym_addr == nullptr) return false;
|
|
||||||
args.clear();
|
|
||||||
str = push_string(pid, regs, "entry");
|
|
||||||
args.push_back(remote_handle);
|
|
||||||
args.push_back((long) str);
|
|
||||||
injector_entry = remote_call(pid, regs, (uintptr_t) dlsym_addr,
|
|
||||||
(uintptr_t) libc_return_addr, args);
|
|
||||||
LOGD("injector entry %p", (void *) injector_entry);
|
|
||||||
if (injector_entry == 0) {
|
|
||||||
LOGE("injector entry is null");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// call injector entry(handle)
|
|
||||||
{
|
|
||||||
args.clear();
|
|
||||||
args.push_back(remote_handle);
|
|
||||||
remote_call(pid, regs, injector_entry, (uintptr_t) libc_return_addr, args);
|
|
||||||
}
|
|
||||||
|
|
||||||
LOGD("restore context");
|
|
||||||
// restore registers
|
|
||||||
if (!set_regs(pid, backup)) return false;
|
|
||||||
if (ptrace(PTRACE_DETACH, pid, 0, 0) == -1) {
|
|
||||||
PLOGE("failed to detach");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
} else {
|
|
||||||
LOGE("stopped by other reason: %s", parse_status(status).c_str());
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
int main(int argc, char **argv) {
|
|
||||||
#ifndef NDEBUG
|
|
||||||
logging::setPrintEnabled(true);
|
|
||||||
#endif
|
|
||||||
auto pid = strtol(argv[1], nullptr, 0);
|
|
||||||
char buf[4096];
|
|
||||||
realpath(argv[2], buf);
|
|
||||||
return !inject_library(pid, buf, argv[3]);
|
|
||||||
}
|
|
||||||
@@ -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 ®s) {
|
|
||||||
#if defined(__x86_64__) || defined(__i386__)
|
|
||||||
if (ptrace(PTRACE_GETREGS, pid, 0, ®s) == -1) {
|
|
||||||
PLOGE("getregs");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
#elif defined(__aarch64__) || defined(__arm__)
|
|
||||||
struct iovec iov = {
|
|
||||||
.iov_base = ®s,
|
|
||||||
.iov_len = sizeof(struct user_regs_struct),
|
|
||||||
};
|
|
||||||
if (ptrace(PTRACE_GETREGSET, pid, NT_PRSTATUS, &iov) == -1) {
|
|
||||||
PLOGE("getregs");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool set_regs(int pid, struct user_regs_struct ®s) {
|
|
||||||
#if defined(__x86_64__) || defined(__i386__)
|
|
||||||
if (ptrace(PTRACE_SETREGS, pid, 0, ®s) == -1) {
|
|
||||||
PLOGE("setregs");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
#elif defined(__aarch64__) || defined(__arm__)
|
|
||||||
struct iovec iov = {
|
|
||||||
.iov_base = ®s,
|
|
||||||
.iov_len = sizeof(struct user_regs_struct),
|
|
||||||
};
|
|
||||||
if (ptrace(PTRACE_SETREGSET, pid, NT_PRSTATUS, &iov) == -1) {
|
|
||||||
PLOGE("setregs");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string get_addr_mem_region(std::vector<lsplt::MapInfo> &info, uintptr_t addr) {
|
|
||||||
for (auto &map: info) {
|
|
||||||
if (map.start <= addr && map.end > addr) {
|
|
||||||
auto s = std::string(map.path);
|
|
||||||
s += ' ';
|
|
||||||
s += map.perms & PROT_READ ? 'r' : '-';
|
|
||||||
s += map.perms & PROT_WRITE ? 'w' : '-';
|
|
||||||
s += map.perms & PROT_EXEC ? 'x' : '-';
|
|
||||||
return s;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return "<unknown>";
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void *find_module_return_addr(std::vector<lsplt::MapInfo> &info, std::string_view suffix) {
|
|
||||||
for (auto &map: info) {
|
|
||||||
if ((map.perms & PROT_EXEC) == 0 && map.path.ends_with(suffix)) {
|
|
||||||
return (void *) map.start;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
void *find_module_base(std::vector<lsplt::MapInfo> &info, std::string_view suffix) {
|
|
||||||
for (auto &map: info) {
|
|
||||||
if (map.offset == 0 && map.path.ends_with(suffix)) {
|
|
||||||
return (void *) map.start;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
void *find_func_addr(
|
|
||||||
std::vector<lsplt::MapInfo> &local_info,
|
|
||||||
std::vector<lsplt::MapInfo> &remote_info,
|
|
||||||
std::string_view module,
|
|
||||||
std::string_view func) {
|
|
||||||
auto lib = dlopen(module.data(), RTLD_NOW);
|
|
||||||
if (lib == nullptr) {
|
|
||||||
LOGE("failed to open lib %s: %s", module.data(), dlerror());
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
auto sym = reinterpret_cast<uint8_t *>(dlsym(lib, func.data()));
|
|
||||||
if (sym == nullptr) {
|
|
||||||
LOGE("failed to find sym %s in %s: %s", func.data(), module.data(), dlerror());
|
|
||||||
dlclose(lib);
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
LOGD("sym %s: %p", func.data(), sym);
|
|
||||||
dlclose(lib);
|
|
||||||
auto local_base = reinterpret_cast<uint8_t *>(find_module_base(local_info, module));
|
|
||||||
if (local_base == nullptr) {
|
|
||||||
LOGE("failed to find local base for module %s", module.data());
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
auto remote_base = reinterpret_cast<uint8_t *>(find_module_base(remote_info, module));
|
|
||||||
if (remote_base == nullptr) {
|
|
||||||
LOGE("failed to find remote base for module %s", module.data());
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
LOGD("found local base %p remote base %p", local_base, remote_base);
|
|
||||||
auto addr = (sym - local_base) + remote_base;
|
|
||||||
LOGD("addr %p", addr);
|
|
||||||
return addr;
|
|
||||||
}
|
|
||||||
|
|
||||||
void align_stack(struct user_regs_struct ®s, uintptr_t preserve) {
|
|
||||||
regs.REG_SP = (regs.REG_SP - preserve) & ~0xf;
|
|
||||||
}
|
|
||||||
|
|
||||||
uintptr_t push_memory(int pid, struct user_regs_struct ®s, void* local_addr, size_t len) {
|
|
||||||
regs.REG_SP -= len;
|
|
||||||
align_stack(regs);
|
|
||||||
auto addr = static_cast<uintptr_t>(regs.REG_SP);
|
|
||||||
if (!write_proc(pid, addr, local_addr, len)) {
|
|
||||||
LOGE("failed to write mem %p+%zu", local_addr, len);
|
|
||||||
}
|
|
||||||
LOGD("pushed mem %" PRIxPTR, addr);
|
|
||||||
return addr;
|
|
||||||
}
|
|
||||||
|
|
||||||
uintptr_t push_string(int pid, struct user_regs_struct ®s, const char *str) {
|
|
||||||
auto len = strlen(str) + 1;
|
|
||||||
regs.REG_SP -= len;
|
|
||||||
align_stack(regs);
|
|
||||||
auto addr = static_cast<uintptr_t>(regs.REG_SP);
|
|
||||||
if (!write_proc(pid, addr, str, len)) {
|
|
||||||
LOGE("failed to write string %s", str);
|
|
||||||
}
|
|
||||||
LOGD("pushed string %" PRIxPTR, addr);
|
|
||||||
return addr;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool remote_pre_call(int pid, struct user_regs_struct ®s, uintptr_t func_addr, uintptr_t return_addr,
|
|
||||||
std::vector<uintptr_t> &args) {
|
|
||||||
align_stack(regs);
|
|
||||||
LOGV("calling remote function %" PRIxPTR " args %zu", func_addr, args.size());
|
|
||||||
for (auto &a: args) {
|
|
||||||
LOGV("arg %p", (void *) a);
|
|
||||||
}
|
|
||||||
#if defined(__x86_64__)
|
|
||||||
if (args.size() >= 1) {
|
|
||||||
regs.rdi = args[0];
|
|
||||||
}
|
|
||||||
if (args.size() >= 2) {
|
|
||||||
regs.rsi = args[1];
|
|
||||||
}
|
|
||||||
if (args.size() >= 3) {
|
|
||||||
regs.rdx = args[2];
|
|
||||||
}
|
|
||||||
if (args.size() >= 4) {
|
|
||||||
regs.rcx = args[3];
|
|
||||||
}
|
|
||||||
if (args.size() >= 5) {
|
|
||||||
regs.r8 = args[4];
|
|
||||||
}
|
|
||||||
if (args.size() >= 6) {
|
|
||||||
regs.r9 = args[5];
|
|
||||||
}
|
|
||||||
if (args.size() > 6) {
|
|
||||||
auto remain = (args.size() - 6) * sizeof(uintptr_t);
|
|
||||||
align_stack(regs, remain);
|
|
||||||
if (!write_proc(pid, (uintptr_t) regs.REG_SP, args.data(), remain)) {
|
|
||||||
LOGE("failed to push arguments");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
regs.REG_SP -= sizeof(uintptr_t);
|
|
||||||
if (!write_proc(pid, (uintptr_t) regs.REG_SP, &return_addr, sizeof(return_addr))) {
|
|
||||||
LOGE("failed to write return addr");
|
|
||||||
}
|
|
||||||
regs.REG_IP = func_addr;
|
|
||||||
#elif defined(__i386__)
|
|
||||||
if (args.size() > 0) {
|
|
||||||
auto remain = (args.size()) * sizeof(uintptr_t);
|
|
||||||
align_stack(regs, remain);
|
|
||||||
if (!write_proc(pid, (uintptr_t) regs.REG_SP, args.data(), remain)) {
|
|
||||||
LOGE("failed to push arguments");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
regs.REG_SP -= sizeof(uintptr_t);
|
|
||||||
if (!write_proc(pid, (uintptr_t) regs.REG_SP, &return_addr, sizeof(return_addr))) {
|
|
||||||
LOGE("failed to write return addr");
|
|
||||||
}
|
|
||||||
regs.REG_IP = func_addr;
|
|
||||||
#elif defined(__aarch64__)
|
|
||||||
for (size_t i = 0; i < args.size() && i < 8; i++) {
|
|
||||||
regs.regs[i] = args[i];
|
|
||||||
}
|
|
||||||
if (args.size() > 8) {
|
|
||||||
auto remain = (args.size() - 8) * sizeof(uintptr_t);
|
|
||||||
align_stack(regs, remain);
|
|
||||||
write_proc(pid, (uintptr_t)regs.REG_SP, args.data(), remain);
|
|
||||||
}
|
|
||||||
regs.regs[30] = return_addr;
|
|
||||||
regs.REG_IP = func_addr;
|
|
||||||
#elif defined(__arm__)
|
|
||||||
for (size_t i = 0; i < args.size() && i < 4; i++) {
|
|
||||||
regs.uregs[i] = args[i];
|
|
||||||
}
|
|
||||||
if (args.size() > 4) {
|
|
||||||
auto remain = (args.size() - 4) * sizeof(uintptr_t);
|
|
||||||
align_stack(regs, remain);
|
|
||||||
write_proc(pid, (uintptr_t)regs.REG_SP, args.data(), remain);
|
|
||||||
}
|
|
||||||
regs.uregs[14] = return_addr;
|
|
||||||
regs.REG_IP = func_addr;
|
|
||||||
constexpr auto CPSR_T_MASK = 1lu << 5;
|
|
||||||
if ((regs.REG_IP & 1) != 0) {
|
|
||||||
regs.REG_IP = regs.REG_IP & ~1;
|
|
||||||
regs.uregs[16] = regs.uregs[16] | CPSR_T_MASK;
|
|
||||||
} else {
|
|
||||||
regs.uregs[16] = regs.uregs[16] & ~CPSR_T_MASK;
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
if (!set_regs(pid, regs)) {
|
|
||||||
LOGE("failed to set regs");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return ptrace(PTRACE_CONT, pid, 0, 0) == 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
uintptr_t remote_post_call(int pid, struct user_regs_struct ®s, uintptr_t return_addr) {
|
|
||||||
int status;
|
|
||||||
wait_for_trace(pid, &status, __WALL);
|
|
||||||
if (!get_regs(pid, regs)) {
|
|
||||||
LOGE("failed to get regs after call");
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
if (WSTOPSIG(status) == SIGSEGV) {
|
|
||||||
if (static_cast<uintptr_t>(regs.REG_IP) != return_addr) {
|
|
||||||
LOGE("wrong return addr %p", (void *) regs.REG_IP);
|
|
||||||
siginfo_t siginfo;
|
|
||||||
if (ptrace(PTRACE_GETSIGINFO, pid, 0, &siginfo) == -1) {
|
|
||||||
PLOGE("failed to get siginfo");
|
|
||||||
} else {
|
|
||||||
LOGE("si_code=%d si_addr=%p", siginfo.si_code, siginfo.si_addr);
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
return regs.REG_RET;
|
|
||||||
} else {
|
|
||||||
LOGE("stopped by other reason %s at addr %p", parse_status(status).c_str(), (void*) regs.REG_IP);
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
uintptr_t remote_call(int pid, struct user_regs_struct ®s, uintptr_t func_addr, uintptr_t return_addr,
|
|
||||||
std::vector<uintptr_t> &args) {
|
|
||||||
if (!remote_pre_call(pid, regs, func_addr, return_addr, args)) return 0;
|
|
||||||
return remote_post_call(pid, regs, return_addr);
|
|
||||||
}
|
|
||||||
|
|
||||||
int fork_dont_care() {
|
|
||||||
auto pid = fork();
|
|
||||||
if (pid < 0) {
|
|
||||||
PLOGE("fork 1");
|
|
||||||
} else if (pid == 0) {
|
|
||||||
pid = fork();
|
|
||||||
if (pid < 0) {
|
|
||||||
PLOGE("fork 2");
|
|
||||||
} else if (pid > 0) {
|
|
||||||
exit(0);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
int status;
|
|
||||||
waitpid(pid, &status, __WALL);
|
|
||||||
}
|
|
||||||
return pid;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool wait_for_trace(int pid, int* status, int flags) {
|
|
||||||
while (true) {
|
|
||||||
auto result = waitpid(pid, status, flags);
|
|
||||||
if (result == -1) {
|
|
||||||
if (errno == EINTR) {
|
|
||||||
continue;
|
|
||||||
} else {
|
|
||||||
PLOGE("wait %d failed", pid);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!WIFSTOPPED(*status)) {
|
|
||||||
LOGE("process %d not stopped for trace: %s, exit", pid, parse_status(*status).c_str());
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string parse_status(int status) {
|
|
||||||
char buf[64];
|
|
||||||
if (WIFEXITED(status)) {
|
|
||||||
snprintf(buf, sizeof(buf), "0x%x exited with %d", status, WEXITSTATUS(status));
|
|
||||||
} else if (WIFSIGNALED(status)) {
|
|
||||||
snprintf(buf, sizeof(buf), "0x%x signaled with %s(%d)", status, sigabbrev_np(WTERMSIG(status)), WTERMSIG(status));
|
|
||||||
} else if (WIFSTOPPED(status)) {
|
|
||||||
auto stop_sig = WSTOPSIG(status);
|
|
||||||
snprintf(buf, sizeof(buf), "0x%x stopped by signal=%s(%d),event=%s", status, sigabbrev_np(stop_sig), stop_sig, parse_ptrace_event(status));
|
|
||||||
} else {
|
|
||||||
snprintf(buf, sizeof(buf), "0x%x unknown", status);
|
|
||||||
}
|
|
||||||
return buf;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string get_program(int pid) {
|
|
||||||
std::string path = "/proc/";
|
|
||||||
path += std::to_string(pid);
|
|
||||||
path += "/exe";
|
|
||||||
constexpr const auto SIZE = 256;
|
|
||||||
char buf[SIZE + 1];
|
|
||||||
auto sz = readlink(path.c_str(), buf, SIZE);
|
|
||||||
if (sz == -1) {
|
|
||||||
PLOGE("readlink /proc/%d/exe", pid);
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
buf[sz] = 0;
|
|
||||||
return buf;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string parse_exec(int pid) {
|
|
||||||
struct user_regs_struct regs;
|
|
||||||
if (!get_regs(pid, regs)) return "";
|
|
||||||
auto nr = regs.REG_NR;
|
|
||||||
if (nr != SYS_execve) {
|
|
||||||
// LOGI("syscall %ld != %d", nr, SYS_execve);
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
auto file_name_ptr = regs.REG_SYS_ARG0;
|
|
||||||
char buf[128];
|
|
||||||
auto sz = read_proc(pid, file_name_ptr, buf, sizeof(buf));
|
|
||||||
if (sz < 0) return "";
|
|
||||||
for (auto i = 0; i < sz; i++) {
|
|
||||||
if (buf[i] == 0) {
|
|
||||||
LOGD("exec len %d prog %s", i, buf);
|
|
||||||
return buf;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// too long
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
|
|
||||||
bool skip_syscall(int pid) {
|
|
||||||
struct user_regs_struct regs;
|
|
||||||
if (!get_regs(pid, regs)) return false;
|
|
||||||
#if defined(__aarch64__)
|
|
||||||
// https://stackoverflow.com/questions/63620203/ptrace-change-syscall-number-arm64
|
|
||||||
int syscallno = 0xffff;
|
|
||||||
struct iovec iov = {
|
|
||||||
.iov_base = &syscallno,
|
|
||||||
.iov_len = sizeof (int),
|
|
||||||
};
|
|
||||||
if (ptrace(PTRACE_SETREGSET, pid, NT_ARM_SYSTEM_CALL, &iov) == -1) {
|
|
||||||
PLOGE("failed to set syscall");
|
|
||||||
}
|
|
||||||
#elif defined(__arm__)
|
|
||||||
if (ptrace(PTRACE_SET_SYSCALL, pid, 0, 0xffff) == -1) {
|
|
||||||
PLOGE("failed to set syscall");
|
|
||||||
}
|
|
||||||
#else
|
|
||||||
auto orig_nr = regs.REG_NR;
|
|
||||||
regs.REG_NR = 0xffff;
|
|
||||||
if (!set_regs(pid, regs)) return false;
|
|
||||||
regs.REG_NR = orig_nr;
|
|
||||||
#endif
|
|
||||||
if (ptrace(PTRACE_SYSCALL, pid, 0, 0) == -1) {
|
|
||||||
PLOGE("failed to singlestep");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
int status;
|
|
||||||
waitpid(pid, &status, __WALL);
|
|
||||||
#if defined(__x86_64__)
|
|
||||||
regs.REG_IP -= 2;
|
|
||||||
regs.rax = orig_nr;
|
|
||||||
#elif defined(__i386__)
|
|
||||||
regs.REG_IP -= 2;
|
|
||||||
regs.eax = orig_nr;
|
|
||||||
#elif defined(__aarch64__)
|
|
||||||
regs.REG_IP -= 4;
|
|
||||||
#elif defined(__arm__)
|
|
||||||
regs.REG_IP -= (regs.REG_IP % 2) ? 2 : 4;
|
|
||||||
#endif
|
|
||||||
return set_regs(pid, regs);
|
|
||||||
}
|
|
||||||
|
|
||||||
void wait_for_syscall(int pid) {
|
|
||||||
int status;
|
|
||||||
for (;;) {
|
|
||||||
if (!wait_for_trace(pid, &status, __WALL)) {
|
|
||||||
LOGE("could not wait for trace");
|
|
||||||
exit(1);
|
|
||||||
}
|
|
||||||
if (WIFSTOPPED(status) && WSTOPSIG(status) == (SIGTRAP | 0x80)) {
|
|
||||||
LOGV("!! stopped at syscall");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
LOGV("! not syscall: %s", parse_status(status).c_str());
|
|
||||||
if (ptrace(PTRACE_CONT, pid, 0, WSTOPSIG(status)) == -1) {
|
|
||||||
PLOGE("failed to cont");
|
|
||||||
exit(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool do_syscall(int pid, uintptr_t &ret, int nr, uintptr_t arg0, uintptr_t arg1, uintptr_t arg2, uintptr_t arg3, uintptr_t arg4, uintptr_t arg5) {
|
|
||||||
#if defined(__i386__)
|
|
||||||
LOGE("do_syscall is unsupported on i386!");
|
|
||||||
return false;
|
|
||||||
#else
|
|
||||||
struct user_regs_struct regs, backup_regs;
|
|
||||||
if (ptrace(PTRACE_SYSCALL, pid, 0, 0) == -1) {
|
|
||||||
PLOGE("failed to singlestep");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
wait_for_syscall(pid);
|
|
||||||
if (!get_regs(pid, regs)) return false;
|
|
||||||
memcpy(&backup_regs, ®s, sizeof(struct user_regs_struct));
|
|
||||||
// set syscall nr and args
|
|
||||||
#if defined(__aarch64__)
|
|
||||||
// https://stackoverflow.com/questions/63620203/ptrace-change-syscall-number-arm64
|
|
||||||
int syscallno = nr;
|
|
||||||
struct iovec iov = {
|
|
||||||
.iov_base = &syscallno,
|
|
||||||
.iov_len = sizeof (int),
|
|
||||||
};
|
|
||||||
if (ptrace(PTRACE_SETREGSET, pid, NT_ARM_SYSTEM_CALL, &iov) == -1) {
|
|
||||||
PLOGE("failed to set syscall");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
regs.regs[0] = arg0;
|
|
||||||
regs.regs[1] = arg1;
|
|
||||||
regs.regs[2] = arg2;
|
|
||||||
regs.regs[3] = arg3;
|
|
||||||
regs.regs[4] = arg4;
|
|
||||||
regs.regs[5] = arg5;
|
|
||||||
#elif defined(__arm__)
|
|
||||||
if (ptrace(PTRACE_SET_SYSCALL, pid, 0, nr) == -1) {
|
|
||||||
PLOGE("failed to set syscall");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
regs.uregs[0] = arg0;
|
|
||||||
regs.uregs[1] = arg1;
|
|
||||||
regs.uregs[2] = arg2;
|
|
||||||
regs.uregs[3] = arg3;
|
|
||||||
regs.uregs[4] = arg4;
|
|
||||||
regs.uregs[5] = arg5;
|
|
||||||
#elif defined(__x86_64__)
|
|
||||||
auto orig_nr = regs.REG_NR;
|
|
||||||
regs.REG_NR = nr;
|
|
||||||
regs.rdi = arg0;
|
|
||||||
regs.rsi = arg1;
|
|
||||||
regs.rdx = arg2;
|
|
||||||
regs.r10 = arg3;
|
|
||||||
regs.r8 = arg4;
|
|
||||||
regs.r9 = arg5;
|
|
||||||
#endif
|
|
||||||
if (!set_regs(pid, regs)) return false;
|
|
||||||
if (ptrace(PTRACE_SYSCALL, pid, 0, 0) == -1) {
|
|
||||||
PLOGE("failed to singlestep");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
wait_for_syscall(pid);
|
|
||||||
if (!get_regs(pid, regs)) return false;
|
|
||||||
// go back to last instruction
|
|
||||||
// on x86, we should fill the nr register with orig_nr
|
|
||||||
#if defined(__x86_64__)
|
|
||||||
ret = regs.rax;
|
|
||||||
backup_regs.REG_IP -= 2;
|
|
||||||
backup_regs.rax = orig_nr;
|
|
||||||
#elif defined(__aarch64__)
|
|
||||||
ret = regs.regs[0];
|
|
||||||
backup_regs.REG_IP -= 4;
|
|
||||||
#elif defined(__arm__)
|
|
||||||
ret = regs.uregs[0];
|
|
||||||
backup_regs.REG_IP -= (regs.REG_IP % 2) ? 2 : 4;
|
|
||||||
#endif
|
|
||||||
return set_regs(pid, backup_regs);
|
|
||||||
#endif // i386
|
|
||||||
}
|
|
||||||
|
|
||||||
uintptr_t remote_syscall(int pid, uintptr_t &ret, int nr, uintptr_t arg0 = 0, uintptr_t arg1 = 0, uintptr_t arg2 = 0, uintptr_t arg3 = 0, uintptr_t arg4 = 0, uintptr_t arg5 = 0) {
|
|
||||||
if (!do_syscall(pid, ret, nr, arg0, arg1, arg2, arg3, arg4, arg5)) {
|
|
||||||
LOGE("do remote syscall %d failed", nr);
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
if (SYSCALL_IS_ERR(ret)) {
|
|
||||||
LOGE("do remote syscall %d return error %d %s", nr, SYSCALL_ERR(ret), strerror(SYSCALL_ERR(ret)));
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
uintptr_t remote_mmap(int pid, uintptr_t addr, size_t size, int prot, int flags, int fd, off_t offset) {
|
|
||||||
uintptr_t result;
|
|
||||||
LOGD("remote mmap %" PRIxPTR " size %zu fd %d off %lu", addr, size, fd, offset);
|
|
||||||
if (!remote_syscall(pid, result, SYS_mmap, addr, size, prot, flags, fd, offset)) {
|
|
||||||
LOGE("do remote mmap failed");
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
LOGD("remote mmap get %" PRIxPTR, result);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool remote_munmap(int pid, uintptr_t addr, size_t size) {
|
|
||||||
uintptr_t result = 0;
|
|
||||||
if (remote_syscall(pid, result, SYS_munmap, addr, size)) {
|
|
||||||
LOGE("do remote munmap failed");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
int remote_open(int pid, uintptr_t path_addr, int flags) {
|
|
||||||
uintptr_t result;
|
|
||||||
if (!remote_syscall(pid, result, SYS_openat, AT_FDCWD, path_addr, flags)) {
|
|
||||||
LOGE("remote open failed");
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool remote_close(int pid, int fd) {
|
|
||||||
uintptr_t result = 0;
|
|
||||||
if (remote_syscall(pid, result, SYS_close, fd)) {
|
|
||||||
LOGE("remote close failed");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
int wait_for_child(int pid) {
|
|
||||||
int status;
|
|
||||||
for (;;) {
|
|
||||||
if (waitpid(pid, &status, 0) == -1) {
|
|
||||||
if (errno == EINTR) continue;
|
|
||||||
PLOGE("waitpid %d", pid);
|
|
||||||
return -1;
|
|
||||||
} else {
|
|
||||||
return status;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string generateMagic(size_t len) {
|
|
||||||
constexpr const char chrs[] = "0123456789"
|
|
||||||
"abcdefghijklmnopqrstuvwxyz"
|
|
||||||
"ABCDEFGHIJKLMNOPQRSTUVWXYZ";
|
|
||||||
std::mt19937 rg{std::random_device{}()};
|
|
||||||
std::uniform_int_distribution<std::string::size_type> pick(0, sizeof(chrs) - 2);
|
|
||||||
|
|
||||||
std::string s;
|
|
||||||
s.reserve(len);
|
|
||||||
while(len--) s += chrs[pick(rg)];
|
|
||||||
|
|
||||||
return s;
|
|
||||||
}
|
|
||||||
|
|
||||||
int setfilecon(const char* path, const char* con) {
|
|
||||||
return syscall(__NR_setxattr, path, XATTR_NAME_SELINUX, con, strlen(con) + 1, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool set_sockcreate_con(const char* con) {
|
|
||||||
auto sz = static_cast<ssize_t>(strlen(con) + 1);
|
|
||||||
UniqueFd fd = open("/proc/thread-self/attr/sockcreate", O_WRONLY | O_CLOEXEC);
|
|
||||||
if (fd == -1 || write(fd, con, sz) != sz) {
|
|
||||||
PLOGE("set socket con");
|
|
||||||
char buf[128];
|
|
||||||
snprintf(buf, sizeof(buf), "/proc/%d/attr/sockcreate", gettid());
|
|
||||||
fd = open(buf, O_WRONLY | O_CLOEXEC);
|
|
||||||
if (fd == -1 || write(fd, con, sz) != sz) {
|
|
||||||
PLOGE("set socket con fallback");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
@@ -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 ®s);
|
|
||||||
|
|
||||||
bool set_regs(int pid, struct user_regs_struct ®s);
|
|
||||||
|
|
||||||
std::string get_addr_mem_region(std::vector<lsplt::MapInfo> &info, uintptr_t addr);
|
|
||||||
|
|
||||||
void *find_module_base(std::vector<lsplt::MapInfo> &info, std::string_view suffix);
|
|
||||||
|
|
||||||
void *find_func_addr(
|
|
||||||
std::vector<lsplt::MapInfo> &local_info,
|
|
||||||
std::vector<lsplt::MapInfo> &remote_info,
|
|
||||||
std::string_view module,
|
|
||||||
std::string_view func);
|
|
||||||
|
|
||||||
void align_stack(struct user_regs_struct ®s, uintptr_t preserve = 0);
|
|
||||||
|
|
||||||
uintptr_t push_memory(int pid, struct user_regs_struct ®s, void* local_addr, size_t len);
|
|
||||||
|
|
||||||
uintptr_t push_string(int pid, struct user_regs_struct ®s, const char *str);
|
|
||||||
|
|
||||||
uintptr_t remote_call(int pid, struct user_regs_struct ®s, uintptr_t func_addr, uintptr_t return_addr,
|
|
||||||
std::vector<uintptr_t> &args);
|
|
||||||
|
|
||||||
int fork_dont_care();
|
|
||||||
|
|
||||||
bool wait_for_trace(int pid, int* status, int flags);
|
|
||||||
|
|
||||||
std::string parse_status(int status);
|
|
||||||
|
|
||||||
#define WPTEVENT(x) (x >> 16)
|
|
||||||
|
|
||||||
#define CASE_CONST_RETURN(x) case x: return #x;
|
|
||||||
|
|
||||||
inline const char* parse_ptrace_event(int status) {
|
|
||||||
status = status >> 16;
|
|
||||||
switch (status) {
|
|
||||||
CASE_CONST_RETURN(PTRACE_EVENT_FORK)
|
|
||||||
CASE_CONST_RETURN(PTRACE_EVENT_VFORK)
|
|
||||||
CASE_CONST_RETURN(PTRACE_EVENT_CLONE)
|
|
||||||
CASE_CONST_RETURN(PTRACE_EVENT_EXEC)
|
|
||||||
CASE_CONST_RETURN(PTRACE_EVENT_VFORK_DONE)
|
|
||||||
CASE_CONST_RETURN(PTRACE_EVENT_EXIT)
|
|
||||||
CASE_CONST_RETURN(PTRACE_EVENT_SECCOMP)
|
|
||||||
CASE_CONST_RETURN(PTRACE_EVENT_STOP)
|
|
||||||
default:
|
|
||||||
return "(no event)";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
inline const char* sigabbrev_np(int sig) {
|
|
||||||
if (sig > 0 && sig < NSIG) return sys_signame[sig];
|
|
||||||
return "(unknown)";
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string get_program(int pid);
|
|
||||||
void *find_module_return_addr(std::vector<lsplt::MapInfo> &info, std::string_view suffix);
|
|
||||||
|
|
||||||
// pid = 0, fd != nullptr -> set to fd
|
|
||||||
// pid != 0, fd != nullptr -> set to pid ns, give orig ns in fd
|
|
||||||
bool switch_mnt_ns(int pid, int *fd);
|
|
||||||
|
|
||||||
std::vector<std::string> get_cmdline(int pid);
|
|
||||||
|
|
||||||
std::string parse_exec(int pid);
|
|
||||||
|
|
||||||
bool skip_syscall(int pid);
|
|
||||||
bool do_syscall(int pid, uintptr_t &ret, int nr, uintptr_t arg0 = 0, uintptr_t arg1 = 0, uintptr_t arg2 = 0, uintptr_t arg3 = 0, uintptr_t arg4 = 0, uintptr_t arg5 = 0);
|
|
||||||
|
|
||||||
uintptr_t remote_mmap(int pid, uintptr_t addr, size_t size, int prot, int flags, int fd, off_t offset);
|
|
||||||
bool remote_munmap(int pid, uintptr_t addr, size_t size);
|
|
||||||
|
|
||||||
int remote_open(int pid, uintptr_t path_addr, int flags);
|
|
||||||
bool remote_close(int pid, int fd);
|
|
||||||
|
|
||||||
int wait_for_child(int pid);
|
|
||||||
int get_elf_class(std::string_view path);
|
|
||||||
|
|
||||||
bool remote_pre_call(int pid, struct user_regs_struct ®s, uintptr_t func_addr, uintptr_t return_addr,
|
|
||||||
std::vector<uintptr_t> &args);
|
|
||||||
|
|
||||||
uintptr_t remote_post_call(int pid, struct user_regs_struct ®s, uintptr_t return_addr);
|
|
||||||
|
|
||||||
|
|
||||||
// magic.h
|
|
||||||
constexpr const auto kMainMagicLength = 16;
|
|
||||||
std::string generateMagic(size_t len);
|
|
||||||
|
|
||||||
// files.hpp
|
|
||||||
|
|
||||||
int setfilecon(const char* path, const char* con);
|
|
||||||
|
|
||||||
// utils.hpp
|
|
||||||
class UniqueFd {
|
|
||||||
using Fd = int;
|
|
||||||
public:
|
|
||||||
UniqueFd() = default;
|
|
||||||
|
|
||||||
UniqueFd(Fd fd) : fd_(fd) {}
|
|
||||||
|
|
||||||
~UniqueFd() { if (fd_ >= 0) close(fd_); }
|
|
||||||
|
|
||||||
// Disallow copy
|
|
||||||
UniqueFd(const UniqueFd&) = delete;
|
|
||||||
|
|
||||||
UniqueFd& operator=(const UniqueFd&) = delete;
|
|
||||||
|
|
||||||
// Allow move
|
|
||||||
UniqueFd(UniqueFd&& other) { std::swap(fd_, other.fd_); }
|
|
||||||
|
|
||||||
UniqueFd& operator=(UniqueFd&& other) {
|
|
||||||
std::swap(fd_, other.fd_);
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Implict cast to Fd
|
|
||||||
operator const Fd&() const { return fd_; }
|
|
||||||
|
|
||||||
private:
|
|
||||||
Fd fd_ = -1;
|
|
||||||
};
|
|
||||||
|
|
||||||
// socket_utils.hpp
|
|
||||||
bool set_sockcreate_con(const char* con);
|
|
||||||
@@ -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, ...);
|
|
||||||
}
|
|
||||||
@@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,67 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include <sys/types.h>
|
|
||||||
|
|
||||||
#include <string>
|
|
||||||
#include <string_view>
|
|
||||||
|
|
||||||
/// \namespace lsplt
|
|
||||||
namespace lsplt {
|
|
||||||
inline namespace v2 {
|
|
||||||
/// \struct MapInfo
|
|
||||||
/// \brief An entry that describes a line in /proc/self/maps. You can obtain a list of these entries
|
|
||||||
/// by calling #Scan().
|
|
||||||
struct MapInfo {
|
|
||||||
/// \brief The start address of the memory region.
|
|
||||||
uintptr_t start;
|
|
||||||
/// \brief The end address of the memory region.
|
|
||||||
uintptr_t end;
|
|
||||||
/// \brief The permissions of the memory region. This is a bit mask of the following values:
|
|
||||||
/// - PROT_READ
|
|
||||||
/// - PROT_WRITE
|
|
||||||
/// - PROT_EXEC
|
|
||||||
uint8_t perms;
|
|
||||||
/// \brief Whether the memory region is private.
|
|
||||||
bool is_private;
|
|
||||||
/// \brief The offset of the memory region.
|
|
||||||
uintptr_t offset;
|
|
||||||
/// \brief The device number of the memory region.
|
|
||||||
/// Major can be obtained by #major()
|
|
||||||
/// Minor can be obtained by #minor()
|
|
||||||
dev_t dev;
|
|
||||||
/// \brief The inode number of the memory region.
|
|
||||||
ino_t inode;
|
|
||||||
/// \brief The path of the memory region.
|
|
||||||
std::string path;
|
|
||||||
|
|
||||||
/// \brief Scans /proc/self/maps and returns a list of \ref MapInfo entries.
|
|
||||||
/// This is useful to find out the inode of the library to hook.
|
|
||||||
/// \return A list of \ref MapInfo entries.
|
|
||||||
[[maybe_unused, gnu::visibility("default")]]
|
|
||||||
static std::vector<MapInfo> Scan(std::string proc = "self",
|
|
||||||
std::function<bool(const MapInfo &)> filter = [](
|
|
||||||
auto &map) -> bool { return true; });
|
|
||||||
|
|
||||||
virtual std::string display() const;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct SMapInfo : public MapInfo {
|
|
||||||
ssize_t size;
|
|
||||||
ssize_t rss;
|
|
||||||
ssize_t pss;
|
|
||||||
ssize_t shared_clean;
|
|
||||||
ssize_t shared_dirty;
|
|
||||||
ssize_t private_clean;
|
|
||||||
ssize_t private_dirty;
|
|
||||||
ssize_t referenced;
|
|
||||||
ssize_t anonymous;
|
|
||||||
|
|
||||||
[[maybe_unused, gnu::visibility("default")]]
|
|
||||||
static std::vector<SMapInfo> Scan(std::string proc = "self",
|
|
||||||
std::function<bool(const MapInfo &)> filter = [](
|
|
||||||
auto &map) -> bool { return true; });
|
|
||||||
|
|
||||||
std::string display() const override;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,199 +0,0 @@
|
|||||||
#include "lsplt.hpp"
|
|
||||||
|
|
||||||
#include <sys/mman.h>
|
|
||||||
#include <sys/sysmacros.h>
|
|
||||||
#include <array>
|
|
||||||
#include <cinttypes>
|
|
||||||
#include <list>
|
|
||||||
#include <map>
|
|
||||||
#include <mutex>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
namespace lsplt {
|
|
||||||
inline namespace v2 {
|
|
||||||
[[maybe_unused]] std::vector<MapInfo>
|
|
||||||
MapInfo::Scan(std::string proc, std::function<bool(const MapInfo &)> filter) {
|
|
||||||
constexpr static auto kPermLength = 5;
|
|
||||||
constexpr static auto kMapEntry = 7;
|
|
||||||
std::vector<MapInfo> info;
|
|
||||||
auto name = std::string("/proc/") + proc + "/maps";
|
|
||||||
auto maps = std::unique_ptr<FILE, decltype(&fclose)>{fopen(name.c_str(), "r"),
|
|
||||||
&fclose};
|
|
||||||
if (maps) {
|
|
||||||
char *line = nullptr;
|
|
||||||
size_t len = 0;
|
|
||||||
ssize_t read;
|
|
||||||
while ((read = getline(&line, &len, maps.get())) > 0) {
|
|
||||||
line[read - 1] = '\0';
|
|
||||||
uintptr_t start = 0;
|
|
||||||
uintptr_t end = 0;
|
|
||||||
uintptr_t off = 0;
|
|
||||||
ino_t inode = 0;
|
|
||||||
unsigned int dev_major = 0;
|
|
||||||
unsigned int dev_minor = 0;
|
|
||||||
std::array<char, kPermLength> perm{'\0'};
|
|
||||||
int path_off;
|
|
||||||
if (sscanf(line, "%" PRIxPTR"-%"
|
|
||||||
PRIxPTR
|
|
||||||
" %4s %"
|
|
||||||
PRIxPTR
|
|
||||||
" %x:%x %lu %n%*s", &start,
|
|
||||||
&end, perm.data(), &off, &dev_major, &dev_minor, &inode,
|
|
||||||
&path_off) == kMapEntry) {
|
|
||||||
while (path_off < read && isspace(line[path_off])) path_off++;
|
|
||||||
MapInfo sm{};
|
|
||||||
sm.start = start;
|
|
||||||
sm.end = end;
|
|
||||||
sm.perms = 0;
|
|
||||||
sm.is_private = perm[3] == 'p';
|
|
||||||
sm.offset = off;
|
|
||||||
sm.dev = static_cast<dev_t>(makedev(dev_major, dev_minor));
|
|
||||||
sm.inode = inode;
|
|
||||||
sm.path = line + path_off;
|
|
||||||
if (perm[0] == 'r') sm.perms |= PROT_READ;
|
|
||||||
if (perm[1] == 'w') sm.perms |= PROT_WRITE;
|
|
||||||
if (perm[2] == 'x') sm.perms |= PROT_EXEC;
|
|
||||||
if (filter(sm)) info.emplace_back(sm);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
free(line);
|
|
||||||
}
|
|
||||||
return info;
|
|
||||||
}
|
|
||||||
|
|
||||||
// https://cs.android.com/android/kernel/superproject/+/common-android-mainline:common/fs/proc/task_mmu.c;l=827;drc=08582d678fcf11fc86188f0b92239d3d49667d8e
|
|
||||||
[[maybe_unused]] std::vector<SMapInfo>
|
|
||||||
SMapInfo::Scan(std::string proc, std::function<bool(const MapInfo &)> filter) {
|
|
||||||
constexpr static auto kPermLength = 5;
|
|
||||||
constexpr static auto kMapEntry = 7;
|
|
||||||
std::vector<SMapInfo> info;
|
|
||||||
auto name = std::string("/proc/") + proc + "/smaps";
|
|
||||||
auto maps = std::unique_ptr<FILE, decltype(&fclose)>{fopen(name.c_str(), "r"),
|
|
||||||
&fclose};
|
|
||||||
if (!maps) {
|
|
||||||
return info;
|
|
||||||
}
|
|
||||||
char *line = nullptr;
|
|
||||||
size_t len = 0;
|
|
||||||
ssize_t read;
|
|
||||||
while ((read = getline(&line, &len, maps.get())) > 0) {
|
|
||||||
line[read - 1] = '\0';
|
|
||||||
uintptr_t start = 0;
|
|
||||||
uintptr_t end = 0;
|
|
||||||
uintptr_t off = 0;
|
|
||||||
ino_t inode = 0;
|
|
||||||
unsigned int dev_major = 0;
|
|
||||||
unsigned int dev_minor = 0;
|
|
||||||
std::array<char, kPermLength> perm{'\0'};
|
|
||||||
int path_off;
|
|
||||||
if (sscanf(line, "%" PRIxPTR"-%"
|
|
||||||
PRIxPTR
|
|
||||||
" %4s %"
|
|
||||||
PRIxPTR
|
|
||||||
" %x:%x %lu %n%*s", &start,
|
|
||||||
&end, perm.data(), &off, &dev_major, &dev_minor, &inode,
|
|
||||||
&path_off) == kMapEntry) {
|
|
||||||
while (path_off < read && isspace(line[path_off])) path_off++;
|
|
||||||
SMapInfo sm{};
|
|
||||||
sm.start = start;
|
|
||||||
sm.end = end;
|
|
||||||
sm.perms = 0;
|
|
||||||
sm.is_private = perm[3] == 'p';
|
|
||||||
sm.offset = off;
|
|
||||||
sm.dev = static_cast<dev_t>(makedev(dev_major, dev_minor));
|
|
||||||
sm.inode = inode;
|
|
||||||
sm.path = line + path_off;
|
|
||||||
if (perm[0] == 'r') sm.perms |= PROT_READ;
|
|
||||||
if (perm[1] == 'w') sm.perms |= PROT_WRITE;
|
|
||||||
if (perm[2] == 'x') sm.perms |= PROT_EXEC;
|
|
||||||
if (filter(sm)) info.emplace_back(sm);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (info.empty()) continue;
|
|
||||||
auto ¤t = *info.rbegin();
|
|
||||||
ssize_t value;
|
|
||||||
auto s = strstr(line, ":");
|
|
||||||
if (s == nullptr) continue;
|
|
||||||
*s = 0;
|
|
||||||
if (sscanf(s + 1, "%zu", &value) != 1) continue;
|
|
||||||
std::string_view ss{line};
|
|
||||||
if (ss == "Size") current.size = value;
|
|
||||||
else if (ss == "Rss") current.rss = value;
|
|
||||||
else if (ss == "Pss") current.pss = value;
|
|
||||||
else if (ss == "Shared_Clean") current.shared_clean = value;
|
|
||||||
else if (ss == "Shared_Dirty") current.shared_dirty = value;
|
|
||||||
else if (ss == "Private_Clean") current.private_clean = value;
|
|
||||||
else if (ss == "Private_Dirty") current.private_dirty = value;
|
|
||||||
else if (ss == "Referenced") current.referenced = value;
|
|
||||||
else if (ss == "Anonymous") current.anonymous = value;
|
|
||||||
}
|
|
||||||
free(line);
|
|
||||||
return info;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string MapInfo::display() const {
|
|
||||||
char buf[sizeof(long) * 2 + 1];
|
|
||||||
std::string result;
|
|
||||||
snprintf(buf, sizeof(buf), "%" PRIxPTR, start);
|
|
||||||
result += buf;
|
|
||||||
result += "-";
|
|
||||||
snprintf(buf, sizeof(buf), "%" PRIxPTR, end);
|
|
||||||
result += buf;
|
|
||||||
result += ' ';
|
|
||||||
result += (perms & PROT_READ) ? 'r' : '-';
|
|
||||||
result += (perms & PROT_WRITE) ? 'w' : '-';
|
|
||||||
result += (perms & PROT_EXEC) ? 'x' : '-';
|
|
||||||
result += is_private ? 'p' : 's';
|
|
||||||
result += ' ';
|
|
||||||
snprintf(buf, sizeof(buf), "%08" PRIxPTR, offset);
|
|
||||||
result += buf;
|
|
||||||
result += ' ';
|
|
||||||
snprintf(buf, sizeof(buf), "%02" PRIxPTR, major(dev));
|
|
||||||
result += buf;
|
|
||||||
result += ':';
|
|
||||||
snprintf(buf, sizeof(buf), "%02" PRIxPTR, minor(dev));
|
|
||||||
result += buf;
|
|
||||||
result += ' ';
|
|
||||||
snprintf(buf, sizeof(buf), "%lu", inode);
|
|
||||||
result += buf;
|
|
||||||
result += ' ';
|
|
||||||
result += path;
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string SMapInfo::display() const {
|
|
||||||
auto result = MapInfo::display();
|
|
||||||
char buf[sizeof(long) * 2 + 1];
|
|
||||||
result += '\n';
|
|
||||||
result += "SZ:";
|
|
||||||
snprintf(buf, sizeof(buf), "%zu", size);
|
|
||||||
result += buf;
|
|
||||||
result += " RSS:";
|
|
||||||
snprintf(buf, sizeof(buf), "%zu", rss);
|
|
||||||
result += buf;
|
|
||||||
result += " PSS:";
|
|
||||||
snprintf(buf, sizeof(buf), "%zu", pss);
|
|
||||||
result += buf;
|
|
||||||
result += " SC:";
|
|
||||||
snprintf(buf, sizeof(buf), "%zu", shared_clean);
|
|
||||||
result += buf;
|
|
||||||
result += " SD:";
|
|
||||||
snprintf(buf, sizeof(buf), "%zu", shared_dirty);
|
|
||||||
result += buf;
|
|
||||||
result += " PC:";
|
|
||||||
snprintf(buf, sizeof(buf), "%zu", private_clean);
|
|
||||||
result += buf;
|
|
||||||
result += " PD:";
|
|
||||||
snprintf(buf, sizeof(buf), "%zu", private_dirty);
|
|
||||||
result += buf;
|
|
||||||
result += " REF:";
|
|
||||||
snprintf(buf, sizeof(buf), "%zu", referenced);
|
|
||||||
result += buf;
|
|
||||||
result += " ANON:";
|
|
||||||
snprintf(buf, sizeof(buf), "%zu", anonymous);
|
|
||||||
result += buf;
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,77 +0,0 @@
|
|||||||
#include <cstdlib>
|
|
||||||
#include <unistd.h>
|
|
||||||
#include <fcntl.h>
|
|
||||||
#include <android/log.h>
|
|
||||||
#include <string_view>
|
|
||||||
|
|
||||||
#include "logging.hpp"
|
|
||||||
#include "zygisk.hpp"
|
|
||||||
|
|
||||||
using zygisk::Api;
|
|
||||||
using zygisk::AppSpecializeArgs;
|
|
||||||
using zygisk::ServerSpecializeArgs;
|
|
||||||
using namespace std::string_view_literals;
|
|
||||||
|
|
||||||
class TrickyStore : public zygisk::ModuleBase {
|
|
||||||
public:
|
|
||||||
void onLoad(Api *api, JNIEnv *env) override {
|
|
||||||
this->api_ = api;
|
|
||||||
this->env_ = env;
|
|
||||||
}
|
|
||||||
|
|
||||||
void preAppSpecialize(AppSpecializeArgs *args) override {
|
|
||||||
int enabled = 0;
|
|
||||||
api_->setOption(zygisk::DLCLOSE_MODULE_LIBRARY);
|
|
||||||
{
|
|
||||||
auto fd = api_->connectCompanion();
|
|
||||||
if (fd >= 0) {
|
|
||||||
read(fd, &enabled, sizeof(enabled));
|
|
||||||
close(fd);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!enabled) return;
|
|
||||||
const char *process = env_->GetStringUTFChars(args->nice_name, nullptr);
|
|
||||||
if (process == "com.google.android.gms.unstable"sv) {
|
|
||||||
LOGI("spoofing build vars in %s!", process);
|
|
||||||
auto buildClass = env_->FindClass("android/os/Build");
|
|
||||||
auto buildVersionClass = env_->FindClass("android/os/Build$VERSION");
|
|
||||||
|
|
||||||
#define SET_FIELD(CLAZZ, FIELD, VALUE) ({ \
|
|
||||||
auto id = env_->GetStaticFieldID(CLAZZ, FIELD, "Ljava/lang/String;"); \
|
|
||||||
env_->SetStaticObjectField(buildClass, id, env_->NewStringUTF(VALUE)); })
|
|
||||||
|
|
||||||
SET_FIELD(buildClass, "MANUFACTURER", "Google");
|
|
||||||
SET_FIELD(buildClass, "MODEL", "Pixel");
|
|
||||||
SET_FIELD(buildClass, "FINGERPRINT",
|
|
||||||
"google/sailfish/sailfish:8.1.0/OPM1.171019.011/4448085:user/release-keys");
|
|
||||||
SET_FIELD(buildClass, "BRAND", "google");
|
|
||||||
SET_FIELD(buildClass, "PRODUCT", "sailfish");
|
|
||||||
SET_FIELD(buildClass, "DEVICE", "sailfish");
|
|
||||||
SET_FIELD(buildVersionClass, "RELEASE", "8.1.0");
|
|
||||||
SET_FIELD(buildClass, "ID", "OPM1.171019.011");
|
|
||||||
SET_FIELD(buildVersionClass, "INCREMENTAL", "4448085");
|
|
||||||
SET_FIELD(buildVersionClass, "SECURITY_PATCH", "2017-12-05");
|
|
||||||
SET_FIELD(buildClass, "TYPE", "user");
|
|
||||||
SET_FIELD(buildClass, "TAGS", "release-keys");
|
|
||||||
}
|
|
||||||
env_->ReleaseStringUTFChars(args->nice_name, process);
|
|
||||||
}
|
|
||||||
|
|
||||||
void preServerSpecialize(ServerSpecializeArgs *args) override {
|
|
||||||
api_->setOption(zygisk::DLCLOSE_MODULE_LIBRARY);
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
Api *api_;
|
|
||||||
JNIEnv *env_;
|
|
||||||
};
|
|
||||||
|
|
||||||
static void companion_handler(int fd) {
|
|
||||||
int enabled = access("/data/adb/tricky_store/spoof_build_vars", F_OK) == 0;
|
|
||||||
write(fd, &enabled, sizeof(enabled));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Register our module class and the companion handler function
|
|
||||||
REGISTER_ZYGISK_MODULE(TrickyStore)
|
|
||||||
|
|
||||||
REGISTER_ZYGISK_COMPANION(companion_handler)
|
|
||||||
@@ -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
|
|
||||||
@@ -1,89 +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"
|
|
||||||
extract "$ZIPFILE" 'sepolicy.rule' "$TMPDIR"
|
|
||||||
|
|
||||||
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"
|
|
||||||
mv "$TMPDIR/sepolicy.rule" "$MODPATH"
|
|
||||||
|
|
||||||
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"
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
id=${moduleId}
|
|
||||||
name=${moduleName}
|
|
||||||
version=${versionName}
|
|
||||||
versionCode=${versionCode}
|
|
||||||
author=5ec1cff
|
|
||||||
description=A trick of keystore
|
|
||||||
#updateJson=
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
MODDIR=${0%/*}
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
allow keystore keystore process execmem
|
|
||||||
allow keystore system_file unix_dgram_socket *
|
|
||||||
allow system_file keystore unix_dgram_socket *
|
|
||||||
allow keystore system_file file *
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
DEBUG=@DEBUG@
|
|
||||||
|
|
||||||
MODDIR=${0%/*}
|
|
||||||
|
|
||||||
cd $MODDIR
|
|
||||||
|
|
||||||
(
|
|
||||||
while [ true ]; do
|
|
||||||
/system/bin/app_process -Djava.class.path=./service.apk / --nice-name=TrickyStore io.github.a13e300.tricky_store.MainKt
|
|
||||||
if [ $? -ne 0 ]; then
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
) &
|
|
||||||
@@ -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 +0,0 @@
|
|||||||
/build
|
|
||||||
@@ -1,84 +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 += "**"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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\"")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Vendored
-33
@@ -1,33 +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.**
|
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<manifest />
|
|
||||||
@@ -1,97 +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 val DEFAULT_TARGET_PACKAGES = listOf(
|
|
||||||
"com.google.android.gms",
|
|
||||||
"icu.nullptr.nativetest",
|
|
||||||
"io.github.vvb2060.mahoshojo",
|
|
||||||
"io.github.vvb2060.keyattestation"
|
|
||||||
)
|
|
||||||
|
|
||||||
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 target = File(root, TARGET_FILE)
|
|
||||||
if (!target.exists()) {
|
|
||||||
target.createNewFile()
|
|
||||||
target.writeText(DEFAULT_TARGET_PACKAGES.joinToString("\n"))
|
|
||||||
}
|
|
||||||
updateTargetPackages(target)
|
|
||||||
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,147 +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("use generated key $callingUid ${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
|
|
||||||
val p = Parcel.obtain()
|
|
||||||
Logger.d("intercept post $target uid=$callingUid pid=$callingPid dataSz=${data.dataSize()} replySz=${reply.dataSize()}")
|
|
||||||
try {
|
|
||||||
reply.readException()
|
|
||||||
val response = reply.readTypedObject(KeyEntryResponse.CREATOR)
|
|
||||||
val chain = Utils.getCertificateChain(response)
|
|
||||||
if (chain != null) {
|
|
||||||
val newChain = CertHack.hackCertificateChain(chain)
|
|
||||||
Utils.putCertificateChain(response, newChain)
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
fun tryRunKeystoreInterceptor(): Boolean {
|
|
||||||
Logger.i("trying to register keystore interceptor ...")
|
|
||||||
val b = ServiceManager.getService("android.system.keystore2.IKeystoreService/default") ?: return false
|
|
||||||
val bd = getBinderBackdoor(b)
|
|
||||||
if (bd == null) {
|
|
||||||
// no binder hook, try inject
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
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,105 +0,0 @@
|
|||||||
package io.github.a13e300.tricky_store.binder
|
|
||||||
|
|
||||||
import android.os.Binder
|
|
||||||
import android.os.IBinder
|
|
||||||
import android.os.Parcel
|
|
||||||
|
|
||||||
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 {
|
|
||||||
b.transact(0xdeadbeef.toInt(), data, reply, 0)
|
|
||||||
return reply.readStrongBinder()
|
|
||||||
} catch (ignored: Throwable) {
|
|
||||||
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,542 +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.SecureRandom;
|
|
||||||
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 java.util.concurrent.ThreadLocalRandom;
|
|
||||||
|
|
||||||
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(key))) {
|
|
||||||
return (PEMKeyPair) parser.readObject();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Certificate parseCert(String cert) throws Throwable {
|
|
||||||
try (PemReader reader = new PemReader(new StringReader(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 = null;
|
|
||||||
byte[] verifiedBootHash = null;
|
|
||||||
try {
|
|
||||||
if (!(rootOfTrust instanceof ASN1Sequence r)) {
|
|
||||||
throw new CertificateParsingException("Expected sequence for root of trust, found "
|
|
||||||
+ rootOfTrust.getClass().getName());
|
|
||||||
}
|
|
||||||
verifiedBootKey = getByteArrayFromAsn1(r.getObjectAt(0));
|
|
||||||
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 (verifiedBootKey == null) {
|
|
||||||
verifiedBootKey = new byte[32];
|
|
||||||
ThreadLocalRandom.current().nextBytes(verifiedBootKey);
|
|
||||||
}
|
|
||||||
if (verifiedBootHash == null) {
|
|
||||||
verifiedBootHash = new byte[32];
|
|
||||||
ThreadLocalRandom.current().nextBytes(verifiedBootHash);
|
|
||||||
}
|
|
||||||
|
|
||||||
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 {
|
|
||||||
SecureRandom random = new SecureRandom();
|
|
||||||
|
|
||||||
byte[] key = new byte[32];
|
|
||||||
byte[] hash = UtilKt.getBootHashFromProp();
|
|
||||||
|
|
||||||
random.nextBytes(key);
|
|
||||||
if (hash == null || hash.length != 32) {
|
|
||||||
hash = new byte[32];
|
|
||||||
random.nextBytes(hash);
|
|
||||||
}
|
|
||||||
|
|
||||||
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,49 +0,0 @@
|
|||||||
package io.github.a13e300.tricky_store
|
|
||||||
|
|
||||||
import android.content.pm.IPackageManager
|
|
||||||
import android.os.Build
|
|
||||||
import android.os.SystemProperties
|
|
||||||
|
|
||||||
fun getTransactCode(clazz: Class<*>, method: String) =
|
|
||||||
clazz.getDeclaredField("TRANSACTION_$method").apply { isAccessible = true }
|
|
||||||
.getInt(null) // 2
|
|
||||||
|
|
||||||
@OptIn(ExperimentalStdlibApi::class)
|
|
||||||
val bootHashFromProp by lazy {
|
|
||||||
val b = SystemProperties.get("ro.boot.vbmeta.digest", null) ?: return@lazy null
|
|
||||||
if (b.length != 64) return@lazy null
|
|
||||||
b.hexToByteArray()
|
|
||||||
}
|
|
||||||
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -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 +0,0 @@
|
|||||||
/build
|
|
||||||
@@ -1,24 +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"
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
dependencies {
|
|
||||||
compileOnly(libs.annotation)
|
|
||||||
}
|
|
||||||
Vendored
-21
@@ -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
|
|
||||||
@@ -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("");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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"
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user