安卓手机NFC模拟门禁卡(设置UID)的一种方法

2019-01-31 199391人围观 ,发现 3 个不明物体 极客

*本文作者:新希望鲜牛奶,本文属FreeBuf原创奖励计划,未经许可禁止转载。

本文通过对Android源码中NFC部分的简单分析,实现了另外一种设置UID的方式,可用于部分场景下的门禁卡模拟。

一、背景

本人就读于西南地区某大学,学校于2016年为学生宿舍楼大门安装了NFC门禁系统。这个时候手机的NFC技术已经相当成熟,网上充斥着各种手机模拟门禁、刷公交的帖子,各大手机厂商也与公交公司合作共同推进手机刷公交的进步。于是我也试着看看能不能用手机来刷开宿舍的门禁。我通过Acr122u将校园卡的UID写入一张MIFARE® Classic 1K兼容卡片后,成功刷开了宿舍的大门。

从08年NXP公司的MIFARE® Classic Cards被攻破后,M1卡就不再具有安全性,在如身份识别、电子钱包等需要一定安全性的场景下逐渐被安全性更高CPU卡取代。但是由于CPU卡本生比M1卡成本高,并且某些工程中大量使用的M1卡及相关系统全面更新将会是一大笔支出,加之新系统建设时监管不严,目前仍有部分工程中使用着M1卡。可笑的是16年安装的门禁居然是通过UID来进行身份验证(即使我们校园卡是复旦CPU卡)。安全建设的实施情况可见一斑。既然已经确定了它通过UID进行身份识别,那接下来的工作便是在手机上来模拟这样一张具有固定UID的卡片了。

二、原理分析

NFC设备有三种工作模式:Tag Reader/Writer、Peer to Peer、Card Emulation模式,详情可参见NFC Forum的介绍。现在很多安卓手机都具有NFC芯片,安卓系统也从Android 4.4开始原生提供了NFC卡片模拟的实现,即HCE。但是Android系统提供的卡模拟API是工作在国际智能卡标准ISO 7816-4下,同时Android也明确指出了使用ISO/IEC 14443-3协议中用于冲突检测的UID进行身份识别是不安全的,所以Android也没有提供控制UID的相关API,详情可参见这里。因此我们使用Android手机来进行卡模拟时,通过读卡器读到的UID通常是以 0×08 开头的随即值,这是ISO/IEC 14443-3标准的Anticollision部分要求的。当然这一点,不同的厂家有不同的实现,并且目前流行于Android平台的Broadcom和NXP这两家公司的芯片通常都可以通过修改配置文件的方式来指定UID。

如果在配置文件中没有指定UID,将由NFCC(NFC Controler)产生随机值。基于这点,网上有很多热心网友写了指定UID的教程,可以参见这里这里。甚至有人写了相应软件来更方便的修改UID。后来有些手机厂商甚至在自家应用中添加了门禁卡模拟的功能,比如(18年初?)更新的小米钱包。有些门禁是要读取卡内的除UID以外的其他信息的,M1卡它可能读取加密或不加密的Sector,而CPU卡你也很难知道它会读取哪个DF里的信息,以及是否需要密钥认证。因此通用的门禁模拟软件还大多停留在UID的模拟上,本文也只讨论如何设置固定的NFCID1。

三、修改配置文件

经过前面的分析,我开始在Mi 5s Plus手机上进行尝试。这款手机采用了NXP的 pn551 芯片,在文档AN11690.pdf中介绍了NXP的NFC芯片在Android下的移植过程。从文档中我们得知在Android O平台上的移植需要用到 libnfc-brcm.conf、libnfc-nxp.conf 这两个配置文件,在Android P上则变为了 libnfc-nci.conf 和 libnfc-nxp.conf 这两个配置文件。我在手机上刷入了LineageOS 15.1,在 /vendor/etc/ 路径下可以找到这两个配置文件。通过修改libnfc-brcm.conf中的APPL_TRACE_LEVEL和PROTOCOL_TRACELEVEL日志级别可以在logcat中看到NCI协议栈及NFC HAL层详细的调试信息,libnfc-nxp.conf中修改NXPLOG*_LOGLEVEL来更改日志级别。按照前面帖子介绍的方式修改了NFA_DM_START_UP_CFG和NXP_CORE_CONF,杀死 com.android.nfc 进程重启NFC服务。

