diff --git a/.idea/.gitignore b/.idea/.gitignore
new file mode 100644
index 0000000..26d3352
--- /dev/null
+++ b/.idea/.gitignore
@@ -0,0 +1,3 @@
+# Default ignored files
+/shelf/
+/workspace.xml
diff --git a/.idea/compiler.xml b/.idea/compiler.xml
new file mode 100644
index 0000000..fb7f4a8
--- /dev/null
+++ b/.idea/compiler.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
new file mode 100644
index 0000000..7ad0a7b
--- /dev/null
+++ b/.idea/misc.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/EZAndroid.keystore b/EZAndroid.keystore
new file mode 100644
index 0000000..a2703e6
Binary files /dev/null and b/EZAndroid.keystore differ
diff --git a/README.md b/README.md
index aa5b5db..fbcc350 100644
--- a/README.md
+++ b/README.md
@@ -1 +1,70 @@
-# EZProtect
\ No newline at end of file
+# EZProtect
+
+一个Native级的Android应用防篡改库
+
+去年在开发一个个人项目的过程中,发现自己的应用被人破解修改,并且被二次打包放到了网上,因此对应用安全防护进行了一点研究。
+
+虽然市场上大部分App已经进行了代码混淆、签名校验和Dex加固,但是现在的破解工具太方便了。诸如脱壳、去签名、重打包等功能,手机安装MT管理器或者NP管理器后都可以一条龙完成,因此需要继续增加破解难度。
+
+国内开发者用的最多的防篡改库可能是 https://github.com/lamster2018/EasyProtector ,该库集成了不少检测方法,但是他的重大缺点就是校验方法放在Java层,使用Xposed等框架可以轻易过掉检测,因此实现了一个Native级的Android应用防篡改库。
+
+### 防护能力
+
+1.防动态调试
+
+2.防逆向分析
+
+3.防恶意注入
+
+4.防二次打包
+
+5.防数据窃取
+
+### 检测功能
+
+#### 1.检测是否被动态调试
+
+有经验的破解者通常会通过IDA等工具来动态调试So,以过掉So库中的检测功能,因此首先需要对动态调试进行拦截,通常采用
+
+1.通过ptrace自添加防止被调试器挂载来反调试
+
+2.通过系统提供的Debug.isDebuggerConnected()方法来进行判断
+
+#### 2.检测APK的签名
+
+非常基础的检测手段,因为容易被一键破解,因此需要配合其他手段防御
+
+#### 3.检测APK的包名
+
+判断opendir("/data/data/CORRECT_PACKAGE_NAME")是否等于nullptr,如果没有指定私有目录的访问权限,说明不是正确的包名
+
+#### 4.检测是否被Xposed等框架注入
+
+检测"/proc/self/maps"目录下的文件,如果出现包含xposed、substrate、frida等名称的文件,说明有框架正在注入
+
+#### 5.检测PMS是否被Hook
+
+通过检测PMS是否继承Proxy类可以知道是否已被Hook
+
+#### 6.检测Application的className属性
+
+大部分工具都会通过修改入口Application类,并在修改后的Application类中添加各种破解代码来实现去签名校验,它们常常费了很大的劲比如Hook了PMS,同时为了防止你读取原包做文件完整性校验可能还进行了IO重定向,但偏偏忽视了对Application类名的隐藏,经测试该检测可以防御大部分工具的一键破解
+
+#### 7.检测Application的allowBackup属性
+
+正常情况下应该为false,如果检测到为true,说明应用被Hook了,破解者正在尝试导出应用私有信息
+
+#### 8.检测应用版本号
+
+破解者为了防止应用更新导致破解失效,通常会修改此versionCode,因此需要进行检测
+
+#### 9.检测Native与Java层获取到的APK的文件路径或者大小
+
+因为pm命令获取到的APK文件路径通常不容易被修改,这里与通过JavaApi获取到的APK文件路径做比较,如果文件路径或者文件大小不一致时,大概率是应用被IO重定向了,或者使用了VirtualXposed等分身软件
+
+#### 10.Apk和So文件完整性校验
+
+可以获取到当前APK文件和此加密So包的文件信息用于服务器校验
+
+注意,编译时需要使用Ollvm混淆,配置可以参考文章
+https://blog.csdn.net/u013314647/article/details/117740784?utm_medium=distribute.pc_relevant.none-task-blog-2~default~baidujs_title~default-0.no_search_link&spm=1001.2101.3001.4242
\ No newline at end of file
diff --git a/app/.gitignore b/app/.gitignore
new file mode 100644
index 0000000..42afabf
--- /dev/null
+++ b/app/.gitignore
@@ -0,0 +1 @@
+/build
\ No newline at end of file
diff --git a/app/build.gradle b/app/build.gradle
new file mode 100644
index 0000000..f64a6c0
--- /dev/null
+++ b/app/build.gradle
@@ -0,0 +1,44 @@
+plugins {
+ id 'com.android.application'
+ id 'org.jetbrains.kotlin.android'
+}
+
+android {
+ compileSdk 31
+
+ defaultConfig {
+ applicationId project.applicationId
+ minSdk 16
+ targetSdk 31
+ versionCode project.versionCode as int
+ versionName project.versionName
+
+ testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
+ }
+
+ buildTypes {
+ release {
+ minifyEnabled false
+ proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
+ }
+ }
+ compileOptions {
+ sourceCompatibility JavaVersion.VERSION_1_8
+ targetCompatibility JavaVersion.VERSION_1_8
+ }
+ kotlinOptions {
+ jvmTarget = '1.8'
+ }
+}
+
+dependencies {
+
+ api project(':app:ezprotect')
+ implementation 'androidx.core:core-ktx:1.7.0'
+ implementation 'androidx.appcompat:appcompat:1.3.1'
+ implementation 'com.google.android.material:material:1.4.0'
+ implementation 'androidx.constraintlayout:constraintlayout:2.1.1'
+ testImplementation 'junit:junit:4.13.2'
+ androidTestImplementation 'androidx.test.ext:junit:1.1.3'
+ androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
+}
\ No newline at end of file
diff --git a/app/ezprotect/.gitignore b/app/ezprotect/.gitignore
new file mode 100644
index 0000000..42afabf
--- /dev/null
+++ b/app/ezprotect/.gitignore
@@ -0,0 +1 @@
+/build
\ No newline at end of file
diff --git a/app/ezprotect/CMakeLists.txt b/app/ezprotect/CMakeLists.txt
new file mode 100644
index 0000000..3fb19ee
--- /dev/null
+++ b/app/ezprotect/CMakeLists.txt
@@ -0,0 +1,67 @@
+# For more information about using CMake with Android Studio, read the
+# documentation: https://d.android.com/studio/projects/add-native-code.html
+
+# Sets the minimum version of CMake required to build the native library.
+
+cmake_minimum_required(VERSION 3.18.1)
+
+#-mllvm -fla 控制流扁平化
+#-mllvm -sub 指令替换
+#-mllvm -bcf 虚假控制流程
+#-mllvm -sobf 字符串加密
+set(CMAKE_CXX_FLAGS "-mllvm -fla -mllvm -sub -mllvm -sobf ${CMAKE_CXX_FLAGS}")
+
+# Creates and names a library, sets it as either STATIC
+# or SHARED, and provides the relative paths to its source code.
+# You can define multiple libraries, and CMake builds them for you.
+# Gradle automatically packages shared libraries with your APK.
+
+file(GLOB native_src
+ "src/main/cpp/util/*.cpp"
+ "src/main/cpp/protect/*.cpp"
+ )
+add_library( # Sets the name of the library.
+ ezcore
+
+ # Sets the library as a shared library.
+ SHARED
+
+ # Provides a relative path to your source file(s).
+ ${native_src})
+
+# Searches for a specified prebuilt library and stores the path as a
+# variable. Because CMake includes system libraries in the search path by
+# default, you only need to specify the name of the public NDK library
+# you want to add. CMake verifies that the library exists before
+# completing its build.
+
+find_library( # Sets the name of the path variable.
+ log-lib
+
+ # Specifies the name of the NDK library that
+ # you want CMake to locate.
+ log)
+
+# Specifies libraries CMake should link to your target library. You
+# can link multiple libraries, such as libraries you define in this
+# build script, prebuilt third-party libraries, or system libraries.
+
+target_link_libraries( # Specifies the target library.
+ ezcore
+
+ # Links the target library to the log library
+ # included in the NDK.
+ ${log-lib})
+
+add_definitions("-fvisibility=hidden")
+
+# APK签名MD5值
+add_definitions(-DCORRECT_APK_SIGN="${CORRECT_APK_SIGN}")
+# APK包名
+add_definitions(-DCORRECT_PACKAGE_NAME="${CORRECT_PACKAGE_NAME}")
+# Application名
+add_definitions(-DCORRECT_APPLICATION_NAME="${CORRECT_APPLICATION_NAME}")
+# 加固后的Application名 【可选】比如360免费加固后的Application名为com.stub.StubApp
+add_definitions(-DCORRECT_APPLICATION_NAME_REINFORCE="")
+# 版本号,破解者为了防止应用更新导致破解失效,通常会修改此versionCode,因此进行检测
+add_definitions(-DCORRECT_VERSION_CODE=${CORRECT_VERSION_CODE})
\ No newline at end of file
diff --git a/app/ezprotect/build.gradle b/app/ezprotect/build.gradle
new file mode 100644
index 0000000..d83ff9f
--- /dev/null
+++ b/app/ezprotect/build.gradle
@@ -0,0 +1,49 @@
+plugins {
+ id 'com.android.library'
+ id 'org.jetbrains.kotlin.android'
+}
+
+android {
+ compileSdk 31
+
+ defaultConfig {
+ minSdk 16
+ targetSdk 31
+
+ consumerProguardFiles "consumer-rules.pro"
+ externalNativeBuild {
+ cmake {
+ arguments "-DANDROID_STL=c++_static",
+ "-DCORRECT_VERSION_CODE=" + project.versionCode,
+ "-DCORRECT_APPLICATION_NAME=" + "cn.ezandroid.ezprotect.MyApplication",
+ "-DCORRECT_PACKAGE_NAME=" + project.applicationId,
+ "-DCORRECT_APK_SIGN=" + "0f65981c4d8c44aaea1d9e232fbde4b1"
+ }
+ }
+ }
+
+ buildTypes {
+ release {
+ minifyEnabled false
+ proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
+ }
+ }
+ externalNativeBuild {
+ cmake {
+ path "CMakeLists.txt"
+ version "3.18.1"
+ }
+ }
+ compileOptions {
+ sourceCompatibility JavaVersion.VERSION_1_8
+ targetCompatibility JavaVersion.VERSION_1_8
+ }
+ kotlinOptions {
+ jvmTarget = '1.8'
+ }
+
+ ndkVersion "21.1.6352462"
+}
+
+dependencies {
+}
\ No newline at end of file
diff --git a/app/ezprotect/consumer-rules.pro b/app/ezprotect/consumer-rules.pro
new file mode 100644
index 0000000..e69de29
diff --git a/app/ezprotect/proguard-rules.pro b/app/ezprotect/proguard-rules.pro
new file mode 100644
index 0000000..481bb43
--- /dev/null
+++ b/app/ezprotect/proguard-rules.pro
@@ -0,0 +1,21 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile
\ No newline at end of file
diff --git a/app/ezprotect/src/main/AndroidManifest.xml b/app/ezprotect/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..3c00ea7
--- /dev/null
+++ b/app/ezprotect/src/main/AndroidManifest.xml
@@ -0,0 +1,2 @@
+
+
\ No newline at end of file
diff --git a/app/ezprotect/src/main/cpp/protect/NativeApi.cpp b/app/ezprotect/src/main/cpp/protect/NativeApi.cpp
new file mode 100644
index 0000000..deb1d6a
--- /dev/null
+++ b/app/ezprotect/src/main/cpp/protect/NativeApi.cpp
@@ -0,0 +1,517 @@
+#include
+#include
+#include
+#include
+#include
+#include "NativeApi.h"
+#include "../util/MD5.h"
+#include "../util/Logger.h"
+#include "../util/Aes.h"
+
+/// 使用pm命令获取真实的APK安装包路径
+static const char *getRealAPKPath() {
+ string cmd("/system/bin/pm path ");
+#if defined( CORRECT_PACKAGE_NAME )
+ cmd.append(CORRECT_PACKAGE_NAME);
+#endif
+ cmd.append(" | /system/bin/sed 's/package://'");
+ FILE *fp;
+ char *path = new char[1024];
+ fp = popen(cmd.c_str(), "r");
+ if (fp == nullptr) {
+ return "";
+ }
+ int readCnt = 0;
+ while (fgets(path, 1024, fp) != nullptr) {
+ readCnt++;
+ }
+ pclose(fp);
+ // 将换行符替换为\0,以便将其传递给fopen
+ path[strcspn(path, "\n")] = 0;
+ if (readCnt != 1) {
+ return "";
+ }
+ return path;
+}
+
+static const char *getFileDir(const char *path) {
+ // path一般为/data/app/xxx/base.apk
+ return dirname(path);
+}
+
+static int getFileSize(const char *path) {
+ FILE *fp = fopen(path, "rb");
+ if (fp == nullptr) {
+ return 0;
+ }
+ fseek(fp, 0L, SEEK_END);
+ long size = ftell(fp);
+ fclose(fp);
+ return size;
+}
+
+/// 检测是否被动态调试
+/// 1.通过ptrace自添加防止被调试器挂载来反调试
+/// 2.通过系统提供的Debug.isDebuggerConnected()方法来进行判断
+static bool checkDebug(JNIEnv *env) {
+// // 自添加似乎有兼容性问题?待测试
+// ptrace(PTRACE_TRACEME, 0, 0, 0);
+
+ jclass vm_debug_clz = env->FindClass("android/os/Debug");
+ jmethodID isDebuggerConnected = env->GetStaticMethodID(vm_debug_clz, "isDebuggerConnected", "()Z");
+ if (isDebuggerConnected == nullptr) {
+ return false;
+ }
+ return env->CallStaticBooleanMethod(vm_debug_clz, isDebuggerConnected);
+}
+
+char *strlwr(char *str) {
+ char *origin = str;
+ for (; *str != '\0'; str++)
+ *str = (char) tolower(*str);
+ return origin;
+}
+
+/// 检测是否被Xposed等框架注入
+/// 已支持检测Xposed、Virtual Xposed、Cydia Substrate、Frida等
+/// TODO 未来可增加对太极等更多框架的检测
+static bool checkXposedOrCydia(JNIEnv *env) {
+ const char *file_path = "/proc/self/maps";
+ const char *xposed_lib = "xposed"; // Xposed、Virtual Xposed框架
+ const char *substrate_lib = "substrate"; // Cydia Substrate框架
+ const char *frida_lib = "frida"; // Frida框架
+ FILE *fp = fopen(file_path, "r");
+ if (fp != nullptr) {
+ char line[1024] = {0};
+ while (fgets(line, sizeof(line), fp) != nullptr) {
+// LOGE("%s", line);
+ char *lwr = strlwr(line);
+ if (strstr(lwr, xposed_lib) || strstr(lwr, substrate_lib) || strstr(lwr, frida_lib)) {
+ return true;
+ }
+ }
+ fclose(fp);
+ }
+ return false;
+}
+
+/// 获取sPackageManager对象
+static jobject getPackageManager(JNIEnv *env) {
+ jclass activity_thread_clz = env->FindClass("android/app/ActivityThread");
+ if (activity_thread_clz == nullptr) {
+ return nullptr;
+ }
+ jmethodID getPackageManager = env->GetStaticMethodID(activity_thread_clz, "getPackageManager", "()Landroid/content/pm/IPackageManager;");
+ if (getPackageManager == nullptr) {
+ return nullptr;
+ }
+ return env->CallStaticObjectMethod(activity_thread_clz, getPackageManager);
+}
+
+/// 检测PMS是否被Hook
+/// 大部分一键去签名校验的工具(如MT管理器和NP管理器)都是通过Hook PMS来实现的,因此这里进行检测
+static bool checkHookPMS(JNIEnv *env) {
+ jobject pms = getPackageManager(env);
+ jclass pms_clz = env->GetObjectClass(pms);
+ if (pms_clz == nullptr) {
+ return false;
+ }
+ jclass pms_parent_clz = env->GetSuperclass(pms_clz);
+ if (pms_parent_clz == nullptr) {
+ return false;
+ }
+ jclass proxyClass = env->FindClass("java/lang/reflect/Proxy");
+ if (proxyClass == nullptr) {
+ return false;
+ }
+
+// jclass cls = env->FindClass("java/lang/Class");
+// jmethodID getName = env->GetMethodID(cls, "getName", "()Ljava/lang/String;");
+// auto cls_name = (jstring) env->CallObjectMethod(pms_clz, getName);
+// const char *cls_name_chars = env->GetStringUTFChars(cls_name, nullptr);
+// LOGI("cls_name : %s", cls_name_chars);
+
+ if (env->IsAssignableFrom(pms_parent_clz, proxyClass)) {
+ return true;
+ }
+ return false;
+}
+
+/// 获取Application对象
+static jobject getApplication(JNIEnv *env) {
+ jclass activity_thread_clz = env->FindClass("android/app/ActivityThread");
+ if (activity_thread_clz == nullptr) {
+ return nullptr;
+ }
+ jmethodID currentApplication = env->GetStaticMethodID(activity_thread_clz, "currentApplication", "()Landroid/app/Application;");
+ if (currentApplication == nullptr) {
+ return nullptr;
+ }
+ return env->CallStaticObjectMethod(activity_thread_clz, currentApplication);
+}
+
+/// 检测Application的className属性
+/// 大部分一键去签名校验的工具(如MT管理器和NP管理器)都会通过修改入口Application类,并在修改后的Application类中添加各种破解代码来实现去签名校验,
+/// 它们常常费了很大的劲比如Hook了PMS,同时为了防止你读取原包做文件完整性校验可能还进行了IO重定向,但偏偏忽视了对Application类名的隐藏,因此进行检测
+static bool checkApplicationName(JNIEnv *env) {
+ jobject context = getApplication(env);
+ // 这里从android/app/Activity取jmethodID而没有从android/content/ContextWrapper或者android/app/Application里取
+ // 是用来对抗AndroidNativeEmu框架
+ jclass context_wrapper_clz = env->FindClass("android/app/Activity");
+ jmethodID getApplicationInfo = env->GetMethodID(context_wrapper_clz, "getApplicationInfo", "()Landroid/content/pm/ApplicationInfo;");
+ if (getApplicationInfo == nullptr) {
+ return false;
+ }
+ jobject application_info = env->CallObjectMethod(context, getApplicationInfo);
+ if (application_info == nullptr) {
+ return false;
+ }
+ jclass application_info_clz = env->GetObjectClass(application_info);
+ jfieldID application_name_id = env->GetFieldID(application_info_clz, "className", "Ljava/lang/String;");
+
+ // 检测Application名是否一致
+ auto application_name = (jstring) env->GetObjectField(application_info, application_name_id);
+ const char *name = env->GetStringUTFChars(application_name, nullptr);
+#if defined( CORRECT_APPLICATION_NAME )
+ if (strcmp(name, CORRECT_APPLICATION_NAME) == 0) {
+ return true;
+ }
+#endif
+#if defined( CORRECT_APPLICATION_NAME_REINFORCE )
+ if (strcmp(name, CORRECT_APPLICATION_NAME_REINFORCE) == 0) {
+ return true;
+ }
+#endif
+ return false;
+}
+
+/// 检测Application的allowBackup属性
+/// 正常情况下应该为false,如果检测到为true,说明应用被Hook了,破解者正在尝试导出应用私有信息
+static bool checkApplicationAllowBackup(JNIEnv *env) {
+ jobject context = getApplication(env);
+ // 这里从android/app/Activity取jmethodID而没有从android/content/ContextWrapper或者android/app/Application里取
+ // 是用来对抗AndroidNativeEmu框架
+ jclass context_wrapper_clz = env->FindClass("android/app/Activity");
+ jmethodID getApplicationInfo = env->GetMethodID(context_wrapper_clz, "getApplicationInfo", "()Landroid/content/pm/ApplicationInfo;");
+ if (getApplicationInfo == nullptr) {
+ return false;
+ }
+ jobject application_info = env->CallObjectMethod(context, getApplicationInfo);
+ if (application_info == nullptr) {
+ return false;
+ }
+ jclass application_info_clz = env->GetObjectClass(application_info);
+ jfieldID application_flags_id = env->GetFieldID(application_info_clz, "flags", "I");
+
+ int application_flags = env->GetIntField(application_info, application_flags_id);
+ // FLAG_ALLOW_BACKUP = 1<<15 = 32768
+ if ((application_flags & 32768) != 0) {
+ return false;
+ }
+ return true;
+}
+
+/// 检测应用版本号
+/// 破解者为了防止应用更新导致破解失效,通常会修改此versionCode,因此进行检测
+static bool checkVersionCode(JNIEnv *env) {
+ jobject context = getApplication(env);
+ // 这里从android/app/Activity取jmethodID而没有从android/content/ContextWrapper或者android/app/Application里取
+ // 是用来对抗AndroidNativeEmu框架
+ jclass context_wrapper_clz = env->FindClass("android/app/Activity");
+ jmethodID getPackageManager = env->GetMethodID(context_wrapper_clz, "getPackageManager", "()Landroid/content/pm/PackageManager;");
+ if (getPackageManager == nullptr) {
+ return false;
+ }
+ jobject package_manager = env->CallObjectMethod(context, getPackageManager);
+ if (package_manager == nullptr) {
+ return false;
+ }
+ jmethodID getPackageName = env->GetMethodID(context_wrapper_clz, "getPackageName", "()Ljava/lang/String;");
+ if (getPackageName == nullptr) {
+ return false;
+ }
+ auto application_package = (jstring) env->CallObjectMethod(context, getPackageName);
+ jclass package_manager_clz = env->GetObjectClass(package_manager);
+ jmethodID getPackageInfo = env->GetMethodID(package_manager_clz, "getPackageInfo", "(Ljava/lang/String;I)Landroid/content/pm/PackageInfo;");
+ jobject package_info = env->CallObjectMethod(package_manager, getPackageInfo, application_package, 0x40);
+ jclass package_info_clz = env->GetObjectClass(package_info);
+ jfieldID version_code_id = env->GetFieldID(package_info_clz, "versionCode", "I");
+
+ int version_code = env->GetIntField(package_info, version_code_id);
+#if defined( CORRECT_VERSION_CODE )
+ LOGI("checkVersionCode %d %d", version_code, CORRECT_VERSION_CODE);
+ if (CORRECT_VERSION_CODE != version_code) {
+ return false;
+ }
+#endif
+ return true;
+}
+
+/// 检测APK的包名
+/// 如果没有指定私有目录的访问权限,说明不是正确的包名
+static bool checkPackageName(JNIEnv *env) {
+ // 通过检查私有目录的访问权限,判断CORRECT_PACKAGE_NAME是否是当前正在运行的包
+ string dir("/data/data/");
+#if defined( CORRECT_PACKAGE_NAME )
+ dir.append(CORRECT_PACKAGE_NAME);
+#endif
+ dir.append("/");
+ if (opendir(dir.c_str()) == nullptr) {
+ return false;
+ }
+ return true;
+}
+
+/// 检测APK的签名
+/// 非常基础的检测手段,也容易被一键破解,因此需要配合其他手段防御
+static bool checkSignature(JNIEnv *env) {
+ jobject context = getApplication(env);
+ // 这里从android/app/Activity取jmethodID而没有从android/content/ContextWrapper或者android/app/Application里取
+ // 是用来对抗AndroidNativeEmu框架
+ jclass context_wrapper_clz = env->FindClass("android/app/Activity");
+ jmethodID getPackageManager = env->GetMethodID(context_wrapper_clz, "getPackageManager", "()Landroid/content/pm/PackageManager;");
+ if (getPackageManager == nullptr) {
+ return false;
+ }
+ jobject package_manager = env->CallObjectMethod(context, getPackageManager);
+ if (package_manager == nullptr) {
+ return false;
+ }
+ jmethodID getPackageName = env->GetMethodID(context_wrapper_clz, "getPackageName", "()Ljava/lang/String;");
+ if (getPackageName == nullptr) {
+ return false;
+ }
+ auto application_package = (jstring) env->CallObjectMethod(context, getPackageName);
+ jclass package_manager_clz = env->GetObjectClass(package_manager);
+ jmethodID getPackageInfo = env->GetMethodID(package_manager_clz, "getPackageInfo", "(Ljava/lang/String;I)Landroid/content/pm/PackageInfo;");
+ jobject package_info = env->CallObjectMethod(package_manager, getPackageInfo, application_package, 0x40);
+ jclass package_info_clz = env->GetObjectClass(package_info);
+ jfieldID signatures_id = env->GetFieldID(package_info_clz, "signatures", "[Landroid/content/pm/Signature;");
+ auto signs = (jobjectArray) env->GetObjectField(package_info, signatures_id);
+ jobject sign = env->GetObjectArrayElement(signs, 0);
+ jclass signature_clz = env->GetObjectClass(sign);
+ jmethodID toCharsString = env->GetMethodID(signature_clz, "toCharsString", "()Ljava/lang/String;");
+ if (toCharsString == nullptr) {
+ return false;
+ }
+ auto sign_jstr = (jstring) env->CallObjectMethod(sign, toCharsString);
+ const char *sign_chars = env->GetStringUTFChars(sign_jstr, nullptr);
+ string sign_str(sign_chars);
+ env->ReleaseStringUTFChars(sign_jstr, sign_chars);
+
+ // 检测签名md5值是否一致
+ string apkSignature = md5(sign_str);
+// LOGE("checkSignature %s", apkSignature.c_str());
+#if defined( CORRECT_APK_SIGN )
+ if (apkSignature != CORRECT_APK_SIGN) {
+ return false;
+ }
+#endif
+ return true;
+}
+
+/// 检测Native与Java层获取到的APK的文件路径或者大小
+/// 因为pm命令获取到的APK文件路径通常不容易被修改,这里与通过JavaApi获取到的APK文件路径做比较,如果文件路径或者文件大小不一致时
+/// 大概率是应用被IO重定向了,或者使用了VirtualXposed等分身软件
+static bool checkAPKFile(JNIEnv *env) {
+ jobject context = getApplication(env);
+ // 这里从android/app/Activity取jmethodID而没有从android/content/ContextWrapper或者android/app/Application里取
+ // 是用来对抗AndroidNativeEmu框架
+ jclass context_wrapper_clz = env->FindClass("android/app/Activity");
+ jmethodID getPackageCodePath = env->GetMethodID(context_wrapper_clz, "getPackageCodePath", "()Ljava/lang/String;");
+ if (getPackageCodePath == nullptr) {
+ return false;
+ }
+
+ auto apk_path_jstr = (jstring) env->CallObjectMethod(context, getPackageCodePath);
+ const char *apk_path_chars = env->GetStringUTFChars(apk_path_jstr, nullptr);
+ const char *native_apk_path_chars = getRealAPKPath();
+ // 当Native与Java层获取到的APK文件路径或者文件大小不一致时,大概率是被IO重定向了,或者使用了VirtualXposed等分身软件
+ if (apk_path_chars != nullptr
+ && native_apk_path_chars != nullptr
+ && (strcmp(apk_path_chars, native_apk_path_chars) != 0 || getFileSize(apk_path_chars) != getFileSize(native_apk_path_chars))) {
+ return false;
+ }
+ return true;
+}
+
+/// 通过调用RegisterNatives方法来注册我们的函数
+static int registerNativeMethods(JNIEnv *env) {
+ // 找到声明native方法的类
+ jclass clazz = env->FindClass(className);
+ if (clazz == nullptr) {
+ return JNI_FALSE;
+ }
+ // 注册函数 参数:java类 所要注册的函数数组 注册函数的个数
+ if (env->RegisterNatives(clazz, jniMethods, sizeof(jniMethods) / sizeof(jniMethods[0])) < 0) {
+ return JNI_FALSE;
+ }
+ return JNI_TRUE;
+}
+
+/// JNI初始化回调
+jint JNI_OnLoad(JavaVM *vm, void *reserved) {
+ JNIEnv *env = nullptr;
+ if (vm->GetEnv((void **) &env, JNI_VERSION_1_6) != JNI_OK) {
+ return JNI_ERR;
+ }
+
+ // 检测是否被动态调试 【可选】建议Release阶段开启
+ if (checkDebug(env)) {
+ LOGI("checkDebug failed.");
+ return JNI_ERR;
+ }
+
+ // 检测APK的签名 必要,基本的防破解手段
+ if (!checkSignature(env)) {
+ LOGI("checkSignature failed.");
+ return JNI_ERR;
+ }
+
+ // 检测APK的包名 【可选】如允许用户双开,可关闭此检测
+ if (!checkPackageName(env)) {
+ LOGI("checkPackageName failed.");
+ return JNI_ERR;
+ }
+
+ // 检测是否被Xposed等框架注入 必要,基本的防破解手段
+ if (checkXposedOrCydia(env)) {
+ LOGI("checkXposedOrCydia failed.");
+ return JNI_ERR;
+ }
+
+ // 检测PMS是否被Hook 【可选】基本的防破解手段,如允许用户双开,可关闭此检测
+ if (checkHookPMS(env)) {
+ LOGI("checkHookPMS failed.");
+ return JNI_ERR;
+ }
+
+ // 检测Application的className属性 必要,基本的防破解手段
+ if (!checkApplicationName(env)) {
+ LOGI("checkApplicationName failed.");
+ return JNI_ERR;
+ }
+
+ // 检测Application的allowBackup属性 【可选】用处不大,Root后的手机不修改该值依然可导出用户私有信息
+ if (!checkApplicationAllowBackup(env)) {
+ LOGI("checkApplicationAllowBackup failed.");
+ return JNI_ERR;
+ }
+
+ // 检测应用版本号 必要,基本的防破解手段
+ if (!checkVersionCode(env)) {
+ LOGI("checkVersionCode failed.");
+ return JNI_ERR;
+ }
+
+ // 检测Native与Java层获取到的APK的文件路径或者大小 【可选】如允许用户双开,可关闭此检测
+ if (!checkAPKFile(env)) {
+ LOGI("checkAPKFile failed.");
+ return JNI_ERR;
+ }
+
+ // 注册函数
+ if (!registerNativeMethods(env)) {
+ LOGI("registerNativeMethods failed.");
+ return JNI_ERR;
+ }
+
+ return JNI_VERSION_1_6;
+}
+
+static jstring charToJstring(JNIEnv *env, char *src) {
+ jsize len = strlen(src);
+ jclass clsstring = env->FindClass("java/lang/String");
+ jstring strencode = env->NewStringUTF("UTF-8");
+ jmethodID mid = env->GetMethodID(clsstring, "", "([BLjava/lang/String;)V");
+ jbyteArray barr = env->NewByteArray(len);
+ env->SetByteArrayRegion(barr, 0, len, (jbyte *) src);
+ return (jstring) env->NewObject(clsstring, mid, barr, strencode);
+}
+
+jstring encrypt(JNIEnv *env, jobject thiz, jstring jstr) {
+ if (nullptr == jstr) {
+ return env->NewStringUTF("");
+ }
+
+ const char *str = env->GetStringUTFChars(jstr, nullptr);
+ char *encrypt_result = aes_encrypt(str, SECRET_KEY);
+
+ env->ReleaseStringUTFChars(jstr, str);
+
+ jstring result;
+ if (nullptr != encrypt_result) {
+ result = env->NewStringUTF(encrypt_result);
+ free(encrypt_result);
+ } else {
+ result = env->NewStringUTF("");
+ }
+ return result;
+}
+
+jstring decrypt(JNIEnv *env, jobject thiz, jstring jstr) {
+ if (nullptr == jstr) {
+ return env->NewStringUTF("");
+ }
+
+ const char *str = env->GetStringUTFChars(jstr, nullptr);
+ char *decrypt_result = aes_decrypt(str, SECRET_KEY);
+
+ env->ReleaseStringUTFChars(jstr, str);
+
+ jstring result;
+ if (nullptr != decrypt_result) {
+ // 不用系统自带的方法NewStringUTF是因为如果decrypt_result是乱码,会抛出异常
+ result = charToJstring(env, decrypt_result);
+ free(decrypt_result);
+ } else {
+ result = env->NewStringUTF("");
+ }
+ return result;
+}
+
+/// 返回当前APK文件和此加密So包的文件信息用于服务器校验
+jstring integrity(JNIEnv *env, jobject thiz) {
+ const char *path = getRealAPKPath();
+ int size = getFileSize(path);
+ const char *dir = getFileDir(path);
+ string tmp("{");
+ tmp.append("\"ApkPath\":");
+ tmp.append("\"");
+ tmp.append(path);
+ tmp.append("\"");
+ tmp.append(",");
+ tmp.append("\"ApkSize\":");
+ tmp.append(std::to_string(size));
+ tmp.append(",");
+// // 由于大文件MD5计算比较费时,所以不提供此数据
+// tmp.append("\"ApkMD5\":");
+// tmp.append("\"");
+// tmp.append(md5file(path));
+// tmp.append("\"");
+// tmp.append(",");
+ char *abi = (char *) malloc(128);
+ __system_property_get("ro.product.cpu.abilist", abi);
+ string so(dir);
+ if (strncmp(abi, "arm64-v8a", 9) == 0) {
+ // arm64
+ so.append("/lib/arm64/libezcore.so");
+ } else {
+ // arm
+ so.append("/lib/arm/libezcore.so");
+ }
+ tmp.append("\"SoPath\":");
+ tmp.append("\"");
+ tmp.append(so);
+ tmp.append("\"");
+ tmp.append(",");
+ tmp.append("\"SoSize\":");
+ tmp.append(std::to_string(getFileSize(&so[0])));
+ tmp.append(",");
+ tmp.append("\"SoMD5\":");
+ tmp.append("\"");
+ tmp.append(md5file(so.c_str()));
+ tmp.append("\"");
+ tmp.append("}");
+ return env->NewStringUTF(tmp.c_str());
+}
diff --git a/app/ezprotect/src/main/cpp/protect/NativeApi.h b/app/ezprotect/src/main/cpp/protect/NativeApi.h
new file mode 100755
index 0000000..1a91881
--- /dev/null
+++ b/app/ezprotect/src/main/cpp/protect/NativeApi.h
@@ -0,0 +1,33 @@
+#include
+#include
+#include
+
+#ifndef _Included_Signature
+#define _Included_Signature
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/// AES加密秘钥
+unsigned char SECRET_KEY[] = "ZXphbmRyb2lk";
+
+/// 指定类的路径,通过FindClass方法来找到对应的类
+const char *className = "cn/ezandroid/ezprotect/lib/NativeApi";
+
+jstring encrypt(JNIEnv *, jobject, jstring);
+
+jstring decrypt(JNIEnv *, jobject, jstring);
+
+jstring integrity(JNIEnv *, jobject);
+
+/// 定义Native和Java方法映射关系
+static JNINativeMethod jniMethods[] = {
+ {"a", "(Ljava/lang/String;)Ljava/lang/String;", (void *) encrypt},
+ {"b", "(Ljava/lang/String;)Ljava/lang/String;", (void *) decrypt},
+ {"c", "()Ljava/lang/String;", (void *) integrity}
+};
+
+#ifdef __cplusplus
+}
+#endif
+#endif
diff --git a/app/ezprotect/src/main/cpp/util/Aes.cpp b/app/ezprotect/src/main/cpp/util/Aes.cpp
new file mode 100644
index 0000000..6d88b4d
--- /dev/null
+++ b/app/ezprotect/src/main/cpp/util/Aes.cpp
@@ -0,0 +1,115 @@
+//
+// Created by like on 2022/1/26.
+//
+
+#include
+#include
+#include
+
+#include "AesCipher.h"
+#include "Base64.h"
+#include "Aes.h"
+#include "Logger.h"
+
+const static char HEX[] = {0x10, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf};
+
+void fill(const char *from, unsigned char *to, size_t n) {
+ for (size_t i = 0; i < n; ++i) {
+ to[i] = from[i];
+ }
+}
+
+/// encrypt by AES/ECB/PKCS5Padding
+char *aes_encrypt(const char *text, const unsigned char *key) {
+ using ::cipher::AES_ECB_Cipher;
+
+ char tail_space[16] = {HEX[0]};
+ ssize_t text_size = strlen(text);
+ size_t directly_size = text_size;
+
+ // PKCS5Padding补码
+ if (text_size % 16) {
+ directly_size = (text_size / 16) * 16;
+ size_t tail_size = text_size - directly_size;
+ memcpy(tail_space, text + directly_size, tail_size);
+ memset(tail_space + tail_size, HEX[16 - tail_size], 16 - tail_size);
+ } else {
+ memset(tail_space, HEX[0], 16);
+ }
+
+ size_t target_size = directly_size + 16;
+ unsigned char *target = (unsigned char *) malloc(target_size);
+
+ AES_ECB_Cipher context(key);
+ unsigned char buffer[17] = {0x0};
+ size_t round = directly_size / 16;
+ for (size_t i = 0; i < round; ++i) {
+ fill(text + i * 16, buffer, 16);
+ context.AES128_ECB_encrypt(buffer, target + i * 16);
+ }
+ fill(tail_space, buffer, 16);
+ context.AES128_ECB_encrypt(buffer, target + round * 16);
+
+ // `target` strict-alias enabled.
+ char *result;
+ size_t result_size = 0;
+ int n = base64_encode((char *) target, target_size, &result, &result_size);
+ free(target);
+ if (n != 0) {
+ LOGD("Base64 encode result: %d", n);
+ return NULL;
+ }
+
+ return result;
+}
+
+/// decrypt by AES/ECB/PKCS5Padding
+char *aes_decrypt(const char *raw, const unsigned char *key) {
+ using ::cipher::AES_ECB_Cipher;
+
+ size_t text_size = 0;
+ char *text;
+ int n = base64_decode(raw, strlen(raw), &text, &text_size);
+ if (n != 0) {
+ LOGD("Base64 decode result: %d", n);
+ return NULL;
+ }
+
+ unsigned char *source = (unsigned char *) malloc(text_size);
+
+ AES_ECB_Cipher context(key);
+ unsigned char buffer[17] = {0x0};
+ size_t round = text_size / 16;
+ for (size_t i = 0; i < round; ++i) {
+ fill(text + i * 16, buffer, 16);
+ context.AES128_ECB_decrypt(buffer, source + i * 16);
+ }
+
+ // unpadding with pkcs5, remove unused characters
+ unsigned char last_char = source[text_size - 1];
+ size_t size = text_size - last_char;
+ source[size] = '\0';
+
+ free(text);
+
+ // `source` strict-alias enabled.
+ return (char *) source;
+}
+
+//int test(int argc, char **argv) {
+// if (argc == 1) {
+// printf("%s data\n", argv[0]);
+// return 1;
+// }
+//
+// unsigned char key[] = "ikw35kf9IK9eE93J";
+// char *secret = aes_encrypt(argv[1], key);
+// printf("encrypt: %s\n", secret);
+// char *source = aes_decrypt(secret, key);
+// printf("decrypt: %s\n", source);
+//
+// free(secret);
+// free(source);
+//
+// return 0;
+//}
diff --git a/app/ezprotect/src/main/cpp/util/Aes.h b/app/ezprotect/src/main/cpp/util/Aes.h
new file mode 100755
index 0000000..e677719
--- /dev/null
+++ b/app/ezprotect/src/main/cpp/util/Aes.h
@@ -0,0 +1,20 @@
+//
+// Created by like on 2022/1/26.
+//
+
+#ifndef AES_H
+#define AES_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+char *aes_encrypt(const char *text, const unsigned char *key);
+
+char *aes_decrypt(const char *raw, const unsigned char *key);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/app/ezprotect/src/main/cpp/util/AesCipher.cpp b/app/ezprotect/src/main/cpp/util/AesCipher.cpp
new file mode 100644
index 0000000..8fca284
--- /dev/null
+++ b/app/ezprotect/src/main/cpp/util/AesCipher.cpp
@@ -0,0 +1,437 @@
+//
+// Created by like on 2022/1/26.
+//
+
+#include
+#include "AesCipher.h"
+#include "Logger.h"
+
+namespace cipher {
+
+ const uint8_t AES_ECB_Cipher::scSbox[256] = {
+ //0 1 2 3 4 5 6 7 8 9 A B C D E F
+ 0x63, 0x7c, 0x77, 0x7b, 0xf2, 0x6b, 0x6f, 0xc5, 0x30, 0x01, 0x67, 0x2b, 0xfe, 0xd7, 0xab, 0x76, //0
+ 0xca, 0x82, 0xc9, 0x7d, 0xfa, 0x59, 0x47, 0xf0, 0xad, 0xd4, 0xa2, 0xaf, 0x9c, 0xa4, 0x72, 0xc0, //1
+ 0xb7, 0xfd, 0x93, 0x26, 0x36, 0x3f, 0xf7, 0xcc, 0x34, 0xa5, 0xe5, 0xf1, 0x71, 0xd8, 0x31, 0x15, //2
+ 0x04, 0xc7, 0x23, 0xc3, 0x18, 0x96, 0x05, 0x9a, 0x07, 0x12, 0x80, 0xe2, 0xeb, 0x27, 0xb2, 0x75, //3
+ 0x09, 0x83, 0x2c, 0x1a, 0x1b, 0x6e, 0x5a, 0xa0, 0x52, 0x3b, 0xd6, 0xb3, 0x29, 0xe3, 0x2f, 0x84, //4
+ 0x53, 0xd1, 0x00, 0xed, 0x20, 0xfc, 0xb1, 0x5b, 0x6a, 0xcb, 0xbe, 0x39, 0x4a, 0x4c, 0x58, 0xcf, //5
+ 0xd0, 0xef, 0xaa, 0xfb, 0x43, 0x4d, 0x33, 0x85, 0x45, 0xf9, 0x02, 0x7f, 0x50, 0x3c, 0x9f, 0xa8, //6
+ 0x51, 0xa3, 0x40, 0x8f, 0x92, 0x9d, 0x38, 0xf5, 0xbc, 0xb6, 0xda, 0x21, 0x10, 0xff, 0xf3, 0xd2, //7
+ 0xcd, 0x0c, 0x13, 0xec, 0x5f, 0x97, 0x44, 0x17, 0xc4, 0xa7, 0x7e, 0x3d, 0x64, 0x5d, 0x19, 0x73, //8
+ 0x60, 0x81, 0x4f, 0xdc, 0x22, 0x2a, 0x90, 0x88, 0x46, 0xee, 0xb8, 0x14, 0xde, 0x5e, 0x0b, 0xdb, //9
+ 0xe0, 0x32, 0x3a, 0x0a, 0x49, 0x06, 0x24, 0x5c, 0xc2, 0xd3, 0xac, 0x62, 0x91, 0x95, 0xe4, 0x79, //A
+ 0xe7, 0xc8, 0x37, 0x6d, 0x8d, 0xd5, 0x4e, 0xa9, 0x6c, 0x56, 0xf4, 0xea, 0x65, 0x7a, 0xae, 0x08, //B
+ 0xba, 0x78, 0x25, 0x2e, 0x1c, 0xa6, 0xb4, 0xc6, 0xe8, 0xdd, 0x74, 0x1f, 0x4b, 0xbd, 0x8b, 0x8a, //C
+ 0x70, 0x3e, 0xb5, 0x66, 0x48, 0x03, 0xf6, 0x0e, 0x61, 0x35, 0x57, 0xb9, 0x86, 0xc1, 0x1d, 0x9e, //D
+ 0xe1, 0xf8, 0x98, 0x11, 0x69, 0xd9, 0x8e, 0x94, 0x9b, 0x1e, 0x87, 0xe9, 0xce, 0x55, 0x28, 0xdf, //E
+ 0x8c, 0xa1, 0x89, 0x0d, 0xbf, 0xe6, 0x42, 0x68, 0x41, 0x99, 0x2d, 0x0f, 0xb0, 0x54, 0xbb, 0x16};
+
+ const uint8_t AES_ECB_Cipher::scRsbox[256] = {
+ //0 1 2 3 4 5 6 7 8 9 A B C D E F
+ 0x52, 0x09, 0x6a, 0xd5, 0x30, 0x36, 0xa5, 0x38, 0xbf, 0x40, 0xa3, 0x9e, 0x81, 0xf3, 0xd7, 0xfb,
+ 0x7c, 0xe3, 0x39, 0x82, 0x9b, 0x2f, 0xff, 0x87, 0x34, 0x8e, 0x43, 0x44, 0xc4, 0xde, 0xe9, 0xcb,
+ 0x54, 0x7b, 0x94, 0x32, 0xa6, 0xc2, 0x23, 0x3d, 0xee, 0x4c, 0x95, 0x0b, 0x42, 0xfa, 0xc3, 0x4e,
+ 0x08, 0x2e, 0xa1, 0x66, 0x28, 0xd9, 0x24, 0xb2, 0x76, 0x5b, 0xa2, 0x49, 0x6d, 0x8b, 0xd1, 0x25,
+ 0x72, 0xf8, 0xf6, 0x64, 0x86, 0x68, 0x98, 0x16, 0xd4, 0xa4, 0x5c, 0xcc, 0x5d, 0x65, 0xb6, 0x92,
+ 0x6c, 0x70, 0x48, 0x50, 0xfd, 0xed, 0xb9, 0xda, 0x5e, 0x15, 0x46, 0x57, 0xa7, 0x8d, 0x9d, 0x84,
+ 0x90, 0xd8, 0xab, 0x00, 0x8c, 0xbc, 0xd3, 0x0a, 0xf7, 0xe4, 0x58, 0x05, 0xb8, 0xb3, 0x45, 0x06,
+ 0xd0, 0x2c, 0x1e, 0x8f, 0xca, 0x3f, 0x0f, 0x02, 0xc1, 0xaf, 0xbd, 0x03, 0x01, 0x13, 0x8a, 0x6b,
+ 0x3a, 0x91, 0x11, 0x41, 0x4f, 0x67, 0xdc, 0xea, 0x97, 0xf2, 0xcf, 0xce, 0xf0, 0xb4, 0xe6, 0x73,
+ 0x96, 0xac, 0x74, 0x22, 0xe7, 0xad, 0x35, 0x85, 0xe2, 0xf9, 0x37, 0xe8, 0x1c, 0x75, 0xdf, 0x6e,
+ 0x47, 0xf1, 0x1a, 0x71, 0x1d, 0x29, 0xc5, 0x89, 0x6f, 0xb7, 0x62, 0x0e, 0xaa, 0x18, 0xbe, 0x1b,
+ 0xfc, 0x56, 0x3e, 0x4b, 0xc6, 0xd2, 0x79, 0x20, 0x9a, 0xdb, 0xc0, 0xfe, 0x78, 0xcd, 0x5a, 0xf4,
+ 0x1f, 0xdd, 0xa8, 0x33, 0x88, 0x07, 0xc7, 0x31, 0xb1, 0x12, 0x10, 0x59, 0x27, 0x80, 0xec, 0x5f,
+ 0x60, 0x51, 0x7f, 0xa9, 0x19, 0xb5, 0x4a, 0x0d, 0x2d, 0xe5, 0x7a, 0x9f, 0x93, 0xc9, 0x9c, 0xef,
+ 0xa0, 0xe0, 0x3b, 0x4d, 0xae, 0x2a, 0xf5, 0xb0, 0xc8, 0xeb, 0xbb, 0x3c, 0x83, 0x53, 0x99, 0x61,
+ 0x17, 0x2b, 0x04, 0x7e, 0xba, 0x77, 0xd6, 0x26, 0xe1, 0x69, 0x14, 0x63, 0x55, 0x21, 0x0c, 0x7d};
+
+ const uint8_t AES_ECB_Cipher::scRcon[255] = {
+ 0x8d, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36, 0x6c, 0xd8, 0xab, 0x4d, 0x9a,
+ 0x2f, 0x5e, 0xbc, 0x63, 0xc6, 0x97, 0x35, 0x6a, 0xd4, 0xb3, 0x7d, 0xfa, 0xef, 0xc5, 0x91, 0x39,
+ 0x72, 0xe4, 0xd3, 0xbd, 0x61, 0xc2, 0x9f, 0x25, 0x4a, 0x94, 0x33, 0x66, 0xcc, 0x83, 0x1d, 0x3a,
+ 0x74, 0xe8, 0xcb, 0x8d, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36, 0x6c, 0xd8,
+ 0xab, 0x4d, 0x9a, 0x2f, 0x5e, 0xbc, 0x63, 0xc6, 0x97, 0x35, 0x6a, 0xd4, 0xb3, 0x7d, 0xfa, 0xef,
+ 0xc5, 0x91, 0x39, 0x72, 0xe4, 0xd3, 0xbd, 0x61, 0xc2, 0x9f, 0x25, 0x4a, 0x94, 0x33, 0x66, 0xcc,
+ 0x83, 0x1d, 0x3a, 0x74, 0xe8, 0xcb, 0x8d, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b,
+ 0x36, 0x6c, 0xd8, 0xab, 0x4d, 0x9a, 0x2f, 0x5e, 0xbc, 0x63, 0xc6, 0x97, 0x35, 0x6a, 0xd4, 0xb3,
+ 0x7d, 0xfa, 0xef, 0xc5, 0x91, 0x39, 0x72, 0xe4, 0xd3, 0xbd, 0x61, 0xc2, 0x9f, 0x25, 0x4a, 0x94,
+ 0x33, 0x66, 0xcc, 0x83, 0x1d, 0x3a, 0x74, 0xe8, 0xcb, 0x8d, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20,
+ 0x40, 0x80, 0x1b, 0x36, 0x6c, 0xd8, 0xab, 0x4d, 0x9a, 0x2f, 0x5e, 0xbc, 0x63, 0xc6, 0x97, 0x35,
+ 0x6a, 0xd4, 0xb3, 0x7d, 0xfa, 0xef, 0xc5, 0x91, 0x39, 0x72, 0xe4, 0xd3, 0xbd, 0x61, 0xc2, 0x9f,
+ 0x25, 0x4a, 0x94, 0x33, 0x66, 0xcc, 0x83, 0x1d, 0x3a, 0x74, 0xe8, 0xcb, 0x8d, 0x01, 0x02, 0x04,
+ 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36, 0x6c, 0xd8, 0xab, 0x4d, 0x9a, 0x2f, 0x5e, 0xbc, 0x63,
+ 0xc6, 0x97, 0x35, 0x6a, 0xd4, 0xb3, 0x7d, 0xfa, 0xef, 0xc5, 0x91, 0x39, 0x72, 0xe4, 0xd3, 0xbd,
+ 0x61, 0xc2, 0x9f, 0x25, 0x4a, 0x94, 0x33, 0x66, 0xcc, 0x83, 0x1d, 0x3a, 0x74, 0xe8, 0xcb};
+
+ const uint32_t AES_ECB_Cipher::NR = 10;
+ const uint32_t AES_ECB_Cipher::NB = 4;
+ const uint32_t AES_ECB_Cipher::NK = 4;
+ const uint32_t AES_ECB_Cipher::KEYLEN = 16;
+
+ uint8_t AES_ECB_Cipher::getSBoxValue(uint8_t num) {
+ return scSbox[num];
+ }
+
+ uint8_t AES_ECB_Cipher::getSBoxInvert(uint8_t num) {
+ return scRsbox[num];
+ }
+
+ uint8_t AES_ECB_Cipher::xtime(uint8_t x) {
+ return ((x << 1) ^ (((x >> 7) & 1) * 0x1b));
+ }
+
+ uint8_t AES_ECB_Cipher::Multiply(uint8_t x, uint8_t y) {
+ return (((y & 1) * x) ^
+ ((y >> 1 & 1) * xtime(x)) ^
+ ((y >> 2 & 1) * xtime(xtime(x))) ^
+ ((y >> 3 & 1) * xtime(xtime(xtime(x)))) ^
+ ((y >> 4 & 1) * xtime(xtime(xtime(xtime(x))))));
+ }
+
+ void AES_ECB_Cipher::AddRoundKey(uint8_t round) {
+ uint8_t i, j;
+ for (i = 0; i < 4; ++i) {
+ for (j = 0; j < 4; ++j) {
+ (*mState)[j][i] ^= mRoundKey[round * NB * 4 + i * NB + j];
+ }
+ }
+ }
+
+ void AES_ECB_Cipher::InvAddRoundKey(uint8_t round) {
+ uint8_t i, j;
+ for (i = 0; i < 4; ++i) {
+ for (j = 0; j < 4; ++j) {
+ (*mState)[i][j] ^= mRoundKey[round * NB * 4 + i * NB + j];
+ }
+ }
+ }
+
+ void AES_ECB_Cipher::KeyExpansion() {
+ int i, j;
+ unsigned char temp[4], k;
+
+ bzero(mRoundKey, sizeof(mRoundKey));
+
+ // The first round key is the key itself.
+ for (i = 0; i < NK; i++) {
+ mRoundKey[i * 4] = mKey[i * 4];
+ mRoundKey[i * 4 + 1] = mKey[i * 4 + 1];
+ mRoundKey[i * 4 + 2] = mKey[i * 4 + 2];
+ mRoundKey[i * 4 + 3] = mKey[i * 4 + 3];
+ }
+
+ // All other round keys are found from the previous round keys.
+ while (i < (NB * (NR + 1))) {
+ for (j = 0; j < 4; j++) {
+ temp[j] = mRoundKey[(i - 1) * 4 + j];
+ }
+ if (i % NK == 0) {
+ // This function rotates the 4 bytes in a word to the left once.
+ // [a0,a1,a2,a3] becomes [a1,a2,a3,a0]
+
+ // Function RotWord()
+ {
+ k = temp[0];
+ temp[0] = temp[1];
+ temp[1] = temp[2];
+ temp[2] = temp[3];
+ temp[3] = k;
+ }
+
+ // SubWord() is a function that takes a four-byte input word and
+ // applies the S-box to each of the four bytes to produce an output word.
+
+ // Function Subword()
+ {
+ temp[0] = getSBoxValue(temp[0]);
+ temp[1] = getSBoxValue(temp[1]);
+ temp[2] = getSBoxValue(temp[2]);
+ temp[3] = getSBoxValue(temp[3]);
+ }
+
+ temp[0] = temp[0] ^ scRcon[i / NK];
+ } else if (NK > 6 && i % NK == 4) {
+ // Function Subword()
+ {
+ temp[0] = getSBoxValue(temp[0]);
+ temp[1] = getSBoxValue(temp[1]);
+ temp[2] = getSBoxValue(temp[2]);
+ temp[3] = getSBoxValue(temp[3]);
+ }
+ }
+ mRoundKey[i * 4 + 0] = mRoundKey[(i - NK) * 4 + 0] ^ temp[0];
+ mRoundKey[i * 4 + 1] = mRoundKey[(i - NK) * 4 + 1] ^ temp[1];
+ mRoundKey[i * 4 + 2] = mRoundKey[(i - NK) * 4 + 2] ^ temp[2];
+ mRoundKey[i * 4 + 3] = mRoundKey[(i - NK) * 4 + 3] ^ temp[3];
+ i++;
+ }
+ }
+
+// static const int MAX_DECODE_BUF_LEN = 256;
+//
+// int AES_ECB_Cipher::encode(const unsigned char *src, uint32_t src_len, unsigned char *dest, uint32_t &len) {
+// // check input null pointer
+// if (NULL == src) {
+// print("AES_ECB_Cipher::encode src is NULL ");
+// return -1;
+// }
+// // make sure dest buff is larger than src buff
+// if (len < src_len) {
+// print("AES_ECB_Cipher::encode src is NULL ");
+// return -1;
+// }
+//
+// unsigned char encode_buf[MAX_DECODE_BUF_LEN];
+// bzero(encode_buf, sizeof(encode_buf));
+// memcpy(encode_buf, src, src_len);
+// uint32_t encode_buf_size = src_len;
+//
+// // input been padded well with pkcs5padding
+// uint8_t pading_size = AES_ECB_Cipher::KEYLEN - encode_buf_size % AES_ECB_Cipher::KEYLEN;
+// // PKCS5Padding rules: ��(16-len)��(16-len)
+// for (uint8_t pading = 0; pading < pading_size; pading++) {
+// encode_buf[encode_buf_size + pading] = pading_size;
+// }
+// encode_buf_size += pading_size;
+//
+// uint32_t round = encode_buf_size / AES_ECB_Cipher::KEYLEN;
+// const unsigned char *iv = mKey;
+// for (uint32_t i = 0; i < round; ++i) {
+// AES128_ECB_encrypt(encode_buf + i * AES_ECB_Cipher::KEYLEN, dest + i * AES_ECB_Cipher::KEYLEN);
+// if (use_cbc) {
+// for (int j = 0; j < AES_ECB_Cipher::KEYLEN; ++j) {
+// (dest + i * AES_ECB_Cipher::KEYLEN)[j] ^= iv[j];
+// }
+// iv = encode_buf + i * AES_ECB_Cipher::KEYLEN;
+// }
+// }
+//
+// len = encode_buf_size;
+// if (len < 0 || len <= src_len) {
+// print("AES_ECB_Cipher::encode, fail to encrypt src");
+// return -1;
+// }
+// dest[len] = 0;
+// return 0;
+// }
+//
+// int AES_ECB_Cipher::decode(const unsigned char *src, uint32_t src_len, unsigned char *dest, uint32_t &len) {
+// // check input null pointer
+// if (NULL == src) {
+// print("AES_ECB_Cipher::decode src is NULL ");
+// return -1;
+// }
+// // make sure dest buff is larger than src buff
+// if (len < src_len) {
+// print("AES_ECB_Cipher::decode src is NULL ");
+// return -1;
+// }
+// // assume input has been padded well with pkcs5padding
+// if (src_len % AES_ECB_Cipher::KEYLEN != 0) {
+// print("AES_ECB_Cipher::decode, src len has to be divided by 16");
+// return -1;
+// }
+// uint32_t round = src_len / AES_ECB_Cipher::KEYLEN;
+// const unsigned char *iv = mKey;
+// for (uint32_t i = 0; i < round; ++i) {
+// AES128_ECB_decrypt(src + i * AES_ECB_Cipher::KEYLEN, dest + i * AES_ECB_Cipher::KEYLEN);
+// if (use_cbc) {
+// for (int j = 0; j < AES_ECB_Cipher::KEYLEN; ++j) {
+// (dest + i * AES_ECB_Cipher::KEYLEN)[j] ^= iv[j];
+// }
+// iv = src + i * AES_ECB_Cipher::KEYLEN;
+// }
+// }
+//
+// // unpad with pkcs5, remove unused charactors
+// uint8_t lastASIIC = (uint8_t) dest[src_len - 1];
+// len = src_len - lastASIIC;
+// if (len < 0 || len >= src_len) {
+// print("AES_ECB_Cipher::decode, fail to decrypt src");
+// return -1;
+// }
+// dest[len] = 0;
+// return 0;
+// }
+
+ void AES_ECB_Cipher::MixColumns() {
+ int i;
+ unsigned char Tmp, Tm, t;
+ for (i = 0; i < 4; i++) {
+ t = (*mState)[0][i];
+ Tmp = (*mState)[0][i] ^ (*mState)[1][i] ^ (*mState)[2][i] ^ (*mState)[3][i];
+ Tm = (*mState)[0][i] ^ (*mState)[1][i];
+ Tm = xtime(Tm);
+ (*mState)[0][i] ^= Tm ^ Tmp;
+ Tm = (*mState)[1][i] ^ (*mState)[2][i];
+ Tm = xtime(Tm);
+ (*mState)[1][i] ^= Tm ^ Tmp;
+ Tm = (*mState)[2][i] ^ (*mState)[3][i];
+ Tm = xtime(Tm);
+ (*mState)[2][i] ^= Tm ^ Tmp;
+ Tm = (*mState)[3][i] ^ t;
+ Tm = xtime(Tm);
+ (*mState)[3][i] ^= Tm ^ Tmp;
+ }
+ }
+
+ void AES_ECB_Cipher::SubBytes() {
+ uint8_t i, j;
+ for (i = 0; i < 4; i++) {
+ for (j = 0; j < 4; j++) {
+ (*mState)[i][j] = getSBoxValue((*mState)[i][j]);
+ }
+ }
+ }
+
+ void AES_ECB_Cipher::ShiftRows() {
+ unsigned char temp;
+
+ // Rotate first row 1 columns to left
+ temp = (*mState)[1][0];
+ (*mState)[1][0] = (*mState)[1][1];
+ (*mState)[1][1] = (*mState)[1][2];
+ (*mState)[1][2] = (*mState)[1][3];
+ (*mState)[1][3] = temp;
+
+ // Rotate second row 2 columns to left
+ temp = (*mState)[2][0];
+ (*mState)[2][0] = (*mState)[2][2];
+ (*mState)[2][2] = temp;
+ temp = (*mState)[2][1];
+ (*mState)[2][1] = (*mState)[2][3];
+ (*mState)[2][3] = temp;
+
+ // Rotate third row 3 columns to left
+ temp = (*mState)[3][0];
+ (*mState)[3][0] = (*mState)[3][3];
+ (*mState)[3][3] = (*mState)[3][2];
+ (*mState)[3][2] = (*mState)[3][1];
+ (*mState)[3][1] = temp;
+ }
+
+ void AES_ECB_Cipher::Cipher() {
+ uint8_t round = 0;
+
+ // Add the First round key to the state before starting the rounds.
+ AddRoundKey(0);
+
+ // There will be Nr rounds.
+ // The first Nr-1 rounds are identical.
+ // These Nr-1 rounds are executed in the loop below.
+ for (round = 1; round < NR; round++) {
+ SubBytes();
+ ShiftRows();
+ MixColumns();
+ AddRoundKey(round);
+ }
+
+ // The last round is given below.
+ // The MixColumns function is not here in the last round.
+ SubBytes();
+ ShiftRows();
+ AddRoundKey(NR);
+ }
+
+ void AES_ECB_Cipher::AES128_ECB_encrypt(const unsigned char *input, unsigned char *output) {
+ memcpy(output, input, AES_ECB_Cipher::KEYLEN);
+ mState = (state_t *) output;
+
+ state_t state;
+ mState = &state;
+
+ uint8_t i, j;
+ for (i = 0; i < 4; i++) {
+ for (j = 0; j < 4; j++) {
+ (*mState)[j][i] = input[i * 4 + j];
+ }
+ }
+
+ KeyExpansion();
+ Cipher();
+
+ for (i = 0; i < 4; i++) {
+ for (j = 0; j < 4; j++) {
+ output[i * 4 + j] = (*mState)[j][i];
+ }
+ }
+ }
+
+ void AES_ECB_Cipher::InvMixColumns() {
+ uint8_t a, b, c, d;
+
+ for (uint32_t i = 0; i < 4; ++i) {
+ a = (*mState)[i][0];
+ b = (*mState)[i][1];
+ c = (*mState)[i][2];
+ d = (*mState)[i][3];
+
+ (*mState)[i][0] = Multiply(a, 0x0e) ^ Multiply(b, 0x0b) ^ Multiply(c, 0x0d) ^ Multiply(d, 0x09);
+ (*mState)[i][1] = Multiply(a, 0x09) ^ Multiply(b, 0x0e) ^ Multiply(c, 0x0b) ^ Multiply(d, 0x0d);
+ (*mState)[i][2] = Multiply(a, 0x0d) ^ Multiply(b, 0x09) ^ Multiply(c, 0x0e) ^ Multiply(d, 0x0b);
+ (*mState)[i][3] = Multiply(a, 0x0b) ^ Multiply(b, 0x0d) ^ Multiply(c, 0x09) ^ Multiply(d, 0x0e);
+ }
+ }
+
+ void AES_ECB_Cipher::InvSubBytes() {
+ uint8_t i, j;
+ for (i = 0; i < 4; ++i) {
+ for (j = 0; j < 4; ++j) {
+ (*mState)[j][i] = getSBoxInvert((*mState)[j][i]);
+ }
+ }
+ }
+
+ void AES_ECB_Cipher::InvShiftRows() {
+ uint8_t temp;
+
+ // Rotate first row 1 columns to right
+ temp = (*mState)[3][1];
+ (*mState)[3][1] = (*mState)[2][1];
+ (*mState)[2][1] = (*mState)[1][1];
+ (*mState)[1][1] = (*mState)[0][1];
+ (*mState)[0][1] = temp;
+
+ // Rotate second row 2 columns to right
+ temp = (*mState)[0][2];
+ (*mState)[0][2] = (*mState)[2][2];
+ (*mState)[2][2] = temp;
+
+ temp = (*mState)[1][2];
+ (*mState)[1][2] = (*mState)[3][2];
+ (*mState)[3][2] = temp;
+
+ // Rotate third row 3 columns to right
+ temp = (*mState)[0][3];
+ (*mState)[0][3] = (*mState)[1][3];
+ (*mState)[1][3] = (*mState)[2][3];
+ (*mState)[2][3] = (*mState)[3][3];
+ (*mState)[3][3] = temp;
+ }
+
+ void AES_ECB_Cipher::InvCipher() {
+ uint8_t round = 0;
+ InvAddRoundKey(NR);
+
+ for (round = NR - 1; round > 0; round--) {
+ InvShiftRows();
+ InvSubBytes();
+ InvAddRoundKey(round);
+ InvMixColumns();
+ }
+
+ InvShiftRows();
+ InvSubBytes();
+ InvAddRoundKey(0);
+ }
+
+ void AES_ECB_Cipher::AES128_ECB_decrypt(const unsigned char *input, unsigned char *output) {
+ memcpy(output, input, AES_ECB_Cipher::KEYLEN);
+ mState = (state_t *) output;
+
+ KeyExpansion();
+ InvCipher();
+ }
+
+}
\ No newline at end of file
diff --git a/app/ezprotect/src/main/cpp/util/AesCipher.h b/app/ezprotect/src/main/cpp/util/AesCipher.h
new file mode 100644
index 0000000..554a923
--- /dev/null
+++ b/app/ezprotect/src/main/cpp/util/AesCipher.h
@@ -0,0 +1,72 @@
+//
+// Created by like on 2022/1/26.
+//
+
+#ifndef _AES_CIPHER_H_
+#define _AES_CIPHER_H_
+
+#include
+#include
+
+namespace cipher {
+
+ class AES_ECB_Cipher {
+ public:
+ explicit AES_ECB_Cipher(const unsigned char *key, bool use_cbc = false) : mKey(key), mState(NULL), use_cbc(use_cbc) {};
+
+ void AES128_ECB_encrypt(const unsigned char *input, unsigned char *out);
+
+ void AES128_ECB_decrypt(const unsigned char *input, unsigned char *out);
+
+// int encode(const unsigned char *src, uint32_t src_len, unsigned char *dest, uint32_t &dest_len);
+//
+// int decode(const unsigned char *src, uint32_t src_len, unsigned char *dest, uint32_t &dest_len);
+
+ private:
+ typedef uint8_t state_t[4][4];
+ uint8_t mRoundKey[240];
+ const unsigned char *mKey;
+ state_t *mState;
+ bool use_cbc;
+ static const uint8_t scSbox[256];
+ static const uint8_t scRsbox[256];
+ static const uint8_t scRcon[255];
+ static const uint32_t KEYLEN;
+ static const uint32_t NR;
+ static const uint32_t NB;
+ static const uint32_t NK;
+
+ static inline uint8_t getSBoxValue(uint8_t num);
+
+ static inline uint8_t getSBoxInvert(uint8_t num);
+
+ static inline uint8_t xtime(uint8_t num);
+
+ static inline uint8_t Multiply(uint8_t x, uint8_t y);
+
+ void AddRoundKey(uint8_t round);
+
+ void InvAddRoundKey(uint8_t round);
+
+ void KeyExpansion();
+
+ void MixColumns();
+
+ void SubBytes();
+
+ void ShiftRows();
+
+ void Cipher();
+
+ void InvMixColumns();
+
+ void InvSubBytes();
+
+ void InvShiftRows();
+
+ void InvCipher();
+ };
+
+}
+
+#endif
diff --git a/app/ezprotect/src/main/cpp/util/Base64.cpp b/app/ezprotect/src/main/cpp/util/Base64.cpp
new file mode 100755
index 0000000..9369e9f
--- /dev/null
+++ b/app/ezprotect/src/main/cpp/util/Base64.cpp
@@ -0,0 +1,138 @@
+//
+// Created by like on 2022/1/26.
+//
+
+#include "Base64.h"
+
+#include
+#include
+#include
+
+static const char base[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
+
+static char find_pos(char ch) {
+ // the last position (the only) in base[]
+ char *ptr = (char *) strrchr(base, ch);
+ return (ptr - base);
+}
+
+static void encode_translate_group(const char *text, char *output) {
+ uint32_t bits = 0;
+ for (size_t j = 0; j < 3; ++j) {
+ bits = (bits << 8) | (text[j] & 0xFF);
+ }
+
+ size_t cur = 0;
+ for (size_t j = 0; j < 4; ++j) {
+ char shift = (3 - j) * 6;
+ char changed = (bits >> shift) & 0x3F;
+ output[cur++] = base[(size_t) changed];
+ }
+}
+
+int base64_encode(const char *text, size_t text_size, char **output,
+ size_t *output_size) {
+ // 1. caculate size and alloc memory.
+ size_t group = (text_size + 2) / 3;
+ size_t total_size = group * 4 + 1;
+ char *buffer = (char *) malloc(total_size);
+ size_t cur = 0;
+
+ if (buffer == NULL) {
+ return -ENOMEM;
+ }
+
+ // 2. translate by group.
+ for (size_t i = 0; i < text_size; i += 3, cur += 4) {
+ if (i + 3 > text_size) {
+ size_t have = text_size - i;
+ size_t owe = 3 - have;
+
+ char equals[4] = {0x0};
+ memset(equals, 0, 4);
+ memcpy(equals, text + i, have);
+ encode_translate_group(equals, buffer + cur);
+ memset(buffer + cur + 4 - owe, '=', owe);
+ } else {
+ encode_translate_group(text + i, buffer + cur);
+ }
+ }
+ buffer[cur] = '\0';
+
+ *output = buffer;
+ *output_size = cur;
+
+ return 0;
+}
+
+static size_t tail_equals_count(const char *text, size_t text_size) {
+ size_t equals_count = 0;
+ for (size_t i = 1; i < 3; ++i) {
+ if (text[text_size - i] == '=') {
+ equals_count += 1;
+ }
+ }
+ return equals_count;
+}
+
+static size_t padding_size(const char *text, size_t text_size) {
+ size_t actual_size = 0;
+ size_t equal_count = 0;
+ for (size_t i = 1; i < 4; ++i) {
+ if (text[text_size - i] == '=') {
+ equal_count += 1;
+ }
+ }
+ switch (equal_count) {
+ case 0:
+ actual_size += 4; // 3 + 1 [1 for NULL]
+ break;
+ case 1:
+ actual_size += 4; // Ceil((6*3)/8)+1
+ break;
+ case 2:
+ actual_size += 3; // Ceil((6*2)/8)+1
+ break;
+ case 3:
+ actual_size += 2; // Ceil((6*1)/8)+1
+ break;
+ }
+ return actual_size;
+}
+
+static void decode_translate_group(const char *text, char *output) {
+ uint32_t bits = 0;
+ size_t cur = 0;
+ for (size_t j = 0; j < 4; ++j) {
+ bits = (bits << 6) | find_pos(text[j]);
+ }
+ for (size_t j = 0; j < 3; ++j) {
+ char shift = (2 - j) * 8;
+ char changed = (bits >> shift) & 0xFF;
+ output[cur++] = changed;
+ }
+}
+
+int base64_decode(const char *text, size_t text_size, char **output,
+ size_t *output_size) {
+ if (text_size % 4) {
+ // wrong format for base64.
+ return -EINVAL;
+ }
+
+ size_t actual_size = text_size / 4 * 3 + 1;
+ size_t cur = 0;
+ size_t cnt = tail_equals_count(text, text_size);
+ char *buffer = (char *) malloc(actual_size);
+ for (size_t i = 0; i < text_size; i += 4, cur += 3) {
+ decode_translate_group(text + i, buffer + cur);
+ }
+ cur -= cnt;
+ buffer[cur] = '\0';
+
+ *output = buffer;
+ *output_size = cur;
+
+ return 0;
+}
+
diff --git a/app/ezprotect/src/main/cpp/util/Base64.h b/app/ezprotect/src/main/cpp/util/Base64.h
new file mode 100755
index 0000000..01b8ea5
--- /dev/null
+++ b/app/ezprotect/src/main/cpp/util/Base64.h
@@ -0,0 +1,24 @@
+//
+// Created by like on 2022/1/26.
+//
+
+#ifndef _BASE64_H_
+#define _BASE64_H_
+
+#include
+#include
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+int base64_decode(const char *text, size_t text_size, char **output, size_t *output_size);
+
+int base64_encode(const char *text, size_t text_size, char **output, size_t *output_size);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
+
diff --git a/app/ezprotect/src/main/cpp/util/Logger.h b/app/ezprotect/src/main/cpp/util/Logger.h
new file mode 100755
index 0000000..59f1528
--- /dev/null
+++ b/app/ezprotect/src/main/cpp/util/Logger.h
@@ -0,0 +1,28 @@
+//
+// Created by like on 2022/1/26.
+//
+
+#ifndef LOGGER_H
+#define LOGGER_H
+
+#include "jni.h"
+#include "stdio.h"
+#include
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define TAG "NativeApi" // 这个是自定义的LOG的标识
+#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG,TAG ,__VA_ARGS__) // 定义LOGD类型
+#define LOGI(...) __android_log_print(ANDROID_LOG_INFO,TAG ,__VA_ARGS__) // 定义LOGI类型
+#define LOGW(...) __android_log_print(ANDROID_LOG_WARN,TAG ,__VA_ARGS__) // 定义LOGW类型
+#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR,TAG ,__VA_ARGS__) // 定义LOGE类型
+#define LOGF(...) __android_log_print(ANDROID_LOG_FATAL,TAG ,__VA_ARGS__) // 定义LOGF类型
+
+#ifdef __cplusplus
+}
+#endif
+
+
+#endif
diff --git a/app/ezprotect/src/main/cpp/util/MD5.cpp b/app/ezprotect/src/main/cpp/util/MD5.cpp
new file mode 100644
index 0000000..5ceabec
--- /dev/null
+++ b/app/ezprotect/src/main/cpp/util/MD5.cpp
@@ -0,0 +1,293 @@
+//
+// Created by like on 2022/1/26.
+//
+
+#include "MD5.h"
+
+#ifndef HAVE_OPENSSL
+
+#define F(x, y, z) ((z) ^ ((x) & ((y) ^ (z))))
+#define G(x, y, z) ((y) ^ ((z) & ((x) ^ (y))))
+#define H(x, y, z) ((x) ^ (y) ^ (z))
+#define I(x, y, z) ((y) ^ ((x) | ~(z)))
+#define STEP(f, a, b, c, d, x, t, s) \
+ (a) += f((b), (c), (d)) + (x) + (t); \
+ (a) = (((a) << (s)) | (((a) & 0xffffffff) >> (32 - (s)))); \
+ (a) += (b);
+
+#if defined(__i386__) || defined(__x86_64__) || defined(__vax__)
+#define SET(n) \
+ (*(MD5_u32 *)&ptr[(n) * 4])
+#define GET(n) \
+ SET(n)
+#else
+#define SET(n) \
+ (ctx->block[(n)] = \
+ (MD5_u32)ptr[(n) * 4] | \
+ ((MD5_u32)ptr[(n) * 4 + 1] << 8) | \
+ ((MD5_u32)ptr[(n) * 4 + 2] << 16) | \
+ ((MD5_u32)ptr[(n) * 4 + 3] << 24))
+#define GET(n) \
+ (ctx->block[(n)])
+#endif
+
+typedef unsigned int MD5_u32;
+
+typedef struct {
+ MD5_u32 lo, hi;
+ MD5_u32 a, b, c, d;
+ unsigned char buffer[64];
+ MD5_u32 block[16];
+} MD5_CTX;
+
+static void MD5_Init(MD5_CTX *ctx);
+
+static void MD5_Update(MD5_CTX *ctx, const void *data, unsigned long size);
+
+static void MD5_Final(unsigned char *result, MD5_CTX *ctx);
+
+static const void *body(MD5_CTX *ctx, const void *data, unsigned long size) {
+ const unsigned char *ptr;
+ MD5_u32 a, b, c, d;
+ MD5_u32 saved_a, saved_b, saved_c, saved_d;
+
+ ptr = (const unsigned char *) data;
+
+ a = ctx->a;
+ b = ctx->b;
+ c = ctx->c;
+ d = ctx->d;
+
+ do {
+ saved_a = a;
+ saved_b = b;
+ saved_c = c;
+ saved_d = d;
+
+ STEP(F, a, b, c, d, SET(0), 0xd76aa478, 7)
+ STEP(F, d, a, b, c, SET(1), 0xe8c7b756, 12)
+ STEP(F, c, d, a, b, SET(2), 0x242070db, 17)
+ STEP(F, b, c, d, a, SET(3), 0xc1bdceee, 22)
+ STEP(F, a, b, c, d, SET(4), 0xf57c0faf, 7)
+ STEP(F, d, a, b, c, SET(5), 0x4787c62a, 12)
+ STEP(F, c, d, a, b, SET(6), 0xa8304613, 17)
+ STEP(F, b, c, d, a, SET(7), 0xfd469501, 22)
+ STEP(F, a, b, c, d, SET(8), 0x698098d8, 7)
+ STEP(F, d, a, b, c, SET(9), 0x8b44f7af, 12)
+ STEP(F, c, d, a, b, SET(10), 0xffff5bb1, 17)
+ STEP(F, b, c, d, a, SET(11), 0x895cd7be, 22)
+ STEP(F, a, b, c, d, SET(12), 0x6b901122, 7)
+ STEP(F, d, a, b, c, SET(13), 0xfd987193, 12)
+ STEP(F, c, d, a, b, SET(14), 0xa679438e, 17)
+ STEP(F, b, c, d, a, SET(15), 0x49b40821, 22)
+ STEP(G, a, b, c, d, GET(1), 0xf61e2562, 5)
+ STEP(G, d, a, b, c, GET(6), 0xc040b340, 9)
+ STEP(G, c, d, a, b, GET(11), 0x265e5a51, 14)
+ STEP(G, b, c, d, a, GET(0), 0xe9b6c7aa, 20)
+ STEP(G, a, b, c, d, GET(5), 0xd62f105d, 5)
+ STEP(G, d, a, b, c, GET(10), 0x02441453, 9)
+ STEP(G, c, d, a, b, GET(15), 0xd8a1e681, 14)
+ STEP(G, b, c, d, a, GET(4), 0xe7d3fbc8, 20)
+ STEP(G, a, b, c, d, GET(9), 0x21e1cde6, 5)
+ STEP(G, d, a, b, c, GET(14), 0xc33707d6, 9)
+ STEP(G, c, d, a, b, GET(3), 0xf4d50d87, 14)
+ STEP(G, b, c, d, a, GET(8), 0x455a14ed, 20)
+ STEP(G, a, b, c, d, GET(13), 0xa9e3e905, 5)
+ STEP(G, d, a, b, c, GET(2), 0xfcefa3f8, 9)
+ STEP(G, c, d, a, b, GET(7), 0x676f02d9, 14)
+ STEP(G, b, c, d, a, GET(12), 0x8d2a4c8a, 20)
+ STEP(H, a, b, c, d, GET(5), 0xfffa3942, 4)
+ STEP(H, d, a, b, c, GET(8), 0x8771f681, 11)
+ STEP(H, c, d, a, b, GET(11), 0x6d9d6122, 16)
+ STEP(H, b, c, d, a, GET(14), 0xfde5380c, 23)
+ STEP(H, a, b, c, d, GET(1), 0xa4beea44, 4)
+ STEP(H, d, a, b, c, GET(4), 0x4bdecfa9, 11)
+ STEP(H, c, d, a, b, GET(7), 0xf6bb4b60, 16)
+ STEP(H, b, c, d, a, GET(10), 0xbebfbc70, 23)
+ STEP(H, a, b, c, d, GET(13), 0x289b7ec6, 4)
+ STEP(H, d, a, b, c, GET(0), 0xeaa127fa, 11)
+ STEP(H, c, d, a, b, GET(3), 0xd4ef3085, 16)
+ STEP(H, b, c, d, a, GET(6), 0x04881d05, 23)
+ STEP(H, a, b, c, d, GET(9), 0xd9d4d039, 4)
+ STEP(H, d, a, b, c, GET(12), 0xe6db99e5, 11)
+ STEP(H, c, d, a, b, GET(15), 0x1fa27cf8, 16)
+ STEP(H, b, c, d, a, GET(2), 0xc4ac5665, 23)
+ STEP(I, a, b, c, d, GET(0), 0xf4292244, 6)
+ STEP(I, d, a, b, c, GET(7), 0x432aff97, 10)
+ STEP(I, c, d, a, b, GET(14), 0xab9423a7, 15)
+ STEP(I, b, c, d, a, GET(5), 0xfc93a039, 21)
+ STEP(I, a, b, c, d, GET(12), 0x655b59c3, 6)
+ STEP(I, d, a, b, c, GET(3), 0x8f0ccc92, 10)
+ STEP(I, c, d, a, b, GET(10), 0xffeff47d, 15)
+ STEP(I, b, c, d, a, GET(1), 0x85845dd1, 21)
+ STEP(I, a, b, c, d, GET(8), 0x6fa87e4f, 6)
+ STEP(I, d, a, b, c, GET(15), 0xfe2ce6e0, 10)
+ STEP(I, c, d, a, b, GET(6), 0xa3014314, 15)
+ STEP(I, b, c, d, a, GET(13), 0x4e0811a1, 21)
+ STEP(I, a, b, c, d, GET(4), 0xf7537e82, 6)
+ STEP(I, d, a, b, c, GET(11), 0xbd3af235, 10)
+ STEP(I, c, d, a, b, GET(2), 0x2ad7d2bb, 15)
+ STEP(I, b, c, d, a, GET(9), 0xeb86d391, 21)
+
+ a += saved_a;
+ b += saved_b;
+ c += saved_c;
+ d += saved_d;
+
+ ptr += 64;
+ } while (size -= 64);
+
+ ctx->a = a;
+ ctx->b = b;
+ ctx->c = c;
+ ctx->d = d;
+
+ return ptr;
+}
+
+void MD5_Init(MD5_CTX *ctx) {
+ ctx->a = 0x67452301;
+ ctx->b = 0xefcdab89;
+ ctx->c = 0x98badcfe;
+ ctx->d = 0x10325476;
+
+ ctx->lo = 0;
+ ctx->hi = 0;
+}
+
+void MD5_Update(MD5_CTX *ctx, const void *data, unsigned long size) {
+ MD5_u32 saved_lo;
+ unsigned long used, free;
+
+ saved_lo = ctx->lo;
+ if ((ctx->lo = (saved_lo + size) & 0x1fffffff) < saved_lo)
+ ctx->hi++;
+ ctx->hi += size >> 29;
+ used = saved_lo & 0x3f;
+
+ if (used) {
+ free = 64 - used;
+ if (size < free) {
+ memcpy(&ctx->buffer[used], data, size);
+ return;
+ }
+
+ memcpy(&ctx->buffer[used], data, free);
+ data = (unsigned char *) data + free;
+ size -= free;
+ body(ctx, ctx->buffer, 64);
+ }
+
+ if (size >= 64) {
+ data = body(ctx, data, size & ~(unsigned long) 0x3f);
+ size &= 0x3f;
+ }
+
+ memcpy(ctx->buffer, data, size);
+}
+
+void MD5_Final(unsigned char *result, MD5_CTX *ctx) {
+ unsigned long used, free;
+ used = ctx->lo & 0x3f;
+ ctx->buffer[used++] = 0x80;
+ free = 64 - used;
+
+ if (free < 8) {
+ memset(&ctx->buffer[used], 0, free);
+ body(ctx, ctx->buffer, 64);
+ used = 0;
+ free = 64;
+ }
+
+ memset(&ctx->buffer[used], 0, free - 8);
+
+ ctx->lo <<= 3;
+ ctx->buffer[56] = ctx->lo;
+ ctx->buffer[57] = ctx->lo >> 8;
+ ctx->buffer[58] = ctx->lo >> 16;
+ ctx->buffer[59] = ctx->lo >> 24;
+ ctx->buffer[60] = ctx->hi;
+ ctx->buffer[61] = ctx->hi >> 8;
+ ctx->buffer[62] = ctx->hi >> 16;
+ ctx->buffer[63] = ctx->hi >> 24;
+ body(ctx, ctx->buffer, 64);
+ result[0] = ctx->a;
+ result[1] = ctx->a >> 8;
+ result[2] = ctx->a >> 16;
+ result[3] = ctx->a >> 24;
+ result[4] = ctx->b;
+ result[5] = ctx->b >> 8;
+ result[6] = ctx->b >> 16;
+ result[7] = ctx->b >> 24;
+ result[8] = ctx->c;
+ result[9] = ctx->c >> 8;
+ result[10] = ctx->c >> 16;
+ result[11] = ctx->c >> 24;
+ result[12] = ctx->d;
+ result[13] = ctx->d >> 8;
+ result[14] = ctx->d >> 16;
+ result[15] = ctx->d >> 24;
+ memset(ctx, 0, sizeof(*ctx));
+}
+
+#else
+#include
+#endif
+
+
+using namespace std;
+
+/* Return Calculated raw result(always little-endian), the size is always 16 */
+void md5bin(const void *dat, size_t len, unsigned char out[16]) {
+ MD5_CTX c;
+ MD5_Init(&c);
+ MD5_Update(&c, dat, len);
+ MD5_Final(out, &c);
+}
+
+static char hb2hex(unsigned char hb) {
+ hb = hb & 0xF;
+ return hb < 10 ? '0' + hb : hb - 10 + 'a';
+}
+
+string md5file(const char *filename) {
+ std::FILE *file = std::fopen(filename, "rb");
+ string res = md5file(file);
+ std::fclose(file);
+ return res;
+}
+
+string md5file(std::FILE *file) {
+ MD5_CTX c;
+ MD5_Init(&c);
+
+ char buff[BUFSIZ];
+ unsigned char out[16];
+ size_t len = 0;
+ while ((len = std::fread(buff, sizeof(char), BUFSIZ, file)) > 0) {
+ MD5_Update(&c, buff, len);
+ }
+ MD5_Final(out, &c);
+
+ string res;
+ for (size_t i = 0; i < 16; ++i) {
+ res.push_back(hb2hex(out[i] >> 4));
+ res.push_back(hb2hex(out[i]));
+ }
+ return res;
+}
+
+string md5(const void *dat, size_t len) {
+ string res;
+ unsigned char out[16];
+ md5bin(dat, len, out);
+ for (size_t i = 0; i < 16; ++i) {
+ res.push_back(hb2hex(out[i] >> 4));
+ res.push_back(hb2hex(out[i]));
+ }
+ return res;
+}
+
+std::string md5(std::string dat) {
+ return md5(dat.c_str(), dat.length());
+}
\ No newline at end of file
diff --git a/app/ezprotect/src/main/cpp/util/MD5.h b/app/ezprotect/src/main/cpp/util/MD5.h
new file mode 100644
index 0000000..2a25bd9
--- /dev/null
+++ b/app/ezprotect/src/main/cpp/util/MD5.h
@@ -0,0 +1,23 @@
+//
+// Created by like on 2022/1/26.
+//
+
+#ifndef MD5_H
+#define MD5_H
+
+#define _CRT_SECURE_NO_WARNINGS
+
+#include
+#include
+
+using std::string;
+
+std::string md5(std::string dat);
+
+std::string md5(const void *dat, size_t len);
+
+std::string md5file(const char *filename);
+
+std::string md5file(std::FILE *file);
+
+#endif // end of MD5_H
diff --git a/app/ezprotect/src/main/java/cn/ezandroid/ezprotect/lib/NativeApi.kt b/app/ezprotect/src/main/java/cn/ezandroid/ezprotect/lib/NativeApi.kt
new file mode 100644
index 0000000..ab60837
--- /dev/null
+++ b/app/ezprotect/src/main/java/cn/ezandroid/ezprotect/lib/NativeApi.kt
@@ -0,0 +1,49 @@
+package cn.ezandroid.ezprotect.lib
+
+/**
+ * Jni接口,通过动态注册,因此该类不能混淆,为避免破解者反编译后通过函数名了解函数实际功能,这里使用无意义方法名,应用时通过Native类进行调用
+ */
+class NativeApi {
+
+ init {
+ System.loadLibrary("ezcore")
+ }
+
+ /**
+ * AES加密
+ *
+ * @param text
+ */
+ external fun a(text: String): String
+
+ /**
+ * AES解密
+ *
+ * @param text
+ */
+ external fun b(text: String): String
+
+ /**
+ * 当前APK文件和此加密So包的文件信息用于服务器校验或攻击预警
+ */
+ external fun c(): String
+}
+
+/**
+ * 该类可混淆,因此破解者反编译后无法通过函数名了解函数实际功能
+ */
+object Native {
+ private val api: NativeApi by lazy { NativeApi() }
+
+ fun encrypt(text: String): String {
+ return if (text.isNotEmpty()) api.a(text) else ""
+ }
+
+ fun decrypt(text: String): String {
+ return if (text.isNotEmpty()) api.b(text) else ""
+ }
+
+ fun integrity(): String {
+ return api.c()
+ }
+}
\ No newline at end of file
diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro
new file mode 100644
index 0000000..481bb43
--- /dev/null
+++ b/app/proguard-rules.pro
@@ -0,0 +1,21 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile
\ No newline at end of file
diff --git a/app/release/output-metadata.json b/app/release/output-metadata.json
new file mode 100644
index 0000000..c9c7120
--- /dev/null
+++ b/app/release/output-metadata.json
@@ -0,0 +1,20 @@
+{
+ "version": 3,
+ "artifactType": {
+ "type": "APK",
+ "kind": "Directory"
+ },
+ "applicationId": "cn.ezandroid.ezprotect",
+ "variantName": "release",
+ "elements": [
+ {
+ "type": "SINGLE",
+ "filters": [],
+ "attributes": [],
+ "versionCode": 1,
+ "versionName": "1.0",
+ "outputFile": "app-release.apk"
+ }
+ ],
+ "elementType": "File"
+}
\ No newline at end of file
diff --git a/app/src/androidTest/java/cn/ezandroid/ezprotect/ExampleInstrumentedTest.kt b/app/src/androidTest/java/cn/ezandroid/ezprotect/ExampleInstrumentedTest.kt
new file mode 100644
index 0000000..8ccfbd3
--- /dev/null
+++ b/app/src/androidTest/java/cn/ezandroid/ezprotect/ExampleInstrumentedTest.kt
@@ -0,0 +1,24 @@
+package cn.ezandroid.ezprotect
+
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.ext.junit.runners.AndroidJUnit4
+
+import org.junit.Test
+import org.junit.runner.RunWith
+
+import org.junit.Assert.*
+
+/**
+ * Instrumented test, which will execute on an Android device.
+ *
+ * See [testing documentation](http://d.android.com/tools/testing).
+ */
+@RunWith(AndroidJUnit4::class)
+class ExampleInstrumentedTest {
+ @Test
+ fun useAppContext() {
+ // Context of the app under test.
+ val appContext = InstrumentationRegistry.getInstrumentation().targetContext
+ assertEquals("cn.ezandroid.ezprotect", appContext.packageName)
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..6761a5a
--- /dev/null
+++ b/app/src/main/AndroidManifest.xml
@@ -0,0 +1,24 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/java/cn/ezandroid/ezprotect/MainActivity.kt b/app/src/main/java/cn/ezandroid/ezprotect/MainActivity.kt
new file mode 100644
index 0000000..c0a7f8f
--- /dev/null
+++ b/app/src/main/java/cn/ezandroid/ezprotect/MainActivity.kt
@@ -0,0 +1,15 @@
+package cn.ezandroid.ezprotect
+
+import androidx.appcompat.app.AppCompatActivity
+import android.os.Bundle
+import android.widget.TextView
+import cn.ezandroid.ezprotect.lib.Native
+
+class MainActivity : AppCompatActivity() {
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setContentView(R.layout.activity_main)
+
+ findViewById(R.id.info).text = Native.decrypt(Native.encrypt("ezproject")) + " : " + Native.integrity()
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/cn/ezandroid/ezprotect/MyApplication.kt b/app/src/main/java/cn/ezandroid/ezprotect/MyApplication.kt
new file mode 100644
index 0000000..7d5b4c1
--- /dev/null
+++ b/app/src/main/java/cn/ezandroid/ezprotect/MyApplication.kt
@@ -0,0 +1,15 @@
+package cn.ezandroid.ezprotect
+
+import android.app.Application
+import android.content.Context
+
+class MyApplication : Application() {
+
+ override fun attachBaseContext(base: Context?) {
+ super.attachBaseContext(base)
+ }
+
+ override fun onCreate() {
+ super.onCreate()
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/res/drawable-v24/ic_launcher_foreground.xml b/app/src/main/res/drawable-v24/ic_launcher_foreground.xml
new file mode 100644
index 0000000..2b068d1
--- /dev/null
+++ b/app/src/main/res/drawable-v24/ic_launcher_foreground.xml
@@ -0,0 +1,30 @@
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/ic_launcher_background.xml b/app/src/main/res/drawable/ic_launcher_background.xml
new file mode 100644
index 0000000..07d5da9
--- /dev/null
+++ b/app/src/main/res/drawable/ic_launcher_background.xml
@@ -0,0 +1,170 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml
new file mode 100644
index 0000000..eb22391
--- /dev/null
+++ b/app/src/main/res/layout/activity_main.xml
@@ -0,0 +1,19 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
new file mode 100644
index 0000000..eca70cf
--- /dev/null
+++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
new file mode 100644
index 0000000..eca70cf
--- /dev/null
+++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher.webp b/app/src/main/res/mipmap-hdpi/ic_launcher.webp
new file mode 100644
index 0000000..c209e78
Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher.webp differ
diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp
new file mode 100644
index 0000000..b2dfe3d
Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp differ
diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher.webp b/app/src/main/res/mipmap-mdpi/ic_launcher.webp
new file mode 100644
index 0000000..4f0f1d6
Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_launcher.webp differ
diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp
new file mode 100644
index 0000000..62b611d
Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp differ
diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher.webp b/app/src/main/res/mipmap-xhdpi/ic_launcher.webp
new file mode 100644
index 0000000..948a307
Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher.webp differ
diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp
new file mode 100644
index 0000000..1b9a695
Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp differ
diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp b/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp
new file mode 100644
index 0000000..28d4b77
Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp differ
diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp
new file mode 100644
index 0000000..9287f50
Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp differ
diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp
new file mode 100644
index 0000000..aa7d642
Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp differ
diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp
new file mode 100644
index 0000000..9126ae3
Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp differ
diff --git a/app/src/main/res/values-night/themes.xml b/app/src/main/res/values-night/themes.xml
new file mode 100644
index 0000000..bc6d0a4
--- /dev/null
+++ b/app/src/main/res/values-night/themes.xml
@@ -0,0 +1,16 @@
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml
new file mode 100644
index 0000000..f8c6127
--- /dev/null
+++ b/app/src/main/res/values/colors.xml
@@ -0,0 +1,10 @@
+
+
+ #FFBB86FC
+ #FF6200EE
+ #FF3700B3
+ #FF03DAC5
+ #FF018786
+ #FF000000
+ #FFFFFFFF
+
\ No newline at end of file
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
new file mode 100644
index 0000000..c367e3d
--- /dev/null
+++ b/app/src/main/res/values/strings.xml
@@ -0,0 +1,3 @@
+
+ EZProtect
+
\ No newline at end of file
diff --git a/app/src/main/res/values/themes.xml b/app/src/main/res/values/themes.xml
new file mode 100644
index 0000000..d32f12d
--- /dev/null
+++ b/app/src/main/res/values/themes.xml
@@ -0,0 +1,16 @@
+
+
+
+
\ No newline at end of file
diff --git a/app/src/test/java/cn/ezandroid/ezprotect/ExampleUnitTest.kt b/app/src/test/java/cn/ezandroid/ezprotect/ExampleUnitTest.kt
new file mode 100644
index 0000000..9086971
--- /dev/null
+++ b/app/src/test/java/cn/ezandroid/ezprotect/ExampleUnitTest.kt
@@ -0,0 +1,17 @@
+package cn.ezandroid.ezprotect
+
+import org.junit.Test
+
+import org.junit.Assert.*
+
+/**
+ * Example local unit test, which will execute on the development machine (host).
+ *
+ * See [testing documentation](http://d.android.com/tools/testing).
+ */
+class ExampleUnitTest {
+ @Test
+ fun addition_isCorrect() {
+ assertEquals(4, 2 + 2)
+ }
+}
\ No newline at end of file
diff --git a/build.gradle b/build.gradle
new file mode 100644
index 0000000..7639ace
--- /dev/null
+++ b/build.gradle
@@ -0,0 +1,10 @@
+// Top-level build file where you can add configuration options common to all sub-projects/modules.
+plugins {
+ id 'com.android.application' version '7.1.0' apply false
+ id 'com.android.library' version '7.1.0' apply false
+ id 'org.jetbrains.kotlin.android' version '1.5.30' apply false
+}
+
+task clean(type: Delete) {
+ delete rootProject.buildDir
+}
\ No newline at end of file
diff --git a/gradle.properties b/gradle.properties
new file mode 100644
index 0000000..91d0bb2
--- /dev/null
+++ b/gradle.properties
@@ -0,0 +1,27 @@
+# Project-wide Gradle settings.
+# IDE (e.g. Android Studio) users:
+# Gradle settings configured through the IDE *will override*
+# any settings specified in this file.
+# For more details on how to configure your build environment visit
+# http://www.gradle.org/docs/current/userguide/build_environment.html
+# Specifies the JVM arguments used for the daemon process.
+# The setting is particularly useful for tweaking memory settings.
+org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
+# When configured, Gradle will run in incubating parallel mode.
+# This option should only be used with decoupled projects. More details, visit
+# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
+# org.gradle.parallel=true
+# AndroidX package structure to make it clearer which packages are bundled with the
+# Android operating system, and which are packaged with your app"s APK
+# https://developer.android.com/topic/libraries/support-library/androidx-rn
+android.useAndroidX=true
+# Kotlin code style for this project: "official" or "obsolete":
+kotlin.code.style=official
+# Enables namespacing of each library's R class so that its R class includes only the
+# resources declared in the library itself and none from the library's dependencies,
+# thereby reducing the size of the R class for that library
+android.nonTransitiveRClass=true
+
+applicationId=cn.ezandroid.ezprotect
+versionCode=100
+versionName=1.0.0
\ No newline at end of file
diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 0000000..e708b1c
Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000..ffc3a94
--- /dev/null
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,6 @@
+#Wed Jan 26 14:44:47 CST 2022
+distributionBase=GRADLE_USER_HOME
+distributionUrl=https\://services.gradle.org/distributions/gradle-7.2-bin.zip
+distributionPath=wrapper/dists
+zipStorePath=wrapper/dists
+zipStoreBase=GRADLE_USER_HOME
diff --git a/gradlew b/gradlew
new file mode 100755
index 0000000..4f906e0
--- /dev/null
+++ b/gradlew
@@ -0,0 +1,185 @@
+#!/usr/bin/env sh
+
+#
+# Copyright 2015 the original author or authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+##############################################################################
+##
+## Gradle start up script for UN*X
+##
+##############################################################################
+
+# Attempt to set APP_HOME
+# Resolve links: $0 may be a link
+PRG="$0"
+# Need this for relative symlinks.
+while [ -h "$PRG" ] ; do
+ ls=`ls -ld "$PRG"`
+ link=`expr "$ls" : '.*-> \(.*\)$'`
+ if expr "$link" : '/.*' > /dev/null; then
+ PRG="$link"
+ else
+ PRG=`dirname "$PRG"`"/$link"
+ fi
+done
+SAVED="`pwd`"
+cd "`dirname \"$PRG\"`/" >/dev/null
+APP_HOME="`pwd -P`"
+cd "$SAVED" >/dev/null
+
+APP_NAME="Gradle"
+APP_BASE_NAME=`basename "$0"`
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD="maximum"
+
+warn () {
+ echo "$*"
+}
+
+die () {
+ echo
+ echo "$*"
+ echo
+ exit 1
+}
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+nonstop=false
+case "`uname`" in
+ CYGWIN* )
+ cygwin=true
+ ;;
+ Darwin* )
+ darwin=true
+ ;;
+ MINGW* )
+ msys=true
+ ;;
+ NONSTOP* )
+ nonstop=true
+ ;;
+esac
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD="$JAVA_HOME/jre/sh/java"
+ else
+ JAVACMD="$JAVA_HOME/bin/java"
+ fi
+ if [ ! -x "$JAVACMD" ] ; then
+ die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+else
+ JAVACMD="java"
+ which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
+ MAX_FD_LIMIT=`ulimit -H -n`
+ if [ $? -eq 0 ] ; then
+ if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
+ MAX_FD="$MAX_FD_LIMIT"
+ fi
+ ulimit -n $MAX_FD
+ if [ $? -ne 0 ] ; then
+ warn "Could not set maximum file descriptor limit: $MAX_FD"
+ fi
+ else
+ warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
+ fi
+fi
+
+# For Darwin, add options to specify how the application appears in the dock
+if $darwin; then
+ GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
+fi
+
+# For Cygwin or MSYS, switch paths to Windows format before running java
+if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
+ APP_HOME=`cygpath --path --mixed "$APP_HOME"`
+ CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+
+ JAVACMD=`cygpath --unix "$JAVACMD"`
+
+ # We build the pattern for arguments to be converted via cygpath
+ ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
+ SEP=""
+ for dir in $ROOTDIRSRAW ; do
+ ROOTDIRS="$ROOTDIRS$SEP$dir"
+ SEP="|"
+ done
+ OURCYGPATTERN="(^($ROOTDIRS))"
+ # Add a user-defined pattern to the cygpath arguments
+ if [ "$GRADLE_CYGPATTERN" != "" ] ; then
+ OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
+ fi
+ # Now convert the arguments - kludge to limit ourselves to /bin/sh
+ i=0
+ for arg in "$@" ; do
+ CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
+ CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
+
+ if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
+ eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
+ else
+ eval `echo args$i`="\"$arg\""
+ fi
+ i=`expr $i + 1`
+ done
+ case $i in
+ 0) set -- ;;
+ 1) set -- "$args0" ;;
+ 2) set -- "$args0" "$args1" ;;
+ 3) set -- "$args0" "$args1" "$args2" ;;
+ 4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+ 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+ 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+ 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+ 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+ 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+ esac
+fi
+
+# Escape application args
+save () {
+ for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
+ echo " "
+}
+APP_ARGS=`save "$@"`
+
+# Collect all arguments for the java command, following the shell quoting and substitution rules
+eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
+
+exec "$JAVACMD" "$@"
diff --git a/gradlew.bat b/gradlew.bat
new file mode 100644
index 0000000..107acd3
--- /dev/null
+++ b/gradlew.bat
@@ -0,0 +1,89 @@
+@rem
+@rem Copyright 2015 the original author or authors.
+@rem
+@rem Licensed under the Apache License, Version 2.0 (the "License");
+@rem you may not use this file except in compliance with the License.
+@rem You may obtain a copy of the License at
+@rem
+@rem https://www.apache.org/licenses/LICENSE-2.0
+@rem
+@rem Unless required by applicable law or agreed to in writing, software
+@rem distributed under the License is distributed on an "AS IS" BASIS,
+@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+@rem See the License for the specific language governing permissions and
+@rem limitations under the License.
+@rem
+
+@if "%DEBUG%" == "" @echo off
+@rem ##########################################################################
+@rem
+@rem Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+set DIRNAME=%~dp0
+if "%DIRNAME%" == "" set DIRNAME=.
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Resolve any "." and ".." in APP_HOME to make it shorter.
+for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if "%ERRORLEVEL%" == "0" goto execute
+
+echo.
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto execute
+
+echo.
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
+
+:end
+@rem End local scope for the variables with windows NT shell
+if "%ERRORLEVEL%"=="0" goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
+exit /b 1
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/settings.gradle b/settings.gradle
new file mode 100644
index 0000000..81f36af
--- /dev/null
+++ b/settings.gradle
@@ -0,0 +1,17 @@
+pluginManagement {
+ repositories {
+ gradlePluginPortal()
+ google()
+ mavenCentral()
+ }
+}
+dependencyResolutionManagement {
+ repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
+ repositories {
+ google()
+ mavenCentral()
+ }
+}
+rootProject.name = "EZProtect"
+include ':app'
+include ':app:ezprotect'