x1ongzhu 1 rok temu
rodzic
commit
9569435cb6

BIN
app/src/main/assets/bin/frida-inject-16.3.3-android-arm64


+ 214 - 0
app/src/main/assets/scripts/phone.js

@@ -0,0 +1,214 @@
+class Log {
+    static TAG = '[Phone]'
+    static Debug = true
+    static format(...msg) {
+        let m = []
+        for (let i = 0; i < msg.length; i++) {
+            if (typeof msg[i] === 'object') {
+                m.push(JSON.stringify(msg[i]))
+            } else {
+                m.push(msg[i])
+            }
+        }
+        m = m.join(' ')
+        return m
+    }
+    static i(...msg) {
+        if (!this.Debug) return
+        console.log(`\x1b[30m${this.TAG} ${this.format(...msg)}\x1b[0m`)
+    }
+    static w(...msg) {
+        console.log(`\x1b[33m${this.TAG} ${this.format(...msg)}\x1b[0m`)
+    }
+    static e(...msg) {
+        console.log(`\x1b[31m${this.TAG} ${this.format(...msg)}\x1b[0m`)
+    }
+    static s(...msg) {
+        console.log(`\x1b[32m${this.TAG} ${this.format(...msg)}\x1b[0m`)
+    }
+}
+
+function trace(tag) {
+    Log.e((tag || '') + Java.use('android.util.Log').getStackTraceString(Java.use('java.lang.Throwable').$new()))
+}
+
+setImmediate(() => {
+    Java.perform(function () {
+        function checkPackage(name) {
+            // return (
+            //     name.startsWith('com.google.android.gsf') ||
+            //     name.startsWith('com.google.android.gms') ||
+            //     name.startsWith('com.google.android.apps') ||
+            //     name.startsWith('com.example')
+            // )
+            return true
+        }
+
+        const SystemProperties = Java.use('android.os.SystemProperties')
+
+        const PhoneInterfaceManager = Java.use('com.android.phone.PhoneInterfaceManager')
+        PhoneInterfaceManager.getLine1NumberForDisplay.overload(
+            'int',
+            'java.lang.String',
+            'java.lang.String'
+        ).implementation = function (subId, callingPackage, callingFeatureId) {
+            const res = this.getLine1NumberForDisplay(subId, callingPackage, callingFeatureId)
+            if (!checkPackage(callingPackage)) {
+                return res
+            }
+            Log.i(
+                `spoof PhoneInterfaceManager.getLine1NumberForDisplay(${subId}, ${callingPackage}, ${callingFeatureId}): ${res} -> ${SystemProperties.get('persist.spoof.number')}`
+            )
+            return SystemProperties.get('persist.spoof.number')
+        }
+
+        PhoneInterfaceManager.getNetworkCountryIsoForPhone.overload('int').implementation = function (phoneId) {
+            const res = this.getNetworkCountryIsoForPhone(phoneId)
+            Log.i(`spoof PhoneInterfaceManager.getNetworkCountryIsoForPhone(${phoneId}): ${res} -> ${SystemProperties.get('persist.spoof.country')}`)
+            return SystemProperties.get('persist.spoof.country')
+        }
+
+        PhoneInterfaceManager.getImeiForSlot.overload('int', 'java.lang.String', 'java.lang.String').implementation =
+            function (slotId, callingPackage, callingFeatureId) {
+                const res = this.getImeiForSlot(slotId, callingPackage, callingFeatureId)
+                if (!checkPackage(callingPackage)) {
+                    return res
+                }
+                Log.i(
+                    `spoof PhoneInterfaceManager.getImeiForSlot(${slotId}, ${callingPackage}, ${callingFeatureId}): ${res} -> ${SystemProperties.get('persist.spoof.imei')}`
+                )
+                return SystemProperties.get('persist.spoof.imei')
+            }
+
+        SystemProperties.get.overload('java.lang.String').implementation = function (key) {
+            if ('gsm.sim.operator.iso-country' === key) {
+                Log.i(`spoof SystemProperties.get(${key}): ${SystemProperties.get('persist.spoof.country')}`)
+                return SystemProperties.get('persist.spoof.country')
+            }
+            if ('gsm.sim.operator.numeric' === key) {
+                Log.i(`spoof SystemProperties.get(${key}): ${SystemProperties.get('persist.spoof.mcc') + SystemProperties.get('persist.spoof.mnc')}`)
+                return SystemProperties.get('persist.spoof.mcc') + SystemProperties.get('persist.spoof.mnc')
+            }
+            if ('gsm.operator.numeric' === key) {
+                Log.i(`spoof SystemProperties.get(${key}): ${SystemProperties.get('persist.spoof.mcc') + SystemProperties.get('persist.spoof.mnc')}`)
+                return SystemProperties.get('persist.spoof.mcc') + SystemProperties.get('persist.spoof.mnc')
+            }
+            return this.get(key)
+        }
+
+        const SubscriptionController = Java.use('com.android.internal.telephony.SubscriptionController')
+        SubscriptionController.getSimStateForSlotIndex.overload('int').implementation = function (slotIndex) {
+            const res = this.getSimStateForSlotIndex(slotIndex)
+            Log.i(`spoof SubscriptionController.getSimStateForSlotIndex(${slotIndex}): ${res} -> 5`)
+            return 5
+        }
+        SubscriptionController.getPhoneNumberFromFirstAvailableSource.overload(
+            'int',
+            'java.lang.String',
+            'java.lang.String'
+        ).implementation = function (subId, callingPackage, callingFeatureId) {
+            const res = this.getPhoneNumberFromFirstAvailableSource(subId, callingPackage, callingFeatureId)
+            if (!checkPackage(callingPackage)) {
+                return res
+            }
+            Log.i(
+                `spoof SubscriptionController.getPhoneNumberFromFirstAvailableSource(${subId}, ${callingPackage}, ${callingFeatureId}): ${res} -> ${SystemProperties.get('persist.spoof.number')}`
+            )
+            return SystemProperties.get('persist.spoof.number')
+        }
+        const SubscriptionInfo = Java.use('android.telephony.SubscriptionInfo')
+        SubscriptionController.getActiveSubscriptionInfoList.overload('java.lang.String').implementation = function (
+            callingPackage
+        ) {
+            const res = this.getActiveSubscriptionInfoList(callingPackage)
+            if (!checkPackage(callingPackage)) {
+                return res
+            }
+            Log.i(`spoof SubscriptionController.getActiveSubscriptionInfoList(${callingPackage})`)
+            for (let i = 0; i < res.size(); i++) {
+                const info = Java.cast(res.get(i), SubscriptionInfo)
+                info.mMcc.value = SystemProperties.get('persist.spoof.mcc')
+                info.mMnc.value = SystemProperties.get('persist.spoof.mnc')
+                info.mCountryIso.value = SystemProperties.get('persist.spoof.country')
+                info.mIccId.value = SystemProperties.get('persist.spoof.iccid')
+            }
+            SystemProperties.set('gsm.sim.operator.iso-country', SystemProperties.get('persist.spoof.country'))
+            SystemProperties.set('gsm.sim.operator.numeric', SystemProperties.get('persist.spoof.mcc') + SystemProperties.get('persist.spoof.mnc'))
+            SystemProperties.set('gsm.operator.numeric', SystemProperties.get('persist.spoof.mcc') + SystemProperties.get('persist.spoof.mnc'))
+            return res
+        }
+
+        SubscriptionController.getActiveSubscriptionInfoList.overload(
+            'java.lang.String',
+            'java.lang.String'
+        ).implementation = function (callingPackage, callingFeatureId) {
+            const res = this.getActiveSubscriptionInfoList(callingPackage, callingFeatureId)
+            if (!checkPackage(callingPackage)) {
+                return res
+            }
+            Log.i(`spoof SubscriptionController.getActiveSubscriptionInfoList(${callingPackage}, ${callingFeatureId})`)
+            for (let i = 0; i < res.size(); i++) {
+                const info = Java.cast(res.get(i), SubscriptionInfo)
+                info.mMcc.value = SystemProperties.get('persist.spoof.mcc')
+                info.mMnc.value = SystemProperties.get('persist.spoof.mnc')
+                info.mCountryIso.value = SystemProperties.get('persist.spoof.country')
+                info.mIccId.value = SystemProperties.get('persist.spoof.iccid')
+            }
+            SystemProperties.set('gsm.sim.operator.iso-country', SystemProperties.get('persist.spoof.country'))
+            SystemProperties.set('gsm.sim.operator.numeric', SystemProperties.get('persist.spoof.mcc') + SystemProperties.get('persist.spoof.mnc'))
+            SystemProperties.set('gsm.operator.numeric', SystemProperties.get('persist.spoof.mcc') + SystemProperties.get('persist.spoof.mnc'))
+            return res
+        }
+
+        SubscriptionController.getActiveSubscriptionInfoList.overload('java.lang.String').implementation = function (
+            callingPackage
+        ) {
+            const res = this.getActiveSubscriptionInfoList(callingPackage)
+            if (!checkPackage(callingPackage)) {
+                return res
+            }
+            Log.i(`spoof SubscriptionController.getActiveSubscriptionInfoList(${callingPackage})`)
+            for (let i = 0; i < res.size(); i++) {
+                const info = Java.cast(res.get(i), SubscriptionInfo)
+                info.mMcc.value = SystemProperties.get('persist.spoof.mcc')
+                info.mMnc.value = SystemProperties.get('persist.spoof.mnc')
+                info.mCountryIso.value = SystemProperties.get('persist.spoof.country')
+                info.mIccId.value = SystemProperties.get('persist.spoof.iccid')
+            }
+            SystemProperties.set('gsm.sim.operator.iso-country', SystemProperties.get('persist.spoof.country'))
+            SystemProperties.set('gsm.sim.operator.numeric', SystemProperties.get('persist.spoof.mcc') + SystemProperties.get('persist.spoof.mnc'))
+            SystemProperties.set('gsm.operator.numeric', SystemProperties.get('persist.spoof.mcc') + SystemProperties.get('persist.spoof.mnc'))
+            return res
+        }
+
+        const PhoneSubInfoController = Java.use('com.android.internal.telephony.PhoneSubInfoController')
+        PhoneSubInfoController.getIccSerialNumberForSubscriber.overload(
+            'int',
+            'java.lang.String',
+            'java.lang.String'
+        ).implementation = function (subId, callingPackage, callingFeatureId) {
+            const res = this.getIccSerialNumberForSubscriber(subId, callingPackage, callingFeatureId)
+            if (!checkPackage(callingPackage)) {
+                return res
+            }
+            Log.i(
+                `spoof PhoneInterfaceManager.getIccSerialNumberForSubscriber(${subId}, ${callingPackage}, ${callingFeatureId}): ${res} -> ${SystemProperties.get('persist.spoof.iccid')}`
+            )
+            return SystemProperties.get('persist.spoof.iccid')
+        }
+        PhoneSubInfoController.getSubscriberIdForSubscriber.overload(
+            'int',
+            'java.lang.String',
+            'java.lang.String'
+        ).implementation = function (subId, callingPackage, callingFeatureId) {
+            const res = this.getSubscriberIdForSubscriber(subId, callingPackage, callingFeatureId)
+            if (!checkPackage(callingPackage)) {
+                return res
+            }
+            Log.i(
+                `spoof PhoneInterfaceManager.getSubscriberIdForSubscriber(${subId}, ${callingPackage}, ${callingFeatureId}): ${res} -> ${SystemProperties.get('persist.spoof.imsi')}`
+            )
+            return SystemProperties.get('persist.spoof.imsi')
+        }
+    })
+})

