xiongzhu 11 luni în urmă
părinte
comite
d9f99131e6

+ 1 - 0
app/build.gradle

@@ -79,4 +79,5 @@ dependencies {
     testImplementation libs.junit
     androidTestImplementation libs.androidx.junit
     androidTestImplementation libs.androidx.espresso.core
+    implementation 'org.apache.commons:commons-lang3:3.14.0'
 }

+ 2 - 0
app/src/main/AndroidManifest.xml

@@ -16,6 +16,7 @@
     <uses-permission android:name="android.permission.WRITE_EMBEDDED_SUBSCRIPTIONS" />
     <uses-permission android:name="android.permission.SECURE_ELEMENT_PRIVILEGED_OPERATION" />
     <uses-permission android:name="android.permission.INTERNET" />
+    <uses-permission android:name="android.permission.READ_SMS"/>
 
     <application
         android:allowBackup="true"
@@ -34,6 +35,7 @@
 
         <activity
             android:name=".MainActivity"
+            android:windowSoftInputMode="adjustPan"
             android:exported="true">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />

+ 0 - 5
app/src/main/java/com/example/uicceditor/ApduChannel.kt

@@ -86,11 +86,6 @@ class ApduChannel(private val telephonyManager: TelephonyManager, aid: String) {
         }
     }
 
