1. USB 協(xié)議核心概念與實(shí)踐

        共 15612字,需瀏覽 32分鐘

         ·

        2021-03-06 01:53

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

        USB 101

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

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

        ?調(diào)用用戶層 API,如?libusb_bulk_transfer?對(duì)內(nèi)核的 USB 驅(qū)動(dòng)執(zhí)行對(duì)應(yīng)系統(tǒng)調(diào)用,添加發(fā)送隊(duì)列如?ioctl(IOCTL_USBFS_SUBMITURB)?內(nèi)核驅(qū)動(dòng)中通過(guò) HCI 接口向 USB 設(shè)備發(fā)送請(qǐng)求+數(shù)據(jù)?數(shù)據(jù)發(fā)送到設(shè)備端的 Controller -> HCI -> Host

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

        描述符

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

        ?Device Descriptors: 設(shè)備描述?Configuration Descriptors: 配置描述?Interface Descriptors: 接口描述?Endpoint Descriptors: 端點(diǎn)描述?String Descriptors: 字符串描述

        它們之間的層級(jí)結(jié)構(gòu)關(guān)系如下:

        83dc933d27676a97011638ae84f04738.webpdes.png

        每種描述符都有對(duì)應(yīng)的數(shù)據(jù)結(jié)構(gòu),定義在標(biāo)準(zhǔn)中的第九章,俗稱 ch9。下面以 Linux 內(nèi)核的實(shí)現(xiàn)為例來(lái)簡(jiǎn)要介紹各個(gè)描述符,主要參考頭文件?include/uapi/linux/usb/ch9.h

        設(shè)備描述

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

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

        每個(gè)字段的含義都寫在注釋中了,其中有幾點(diǎn)值得一提。

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

        BCD: binary- coded decimal

        配置描述

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

        /* 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;// 返回?cái)?shù)據(jù)的總長(zhǎng)度    __u8  bNumInterfaces;// 接口描述符的個(gè)數(shù)    __u8  bConfigurationValue;// 當(dāng)前配置描述符的值 (用來(lái)選擇該配置)    __u8  iConfiguration;// 該配置的字符串信息 (在字符串描述符中的索引)    __u8  bmAttributes;// 屬性信息    __u8  bMaxPower;// 最大功耗,以 2mA 為單位} __attribute__ ((packed));#define USB_DT_CONFIG_SIZE        9

        當(dāng)主設(shè)備讀取配置描述的時(shí)候,從設(shè)備會(huì)返回該配置下所有的其他描述符,如接口、端點(diǎn)和字符串描述符,因此需要?wTotalLength?來(lái)表示返回?cái)?shù)據(jù)的總長(zhǎng)度。

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

        接口描述

        一個(gè)配置下有多個(gè)接口,可以看成是一組相似功能的端點(diǎn)的集合,每個(gè)接口描述符的結(jié)構(gòu)如下:

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

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

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

        端點(diǎn)描述

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

        /* USB_DT_ENDPOINT: Endpoint descriptor */struct usb_endpoint_descriptor {    __u8  bLength;    __u8  bDescriptorType;// 0x05    __u8  bEndpointAddress;// 端點(diǎn)地址    __u8  bmAttributes;// 端點(diǎn)屬性    __le16 wMaxPacketSize;// 該端點(diǎn)收發(fā)的最大包大小    __u8  bInterval;// 輪詢間隔,只對(duì) Isochronous 和 interrupt 傳輸類型的端點(diǎn)有效 (見(jiàn)下)/* 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: 端點(diǎn)號(hào)?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: 對(duì)非 Isochronous 端點(diǎn)來(lái)說(shuō)是保留位,對(duì) Isochronous 端點(diǎn)而言表示 Synchronisation Type 和 Usage Type,不贅述;

        每種端點(diǎn)類型對(duì)應(yīng)一種傳輸類型,詳見(jiàn)后文。

        字符串描述

        字符串描述符(String Descriptor)中包含了可選的可讀字符串信息,如果沒(méi)提供,則前文所述的字符串索引應(yīng)該都設(shè)置為0,字符串表結(jié)構(gòu)如下:

        /* 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]?格式編碼,并且可以支持多種語(yǔ)言。0號(hào)字符串表較為特殊,其中 wData 包含一組所支持的語(yǔ)言代碼,每個(gè)語(yǔ)言碼為 2 字節(jié),例如 0x0409 表示英文。

        傳輸

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

        USB Packet

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

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

        Transfer

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

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

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

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

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

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

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

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

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

        下面是一些標(biāo)準(zhǔn)請(qǐng)求的示例:

        08cbd318733a958528b432e927e5376d.webp

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

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

        主機(jī)端

        在主機(jī)端能做的事情相對(duì)有限,主要是分析和使用對(duì)應(yīng)的 USB 設(shè)備。

        抓包分析

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

        b05ec88d456dd83d36f454aaa946fc93.webpdevice.png

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

        應(yīng)用開發(fā)

        對(duì)于應(yīng)用開發(fā)者而言,通常是使用封裝好的庫(kù),早期只有 libusb,后來(lái)更新了 libusb1.0,早期的版本變成 libusb0.1,然后又有了?OpenUSB[3]?和其他的 USB 庫(kù)。但不管用哪個(gè)庫(kù),調(diào)用的流程都是大同小異的。以 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')

        總的來(lái)說(shuō)分為幾步,

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

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

        設(shè)備端

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

        一些相關(guān)的配置資料可以參考:

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

        內(nèi)核驅(qū)動(dòng)

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

        因?yàn)?Linux 內(nèi)核既能運(yùn)行在主機(jī)端,也能運(yùn)行在設(shè)備端,因此設(shè)備端的 USB 驅(qū)動(dòng)有個(gè)不同的名字:?gadget?driver。對(duì)于不同設(shè)備,也提供不同的內(nèi)核接口,即 Host-Side API 和 Gadget API。既然我們是想實(shí)現(xiàn)自己的設(shè)備,就需要從 gadget 驅(qū)動(dòng)入手。

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

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

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

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

        關(guān)于 Linux USB 子系統(tǒng)的詳細(xì)設(shè)計(jì)結(jié)構(gòu),可以參考源碼中的文檔:?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 驅(qū)動(dòng),依葫蘆畫瓢可以很容易實(shí)現(xiàn)一個(gè)自定義的 USB Gadget。但是這樣存在一些問(wèn)題,如果我想實(shí)現(xiàn)一個(gè)八聲道的麥克風(fēng),還要重新寫一遍驅(qū)動(dòng)、編譯、安裝,明明內(nèi)核中麥克風(fēng)的功能已經(jīng)有了,復(fù)制粘貼就顯得很不優(yōu)雅。

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

        5442c38278251da3626399de469785b8.webpgfs.png

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

        CONFIGFS_HOME=/sys/kernel/config/usb_gadget# 1. 新建一個(gè) gadget,并寫入實(shí)際的設(shè)備描述mkdir $CONFIGFS_HOME/mydev # 創(chuàng)建設(shè)備目錄后,該目錄下自動(dòng)創(chuàng)建并初始化了一個(gè)設(shè)備模板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. 新建一個(gè)配置,并寫入實(shí)際的配置描述mkdir configs/c.1# 創(chuàng)建一個(gè)配置實(shí)例: <config name>.<config number>cd configs/c.1echo 0x01>MaxPowerecho 0x80> bmAttributes# 3. 新建一個(gè)接口(function),或者將已有接口鏈接到當(dāng)前配置下cd $CONFIGFS_HOME/mydevmkdir functions/hid.usb0 # 創(chuàng)建一個(gè) function 實(shí)例: <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. 將當(dāng)前 USB 設(shè)備綁定到 UDC 驅(qū)動(dòng)中echo ls /sys/class/udc > $CONFIGFS_HOME/mydev/UDC

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

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

        FunctionFS

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

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

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

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

        cd /mntmount usb0 ffs -t functionfs

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

        案例分析: checkm8 漏洞

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

        關(guān)于 checkm8 的分析已經(jīng)有很多了,我們就不再鸚鵡學(xué)舌,更多是通過(guò) checkm8 的成因,來(lái)從漏洞角度加深對(duì) USB device 開發(fā)的理解。

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

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

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

        7ea4c41f3917ed050c5fa756e735be8b.webpdfu.png

        Control Transfer 主要是在 ep0 上傳輸,因此 ep0 的讀寫回調(diào)中就會(huì)根據(jù)收到的數(shù)據(jù)來(lái)派發(fā)到不同的 handler,對(duì)于 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 中傳入的是一個(gè)指針的指針,并在處理函數(shù)中復(fù)制為 io_buffer 的地址。而開頭的 data stage 階段,內(nèi)部實(shí)現(xiàn)就是將收到的數(shù)據(jù)拷貝到 data_buf 即 io_buffer 中。

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

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

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

        網(wǎng)上有人評(píng)論說(shuō)這么簡(jiǎn)單的漏洞為什么沒(méi)有通過(guò)自動(dòng)化測(cè)試發(fā)現(xiàn)出來(lái),個(gè)人感覺(jué)這其實(shí)涉及到模糊測(cè)試的兩大難題:

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

        二是硬件依賴,要測(cè)試這個(gè) USB 狀態(tài)機(jī),需要 mock 出底層的驅(qū)動(dòng)接口,工作量和寫一個(gè)新的 USB 驅(qū)動(dòng)差不多,更不用說(shuō) DFU 本身還會(huì)涉及存儲(chǔ)設(shè)備的讀寫,這部分接口是不是也要模擬?

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

        后記

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

        參考資料

        ?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
        點(diǎn)贊
        評(píng)論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        評(píng)論
        圖片
        表情
        推薦
        點(diǎn)贊
        評(píng)論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
          
          

            1. 黄毛网站| 在线观看国产精品秘 入口 | 免费 成人进口 | 国产稀缺雏泬泬精品 | 亚洲免费视频在线播放 |