+ 60 - 0
app/src/main/assets/scripts/sms.js

@@ -0,0 +1,60 @@
+Java.perform(() => {
+    Java.deoptimizeEverything()
+
+    let found = false
+    Java.choose('com.android.internal.telephony.SmsDispatchersController', {
+        onMatch: function (instance) {
+            if (found) {
+                return
+            }
+            found = true
+
+            const Intent = Java.use('android.content.Intent')
+            const SubscriptionManager = Java.use('android.telephony.SubscriptionManager')
+            const Base64 = Java.use('java.util.Base64')
+
+            let subId = 0
+            let slot = 0
+
+            const subscriptionManager = SubscriptionManager.from(instance.mContext.value)
+            for (let i = 0; i < subscriptionManager.getActiveSubscriptionInfoCountMax(); i++) {
+                const subInfo = subscriptionManager.getActiveSubscriptionInfoForSimSlotIndex(i)
+                if (subInfo != null) {
+                    subId = subInfo.getSubscriptionId()
+                    slot = subInfo.getSimSlotIndex()
+                    break
+                }
+            }
+
+            const Integer = Java.use('java.lang.Integer')
+            subId = Integer.valueOf(subId + '')
+            slot = Integer.valueOf(slot + '')
+
+            const intent = Intent.$new()
+            intent.putExtra('android.telephony.extra.SUBSCRIPTION_INDEX', subId)
+
+            intent.putExtra('messageId', Java.use('java.lang.Long').parseLong('' + parseInt(Math.random() * 100000000)))
+
+            const pdu = Base64.getDecoder().decode('{pduBase64}')
+            const pdus = Java.array('[B', [pdu])
+            intent.putExtra.overload('java.lang.String', 'java.io.Serializable').call(intent, 'pdus', pdus.$w)
+            intent.putExtra('format', '3gpp')
+            intent.putExtra('android.telephony.extra.SLOT_INDEX', slot)
+            intent.putExtra('phone', slot)
+            intent.putExtra('subscription', subId)
+            // instance.mContext.value.sendBroadcast(intent)
+
+            intent.setAction('android.provider.Telephony.SMS_RECEIVED')
+            instance.mContext.value.sendBroadcast(intent)
+
+            intent.setAction('android.provider.Telephony.SMS_DELIVER')
+            instance.mContext.value.sendBroadcast(intent)
+
+            console.log('OK')
+            send("OK")
+        },
+        onComplete: function () {
+            console.log('onComplete')
+        }
+    })
+})

