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

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

本文通过对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通常是以 0x08 开头的随即值,这是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仍然是 0x08 开头的NFCID3。使用小米钱包的门禁模拟功能应该是可以成功的,看网上的介绍说支持Mi 5s Plus,但我不想为了刷个门禁刷回MIUI。于是我开始尝试着用其它的方式来解决问题。

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

LineageOS源代码clone到本地Lineageos目录下,确保能为Mi 5s Plus设备正常编译。以下实验均在此目录下完成。我们首先通过AN11690.pdf中的一幅图来整体认识一下NFC在Android平台的实现。

安卓底层是基于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的对象,INfcAdapter是AIDL定义的接口,用于调用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协议规定为 0x80 开头的随机值)。下面截取该函数的部分代码:

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原创奖励计划,未经许可禁止转载。

本文分享自微信公众号 - FreeBuf(freebuf)

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2019-01-31

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

推荐阅读

  • 远程办公经验为0,如何将日常工作平滑过度到线上?

    我是一名创业者,我的公司(深圳市友浩达科技有限公司)在2018年8月8日开始运营,现在还属于微型公司。这个春节假期,我一直十分关注疫情动向,也非常关心其对公司带来的影响。

    TVP官方团队
    TAPD 敏捷项目管理腾讯乐享企业邮箱企业编程算法
  • 数据中台,概念炒作还是另有奇效? | TVP思享

    作者简介:史凯,花名凯哥,腾讯云最具价值专家TVP,ThoughtWorks数据智能业务总经理。投身于企业数字化转型工作近20年。2000年初,在IBM 研发企业级中间件,接着加入埃森哲,为大型企业提供信息化架构规划,设计,ERP,云平台,数据仓库构建等技术咨询实施服务,随后在EMC负责企业应用转型业务,为企业提供云迁移,应用现代化服务。现在专注于企业智能化转型领域,是数据驱动的数字化转型的行业布道者,数据中台的推广者,精益数据创新体系的创始人,2019年荣获全球Data IQ 100人的数据赋能者称号,创业邦卓越生态聚合赋能官TOP 5。2019年度数字化转型专家奖。打造了行业第一个数据创新的数字化转型卡牌和工作坊。创建了精益数据创新方法论体系构建数据驱动的智能企业,并在多个企业验证成功,正在向国内外推广。

    TVP官方团队
    大数据数据分析企业
  • 扩展 Kubernetes 之 CRI

    使用 cri-containerd 的调用流程更为简洁, 省去了上面的调用流程的 1,2 两步

    王磊-AI基础
    Kubernetes
  • 扩展 Kubernetes 之 Kubectl Plugin

    kubectl 功能非常强大, 常见的命令使用方式可以参考 kubectl --help,或者这篇文章

    王磊-AI基础
    Kubernetes
  • 多种登录方式定量性能测试方案

    最近接到到一个测试任务,某服务提供了两种登录方式:1、账号密码登录;2、手机号+验证码登录。要对这两种登录按照一定的比例进行压测。

    八音弦
    测试服务 WeTest
  • 线程安全类在性能测试中应用

    首先验证接口参数签名是否正确,然后加锁去判断订单信息和状态,处理用户增添VIP时间事务,成功之后释放锁。锁是针对用户和订单的分布式锁,使用方案是用的redis。

    八音弦
    安全编程算法
  • 使用CDN(jsdelivr) 优化博客访问速度

    PS: 此篇文章适用于 使用 Github pages 或者 coding pages 的朋友,其他博客也类似.

    IFONLY@CUIT
    CDNGitGitHub开源
  • 扩展 Kubernetes 之 CNI

    Network Configuration 是 CNI 输入参数中最重要当部分, 可以存储在磁盘上

    王磊-AI基础
    Kubernetes
  • 聚焦【技术应变力】云加社区沙龙online重磅上线!

    云加社区结合特殊时期热点,挑选备受关注的音视频流量暴增、线下业务快速转线上、紧急上线防疫IoT应用等话题,邀请众多业界专家,为大家提供连续十一天的干货分享。从视野、预判、应对等多角度,帮助大家全面提升「技术应变力」!

    腾小云
  • 京东购物小程序购物车性能优化实践

    它是小程序开发工具内置的一个可视化监控工具,能够在 OS 级别上实时记录系统资源的使用情况。

    WecTeam
    渲染JavaScripthttps网络安全缓存

扫码关注云+社区

领取腾讯云代金券