xiongzhu 1 éve
szülő
commit
7224110170

+ 1 - 6
.gitignore

@@ -1,12 +1,7 @@
 *.iml
 .gradle
 /local.properties
-/.idea/caches
-/.idea/libraries
-/.idea/modules.xml
-/.idea/workspace.xml
-/.idea/navEditor.xml
-/.idea/assetWizardSettings.xml
+/.idea
 .DS_Store
 /build
 /captures

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

@@ -0,0 +1,199 @@
+package com.example.uicceditor
+
+import android.telephony.TelephonyManager
+import android.util.Log
+import java.math.BigDecimal
+import java.math.RoundingMode
+import kotlin.math.roundToInt
+
+fun String.swapNibbles(): String {
+    val sb = StringBuilder()
+    var i = 0
+    while (i < length) {
+        sb.append(this[i + 1]).append(this[i])
+        i += 2
+    }
+    return sb.toString()
+}
+
+fun String.stripF(): String {
+    return this.replace(Regex("^f+"), "").replace(Regex("f+$"), "")
+}
+
+
+class ApduChannel(private val telephonyManager: TelephonyManager, private val adm: String) {
+
+    class ApduResponse(rawStr: String) {
+
+        val data: String
+        val sw: String
+        val sw1: String
+        val sw2: String
+
+        init {
+            data = rawStr.substring(0, rawStr.length - 4)
+            sw = rawStr.substring(rawStr.length - 4)
+            sw1 = sw.substring(0, 2)
+            sw2 = sw.substring(2)
+        }
+    }
+
+    private val TAG = "ApduShell"
+
+    private var authorized: Boolean = false
+
+    fun execute(cmd: String): ApduResponse {
+        val cla = cmd.substring(0, 2).toInt(16)
+        val ins = cmd.substring(2, 4).toInt(16)
+        val p1 = cmd.substring(4, 6).toInt(16)
+        val p2 = cmd.substring(6, 8).toInt(16)
+        val p3 = cmd.substring(8, 10).toInt(16)
+        val data = cmd.substring(10)
+        Log.i(TAG, "-> $cmd")
+        val res =
+            telephonyManager.iccTransmitApduBasicChannel(
+                cla,
+                ins,
+                p1,
+                p2,
+                p3,
+                data
+            )
+        Log.i(TAG, "<- $res")
+        return ApduResponse(res)
+    }
+
+    fun asciiToHex(ascii: String): String {
+        return ascii.toByteArray().joinToString("") { "%02x".format(it) }
+    }
+
+    fun authorize() {
+        if (adm.length != 8) {
+            Log.e(TAG, "ADM must be 8 characters long")
+            throw IllegalArgumentException("ADM must be 8 characters long")
+        }
+        val res = execute("0020000A08${asciiToHex(adm)}")
+
+        if (res.sw != "9000") {
+            Log.e(TAG, "Authorization failed")
+            throw SecurityException("Authorization failed")
+        }
+    }
+
+    init {
+        authorize()
+    }
+
+    fun readIccid(): String {
+        var res = execute("00a40004023f00")
+        if (res.sw != "9000") {
+            throw Exception("Failed to select MF")
+        }
+        res = execute("00a40004022fe2")
+        if (res.sw != "9000") {
+            throw Exception("Failed to select EF.ICCID")
+        }
+        res = execute("00b000000a")
+        if (res.sw != "9000") {
+            throw Exception("Failed to read EF.ICCID")
+        }
+        return res.data.swapNibbles().stripF()
+    }
+
+    fun readImsi(): String {
+        var res = execute("00a4040410a0000000871002ff86ff0389ffffffff")
+        if (res.sw != "9000") {
+            throw Exception("Failed to select AF.USIM")
+        }
+        res = execute("00a40004026f07")
+        if (res.sw != "9000") {
+            throw Exception("Failed to select EF.IMSI")
+        }
+        res = execute("00b0000009")
+        if (res.sw != "9000") {
+            throw Exception("Failed to read EF.IMSI")
+        }
+        return decImsi(res.data)!!
+    }
+
+    fun decImsi(ef: String): String? {
+        if (ef.length < 4) {
+            return null
+        }
+        var l = ef.substring(0, 2).toInt(16) * 2
+        l -= 1
+        val swapped: String = ef.substring(2).swapNibbles().stripF()
+        if (swapped.isEmpty()) {
+            return null
+        }
+        val oe = (swapped[0].toString().toInt() shr 3) and 1
+        if (oe == 0) {
+            l -= 1
+        }
+        if (l != swapped.length - 1) {
+            return null
+        }
+        val imsi = swapped.substring(1)
+        return imsi
+    }
+
+    fun encImsi(imsi: String): String {
+        val l = BigDecimal(((imsi.length + 1) / 2f).toDouble()).setScale(0, RoundingMode.HALF_UP)
+            .toInt()
+        val oe = imsi.length and 1
+        val ei =
+            String.format("%02x", l) +
+                    (String.format("%01x", (oe shl 3) or 1) + imsi.padEnd(
+                        15,
+                        'f'
+                    )).swapNibbles()
+        return ei
+    }
+
+    fun encIccid(iccid: String): String {
+        return iccid.padEnd(20, 'f').swapNibbles()
+    }
+
+    fun writeIccid(iccid: String) {
+        var res = execute("00a40004023f00")
+        if (res.sw != "9000") {
+            throw Exception("Failed to select MF")
+        }
+        res = execute("00a40004022fe2")
+        if (res.sw != "9000") {
+            throw Exception("Failed to select EF.ICCID")
+        }
+        res = execute("00d600000a${encIccid(iccid)}")
+        if (res.sw != "9000") {
+            throw Exception("Failed to write EF.ICCID")
+        }
+    }
+
+    fun writeImsi(imsi: String) {
+        var res = execute("00a40004023f00")
+        if (res.sw != "9000") {
+            throw Exception("Failed to select MF")
+        }
+        res = execute("00a4040410a0000000871002ff86ff0389ffffffff")
+        if (res.sw != "9000") {
+            throw Exception("Failed to select ADF.USIM")
+        }
+        res = execute("00a40004026f07")
+        if (res.sw != "9000") {
+            throw Exception("Failed to select EF.IMSI")
+        }
+        res = execute("00d6000009083901428002362719")
+        var s = telephonyManager.iccTransmitApduBasicChannel(
+            0x00,
+            0xd6,
+            0x00,
+            0x00,
+            0x09,
+            "083901428002362719"
+        )
+        Log.i(TAG, "writeImsi: $s")
+        if (res.sw != "9000") {
+            throw Exception("Failed to write EF.IMSI")
+        }
+    }
+}