+ 99 - 0
app/src/main/java/com/example/modifier/Frida.kt

@@ -0,0 +1,99 @@
+package com.example.modifier
+
+import android.util.Log
+import androidx.core.content.ContextCompat
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.coroutineScope
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
+import org.apache.commons.io.FileUtils
+import org.apache.commons.io.IOUtils
+import java.io.File
+import java.io.InterruptedIOException
+
+class Frida {
+
+    companion object {
+        var p: Process? = null
+        var script: File? = null
+        private val TAG = "FridaScript"
+
+        suspend fun start() {
+            if (p != null) {
+                p!!.destroy()
+                p = null
+            }
+            if (script != null) {
+                script!!.delete()
+                script = null
+            }
+
+            val context = Utils.getContext()
+            val dataDir = ContextCompat.getDataDir(context)
+            Utils.copyAssetFolder(context.assets, "bin", File(dataDir, "bin").path)
+            val binPath = File(dataDir, "bin/frida-inject-16.3.3-android-arm64").path
+            val pid = Utils.runAsRoot("pidof com.android.phone").trim()
+            if (!Regex("[0-9]+").matches(pid)) {
+                return
+            }
+
+            val scriptContent = IOUtils.toString(context.assets.open("scripts/phone.js"), "UTF-8")
+            withContext(Dispatchers.IO) {
+                script = File.createTempFile("script", ".js")
+                FileUtils.writeStringToFile(script, scriptContent, "UTF-8")
+                Log.i(TAG, "start: $binPath -p $pid  -s $script")
+
+                p = Runtime.getRuntime().exec("su -M")
+                p!!.outputStream.bufferedWriter().use {
+                    it.write("chmod +x $binPath")
+                    it.newLine()
+                    it.flush()
+                    it.write("$binPath -p $pid  -s $script")
+                    it.newLine()
+                    it.flush()
+                }
+                coroutineScope {
+                    launch {
+                        try {
+                            p!!.inputStream
+                                .bufferedReader()
+                                .useLines { lines ->
+                                    lines.forEach {
+                                        Log.i(TAG, it)
+                                    }
+                                }
+                        } catch (e: Exception) {
+                            e.printStackTrace()
+                        }
+                    }
+                    launch {
+                        try {
+                            p!!.errorStream
+                                .bufferedReader()
+                                .useLines { lines ->
+                                    lines.forEach {
+                                        Log.e(TAG, it)
+                                    }
+                                }
+                        } catch (e: Exception) {
+                            e.printStackTrace()
+                        }
+                    }
+                }
+            }
+        }
+
+        fun stop() {
+            if (p != null) {
+                p!!.inputStream.close()
+                p!!.errorStream.close()
+                p!!.destroy()
+                p = null
+            }
+            if (script != null) {
+                script!!.delete()
+                script = null
+            }
+        }
+    }
+}

