├── module ├── .gitignore ├── system │ └── etc │ │ └── security │ │ └── cacerts │ │ └── .gitkeep ├── module.prop └── post-fs-data.sh ├── zygisk_module ├── .gitignore ├── src │ └── main │ │ └── AndroidManifest.xml ├── jni │ ├── Application.mk │ ├── Android.mk │ ├── common.cpp │ ├── common.h │ ├── module.cpp │ ├── companion.cpp │ └── zygisk.hpp └── build.gradle ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── .gitmodules ├── .gitignore ├── settings.gradle ├── ndk_path.py ├── gradle.properties ├── dist.sh ├── gradlew.bat ├── README.md └── gradlew /module/.gitignore: -------------------------------------------------------------------------------- 1 | /zygisk 2 | /META-INF 3 | -------------------------------------------------------------------------------- /module/system/etc/security/cacerts/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /zygisk_module/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | /libs 3 | /obj 4 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ngorskikh/adguardcert/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /zygisk_module/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "zygisk_module/jni/libcxx"] 2 | path = zygisk_module/jni/libcxx 3 | url = https://github.com/topjohnwu/libcxx.git 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea 5 | .DS_Store 6 | /build 7 | /captures 8 | .externalNativeBuild 9 | .cxx 10 | local.properties 11 | *.zip 12 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | dependencyResolutionManagement { 2 | repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) 3 | repositories { 4 | google() 5 | mavenCentral() 6 | } 7 | } 8 | include ':zygisk_module' 9 | -------------------------------------------------------------------------------- /zygisk_module/jni/Application.mk: -------------------------------------------------------------------------------- 1 | APP_ABI := armeabi-v7a arm64-v8a x86 x86_64 2 | APP_CPPFLAGS := -std=c++17 -fno-exceptions -fno-rtti -fvisibility=hidden -fvisibility-inlines-hidden 3 | APP_STL := none 4 | APP_PLATFORM := android-21 5 | -------------------------------------------------------------------------------- /module/module.prop: -------------------------------------------------------------------------------- 1 | id=adguardcert 2 | name=AdGuard Certificate 3 | version=v1.0 4 | versionCode=1 5 | author=AdGuard 6 | description=Copies AdGuard's CA certificate from the user certificate store to the system store and forces Zygisk unmount procedures for Chrome. 7 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Sun Sep 26 02:13:18 PDT 2021 2 | distributionBase=GRADLE_USER_HOME 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.0.2-all.zip 4 | distributionPath=wrapper/dists 5 | zipStorePath=wrapper/dists 6 | zipStoreBase=GRADLE_USER_HOME 7 | -------------------------------------------------------------------------------- /zygisk_module/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'com.android.library' 3 | } 4 | 5 | android { 6 | compileSdkVersion 31 7 | ndkVersion "23.1.7779620" 8 | 9 | externalNativeBuild { 10 | ndkBuild { 11 | path("jni/Android.mk") 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /zygisk_module/jni/Android.mk: -------------------------------------------------------------------------------- 1 | LOCAL_PATH := $(call my-dir) 2 | 3 | include $(CLEAR_VARS) 4 | LOCAL_MODULE := copycert 5 | LOCAL_SRC_FILES := module.cpp companion.cpp common.cpp 6 | LOCAL_STATIC_LIBRARIES := libcxx 7 | LOCAL_LDLIBS := -llog 8 | include $(BUILD_SHARED_LIBRARY) 9 | 10 | include jni/libcxx/Android.mk 11 | 12 | # If you do not want to use libc++, link to system stdc++ 13 | # so that you can at least call the new operator in your code 14 | 15 | # include $(CLEAR_VARS) 16 | # LOCAL_MODULE := example 17 | # LOCAL_SRC_FILES := example.cpp 18 | # LOCAL_LDLIBS := -llog -lstdc++ 19 | # include $(BUILD_SHARED_LIBRARY) 20 | -------------------------------------------------------------------------------- /ndk_path.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import os 4 | 5 | def get_ndk_path(): 6 | sdk_path = os.environ.get('ANDROID_HOME') 7 | if os.path.isdir(sdk_path): 8 | path = os.path.join(sdk_path, 'ndk') 9 | if os.path.isdir(path): 10 | # Android Studio can install multiple ndk versions in 'ndk'. 11 | # Find the newest one. 12 | ndk_version = None 13 | for name in os.listdir(path): 14 | if not ndk_version or ndk_version < name: 15 | ndk_version = name 16 | if ndk_version: 17 | return os.path.join(path, ndk_version) 18 | ndk_path = os.path.join(sdk_path, 'ndk-bundle') 19 | if os.path.isdir(ndk_path): 20 | return ndk_path 21 | return None 22 | 23 | print(get_ndk_path()) 24 | -------------------------------------------------------------------------------- /zygisk_module/jni/common.cpp: -------------------------------------------------------------------------------- 1 | #include "common.h" 2 | #include 3 | 4 | int ag::read_int(int fd) { 5 | int i; 6 | if (read(fd, &i, sizeof(i)) != sizeof(i)) { 7 | return INT_MIN; 8 | } 9 | return i; 10 | } 11 | 12 | std::string ag::read_string(int fd) { 13 | size_t size = 0; 14 | if (read(fd, &size, sizeof(size)) != sizeof(size)) { 15 | return ""; 16 | } 17 | std::string s; 18 | s.resize(size); 19 | if (read(fd, s.data(), s.size()) != s.size()) { 20 | return ""; 21 | } 22 | return s; 23 | } 24 | 25 | void ag::write_int(int fd, int v) { 26 | write(fd, &v, sizeof(v)); 27 | } 28 | 29 | void ag::write_string(int fd, std::string_view s) { 30 | size_t size = s.size(); 31 | write(fd, &size, sizeof(size)); 32 | write(fd, s.data(), size); 33 | } 34 | -------------------------------------------------------------------------------- /module/post-fs-data.sh: -------------------------------------------------------------------------------- 1 | #!/system/bin/sh 2 | MODDIR=${0%/*} 3 | 4 | # Android hashes the subject to get the filename, field order is significant 5 | # AdGuard certificate is /C=EN/O=AdGuard/CN=AdGuard Personal CA 6 | # The filename is then . where is an integer 7 | AG_CERT_HASH=0f4ed297 8 | cp -f /data/misc/user/*/cacerts-added/${AG_CERT_HASH}.* $MODDIR/system/etc/security/cacerts 9 | chown -R 0:0 $MODDIR/system/etc/security/cacerts 10 | 11 | [ "$(getenforce)" = "Enforcing" ] || exit 0 12 | 13 | default_selinux_context=u:object_r:system_file:s0 14 | selinux_context=$(ls -Zd /system/etc/security/cacerts | awk '{print $1}') 15 | 16 | if [ -n "$selinux_context" ] && [ "$selinux_context" != "?" ]; then 17 | chcon -R $selinux_context $MODDIR/system/etc/security/cacerts 18 | else 19 | chcon -R $default_selinux_context $MODDIR/system/etc/security/cacerts 20 | fi 21 | -------------------------------------------------------------------------------- /zygisk_module/jni/common.h: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include 7 | 8 | #include 9 | 10 | #define LOG_TAG "CopyCertificates" 11 | 12 | #ifndef NDEBUG 13 | #define dbglog(fmt_, ...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, "%s(): " fmt_, __func__, ##__VA_ARGS__) 14 | #else 15 | #define dbglog(fmt_, ...) ((void) fmt_) 16 | #endif 17 | 18 | #define warnlog(fmt_, ...) __android_log_print(ANDROID_LOG_WARN, LOG_TAG, "%s(): " fmt_, __func__, ##__VA_ARGS__) 19 | 20 | namespace ag { 21 | 22 | template 23 | using Ftor = std::integral_constant; 24 | using Dir = std::unique_ptr>; 25 | 26 | int read_int(int fd); 27 | std::string read_string(int fd); 28 | 29 | void write_int(int fd, int v); 30 | void write_string(int fd, std::string_view s); 31 | 32 | } 33 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | # IDE (e.g. Android Studio) users: 3 | # Gradle settings configured through the IDE *will override* 4 | # any settings specified in this file. 5 | # For more details on how to configure your build environment visit 6 | # http://www.gradle.org/docs/current/userguide/build_environment.html 7 | # Specifies the JVM arguments used for the daemon process. 8 | # The setting is particularly useful for tweaking memory settings. 9 | org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8 10 | # When configured, Gradle will run in incubating parallel mode. 11 | # This option should only be used with decoupled projects. More details, visit 12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 13 | # org.gradle.parallel=true 14 | # AndroidX package structure to make it clearer which packages are bundled with the 15 | # Android operating system, and which are packaged with your app"s APK 16 | # https://developer.android.com/topic/libraries/support-library/androidx-rn 17 | android.useAndroidX=true 18 | # Automatically convert third-party libraries to use AndroidX 19 | android.enableJetifier=true 20 | -------------------------------------------------------------------------------- /dist.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | if [ -z "${ANDROID_HOME}" ]; then 4 | echo "Specify the Android SDK directory through the ANDROID_HOME environment variable" 5 | exit 1 6 | fi 7 | 8 | NDK_PATH=$(./ndk_path.py) 9 | 10 | if [ ! -d "${NDK_PATH}" ]; then 11 | echo "NDK version ${NDK_VERSION} is required and was not found at ${NDK_PATH}" 12 | exit 1 13 | fi 14 | 15 | NDK_BUILD="${NDK_PATH}/ndk-build" 16 | 17 | (cd ./zygisk_module && ${NDK_BUILD} -j8) || exit 1 18 | 19 | mkdir ./module/zygisk 20 | 21 | for i in $(ls ./zygisk_module/libs); do 22 | cp -f ./zygisk_module/libs/$i/*.so ./module/zygisk/$i.so 23 | done 24 | 25 | UPDATE_BINARY_URL="https://raw.githubusercontent.com/topjohnwu/Magisk/master/scripts/module_installer.sh" 26 | 27 | mkdir -p ./module/META-INF/com/google/android 28 | curl "${UPDATE_BINARY_URL}" > ./module/META-INF/com/google/android/update-binary 29 | echo "#MAGISK" > ./module/META-INF/com/google/android/updater-script 30 | 31 | VERSION=$(sed -ne "s/version=\(.*\)/\1/gp" ./module/module.prop) 32 | NAME=$(sed -ne "s/id=\(.*\)/\1/gp" ./module/module.prop) 33 | 34 | rm -f ${NAME}-${VERSION}.zip 35 | ( 36 | cd ./module 37 | zip ../${NAME}-${VERSION}.zip -r * -x ".*" "*/.*" 38 | ) 39 | -------------------------------------------------------------------------------- /zygisk_module/jni/module.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include 5 | 6 | #include "common.h" 7 | #include "zygisk.hpp" 8 | 9 | static const std::string PACKAGES_TO_UNMOUNT[] = { 10 | "com.android.chrome", 11 | "com.chrome.canary", 12 | "com.chrome.beta", 13 | "com.chrome.dev", 14 | }; 15 | 16 | class MyModule : public zygisk::ModuleBase { 17 | public: 18 | void onLoad(zygisk::Api *api, JNIEnv *env) override { 19 | this->api = api; 20 | this->env = env; 21 | } 22 | 23 | void preAppSpecialize(zygisk::AppSpecializeArgs *args) override { 24 | int fd = api->connectCompanion(); 25 | if (fd < 0) { 26 | warnlog("Failed to connect companion"); 27 | return; 28 | } 29 | ag::write_int(fd, args->uid); 30 | std::string package = ag::read_string(fd); 31 | if (std::any_of(std::begin(PACKAGES_TO_UNMOUNT), std::end(PACKAGES_TO_UNMOUNT), 32 | [&](const std::string &pkg) { return pkg == package; })) { 33 | dbglog("Forcing denylist unmount routines for package: %s", package.c_str()); 34 | api->setOption(zygisk::Option::FORCE_DENYLIST_UNMOUNT); 35 | } 36 | api->setOption(zygisk::Option::DLCLOSE_MODULE_LIBRARY); 37 | close(fd); 38 | } 39 | 40 | private: 41 | zygisk::Api *api; 42 | JNIEnv *env; 43 | }; 44 | 45 | REGISTER_ZYGISK_MODULE(MyModule) 46 | -------------------------------------------------------------------------------- /zygisk_module/jni/companion.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | #include "common.h" 11 | #include "zygisk.hpp" 12 | 13 | #define APP_ID(uid) (uid % 100000) 14 | 15 | using Map = std::unordered_map; 16 | static std::shared_ptr g_app_id_to_pkg; 17 | 18 | static void rescan_packages() { 19 | struct stat st{}; 20 | static std::atomic_int64_t packages_xml_ino{0}; 21 | 22 | stat("/data/system/packages.xml", &st); 23 | 24 | if (packages_xml_ino == st.st_ino) { 25 | dbglog("Packages unchanged, no need to rescan"); 26 | return; 27 | } 28 | 29 | static std::mutex rescan_mutex; 30 | std::scoped_lock l(rescan_mutex); 31 | 32 | if (packages_xml_ino == st.st_ino) { 33 | dbglog("Packages unchanged, no need to rescan"); 34 | return; 35 | } 36 | 37 | dbglog("Rescanning packages"); 38 | 39 | packages_xml_ino = st.st_ino; 40 | 41 | ag::Dir data_dir; 42 | data_dir.reset(opendir("/data/user_de")); 43 | 44 | if (!data_dir) { 45 | dbglog("Failed to open /data/user_de"); 46 | data_dir.reset(opendir("/data/user")); 47 | if (!data_dir) { 48 | warnlog("Failed to open /data/user, can't scan apps"); 49 | return; 50 | } 51 | } 52 | 53 | Map app_id_to_pkg; 54 | dirent *entry; 55 | while ((entry = readdir(data_dir.get()))) { 56 | // For each user 57 | int dir_fd = openat(dirfd(data_dir.get()), entry->d_name, O_RDONLY); 58 | if (ag::Dir dir{fdopendir(dir_fd)}) { 59 | while ((entry = readdir(dir.get()))) { 60 | // For each package 61 | struct stat st{}; 62 | fstatat(dir_fd, entry->d_name, &st, 0); 63 | int uid = st.st_uid; 64 | int app_id = APP_ID(uid); 65 | if (app_id_to_pkg.count(app_id)) { 66 | // This app ID has been handled 67 | continue; 68 | } 69 | dbglog("uid: %d, app_id: %d, package: %s", uid, app_id, entry->d_name); 70 | app_id_to_pkg.emplace(app_id, entry->d_name); 71 | } 72 | } else { 73 | close(dir_fd); 74 | } 75 | } 76 | 77 | std::atomic_store(&g_app_id_to_pkg, std::make_shared(std::move(app_id_to_pkg))); 78 | } 79 | 80 | static void companion_handler(int fd) { 81 | rescan_packages(); 82 | 83 | auto app_id_to_pkg = std::atomic_load(&g_app_id_to_pkg); 84 | 85 | int uid = ag::read_int(fd); 86 | 87 | auto it = app_id_to_pkg->find(APP_ID(uid)); 88 | if (it == app_id_to_pkg->end()) { 89 | dbglog("uid: %d, app_id: %d, package not found", uid, APP_ID(uid)); 90 | ag::write_string(fd, ""); 91 | } else { 92 | dbglog("uid: %d, app_id: %d, package: %s", uid, APP_ID(uid), it->second.c_str()); 93 | ag::write_string(fd, it->second); 94 | } 95 | } 96 | 97 | REGISTER_ZYGISK_COMPANION(companion_handler) 98 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%" == "" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%" == "" set DIRNAME=. 29 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME% 31 | 32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 34 | 35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 37 | 38 | @rem Find java.exe 39 | if defined JAVA_HOME goto findJavaFromJavaHome 40 | 41 | set JAVA_EXE=java.exe 42 | %JAVA_EXE% -version >NUL 2>&1 43 | if "%ERRORLEVEL%" == "0" goto execute 44 | 45 | echo. 46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 47 | echo. 48 | echo Please set the JAVA_HOME variable in your environment to match the 49 | echo location of your Java installation. 50 | 51 | goto fail 52 | 53 | :findJavaFromJavaHome 54 | set JAVA_HOME=%JAVA_HOME:"=% 55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 56 | 57 | if exist "%JAVA_EXE%" goto execute 58 | 59 | echo. 60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 61 | echo. 62 | echo Please set the JAVA_HOME variable in your environment to match the 63 | echo location of your Java installation. 64 | 65 | goto fail 66 | 67 | :execute 68 | @rem Setup the command line 69 | 70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 71 | 72 | 73 | @rem Execute Gradle 74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 75 | 76 | :end 77 | @rem End local scope for the variables with windows NT shell 78 | if "%ERRORLEVEL%"=="0" goto mainEnd 79 | 80 | :fail 81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 82 | rem the _cmd.exe /c_ return code! 83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 84 | exit /b 1 85 | 86 | :mainEnd 87 | if "%OS%"=="Windows_NT" endlocal 88 | 89 | :omega 90 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # AdGuard Certificate 2 | 3 | Based on [Move Certificates](https://github.com/Magisk-Modules-Repo/movecert). 4 | 5 | This Magisk module supplements [AdGuard for Android][agandroid] and allows installing 6 | AdGuard's CA certificate to the System store on rooted devices. 7 | 8 | ## Why could you need it? 9 | 10 | AdGuard for Android provides a feature called [HTTPS filtering][httpsfiltering]. It allows 11 | filtering of encrypted HTTPS traffic on your Android device. This feature requires 12 | adding the AdGuard's CA certificate to the list of trusted certificates. 13 | 14 | By default, on a non-rooted device only a limited subset of apps (mostly, browsers) 15 | trust the CA certificates installed to the **User store**. The only option to allow 16 | filtering of all other apps' traffic is to install the certificate to the **System store**. 17 | Unfortunately, this is only possible on rooted devices. 18 | 19 | [agandroid]: https://adguard.com/adguard-android/overview.html 20 | [httpsfiltering]: https://kb.adguard.com/general/https-filtering 21 | 22 | ## Usage 23 | 24 | 1. Enable HTTPS filtering in AdGuard for Android and save AdGuard's certificate to the User store. 25 | 2. Go to *Magisk -> Settings* and enable **Zygisk**. 26 | 3. Download the `.zip` file from the [latest release][latestrelease]. 27 | 4. Go to *Magisk -> Modules -> Install from storage* and select the downloaded `.zip` file. 28 | 5. Reboot. 29 | 30 | If a new version comes out, repeat steps 3-5 to update the module. 31 | 32 |
33 | Illustrated instruction 34 | 35 | ![Open Magisk settings](https://user-images.githubusercontent.com/5947035/161061257-680c784b-b476-432d-8dfd-2528fe239346.png) 36 | 37 | ![Enable Zygisk](https://user-images.githubusercontent.com/5947035/161061268-3367d668-cbbd-441d-9e6d-a4cbc3978b3e.png) 38 | 39 | ![Go back to Magisk main screen](https://user-images.githubusercontent.com/5947035/161061273-329e3f8a-c957-4005-a8f7-2056b1866b08.png) 40 | 41 | ![Open Magisk modules](https://user-images.githubusercontent.com/5947035/161061277-1ada3a87-d0cb-44c0-9edd-77b00669759c.png) 42 | 43 | ![Install from storage](https://user-images.githubusercontent.com/5947035/161061283-8e3d6ed2-ca36-4825-bca4-fbb9f9185f68.png) 44 | 45 | ![Select AdGuard certificate module](https://user-images.githubusercontent.com/5947035/161061285-4ea302ad-99ec-4619-be05-3b83f64b9e4f.png) 46 | 47 | ![Reboot the device](https://user-images.githubusercontent.com/5947035/161061291-54ad008f-4c76-4ee3-975d-307fd0fe7220.png) 48 | 49 |
50 | 51 | 52 | [latestrelease]: https://github.com/AdguardTeam/adguardcert/releases/latest/ 53 | 54 | ## Chrome and Chromium-based browsers 55 | 56 | Chrome has recently started requiring CT logs for CA certs found in the **System store**. 57 | This module copies AdGuard's CA certificate from the **User store** to the **System store**. 58 | It also contains a Zygisk module that reverts any modifications done by Magisk for 59 | Chrome's processes. This way Chrome only finds AdGuard's certificate in the User store 60 | and doesn't complain about the missing CT log, while other apps continue to use the 61 | same certificate from the System store. 62 | 63 | ## Building 64 | 65 | Update git modules: 66 | 67 | ```shell 68 | git submodule init && git submodule update 69 | ``` 70 | 71 | You'll need an Android SDK with NDK installed (tested with NDK 22 and 23). Run: 72 | 73 | ```shell 74 | ANDROID_HOME= ./dist.sh 75 | ``` 76 | 77 | ## Advanced 78 | 79 | If you prefer to manage your Zygisk denylist yourself, simply remove the Zygisk part of the module: 80 | 81 | ```shell 82 | zip adguardcert-v1.0.zip -d "zygisk/*" 83 | ``` -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | # 4 | # Copyright 2015 the original author or authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | ############################################################################## 20 | ## 21 | ## Gradle start up script for UN*X 22 | ## 23 | ############################################################################## 24 | 25 | # Attempt to set APP_HOME 26 | # Resolve links: $0 may be a link 27 | PRG="$0" 28 | # Need this for relative symlinks. 29 | while [ -h "$PRG" ] ; do 30 | ls=`ls -ld "$PRG"` 31 | link=`expr "$ls" : '.*-> \(.*\)$'` 32 | if expr "$link" : '/.*' > /dev/null; then 33 | PRG="$link" 34 | else 35 | PRG=`dirname "$PRG"`"/$link" 36 | fi 37 | done 38 | SAVED="`pwd`" 39 | cd "`dirname \"$PRG\"`/" >/dev/null 40 | APP_HOME="`pwd -P`" 41 | cd "$SAVED" >/dev/null 42 | 43 | APP_NAME="Gradle" 44 | APP_BASE_NAME=`basename "$0"` 45 | 46 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 47 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 48 | 49 | # Use the maximum available, or set MAX_FD != -1 to use that value. 50 | MAX_FD="maximum" 51 | 52 | warn () { 53 | echo "$*" 54 | } 55 | 56 | die () { 57 | echo 58 | echo "$*" 59 | echo 60 | exit 1 61 | } 62 | 63 | # OS specific support (must be 'true' or 'false'). 64 | cygwin=false 65 | msys=false 66 | darwin=false 67 | nonstop=false 68 | case "`uname`" in 69 | CYGWIN* ) 70 | cygwin=true 71 | ;; 72 | Darwin* ) 73 | darwin=true 74 | ;; 75 | MINGW* ) 76 | msys=true 77 | ;; 78 | NONSTOP* ) 79 | nonstop=true 80 | ;; 81 | esac 82 | 83 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 84 | 85 | 86 | # Determine the Java command to use to start the JVM. 87 | if [ -n "$JAVA_HOME" ] ; then 88 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 89 | # IBM's JDK on AIX uses strange locations for the executables 90 | JAVACMD="$JAVA_HOME/jre/sh/java" 91 | else 92 | JAVACMD="$JAVA_HOME/bin/java" 93 | fi 94 | if [ ! -x "$JAVACMD" ] ; then 95 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 96 | 97 | Please set the JAVA_HOME variable in your environment to match the 98 | location of your Java installation." 99 | fi 100 | else 101 | JAVACMD="java" 102 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 103 | 104 | Please set the JAVA_HOME variable in your environment to match the 105 | location of your Java installation." 106 | fi 107 | 108 | # Increase the maximum file descriptors if we can. 109 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 110 | MAX_FD_LIMIT=`ulimit -H -n` 111 | if [ $? -eq 0 ] ; then 112 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 113 | MAX_FD="$MAX_FD_LIMIT" 114 | fi 115 | ulimit -n $MAX_FD 116 | if [ $? -ne 0 ] ; then 117 | warn "Could not set maximum file descriptor limit: $MAX_FD" 118 | fi 119 | else 120 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 121 | fi 122 | fi 123 | 124 | # For Darwin, add options to specify how the application appears in the dock 125 | if $darwin; then 126 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 127 | fi 128 | 129 | # For Cygwin or MSYS, switch paths to Windows format before running java 130 | if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then 131 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 132 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 133 | 134 | JAVACMD=`cygpath --unix "$JAVACMD"` 135 | 136 | # We build the pattern for arguments to be converted via cygpath 137 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 138 | SEP="" 139 | for dir in $ROOTDIRSRAW ; do 140 | ROOTDIRS="$ROOTDIRS$SEP$dir" 141 | SEP="|" 142 | done 143 | OURCYGPATTERN="(^($ROOTDIRS))" 144 | # Add a user-defined pattern to the cygpath arguments 145 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 146 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 147 | fi 148 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 149 | i=0 150 | for arg in "$@" ; do 151 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 152 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 153 | 154 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 155 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 156 | else 157 | eval `echo args$i`="\"$arg\"" 158 | fi 159 | i=`expr $i + 1` 160 | done 161 | case $i in 162 | 0) set -- ;; 163 | 1) set -- "$args0" ;; 164 | 2) set -- "$args0" "$args1" ;; 165 | 3) set -- "$args0" "$args1" "$args2" ;; 166 | 4) set -- "$args0" "$args1" "$args2" "$args3" ;; 167 | 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 168 | 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 169 | 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 170 | 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 171 | 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 172 | esac 173 | fi 174 | 175 | # Escape application args 176 | save () { 177 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 178 | echo " " 179 | } 180 | APP_ARGS=`save "$@"` 181 | 182 | # Collect all arguments for the java command, following the shell quoting and substitution rules 183 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 184 | 185 | exec "$JAVACMD" "$@" 186 | -------------------------------------------------------------------------------- /zygisk_module/jni/zygisk.hpp: -------------------------------------------------------------------------------- 1 | // This is the public API for Zygisk modules. 2 | // DO NOT MODIFY ANY CODE IN THIS HEADER. 3 | 4 | #pragma once 5 | 6 | #include 7 | 8 | #define ZYGISK_API_VERSION 3 9 | 10 | /* 11 | 12 | Define a class and inherit zygisk::ModuleBase to implement the functionality of your module. 13 | Use the macro REGISTER_ZYGISK_MODULE(className) to register that class to Zygisk. 14 | 15 | Please note that modules will only be loaded after zygote has forked the child process. 16 | THIS MEANS ALL OF YOUR CODE RUNS IN THE APP/SYSTEM SERVER PROCESS, NOT THE ZYGOTE DAEMON! 17 | 18 | Example code: 19 | 20 | static jint (*orig_logger_entry_max)(JNIEnv *env); 21 | static jint my_logger_entry_max(JNIEnv *env) { return orig_logger_entry_max(env); } 22 | 23 | static void example_handler(int socket) { ... } 24 | 25 | class ExampleModule : public zygisk::ModuleBase { 26 | public: 27 | void onLoad(zygisk::Api *api, JNIEnv *env) override { 28 | this->api = api; 29 | this->env = env; 30 | } 31 | void preAppSpecialize(zygisk::AppSpecializeArgs *args) override { 32 | JNINativeMethod methods[] = { 33 | { "logger_entry_max_payload_native", "()I", (void*) my_logger_entry_max }, 34 | }; 35 | api->hookJniNativeMethods(env, "android/util/Log", methods, 1); 36 | *(void **) &orig_logger_entry_max = methods[0].fnPtr; 37 | } 38 | private: 39 | zygisk::Api *api; 40 | JNIEnv *env; 41 | }; 42 | 43 | REGISTER_ZYGISK_MODULE(ExampleModule) 44 | 45 | REGISTER_ZYGISK_COMPANION(example_handler) 46 | 47 | */ 48 | 49 | namespace zygisk { 50 | 51 | struct Api; 52 | struct AppSpecializeArgs; 53 | struct ServerSpecializeArgs; 54 | 55 | class ModuleBase { 56 | public: 57 | 58 | // This function is called when the module is loaded into the target process. 59 | // A Zygisk API handle will be sent as an argument; call utility functions or interface 60 | // with Zygisk through this handle. 61 | virtual void onLoad([[maybe_unused]] Api *api, [[maybe_unused]] JNIEnv *env) {} 62 | 63 | // This function is called before the app process is specialized. 64 | // At this point, the process just got forked from zygote, but no app specific specialization 65 | // is applied. This means that the process does not have any sandbox restrictions and 66 | // still runs with the same privilege of zygote. 67 | // 68 | // All the arguments that will be sent and used for app specialization is passed as a single 69 | // AppSpecializeArgs object. You can read and overwrite these arguments to change how the app 70 | // process will be specialized. 71 | // 72 | // If you need to run some operations as superuser, you can call Api::connectCompanion() to 73 | // get a socket to do IPC calls with a root companion process. 74 | // See Api::connectCompanion() for more info. 75 | virtual void preAppSpecialize([[maybe_unused]] AppSpecializeArgs *args) {} 76 | 77 | // This function is called after the app process is specialized. 78 | // At this point, the process has all sandbox restrictions enabled for this application. 79 | // This means that this function runs as the same privilege of the app's own code. 80 | virtual void postAppSpecialize([[maybe_unused]] const AppSpecializeArgs *args) {} 81 | 82 | // This function is called before the system server process is specialized. 83 | // See preAppSpecialize(args) for more info. 84 | virtual void preServerSpecialize([[maybe_unused]] ServerSpecializeArgs *args) {} 85 | 86 | // This function is called after the system server process is specialized. 87 | // At this point, the process runs with the privilege of system_server. 88 | virtual void postServerSpecialize([[maybe_unused]] const ServerSpecializeArgs *args) {} 89 | }; 90 | 91 | struct AppSpecializeArgs { 92 | // Required arguments. These arguments are guaranteed to exist on all Android versions. 93 | jint &uid; 94 | jint &gid; 95 | jintArray &gids; 96 | jint &runtime_flags; 97 | jobjectArray &rlimits; 98 | jint &mount_external; 99 | jstring &se_info; 100 | jstring &nice_name; 101 | jstring &instruction_set; 102 | jstring &app_data_dir; 103 | 104 | // Optional arguments. Please check whether the pointer is null before de-referencing 105 | jintArray *const fds_to_ignore; 106 | jboolean *const is_child_zygote; 107 | jboolean *const is_top_app; 108 | jobjectArray *const pkg_data_info_list; 109 | jobjectArray *const whitelisted_data_info_list; 110 | jboolean *const mount_data_dirs; 111 | jboolean *const mount_storage_dirs; 112 | 113 | AppSpecializeArgs() = delete; 114 | }; 115 | 116 | struct ServerSpecializeArgs { 117 | jint &uid; 118 | jint &gid; 119 | jintArray &gids; 120 | jint &runtime_flags; 121 | jlong &permitted_capabilities; 122 | jlong &effective_capabilities; 123 | 124 | ServerSpecializeArgs() = delete; 125 | }; 126 | 127 | namespace internal { 128 | struct api_table; 129 | template void entry_impl(api_table *, JNIEnv *); 130 | } 131 | 132 | // These values are used in Api::setOption(Option) 133 | enum Option : int { 134 | // Force Magisk's denylist unmount routines to run on this process. 135 | // 136 | // Setting this option only makes sense in preAppSpecialize. 137 | // The actual unmounting happens during app process specialization. 138 | // 139 | // Set this option to force all Magisk and modules' files to be unmounted from the 140 | // mount namespace of the process, regardless of the denylist enforcement status. 141 | FORCE_DENYLIST_UNMOUNT = 0, 142 | 143 | // When this option is set, your module's library will be dlclose-ed after post[XXX]Specialize. 144 | // Be aware that after dlclose-ing your module, all of your code will be unmapped from memory. 145 | // YOU MUST NOT ENABLE THIS OPTION AFTER HOOKING ANY FUNCTIONS IN THE PROCESS. 146 | DLCLOSE_MODULE_LIBRARY = 1, 147 | }; 148 | 149 | // Bit masks of the return value of Api::getFlags() 150 | enum StateFlag : uint32_t { 151 | // The user has granted root access to the current process 152 | PROCESS_GRANTED_ROOT = (1u << 0), 153 | 154 | // The current process was added on the denylist 155 | PROCESS_ON_DENYLIST = (1u << 1), 156 | }; 157 | 158 | // All API functions will stop working after post[XXX]Specialize as Zygisk will be unloaded 159 | // from the specialized process afterwards. 160 | struct Api { 161 | 162 | // Connect to a root companion process and get a Unix domain socket for IPC. 163 | // 164 | // This API only works in the pre[XXX]Specialize functions due to SELinux restrictions. 165 | // 166 | // The pre[XXX]Specialize functions run with the same privilege of zygote. 167 | // If you would like to do some operations with superuser permissions, register a handler 168 | // function that would be called in the root process with REGISTER_ZYGISK_COMPANION(func). 169 | // Another good use case for a companion process is that if you want to share some resources 170 | // across multiple processes, hold the resources in the companion process and pass it over. 171 | // 172 | // The root companion process is ABI aware; that is, when calling this function from a 32-bit 173 | // process, you will be connected to a 32-bit companion process, and vice versa for 64-bit. 174 | // 175 | // Returns a file descriptor to a socket that is connected to the socket passed to your 176 | // module's companion request handler. Returns -1 if the connection attempt failed. 177 | int connectCompanion(); 178 | 179 | // Get the file descriptor of the root folder of the current module. 180 | // 181 | // This API only works in the pre[XXX]Specialize functions. 182 | // Accessing the directory returned is only possible in the pre[XXX]Specialize functions 183 | // or in the root companion process (assuming that you sent the fd over the socket). 184 | // Both restrictions are due to SELinux and UID. 185 | // 186 | // Returns -1 if errors occurred. 187 | int getModuleDir(); 188 | 189 | // Set various options for your module. 190 | // Please note that this function accepts one single option at a time. 191 | // Check zygisk::Option for the full list of options available. 192 | void setOption(Option opt); 193 | 194 | // Get information about the current process. 195 | // Returns bitwise-or'd zygisk::StateFlag values. 196 | uint32_t getFlags(); 197 | 198 | // Hook JNI native methods for a class 199 | // 200 | // Lookup all registered JNI native methods and replace it with your own functions. 201 | // The original function pointer will be saved in each JNINativeMethod's fnPtr. 202 | // If no matching class, method name, or signature is found, that specific JNINativeMethod.fnPtr 203 | // will be set to nullptr. 204 | void hookJniNativeMethods(JNIEnv *env, const char *className, JNINativeMethod *methods, int numMethods); 205 | 206 | // For ELFs loaded in memory matching `regex`, replace function `symbol` with `newFunc`. 207 | // If `oldFunc` is not nullptr, the original function pointer will be saved to `oldFunc`. 208 | void pltHookRegister(const char *regex, const char *symbol, void *newFunc, void **oldFunc); 209 | 210 | // For ELFs loaded in memory matching `regex`, exclude hooks registered for `symbol`. 211 | // If `symbol` is nullptr, then all symbols will be excluded. 212 | void pltHookExclude(const char *regex, const char *symbol); 213 | 214 | // Commit all the hooks that was previously registered. 215 | // Returns false if an error occurred. 216 | bool pltHookCommit(); 217 | 218 | private: 219 | internal::api_table *impl; 220 | template friend void internal::entry_impl(internal::api_table *, JNIEnv *); 221 | }; 222 | 223 | // Register a class as a Zygisk module 224 | 225 | #define REGISTER_ZYGISK_MODULE(clazz) \ 226 | void zygisk_module_entry(zygisk::internal::api_table *table, JNIEnv *env) { \ 227 | zygisk::internal::entry_impl(table, env); \ 228 | } 229 | 230 | // Register a root companion request handler function for your module 231 | // 232 | // The function runs in a superuser daemon process and handles a root companion request from 233 | // your module running in a target process. The function has to accept an integer value, 234 | // which is a socket that is connected to the target process. 235 | // See Api::connectCompanion() for more info. 236 | // 237 | // NOTE: the function can run concurrently on multiple threads. 238 | // Be aware of race conditions if you have a globally shared resource. 239 | 240 | #define REGISTER_ZYGISK_COMPANION(func) \ 241 | void zygisk_companion_entry(int client) { func(client); } 242 | 243 | /************************************************************************************ 244 | * All the code after this point is internal code used to interface with Zygisk 245 | * and guarantee ABI stability. You do not have to understand what it is doing. 246 | ************************************************************************************/ 247 | 248 | namespace internal { 249 | 250 | struct module_abi { 251 | long api_version; 252 | ModuleBase *_this; 253 | 254 | void (*preAppSpecialize)(ModuleBase *, AppSpecializeArgs *); 255 | void (*postAppSpecialize)(ModuleBase *, const AppSpecializeArgs *); 256 | void (*preServerSpecialize)(ModuleBase *, ServerSpecializeArgs *); 257 | void (*postServerSpecialize)(ModuleBase *, const ServerSpecializeArgs *); 258 | 259 | module_abi(ModuleBase *module) : api_version(ZYGISK_API_VERSION), _this(module) { 260 | preAppSpecialize = [](auto self, auto args) { self->preAppSpecialize(args); }; 261 | postAppSpecialize = [](auto self, auto args) { self->postAppSpecialize(args); }; 262 | preServerSpecialize = [](auto self, auto args) { self->preServerSpecialize(args); }; 263 | postServerSpecialize = [](auto self, auto args) { self->postServerSpecialize(args); }; 264 | } 265 | }; 266 | 267 | struct api_table { 268 | // These first 2 entries are permanent, shall never change 269 | void *_this; 270 | bool (*registerModule)(api_table *, module_abi *); 271 | 272 | // Utility functions 273 | void (*hookJniNativeMethods)(JNIEnv *, const char *, JNINativeMethod *, int); 274 | void (*pltHookRegister)(const char *, const char *, void *, void **); 275 | void (*pltHookExclude)(const char *, const char *); 276 | bool (*pltHookCommit)(); 277 | 278 | // Zygisk functions 279 | int (*connectCompanion)(void * /* _this */); 280 | void (*setOption)(void * /* _this */, Option); 281 | int (*getModuleDir)(void * /* _this */); 282 | uint32_t (*getFlags)(void * /* _this */); 283 | }; 284 | 285 | template 286 | void entry_impl(api_table *table, JNIEnv *env) { 287 | ModuleBase *module = new T(); 288 | if (!table->registerModule(table, new module_abi(module))) 289 | return; 290 | auto api = new Api(); 291 | api->impl = table; 292 | module->onLoad(api, env); 293 | } 294 | 295 | } // namespace internal 296 | 297 | inline int Api::connectCompanion() { 298 | return impl->connectCompanion ? impl->connectCompanion(impl->_this) : -1; 299 | } 300 | inline int Api::getModuleDir() { 301 | return impl->getModuleDir ? impl->getModuleDir(impl->_this) : -1; 302 | } 303 | inline void Api::setOption(Option opt) { 304 | if (impl->setOption) impl->setOption(impl->_this, opt); 305 | } 306 | inline uint32_t Api::getFlags() { 307 | return impl->getFlags ? impl->getFlags(impl->_this) : 0; 308 | } 309 | inline void Api::hookJniNativeMethods(JNIEnv *env, const char *className, JNINativeMethod *methods, int numMethods) { 310 | if (impl->hookJniNativeMethods) impl->hookJniNativeMethods(env, className, methods, numMethods); 311 | } 312 | inline void Api::pltHookRegister(const char *regex, const char *symbol, void *newFunc, void **oldFunc) { 313 | if (impl->pltHookRegister) impl->pltHookRegister(regex, symbol, newFunc, oldFunc); 314 | } 315 | inline void Api::pltHookExclude(const char *regex, const char *symbol) { 316 | if (impl->pltHookExclude) impl->pltHookExclude(regex, symbol); 317 | } 318 | inline bool Api::pltHookCommit() { 319 | return impl->pltHookCommit != nullptr && impl->pltHookCommit(); 320 | } 321 | 322 | } // namespace zygisk 323 | 324 | [[gnu::visibility("default")]] [[gnu::used]] 325 | extern "C" void zygisk_module_entry(zygisk::internal::api_table *, JNIEnv *); 326 | 327 | [[gnu::visibility("default")]] [[gnu::used]] 328 | extern "C" void zygisk_companion_entry(int); 329 | --------------------------------------------------------------------------------