diff --git a/zygisk/module/jni/Android.mk b/zygisk/module/jni/Android.mk index 0e485e7..9dc6b4c 100644 --- a/zygisk/module/jni/Android.mk +++ b/zygisk/module/jni/Android.mk @@ -1,8 +1,8 @@ LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) -LOCAL_MODULE := example -LOCAL_SRC_FILES := example.cpp +LOCAL_MODULE := safetynetfix +LOCAL_SRC_FILES := module.cpp LOCAL_STATIC_LIBRARIES := libcxx LOCAL_LDLIBS := -llog include $(BUILD_SHARED_LIBRARY) diff --git a/zygisk/module/jni/example.cpp b/zygisk/module/jni/example.cpp deleted file mode 100644 index 0c29ce2..0000000 --- a/zygisk/module/jni/example.cpp +++ /dev/null @@ -1,58 +0,0 @@ -#include -#include -#include -#include - -#include "zygisk.hpp" - -using zygisk::Api; -using zygisk::AppSpecializeArgs; -using zygisk::ServerSpecializeArgs; - -#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, "Magisk", __VA_ARGS__) - -class MyModule : public zygisk::ModuleBase { -public: - void onLoad(Api *api, JNIEnv *env) override { - this->api = api; - this->env = env; - } - - void preAppSpecialize(AppSpecializeArgs *args) override { - // Use JNI to fetch our process name - const char *process = env->GetStringUTFChars(args->nice_name, nullptr); - preSpecialize(process); - env->ReleaseStringUTFChars(args->nice_name, process); - } - - void preServerSpecialize(ServerSpecializeArgs *args) override { - preSpecialize("system_server"); - } - -private: - Api *api; - JNIEnv *env; - - void preSpecialize(const char *process) { - // Demonstrate connecting to to companion process - // We ask the companion for a random number - int r = 0; - int fd = api->connectCompanion(); - read(fd, &r, sizeof(r)); - close(fd); - LOGD("example: process=[%s], r=[%u]\n", process, r); - } - -}; - -static void companion_handler(int i) { - int fd = open("/dev/urandom", O_RDONLY); - int r; - read(fd, &r, sizeof(r)); - close(fd); - LOGD("example: companion r=[%u]\n", r); - write(i, &r, sizeof(r)); -} - -REGISTER_ZYGISK_MODULE(MyModule) -REGISTER_ZYGISK_COMPANION(companion_handler) diff --git a/zygisk/module/jni/module.cpp b/zygisk/module/jni/module.cpp new file mode 100644 index 0000000..c2c9e9a --- /dev/null +++ b/zygisk/module/jni/module.cpp @@ -0,0 +1,168 @@ +#include +#include +#include +#include +#include +#include +#include + +#include "zygisk.hpp" +#include "module.h" + +namespace safetynetfix { + +class SafetyNetFixModule : public zygisk::ModuleBase { +public: + void onLoad(zygisk::Api *api, JNIEnv *env) override { + this->api = api; + this->env = env; + } + + void preAppSpecialize(zygisk::AppSpecializeArgs *args) override { + const char *rawProcess = env->GetStringUTFChars(args->nice_name, nullptr); + std::string process(rawProcess); + env->ReleaseStringUTFChars(args->nice_name, rawProcess); + + preSpecialize(process); + } + +private: + zygisk::Api *api; + JNIEnv *env; + + std::vector moduleDex; + + static int receiveFile(int remote_fd, std::vector& buf) { + off_t size; + int ret = read(remote_fd, &size, sizeof(size)); + if (ret < 0) { + LOGE("Failed to read size"); + return -1; + } + + buf.resize(size); + + int bytesReceived = 0; + while (bytesReceived < size) { + ret = read(remote_fd, buf.data() + bytesReceived, size - bytesReceived); + if (ret < 0) { + LOGE("Failed to read data"); + return -1; + } + + bytesReceived += ret; + } + + return bytesReceived; + } + + void loadPayload() { + int fd = api->connectCompanion(); + + auto size = receiveFile(fd, moduleDex); + LOGD("Loaded module payload: %d bytes", size); + + close(fd); + } + + void preSpecialize(std::string process) { + // Only take action for GMS, otherwise unload + if (process.rfind("com.google.android.gms", 0) != 0) { + // If this isn't GMS, bail out and unload. + api->setOption(zygisk::DLCLOSE_MODULE_LIBRARY); + return; + } + + // Force DenyList unmounting for all GMS processes + api->setOption(zygisk::FORCE_DENYLIST_UNMOUNT); + + // The unstable process is where SafetyNet attestation actually runs, so we only need to + // spoof the model in that process. Leaving other processes alone fixes various issues + // caused by model detection and flag provisioning, such as broken weather with the new + // smartspace on Android 12. + if (process == "com.google.android.gms.unstable") { + // This is post-fork, so just inject the payload now + loadPayload(); + injectPayload(); + LOGI("Payload injected"); + } + } + + void injectPayload() { + // First, get the system classloader + LOGD("get system classloader"); + jclass clClass = env->FindClass("java/lang/ClassLoader"); + jmethodID getSystemClassLoader = env->GetStaticMethodID(clClass, "getSystemClassLoader", + "()Ljava/lang/ClassLoader;"); + jobject systemClassLoader = env->CallStaticObjectMethod(clClass, getSystemClassLoader); + + // Assuming we have a valid mapped module, load it. This is similar to the approach used for + // Dynamite modules in GmsCompat, except we can use InMemoryDexClassLoader directly instead of + // tampering with DelegateLastClassLoader's DexPathList. + LOGD("create buffer"); + jobject buf = env->NewDirectByteBuffer(moduleDex.data(), moduleDex.size()); + LOGD("create class loader"); + jclass dexClClass = env->FindClass("dalvik/system/InMemoryDexClassLoader"); + jmethodID dexClInit = env->GetMethodID(dexClClass, "", + "(Ljava/nio/ByteBuffer;Ljava/lang/ClassLoader;)V"); + jobject dexCl = env->NewObject(dexClClass, dexClInit, buf, systemClassLoader); + + // Load the class + LOGD("load class"); + jmethodID loadClass = env->GetMethodID(clClass, "loadClass", + "(Ljava/lang/String;)Ljava/lang/Class;"); + jstring entryClassName = env->NewStringUTF("dev.kdrag0n.safetynetriru.EntryPoint"); + jobject entryClassObj = env->CallObjectMethod(dexCl, loadClass, entryClassName); + + // Call init. Static initializers don't run when merely calling loadClass from JNI. + LOGD("call init"); + auto entryClass = (jclass) entryClassObj; + jmethodID entryInit = env->GetStaticMethodID(entryClass, "init", "()V"); + env->CallStaticVoidMethod(entryClass, entryInit); + } +}; + +static off_t sendFile(int remote_fd, const std::string& path) { + int in_fd = open(path.c_str(), O_RDONLY); + if (in_fd < 0) { + LOGE("Failed to open file %s: %d (%s)", path.c_str(), errno, strerror(errno)); + return -1; + } + + auto size = lseek(in_fd, 0, SEEK_END); + if (size < 0) { + LOGERRNO("Failed to get file size"); + close(in_fd); + return -1; + } + lseek(in_fd, 0, SEEK_SET); + + // Send size first for buffer allocation + int ret = write(remote_fd, &size, sizeof(size)); + if (ret < 0) { + LOGERRNO("Failed to send size"); + close(in_fd); + return -1; + } + + ret = sendfile(remote_fd, in_fd, nullptr, size); + if (ret < 0) { + LOGERRNO("Failed to send data"); + close(in_fd); + return -1; + } + + close(in_fd); + return size; +} + +static void companionHandler(int remote_fd) { + // Serve module dex + auto size = sendFile(remote_fd, "/data/adb/modules/safetynet-fix/classes.dex"); + LOGD("Sent module payload: %ld bytes", size); +} + +} + +REGISTER_ZYGISK_COMPANION(safetynetfix::companionHandler) +REGISTER_ZYGISK_MODULE(safetynetfix::SafetyNetFixModule) diff --git a/zygisk/module/jni/module.h b/zygisk/module/jni/module.h new file mode 100644 index 0000000..3afe597 --- /dev/null +++ b/zygisk/module/jni/module.h @@ -0,0 +1,17 @@ +#pragma once + +namespace safetynetfix { + +static constexpr auto TAG = "SafetyNetFix/JNI"; + +#ifdef NDEBUG +#define LOGD(...) +#else +#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, TAG, __VA_ARGS__) +#endif + +#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, TAG, __VA_ARGS__) +#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, TAG, __VA_ARGS__) +#define LOGERRNO(...) __android_log_print(ANDROID_LOG_ERROR, TAG, __VA_ARGS__ ": %d (%s)", errno, strerror(errno)) + +}