first commit

This commit is contained in:
Hamza-Ayed
2025-07-30 10:22:20 +03:00
parent b92fc5bc1a
commit 66ae6c0ddb
231 changed files with 46683 additions and 0 deletions

14
android/.gitignore vendored Normal file
View File

@@ -0,0 +1,14 @@
gradle-wrapper.jar
/.gradle
/captures/
/gradlew
/gradlew.bat
/local.properties
GeneratedPluginRegistrant.java
.cxx/
# Remember to never publicly share your keystore.
# See https://flutter.dev/to/reference-keystore
key.properties
**/*.keystore
**/*.jks

View File

@@ -0,0 +1,92 @@
import java.util.Properties
plugins {
id("com.android.application")
id("kotlin-android")
id("com.google.gms.google-services")
id("dev.flutter.flutter-gradle-plugin")
}
// تحميل local.properties
val localProperties = Properties()
val localPropertiesFile = rootProject.file("local.properties")
if (localPropertiesFile.exists()) {
localPropertiesFile.reader().use {
localProperties.load(it)
}
}
// تحميل key.properties
val keystoreProperties = Properties()
val keystorePropertiesFile = rootProject.file("key.properties")
if (keystorePropertiesFile.exists()) {
keystorePropertiesFile.inputStream().use {
keystoreProperties.load(it)
}
}
android {
namespace = "com.service_intaleq"
compileSdk = 36
ndkVersion = "27.0.12077973"
defaultConfig {
applicationId = "com.service_intaleq"
minSdk = 23
targetSdk = 36
versionCode = 1
versionName = "1.0"
multiDexEnabled = true
ndk {
abiFilters += listOf("armeabi-v7a", "arm64-v8a", "x86", "x86_64")
}
}
signingConfigs {
create("release") {
keyAlias = keystoreProperties["keyAlias"] as String?
keyPassword = keystoreProperties["keyPassword"] as String?
storeFile = keystoreProperties["storeFile"]?.let { file(it as String) }
storePassword = keystoreProperties["storePassword"] as String?
}
}
buildTypes {
getByName("release") {
signingConfig = signingConfigs.getByName("release")
isMinifyEnabled = true
isShrinkResources = true
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
}
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
}
kotlinOptions {
jvmTarget = "11"
}
externalNativeBuild {
cmake {
path = file("src/main/cpp/CMakeLists.txt")
version = "3.31.5"
}
}
}
flutter {
source = "../.."
}
dependencies {
implementation("com.scottyab:rootbeer-lib:0.1.0")
implementation("com.google.android.gms:play-services-safetynet:18.0.1")
coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:2.1.4")
}

View File

