🇮🇷苏莱曼尼遇刺,竟是因为这个....

6,700 阅读8分钟

最近伊朗的少将苏莱曼尼遇刺,被全网称为2020年第一只黑天鹅事件。人们为美国没有夺走其他无辜生命情况下,精准打击目标的能力感到震惊。毕竟上一个有记载能做到“在百万军中取上将首级如探囊取物”的人是咱们的张飞大哥。

我也闲来无事,打开万年不开的微博,看到一条微博内容:

突然感觉虎躯一震,在如今的科技时代,掌握科技的制高点和核心研发能力是多么的重要。不过,如果对于这号人物真通过IMEI就能简单的定位的话,或许他们需要一台装了xposed的安卓手机。好像发现了商机?

在震惊之余,我拿出刚好这周复习的安卓设备ID信息和Sim卡信息的笔记,认真复习了一番。

下面是Demo的运行结果:

IMEI和MEID

IMEI和MEID都是移动设备的身份识别码。类似于设备的身份证。他们的区别如下:

  • IMEI:国际移动设备识别码International Mobile Equipment IdentityIMEI),即通常所说的手机“串号”,用于在移动电话网络中识别每一部独立的手机等移动通信设备。序列号共有15位数字。
  • MEID:移动设备识别码(Mobile Equipment Identifier)是CDMA手机的身份识别码。序列号共有14位数字。

在安卓8.0以下手机,我们通过APITelephonyManager.getDeviceId()获取插有电信运营商SIM卡的卡槽时,默认返回MEID号;移动和联通运营商SIM卡的卡槽返回IMEI号。

获取IMEI或MEID

在Android 8.0以上的系统,TelephonyManager废弃了getDeviceId()方法,提供了两个独立的API来准确的获取IMEI和MEID:getImei()getMeid()。两个API都可以支持传入slotIndex来获取对应位置的IMEI和MEID。

在Android8.0以下的系统,TelephonyManager通过getDeviceId()来获取IMEI或MEID;且在Android 6.0系统及以上系统,getDeviceId(slotIndex)支持通过传入slotIndex来获取对应位置的IMEI和MEID,来适应一机双卡的情况。当存在电信SIM卡时,getDeviceId()方法优先返回MEID,当移动和联通SIM卡时,返回IMEI。

卡1 卡2 getDeviceId(0) getDeviceId(1)
无/非电信卡 无/非电信卡 IMEI IMEI
电信卡 无/非电信卡 MEID IMEI
无/非电信卡 电信卡 IMEI MEID
    /**
     * 获取IMEI或MEID
     */
    fun getIMEIORMEID(){
        //检查是否有READ_PHONE_STATE权限
        if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_PHONE_STATE)
            == PackageManager.PERMISSION_GRANTED){
            //获取IMEI和MEID
            val tm = getSystemService<TelephonyManager>()
            //getDeviceId()从API1就已经存在,默认返回卡槽一的IMEI或MEID
            var imei: String? = tm?.getDeviceId()
            //getDeviceId(solotIndex)在API23加入可以通过指定卡槽位置获取IMEI或MEID
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
                //solotIndex:0 -> 卡槽1 IMEI或MEID
                tvIMEI1.text = "卡槽一 IMEI或MEID:${tm?.getDeviceId(0)}"
                //solotIndex:1 -> 卡槽2 IMEI或MEID
                tvIMEI2.text = "卡槽二 IMEI或MEID:${tm?.getDeviceId(1)}"
            }
        }
    }

    /**
     * 获取MEID
     */
    fun getMEID(){
        //检查是否有READ_PHONE_STATE权限
        if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_PHONE_STATE)
            == PackageManager.PERMISSION_GRANTED){
            //获取IMEI和MEID
            val tm = getSystemService<TelephonyManager>()
            //获取MEID在API26加入
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
                //返回默认可用卡槽的MEID
                var meid:String? = tm?.meid
                //可以通过指定卡槽获取MEID
                tvMEID.text = "MEID:${tm?.getMeid(0)}"
            }
        }
    }

MAC地址

MAC地址(Media Access Control Address),它是一个用来确认网络设备位置的地址。MAC地址用于在网络中唯一标示一个网卡,一台设备若一个或多个网卡,则每个网卡都存在一个唯一的MAC地址。

一般会将IMEI或MEID结合MAC地址作为设备的唯一标识。

MAC地址在Android中不同版本的获取方式也不太一样。大家可以自行Google。这里就不再赘述。后续的Demo中也会给出代码。

IMSI和ICCID

IMSI

国际移动用户识别码(International Mobile Subscriber Identity),是用于区分蜂窝网络中不同用户的、在所有蜂窝网络中不重复的识别码。