+ 55 - 24
app/src/main/java/com/example/uicceditor/MainActivity.kt

@@ -1,50 +1,81 @@
 package com.example.uicceditor
 
 import android.os.Bundle
-import android.telephony.IccOpenLogicalChannelResponse
+import android.os.Handler
+import android.os.Looper
 import android.telephony.TelephonyManager
 import android.util.Log
+import android.widget.Toast
 import androidx.activity.enableEdgeToEdge
 import androidx.appcompat.app.AppCompatActivity
 import androidx.core.view.ViewCompat
 import androidx.core.view.WindowInsetsCompat
+import com.example.uicceditor.databinding.ActivityMainBinding
 
 class MainActivity : AppCompatActivity() {
     private val TAG = "APDU"
 
+    private val handler = Handler(Looper.getMainLooper())
+
+    private lateinit var apduChannel: ApduChannel
+
+    private val binding: ActivityMainBinding by lazy {
+        ActivityMainBinding.inflate(layoutInflater)
+    }
+
     override fun onCreate(savedInstanceState: Bundle?) {
         super.onCreate(savedInstanceState)
         enableEdgeToEdge()
-        setContentView(R.layout.activity_main)
+        setContentView(binding.root)
         ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main)) { v, insets ->
             val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars())
             v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom)
             insets
         }
 