NFC服务有个 android:persistent=”true” 属性, ActivityManager 检测到进程被杀死后会自动重启它。从logcat中可以看到两个配置文件均被加载了,但是读卡器读到的UID仍然是 0×08 开头的NFCID3。使用小米钱包的门禁模拟功能应该是可以成功的,看网上的介绍说支持Mi 5s Plus,但我不想为了刷个门禁刷回MIUI。于是我开始尝试着用其它的方式来解决问题。

四、安卓系统如何与NFC硬件交流

LineageOS源代码clone到本地Lineageos目录下,确保能为Mi 5s Plus设备正常编译。以下实验均在此目录下完成。我们首先通过AN11690.pdf中的一幅图来整体认识一下NFC在Android平台的实现。Android NFC stack overview安卓底层是基于Linux内核的,因此驱动一个硬件设备的Linux设备驱动必不可少。代码位于 Lineageos/kernel/xiaomi/msm8996/drivers/nfc,编译后在内核镜像中。

HAL意为硬件抽象层,运行在用户空间,与内核中实现设备基本操作的Linux设备驱动共同组成完整的设备驱动。HAL的最初目的是规避Linux内核GPL协议,现在已发展为规范设备驱动程序编写,便于移植。详情可以参见这里Android Treble详细分析。Android O开始强制使用HIDL来定义HAL接口,NFC HAL代码位于 Lineageos/hardware/interfaces/nfc,编译后生成 android.hardware.nfc@1.0.so , android.hardware.nfc@1.0-impl.so , android.hardware.nfc@1.0-service, 启动NFC HAL的脚本 android.hardware.nfc@1.0-service.rc

NCI层实现了NFC协议栈,上层通过它与NFCC进行通信。NCI的实现与蓝牙协议栈在Android的实现类似。代码位于 Lineageos/system/nfc,编译后生成 libnfc-nci.so 以及 nfc_nci.msm8996.so

通过JNI实现Android框架中Java代码与NCI中的代码相互调用。代码位于 Lineageos/packages/apps/Nfc/nci/jni,编译后生成 libnfc_nci_jni.so

与蓝牙类似,NFC在Android中也以服务的形式存在,Android Framework通过AIDL与服务通信。NFC Service代码位于 Lineageos/packages/apps/Nfc,对应NXP的芯片编译后生成 NfcNci.apk,而Broadcom的芯片生成 Nfc.apk

Android APP通过调用Android框架提供的API来使用NFC功能。

五、NFC Enable流程

上一节介绍了NFC在Android的总体结构,本节结合具体代码来跟踪一下当我们点击设置菜单里的NFC按钮后NFC Enable的具体流程。

首先找到Preferences中切换NFC这个开关。系统设置是一个软件包,代码位于 Lineageos/packages/apps/Settings。从Android项目中文件及目录的命名可以看出Android的命名是相当规范的,因此我们进入到这个目录后应该就能猜出它会通过 NfcEnabler.java 中的 NfcEnabler 类的相关方法来启用NFC。当然,我们也可以一步步把它找出来。

strings.xml 找到如下与设置界面一致的字符串:

    <string name="connected_devices_dashboard_title" msgid="2355264951438890709">"已连接的设备"</string>

搜索以下看哪些布局用到这个字符串,在 connected_devices.xml 中找到:

    <SwitchPreference
        android:key="toggle_nfc"
        android:title="@string/nfc_quick_toggle_title"
        android:icon="@drawable/ic_nfc"
        android:summary="@string/nfc_quick_toggle_summary"
        android:order="-5"/>

