25-3/12/1

This commit is contained in:
Hamza-Ayed
2025-03-12 15:18:21 +03:00
parent 5a4664ed67
commit a07a1b10ea
603 changed files with 66922 additions and 328 deletions

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(sefer_driver) # 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,167 @@
#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_sefer_1driver_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

@@ -1,95 +1,202 @@
package com.sefer_driver
// import android.content.Intent
// import io.flutter.embedding.android.FlutterFragmentActivity
// import io.flutter.embedding.engine.FlutterEngine
// class MainActivity : FlutterFragmentActivity() {
// override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
// try {
// super.configureFlutterEngine(MyApplication.flutterEngine)
// } catch (e: UninitializedPropertyAccessException) {
// super.configureFlutterEngine(flutterEngine)
// }
// }
// private fun bringAppToForeground() {
// val intent = Intent(this, MainActivity::class.java)
// intent.addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT)
// startActivity(intent)
// }
// }
import android.app.AlertDialog
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 java.util.*
import java.io.File
import java.util.Timer
import kotlin.concurrent.schedule
import io.flutter.embedding.engine.FlutterEngine // Import FlutterEngine
import io.flutter.plugin.common.MethodChannel // Import MethodChannel
import io.flutter.plugin.common.MethodCall // Import MethodCall
import io.flutter.plugin.common.MethodChannel.Result // Import Result
class MainActivity : FlutterFragmentActivity() {
private lateinit var channel: MethodChannel // Declare a MethodChannel variable
override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
super.configureFlutterEngine(flutterEngine)
// Initialize the MethodChannel
channel = MethodChannel(flutterEngine.dartExecutor.binaryMessenger, "com.sefer_driver/security")
// Set a MethodCallHandler to handle method calls from Flutter
channel.setMethodCallHandler { call, result ->
when (call.method) {
"isNativeRooted" -> {
val isCompromised = isDeviceCompromised()
result.success(isCompromised) // Send the result back to Flutter
}
else -> {
result.notImplemented() // Handle unknown method calls
}
}
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
Log.d("DEBUG", "onCreate executed - Checking root status...")
// بدء فحص الأمان
performSecurityChecks()
// Perform all checks. The order can matter; you might want to prioritize
// the faster checks first.
if (isDeviceCompromised()) {
showSecurityWarningDialog()
}
}
private fun performSecurityChecks() {
val rootDetection = RootDetection
val isRooted = rootDetection.isRooted()
val isDevMode = rootDetection.isDevMode(this)
val isTampered = rootDetection.isTampered(this)
val isRealDevice = rootDetection.isRealDevice()
val isOnExternalStorage = rootDetection.isOnExternalStorage()
val isNotTrust = rootDetection.isNotTrust()
val checkForIssues = rootDetection.checkForIssues()
// Log.d("DEBUG", "Security Check Results:")
// Log.d("DEBUG", "isRooted: $isRooted")
// Log.d("DEBUG", "isDevMode: $isDevMode")
// Log.d("DEBUG", "isTampered: $isTampered")
// Log.d("DEBUG", "isRealDevice: $isRealDevice")
// Log.d("DEBUG", "isOnExternalStorage: $isOnExternalStorage")
// Log.d("DEBUG", "isNotTrust: $isNotTrust")
// Log.d("DEBUG", "checkForIssues: $checkForIssues")
if (isRooted || isTampered || !isRealDevice) {
// Log.e("DEBUG", "Security issue detected! Showing dialog.")
showSecurityWarningDialog()
} else {
// Log.d(
// "DEBUG",
// getString(R.string.device_secure)
// ) // Using dynamic string based on the device language
private fun isDeviceCompromised(): Boolean {
return try {
nativeRootCheck() || rootBeerCheck() //|| !safetyNetCheck()
} catch (e: Exception) {
Log.e("DEBUG", "Error during security checks: ${e.message}", e)
true // Consider the device compromised on error
}
}
private fun nativeRootCheck(): Boolean {
Log.d("DEBUG", "Starting native root detection...")
return try {
val isNativeRooted = RootDetection.isNativeRooted()
Log.d("DEBUG", "Native root detection result: $isNativeRooted")
isNativeRooted
} catch (e: Exception) {
Log.e("DEBUG", "Error in native root detection: ${e.message}", e)
true // Consider rooted on exception
}
}
private fun rootBeerCheck(): Boolean {
Log.d("DEBUG", "Starting RootBeer root detection...")
val rootBeer = RootBeer(this)
val isRooted = rootBeer.isRooted
Log.d("DEBUG", "RootBeer detection result: $isRooted")
return isRooted
}
private fun safetyNetCheck(): Boolean {
Log.d("DEBUG", "Starting SafetyNet check...")
var isSafe = false // Initialize a variable to store result
val semaphore = java.util.concurrent.Semaphore(0) // Create a semaphore
SafetyNetCheck.checkSafetyNet(this, getString(R.string.api_key_safety)) { result ->
isSafe = result
Log.d("DEBUG", "SafetyNet check result: $isSafe")
semaphore.release() // Release the semaphore when the callback is executed
}
try {
semaphore.acquire() // Wait for the callback to complete
} catch (e: InterruptedException) {
Log.e("DEBUG", "Interrupted while waiting for SafetyNet check", e)
return false // Or handle as appropriate for your app
}
return isSafe
}
private fun showSecurityWarningDialog() {
val builder = AlertDialog.Builder(this)
builder.setTitle(getString(R.string.security_warning_title)) // Title based on language
builder.setMessage(
getString(R.string.security_warning_message)
) // Message based on language
var secondsRemaining = 10 // Start at 10 seconds
builder.setPositiveButton(getString(R.string.exit_button)) { _, _ ->
Log.e("DEBUG", "User clicked exit. Closing app.")
clearAppDataAndExit()
// Create views programmatically
val progressBar = ProgressBar(this, null, android.R.attr.progressBarStyleHorizontal)
progressBar.max = 10 // Set max to 10 for 10 seconds
progressBar.progress = 10 // Start full
val textView = TextView(this)
textView.text = getString(R.string.security_warning_message) // Your message
textView.textAlignment = TextView.TEXT_ALIGNMENT_CENTER
val layout = LinearLayout(this)
layout.orientation = LinearLayout.VERTICAL
layout.setPadding(48)
layout.addView(textView)
layout.addView(progressBar)
val dialog =
AlertDialog.Builder(this)
.setTitle(getString(R.string.security_warning_title)) // Your title
.setView(layout) // Set the custom layout
.setCancelable(
false
) // Prevent dismissing by tapping outside or back button
.create()
dialog.show()
// Use a Timer to update the progress bar and countdown
val timer = Timer()
timer.schedule(0, 1000) { // Update every 1000ms (1 second)
secondsRemaining--
runOnUiThread { // Update UI on the main thread
progressBar.progress =
secondsRemaining // Set the progress bar to show remaining seconds
if (secondsRemaining <= 0) {
timer.cancel() // Stop the timer
dialog.dismiss() // Dismiss the dialog
clearAppDataAndExit() // Clear data and exit
}
}
}
builder.setCancelable(false)
val alertDialog = builder.create()
alertDialog.show()
}
private fun clearAppDataAndExit() {
// حذف جميع البيانات هنا، مثل ملفات التخزين وبيانات التطبيق
// يمكنك استخدام SharedPreferences أو قاعدة بيانات
Log.d("DEBUG", "Clearing app data...")
try {
val packageName = applicationContext.packageName
val runtime = Runtime.getRuntime()
runtime.exec("pm clear $packageName") // Clear app data
} catch (e: Exception) {
clearCache()
clearAppData()
}
// يمكنك استخدام كود لحذف جميع البيانات (SharedPreferences, DB, ملفات)
// ثم إغلاق التطبيق:
finishAffinity() // يغلق جميع الأنشطة ويفتح نافذة جديدة
finishAffinity() // Finish all activities from this app
System.exit(0) // Terminate the app's process
}
}
private fun clearCache() {
try {
val cacheDir = cacheDir
if (cacheDir != null && cacheDir.isDirectory) {
deleteDir(cacheDir)
}
val externalCacheDir = externalCacheDir
if (externalCacheDir != null && externalCacheDir.isDirectory) {
deleteDir(externalCacheDir)
}
} catch (e: Exception) {
Log.e("DEBUG", "Error clearing cache: ${e.message}", e)
}
}
private fun clearAppData() {
try {
val dataDir = applicationContext.dataDir
if (dataDir != null && dataDir.isDirectory) {
deleteDir(dataDir)
}
} catch (e: Exception) {
Log.e("DEBUG", "Error clearing app data: ${e.message}", e)
}
}
private fun deleteDir(dir: File?): Boolean {
if (dir != null && dir.isDirectory) {
val children = dir.list()
if (children != null) {
for (child in children) {
val success = deleteDir(File(dir, child))
if (!success) {
return false
}
}
}
}
return dir?.delete() ?: false
}
}

View File

@@ -1,61 +1,10 @@
package com.sefer_driver
import android.content.Context
import android.os.Build
import android.os.Environment
import android.provider.Settings
import com.scottyab.rootbeer.RootBeer
import java.io.File
object RootDetection {
fun isRooted(): Boolean {
val paths =
arrayOf(
"/system/app/Superuser.apk",
"/system/xbin/su",
"/system/bin/su",
"/system/bin/magisk",
"/system/xbin/magisk",
"/sbin/magisk"
)
for (path in paths) {
if (File(path).exists()) {
return true
}
}
return false
init {
System.loadLibrary("native-lib") // Load the native library
}
fun isDevMode(context: Context): Boolean {
return Settings.Global.getInt(
context.contentResolver,
Settings.Global.DEVELOPMENT_SETTINGS_ENABLED,
0
) == 1
}
fun isTampered(context: Context): Boolean {
val rootBeer = RootBeer(context)
return rootBeer.isRooted
}
fun checkForIssues(): Boolean {
// يمكنك إضافة المزيد من الفحوصات حسب حاجتك
return false // نفترض أنه لا توجد مشاكل للأجهزة غير المتجذرة
}
fun isRealDevice(): Boolean {
return !Build.FINGERPRINT.contains("generic") && !Build.MODEL.contains("google_sdk")
}
fun isOnExternalStorage(): Boolean {
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT &&
Environment.getExternalStorageState() == Environment.MEDIA_MOUNTED
}
fun isNotTrust(): Boolean {
// مثال تحقق من الثقة (قد تحتاج لتطوير هذا حسب متطلباتك)
return false // نفترض أن الجهاز موثوق
}
external fun isNativeRooted(): Boolean // Declare the external function
}

View File

@@ -0,0 +1,109 @@
import android.content.Context
import android.util.Base64
import android.util.Log
import com.google.android.gms.safetynet.SafetyNet
import com.google.android.gms.safetynet.SafetyNetApi
import com.google.android.gms.tasks.OnFailureListener
import com.google.android.gms.tasks.OnSuccessListener
import org.json.JSONObject
import java.io.IOException
import java.security.GeneralSecurityException
import java.security.SecureRandom
import java.util.Arrays
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

@@ -2,6 +2,7 @@
<string name="app_name">My App</string>
<string name="default_notification_channel_id">high_importance_channel</string>
<string name="api_key">AIzaSyCyfwRXTwSTLOFQSQgN5p7QZgGJVZnEKq0</string>
<string name="api_key_safety">AIzaSyB04YNW3LbvmQ5lX1t2bOwEU18-KUoovzw</string>
<string name="channel_name">FCM Notifications</string>
<string name="channel_description">Notifications from Firebase Cloud Messaging</string>
<string name="security_warning_title">Security Warning</string>