获取IMSI

我们可以通过TelephonyManager.getSubscriberId()获取SIM卡的IMSI,该API在SDK API 1就已经存在,但如果在双卡双待的手机上有多个卡槽的情况下,我们想获取对应卡槽的IMSI,则需要调用TelephonyManager.getSubscriberId(int subId) 方法,传入对应卡槽的subId,0代表卡槽1,1代表卡槽2,该方法在SDK API 21加入,但在SDK API 29及以上则无法再调用,但我们目前可以通过反射的方式去调用。

在这里安利一个工具:对于一个API,在各个Android系统的源码,我们可以在http://androidxref.com/ 这个网站进行查看。

我们通过这个网站搜索查看,还会发现在SDK API 21加入时,TelephonyManager.getSubscriberId(long subId)它的入参是Long类型,在之后的版本都是int类型。所以在版本兼容的时候,我们如果是反射调用时,可能需要注意参数类型。

SDK API getSubscriberId()
1~20 getSubscriberId()
21 getSubscriberId()或getSubscriberId(long subId)
22~28 getSubscriberId()或getSubscriberId(int subId)
29及以上 暴露getSubscriberId(),但getSubscriberId(int subId)方法不再暴露
    /**
     * 通过反射调用
     */
    fun getIMSI(){
        //检查是否有READ_PHONE_STATE权限
        if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_PHONE_STATE)
            == PackageManager.PERMISSION_GRANTED){
            //获取IMSI
            val tm = getSystemService<TelephonyManager>()
            //在SDKAPI 1就存在getSubscriberId()方法,返回默认卡槽IMSI
            var imsi = tm?.subscriberId
            //在SDKAPI 21加入getSubscriberId(subId)来指定返回卡槽位置IMSI
            //在SDKAPI 29以上,指定卡槽位置的方法不再暴露,但依旧能通过反射来获取
            tvIMSI1.text = "卡槽一 IMSI:${getReflectMethod(this,"getSubscriberId",0) as CharSequence?}"
            tvIMSI2.text = "卡槽二 IMSI:${getReflectMethod(this,"getSubscriberId",1) as CharSequence?}"

        }
    }


    fun getReflectMethod(context: Context, method: String, param: Int): Any? {
        val telephony = context.getSystemService(Context.TELEPHONY_SERVICE) as TelephonyManager
        try {
            val telephonyClass = Class.forName(telephony.javaClass.name)
            val parameter = arrayOfNulls<Class<*>>(1)
            parameter[0] = Int::class.javaPrimitiveType
            val getSimState = telephonyClass.getMethod(method, *parameter)
            val ob_phone = getSimState.invoke(telephony, param)

            if (ob_phone != null) {
                return ob_phone
            }
        } catch (e: Exception) {
            e.printStackTrace()
        }

        return null
    }

上面的代码展示了如何通过反射获取IMSI。在获取到IMSI之后,我们可以通过IMSI的开头五位数来区别移动用户所属的国家和运营商。

在中国IMSI的开头三位为460,之后的两位代表中国的运营商信息:

运营商 MNC号码
移动 00, 02,04, 07,08
联通 01, 06,09
电信 03, 05
电信4G 11
铁通 20

ICCID

SIM卡卡号,是卡的标识,不作接入网络的鉴权认证,可在SIM卡卡后查询到。格式:大多为19或20位0-9的数字,亦存在6位/12位的情况。

获取ICCID