以上布局是如何被加载的这里不用关心,知道PreferenceScreen可以通过key找到这个组件就行啦。以toggle_nfc为关键字搜索java代码,可以发现 NfcPreferenceController.java 用到了它:

    public class NfcPreferenceController extends AbstractPreferenceController
            implements PreferenceControllerMixin, LifecycleObserver, OnResume, OnPause {

        public static final String KEY_TOGGLE_NFC = "toggle_nfc";
        public static final String KEY_ANDROID_BEAM_SETTINGS = "android_beam_settings";

        private NfcEnabler mNfcEnabler;
        private NfcAdapter mNfcAdapter;
        ......
        @Override
        public void displayPreference(PreferenceScreen screen) {
            if (!isAvailable()) {
                removePreference(screen, KEY_TOGGLE_NFC);
                removePreference(screen, KEY_ANDROID_BEAM_SETTINGS);
                mNfcEnabler = null;
                return;
            }
            mNfcPreference = (SwitchPreference) screen.findPreference(KEY_TOGGLE_NFC);
            mBeamPreference = (RestrictedPreference) screen.findPreference(
                    KEY_ANDROID_BEAM_SETTINGS);
            mNfcEnabler = new NfcEnabler(mContext, mNfcPreference, mBeamPreference);
            // Manually set dependencies for NFC when not toggleable.
            if (!isToggleableInAirplaneMode(mContext)) {
                mAirplaneModeObserver = new AirplaneModeObserver();
                updateNfcPreference();
            }
        }
        ......
    }

从上面的代码可以看出显示这个Fragment的时候new了一个NfcEnabler对象,正是通过它来进行NFC的开与关。下面截取 NfcEnabler.java 部分代码:

    /**
    * NfcEnabler is a helper to manage the Nfc on/off checkbox preference. It is
    * turns on/off Nfc and ensures the summary of the preference reflects the
    * current state.
    */
    public class NfcEnabler implements Preference.OnPreferenceChangeListener {
        private final Context mContext;
        private final SwitchPreference mSwitch;
        private final RestrictedPreference mAndroidBeam;
        private final NfcAdapter mNfcAdapter;
        private final IntentFilter mIntentFilter;
        private boolean mBeamDisallowedBySystem;

        private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
            @Override
            public void onReceive(Context context, Intent intent) {
                String action = intent.getAction();
                if (NfcAdapter.ACTION_ADAPTER_STATE_CHANGED.equals(action)) {
                    handleNfcStateChanged(intent.getIntExtra(NfcAdapter.EXTRA_ADAPTER_STATE,
                            NfcAdapter.STATE_OFF));
                }
            }
        };
        ......
        public boolean onPreferenceChange(Preference preference, Object value) {
            // Turn NFC on/off

            final boolean desiredState = (Boolean) value;
            mSwitch.setChecked(desiredState);
            mSwitch.setEnabled(false);

            if (desiredState) {
                mNfcAdapter.enable();
            } else {
                mNfcAdapter.disable();
            }

            return false;
        }
        ......
    }

可以看到在这个Listener中创建了一个Brodcasteceiver,当我们点击NFC设置项那个SwitchPreference(相当于ListView的自定义item)时,它就会收到广播,并通过NfcAdapter来开关NFC。

前面我们知道,通过调用NfcAdapter.enable()方法来进行NFC硬件的开关。它具体又做了些什么事呢?我们来看看 Lineageos/frameworks/base/core/java/android/nfc/NfcAdapter.java

    @SystemApi
    @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)
    public boolean enable() {
        try {
            return sService.enable();
        } catch (RemoteException e) {
            attemptDeadServiceRecovery(e);
            return false;
        }
    }

可以看出这是一个系统API,也就是说我们编写的一般应用是不能调用这个API的。sService是一个static INfcAdapter的对象,INfcAdapterAIDL定义的接口,用于调用NfcService的方法。可以看出它执行了Service的enable()方法。代码位于 Lineageos/packages/apps/Nfc/NfcService.java,相关aidl也定义在这个目录。实现如下:

    @Override
    public boolean enable() throws RemoteException {
        NfcPermissions.enforceAdminPermissions(mContext);
        saveNfcOnSetting(true);
        new EnableDisableTask().execute(TASK_ENABLE);
        return true;
    }

