科技

安卓手機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,編譯後生成 [email protected] , [email protected] , [email protected], 啟動NFC HAL的指令碼 [email protected]

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 找到如下與設定介面一致的字串:

"已連線的裝置"搜尋以下看哪些佈局用到這個字串,在 connected_devices.xml 中找到:

以上佈局是如何被載入的這裡不用關心,知道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 { @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:NfcNci和Nfc。這裡有兩個包是因為以前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 得到印證。其中包含的這部分程式碼:# NFCPRODUCT_PACKAGES += \ [email protected] \ [email protected] \ com.android.nfc_extras \ nfc_nci.msm8996 \ NfcNci \ Tag我們來看看NativeNfcManager類的 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對它進行了一下封裝。下面我們來看看這個函式。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] = ; 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;}可以看到,它呼叫了NfcAdaptation的Initialize方法和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。在函式中設定了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原創獎勵計劃,未經許可禁止轉載。

Reference:科技日報

看更多!請加入我們的粉絲團

轉載請附文章網址

不可錯過的話題