-        runApdu("00a40004022fe2")
-        runApdu("00b000000a")
+        apduChannel =
+            ApduChannel(getSystemService(TELEPHONY_SERVICE) as TelephonyManager, "88888888")
+
+        binding.btnReadIccid.setOnClickListener {
+            val iccid = apduChannel.readIccid()
+            binding.etIccid.setText(iccid)
+        }
+
+        binding.btnWriteIccid.setOnClickListener {
+            val iccid = binding.etIccid.text.toString()
+            if (Regex("^[0-9]{20}$").matches(iccid)) {
+                apduChannel.writeIccid(iccid)
+                Toast.makeText(this, "ICCID written", Toast.LENGTH_SHORT).show()
+            } else {
+                Toast.makeText(this, "Invalid ICCID", Toast.LENGTH_SHORT).show()
+            }
+        }
+
+        binding.btnReadImsi.setOnClickListener {
+            val imsi = apduChannel.readImsi()
+            binding.etImsi.setText(imsi)
+        }
+
+        binding.btnWriteImsi.setOnClickListener {
+            try {
+                val imsi = binding.etImsi.text.toString()
+                if (Regex("^[0-9]{15}$").matches(imsi)) {
+                    apduChannel.writeImsi(imsi)
+                    Toast.makeText(this, "IMSI written", Toast.LENGTH_SHORT).show()
+                } else {
+                    Toast.makeText(this, "Invalid IMSI", Toast.LENGTH_SHORT).show()
+                }
+            } catch (e: Exception) {
+                Log.e(TAG, "Error writing IMSI", e)
+                Toast.makeText(this, "Error writing IMSI", Toast.LENGTH_SHORT).show()
+            }
+        }
     }
 
-    fun runApdu(cmd: String): String {
-        val cla = cmd.substring(0, 2).toInt(16)
-        val ins = cmd.substring(2, 4).toInt(16)
-        val p1 = cmd.substring(4, 6).toInt(16)
-        val p2 = cmd.substring(6, 8).toInt(16)
-        val p3 = cmd.substring(8, 10).toInt(16)
-        val data = cmd.substring(10)
-
-        Log.i(TAG, "sendApdu: $cmd")
-        val res =
-            (getSystemService(TELEPHONY_SERVICE) as TelephonyManager).iccTransmitApduBasicChannel(
-                cla,
-                ins,
-                p1,
-                p2,
-                p3,
-                data
-            )
-        Log.i(TAG, "apdu response: $res")
-        return res
+    fun getRandomString(length: Int): String {
+        val allowedChars = ('0'..'9')
+        return (1..length)
+            .map { allowedChars.random() }
+            .joinToString("")
     }
 }

+ 74 - 8
app/src/main/res/layout/activity_main.xml

@@ -5,15 +5,81 @@
     android:id="@+id/main"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
+    android:fitsSystemWindows="true"
+    android:orientation="vertical"
     tools:context=".MainActivity">
 
-    <TextView
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:text="Hello World!"
-        app:layout_constraintBottom_toBottomOf="parent"
-        app:layout_constraintEnd_toEndOf="parent"
-        app:layout_constraintStart_toStartOf="parent"
-        app:layout_constraintTop_toTopOf="parent" />
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:orientation="vertical"
+        android:paddingHorizontal="16dp">
 
+        <com.google.android.material.textfield.TextInputLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:hint="ICCID">
+
+            <com.google.android.material.textfield.TextInputEditText
+                android:id="@+id/et_iccid"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:lines="1" />
+        </com.google.android.material.textfield.TextInputLayout>
+
+        <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_marginTop="8dp"
+            android:gravity="center"
+            android:orientation="horizontal">
+
+            <com.google.android.material.button.MaterialButton
+                android:id="@+id/btn_read_iccid"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:text="READ" />
+
+            <com.google.android.material.button.MaterialButton
+                android:id="@+id/btn_write_iccid"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_marginLeft="16dp"
+                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="IMSI">
+
+            <com.google.android.material.textfield.TextInputEditText
+                android:id="@+id/et_imsi"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:lines="1" />
+        </com.google.android.material.textfield.TextInputLayout>
+
+        <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_marginTop="8dp"
+            android:gravity="center"
+            android:orientation="horizontal">
+
+            <com.google.android.material.button.MaterialButton
+                android:id="@+id/btn_read_imsi"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:text="READ" />
+
+            <com.google.android.material.button.MaterialButton
+                android:id="@+id/btn_write_imsi"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_marginLeft="16dp"
+                android:text="WRITE" />
+        </LinearLayout>
+    </LinearLayout>
 </androidx.constraintlayout.widget.ConstraintLayout>