NfcService作为系统服务,由NfcNci.apk提供,并在开机时启动由NfcApplication启动。下面我们来看看NfcService在这个异步任务里面又做了些什么。

class EnableDisableTask extends AsyncTask<Integer, Void, Void> {
        @Override
        protected Void doInBackground(Integer... params) {
            ......
            switch (params[0].intValue()) {
                case TASK_ENABLE:
                    enableInternal();
                    break;
                case TASK_DISABLE:
                    disableInternal();
                    break;
                case TASK_BOOT:
                    Log.d(TAG, "checking on firmware download");
                    if (mPrefs.getBoolean(PREF_NFC_ON, NFC_ON_DEFAULT)) {
                        Log.d(TAG, "NFC is on. Doing normal stuff");
                        enableInternal();
                    } else if (!isSecHal()) {
                        Log.d(TAG, "NFC is off.  Checking firmware version");
                        mDeviceHost.checkFirmware();
                    }
                    ......
            }
            ......
        }

        /**
         * Enable NFC adapter functions.
         * Does not toggle preferences.
         */
        boolean enableInternal() {
            if (mState == NfcAdapter.STATE_ON) {
                return true;
            }
            Log.i(TAG, "Enabling NFC");
            updateState(NfcAdapter.STATE_TURNING_ON);

            WatchDogThread watchDog = new WatchDogThread("enableInternal", INIT_WATCHDOG_MS);
            watchDog.start();
            try {
                mRoutingWakeLock.acquire();
                try {
                    if (!mDeviceHost.initialize()) {
                        Log.w(TAG, "Error enabling NFC");
                        updateState(NfcAdapter.STATE_OFF);
                        return false;
                    }
                } finally {
                    mRoutingWakeLock.release();
                }
            } finally {
                watchDog.cancel();
            }

            if (mIsHceCapable) {
                // Generate the initial card emulation routing table
                mCardEmulationManager.onNfcEnabled();
            }

            nci_version = getNciVersion();
            Log.d(TAG, "NCI_Version: " + nci_version);

            synchronized (NfcService.this) {
                mObjectMap.clear();
                mP2pLinkManager.enableDisable(mIsNdefPushEnabled, true);
                updateState(NfcAdapter.STATE_ON);
            }

            initSoundPool();

            mScreenState = mScreenStateHelper.checkScreenState();
            int screen_state_mask = (mNfcUnlockManager.isLockscreenPollingEnabled()) ?
                             (ScreenStateHelper.SCREEN_POLLING_TAG_MASK | mScreenState) : mScreenState;

            if(mNfcUnlockManager.isLockscreenPollingEnabled())
                applyRouting(false);

            mDeviceHost.doSetScreenState(screen_state_mask);

            /* Start polling loop */

            applyRouting(true);
            return true;
        }
        ......
    }

enableInternal方法里调用了mDeviceHost的initialize()方法。

mDeviceHost = new NativeNfcManager(mContext, this);

在nci和nxp目录下都有相应的 NativeNfcManager.java 实现了DeviceHost接口。从 Android.mk 中可以看出他们分属于两个不同的Package:NfcNciNfc。这里有两个包是因为以前Android平台的NFC HAL层没有一个统一的接口,NfcNci对应的是Broadcom公司NFC芯片的实现,而Nfc对应的是NXP公司的芯片。在Linageos 15.1中Mi 5s Plus采用的这款NXP的pn54x芯片,用的是NfcNci的代码实现,说明两家公司NCI的实现终于还是统一了。从手机/system/lib*/下的libnfc-nci.so、libnfc_nci_jni.so,以及/system/app/NfcNci.apk都可以看出的确是用的NfcNci这个Package,当然我们也可以从 Lineageos/device/xiaomi/msm8996-common/msm8996.mk 得到印证。其中包含的这部分代码:

# NFC
PRODUCT_PACKAGES += \
    android.hardware.nfc@1.0-impl \
    android.hardware.nfc@1.0-service \
    com.android.nfc_extras \
    nfc_nci.msm8996 \
    NfcNci \
    Tag