获取ICCID有两种方式:

  • 通过TelephonyManager的getSimSerialNumber()方法来获取ICCID,在SDK API 21加入了getSubscriberId(subId)来指定获取对应卡槽位置的ICCID;但是和IMSI一样,在SDK API 29及以上,getSubscriberId(subId)的方法不再暴露外部调用,但我们可以通过反射来调用。
  • 通过SubscriptionManager的getActiveSubscriptionInfoList()方法来获取SubscriptionInfo列表,这个列表包含了有效的SIM卡的信息,如果有多个SIM卡,则会返回多个SubscriptionInfo对象。每个SubscriptionInfo对象代表一个有效的SIM卡。通过循环遍历SubscriptionInfo列表,调用SubscriptionInfogetIccId()的方法,可以获取ICCID的值,但是SubscriptionManager在SDk API 22才被加到SDK中。
		/**
     * 通过反射调用
     */
    fun getReflectMethod(context: Context, method: String, param: Int): Any? {
        val telephony = context.getSystemService(Context.TELEPHONY_SERVICE) as TelephonyManager
        try {
            val telephonyClass = Class.forName(telephony.javaClass.name)
            val parameter = arrayOfNulls<Class<*>>(1)
            parameter[0] = Int::class.javaPrimitiveType
            val getSimState = telephonyClass.getMethod(method, *parameter)
            val ob_phone = getSimState.invoke(telephony, param)

            if (ob_phone != null) {
                return ob_phone
            }
        } catch (e: Exception) {
            e.printStackTrace()
        }

        return null
    }

    /**
     * 通过TelephonyManager获取CCID
     */
    fun getICCIDByTM(){
        //检查是否有READ_PHONE_STATE权限
        if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_PHONE_STATE)
            == PackageManager.PERMISSION_GRANTED){
            //获取IMSI
            val tm = getSystemService<TelephonyManager>()
            //在SDKAPI 1就存在getSubscriberId()方法,返回默认卡槽IMSI
            var iccid = tm?.simSerialNumber
            //在SDKAPI 21加入getSubscriberId(subId)来指定返回卡槽位置IMSI
            //在SDKAPI 29以上,指定卡槽位置的方法不再暴露,但依旧能通过反射来获取
            tvICCID1.text = "卡槽一 ICCID:${getReflectMethod(this,"getSimSerialNumber",0) as CharSequence?}"
            tvICCID2.text = "卡槽二 ICCID:${getReflectMethod(this,"getSimSerialNumber",1) as CharSequence?}"

        }
    }

    /**
     * 通过SubscriptionManager获取CCID SDK API 22及以上使用
     */
    fun getICCIDBySM(){
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP_MR1) {
            val mSubscriptionManager = getSystemService(Context.TELEPHONY_SUBSCRIPTION_SERVICE) as SubscriptionManager
            //检查是否有READ_PHONE_STATE权限
            if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_PHONE_STATE)
                == PackageManager.PERMISSION_GRANTED){
                //获取当前有效的SIM卡列表
                var subInfos = mSubscriptionManager.activeSubscriptionInfoList
                for ((index,item) in subInfos.withIndex()){
                    when(index){
                        0->tvICCID1.text = "卡槽一 ICCID:${item.iccId}"
                        1->tvICCID2.text = "卡槽二 ICCID:${item.iccId}"
                    }
                }
            }
        }
    }

SN

SN码是Serial Number的缩写,是产品的序列号,主要为了验证"产品的合法性",主要用来保护用户的正版权益。一般是不可改变的。我们可以通过adb devices命令来查看。

获取SN

我们在SDK API 26以下,可以直接通过Build.SERIAL进行获取,在SDK API 26以上则需要READ_PHONE_STATE权限,调用Build.getSerial()方法进行获取。

		/**
     * 获取SN
     */
    fun getSN(){
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_PHONE_STATE)
                == PackageManager.PERMISSION_GRANTED){
                // 需要READ_PHONE_STATE权限
                tvSN.text = "SN:${Build.getSerial()}"
            }
        }else{
            tvSN.text = "SN:${Build.SERIAL}"
        }
    }

ANDROID ID

设备首次启动随机生成的ID,设备还原出厂设置后或系统升级后会重新生成 格式:长度为16位的字符串,但由于生产厂商定制系统的Bug,有的设备会生成相同的ID,或者返回null

获取ANDROID ID

