commit b14532df4e66f6a9998d9972a2b308797af530ad Author: kikyps Date: Sun Dec 28 14:40:46 2025 +0700 first commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..aa724b7 --- /dev/null +++ b/.gitignore @@ -0,0 +1,15 @@ +*.iml +.gradle +/local.properties +/.idea/caches +/.idea/libraries +/.idea/modules.xml +/.idea/workspace.xml +/.idea/navEditor.xml +/.idea/assetWizardSettings.xml +.DS_Store +/build +/captures +.externalNativeBuild +.cxx +local.properties 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/AndroidProjectSystem.xml b/.idea/AndroidProjectSystem.xml new file mode 100644 index 0000000..4a53bee --- /dev/null +++ b/.idea/AndroidProjectSystem.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/.idea/compiler.xml b/.idea/compiler.xml new file mode 100644 index 0000000..b86273d --- /dev/null +++ b/.idea/compiler.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/.idea/deploymentTargetSelector.xml b/.idea/deploymentTargetSelector.xml new file mode 100644 index 0000000..b268ef3 --- /dev/null +++ b/.idea/deploymentTargetSelector.xml @@ -0,0 +1,10 @@ + + + + + + + + + \ No newline at end of file diff --git a/.idea/gradle.xml b/.idea/gradle.xml new file mode 100644 index 0000000..639c779 --- /dev/null +++ b/.idea/gradle.xml @@ -0,0 +1,19 @@ + + + + + + + \ No newline at end of file diff --git a/.idea/migrations.xml b/.idea/migrations.xml new file mode 100644 index 0000000..f8051a6 --- /dev/null +++ b/.idea/migrations.xml @@ -0,0 +1,10 @@ + + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..b2c751a --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,9 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/runConfigurations.xml b/.idea/runConfigurations.xml new file mode 100644 index 0000000..16660f1 --- /dev/null +++ b/.idea/runConfigurations.xml @@ -0,0 +1,17 @@ + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..94a25f7 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ 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.kts b/app/build.gradle.kts new file mode 100644 index 0000000..9dc399f --- /dev/null +++ b/app/build.gradle.kts @@ -0,0 +1,56 @@ +plugins { + alias(libs.plugins.android.application) +} + +android { + namespace = "com.example.customtoastview" + compileSdk { + version = release(36) + } + + defaultConfig { + applicationId = "com.example.customtoastview" + minSdk = 24 + targetSdk = 36 + versionCode = 1 + versionName = "1.0" + + testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" + externalNativeBuild { + cmake { + cppFlags += "" + } + } + } + + buildTypes { + release { + isMinifyEnabled = false + proguardFiles( + getDefaultProguardFile("proguard-android-optimize.txt"), + "proguard-rules.pro" + ) + } + } + compileOptions { + sourceCompatibility = JavaVersion.VERSION_11 + targetCompatibility = JavaVersion.VERSION_11 + } + externalNativeBuild { + cmake { + path = file("src/main/cpp/CMakeLists.txt") + version = "3.22.1" + } + } + ndkVersion = "26.0.10792818" +} + +dependencies { + implementation(libs.appcompat) + implementation(libs.material) + implementation(libs.activity) + implementation(libs.constraintlayout) + testImplementation(libs.junit) + androidTestImplementation(libs.ext.junit) + androidTestImplementation(libs.espresso.core) +} \ 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/app-release.apk b/app/release/app-release.apk new file mode 100644 index 0000000..2add774 Binary files /dev/null and b/app/release/app-release.apk differ diff --git a/app/release/baselineProfiles/0/app-release.dm b/app/release/baselineProfiles/0/app-release.dm new file mode 100644 index 0000000..86e06de Binary files /dev/null and b/app/release/baselineProfiles/0/app-release.dm differ diff --git a/app/release/baselineProfiles/1/app-release.dm b/app/release/baselineProfiles/1/app-release.dm new file mode 100644 index 0000000..a2af477 Binary files /dev/null and b/app/release/baselineProfiles/1/app-release.dm differ diff --git a/app/release/output-metadata.json b/app/release/output-metadata.json new file mode 100644 index 0000000..b1968f2 --- /dev/null +++ b/app/release/output-metadata.json @@ -0,0 +1,37 @@ +{ + "version": 3, + "artifactType": { + "type": "APK", + "kind": "Directory" + }, + "applicationId": "com.example.customtoastview", + "variantName": "release", + "elements": [ + { + "type": "SINGLE", + "filters": [], + "attributes": [], + "versionCode": 1, + "versionName": "1.0", + "outputFile": "app-release.apk" + } + ], + "elementType": "File", + "baselineProfiles": [ + { + "minApi": 28, + "maxApi": 30, + "baselineProfiles": [ + "baselineProfiles/1/app-release.dm" + ] + }, + { + "minApi": 31, + "maxApi": 2147483647, + "baselineProfiles": [ + "baselineProfiles/0/app-release.dm" + ] + } + ], + "minSdkVersionForDexing": 24 +} \ No newline at end of file diff --git a/app/src/androidTest/java/com/example/customtoastview/ExampleInstrumentedTest.java b/app/src/androidTest/java/com/example/customtoastview/ExampleInstrumentedTest.java new file mode 100644 index 0000000..c9ff5b7 --- /dev/null +++ b/app/src/androidTest/java/com/example/customtoastview/ExampleInstrumentedTest.java @@ -0,0 +1,26 @@ +package com.example.customtoastview; + +import android.content.Context; + +import androidx.test.platform.app.InstrumentationRegistry; +import androidx.test.ext.junit.runners.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import static org.junit.Assert.*; + +/** + * Instrumented test, which will execute on an Android device. + * + * @see Testing documentation + */ +@RunWith(AndroidJUnit4.class) +public class ExampleInstrumentedTest { + @Test + public void useAppContext() { + // Context of the app under test. + Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext(); + assertEquals("com.example.customtoastview", appContext.getPackageName()); + } +} \ 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..2140eb3 --- /dev/null +++ b/app/src/main/AndroidManifest.xml @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/cpp/CMakeLists.txt b/app/src/main/cpp/CMakeLists.txt new file mode 100644 index 0000000..e46f485 --- /dev/null +++ b/app/src/main/cpp/CMakeLists.txt @@ -0,0 +1,69 @@ + +# For more information about using CMake with Android Studio, read the +# documentation: https://d.android.com/studio/projects/add-native-code.html. +# For more examples on how to use CMake, see https://github.com/android/ndk-samples. + +# Sets the minimum CMake version required for this project. +cmake_minimum_required(VERSION 3.22.1) + +# akira ollvm +#set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -mllvm -irobf-indbr -mllvm -irobf-icall -mllvm -irobf-indgv -mllvm -irobf-cse -mllvm -irobf-cff") +#set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -mllvm -irobf-indbr -mllvm -irobf-icall -mllvm -irobf-indgv -mllvm -irobf-cse -mllvm -irobf-cff") + +set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -mllvm -fncmd -mllvm -sobf") +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -mllvm -fncmd -mllvm -sobf") + +# Hikari Ollvm +#set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -mllvm -hikari -mllvm -enable-strcry -mllvm -enable-bcfobf -mllvm -bcf_createfunc -mllvm -enable-cffobf -mllvm -enable-funcwra -mllvm -mllvm -enable-constenc -mllvm -constenc_subxor -mllvm -enable-subobf -mllvm -enable-fco -mllvm -ah_objcruntime -mllvm -enable-antihook -mllvm -ah_inline -mllvm -enable-indibran -mllvm -indibran-enc-jump-target -mllvm -ah_antirebind") +#set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -mllvm -hikari -mllvm -enable-strcry -mllvm -enable-bcfobf -mllvm -bcf_createfunc -mllvm -enable-cffobf -mllvm -enable-funcwra -mllvm -enable-constenc -mllvm -constenc_subxor -mllvm -enable-subobf -mllvm -enable-fco -mllvm -ah_objcruntime -mllvm -enable-antihook -mllvm -ah_inline -mllvm -enable-indibran -mllvm -indibran-enc-jump-target -mllvm -ah_antirebind") +#set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -mllvm -enable-strcry -mllvm -enable-bcfobf -mllvm -enable-cffobf -mllvm -enable-constenc -mllvm -constenc_subxor -mllvm -enable-subobf -mllvm -enable-fco -mllvm -enable-indibran -mllvm -indibran-enc-jump-target -mllvm -enable-antihook -mllvm -ah_objcruntime -mllvm -ah_inline -mllvm -ah_antirebind") +#set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -mllvm -enable-strcry -mllvm -enable-bcfobf -mllvm -enable-cffobf -mllvm -enable-constenc -mllvm -constenc_subxor -mllvm -enable-subobf -mllvm -enable-fco -mllvm -enable-indibran -mllvm -indibran-enc-jump-target -mllvm -enable-antihook -mllvm -ah_objcruntime -mllvm -ah_inline -mllvm -ah_antirebind") + +# Declares the project name. The project name can be accessed via ${ PROJECT_NAME}, +# Since this is the top level CMakeLists.txt, the project name is also accessible +# with ${CMAKE_PROJECT_NAME} (both CMake variables are in-sync within the top level +# build script scope). +project("customtoastview") + +# 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. +# +# In this top level CMakeLists.txt, ${CMAKE_PROJECT_NAME} is used to define +# the target library name; in the sub-module's CMakeLists.txt, ${PROJECT_NAME} +# is preferred for the same purpose. +# +# In order to load a library into your app from Java/Kotlin, you must call +# System.loadLibrary() and pass the name of the library defined here; +# for GameActivity/NativeActivity derived applications, the same library name must be +# used in the AndroidManifest.xml file. +add_library(${CMAKE_PROJECT_NAME} SHARED + # List C/C++ source files with relative paths to this CMakeLists.txt. + customtoastview.cpp) + +# Specifies libraries CMake should link to your target library. You +# can link libraries from various origins, such as libraries defined in this +# build script, prebuilt third-party libraries, or Android system libraries. +target_link_libraries(${CMAKE_PROJECT_NAME} + # List libraries link to the target library + android + log) + +#[[ +add_compile_options( + -mllvm -hikari + -mllvm -enable-strcry + -mllvm -enable-bcfobf + -mllvm -enable-cffobf + -mllvm -enable-funcwra + -mllvm -enable-constenc + -mllvm -enable-subobf + -mllvm -enable-fco + -mllvm -ah_objcruntime + -mllvm -enable-antihook + -mllvm -ah_inline + -mllvm -enable-indibran + -mllvm -indibran-enc-jump-target + -mllvm -ah_antirebind +)]] diff --git a/app/src/main/cpp/customtoastview.cpp b/app/src/main/cpp/customtoastview.cpp new file mode 100644 index 0000000..018235c --- /dev/null +++ b/app/src/main/cpp/customtoastview.cpp @@ -0,0 +1,858 @@ +#include + +// Write C++ code here. +// +// Do not forget to dynamically load the C++ library into your application. +// +// For instance, +// +// In MainActivity.java: +// static { +// System.loadLibrary("customtoastview"); +// } +// +// Or, in MainActivity.kt: +// companion object { +// init { +// System.loadLibrary("customtoastview") +// } +// } + +#include +#include +#include +#include +#include +#include + +#define LOG_TAG "IntegrityGuard" +#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__) +#define LOGW(...) __android_log_print(ANDROID_LOG_WARN, LOG_TAG, __VA_ARGS__) +#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__) + +// Helper Macro untuk pengecekan error dan pembersihan cepat +#define CHECK_EXCEPTION(env) \ + if (env->ExceptionCheck()) { \ + env->ExceptionDescribe(); \ + env->ExceptionClear(); \ + return; \ + } + +// Helper Function: dpToPx (Versi Bersih) +int dpToPx(JNIEnv *env, jobject context, int dp) { + jclass contextClass = env->GetObjectClass(context); + jmethodID getResourcesId = env->GetMethodID(contextClass, "getResources", "()Landroid/content/res/Resources;"); + jobject resources = env->CallObjectMethod(context, getResourcesId); + + jclass resourcesClass = env->GetObjectClass(resources); + jmethodID getDisplayMetricsId = env->GetMethodID(resourcesClass, "getDisplayMetrics", "()Landroid/util/DisplayMetrics;"); + jobject displayMetrics = env->CallObjectMethod(resources, getDisplayMetricsId); + + jclass typedValueClass = env->FindClass("android/util/TypedValue"); + jmethodID applyDimensionId = env->GetStaticMethodID(typedValueClass, "applyDimension", "(IFLandroid/util/DisplayMetrics;)F"); + + float pixels = env->CallStaticFloatMethod(typedValueClass, applyDimensionId, 1, (float)dp, displayMetrics); + + // --- CLEANUP --- + env->DeleteLocalRef(contextClass); + env->DeleteLocalRef(resources); + env->DeleteLocalRef(resourcesClass); + env->DeleteLocalRef(displayMetrics); + env->DeleteLocalRef(typedValueClass); + + return (int)pixels; +} + +// ============================================================================ +// MARKER MACRO DEFINITIONS FOR MULTI-ARCH +// ============================================================================ + +#if defined(__aarch64__) +// --- ARM64 (AArch64) --- + // Instruksi: 'b' (Branch relative) + #define START_MARKER_ASM \ + asm volatile ( \ + "b 1f \n" \ + ".byte 0xDB, 0x6A, 0x27, 0x4F \n" \ + "1: \n" \ + ::: "memory" \ + ) + + #define END_MARKER_ASM \ + asm volatile ( \ + "b 2f \n" \ + ".byte 0x4C, 0x91, 0x3B, 0xE2 \n" \ + "2: \n" \ + ::: "memory" \ + ) + +#elif defined(__arm__) +// --- ARM32 (armeabi-v7a) --- + // Instruksi: 'b' (Branch) sama, tapi layout instruksi 4-byte (ARM) atau 2-byte (Thumb) + // Aman menggunakan 'b' karena assembler menanganinya. + #define START_MARKER_ASM \ + asm volatile ( \ + "b 1f \n" \ + ".byte 0xDB, 0x6A, 0x27, 0x4F \n" \ + "1: \n" \ + ::: "memory" \ + ) + + #define END_MARKER_ASM \ + asm volatile ( \ + "b 2f \n" \ + ".byte 0x4C, 0x91, 0x3B, 0xE2 \n" \ + "2: \n" \ + ::: "memory" \ + ) + +#elif defined(__x86_64__) +// --- x86_64 (Intel 64-bit) --- + // Instruksi: 'jmp' (Short jump 2-byte biasanya cukup untuk melompati 4 byte) + #define START_MARKER_ASM \ + asm volatile ( \ + "jmp 1f \n" \ + ".byte 0xDB, 0x6A, 0x27, 0x4F \n" \ + "1: \n" \ + ::: "memory" \ + ) + + #define END_MARKER_ASM \ + asm volatile ( \ + "jmp 2f \n" \ + ".byte 0x4C, 0x91, 0x3B, 0xE2 \n" \ + "2: \n" \ + ::: "memory" \ + ) + +#elif defined(__i386__) +// --- x86 (Intel 32-bit) --- +// Instruksi: 'jmp' +#define START_MARKER_ASM \ + asm volatile ( \ + "jmp 1f \n" \ + ".byte 0xDB, 0x6A, 0x27, 0x4F \n" \ + "1: \n" \ + ::: "memory" \ + ) + +#define END_MARKER_ASM \ + asm volatile ( \ + "jmp 2f \n" \ + ".byte 0x4C, 0x91, 0x3B, 0xE2 \n" \ + "2: \n" \ + ::: "memory" \ + ) + +#else +// --- Unknown Architecture --- + // Fallback: Jangan pakai branch, berisiko crash SIGILL. + // Opsi aman: compile error atau empty dummy. + // Kita pilih error agar developer sadar. + #error "Unsupported architecture for integrity markers!" +#endif + +// --- FUNGSI UTAMA --- +void showCustomToastNative_ibr_icall_split_sub_fla_bcf_(JNIEnv *env, jobject context, const char *messageStr) { + START_MARKER_ASM; + + // Pastikan frame JNI stack baru dibuat. + // Ini membantu membersihkan referensi lokal otomatis jika fungsi return. + if (env->PushLocalFrame(128) < 0) { + return; // Out of memory + } + + // --- 0. PREPARE CLASSES & METHODS (Cache ID untuk performa, tapi di sini kita load inline) --- + // Load semua class yang diperlukan di awal untuk memudahkan cleanup jika error + jclass colorClass = env->FindClass("android/graphics/Color"); + jclass relativeLayoutClass = env->FindClass("android/widget/RelativeLayout"); + jclass linearLayoutClass = env->FindClass("android/widget/LinearLayout"); + jclass viewClass = env->FindClass("android/view/View"); + jclass groupLpClass = env->FindClass("android/view/ViewGroup$LayoutParams"); + jclass gradientDrawableClass = env->FindClass("android/graphics/drawable/GradientDrawable"); + jclass relativeLpClass = env->FindClass("android/widget/RelativeLayout$LayoutParams"); + jclass textViewClass = env->FindClass("android/widget/TextView"); + jclass frameLayoutClass = env->FindClass("android/widget/FrameLayout"); + jclass marginLpClass = env->FindClass("android/view/ViewGroup$MarginLayoutParams"); + jclass imageViewClass = env->FindClass("android/widget/ImageView"); + jclass frameLpClass = env->FindClass("android/widget/FrameLayout$LayoutParams"); + jclass toastClass = env->FindClass("android/widget/Toast"); + jclass viewGroupClass = env->FindClass("android/view/ViewGroup"); + + // --- CONSTANTS --- + jmethodID parseColorId = env->GetStaticMethodID(colorClass, "parseColor", "(Ljava/lang/String;)I"); + jstring colorString = env->NewStringUTF("#FF4747"); + int toastColor = env->CallStaticIntMethod(colorClass, parseColorId, colorString); + env->DeleteLocalRef(colorString); // Hapus string segera setelah dipakai + + int gravityCenter = 17; + int iconSize = dpToPx(env, context, 40); + int iconPadding = dpToPx(env, context, 8); + int gapBetweenIconAndText = dpToPx(env, context, 8); + int iconBgSize = iconSize + (iconPadding * 2); + + // --- 1. ROOT LAYOUT --- + jmethodID relativeLayoutCtor = env->GetMethodID(relativeLayoutClass, "", "(Landroid/content/Context;)V"); + jobject rootLayout = env->NewObject(relativeLayoutClass, relativeLayoutCtor, context); + + jmethodID groupLpCtor = env->GetMethodID(groupLpClass, "", "(II)V"); + jobject rootParams = env->NewObject(groupLpClass, groupLpCtor, -2, -2); // WRAP_CONTENT + + jmethodID setLayoutParamsGroup = env->GetMethodID(viewClass, "setLayoutParams", "(Landroid/view/ViewGroup$LayoutParams;)V"); + env->CallVoidMethod(rootLayout, setLayoutParamsGroup, rootParams); + env->DeleteLocalRef(rootParams); // Hapus params segera + + jmethodID setClipChildren = env->GetMethodID(viewGroupClass, "setClipChildren", "(Z)V"); + jmethodID setClipToPadding = env->GetMethodID(viewGroupClass, "setClipToPadding", "(Z)V"); + env->CallVoidMethod(rootLayout, setClipChildren, JNI_FALSE); + env->CallVoidMethod(rootLayout, setClipToPadding, JNI_FALSE); + + jmethodID setPaddingId = env->GetMethodID(viewClass, "setPadding", "(IIII)V"); + env->CallVoidMethod(rootLayout, setPaddingId, 0, iconBgSize / 2, 0, 0); + + // --- 2. CONTAINER TEXT --- + jmethodID linearLayoutCtor = env->GetMethodID(linearLayoutClass, "", "(Landroid/content/Context;)V"); + jobject contentContainer = env->NewObject(linearLayoutClass, linearLayoutCtor, context); + + jmethodID generateViewId = env->GetStaticMethodID(viewClass, "generateViewId", "()I"); + int containerId = env->CallStaticIntMethod(viewClass, generateViewId); + jmethodID setId = env->GetMethodID(viewClass, "setId", "(I)V"); + env->CallVoidMethod(contentContainer, setId, containerId); + + jmethodID setOrientation = env->GetMethodID(linearLayoutClass, "setOrientation", "(I)V"); + jmethodID setGravityLinear = env->GetMethodID(linearLayoutClass, "setGravity", "(I)V"); + env->CallVoidMethod(contentContainer, setOrientation, 1); + env->CallVoidMethod(contentContainer, setGravityLinear, gravityCenter); + + int dynamicPaddingTop = (iconBgSize / 2) + gapBetweenIconAndText; + int paddingBottom = dpToPx(env, context, 16); + int paddingSide = dpToPx(env, context, 32); + env->CallVoidMethod(contentContainer, setPaddingId, paddingSide, dynamicPaddingTop, paddingSide, paddingBottom); + + // Background Body + jmethodID gradientCtor = env->GetMethodID(gradientDrawableClass, "", "()V"); + jobject bodyShape = env->NewObject(gradientDrawableClass, gradientCtor); + + jmethodID setShape = env->GetMethodID(gradientDrawableClass, "setShape", "(I)V"); + jmethodID setColorDrawable = env->GetMethodID(gradientDrawableClass, "setColor", "(I)V"); + jmethodID setCornerRadius = env->GetMethodID(gradientDrawableClass, "setCornerRadius", "(F)V"); + + env->CallVoidMethod(bodyShape, setShape, 0); + env->CallVoidMethod(bodyShape, setColorDrawable, toastColor); + env->CallVoidMethod(bodyShape, setCornerRadius, (float)dpToPx(env, context, 24)); + + jmethodID setBackground = env->GetMethodID(viewClass, "setBackground", "(Landroid/graphics/drawable/Drawable;)V"); + env->CallVoidMethod(contentContainer, setBackground, bodyShape); + env->DeleteLocalRef(bodyShape); // Hapus shape + + // Container Params + jmethodID relativeLpCtor = env->GetMethodID(relativeLpClass, "", "(II)V"); + jobject containerParams = env->NewObject(relativeLpClass, relativeLpCtor, -2, -2); + jmethodID addRule = env->GetMethodID(relativeLpClass, "addRule", "(I)V"); + env->CallVoidMethod(containerParams, addRule, 14); + env->CallVoidMethod(contentContainer, setLayoutParamsGroup, containerParams); + env->DeleteLocalRef(containerParams); + + // --- 3. TEXTVIEW --- + jmethodID textViewCtor = env->GetMethodID(textViewClass, "", "(Landroid/content/Context;)V"); + jobject textView = env->NewObject(textViewClass, textViewCtor, context); + + jmethodID setText = env->GetMethodID(textViewClass, "setText", "(Ljava/lang/CharSequence;)V"); + jmethodID setTextColor = env->GetMethodID(textViewClass, "setTextColor", "(I)V"); + jmethodID setTypeface = env->GetMethodID(textViewClass, "setTypeface", "(Landroid/graphics/Typeface;I)V"); + jmethodID setGravityText = env->GetMethodID(textViewClass, "setGravity", "(I)V"); + jmethodID setTextSize = env->GetMethodID(textViewClass, "setTextSize", "(IF)V"); + + jstring msgJString = env->NewStringUTF(messageStr); + env->CallVoidMethod(textView, setText, msgJString); + env->DeleteLocalRef(msgJString); // Hapus string pesan segera + + env->CallVoidMethod(textView, setTextColor, -1); // 0xFFFFFFFF or use (jint)0xFFFFFFFF + env->CallVoidMethod(textView, setTypeface, (jobject)nullptr, 1); + env->CallVoidMethod(textView, setGravityText, 17); + env->CallVoidMethod(textView, setTextSize, 2, 14.0f); + + jmethodID addView = env->GetMethodID(viewGroupClass, "addView", "(Landroid/view/View;)V"); + env->CallVoidMethod(contentContainer, addView, textView); + env->DeleteLocalRef(textView); // TextView sudah dipegang container, hapus ref lokal + + // Add Container to Root + env->CallVoidMethod(rootLayout, addView, contentContainer); + env->DeleteLocalRef(contentContainer); // Sudah dipegang root + + // --- 4. ICON WRAPPER --- + jmethodID frameLayoutCtor = env->GetMethodID(frameLayoutClass, "", "(Landroid/content/Context;)V"); + jobject iconWrapper = env->NewObject(frameLayoutClass, frameLayoutCtor, context); + + jobject iconBgShape = env->NewObject(gradientDrawableClass, gradientCtor); + env->CallVoidMethod(iconBgShape, setShape, 1); // OVAL + env->CallVoidMethod(iconBgShape, setColorDrawable, toastColor); + env->CallVoidMethod(iconWrapper, setBackground, iconBgShape); + env->DeleteLocalRef(iconBgShape); + + jobject wrapperParams = env->NewObject(relativeLpClass, relativeLpCtor, iconBgSize, iconBgSize); + env->CallVoidMethod(wrapperParams, addRule, 14); + jmethodID addRuleAnchor = env->GetMethodID(relativeLpClass, "addRule", "(II)V"); + env->CallVoidMethod(wrapperParams, addRuleAnchor, 6, containerId); + + jfieldID topMarginField = env->GetFieldID(marginLpClass, "topMargin", "I"); + env->SetIntField(wrapperParams, topMarginField, -(iconBgSize / 2)); + + env->CallVoidMethod(iconWrapper, setLayoutParamsGroup, wrapperParams); + env->DeleteLocalRef(wrapperParams); + + // --- 5. IMAGEVIEW --- + jmethodID imageViewCtor = env->GetMethodID(imageViewClass, "", "(Landroid/content/Context;)V"); + jobject iconView = env->NewObject(imageViewClass, imageViewCtor, context); + + jmethodID frameLpCtor = env->GetMethodID(frameLpClass, "", "(II)V"); + jobject iconParams = env->NewObject(frameLpClass, frameLpCtor, iconSize, iconSize); + jfieldID gravityField = env->GetFieldID(frameLpClass, "gravity", "I"); + env->SetIntField(iconParams, gravityField, gravityCenter); + env->CallVoidMethod(iconView, setLayoutParamsGroup, iconParams); + env->DeleteLocalRef(iconParams); + + // Get App Icon + jclass contextClass = env->GetObjectClass(context); + jmethodID getPackageManager = env->GetMethodID(contextClass, "getPackageManager", "()Landroid/content/pm/PackageManager;"); + jobject pm = env->CallObjectMethod(context, getPackageManager); + jmethodID getPackageName = env->GetMethodID(contextClass, "getPackageName", "()Ljava/lang/String;"); + auto pkgName = (jstring)env->CallObjectMethod(context, getPackageName); + + jclass pmClass = env->GetObjectClass(pm); + jmethodID getAppIcon = env->GetMethodID(pmClass, "getApplicationIcon", "(Ljava/lang/String;)Landroid/graphics/drawable/Drawable;"); + + jobject appIconDrawable = nullptr; + // Try catch block aman untuk exception + if (!env->ExceptionCheck()) { + appIconDrawable = env->CallObjectMethod(pm, getAppIcon, pkgName); + if (env->ExceptionCheck()) { + env->ExceptionClear(); // Hapus exception jika icon tidak ketemu + appIconDrawable = nullptr; + } + } + + if (appIconDrawable != nullptr) { + jmethodID setImageDrawable = env->GetMethodID(imageViewClass, "setImageDrawable", "(Landroid/graphics/drawable/Drawable;)V"); + env->CallVoidMethod(iconView, setImageDrawable, appIconDrawable); + env->DeleteLocalRef(appIconDrawable); + } + // Cleanup PM objects + env->DeleteLocalRef(contextClass); + env->DeleteLocalRef(pm); + env->DeleteLocalRef(pkgName); + env->DeleteLocalRef(pmClass); + + env->CallVoidMethod(iconWrapper, addView, iconView); + env->DeleteLocalRef(iconView); + + env->CallVoidMethod(rootLayout, addView, iconWrapper); + env->DeleteLocalRef(iconWrapper); + + // --- 6. SHOW TOAST --- + jmethodID makeText = env->GetStaticMethodID(toastClass, "makeText", "(Landroid/content/Context;Ljava/lang/CharSequence;I)Landroid/widget/Toast;"); + jstring emptyStr = env->NewStringUTF(""); + jobject dummyToast = env->CallStaticObjectMethod(toastClass, makeText, context, emptyStr, 0); + env->DeleteLocalRef(emptyStr); + + jmethodID getGravity = env->GetMethodID(toastClass, "getGravity", "()I"); + jmethodID getXOffset = env->GetMethodID(toastClass, "getXOffset", "()I"); + jmethodID getYOffset = env->GetMethodID(toastClass, "getYOffset", "()I"); + + int dGravity = env->CallIntMethod(dummyToast, getGravity); + int dX = env->CallIntMethod(dummyToast, getXOffset); + int dY = env->CallIntMethod(dummyToast, getYOffset); + env->DeleteLocalRef(dummyToast); // Hapus dummy + + jmethodID toastCtor = env->GetMethodID(toastClass, "", "(Landroid/content/Context;)V"); + jobject toast = env->NewObject(toastClass, toastCtor, context); + + jmethodID setGravityToast = env->GetMethodID(toastClass, "setGravity", "(III)V"); + jmethodID setDuration = env->GetMethodID(toastClass, "setDuration", "(I)V"); + jmethodID setView = env->GetMethodID(toastClass, "setView", "(Landroid/view/View;)V"); + jmethodID show = env->GetMethodID(toastClass, "show", "()V"); + + env->CallVoidMethod(toast, setGravityToast, dGravity, dX, dY); + env->CallVoidMethod(toast, setDuration, 1); + env->CallVoidMethod(toast, setView, rootLayout); + env->CallVoidMethod(toast, show); + + // Root layout sekarang dipegang Toast, tapi kita masih pegang referensi lokalnya. + // PushLocalFrame akan otomatis menghapus 'toast', 'rootLayout', dan semua 'jclass' + // ketika kita memanggil PopLocalFrame. + + // --- CLEANUP OTOMATIS --- + // PopLocalFrame menghapus SEMUA local reference yang dibuat SEJAK PushLocalFrame. + // Ini adalah cara paling aman dan ringkas untuk mencegah leak. + env->PopLocalFrame(nullptr); + + END_MARKER_ASM; +} + +// Konteks untuk callback +typedef struct { + void *target; + uintptr_t base; + size_t size; + int found; +} find_ctx_t; + +static int phdr_cb(struct dl_phdr_info *info, size_t size, void *data) { + (void)size; + auto *ctx = (find_ctx_t *)data; + auto t = (uintptr_t)ctx->target; + + for (int i = 0; i < info->dlpi_phnum; ++i) { + const ElfW(Phdr) *ph = &info->dlpi_phdr[i]; + + if (ph->p_type != PT_LOAD) + continue; + if ((ph->p_flags & PF_X) == 0) + continue; // hanya segmen executable (text)[web:20][web:160][web:161] + + uintptr_t seg_start = (uintptr_t)info->dlpi_addr + ph->p_vaddr; + uintptr_t seg_end = seg_start + ph->p_memsz; + + if (t >= seg_start && t < seg_end) { + ctx->base = seg_start; + ctx->size = ph->p_memsz; + ctx->found = 1; + return 1; // stop iterasi + } + } + return 0; +} + +int get_code_segment_for_addr(void *func_addr, uintptr_t *out_base, size_t *out_size) { + if (!func_addr || !out_base || !out_size) + return -1; + + find_ctx_t ctx; + memset(&ctx, 0, sizeof(ctx)); + ctx.target = func_addr; + + dl_iterate_phdr(phdr_cb, &ctx); // standard di glibc & Bionic untuk enumerasi objek ELF runtime.[web:5][web:134] + + if (!ctx.found) + return -2; + + *out_base = ctx.base; + *out_size = ctx.size; + return 0; +} + +int elf64_get_code_segment_from_file(const char *path, + uint64_t *out_file_offset, + uint64_t *out_file_size) { + if (!path || !out_file_offset || !out_file_size) { + LOGE("elf64_get_code_segment_from_file: invalid args"); + return -1; + } + + int fd = open(path, O_RDONLY); + if (fd < 0) { + LOGE("elf64_get_code_segment_from_file: open('%s') failed, errno=%d", path, errno); + return -2; + } + + Elf64_Ehdr eh; + ssize_t n = read(fd, &eh, sizeof(eh)); + if (n != (ssize_t)sizeof(eh)) { + LOGE("elf64_get_code_segment_from_file: read ehdr failed, n=%zd", n); + close(fd); + return -3; + } + + // Validasi magic ELF dan kelas 64-bit + if (memcmp(eh.e_ident, ELFMAG, SELFMAG) != 0) { + LOGE("elf64_get_code_segment_from_file: not an ELF file"); + close(fd); + return -4; + } + + if (eh.e_ident[EI_CLASS] != ELFCLASS64) { + LOGE("elf64_get_code_segment_from_file: not ELF64, EI_CLASS=%d", eh.e_ident[EI_CLASS]); + close(fd); + return -5; // di sini nanti bisa kamu ganti tambah parser ELF32 kalau perlu + } + + LOGI("ELF '%s': phoff=0x%llx, phnum=%u, phentsize=%u", + path, + (unsigned long long)eh.e_phoff, + (unsigned)eh.e_phnum, + (unsigned)eh.e_phentsize); + + // Lompat ke program header table + if (lseek(fd, (off_t)eh.e_phoff, SEEK_SET) < 0) { + LOGE("elf64_get_code_segment_from_file: lseek to phoff failed"); + close(fd); + return -6; + } + + // Validasi ukuran & jumlah program header + if (eh.e_phnum == 0 || eh.e_phentsize != sizeof(Elf64_Phdr)) { + LOGE("elf64_get_code_segment_from_file: invalid phnum=%u phentsize=%u", + (unsigned)eh.e_phnum, (unsigned)eh.e_phentsize); + close(fd); + return -7; + } + + Elf64_Phdr ph; + int found = 0; + + for (uint16_t i = 0; i < eh.e_phnum; ++i) { + n = read(fd, &ph, sizeof(ph)); + if (n != (ssize_t)sizeof(ph)) { + LOGE("elf64_get_code_segment_from_file: read phdr[%u] failed, n=%zd", (unsigned)i, n); + close(fd); + return -8; + } + + LOGI("PH[%u]: type=0x%x flags=0x%x off=0x%llx vaddr=0x%llx filesz=0x%llx memsz=0x%llx", + (unsigned)i, + ph.p_type, + ph.p_flags, + (unsigned long long)ph.p_offset, + (unsigned long long)ph.p_vaddr, + (unsigned long long)ph.p_filesz, + (unsigned long long)ph.p_memsz); + + if (ph.p_type != PT_LOAD) + continue; + if ((ph.p_flags & PF_X) == 0) + continue; // hanya segmen executable (kode)[web:20][web:162] + + // Ambil segmen pertama yang executable sebagai segmen kode utama + *out_file_offset = ph.p_offset; + *out_file_size = ph.p_filesz; + found = 1; + LOGI("Code segment found: off=0x%llx size=0x%llx", + (unsigned long long)*out_file_offset, + (unsigned long long)*out_file_size); + break; + } + + close(fd); + if (!found) { + LOGE("elf64_get_code_segment_from_file: no PT_LOAD | PF_X segment found"); + return -9; + } + return 0; +} + +// Hash contoh (ganti SHA-256 kalau mau lebih kuat) +static uint32_t simple_hash(const uint8_t *data, size_t len) { + uint32_t h = 0x12345678u; + for (size_t i = 0; i < len; ++i) { + h ^= data[i]; + h = (h << 5) | (h >> (32 - 5)); + } + return h; +} + +static uint8_t *find_pattern(uint8_t *hay, size_t hay_len, + const uint8_t *needle, size_t needle_len) { + if (!hay || !needle || needle_len == 0 || hay_len < needle_len) + return NULL; + + uint8_t first = needle[0]; + uint8_t *p = hay; + uint8_t *end = hay + hay_len - needle_len + 1; + + while (p < end) { + if (*p == first) { + if (memcmp(p, needle, needle_len) == 0) + return p; + } + ++p; + } + return NULL; +} + +#define START_MARK_LEN 4 +#define END_MARK_LEN 4 + +// --- Helper untuk membangun marker secara runtime (Anti-Self-Detection) --- +static void get_start_marker(uint8_t *buf) { + buf[0] = 0xDB; + buf[1] = 0x6A; + buf[2] = 0x27; + buf[3] = 0x4F; +} + +static void get_end_marker(uint8_t *buf) { + buf[0] = 0x4C; + buf[1] = 0x91; + buf[2] = 0x3B; + buf[3] = 0xE2; +} + +/* ---------- Hash dari MEMORY (dl_iterate_phdr) ---------- */ + +int integrity_hash_memory(void *marker_func, uint32_t *out_hash) { + if (!marker_func || !out_hash) + return -1; + + uintptr_t base; + size_t size; + + int r = get_code_segment_for_addr(marker_func, &base, &size); + if (r != 0) + return -2; + + auto *code = (uint8_t *)base; + + // 1. Bangun marker di stack (runtime) + uint8_t scan_start[START_MARK_LEN]; + uint8_t scan_end[END_MARK_LEN]; + get_start_marker(scan_start); + get_end_marker(scan_end); + + // 2. Cari START marker + uint8_t *start_mark = find_pattern( + code, size, scan_start, START_MARK_LEN); + if (!start_mark) + return -3; + + // 3. Cari END marker setelah START + uint8_t *search_from = start_mark + START_MARK_LEN; + size_t remain = (code + size) - search_from; + + uint8_t *end_mark = find_pattern( + search_from, remain, scan_end, END_MARK_LEN); + if (!end_mark) + return -4; + + uint8_t *range_begin = start_mark + START_MARK_LEN; + uint8_t *range_end = end_mark; + + LOGI("MEMORY: base=%p start=%p end=%p range_len=%zu", + (void*)base, start_mark, end_mark, (size_t)(range_end - range_begin)); + + if (range_end <= range_begin) + return -5; + + auto range_len = (size_t)(range_end - range_begin); + *out_hash = simple_hash(range_begin, range_len); + return 0; +} + +/* ---------- Hash dari FILE ELF di DISK ---------- */ + +int integrity_hash_file(const char *elf_path, uint32_t *out_hash) { + if (!elf_path || !out_hash) + return -1; + + uint64_t off, size; + int r = elf64_get_code_segment_from_file(elf_path, &off, &size); + LOGI("FILE: elf64_get_code_segment_from_file('%s') = %d, off=0x%llx size=0x%llx", + elf_path, r, (unsigned long long)off, (unsigned long long)size); + if (r != 0) + return -2; + + int fd = open(elf_path, O_RDONLY); + if (fd < 0) + return -3; + + auto *buf = (uint8_t *)malloc(size); + if (!buf) { + close(fd); + return -4; + } + + if (lseek(fd, (off_t)off, SEEK_SET) < 0) { + free(buf); + close(fd); + return -5; + } + + ssize_t n = read(fd, buf, (size_t)size); + close(fd); + if (n != (ssize_t)size) { + free(buf); + return -6; + } + + // 1. Bangun marker di stack (runtime) + uint8_t scan_start[START_MARK_LEN]; + uint8_t scan_end[END_MARK_LEN]; + get_start_marker(scan_start); + get_end_marker(scan_end); + + // 2. Cari START marker + uint8_t *start_mark = find_pattern(buf, (size_t)size, + scan_start, START_MARK_LEN); + if (!start_mark) { + free(buf); + return -7; + } + + // 3. Cari END marker setelah START + uint8_t *search_from = start_mark + START_MARK_LEN; + size_t remain = (buf + size) - search_from; + + uint8_t *end_mark = find_pattern(search_from, remain, + scan_end, END_MARK_LEN); + if (!end_mark) { + free(buf); + return -8; + } + + uint8_t *range_begin = start_mark + START_MARK_LEN; + uint8_t *range_end = end_mark; + + LOGI("FILE: buf_base=%p start_offset=%zu end_offset=%zu range_len=%zu", + buf, (size_t)(start_mark - buf), (size_t)(end_mark - buf), (size_t)(range_end - range_begin)); + + if (range_end <= range_begin) { + free(buf); + return -9; + } + + size_t range_len = (size_t)(range_end - range_begin); + *out_hash = simple_hash(range_begin, range_len); + free(buf); + return 0; +} + +/* ---------- Bandingkan hash memory vs file ---------- */ + +int integrity_compare_self(const char *elf_path, void *marker_func) { + uint32_t h_mem, h_file; + int r1 = integrity_hash_memory(marker_func, &h_mem); + LOGI("hash memory = %d, hash = %d", r1, h_mem); + if (r1 != 0) + return -10 + r1; + + + + int r2 = integrity_hash_file(elf_path, &h_file); + LOGI("hash file = %d, hash = %d", r2, h_file); + if (r2 != 0) + return -20 + r2; + + return (h_mem == h_file) ? 0 : 1; +} + +typedef struct { + const char *lib_name; + char *out_path; + size_t out_size; + int found; +} so_find_ctx_t; + +static int has_suffix(const char *str, const char *suffix) { + size_t len_str = strlen(str); + size_t len_suf = strlen(suffix); + if (len_suf > len_str) return 0; + return memcmp(str + (len_str - len_suf), suffix, len_suf) == 0; +} + +static int so_iter_cb(struct dl_phdr_info *info, size_t size, void *data) { + (void)size; + so_find_ctx_t *ctx = (so_find_ctx_t *)data; + + const char *name = info->dlpi_name; + if (!name || name[0] == '\0') { + // entry pertama sering kosong untuk main exe[web:145] + return 0; + } + + if (has_suffix(name, ctx->lib_name)) { + size_t len = strlen(name); + if (len + 1 <= ctx->out_size) { + memcpy(ctx->out_path, name, len + 1); + ctx->found = 1; + return 1; // stop iterasi + } + } + return 0; +} + +int find_so_full_path(const char *lib_name, char *out_path, size_t out_size) { + if (!lib_name || !out_path || out_size == 0) + return -1; + + so_find_ctx_t ctx; + ctx.lib_name = lib_name; + ctx.out_path = out_path; + ctx.out_size = out_size; + ctx.found = 0; + + dl_iterate_phdr(so_iter_cb, &ctx); // enumerasi semua shared object yang sudah di-load.[web:5][web:168] + + return ctx.found ? 0 : -2; +} + +static bool get_self_lib_path(JNIEnv *env, jobject context, + const char *lib_name, + char *out_path, size_t out_size) { + if (!env || !context || !lib_name || !out_path || out_size == 0) + return false; + + jclass ctxCls = env->GetObjectClass(context); + jmethodID midGetAppInfo = env->GetMethodID( + ctxCls, "getApplicationInfo", "()Landroid/content/pm/ApplicationInfo;"); + jobject appInfo = env->CallObjectMethod(context, midGetAppInfo); + + jclass appInfoCls = env->GetObjectClass(appInfo); + jfieldID fidNativeDir = env->GetFieldID( + appInfoCls, "nativeLibraryDir", "Ljava/lang/String;"); + jstring jNativeDir = (jstring) env->GetObjectField(appInfo, fidNativeDir); + + const char *nativeDir = env->GetStringUTFChars(jNativeDir, nullptr); + if (!nativeDir) { + env->DeleteLocalRef(jNativeDir); + env->DeleteLocalRef(appInfoCls); + env->DeleteLocalRef(appInfo); + env->DeleteLocalRef(ctxCls); + return false; + } + + // Build path: / + // mis: "/data/app/.../lib/arm64/libcustomtoastview.so" + int written = snprintf(out_path, out_size, "%s/%s", nativeDir, lib_name); + + env->ReleaseStringUTFChars(jNativeDir, nativeDir); + env->DeleteLocalRef(jNativeDir); + env->DeleteLocalRef(appInfoCls); + env->DeleteLocalRef(appInfo); + env->DeleteLocalRef(ctxCls); + + return written > 0 && (size_t)written < out_size; +} + + +extern "C" +JNIEXPORT void JNICALL +Java_com_example_customtoastview_MainActivity_showToast(JNIEnv *env, jobject thiz, jstring str) { + // 1. Konversi jstring (Java String) ke const char* (C String) + const char *nativeString = env->GetStringUTFChars(str, nullptr); + + char path[512]; + + // Misal library yang ingin dicek: "libhello.so" + if (!get_self_lib_path(env, thiz, "libcustomtoastview.so", path, sizeof(path))) { + LOGE("Cannot resolve libcustomtoastview.so path from nativeLibraryDir"); + return; + } + + LOGI("Resolved libhello.so path: %s", path); + + int res = integrity_compare_self(path, (void *) showCustomToastNative_ibr_icall_split_sub_fla_bcf_); + if (res == 0) { + LOGI("Integrity OK for libhello.so"); + } else if (res == 1) { + LOGE("Integrity MISMATCH for libhello.so (possible hook/patch)"); + } else { + LOGE("Integrity check error: %d", res); + } + + // 2. Panggil fungsi implementasi kita + // 'thiz' di sini adalah MainActivity, yang juga merupakan Context. + // Jadi bisa langsung dipassing sebagai argumen context. + showCustomToastNative_ibr_icall_split_sub_fla_bcf_(env, thiz, nativeString); + + // 3. Lepaskan string C (PENTING! Jangan lupa ini atau memory leak) + env->ReleaseStringUTFChars(str, nativeString); +} \ No newline at end of file diff --git a/app/src/main/java/com/example/customtoastview/MainActivity.java b/app/src/main/java/com/example/customtoastview/MainActivity.java new file mode 100644 index 0000000..a328808 --- /dev/null +++ b/app/src/main/java/com/example/customtoastview/MainActivity.java @@ -0,0 +1,221 @@ +package com.example.customtoastview; + +import android.accessibilityservice.AccessibilityServiceInfo; +import android.content.Context; +import android.content.pm.PackageManager; +import android.graphics.Color; +import android.graphics.Typeface; +import android.graphics.drawable.Drawable; +import android.graphics.drawable.GradientDrawable; +import android.os.Bundle; +import android.provider.Settings; +import android.util.Log; +import android.util.TypedValue; +import android.view.Gravity; +import android.view.View; +import android.view.ViewGroup; +import android.view.accessibility.AccessibilityManager; +import android.widget.Button; +import android.widget.FrameLayout; +import android.widget.ImageView; +import android.widget.LinearLayout; +import android.widget.RelativeLayout; +import android.widget.TextView; +import android.widget.Toast; + +import androidx.activity.EdgeToEdge; +import androidx.appcompat.app.AppCompatActivity; +import androidx.core.graphics.Insets; +import androidx.core.view.ViewCompat; +import androidx.core.view.WindowInsetsCompat; + +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +public class MainActivity extends AppCompatActivity { + + static { + System.loadLibrary("customtoastview"); + } + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + EdgeToEdge.enable(this); + setContentView(R.layout.activity_main); + ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main), (v, insets) -> { + Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars()); + v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom); + return insets; + }); + + Button button = findViewById(R.id.btn_show); + button.setOnClickListener(v -> { + showToast("Desain Menyatu!\nBackground icon & body sama."); + }); + + Context context = getApplicationContext(); + + if (isHmaDetected(context)){ + Toast.makeText(context , "HMA Detected!", Toast.LENGTH_LONG).show(); + } + } + + /*public void showCustomToast(Context context, String str) { + + // --- SETTING UTAMA --- + int iconSize = dpToPx(40); // Ukuran gambar icon asli + int iconPadding = dpToPx(8); // Jarak antara gambar icon dengan pinggir lingkaran backgroundnya + int gapBetweenIconAndText = dpToPx(8); + int toastColor = Color.parseColor("#FF4747"); // Warna TEMA (Pink) + int gravityCenter = 17; // Gravity.CENTER + // --------------------- + + // Hitung ukuran TOTAL lingkaran pembungkus icon + // (Ukuran Icon + Padding Kiri + Padding Kanan) + int iconBgSize = iconSize + (iconPadding * 2); + + // 1. ROOT LAYOUT + RelativeLayout rootLayout = new RelativeLayout(context); + rootLayout.setLayoutParams(new ViewGroup.LayoutParams( + ViewGroup.LayoutParams.WRAP_CONTENT, + ViewGroup.LayoutParams.WRAP_CONTENT)); + rootLayout.setClipChildren(false); + rootLayout.setClipToPadding(false); + + // Padding atas root = Setengah dari ukuran lingkaran icon + rootLayout.setPadding(0, iconBgSize / 2, 0, 0); + + // 2. CONTAINER TEXT (BODY TOAST) + LinearLayout contentContainer = new LinearLayout(context); + contentContainer.setId(View.generateViewId()); + contentContainer.setOrientation(LinearLayout.VERTICAL); + contentContainer.setGravity(gravityCenter); + + // Padding atas dinamis: Setengah lingkaran + Jarak + int dynamicPaddingTop = (iconBgSize / 2) + gapBetweenIconAndText; + int paddingBottom = dpToPx(16); + int paddingSide = dpToPx(32); + contentContainer.setPadding(paddingSide, dynamicPaddingTop, paddingSide, paddingBottom); + + // Background Body (Rounded Rectangle) + GradientDrawable bodyShape = new GradientDrawable(); + bodyShape.setShape(GradientDrawable.RECTANGLE); + bodyShape.setColor(toastColor); // WARNA SAMA + bodyShape.setCornerRadius(dpToPx(24)); + contentContainer.setBackground(bodyShape); + + // Layout Params Body + RelativeLayout.LayoutParams containerParams = new RelativeLayout.LayoutParams( + RelativeLayout.LayoutParams.WRAP_CONTENT, + RelativeLayout.LayoutParams.WRAP_CONTENT); + containerParams.addRule(14); // RelativeLayout.CENTER_HORIZONTAL + contentContainer.setLayoutParams(containerParams); + + // 3. TEXTVIEW + TextView textView = new TextView(context); + textView.setText(str); + textView.setTextColor(Color.WHITE); + textView.setTypeface(null, Typeface.BOLD); + textView.setGravity(17); // Gravity.CENTER + textView.setTextSize(2 *//*TypedValue.COMPLEX_UNIT_SP*//*, 14); + contentContainer.addView(textView); + + rootLayout.addView(contentContainer); + + // 4. ICON WRAPPER (LINGKARAN BACKGROUND ICON) + FrameLayout iconWrapper = new FrameLayout(context); + + // Background Lingkaran (OVAL) + GradientDrawable iconBgShape = new GradientDrawable(); + iconBgShape.setShape(GradientDrawable.OVAL); + iconBgShape.setColor(toastColor); // WARNA SAMA (Kunci agar menyatu) + // Opsional: Tambah stroke putih biar ada batas tegas (border) +// iconBgShape.setStroke(dpToPx(4), Color.WHITE); + + iconWrapper.setBackground(iconBgShape); + + // Params Wrapper: Ukuran total lingkaran + RelativeLayout.LayoutParams wrapperParams = new RelativeLayout.LayoutParams(iconBgSize, iconBgSize); + wrapperParams.addRule(14); // RelativeLayout.CENTER_HORIZONTAL + wrapperParams.addRule(6 *//*RelativeLayout.ALIGN_TOP*//*, contentContainer.getId()); + // Tarik ke atas setengah ukuran lingkaran + wrapperParams.topMargin = -(iconBgSize / 2); + iconWrapper.setLayoutParams(wrapperParams); + + // 5. IMAGEVIEW (GAMBAR ICON DI DALAM LINGKARAN) + ImageView iconView = new ImageView(context); + FrameLayout.LayoutParams iconParams = new FrameLayout.LayoutParams(iconSize, iconSize); + iconParams.gravity = gravityCenter; // Taruh icon di tengah lingkaran Gravity.CENTER + iconView.setLayoutParams(iconParams); + + try { + Drawable appIcon = getPackageManager().getApplicationIcon(getPackageName()); + iconView.setImageDrawable(appIcon); + } catch (PackageManager.NameNotFoundException ignored) { + + } + + iconWrapper.addView(iconView); + rootLayout.addView(iconWrapper); + + // 6. SHOW TOAST + Toast dummyToast = Toast.makeText(context, "", Toast.LENGTH_SHORT); + Toast toast = new Toast(context); + toast.setGravity(dummyToast.getGravity(), dummyToast.getXOffset(), dummyToast.getYOffset()); + toast.setDuration(Toast.LENGTH_LONG); + toast.setView(rootLayout); + toast.show(); + } + + private int dpToPx(int dp) { + return (int) TypedValue.applyDimension( + TypedValue.COMPLEX_UNIT_DIP, dp, getResources().getDisplayMetrics()); + }*/ + + public boolean isHmaDetected(Context context) { + // 1. Ambil data dari API Manager (Yang kemungkinan di-hook/dibohongi) + AccessibilityManager am = (AccessibilityManager) context.getSystemService(Context.ACCESSIBILITY_SERVICE); + List apiList = am.getEnabledAccessibilityServiceList(AccessibilityServiceInfo.FEEDBACK_ALL_MASK); + + Set apiServiceNames = new HashSet<>(); + if (apiList != null) { + for (AccessibilityServiceInfo info : apiList) { + // Format ID biasanya: com.example.package/.MyAccessibilityService + apiServiceNames.add(info.getId()); + } + } + + // 2. Ambil data mentah dari Settings Secure (Seringkali "lupa" di-hook atau sulit diparsing oleh HMA) + String settingValue = Settings.Secure.getString( + context.getContentResolver(), + Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES + ); + + if (settingValue == null || settingValue.isEmpty()) { + return false; // Tidak ada accessibility service aktif sama sekali + } + + // Data setting berbentuk string panjang: "com.a/.ServiceA:com.b/.ServiceB" + String[] rawServices = settingValue.split(":"); + + // 3. Bandingkan! + for (String rawService : rawServices) { + // Bersihkan nama komponen (kadang ada karakter aneh di setting string) + String cleanName = rawService.trim(); + + if (!cleanName.isEmpty()) { + // JIKA ada di Settings TAPI tidak ada di API List -> DETECTED! + if (!apiServiceNames.contains(cleanName)) { + Log.e("SecurityCheck", "HMA Detected! Hidden Service found: " + cleanName); + return true; + } + } + } + + return false; + } + + private native void showToast(String str); +} \ 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/drawable/ic_launcher_foreground.xml b/app/src/main/res/drawable/ic_launcher_foreground.xml new file mode 100644 index 0000000..2b068d1 --- /dev/null +++ b/app/src/main/res/drawable/ic_launcher_foreground.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + \ No newline at end of file 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..7f29e9f --- /dev/null +++ b/app/src/main/res/layout/activity_main.xml @@ -0,0 +1,20 @@ + + + +