我们来看看NativeNfcManager类的initialize()方法:

    private native boolean doInitialize();
    private native int getIsoDepMaxTransceiveLength();
    @Override
    public boolean initialize() {
        boolean ret = doInitialize();
        mIsoDepMaxTransceiveLength = getIsoDepMaxTransceiveLength();
        return ret;
    }

它调用了一个名为doInitialize的native方法。这个方法通过jniRegisterNativeMethods注册到了函数nfcManager_doInitialize,其实最终调用的是JNIEnv里面的RegisterNatives函数来完成动态注册,这里Android对它进行了一下封装。下面我们来看看nfcManager_doInitialize这个函数。

static jboolean nfcManager_doInitialize (JNIEnv* e, jobject o)
{
    ALOGV("%s: enter; ver=%s nfa=%s NCI_VERSION=0x%02X",
        __func__, nfca_version_string, nfa_version_string, NCI_VERSION);
    tNFA_STATUS stat = NFA_STATUS_OK;

    PowerSwitch & powerSwitch = PowerSwitch::getInstance ();

    if (sIsNfaEnabled)
    {
        ALOGV("%s: already enabled", __func__);
        goto TheEnd;
    }

    powerSwitch.initialize (PowerSwitch::FULL_POWER);

    {
        unsigned long num = 0;

        NfcAdaptation& theInstance = NfcAdaptation::GetInstance();
        theInstance.Initialize(); //start GKI, NCI task, NFC task

        {
            SyncEventGuard guard (sNfaEnableEvent);
            tHAL_NFC_ENTRY* halFuncEntries = theInstance.GetHalEntryFuncs ();

            NFA_Init (halFuncEntries);

            stat = NFA_Enable (nfaDeviceManagementCallback, nfaConnectionCallback);
            if (stat == NFA_STATUS_OK)
            {
                num = initializeGlobalAppLogLevel ();
                CE_SetTraceLevel (num);
                LLCP_SetTraceLevel (num);
                NFC_SetTraceLevel (num);
                RW_SetTraceLevel (num);
                NFA_SetTraceLevel (num);
                NFA_P2pSetTraceLevel (num);
                sNfaEnableEvent.wait(); //wait for NFA command to finish
            }
            EXTNS_Init (nfaDeviceManagementCallback, nfaConnectionCallback);
        }

        if (stat == NFA_STATUS_OK)
        {
            //sIsNfaEnabled indicates whether stack started successfully
            if (sIsNfaEnabled)
            {
                RoutingManager::getInstance().initialize(getNative(e, o));
                nativeNfcTag_registerNdefTypeHandler ();
                NfcTag::getInstance().initialize (getNative(e, o));
                PeerToPeer::getInstance().initialize ();
                PeerToPeer::getInstance().handleNfcOnOff (true);

                /////////////////////////////////////////////////////////////////////////////////
                // Add extra configuration here (work-arounds, etc.)

                if (gIsDtaEnabled == true)
                {
                    uint8_t configData = 0;
                    configData = 0x01;    /* Poll NFC-DEP : Highest Available Bit Rates */
                    NFA_SetConfig(NFC_PMID_BITR_NFC_DEP, sizeof(uint8_t), &configData);
                    configData = 0x0B;    /* Listen NFC-DEP : Waiting Time */
                    NFA_SetConfig(NFC_PMID_WT, sizeof(uint8_t), &configData);
                    configData = 0x0F;    /* Specific Parameters for NFC-DEP RF Interface */
                    NFA_SetConfig(NFC_PMID_NFC_DEP_OP, sizeof(uint8_t), &configData);
                }

                struct nfc_jni_native_data *nat = getNative(e, o);

                if ( nat )
                {
                    if (GetNumValue(NAME_POLLING_TECH_MASK, &num, sizeof(num)))
                        nat->tech_mask = num;
                    else
                        nat->tech_mask = DEFAULT_TECH_MASK;
                    ALOGV("%s: tag polling tech mask=0x%X", __func__, nat->tech_mask);
                }

                // if this value exists, set polling interval.
                if (GetNumValue(NAME_NFA_DM_DISC_DURATION_POLL, &num, sizeof(num)))
                    nat->discovery_duration = num;
                else
                    nat->discovery_duration = DEFAULT_DISCOVERY_DURATION;

                NFA_SetRfDiscoveryDuration(nat->discovery_duration);

                // get LF_T3T_MAX
                {
                    SyncEventGuard guard (sNfaGetConfigEvent);
                    tNFA_PMID configParam[1] = {NCI_PARAM_ID_LF_T3T_MAX};
                    stat = NFA_GetConfig(1, configParam);
                    if (stat == NFA_STATUS_OK)
                    {
                        sNfaGetConfigEvent.wait ();
                        if (sCurrentConfigLen >= 4 || sConfig[1] == NCI_PARAM_ID_LF_T3T_MAX) {
                            ALOGV("%s: lfT3tMax=%d", __func__, sConfig[3]);
                            sLfT3tMax = sConfig[3];
                        }
                    }
                }

                prevScreenState = NFA_SCREEN_STATE_OFF_LOCKED;

                // Do custom NFCA startup configuration.
                doStartupConfig();
                goto TheEnd;
            }
        }

        ALOGE("%s: fail nfa enable; error=0x%X", __func__, stat);

        if (sIsNfaEnabled)
        {
            EXTNS_Close ();
            stat = NFA_Disable (FALSE /* ungraceful */);
        }

        theInstance.Finalize();
    }

TheEnd:
    if (sIsNfaEnabled)
        PowerSwitch::getInstance ().setLevel (PowerSwitch::LOW_POWER);
    ALOGV("%s: exit", __func__);
    return sIsNfaEnabled ? JNI_TRUE : JNI_FALSE;
}