通过Settings.Secure.getString(this.getContentResolver(), Settings.Secure.ANDROID_ID即可获取ANDROID ID

		/**
     * 获取AndroidId
     */
    fun getAndroidId(){
        tvAndroidId.text = "Android ID:${Settings.Secure.getString(this.getContentResolver(), Settings.Secure.ANDROID_ID)}"
    }

Android10 AndroidId

在Android10已经无法获取设备硬件相关的Id,例如IMEI、MAC地址等。如果代码中获取这方面的信息,则会给你抛出一个大写的SecurityException错误。

受影响的API为:

  • Build

    getSerial()

  • TelephonyManager

    getImei()

    getDeviceId()

    getMeid()

    getSimSerialNumber()

    getSubscriberId()

在Android10,除非系统应用或者特殊的应用商应用READ_PRIVILEGED_PHONE_STATE才能访问以上的API。这就意味这Android ID的许多获取方法在Android10基本GG。

Google为我们提出了Android10获取软件范畴的ID的方法。具体参考文章:唯一标识符最佳做法

但这方案不能解决我们很多场景:需要获取设备的唯一不可更改的ID,例如:风控、推送和用户画像等领域。但不用担心,我们伟大的祖国厂商系统,为我们提供了统一的OAID(匿名设备标识符)来替代IMEI的作用。具体参考文档:OAID文档

SIM卡相关拓展

最后再补充几点与SIM卡相关,但和Android设备信息无关的内容。主要是整理了一下如何检测一台设备当前插入了几张SIM卡、当前手机最多支持几张Sim卡和获取当前使用蜂窝网络的SIM卡。

检测当前手机插入几张SIM卡

获取当前手机插入几张SIM卡也有两个途径,一个是通过TelephonyManager的getSimState()方法获取,另一个是通过SubscriptionManager获取

  • TelephonyManager在SDK API 1就加入了``getSimState()方法获取默认的Sim卡状态;在SDK API 21加入了getSimState(int slotId)方法,获取对应卡槽的Sim卡状态,但是该方法却是通过注解@hide`修饰的,所以没有对外暴露方法,外部无法调用,但可以通过反射进行调用;在SDK API 26才对外暴露。
SDK API getSubscriberId()
1~20 getSimState()
21~26 getSimState()或反射调用getSimState(int slotId)
26以上 getSimState()或getSimState(int slotId)

Sim卡状态表:

状态 状态码 解释
SIM_STATE_UNKNOWN 0 SIM卡状态:未知
SIM_STATE_ABSENT 1 SIM卡状态:设备中没有SIM卡
SIM_STATE_PIN_REQUIRED 2 SIM卡状态:锁定:要求用户的SIM卡PIN解锁
SIM_STATE_PUK_REQUIRED 3 SIM卡状态:锁定:要求用户的SIM PUK解锁
SIM_STATE_NETWORK_LOCKED 4 SIM卡状态:锁定:需要网络PIN才能解锁
SIM_STATE_READY 5 SIM卡状态:就绪
SIM_STATE_NOT_READY 6 SIM卡状态:SIM卡尚未就绪
SIM_STATE_PERM_DISABLED 7 SIM卡状态:SIM卡错误,已永久禁用
SIM_STATE_CARD_IO_ERROR 8 SIM卡状态:SIM卡错误,存在但有故障
SIM_STATE_CARD_RESTRICTED 9 SIM卡状态:SIM卡受限,存在,但由于运营商的限制而无法使用
  • SubscriptionManager在SDK API 22才被加入SDK API中。我们在SDK API 22及以上可以通过SubscriptionManager.getActiveSubscriptionInfoCount()方法获取有效的Sim卡个数。但这个方法需要READ_PHONE_STATE用户权限。

因此结合TelephonyManager.getSimState()SubscriptionManager.getActiveSubscriptionInfoCount()方法,个人整理了一个获取当前手机SIM卡的方法。

		/**
     * 获取Sim卡个数
     * @param context
     * @return
     */
    fun checkSimCount(context: Context): Int {
        var simCount: Int
        try {
            //如果SDK Version >=22 && 拥有Manifest.permission.READ_PHONE_STATE 权限
            if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP_MR1 && ContextCompat.checkSelfPermission(
                    context,
                    Manifest.permission.READ_PHONE_STATE
                ) == PackageManager.PERMISSION_GRANTED
            ) {
                simCount = getSimCountBySubscriptionManager(context)
                if (simCount == -1) {
                    //如果SubscriptionManager获取失败,则TelephonyManager尝试获取
                    simCount = getSimCountByTelephonyManager(context)
                }
            } else {
                simCount = getSimCountByTelephonyManager(context)
            }

        } catch (e: Exception) {
            simCount = getSimCountByTelephonyManager(context)
        }

        return simCount
    }


    /**
     * 通过SubscriptionManager获取Sim卡张数,如果获取失败返回-1
     * @param context
     * @return
     */
    @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP_MR1)
    @RequiresPermission(Manifest.permission.READ_PHONE_STATE)
    private fun getSimCountBySubscriptionManager(context: Context): Int {
        try {
            val subscriptionManager =
                context.getSystemService(Context.TELEPHONY_SUBSCRIPTION_SERVICE) as SubscriptionManager
            return subscriptionManager?.activeSubscriptionInfoCount ?: -1
        } catch (e: Throwable) {
            return -1
        }

    }

    /**
     * 通过TelephonyManager反射获取Sim卡张数
     * @param context
     * @return
     */
    private fun getSimCountByTelephonyManager(context: Context): Int {
        val slotOne = getSimStateBySlotIdx(context, 0)
        val slotTwo = getSimStateBySlotIdx(context, 1)
        if (slotOne && slotTwo) {
            return 2
        } else if (slotOne || slotTwo) {
            return 1
        } else {
            val telephony = context.getSystemService(Context.TELEPHONY_SERVICE) as TelephonyManager
            val simState = telephony.simState
            return if (simState != TelephonyManager.SIM_STATE_ABSENT && simState != TelephonyManager.SIM_STATE_UNKNOWN) {
                1
            } else 0
        }
    }


    /**
     * 通过反射去调用TelephonyManager.getSimState(int slotIdx)方法,获取sim卡状态
     * SIM_STATE_UNKNOWN 0 SIM卡状态:未知
     * SIM_STATE_ABSENT 1 SIM卡状态:设备中没有SIM卡
     * SIM_STATE_PIN_REQUIRED 2 SIM卡状态:锁定:要求用户的SIM卡PIN解锁
     * SIM_STATE_PUK_REQUIRED 3 SIM卡状态:锁定:要求用户的SIM PUK解锁
     * SIM_STATE_NETWORK_LOCKED 4 SIM卡状态:锁定:需要网络PIN才能解锁
     * SIM_STATE_READY 5 SIM卡状态:就绪
     * SIM_STATE_NOT_READY 6 SIM卡状态:SIM卡尚未就绪
     * SIM_STATE_PERM_DISABLED 7 SIM卡状态:SIM卡错误,已永久禁用
     * SIM_STATE_CARD_IO_ERROR 8 SIM卡状态:SIM卡错误,存在但有故障
     * SIM_STATE_CARD_RESTRICTED 9 SIM卡状态:SIM卡受限,存在,但由于运营商的限制而无法使用。
     * @param context
     * @param slotIdx:0(sim1),1(sim2)
     * @return
     */
    fun getSimStateBySlotIdx(context: Context, slotIdx: Int): Boolean {
        var isReady = false
        try {
            val getSimState = getSimByMethod(context, "getSimState", slotIdx)
            if (getSimState != null) {
                val simState = Integer.parseInt(getSimState.toString())
                if (simState != TelephonyManager.SIM_STATE_ABSENT && simState != TelephonyManager.SIM_STATE_UNKNOWN) {
                    isReady = true
                }
            }
        } catch (e: Throwable) {
        }

        return isReady
    }


    /**
     * 通过反射获取Sim卡状态
     * @param context
     * @param method
     * @param param
     * @return
     */
    private fun getSimByMethod(context: Context, method: String, param: Int): Any? {
        val telephony = context.getSystemService(Context.TELEPHONY_SERVICE) as TelephonyManager
        try {
            val telephonyClass = Class.forName(telephony.javaClass.name)
            val parameter = arrayOfNulls<Class<*>>(1)
            parameter[0] = Int::class.javaPrimitiveType
            val getSimState = telephonyClass.getMethod(method, *parameter)
            val obParameter = arrayOfNulls<Any>(1)
            obParameter[0] = param
            val result = getSimState.invoke(telephony, *obParameter)

            if (result != null) {
                return result
            }
        } catch (e: Throwable) {
            e.printStackTrace()
        }

        return null
    }

当前可支持最大Sim卡张数

SubscriptionManager我们可以通过getActiveSubscriptionInfoCountMax()方法来获取当前手机可支持的Sim卡最大数量。

		/**
     * 获取当前支持最多sim卡数量
     */
    fun getMaxSimCount(context: Context){
        try {
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP_MR1) {
                val subscriptionManager = context.getSystemService(Context.TELEPHONY_SUBSCRIPTION_SERVICE) as SubscriptionManager
                tvSimMaxCount.text =  "当前支持最多Sim卡数量:${subscriptionManager?.activeSubscriptionInfoCountMax}"
            }

        } catch (e: Throwable) {
        }
    }

当前蜂窝网络Sim卡信息

我们可以通过TelephonyManager的getSimOperator()方法获取当前网络的SIM卡的MCC+MNC(mobile country code + mobile network code)其实就是IMSI的前五位,这个API在SDK API 1就已经加入。然后根据返回的信息根据运营商的MNC去判断对应的运营商信息。

		/**
     * 通过TelephonyManager当前蜂窝网络运营商信息
     */
    fun getNetType(){
        //检查是否有READ_PHONE_STATE权限
        if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_PHONE_STATE)
            == PackageManager.PERMISSION_GRANTED){
            //获取TelephonyManager
            val tm = getSystemService<TelephonyManager>()
            //在SDK API 1 getSimOperator()方法
            when(tm?.simOperator){
                "46000","46002","46004","46007","46008"->tvSimNetType.text = "当前上网SIM卡运营商:移动"
                "46001","46006","46009"->tvSimNetType.text = "当前上网SIM卡运营商:联通"
                "46003","46005","46011"->tvSimNetType.text = "当前上网SIM卡运营商:电信"
                "46020"->tvSimNetType.text = "当前上网SIM卡运营商:铁通"
            }

        }
    }

源码:github.com/DaMaiGit/An…