-    fun reset() {
-//        execute(0x00, 0xE0, 0x00, 0x00, 0x00, "")
-        telephonyManager.rebootModem()
-    }
-
     fun select(fid: String) {
         if (execute(0x80, INS_SEL, 0x00, 0x00, fid.length / 2, fid).sw != "9000") {
             throw Exception("Failed to select file")

+ 63 - 6
app/src/main/java/com/example/uicceditor/Encoder.kt

@@ -161,9 +161,8 @@ fun decPLMN(efPlmn: String): String? {
         return null
     }
     return efPlmn.stripF().chunked(6).joinToString(",") {
-        val str = it.swapNibbles()
-        val mcc = str.substring(0, 3)
-        val mnc = str.substring(3).stripF()
+        val mcc = decMcc(it)
+        val mnc = decMnc(it)
         "$mcc$mnc"
     }
 }
@@ -175,10 +174,68 @@ fun decPLMNwAcT(efPlmn: String): String? {
     return efPlmn.chunked(10)
         .filter { !it.substring(0, 6).matches(Regex("^f{3,}", RegexOption.IGNORE_CASE)) }
         .joinToString(",") {
-            val str = it.substring(0, 6).swapNibbles()
-            val mcc = str.substring(0, 3)
-            val mnc = str.substring(3).stripF()
+            val mcc = decMcc(it)
+            val mnc = decMnc(it)
             val act = it.substring(6)
             "$mcc$mnc:$act"
         }
+}
+
+fun encPLMN(plmn: String): String {
+    return plmn.split(",", ",").map { it.trim() }
+        .filter { it.matches(Regex("\\d{5,6}")) }
+        .joinToString("") { encMccMnc(it) }
+}
+
+fun encPLMNwAcT(plmn: String): String {
+    return plmn.split(",", ",").map { it.trim() }
+        .filter { it.matches(Regex("\\d{5,6}:\\d{4}")) }
+        .joinToString("") {
+            it.split(":")
+                .let { (plmn, act) ->
+                    "${encMccMnc(plmn)}${act}"
+                }
+        }
+}
+
+fun decMcc(plmn: String): String {
+    val digit1 = plmn[1]  // 1st byte, LSB
+    val digit2 = plmn[0]  // 1st byte, MSB
+    val digit3 = plmn[3]  // 2nd byte, LSB
+    val res = "$digit1$digit2$digit3"
+    return res.stripF()
+}
+
+fun decMnc(plmn: String): String {
+    val digit1 = plmn[5]  // 3rd byte, LSB
+    val digit2 = plmn[4]  // 3rd byte, MSB
+    val digit3 = plmn[2]  // 2nd byte, MSB
+    val res = "$digit1$digit2$digit3"
+    return res.stripF()
+}
+
+fun encMccMnc(mccmnc: String): String {
+    if (!mccmnc.matches(Regex("^\\d{5,6}$"))) {
+        throw IllegalArgumentException("mccmnc must be 5 or 6 digits")
+    }
+    // Make sure there are no excess whitespaces in the input parameters
+    var mccVar = mccmnc.substring(0, 3)
+    var mncVar = mccmnc.substring(3)
+
+    // Make sure that MCC/MNC are correctly padded with leading zeros or 'F', depending on the length
+    mncVar = when (mncVar.length) {
+        0 -> "FFF"
+        1 -> "0$mncVar" + "F"
+        2 -> mncVar + "F"
+        else -> mncVar
+    }
+
+    mccVar = when (mccVar.length) {
+        0 -> "FFF"
+        1 -> "00$mccVar"
+        2 -> "0$mccVar"
+        else -> mccVar
+    }
+
+    return "${mccVar[1]}${mccVar[0]}${mncVar[2]}${mccVar[2]}${mncVar[1]}${mncVar[0]}"
 }

+ 137 - 26
app/src/main/java/com/example/uicceditor/MainActivity.kt

@@ -1,30 +1,27 @@
 package com.example.uicceditor
 
 import android.app.PendingIntent
+import android.content.Context
 import android.content.Intent
+import android.net.Uri
 import android.os.Bundle
 import android.os.Handler
 import android.os.Looper
-import android.telephony.AccessNetworkConstants
-import android.telephony.CarrierConfigManager
-import android.telephony.CellInfo
-import android.telephony.NetworkScanRequest
-import android.telephony.RadioAccessSpecifier
 import android.telephony.SmsManager
 import android.telephony.SmsMessage
+import android.telephony.SubscriptionManager
 import android.telephony.TelephonyManager
-import android.telephony.TelephonyManager.INCLUDE_LOCATION_DATA_NONE
-import android.telephony.TelephonyScanManager.NetworkScanCallback
 import android.util.Log
 import android.widget.Toast
 import androidx.activity.enableEdgeToEdge
 import androidx.appcompat.app.AppCompatActivity
-import androidx.arch.core.executor.TaskExecutor
-import androidx.core.os.ExecutorCompat
 import androidx.core.view.ViewCompat
 import androidx.core.view.WindowInsetsCompat
+import com.example.uicceditor.ApduChannel.Companion.INS_SEL
+import com.example.uicceditor.ApduChannel.Companion.INS_WR
 import com.example.uicceditor.databinding.ActivityMainBinding
 
+
 class MainActivity : AppCompatActivity() {
     private val TAG = "APDU"
 
@@ -66,6 +63,10 @@ class MainActivity : AppCompatActivity() {
             }
         }
 
+        binding.tlIccid.setEndIconOnClickListener {
+            binding.etIccid.setText(genICCID("310", "1"))
+        }
+
         binding.btnReadImsi.setOnClickListener {
             val apduChannel = ApduChannel(telephonyManager, AID)
             val imsi = apduChannel.readIMSI()
@@ -90,6 +91,10 @@ class MainActivity : AppCompatActivity() {
             }
         }
 
+        binding.tlImsi.setEndIconOnClickListener {
+            binding.etImsi.setText(genIMSI("310210"))
+        }
+
         binding.btnReadMsisdn.setOnClickListener {
             val apduChannel = ApduChannel(telephonyManager, AID)
             apduChannel.select(SIMView.FID_MF)
@@ -109,6 +114,24 @@ class MainActivity : AppCompatActivity() {
             apduChannel.select(SIMView.FID_EF_MSISDN)
             apduChannel.writeRecord(1, encMSISDN(msisdn).padStart(56, 'F'))
             apduChannel.close()
+
+//            val subscriptionManager =
+//                getSystemService("telephony_subscription_service") as SubscriptionManager
+//            val simCount = subscriptionManager.getActiveSubscriptionInfoCountMax();
+//            telephonyManager.setLine1NumberForDisplay(null, msisdn)
+
+
+            val apduChannel1 = ApduChannel(telephonyManager, "a0000000871002ff86ffff89ffffffff00")
+            apduChannel1.execute(0x00, INS_SEL, 0x00, 0x04, 0x02, SIMView.FID_EF_MSISDN)
+            apduChannel1.execute(
+                0x00,
+                INS_WR,
+                0x01,
+                0x04,
+                0x1C,
+                encMSISDN(msisdn).padStart(56, 'F')
+            )
+            apduChannel.close()
         }
 
         binding.btnReadPlmn.setOnClickListener {
@@ -120,6 +143,69 @@ class MainActivity : AppCompatActivity() {
             apduChannel.close()
         }
 
+        binding.btnWritePlmn.setOnClickListener {
+            val plmn = encPLMN(binding.etPlmn.text.toString())
+            Log.i(TAG, "btnWritePlmn: $plmn")
+            val apduChannel = ApduChannel(telephonyManager, AID)
+            apduChannel.select(SIMView.FID_MF)
+            apduChannel.select(SIMView.FID_DF_GSM)
+            apduChannel.select(SIMView.FID_EF_PLMNSEL)
+            apduChannel.writeBinary(plmn.padEnd(30, 'f'))
+            apduChannel.close()
+        }
+
+        binding.btnWritePlmnAll.setOnClickListener {
+            val plmn = binding.etPlmn.text.toString().split(",", ",").map { it.trim() }
+                .filter { it.matches(Regex("\\d{5,6}")) }
+            if (plmn.isEmpty()) {
+                Toast.makeText(this, "Invalid PLMN", Toast.LENGTH_SHORT).show()
+                return@setOnClickListener
+            }
+            val plmnHex = encPLMN(plmn.joinToString(","))
+            Log.i(TAG, "plmnHex: $plmnHex")
+
+            val plmnwactHex = encPLMNwAcT(plmn.flatMap {
+                listOf("${it}:4000,$it:8000,$it:0080")
+            }.joinToString(","))
+            Log.i(TAG, "plmnwactHex: $plmnwactHex")
+
+            val fplmn =
+                encPLMN("46000,46001,46002,46006,46007,46011,46012,46015,46020")
+
+            val apduChannel = ApduChannel(telephonyManager, AID)
+            apduChannel.select(SIMView.FID_MF)
+            apduChannel.select(SIMView.FID_DF_GSM)
+            apduChannel.select(SIMView.FID_EF_PLMNSEL)
+            apduChannel.writeBinary(plmnHex.padEnd(120, 'f'))
+
+            apduChannel.select(SIMView.FID_EF_EHPLMN)
+            apduChannel.writeBinary(plmnHex.padEnd(24, 'f'))
+
+            apduChannel.select(SIMView.FID_EF_PLMNWACT)
+            apduChannel.writeBinary(plmnwactHex.padEnd(240, 'f'))
+
+            apduChannel.select(SIMView.FID_EF_OPLMNWACT)
+            apduChannel.writeBinary(plmnwactHex.padEnd(120, 'f'))
+
+            apduChannel.select(SIMView.FID_EF_HPLMNWACT)
+            apduChannel.writeBinary(plmnwactHex.padEnd(40, 'f'))
+
+            apduChannel.select(SIMView.FID_EF_FPLMN)
+            apduChannel.writeBinary(fplmn.padEnd(60, 'f'))
+
+            if (plmn.isNotEmpty()) {
+                if (plmn[0].length == 5) {
+                    apduChannel.select(SIMView.FID_EF_AD)
+                    apduChannel.writeBinary("00000102")
+                } else if (plmn[0].length == 6) {
+                    apduChannel.select(SIMView.FID_EF_AD)
+                    apduChannel.writeBinary("00000103")
+                }
+            }
+
+            apduChannel.close()
+        }
+
         binding.btnReadEhplmn.setOnClickListener {
             val apduChannel = ApduChannel(telephonyManager, AID)
             apduChannel.select(SIMView.FID_MF)
@@ -138,6 +224,17 @@ class MainActivity : AppCompatActivity() {
             apduChannel.close()
         }
 
+        binding.btnWriteFplmn.setOnClickListener {
+            val plmn = encPLMN(binding.etFplmn.text.toString())
+            Log.i(TAG, "btnWritePlmn: $plmn")
+            val apduChannel = ApduChannel(telephonyManager, AID)
+            apduChannel.select(SIMView.FID_MF)
+            apduChannel.select(SIMView.FID_DF_GSM)
+            apduChannel.select(SIMView.FID_EF_FPLMN)
+            apduChannel.writeBinary(plmn.padEnd(60, 'f'))
+            apduChannel.close()
+        }
+
         binding.btnReadPlmnwact.setOnClickListener {
             val apduChannel = ApduChannel(telephonyManager, AID)
             apduChannel.select(SIMView.FID_MF)
@@ -166,26 +263,28 @@ class MainActivity : AppCompatActivity() {
         }
 
         binding.btnReset.setOnClickListener {
-            val apduChannel = ApduChannel(telephonyManager, AID)
-            apduChannel.reset()
-            apduChannel.close()
+            telephonyManager.rebootModem()
         }
 
         binding.btnSms.setOnClickListener {
-
-            val smsManager = getSystemService("sms") as SmsManager
-            val pendingIntent = PendingIntent.getBroadcast(
-                this,
-                1,
-                Intent("${packageName}.MyReceiver"),
-                PendingIntent.FLAG_IMMUTABLE
-            )
-            smsManager.injectSmsPdu(
-                SmsUtils.createFakeSms("123", "hello"),
-                SmsMessage.FORMAT_3GPP,
-                pendingIntent
-            )
-
+            val sender = binding.etSender.text.toString()
+            val body = binding.etSms.text.toString()
+            if (sender.isNotEmpty() && body.isNotEmpty()) {
+                val smsManager = (getSystemService("sms") as SmsManager).createForSubscriptionId(12)
+                val pendingIntent = PendingIntent.getBroadcast(
+                    this,
+                    1,
+                    Intent("${packageName}.MyReceiver"),
+                    PendingIntent.FLAG_IMMUTABLE
+                )
+                smsManager.injectSmsPdu(
+                    SmsUtils.createFakeSms(sender, body),
+                    SmsMessage.FORMAT_3GPP,
+                    pendingIntent
+                )
+            } else {
+                Toast.makeText(this, "Sender or body is empty", Toast.LENGTH_SHORT).show()
+            }
         }
     }
 
@@ -195,4 +294,16 @@ class MainActivity : AppCompatActivity() {
             .map { allowedChars.random() }
             .joinToString("")
     }
+
+    override fun onRequestPermissionsResult(
+        requestCode: Int,
+        permissions: Array<out String>,
+        grantResults: IntArray
+    ) {
+        super.onRequestPermissionsResult(requestCode, permissions, grantResults)
+        Log.i(
+            TAG,
+            "requestCode: $requestCode, permissions: $permissions, grantResults: $grantResults"
+        )
+    }
 }

+ 132 - 0
app/src/main/java/com/example/uicceditor/SimInfo.kt

@@ -0,0 +1,132 @@
+package com.example.uicceditor
+
+import org.apache.commons.lang3.RandomStringUtils
+import java.util.Locale
+import kotlin.math.floor
+
+fun genIMEI(): String {
+    val str = ("35684610" + RandomStringUtils.randomNumeric(7)).map { it.toString().toInt() }
+        .toIntArray()
+    var sum = 0
+    var t: Int
+    val len = 15
+    var imei = ""
+
+    var pos = 0
+    while (pos < len - 1) {
+        if ((pos % 2) != 0) {
+            t = str[pos] * 2
+            if (t > 9) {
+                t -= 9
+            }
+            sum += t
+        } else {
+            sum += str[pos]
+        }
+        pos++
+    }
+
+    val finalDigit = (10 - (sum % 10)) % 10
+    str[len - 1] = finalDigit
+
+    for (d in str) {
+        imei += d.toString()
+    }
+
+    return imei
+}
+
+fun generateIMEI1(): String {
+    val str = intArrayOf(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)
+    var sum = 0
+    var t: Int
+    val len_offset: Int
+    val len = 15
+    var imei = ""
+
+    val rbi = arrayOf(
+        "01",
+        "10",
+        "30",
+        "33",
+        "35",
+        "44",
+        "45",
+        "49",
+        "50",
+        "51",
+        "52",
+        "53",
+        "54",
+        "86",
+        "91",
+        "98",
+        "99"
+    )
+    val arr = rbi[floor(Math.random() * rbi.size)
+        .toInt()].split("".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()
+    str[0] = arr[0].toInt()
+    str[1] = arr[1].toInt()
+    var pos = 2
+
+    while (pos < len - 1) {
+        str[pos++] = (floor(Math.random() * 10) % 10).toInt()
+    }
+
+    len_offset = (len + 1) % 2
+    pos = 0
+    while (pos < len - 1) {
+        if ((pos + len_offset) % 2 != 0) {
+            t = str[pos] * 2
+            if (t > 9) {
+                t -= 9
+            }
+            sum += t
+        } else {
+            sum += str[pos]
+        }
+        pos++
+    }
+
+    val final_digit = (10 - (sum % 10)) % 10
+    str[len - 1] = final_digit
+
+    for (d in str) {
+        imei += d.toString()
+    }
+
+    return imei
+}
+
+fun genICCID(mnc: String, areaCode: String): String {
+    val prefix = String.format(Locale.US, "89%02d%s", areaCode.toInt(), mnc)
+    return luhn(prefix + RandomStringUtils.randomNumeric(20 - prefix.length))
+}
+
+fun genIMSI(mccmnc: String): String {
+    return luhn(mccmnc + RandomStringUtils.randomNumeric(15 - mccmnc.length))
+}
+
+fun luhn(number: String): String {
+    if (!number.matches(Regex("[0-9]+"))) {
+        throw IllegalArgumentException("Invalid number: $number")
+    }
+    val checks = "0987654321".toCharArray()
+    val chs = number.toCharArray()
+    var count = 0
+
+    var i = chs.size - 2
+    var k = 1
+    while (i >= 0) {
+        // 偶位数字 * 2,奇位不乘
+        val num = (chs[i].code - '0'.code) shl (k and 1)
+
+        // 累加
+        count += num % 10 + num / 10
+        i--
+        k++
+    }
+
+    chs[chs.size - 1] = checks[count % 10]
+    return String(chs)
+}

+ 1 - 1
app/src/main/java/com/example/uicceditor/SmsUtils.java

@@ -82,7 +82,7 @@ public class SmsUtils {
                     Log.d("stringToGsm7BitPacked", "createFakeSms: " + same);
                 }
 
-                bo.write(0x00); // encoding: 0 for default 7bit
+                bo.write(0xf1); // encoding: 0 for default 7bit
                 bo.write(dateBytes);
                 bo.write(bodybytes);
             } catch (Exception e) {

+ 10 - 0
app/src/main/res/drawable/ic_refresh.xml

@@ -0,0 +1,10 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="960"
+    android:viewportHeight="960"
+    android:tint="?attr/colorPrimary">
+  <path
+      android:fillColor="@android:color/white"
+      android:pathData="M480,800Q346,800 253,707Q160,614 160,480Q160,346 253,253Q346,160 480,160Q549,160 612,188.5Q675,217 720,270L720,160L800,160L800,440L520,440L520,360L688,360Q656,304 600.5,272Q545,240 480,240Q380,240 310,310Q240,380 240,480Q240,580 310,650Q380,720 480,720Q557,720 619,676Q681,632 706,560L790,560Q762,666 676,733Q590,800 480,800Z"/>
+</vector>

+ 49 - 10
app/src/main/res/layout/activity_main.xml

@@ -20,10 +20,13 @@
             android:paddingHorizontal="16dp">
 
             <com.google.android.material.textfield.TextInputLayout
+                android:id="@+id/tl_iccid"
                 android:layout_width="match_parent"
                 android:layout_height="wrap_content"
                 android:layout_marginTop="8dp"
-                android:hint="ICCID">
+                android:hint="ICCID"
+                app:endIconDrawable="@drawable/ic_refresh"
+                app:endIconMode="custom">
 
                 <com.google.android.material.textfield.TextInputEditText
                     android:id="@+id/et_iccid"
@@ -55,10 +58,13 @@
             </LinearLayout>
 
             <com.google.android.material.textfield.TextInputLayout
+                android:id="@+id/tl_imsi"
                 android:layout_width="match_parent"
                 android:layout_height="wrap_content"
                 android:layout_marginTop="16dp"
-                android:hint="IMSI">
+                android:hint="IMSI"
+                app:endIconDrawable="@drawable/ic_refresh"
+                app:endIconMode="custom">
 
                 <com.google.android.material.textfield.TextInputEditText
                     android:id="@+id/et_imsi"
@@ -158,6 +164,13 @@
                     android:layout_height="wrap_content"
                     android:layout_marginLeft="16dp"
                     android:text="WRITE" />
+
+                <com.google.android.material.button.MaterialButton
+                    android:id="@+id/btn_write_plmn_all"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:layout_marginLeft="16dp"
+                    android:text="WRITE ALL" />
             </LinearLayout>
 
             <com.google.android.material.textfield.TextInputLayout
@@ -335,22 +348,48 @@
                     android:text="WRITE" />
             </LinearLayout>
 
+            <com.google.android.material.textfield.TextInputLayout
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_marginTop="16dp"
+                android:hint="SENDER">
+
+                <com.google.android.material.textfield.TextInputEditText
+                    android:id="@+id/et_sender"
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:inputType="number"
+                    android:lines="1" />
+            </com.google.android.material.textfield.TextInputLayout>
+
+            <com.google.android.material.textfield.TextInputLayout
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_marginTop="8dp"
+                android:hint="SMS">
+
+                <com.google.android.material.textfield.TextInputEditText
+                    android:id="@+id/et_sms"
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:lines="1" />
+            </com.google.android.material.textfield.TextInputLayout>
+
             <Button
-                android:id="@+id/btn_reset"
+                android:id="@+id/btn_sms"
                 android:layout_width="wrap_content"
-                android:layout_height="38dp"
+                android:layout_height="wrap_content"
                 android:layout_gravity="center"
-                android:layout_marginTop="16dp"
-                android:text="RESET" />
+                android:layout_marginTop="8dp"
+                android:text="SEND" />
 
             <Button
-                android:id="@+id/btn_sms"
+                android:id="@+id/btn_reset"
                 android:layout_width="wrap_content"
-                android:layout_height="38dp"
+                android:layout_height="wrap_content"
                 android:layout_gravity="center"
                 android:layout_marginTop="16dp"
-                android:text="SMS" />
-
+                android:text="REBOOT MODEM" />
         </LinearLayout>
     </ScrollView>
 </androidx.constraintlayout.widget.ConstraintLayout>