@@ -0,0 +1,202 @@
{
"project_info": {
"project_number": "1086900987150",
"project_id": "intaleq-d48a7",
"storage_bucket": "intaleq-d48a7.firebasestorage.app"
},
"client": [
{
"client_info": {
"mobilesdk_app_id": "1:1086900987150:android:b7231956aa6d3b3377a35f",
"android_client_info": {
"package_name": "com.Intaleq.intaleq"
}
},
"oauth_client": [
{
"client_id": "1086900987150-060srlmdjocdcav377rbur4ka14m90b7.apps.googleusercontent.com",
"client_type": 1,
"android_info": {
"package_name": "com.Intaleq.intaleq",
"certificate_hash": "765bbb7c5d30bc58a7ba44372db614d6bbe6e34d"
}
},
{
"client_id": "1086900987150-aomdf61hg1g6a76pak4k6lfkdgvfj3vn.apps.googleusercontent.com",
"client_type": 1,
"android_info": {
"package_name": "com.Intaleq.intaleq",
"certificate_hash": "3997f1e87f9fc7190d55c049c0de02c825085267"
}
},
{
"client_id": "1086900987150-44lu3vt9fpbfiif37e8iji7besrfuha9.apps.googleusercontent.com",
"client_type": 3
}
],
"api_key": [
{
"current_key": "AIzaSyCFsWBqvkXzk1Gb-bCGxwqTwJQKIeHjH64"
},
{
"current_key": "AIzaSyCwWsOw7WSMohXhBTTn0mY_Jyc90d5a0t4"
},
{
"current_key": "AIzaSyBTNkIyhQf4oCKdg2SthaLAOUSctS1WiMU"
}
],
"services": {
"appinvite_service": {
"other_platform_oauth_client": [
{
"client_id": "1086900987150-44lu3vt9fpbfiif37e8iji7besrfuha9.apps.googleusercontent.com",
"client_type": 3
},
{
"client_id": "1086900987150-9jv4oa8l3t23d54lrf27c1d22tbt9i6d.apps.googleusercontent.com",
"client_type": 2,
"ios_info": {
"bundle_id": "com.Intaleq.intaleq"
}
}
]
}
}
},
{
"client_info": {
"mobilesdk_app_id": "1:1086900987150:android:7f0b54792b737a3d77a35f",
"android_client_info": {
"package_name": "com.intaleq.intaleq_admin"
}
},
"oauth_client": [
{
"client_id": "1086900987150-44lu3vt9fpbfiif37e8iji7besrfuha9.apps.googleusercontent.com",
"client_type": 3
}
],
"api_key": [
{
"current_key": "AIzaSyCFsWBqvkXzk1Gb-bCGxwqTwJQKIeHjH64"
},
{
"current_key": "AIzaSyCwWsOw7WSMohXhBTTn0mY_Jyc90d5a0t4"
},
{
"current_key": "AIzaSyBTNkIyhQf4oCKdg2SthaLAOUSctS1WiMU"
}
],
"services": {
"appinvite_service": {
"other_platform_oauth_client": [
{
"client_id": "1086900987150-44lu3vt9fpbfiif37e8iji7besrfuha9.apps.googleusercontent.com",
"client_type": 3
},
{
"client_id": "1086900987150-9jv4oa8l3t23d54lrf27c1d22tbt9i6d.apps.googleusercontent.com",
"client_type": 2,
"ios_info": {
"bundle_id": "com.Intaleq.intaleq"
}
}
]
}
}
},
{
"client_info": {
"mobilesdk_app_id": "1:1086900987150:android:e3daebe53bf691de77a35f",
"android_client_info": {
"package_name": "com.intaleq_driver"
}
},
"oauth_client": [
{
"client_id": "1086900987150-to2jdiukfmr30qsfvov71ra4tp1koluk.apps.googleusercontent.com",
"client_type": 1,
"android_info": {
"package_name": "com.intaleq_driver",
"certificate_hash": "765bbb7c5d30bc58a7ba44372db614d6bbe6e34d"
}
},
{
"client_id": "1086900987150-44lu3vt9fpbfiif37e8iji7besrfuha9.apps.googleusercontent.com",
"client_type": 3
}
],
"api_key": [
{
"current_key": "AIzaSyCFsWBqvkXzk1Gb-bCGxwqTwJQKIeHjH64"
},
{
"current_key": "AIzaSyCwWsOw7WSMohXhBTTn0mY_Jyc90d5a0t4"
},
{
"current_key": "AIzaSyBTNkIyhQf4oCKdg2SthaLAOUSctS1WiMU"
}
],
"services": {
"appinvite_service": {
"other_platform_oauth_client": [
{
"client_id": "1086900987150-44lu3vt9fpbfiif37e8iji7besrfuha9.apps.googleusercontent.com",
"client_type": 3
},
{
"client_id": "1086900987150-9jv4oa8l3t23d54lrf27c1d22tbt9i6d.apps.googleusercontent.com",
"client_type": 2,
"ios_info": {
"bundle_id": "com.Intaleq.intaleq"
}
}
]
}
}
},
{
"client_info": {
"mobilesdk_app_id": "1:1086900987150:android:634c6a26836e668a77a35f",
"android_client_info": {
"package_name": "com.service_intaleq"
}
},
"oauth_client": [
{
"client_id": "1086900987150-44lu3vt9fpbfiif37e8iji7besrfuha9.apps.googleusercontent.com",
"client_type": 3
}
],
"api_key": [
{
"current_key": "AIzaSyCFsWBqvkXzk1Gb-bCGxwqTwJQKIeHjH64"
},
{
"current_key": "AIzaSyCwWsOw7WSMohXhBTTn0mY_Jyc90d5a0t4"
},
{
"current_key": "AIzaSyBTNkIyhQf4oCKdg2SthaLAOUSctS1WiMU"
}
],
"services": {
"appinvite_service": {
"other_platform_oauth_client": [
{
"client_id": "1086900987150-44lu3vt9fpbfiif37e8iji7besrfuha9.apps.googleusercontent.com",
"client_type": 3
},
{
"client_id": "1086900987150-9jv4oa8l3t23d54lrf27c1d22tbt9i6d.apps.googleusercontent.com",
"client_type": 2,
"ios_info": {
"bundle_id": "com.Intaleq.intaleq"
}
}
]
}
}
}
],
"configuration_version": "1"
}