+ 77 - 2
app/src/main/java/com/example/modifier/Global.kt

@@ -1,16 +1,22 @@
 package com.example.modifier
 
-import android.annotation.SuppressLint
 import android.content.Context
 import android.os.Build
 import android.util.Log
 import androidx.core.content.ContextCompat
 import com.example.modifier.model.TelephonyConfig
+import com.example.modifier.utils.RcsHackTool
 import com.google.gson.Gson
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.coroutineScope
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
 import org.apache.commons.io.FileUtils
+import org.apache.commons.io.IOUtils
 import org.apache.commons.lang3.StringUtils
 import java.io.File
 import java.io.FileWriter
+import java.util.Base64
 
 object Global {
     @JvmField
@@ -339,7 +345,7 @@ object Global {
     }
 
     @JvmStatic
-    fun killPhoneProcess(){
+    fun killPhoneProcess() {
         try {
             Utils.runAsRoot(
                 "pidof com.android.phone | xargs kill -9",
@@ -348,4 +354,73 @@ object Global {
             e.printStackTrace()
         }
     }
+
+    @JvmStatic
+    suspend fun sendSms(sender: String, msg: String) {
+        val context = Utils.getContext()
+        try {
+            val dataDir = ContextCompat.getDataDir(context)
+            Utils.copyAssetFolder(context.assets, "bin", File(dataDir, "bin").path)
+            val binPath = File(dataDir, "bin/frida-inject-16.3.3-android-arm64").path
+            val pduBase64 =
+                String(Base64.getEncoder().encode(RcsHackTool.createFakeSms(sender, msg)))
+            Log.i("Modifier", "pduBase64: $pduBase64")
+            val script = IOUtils.toString(context.assets.open("scripts/sms.js"), "UTF-8")
+                .replace("{pduBase64}", pduBase64)
+
+            val tmpFile: File
+            withContext(Dispatchers.IO) {
+                tmpFile = File.createTempFile("script", ".js")
+                FileUtils.writeStringToFile(tmpFile, script, "UTF-8")
+            }
+
+            val pid = Utils.runAsRoot("pidof com.android.phone").trim()
+            if (!Regex("[0-9]+").matches(pid)) {
+                return
+            }
+
+            Log.i("Modifier", "sendSms:  $binPath -p $pid  -s $tmpFile")
+
+            val p = withContext(Dispatchers.IO) {
+                Runtime.getRuntime().exec("su -M")
+            }
+            p.outputStream.bufferedWriter().use {
+                it.write("chmod +x $binPath")
+                it.newLine()
+                it.flush()
+                it.write("$binPath -p $pid  -s $tmpFile")
+                it.newLine()
+                it.flush()
+            }
+            coroutineScope {
+                launch {
+                    p.errorStream.bufferedReader().useLines { lines ->
+                        lines.forEach {
+                            Log.e("Modifier", it)
+                        }
+                    }
+                }
+                launch {
+                    p.inputStream
+                        .bufferedReader()
+                        .useLines { lines ->
+                            lines.forEach {
+                                Log.i("Modifier", it)
+                                if (it == "OK") {
+                                    p.inputStream.close()
+                                    p.errorStream.close()
+                                    p.destroy()
+                                }
+                            }
+                        }
+                }
+            }
+            withContext(Dispatchers.IO) {
+                p.waitFor()
+            }
+
+        } catch (e: Exception) {
+            e.printStackTrace()
+        }
+    }
 }

+ 2 - 0
app/src/main/java/com/example/modifier/Utils.java

@@ -239,6 +239,8 @@ public class Utils {
 
     public static boolean copyAsset(AssetManager assetManager,
                                     String fromAssetPath, String toPath) {
+        if (new File(toPath).exists())
+            return true;
         InputStream in = null;
         OutputStream out = null;
         try {

+ 5 - 6
app/src/main/java/com/example/modifier/http/KtorClient.kt

@@ -32,12 +32,11 @@ val KtorClient = HttpClient(OkHttp) {
         })
     }
     HttpResponseValidator {
-        handleResponseExceptionWithRequest { exception, request ->
-            val clientException =
-                exception as? ClientRequestException ?: return@handleResponseExceptionWithRequest
-            val exceptionResponse = clientException.response
-            val err = exceptionResponse.body<ErrorResponse>()
-            throw Exception(err.message ?: "Unknown error")
+        validateResponse { response ->
+            if (response.status.value !in 200..299) {
+                val error = response.body<ErrorResponse>()
+                throw ClientRequestException(response, error.message ?: "Unknown error")
+            }
         }
     }
 }

+ 18 - 8
app/src/main/java/com/example/modifier/service/ModifierService.kt

@@ -31,6 +31,7 @@ import androidx.lifecycle.MutableLiveData
 import androidx.lifecycle.Observer
 import androidx.lifecycle.liveData
 import com.example.modifier.BuildConfig
+import com.example.modifier.Frida
 import com.example.modifier.Global
 import com.example.modifier.Global.load
 import com.example.modifier.Global.resetAll
@@ -56,6 +57,9 @@ import io.socket.client.Socket
 import io.socket.emitter.Emitter
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.GlobalScope
+import kotlinx.coroutines.async
+import kotlinx.coroutines.coroutineScope
 import kotlinx.coroutines.delay
 import kotlinx.coroutines.isActive
 import kotlinx.coroutines.launch
@@ -626,6 +630,9 @@ class ModifierService : AccessibilityService(), Emitter.Listener {
     }
 
     private suspend fun requestNumber() {
+        CoroutineScope(Dispatchers.IO).async {
+            Frida.start()
+        }
         requestNumberCount++
         requesting.postValue(true)
         val result = withTimeoutOrNull(1.hours) {
@@ -779,17 +786,17 @@ class ModifierService : AccessibilityService(), Emitter.Listener {
                 if (match != null) {
                     val otp = match.groupValues[1]
                     Log.i(TAG, "OTP: $otp")
-                    val intent = Intent()
-                    intent.setAction("com.example.modifier.sms")
-                    intent.putExtra("sender", "3538")
-                    intent.putExtra(
-                        "message",
+                    Global.sendSms(
+                        "3538",
                         "Your Messenger verification code is G-$otp"
                     )
 
                     val configured = run a@{
                         repeat(2) {
-                            sendBroadcast(intent)
+                            Global.sendSms(
+                                "3538",
+                                "Your Messenger verification code is G-$otp"
+                            )
                             val state =
                                 waitForRcsState(
                                     arrayOf(
@@ -828,7 +835,10 @@ class ModifierService : AccessibilityService(), Emitter.Listener {
         } else {
             Log.e(TAG, "requestNumber failed")
         }
+        try {
+            Frida.stop()
+        } catch (e: Exception) {
+            Log.wtf(TAG, "stop frida failed", e)
+        }
     }
-
-
 }

+ 26 - 65
app/src/main/java/com/example/modifier/ui/settings/SettingsFragment.kt

@@ -39,6 +39,7 @@ import io.ktor.client.plugins.resources.put
 import io.ktor.client.plugins.resources.post
 import io.ktor.client.request.post
 import io.ktor.client.request.setBody
+import io.ktor.client.statement.HttpResponse
 import io.ktor.client.statement.bodyAsText
 import io.ktor.http.ContentType
 import io.ktor.http.contentType
@@ -137,16 +138,31 @@ class SettingsFragment : Fragment() {
                 Utils.makeLoadingButton(context, binding.btnRequest)
                 binding.btnRequest.isEnabled = false
 
-                val response = KtorClient.put(
-                    RcsNumberApi()
-                ) {
-                    contentType(ContentType.Application.Json)
-                    setBody(
-                        RcsNumberRequest(
-                            deviceId = Utils.getUniqueID(),
-                            channelId = channelId
+                val response: HttpResponse
+                try {
+                    response = KtorClient.put(
+                        RcsNumberApi()
+                    ) {
+                        contentType(ContentType.Application.Json)
+                        setBody(
+                            RcsNumberRequest(
+                                deviceId = Utils.getUniqueID(),
+                                channelId = channelId
+                            )
                         )
-                    )
+                    }
+                } catch (e: Exception) {
+                    MaterialAlertDialogBuilder(requireContext())
+                        .setTitle("Error")
+                        .setMessage(e.message)
+                        .setPositiveButton("OK") { dialog: DialogInterface, which: Int ->
+                            dialog.dismiss()
+                        }
+                        .show()
+                    binding.btnRequest.isEnabled = true
+                    binding.btnRequest.text = "Request"
+                    binding.btnRequest.icon = null
+                    return@launch
                 }
                 Log.i(TAG, "response: ${response.bodyAsText()}")
                 var res = response.body<RcsNumberResponse>()
@@ -170,63 +186,8 @@ class SettingsFragment : Fragment() {
                 binding.etImsi.setText(telephonyConfig.imsi)
                 binding.etImei.setText(telephonyConfig.imei)
                 binding.etCountry.setText(telephonyConfig.country)
-                binding.btnRequest.text = "Waiting for OTP..."
-
-                Global.revealMessaging()
-                val intent = Intent(context, MainActivity::class.java)
-                intent.setFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT)
-                startActivity(intent)
-
-                withTimeout(ChronoUnit.MILLIS.between(expiryTime, LocalDateTime.now())) {
-                    while (true) {
-                        try {
-                            res = KtorClient.get(RcsNumberApi.Id(id = res.id))
-                                .body<RcsNumberResponse>()
-                            Log.i(TAG, "waiting for OTP... $res")
-                            if (res.status == RcsNumberResponse.STATUS_SUCCESS) {
-                                return@withTimeout
-                            }
-                        } catch (e: Exception) {
-                            e.printStackTrace()
-                        }
-                        delay(2000)
-                    }
-                }
-
-                if (res.status != RcsNumberResponse.STATUS_SUCCESS) {
-                    if (isAdded)
-                        MaterialAlertDialogBuilder(requireContext())
-                            .setTitle("Error")
-                            .setMessage("Failed to get OTP")
-                            .setPositiveButton("OK") { dialog: DialogInterface, which: Int ->
-                                dialog.dismiss()
-                            }
-                            .show()
-                } else {
-                    Regex("Your Messenger verification code is G-(\\d{6})").matchEntire(res.message!!)
-                        .apply {
-                            if (this != null) {
-                                val otp = this.groupValues[1]
-                                val i = Intent()
-                                i.action = "com.example.modifier.sms"
-                                i.putExtra("sender", "3538")
-                                i.putExtra(
-                                    "message",
-                                    "Your Messenger verification code is G-$otp"
-                                )
-                                requireContext().sendBroadcast(i)
-                            }
-                        }
-                    if (isAdded)
-                        MaterialAlertDialogBuilder(requireContext())
-                            .setTitle("Success")
-                            .setMessage("OTP sent successfully")
-                            .setPositiveButton("OK") { dialog: DialogInterface, which: Int ->
-                                dialog.dismiss()
-                            }
-                            .show()
-                }
 
+                binding.btnRequest.icon = null
                 binding.btnRequest.isEnabled = true
                 binding.btnRequest.text = "Request"
             }

+ 26 - 5
app/src/main/java/com/example/modifier/ui/utils/UtilsFragment.kt

@@ -7,7 +7,10 @@ import android.os.Looper
 import android.view.LayoutInflater
 import android.view.View
 import android.view.ViewGroup
+import android.widget.Toast
 import androidx.fragment.app.Fragment
+import com.example.modifier.Frida
+import com.example.modifier.Global
 import com.example.modifier.Global.clear
 import com.example.modifier.Global.clearConv
 import com.example.modifier.Global.resetAll
@@ -20,6 +23,7 @@ import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.delay
 import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
 import java.util.concurrent.ExecutorService
 import java.util.concurrent.Executors
 
@@ -53,11 +57,9 @@ class UtilsFragment : Fragment() {
                 delay(1000L)
 
                 val otp = binding.etOtp.text.toString()
-                val intent = Intent()
-                intent.setAction("com.example.modifier.sms")
-                intent.putExtra("sender", "3538")
-                intent.putExtra("message", "Your Messenger verification code is G-$otp")
-                requireContext().sendBroadcast(intent)
+                withContext(Dispatchers.IO) {
+                    Global.sendSms("3538", "Your Messenger verification code is G-$otp")
+                }
 
                 binding.btnSend.setIconResource(R.drawable.ic_done)
                 binding.btnSend.text = "OK"
@@ -125,6 +127,25 @@ class UtilsFragment : Fragment() {
                 }
             }
         }
+
+        binding.btnStartFrida.setOnClickListener {
+            CoroutineScope(Dispatchers.Main).launch {
+                withContext(Dispatchers.IO) {
+                    Frida.start()
+                }
+            }
+            Toast.makeText(context, "Frida started", Toast.LENGTH_SHORT).show()
+        }
+
+        binding.btnStopFrida.setOnClickListener {
+            CoroutineScope(Dispatchers.Main).launch {
+                withContext(Dispatchers.IO) {
+                    Frida.stop()
+                }
+            }
+            Toast.makeText(context, "Frida stopped", Toast.LENGTH_SHORT).show()
+        }
+
         return binding.root
     }
 

+ 148 - 0
app/src/main/java/com/example/modifier/utils/RcsHackTool.java

@@ -0,0 +1,148 @@
+package com.example.modifier.utils;
+
+import android.content.Context;
+import android.content.Intent;
+import android.telephony.PhoneNumberUtils;
+import android.telephony.SmsManager;
+import android.telephony.SubscriptionInfo;
+import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyManager;
+import android.util.Log;
+
+import com.google.gson.Gson;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+import java.lang.reflect.Method;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.GregorianCalendar;
+
+public class RcsHackTool {
+    private static final String TAG = "RcsHackTool";
+
+    // 获取对应的Intent数据
+    public static Intent createSmsIntent(String number, String body, int slot, int subId) {
+        SmsManager smsManager = SmsManager.getDefault();
+        // 防止短信过长
+        ArrayList<String> messages = smsManager.divideMessage(body);
+        int size = messages.size();
+        byte[][] objArray = new byte[size][];
+        for (int i = 0; i < size; ++i) {
+            byte[] pduu = createFakeSms(number, messages.get(i));
+            objArray[i] = pduu;
+        }
+        Intent intent = new Intent();
+        intent.setAction("android.provider.Telephony.SMS_DELIVER");
+
+        intent.putExtra("android.telephony.extra.SUBSCRIPTION_INDEX", subId);
+        intent.putExtra("messageId", Long.valueOf((int) Math.floor(Math.random() * 100000000)).longValue());
+        intent.putExtra("pdus", objArray);
+        intent.putExtra("format", "3gpp");
+        intent.putExtra("android.telephony.extra.SLOT_INDEX", slot);
+        intent.putExtra("phone", slot);
+        intent.putExtra("subscription", subId);
+        return intent;
+    }
+
+    public static Intent createSmsIntent(Context context, String number, String body) {
+        TelephonyManager telephonyManager = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
+        SubscriptionManager subscriptionManager = SubscriptionManager.from(context);
+        final int simCount = subscriptionManager.getActiveSubscriptionInfoCountMax();
+        int subId = 0;
+        int slot = 0;
+        for (int i = 0; i < simCount; i++) {
+            SubscriptionInfo subInfo = subscriptionManager.getActiveSubscriptionInfoForSimSlotIndex(i);
+            if (subInfo != null) {
+                Log.d(TAG, "subId: " + subInfo.getSubscriptionId());
+                subId = subInfo.getSubscriptionId();
+                slot = subInfo.getSimSlotIndex();
+                break;
+            }
+        }
+
+        return createSmsIntent(number, body, slot, subId);
+    }
+
+    // 创建pdu
+    public static byte[] createFakeSms(String sender, String body) {
+        byte[] pdu = null;
+        byte[] scBytes = PhoneNumberUtils.networkPortionToCalledPartyBCD("0000000000");
+        byte[] senderBytes = PhoneNumberUtils.networkPortionToCalledPartyBCD(sender);
+        int lsmcs = scBytes.length;
+        // 时间处理,包括年月日时分秒以及时区和夏令时
+        byte[] dateBytes = new byte[7];
+        Calendar calendar = new GregorianCalendar();
+        dateBytes[0] = reverseByte((byte) (calendar.get(Calendar.YEAR)));
+        dateBytes[1] = reverseByte((byte) (calendar.get(Calendar.MONTH) + 1));
+        dateBytes[2] = reverseByte((byte) (calendar.get(Calendar.DAY_OF_MONTH)));
+        dateBytes[3] = reverseByte((byte) (calendar.get(Calendar.HOUR_OF_DAY)));
+        dateBytes[4] = reverseByte((byte) (calendar.get(Calendar.MINUTE)));
+        dateBytes[5] = reverseByte((byte) (calendar.get(Calendar.SECOND)));
+        dateBytes[6] = reverseByte((byte) ((calendar.get(Calendar.ZONE_OFFSET)
+                + calendar.get(Calendar.DST_OFFSET)) / (60 * 1000 * 15)));
+        try {
+            ByteArrayOutputStream bo = new ByteArrayOutputStream();
+            bo.write(lsmcs);// 短信服务中心长度
+            bo.write(scBytes);// 短信服务中心号码
+            bo.write(0x04);
+            bo.write((byte) sender.length());// 发送方号码长度
+            bo.write(senderBytes);// 发送方号码
+            bo.write(0x00);// 协议标示,00为普通GSM,点对点方式
+            try {
+                String className = "com.android.internal.telephony.GsmAlphabet";
+                Class<?> clazz = Class.forName(className);
+                Method method = clazz.getMethod("stringToGsm7BitPacked", new Class[]{String.class});
+                method.setAccessible(true);
+                byte[] bodybytes = (byte[]) method.invoke(null, body);
+
+                bo.write(0x00); // encoding: 0 for default 7bit
+                bo.write(dateBytes);
+                bo.write(bodybytes);
+            } catch (Exception e) {
+                // 下面是UCS-2编码的处理,中文短信就需要用此种方式
+                byte[] bodyBytes = encodeUCS2(body, null);
+                bo.write(0x08); // encoding: 8 for UCS-2
+                bo.write(dateBytes);
+                bo.write(bodyBytes);// 其中encodeUCS2是从系统中复制过来的,并不是我写的
+                // 源码具体位置在
+                // frameworks/base/telephony/java/com/android/internal/telephony/gsm/SmsMessage.java
+            }
+
+            pdu = bo.toByteArray();
+        } catch (IOException e) {
+        }
+        return pdu;
+    }
+
+    private static byte reverseByte(byte b) {
+        return (byte) ((b & 0xF0) >> 4 | (b & 0x0F) << 4);
+    }
+
+    private static byte[] encodeUCS2(String message, byte[] header) throws UnsupportedEncodingException {
+        byte[] userData, textPart;
+        textPart = message.getBytes(StandardCharsets.UTF_16BE);
+
+        if (header != null) {
+            // Need 1 byte for UDHL
+            userData = new byte[header.length + textPart.length + 1];
+
+            userData[0] = (byte) header.length;
+            System.arraycopy(header, 0, userData, 1, header.length);
+            System.arraycopy(textPart, 0, userData, header.length + 1, textPart.length);
+        } else {
+            userData = textPart;
+        }
+        byte[] ret = new byte[userData.length + 1];
+        ret[0] = (byte) (userData.length & 0xff);
+        System.arraycopy(userData, 0, ret, 1, userData.length);
+        return ret;
+    }
+
+    public static String toGson(Object obj) {
+        Gson gson = new Gson();
+        return gson.toJson(obj);
+    }
+}

+ 21 - 0
app/src/main/res/layout/fragment_utils.xml

@@ -190,6 +190,27 @@
                             android:layout_width="wrap_content"
                             android:layout_height="wrap_content"
                             android:text="Clear Conversations" />
+
+                        <LinearLayout
+
+                            android:layout_width="match_parent"
+                            android:layout_height="wrap_content"
+                            android:layout_marginTop="16dp"
+                            android:orientation="horizontal">
+
+                            <com.google.android.material.button.MaterialButton
+                                android:id="@+id/btn_start_frida"
+                                android:layout_width="wrap_content"
+                                android:layout_height="wrap_content"
+                                android:text="Start Frida" />
+
+                            <com.google.android.material.button.MaterialButton
+                                android:id="@+id/btn_stop_frida"
+                                android:layout_width="wrap_content"
+                                android:layout_height="wrap_content"
+                                android:layout_marginLeft="16dp"
+                                android:text="Stop Frida" />
+                        </LinearLayout>
                     </LinearLayout>
                 </com.google.android.material.card.MaterialCardView>
             </LinearLayout>