<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          USB 協(xié)議核心概念與實踐

          共 15612字,需瀏覽 32分鐘

           ·

          2021-03-06 01:53

          USB,全稱是?Universal Serial Bus[1],即通用串行總線,既是一個針對電纜和連接器的工業(yè)標準,也指代其中使用的連接協(xié)議。本文不會過多介紹標準中的細節(jié),而是從軟件工程師的角度出發(fā),介紹一些重要的基本概念,以及實際的主機和從機應用。最后作為實際案例,從 USB 協(xié)議實現(xiàn)的角度分析了checkm8漏洞的成因。

          USB 101

          首先要明確的一點,USB 協(xié)議是以主機為中心的 (Host Centric),也就是說只有主機端向設備端請求數(shù)據(jù)后,設備端才能向主機發(fā)送數(shù)據(jù)。從數(shù)據(jù)的角度來看,開發(fā)者最直接接觸的就是端點 (Endpoint),端點可以看做是數(shù)據(jù)收發(fā)的管道。

          當主機給設備發(fā)送數(shù)據(jù)時,通常流程是:

          ?調用用戶層 API,如?libusb_bulk_transfer?對內核的 USB 驅動執(zhí)行對應系統(tǒng)調用,添加發(fā)送隊列如?ioctl(IOCTL_USBFS_SUBMITURB)?內核驅動中通過 HCI 接口向 USB 設備發(fā)送請求+數(shù)據(jù)?數(shù)據(jù)發(fā)送到設備端的 Controller -> HCI -> Host

          設備給主機發(fā)送請求也是類似,只不過由于是主機中心,發(fā)送的數(shù)據(jù)會保存在緩存中,等待主機發(fā)送 IN TOKEN 之后才真正發(fā)送到主機。在介紹數(shù)據(jù)發(fā)送流程之前,我們先來看下描述符。

          描述符

          所有的 USB 設備端設備,都使用一系列層級的描述符 (Descriptors) 來向主機描述自身信息。這些描述符包括:

          ?Device Descriptors: 設備描述?Configuration Descriptors: 配置描述?Interface Descriptors: 接口描述?Endpoint Descriptors: 端點描述?String Descriptors: 字符串描述

          它們之間的層級結構關系如下:

          83dc933d27676a97011638ae84f04738.webpdes.png

          每種描述符都有對應的數(shù)據(jù)結構,定義在標準中的第九章,俗稱 ch9。下面以 Linux 內核的實現(xiàn)為例來簡要介紹各個描述符,主要參考頭文件?include/uapi/linux/usb/ch9.h

          設備描述

          每個 USB 設備只能有一個設備描述(Device Descriptor),該描述符中包括了設備的 USB 版本、廠商、產品 ID 以及包含的配置描述符個數(shù)等信息,如下所示:

          /* USB_DT_DEVICE: Device descriptor */struct usb_device_descriptor {    __u8  bLength;// 18 字節(jié)    __u8  bDescriptorType;// 0x01    __le16 bcdUSB;// 設備所依從的 USB 版本號    __u8  bDeviceClass;// 設備類型    __u8  bDeviceSubClass;// 設備子類型    __u8  bDeviceProtocol;// 設備協(xié)議    __u8  bMaxPacketSize0;// ep0 的最大包長度,有效值為 8,6,32,64    __le16 idVendor;// 廠商號    __le16 idProduct;// 產品號    __le16 bcdDevice;// 設備版本號    __u8  iManufacturer;// 產商字名稱    __u8  iProduct;// 產品名稱    __u8  iSerialNumber;// 序列號    __u8  bNumConfigurations;// 配置描述符的個數(shù)} __attribute__ ((packed));#define USB_DT_DEVICE_SIZE        18

          每個字段的含義都寫在注釋中了,其中有幾點值得一提。

          ?設備類型、子類型和協(xié)議碼,是由 USB 組織定義的;?產商號也是由 USB 組織定義的,但是產品號可以由廠商自行定義;?廠商、產品和序列號分別只有 1 字節(jié),表示在字符串描述符中的索引;

          BCD: binary- coded decimal

          配置描述

          每種不同的配置描述(Configuration Descriptor)中分別指定了 USB 設備所支持的配置,如功率等信息;一個 USB 設備可以包含多個配置,但同一時間只能有一個配置是激活狀態(tài)。實際上大部分的 USB 設備都只包含一個配置描述符。

          /* USB_DT_CONFIG: Configuration descriptor information.** USB_DT_OTHER_SPEED_CONFIG is the same descriptor,except that the* descriptor type is different.Highspeed-capable devices can look* different depending on what speed they're currently running.  Only* devices with a USB_DT_DEVICE_QUALIFIER have any OTHER_SPEED_CONFIG* descriptors.*/struct usb_config_descriptor {    __u8  bLength;// 9    __u8  bDescriptorType;// 0x02    __le16 wTotalLength;// 返回數(shù)據(jù)的總長度    __u8  bNumInterfaces;// 接口描述符的個數(shù)    __u8  bConfigurationValue;// 當前配置描述符的值 (用來選擇該配置)    __u8  iConfiguration;// 該配置的字符串信息 (在字符串描述符中的索引)    __u8  bmAttributes;// 屬性信息    __u8  bMaxPower;// 最大功耗,以 2mA 為單位} __attribute__ ((packed));#define USB_DT_CONFIG_SIZE        9

          當主設備讀取配置描述的時候,從設備會返回該配置下所有的其他描述符,如接口、端點和字符串描述符,因此需要?wTotalLength?來表示返回數(shù)據(jù)的總長度。

          bmAttributes?指定了該配置的電源參數(shù)信息,D6 表示是否為自電源驅動;D5 表示是否支持遠程喚醒;D7 在 USB1.0 中曾用于表示是否為總線供電的設備,但是在 USB2.0 中被?bMaxPower?字段取代了,該字段表示設備從總線上消耗的電壓最大值,以 2mA 為單位,因此最大電流大約是?0xff * 2mA = 510mA

          接口描述

          一個配置下有多個接口,可以看成是一組相似功能的端點的集合,每個接口描述符的結構如下:

          /* USB_DT_INTERFACE: Interface descriptor */struct usb_interface_descriptor {    __u8  bLength;    __u8  bDescriptorType;// 0x04    __u8  bInterfaceNumber;// 接口序號    __u8  bAlternateSetting;    __u8  bNumEndpoints;    __u8  bInterfaceClass;    __u8  bInterfaceSubClass;    __u8  bInterfaceProtocol;    __u8  iInterface;// 接口的字符串描述,同上} __attribute__ ((packed));#define USB_DT_INTERFACE_SIZE        9

          其中接口類型、子類型和協(xié)議與前面遇到的類似,都是由 USB 組織定義的。在 Linux 內核中,每個接口封裝成一個高層級的功能,即邏輯鏈接(Logical Connection),例如對 USB 攝像頭而言,接口可以分為視頻流、音頻流和鍵盤(攝像頭上的控制按鍵)等。

          還有值得一提的是?bAlternateSetting,每個 USB 接口都可以有不同的參數(shù)設置,例如對于音頻接口可以有不同的帶寬設置。實際上 Alternate Settings 就是用來控制周期性的端點參數(shù)的,比如 isochronous endpoint。

          端點描述

          端點描述符用來描述除了零端點(ep0)之外的其他端點,零端點總是被假定為控制端點,并且在開始請求任意描述符之前就已經被配置好了。端點(Endpoint),可以認為是一個單向數(shù)據(jù)信道的抽象,因此端點描述符中包括傳輸?shù)乃俾屎蛶挼刃畔ⅲ缦滤?

          /* USB_DT_ENDPOINT: Endpoint descriptor */struct usb_endpoint_descriptor {    __u8  bLength;    __u8  bDescriptorType;// 0x05    __u8  bEndpointAddress;// 端點地址    __u8  bmAttributes;// 端點屬性    __le16 wMaxPacketSize;// 該端點收發(fā)的最大包大小    __u8  bInterval;// 輪詢間隔,只對 Isochronous 和 interrupt 傳輸類型的端點有效 (見下)/* NOTE:  these two are _only_ in audio endpoints. *//* use USB_DT_ENDPOINT*_SIZE in bLength, not sizeof. */    __u8  bRefresh;    __u8  bSynchAddress;} __attribute__ ((packed));#define USB_DT_ENDPOINT_SIZE        7#define USB_DT_ENDPOINT_AUDIO_SIZE    9/* Audio extension */

          bEndpointAddress?8位數(shù)據(jù)分別代表:

          ?Bit 0-3: 端點號?Bit 4-6: 保留,值為0?Bit 7: 數(shù)據(jù)方向,0 為 OUT,1 為 IN

          bmAttributes?8位數(shù)據(jù)分別代表:

          ?Bit 0-1: 傳輸類型?00: Control?01: Isochronous?10: Bulk?11: Interrupt?Bit 2-7: 對非 Isochronous 端點來說是保留位,對 Isochronous 端點而言表示 Synchronisation Type 和 Usage Type,不贅述;

          每種端點類型對應一種傳輸類型,詳見后文。

          字符串描述

          字符串描述符(String Descriptor)中包含了可選的可讀字符串信息,如果沒提供,則前文所述的字符串索引應該都設置為0,字符串表結構如下:

          /* USB_DT_STRING: String descriptor */struct usb_string_descriptor {    __u8  bLength;    __u8  bDescriptorType;// 0x03    __le16 wData[1];/* UTF-16LE encoded */} __attribute__ ((packed));/* note that "string" zero is special, it holds language codes that* the device supports,notUnicode characters.*/

          字符串表中的字符都以?Unicode[2]?格式編碼,并且可以支持多種語言。0號字符串表較為特殊,其中 wData 包含一組所支持的語言代碼,每個語言碼為 2 字節(jié),例如 0x0409 表示英文。

          傳輸

          不像 RS-232 和其他類似的串口協(xié)議,USB 實際上由多層協(xié)議構造而成,不過大部分底層的協(xié)議都在 Controller 端上的硬件或者固件進行處理了,最終開發(fā)者所要關心的只有上層協(xié)議。

          USB Packet

          在 HCI 之下,實際傳輸?shù)臄?shù)據(jù)包稱為 Packet,每次上層 USB 傳輸都會涉及到 2-3 次底層的 Packet 傳輸,分別是:

          ?Token Packet: 總是由主機發(fā)起,指示一次新的傳輸或者事件?In: 告訴 USB 設備,主機我想要讀點信息?Out: 告訴 USB 設備,主機我想要寫點信息?Setup: 用于開始 Control Transfer?Data Packet: 可選,表示傳輸?shù)臄?shù)據(jù),可以是主機發(fā)送到設備,也可以是設備發(fā)送到主機?Data0?Data1?Status Packet: 狀態(tài)包,用于響應傳輸,以及提供糾錯功能?Handshake Packets: ACK/NAK/STALL?Start of Frame Packets

          Transfer

          基于這些底層包,USB 協(xié)議定義了四種不同的傳輸類型,分別對應上節(jié)中的四種端點類型,分別是:

          Control Transfers: 主要用來發(fā)送狀態(tài)和命令,比如用來請求設備、配置等描述以及選擇和設置指定的描述符。只有控制端點是雙向的。

          Interrupt Transfers: 由于 USB 協(xié)議是主機主導的,設備端的中斷信息需要被及時響應,就要用到中斷傳輸,其提供了有保證的延遲以及錯誤檢測和重傳功能。中斷傳輸通常是非周期性的,并且傳輸過程保留部分帶寬,常用于時間敏感的數(shù)據(jù),比如鍵盤、鼠標等 HID 設備。

          Isochronous Transfers: 等時傳輸,如其名字所言,該類傳輸是連續(xù)和周期性的,通常包含時間敏感的信息,比如音頻或視頻流。因此這類傳輸不保證到達,即沒有 ACK 響應。

          Bulk Transfers: 用于傳輸大塊的突發(fā)數(shù)據(jù)(小塊也可以),不保留帶寬。提供了錯誤校驗(CRC16)和重傳機制來保證傳輸數(shù)據(jù)的完整性。塊傳輸只支持高速/全速模式。

          這里以控制傳輸(Control Transfers)為例,來看看底層 Packet 如何組成一次完整的傳輸。控制傳輸實際上又可能最多包含三個階段,每個階段在應用層可以看成是一次 “USB 傳輸” (在Wireshark中占一行),分別是:

          ?Setup Stage: 主機發(fā)送到設備的請求,包含三次底層數(shù)據(jù)傳輸1.Setup Token Packet: 指定地址和端點號(應為0)2.Data0 Packet: 請求數(shù)據(jù),假設是 8 字節(jié)的?Device Descriptor Request3.ACK Handshake Packet: 設備的響應, 不允許用 STALL 或者 NAK 來響應 Setup Packet?Data Stage: 可選階段,包含一個或者多個 IN/OUT 傳輸,以 IN 為例,也包含三次傳輸1.IN Token Packet: 表示主機端要從設備端讀數(shù)據(jù)2.Datax Packet: 如果上面 Setup Stage 是?Device Descriptor Request, 這里返回?Device Descriptor Response?(的前8字節(jié),然后再根據(jù)實際長度再 IN 一次)。3.ACK/STALL/NAK Status Packet?Status Stage: 報告本次請求的狀態(tài),底層也是三次傳輸,但是和方向有關:?如果在 Data Stage 發(fā)送的是 IN Token,則該階段包括:1.OUT Token2.Data0 ZLP(zero length packet): 主機發(fā)送長度為0的數(shù)據(jù)3.ACK/NACK/STALL: 設備返回給主機?如果在 Data Stage 發(fā)送的是 OUT Token,則該階段包括:1.IN Token2.Data0 ZLP: 設備發(fā)送給主機,表示正常完成,否則發(fā)送 NACK/STALL3.ACK: 如果是 ZLP,主機響應設備,雙向確認

          每個階段的數(shù)據(jù)都有自己的格式,例如 Setup Stage 的 Request,即 Data0 部分發(fā)送的 8 字節(jié)數(shù)據(jù)結構如下:

          struct usb_ctrlrequest {    __u8 bRequestType;// 對應 USB 協(xié)議中的 bmRequestType,包含請求的方向、類型和指定接受者    __u8 bRequest;// 決定所要執(zhí)行的請求    __le16 wValue;// 請求參數(shù)    __le16 wIndex;// 同上    __le16 wLength;// 如果請求包含 Data Stage,則指定數(shù)據(jù)的長度} __attribute__ ((packed));

          下面是一些標準請求的示例:

          08cbd318733a958528b432e927e5376d.webp

          ref: https://www.beyondlogic.org/usbnutshell/usb6.shtml

          雖然 HCI 之下傳輸?shù)臄?shù)據(jù)包大部分情況下對應用開發(fā)者透明,但是了解底層協(xié)議發(fā)生了什么也有助于加深我們對 USB 的理解,后文中介紹 checkm8 漏洞時候就用到了相關知識。

          主機端

          在主機端能做的事情相對有限,主要是分析和使用對應的 USB 設備。

          抓包分析

          使用 wireshark 可以分析 USB 流量,根據(jù)上面介紹的描述符字段以及 USB 傳輸過程進行對照,可以加深我們對 USB 協(xié)議的理解。如下是對某個安卓設備的?Device Descriptor Response?響應:

          b05ec88d456dd83d36f454aaa946fc93.webpdevice.png

          也就是所謂安卓變磚恢復時經常用到的高通 9008 模式。說個題外話,最近對于高通芯片 BootROM 的研究發(fā)現(xiàn)了一些有趣的東西,后面可能會另外分享,Stay Tune!

          應用開發(fā)

          對于應用開發(fā)者而言,通常是使用封裝好的庫,早期只有 libusb,后來更新了 libusb1.0,早期的版本變成 libusb0.1,然后又有了?OpenUSB[3]?和其他的 USB 庫。但不管用哪個庫,調用的流程都是大同小異的。以 Python 的封裝 pyusb 為例,官方給的示例如下:

          import usb.coreimport usb.util# find our devicedev = usb.core.find(idVendor=0xfffe, idProduct=0x0001)# was it found?if dev isNone:raiseValueError('Device not found')# set the active configuration. With no arguments, the first# configuration will be the active onedev.set_configuration()# get an endpoint instancecfg = dev.get_active_configuration()intf = cfg[(0,0)]ep = usb.util.find_descriptor(    intf,# match the first OUT endpoint    custom_match = \lambda e: \        usb.util.endpoint_direction(e.bEndpointAddress)== \        usb.util.ENDPOINT_OUT)assert ep isnotNone# write the dataep.write('test')

          總的來說分為幾步,

          1.根據(jù)設備描述符查找到指定的設備2.獲取該設備的配置描述符,選擇并激活其中一個3.在指定的配置中查找接口和端點描述符4.使用端點描述符進行數(shù)據(jù)傳輸

          如果不清楚 USB 的工作原理,會覺得上面代碼的調用流程很奇怪,往 USB 上讀寫數(shù)據(jù)需要那么復雜嗎?但正是因為 USB 協(xié)議的高度拓展性,才得以支持這么多種類的外設,從而流行至今。

          設備端

          對于想要開發(fā)設備端 USB 功能的開發(fā)者而言,使用最廣泛的要數(shù)樹莓派 Zero了,畢竟這是樹莓派系列中唯一支持 USB OTG 的型號。網上已經有很多資料教我們如何將樹莓派 Zero 配置成 USB 鍵盤、打印機、網卡等 USB 設備的教程。當然使用其他硬件也是可以的,配置自定義的 USB 設備端可以讓我們做很多有趣的事情,比如網卡中間人或者 Bad USB 這種近源滲透方式。后文中我們會使用 Zero 進行簡單測試。

          一些相關的配置資料可以參考:

          ?https://github.com/RoganDawes/P4wnP1?Using RPi Zero as a Keyboard[4]

          內核驅動

          在介紹應用之間,我們先看看內核的實現(xiàn)。還是以 Linux 內核為例,具體來說,我們想了解如何通過添加內核模塊的方式實現(xiàn)一個新的自定義 USB 設備。俗話說得好,添加 Linux 驅動的最好方式是參看現(xiàn)有的驅動,畢竟當前內核中大部分都是驅動代碼。

          因為 Linux 內核既能運行在主機端,也能運行在設備端,因此設備端的 USB 驅動有個不同的名字:?gadget?driver。對于不同設備,也提供不同的內核接口,即 Host-Side API 和 Gadget API。既然我們是想實現(xiàn)自己的設備,就需要從 gadget 驅動入手。

          g_zero.ko?就是這么一個驅動,代碼在?drivers/usb/gadget/legacy/zero.c。該驅動實現(xiàn)了一個簡單的 USB 設備,包含 2 個配置描述,各包含 1 個功能,分別是 sink 和 loopback,前者接收數(shù)據(jù)并返回 0,后者接收數(shù)據(jù)并原樣返回:

          ?drivers/usb/gadget/function/f_sourcesink.c?drivers/usb/gadget/function/f_loopback.c

          代碼量不多,感興趣的自行 RTFSC。另外值得一提的是,對于運行于 USB device 端的系統(tǒng)而言,內核中至少有三個層級處理 USB 協(xié)議,可能用戶層還有更多。gadget API 屬于三層的中間層。至底向上,三層分別是:

          1.USB Controller Driver: 這是軟件的最底層,通過寄存器、FIFO、DMA、IRQ 等其他手段直接和硬件打交道,通常稱為?UDC?(USB Device Controller) Driver。2.Gadget Driver: 作為承上啟下的部分,通過調用抽象的 UDC 驅動接口,底層實現(xiàn)了硬件無關的 USB function。主要用于實現(xiàn)前面提到的 USB 功能,包括處理 setup packet (ep0)、返回各類描述符、處理各類修改配置情況、處理各類 USB 事件以及 IN/OUT 的傳輸?shù)鹊取?/span>3.Upper Level: 通過 Gadget Driver 抽象的接口,實現(xiàn)基于 USB 協(xié)議的上層應用,比如 USB 網卡、聲卡、文件存儲、HID 設備等。

          關于 Linux USB 子系統(tǒng)的詳細設計結構,可以參考源碼中的文檔:?Linux USB API[5],以及其他一些資料,如下所示:

          ?https://bootlin.com/doc/legacy/linux-usb/linux-usb.pdf?https://static.lwn.net/images/pdf/LDD3/ch13.pdf?https://elinux.org/images/5/5e/Opasiak.pdf

          GadgetFS/ConfigFS

          參考現(xiàn)有的 Linux 驅動,依葫蘆畫瓢可以很容易實現(xiàn)一個自定義的 USB Gadget。但是這樣存在一些問題,如果我想實現(xiàn)一個八聲道的麥克風,還要重新寫一遍驅動、編譯、安裝,明明內核中麥克風的功能已經有了,復制粘貼就顯得很不優(yōu)雅。

          那么,有沒有什么辦法可以方便組合和復用現(xiàn)有的 gadget function 呢?在 Linux 3.11 中,引入了 USB Gadget ConfigFS,提供了用戶態(tài)的 API 來方便創(chuàng)建新的 USB 設備,并可以組合復用現(xiàn)有內核中的驅動。

          5442c38278251da3626399de469785b8.webpgfs.png

          前文提到的基于樹莓派 Zero 實現(xiàn)的各類 USB 設備,大部分都是基于 Gadget ConfigFS 接口實現(xiàn)的。基于 configfs 創(chuàng)建 USB gadget 的步驟一般如下:

          CONFIGFS_HOME=/sys/kernel/config/usb_gadget# 1. 新建一個 gadget,并寫入實際的設備描述mkdir $CONFIGFS_HOME/mydev # 創(chuàng)建設備目錄后,該目錄下自動創(chuàng)建并初始化了一個設備模板cd $CONFIGFS_HOME/mydevecho 0x0100> bcdDevice # Version 1.0.0echo 0x0200> bcdUSB # USB 2.0echo 0x00> bDeviceClassecho 0x00> bDeviceProtocolecho 0x40> bMaxPacketSize0echo 0x0104> idProduct # Multifunction Composite Gadgetecho 0x1d6b> idVendor # Linux Foundation# 2. 新建一個配置,并寫入實際的配置描述mkdir configs/c.1# 創(chuàng)建一個配置實例: <config name>.<config number>cd configs/c.1echo 0x01>MaxPowerecho 0x80> bmAttributes# 3. 新建一個接口(function),或者將已有接口鏈接到當前配置下cd $CONFIGFS_HOME/mydevmkdir functions/hid.usb0 # 創(chuàng)建一個 function 實例: <function type>.<instance name>echo 1> functions/hid.usb0/protocolecho 8> functions/hid.usb0/report_length # 8-byte reportsecho 1> functions/hid.usb0/subclassln -s functions/hid.usb0 configs/c.1# 4. 將當前 USB 設備綁定到 UDC 驅動中echo ls /sys/class/udc > $CONFIGFS_HOME/mydev/UDC

          這樣就實現(xiàn)了一個最簡單的 USB gadget,當然要完整實現(xiàn)的話還可以添加字符串描述,以及增加各個端點的功能。使用 configfs 實現(xiàn)一個 USB 鍵盤的示例可以參考網上其他文章,比如?Using RPi Zero as a Keyboard[6],或者 Github 上的開源項目,比如?P4wnP1[7]

          有些人覺得 ConfigFS 配置起來很繁瑣,所以開發(fā)了一些函數(shù)庫(如 libusbgx) 來通過調用創(chuàng)建 gadget;有人覺得通過函數(shù)操作也還是繁瑣,就創(chuàng)建了一些工具(如?gt[8]) 來通過處理一個類似于 libconfig 的配置文件直接創(chuàng)建 gadget,不過筆者用得不多。

          FunctionFS

          FunctionFS 最初是對 GadgetFS 的重寫,用于支持實現(xiàn)用戶態(tài)的 gadget function,并組合到現(xiàn)有設備中。這里說的 FunctionFS 實際上是新版基于 ConfigFS 的 GadgetFS 拓展。在上一節(jié)中說到創(chuàng)建設備 gadget 的第四步就是給對應的 configuration 添加 function,格式為?function_type.instance_name,type 對應一個已有的內核驅動,比如上節(jié)中是?hid

          如果要使用當前內核中沒有的 function 實現(xiàn)自定義的功能,那么內核還提供了一個驅動可以方便在用戶態(tài)創(chuàng)建接口,該驅動就是 ffs 即 FunctionFS。使用 ffs 的方式也很簡單,將上面第三步替換為:

          cd $CONFIGFS_HOME/mydevmkdir functions/ffs.usb0ln -s functions/ffs.usb0 configs/c.1

          創(chuàng)建一個類型為 ffs,名稱為 usb0 的function,然后掛載到任意目錄:

          cd /mntmount usb0 ffs -t functionfs

          掛載完后,/mnt/ffs?目錄下就已經有了一個 ep0 文件,如名字所言正是 USB 設備的零端點,用于收發(fā) Controller Transfer 數(shù)據(jù)以及各類事件。在該目錄中可以創(chuàng)建其他的端點,并使用類似文件讀寫的操作去實現(xiàn)端點的讀寫,內核源碼中提供了一個用戶態(tài)應用示例,代碼在?tools/usb/ffs-test.c。如果嫌 C 代碼寫起來復雜,還可以使用 Python 編寫 ffs 實現(xiàn),比如?python-functionfs[9]

          案例分析: checkm8 漏洞

          checkm8 漏洞就不用過多介紹了,曾經的神洞,影響了一系列蘋果設備,存在于 BootROM 中,不可通過軟件更新來修復,一度 Make iOS Jailbreak Great Again。當然現(xiàn)在可以通過 SEP 的檢查來對該漏洞進行緩解,這是后話。

          關于 checkm8 的分析已經有很多了,我們就不再鸚鵡學舌,更多是通過 checkm8 的成因,來從漏洞角度加深對 USB device 開發(fā)的理解。

          checkm8 漏洞發(fā)生在蘋果的救磚模式 DFU (Device Firmware Upgrade),即通過 USB 向蘋果設備刷機的協(xié)議。該協(xié)議是基于 USB 協(xié)議的一個拓展,具體來說:

          ?基于 USB Control Transfer?bmRequestType[6:5] 為 0x20,即?Type?為 Class?bmRequestType[4:0] 為 0x01,即?Recipient?為 Interface?bRequest 為 DFU 相關操作,比如 Detach、Download、Upload、GetStatus、Abort 等

          DFU 接口初始化的代碼片段如下:

          7ea4c41f3917ed050c5fa756e735be8b.webpdfu.png

          Control Transfer 主要是在 ep0 上傳輸,因此 ep0 的讀寫回調中就會根據(jù)收到的數(shù)據(jù)來派發(fā)到不同的 handler,對于 DFU 協(xié)議的分發(fā)偽代碼如下:

          staticbyte*data_buf;staticsize_t data_rcvd;staticsize_t data_size;staticstruct usb_ctrlrequest setup_request;void handle_ctr_transfer_recv(byte*buf,int len,int*p_stage,int is_setup){*p_stage =0;if(!is_setup){    handle_data_recv(buf, len, p_stage);}// handle control request  memcpy(&setup_request, buf,8);switch(setup_request.bRequestType &0x60){case STANDARD:// ...case VENDOR:// ...case CLASS:if(setup_request.bRequestType &0x1f== INTERFACE){int n = intf_handlers[setup_request.wIndex]->handle_request(&setup_request,&data_buf);if(n >0){          data_size = n;}}default:// ...}}

          其中 intf_handlers 是 usb_core_regisger_interface 函數(shù)中添加到的的全局函數(shù)數(shù)組。handle_reuqest 中傳入的是一個指針的指針,并在處理函數(shù)中復制為 io_buffer 的地址。而開頭的 data stage 階段,內部實現(xiàn)就是將收到的數(shù)據(jù)拷貝到 data_buf 即 io_buffer 中。

          io_buffer 一直是有效的嗎?并不盡然,因為 io_buffer 在 DFU 退出階段會被 free 釋放掉,此后 data_buf 仍然持有著無效指針,就構成了一個典型的 UAF 場景,這正是 checkm8 的漏洞所在。至于如何觸發(fā)以及如何構造利用,可以需要額外的篇幅去進行介紹,感興趣的朋友可以參考文末的文章。

          從 checkm8 漏洞中我們可以看到出現(xiàn)漏洞的根本成因:

          ?大量使用全局變量?在處理 USB 內部狀態(tài)機出現(xiàn)異常時,沒有充分清除全局變量的值,比如只將 io_buffer 置零而沒有將 data_buf 置零?在重新進入狀態(tài)機時,全局變量仍然有殘留,導致進入異常狀態(tài)或者處理異常數(shù)據(jù)

          網上有人評論說這么簡單的漏洞為什么沒有通過自動化測試發(fā)現(xiàn)出來,個人感覺這其實涉及到模糊測試的兩大難題:

          一是針對 stateful 的數(shù)據(jù)測試,每增加一種內部狀態(tài),測試的分支就成指數(shù)級別增長,從而增加了控制流覆蓋到目標代碼的難度;

          二是硬件依賴,要測試這個 USB 狀態(tài)機,需要 mock 出底層的驅動接口,工作量和寫一個新的 USB 驅動差不多,更不用說 DFU 本身還會涉及存儲設備的讀寫,這部分接口是不是也要模擬?

          因此這類漏洞的更多是通過代碼審計發(fā)現(xiàn)出來,不過廠商又執(zhí)著于?Security by Obsecurity,這就導致投入的更多是利益驅動的組織,對個人用戶安全而言并不算是件好事。如果 iBoot 開源,那么估計這個漏洞早就被提交給蘋果 SRC,成本也就幾千歡樂豆的事,也不至于鬧出這么大的輿情,甚至以 checkm8 為跳板,把 SEPOS 也擼了個遍。

          后記

          本文是最近對 USB 相關的一些學習記錄,雖然文章是從前往后寫的,但實際研究卻是從后往前做的。即先看到了網上分析 checkm8 的文章,為了復現(xiàn)去寫一個 USB 設備,然后再去學習 USB 協(xié)議的細節(jié),可以算是個 Leaning By Hacking 的案例吧。個人感覺這種方式前期較為痛苦,但后期將點連成線之后還是挺醍醐灌頂?shù)模菜闶且环N值得推薦的研究方法。

          參考資料

          ?USB in a NutShell[10]?USB and the Real World[11]?pyusb/pyusb[12]?Linux USB API[13]?Kernel USB Gadget Configfs Interface[14]?Technical analysis of the checkm8 exploit[15]

          引用鏈接

          [1]?Universal Serial Bus:?https://en.wikipedia.org/wiki/USB
          [2]?Unicode:?http://www.unicode.org/
          [3]?OpenUSB:?http://sourceforge.net/p/openusb/wiki/Home/
          [4,6]?Using RPi Zero as a Keyboard:?https://www.rmedgar.com/blog/using-rpi-zero-as-keyboard-setup-and-device-definition
          [5]?Linux USB API:?https://www.kernel.org/doc/html/v4.18/driver-api/usb/index.html
          [7]?P4wnP1:?https://github.com/RoganDawes/P4wnP1
          [8]?gt:?https://github.com/kopasiak/gt
          [9]?python-functionfs:?https://github.com/vpelletier/python-functionfs
          [10]?USB in a NutShell:?https://www.beyondlogic.org/usbnutshell/usb1.shtml
          [11]?USB and the Real World:?https://elinux.org/images/a/ae/Ott--usb_and_the_real_world.pdf
          [12]?pyusb/pyusb:?https://github.com/pyusb/pyusb/blob/master/docs/tutorial.rst
          [13]?Linux USB API:?https://www.kernel.org/doc/html/v4.18/driver-api/usb/index.html
          [14]?Kernel USB Gadget Configfs Interface:?https://www.elinux.org/images/e/ef/USB_Gadget_Configfs_API_0.pdf
          [15]?Technical analysis of the checkm8 exploit:?https://habr.com/en/company/dsec/blog/472762/


          瀏覽 69
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  国产内射视频在线观看 | 成人黄色片在线免费看 | 经典无码一区二区三区 | 色播日韩 | 六月丁香九月婷婷 |