View File

@@ -0,0 +1,7 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<!-- The INTERNET permission is required for development. Specifically,
the Flutter tool needs it to communicate with the running application
to allow setting breakpoints, to provide hot reload, etc.
-->
<uses-permission android:name="android.permission.INTERNET"/>
</manifest>

View File

@@ -0,0 +1,54 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"
android:maxSdkVersion="32" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
android:maxSdkVersion="28" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
<uses-permission android:name="android.permission.READ_CONTACTS" />
<uses-permission android:name="android.permission.WRITE_CONTACTS" />
<application
android:label="service"
android:name="${applicationName}"
android:icon="@mipmap/launcher_icon">
<activity
android:name=".MainActivity"
android:exported="true"
android:launchMode="singleTop"
android:taskAffinity=""
android:theme="@style/LaunchTheme"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
android:hardwareAccelerated="true"
android:windowSoftInputMode="adjustResize">
<!-- Specifies an Android theme to apply to this Activity as soon as
the Android process has started. This theme is visible to the user
while the Flutter UI initializes. After that, this theme continues
to determine the Window background behind the Flutter UI. -->
<meta-data
android:name="io.flutter.embedding.android.NormalTheme"
android:resource="@style/NormalTheme"
/>
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<!-- Don't delete the meta-data below.
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
<meta-data
android:name="flutterEmbedding"
android:value="2" />
</application>
<!-- Required to query activities that can process text, see:
https://developer.android.com/training/package-visibility and
https://developer.android.com/reference/android/content/Intent#ACTION_PROCESS_TEXT.
In particular, this is used by the Flutter engine in io.flutter.plugin.text.ProcessTextPlugin. -->
<queries>
<intent>
<action android:name="android.intent.action.PROCESS_TEXT" />
<data android:mimeType="text/plain" />
</intent>
</queries>
</manifest>

View File

@@ -0,0 +1,11 @@
cmake_minimum_required(VERSION 3.10.2) # 3.10.2 is fine, but no need to go as high as 3.31.5
project(intaleq_service) # Good
# Add your C++ source file(s) to create a SHARED library.
add_library(native-lib SHARED native-lib.cpp)
# Find the Android log library.
find_library(log-lib log)
# Link your library against the log library. This is essential for debugging.
target_link_libraries(native-lib ${log-lib})

View File