可以看到,它调用了NfcAdaptationInitialize()方法和NFA_SetConfig()等在libnfc-nci中定义的API函数,对硬件和GKI、NFA等子系统进行了初始化,最后启动Discovery。再往下就是HAL层调用,这里算是和硬件打上交道了。至此enable过程分析完成。

六、从NCI层入手

从上面NFC Service的相关分析也可以看出,安卓系统正是通过NCI层来与NFCC进行交互的。因此我们只要合理调用libnfc-nci.so中的函数,也能达到控制NFCC的目的,当然也应该可以实现设置UID的目的。这里不再对NCI层代码作详细分析,感兴趣的同学可以参考Bluetooth在Android的实现,他们是差不多的。网上关于Bluetooth分析的文章非常多,这里推荐一个CSDN博主风语比较全面的分析。

通过分析我们知道Nfc Service启动Rf Discovery时会调用libnfc-nci中的NFA_StartRfDiscovery()函数,这个函数会发送一个表示事件NFA_DM_API_START_RF_DISCOVERY_EVT的消息,经过消息分发后会执行nfa_dm_start_rf_discover()函数,在此函数中又会调用nfa_dm_set_rf_listen_mode_config()。在nfa_dm_set_rf_listen_mode_config()函数中设置了Listen的参数,但是没有指定NFCID1,将由NFCC自行决定(NCI协议规定为 0×80 开头的随机值)。下面截取该函数的部分代码:

static tNFA_STATUS nfa_dm_set_rf_listen_mode_config(
    tNFA_DM_DISC_TECH_PROTO_MASK tech_proto_mask) {
  uint8_t params[40], *p;
  uint8_t platform = 0;
  uint8_t sens_info = 0;
......
  p = params;
  /*
  ** for Listen A
  **
  ** Set ATQA 0x0C00 for T1T listen
  ** If the ATQA values are 0x0000, then the FW will use 0x0400
  ** which works for ISODEP, T2T and NFCDEP.
  */
  if (nfa_dm_cb.disc_cb.listen_RT[NFA_DM_DISC_LRT_NFC_A] ==
      NFA_DM_DISC_HOST_ID_DH) {
    UINT8_TO_STREAM(p, NFC_PMID_LA_BIT_FRAME_SDD);
    UINT8_TO_STREAM(p, NCI_PARAM_LEN_LA_BIT_FRAME_SDD);
    UINT8_TO_STREAM(p, 0x04);
    UINT8_TO_STREAM(p, NFC_PMID_LA_PLATFORM_CONFIG);
    UINT8_TO_STREAM(p, NCI_PARAM_LEN_LA_PLATFORM_CONFIG);
    UINT8_TO_STREAM(p, platform);
    UINT8_TO_STREAM(p, NFC_PMID_LA_SEL_INFO);
    UINT8_TO_STREAM(p, NCI_PARAM_LEN_LA_SEL_INFO);
    UINT8_TO_STREAM(p, sens_info);
  } 
......
  if (p > params) {
    nfa_dm_check_set_config((uint8_t)(p - params), params, false);
  }
  return NFA_STATUS_OK;
}

从以上代码可以看出在设置的参数中没有NFCID1,我们在UINT8_TO_STREAM(p, sens_info);之后加入设置NFCID1的代码:

    UINT8_TO_STREAM(p, NFC_PMID_LA_NFCID1);// parameter type is nfcid1
    UINT8_TO_STREAM(p, 0x04); // parameter length
    UINT8_TO_STREAM(p, 0x01); 
    UINT8_TO_STREAM(p, 0x02);
    UINT8_TO_STREAM(p, 0x03);
    UINT8_TO_STREAM(p, 0x04);

在LineageOS代码根目录使用mmm system/nfc即可编译这个模块。使用adb push将生成的 libnfc-nci.so 传送到手机的 /system/lib64/,通过kill命令杀死 com.android.nfc 进程,NFC Service将自动重启。通过读卡器读取手机模拟的NFC卡片UID为:01020304。实验成功。

将UID写死可不是我们想要的,既然通过上面的函数将UID写入到NFCC就会生效,那么我们自己写软件来调用这个函数设置UID可以不能?答案是可以的。下面我们将通过写程序来动态控制UID。

从上一节的分析我们可以看出NFA模块的初始化是比较复杂的,因此我们直接在程序中加载libnfc-nci.so来调用它提供的API是会崩溃的,除非我们也如同NFC Service那样进行以系列初始化工作。我们应该在初始化完成的环境中来调用API,所以我们需要注入到 com.android.nfc 进程中去。我在demo中用的注入工具是TinyInjector,当然我们的目的是仅仅是把动态库加载到目标进程中去,用xposed等框架也是可以的。寻找目标函数在进程空间的地址也是个麻烦事,我直接使用了iqiyi团队开源的xHook将目标函数地址替换为我的函数地址,然后在我的函数里调用目标函数,也算是一种曲线救国的方式。我选择调用nfa_dm_set_config来设置参数,这个函数会在NFA_SetConfig调用后作为消息处理函数被调用。设置UID后需要重启Listening来使配置生效,这里通过调用NFC_Deactivate函数将NFCC设置为IDLE状态再设置为DISCOVERY状态实现重启,通过其他如Stop/StartRf函数也是可以的。测试代码在这里

七、总结

为了给NFCC设置固定的UID,从而达到模拟门禁卡的目的。本文先尝试了网上广泛流传的修改配置文件的方式,在尝试未果后结合Android的源代码分析,实现了通过注入来设置UID的一种方式。该方法与修改配置文件的方法均需要root权限,同时修改配置文件的方法在新机器上还需要解锁system分区,而本方法则不需要。我们的目的是把so注入到目标进程中去,但是为了动态改变UID,我们还需要与动态库进行通信。Android上跨进程的java与native通信可以用grpc或者自己写socket通信。如果我们写成xposed模块,则可以使用xposed自带的注入,还可以在目标进程中建立Broadcast Receiver来接收控制APP的指令,在模块内直接通过jni即可调用我们native函数。

*本文作者:新希望鲜牛奶,本文属FreeBuf原创奖励计划,未经许可禁止转载。

发表评论

已有 3 条评论

取消
Loading...
css.php