@@ -0,0 +1,17 @@
|
||||
BasedOnStyle: LLVM
|
||||
|
||||
Language: Cpp
|
||||
Standard: c++20
|
||||
|
||||
ColumnLimit: 135
|
||||
|
||||
AlignEscapedNewlines: Left
|
||||
AllowShortFunctionsOnASingleLine: Empty
|
||||
AllowShortLambdasOnASingleLine: Empty
|
||||
AlwaysBreakTemplateDeclarations: true
|
||||
IndentPPDirectives: AfterHash
|
||||
|
||||
AccessModifierOffset: -4
|
||||
IndentWidth: 4
|
||||
UseTab: Never
|
||||
|
||||
@@ -13,3 +13,7 @@
|
||||
.externalNativeBuild
|
||||
.cxx
|
||||
local.properties
|
||||
out/
|
||||
module/lib/
|
||||
module/service.apk
|
||||
module/classes.dex
|
||||
|
||||
Generated
+1
@@ -1,5 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="GradleMigrationSettings" migrationVersion="1" />
|
||||
<component name="GradleSettings">
|
||||
<option name="linkedExternalProjectsSettings">
|
||||
<GradleProjectSettings>
|
||||
|
||||
Generated
+8
-9
@@ -1,10 +1,9 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ExternalStorageConfigurationManager" enabled="true" />
|
||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_21" default="true" project-jdk-name="jbr-21" project-jdk-type="JavaSDK">
|
||||
<output url="file://$PROJECT_DIR$/build/classes" />
|
||||
</component>
|
||||
<component name="ProjectType">
|
||||
<option name="id" value="Android" />
|
||||
</component>
|
||||
<project version="4">
|
||||
<component name="ExternalStorageConfigurationManager" enabled="true" />
|
||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_21" default="true" project-jdk-name="jbr-21" project-jdk-type="JavaSDK">
|
||||
<output url="file://$PROJECT_DIR$/build/classes" />
|
||||
</component>
|
||||
<component name="ProjectType">
|
||||
<option name="id" value="Android" />
|
||||
</component>
|
||||
</project>
|
||||
Generated
+1
@@ -2,5 +2,6 @@
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
||||
<mapping directory="$PROJECT_DIR$/app/src/main/cpp/external/LSPlt" vcs="Git" />
|
||||
</component>
|
||||
</project>
|
||||
+143
-4
@@ -3,26 +3,69 @@
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import java.io.ByteArrayOutputStream
|
||||
|
||||
plugins {
|
||||
alias(libs.plugins.android.application)
|
||||
alias(libs.plugins.kotlin.android)
|
||||
}
|
||||
|
||||
fun String.execute(currentWorkingDir: File = File("./")): String {
|
||||
val parts = this.split("\\s+".toRegex())
|
||||
val process = ProcessBuilder(parts)
|
||||
.directory(currentWorkingDir)
|
||||
.redirectErrorStream(true)
|
||||
.start()
|
||||
|
||||
val output = process.inputStream.bufferedReader().readText()
|
||||
process.waitFor()
|
||||
return output.trim()
|
||||
}
|
||||
|
||||
|
||||
val gitCommitCount = "git rev-list HEAD --count".execute().toInt()
|
||||
val gitCommitHash = "git rev-parse --verify --short HEAD".execute()
|
||||
val verName = "v2.0.0"
|
||||
|
||||
android {
|
||||
namespace = "io.github.beakthoven.TrickyStoreOSS"
|
||||
compileSdk = 36
|
||||
ndkVersion = "28.2.13676358"
|
||||
buildToolsVersion = "36.0.0"
|
||||
|
||||
defaultConfig {
|
||||
applicationId = "io.github.beakthoven.TrickyStoreOSS"
|
||||
minSdk = 29
|
||||
targetSdk = 36
|
||||
versionCode = 1
|
||||
versionName = "1.0"
|
||||
versionCode = gitCommitCount
|
||||
versionName = verName
|
||||
|
||||
externalNativeBuild {
|
||||
cmake {
|
||||
arguments += "-DANDROID_STL=none"
|
||||
arguments += "-DCMAKE_BUILD_TYPE=Release"
|
||||
arguments += "-DANDROID_SUPPORT_FLEXIBLE_PAGE_SIZES=ON"
|
||||
arguments += "-DANDROID_ALLOW_UNDEFINED_SYMBOLS=ON"
|
||||
arguments += "-DCMAKE_CXX_STANDARD=20"
|
||||
arguments += "-DCMAKE_C_STANDARD=23"
|
||||
arguments += "-DCMAKE_INTERPROCEDURAL_OPTIMIZATION=ON"
|
||||
|
||||
cppFlags += "-std=c++20"
|
||||
cppFlags += "-fno-exceptions"
|
||||
cppFlags += "-fno-rtti"
|
||||
cppFlags += "-fvisibility=hidden"
|
||||
cppFlags += "-fvisibility-inlines-hidden"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
buildFeatures {
|
||||
prefab = true
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
release {
|
||||
isMinifyEnabled = false
|
||||
isMinifyEnabled = true
|
||||
proguardFiles(
|
||||
getDefaultProguardFile("proguard-android-optimize.txt"),
|
||||
"proguard-rules.pro"
|
||||
@@ -40,9 +83,105 @@ android {
|
||||
}
|
||||
}
|
||||
buildFeatures {
|
||||
viewBinding = true
|
||||
viewBinding = false
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compileOnly(project(":stub"))
|
||||
compileOnly(libs.annotation)
|
||||
compileOnly(libs.dev.rikka.hidden.stub)
|
||||
implementation(libs.org.bouncycastle.bcpkix.jdk18on)
|
||||
implementation(libs.org.lsposed.libcxx.libcxx)
|
||||
}
|
||||
|
||||
afterEvaluate {
|
||||
android.applicationVariants.forEach { variant ->
|
||||
val variantName = variant.name
|
||||
val capitalized = variantName.replaceFirstChar { it.uppercase() }
|
||||
val tempModuleDir = project.layout.buildDirectory.dir("tmp/module-${variantName}")
|
||||
|
||||
tasks.register("copyFiles${capitalized}") {
|
||||
val moduleFolder = project.rootDir.resolve("module")
|
||||
val buildDir = project.layout.buildDirectory
|
||||
|
||||
doLast {
|
||||
val isDebug = variantName.contains("debug", ignoreCase = true)
|
||||
//val apkFile = variant.outputs.first().outputFile
|
||||
|
||||
listOf("service.apk", "classes.dex").forEach { fileName ->
|
||||
val oldFile = moduleFolder.resolve(fileName)
|
||||
if (oldFile.exists()) oldFile.delete()
|
||||
}
|
||||
|
||||
// Select source file based on build type
|
||||
val sourceFile = if (isDebug) {
|
||||
variant.outputs.first().outputFile
|
||||
} else {
|
||||
buildDir.get().asFile.resolve("intermediates/dex/release/minifyReleaseWithR8/classes.dex")
|
||||
}
|
||||
|
||||
val destFileName = if (isDebug) "service.apk" else "classes.dex"
|
||||
sourceFile.copyTo(moduleFolder.resolve(destFileName), overwrite = true)
|
||||
|
||||
val soDir = buildDir.get()
|
||||
.asFile
|
||||
.resolve("intermediates/stripped_native_libs/$variantName/strip${capitalized}DebugSymbols/out/lib")
|
||||
|
||||
//apkFile.copyTo(moduleFolder.resolve("service.apk"), overwrite = true)
|
||||
|
||||
val allowedLibs = setOf("libinject.so", "libTrickyStoreOSS.so")
|
||||
soDir.walk()
|
||||
.filter { it.isFile && it.name in allowedLibs }
|
||||
.forEach { soFile ->
|
||||
val abiFolder = soFile.parentFile.name
|
||||
val destination = moduleFolder.resolve("lib/$abiFolder/${soFile.name}")
|
||||
soFile.copyTo(destination, overwrite = true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Prepare temp directory with all files
|
||||
tasks.register("prepareModuleFiles${capitalized}") {
|
||||
dependsOn("copyFiles${capitalized}")
|
||||
val sourceDir = project.rootDir.resolve("module")
|
||||
|
||||
doLast {
|
||||
val tempDir = tempModuleDir.get().asFile
|
||||
|
||||
// Clean and create temp directory
|
||||
tempDir.deleteRecursively()
|
||||
tempDir.mkdirs()
|
||||
|
||||
// Copy all files except module.prop
|
||||
sourceDir.walkTopDown()
|
||||
.filter { it.isFile && it.name != "module.prop" }
|
||||
.forEach { sourceFile ->
|
||||
val relativePath = sourceFile.relativeTo(sourceDir)
|
||||
val destFile = tempDir.resolve(relativePath)
|
||||
destFile.parentFile.mkdirs()
|
||||
sourceFile.copyTo(destFile, overwrite = true)
|
||||
}
|
||||
|
||||
// Process module.prop
|
||||
val sourceProp = sourceDir.resolve("module.prop")
|
||||
val destProp = tempDir.resolve("module.prop")
|
||||
val content = sourceProp.readText()
|
||||
val processedContent = content
|
||||
.replace("REPLACEMEVERCODE", gitCommitCount.toString())
|
||||
.replace("REPLACEMEVER", "$verName ($gitCommitCount-$gitCommitHash-$variantName)")
|
||||
destProp.writeText(processedContent)
|
||||
}
|
||||
}
|
||||
|
||||
// Zip task uses the temp directory
|
||||
tasks.register<Zip>("zip${capitalized}") {
|
||||
dependsOn("prepareModuleFiles${capitalized}")
|
||||
archiveFileName.set("Tricky-Store-OSS-$verName-$gitCommitCount-$gitCommitHash-${capitalized}.zip")
|
||||
destinationDirectory.set(project.rootDir.resolve("out"))
|
||||
from(tempModuleDir)
|
||||
}
|
||||
|
||||
tasks["assemble${capitalized}"].finalizedBy("zip${capitalized}")
|
||||
}
|
||||
}
|
||||
Vendored
+74
-1
@@ -18,4 +18,77 @@
|
||||
|
||||
# If you keep the line number information, uncomment this to
|
||||
# hide the original source file name.
|
||||
#-renamesourcefileattribute SourceFile
|
||||
#-renamesourcefileattribute SourceFile
|
||||
|
||||
-keepclasseswithmembers class io.github.beakthoven.TrickyStoreOSS.MainKt {
|
||||
public static void main(java.lang.String[]);
|
||||
}
|
||||
|
||||
-assumenosideeffects class io.github.beakthoven.TrickyStoreOSS.core.logging.Logger {
|
||||
public static void d(java.lang.String);
|
||||
public static void dd(java.lang.String);
|
||||
public static void v(java.lang.String);
|
||||
}
|
||||
|
||||
-assumenosideeffects class android.util.Log {
|
||||
public static int v(...);
|
||||
public static int d(...);
|
||||
}
|
||||
|
||||
# keep these or bouncycastle will not work
|
||||
-keep class org.bouncycastle.jcajce.provider.** { *; }
|
||||
-keep class org.bouncycastle.jce.provider.** { *; }
|
||||
-dontwarn javax.naming.**
|
||||
|
||||
# Keep `Companion` object fields of serializable classes.
|
||||
# This avoids serializer lookup through `getDeclaredClasses` as done for named companion objects.
|
||||
-if @kotlinx.serialization.Serializable class **
|
||||
-keepclassmembers class <1> {
|
||||
static <1>$Companion Companion;
|
||||
}
|
||||
|
||||
# Keep `serializer()` on companion objects (both default and named) of serializable classes.
|
||||
-if @kotlinx.serialization.Serializable class ** {
|
||||
static **$* *;
|
||||
}
|
||||
-keepclassmembers class <2>$<3> {
|
||||
kotlinx.serialization.KSerializer serializer(...);
|
||||
}
|
||||
|
||||
# Keep `INSTANCE.serializer()` of serializable objects.
|
||||
-if @kotlinx.serialization.Serializable class ** {
|
||||
public static ** INSTANCE;
|
||||
}
|
||||
-keepclassmembers class <1> {
|
||||
public static <1> INSTANCE;
|
||||
kotlinx.serialization.KSerializer serializer(...);
|
||||
}
|
||||
|
||||
# Keep all interceptor classes and their methods - used via reflection and JNI
|
||||
-keep class io.github.beakthoven.TrickyStoreOSS.interceptors.** {
|
||||
*;
|
||||
}
|
||||
|
||||
# Keep SecurityLevelInterceptor and its inner classes
|
||||
-keep class io.github.beakthoven.TrickyStoreOSS.interceptors.SecurityLevelInterceptor {
|
||||
*;
|
||||
}
|
||||
|
||||
# Keep Key and Info inner classes used in maps - critical for runtime
|
||||
-keepclassmembers class io.github.beakthoven.TrickyStoreOSS.interceptors.SecurityLevelInterceptor$Key {
|
||||
*;
|
||||
}
|
||||
-keepclassmembers class io.github.beakthoven.TrickyStoreOSS.interceptors.SecurityLevelInterceptor$Info {
|
||||
*;
|
||||
}
|
||||
|
||||
# Keep Parcelable CREATOR fields
|
||||
-keepclassmembers class * implements android.os.Parcelable {
|
||||
public static final ** CREATOR;
|
||||
}
|
||||
|
||||
-repackageclasses
|
||||
-allowaccessmodification
|
||||
-overloadaggressively
|
||||
-keepattributes SourceFile,LineNumberTable,LocalVariableTable
|
||||
-renamesourcefileattribute
|
||||
@@ -1,30 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
Copyright 2025 Dakkshesh <beakthoven@gmail.com>
|
||||
SPDX-License-Identifier: GPL-3.0-or-later
|
||||
-->
|
||||
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools">
|
||||
<!--
|
||||
Copyright 2025 Dakkshesh <beakthoven@gmail.com>
|
||||
SPDX-License-Identifier: GPL-3.0-or-later
|
||||
-->
|
||||
|
||||
<application
|
||||
android:allowBackup="true"
|
||||
android:dataExtractionRules="@xml/data_extraction_rules"
|
||||
android:fullBackupContent="@xml/backup_rules"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:label="@string/app_name"
|
||||
android:roundIcon="@mipmap/ic_launcher_round"
|
||||
android:supportsRtl="true"
|
||||
android:theme="@style/Theme.TrickyStoreOSS">
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
<manifest/>
|
||||
@@ -1,3 +1,33 @@
|
||||
# Copyright 2025 Dakkshesh <beakthoven@gmail.com>
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
project(TrickyStoreOSS)
|
||||
cmake_minimum_required(VERSION 3.28)
|
||||
project(TrickyStoreOSS)
|
||||
|
||||
find_package(cxx REQUIRED CONFIG)
|
||||
link_libraries(cxx::cxx)
|
||||
|
||||
add_library(my_logging STATIC logging/logging.cpp)
|
||||
target_include_directories(my_logging PUBLIC logging/include)
|
||||
target_link_libraries(my_logging log)
|
||||
|
||||
set(LSPLT_SOURCES external/LSPlt/lsplt/src/main/jni/lsplt.cc external/LSPlt/lsplt/src/main/jni/elf_util.cc)
|
||||
add_library(lsplt STATIC ${LSPLT_SOURCES})
|
||||
target_include_directories(lsplt PUBLIC external/LSPlt/lsplt/src/main/jni/include)
|
||||
target_include_directories(lsplt PRIVATE external/LSPlt/lsplt/src/main/jni)
|
||||
target_link_libraries(lsplt PUBLIC my_logging)
|
||||
|
||||
# libutils stub
|
||||
add_library(utils SHARED stub/stub_utils.cpp)
|
||||
target_include_directories(utils PUBLIC external/AOSP/include)
|
||||
|
||||
# libbinder stub
|
||||
add_library(binder SHARED stub/stub_binder.cpp)
|
||||
target_include_directories(binder PUBLIC external/AOSP/include)
|
||||
target_link_libraries(binder PRIVATE utils)
|
||||
|
||||
add_executable(libinject.so inject/main.cpp inject/utils.cpp)
|
||||
target_link_libraries(libinject.so PRIVATE lsplt my_logging)
|
||||
|
||||
add_library(${CMAKE_PROJECT_NAME} SHARED binder_interceptor.cpp)
|
||||
target_include_directories(${CMAKE_PROJECT_NAME} PUBLIC external/linux-kernel/include)
|
||||
target_link_libraries(${CMAKE_PROJECT_NAME} PRIVATE log binder utils lsplt my_logging)
|
||||
|
||||
@@ -0,0 +1,543 @@
|
||||
// Copyright 2025 Dakkshesh <beakthoven@gmail.com>
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#include <android/binder.h>
|
||||
#include <binder/Binder.h>
|
||||
#include <binder/Common.h>
|
||||
#include <binder/IPCThreadState.h>
|
||||
#include <binder/IServiceManager.h>
|
||||
#include <binder/Parcel.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <utils/StrongPointer.h>
|
||||
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <queue>
|
||||
#include <shared_mutex>
|
||||
#include <span>
|
||||
#include <string_view>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "logging.hpp"
|
||||
#include "lsplt.hpp"
|
||||
|
||||
using namespace android;
|
||||
|
||||
namespace {
|
||||
namespace intercept_constants {
|
||||
constexpr uint32_t kRegisterInterceptor = 1;
|
||||
constexpr uint32_t kUnregisterInterceptor = 2;
|
||||
|
||||
constexpr uint32_t kPreTransact = 1;
|
||||
constexpr uint32_t kPostTransact = 2;
|
||||
|
||||
constexpr uint32_t kActionSkip = 1;
|
||||
constexpr uint32_t kActionContinue = 2;
|
||||
constexpr uint32_t kActionOverrideReply = 3;
|
||||
constexpr uint32_t kActionOverrideData = 4;
|
||||
|
||||
constexpr uint32_t kBackdoorCode = 0xdeadbeef;
|
||||
} // namespace intercept_constants
|
||||
} // namespace
|
||||
|
||||
class BinderInterceptor : public BBinder {
|
||||
struct InterceptorRegistration {
|
||||
wp<IBinder> target_binder{};
|
||||
sp<IBinder> interceptor_binder;
|
||||
|
||||
InterceptorRegistration() = default;
|
||||
InterceptorRegistration(wp<IBinder> target, sp<IBinder> interceptor)
|
||||
: target_binder(std::move(target)), interceptor_binder(std::move(interceptor)) {}
|
||||
};
|
||||
using RwLock = std::shared_mutex;
|
||||
using WriteGuard = std::unique_lock<RwLock>;
|
||||
using ReadGuard = std::shared_lock<RwLock>;
|
||||
|
||||
mutable RwLock interceptor_registry_lock_;
|
||||
std::map<wp<IBinder>, InterceptorRegistration> interceptor_registry_{};
|
||||
|
||||
public:
|
||||
status_t onTransact(uint32_t code, const android::Parcel &data, android::Parcel *reply, uint32_t flags) override;
|
||||
|
||||
bool handleInterceptedTransaction(sp<BBinder> target_binder, uint32_t transaction_code, const Parcel &request_data,
|
||||
Parcel *reply_data, uint32_t transaction_flags, status_t &result);
|
||||
|
||||
bool shouldInterceptBinder(const wp<BBinder> &target_binder) const;
|
||||
|
||||
private:
|
||||
status_t handleRegisterInterceptor(const android::Parcel &data);
|
||||
status_t handleUnregisterInterceptor(const android::Parcel &data);
|
||||
|
||||
template <typename ParcelWriter>
|
||||
status_t writeInterceptorCallData(ParcelWriter &writer, sp<BBinder> target_binder, uint32_t transaction_code,
|
||||
uint32_t transaction_flags, const Parcel &data) const;
|
||||
|
||||
status_t validateInterceptorResponse(const Parcel &response, int32_t &action_type) const;
|
||||
};
|
||||
|
||||
static sp<BinderInterceptor> g_binder_interceptor = nullptr;
|
||||
|
||||
struct ThreadTransactionInfo {
|
||||
uint32_t transaction_code;
|
||||
wp<BBinder> target_binder;
|
||||
|
||||
ThreadTransactionInfo() = default;
|
||||
ThreadTransactionInfo(uint32_t code, wp<BBinder> target) : transaction_code(code), target_binder(std::move(target)) {}
|
||||
};
|
||||
|
||||
thread_local std::queue<ThreadTransactionInfo> g_thread_transaction_queue;
|
||||
|
||||
class BinderStub : public BBinder {
|
||||
status_t onTransact(uint32_t code, const android::Parcel &data, android::Parcel *reply, uint32_t flags) override {
|
||||
LOGD("BinderStub transaction: %u", code);
|
||||
|
||||
if (g_thread_transaction_queue.empty()) {
|
||||
LOGW("No pending transaction info for stub");
|
||||
return UNKNOWN_TRANSACTION;
|
||||
}
|
||||
|
||||
auto transaction_info = g_thread_transaction_queue.front();
|
||||
g_thread_transaction_queue.pop();
|
||||
|
||||
if (transaction_info.target_binder == nullptr && transaction_info.transaction_code == intercept_constants::kBackdoorCode &&
|
||||
reply != nullptr) {
|
||||
LOGD("Backdoor access requested - providing interceptor reference");
|
||||
reply->writeStrongBinder(g_binder_interceptor);
|
||||
return OK;
|
||||
}
|
||||
|
||||
if (auto promoted_target = transaction_info.target_binder.promote()) {
|
||||
LOGD("Processing intercepted transaction");
|
||||
status_t result;
|
||||
if (!g_binder_interceptor->handleInterceptedTransaction(promoted_target, transaction_info.transaction_code, data, reply,
|
||||
flags, result)) {
|
||||
LOGD("Forwarding to original binder");
|
||||
result = promoted_target->transact(transaction_info.transaction_code, data, reply, flags);
|
||||
}
|
||||
return result;
|
||||
} else {
|
||||
LOGE("Failed to promote weak reference to target binder");
|
||||
return DEAD_OBJECT;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
static sp<BinderStub> g_binder_stub = nullptr;
|
||||
|
||||
int (*original_ioctl_function)(int fd, int request, ...) = nullptr;
|
||||
|
||||
namespace {
|
||||
bool processBinderTransaction(binder_transaction_data *transaction_data) {
|
||||
if (!transaction_data || transaction_data->target.ptr == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool should_intercept = false;
|
||||
ThreadTransactionInfo transaction_info{};
|
||||
|
||||
if (transaction_data->code == intercept_constants::kBackdoorCode && transaction_data->sender_euid == 0) {
|
||||
transaction_info.transaction_code = intercept_constants::kBackdoorCode;
|
||||
transaction_info.target_binder = nullptr;
|
||||
should_intercept = true;
|
||||
LOGD("Backdoor transaction detected from root user");
|
||||
} else {
|
||||
auto *weak_ref = reinterpret_cast<RefBase::weakref_type *>(transaction_data->target.ptr);
|
||||
if (weak_ref->attemptIncStrong(nullptr)) {
|
||||
auto *target_binder = reinterpret_cast<BBinder *>(transaction_data->cookie);
|
||||
auto weak_binder = wp<BBinder>::fromExisting(target_binder);
|
||||
|
||||
if (g_binder_interceptor->shouldInterceptBinder(weak_binder)) {
|
||||
transaction_info.transaction_code = transaction_data->code;
|
||||
transaction_info.target_binder = weak_binder;
|
||||
should_intercept = true;
|
||||
LOGD("Interception required for transaction code=%u target=%p", transaction_data->code, target_binder);
|
||||
}
|
||||
target_binder->decStrong(nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
if (should_intercept) {
|
||||
LOGD("Redirecting transaction through stub");
|
||||
transaction_data->target.ptr = reinterpret_cast<uintptr_t>(g_binder_stub->getWeakRefs());
|
||||
transaction_data->cookie = reinterpret_cast<uintptr_t>(g_binder_stub.get());
|
||||
transaction_data->code = intercept_constants::kBackdoorCode;
|
||||
g_thread_transaction_queue.push(std::move(transaction_info));
|
||||
}
|
||||
|
||||
return should_intercept;
|
||||
}
|
||||
|
||||
void processBinderWriteRead(const binder_write_read &write_read_data) {
|
||||
if (write_read_data.read_buffer == 0 || write_read_data.read_size == 0 || write_read_data.read_consumed <= sizeof(uint32_t)) {
|
||||
return;
|
||||
}
|
||||
|
||||
LOGD("Processing binder read buffer: ptr=%p size=%zu consumed=%zu", reinterpret_cast<void *>(write_read_data.read_buffer),
|
||||
write_read_data.read_size, write_read_data.read_consumed);
|
||||
|
||||
auto buffer_ptr = write_read_data.read_buffer;
|
||||
auto remaining_bytes = write_read_data.read_consumed;
|
||||
|
||||
while (remaining_bytes > 0) {
|
||||
if (remaining_bytes < sizeof(uint32_t)) {
|
||||
LOGE("Insufficient bytes for command header: %llu", static_cast<unsigned long long>(remaining_bytes));
|
||||
break;
|
||||
}
|
||||
|
||||
auto command = *reinterpret_cast<const uint32_t *>(buffer_ptr);
|
||||
buffer_ptr += sizeof(uint32_t);
|
||||
remaining_bytes -= sizeof(uint32_t);
|
||||
|
||||
auto command_size = _IOC_SIZE(command);
|
||||
LOGD("Processing binder command: %u (size: %u)", command, command_size);
|
||||
|
||||
if (remaining_bytes < command_size) {
|
||||
LOGE("Insufficient bytes for command data: %llu < %u", static_cast<unsigned long long>(remaining_bytes), command_size);
|
||||
break;
|
||||
}
|
||||
|
||||
if (command == BR_TRANSACTION_SEC_CTX || command == BR_TRANSACTION) {
|
||||
binder_transaction_data *transaction_data = nullptr;
|
||||
|
||||
if (command == BR_TRANSACTION_SEC_CTX) {
|
||||
LOGD("Processing BR_TRANSACTION_SEC_CTX");
|
||||
auto *secctx_data = reinterpret_cast<const binder_transaction_data_secctx *>(buffer_ptr);
|
||||
transaction_data = const_cast<binder_transaction_data *>(&secctx_data->transaction_data);
|
||||
} else {
|
||||
LOGD("Processing BR_TRANSACTION");
|
||||
transaction_data = reinterpret_cast<binder_transaction_data *>(buffer_ptr);
|
||||
}
|
||||
|
||||
if (transaction_data) {
|
||||
processBinderTransaction(transaction_data);
|
||||
} else {
|
||||
LOGE("Failed to extract transaction data");
|
||||
}
|
||||
}
|
||||
|
||||
buffer_ptr += command_size;
|
||||
remaining_bytes -= command_size;
|
||||
}
|
||||
}
|
||||
} // namespace
|
||||
|
||||
int intercepted_ioctl_function(int fd, int request, ...) {
|
||||
va_list args;
|
||||
va_start(args, request);
|
||||
auto *argument = va_arg(args, void *);
|
||||
va_end(args);
|
||||
|
||||
auto result = original_ioctl_function(fd, request, argument);
|
||||
|
||||
if (result >= 0 && request == BINDER_WRITE_READ && argument) {
|
||||
const auto &write_read_data = *static_cast<const binder_write_read *>(argument);
|
||||
processBinderWriteRead(write_read_data);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
bool BinderInterceptor::shouldInterceptBinder(const wp<BBinder> &target_binder) const {
|
||||
ReadGuard guard{interceptor_registry_lock_};
|
||||
return interceptor_registry_.find(target_binder) != interceptor_registry_.end();
|
||||
}
|
||||
|
||||
status_t BinderInterceptor::onTransact(uint32_t code, const android::Parcel &data, android::Parcel *reply, uint32_t flags) {
|
||||
switch (code) {
|
||||
case intercept_constants::kRegisterInterceptor:
|
||||
return handleRegisterInterceptor(data);
|
||||
case intercept_constants::kUnregisterInterceptor:
|
||||
return handleUnregisterInterceptor(data);
|
||||
default:
|
||||
return UNKNOWN_TRANSACTION;
|
||||
}
|
||||
}
|
||||
|
||||
status_t BinderInterceptor::handleRegisterInterceptor(const android::Parcel &data) {
|
||||
sp<IBinder> target_binder, interceptor_binder;
|
||||
|
||||
if (data.readStrongBinder(&target_binder) != OK) {
|
||||
LOGE("Failed to read target binder from registration data");
|
||||
return BAD_VALUE;
|
||||
}
|
||||
|
||||
if (!target_binder->localBinder()) {
|
||||
LOGE("Target binder is not a local binder");
|
||||
return BAD_VALUE;
|
||||
}
|
||||
|
||||
if (data.readStrongBinder(&interceptor_binder) != OK) {
|
||||
LOGE("Failed to read interceptor binder from registration data");
|
||||
return BAD_VALUE;
|
||||
}
|
||||
|
||||
{
|
||||
WriteGuard write_guard{interceptor_registry_lock_};
|
||||
wp<IBinder> weak_target = target_binder;
|
||||
|
||||
auto iterator = interceptor_registry_.lower_bound(weak_target);
|
||||
if (iterator == interceptor_registry_.end() || iterator->first != weak_target) {
|
||||
iterator =
|
||||
interceptor_registry_.emplace_hint(iterator, weak_target, InterceptorRegistration{weak_target, interceptor_binder});
|
||||
} else {
|
||||
iterator->second.interceptor_binder = interceptor_binder;
|
||||
}
|
||||
|
||||
LOGI("Registered interceptor for binder %p", target_binder.get());
|
||||
return OK;
|
||||
}
|
||||
}
|
||||
|
||||
status_t BinderInterceptor::handleUnregisterInterceptor(const android::Parcel &data) {
|
||||
sp<IBinder> target_binder, interceptor_binder;
|
||||
|
||||
if (data.readStrongBinder(&target_binder) != OK) {
|
||||
LOGE("Failed to read target binder from unregistration data");
|
||||
return BAD_VALUE;
|
||||
}
|
||||
|
||||
if (!target_binder->localBinder()) {
|
||||
LOGE("Target binder is not a local binder");
|
||||
return BAD_VALUE;
|
||||
}
|
||||
|
||||
if (data.readStrongBinder(&interceptor_binder) != OK) {
|
||||
LOGE("Failed to read interceptor binder from unregistration data");
|
||||
return BAD_VALUE;
|
||||
}
|
||||
|
||||
{
|
||||
WriteGuard write_guard{interceptor_registry_lock_};
|
||||
wp<IBinder> weak_target = target_binder;
|
||||
|
||||
auto iterator = interceptor_registry_.find(weak_target);
|
||||
if (iterator != interceptor_registry_.end()) {
|
||||
if (iterator->second.interceptor_binder != interceptor_binder) {
|
||||
LOGE("Interceptor mismatch during unregistration");
|
||||
return BAD_VALUE;
|
||||
}
|
||||
interceptor_registry_.erase(iterator);
|
||||
LOGI("Unregistered interceptor for binder %p", target_binder.get());
|
||||
return OK;
|
||||
}
|
||||
|
||||
LOGW("Attempted to unregister non-existent interceptor");
|
||||
return BAD_VALUE;
|
||||
}
|
||||
}
|
||||
|
||||
bool BinderInterceptor::handleInterceptedTransaction(sp<BBinder> target_binder, uint32_t transaction_code, const Parcel &request_data,
|
||||
Parcel *reply_data, uint32_t transaction_flags, status_t &result) {
|
||||
#define VALIDATE_STATUS(expr) \
|
||||
do { \
|
||||
auto __result = (expr); \
|
||||
if (__result != OK) { \
|
||||
LOGE("Operation failed: " #expr " = %d", __result); \
|
||||
return false; \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
sp<IBinder> interceptor_binder;
|
||||
{
|
||||
ReadGuard read_guard{interceptor_registry_lock_};
|
||||
auto iterator = interceptor_registry_.find(target_binder);
|
||||
if (iterator == interceptor_registry_.end()) {
|
||||
LOGE("No interceptor found for target binder %p", target_binder.get());
|
||||
return false;
|
||||
}
|
||||
interceptor_binder = iterator->second.interceptor_binder;
|
||||
}
|
||||
|
||||
LOGD("Intercepting transaction: binder=%p code=%u flags=%u reply=%s", target_binder.get(), transaction_code, transaction_flags,
|
||||
reply_data ? "true" : "false");
|
||||
|
||||
Parcel pre_request_data, pre_response_data, modified_request_data;
|
||||
|
||||
VALIDATE_STATUS(writeInterceptorCallData(pre_request_data, target_binder, transaction_code, transaction_flags, request_data));
|
||||
VALIDATE_STATUS(interceptor_binder->transact(intercept_constants::kPreTransact, pre_request_data, &pre_response_data));
|
||||
|
||||
int32_t pre_action_type;
|
||||
VALIDATE_STATUS(validateInterceptorResponse(pre_response_data, pre_action_type));
|
||||
|
||||
LOGD("Pre-transaction action type: %d", pre_action_type);
|
||||
|
||||
switch (pre_action_type) {
|
||||
case intercept_constants::kActionSkip:
|
||||
return false;
|
||||
|
||||
case intercept_constants::kActionOverrideReply:
|
||||
result = pre_response_data.readInt32();
|
||||
if (reply_data) {
|
||||
size_t reply_size = pre_response_data.readUint64();
|
||||
VALIDATE_STATUS(reply_data->appendFrom(&pre_response_data, pre_response_data.dataPosition(), reply_size));
|
||||
}
|
||||
return true;
|
||||
|
||||
case intercept_constants::kActionOverrideData: {
|
||||
size_t data_size = pre_response_data.readUint64();
|
||||
VALIDATE_STATUS(modified_request_data.appendFrom(&pre_response_data, pre_response_data.dataPosition(), data_size));
|
||||
break;
|
||||
}
|
||||
|
||||
case intercept_constants::kActionContinue:
|
||||
default:
|
||||
VALIDATE_STATUS(modified_request_data.appendFrom(&request_data, 0, request_data.dataSize()));
|
||||
break;
|
||||
}
|
||||
|
||||
result = target_binder->transact(transaction_code, modified_request_data, reply_data, transaction_flags);
|
||||
|
||||
Parcel post_request_data, post_response_data;
|
||||
|
||||
VALIDATE_STATUS(post_request_data.writeStrongBinder(target_binder));
|
||||
VALIDATE_STATUS(post_request_data.writeUint32(transaction_code));
|
||||
VALIDATE_STATUS(post_request_data.writeUint32(transaction_flags));
|
||||
VALIDATE_STATUS(post_request_data.writeInt32(IPCThreadState::self()->getCallingUid()));
|
||||
VALIDATE_STATUS(post_request_data.writeInt32(IPCThreadState::self()->getCallingPid()));
|
||||
VALIDATE_STATUS(post_request_data.writeInt32(result));
|
||||
VALIDATE_STATUS(post_request_data.writeUint64(request_data.dataSize()));
|
||||
VALIDATE_STATUS(post_request_data.appendFrom(&request_data, 0, request_data.dataSize()));
|
||||
|
||||
size_t reply_size = reply_data ? reply_data->dataSize() : 0;
|
||||
VALIDATE_STATUS(post_request_data.writeUint64(reply_size));
|
||||
LOGD("Transaction sizes: request=%zu reply=%zu", request_data.dataSize(), reply_size);
|
||||
|
||||
if (reply_data && reply_size > 0) {
|
||||
VALIDATE_STATUS(post_request_data.appendFrom(reply_data, 0, reply_size));
|
||||
}
|
||||
|
||||
VALIDATE_STATUS(interceptor_binder->transact(intercept_constants::kPostTransact, post_request_data, &post_response_data));
|
||||
|
||||
int32_t post_action_type;
|
||||
VALIDATE_STATUS(validateInterceptorResponse(post_response_data, post_action_type));
|
||||
|
||||
LOGD("Post-transaction action type: %d", post_action_type);
|
||||
|
||||
if (post_action_type == intercept_constants::kActionOverrideReply) {
|
||||
result = post_response_data.readInt32();
|
||||
if (reply_data) {
|
||||
size_t new_reply_size = post_response_data.readUint64();
|
||||
reply_data->freeData();
|
||||
VALIDATE_STATUS(reply_data->appendFrom(&post_response_data, post_response_data.dataPosition(), new_reply_size));
|
||||
LOGD("Reply overridden: original_size=%zu new_size=%zu", reply_size, new_reply_size);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
#undef VALIDATE_STATUS
|
||||
}
|
||||
|
||||
template <typename ParcelWriter>
|
||||
status_t BinderInterceptor::writeInterceptorCallData(ParcelWriter &writer, sp<BBinder> target_binder, uint32_t transaction_code,
|
||||
uint32_t transaction_flags, const Parcel &data) const {
|
||||
auto status = writer.writeStrongBinder(target_binder);
|
||||
if (status != OK)
|
||||
return status;
|
||||
|
||||
status = writer.writeUint32(transaction_code);
|
||||
if (status != OK)
|
||||
return status;
|
||||
|
||||
status = writer.writeUint32(transaction_flags);
|
||||
if (status != OK)
|
||||
return status;
|
||||
|
||||
status = writer.writeInt32(IPCThreadState::self()->getCallingUid());
|
||||
if (status != OK)
|
||||
return status;
|
||||
|
||||
status = writer.writeInt32(IPCThreadState::self()->getCallingPid());
|
||||
if (status != OK)
|
||||
return status;
|
||||
|
||||
status = writer.writeUint64(data.dataSize());
|
||||
if (status != OK)
|
||||
return status;
|
||||
|
||||
return writer.appendFrom(&data, 0, data.dataSize());
|
||||
}
|
||||
|
||||
status_t BinderInterceptor::validateInterceptorResponse(const Parcel &response, int32_t &action_type) const {
|
||||
auto status = response.readInt32(&action_type);
|
||||
if (status != OK) {
|
||||
LOGE("Failed to read action type from interceptor response");
|
||||
return status;
|
||||
}
|
||||
|
||||
switch (action_type) {
|
||||
case intercept_constants::kActionSkip:
|
||||
case intercept_constants::kActionContinue:
|
||||
case intercept_constants::kActionOverrideReply:
|
||||
case intercept_constants::kActionOverrideData:
|
||||
return OK;
|
||||
default:
|
||||
LOGE("Invalid action type from interceptor: %d", action_type);
|
||||
return BAD_VALUE;
|
||||
}
|
||||
}
|
||||
|
||||
namespace {
|
||||
constexpr std::string_view kBinderLibraryName = "/libbinder.so";
|
||||
constexpr std::string_view kIoctlFunctionName = "ioctl";
|
||||
} // namespace
|
||||
|
||||
bool initializeBinderInterception() {
|
||||
auto memory_maps = lsplt::MapInfo::Scan();
|
||||
|
||||
dev_t binder_device_id;
|
||||
ino_t binder_inode;
|
||||
bool binder_library_found = false;
|
||||
|
||||
for (const auto &memory_map : memory_maps) {
|
||||
if (memory_map.path.ends_with(kBinderLibraryName)) {
|
||||
binder_device_id = memory_map.dev;
|
||||
binder_inode = memory_map.inode;
|
||||
binder_library_found = true;
|
||||
LOGD("Found binder library: %s (dev=0x%lx, inode=%lu)", memory_map.path.c_str(),
|
||||
static_cast<unsigned long>(binder_device_id), static_cast<unsigned long>(binder_inode));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!binder_library_found) {
|
||||
LOGE("Failed to locate libbinder.so in process memory maps");
|
||||
return false;
|
||||
}
|
||||
|
||||
g_binder_interceptor = sp<BinderInterceptor>::make();
|
||||
g_binder_stub = sp<BinderStub>::make();
|
||||
|
||||
if (!g_binder_interceptor || !g_binder_stub) {
|
||||
LOGE("Failed to create binder interceptor components");
|
||||
return false;
|
||||
}
|
||||
|
||||
lsplt::RegisterHook(binder_device_id, binder_inode, kIoctlFunctionName.data(),
|
||||
reinterpret_cast<void *>(intercepted_ioctl_function), reinterpret_cast<void **>(&original_ioctl_function));
|
||||
|
||||
if (!lsplt::CommitHook()) {
|
||||
LOGE("Failed to commit binder ioctl hook");
|
||||
g_binder_interceptor.clear();
|
||||
g_binder_stub.clear();
|
||||
return false;
|
||||
}
|
||||
|
||||
LOGI("Binder interception initialized successfully");
|
||||
return true;
|
||||
}
|
||||
|
||||
extern "C" [[gnu::visibility("default")]] [[gnu::used]]
|
||||
bool entry(void *library_handle) {
|
||||
LOGI("TrickyStore binder interceptor loaded (handle: %p)", library_handle);
|
||||
|
||||
bool success = initializeBinderInterception();
|
||||
if (success) {
|
||||
LOGI("Binder interception entry point completed successfully");
|
||||
} else {
|
||||
LOGE("Binder interception initialization failed");
|
||||
}
|
||||
|
||||
return success;
|
||||
}
|
||||
@@ -0,0 +1,453 @@
|
||||
// Copyright 2025 Dakkshesh <beakthoven@gmail.com>
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#include <android/dlext.h>
|
||||
#include <dlfcn.h>
|
||||
#include <elf.h>
|
||||
#include <fcntl.h>
|
||||
#include <link.h>
|
||||
#include <sys/auxv.h>
|
||||
#include <sys/mman.h>
|
||||
#include <sys/ptrace.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/system_properties.h>
|
||||
#include <sys/uio.h>
|
||||
#include <sys/un.h>
|
||||
#include <sys/wait.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <cinttypes>
|
||||
#include <climits>
|
||||
#include <csignal>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "logging.hpp"
|
||||
#include "lsplt.hpp"
|
||||
#include "utils.hpp"
|
||||
|
||||
using namespace std::string_literals;
|
||||
|
||||
namespace inject {
|
||||
|
||||
namespace constants {
|
||||
constexpr size_t kMagicLength = 16;
|
||||
constexpr size_t kMaxPathLength = 4096;
|
||||
constexpr const char *kSystemFileContext = "u:object_r:system_file:s0";
|
||||
constexpr const char *kLibcModule = "libc.so";
|
||||
constexpr const char *kLibdlModule = "libdl.so";
|
||||
constexpr const char *kEntrySymbol = "entry";
|
||||
} // namespace constants
|
||||
|
||||
class RemoteLibraryHandle {
|
||||
public:
|
||||
RemoteLibraryHandle(int pid, int fd, uintptr_t handle) : pid_(pid), fd_(fd), handle_(handle) {}
|
||||
|
||||
~RemoteLibraryHandle() {
|
||||
if (fd_ != -1) {
|
||||
struct user_regs_struct regs{};
|
||||
std::vector<lsplt::MapInfo> local_map, remote_map;
|
||||
if (get_regs(pid_, regs)) {
|
||||
local_map = lsplt::MapInfo::Scan();
|
||||
remote_map = lsplt::MapInfo::Scan(std::to_string(pid_));
|
||||
if (auto close_addr = find_func_addr(local_map, remote_map, constants::kLibcModule, "close")) {
|
||||
std::vector<uintptr_t> args = {static_cast<uintptr_t>(fd_)};
|
||||
remote_call(pid_, regs, reinterpret_cast<uintptr_t>(close_addr), 0, args);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
RemoteLibraryHandle(const RemoteLibraryHandle &) = delete;
|
||||
RemoteLibraryHandle &operator=(const RemoteLibraryHandle &) = delete;
|
||||
RemoteLibraryHandle(RemoteLibraryHandle &&other) noexcept : pid_(other.pid_), fd_(other.fd_), handle_(other.handle_) {
|
||||
other.fd_ = -1;
|
||||
other.handle_ = 0;
|
||||
}
|
||||
|
||||
uintptr_t handle() const {
|
||||
return handle_;
|
||||
}
|
||||
int fd() const {
|
||||
return fd_;
|
||||
}
|
||||
|
||||
private:
|
||||
int pid_;
|
||||
int fd_;
|
||||
uintptr_t handle_;
|
||||
};
|
||||
|
||||
static std::optional<int> transfer_fd_to_remote(int pid, const char *lib_path, struct user_regs_struct ®s,
|
||||
const std::vector<lsplt::MapInfo> &local_map,
|
||||
const std::vector<lsplt::MapInfo> &remote_map) {
|
||||
if (!set_sockcreate_con(constants::kSystemFileContext)) {
|
||||
LOGE("Failed to set socket creation context");
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
UniqueFd local_socket = socket(AF_UNIX, SOCK_DGRAM | SOCK_CLOEXEC, 0);
|
||||
if (local_socket == -1) {
|
||||
PLOGE("create local socket");
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
if (setfilecon(lib_path, constants::kSystemFileContext) == -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: %s", lib_path);
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
struct RemoteFunctions {
|
||||
void *socket_addr;
|
||||
void *bind_addr;
|
||||
void *recvmsg_addr;
|
||||
void *close_addr;
|
||||
void *errno_addr;
|
||||
} funcs{};
|
||||
|
||||
funcs.socket_addr = find_func_addr(local_map, remote_map, constants::kLibcModule, "socket");
|
||||
funcs.bind_addr = find_func_addr(local_map, remote_map, constants::kLibcModule, "bind");
|
||||
funcs.recvmsg_addr = find_func_addr(local_map, remote_map, constants::kLibcModule, "recvmsg");
|
||||
funcs.close_addr = find_func_addr(local_map, remote_map, constants::kLibcModule, "close");
|
||||
funcs.errno_addr = find_func_addr(local_map, remote_map, constants::kLibcModule, "__errno");
|
||||
|
||||
if (!funcs.socket_addr || !funcs.bind_addr || !funcs.recvmsg_addr || !funcs.close_addr) {
|
||||
LOGE("Failed to resolve required libc functions");
|
||||
return std::nullopt;
|
||||
}
|
||||
std::vector<uintptr_t> args;
|
||||
auto get_remote_errno = [&]() -> int {
|
||||
if (!funcs.errno_addr)
|
||||
return 0;
|
||||
args.clear();
|
||||
auto addr = remote_call(pid, regs, reinterpret_cast<uintptr_t>(funcs.errno_addr), 0, args);
|
||||
int err = 0;
|
||||
if (!read_proc(pid, addr, &err, sizeof(err)))
|
||||
return 0;
|
||||
return err;
|
||||
};
|
||||
|
||||
auto close_remote = [&](int fd) {
|
||||
args = {static_cast<uintptr_t>(fd)};
|
||||
if (remote_call(pid, regs, reinterpret_cast<uintptr_t>(funcs.close_addr), 0, args) != 0) {
|
||||
LOGE("Failed to close remote fd: %d", fd);
|
||||
}
|
||||
};
|
||||
|
||||
args = {AF_UNIX, SOCK_DGRAM | SOCK_CLOEXEC, 0};
|
||||
int remote_fd = static_cast<int>(remote_call(pid, regs, reinterpret_cast<uintptr_t>(funcs.socket_addr), 0, args));
|
||||
if (remote_fd == -1) {
|
||||
errno = get_remote_errno();
|
||||
PLOGE("remote socket creation failed");
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
auto magic = generateMagic(constants::kMagicLength);
|
||||
struct sockaddr_un sock_addr{.sun_family = AF_UNIX, .sun_path = {0}};
|
||||
memcpy(sock_addr.sun_path + 1, magic.c_str(), magic.size());
|
||||
socklen_t addr_len = sizeof(sock_addr.sun_family) + 1 + magic.size();
|
||||
|
||||
auto remote_addr = push_memory(pid, regs, &sock_addr, sizeof(sock_addr));
|
||||
args = {static_cast<uintptr_t>(remote_fd), remote_addr, static_cast<uintptr_t>(addr_len)};
|
||||
auto bind_result = remote_call(pid, regs, reinterpret_cast<uintptr_t>(funcs.bind_addr), 0, args);
|
||||
if (bind_result == static_cast<uintptr_t>(-1)) {
|
||||
errno = get_remote_errno();
|
||||
PLOGE("remote bind failed");
|
||||
close_remote(remote_fd);
|
||||
return std::nullopt;
|
||||
}
|
||||
char cmsgbuf[CMSG_SPACE(sizeof(int))] = {0};
|
||||
auto remote_cmsgbuf = push_memory(pid, regs, &cmsgbuf, sizeof(cmsgbuf));
|
||||
|
||||
struct msghdr msg_hdr{};
|
||||
msg_hdr.msg_control = reinterpret_cast<void *>(remote_cmsgbuf);
|
||||
msg_hdr.msg_controllen = sizeof(cmsgbuf);
|
||||
auto remote_hdr = push_memory(pid, regs, &msg_hdr, sizeof(msg_hdr));
|
||||
|
||||
args = {static_cast<uintptr_t>(remote_fd), remote_hdr, MSG_WAITALL};
|
||||
if (!remote_pre_call(pid, regs, reinterpret_cast<uintptr_t>(funcs.recvmsg_addr), 0, args)) {
|
||||
LOGE("Failed to start remote recvmsg call");
|
||||
close_remote(remote_fd);
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
msg_hdr.msg_control = &cmsgbuf;
|
||||
msg_hdr.msg_name = &sock_addr;
|
||||
msg_hdr.msg_namelen = addr_len;
|
||||
|
||||
{
|
||||
auto *cmsg = CMSG_FIRSTHDR(&msg_hdr);
|
||||
cmsg->cmsg_len = CMSG_LEN(sizeof(int));
|
||||
cmsg->cmsg_level = SOL_SOCKET;
|
||||
cmsg->cmsg_type = SCM_RIGHTS;
|
||||
*reinterpret_cast<int *>(CMSG_DATA(cmsg)) = local_lib_fd;
|
||||
}
|
||||
|
||||
if (sendmsg(local_socket, &msg_hdr, 0) == -1) {
|
||||
PLOGE("Failed to send fd to remote process");
|
||||
close_remote(remote_fd);
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
auto recvmsg_result = static_cast<ssize_t>(remote_post_call(pid, regs, 0));
|
||||
if (recvmsg_result == -1) {
|
||||
errno = get_remote_errno();
|
||||
PLOGE("Remote recvmsg failed");
|
||||
close_remote(remote_fd);
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
if (read_proc(pid, remote_cmsgbuf, &cmsgbuf, sizeof(cmsgbuf)) != sizeof(cmsgbuf)) {
|
||||
LOGE("Failed to read control message from remote process");
|
||||
close_remote(remote_fd);
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
auto *cmsg = CMSG_FIRSTHDR(&msg_hdr);
|
||||
if (!cmsg || cmsg->cmsg_len != CMSG_LEN(sizeof(int)) || cmsg->cmsg_level != SOL_SOCKET || cmsg->cmsg_type != SCM_RIGHTS) {
|
||||
LOGE("Invalid control message received from remote process");
|
||||
close_remote(remote_fd);
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
int transferred_fd = *reinterpret_cast<int *>(CMSG_DATA(cmsg));
|
||||
LOGD("Successfully transferred fd %d to remote process", transferred_fd);
|
||||
close_remote(remote_fd);
|
||||
return transferred_fd;
|
||||
}
|
||||
|
||||
static std::optional<uintptr_t> remote_dlopen(int pid, struct user_regs_struct ®s, const std::vector<lsplt::MapInfo> &local_map,
|
||||
const std::vector<lsplt::MapInfo> &remote_map, int lib_fd, const char *lib_path,
|
||||
uintptr_t libc_return_addr) {
|
||||
auto dlopen_addr = find_func_addr(local_map, remote_map, constants::kLibdlModule, "android_dlopen_ext");
|
||||
if (!dlopen_addr) {
|
||||
LOGE("Failed to find android_dlopen_ext in %s", constants::kLibdlModule);
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
android_dlextinfo dlext_info{};
|
||||
dlext_info.flags = ANDROID_DLEXT_USE_LIBRARY_FD;
|
||||
dlext_info.library_fd = lib_fd;
|
||||
|
||||
uintptr_t remote_info = push_memory(pid, regs, &dlext_info, sizeof(dlext_info));
|
||||
uintptr_t remote_path = push_string(pid, regs, lib_path);
|
||||
|
||||
std::vector<uintptr_t> args = {remote_path, RTLD_NOW, remote_info};
|
||||
uintptr_t remote_handle = remote_call(pid, regs, reinterpret_cast<uintptr_t>(dlopen_addr), libc_return_addr, args);
|
||||
|
||||
if (remote_handle == 0) {
|
||||
LOGE("Remote dlopen failed for library: %s", lib_path);
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
LOGD("Successfully loaded library with handle: %p", reinterpret_cast<void *>(remote_handle));
|
||||
return remote_handle;
|
||||
}
|
||||
|
||||
static std::optional<uintptr_t> remote_find_entry(int pid, struct user_regs_struct ®s, const std::vector<lsplt::MapInfo> &local_map,
|
||||
const std::vector<lsplt::MapInfo> &remote_map, uintptr_t remote_handle,
|
||||
uintptr_t libc_return_addr) {
|
||||
auto dlsym_addr = find_func_addr(local_map, remote_map, constants::kLibdlModule, "dlsym");
|
||||
if (!dlsym_addr) {
|
||||
LOGE("Failed to find dlsym in %s", constants::kLibdlModule);
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
uintptr_t remote_symbol = push_string(pid, regs, constants::kEntrySymbol);
|
||||
|
||||
std::vector<uintptr_t> args = {remote_handle, remote_symbol};
|
||||
uintptr_t entry_addr = remote_call(pid, regs, reinterpret_cast<uintptr_t>(dlsym_addr), libc_return_addr, args);
|
||||
|
||||
if (entry_addr == 0) {
|
||||
LOGE("Failed to find entry symbol '%s' in remote library", constants::kEntrySymbol);
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
LOGD("Found entry point at: %p", reinterpret_cast<void *>(entry_addr));
|
||||
return entry_addr;
|
||||
}
|
||||
|
||||
static bool remote_call_entry(int pid, struct user_regs_struct ®s, uintptr_t entry_addr, uintptr_t remote_handle,
|
||||
uintptr_t libc_return_addr) {
|
||||
std::vector<uintptr_t> args = {remote_handle};
|
||||
uintptr_t result = remote_call(pid, regs, entry_addr, libc_return_addr, args);
|
||||
|
||||
LOGD("Entry point called with result: %p", reinterpret_cast<void *>(result));
|
||||
return true;
|
||||
}
|
||||
|
||||
class PtraceAttachment {
|
||||
public:
|
||||
explicit PtraceAttachment(int target_pid) : pid_(target_pid), attached_(false) {
|
||||
if (ptrace(PTRACE_ATTACH, pid_, 0, 0) == -1) {
|
||||
PLOGE("Failed to attach to process %d", pid_);
|
||||
return;
|
||||
}
|
||||
attached_ = true;
|
||||
LOGD("Successfully attached to process %d", pid_);
|
||||
}
|
||||
|
||||
~PtraceAttachment() {
|
||||
if (attached_) {
|
||||
if (ptrace(PTRACE_DETACH, pid_, 0, 0) == -1) {
|
||||
PLOGE("Failed to detach from process %d", pid_);
|
||||
} else {
|
||||
LOGD("Successfully detached from process %d", pid_);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool is_attached() const {
|
||||
return attached_;
|
||||
}
|
||||
|
||||
PtraceAttachment(const PtraceAttachment &) = delete;
|
||||
PtraceAttachment &operator=(const PtraceAttachment &) = delete;
|
||||
|
||||
private:
|
||||
int pid_;
|
||||
bool attached_;
|
||||
};
|
||||
|
||||
bool inject_library(int pid, const char *lib_path, const char *entry_name) {
|
||||
LOGI("Starting injection of %s (entry: %s) into process %d", lib_path, entry_name, pid);
|
||||
|
||||
PtraceAttachment ptrace_guard(pid);
|
||||
if (!ptrace_guard.is_attached()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
int status;
|
||||
if (!wait_for_trace(pid, &status, __WALL)) {
|
||||
LOGE("Failed to wait for trace");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!WIFSTOPPED(status) || WSTOPSIG(status) != SIGSTOP) {
|
||||
LOGE("Process stopped for unexpected reason: %s", parse_status(status).c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
struct user_regs_struct current_regs{}, backup_regs{};
|
||||
if (!get_regs(pid, current_regs)) {
|
||||
LOGE("Failed to get process registers");
|
||||
return false;
|
||||
}
|
||||
backup_regs = current_regs;
|
||||
LOGD("Process stopped and registers backed up");
|
||||
|
||||
auto remote_map = lsplt::MapInfo::Scan(std::to_string(pid));
|
||||
auto local_map = lsplt::MapInfo::Scan();
|
||||
|
||||
auto libc_return_addr = find_module_return_addr(remote_map, constants::kLibcModule);
|
||||
if (!libc_return_addr) {
|
||||
LOGE("Failed to find return address for %s", constants::kLibcModule);
|
||||
return false;
|
||||
}
|
||||
LOGD("Found libc return address: %p", libc_return_addr);
|
||||
|
||||
auto lib_fd_opt = transfer_fd_to_remote(pid, lib_path, current_regs, local_map, remote_map);
|
||||
if (!lib_fd_opt) {
|
||||
LOGE("Failed to transfer library fd to remote process");
|
||||
return false;
|
||||
}
|
||||
int lib_fd = *lib_fd_opt;
|
||||
|
||||
auto handle_opt =
|
||||
remote_dlopen(pid, current_regs, local_map, remote_map, lib_fd, lib_path, reinterpret_cast<uintptr_t>(libc_return_addr));
|
||||
if (!handle_opt) {
|
||||
LOGE("Failed to load library in remote process");
|
||||
return false;
|
||||
}
|
||||
uintptr_t remote_handle = *handle_opt;
|
||||
|
||||
auto close_addr = find_func_addr(local_map, remote_map, constants::kLibcModule, "close");
|
||||
if (close_addr) {
|
||||
std::vector<uintptr_t> args = {static_cast<uintptr_t>(lib_fd)};
|
||||
if (remote_call(pid, current_regs, reinterpret_cast<uintptr_t>(close_addr), 0, args) != 0) {
|
||||
LOGW("Failed to close remote library fd: %d", lib_fd);
|
||||
}
|
||||
}
|
||||
|
||||
auto entry_opt =
|
||||
remote_find_entry(pid, current_regs, local_map, remote_map, remote_handle, reinterpret_cast<uintptr_t>(libc_return_addr));
|
||||
if (!entry_opt) {
|
||||
LOGE("Failed to find entry point in remote library");
|
||||
return false;
|
||||
}
|
||||
uintptr_t entry_addr = *entry_opt;
|
||||
|
||||
if (!remote_call_entry(pid, current_regs, entry_addr, remote_handle, reinterpret_cast<uintptr_t>(libc_return_addr))) {
|
||||
LOGE("Failed to call entry point");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!set_regs(pid, backup_regs)) {
|
||||
LOGE("Failed to restore original registers");
|
||||
return false;
|
||||
}
|
||||
|
||||
LOGI("Library injection completed successfully");
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace inject
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
#ifndef NDEBUG
|
||||
logging::setPrintEnabled(true);
|
||||
#endif
|
||||
|
||||
if (argc < 4) {
|
||||
fprintf(stderr, "Usage: %s <pid> <lib_path> <entry_name>\n", argv[0]);
|
||||
fprintf(stderr, " pid - Target process ID\n");
|
||||
fprintf(stderr, " lib_path - Path to shared library to inject\n");
|
||||
fprintf(stderr, " entry_name - Entry point symbol name in library\n");
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
char *endptr;
|
||||
long pid_long = strtol(argv[1], &endptr, 10);
|
||||
if (*endptr != '\0' || pid_long <= 0 || pid_long > INT_MAX) {
|
||||
fprintf(stderr, "Error: Invalid PID '%s'\n", argv[1]);
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
int pid = static_cast<int>(pid_long);
|
||||
|
||||
char resolved_path[inject::constants::kMaxPathLength];
|
||||
if (realpath(argv[2], resolved_path) == nullptr) {
|
||||
fprintf(stderr, "Error: Failed to resolve library path '%s': %s\n", argv[2], strerror(errno));
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
if (access(resolved_path, R_OK) != 0) {
|
||||
fprintf(stderr, "Error: Library file '%s' is not readable: %s\n", resolved_path, strerror(errno));
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
const char *entry_name = argv[3];
|
||||
if (strlen(entry_name) == 0) {
|
||||
fprintf(stderr, "Error: Entry name cannot be empty\n");
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
LOGI("TrickyStore injector starting...");
|
||||
bool success = inject::inject_library(pid, resolved_path, entry_name);
|
||||
|
||||
if (success) {
|
||||
LOGI("Injection completed successfully");
|
||||
return EXIT_SUCCESS;
|
||||
} else {
|
||||
LOGE("Injection failed");
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,693 @@
|
||||
// Copyright 2025 Dakkshesh <beakthoven@gmail.com>
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#include "utils.hpp"
|
||||
|
||||
#include <dlfcn.h>
|
||||
#include <elf.h>
|
||||
#include <fcntl.h>
|
||||
#include <link.h>
|
||||
#include <sched.h>
|
||||
#include <sys/auxv.h>
|
||||
#include <sys/mman.h>
|
||||
#include <sys/ptrace.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/syscall.h>
|
||||
#include <sys/sysmacros.h>
|
||||
#include <sys/uio.h>
|
||||
#include <sys/wait.h>
|
||||
#include <sys/xattr.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <array>
|
||||
#include <cinttypes>
|
||||
#include <csignal>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <optional>
|
||||
#include <random>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
|
||||
#include "logging.hpp"
|
||||
|
||||
namespace {
|
||||
constexpr size_t kMaxPathLength = 256;
|
||||
constexpr size_t kMsgBufferSize = 64;
|
||||
constexpr size_t kStatusBufferSize = 128;
|
||||
constexpr int kInvalidFd = -1;
|
||||
constexpr uintptr_t kStackAlignment = 0xf;
|
||||
constexpr int kMaxArguments = 8;
|
||||
|
||||
constexpr char kReadPerm = 'r';
|
||||
constexpr char kWritePerm = 'w';
|
||||
constexpr char kExecPerm = 'x';
|
||||
constexpr char kNoPerm = '-';
|
||||
|
||||
constexpr std::string_view kRandomChars = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
|
||||
} // namespace
|
||||
|
||||
bool switch_mnt_ns(int pid, int *fd) {
|
||||
if (pid == 0) {
|
||||
if (!fd || *fd == kInvalidFd) {
|
||||
LOGE("Invalid file descriptor for namespace switch");
|
||||
return false;
|
||||
}
|
||||
|
||||
UniqueFd nsfd(*fd);
|
||||
*fd = kInvalidFd;
|
||||
|
||||
std::string path = "/proc/self/fd/" + std::to_string(nsfd);
|
||||
if (setns(nsfd, CLONE_NEWNS) == -1) {
|
||||
PLOGE("Failed to switch to namespace: %s", path.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
LOGD("Successfully switched back to original namespace");
|
||||
return true;
|
||||
} else {
|
||||
int old_nsfd = kInvalidFd;
|
||||
|
||||
if (fd) {
|
||||
old_nsfd = open("/proc/self/ns/mnt", O_RDONLY | O_CLOEXEC);
|
||||
if (old_nsfd == kInvalidFd) {
|
||||
PLOGE("Failed to open current namespace");
|
||||
return false;
|
||||
}
|
||||
*fd = old_nsfd;
|
||||
}
|
||||
|
||||
std::string target_path = "/proc/" + std::to_string(pid) + "/ns/mnt";
|
||||
UniqueFd target_nsfd = open(target_path.c_str(), O_RDONLY | O_CLOEXEC);
|
||||
if (target_nsfd == kInvalidFd) {
|
||||
PLOGE("Failed to open target namespace: %s", target_path.c_str());
|
||||
if (fd)
|
||||
*fd = kInvalidFd;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (setns(target_nsfd, CLONE_NEWNS) == -1) {
|
||||
PLOGE("Failed to switch to target namespace: %s", target_path.c_str());
|
||||
if (fd)
|
||||
*fd = kInvalidFd;
|
||||
return false;
|
||||
}
|
||||
|
||||
LOGD("Successfully switched to namespace for PID %d", pid);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
ssize_t write_proc(int pid, uintptr_t remote_addr, const void *buf, size_t len, bool use_proc_mem) {
|
||||
if (!buf || len == 0) {
|
||||
LOGE("Invalid parameters for write_proc");
|
||||
return -1;
|
||||
}
|
||||
|
||||
LOGV("Writing %zu bytes to PID %d at address %" PRIxPTR " (use_proc_mem=%s)", len, pid, remote_addr,
|
||||
use_proc_mem ? "true" : "false");
|
||||
|
||||
ssize_t bytes_written;
|
||||
|
||||
if (use_proc_mem) {
|
||||
char proc_path[kMaxPathLength];
|
||||
snprintf(proc_path, sizeof(proc_path), "/proc/%d/mem", pid);
|
||||
|
||||
UniqueFd proc_fd = open(proc_path, O_WRONLY | O_CLOEXEC);
|
||||
if (proc_fd == kInvalidFd) {
|
||||
PLOGE("Failed to open %s", proc_path);
|
||||
return -1;
|
||||
}
|
||||
|
||||
bytes_written = pwrite(proc_fd, buf, len, static_cast<off_t>(remote_addr));
|
||||
if (bytes_written == -1) {
|
||||
PLOGE("pwrite failed for address %" PRIxPTR, remote_addr);
|
||||
}
|
||||
} else {
|
||||
struct iovec local_iov = {.iov_base = const_cast<void *>(buf), .iov_len = len};
|
||||
struct iovec remote_iov = {.iov_base = reinterpret_cast<void *>(remote_addr), .iov_len = len};
|
||||
|
||||
bytes_written = process_vm_writev(pid, &local_iov, 1, &remote_iov, 1, 0);
|
||||
if (bytes_written == -1) {
|
||||
PLOGE("process_vm_writev failed for address %" PRIxPTR, remote_addr);
|
||||
}
|
||||
}
|
||||
|
||||
if (bytes_written != -1 && static_cast<size_t>(bytes_written) != len) {
|
||||
LOGW("Partial write: %zd bytes written, %zu expected", bytes_written, len);
|
||||
}
|
||||
|
||||
return bytes_written;
|
||||
}
|
||||
|
||||
ssize_t read_proc(int pid, uintptr_t remote_addr, void *buf, size_t len) {
|
||||
if (!buf || len == 0) {
|
||||
LOGE("Invalid parameters for read_proc");
|
||||
return -1;
|
||||
}
|
||||
|
||||
LOGV("Reading %zu bytes from PID %d at address %" PRIxPTR, len, pid, remote_addr);
|
||||
|
||||
struct iovec local_iov = {.iov_base = buf, .iov_len = len};
|
||||
struct iovec remote_iov = {.iov_base = reinterpret_cast<void *>(remote_addr), .iov_len = len};
|
||||
|
||||
ssize_t bytes_read = process_vm_readv(pid, &local_iov, 1, &remote_iov, 1, 0);
|
||||
if (bytes_read == -1) {
|
||||
PLOGE("process_vm_readv failed for address %" PRIxPTR, remote_addr);
|
||||
} else if (static_cast<size_t>(bytes_read) != len) {
|
||||
LOGW("Partial read: %zd bytes read, %zu expected", bytes_read, len);
|
||||
}
|
||||
|
||||
return bytes_read;
|
||||
}
|
||||
|
||||
bool get_regs(int pid, struct user_regs_struct ®s) {
|
||||
LOGV("Getting registers for PID %d", pid);
|
||||
|
||||
#if defined(__x86_64__) || defined(__i386__)
|
||||
if (ptrace(PTRACE_GETREGS, pid, 0, ®s) == -1) {
|
||||
PLOGE("Failed to get registers for PID %d", pid);
|
||||
return false;
|
||||
}
|
||||
#elif defined(__aarch64__) || defined(__arm__)
|
||||
struct iovec reg_iov = {.iov_base = ®s, .iov_len = sizeof(struct user_regs_struct)};
|
||||
if (ptrace(PTRACE_GETREGSET, pid, NT_PRSTATUS, ®_iov) == -1) {
|
||||
PLOGE("Failed to get register set for PID %d", pid);
|
||||
return false;
|
||||
}
|
||||
#else
|
||||
# error "Unsupported architecture for register access"
|
||||
#endif
|
||||
|
||||
LOGV("Successfully retrieved registers for PID %d", pid);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool set_regs(int pid, struct user_regs_struct ®s) {
|
||||
LOGV("Setting registers for PID %d", pid);
|
||||
|
||||
#if defined(__x86_64__) || defined(__i386__)
|
||||
if (ptrace(PTRACE_SETREGS, pid, 0, ®s) == -1) {
|
||||
PLOGE("Failed to set registers for PID %d", pid);
|
||||
return false;
|
||||
}
|
||||
#elif defined(__aarch64__) || defined(__arm__)
|
||||
struct iovec reg_iov = {.iov_base = ®s, .iov_len = sizeof(struct user_regs_struct)};
|
||||
if (ptrace(PTRACE_SETREGSET, pid, NT_PRSTATUS, ®_iov) == -1) {
|
||||
PLOGE("Failed to set register set for PID %d", pid);
|
||||
return false;
|
||||
}
|
||||
#else
|
||||
# error "Unsupported architecture for register access"
|
||||
#endif
|
||||
|
||||
LOGV("Successfully set registers for PID %d", pid);
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string get_addr_mem_region(const std::vector<lsplt::MapInfo> &map_info, uintptr_t addr) {
|
||||
for (const auto &map : map_info) {
|
||||
if (map.start <= addr && map.end > addr) {
|
||||
std::string perms_str;
|
||||
perms_str.reserve(4);
|
||||
|
||||
perms_str += (map.perms & PROT_READ) ? kReadPerm : kNoPerm;
|
||||
perms_str += (map.perms & PROT_WRITE) ? kWritePerm : kNoPerm;
|
||||
perms_str += (map.perms & PROT_EXEC) ? kExecPerm : kNoPerm;
|
||||
|
||||
return map.path + ' ' + perms_str;
|
||||
}
|
||||
}
|
||||
return "<unknown>";
|
||||
}
|
||||
|
||||
void *find_module_base(const std::vector<lsplt::MapInfo> &map_info, std::string_view module_suffix) {
|
||||
for (const auto &map : map_info) {
|
||||
if (map.offset == 0 && map.path.ends_with(module_suffix)) {
|
||||
LOGV("Found module base for '%.*s' at %p", static_cast<int>(module_suffix.length()), module_suffix.data(),
|
||||
reinterpret_cast<void *>(map.start));
|
||||
return reinterpret_cast<void *>(map.start);
|
||||
}
|
||||
}
|
||||
|
||||
LOGV("Module base not found for suffix '%.*s'", static_cast<int>(module_suffix.length()), module_suffix.data());
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void *find_func_addr(const std::vector<lsplt::MapInfo> &local_map_info, const std::vector<lsplt::MapInfo> &remote_map_info,
|
||||
std::string_view module_name, std::string_view function_name) {
|
||||
LOGV("Resolving function '%.*s' in module '%.*s'", static_cast<int>(function_name.length()), function_name.data(),
|
||||
static_cast<int>(module_name.length()), module_name.data());
|
||||
|
||||
void *lib_handle = dlopen(module_name.data(), RTLD_NOW);
|
||||
if (!lib_handle) {
|
||||
LOGE("Failed to open library '%.*s': %s", static_cast<int>(module_name.length()), module_name.data(), dlerror());
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto lib_closer = [lib_handle]() {
|
||||
dlclose(lib_handle);
|
||||
};
|
||||
|
||||
auto *symbol_addr = reinterpret_cast<uint8_t *>(dlsym(lib_handle, function_name.data()));
|
||||
if (!symbol_addr) {
|
||||
LOGE("Failed to find symbol '%.*s' in library '%.*s': %s", static_cast<int>(function_name.length()), function_name.data(),
|
||||
static_cast<int>(module_name.length()), module_name.data(), dlerror());
|
||||
lib_closer();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
LOGV("Found symbol '%.*s' at local address %p", static_cast<int>(function_name.length()), function_name.data(), symbol_addr);
|
||||
lib_closer();
|
||||
|
||||
auto *local_base = reinterpret_cast<uint8_t *>(find_module_base(local_map_info, module_name));
|
||||
if (!local_base) {
|
||||
LOGE("Failed to find local base address for module '%.*s'", static_cast<int>(module_name.length()), module_name.data());
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto *remote_base = reinterpret_cast<uint8_t *>(find_module_base(remote_map_info, module_name));
|
||||
if (!remote_base) {
|
||||
LOGE("Failed to find remote base address for module '%.*s'", static_cast<int>(module_name.length()), module_name.data());
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
ptrdiff_t symbol_offset = symbol_addr - local_base;
|
||||
auto *remote_symbol_addr = remote_base + symbol_offset;
|
||||
|
||||
LOGV("Address translation: local_base=%p remote_base=%p offset=%td -> "
|
||||
"remote_addr=%p",
|
||||
local_base, remote_base, symbol_offset, remote_symbol_addr);
|
||||
|
||||
return remote_symbol_addr;
|
||||
}
|
||||
|
||||
void align_stack(struct user_regs_struct ®s, uintptr_t preserve_bytes) {
|
||||
regs.REG_SP = (regs.REG_SP - preserve_bytes) & ~kStackAlignment;
|
||||
LOGV("Stack aligned to %" PRIxPTR " (preserved %zu bytes)", static_cast<uintptr_t>(regs.REG_SP), preserve_bytes);
|
||||
}
|
||||
|
||||
uintptr_t push_memory(int pid, struct user_regs_struct ®s, const void *data, size_t length) {
|
||||
if (!data || length == 0) {
|
||||
LOGE("Invalid parameters for push_memory: data=%p, length=%zu", data, length);
|
||||
return 0;
|
||||
}
|
||||
|
||||
regs.REG_SP -= length;
|
||||
align_stack(regs);
|
||||
|
||||
auto stack_addr = static_cast<uintptr_t>(regs.REG_SP);
|
||||
|
||||
if (write_proc(pid, stack_addr, data, length) != static_cast<ssize_t>(length)) {
|
||||
LOGE("Failed to push %zu bytes to remote stack at %" PRIxPTR, length, stack_addr);
|
||||
return 0;
|
||||
}
|
||||
|
||||
LOGV("Pushed %zu bytes to remote stack at %" PRIxPTR, length, stack_addr);
|
||||
return stack_addr;
|
||||
}
|
||||
|
||||
uintptr_t push_string(int pid, struct user_regs_struct ®s, const char *str) {
|
||||
if (!str) {
|
||||
LOGE("Null string pointer passed to push_string");
|
||||
return 0;
|
||||
}
|
||||
|
||||
size_t str_length = strlen(str) + 1;
|
||||
|
||||
regs.REG_SP -= str_length;
|
||||
align_stack(regs);
|
||||
|
||||
auto stack_addr = static_cast<uintptr_t>(regs.REG_SP);
|
||||
|
||||
if (write_proc(pid, stack_addr, str, str_length) != static_cast<ssize_t>(str_length)) {
|
||||
LOGE("Failed to push string '%s' to remote stack", str);
|
||||
return 0;
|
||||
}
|
||||
|
||||
LOGV("Pushed string '%s' (%zu bytes) to remote stack at %" PRIxPTR, str, str_length, stack_addr);
|
||||
return stack_addr;
|
||||
}
|
||||
|
||||
namespace {
|
||||
#if defined(__x86_64__)
|
||||
constexpr size_t kMaxRegisterArgs = 6;
|
||||
void setup_x86_64_args(struct user_regs_struct ®s, const std::vector<uintptr_t> &args) {
|
||||
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];
|
||||
}
|
||||
#elif defined(__i386__)
|
||||
constexpr size_t kMaxRegisterArgs = 0;
|
||||
#elif defined(__aarch64__)
|
||||
constexpr size_t kMaxRegisterArgs = 8;
|
||||
void setup_aarch64_args(struct user_regs_struct ®s, const std::vector<uintptr_t> &args) {
|
||||
for (size_t i = 0; i < std::min(args.size(), kMaxRegisterArgs); i++) {
|
||||
regs.regs[i] = args[i];
|
||||
}
|
||||
}
|
||||
#elif defined(__arm__)
|
||||
constexpr size_t kMaxRegisterArgs = 4;
|
||||
void setup_arm_args(struct user_regs_struct ®s, const std::vector<uintptr_t> &args) {
|
||||
for (size_t i = 0; i < std::min(args.size(), kMaxRegisterArgs); i++) {
|
||||
regs.uregs[i] = args[i];
|
||||
}
|
||||
}
|
||||
#endif
|
||||
} // namespace
|
||||
|
||||
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("Setting up remote function call to %" PRIxPTR " with %zu arguments", func_addr, args.size());
|
||||
for (size_t i = 0; i < args.size(); i++) {
|
||||
LOGV(" arg[%zu] = %p", i, reinterpret_cast<void *>(args[i]));
|
||||
}
|
||||
|
||||
#if defined(__x86_64__)
|
||||
setup_x86_64_args(regs, args);
|
||||
|
||||
if (args.size() > kMaxRegisterArgs) {
|
||||
size_t stack_args_size = (args.size() - kMaxRegisterArgs) * sizeof(uintptr_t);
|
||||
align_stack(regs, stack_args_size);
|
||||
|
||||
if (write_proc(pid, static_cast<uintptr_t>(regs.REG_SP), args.data() + kMaxRegisterArgs, stack_args_size) !=
|
||||
static_cast<ssize_t>(stack_args_size)) {
|
||||
LOGE("Failed to push stack arguments for x86_64");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
regs.REG_SP -= sizeof(uintptr_t);
|
||||
if (write_proc(pid, static_cast<uintptr_t>(regs.REG_SP), &return_addr, sizeof(return_addr)) != sizeof(return_addr)) {
|
||||
LOGE("Failed to write return address");
|
||||
return false;
|
||||
}
|
||||
|
||||
regs.REG_IP = func_addr;
|
||||
|
||||
#elif defined(__i386__)
|
||||
if (args.size() > 0) {
|
||||
size_t stack_args_size = args.size() * sizeof(uintptr_t);
|
||||
align_stack(regs, stack_args_size);
|
||||
|
||||
if (write_proc(pid, static_cast<uintptr_t>(regs.REG_SP), args.data(), stack_args_size) !=
|
||||
static_cast<ssize_t>(stack_args_size)) {
|
||||
LOGE("Failed to push arguments for i386");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
regs.REG_SP -= sizeof(uintptr_t);
|
||||
if (write_proc(pid, static_cast<uintptr_t>(regs.REG_SP), &return_addr, sizeof(return_addr)) != sizeof(return_addr)) {
|
||||
LOGE("Failed to write return address for i386");
|
||||
return false;
|
||||
}
|
||||
|
||||
regs.REG_IP = func_addr;
|
||||
|
||||
#elif defined(__aarch64__)
|
||||
setup_aarch64_args(regs, args);
|
||||
|
||||
if (args.size() > kMaxRegisterArgs) {
|
||||
size_t stack_args_size = (args.size() - kMaxRegisterArgs) * sizeof(uintptr_t);
|
||||
align_stack(regs, stack_args_size);
|
||||
|
||||
if (write_proc(pid, static_cast<uintptr_t>(regs.REG_SP), args.data() + kMaxRegisterArgs, stack_args_size) !=
|
||||
static_cast<ssize_t>(stack_args_size)) {
|
||||
LOGE("Failed to push stack arguments for aarch64");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
regs.regs[30] = return_addr;
|
||||
regs.REG_IP = func_addr;
|
||||
|
||||
#elif defined(__arm__)
|
||||
setup_arm_args(regs, args);
|
||||
|
||||
if (args.size() > kMaxRegisterArgs) {
|
||||
size_t stack_args_size = (args.size() - kMaxRegisterArgs) * sizeof(uintptr_t);
|
||||
align_stack(regs, stack_args_size);
|
||||
|
||||
if (write_proc(pid, static_cast<uintptr_t>(regs.REG_SP), args.data() + kMaxRegisterArgs, stack_args_size) !=
|
||||
static_cast<ssize_t>(stack_args_size)) {
|
||||
LOGE("Failed to push stack arguments for ARM");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
#else
|
||||
# error "Unsupported architecture for remote function calls"
|
||||
#endif
|
||||
|
||||
if (!set_regs(pid, regs)) {
|
||||
LOGE("Failed to set registers for remote function call");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (ptrace(PTRACE_CONT, pid, 0, 0) == -1) {
|
||||
PLOGE("Failed to continue remote process execution");
|
||||
return false;
|
||||
}
|
||||
|
||||
LOGV("Remote function call initiated successfully");
|
||||
return true;
|
||||
}
|
||||
|
||||
uintptr_t remote_post_call(int pid, struct user_regs_struct ®s, uintptr_t expected_return_addr) {
|
||||
LOGV("Waiting for remote function call completion");
|
||||
|
||||
int status;
|
||||
if (!wait_for_trace(pid, &status, __WALL)) {
|
||||
LOGE("Failed to wait for remote function completion");
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!get_regs(pid, regs)) {
|
||||
LOGE("Failed to get registers after remote call");
|
||||
return 0;
|
||||
}
|
||||
|
||||
int stop_signal = WSTOPSIG(status);
|
||||
LOGV("Remote function stopped with signal: %s(%d) at address %p", sigabbrev_np(stop_signal), stop_signal,
|
||||
reinterpret_cast<void *>(regs.REG_IP));
|
||||
|
||||
if (stop_signal == SIGSEGV) {
|
||||
if (static_cast<uintptr_t>(regs.REG_IP) != expected_return_addr) {
|
||||
LOGE("Function returned to unexpected address %p (expected %p)", reinterpret_cast<void *>(regs.REG_IP),
|
||||
reinterpret_cast<void *>(expected_return_addr));
|
||||
|
||||
siginfo_t crash_info;
|
||||
if (ptrace(PTRACE_GETSIGINFO, pid, 0, &crash_info) == 0) {
|
||||
LOGE("Crash details: si_code=%d si_addr=%p", crash_info.si_code, crash_info.si_addr);
|
||||
} else {
|
||||
PLOGE("Failed to get crash signal info");
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
uintptr_t return_value = regs.REG_RET;
|
||||
LOGV("Remote function completed with return value: %p", reinterpret_cast<void *>(return_value));
|
||||
return return_value;
|
||||
} else {
|
||||
LOGE("Remote function stopped unexpectedly: %s at address %p", parse_status(status).c_str(),
|
||||
reinterpret_cast<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)) {
|
||||
LOGE("Failed to prepare remote function call");
|
||||
return 0;
|
||||
}
|
||||
return remote_post_call(pid, regs, return_addr);
|
||||
}
|
||||
|
||||
int fork_dont_care() {
|
||||
int first_pid = fork();
|
||||
if (first_pid < 0) {
|
||||
PLOGE("Failed first fork for daemon process");
|
||||
return first_pid;
|
||||
}
|
||||
|
||||
if (first_pid == 0) {
|
||||
int second_pid = fork();
|
||||
if (second_pid < 0) {
|
||||
PLOGE("Failed second fork for daemon process");
|
||||
exit(EXIT_FAILURE);
|
||||
} else if (second_pid > 0) {
|
||||
exit(EXIT_SUCCESS);
|
||||
}
|
||||
return 0;
|
||||
} else {
|
||||
int status;
|
||||
waitpid(first_pid, &status, __WALL);
|
||||
return first_pid;
|
||||
}
|
||||
}
|
||||
|
||||
bool wait_for_trace(int pid, int *status, int flags) {
|
||||
if (!status) {
|
||||
LOGE("Null status pointer passed to wait_for_trace");
|
||||
return false;
|
||||
}
|
||||
|
||||
while (true) {
|
||||
pid_t result = waitpid(pid, status, flags);
|
||||
if (result == -1) {
|
||||
if (errno == EINTR) {
|
||||
LOGV("waitpid interrupted, retrying");
|
||||
continue;
|
||||
} else {
|
||||
PLOGE("waitpid failed for PID %d", pid);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!WIFSTOPPED(*status)) {
|
||||
LOGE("Process %d not stopped for trace: %s", pid, parse_status(*status).c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
LOGV("Process %d stopped for trace with status: %s", pid, parse_status(*status).c_str());
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
std::string parse_status(int status) {
|
||||
char status_buf[kStatusBufferSize];
|
||||
|
||||
if (WIFEXITED(status)) {
|
||||
snprintf(status_buf, sizeof(status_buf), "0x%x exited with code %d", status, WEXITSTATUS(status));
|
||||
} else if (WIFSIGNALED(status)) {
|
||||
snprintf(status_buf, sizeof(status_buf), "0x%x terminated by signal %s(%d)", status, sigabbrev_np(WTERMSIG(status)),
|
||||
WTERMSIG(status));
|
||||
} else if (WIFSTOPPED(status)) {
|
||||
int stop_signal = WSTOPSIG(status);
|
||||
snprintf(status_buf, sizeof(status_buf), "0x%x stopped by signal=%s(%d), event=%s", status, sigabbrev_np(stop_signal),
|
||||
stop_signal, parse_ptrace_event(status));
|
||||
} else {
|
||||
snprintf(status_buf, sizeof(status_buf), "0x%x unknown status", status);
|
||||
}
|
||||
|
||||
return std::string(status_buf);
|
||||
}
|
||||
|
||||
std::string get_program(int pid) {
|
||||
std::string exe_path = "/proc/" + std::to_string(pid) + "/exe";
|
||||
char resolved_path[kMaxPathLength + 1];
|
||||
|
||||
ssize_t link_size = readlink(exe_path.c_str(), resolved_path, kMaxPathLength);
|
||||
if (link_size == -1) {
|
||||
PLOGE("Failed to read executable path for PID %d", pid);
|
||||
return "";
|
||||
}
|
||||
|
||||
resolved_path[link_size] = '\0';
|
||||
return std::string(resolved_path);
|
||||
}
|
||||
|
||||
void *find_module_return_addr(const std::vector<lsplt::MapInfo> &map_info, std::string_view module_suffix) {
|
||||
for (const auto &map : map_info) {
|
||||
if ((map.perms & PROT_EXEC) == 0 && map.path.ends_with(module_suffix)) {
|
||||
LOGV("Found return address region for '%.*s' at %p", static_cast<int>(module_suffix.length()), module_suffix.data(),
|
||||
reinterpret_cast<void *>(map.start));
|
||||
return reinterpret_cast<void *>(map.start);
|
||||
}
|
||||
}
|
||||
|
||||
LOGV("No return address region found for module suffix '%.*s'", static_cast<int>(module_suffix.length()), module_suffix.data());
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::string generateMagic(size_t length) {
|
||||
if (length == 0) {
|
||||
LOGW("Zero length requested for magic string");
|
||||
return "";
|
||||
}
|
||||
|
||||
std::mt19937 random_generator{std::random_device{}()};
|
||||
std::uniform_int_distribution<size_t> char_distribution(0, kRandomChars.length() - 1);
|
||||
|
||||
std::string magic_string;
|
||||
magic_string.reserve(length);
|
||||
|
||||
for (size_t i = 0; i < length; i++) {
|
||||
magic_string += kRandomChars[char_distribution(random_generator)];
|
||||
}
|
||||
|
||||
LOGV("Generated magic string of length %zu", length);
|
||||
return magic_string;
|
||||
}
|
||||
|
||||
int setfilecon(const char *file_path, const char *security_context) {
|
||||
if (!file_path || !security_context) {
|
||||
LOGE("Invalid parameters for setfilecon: path=%p, context=%p", file_path, security_context);
|
||||
return -1;
|
||||
}
|
||||
|
||||
size_t context_len = strlen(security_context) + 1;
|
||||
int result = syscall(__NR_setxattr, file_path, XATTR_NAME_SELINUX, security_context, context_len, 0);
|
||||
|
||||
if (result == 0) {
|
||||
LOGV("Successfully set SELinux context '%s' for file '%s'", security_context, file_path);
|
||||
} else {
|
||||
PLOGE("Failed to set SELinux context '%s' for file '%s'", security_context, file_path);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
bool set_sockcreate_con(const char *security_context) {
|
||||
if (!security_context) {
|
||||
LOGE("Null security context passed to set_sockcreate_con");
|
||||
return false;
|
||||
}
|
||||
|
||||
size_t context_size = strlen(security_context) + 1;
|
||||
|
||||
UniqueFd sockcreate_fd = open("/proc/thread-self/attr/sockcreate", O_WRONLY | O_CLOEXEC);
|
||||
if (sockcreate_fd != kInvalidFd && write(sockcreate_fd, security_context, context_size) == static_cast<ssize_t>(context_size)) {
|
||||
LOGV("Successfully set socket creation context via thread-self: '%s'", security_context);
|
||||
return true;
|
||||
}
|
||||
|
||||
LOGV("Thread-self sockcreate failed, trying process-specific fallback");
|
||||
char process_path[kMaxPathLength];
|
||||
snprintf(process_path, sizeof(process_path), "/proc/%d/attr/sockcreate", gettid());
|
||||
|
||||
sockcreate_fd = open(process_path, O_WRONLY | O_CLOEXEC);
|
||||
if (sockcreate_fd == kInvalidFd || write(sockcreate_fd, security_context, context_size) != static_cast<ssize_t>(context_size)) {
|
||||
PLOGE("Failed to set socket creation context via fallback path '%s'", process_path);
|
||||
return false;
|
||||
}
|
||||
|
||||
LOGV("Successfully set socket creation context via fallback: '%s'", security_context);
|
||||
return true;
|
||||
}
|
||||
@@ -0,0 +1,140 @@
|
||||
// Copyright 2025 Dakkshesh <beakthoven@gmail.com>
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#pragma once
|
||||
#include <sys/ptrace.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <map>
|
||||
#include <string>
|
||||
|
||||
#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(const std::vector<lsplt::MapInfo> &map_info, uintptr_t addr);
|
||||
void *find_module_base(const std::vector<lsplt::MapInfo> &map_info, std::string_view module_suffix);
|
||||
void *find_func_addr(const std::vector<lsplt::MapInfo> &local_map_info, const std::vector<lsplt::MapInfo> &remote_map_info,
|
||||
std::string_view module_name, std::string_view function_name);
|
||||
void align_stack(struct user_regs_struct ®s, uintptr_t preserve_bytes = 0);
|
||||
uintptr_t push_memory(int pid, struct user_regs_struct ®s, const void *data, size_t length);
|
||||
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);
|
||||
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 expected_return_addr);
|
||||
|
||||
int fork_dont_care();
|
||||
bool wait_for_trace(int pid, int *status, int flags);
|
||||
std::string parse_status(int status);
|
||||
std::string get_program(int pid);
|
||||
void *find_module_return_addr(const std::vector<lsplt::MapInfo> &map_info, std::string_view module_suffix);
|
||||
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);
|
||||
|
||||
constexpr size_t kMainMagicLength = 16;
|
||||
std::string generateMagic(size_t length);
|
||||
int setfilecon(const char *file_path, const char *security_context);
|
||||
|
||||
class UniqueFd {
|
||||
using Fd = int;
|
||||
|
||||
public:
|
||||
UniqueFd() = default;
|
||||
UniqueFd(Fd fd) : fd_(fd) {}
|
||||
~UniqueFd() {
|
||||
if (fd_ >= 0)
|
||||
close(fd_);
|
||||
}
|
||||
UniqueFd(const UniqueFd &) = delete;
|
||||
UniqueFd &operator=(const UniqueFd &) = delete;
|
||||
UniqueFd(UniqueFd &&other) {
|
||||
std::swap(fd_, other.fd_);
|
||||
}
|
||||
UniqueFd &operator=(UniqueFd &&other) {
|
||||
std::swap(fd_, other.fd_);
|
||||
return *this;
|
||||
}
|
||||
operator const Fd &() const {
|
||||
return fd_;
|
||||
}
|
||||
|
||||
private:
|
||||
Fd fd_ = -1;
|
||||
};
|
||||
|
||||
bool set_sockcreate_con(const char *security_context);
|
||||
|
||||
#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)";
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
// Copyright 2025 Dakkshesh <beakthoven@gmail.com>
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <android/log.h>
|
||||
#include <cerrno>
|
||||
#include <cstring>
|
||||
#include <string>
|
||||
|
||||
#ifndef LOG_TAG
|
||||
# define LOG_TAG "TrickyStore"
|
||||
#endif
|
||||
|
||||
#ifndef NDEBUG
|
||||
# define LOGD(...) logging::log(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)
|
||||
# define LOGV(...) logging::log(ANDROID_LOG_VERBOSE, LOG_TAG, __VA_ARGS__)
|
||||
#else
|
||||
# define LOGD(...) (void)0
|
||||
# define LOGV(...) (void)0
|
||||
#endif
|
||||
#define LOGI(...) logging::log(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)
|
||||
#define LOGW(...) logging::log(ANDROID_LOG_WARN, LOG_TAG, __VA_ARGS__)
|
||||
#define LOGE(...) logging::log(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)
|
||||
#define LOGF(...) logging::log(ANDROID_LOG_FATAL, LOG_TAG, __VA_ARGS__)
|
||||
#define PLOGE(fmt, args...) LOGE(fmt " failed with %d: %s", ##args, errno, strerror(errno))
|
||||
|
||||
namespace logging {
|
||||
void setPrintEnabled(bool print);
|
||||
|
||||
[[gnu::format(printf, 3, 4)]]
|
||||
void log(int prio, const char *tag, const char *fmt, ...);
|
||||
} // namespace logging
|
||||
@@ -0,0 +1,36 @@
|
||||
// Copyright 2025 Dakkshesh <beakthoven@gmail.com>
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#include <android/log.h>
|
||||
#include <cstdio>
|
||||
#include <string>
|
||||
#include <unistd.h>
|
||||
|
||||
#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);
|
||||
}
|
||||
}
|
||||
} // namespace logging
|
||||
@@ -0,0 +1,120 @@
|
||||
// Copyright 2025 Dakkshesh <beakthoven@gmail.com>
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#include "binder/Binder.h"
|
||||
#include "binder/BpBinder.h"
|
||||
#include "binder/IPCThreadState.h"
|
||||
#include "binder/IServiceManager.h"
|
||||
#include "binder/Status.h"
|
||||
|
||||
namespace android {
|
||||
|
||||
IBinder::IBinder() {}
|
||||
IBinder::~IBinder() {}
|
||||
sp<IInterface> IBinder::queryLocalInterface(const String16&) { return nullptr; }
|
||||
BBinder* IBinder::localBinder() { return nullptr; }
|
||||
BpBinder* IBinder::remoteBinder() { return nullptr; }
|
||||
bool IBinder::checkSubclass(const void*) const { return false; }
|
||||
void IBinder::withLock(const std::function<void()>&) {}
|
||||
|
||||
#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() {}
|
||||
BBinder::~BBinder() {}
|
||||
|
||||
const String16& BBinder::getInterfaceDescriptor() const { __builtin_unreachable(); }
|
||||
bool BBinder::isBinderAlive() const { return false; }
|
||||
status_t BBinder::pingBinder() { return 0; }
|
||||
status_t BBinder::dump(int, const Vector<String16>&) { return 0; }
|
||||
status_t BBinder::transact(uint32_t, const Parcel&, Parcel*, uint32_t) { return 0; }
|
||||
status_t BBinder::linkToDeath(const sp<DeathRecipient>&, void*, uint32_t) { return 0; }
|
||||
status_t BBinder::unlinkToDeath(const wp<DeathRecipient>&, void*, uint32_t, wp<DeathRecipient>*) { return 0; }
|
||||
void* BBinder::attachObject(const void*, void*, void*, object_cleanup_func) { return nullptr; }
|
||||
void* BBinder::findObject(const void*) const { return nullptr; }
|
||||
void* BBinder::detachObject(const void*) { return nullptr; }
|
||||
void BBinder::withLock(const std::function<void()>&) {}
|
||||
BBinder* BBinder::localBinder() { return nullptr; }
|
||||
status_t BBinder::onTransact(uint32_t, const Parcel&, Parcel*, uint32_t) { return 0; }
|
||||
|
||||
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; }
|
||||
|
||||
#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) { return 0; }
|
||||
void Parcel::setDataPosition(size_t) const {}
|
||||
status_t Parcel::setDataCapacity(size_t) { return 0; }
|
||||
status_t Parcel::setData(const uint8_t*, size_t) { return 0; }
|
||||
status_t Parcel::appendFrom(const Parcel*, size_t, size_t) { return 0; }
|
||||
binder::Status Parcel::enforceNoDataAvail() const { return {}; }
|
||||
void Parcel::setEnforceNoDataAvail(bool) {}
|
||||
void Parcel::freeData() {}
|
||||
status_t Parcel::write(const void*, size_t) { return 0; }
|
||||
void* Parcel::writeInplace(size_t) { return nullptr; }
|
||||
status_t Parcel::writeInt32(int32_t) { return 0; }
|
||||
status_t Parcel::writeUint32(uint32_t) { return 0; }
|
||||
status_t Parcel::writeInt64(int64_t) { return 0; }
|
||||
status_t Parcel::writeUint64(uint64_t) { return 0; }
|
||||
status_t Parcel::writeFloat(float) { return 0; }
|
||||
status_t Parcel::writeDouble(double) { return 0; }
|
||||
status_t Parcel::writeCString(const char*) { return 0; }
|
||||
status_t Parcel::writeString8(const char*, size_t) { return 0; }
|
||||
status_t Parcel::writeStrongBinder(const sp<IBinder>&) { return 0; }
|
||||
status_t Parcel::writeBool(bool) { return 0; }
|
||||
status_t Parcel::writeChar(char16_t) { return 0; }
|
||||
status_t Parcel::writeByte(int8_t) { return 0; }
|
||||
status_t Parcel::writeNoException() { return 0; }
|
||||
status_t Parcel::read(void*, size_t) const { return 0; }
|
||||
const void* Parcel::readInplace(size_t) const { return nullptr; }
|
||||
int32_t Parcel::readInt32() const { return 0; }
|
||||
status_t Parcel::readInt32(int32_t*) const { return 0; }
|
||||
uint32_t Parcel::readUint32() const { return 0; }
|
||||
status_t Parcel::readUint32(uint32_t*) const { return 0; }
|
||||
int64_t Parcel::readInt64() const { return 0; }
|
||||
status_t Parcel::readInt64(int64_t*) const { return 0; }
|
||||
uint64_t Parcel::readUint64() const { return 0; }
|
||||
status_t Parcel::readUint64(uint64_t*) const { return 0; }
|
||||
float Parcel::readFloat() const { return 0; }
|
||||
status_t Parcel::readFloat(float*) const { return 0; }
|
||||
double Parcel::readDouble() const { return 0; }
|
||||
status_t Parcel::readDouble(double*) const { return 0; }
|
||||
bool Parcel::readBool() const { return 0; }
|
||||
status_t Parcel::readBool(bool*) const { return 0; }
|
||||
char16_t Parcel::readChar() const { return 0; }
|
||||
status_t Parcel::readChar(char16_t*) const { return 0; }
|
||||
int8_t Parcel::readByte() const { return 0; }
|
||||
status_t Parcel::readByte(int8_t*) const { return 0; }
|
||||
sp<IBinder> Parcel::readStrongBinder() const { return nullptr; }
|
||||
status_t Parcel::readStrongBinder(sp<IBinder>*) const { return 0; }
|
||||
status_t Parcel::readNullableStrongBinder(sp<IBinder>*) const { return 0; }
|
||||
int32_t Parcel::readExceptionCode() const { return 0; }
|
||||
int Parcel::readFileDescriptor() const { return 0; }
|
||||
|
||||
IServiceManager::IServiceManager() {}
|
||||
IServiceManager::~IServiceManager() {}
|
||||
const String16& IServiceManager::getInterfaceDescriptor() const { __builtin_unreachable(); }
|
||||
sp<IServiceManager> defaultServiceManager() { return nullptr; }
|
||||
void setDefaultServiceManager(const sp<IServiceManager>&) {}
|
||||
|
||||
} // namespace android
|
||||
@@ -0,0 +1,62 @@
|
||||
// Copyright 2025 Dakkshesh <beakthoven@gmail.com>
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#include "utils/StrongPointer.h"
|
||||
#include "utils/RefBase.h"
|
||||
#include "utils/String16.h"
|
||||
|
||||
namespace android {
|
||||
void RefBase::incStrong(const void *id) const {
|
||||
|
||||
}
|
||||
|
||||
void RefBase::incStrongRequireStrong(const void *id) const {
|
||||
|
||||
}
|
||||
|
||||
void RefBase::decStrong(const void *id) const {
|
||||
|
||||
}
|
||||
|
||||
void RefBase::forceIncStrong(const void *id) const {
|
||||
|
||||
}
|
||||
|
||||
RefBase::weakref_type* RefBase::createWeak(const void* id) const {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
RefBase::weakref_type* RefBase::getWeakRefs() const {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
RefBase::RefBase(): mRefs(nullptr) {}
|
||||
RefBase::~RefBase() {}
|
||||
|
||||
void RefBase::onFirstRef() {}
|
||||
void RefBase::onLastStrongRef(const void* id) {}
|
||||
bool RefBase::onIncStrongAttempted(uint32_t flags, const void* id) { return false; }
|
||||
void RefBase::onLastWeakRef(const void* id) {}
|
||||
|
||||
RefBase* RefBase::weakref_type::refBase() const { return nullptr; }
|
||||
|
||||
void RefBase::weakref_type::incWeak(const void* id) {}
|
||||
void RefBase::weakref_type::incWeakRequireWeak(const void* id) {}
|
||||
void RefBase::weakref_type::decWeak(const void* id) {}
|
||||
|
||||
bool RefBase::weakref_type::attemptIncStrong(const void* id) { return false; }
|
||||
|
||||
bool RefBase::weakref_type::attemptIncWeak(const void* id) { return false; }
|
||||
|
||||
void sp_report_race() {}
|
||||
|
||||
String16::String16() {}
|
||||
|
||||
String16::String16(const String16 &o) {}
|
||||
|
||||
String16::String16(String16 &&o) noexcept {}
|
||||
|
||||
String16::String16(const char *o) {}
|
||||
|
||||
String16::~String16() {}
|
||||
}
|
||||
@@ -0,0 +1,188 @@
|
||||
/*
|
||||
* Copyright 2025 Dakkshesh <beakthoven@gmail.com>
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
package io.github.beakthoven.TrickyStoreOSS
|
||||
|
||||
import android.content.pm.IPackageManager
|
||||
import android.content.pm.PackageManager
|
||||
import android.os.Build
|
||||
import android.os.ServiceManager
|
||||
import android.os.SystemProperties
|
||||
import io.github.beakthoven.TrickyStoreOSS.core.config.Config
|
||||
import io.github.beakthoven.TrickyStoreOSS.core.config.CustomPatchLevel
|
||||
import io.github.beakthoven.TrickyStoreOSS.core.logging.Logger
|
||||
import org.bouncycastle.asn1.ASN1Integer
|
||||
import org.bouncycastle.asn1.DEROctetString
|
||||
import org.bouncycastle.asn1.DERSequence
|
||||
import java.security.MessageDigest
|
||||
import java.util.concurrent.ThreadLocalRandom
|
||||
|
||||
fun getTransactCode(clazz: Class<*>, method: String): Int =
|
||||
clazz.getDeclaredField("TRANSACTION_$method").apply { isAccessible = true }
|
||||
.getInt(null)
|
||||
|
||||
val bootHash: ByteArray by lazy {
|
||||
getBootHashFromProp() ?: randomBytes()
|
||||
}
|
||||
|
||||
val bootKey: ByteArray by lazy {
|
||||
randomBytes()
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalStdlibApi::class)
|
||||
private fun getBootHashFromProp(): ByteArray? {
|
||||
val digest = SystemProperties.get("ro.boot.vbmeta.digest", null) ?: return null
|
||||
return if (digest.length == 64) digest.hexToByteArray() else null
|
||||
}
|
||||
|
||||
private fun randomBytes(): ByteArray = ByteArray(32).also {
|
||||
ThreadLocalRandom.current().nextBytes(it)
|
||||
}
|
||||
|
||||
val patchLevel: Int
|
||||
get() = getCustomPatchLevel("system", false)
|
||||
?: Build.VERSION.SECURITY_PATCH.convertPatchLevel(false)
|
||||
|
||||
val patchLevelLong: Int
|
||||
get() = getCustomPatchLevel("system", true)
|
||||
?: Build.VERSION.SECURITY_PATCH.convertPatchLevel(true)
|
||||
|
||||
val vendorPatchLevel: Int
|
||||
get() = getCustomPatchLevel("vendor", false)
|
||||
?: Build.VERSION.SECURITY_PATCH.convertPatchLevel(false)
|
||||
|
||||
val vendorPatchLevelLong: Int
|
||||
get() = getCustomPatchLevel("vendor", true)
|
||||
?: Build.VERSION.SECURITY_PATCH.convertPatchLevel(true)
|
||||
|
||||
val bootPatchLevel: Int
|
||||
get() = getCustomPatchLevel("boot", false)
|
||||
?: Build.VERSION.SECURITY_PATCH.convertPatchLevel(false)
|
||||
|
||||
val bootPatchLevelLong: Int
|
||||
get() = getCustomPatchLevel("boot", true)
|
||||
?: Build.VERSION.SECURITY_PATCH.convertPatchLevel(true)
|
||||
|
||||
private val customPatchLevel: CustomPatchLevel?
|
||||
get() = Config._customPatchLevel
|
||||
|
||||
private fun getCustomPatchLevel(component: String, isLong: Boolean): Int? {
|
||||
val config = customPatchLevel ?: return null
|
||||
val value = when (component) {
|
||||
"system" -> config.system ?: config.all
|
||||
"vendor" -> config.vendor ?: config.all
|
||||
"boot" -> config.boot ?: config.all
|
||||
else -> config.all
|
||||
} ?: return null
|
||||
|
||||
when {
|
||||
value.equals("no", ignoreCase = true) -> return null
|
||||
value.equals("prop", ignoreCase = true) -> return null
|
||||
}
|
||||
|
||||
return parsePatchLevelValue(value, component, isLong)
|
||||
}
|
||||
|
||||
private fun parsePatchLevelValue(value: String, component: String, isLong: Boolean): Int? {
|
||||
val normalized = value.replace("-", "")
|
||||
|
||||
return try {
|
||||
when (normalized.length) {
|
||||
8 -> {
|
||||
val year = normalized.substring(0, 4).toInt()
|
||||
val month = normalized.substring(4, 6).toInt()
|
||||
val day = normalized.substring(6, 8).toInt()
|
||||
if (isLong) year * 10000 + month * 100 + day
|
||||
else year * 100 + month
|
||||
}
|
||||
6 -> {
|
||||
val year = normalized.substring(0, 4).toInt()
|
||||
val month = normalized.substring(4, 6).toInt()
|
||||
if (isLong) year * 10000 + month * 100
|
||||
else year * 100 + month
|
||||
}
|
||||
else -> {
|
||||
Logger.e("Invalid patch level length for $component: $normalized")
|
||||
null
|
||||
}
|
||||
}
|
||||
} catch (e: NumberFormatException) {
|
||||
Logger.e("Patch level parse error for $component=$value", e)
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
val osVersion: Int
|
||||
get() = getOsVersion(Build.VERSION.SDK_INT)
|
||||
|
||||
private val osVersionMap = mapOf(
|
||||
Build.VERSION_CODES.BAKLAVA to 160000,
|
||||
Build.VERSION_CODES.VANILLA_ICE_CREAM to 150000,
|
||||
Build.VERSION_CODES.UPSIDE_DOWN_CAKE to 140000,
|
||||
Build.VERSION_CODES.TIRAMISU to 130000,
|
||||
Build.VERSION_CODES.S_V2 to 120100,
|
||||
Build.VERSION_CODES.S to 120000,
|
||||
Build.VERSION_CODES.R to 110000,
|
||||
Build.VERSION_CODES.Q to 100000
|
||||
)
|
||||
|
||||
private fun getOsVersion(sdkVersion: Int): Int = osVersionMap[sdkVersion] ?: 160000
|
||||
|
||||
fun String.convertPatchLevel(isLong: Boolean): Int = runCatching {
|
||||
val parts = split("-")
|
||||
when {
|
||||
isLong && parts.size >= 3 -> parts[0].toInt() * 10000 + parts[1].toInt() * 100 + parts[2].toInt()
|
||||
parts.size >= 2 -> parts[0].toInt() * 100 + parts[1].toInt()
|
||||
else -> throw IllegalArgumentException("Invalid patch level format: $this")
|
||||
}
|
||||
}.onFailure {
|
||||
Logger.e("Invalid patch level format: $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 {
|
||||
@Suppress("DEPRECATION")
|
||||
getPackageInfo(name, flags.toInt(), userId)
|
||||
}
|
||||
|
||||
val apexInfos: List<Pair<String, Long>> by lazy {
|
||||
runCatching {
|
||||
val packageManager = IPackageManager.Stub.asInterface(ServiceManager.getService("package"))
|
||||
val packages = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
packageManager.getInstalledPackages(PackageManager.MATCH_APEX.toLong(), 0)
|
||||
} else {
|
||||
@Suppress("DEPRECATION")
|
||||
packageManager.getInstalledPackages(PackageManager.MATCH_APEX, 0)
|
||||
}
|
||||
|
||||
packages.list
|
||||
.map { it.packageName to it.longVersionCode }
|
||||
.sortedBy { it.first }
|
||||
}.getOrElse {
|
||||
Logger.e("Failed to get APEX package information")
|
||||
emptyList()
|
||||
}
|
||||
}
|
||||
|
||||
val moduleHash: ByteArray by lazy {
|
||||
runCatching {
|
||||
val encodables = apexInfos.flatMap { (packageName, versionCode) ->
|
||||
listOf(
|
||||
DEROctetString(packageName.toByteArray()),
|
||||
ASN1Integer(versionCode)
|
||||
)
|
||||
}
|
||||
|
||||
val sequence = DERSequence(encodables.toTypedArray())
|
||||
MessageDigest.getInstance("SHA-256").digest(sequence.encoded)
|
||||
}.getOrElse {
|
||||
Logger.e("Failed to compute module hash", it)
|
||||
ByteArray(32)
|
||||
}
|
||||
}
|
||||
|
||||
fun String.trimLine(): String = trim().split("\n").joinToString("\n") { it.trim() }
|
||||
@@ -0,0 +1,868 @@
|
||||
/*
|
||||
* Copyright 2025 Dakkshesh <beakthoven@gmail.com>
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
package io.github.beakthoven.TrickyStoreOSS
|
||||
|
||||
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 io.github.beakthoven.TrickyStoreOSS.*
|
||||
import io.github.beakthoven.TrickyStoreOSS.core.config.Config
|
||||
import io.github.beakthoven.TrickyStoreOSS.core.logging.Logger
|
||||
import io.github.beakthoven.TrickyStoreOSS.interceptors.SecurityLevelInterceptor
|
||||
import org.bouncycastle.asn1.*
|
||||
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.jcajce.JcaContentSignerBuilder
|
||||
import org.bouncycastle.util.io.pem.PemReader
|
||||
import java.io.ByteArrayInputStream
|
||||
import java.io.StringReader
|
||||
import java.math.BigInteger
|
||||
import java.nio.charset.StandardCharsets
|
||||
import java.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.*
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
import javax.security.auth.x500.X500Principal
|
||||
|
||||
object CertificateHacker {
|
||||
|
||||
private val ATTESTATION_OID = ASN1ObjectIdentifier("1.3.6.1.4.1.11129.2.1.17")
|
||||
|
||||
private val certificateFactory: CertificateFactory by lazy {
|
||||
try {
|
||||
CertificateFactory.getInstance("X.509")
|
||||
} catch (t: Throwable) {
|
||||
Logger.e("Failed to initialize certificate factory", t)
|
||||
throw RuntimeException("Cannot initialize certificate factory", t)
|
||||
}
|
||||
}
|
||||
|
||||
data class KeyBox(
|
||||
val pemKeyPair: PEMKeyPair,
|
||||
val keyPair: KeyPair,
|
||||
val certificates: List<Certificate>
|
||||
)
|
||||
|
||||
data class KeyIdentifier(
|
||||
val alias: String,
|
||||
val uid: Int
|
||||
)
|
||||
|
||||
sealed class ParseResult<out T> {
|
||||
data class Success<T>(val data: T) : ParseResult<T>()
|
||||
data class Error(val message: String, val cause: Throwable? = null) : ParseResult<Nothing>()
|
||||
}
|
||||
|
||||
sealed class HackResult<out T> {
|
||||
data class Success<T>(val data: T) : HackResult<T>()
|
||||
data class Error(val message: String, val cause: Throwable? = null) : HackResult<Nothing>()
|
||||
}
|
||||
|
||||
data class KeyGenParameters(
|
||||
var keySize: Int = 0,
|
||||
var algorithm: Int = 0,
|
||||
var certificateSerial: BigInteger? = null,
|
||||
var certificateNotBefore: Date? = null,
|
||||
var certificateNotAfter: Date? = null,
|
||||
var certificateSubject: X500Name? = null,
|
||||
var rsaPublicExponent: BigInteger? = null,
|
||||
var ecCurve: Int = 0,
|
||||
var ecCurveName: String? = null,
|
||||
var purpose: MutableList<Int> = mutableListOf(),
|
||||
var digest: MutableList<Int> = mutableListOf(),
|
||||
var attestationChallenge: ByteArray? = null,
|
||||
var brand: ByteArray? = null,
|
||||
var device: ByteArray? = null,
|
||||
var product: ByteArray? = null,
|
||||
var manufacturer: ByteArray? = null,
|
||||
var model: ByteArray? = null,
|
||||
var imei1: ByteArray? = null,
|
||||
var imei2: ByteArray? = null,
|
||||
var meid: ByteArray? = null,
|
||||
var serialno: ByteArray? = null
|
||||
) {
|
||||
|
||||
constructor(params: Array<KeyParameter>) : this() {
|
||||
parseKeyParameters(params)
|
||||
}
|
||||
|
||||
private fun parseKeyParameters(params: Array<KeyParameter>) {
|
||||
params.forEach { param ->
|
||||
Logger.d("Processing key parameter: ${param.tag}")
|
||||
val value = param.value
|
||||
|
||||
when (param.tag) {
|
||||
Tag.KEY_SIZE -> keySize = value.integer
|
||||
Tag.ALGORITHM -> algorithm = value.algorithm
|
||||
Tag.CERTIFICATE_SERIAL -> certificateSerial = BigInteger(value.blob)
|
||||
Tag.CERTIFICATE_NOT_BEFORE -> certificateNotBefore = Date(value.dateTime)
|
||||
Tag.CERTIFICATE_NOT_AFTER -> certificateNotAfter = Date(value.dateTime)
|
||||
Tag.CERTIFICATE_SUBJECT -> certificateSubject = X500Name(X500Principal(value.blob).name)
|
||||
Tag.RSA_PUBLIC_EXPONENT -> rsaPublicExponent = BigInteger(value.blob)
|
||||
Tag.EC_CURVE -> {
|
||||
ecCurve = value.ecCurve
|
||||
ecCurveName = getEcCurveName(ecCurve)
|
||||
}
|
||||
Tag.PURPOSE -> purpose.add(value.keyPurpose)
|
||||
Tag.DIGEST -> digest.add(value.digest)
|
||||
Tag.ATTESTATION_CHALLENGE -> attestationChallenge = value.blob
|
||||
Tag.ATTESTATION_ID_BRAND -> brand = value.blob
|
||||
Tag.ATTESTATION_ID_DEVICE -> device = value.blob
|
||||
Tag.ATTESTATION_ID_PRODUCT -> product = value.blob
|
||||
Tag.ATTESTATION_ID_MANUFACTURER -> manufacturer = value.blob
|
||||
Tag.ATTESTATION_ID_MODEL -> model = value.blob
|
||||
Tag.ATTESTATION_ID_IMEI -> imei1 = value.blob
|
||||
Tag.ATTESTATION_ID_SECOND_IMEI -> imei2 = value.blob
|
||||
Tag.ATTESTATION_ID_MEID -> meid = value.blob
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun setEcCurveName(curveSize: Int) {
|
||||
ecCurveName = when (curveSize) {
|
||||
224 -> "secp224r1"
|
||||
256 -> "secp256r1"
|
||||
384 -> "secp384r1"
|
||||
521 -> "secp521r1"
|
||||
else -> "secp256r1"
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private fun getEcCurveName(curve: Int): String = when (curve) {
|
||||
EcCurve.CURVE_25519 -> "CURVE_25519"
|
||||
EcCurve.P_224 -> "secp224r1"
|
||||
EcCurve.P_256 -> "secp256r1"
|
||||
EcCurve.P_384 -> "secp384r1"
|
||||
EcCurve.P_521 -> "secp521r1"
|
||||
else -> throw IllegalArgumentException("Unknown EC curve: $curve")
|
||||
}
|
||||
}
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
if (javaClass != other?.javaClass) return false
|
||||
|
||||
other as KeyGenParameters
|
||||
|
||||
return keySize == other.keySize &&
|
||||
algorithm == other.algorithm &&
|
||||
certificateSerial == other.certificateSerial &&
|
||||
certificateNotBefore == other.certificateNotBefore &&
|
||||
certificateNotAfter == other.certificateNotAfter &&
|
||||
certificateSubject == other.certificateSubject &&
|
||||
rsaPublicExponent == other.rsaPublicExponent &&
|
||||
ecCurve == other.ecCurve &&
|
||||
ecCurveName == other.ecCurveName &&
|
||||
purpose == other.purpose &&
|
||||
digest == other.digest &&
|
||||
attestationChallenge.contentEquals(other.attestationChallenge) &&
|
||||
brand.contentEquals(other.brand) &&
|
||||
device.contentEquals(other.device) &&
|
||||
product.contentEquals(other.product) &&
|
||||
manufacturer.contentEquals(other.manufacturer) &&
|
||||
model.contentEquals(other.model) &&
|
||||
imei1.contentEquals(other.imei1) &&
|
||||
imei2.contentEquals(other.imei2) &&
|
||||
meid.contentEquals(other.meid) &&
|
||||
serialno.contentEquals(other.serialno)
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
var result = keySize
|
||||
result = 31 * result + algorithm
|
||||
result = 31 * result + (certificateSerial?.hashCode() ?: 0)
|
||||
result = 31 * result + (certificateNotBefore?.hashCode() ?: 0)
|
||||
result = 31 * result + (certificateNotAfter?.hashCode() ?: 0)
|
||||
result = 31 * result + (certificateSubject?.hashCode() ?: 0)
|
||||
result = 31 * result + (rsaPublicExponent?.hashCode() ?: 0)
|
||||
result = 31 * result + ecCurve
|
||||
result = 31 * result + (ecCurveName?.hashCode() ?: 0)
|
||||
result = 31 * result + purpose.hashCode()
|
||||
result = 31 * result + digest.hashCode()
|
||||
result = 31 * result + (attestationChallenge?.contentHashCode() ?: 0)
|
||||
result = 31 * result + (brand?.contentHashCode() ?: 0)
|
||||
result = 31 * result + (device?.contentHashCode() ?: 0)
|
||||
result = 31 * result + (product?.contentHashCode() ?: 0)
|
||||
result = 31 * result + (manufacturer?.contentHashCode() ?: 0)
|
||||
result = 31 * result + (model?.contentHashCode() ?: 0)
|
||||
result = 31 * result + (imei1?.contentHashCode() ?: 0)
|
||||
result = 31 * result + (imei2?.contentHashCode() ?: 0)
|
||||
result = 31 * result + (meid?.contentHashCode() ?: 0)
|
||||
result = 31 * result + (serialno?.contentHashCode() ?: 0)
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
private val keyboxes = ConcurrentHashMap<String, KeyBox>()
|
||||
private val leafAlgorithm = ConcurrentHashMap<KeyIdentifier, String>()
|
||||
|
||||
private const val ATTESTATION_APPLICATION_ID_PACKAGE_INFOS_INDEX = 0
|
||||
private const val ATTESTATION_APPLICATION_ID_SIGNATURE_DIGESTS_INDEX = 1
|
||||
private const val ATTESTATION_PACKAGE_INFO_PACKAGE_NAME_INDEX = 0
|
||||
private const val ATTESTATION_PACKAGE_INFO_VERSION_INDEX = 1
|
||||
|
||||
fun canHack(): Boolean = keyboxes.isNotEmpty()
|
||||
|
||||
private data class Digest(val digest: ByteArray) {
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
if (javaClass != other?.javaClass) return false
|
||||
other as Digest
|
||||
return digest.contentEquals(other.digest)
|
||||
}
|
||||
|
||||
override fun hashCode(): Int = digest.contentHashCode()
|
||||
}
|
||||
|
||||
private fun parseKeyPair(keyContent: String): ParseResult<PEMKeyPair> {
|
||||
return try {
|
||||
PEMParser(StringReader(keyContent.trimLine())).use { parser ->
|
||||
val pemObject = parser.readObject()
|
||||
if (pemObject is PEMKeyPair) {
|
||||
ParseResult.Success(pemObject)
|
||||
} else {
|
||||
ParseResult.Error("Invalid PEM key pair format")
|
||||
}
|
||||
}
|
||||
} catch (t: Throwable) {
|
||||
ParseResult.Error("Failed to parse PEM key pair", t)
|
||||
}
|
||||
}
|
||||
|
||||
private fun parseCertificate(certContent: String): ParseResult<Certificate> {
|
||||
return try {
|
||||
PemReader(StringReader(certContent.trimLine())).use { reader ->
|
||||
val pemObject = reader.readPemObject()
|
||||
val certificate = certificateFactory.generateCertificate(
|
||||
ByteArrayInputStream(pemObject.content)
|
||||
)
|
||||
ParseResult.Success(certificate)
|
||||
}
|
||||
} catch (t: Throwable) {
|
||||
ParseResult.Error("Failed to parse certificate", t)
|
||||
}
|
||||
}
|
||||
|
||||
@Throws(CertificateParsingException::class)
|
||||
private fun getByteArrayFromAsn1(asn1Encodable: ASN1Encodable): ByteArray {
|
||||
return when (asn1Encodable) {
|
||||
is DEROctetString -> asn1Encodable.octets
|
||||
else -> throw CertificateParsingException("Expected DEROctetString, got ${asn1Encodable::class.simpleName}")
|
||||
}
|
||||
}
|
||||
|
||||
fun readFromXml(xmlData: String?) {
|
||||
keyboxes.clear()
|
||||
leafAlgorithm.clear()
|
||||
|
||||
if (xmlData == null) {
|
||||
Logger.i("Clearing all keyboxes")
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
val xmlParser = XmlParser(xmlData)
|
||||
|
||||
val numberOfKeyboxesResult = xmlParser.obtainPath("AndroidAttestation.NumberOfKeyboxes")
|
||||
val numberOfKeyboxes = when (numberOfKeyboxesResult) {
|
||||
is XmlParser.ParseResult.Success -> numberOfKeyboxesResult.attributes["text"]?.toIntOrNull()
|
||||
?: throw IllegalArgumentException("Invalid number of keyboxes")
|
||||
is XmlParser.ParseResult.Error -> throw Exception(numberOfKeyboxesResult.message, numberOfKeyboxesResult.cause)
|
||||
}
|
||||
|
||||
repeat(numberOfKeyboxes) { i ->
|
||||
processKeybox(xmlParser, i)
|
||||
}
|
||||
|
||||
Logger.i("Successfully updated $numberOfKeyboxes keyboxes")
|
||||
} catch (t: Throwable) {
|
||||
Logger.e("Error loading XML file (keyboxes cleared)", t)
|
||||
}
|
||||
}
|
||||
|
||||
private fun processKeybox(xmlParser: XmlParser, index: Int) {
|
||||
try {
|
||||
val algorithmResult = xmlParser.obtainPath("AndroidAttestation.Keybox.Key[$index]")
|
||||
val keyboxAlgorithm = when (algorithmResult) {
|
||||
is XmlParser.ParseResult.Success -> algorithmResult.attributes["algorithm"]
|
||||
?: throw IllegalArgumentException("Missing algorithm attribute")
|
||||
is XmlParser.ParseResult.Error -> throw Exception(algorithmResult.message, algorithmResult.cause)
|
||||
}
|
||||
|
||||
val privateKeyResult = xmlParser.obtainPath("AndroidAttestation.Keybox.Key[$index].PrivateKey")
|
||||
val privateKeyContent = when (privateKeyResult) {
|
||||
is XmlParser.ParseResult.Success -> privateKeyResult.attributes["text"]
|
||||
?: throw IllegalArgumentException("Missing private key text")
|
||||
is XmlParser.ParseResult.Error -> throw Exception(privateKeyResult.message, privateKeyResult.cause)
|
||||
}
|
||||
|
||||
val numberOfCertificatesResult = xmlParser.obtainPath(
|
||||
"AndroidAttestation.Keybox.Key[$index].CertificateChain.NumberOfCertificates"
|
||||
)
|
||||
val numberOfCertificates = when (numberOfCertificatesResult) {
|
||||
is XmlParser.ParseResult.Success -> numberOfCertificatesResult.attributes["text"]?.toIntOrNull()
|
||||
?: throw IllegalArgumentException("Invalid number of certificates")
|
||||
is XmlParser.ParseResult.Error -> throw Exception(numberOfCertificatesResult.message, numberOfCertificatesResult.cause)
|
||||
}
|
||||
|
||||
val certificateChain = mutableListOf<Certificate>()
|
||||
repeat(numberOfCertificates) { j ->
|
||||
val certResult = xmlParser.obtainPath(
|
||||
"AndroidAttestation.Keybox.Key[$index].CertificateChain.Certificate[$j]"
|
||||
)
|
||||
val certContent = when (certResult) {
|
||||
is XmlParser.ParseResult.Success -> certResult.attributes["text"]
|
||||
?: throw IllegalArgumentException("Missing certificate text")
|
||||
is XmlParser.ParseResult.Error -> throw Exception(certResult.message, certResult.cause)
|
||||
}
|
||||
|
||||
when (val certParseResult = parseCertificate(certContent)) {
|
||||
is ParseResult.Success -> certificateChain.add(certParseResult.data)
|
||||
is ParseResult.Error -> throw Exception(certParseResult.message, certParseResult.cause)
|
||||
}
|
||||
}
|
||||
|
||||
val pemKeyPair = when (val keyParseResult = parseKeyPair(privateKeyContent)) {
|
||||
is ParseResult.Success -> keyParseResult.data
|
||||
is ParseResult.Error -> throw Exception(keyParseResult.message, keyParseResult.cause)
|
||||
}
|
||||
|
||||
val keyPair = JcaPEMKeyConverter().getKeyPair(pemKeyPair)
|
||||
|
||||
val algorithmName = when (keyboxAlgorithm.lowercase()) {
|
||||
"ecdsa" -> KeyProperties.KEY_ALGORITHM_EC
|
||||
"rsa" -> KeyProperties.KEY_ALGORITHM_RSA
|
||||
else -> keyboxAlgorithm
|
||||
}
|
||||
|
||||
keyboxes[algorithmName] = KeyBox(pemKeyPair, keyPair, certificateChain)
|
||||
|
||||
} catch (t: Throwable) {
|
||||
Logger.e("Error processing keybox $index", t)
|
||||
throw t
|
||||
}
|
||||
}
|
||||
|
||||
fun hackCertificateChain(certificateChain: Array<Certificate>?): Array<Certificate> {
|
||||
if (certificateChain == null) {
|
||||
throw UnsupportedOperationException("Certificate chain is null!")
|
||||
}
|
||||
|
||||
return try {
|
||||
val leaf = certificateFactory.generateCertificate(
|
||||
ByteArrayInputStream(certificateChain[0].encoded)
|
||||
) as X509Certificate
|
||||
|
||||
val extensionBytes = leaf.getExtensionValue(ATTESTATION_OID.id)
|
||||
?: return certificateChain // No attestation extension, return original
|
||||
|
||||
hackCertificateWithAttestation(leaf, certificateChain)
|
||||
} catch (t: Throwable) {
|
||||
Logger.e("Failed to hack certificate chain", t)
|
||||
certificateChain
|
||||
}
|
||||
}
|
||||
|
||||
fun hackCertificateChainCA(caList: ByteArray?, alias: String, uid: Int): ByteArray {
|
||||
if (caList == null) {
|
||||
throw UnsupportedOperationException("CA list is null!")
|
||||
}
|
||||
|
||||
return try {
|
||||
val key = KeyIdentifier(alias, uid)
|
||||
val algorithm = leafAlgorithm.remove(key)
|
||||
?: throw UnsupportedOperationException("No algorithm found for key $key")
|
||||
|
||||
val keybox = keyboxes[algorithm]
|
||||
?: throw UnsupportedOperationException("Unsupported algorithm: $algorithm")
|
||||
|
||||
CertificateUtils.run { keybox.certificates.toByteArray() } ?: caList
|
||||
} catch (t: Throwable) {
|
||||
Logger.e("Failed to hack CA certificate chain", t)
|
||||
caList
|
||||
}
|
||||
}
|
||||
|
||||
fun hackCertificateChainUSR(certificate: ByteArray?, alias: String, uid: Int): ByteArray {
|
||||
if (certificate == null) {
|
||||
throw UnsupportedOperationException("Leaf certificate is null!")
|
||||
}
|
||||
|
||||
return try {
|
||||
val leaf = certificateFactory.generateCertificate(
|
||||
ByteArrayInputStream(certificate)
|
||||
) as X509Certificate
|
||||
|
||||
val extensionBytes = leaf.getExtensionValue(ATTESTATION_OID.id)
|
||||
?: return certificate // No attestation extension, return original
|
||||
|
||||
val keyIdentifier = KeyIdentifier(alias, uid)
|
||||
leafAlgorithm[keyIdentifier] = leaf.publicKey.algorithm
|
||||
|
||||
hackSingleCertificate(leaf)?.encoded ?: certificate
|
||||
} catch (t: Throwable) {
|
||||
Logger.e("Failed to hack user certificate", t)
|
||||
certificate
|
||||
}
|
||||
}
|
||||
|
||||
fun generateKeyPair(params: KeyGenParameters): KeyPair? {
|
||||
return try {
|
||||
when (params.algorithm) {
|
||||
Algorithm.EC -> {
|
||||
Logger.d("Generating EC keypair of size ${params.keySize}")
|
||||
buildECKeyPair(params)
|
||||
}
|
||||
Algorithm.RSA -> {
|
||||
Logger.d("Generating RSA keypair of size ${params.keySize}")
|
||||
buildRSAKeyPair(params)
|
||||
}
|
||||
else -> {
|
||||
Logger.e("Unsupported algorithm: ${params.algorithm}")
|
||||
null
|
||||
}
|
||||
}
|
||||
} catch (t: Throwable) {
|
||||
Logger.e("Failed to generate key pair", t)
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
fun generateChain(uid: Int, params: KeyGenParameters, keyPair: KeyPair): List<ByteArray>? {
|
||||
return try {
|
||||
val keybox = getKeyboxForAlgorithm(params.algorithm)
|
||||
?: return null
|
||||
|
||||
val issuer = X509CertificateHolder(keybox.certificates[0].encoded).subject
|
||||
val leaf = buildCertificate(keyPair, keybox, params, issuer, uid)
|
||||
|
||||
val chain = mutableListOf<Certificate>().apply {
|
||||
add(leaf)
|
||||
addAll(keybox.certificates)
|
||||
}
|
||||
|
||||
CertificateUtils.run { chain.toByteArrayList() }
|
||||
} catch (t: Throwable) {
|
||||
Logger.e("Failed to generate certificate chain", t)
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
fun generateKeyPair(
|
||||
uid: Int,
|
||||
descriptor: KeyDescriptor,
|
||||
attestKeyDescriptor: KeyDescriptor?,
|
||||
params: KeyGenParameters
|
||||
): Pair<KeyPair, List<Certificate>>? {
|
||||
Logger.i("Requested KeyPair with alias: ${descriptor.alias}")
|
||||
|
||||
val isAttestPurpose = attestKeyDescriptor != null
|
||||
if (isAttestPurpose) {
|
||||
Logger.i("Requested KeyPair with attestKey: ${attestKeyDescriptor?.alias}")
|
||||
}
|
||||
|
||||
return try {
|
||||
val keyPair = generateKeyPair(params) ?: return null
|
||||
val keybox = getKeyboxForAlgorithm(params.algorithm) ?: return null
|
||||
|
||||
val (rootKeyPair, issuer) = if (isAttestPurpose) {
|
||||
val attestInfo = getAttestationKeyInfo(uid, attestKeyDescriptor!!)
|
||||
if (attestInfo != null) {
|
||||
attestInfo.first to attestInfo.second
|
||||
} else {
|
||||
keybox.keyPair to X509CertificateHolder(keybox.certificates[0].encoded).subject
|
||||
}
|
||||
} else {
|
||||
keybox.keyPair to X509CertificateHolder(keybox.certificates[0].encoded).subject
|
||||
}
|
||||
|
||||
val leaf = buildCertificate(keyPair, keybox, params, issuer, uid, rootKeyPair)
|
||||
val chain = if (isAttestPurpose) mutableListOf() else mutableListOf<Certificate>().apply { addAll(keybox.certificates) }
|
||||
chain.add(0, leaf)
|
||||
|
||||
Logger.d("Successfully generated certificate for alias: ${descriptor.alias}")
|
||||
Pair(keyPair, chain)
|
||||
} catch (t: Throwable) {
|
||||
Logger.e("Failed to generate key pair with certificates", t)
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
private fun buildECKeyPair(params: KeyGenParameters): KeyPair {
|
||||
Security.removeProvider(BouncyCastleProvider.PROVIDER_NAME)
|
||||
Security.addProvider(BouncyCastleProvider())
|
||||
|
||||
val spec = ECGenParameterSpec(params.ecCurveName)
|
||||
val keyPairGenerator = KeyPairGenerator.getInstance("ECDSA", BouncyCastleProvider.PROVIDER_NAME)
|
||||
keyPairGenerator.initialize(spec)
|
||||
return keyPairGenerator.generateKeyPair()
|
||||
}
|
||||
|
||||
private fun buildRSAKeyPair(params: KeyGenParameters): KeyPair {
|
||||
Security.removeProvider(BouncyCastleProvider.PROVIDER_NAME)
|
||||
Security.addProvider(BouncyCastleProvider())
|
||||
|
||||
val spec = RSAKeyGenParameterSpec(params.keySize, params.rsaPublicExponent)
|
||||
val keyPairGenerator = KeyPairGenerator.getInstance("RSA", BouncyCastleProvider.PROVIDER_NAME)
|
||||
keyPairGenerator.initialize(spec)
|
||||
return keyPairGenerator.generateKeyPair()
|
||||
}
|
||||
|
||||
private fun getKeyboxForAlgorithm(algorithm: Int): KeyBox? {
|
||||
val algorithmName = when (algorithm) {
|
||||
Algorithm.EC -> KeyProperties.KEY_ALGORITHM_EC
|
||||
Algorithm.RSA -> KeyProperties.KEY_ALGORITHM_RSA
|
||||
else -> {
|
||||
Logger.e("Unsupported algorithm: $algorithm")
|
||||
return null
|
||||
}
|
||||
}
|
||||
return keyboxes[algorithmName]
|
||||
}
|
||||
|
||||
private fun getAttestationKeyInfo(uid: Int, attestKeyDescriptor: KeyDescriptor): Pair<KeyPair, X500Name>? {
|
||||
Logger.d("Looking for attestation key: uid=$uid alias=${attestKeyDescriptor.alias}")
|
||||
|
||||
val keyInfo = SecurityLevelInterceptor.getKeyPairs(uid, attestKeyDescriptor.alias)
|
||||
return if (keyInfo != null) {
|
||||
val issuer = X509CertificateHolder(keyInfo.second[0].encoded).subject
|
||||
Pair(keyInfo.first, issuer)
|
||||
} else {
|
||||
Logger.e("Attestation key info not found, falling back to default keybox")
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
private fun hackCertificateWithAttestation(leaf: X509Certificate, originalChain: Array<Certificate>): Array<Certificate> {
|
||||
val leafHolder = X509CertificateHolder(leaf.encoded)
|
||||
val extension = leafHolder.getExtension(ATTESTATION_OID)
|
||||
val sequence = ASN1Sequence.getInstance(extension.extnValue.octets)
|
||||
val encodables = sequence.toArray()
|
||||
val teeEnforced = encodables[7] as ASN1Sequence
|
||||
|
||||
val vector = ASN1EncodableVector()
|
||||
var rootOfTrust: ASN1Encodable? = null
|
||||
|
||||
teeEnforced.forEach { element ->
|
||||
val taggedObject = element as ASN1TaggedObject
|
||||
if (taggedObject.tagNo == 704) {
|
||||
rootOfTrust = taggedObject.baseObject.toASN1Primitive()
|
||||
} else {
|
||||
vector.add(taggedObject)
|
||||
}
|
||||
}
|
||||
|
||||
val keybox = keyboxes[leaf.publicKey.algorithm]
|
||||
?: throw UnsupportedOperationException("Unsupported algorithm: ${leaf.publicKey.algorithm}")
|
||||
|
||||
val certificates = LinkedList(keybox.certificates)
|
||||
val builder = X509v3CertificateBuilder(
|
||||
X509CertificateHolder(certificates[0].encoded).subject,
|
||||
leafHolder.serialNumber,
|
||||
leafHolder.notBefore,
|
||||
leafHolder.notAfter,
|
||||
leafHolder.subject,
|
||||
leafHolder.subjectPublicKeyInfo
|
||||
)
|
||||
|
||||
val signer = JcaContentSignerBuilder(leaf.sigAlgName).build(keybox.keyPair.private)
|
||||
|
||||
val hackedExtension = createHackedAttestationExtension(rootOfTrust, vector, encodables)
|
||||
builder.addExtension(hackedExtension)
|
||||
|
||||
leafHolder.extensions.extensionOIDs.forEach { oid ->
|
||||
if (oid.id != ATTESTATION_OID.id) {
|
||||
builder.addExtension(leafHolder.getExtension(oid))
|
||||
}
|
||||
}
|
||||
|
||||
certificates.addFirst(JcaX509CertificateConverter().getCertificate(builder.build(signer)))
|
||||
return certificates.toTypedArray()
|
||||
}
|
||||
|
||||
private fun hackSingleCertificate(leaf: X509Certificate): Certificate? {
|
||||
return try {
|
||||
val leafHolder = X509CertificateHolder(leaf.encoded)
|
||||
val extension = leafHolder.getExtension(ATTESTATION_OID)
|
||||
val sequence = ASN1Sequence.getInstance(extension.extnValue.octets)
|
||||
val encodables = sequence.toArray()
|
||||
val teeEnforced = encodables[7] as ASN1Sequence
|
||||
|
||||
val vector = ASN1EncodableVector()
|
||||
var rootOfTrust: ASN1Encodable? = null
|
||||
|
||||
teeEnforced.forEach { element ->
|
||||
val taggedObject = element as ASN1TaggedObject
|
||||
if (taggedObject.tagNo == 704) {
|
||||
rootOfTrust = taggedObject.baseObject.toASN1Primitive()
|
||||
} else {
|
||||
vector.add(taggedObject)
|
||||
}
|
||||
}
|
||||
|
||||
val keybox = keyboxes[leaf.publicKey.algorithm]
|
||||
?: throw UnsupportedOperationException("Unsupported algorithm: ${leaf.publicKey.algorithm}")
|
||||
|
||||
val builder = X509v3CertificateBuilder(
|
||||
X509CertificateHolder(keybox.certificates[0].encoded).subject,
|
||||
leafHolder.serialNumber,
|
||||
leafHolder.notBefore,
|
||||
leafHolder.notAfter,
|
||||
leafHolder.subject,
|
||||
leafHolder.subjectPublicKeyInfo
|
||||
)
|
||||
|
||||
val signer = JcaContentSignerBuilder(leaf.sigAlgName).build(keybox.keyPair.private)
|
||||
|
||||
val hackedExtension = createHackedAttestationExtension(rootOfTrust, vector, encodables)
|
||||
builder.addExtension(hackedExtension)
|
||||
|
||||
leafHolder.extensions.extensionOIDs.forEach { oid ->
|
||||
if (oid.id != ATTESTATION_OID.id) {
|
||||
builder.addExtension(leafHolder.getExtension(oid))
|
||||
}
|
||||
}
|
||||
|
||||
JcaX509CertificateConverter().getCertificate(builder.build(signer))
|
||||
} catch (t: Throwable) {
|
||||
Logger.e("Failed to hack single certificate", t)
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
private fun createHackedAttestationExtension(
|
||||
originalRootOfTrust: ASN1Encodable?,
|
||||
vector: ASN1EncodableVector,
|
||||
originalEncodables: Array<ASN1Encodable>
|
||||
): Extension {
|
||||
val verifiedBootKey = bootKey
|
||||
var verifiedBootHash: ByteArray? = null
|
||||
|
||||
try {
|
||||
if (originalRootOfTrust is ASN1Sequence) {
|
||||
verifiedBootHash = getByteArrayFromAsn1(originalRootOfTrust.getObjectAt(3))
|
||||
}
|
||||
} catch (t: Throwable) {
|
||||
Logger.e("Failed to get verified boot hash from original, using generated", t)
|
||||
}
|
||||
|
||||
if (verifiedBootHash == null) {
|
||||
verifiedBootHash = bootHash
|
||||
}
|
||||
|
||||
val rootOfTrustElements = arrayOf(
|
||||
DEROctetString(verifiedBootKey),
|
||||
ASN1Boolean.TRUE,
|
||||
ASN1Enumerated(0),
|
||||
DEROctetString(verifiedBootHash)
|
||||
)
|
||||
val hackedRootOfTrust = DERSequence(rootOfTrustElements)
|
||||
|
||||
vector.add(DERTaggedObject(true, 718, ASN1Integer(vendorPatchLevelLong.toLong())))
|
||||
vector.add(DERTaggedObject(true, 719, ASN1Integer(bootPatchLevelLong.toLong())))
|
||||
vector.add(DERTaggedObject(true, 706, ASN1Integer(patchLevel.toLong())))
|
||||
vector.add(DERTaggedObject(true, 705, ASN1Integer(osVersion.toLong())))
|
||||
vector.add(DERTaggedObject(704, hackedRootOfTrust))
|
||||
|
||||
val hackEnforced = DERSequence(vector)
|
||||
originalEncodables[7] = hackEnforced
|
||||
val hackedSequence = DERSequence(originalEncodables)
|
||||
val hackedSequenceOctets = DEROctetString(hackedSequence)
|
||||
|
||||
return Extension(ATTESTATION_OID, false, hackedSequenceOctets)
|
||||
}
|
||||
|
||||
private fun buildCertificate(
|
||||
keyPair: KeyPair,
|
||||
keybox: KeyBox,
|
||||
params: KeyGenParameters,
|
||||
issuer: X500Name,
|
||||
uid: Int,
|
||||
signingKeyPair: KeyPair = keybox.keyPair
|
||||
): Certificate {
|
||||
val builder = JcaX509v3CertificateBuilder(
|
||||
issuer,
|
||||
params.certificateSerial ?: BigInteger.ONE,
|
||||
params.certificateNotBefore ?: Date(),
|
||||
params.certificateNotAfter ?: (keybox.certificates[0] as X509Certificate).notAfter,
|
||||
params.certificateSubject ?: X500Name("CN=Android KeyStore Key"),
|
||||
keyPair.public
|
||||
)
|
||||
|
||||
builder.addExtension(Extension.keyUsage, true, KeyUsage(KeyUsage.keyCertSign))
|
||||
builder.addExtension(createAttestationExtension(params, uid))
|
||||
|
||||
val contentSigner = when (params.algorithm) {
|
||||
Algorithm.EC -> JcaContentSignerBuilder("SHA256withECDSA").build(signingKeyPair.private)
|
||||
Algorithm.RSA -> JcaContentSignerBuilder("SHA256withRSA").build(signingKeyPair.private)
|
||||
else -> throw IllegalArgumentException("Unsupported algorithm: ${params.algorithm}")
|
||||
}
|
||||
|
||||
return JcaX509CertificateConverter().getCertificate(builder.build(contentSigner))
|
||||
}
|
||||
|
||||
private fun createAttestationExtension(params: KeyGenParameters, uid: Int): Extension {
|
||||
try {
|
||||
val key = bootKey
|
||||
val hash = bootHash
|
||||
|
||||
val rootOfTrustEncodables = arrayOf(
|
||||
DEROctetString(key),
|
||||
ASN1Boolean.TRUE,
|
||||
ASN1Enumerated(0),
|
||||
DEROctetString(hash)
|
||||
)
|
||||
val rootOfTrustSeq = DERSequence(rootOfTrustEncodables)
|
||||
|
||||
val purpose = DERSet(fromIntList(params.purpose))
|
||||
val algorithm = ASN1Integer(params.algorithm.toLong())
|
||||
val keySize = ASN1Integer(params.keySize.toLong())
|
||||
val digest = DERSet(fromIntList(params.digest))
|
||||
val ecCurve = ASN1Integer(params.ecCurve.toLong())
|
||||
val noAuthRequired = DERNull.INSTANCE
|
||||
|
||||
val osVersion = ASN1Integer(io.github.beakthoven.TrickyStoreOSS.osVersion.toLong())
|
||||
val osPatchLevel = ASN1Integer(io.github.beakthoven.TrickyStoreOSS.patchLevel.toLong())
|
||||
val applicationID = createApplicationId(uid)
|
||||
val bootPatchLevel = ASN1Integer(bootPatchLevelLong.toLong())
|
||||
val vendorPatchLevel = ASN1Integer(vendorPatchLevelLong.toLong())
|
||||
val creationDateTime = ASN1Integer(System.currentTimeMillis())
|
||||
val origin = ASN1Integer(0L)
|
||||
val moduleHash = DEROctetString(io.github.beakthoven.TrickyStoreOSS.moduleHash)
|
||||
|
||||
val teeEnforcedObjects = mutableListOf(
|
||||
DERTaggedObject(true, 1, purpose),
|
||||
DERTaggedObject(true, 2, algorithm),
|
||||
DERTaggedObject(true, 3, keySize),
|
||||
DERTaggedObject(true, 5, digest),
|
||||
DERTaggedObject(true, 10, ecCurve),
|
||||
DERTaggedObject(true, 503, noAuthRequired),
|
||||
DERTaggedObject(true, 702, origin),
|
||||
DERTaggedObject(true, 704, rootOfTrustSeq),
|
||||
DERTaggedObject(true, 705, osVersion),
|
||||
DERTaggedObject(true, 706, osPatchLevel),
|
||||
DERTaggedObject(true, 718, vendorPatchLevel),
|
||||
DERTaggedObject(true, 719, bootPatchLevel),
|
||||
DERTaggedObject(true, 724, moduleHash)
|
||||
)
|
||||
|
||||
params.brand?.let { teeEnforcedObjects.add(DERTaggedObject(true, 710, DEROctetString(it))) }
|
||||
params.device?.let { teeEnforcedObjects.add(DERTaggedObject(true, 711, DEROctetString(it))) }
|
||||
params.product?.let { teeEnforcedObjects.add(DERTaggedObject(true, 712, DEROctetString(it))) }
|
||||
params.manufacturer?.let { teeEnforcedObjects.add(DERTaggedObject(true, 716, DEROctetString(it))) }
|
||||
params.model?.let { teeEnforcedObjects.add(DERTaggedObject(true, 717, DEROctetString(it))) }
|
||||
|
||||
params.serialno?.let { teeEnforcedObjects.add(DERTaggedObject(true, 713, DEROctetString(it))) }
|
||||
params.imei1?.let { teeEnforcedObjects.add(DERTaggedObject(true, 714, DEROctetString(it))) }
|
||||
params.imei2?.let { teeEnforcedObjects.add(DERTaggedObject(true, 715, DEROctetString(it))) }
|
||||
params.meid?.let { teeEnforcedObjects.add(DERTaggedObject(true, 723, DEROctetString(it))) }
|
||||
|
||||
teeEnforcedObjects.sortBy { it.tagNo }
|
||||
|
||||
val softwareEnforcedObjects = arrayOf<ASN1Encodable>(
|
||||
DERTaggedObject(true, 709, applicationID),
|
||||
DERTaggedObject(true, 701, creationDateTime)
|
||||
)
|
||||
|
||||
return Extension(
|
||||
ATTESTATION_OID,
|
||||
false,
|
||||
getAsn1OctetString(teeEnforcedObjects.toTypedArray(), softwareEnforcedObjects, params)
|
||||
)
|
||||
} catch (t: Throwable) {
|
||||
Logger.e("Failed to create attestation extension", t)
|
||||
throw t
|
||||
}
|
||||
}
|
||||
|
||||
private fun fromIntList(list: List<Int>): Array<ASN1Encodable> {
|
||||
return list.map { ASN1Integer(it.toLong()) }.toTypedArray()
|
||||
}
|
||||
|
||||
private fun getAsn1OctetString(
|
||||
teeEnforcedEncodables: Array<ASN1Encodable>,
|
||||
softwareEnforcedEncodables: Array<ASN1Encodable>,
|
||||
params: KeyGenParameters
|
||||
): ASN1OctetString {
|
||||
val attestationVersion = ASN1Integer(400L)
|
||||
val attestationSecurityLevel = ASN1Enumerated(1)
|
||||
val keymasterVersion = ASN1Integer(400L)
|
||||
val keymasterSecurityLevel = ASN1Enumerated(1)
|
||||
val attestationChallenge = DEROctetString(params.attestationChallenge ?: ByteArray(0))
|
||||
val uniqueId = DEROctetString(ByteArray(0))
|
||||
val softwareEnforced = DERSequence(softwareEnforcedEncodables)
|
||||
val teeEnforced = DERSequence(teeEnforcedEncodables)
|
||||
|
||||
val keyDescriptionEncodables = arrayOf(
|
||||
attestationVersion,
|
||||
attestationSecurityLevel,
|
||||
keymasterVersion,
|
||||
keymasterSecurityLevel,
|
||||
attestationChallenge,
|
||||
uniqueId,
|
||||
softwareEnforced,
|
||||
teeEnforced
|
||||
)
|
||||
|
||||
val keyDescriptionSeq = DERSequence(keyDescriptionEncodables)
|
||||
return DEROctetString(keyDescriptionSeq.encoded)
|
||||
}
|
||||
|
||||
@Throws(Throwable::class)
|
||||
private fun createApplicationId(uid: Int): DEROctetString {
|
||||
val pm = Config.getPm() ?: throw IllegalStateException("PackageManager not found!")
|
||||
val packages = pm.getPackagesForUid(uid) ?: throw IllegalStateException("No packages for UID $uid")
|
||||
|
||||
val packageInfoArray = Array(packages.size) { i ->
|
||||
val packageName = packages[i]
|
||||
val packageInfo = pm.getPackageInfoCompat(packageName, PackageManager.GET_SIGNING_CERTIFICATES.toLong(), uid / 100000)
|
||||
|
||||
DERSequence(arrayOf(
|
||||
DEROctetString(packageName.toByteArray(StandardCharsets.UTF_8)),
|
||||
ASN1Integer(packageInfo.longVersionCode)
|
||||
))
|
||||
}
|
||||
|
||||
val signatures = mutableSetOf<Digest>()
|
||||
val messageDigest = MessageDigest.getInstance("SHA-256")
|
||||
|
||||
packages.forEach { packageName ->
|
||||
val packageInfo = pm.getPackageInfoCompat(packageName, PackageManager.GET_SIGNING_CERTIFICATES.toLong(), uid / 100000)
|
||||
packageInfo.signingInfo?.apkContentsSigners?.forEach { signature ->
|
||||
signatures.add(Digest(messageDigest.digest(signature.toByteArray())))
|
||||
}
|
||||
}
|
||||
|
||||
val signaturesArray = signatures.map { DEROctetString(it.digest) }.toTypedArray()
|
||||
|
||||
val applicationIdArray = arrayOf(
|
||||
DERSet(packageInfoArray),
|
||||
DERSet(signaturesArray)
|
||||
)
|
||||
|
||||
return DEROctetString(DERSequence(applicationIdArray).encoded)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,160 @@
|
||||
/*
|
||||
* Copyright 2025 Dakkshesh <beakthoven@gmail.com>
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
package io.github.beakthoven.TrickyStoreOSS
|
||||
|
||||
import android.system.keystore2.KeyEntryResponse
|
||||
import android.system.keystore2.KeyMetadata
|
||||
import android.util.Log
|
||||
import io.github.beakthoven.TrickyStoreOSS.CertificateUtils.putCertificateChain
|
||||
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
|
||||
|
||||
object CertificateUtils {
|
||||
private const val TAG = "CertificateUtils"
|
||||
|
||||
sealed class CertificateResult<out T> {
|
||||
data class Success<T>(val data: T) : CertificateResult<T>()
|
||||
data class Error(val message: String, val cause: Throwable? = null) : CertificateResult<Nothing>()
|
||||
|
||||
inline fun <R> map(transform: (T) -> R): CertificateResult<R> = when (this) {
|
||||
is Success -> Success(transform(data))
|
||||
is Error -> this
|
||||
}
|
||||
|
||||
fun getOrNull(): T? = when (this) {
|
||||
is Success -> data
|
||||
is Error -> null
|
||||
}
|
||||
}
|
||||
|
||||
fun ByteArray?.toCertificate(): X509Certificate? {
|
||||
return this?.let { bytes ->
|
||||
try {
|
||||
val certFactory = CertificateFactory.getInstance("X.509")
|
||||
certFactory.generateCertificate(ByteArrayInputStream(bytes)) as? X509Certificate
|
||||
} catch (e: CertificateException) {
|
||||
Log.w(TAG, "Couldn't parse certificate in keystore", e)
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun ByteArray.toCertificateResult(): CertificateResult<X509Certificate> {
|
||||
return try {
|
||||
val certFactory = CertificateFactory.getInstance("X.509")
|
||||
val certificate = certFactory.generateCertificate(ByteArrayInputStream(this)) as X509Certificate
|
||||
CertificateResult.Success(certificate)
|
||||
} catch (e: CertificateException) {
|
||||
CertificateResult.Error("Failed to parse certificate", e)
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
fun ByteArray?.toCertificates(): Collection<X509Certificate> {
|
||||
return this?.let { bytes ->
|
||||
try {
|
||||
val certFactory = CertificateFactory.getInstance("X.509")
|
||||
certFactory.generateCertificates(ByteArrayInputStream(bytes)) as Collection<X509Certificate>
|
||||
} catch (e: CertificateException) {
|
||||
Log.w(TAG, "Couldn't parse certificates in keystore", e)
|
||||
emptyList()
|
||||
}
|
||||
} ?: emptyList()
|
||||
}
|
||||
|
||||
fun Collection<Certificate>.toByteArray(): ByteArray? = runCatching {
|
||||
ByteArrayOutputStream().use { outputStream ->
|
||||
forEach { cert -> outputStream.write(cert.encoded) }
|
||||
outputStream.toByteArray()
|
||||
}
|
||||
}.onFailure {
|
||||
Log.w(TAG, "Failed to convert certificates to byte array", it)
|
||||
}.getOrNull()
|
||||
|
||||
fun Collection<Certificate>.toByteArrayList(): List<ByteArray>? = runCatching {
|
||||
map { it.encoded }
|
||||
}.onFailure {
|
||||
Log.w(TAG, "Failed to convert certificates to byte array list", it)
|
||||
}.getOrNull()
|
||||
|
||||
fun KeyEntryResponse?.getCertificateChain(): Array<Certificate>? {
|
||||
val metadata = this?.metadata ?: return null
|
||||
val leafCert = metadata.certificate?.toCertificate() ?: return null
|
||||
|
||||
return when (val chainBytes = metadata.certificateChain) {
|
||||
null -> arrayOf(leafCert)
|
||||
else -> {
|
||||
val additionalCerts = chainBytes.toCertificates()
|
||||
buildList {
|
||||
add(leafCert)
|
||||
addAll(additionalCerts)
|
||||
}.toTypedArray()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun KeyEntryResponse.putCertificateChain(chain: Array<Certificate>): Result<Unit> {
|
||||
return runCatching {
|
||||
metadata.putCertificateChain(chain)
|
||||
}
|
||||
}
|
||||
|
||||
fun KeyMetadata.putCertificateChain(chain: Array<Certificate>): Result<Unit> {
|
||||
return runCatching {
|
||||
if (chain.isEmpty()) return@runCatching
|
||||
|
||||
certificate = chain[0].encoded
|
||||
|
||||
if (chain.size > 1) {
|
||||
ByteArrayOutputStream().use { output ->
|
||||
for (i in 1 until chain.size) {
|
||||
output.write(chain[i].encoded)
|
||||
}
|
||||
certificateChain = output.toByteArray()
|
||||
}
|
||||
} else {
|
||||
certificateChain = null
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun ByteArray?.toX509Certificate(): X509Certificate? = CertificateUtils.run { this@toX509Certificate.toCertificate() }
|
||||
|
||||
fun ByteArray?.toX509Certificates(): Collection<X509Certificate> = CertificateUtils.run { this@toX509Certificates.toCertificates() }
|
||||
|
||||
fun Collection<Certificate>.encodedBytes(): ByteArray? = CertificateUtils.run { this@encodedBytes.toByteArray() }
|
||||
|
||||
fun Collection<Certificate>.encodedBytesList(): List<ByteArray>? = CertificateUtils.run { this@encodedBytesList.toByteArrayList() }
|
||||
|
||||
fun KeyEntryResponse.putCertificateChain(chain: Array<Certificate>): Result<Unit> {
|
||||
return runCatching {
|
||||
metadata.putCertificateChain(chain).getOrThrow()
|
||||
}
|
||||
}
|
||||
|
||||
fun KeyMetadata.putCertificateChain(chain: Array<Certificate>): Result<Unit> {
|
||||
return runCatching {
|
||||
if (chain.isEmpty()) return@runCatching
|
||||
|
||||
certificate = chain[0].encoded
|
||||
|
||||
if (chain.size > 1) {
|
||||
ByteArrayOutputStream().use { output ->
|
||||
for (i in 1 until chain.size) {
|
||||
output.write(chain[i].encoded)
|
||||
}
|
||||
certificateChain = output.toByteArray()
|
||||
}
|
||||
} else {
|
||||
certificateChain = null
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
/*
|
||||
* Copyright 2025 Dakkshesh <beakthoven@gmail.com>
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
package io.github.beakthoven.TrickyStoreOSS
|
||||
|
||||
import android.os.Build
|
||||
import io.github.beakthoven.TrickyStoreOSS.core.config.Config
|
||||
import io.github.beakthoven.TrickyStoreOSS.core.logging.Logger
|
||||
import io.github.beakthoven.TrickyStoreOSS.interceptors.KeystoreInterceptor
|
||||
import io.github.beakthoven.TrickyStoreOSS.interceptors.Keystore2Interceptor
|
||||
|
||||
private const val RETRY_DELAY_MS = 1000L
|
||||
private const val SERVICE_SLEEP_MS = 1000000L
|
||||
|
||||
fun main(args: Array<String>) {
|
||||
Logger.i("Welcome to TrickyStoreOSS!")
|
||||
|
||||
try {
|
||||
initializeInterceptors()
|
||||
maintainService()
|
||||
} catch (e: Exception) {
|
||||
Logger.e("Fatal error in main", e)
|
||||
throw e
|
||||
}
|
||||
}
|
||||
|
||||
private fun initializeInterceptors() {
|
||||
val interceptor = selectKeystoreInterceptor()
|
||||
|
||||
while (!interceptor.tryRunKeystoreInterceptor()) {
|
||||
Logger.d("Retrying interceptor initialization...")
|
||||
Thread.sleep(RETRY_DELAY_MS)
|
||||
}
|
||||
|
||||
Config.initialize()
|
||||
Logger.i("Interceptors initialized successfully")
|
||||
}
|
||||
|
||||
private fun selectKeystoreInterceptor() = when {
|
||||
Build.VERSION.SDK_INT in Build.VERSION_CODES.Q..Build.VERSION_CODES.R -> {
|
||||
Logger.i("Using KeystoreInterceptor for Android Q/R (SDK ${Build.VERSION.SDK_INT})")
|
||||
KeystoreInterceptor
|
||||
}
|
||||
else -> {
|
||||
Logger.i("Using Keystore2Interceptor for Android S+ (SDK ${Build.VERSION.SDK_INT})")
|
||||
Keystore2Interceptor
|
||||
}
|
||||
}
|
||||
|
||||
private fun maintainService() {
|
||||
Logger.i("Service started, entering maintenance mode")
|
||||
while (true) {
|
||||
Thread.sleep(SERVICE_SLEEP_MS)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,155 @@
|
||||
/*
|
||||
* Copyright 2025 Dakkshesh <beakthoven@gmail.com>
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
package io.github.beakthoven.TrickyStoreOSS
|
||||
|
||||
import org.xmlpull.v1.XmlPullParser
|
||||
import org.xmlpull.v1.XmlPullParserException
|
||||
import org.xmlpull.v1.XmlPullParserFactory
|
||||
import java.io.IOException
|
||||
import java.io.StringReader
|
||||
|
||||
class XmlParser(private val xmlContent: String) {
|
||||
|
||||
sealed class ParseResult {
|
||||
data class Success(val attributes: Map<String, String>) : ParseResult()
|
||||
data class Error(val message: String, val cause: Throwable? = null) : ParseResult()
|
||||
}
|
||||
|
||||
fun obtainPath(path: String): ParseResult {
|
||||
return try {
|
||||
val factory = XmlPullParserFactory.newInstance()
|
||||
val parser = factory.newPullParser()
|
||||
parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, false)
|
||||
parser.setInput(StringReader(xmlContent))
|
||||
|
||||
val tags = path.split(".").toTypedArray()
|
||||
val result = readData(parser, tags, 0, mutableMapOf())
|
||||
ParseResult.Success(result)
|
||||
} catch (e: XmlPullParserException) {
|
||||
ParseResult.Error("XML parsing error: ${e.message}", e)
|
||||
} catch (e: IOException) {
|
||||
ParseResult.Error("IO error while parsing XML: ${e.message}", e)
|
||||
} catch (e: Exception) {
|
||||
ParseResult.Error("Unexpected error: ${e.message}", e)
|
||||
}
|
||||
}
|
||||
|
||||
@Throws(Exception::class)
|
||||
fun obtainPathLegacy(path: String): Map<String, String> {
|
||||
when (val result = obtainPath(path)) {
|
||||
is ParseResult.Success -> return result.attributes
|
||||
is ParseResult.Error -> throw result.cause ?: Exception(result.message)
|
||||
}
|
||||
}
|
||||
|
||||
@Throws(IOException::class, XmlPullParserException::class)
|
||||
private fun readData(
|
||||
parser: XmlPullParser,
|
||||
tags: Array<String>,
|
||||
index: Int,
|
||||
tagCounts: MutableMap<String, Int>
|
||||
): Map<String, String> {
|
||||
while (parser.next() != XmlPullParser.END_DOCUMENT) {
|
||||
if (parser.eventType != XmlPullParser.START_TAG) {
|
||||
continue
|
||||
}
|
||||
|
||||
val currentTag = parser.name ?: continue
|
||||
val targetTag = tags[index]
|
||||
val tagParts = targetTag.split("[")
|
||||
val baseTagName = tagParts[0]
|
||||
|
||||
if (currentTag == baseTagName) {
|
||||
return if (tagParts.size > 1) {
|
||||
handleIndexedTag(parser, tags, index, tagCounts, currentTag, tagParts[1])
|
||||
} else {
|
||||
handleRegularTag(parser, tags, index)
|
||||
}
|
||||
} else {
|
||||
skipCurrentElement(parser)
|
||||
}
|
||||
}
|
||||
|
||||
throw XmlPullParserException("Path not found: ${tags.joinToString(".")}")
|
||||
}
|
||||
|
||||
@Throws(IOException::class, XmlPullParserException::class)
|
||||
private fun handleIndexedTag(
|
||||
parser: XmlPullParser,
|
||||
tags: Array<String>,
|
||||
index: Int,
|
||||
tagCounts: MutableMap<String, Int>,
|
||||
currentTag: String,
|
||||
indexPart: String
|
||||
): Map<String, String> {
|
||||
val targetIndex = indexPart.replace("]", "").toIntOrNull()
|
||||
?: throw XmlPullParserException("Invalid index in tag: $indexPart")
|
||||
|
||||
val currentCount = tagCounts.getOrDefault(currentTag, 0)
|
||||
|
||||
return if (currentCount < targetIndex) {
|
||||
tagCounts[currentTag] = currentCount + 1
|
||||
readData(parser, tags, index, tagCounts)
|
||||
} else {
|
||||
if (index == tags.size - 1) {
|
||||
readAttributes(parser)
|
||||
} else {
|
||||
readData(parser, tags, index + 1, tagCounts)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Throws(IOException::class, XmlPullParserException::class)
|
||||
private fun handleRegularTag(
|
||||
parser: XmlPullParser,
|
||||
tags: Array<String>,
|
||||
index: Int
|
||||
): Map<String, String> {
|
||||
return if (index == tags.size - 1) {
|
||||
readAttributes(parser)
|
||||
} else {
|
||||
readData(parser, tags, index + 1, mutableMapOf())
|
||||
}
|
||||
}
|
||||
|
||||
@Throws(IOException::class, XmlPullParserException::class)
|
||||
private fun readAttributes(parser: XmlPullParser): Map<String, String> {
|
||||
val attributes = mutableMapOf<String, String>()
|
||||
|
||||
for (i in 0 until parser.attributeCount) {
|
||||
val name = parser.getAttributeName(i)
|
||||
val value = parser.getAttributeValue(i)
|
||||
if (name != null && value != null) {
|
||||
attributes[name] = value
|
||||
}
|
||||
}
|
||||
|
||||
if (parser.next() == XmlPullParser.TEXT) {
|
||||
parser.text?.let { text ->
|
||||
attributes["text"] = text
|
||||
}
|
||||
}
|
||||
|
||||
return attributes
|
||||
}
|
||||
|
||||
@Throws(XmlPullParserException::class, IOException::class)
|
||||
private fun skipCurrentElement(parser: XmlPullParser) {
|
||||
if (parser.eventType != XmlPullParser.START_TAG) {
|
||||
throw IllegalStateException("Parser must be positioned at START_TAG")
|
||||
}
|
||||
|
||||
var depth = 1
|
||||
while (depth != 0) {
|
||||
when (parser.next()) {
|
||||
XmlPullParser.END_TAG -> depth--
|
||||
XmlPullParser.START_TAG -> depth++
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun String.toXmlParser(): XmlParser = XmlParser(this)
|
||||
@@ -0,0 +1,267 @@
|
||||
/*
|
||||
* Copyright 2025 Dakkshesh <beakthoven@gmail.com>
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
package io.github.beakthoven.TrickyStoreOSS.core.config
|
||||
|
||||
import android.content.pm.IPackageManager
|
||||
import android.os.Build
|
||||
import android.os.FileObserver
|
||||
import android.os.ServiceManager
|
||||
import android.security.keystore.KeyGenParameterSpec
|
||||
import android.security.keystore.KeyProperties
|
||||
import io.github.beakthoven.TrickyStoreOSS.CertificateHacker
|
||||
import io.github.beakthoven.TrickyStoreOSS.core.logging.Logger
|
||||
import java.io.File
|
||||
import java.security.KeyPairGenerator
|
||||
import java.security.KeyStore
|
||||
import java.security.SecureRandom
|
||||
import java.security.spec.ECGenParameterSpec
|
||||
|
||||
object Config {
|
||||
private val hackPackages = mutableSetOf<String>()
|
||||
private val generatePackages = mutableSetOf<String>()
|
||||
private val packageModes = mutableMapOf<String, Mode>()
|
||||
|
||||
enum class Mode {
|
||||
AUTO, LEAF_HACK, GENERATE
|
||||
}
|
||||
|
||||
private fun updateTargetPackages(f: File?) = runCatching {
|
||||
hackPackages.clear()
|
||||
generatePackages.clear()
|
||||
packageModes.clear()
|
||||
// Default: always generate for these
|
||||
listOf("com.google.android.gsf", "com.google.android.gms", "com.android.vending").forEach {
|
||||
generatePackages.add(it)
|
||||
packageModes[it] = Mode.GENERATE
|
||||
}
|
||||
f?.readLines()?.forEach {
|
||||
if (it.isNotBlank() && !it.startsWith("#")) {
|
||||
val n = it.trim()
|
||||
when {
|
||||
n.endsWith("!") -> {
|
||||
val pkg = n.removeSuffix("!").trim()
|
||||
generatePackages.add(pkg)
|
||||
packageModes[pkg] = Mode.GENERATE
|
||||
}
|
||||
n.endsWith("?") -> {
|
||||
val pkg = n.removeSuffix("?").trim()
|
||||
hackPackages.add(pkg)
|
||||
packageModes[pkg] = Mode.LEAF_HACK
|
||||
}
|
||||
else -> {
|
||||
// Auto mode
|
||||
packageModes[n] = Mode.AUTO
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Logger.i("update hack packages: $hackPackages, generate packages=$generatePackages, packageModes=$packageModes")
|
||||
}.onFailure {
|
||||
Logger.e("failed to update target files", it)
|
||||
}
|
||||
|
||||
private fun updateKeyBox(f: File?) = runCatching {
|
||||
CertificateHacker.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 const val TEE_STATUS_FILE = "tee_status"
|
||||
private const val PATCHLEVEL_FILE = "security_patch.txt"
|
||||
private val root = File(CONFIG_PATH)
|
||||
|
||||
@Volatile
|
||||
private var teeBroken: Boolean? = null
|
||||
|
||||
private fun isTEEWorking(): Boolean {
|
||||
val alias = "tee_attest_test_key"
|
||||
return try {
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||
android.app.ActivityThread.initializeMainlineModules();
|
||||
}
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
||||
android.security.keystore2.AndroidKeyStoreProvider.install();
|
||||
} else {
|
||||
android.security.keystore.AndroidKeyStoreProvider.install();
|
||||
}
|
||||
|
||||
val keyStore = KeyStore.getInstance("AndroidKeyStore")
|
||||
keyStore.load(null)
|
||||
|
||||
val keyPairGenerator = KeyPairGenerator.getInstance(
|
||||
KeyProperties.KEY_ALGORITHM_EC, "AndroidKeyStore")
|
||||
|
||||
val challenge = ByteArray(16).apply {
|
||||
SecureRandom().nextBytes(this)
|
||||
}
|
||||
|
||||
val parameterSpec = KeyGenParameterSpec.Builder(
|
||||
alias,
|
||||
KeyProperties.PURPOSE_SIGN
|
||||
)
|
||||
.setAlgorithmParameterSpec(ECGenParameterSpec("secp256r1"))
|
||||
.setDigests(KeyProperties.DIGEST_SHA256)
|
||||
.setAttestationChallenge(challenge)
|
||||
.setIsStrongBoxBacked(false)
|
||||
.build()
|
||||
|
||||
keyPairGenerator.initialize(parameterSpec)
|
||||
keyPairGenerator.generateKeyPair()
|
||||
|
||||
keyStore.deleteEntry(alias)
|
||||
true
|
||||
} catch (e: Exception) {
|
||||
Logger.e("TEE check failure: ${e.message}")
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private fun storeTEEStatus(root: File) {
|
||||
val statusFile = File(root, TEE_STATUS_FILE)
|
||||
val status = isTEEWorking()
|
||||
teeBroken = !status
|
||||
try {
|
||||
statusFile.writeText("teeBroken=${!status}")
|
||||
} catch (e: Exception) {
|
||||
Logger.e("Failed to write TEE status: ${e.message}")
|
||||
}
|
||||
}
|
||||
|
||||
private fun loadTEEStatus(root: File) {
|
||||
val statusFile = File(root, TEE_STATUS_FILE)
|
||||
if (statusFile.exists()) {
|
||||
val line = statusFile.readText().trim()
|
||||
teeBroken = line == "teeBroken=true"
|
||||
} else {
|
||||
teeBroken = null
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
PATCHLEVEL_FILE -> updatePatchLevel(f)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun initialize() {
|
||||
root.mkdirs()
|
||||
val scope = File(root, TARGET_FILE)
|
||||
if (scope.exists()) {
|
||||
updateTargetPackages(scope)
|
||||
} else {
|
||||
Logger.e("target.txt file not found, please put it to $scope !")
|
||||
}
|
||||
val keybox = File(root, KEYBOX_FILE)
|
||||
if (!keybox.exists()) {
|
||||
Logger.e("keybox file not found, please put it to $keybox !")
|
||||
} else {
|
||||
updateKeyBox(keybox)
|
||||
}
|
||||
storeTEEStatus(root)
|
||||
val patchFile = File(root, PATCHLEVEL_FILE)
|
||||
updatePatchLevel(if (patchFile.exists()) patchFile else null)
|
||||
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): Boolean = kotlin.runCatching {
|
||||
val ps = getPm()?.getPackagesForUid(callingUid) ?: return false
|
||||
if (teeBroken == null) loadTEEStatus(root)
|
||||
for (pkg in ps) {
|
||||
when (packageModes[pkg]) {
|
||||
Mode.LEAF_HACK -> return true
|
||||
Mode.AUTO -> {
|
||||
if (teeBroken == false) return true
|
||||
}
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}.onFailure { Logger.e("failed to get packages", it) }.getOrNull() ?: false
|
||||
|
||||
fun needGenerate(callingUid: Int): Boolean = kotlin.runCatching {
|
||||
val ps = getPm()?.getPackagesForUid(callingUid) ?: return false
|
||||
if (teeBroken == null) loadTEEStatus(root)
|
||||
for (pkg in ps) {
|
||||
when (packageModes[pkg]) {
|
||||
Mode.GENERATE -> return true
|
||||
Mode.AUTO -> {
|
||||
if (teeBroken == true) return true
|
||||
}
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}.onFailure { Logger.e("failed to get packages", it) }.getOrNull() ?: false
|
||||
|
||||
@Volatile
|
||||
var _customPatchLevel: CustomPatchLevel? = null
|
||||
|
||||
fun updatePatchLevel(f: File?) = runCatching {
|
||||
if (f == null || !f.exists()) {
|
||||
_customPatchLevel = null
|
||||
return@runCatching
|
||||
}
|
||||
val lines = f.readLines().map { it.trim() }.filter { it.isNotEmpty() && !it.startsWith("#") }
|
||||
if (lines.isEmpty()) {
|
||||
_customPatchLevel = null
|
||||
return@runCatching
|
||||
}
|
||||
if (lines.size == 1 && !lines[0].contains("=")) {
|
||||
_customPatchLevel = CustomPatchLevel(all = lines[0])
|
||||
return@runCatching
|
||||
}
|
||||
val map = mutableMapOf<String, String>()
|
||||
for (line in lines) {
|
||||
val idx = line.indexOf('=')
|
||||
if (idx > 0) {
|
||||
val key = line.substring(0, idx).trim().lowercase()
|
||||
val value = line.substring(idx + 1).trim()
|
||||
map[key] = value
|
||||
}
|
||||
}
|
||||
val all = map["all"]
|
||||
_customPatchLevel = CustomPatchLevel(
|
||||
system = map["system"] ?: all,
|
||||
vendor = map["vendor"] ?: all,
|
||||
boot = map["boot"] ?: all,
|
||||
all = all
|
||||
)
|
||||
}.onFailure {
|
||||
Logger.e("failed to update patch level", it)
|
||||
}
|
||||
}
|
||||
|
||||
data class CustomPatchLevel(
|
||||
val system: String? = null,
|
||||
val vendor: String? = null,
|
||||
val boot: String? = null,
|
||||
val all: String? = null
|
||||
)
|
||||
@@ -0,0 +1,64 @@
|
||||
/*
|
||||
* Copyright 2025 Dakkshesh <beakthoven@gmail.com>
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
package io.github.beakthoven.TrickyStoreOSS.core.logging
|
||||
|
||||
import android.util.Log
|
||||
|
||||
object Logger {
|
||||
const val TAG = "TrickyStore"
|
||||
|
||||
sealed class LogLevel(val priority: Int) {
|
||||
object Debug : LogLevel(Log.DEBUG)
|
||||
object Info : LogLevel(Log.INFO)
|
||||
object Warning : LogLevel(Log.WARN)
|
||||
object Error : LogLevel(Log.ERROR)
|
||||
object Verbose : LogLevel(Log.VERBOSE)
|
||||
}
|
||||
|
||||
fun d(message: String) {
|
||||
Log.d(TAG, message)
|
||||
}
|
||||
|
||||
fun e(message: String) {
|
||||
Log.e(TAG, message)
|
||||
}
|
||||
|
||||
fun e(message: String, throwable: Throwable) {
|
||||
Log.e(TAG, "wtf: $message", throwable)
|
||||
}
|
||||
|
||||
fun i(message: String) {
|
||||
Log.i(TAG, message)
|
||||
}
|
||||
|
||||
fun w(message: String) {
|
||||
Log.w(TAG, message)
|
||||
}
|
||||
|
||||
fun w(message: String, throwable: Throwable) {
|
||||
Log.w(TAG, message, throwable)
|
||||
}
|
||||
|
||||
fun v(message: String) {
|
||||
Log.v(TAG, message)
|
||||
}
|
||||
|
||||
fun log(level: LogLevel, message: String, throwable: Throwable? = null) {
|
||||
when (level) {
|
||||
is LogLevel.Debug -> if (throwable != null) Log.d(TAG, message, throwable) else Log.d(TAG, message)
|
||||
is LogLevel.Info -> if (throwable != null) Log.i(TAG, message, throwable) else Log.i(TAG, message)
|
||||
is LogLevel.Warning -> if (throwable != null) Log.w(TAG, message, throwable) else Log.w(TAG, message)
|
||||
is LogLevel.Error -> if (throwable != null) Log.e(TAG, message, throwable) else Log.e(TAG, message)
|
||||
is LogLevel.Verbose -> if (throwable != null) Log.v(TAG, message, throwable) else Log.v(TAG, message)
|
||||
}
|
||||
}
|
||||
|
||||
fun logIf(level: LogLevel, condition: Boolean = true, messageProvider: () -> String) {
|
||||
if (condition && Log.isLoggable(TAG, level.priority)) {
|
||||
log(level, messageProvider())
|
||||
}
|
||||
}
|
||||
}
|
||||
+181
@@ -0,0 +1,181 @@
|
||||
/*
|
||||
* Copyright 2025 Dakkshesh <beakthoven@gmail.com>
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
package io.github.beakthoven.TrickyStoreOSS.interceptors
|
||||
|
||||
import android.os.Binder
|
||||
import android.os.IBinder
|
||||
import android.os.Parcel
|
||||
import io.github.beakthoven.TrickyStoreOSS.core.logging.Logger
|
||||
|
||||
open class BinderInterceptor : Binder() {
|
||||
|
||||
sealed class Result
|
||||
|
||||
data object Skip : Result()
|
||||
|
||||
data object Continue : Result()
|
||||
|
||||
data class OverrideData(val data: Parcel) : Result()
|
||||
|
||||
data class OverrideReply(val code: Int = 0, val reply: Parcel) : Result()
|
||||
|
||||
companion object {
|
||||
private const val BACKDOOR_TRANSACTION_CODE = 0xdeadbeef.toInt()
|
||||
|
||||
private const val REGISTER_INTERCEPTOR_CODE = 1
|
||||
|
||||
private const val PRE_TRANSACT_CODE = 1
|
||||
private const val POST_TRANSACT_CODE = 2
|
||||
|
||||
private const val RESULT_SKIP = 1
|
||||
private const val RESULT_CONTINUE = 2
|
||||
private const val RESULT_OVERRIDE_REPLY = 3
|
||||
private const val RESULT_OVERRIDE_DATA = 4
|
||||
|
||||
fun getBinderBackdoor(binder: IBinder): IBinder? {
|
||||
val data = Parcel.obtain()
|
||||
val reply = Parcel.obtain()
|
||||
|
||||
return try {
|
||||
val success = binder.transact(BACKDOOR_TRANSACTION_CODE, data, reply, 0)
|
||||
if (success) {
|
||||
Logger.d("Backdoor access granted for binder: $binder")
|
||||
reply.readStrongBinder()
|
||||
} else {
|
||||
Logger.d("Backdoor access denied for binder: $binder")
|
||||
null
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Logger.e("Failed to access binder backdoor", e)
|
||||
null
|
||||
} finally {
|
||||
data.recycle()
|
||||
reply.recycle()
|
||||
}
|
||||
}
|
||||
|
||||
fun registerBinderInterceptor(
|
||||
backdoor: IBinder,
|
||||
target: IBinder,
|
||||
interceptor: BinderInterceptor
|
||||
) {
|
||||
val data = Parcel.obtain()
|
||||
val reply = Parcel.obtain()
|
||||
|
||||
try {
|
||||
data.writeStrongBinder(target)
|
||||
data.writeStrongBinder(interceptor)
|
||||
backdoor.transact(REGISTER_INTERCEPTOR_CODE, data, reply, 0)
|
||||
Logger.d("Registered interceptor for target: $target")
|
||||
} catch (e: Exception) {
|
||||
Logger.e("Failed to register binder interceptor", e)
|
||||
} finally {
|
||||
data.recycle()
|
||||
reply.recycle()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
PRE_TRANSACT_CODE -> handlePreTransact(data)
|
||||
POST_TRANSACT_CODE -> handlePostTransact(data)
|
||||
else -> return super.onTransact(code, data, reply, flags)
|
||||
}
|
||||
|
||||
writeResultToReply(result, reply!!)
|
||||
return true
|
||||
}
|
||||
|
||||
private fun handlePreTransact(data: Parcel): Result {
|
||||
val target = data.readStrongBinder()
|
||||
val transactionCode = data.readInt()
|
||||
val transactionFlags = data.readInt()
|
||||
val callingUid = data.readInt()
|
||||
val callingPid = data.readInt()
|
||||
val dataSize = data.readLong()
|
||||
|
||||
val transactionData = Parcel.obtain()
|
||||
return try {
|
||||
transactionData.appendFrom(data, data.dataPosition(), dataSize.toInt())
|
||||
transactionData.setDataPosition(0)
|
||||
onPreTransact(target, transactionCode, transactionFlags, callingUid, callingPid, transactionData)
|
||||
} finally {
|
||||
transactionData.recycle()
|
||||
}
|
||||
}
|
||||
|
||||
private fun handlePostTransact(data: Parcel): Result {
|
||||
val target = data.readStrongBinder()
|
||||
val transactionCode = data.readInt()
|
||||
val transactionFlags = data.readInt()
|
||||
val callingUid = data.readInt()
|
||||
val callingPid = data.readInt()
|
||||
val resultCode = data.readInt()
|
||||
|
||||
val transactionData = Parcel.obtain()
|
||||
val transactionReply = Parcel.obtain()
|
||||
|
||||
return try {
|
||||
val dataSize = data.readLong().toInt()
|
||||
transactionData.appendFrom(data, data.dataPosition(), dataSize)
|
||||
transactionData.setDataPosition(0)
|
||||
data.setDataPosition(data.dataPosition() + dataSize)
|
||||
|
||||
val replySize = data.readLong().toInt()
|
||||
val reply = if (replySize > 0) {
|
||||
transactionReply.appendFrom(data, data.dataPosition(), replySize)
|
||||
transactionReply.setDataPosition(0)
|
||||
transactionReply
|
||||
} else null
|
||||
|
||||
onPostTransact(target, transactionCode, transactionFlags, callingUid, callingPid, transactionData, reply, resultCode)
|
||||
} finally {
|
||||
transactionData.recycle()
|
||||
transactionReply.recycle()
|
||||
}
|
||||
}
|
||||
|
||||
private fun writeResultToReply(result: Result, reply: Parcel) {
|
||||
when (result) {
|
||||
Skip -> reply.writeInt(RESULT_SKIP)
|
||||
Continue -> reply.writeInt(RESULT_CONTINUE)
|
||||
is OverrideReply -> {
|
||||
reply.writeInt(RESULT_OVERRIDE_REPLY)
|
||||
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(RESULT_OVERRIDE_DATA)
|
||||
reply.writeLong(result.data.dataSize().toLong())
|
||||
reply.appendFrom(result.data, 0, result.data.dataSize())
|
||||
result.data.recycle()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+142
@@ -0,0 +1,142 @@
|
||||
/*
|
||||
* Copyright 2025 Dakkshesh <beakthoven@gmail.com>
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
package io.github.beakthoven.TrickyStoreOSS.interceptors
|
||||
|
||||
import android.os.IBinder
|
||||
import android.os.Parcel
|
||||
import android.os.Parcelable
|
||||
import android.os.ServiceManager
|
||||
import android.security.KeyStore
|
||||
import android.security.keystore.KeystoreResponse
|
||||
import io.github.beakthoven.TrickyStoreOSS.core.logging.Logger
|
||||
import kotlin.system.exitProcess
|
||||
|
||||
abstract class BaseKeystoreInterceptor : BinderInterceptor() {
|
||||
|
||||
protected lateinit var keystore: IBinder
|
||||
protected var triedCount = 0
|
||||
protected var injected = false
|
||||
protected open val maxRetries: Int = 3
|
||||
|
||||
protected abstract val serviceName: String
|
||||
protected abstract val injectionCommand: String
|
||||
protected abstract val processName: String
|
||||
|
||||
fun tryRunKeystoreInterceptor(): Boolean {
|
||||
Logger.i("Trying to register ${this::class.simpleName} (attempt $triedCount)...")
|
||||
|
||||
val service = getService() ?: return false
|
||||
val backdoor = getBinderBackdoor(service)
|
||||
|
||||
return if (backdoor != null) {
|
||||
setupInterceptor(service, backdoor)
|
||||
} else {
|
||||
handleMissingBackdoor()
|
||||
}
|
||||
}
|
||||
|
||||
protected open fun getService(): IBinder? = ServiceManager.getService(serviceName)
|
||||
|
||||
protected open fun setupInterceptor(service: IBinder, backdoor: IBinder): Boolean {
|
||||
keystore = service
|
||||
Logger.i("Registering for $serviceName: $keystore")
|
||||
|
||||
registerBinderInterceptor(backdoor, service, this)
|
||||
service.linkToDeath(createDeathRecipient(), 0)
|
||||
onInterceptorSetup(service, backdoor)
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
private fun handleMissingBackdoor(): Boolean {
|
||||
if (triedCount >= maxRetries) {
|
||||
Logger.e("Tried injection $maxRetries times but still no backdoor, exiting")
|
||||
exitProcess(1)
|
||||
}
|
||||
|
||||
if (!injected) {
|
||||
performInjection()
|
||||
injected = true
|
||||
}
|
||||
|
||||
triedCount++
|
||||
return false
|
||||
}
|
||||
|
||||
protected open fun performInjection() {
|
||||
Logger.i("Attempting to inject into $processName...")
|
||||
|
||||
val command = arrayOf("/system/bin/sh", "-c", injectionCommand)
|
||||
Logger.d("Injection command: ${command.joinToString(" ")}")
|
||||
|
||||
val process = Runtime.getRuntime().exec(command)
|
||||
|
||||
if (process.waitFor() != 0) {
|
||||
Logger.e("Injection failed! Daemon will exit")
|
||||
exitProcess(1)
|
||||
}
|
||||
|
||||
Logger.i("Injection completed successfully")
|
||||
}
|
||||
|
||||
protected open fun createDeathRecipient(): IBinder.DeathRecipient = object : IBinder.DeathRecipient {
|
||||
override fun binderDied() {
|
||||
Logger.d("$serviceName died, daemon restarting")
|
||||
exitProcess(0)
|
||||
}
|
||||
}
|
||||
|
||||
protected open fun onInterceptorSetup(service: IBinder, backdoor: IBinder) {
|
||||
// Default implementation does nothing
|
||||
}
|
||||
}
|
||||
|
||||
object InterceptorUtils {
|
||||
|
||||
fun createSuccessKeystoreResponse(): KeystoreResponse {
|
||||
val parcel = Parcel.obtain()
|
||||
try {
|
||||
parcel.writeInt(KeyStore.NO_ERROR)
|
||||
parcel.writeString("")
|
||||
parcel.setDataPosition(0)
|
||||
return KeystoreResponse.CREATOR.createFromParcel(parcel)
|
||||
} finally {
|
||||
parcel.recycle()
|
||||
}
|
||||
}
|
||||
|
||||
fun createSuccessReply(resultCode: Int = KeyStore.NO_ERROR): BinderInterceptor.OverrideReply {
|
||||
val parcel = Parcel.obtain()
|
||||
parcel.writeNoException()
|
||||
parcel.writeInt(resultCode)
|
||||
return BinderInterceptor.OverrideReply(0, parcel)
|
||||
}
|
||||
|
||||
fun createByteArrayReply(data: ByteArray, resultCode: Int = KeyStore.NO_ERROR): BinderInterceptor.OverrideReply {
|
||||
val parcel = Parcel.obtain()
|
||||
parcel.writeNoException()
|
||||
parcel.writeByteArray(data)
|
||||
return BinderInterceptor.OverrideReply(resultCode, parcel)
|
||||
}
|
||||
|
||||
fun <T : Parcelable?> createTypedObjectReply(obj: T, flags: Int = 0, resultCode: Int = 0): BinderInterceptor.OverrideReply {
|
||||
val parcel = Parcel.obtain()
|
||||
parcel.writeNoException()
|
||||
parcel.writeTypedObject(obj, flags)
|
||||
return BinderInterceptor.OverrideReply(resultCode, parcel)
|
||||
}
|
||||
|
||||
fun String.extractAlias(): String {
|
||||
return when {
|
||||
contains("_") -> split("_")[1]
|
||||
else -> this
|
||||
}
|
||||
}
|
||||
|
||||
fun Parcel.hasException(): Boolean {
|
||||
return kotlin.runCatching { readException() }.exceptionOrNull() != null
|
||||
}
|
||||
}
|
||||
+158
@@ -0,0 +1,158 @@
|
||||
/*
|
||||
* Copyright 2025 Dakkshesh <beakthoven@gmail.com>
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
package io.github.beakthoven.TrickyStoreOSS.interceptors
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.hardware.security.keymint.SecurityLevel
|
||||
import android.os.IBinder
|
||||
import android.os.Parcel
|
||||
import android.system.keystore2.IKeystoreService
|
||||
import android.system.keystore2.KeyDescriptor
|
||||
import android.system.keystore2.KeyEntryResponse
|
||||
import io.github.beakthoven.TrickyStoreOSS.CertificateHacker
|
||||
import io.github.beakthoven.TrickyStoreOSS.CertificateUtils
|
||||
import io.github.beakthoven.TrickyStoreOSS.core.config.Config
|
||||
import io.github.beakthoven.TrickyStoreOSS.core.logging.Logger
|
||||
import io.github.beakthoven.TrickyStoreOSS.getTransactCode
|
||||
import io.github.beakthoven.TrickyStoreOSS.interceptors.InterceptorUtils.createTypedObjectReply
|
||||
import io.github.beakthoven.TrickyStoreOSS.interceptors.InterceptorUtils.hasException
|
||||
import io.github.beakthoven.TrickyStoreOSS.putCertificateChain
|
||||
|
||||
@SuppressLint("BlockedPrivateApi")
|
||||
object Keystore2Interceptor : BaseKeystoreInterceptor() {
|
||||
private val getKeyEntryTransaction =
|
||||
getTransactCode(IKeystoreService.Stub::class.java, "getKeyEntry")
|
||||
private val deleteKeyTransaction =
|
||||
getTransactCode(IKeystoreService.Stub::class.java, "deleteKey")
|
||||
|
||||
override val serviceName = "android.system.keystore2.IKeystoreService/default"
|
||||
override val processName = "keystore2"
|
||||
override val injectionCommand = "exec ./inject `pidof keystore2` libTrickyStoreOSS.so entry"
|
||||
|
||||
private var teeInterceptor: SecurityLevelInterceptor? = null
|
||||
private var strongBoxInterceptor: SecurityLevelInterceptor? = null
|
||||
|
||||
override fun onInterceptorSetup(service: IBinder, backdoor: IBinder) {
|
||||
setupSecurityLevelInterceptors(service, backdoor)
|
||||
}
|
||||
|
||||
private fun setupSecurityLevelInterceptors(service: IBinder, backdoor: IBinder) {
|
||||
val ks = IKeystoreService.Stub.asInterface(service)
|
||||
|
||||
val tee = kotlin.runCatching { ks.getSecurityLevel(SecurityLevel.TRUSTED_ENVIRONMENT) }
|
||||
.getOrNull()
|
||||
if (tee != null) {
|
||||
Logger.i("Registering for TEE SecurityLevel: $tee")
|
||||
val interceptor = SecurityLevelInterceptor(tee, SecurityLevel.TRUSTED_ENVIRONMENT)
|
||||
registerBinderInterceptor(backdoor, tee.asBinder(), interceptor)
|
||||
teeInterceptor = interceptor
|
||||
} else {
|
||||
Logger.i("No TEE SecurityLevel found")
|
||||
}
|
||||
|
||||
val strongBox = kotlin.runCatching { ks.getSecurityLevel(SecurityLevel.STRONGBOX) }
|
||||
.getOrNull()
|
||||
if (strongBox != null) {
|
||||
Logger.i("Registering for StrongBox SecurityLevel: $strongBox")
|
||||
val interceptor = SecurityLevelInterceptor(strongBox, SecurityLevel.STRONGBOX)
|
||||
registerBinderInterceptor(backdoor, strongBox.asBinder(), interceptor)
|
||||
strongBoxInterceptor = interceptor
|
||||
} else {
|
||||
Logger.i("No StrongBox SecurityLevel found")
|
||||
}
|
||||
}
|
||||
|
||||
override fun onPreTransact(
|
||||
target: IBinder,
|
||||
code: Int,
|
||||
flags: Int,
|
||||
callingUid: Int,
|
||||
callingPid: Int,
|
||||
data: Parcel
|
||||
): Result {
|
||||
if (code == getKeyEntryTransaction) {
|
||||
if (CertificateHacker.canHack()) {
|
||||
Logger.d("intercept pre $target uid=$callingUid pid=$callingPid dataSz=${data.dataSize()}")
|
||||
kotlin.runCatching {
|
||||
data.enforceInterface(IKeystoreService.DESCRIPTOR)
|
||||
val descriptor = data.readTypedObject(KeyDescriptor.CREATOR) ?: return@runCatching
|
||||
if (Config.needGenerate(callingUid)) {
|
||||
val response = SecurityLevelInterceptor.getKeyResponse(callingUid, descriptor.alias)
|
||||
?: return@runCatching
|
||||
Logger.i("Generate key for uid=$callingUid alias=${descriptor.alias}")
|
||||
return createTypedObjectReply(response)
|
||||
} else if (Config.needHack(callingUid)) {
|
||||
if (SecurityLevelInterceptor.shouldSkipLeafHack(callingUid, descriptor.alias)) {
|
||||
Logger.i("skip leaf hack for uid=$callingUid alias=${descriptor.alias}")
|
||||
val response = SecurityLevelInterceptor.getKeyResponse(callingUid, descriptor.alias)
|
||||
if (response != null) {
|
||||
Logger.i("Found generated response for uid=$callingUid alias=${descriptor.alias}")
|
||||
return createTypedObjectReply(response)
|
||||
} else {
|
||||
Logger.e("No generated response found for uid=$callingUid alias=${descriptor.alias}")
|
||||
return@runCatching
|
||||
}
|
||||
} else {
|
||||
Logger.i("proceeding with leaf hack for uid=$callingUid alias=${descriptor.alias}")
|
||||
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 || reply == null) return Skip
|
||||
if (reply.hasException()) return Skip
|
||||
val p = Parcel.obtain()
|
||||
Logger.d("intercept post $target uid=$callingUid pid=$callingPid dataSz=${data.dataSize()} replySz=${reply.dataSize()}")
|
||||
|
||||
if (code == deleteKeyTransaction && resultCode == 0) {
|
||||
data.enforceInterface("android.system.keystore2.IKeystoreService")
|
||||
|
||||
val keyDescriptor = data.readTypedObject(KeyDescriptor.CREATOR)
|
||||
if (keyDescriptor == null || keyDescriptor.domain == 0) return Skip
|
||||
|
||||
SecurityLevelInterceptor.keys.remove(SecurityLevelInterceptor.Key(callingUid, keyDescriptor.alias))
|
||||
|
||||
return Skip
|
||||
} else if (code == getKeyEntryTransaction) {
|
||||
try {
|
||||
data.enforceInterface("android.system.keystore2.IKeystoreService")
|
||||
val response = reply.readTypedObject(KeyEntryResponse.CREATOR)
|
||||
if (response != null) {
|
||||
val chain = CertificateUtils.run { response.getCertificateChain() }
|
||||
if (chain != null) {
|
||||
val newChain = CertificateHacker.hackCertificateChain(chain)
|
||||
response.putCertificateChain(newChain).getOrThrow()
|
||||
Logger.i("Hacked certificate for uid=$callingUid")
|
||||
return createTypedObjectReply(response)
|
||||
} else {
|
||||
p.recycle()
|
||||
}
|
||||
} else {
|
||||
p.recycle()
|
||||
}
|
||||
} catch (t: Throwable) {
|
||||
Logger.e("failed to hack certificate chain of uid=$callingUid pid=$callingPid!", t)
|
||||
p.recycle()
|
||||
}
|
||||
}
|
||||
return Skip
|
||||
}
|
||||
}
|
||||
+238
@@ -0,0 +1,238 @@
|
||||
/*
|
||||
* Copyright 2025 Dakkshesh <beakthoven@gmail.com>
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
package io.github.beakthoven.TrickyStoreOSS.interceptors
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.os.IBinder
|
||||
import android.os.Parcel
|
||||
import android.security.Credentials
|
||||
import android.security.KeyStore
|
||||
import android.security.keymaster.ExportResult
|
||||
import android.security.keymaster.KeyCharacteristics
|
||||
import android.security.keymaster.KeymasterArguments
|
||||
import android.security.keymaster.KeymasterCertificateChain
|
||||
import android.security.keymaster.KeymasterDefs
|
||||
import android.security.keystore.IKeystoreCertificateChainCallback
|
||||
import android.security.keystore.IKeystoreExportKeyCallback
|
||||
import android.security.keystore.IKeystoreKeyCharacteristicsCallback
|
||||
import android.security.keystore.IKeystoreService
|
||||
import io.github.beakthoven.TrickyStoreOSS.CertificateHacker
|
||||
import io.github.beakthoven.TrickyStoreOSS.core.config.Config
|
||||
import io.github.beakthoven.TrickyStoreOSS.core.logging.Logger
|
||||
import io.github.beakthoven.TrickyStoreOSS.getTransactCode
|
||||
import io.github.beakthoven.TrickyStoreOSS.interceptors.InterceptorUtils.createByteArrayReply
|
||||
import io.github.beakthoven.TrickyStoreOSS.interceptors.InterceptorUtils.createSuccessKeystoreResponse
|
||||
import io.github.beakthoven.TrickyStoreOSS.interceptors.InterceptorUtils.createSuccessReply
|
||||
import io.github.beakthoven.TrickyStoreOSS.interceptors.InterceptorUtils.extractAlias
|
||||
import io.github.beakthoven.TrickyStoreOSS.interceptors.InterceptorUtils.hasException
|
||||
import java.math.BigInteger
|
||||
import java.security.KeyPair
|
||||
import java.util.Date
|
||||
|
||||
@SuppressLint("BlockedPrivateApi")
|
||||
object KeystoreInterceptor : BaseKeystoreInterceptor() {
|
||||
private val getTransaction =
|
||||
getTransactCode(IKeystoreService.Stub::class.java, "get")
|
||||
private val generateKeyTransaction =
|
||||
getTransactCode(IKeystoreService.Stub::class.java, "generateKey")
|
||||
private val getKeyCharacteristicsTransaction =
|
||||
getTransactCode(IKeystoreService.Stub::class.java, "getKeyCharacteristics")
|
||||
private val exportKeyTransaction =
|
||||
getTransactCode(IKeystoreService.Stub::class.java, "exportKey")
|
||||
private val attestKeyTransaction =
|
||||
getTransactCode(IKeystoreService.Stub::class.java, "attestKey")
|
||||
|
||||
override val serviceName = "android.security.keystore"
|
||||
override val processName = "keystore"
|
||||
override val injectionCommand = "exec ./inject `pidof keystore` libTrickyStoreOSS.so entry"
|
||||
|
||||
private const val DESCRIPTOR = "android.security.keystore.IKeystoreService"
|
||||
|
||||
private val keyArguments = HashMap<Key, CertificateHacker.KeyGenParameters>()
|
||||
private val keyPairs = HashMap<Key, KeyPair>()
|
||||
|
||||
data class Key(val uid: Int, val alias: String)
|
||||
|
||||
override fun onPreTransact(
|
||||
target: IBinder,
|
||||
code: Int,
|
||||
flags: Int,
|
||||
callingUid: Int,
|
||||
callingPid: Int,
|
||||
data: Parcel
|
||||
): Result {
|
||||
if (CertificateHacker.canHack()) {
|
||||
if (code == getTransaction) {
|
||||
if (Config.needHack(callingUid)) {
|
||||
return Continue
|
||||
} else if (Config.needGenerate(callingUid)) {
|
||||
return Skip
|
||||
}
|
||||
} else if (Config.needGenerate(callingUid)) {
|
||||
when (code) {
|
||||
generateKeyTransaction -> {
|
||||
kotlin.runCatching {
|
||||
data.enforceInterface(DESCRIPTOR)
|
||||
val callback = IKeystoreKeyCharacteristicsCallback.Stub.asInterface(data.readStrongBinder())
|
||||
val alias = data.readString()!!.extractAlias()
|
||||
Logger.i("generateKeyTransaction uid $callingUid alias $alias")
|
||||
val check = data.readInt()
|
||||
val kma = KeymasterArguments()
|
||||
val kgp = CertificateHacker.KeyGenParameters()
|
||||
if (check == 1) {
|
||||
kma.readFromParcel(data)
|
||||
kgp.algorithm = kma.getEnum(KeymasterDefs.KM_TAG_ALGORITHM, 0)
|
||||
kgp.keySize = kma.getUnsignedInt(KeymasterDefs.KM_TAG_KEY_SIZE, 0).toInt()
|
||||
kgp.setEcCurveName(kgp.keySize)
|
||||
kgp.purpose = kma.getEnums(KeymasterDefs.KM_TAG_PURPOSE)
|
||||
kgp.digest = kma.getEnums(KeymasterDefs.KM_TAG_DIGEST)
|
||||
kgp.certificateNotBefore = kma.getDate(KeymasterDefs.KM_TAG_ACTIVE_DATETIME, Date())
|
||||
if (kgp.algorithm == KeymasterDefs.KM_ALGORITHM_RSA) {
|
||||
try {
|
||||
val getArgumentByTag = KeymasterArguments::class.java.getDeclaredMethods().first { it.name == "getArgumentByTag" }
|
||||
getArgumentByTag.isAccessible = true
|
||||
val rsaArgument = getArgumentByTag.invoke(kma, KeymasterDefs.KM_TAG_RSA_PUBLIC_EXPONENT)
|
||||
|
||||
val getLongTagValue = KeymasterArguments::class.java.getDeclaredMethods().first { it.name == "getLongTagValue" }
|
||||
getLongTagValue.isAccessible = true
|
||||
kgp.rsaPublicExponent = getLongTagValue.invoke(kma, rsaArgument) as BigInteger
|
||||
} catch (ex: Exception) {
|
||||
Logger.e("Read rsaPublicExponent error", ex)
|
||||
}
|
||||
}
|
||||
keyArguments[Key(callingUid, alias)] = kgp
|
||||
}
|
||||
|
||||
val kc = KeyCharacteristics()
|
||||
kc.swEnforced = KeymasterArguments()
|
||||
kc.hwEnforced = kma
|
||||
|
||||
val ksr = createSuccessKeystoreResponse()
|
||||
callback.onFinished(ksr, kc)
|
||||
|
||||
return createSuccessReply()
|
||||
}.onFailure {
|
||||
Logger.e("generateKeyTransaction error", it)
|
||||
}
|
||||
}
|
||||
|
||||
getKeyCharacteristicsTransaction -> {
|
||||
kotlin.runCatching {
|
||||
data.enforceInterface(DESCRIPTOR)
|
||||
val callback = IKeystoreKeyCharacteristicsCallback.Stub.asInterface(data.readStrongBinder())
|
||||
val alias = data.readString()!!.extractAlias()
|
||||
Logger.i("getKeyCharacteristicsTransaction uid $callingUid alias $alias")
|
||||
val kc = KeyCharacteristics()
|
||||
val kma = KeymasterArguments()
|
||||
kma.addEnum(KeymasterDefs.KM_TAG_ALGORITHM, keyArguments[Key(callingUid, alias)]!!.algorithm)
|
||||
kc.swEnforced = KeymasterArguments()
|
||||
kc.hwEnforced = kma
|
||||
|
||||
val ksr = createSuccessKeystoreResponse()
|
||||
callback.onFinished(ksr, kc)
|
||||
|
||||
return createSuccessReply()
|
||||
}.onFailure {
|
||||
Logger.e("getKeyCharacteristicsTransaction error", it)
|
||||
}
|
||||
}
|
||||
|
||||
exportKeyTransaction -> {
|
||||
kotlin.runCatching {
|
||||
data.enforceInterface(DESCRIPTOR)
|
||||
val callback = IKeystoreExportKeyCallback.Stub.asInterface(data.readStrongBinder())
|
||||
val alias = data.readString()!!.extractAlias()
|
||||
Logger.i("exportKeyTransaction uid $callingUid alias $alias")
|
||||
val kp = CertificateHacker.generateKeyPair(keyArguments[Key(callingUid, alias)]!!)
|
||||
keyPairs[Key(callingUid, alias)] = kp!!
|
||||
|
||||
val erP = Parcel.obtain()
|
||||
erP.writeInt(KeyStore.NO_ERROR)
|
||||
erP.writeByteArray(kp.public.encoded)
|
||||
erP.setDataPosition(0)
|
||||
val er = ExportResult.CREATOR.createFromParcel(erP)
|
||||
erP.recycle()
|
||||
|
||||
callback.onFinished(er)
|
||||
|
||||
return createSuccessReply()
|
||||
}.onFailure {
|
||||
Logger.e("exportKeyTransaction error", it)
|
||||
}
|
||||
}
|
||||
|
||||
attestKeyTransaction -> {
|
||||
kotlin.runCatching {
|
||||
data.enforceInterface(DESCRIPTOR)
|
||||
val callback = IKeystoreCertificateChainCallback.Stub.asInterface(data.readStrongBinder())
|
||||
val alias = data.readString()!!.extractAlias()
|
||||
Logger.i("attestKeyTransaction uid $callingUid alias $alias")
|
||||
val check = data.readInt()
|
||||
val kma = KeymasterArguments()
|
||||
if (check == 1) {
|
||||
kma.readFromParcel(data)
|
||||
val attestationChallenge = kma.getBytes(KeymasterDefs.KM_TAG_ATTESTATION_CHALLENGE, ByteArray(0))
|
||||
|
||||
val ksr = createSuccessKeystoreResponse()
|
||||
|
||||
val key = Key(callingUid, alias)
|
||||
val ka = keyArguments[key]!!
|
||||
ka.attestationChallenge = attestationChallenge
|
||||
val chain = CertificateHacker.generateChain(callingUid, ka, keyPairs[key]!!)
|
||||
|
||||
val kcc = KeymasterCertificateChain(chain)
|
||||
callback.onFinished(ksr, kcc)
|
||||
}
|
||||
|
||||
return createSuccessReply()
|
||||
}.onFailure {
|
||||
Logger.e("attestKeyTransaction error", it)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
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 != getTransaction || reply == null) return Skip
|
||||
if (reply.hasException()) return Skip
|
||||
val p = Parcel.obtain()
|
||||
Logger.d("intercept post $target uid=$callingUid pid=$callingPid dataSz=${data.dataSize()} replySz=${reply.dataSize()}")
|
||||
try {
|
||||
data.enforceInterface(DESCRIPTOR)
|
||||
val alias = data.readString() ?: ""
|
||||
var response = reply.createByteArray()
|
||||
when {
|
||||
alias.startsWith(Credentials.USER_CERTIFICATE) -> {
|
||||
response = CertificateHacker.hackCertificateChainUSR(response!!, alias.extractAlias(), callingUid)
|
||||
Logger.i("Hacked leaf certificate for uid=$callingUid")
|
||||
return createByteArrayReply(response)
|
||||
}
|
||||
alias.startsWith(Credentials.CA_CERTIFICATE) -> {
|
||||
response = CertificateHacker.hackCertificateChainCA(response!!, alias.extractAlias(), callingUid)
|
||||
Logger.i("Hacked CA certificate chain for uid=$callingUid")
|
||||
return createByteArrayReply(response)
|
||||
}
|
||||
else -> p.recycle()
|
||||
}
|
||||
} catch (t: Throwable) {
|
||||
Logger.e("failed to hack certificate chain of uid=$callingUid pid=$callingPid!", t)
|
||||
p.recycle()
|
||||
}
|
||||
return Skip
|
||||
}
|
||||
}
|
||||
+180
@@ -0,0 +1,180 @@
|
||||
/*
|
||||
* Copyright 2025 Dakkshesh <beakthoven@gmail.com>
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
package io.github.beakthoven.TrickyStoreOSS.interceptors
|
||||
|
||||
import android.hardware.security.keymint.KeyParameter
|
||||
import android.hardware.security.keymint.KeyParameterValue
|
||||
import android.hardware.security.keymint.Tag
|
||||
import android.os.IBinder
|
||||
import android.os.Parcel
|
||||
import android.system.keystore2.Authorization
|
||||
import android.system.keystore2.IKeystoreSecurityLevel
|
||||
import android.system.keystore2.KeyDescriptor
|
||||
import android.system.keystore2.KeyEntryResponse
|
||||
import android.system.keystore2.KeyMetadata
|
||||
import androidx.annotation.Keep
|
||||
import io.github.beakthoven.TrickyStoreOSS.CertificateHacker
|
||||
import io.github.beakthoven.TrickyStoreOSS.core.config.Config
|
||||
import io.github.beakthoven.TrickyStoreOSS.core.logging.Logger
|
||||
import io.github.beakthoven.TrickyStoreOSS.getTransactCode
|
||||
import io.github.beakthoven.TrickyStoreOSS.putCertificateChain
|
||||
import java.security.KeyPair
|
||||
import java.security.cert.Certificate
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
|
||||
class SecurityLevelInterceptor(
|
||||
private val original: IKeystoreSecurityLevel,
|
||||
private val level: Int
|
||||
) : BinderInterceptor() {
|
||||
companion object {
|
||||
private val generateKeyTransaction =
|
||||
getTransactCode(IKeystoreSecurityLevel.Stub::class.java, "generateKey")
|
||||
private val deleteKeyTransaction =
|
||||
getTransactCode(IKeystoreSecurityLevel.Stub::class.java, "deleteKey")
|
||||
private val createOperationTransaction =
|
||||
getTransactCode(IKeystoreSecurityLevel.Stub::class.java, "createOperation")
|
||||
|
||||
@Keep
|
||||
val keys = ConcurrentHashMap<Key, Info>()
|
||||
|
||||
@Keep
|
||||
val keyPairs = ConcurrentHashMap<Key, Pair<KeyPair, List<Certificate>>>()
|
||||
|
||||
@Keep
|
||||
val skipLeafHacks = ConcurrentHashMap<Key, Boolean>()
|
||||
|
||||
@Keep
|
||||
fun getKeyResponse(uid: Int, alias: String): KeyEntryResponse? =
|
||||
keys[Key(uid, alias)]?.response
|
||||
|
||||
@Keep
|
||||
fun getKeyPairs(uid: Int, alias: String): Pair<KeyPair, List<Certificate>>? =
|
||||
keyPairs[Key(uid, alias)]
|
||||
|
||||
@Keep
|
||||
fun shouldSkipLeafHack(uid: Int, alias: String): Boolean =
|
||||
skipLeafHacks[Key(uid, alias)] ?: false
|
||||
}
|
||||
|
||||
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) {
|
||||
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 = CertificateHacker.KeyGenParameters(params)
|
||||
if (Config.needGenerate(callingUid)) {
|
||||
val pair = CertificateHacker.generateKeyPair(callingUid, keyDescriptor, attestationKeyDescriptor, kgp)
|
||||
?: return@runCatching
|
||||
keyPairs[Key(callingUid, keyDescriptor.alias)] = Pair(pair.first, pair.second)
|
||||
val response = buildResponse(pair.second, kgp, attestationKeyDescriptor ?: 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)
|
||||
} else if (Config.needHack(callingUid)) {
|
||||
if ((kgp.purpose.contains(7)) || (attestationKeyDescriptor != null)) {
|
||||
Logger.i("Generating key in generation mode for attestation: uid=$callingUid alias=${keyDescriptor.alias}")
|
||||
val pair = CertificateHacker.generateKeyPair(callingUid, keyDescriptor, attestationKeyDescriptor, kgp)
|
||||
?: return@runCatching
|
||||
keyPairs[Key(callingUid, keyDescriptor.alias)] = Pair(pair.first, pair.second)
|
||||
val response = buildResponse(pair.second, kgp, attestationKeyDescriptor ?: keyDescriptor)
|
||||
keys[Key(callingUid, keyDescriptor.alias)] = Info(pair.first, response)
|
||||
SecurityLevelInterceptor.skipLeafHacks[Key(callingUid, keyDescriptor.alias)] = true
|
||||
val p = Parcel.obtain()
|
||||
p.writeNoException()
|
||||
p.writeTypedObject(response.metadata, 0)
|
||||
return OverrideReply(0, p)
|
||||
} else {
|
||||
skipLeafHacks.remove(Key(callingUid, keyDescriptor.alias))
|
||||
Logger.i("Cleared skip flag for non-attestation key: uid=$callingUid alias=${keyDescriptor.alias}")
|
||||
return Skip
|
||||
}
|
||||
}
|
||||
}.onFailure {
|
||||
Logger.e("parse key gen request", it)
|
||||
}
|
||||
}
|
||||
return Skip
|
||||
}
|
||||
|
||||
private fun buildResponse(
|
||||
chain: List<Certificate>,
|
||||
params: CertificateHacker.KeyGenParameters,
|
||||
descriptor: KeyDescriptor
|
||||
): KeyEntryResponse {
|
||||
val response = KeyEntryResponse()
|
||||
val metadata = KeyMetadata()
|
||||
metadata.keySecurityLevel = level
|
||||
metadata.putCertificateChain(chain.toTypedArray()).getOrThrow()
|
||||
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.toList()) {
|
||||
a = Authorization()
|
||||
a.keyParameter = KeyParameter()
|
||||
a.keyParameter.tag = Tag.PURPOSE
|
||||
a.keyParameter.value = KeyParameterValue.keyPurpose(i)
|
||||
a.securityLevel = level
|
||||
authorizations.add(a)
|
||||
}
|
||||
for (i in params.digest.toList()) {
|
||||
a = Authorization()
|
||||
a.keyParameter = KeyParameter()
|
||||
a.keyParameter.tag = Tag.DIGEST
|
||||
a.keyParameter.value = KeyParameterValue.digest(i)
|
||||
a.securityLevel = level
|
||||
authorizations.add(a)
|
||||
}
|
||||
a = Authorization()
|
||||
a.keyParameter = KeyParameter()
|
||||
a.keyParameter.tag = Tag.ALGORITHM
|
||||
a.keyParameter.value = KeyParameterValue.algorithm(params.algorithm)
|
||||
a.securityLevel = level
|
||||
authorizations.add(a)
|
||||
a = Authorization()
|
||||
a.keyParameter = KeyParameter()
|
||||
a.keyParameter.tag = Tag.KEY_SIZE
|
||||
a.keyParameter.value = KeyParameterValue.integer(params.keySize)
|
||||
a.securityLevel = level
|
||||
authorizations.add(a)
|
||||
a = Authorization()
|
||||
a.keyParameter = KeyParameter()
|
||||
a.keyParameter.tag = Tag.EC_CURVE
|
||||
a.keyParameter.value = KeyParameterValue.ecCurve(params.ecCurve)
|
||||
a.securityLevel = level
|
||||
authorizations.add(a)
|
||||
a = Authorization()
|
||||
a.keyParameter = KeyParameter()
|
||||
a.keyParameter.tag = Tag.NO_AUTH_REQUIRED
|
||||
a.keyParameter.value = KeyParameterValue.boolValue(true)
|
||||
a.securityLevel = level
|
||||
authorizations.add(a)
|
||||
metadata.authorizations = authorizations.toTypedArray<Authorization>()
|
||||
response.metadata = metadata
|
||||
response.iSecurityLevel = original
|
||||
return response
|
||||
}
|
||||
}
|
||||
@@ -6,5 +6,6 @@
|
||||
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
||||
plugins {
|
||||
alias(libs.plugins.android.application) apply false
|
||||
alias(libs.plugins.android.library) apply false
|
||||
alias(libs.plugins.kotlin.android) apply false
|
||||
}
|
||||
@@ -1,10 +1,19 @@
|
||||
[versions]
|
||||
agp = "8.12.0"
|
||||
annotation = "1.9.1"
|
||||
hidden-api = "4.4.0"
|
||||
jdk18on = "1.81"
|
||||
kotlin = "2.2.0"
|
||||
libcxx = "28.1.13356709"
|
||||
|
||||
[libraries]
|
||||
annotation = { module = "androidx.annotation:annotation", version.ref = "annotation" }
|
||||
dev-rikka-hidden-stub = { module = "dev.rikka.hidden:stub", version.ref = "hidden-api" }
|
||||
org-bouncycastle-bcpkix-jdk18on = { module = "org.bouncycastle:bcpkix-jdk18on", version.ref = "jdk18on" }
|
||||
org-lsposed-libcxx-libcxx = { module = "org.lsposed.libcxx:libcxx", version.ref = "libcxx" }
|
||||
|
||||
[plugins]
|
||||
android-application = { id = "com.android.application", version.ref = "agp" }
|
||||
android-library = { id = "com.android.library", version.ref = "agp" }
|
||||
kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
|
||||
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
#!/sbin/sh
|
||||
|
||||
#################
|
||||
# Initialization
|
||||
#################
|
||||
|
||||
umask 022
|
||||
|
||||
# echo before loading util_functions
|
||||
ui_print() { echo "$1"; }
|
||||
|
||||
require_new_magisk() {
|
||||
ui_print "*******************************"
|
||||
ui_print " Please install Magisk v20.4+! "
|
||||
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 20400 ] && require_new_magisk
|
||||
|
||||
install_module
|
||||
exit 0
|
||||
@@ -0,0 +1 @@
|
||||
#MAGISK
|
||||
@@ -0,0 +1,85 @@
|
||||
# shellcheck disable=SC2034
|
||||
SKIPUNZIP=1
|
||||
MIN_SDK=29
|
||||
CONFIG_DIR=/data/adb/tricky_store
|
||||
|
||||
# --- Installation Context Check ---
|
||||
if [ "$BOOTMODE" != true ]; then
|
||||
ui_print "! Please install in Magisk Manager or KernelSU Manager"
|
||||
abort "! Install from recovery is NOT supported"
|
||||
fi
|
||||
|
||||
if [ "$KSU" = true ] && [ "$KSU_VER_CODE" -lt 10670 ]; then
|
||||
abort "! Please update your KernelSU and KernelSU Manager"
|
||||
fi
|
||||
|
||||
# --- Version Info ---
|
||||
VERSION=$(grep_prop version "${TMPDIR}/module.prop")
|
||||
ui_print "- Installing Tricky Store OSS $VERSION"
|
||||
ui_print ""
|
||||
|
||||
# --- Architecture Handling ---
|
||||
case "$ARCH" in
|
||||
arm64) ABI_DIR="arm64-v8a" ;;
|
||||
arm) ABI_DIR="armeabi-v7a" ;;
|
||||
x64) ABI_DIR="x86_64" ;;
|
||||
x86) ABI_DIR="x86" ;;
|
||||
*) abort "! Unsupported architecture: $ARCH" ;;
|
||||
esac
|
||||
|
||||
ui_print "- Device platform: $ARCH"
|
||||
ui_print "- Using ABI dir: $ABI_DIR"
|
||||
|
||||
# --- SDK Check ---
|
||||
if [ "$API" -lt "$MIN_SDK" ]; then
|
||||
abort "! Unsupported SDK: $API. Minimum required is $MIN_SDK"
|
||||
else
|
||||
ui_print "- Device SDK: $API"
|
||||
fi
|
||||
ui_print ""
|
||||
|
||||
# --- Helper to install files ---
|
||||
install_file() {
|
||||
if ! unzip -qqjo "$ZIPFILE" "$1" -d "$2"; then
|
||||
abort "! Failed to extract $1"
|
||||
fi
|
||||
ui_print "- Extracted $1"
|
||||
}
|
||||
|
||||
# --- Installation ---
|
||||
ui_print "- Extracting module files"
|
||||
for file in customize.sh module.prop post-fs-data.sh service.sh sepolicy.rule daemon; do
|
||||
install_file "$file" "$MODPATH"
|
||||
done
|
||||
|
||||
# Handle service.apk or classes.dex
|
||||
if unzip -l "$ZIPFILE" | grep -q "service.apk"; then
|
||||
install_file "service.apk" "$MODPATH"
|
||||
elif unzip -l "$ZIPFILE" | grep -q "classes.dex"; then
|
||||
install_file "classes.dex" "$MODPATH"
|
||||
else
|
||||
abort "! Neither service.apk nor classes.dex found"
|
||||
fi
|
||||
|
||||
chmod 755 "$MODPATH/daemon"
|
||||
ui_print ""
|
||||
|
||||
|
||||
ui_print "- Extracting $ARCH libraries"
|
||||
install_file "lib/$ABI_DIR/libTrickyStoreOSS.so" "$MODPATH"
|
||||
install_file "lib/$ABI_DIR/libinject.so" "$MODPATH"
|
||||
ui_print ""
|
||||
|
||||
mv "$MODPATH/libinject.so" "$MODPATH/inject"
|
||||
chmod 755 "$MODPATH/inject"
|
||||
|
||||
# --- Configuration Files ---
|
||||
if [ ! -d "$CONFIG_DIR" ]; then
|
||||
ui_print "- Creating configuration directory"
|
||||
mkdir -p "$CONFIG_DIR"
|
||||
fi
|
||||
|
||||
if [ ! -f "$CONFIG_DIR/target.txt" ]; then
|
||||
ui_print "- Adding default target scope"
|
||||
install_file "target.txt" "$CONFIG_DIR"
|
||||
fi
|
||||
@@ -0,0 +1,15 @@
|
||||
#!/system/bin/sh
|
||||
|
||||
MODDIR=$1
|
||||
|
||||
# Determine classpath
|
||||
if [ -f "$MODDIR/classes.dex" ]; then
|
||||
CLASSPATH="$MODDIR/classes.dex"
|
||||
elif [ -f "$MODDIR/service.apk" ]; then
|
||||
CLASSPATH="$MODDIR/service.apk"
|
||||
else
|
||||
echo "Error: Neither classes.dex nor service.apk found in $MODDIR"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
exec /system/bin/app_process -Djava.class.path="$CLASSPATH" "$MODDIR" --nice-name=TrickyStoreOSS io.github.beakthoven.TrickyStoreOSS.MainKt
|
||||
@@ -0,0 +1,7 @@
|
||||
id=tricky_store
|
||||
name=Tricky Store OSS
|
||||
version=REPLACEMEVER
|
||||
versionCode=REPLACEMEVERCODE
|
||||
author=beakthoven
|
||||
description=A trick of keystore but open source.
|
||||
updateJson=https://raw.githubusercontent.com/beakthoven/TrickyStore/changelog/update.json
|
||||
@@ -0,0 +1 @@
|
||||
MODDIR=${0%/*}
|
||||
@@ -0,0 +1,4 @@
|
||||
allow keystore system_file unix_dgram_socket *
|
||||
allow system_file keystore unix_dgram_socket *
|
||||
allow keystore system_file file *
|
||||
allow crash_dump keystore process *
|
||||
@@ -0,0 +1,14 @@
|
||||
DEBUG=false
|
||||
|
||||
MODDIR=${0%/*}
|
||||
|
||||
cd $MODDIR
|
||||
|
||||
(
|
||||
while [ true ]; do
|
||||
$MODDIR/daemon "$MODDIR"
|
||||
if [ $? -ne 0 ]; then
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
) &
|
||||
@@ -0,0 +1,6 @@
|
||||
com.google.android.gsf
|
||||
com.google.android.gms
|
||||
com.android.vending
|
||||
io.github.vvb2060.keyattestation
|
||||
io.github.vvb2060.mahoshojo
|
||||
icu.nullptr.nativetest
|
||||
+1
-1
@@ -25,4 +25,4 @@ dependencyResolutionManagement {
|
||||
}
|
||||
|
||||
rootProject.name = "Tricky Store OSS"
|
||||
include(":app")
|
||||
include(":app", ":stub")
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
/build
|
||||
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* Copyright 2025 Dakkshesh <beakthoven@gmail.com>
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
plugins {
|
||||
alias(libs.plugins.android.library)
|
||||
}
|
||||
|
||||
|
||||
android {
|
||||
namespace = "io.github.beakthoven.stub"
|
||||
compileSdk = 36
|
||||
buildToolsVersion = "36.0.0"
|
||||
defaultConfig {
|
||||
minSdk = 29
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
release {
|
||||
isMinifyEnabled = false
|
||||
}
|
||||
}
|
||||
|
||||
lint {
|
||||
checkReleaseBuilds = false
|
||||
abortOnError = true
|
||||
}
|
||||
compileOptions {
|
||||
sourceCompatibility = JavaVersion.VERSION_21
|
||||
targetCompatibility = JavaVersion.VERSION_21
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compileOnly(libs.annotation)
|
||||
compileOnly(libs.dev.rikka.hidden.stub)
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
Copyright 2025 Dakkshesh <beakthoven@gmail.com>
|
||||
SPDX-License-Identifier: GPL-3.0-or-later
|
||||
-->
|
||||
|
||||
<manifest/>
|
||||
@@ -0,0 +1,12 @@
|
||||
/*
|
||||
* Copyright 2025 Dakkshesh <beakthoven@gmail.com>
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
package android.app;
|
||||
|
||||
public class ActivityThread {
|
||||
public static void initializeMainlineModules() {
|
||||
throw new RuntimeException("");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
/*
|
||||
* Copyright 2025 Dakkshesh <beakthoven@gmail.com>
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
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);
|
||||
|
||||
ParceledListSlice<PackageInfo> getInstalledPackages(int flags, int userId);
|
||||
|
||||
ParceledListSlice<PackageInfo> getInstalledPackages(long flags, int userId);
|
||||
|
||||
class Stub {
|
||||
public static IPackageManager asInterface(IBinder binder) {
|
||||
throw new RuntimeException("");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
/*
|
||||
* Copyright 2025 Dakkshesh <beakthoven@gmail.com>
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
package android.hardware.security.keymint;
|
||||
|
||||
public @interface Algorithm {
|
||||
int AES = 32;
|
||||
int EC = 3;
|
||||
int HMAC = 128;
|
||||
int RSA = 1;
|
||||
int TRIPLE_DES = 33;
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
/*
|
||||
* Copyright 2025 Dakkshesh <beakthoven@gmail.com>
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
package android.hardware.security.keymint;
|
||||
|
||||
/* loaded from: classes2.dex */
|
||||
public @interface EcCurve {
|
||||
public static final int CURVE_25519 = 4;
|
||||
public static final int P_224 = 0;
|
||||
public static final int P_256 = 1;
|
||||
public static final int P_384 = 2;
|
||||
public static final int P_521 = 3;
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* Copyright 2025 Dakkshesh <beakthoven@gmail.com>
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
package android.hardware.security.keymint;
|
||||
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
public class KeyParameter implements Parcelable {
|
||||
public static final Creator<KeyParameter> CREATOR = new Creator<>() {
|
||||
@Override
|
||||
public KeyParameter createFromParcel(Parcel in) {
|
||||
throw new RuntimeException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public KeyParameter[] newArray(int size) {
|
||||
throw new RuntimeException();
|
||||
}
|
||||
};
|
||||
public int tag = 0;
|
||||
public KeyParameterValue value;
|
||||
|
||||
@Override
|
||||
public int describeContents() {
|
||||
throw new RuntimeException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeToParcel(@NonNull Parcel parcel, int i) {
|
||||
throw new RuntimeException();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,243 @@
|
||||
/*
|
||||
* Copyright 2025 Dakkshesh <beakthoven@gmail.com>
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
package android.hardware.security.keymint;
|
||||
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
/* loaded from: classes2.dex */
|
||||
public final class KeyParameterValue implements Parcelable {
|
||||
public static final int algorithm = 1;
|
||||
public static final int blob = 14;
|
||||
public static final int blockMode = 2;
|
||||
public static final int boolValue = 10;
|
||||
public static final int dateTime = 13;
|
||||
public static final int digest = 4;
|
||||
public static final int ecCurve = 5;
|
||||
public static final int hardwareAuthenticatorType = 8;
|
||||
public static final int integer = 11;
|
||||
public static final int invalid = 0;
|
||||
public static final int keyPurpose = 7;
|
||||
public static final int longInteger = 12;
|
||||
public static final int origin = 6;
|
||||
public static final int paddingMode = 3;
|
||||
public static final int securityLevel = 9;
|
||||
public static final Creator<KeyParameterValue> CREATOR = new Creator<KeyParameterValue>() {
|
||||
@Override
|
||||
public KeyParameterValue createFromParcel(Parcel in) {
|
||||
throw new RuntimeException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public KeyParameterValue[] newArray(int size) {
|
||||
throw new RuntimeException();
|
||||
}
|
||||
};
|
||||
|
||||
public KeyParameterValue() {
|
||||
throw new RuntimeException();
|
||||
}
|
||||
|
||||
protected KeyParameterValue(Parcel in) {
|
||||
throw new RuntimeException();
|
||||
}
|
||||
|
||||
public static KeyParameterValue invalid(int _value) {
|
||||
throw new RuntimeException();
|
||||
}
|
||||
|
||||
public static KeyParameterValue algorithm(int _value) {
|
||||
throw new RuntimeException();
|
||||
}
|
||||
|
||||
public static KeyParameterValue blockMode(int _value) {
|
||||
throw new RuntimeException();
|
||||
}
|
||||
|
||||
public static KeyParameterValue paddingMode(int _value) {
|
||||
throw new RuntimeException();
|
||||
}
|
||||
|
||||
public static KeyParameterValue digest(int _value) {
|
||||
throw new RuntimeException();
|
||||
}
|
||||
|
||||
public static KeyParameterValue ecCurve(int _value) {
|
||||
throw new RuntimeException();
|
||||
}
|
||||
|
||||
public static KeyParameterValue origin(int _value) {
|
||||
throw new RuntimeException();
|
||||
}
|
||||
|
||||
public static KeyParameterValue keyPurpose(int _value) {
|
||||
throw new RuntimeException();
|
||||
}
|
||||
|
||||
public static KeyParameterValue hardwareAuthenticatorType(int _value) {
|
||||
throw new RuntimeException();
|
||||
}
|
||||
|
||||
public static KeyParameterValue securityLevel(int _value) {
|
||||
throw new RuntimeException();
|
||||
}
|
||||
|
||||
public static KeyParameterValue boolValue(boolean _value) {
|
||||
throw new RuntimeException();
|
||||
}
|
||||
|
||||
public static KeyParameterValue integer(int _value) {
|
||||
throw new RuntimeException();
|
||||
}
|
||||
|
||||
public static KeyParameterValue longInteger(long _value) {
|
||||
throw new RuntimeException();
|
||||
}
|
||||
|
||||
public static KeyParameterValue dateTime(long _value) {
|
||||
throw new RuntimeException();
|
||||
}
|
||||
|
||||
public static KeyParameterValue blob(byte[] _value) {
|
||||
throw new RuntimeException();
|
||||
}
|
||||
|
||||
public int getTag() {
|
||||
throw new RuntimeException();
|
||||
}
|
||||
|
||||
public int getInvalid() {
|
||||
throw new RuntimeException();
|
||||
}
|
||||
|
||||
public void setInvalid(int _value) {
|
||||
throw new RuntimeException();
|
||||
}
|
||||
|
||||
public int getAlgorithm() {
|
||||
throw new RuntimeException();
|
||||
}
|
||||
|
||||
public void setAlgorithm(int _value) {
|
||||
throw new RuntimeException();
|
||||
}
|
||||
|
||||
public int getBlockMode() {
|
||||
throw new RuntimeException();
|
||||
}
|
||||
|
||||
public void setBlockMode(int _value) {
|
||||
throw new RuntimeException();
|
||||
}
|
||||
|
||||
public int getPaddingMode() {
|
||||
throw new RuntimeException();
|
||||
}
|
||||
|
||||
public void setPaddingMode(int _value) {
|
||||
throw new RuntimeException();
|
||||
}
|
||||
|
||||
public int getDigest() {
|
||||
throw new RuntimeException();
|
||||
}
|
||||
|
||||
public void setDigest(int _value) {
|
||||
throw new RuntimeException();
|
||||
}
|
||||
|
||||
public int getEcCurve() {
|
||||
throw new RuntimeException();
|
||||
}
|
||||
|
||||
public void setEcCurve(int _value) {
|
||||
throw new RuntimeException();
|
||||
}
|
||||
|
||||
public int getOrigin() {
|
||||
throw new RuntimeException();
|
||||
}
|
||||
|
||||
public void setOrigin(int _value) {
|
||||
throw new RuntimeException();
|
||||
}
|
||||
|
||||
public int getKeyPurpose() {
|
||||
throw new RuntimeException();
|
||||
}
|
||||
|
||||
public void setKeyPurpose(int _value) {
|
||||
throw new RuntimeException();
|
||||
}
|
||||
|
||||
public int getHardwareAuthenticatorType() {
|
||||
throw new RuntimeException();
|
||||
}
|
||||
|
||||
public void setHardwareAuthenticatorType(int _value) {
|
||||
throw new RuntimeException();
|
||||
}
|
||||
|
||||
public int getSecurityLevel() {
|
||||
throw new RuntimeException();
|
||||
}
|
||||
|
||||
public void setSecurityLevel(int _value) {
|
||||
throw new RuntimeException();
|
||||
}
|
||||
|
||||
public boolean getBoolValue() {
|
||||
throw new RuntimeException();
|
||||
}
|
||||
|
||||
public void setBoolValue(boolean _value) {
|
||||
throw new RuntimeException();
|
||||
}
|
||||
|
||||
public int getInteger() {
|
||||
throw new RuntimeException();
|
||||
}
|
||||
|
||||
public void setInteger(int _value) {
|
||||
throw new RuntimeException();
|
||||
}
|
||||
|
||||
public long getLongInteger() {
|
||||
throw new RuntimeException();
|
||||
}
|
||||
|
||||
public void setLongInteger(long _value) {
|
||||
throw new RuntimeException();
|
||||
}
|
||||
|
||||
public long getDateTime() {
|
||||
throw new RuntimeException();
|
||||
}
|
||||
|
||||
public void setDateTime(long _value) {
|
||||
throw new RuntimeException();
|
||||
}
|
||||
|
||||
public byte[] getBlob() {
|
||||
throw new RuntimeException();
|
||||
}
|
||||
|
||||
public void setBlob(byte[] _value) {
|
||||
throw new RuntimeException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int describeContents() {
|
||||
throw new RuntimeException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeToParcel(@NonNull Parcel parcel, int i) {
|
||||
throw new RuntimeException();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
/*
|
||||
* Copyright 2025 Dakkshesh <beakthoven@gmail.com>
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
package android.hardware.security.keymint;
|
||||
|
||||
public @interface KeyPurpose {
|
||||
int AGREE_KEY = 6;
|
||||
int ATTEST_KEY = 7;
|
||||
int DECRYPT = 1;
|
||||
int ENCRYPT = 0;
|
||||
int SIGN = 2;
|
||||
int VERIFY = 3;
|
||||
int WRAP_KEY = 5;
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
/*
|
||||
* Copyright 2025 Dakkshesh <beakthoven@gmail.com>
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
package android.hardware.security.keymint;
|
||||
|
||||
public @interface SecurityLevel {
|
||||
int KEYSTORE = 100;
|
||||
int SOFTWARE = 0;
|
||||
int STRONGBOX = 2;
|
||||
int TRUSTED_ENVIRONMENT = 1;
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
/*
|
||||
* Copyright 2025 Dakkshesh <beakthoven@gmail.com>
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
package android.hardware.security.keymint;
|
||||
|
||||
public @interface Tag {
|
||||
int INVALID = 0;
|
||||
int PURPOSE = 536870913;
|
||||
int ALGORITHM = 268435458;
|
||||
int KEY_SIZE = 805306371;
|
||||
int BLOCK_MODE = 536870916;
|
||||
int DIGEST = 536870917;
|
||||
int PADDING = 536870918;
|
||||
int CALLER_NONCE = 1879048199;
|
||||
int MIN_MAC_LENGTH = 805306376;
|
||||
int EC_CURVE = 268435466;
|
||||
int RSA_PUBLIC_EXPONENT = 1342177480;
|
||||
int INCLUDE_UNIQUE_ID = 1879048394;
|
||||
int RSA_OAEP_MGF_DIGEST = 536871115;
|
||||
int BOOTLOADER_ONLY = 1879048494;
|
||||
int ROLLBACK_RESISTANCE = 1879048495;
|
||||
int HARDWARE_TYPE = 268435760;
|
||||
int EARLY_BOOT_ONLY = 1879048497;
|
||||
int ACTIVE_DATETIME = 1610613136;
|
||||
int ORIGINATION_EXPIRE_DATETIME = 1610613137;
|
||||
int USAGE_EXPIRE_DATETIME = 1610613138;
|
||||
int MIN_SECONDS_BETWEEN_OPS = 805306771;
|
||||
int MAX_USES_PER_BOOT = 805306772;
|
||||
int USAGE_COUNT_LIMIT = 805306773;
|
||||
int USER_ID = 805306869;
|
||||
int USER_SECURE_ID = -1610612234;
|
||||
int NO_AUTH_REQUIRED = 1879048695;
|
||||
int USER_AUTH_TYPE = 268435960;
|
||||
int AUTH_TIMEOUT = 805306873;
|
||||
int ALLOW_WHILE_ON_BODY = 1879048698;
|
||||
int TRUSTED_USER_PRESENCE_REQUIRED = 1879048699;
|
||||
int TRUSTED_CONFIRMATION_REQUIRED = 1879048700;
|
||||
int UNLOCKED_DEVICE_REQUIRED = 1879048701;
|
||||
int APPLICATION_ID = -1879047591;
|
||||
int APPLICATION_DATA = -1879047492;
|
||||
int CREATION_DATETIME = 1610613437;
|
||||
int ORIGIN = 268436158;
|
||||
int ROOT_OF_TRUST = -1879047488;
|
||||
int OS_VERSION = 805307073;
|
||||
int OS_PATCHLEVEL = 805307074;
|
||||
int UNIQUE_ID = -1879047485;
|
||||
int ATTESTATION_CHALLENGE = -1879047484;
|
||||
int ATTESTATION_APPLICATION_ID = -1879047483;
|
||||
int ATTESTATION_ID_BRAND = -1879047482;
|
||||
int ATTESTATION_ID_DEVICE = -1879047481;
|
||||
int ATTESTATION_ID_PRODUCT = -1879047480;
|
||||
int ATTESTATION_ID_SERIAL = -1879047479;
|
||||
int ATTESTATION_ID_IMEI = -1879047478;
|
||||
int ATTESTATION_ID_MEID = -1879047477;
|
||||
int ATTESTATION_ID_MANUFACTURER = -1879047476;
|
||||
int ATTESTATION_ID_MODEL = -1879047475;
|
||||
int VENDOR_PATCHLEVEL = 805307086;
|
||||
int BOOT_PATCHLEVEL = 805307087;
|
||||
int DEVICE_UNIQUE_ATTESTATION = 1879048912;
|
||||
int IDENTITY_CREDENTIAL_KEY = 1879048913;
|
||||
int STORAGE_KEY = 1879048914;
|
||||
int ATTESTATION_ID_SECOND_IMEI = -1879047469;
|
||||
int ASSOCIATED_DATA = -1879047192;
|
||||
int NONCE = -1879047191;
|
||||
int MAC_LENGTH = 805307371;
|
||||
int RESET_SINCE_ID_ROTATION = 1879049196;
|
||||
int CONFIRMATION_TOKEN = -1879047187;
|
||||
int CERTIFICATE_SERIAL = -2147482642;
|
||||
int CERTIFICATE_SUBJECT = -1879047185;
|
||||
int CERTIFICATE_NOT_BEFORE = 1610613744;
|
||||
int CERTIFICATE_NOT_AFTER = 1610613745;
|
||||
int MAX_BOOT_LEVEL = 805307378;
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
/*
|
||||
* Copyright 2025 Dakkshesh <beakthoven@gmail.com>
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
package android.os;
|
||||
|
||||
public class ServiceManager {
|
||||
public static IBinder getService(String name) {
|
||||
throw new UnsupportedOperationException("STUB!");
|
||||
}
|
||||
|
||||
public static void addService(String name, IBinder binder) {
|
||||
throw new UnsupportedOperationException("STUB!");
|
||||
}
|
||||
|
||||
public static IBinder checkService(String name) {
|
||||
throw new UnsupportedOperationException("STUB!");
|
||||
}
|
||||
|
||||
public static String[] listServices() {
|
||||
throw new UnsupportedOperationException("STUB!");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
/*
|
||||
* Copyright 2025 Dakkshesh <beakthoven@gmail.com>
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
package android.os;
|
||||
|
||||
public class SystemProperties {
|
||||
public static String get(String key, String def) {
|
||||
throw new RuntimeException("");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* Copyright 2025 Dakkshesh <beakthoven@gmail.com>
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
package android.security;
|
||||
|
||||
public class Credentials {
|
||||
public static final String APP_SOURCE_CERTIFICATE = "FSV_";
|
||||
public static final String CA_CERTIFICATE = "CACERT_";
|
||||
public static final String CERTIFICATE_USAGE_APP_SOURCE = "appsrc";
|
||||
public static final String CERTIFICATE_USAGE_CA = "ca";
|
||||
public static final String CERTIFICATE_USAGE_USER = "user";
|
||||
public static final String CERTIFICATE_USAGE_WIFI = "wifi";
|
||||
public static final String EXTENSION_CER = ".cer";
|
||||
public static final String EXTENSION_CRT = ".crt";
|
||||
public static final String EXTENSION_P12 = ".p12";
|
||||
public static final String EXTENSION_PFX = ".pfx";
|
||||
public static final String EXTRA_CA_CERTIFICATES_DATA = "ca_certificates_data";
|
||||
public static final String EXTRA_CERTIFICATE_USAGE = "certificate_install_usage";
|
||||
public static final String EXTRA_INSTALL_AS_UID = "install_as_uid";
|
||||
public static final String EXTRA_PRIVATE_KEY = "PKEY";
|
||||
public static final String EXTRA_PUBLIC_KEY = "KEY";
|
||||
public static final String EXTRA_USER_CERTIFICATE_DATA = "user_certificate_data";
|
||||
public static final String EXTRA_USER_KEY_ALIAS = "user_key_pair_name";
|
||||
public static final String EXTRA_USER_PRIVATE_KEY_DATA = "user_private_key_data";
|
||||
public static final String INSTALL_ACTION = "android.credentials.INSTALL";
|
||||
public static final String INSTALL_AS_USER_ACTION = "android.credentials.INSTALL_AS_USER";
|
||||
public static final String LOCKDOWN_VPN = "LOCKDOWN_VPN";
|
||||
private static final String LOGTAG = "Credentials";
|
||||
public static final String PLATFORM_VPN = "PLATFORM_VPN_";
|
||||
public static final String USER_CERTIFICATE = "USRCERT_";
|
||||
public static final String USER_PRIVATE_KEY = "USRPKEY_";
|
||||
public static final String USER_SECRET_KEY = "USRSKEY_";
|
||||
public static final String VPN = "VPN_";
|
||||
public static final String WIFI = "WIFI_";
|
||||
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* Copyright 2025 Dakkshesh <beakthoven@gmail.com>
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
package android.security;
|
||||
|
||||
public class KeyStore {
|
||||
public static final int CANNOT_ATTEST_IDS = -66;
|
||||
public static final int CONFIRMATIONUI_ABORTED = 2;
|
||||
public static final int CONFIRMATIONUI_CANCELED = 1;
|
||||
public static final int CONFIRMATIONUI_IGNORED = 4;
|
||||
public static final int CONFIRMATIONUI_OK = 0;
|
||||
public static final int CONFIRMATIONUI_OPERATION_PENDING = 3;
|
||||
public static final int CONFIRMATIONUI_SYSTEM_ERROR = 5;
|
||||
public static final int CONFIRMATIONUI_UIERROR = 65536;
|
||||
public static final int CONFIRMATIONUI_UIERROR_MALFORMED_UTF8_ENCODING = 65539;
|
||||
public static final int CONFIRMATIONUI_UIERROR_MESSAGE_TOO_LONG = 65538;
|
||||
public static final int CONFIRMATIONUI_UIERROR_MISSING_GLYPH = 65537;
|
||||
public static final int CONFIRMATIONUI_UNEXPECTED = 7;
|
||||
public static final int CONFIRMATIONUI_UNIMPLEMENTED = 6;
|
||||
public static final int FLAG_CRITICAL_TO_DEVICE_ENCRYPTION = 8;
|
||||
public static final int FLAG_ENCRYPTED = 1;
|
||||
public static final int FLAG_NONE = 0;
|
||||
public static final int FLAG_SOFTWARE = 2;
|
||||
public static final int FLAG_STRONGBOX = 16;
|
||||
public static final int HARDWARE_TYPE_UNAVAILABLE = -68;
|
||||
public static final int KEY_ALREADY_EXISTS = 16;
|
||||
public static final int KEY_NOT_FOUND = 7;
|
||||
public static final int KEY_PERMANENTLY_INVALIDATED = 17;
|
||||
public static final int LOCKED = 2;
|
||||
public static final int NO_ERROR = 1;
|
||||
public static final int OP_AUTH_NEEDED = 15;
|
||||
public static final int PERMISSION_DENIED = 6;
|
||||
public static final int PROTOCOL_ERROR = 5;
|
||||
public static final int SYSTEM_ERROR = 4;
|
||||
private static final String TAG = "KeyStore";
|
||||
public static final int UID_SELF = -1;
|
||||
public static final int UNDEFINED_ACTION = 9;
|
||||
public static final int UNINITIALIZED = 3;
|
||||
public static final int VALUE_CORRUPTED = 8;
|
||||
public static final int WRONG_PASSWORD = 10;
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* Copyright 2025 Dakkshesh <beakthoven@gmail.com>
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
package android.security.keymaster;
|
||||
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
public class ExportResult implements Parcelable {
|
||||
public final byte[] exportData;
|
||||
public final int resultCode;
|
||||
|
||||
public ExportResult(int resultCode) {
|
||||
this.resultCode = resultCode;
|
||||
this.exportData = new byte[0];
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeToParcel(@NonNull Parcel dest, int flags) {
|
||||
throw new RuntimeException("");
|
||||
}
|
||||
|
||||
@Override
|
||||
public int describeContents() {
|
||||
throw new RuntimeException("");
|
||||
}
|
||||
|
||||
public static final Creator<ExportResult> CREATOR = new Creator<ExportResult>() {
|
||||
@Override
|
||||
public ExportResult createFromParcel(Parcel in) {
|
||||
throw new RuntimeException("");
|
||||
}
|
||||
|
||||
@Override
|
||||
public ExportResult[] newArray(int size) {
|
||||
throw new RuntimeException("");
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* Copyright 2025 Dakkshesh <beakthoven@gmail.com>
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
package android.security.keymaster;
|
||||
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
public class KeyCharacteristics implements Parcelable {
|
||||
public KeymasterArguments hwEnforced;
|
||||
public KeymasterArguments swEnforced;
|
||||
|
||||
@Override
|
||||
public void writeToParcel(@NonNull Parcel dest, int flags) {
|
||||
throw new RuntimeException("");
|
||||
}
|
||||
|
||||
@Override
|
||||
public int describeContents() {
|
||||
throw new RuntimeException("");
|
||||
}
|
||||
|
||||
public static final Creator<KeyCharacteristics> CREATOR = new Creator<KeyCharacteristics>() {
|
||||
@Override
|
||||
public KeyCharacteristics createFromParcel(Parcel in) {
|
||||
throw new RuntimeException("");
|
||||
}
|
||||
|
||||
@Override
|
||||
public KeyCharacteristics[] newArray(int size) {
|
||||
throw new RuntimeException("");
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* Copyright 2025 Dakkshesh <beakthoven@gmail.com>
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
package android.security.keymaster;
|
||||
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
abstract class KeymasterArgument implements Parcelable {
|
||||
public final int tag;
|
||||
|
||||
protected KeymasterArgument(int tag) {
|
||||
this.tag = tag;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeToParcel(@NonNull Parcel dest, int flags) {
|
||||
throw new RuntimeException("");
|
||||
}
|
||||
|
||||
@Override
|
||||
public int describeContents() {
|
||||
throw new RuntimeException("");
|
||||
}
|
||||
|
||||
public static final Creator<KeymasterArgument> CREATOR = new Creator<KeymasterArgument>() {
|
||||
@Override
|
||||
public KeymasterArgument createFromParcel(Parcel in) {
|
||||
throw new RuntimeException("");
|
||||
}
|
||||
|
||||
@Override
|
||||
public KeymasterArgument[] newArray(int size) {
|
||||
throw new RuntimeException("");
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,152 @@
|
||||
/*
|
||||
* Copyright 2025 Dakkshesh <beakthoven@gmail.com>
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
package android.security.keymaster;
|
||||
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import java.math.BigInteger;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
public class KeymasterArguments implements Parcelable {
|
||||
|
||||
private static final long UINT32_RANGE = 1L << 32;
|
||||
public static final long UINT32_MAX_VALUE = UINT32_RANGE - 1;
|
||||
|
||||
private static final BigInteger UINT64_RANGE = BigInteger.ONE.shiftLeft(64);
|
||||
public static final BigInteger UINT64_MAX_VALUE = UINT64_RANGE.subtract(BigInteger.ONE);
|
||||
|
||||
private List<KeymasterArgument> mArguments;
|
||||
|
||||
public static final @NonNull Parcelable.Creator<KeymasterArguments> CREATOR = new Parcelable.Creator<KeymasterArguments>() {
|
||||
@Override
|
||||
public KeymasterArguments createFromParcel(Parcel in) {
|
||||
throw new RuntimeException("");
|
||||
}
|
||||
|
||||
@Override
|
||||
public KeymasterArguments[] newArray(int size) {
|
||||
throw new RuntimeException("");
|
||||
}
|
||||
};
|
||||
|
||||
public KeymasterArguments() {
|
||||
throw new RuntimeException("");
|
||||
}
|
||||
|
||||
private KeymasterArguments(Parcel in) {
|
||||
throw new RuntimeException("");
|
||||
}
|
||||
|
||||
public void addEnum(int tag, int value) {
|
||||
throw new RuntimeException("");
|
||||
}
|
||||
|
||||
public void addEnums(int tag, int... values) {
|
||||
throw new RuntimeException("");
|
||||
}
|
||||
|
||||
public int getEnum(int tag, int defaultValue) {
|
||||
throw new RuntimeException("");
|
||||
}
|
||||
|
||||
public List<Integer> getEnums(int tag) {
|
||||
throw new RuntimeException("");
|
||||
}
|
||||
|
||||
private void addEnumTag(int tag, int value) {
|
||||
throw new RuntimeException("");
|
||||
}
|
||||
|
||||
private int getEnumTagValue(KeymasterArgument arg) {
|
||||
throw new RuntimeException("");
|
||||
}
|
||||
|
||||
public void addUnsignedInt(int tag, long value) {
|
||||
throw new RuntimeException("");
|
||||
}
|
||||
|
||||
public long getUnsignedInt(int tag, long defaultValue) {
|
||||
throw new RuntimeException("");
|
||||
}
|
||||
|
||||
public void addUnsignedLong(int tag, BigInteger value) {
|
||||
throw new RuntimeException("");
|
||||
}
|
||||
|
||||
public List<BigInteger> getUnsignedLongs(int tag) {
|
||||
throw new RuntimeException("");
|
||||
}
|
||||
|
||||
private void addLongTag(int tag, BigInteger value) {
|
||||
throw new RuntimeException("");
|
||||
}
|
||||
|
||||
private BigInteger getLongTagValue(KeymasterArgument arg) {
|
||||
throw new RuntimeException("");
|
||||
}
|
||||
|
||||
public void addBoolean(int tag) {
|
||||
throw new RuntimeException("");
|
||||
}
|
||||
|
||||
public boolean getBoolean(int tag) {
|
||||
throw new RuntimeException("");
|
||||
}
|
||||
|
||||
public void addBytes(int tag, byte[] value) {
|
||||
throw new RuntimeException("");
|
||||
}
|
||||
|
||||
public byte[] getBytes(int tag, byte[] defaultValue) {
|
||||
throw new RuntimeException("");
|
||||
}
|
||||
|
||||
public void addDate(int tag, Date value) {
|
||||
throw new RuntimeException("");
|
||||
}
|
||||
|
||||
public void addDateIfNotNull(int tag, Date value) {
|
||||
throw new RuntimeException("");
|
||||
}
|
||||
|
||||
public Date getDate(int tag, Date defaultValue) {
|
||||
throw new RuntimeException("");
|
||||
}
|
||||
|
||||
private KeymasterArgument getArgumentByTag(int tag) {
|
||||
throw new RuntimeException("");
|
||||
}
|
||||
|
||||
public boolean containsTag(int tag) {
|
||||
throw new RuntimeException("");
|
||||
}
|
||||
|
||||
public int size() {
|
||||
throw new RuntimeException("");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeToParcel(Parcel out, int flags) {
|
||||
throw new RuntimeException("");
|
||||
}
|
||||
|
||||
public void readFromParcel(Parcel in) {
|
||||
throw new RuntimeException("");
|
||||
}
|
||||
|
||||
@Override
|
||||
public int describeContents() {
|
||||
throw new RuntimeException("");
|
||||
}
|
||||
|
||||
public static BigInteger toUint64(long value) {
|
||||
throw new RuntimeException("");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
* Copyright 2025 Dakkshesh <beakthoven@gmail.com>
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
package android.security.keymaster;
|
||||
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class KeymasterCertificateChain implements Parcelable {
|
||||
private List<byte[]> mCertificates;
|
||||
|
||||
public KeymasterCertificateChain() {
|
||||
this.mCertificates = null;
|
||||
}
|
||||
|
||||
public KeymasterCertificateChain(List<byte[]> mCertificates) {
|
||||
this.mCertificates = mCertificates;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeToParcel(@NonNull Parcel dest, int flags) {
|
||||
throw new RuntimeException("");
|
||||
}
|
||||
|
||||
@Override
|
||||
public int describeContents() {
|
||||
throw new RuntimeException("");
|
||||
}
|
||||
|
||||
public static final Creator<KeymasterCertificateChain> CREATOR = new Creator<KeymasterCertificateChain>() {
|
||||
@Override
|
||||
public KeymasterCertificateChain createFromParcel(Parcel in) {
|
||||
throw new RuntimeException("");
|
||||
}
|
||||
|
||||
@Override
|
||||
public KeymasterCertificateChain[] newArray(int size) {
|
||||
throw new RuntimeException("");
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,268 @@
|
||||
/*
|
||||
* Copyright 2025 Dakkshesh <beakthoven@gmail.com>
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
package android.security.keymaster;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public final class KeymasterDefs {
|
||||
|
||||
private KeymasterDefs() {
|
||||
}
|
||||
|
||||
// Tag types.
|
||||
public static final int KM_INVALID = 0 << 28;
|
||||
public static final int KM_ENUM = 1 << 28;
|
||||
public static final int KM_ENUM_REP = 2 << 28;
|
||||
public static final int KM_UINT = 3 << 28;
|
||||
public static final int KM_UINT_REP = 4 << 28;
|
||||
public static final int KM_ULONG = 5 << 28;
|
||||
public static final int KM_DATE = 6 << 28;
|
||||
public static final int KM_BOOL = 7 << 28;
|
||||
public static final int KM_BIGNUM = 8 << 28;
|
||||
public static final int KM_BYTES = 9 << 28;
|
||||
public static final int KM_ULONG_REP = 10 << 28;
|
||||
|
||||
// Tag values.
|
||||
public static final int KM_TAG_INVALID = KM_INVALID | 0;
|
||||
public static final int KM_TAG_PURPOSE = KM_ENUM_REP | 1;
|
||||
public static final int KM_TAG_ALGORITHM = KM_ENUM | 2;
|
||||
public static final int KM_TAG_KEY_SIZE = KM_UINT | 3;
|
||||
public static final int KM_TAG_BLOCK_MODE = KM_ENUM_REP | 4;
|
||||
public static final int KM_TAG_DIGEST = KM_ENUM_REP | 5;
|
||||
public static final int KM_TAG_PADDING = KM_ENUM_REP | 6;
|
||||
public static final int KM_TAG_CALLER_NONCE = KM_BOOL | 7;
|
||||
public static final int KM_TAG_MIN_MAC_LENGTH = KM_UINT | 8;
|
||||
|
||||
public static final int KM_TAG_RESCOPING_ADD = KM_ENUM_REP | 101;
|
||||
public static final int KM_TAG_RESCOPING_DEL = KM_ENUM_REP | 102;
|
||||
public static final int KM_TAG_BLOB_USAGE_REQUIREMENTS = KM_ENUM | 705;
|
||||
|
||||
public static final int KM_TAG_RSA_PUBLIC_EXPONENT = KM_ULONG | 200;
|
||||
public static final int KM_TAG_INCLUDE_UNIQUE_ID = KM_BOOL | 202;
|
||||
|
||||
public static final int KM_TAG_ACTIVE_DATETIME = KM_DATE | 400;
|
||||
public static final int KM_TAG_ORIGINATION_EXPIRE_DATETIME = KM_DATE | 401;
|
||||
public static final int KM_TAG_USAGE_EXPIRE_DATETIME = KM_DATE | 402;
|
||||
public static final int KM_TAG_MIN_SECONDS_BETWEEN_OPS = KM_UINT | 403;
|
||||
public static final int KM_TAG_MAX_USES_PER_BOOT = KM_UINT | 404;
|
||||
|
||||
public static final int KM_TAG_ALL_USERS = KM_BOOL | 500;
|
||||
public static final int KM_TAG_USER_ID = KM_UINT | 501;
|
||||
public static final int KM_TAG_USER_SECURE_ID = KM_ULONG_REP | 502;
|
||||
public static final int KM_TAG_NO_AUTH_REQUIRED = KM_BOOL | 503;
|
||||
public static final int KM_TAG_USER_AUTH_TYPE = KM_ENUM | 504;
|
||||
public static final int KM_TAG_AUTH_TIMEOUT = KM_UINT | 505;
|
||||
public static final int KM_TAG_ALLOW_WHILE_ON_BODY = KM_BOOL | 506;
|
||||
public static final int KM_TAG_TRUSTED_USER_PRESENCE_REQUIRED = KM_BOOL | 507;
|
||||
public static final int KM_TAG_TRUSTED_CONFIRMATION_REQUIRED = KM_BOOL | 508;
|
||||
public static final int KM_TAG_UNLOCKED_DEVICE_REQUIRED = KM_BOOL | 509;
|
||||
|
||||
public static final int KM_TAG_ALL_APPLICATIONS = KM_BOOL | 600;
|
||||
public static final int KM_TAG_APPLICATION_ID = KM_BYTES | 601;
|
||||
|
||||
public static final int KM_TAG_CREATION_DATETIME = KM_DATE | 701;
|
||||
public static final int KM_TAG_ORIGIN = KM_ENUM | 702;
|
||||
public static final int KM_TAG_ROLLBACK_RESISTANT = KM_BOOL | 703;
|
||||
public static final int KM_TAG_ROOT_OF_TRUST = KM_BYTES | 704;
|
||||
public static final int KM_TAG_UNIQUE_ID = KM_BYTES | 707;
|
||||
public static final int KM_TAG_ATTESTATION_CHALLENGE = KM_BYTES | 708;
|
||||
public static final int KM_TAG_ATTESTATION_ID_BRAND = KM_BYTES | 710;
|
||||
public static final int KM_TAG_ATTESTATION_ID_DEVICE = KM_BYTES | 711;
|
||||
public static final int KM_TAG_ATTESTATION_ID_PRODUCT = KM_BYTES | 712;
|
||||
public static final int KM_TAG_ATTESTATION_ID_SERIAL = KM_BYTES | 713;
|
||||
public static final int KM_TAG_ATTESTATION_ID_IMEI = KM_BYTES | 714;
|
||||
public static final int KM_TAG_ATTESTATION_ID_MEID = KM_BYTES | 715;
|
||||
public static final int KM_TAG_ATTESTATION_ID_MANUFACTURER = KM_BYTES | 716;
|
||||
public static final int KM_TAG_ATTESTATION_ID_MODEL = KM_BYTES | 717;
|
||||
public static final int KM_TAG_DEVICE_UNIQUE_ATTESTATION = KM_BOOL | 720;
|
||||
|
||||
public static final int KM_TAG_ASSOCIATED_DATA = KM_BYTES | 1000;
|
||||
public static final int KM_TAG_NONCE = KM_BYTES | 1001;
|
||||
public static final int KM_TAG_AUTH_TOKEN = KM_BYTES | 1002;
|
||||
public static final int KM_TAG_MAC_LENGTH = KM_UINT | 1003;
|
||||
|
||||
// Algorithm values.
|
||||
public static final int KM_ALGORITHM_RSA = 1;
|
||||
public static final int KM_ALGORITHM_EC = 3;
|
||||
public static final int KM_ALGORITHM_AES = 32;
|
||||
public static final int KM_ALGORITHM_3DES = 33;
|
||||
public static final int KM_ALGORITHM_HMAC = 128;
|
||||
|
||||
// Block modes.
|
||||
public static final int KM_MODE_ECB = 1;
|
||||
public static final int KM_MODE_CBC = 2;
|
||||
public static final int KM_MODE_CTR = 3;
|
||||
public static final int KM_MODE_GCM = 32;
|
||||
|
||||
// Padding modes.
|
||||
public static final int KM_PAD_NONE = 1;
|
||||
public static final int KM_PAD_RSA_OAEP = 2;
|
||||
public static final int KM_PAD_RSA_PSS = 3;
|
||||
public static final int KM_PAD_RSA_PKCS1_1_5_ENCRYPT = 4;
|
||||
public static final int KM_PAD_RSA_PKCS1_1_5_SIGN = 5;
|
||||
public static final int KM_PAD_PKCS7 = 64;
|
||||
|
||||
// Digest modes.
|
||||
public static final int KM_DIGEST_NONE = 0;
|
||||
public static final int KM_DIGEST_MD5 = 1;
|
||||
public static final int KM_DIGEST_SHA1 = 2;
|
||||
public static final int KM_DIGEST_SHA_2_224 = 3;
|
||||
public static final int KM_DIGEST_SHA_2_256 = 4;
|
||||
public static final int KM_DIGEST_SHA_2_384 = 5;
|
||||
public static final int KM_DIGEST_SHA_2_512 = 6;
|
||||
|
||||
// Key origins.
|
||||
public static final int KM_ORIGIN_GENERATED = 0;
|
||||
public static final int KM_ORIGIN_IMPORTED = 2;
|
||||
public static final int KM_ORIGIN_UNKNOWN = 3;
|
||||
public static final int KM_ORIGIN_SECURELY_IMPORTED = 4;
|
||||
|
||||
// Key usability requirements.
|
||||
public static final int KM_BLOB_STANDALONE = 0;
|
||||
public static final int KM_BLOB_REQUIRES_FILE_SYSTEM = 1;
|
||||
|
||||
// Operation Purposes.
|
||||
public static final int KM_PURPOSE_ENCRYPT = 0;
|
||||
public static final int KM_PURPOSE_DECRYPT = 1;
|
||||
public static final int KM_PURPOSE_SIGN = 2;
|
||||
public static final int KM_PURPOSE_VERIFY = 3;
|
||||
public static final int KM_PURPOSE_WRAP = 5;
|
||||
|
||||
// Key formats.
|
||||
public static final int KM_KEY_FORMAT_X509 = 0;
|
||||
public static final int KM_KEY_FORMAT_PKCS8 = 1;
|
||||
public static final int KM_KEY_FORMAT_RAW = 3;
|
||||
|
||||
// User authenticators.
|
||||
public static final int HW_AUTH_PASSWORD = 1 << 0;
|
||||
public static final int HW_AUTH_BIOMETRIC = 1 << 1;
|
||||
|
||||
// Error codes.
|
||||
public static final int KM_ERROR_OK = 0;
|
||||
public static final int KM_ERROR_ROOT_OF_TRUST_ALREADY_SET = -1;
|
||||
public static final int KM_ERROR_UNSUPPORTED_PURPOSE = -2;
|
||||
public static final int KM_ERROR_INCOMPATIBLE_PURPOSE = -3;
|
||||
public static final int KM_ERROR_UNSUPPORTED_ALGORITHM = -4;
|
||||
public static final int KM_ERROR_INCOMPATIBLE_ALGORITHM = -5;
|
||||
public static final int KM_ERROR_UNSUPPORTED_KEY_SIZE = -6;
|
||||
public static final int KM_ERROR_UNSUPPORTED_BLOCK_MODE = -7;
|
||||
public static final int KM_ERROR_INCOMPATIBLE_BLOCK_MODE = -8;
|
||||
public static final int KM_ERROR_UNSUPPORTED_MAC_LENGTH = -9;
|
||||
public static final int KM_ERROR_UNSUPPORTED_PADDING_MODE = -10;
|
||||
public static final int KM_ERROR_INCOMPATIBLE_PADDING_MODE = -11;
|
||||
public static final int KM_ERROR_UNSUPPORTED_DIGEST = -12;
|
||||
public static final int KM_ERROR_INCOMPATIBLE_DIGEST = -13;
|
||||
public static final int KM_ERROR_INVALID_EXPIRATION_TIME = -14;
|
||||
public static final int KM_ERROR_INVALID_USER_ID = -15;
|
||||
public static final int KM_ERROR_INVALID_AUTHORIZATION_TIMEOUT = -16;
|
||||
public static final int KM_ERROR_UNSUPPORTED_KEY_FORMAT = -17;
|
||||
public static final int KM_ERROR_INCOMPATIBLE_KEY_FORMAT = -18;
|
||||
public static final int KM_ERROR_UNSUPPORTED_KEY_ENCRYPTION_ALGORITHM = -19;
|
||||
public static final int KM_ERROR_UNSUPPORTED_KEY_VERIFICATION_ALGORITHM = -20;
|
||||
public static final int KM_ERROR_INVALID_INPUT_LENGTH = -21;
|
||||
public static final int KM_ERROR_KEY_EXPORT_OPTIONS_INVALID = -22;
|
||||
public static final int KM_ERROR_DELEGATION_NOT_ALLOWED = -23;
|
||||
public static final int KM_ERROR_KEY_NOT_YET_VALID = -24;
|
||||
public static final int KM_ERROR_KEY_EXPIRED = -25;
|
||||
public static final int KM_ERROR_KEY_USER_NOT_AUTHENTICATED = -26;
|
||||
public static final int KM_ERROR_OUTPUT_PARAMETER_NULL = -27;
|
||||
public static final int KM_ERROR_INVALID_OPERATION_HANDLE = -28;
|
||||
public static final int KM_ERROR_INSUFFICIENT_BUFFER_SPACE = -29;
|
||||
public static final int KM_ERROR_VERIFICATION_FAILED = -30;
|
||||
public static final int KM_ERROR_TOO_MANY_OPERATIONS = -31;
|
||||
public static final int KM_ERROR_UNEXPECTED_NULL_POINTER = -32;
|
||||
public static final int KM_ERROR_INVALID_KEY_BLOB = -33;
|
||||
public static final int KM_ERROR_IMPORTED_KEY_NOT_ENCRYPTED = -34;
|
||||
public static final int KM_ERROR_IMPORTED_KEY_DECRYPTION_FAILED = -35;
|
||||
public static final int KM_ERROR_IMPORTED_KEY_NOT_SIGNED = -36;
|
||||
public static final int KM_ERROR_IMPORTED_KEY_VERIFICATION_FAILED = -37;
|
||||
public static final int KM_ERROR_INVALID_ARGUMENT = -38;
|
||||
public static final int KM_ERROR_UNSUPPORTED_TAG = -39;
|
||||
public static final int KM_ERROR_INVALID_TAG = -40;
|
||||
public static final int KM_ERROR_MEMORY_ALLOCATION_FAILED = -41;
|
||||
public static final int KM_ERROR_INVALID_RESCOPING = -42;
|
||||
public static final int KM_ERROR_IMPORT_PARAMETER_MISMATCH = -44;
|
||||
public static final int KM_ERROR_SECURE_HW_ACCESS_DENIED = -45;
|
||||
public static final int KM_ERROR_OPERATION_CANCELLED = -46;
|
||||
public static final int KM_ERROR_CONCURRENT_ACCESS_CONFLICT = -47;
|
||||
public static final int KM_ERROR_SECURE_HW_BUSY = -48;
|
||||
public static final int KM_ERROR_SECURE_HW_COMMUNICATION_FAILED = -49;
|
||||
public static final int KM_ERROR_UNSUPPORTED_EC_FIELD = -50;
|
||||
public static final int KM_ERROR_MISSING_NONCE = -51;
|
||||
public static final int KM_ERROR_INVALID_NONCE = -52;
|
||||
public static final int KM_ERROR_MISSING_MAC_LENGTH = -53;
|
||||
public static final int KM_ERROR_KEY_RATE_LIMIT_EXCEEDED = -54;
|
||||
public static final int KM_ERROR_CALLER_NONCE_PROHIBITED = -55;
|
||||
public static final int KM_ERROR_KEY_MAX_OPS_EXCEEDED = -56;
|
||||
public static final int KM_ERROR_INVALID_MAC_LENGTH = -57;
|
||||
public static final int KM_ERROR_MISSING_MIN_MAC_LENGTH = -58;
|
||||
public static final int KM_ERROR_UNSUPPORTED_MIN_MAC_LENGTH = -59;
|
||||
public static final int KM_ERROR_CANNOT_ATTEST_IDS = -66;
|
||||
public static final int KM_ERROR_DEVICE_LOCKED = -72;
|
||||
public static final int KM_ERROR_UNIMPLEMENTED = -100;
|
||||
public static final int KM_ERROR_VERSION_MISMATCH = -101;
|
||||
public static final int KM_ERROR_UNKNOWN_ERROR = -1000;
|
||||
|
||||
public static final Map<Integer, String> sErrorCodeToString = new HashMap<Integer, String>();
|
||||
static {
|
||||
sErrorCodeToString.put(KM_ERROR_OK, "OK");
|
||||
sErrorCodeToString.put(KM_ERROR_UNSUPPORTED_PURPOSE, "Unsupported purpose");
|
||||
sErrorCodeToString.put(KM_ERROR_INCOMPATIBLE_PURPOSE, "Incompatible purpose");
|
||||
sErrorCodeToString.put(KM_ERROR_UNSUPPORTED_ALGORITHM, "Unsupported algorithm");
|
||||
sErrorCodeToString.put(KM_ERROR_INCOMPATIBLE_ALGORITHM, "Incompatible algorithm");
|
||||
sErrorCodeToString.put(KM_ERROR_UNSUPPORTED_KEY_SIZE, "Unsupported key size");
|
||||
sErrorCodeToString.put(KM_ERROR_UNSUPPORTED_BLOCK_MODE, "Unsupported block mode");
|
||||
sErrorCodeToString.put(KM_ERROR_INCOMPATIBLE_BLOCK_MODE, "Incompatible block mode");
|
||||
sErrorCodeToString.put(KM_ERROR_UNSUPPORTED_MAC_LENGTH,
|
||||
"Unsupported MAC or authentication tag length");
|
||||
sErrorCodeToString.put(KM_ERROR_UNSUPPORTED_PADDING_MODE, "Unsupported padding mode");
|
||||
sErrorCodeToString.put(KM_ERROR_INCOMPATIBLE_PADDING_MODE, "Incompatible padding mode");
|
||||
sErrorCodeToString.put(KM_ERROR_UNSUPPORTED_DIGEST, "Unsupported digest");
|
||||
sErrorCodeToString.put(KM_ERROR_INCOMPATIBLE_DIGEST, "Incompatible digest");
|
||||
sErrorCodeToString.put(KM_ERROR_INVALID_EXPIRATION_TIME, "Invalid expiration time");
|
||||
sErrorCodeToString.put(KM_ERROR_INVALID_USER_ID, "Invalid user ID");
|
||||
sErrorCodeToString.put(KM_ERROR_INVALID_AUTHORIZATION_TIMEOUT,
|
||||
"Invalid user authorization timeout");
|
||||
sErrorCodeToString.put(KM_ERROR_UNSUPPORTED_KEY_FORMAT, "Unsupported key format");
|
||||
sErrorCodeToString.put(KM_ERROR_INCOMPATIBLE_KEY_FORMAT, "Incompatible key format");
|
||||
sErrorCodeToString.put(KM_ERROR_INVALID_INPUT_LENGTH, "Invalid input length");
|
||||
sErrorCodeToString.put(KM_ERROR_KEY_NOT_YET_VALID, "Key not yet valid");
|
||||
sErrorCodeToString.put(KM_ERROR_KEY_EXPIRED, "Key expired");
|
||||
sErrorCodeToString.put(KM_ERROR_KEY_USER_NOT_AUTHENTICATED, "Key user not authenticated");
|
||||
sErrorCodeToString.put(KM_ERROR_INVALID_OPERATION_HANDLE, "Invalid operation handle");
|
||||
sErrorCodeToString.put(KM_ERROR_VERIFICATION_FAILED, "Signature/MAC verification failed");
|
||||
sErrorCodeToString.put(KM_ERROR_TOO_MANY_OPERATIONS, "Too many operations");
|
||||
sErrorCodeToString.put(KM_ERROR_INVALID_KEY_BLOB, "Invalid key blob");
|
||||
sErrorCodeToString.put(KM_ERROR_INVALID_ARGUMENT, "Invalid argument");
|
||||
sErrorCodeToString.put(KM_ERROR_UNSUPPORTED_TAG, "Unsupported tag");
|
||||
sErrorCodeToString.put(KM_ERROR_INVALID_TAG, "Invalid tag");
|
||||
sErrorCodeToString.put(KM_ERROR_MEMORY_ALLOCATION_FAILED, "Memory allocation failed");
|
||||
sErrorCodeToString.put(KM_ERROR_UNSUPPORTED_EC_FIELD, "Unsupported EC field");
|
||||
sErrorCodeToString.put(KM_ERROR_MISSING_NONCE, "Required IV missing");
|
||||
sErrorCodeToString.put(KM_ERROR_INVALID_NONCE, "Invalid IV");
|
||||
sErrorCodeToString.put(KM_ERROR_CALLER_NONCE_PROHIBITED,
|
||||
"Caller-provided IV not permitted");
|
||||
sErrorCodeToString.put(KM_ERROR_INVALID_MAC_LENGTH,
|
||||
"Invalid MAC or authentication tag length");
|
||||
sErrorCodeToString.put(KM_ERROR_CANNOT_ATTEST_IDS, "Unable to attest device ids");
|
||||
sErrorCodeToString.put(KM_ERROR_DEVICE_LOCKED, "Device locked");
|
||||
sErrorCodeToString.put(KM_ERROR_UNIMPLEMENTED, "Not implemented");
|
||||
sErrorCodeToString.put(KM_ERROR_UNKNOWN_ERROR, "Unknown error");
|
||||
}
|
||||
|
||||
public static int getTagType(int tag) {
|
||||
return tag & (0xF << 28);
|
||||
}
|
||||
|
||||
public static String getErrorMessage(int errorCode) {
|
||||
String result = sErrorCodeToString.get(errorCode);
|
||||
if (result != null) {
|
||||
return result;
|
||||
}
|
||||
return String.valueOf(errorCode);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
/*
|
||||
* Copyright 2025 Dakkshesh <beakthoven@gmail.com>
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
package android.security.keystore;
|
||||
|
||||
public class AndroidKeyStoreProvider {
|
||||
public static void install() {
|
||||
throw new RuntimeException("");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
/*
|
||||
* Copyright 2025 Dakkshesh <beakthoven@gmail.com>
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
package android.security.keystore;
|
||||
|
||||
import android.os.IBinder;
|
||||
import android.os.RemoteException;
|
||||
import android.security.keymaster.KeymasterCertificateChain;
|
||||
|
||||
public interface IKeystoreCertificateChainCallback {
|
||||
void onFinished(KeystoreResponse keystoreResponse, KeymasterCertificateChain keymasterCertificateChain)
|
||||
throws RemoteException;
|
||||
|
||||
public static abstract class Stub {
|
||||
public static IKeystoreCertificateChainCallback asInterface(IBinder b) {
|
||||
throw new RuntimeException("");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
/*
|
||||
* Copyright 2025 Dakkshesh <beakthoven@gmail.com>
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
package android.security.keystore;
|
||||
|
||||
import android.os.IBinder;
|
||||
import android.os.RemoteException;
|
||||
import android.security.keymaster.ExportResult;
|
||||
|
||||
public interface IKeystoreExportKeyCallback {
|
||||
void onFinished(ExportResult exportResult) throws RemoteException;
|
||||
|
||||
public static abstract class Stub {
|
||||
public static IKeystoreExportKeyCallback asInterface(IBinder b) {
|
||||
throw new RuntimeException("");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
/*
|
||||
* Copyright 2025 Dakkshesh <beakthoven@gmail.com>
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
package android.security.keystore;
|
||||
|
||||
import android.os.IBinder;
|
||||
import android.os.IInterface;
|
||||
import android.os.RemoteException;
|
||||
import android.security.keymaster.KeyCharacteristics;
|
||||
|
||||
public interface IKeystoreKeyCharacteristicsCallback extends IInterface {
|
||||
void onFinished(KeystoreResponse keystoreResponse, KeyCharacteristics keyCharacteristics) throws RemoteException;
|
||||
|
||||
public static abstract class Stub {
|
||||
public static IKeystoreKeyCharacteristicsCallback asInterface(IBinder b) {
|
||||
throw new RuntimeException("");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
/*
|
||||
* Copyright 2025 Dakkshesh <beakthoven@gmail.com>
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
package android.security.keystore;
|
||||
|
||||
public interface IKeystoreService {
|
||||
class Stub {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* Copyright 2025 Dakkshesh <beakthoven@gmail.com>
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
package android.security.keystore;
|
||||
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
public class KeystoreResponse implements Parcelable {
|
||||
public final int error_code_;
|
||||
public final String error_msg_;
|
||||
|
||||
protected KeystoreResponse(int error_code, String error_msg) {
|
||||
this.error_code_ = error_code;
|
||||
this.error_msg_ = error_msg;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeToParcel(@NonNull Parcel dest, int flags) {
|
||||
throw new RuntimeException("");
|
||||
}
|
||||
|
||||
@Override
|
||||
public int describeContents() {
|
||||
throw new RuntimeException("");
|
||||
}
|
||||
|
||||
public static final Creator<KeystoreResponse> CREATOR = new Creator<KeystoreResponse>() {
|
||||
@Override
|
||||
public KeystoreResponse createFromParcel(Parcel in) {
|
||||
throw new RuntimeException("");
|
||||
}
|
||||
|
||||
@Override
|
||||
public KeystoreResponse[] newArray(int size) {
|
||||
throw new RuntimeException("");
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
/*
|
||||
* Copyright 2025 Dakkshesh <beakthoven@gmail.com>
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
package android.security.keystore2;
|
||||
|
||||
public class AndroidKeyStoreProvider {
|
||||
public static void install() {
|
||||
throw new RuntimeException("");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
/*
|
||||
* Copyright 2025 Dakkshesh <beakthoven@gmail.com>
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
package android.system.keystore2;
|
||||
|
||||
import android.hardware.security.keymint.KeyParameter;
|
||||
|
||||
public class Authorization {
|
||||
public KeyParameter keyParameter;
|
||||
public int securityLevel = 0;
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
* Copyright 2025 Dakkshesh <beakthoven@gmail.com>
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
package android.system.keystore2;
|
||||
|
||||
import android.hardware.security.keymint.KeyParameter;
|
||||
import android.os.IBinder;
|
||||
import android.os.IInterface;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
public interface IKeystoreSecurityLevel extends IInterface {
|
||||
String DESCRIPTOR = "android.system.keystore2.IKeystoreSecurityLevel";
|
||||
|
||||
KeyMetadata generateKey(KeyDescriptor key, @Nullable KeyDescriptor attestationKey,
|
||||
KeyParameter[] params, int flags, byte[] entropy);
|
||||
|
||||
class Stub {
|
||||
public static IKeystoreSecurityLevel asInterface(IBinder b) {
|
||||
throw new RuntimeException();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
/*
|
||||
* Copyright 2025 Dakkshesh <beakthoven@gmail.com>
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
package android.system.keystore2;
|
||||
|
||||
import android.os.IBinder;
|
||||
|
||||
public interface IKeystoreService {
|
||||
String DESCRIPTOR = "android.system.keystore2.IKeystoreService";
|
||||
|
||||
IKeystoreSecurityLevel getSecurityLevel(int securityLevel);
|
||||
|
||||
class Stub {
|
||||
public static IKeystoreService asInterface(IBinder b) {
|
||||
throw new RuntimeException("");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* Copyright 2025 Dakkshesh <beakthoven@gmail.com>
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
package android.system.keystore2;
|
||||
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
public class KeyDescriptor implements Parcelable {
|
||||
public String alias;
|
||||
public byte[] blob;
|
||||
public int domain = 0;
|
||||
public long nspace = 0;
|
||||
|
||||
public static final Creator<KeyDescriptor> CREATOR = new Creator<KeyDescriptor>() {
|
||||
@Override
|
||||
public KeyDescriptor createFromParcel(Parcel in) {
|
||||
throw new RuntimeException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public KeyDescriptor[] newArray(int size) {
|
||||
throw new RuntimeException();
|
||||
}
|
||||
};
|
||||
|
||||
@Override
|
||||
public int describeContents() {
|
||||
throw new RuntimeException("");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeToParcel(@NonNull Parcel parcel, int i) {
|
||||
throw new RuntimeException("");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* Copyright 2025 Dakkshesh <beakthoven@gmail.com>
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
package android.system.keystore2;
|
||||
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
public class KeyEntryResponse implements Parcelable {
|
||||
public IKeystoreSecurityLevel iSecurityLevel;
|
||||
public KeyMetadata metadata;
|
||||
|
||||
public static final Creator<KeyEntryResponse> CREATOR = new Creator<KeyEntryResponse>() {
|
||||
@Override
|
||||
public KeyEntryResponse createFromParcel(Parcel in) {
|
||||
throw new RuntimeException("");
|
||||
}
|
||||
|
||||
@Override
|
||||
public KeyEntryResponse[] newArray(int size) {
|
||||
throw new RuntimeException("");
|
||||
}
|
||||
};
|
||||
|
||||
@Override
|
||||
public int describeContents() {
|
||||
throw new RuntimeException("");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeToParcel(@NonNull Parcel parcel, int i) {
|
||||
throw new RuntimeException("");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* Copyright 2025 Dakkshesh <beakthoven@gmail.com>
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
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("");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user