@@ -0,0 +1,187 @@
#include <jni.h>
#include <string>
#include <fstream>
#include <unistd.h>
#include <dirent.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <android/log.h>
#include <pthread.h>
#include <dlfcn.h>
#include <link.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/ptrace.h> // Add this line
#define LOG_TAG "NativeLib"
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)
// Function to check for common root binaries
bool isRooted()
{
std::string paths[] = {
"/system/app/Superuser.apk",
"/system/xbin/su",
"/system/bin/su",
"/system/bin/magisk",
"/system/xbin/magisk",
"/sbin/magisk"};
for (const auto &path : paths)
{
std::ifstream file(path);
if (file.good())
{
return true;
}
}
return false;
}
// Function to check for the presence of files or directories commonly associated with Frida.
bool checkFridaFiles()
{
std::string fridaFiles[] = {
"/data/local/tmp/re.frida.server", // Common Frida server path
"/data/local/tmp/frida-server",
"/usr/lib/libfrida-gadget.so", // Frida gadget (injected library)
"/usr/lib64/libfrida-gadget.so",
"/data/local/re.frida.server",
};
for (const auto &path : fridaFiles)
{
if (access(path.c_str(), F_OK) != -1)
{
LOGE("Frida file detected: %s", path.c_str());
return true;
}
}
return false;
}
// Checks for open ports commonly used by Frida. This is less reliable, as ports can be changed.
bool checkFridaPorts()
{
int sock = socket(AF_INET, SOCK_STREAM, 0);
if (sock == -1)
{
return false; // Couldn't create socket, not a strong indicator.
}
sockaddr_in sa;
memset(&sa, 0, sizeof(sa));
sa.sin_family = AF_INET;
sa.sin_port = htons(27042); // Default Frida port
inet_pton(AF_INET, "127.0.0.1", &sa.sin_addr);
if (connect(sock, (struct sockaddr *)&sa, sizeof(sa)) != -1)
{
LOGE("Frida default port (27042) is open.");
close(sock);
return true;
}
close(sock);
return false;
}
// Check the maps file of the current process for any suspicious entries.
bool checkMaps()
{
std::ifstream mapsFile("/proc/self/maps");
std::string line;
if (mapsFile.is_open())
{
while (std::getline(mapsFile, line))
{
// Look for lines that indicate injected libraries, especially Frida.
if (line.find("frida") != std::string::npos ||
line.find("gum-js-") != std::string::npos)
{ // Gum is Frida's JavaScript engine
LOGE("Suspicious entry in /proc/self/maps: %s", line.c_str());
return true;
}
}
mapsFile.close();
}
else
{
LOGE("Could not open /proc/self/maps");
return false;
}
return false;
}
// Check loaded modules.
bool checkLoadedModules()
{
bool found = false;
dl_iterate_phdr([](struct dl_phdr_info *info, size_t size, void *data)
{
bool *found_ptr = static_cast<bool *>(data);
if (std::string(info->dlpi_name).find("frida") != std::string::npos)
{
LOGE("Frida module detected: %s", info->dlpi_name);
*found_ptr = true;
return 1; // Stop iterating
}
return 0; // Continue iterating
},
&found);
return found;
}
// This is a simple ptrace check. More sophisticated checks are possible (and necessary for robust detection).
// bool checkPtrace() {
// // Attempt to ptrace ourselves. If another process is already tracing us, this will fail.
// if (ptrace(PTRACE_TRACEME, 0, 1, 0) < 0) {
// LOGE("ptrace failed. Debugger or tracer detected.");
// return true; // Likely being traced
// }
// // Detach. If attached, need to detach to not interfere.
// ptrace(PTRACE_DETACH, 0, 0, 0);
// return false;
//}
extern "C" JNIEXPORT jboolean JNICALL
Java_com_service_intaleq_RootDetection_isNativeRooted(JNIEnv *env, jobject /* this */)
{
if (isRooted())
{
return JNI_TRUE;
}
if (checkFridaFiles())
{
return JNI_TRUE;
}
if (checkFridaPorts())
{
return JNI_TRUE;
}
if (checkMaps())
{
return JNI_TRUE;
}
if (checkLoadedModules())
{
return JNI_TRUE;
}
// if (checkPtrace()) {
// return JNI_TRUE;
// }
return JNI_FALSE;
}

View File

