• Android 13中的 Open Mobile API


    Open Mobile API Specification v3.2 简称 OMAPI

    Android OMApi 接口架构和实现方式参考

    Android OMApi 特性支持参考

    aospxref Android 13 源码浏览网站

    建议扩展阅读:

    Android SE的多种形态: eSE、UICC、SD Card

    Android 9.0 SecureElementService 初始化流程分析 很清晰的UML类图和调用流程

    API 接口在 Specification 6.2 章节,本文基于 Android 13 的 OMAPI 实现(遵循 v3.3 规范),涉及了 framework 和 packages 层,具体实现与原始规范有些许不同


    一、SE涉及场景及用途

    SE 也就是 Secure Element,译为 “安全元素”

    主要应用场景在 手机手表交通卡、门禁、虚拟钱包、虚拟SIM卡,以及其他身份认证的且对安全级别有一定要求的业务。

    目前 Android 手机主要有三种SE的实现 eSEUICCmicroSD,这些带有SE的芯片有独立的储存和计算能力,可以进行 Applet 安装与个人化等一些列自定义的安全行为操作。

    简单提一下这三种SE实现:

    eSE:Embedded ,硬件内嵌在手机形式,通过 NFC Controller 使用
    UICC:物理插槽形式,日常使用的SIM卡,在Android层面叫UICC,现在 eSIM 阶段新增了 EUICC,而且 Android 提供了一套另外的 eSIM 服务框架
    microSD:物理插槽形式,早期使用的外部 SD 存储卡
    
    • 1
    • 2
    • 3

    Tips:
    Android9 之前需引入 org.simalliance.openmobileapi.jar,从 Android9 开始并入了Android SDK,在 android.se.omapi 包下,且在 /packages/apps/ 下新增了SE相关模块(SecureElement


    二、上层 framework

    包路径:/frameworks/base/omapi/core/java/android/se/omapi/

    目录下的所有文件:

    Channel.java	
    Reader.java
    SEService.java
    Session.java
    ISecureElementChannel.aidl
    ISecureElementListener.aidl	
    ISecureElementReader.aidl
    ISecureElementService.aidl
    ISecureElementSession.aidl
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    Transport API class diagram overview

    和原始的 OMAPI规范 还是有点区别
    在这里插入图片描述


    一、SEService.java

    SEService(Context context, Executor executor, OnConnectedListener listener)

    对应底层类 SecureElementService.java

    内部接口: OnConnectedListener

    初始化为耗时操作,异步进行,成功连接后进行回调

    1.void onConnected();
    
    • 1

    方法:

    - Reader getUiccReader(int)
    - Reader[] getReaders() 返回可用的Reader列表,元素不能重复,即使没有插卡也返回,
    	底层是 Terminal内部类SecureElementReader(Reader相关的通信也由它完成), 内部有 ArrayList mSessions
    - boolean isConnected() 当此服务成功连接时为true
    - void shutdown() 释放所有由此SEService分配的SE资源,建议在程序结束时调用,此方法执行后,isConnected返回false
    - String getVersion() 返回此实现基于哪一个 Open Mobile API 规范版本
    	计算方法:版本号 = 主版本号*1000 + 副版本号,(例如:“3001”是基于OMAPI规范v3.1的实现)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    二、Reader.java

    Reader(@NonNull SEService service, @NonNull String name, @NonNull ISecureElementReader reader)

    对应底层类 Terminal.java,此类非常重要(/packages/apps/SecureElement/src/com/android/se/Terminal.java)

    方法:

    - String getName() 
    - SEService getSEService() 
    - boolean reset()
    - boolean isSecureElementPresent() 
    - Session openSession() 
    - void closeSessions() 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    三、Session.java

    Reader getReader() 
    byte[] getATR() 
    void close() 
    boolean isClosed() 
    void closeChannels() 
    Channel openBasicChannel(byte[] aid) 
    Channel openLogicalChannel(byte[] aid) 
    Channel openBasicChannel(byte[] aid, Byte P2) 
    Channel openLogicalChannel(byte[] aid, Byte P2)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    四、Channel.java

    void close() 
    boolean isClosed()
    boolean selectNext() 遍历匹配相同部分AID的所有applet, true: 在该通道上成功选择了一个新的Applet; false: 保持原有选中
    boolean isBasicChannel()
    Session getSession()
    byte[] getSelectResponse()
    byte[] transmit(byte[] command)
    void setTransmitExpectDataWithWarningSW(boolean expectData)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    三、framework流程:

    通过 SEService 类构造传入注册服务接口,获取 Reader 得到 Session 实例,通过 Session 可以打开 Channel(包括 BasicChannelLogicalChannel),Channel 中提供了发送 APDU 命令的方法。

    总结步骤:

    1. 连接 SEService,需要几百毫秒时间
    2. 指定 Reader 或者 getReaders 遍历取出能使用的 Reader
    3. 通过 Reader 连接一个 Session
    4. 通过 Session 的 openBasicChannel(aid, p2) / openLogicChannel(aid, p2) 打开一个 Channel
    5. 通过 Channel.transmit(cmd) 发送 APDU 指令
    6. 通过 Channel.close() 关闭通道
    7. 通过 Session 关闭会话,或者通过 Reader.closeSessions() 关闭所有已打开的会话
    8. 通过 SEService.shutdown() 断开自身连接,此方法内包了含上一步骤

    四、底层 packages

    包路径:/packages/apps/SecureElement/src/com/android/se/

    目录下的文夹件和类:

    internal/
    security/
    Channel.java	
    CommandApduValidator.java	
    SEApplication.java	
    SecureElementService.java	
    Terminal.java
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    一、SecureElementService.java

    getReader()
    createTerminals() {
        addTerminals(ESE_TERMINAL);
        addTerminals(UICC_TERMINAL);
    }
    addTerminals(String terminalName) {
        ...
        name = terminalName + Integer.toString(index);
        Terminal terminal = new Terminal(name, this);
        terminal.initialize(index == 1);
        mTerminals.put(name, terminal);
        ...
    }
    
    // Seesion 的Binder桥接
    final class SecureElementSession extends ISecureElementSession.Stub {
        closeChannels()
        openBasicChannel(...)
        openLogicalChannel(...)
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    二、Terminal.java

    setUpChannelAccess(...)
    isSecureElementPresent()
    initialize(boolean retryOnFail)
    initializeAccessControl() {
        if (mAccessControlEnforcer == null) {
            mAccessControlEnforcer = new AccessControlEnforcer(this);
        }
        mAccessControlEnforcer.initialize();
    }
    
    byte[] transmit(byte[] cmd);
    byte[] transmitInternal(byte[] cmd) {
    	...
    	// mSEHal 也就是 SecureElement.java
    	// 年轻人我劝你不要去看后面的源码了, 深得很你把握不住, 硬件层的各种 CPP
    	mSEHal.transmit(byteArrayToArrayList(cmd));
    }
    
    // Reader 的Binder桥接
    final class SecureElementReader extends ISecureElementReader.Stub {
    	getAtr()
    	reset()
    	openSeesion()
    	isSecureElementPresent()
    	private getTerminal()
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    三、AccessControlEnforcer.java

    initialize()
    reset()
    setUpChannelAccess(...)
    readSecurityProfile()
    arf...PKCS15
    ara...
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    四、Channel.java

    byte[] transmit(byte[] command) {
    	CommandApduValidator.execute(command);
    	checkCommand(command);
    
    	// 一大堆命令检查, 接着还是通过 Terminal 发送
    	return mTerminal.transmit(command);
    }
    
    final class SecureElementChannel extends ISecureElementChannel.Stub {
    	close()
    	boolean selectNext()
    	byte[] transmit(byte[] command) {
    		Channel.this.transmit(command);
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    常用名词

    Trusted Service Manager (TSM)
    Issuer Security Domain (ISD) 
    Access Rule Files (ARF)
    Access Rule Application (ARA)
    Access Rule Application Master (ARA-M)
    Access Rule Application Client (ARA-C)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    五、packages流程

    framework 层的 SEService.java 在构造里通过 ISecureElementChannel.aidl 类绑定到了
    packages 层的服务 SecureElementService.java 上,后续都通过此服务交互。以下三个比较重要的入口类,SecureElementService 会在 onCreate() 里初始化,并创建多个 Terminal 实例,主要是 eSEUICC(SIM),它们可能会有多个,类似(eSE1、eSE2、SIM1、SIM2)。

    这里即是上层调用的 openSeesionopenBasicChannel()openLogicalChannel()C++ 层的入口,也就是在这里做了 AccessRule 的校验(我在这儿就遇到了问题),规则校验具体实现入口在 AccessControlEnforcer.java,此类在 Terminal 中初始化。

    顺便提两句,第一个是在上层调用 openSeesion 过程中有一个 isSecureElementPresent() 的检查,如果返回 false 会直接抛出异常 “Secure Element is not present.”,也就是当前 SE 不可用(具体原因暂时未解,查看了一些cpp的实现里直接返回的true,但上层确实有返回false,排除异常的情况),所以调用前需要在 Reader 里先行判断。

    第二个是 openXXXChannel() 过程中,会有一个 setUpChannelAccess() 方法进行访问规则的校验,关于 AccessRule GPD 有一本非常厚的 SE_Access_Control_v1.1.pdf 的规范描述。

    当规则校验通过后,Channel 也就打开了,此时可以在上层使用 Channel.transmit() 发送 APDU 指令,且此方法进行了响应。可以是一条指令也可以是多条,发送完成后即返回结果,另外还提供了一个方法用来获取结果 getSelectResponse(),没太细究它们俩直接的区别。

    五、使用案例

    5.1 检查设备支持

    可以使用 PackageManager.hasSystemFeature 检查设备是否支持需要的 SE 区域,或者使用 PackageManager.systemAvailableFeatures 列出所有支持的特性,从里面找如下三个

    FEATURE_SE_OMAPI_ESE
    FEATURE_SE_OMAPI_UICC
    FEATURE_SE_OMAPI_SD
    
    • 1
    • 2
    • 3

    Android 官方相关描述:

    Open Mobile API reader support

    On Android 11 and higher, Open Mobile API (OMAPI) supports checking for eSE, SD, and UICC support hardware on devices with the following flags:

    FEATURE_SE_OMAPI_ESE
    FEATURE_SE_OMAPI_SD
    FEATURE_SE_OMAPI_UICC
    
    • 1
    • 2
    • 3

    Use these values with getSystemAvailableFeatures() or hasSystemFeature() to check for device support.


    
    	if (requireContext().packageManager.hasSystemFeature(PackageManager.FEATURE_SE_OMAPI_UICC)) {
            SIMLog.e("系统是否支持 OMAPI UICC_SE 硬件功能:true")
            lifecycleScope.launch {
               delay(2000)
               mOperator.getEID() // 做了个测试调用, 里面使用 OMAPI 执行了一次 APDU 发送
            }
        } else {
            SIMLog.e("系统是否支持 OMAPI UICC_SE 硬件功能:false")
        }
    
        requireContext().packageManager.systemAvailableFeatures.forEach {
            Log.e("Flyme-SIM", "系统已支持的硬件功能:$it")
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    5.2 客户端调用 SEService.java

    使用了 framework 下的类 SEService.java,也就是SDK自带的包 android.se.omapi,调用的相关日志在后续有贴出来,主要是经历了一个错误记录下来

    
    	// Android 客户端代码
    
    	import android.se.omapi.SEService 
    
     	private var mUICCReader: Reader? = null
        private val mSEService = SEService(context, ThreadUtils.getSinglePool()) {
            SIMLog.e("SEService已连接", TAG)
        }
    
        init {
    		// SEService 大概需要几百毫秒进行连接
            ThreadUtils.getMainHandler().postDelayed({
                SIMLog.e("SEService连接状态: ${mSEService.isConnected}", TAG)
    
                if (mSEService.isConnected) {
    	//             mUICCReader = mSEService.getUiccReader(SLOT_INDEX) // 指定卡槽拿到的Reader不是SE, 直接使用遍历的方式
    				// 我的遍历结果
    				// 已存在的Reader: name=eSE1, isSecureElementPresent=true
    				// 已存在的Reader: name=SIM1, isSecureElementPresent=false
    				// 已存在的Reader: name=SIM2, isSecureElementPresent=false
                    for (reader in mSEService.readers) {
                        SIMLog.printD("已存在的Reader: name=${reader.name}, isSecureElementPresent=${reader.isSecureElementPresent}")
                        if (reader.isSecureElementPresent) mUICCReader = reader
                    }
                }
            }, 1000)
        }
    
    
        /**
         * 发送 APDU 指令
         * 一次发送, 包含 选择Applet、打开通道、读取响应、关闭通道
         * 1.打开 Reader、Session、Channel
         * 2.通过 Channel 选择 Applet,并打开逻辑通道
         * 3.发送 APDU,并接收响应
         * 4.关闭 Reader、Session、Channel
         *
         * @param p2 '00', '04', '08', '0C'
         */
        private fun send(command: ByteArray, aid: ByteArray = AID_BYTE, p2: Byte = 0x00): ByteArray? {
            var resp: ByteArray? = null
    
            mUICCReader?.let { reader ->
                log("sendApdu: Reader.isSecureElementPresent=${reader.isSecureElementPresent}")
    
                try {
                    if (!reader.isSecureElementPresent) {
                        log("此 Reader 不支持 SE")
                        return null
                    }
                    val openSession = reader.openSession()
    
    				// 在执行 openSession.openLogicalChannel(aid, p2) 时,遇到一个错误
                    // java.lang.SecurityException:
                    // Exception in setUpChannelAccess() java.security.AccessControlException: SecureElement-AccessControlEnforcerno APDU access allowed!
                    openSession.openLogicalChannel(aid, p2)?.let {
                        log("发送的req: ${command.bytesToHexString()}")
                        it.transmit(command)
    
                        resp = it.selectResponse
                        log("返回的resp: ${resp?.bytesToHexString()}")
                        log("返回是否成功: ${isSendSuccess(resp)}")
    
                        it.close()
                    }
                    openSession.close()
                } catch (e: Exception) {
                    log("发送APDU异常: $e")
                }
            } ?: kotlin.run {
                log("mUICCReader还未初始化完成, SEService还在连接中")
            }
    
            return resp
        }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77

    5.3 主要讲一下这个错误

    执行 openSession.openLogicalChannel(aid, p2) 遇到错误:

    java.lang.SecurityException:
    Exception in setUpChannelAccess()
    java.security.AccessControlException:SecureElement-AccessControlEnforcerno APDU access allowed!

    这个问题出现是正常的,因为OMAPI的规范里是有AC规则校验的,正常情况下应用什么都没做去访问SE肯定是没有权限的。
    解决方法1:如果是使用SIM-SE,就把应用hash写入SIM卡,如果是使用eSE,就把应用hash写入手机系统;
    解决方法2:修改系统源码,注释掉AC规则校验的相关逻辑,我尝试了下面的方式修改校验,结果是无效的

    5.3.1

    查看源码:

    /frameworks/base/omapi/java/android/se/omapi/Session.java

    	
    	Session.java
    
        public @Nullable Channel openLogicalChannel(@Nullable byte[] aid, @Nullable byte p2) throws IOException {
            if (!mService.isConnected()) {
                throw new IllegalStateException("service not connected to system");
            }
            synchronized (mLock) {
                try {
    				// 执行到这一行, 调用到 ISecureElementChannel.aidl
                    ISecureElementChannel channel = mSession.openLogicalChannel(
                            aid,
                            p2,
                            mReader.getSEService().getListener());
                    if (channel == null) {
                        return null;
                    }
                    return new Channel(mService, this, channel);
                } catch (ServiceSpecificException e) {
                   ...
                } catch (RemoteException e) {
                    throw new IllegalStateException(e.getMessage());
                }
            }
        }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    5.3.2

    后续调用离开了 framework 层到了底层目录 packages:

    错误里提到一个关键类和一个方法:AccessControlEnforcerno.javasetUpChannelAccess(),该类在 Terminal.java 中初始化

    首先来看下 Terminal.java

    
    	Terminal.java
    
        /**
        * Initializes the Access Control for this Terminal
        */
        private synchronized void initializeAccessControl() throws IOException,
               MissingResourceException
        {
    		...
    
            synchronized(mLock) {
                if (mAccessControlEnforcer == null) {
                    mAccessControlEnforcer = new AccessControlEnforcer (this);
                }
    
                try {
                    mAccessControlEnforcer.initialize();
                } catch (IOException | MissingResourceException e) {
                    mAccessControlEnforcer = null;
                    throw e;
                }
            }
        }
    
    	/**
    	 * Opens a logical Channel with AID for the given package name or uuid
    	 */
    	public Channel openLogicalChannel(SecureElementSession session, byte[] aid, byte p2,
    		ISecureElementListener listener, String packageName,
    		byte[] uuid, int pid) throws IOException, NoSuchElementException {
    
    		...
    
    		ChannelAccess channelAccess = null;
    		if (packageName != null || uuid != null) {
    			channelAccess = setUpChannelAccess(aid, packageName, uuid, pid, false);
    		}
    
    		...
    		return logicalChannel;
    	}
    
    
    	// 在 Terminal.java 同名的 setUpChannelAccess 方法里调用了 mAccessControlEnforcer.setUpChannelAccess
    	/**
    	 * Initialize the Access Control and set up the channel access.
    	 */ 
    	private ChannelAccess setUpChannelAccess(byte[] aid, String packageName, byte[] uuid, int pid,
                  boolean isBasicChannel) throws IOException, MissingResourceException {
    
    		ChannelAccess channelAccess =
                              mAccessControlEnforcer.setUpChannelAccess(aid, packageName, uuid, checkRefreshTag);
    	}
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    5.3.3

    接下来就到了最终目的类 AccessControlEnforcerno.java

    /packages/apps/SecureElement/src/com/android/se/security/AccessControlEnforcerno.java

    
    	AccessControlEnforcerno.java
    
    	/** Initializes the Access Control for the Secure Element */
        public synchronized void initialize() throws IOException, MissingResourceException {
    		...
    		readSecurityProfile();
    		...
    	}
    
    	// 后面两个方法就是完整代码了
    
    	/** Sets up the Channel Access for the given Package */
    	public ChannelAccess setUpChannelAccess(byte[] aid, String packageName, byte[] uuid,
            boolean checkRefreshTag) throws IOException, MissingResourceException {
            ChannelAccess channelAccess = null;
            // check result of channel access during initialization procedure
            if (mInitialChannelAccess.getAccess() == ChannelAccess.ACCESS.DENIED) {
                    throw new AccessControlException(mTag + "access denied: " + mInitialChannelAccess.getReason());
                }
    
            // this is the new GP Access Control Enforcer implementation
            if (mUseAra || mUseArf) {
               channelAccess = internal_setUpChannelAccess(aid, packageName, uuid,checkRefreshTag);
            }
    
            if (channelAccess == null || (channelAccess.getApduAccess() != ChannelAccess.ACCESS.ALLOWED
                        && !channelAccess.isUseApduFilter())) {
    
    				// 关键点来了, 摆明了就是一个系统权限问题
                    if (mFullAccess) {
                        // if full access is set then we reuse the initial channel access,
                        // since we got so far it allows everything with a descriptive reason.
                        channelAccess = mInitialChannelAccess;
                    } else {
    					// 错误就是在这里抛出的, mFullAccess 是个全局方法, 在初始化时调用了 
                        throw new AccessControlException(mTag + "no APDU access allowed!");
                    }
                }
            channelAccess.setPackageName(packageName);
            return channelAccess.clone();
        }
    
        private void readSecurityProfile() {
    		// 非 debug 模式下写死了 mFullAccess = false 的,可以将手机 root 或者使用 magisk app 修改为 Debug
            if (!Build.IS_DEBUGGABLE) {
                 mUseArf = true;
                 mUseAra = true;
                 mFullAccess = false; // Per default we don't grant full access.
             } else {
                 String level = SystemProperties.get("service.seek", "useara usearf");
                 level = SystemProperties.get("persist.service.seek", level);
        
                 if (level.contains("usearf")) {
                    mUseArf = true;
                 } else {
                    mUseArf = false;
                 }
                 if (level.contains("useara")) {
                    mUseAra = true;
                 } else {
                    mUseAra = false;
                 }
                 if (level.contains("fullaccess")) {
    				// 全局只有这一处将 mFullAccess 赋值成了 true
    				// 也就是上面 SystemProperties 配置的 "service.seek" 值起了决定性作用
                    mFullAccess = true;
                 } else {
                    mFullAccess = false;
                 } 
             }
            
            if (!mTerminal.getName().startsWith(SecureElementService.UICC_TERMINAL)) {
                  // ARF is supported only on UICC.
                  mUseArf = false;
            }
            Log.i(mTag, "Allowed ACE mode: ara=" + mUseAra + " arf=" + mUseArf + " fullaccess="+ mFullAccess);
        }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79

    5.3.4 修改系统属性

    在系统已经 root 的情况下,直接使用命令修改掉这两个字段的值

    adb shell setprop ro.debuggable "1" //正常情况下 已root 设备就是 1
    adb shell setprop service.seek "useara usearf fullaccess"
    adb shell setprop persist.service.seek "useara usearf fullaccess"
    
    #设置后也可以查看
    adb shell getprop service.seek
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    5.3.5 调用日志

    17:56:39.838 4481  SecureElementService                com.android.se    D  getReaders() eSE1
    17:56:39.838 4481  SecureElementService                com.android.se    D  getReaders() SIM1
    17:56:39.838 4481  SecureElementService                com.android.se    D  getReaders() SIM2
    17:56:39.838 4481  SecureElementService                com.android.se    I  isCtsRunning false
    17:56:39.838 4481  SecureElementService                com.android.se    D  getReader() SIM1
    17:56:39.839 4481  SecureElementService                com.android.se    D  getReader() SIM2
    17:56:39.839 4481  SecureElementService                com.android.se    D  getReader() eSE1
    
    17:56:40.856 4481  SecureElementService                com.android.se    I  openLogicalChannel() AID = 00a4040000, P2 = 0
    17:56:40.857 4481  SecureElement-Terminal-eSE1         com.android.se    I  mzoma Access Check, pkg is com.ccsmec.sim
    17:56:40.859 4481  SecureElement-Terminal-eSE1         com.android.se    W  Enable access control on logical channel for com.ccsmec.sim
    17:56:40.859 4481  SecureElement-AccessControlEnforcer com.android.se    I  setUpChannelAccess() aid = 00a4040000
    17:56:40.859 4481  SecureElement-AccessControlEnforcer com.android.se    I  setUpChannelAccess() packageName = com.ccsmec.sim
    17:56:40.860 4481  SecureElement-Terminal-eSE1         com.android.se    I  mzoma Access Check, pkg is com.ccsmec.sim
    17:56:40.915 4481  SecureElement-AccessControlEnforcer com.android.se    I  checkCommand() : Access = ALLOWED APDU Access = ALLOWED Reason = Unspecified
    17:56:40.928 4481  SecureElement-Terminal-eSE1         com.android.se    I  Sent : 81cadf2000
    17:56:40.928 4481  SecureElement-Terminal-eSE1         com.android.se    I  Received : df20084a46485fc8d7b30b9000
    17:56:40.928 4481  SecureElement-AraController         com.android.se    I  Refresh tag unchanged. Using access rules from cache.
    17:56:40.962 4481  SecureElement-AccessControlEnforcer com.android.se    I  getAccessRule() appCert = dba6d7c5929b57a81387d144da3d04a5a3f32137
    17:56:40.962 4481  SecureElement-AccessControlEnforcer com.android.se    I  getAccessRule() appCert = a89c2be6dbe8eefbcce61b5c02a04d99862ef42fbb147c1a7f1d14e742e7db54
    17:56:40.964 4481  SecureElement-AccessRuleCache       com.android.se    I  findAccessRule() not found
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
  • 相关阅读:
    外汇天眼:想通过外汇交易在几个月内成为亿万富翁吗?你必须知道的七大交易法则
    K8S部署Dashboard
    淘宝获取收货地址列表的 API
    iOS 性能优化方案-弱网优化
    C语言实现几种常见的排序算法
    06.特殊CSS伪选择器
    使用mybatis-plus如何实现分页查询功能
    windows打包软件-Inno Setup
    Java并发杂谈(一):volatile的底层原理,从字节码到CPU
    小优化记录
  • 原文地址:https://blog.csdn.net/qq_39420519/article/details/127904009