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'