@@ -0,0 +1,174 @@
package com.service_intaleq
import android.app.AlertDialog
import android.content.Intent
import android.os.Bundle
import android.util.Log
import android.widget.LinearLayout
import android.widget.ProgressBar
import android.widget.TextView
import androidx.core.view.setPadding
import com.scottyab.rootbeer.RootBeer
import io.flutter.embedding.android.FlutterFragmentActivity
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.plugin.common.MethodChannel
import java.io.File
import java.util.Timer
import kotlin.concurrent.schedule
class MainActivity : FlutterFragmentActivity() {
private val SECURITY_CHANNEL = "com.service_intaleq/security"
private val APP_CONTROL_CHANNEL = "com.service_intaleq/app_control"
override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
super.configureFlutterEngine(flutterEngine)
// Channel for security checks (isRooted)
MethodChannel(flutterEngine.dartExecutor.binaryMessenger, SECURITY_CHANNEL)
.setMethodCallHandler { call, result ->
when (call.method) {
"isNativeRooted" -> result.success(isDeviceCompromised())
else -> result.notImplemented()
}
}
// Channel for app control (bringing to foreground)
MethodChannel(flutterEngine.dartExecutor.binaryMessenger, APP_CONTROL_CHANNEL)
.setMethodCallHandler { call, result ->
when (call.method) {
"bringToForeground" -> {
Log.d("MainActivity", "Received bringToForeground request")
val intent =
Intent(this, MainActivity::class.java).apply {
action = Intent.ACTION_MAIN
addCategory(Intent.CATEGORY_LAUNCHER)
addFlags(
Intent.FLAG_ACTIVITY_NEW_TASK or
Intent.FLAG_ACTIVITY_CLEAR_TOP or
Intent.FLAG_ACTIVITY_SINGLE_TOP
)
}
try {
startActivity(intent)
Log.d(
"MainActivity",
"App brought to foreground successfully with flags: ${intent.flags}"
)
result.success(true)
} catch (e: Exception) {
Log.e(
"MainActivity",
"Error bringing app to foreground: ${e.message}",
e
)
result.error(
"ACTIVITY_START_FAILED",
e.message,
e.stackTraceToString()
)
}
}
else -> result.notImplemented()
}
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
Log.d("MainActivity", "MainActivity onCreate")
if (isDeviceCompromised()) {
showSecurityWarningDialog()
}
}
override fun onNewIntent(intent: Intent) {
super.onNewIntent(intent)
setIntent(intent)
Log.d("MainActivity", "Received new intent: ${intent.action}, flags: ${intent.flags}")
}
private fun isDeviceCompromised(): Boolean {
return try {
val isRootedByRootBeer = RootBeer(this).isRooted
Log.d("MainActivity", "Root check result: $isRootedByRootBeer")
isRootedByRootBeer
} catch (e: Exception) {
Log.e("MainActivity", "Security check error: ${e.message}", e)
true // Fail-safe: assume compromised if check fails
}
}
private fun showSecurityWarningDialog() {
var secondsRemaining = 10
val progressBar =
ProgressBar(this, null, android.R.attr.progressBarStyleHorizontal).apply {
max = 10
progress = 10
}
val textView =
TextView(this).apply {
text = getString(R.string.security_warning_message)
textAlignment = TextView.TEXT_ALIGNMENT_CENTER
}
val layout =
LinearLayout(this).apply {
orientation = LinearLayout.VERTICAL
setPadding(48)
addView(textView)
addView(progressBar)
}
val dialog =
AlertDialog.Builder(this)
.setTitle(getString(R.string.security_warning_title))
.setView(layout)
.setCancelable(false)
.create()
dialog.show()
val timer = Timer()
timer.schedule(0, 1000) {
secondsRemaining--
runOnUiThread {
progressBar.progress = secondsRemaining
if (secondsRemaining <= 0) {
timer.cancel()
dialog.dismiss()
clearAppDataAndExit()
}
}
}
}
private fun clearAppDataAndExit() {
try {
Runtime.getRuntime().exec("pm clear $packageName")
Log.d("MainActivity", "Cleared app data via package manager")
} catch (e: Exception) {
Log.e("MainActivity", "Error clearing app data: ${e.message}", e)
clearCache()
clearAppData()
}
finishAffinity()
System.exit(0)
}
private fun clearCache() {
deleteDir(cacheDir)
deleteDir(externalCacheDir)
Log.d("MainActivity", "Cleared cache directories")
}
private fun clearAppData() {
// Be careful with this, it deletes all app data.
// deleteDir(applicationContext.dataDir)
Log.d("MainActivity", "App data clearing skipped (commented out)")
}
private fun deleteDir(dir: File?): Boolean {
if (dir != null && dir.isDirectory) {
dir.list()?.forEach { deleteDir(File(dir, it)) }
}
val deleted = dir?.delete() ?: false
Log.d("MainActivity", "Deleted directory ${dir?.path}: $deleted")
return deleted
}
}

View File

@@ -0,0 +1,44 @@
package com.service_intaleq
import android.app.Application
import android.content.Intent
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.embedding.engine.dart.DartExecutor
import io.flutter.plugin.common.MethodChannel
class MyApplication : Application() {
companion object {
lateinit var instance: MyApplication
private set
val flutterEngine: FlutterEngine by lazy {
FlutterEngine(instance).apply {
dartExecutor.executeDartEntrypoint(DartExecutor.DartEntrypoint.createDefault())
}
}
}
override fun onCreate() {
super.onCreate()
instance = this
MethodChannel(
flutterEngine.dartExecutor.binaryMessenger,
"com.service_intaleq/app_lifecycle"
)
.setMethodCallHandler { call, result ->
if (call.method == "bringAppToForeground") {
bringAppToForeground()
result.success(null)
} else {
result.notImplemented()
}
}
}
private fun bringAppToForeground() {
val intent = Intent(applicationContext, MainActivity::class.java)
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_REORDER_TO_FRONT)
startActivity(intent)
}
}

View File

@@ -0,0 +1,9 @@
package com.service_intaleq
object RootDetection {
init {
System.loadLibrary("native-lib") // Load the native library
}
external fun isNativeRooted(): Boolean // Declare the external function
}

View File

@@ -0,0 +1,104 @@
import android.content.Context
import android.util.Base64
import android.util.Log
import com.google.android.gms.safetynet.SafetyNet
import java.io.IOException
import java.security.GeneralSecurityException
import java.security.SecureRandom
import org.json.JSONObject
object SafetyNetCheck {
private const val TAG = "SafetyNetCheck"
fun checkSafetyNet(context: Context, apiKey: String, callback: (Boolean) -> Unit) {
// Generate a nonce. A good nonce is large, random, and used only once.
val nonce = generateNonce()
SafetyNet.getClient(context)
.attest(nonce, apiKey)
.addOnSuccessListener { response ->
// Success! Now, *verify* the response.
val jwsResult = response.jwsResult
if (jwsResult != null) {
try {
val isSafe = SafetyNetResponseVerifier.verify(jwsResult)
Log.d(TAG, "SafetyNet verification result: $isSafe")
callback(isSafe) // Now passing a *verified* result.
} catch (e: Exception) {
Log.e(TAG, "Error verifying SafetyNet response: ${e.message}", e)
callback(false) // Treat verification errors as failures.
}
} else {
Log.e(TAG, "SafetyNet jwsResult is null")
callback(false) // Null result is a failure.
}
}
.addOnFailureListener { e ->
Log.e(TAG, "SafetyNet attest API call failed: ${e.message}", e)
callback(false) // API call failure.
}
}
// Helper function to generate a nonce.
private fun generateNonce(): ByteArray {
val byteGenerator = SecureRandom()
val nonce = ByteArray(32)
byteGenerator.nextBytes(nonce)
return nonce
}
}
// Helper class to verify the SafetyNet response.
object SafetyNetResponseVerifier {
private const val TAG = "SafetyNetVerifier"
// This method *must* be implemented on a *backend server* for real security.
// This is just a *simplified example* for demonstration purposes and is
// *not* suitable for production without a backend check.
@Throws(GeneralSecurityException::class, IOException::class)
fun verify(jwsResult: String): Boolean {
// 1. Parse the JWS: Split into header, payload, and signature.
val parts = jwsResult.split(".")
if (parts.size != 3) {
Log.e(TAG, "Invalid JWS format")
return false // Invalid JWS format
}
val header = parts[0]
val payload = parts[1]
val signature = parts[2]
// 2. Decode the payload (it's Base64 encoded).
val decodedPayload = Base64.decode(payload, Base64.DEFAULT)
val payloadJson = JSONObject(String(decodedPayload))
// 3. Check the ctsProfileMatch and basicIntegrity.
val ctsProfileMatch = payloadJson.optBoolean("ctsProfileMatch", false)
val basicIntegrity = payloadJson.optBoolean("basicIntegrity", false)
Log.d(TAG, "ctsProfileMatch: $ctsProfileMatch, basicIntegrity: $basicIntegrity")
// 4. **CRITICAL: In a real application, you *must* send the JWS to your
// backend server for verification. The server should use the
// Google SafetyNet API (or a library that wraps it) to verify
// the signature and check the fields. This prevents attackers
// from tampering with the response on the device.**
//
// // Example (pseudo-code) of what the backend check would do:
// // GoogleCredential credential = ...;
// // SafetyNet safetyNet = new SafetyNet.Builder(httpTransport, jsonFactory)
// // .setApplicationName("YourAppName")
// // .setHttpRequestInitializer(credential)
// // .build();
// // SafetyNetApi.VerifyJwsRequest request = safetyNet.safetynet().verifyJws(jwsResult);
// // SafetyNetApi.VerifyJwsResponse response = request.execute();
// // return response.isValidSignature() && response.getCtsProfileMatch() &&
// response.getBasicIntegrity();
// 5. For this *example* (without a backend), we'll just check the fields.
// This is *NOT SECURE* for production!
return ctsProfileMatch && basicIntegrity
}
}

View File

@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Modify this file to customize your launch splash screen -->
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="?android:colorBackground" />
<!-- You can insert your own image assets here -->
<!-- <item>
<bitmap
android:gravity="center"
android:src="@mipmap/launch_image" />
</item> -->
</layer-list>

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

View File

@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Modify this file to customize your launch splash screen -->
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@android:color/white" />
<!-- You can insert your own image assets here -->
<!-- <item>
<bitmap
android:gravity="center"
android:src="@mipmap/launch_image" />
</item> -->
</layer-list>

Binary file not shown.

After

Width:  |  Height:  |  Size: 544 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 442 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 721 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

View File

@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is on -->
<style name="LaunchTheme" parent="@android:style/Theme.Black.NoTitleBar">
<!-- Show a splash screen on the activity. Automatically removed when
the Flutter engine draws its first frame -->
<item name="android:windowBackground">@drawable/launch_background</item>
</style>
<!-- Theme applied to the Android Window as soon as the process has started.
This theme determines the color of the Android Window while your
Flutter UI initializes, as well as behind your Flutter UI while its
running.
This Theme is only used starting with V2 of Flutter's Android embedding. -->
<style name="NormalTheme" parent="@android:style/Theme.Black.NoTitleBar">
<item name="android:windowBackground">?android:colorBackground</item>
</style>
</resources>

View File

@@ -0,0 +1,15 @@
<resources>
<string name="app_name">My App</string>
<!-- <string name="default_notification_channel_id">ride_channel</string> -->
<!-- <string name="default_notification_channel_id">default_channel</string> -->
<string name="default_notification_channel_id">high_importance_channel</string>
<string name="api_key">AIzaSyCFsWBqvkXzk1Gb-bCGxwqTwJQKIeHjH64</string>
<string name="security_warning_title">Security Warning</string>
<string name="api_key_safety">AIzaSyB04YNW3LbvmQ5lX1t2bOwEU18-KUoovzw</string>
<string name="security_warning_message">A security issue or modification has been detected on
this device. The app cannot run on this device.</string>
<string name="exit_button">Exit App</string>
<string name="device_secure">Device is secure. Proceeding normally.</string>
</resources>

View File

@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is off -->
<style name="LaunchTheme" parent="@android:style/Theme.Light.NoTitleBar">
<!-- Show a splash screen on the activity. Automatically removed when
the Flutter engine draws its first frame -->
<item name="android:windowBackground">@drawable/launch_background</item>
</style>
<!-- Theme applied to the Android Window as soon as the process has started.
This theme determines the color of the Android Window while your
Flutter UI initializes, as well as behind your Flutter UI while its
running.
This Theme is only used starting with V2 of Flutter's Android embedding. -->
<style name="NormalTheme" parent="@android:style/Theme.Light.NoTitleBar">
<item name="android:windowBackground">?android:colorBackground</item>
</style>
</resources>

View File

@@ -0,0 +1,7 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<!-- The INTERNET permission is required for development. Specifically,
the Flutter tool needs it to communicate with the running application
to allow setting breakpoints, to provide hot reload, etc.
-->
<uses-permission android:name="android.permission.INTERNET"/>
</manifest>

21
android/build.gradle.kts Normal file
View File

@@ -0,0 +1,21 @@
allprojects {
repositories {
google()
mavenCentral()
}
}
val newBuildDir: Directory = rootProject.layout.buildDirectory.dir("../../build").get()
rootProject.layout.buildDirectory.value(newBuildDir)
subprojects {
val newSubprojectBuildDir: Directory = newBuildDir.dir(project.name)
project.layout.buildDirectory.value(newSubprojectBuildDir)
}
subprojects {
project.evaluationDependsOn(":app")
}
tasks.register<Delete>("clean") {
delete(rootProject.layout.buildDirectory)
}

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,3 @@
org.gradle.jvmargs=-Xmx8G -XX:MaxMetaspaceSize=4G -XX:ReservedCodeCacheSize=512m -XX:+HeapDumpOnOutOfMemoryError
android.useAndroidX=true
android.enableJetifier=true

View File

@@ -0,0 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-all.zip

View File

@@ -0,0 +1,28 @@
pluginManagement {
val flutterSdkPath = run {
val properties = java.util.Properties()
file("local.properties").inputStream().use { properties.load(it) }
val flutterSdkPath = properties.getProperty("flutter.sdk")
require(flutterSdkPath != null) { "flutter.sdk not set in local.properties" }
flutterSdkPath
}
includeBuild("$flutterSdkPath/packages/flutter_tools/gradle")
repositories {
google()
mavenCentral()
gradlePluginPortal()
}
}
plugins {
id("dev.flutter.flutter-plugin-loader") version "1.0.0"
id("com.android.application") version "8.11.1" apply false
// START: FlutterFire Configuration
id("com.google.gms.google-services") version("4.3.10") apply false
// END: FlutterFire Configuration
id("org.jetbrains.kotlin.android") version "2.1.0" apply false
}
include(":app")