OHM(Observe Hack Make)是一個專為黑客、製造者和那些有探究精神之人舉辦的國際戶外露營節,為期 5 天。今年 7月31日在荷蘭 Geestmerambacht 舉辦,有 3000 人蔘與。
這篇文章相關內容已在 OHM2013 公開。原理是利用硬碟的一些智慧機制,在某個位置嵌入一些資訊(比如:登入資訊),然後作業系統驗證使用者登陸時,會不自主地讀取黑客預留下的使用者名稱和密碼。
簡介
硬碟:如果你在看這篇文章,我肯定你起碼用過一兩個硬碟。硬碟很簡單,基本就是一些512位元組的扇區,由遞增的數字標明地址,稱之為 LBA,也就是“邏輯塊定址”。電腦可以向連線的硬碟的扇區中讀寫資料。通常會有個檔案系統把這些扇區抽象成檔案或資料夾。
如果你從這個幼稚的角度看硬碟,你會認為硬體應該也很簡單:你需要的就是個能連線SATA介面的東西,然後可以定位讀寫頭,從碟片上讀寫資料。但是可能不止這麼簡單:硬碟不是還有處理壞塊、S.M.A.R.T.屬性的功能麼?不是還有什麼快取需要管理的麼?
以上這些意味著硬碟中有些智慧的東西,有智慧就意味著可以黑掉它。我就喜歡可以黑的東西,於是我決定要看看硬碟是如何在非機械層面上工作的。這種研究以前在很多硬體上做過:從筆記本的PCI擴充套件模組到嵌入式控制器,甚至是蘋果的鍵盤。通常這些研究都是為了證明這些硬碟可以被破解,導致其受到軟體的影響,於是我決定達到同樣的目標:我要在這次破解中讓硬碟繞過軟體安全機構。
PCB上的部件
要想知道硬碟是否可以被破解,我需要更瞭解它們。如你們大多數一樣,我也有一摞或壞或舊的硬碟來一看究竟:
當然了,我們都知道硬碟的機械結構應該是好用的,我對那些部分也不感興趣。我的興趣在於大多數硬碟背面都有的那一小塊PCB板子,上面有SATA介面和電源介面。這種PCB看起來是這樣的:
可以看見PCB上有四塊晶片。接下來說說這些晶片:
這是一塊DRAM(動態隨機儲存器)。這塊很好處理,晶片手冊很好找。這些晶片的容量一般在8MB到64MB之間,對應的就是硬碟標稱的快取容量。
這個是電機控制器。這不是個標準器件,資料手冊不好找,但是這些控制器一般都有容易找的差不多的同系列產品。ST Smooth控制器大概是最常用的一種了;除了驅動電機,它還能進行電源整流,還帶一些A/D變換通道。
這是一塊序列快閃記憶體。這個也好處理,容量一般在64KB到256KB之間。看起來這個是用來儲存硬碟控制器的啟動程式。有些硬碟沒有這個晶片,而是在控制器晶片內部有快閃記憶體來儲存程式。
這些小東西不是晶片,而是壓電震動感測器。當硬碟受到撞擊時,它們可以把磁頭移到安全的地方,但是更有可能它在某個地方標記一個值,表示你的保修無效,因為是你自己摔的硬碟。
這裡才是奇蹟將要發生的地方:硬碟控制器。多是由Marvell、ST或者其他的LSI公司製造。有些硬碟廠商自己做控制器:我見過三星和西數就有自己的控制晶片。因為其他的部分都很好處理,這一塊才是我的興趣所在。
不幸的是,這些晶片都沒有文件。話說這些製造控制器的廠商不公開文件有些不厚道真是說輕了:他們甚至在自己的網站上都不提這些晶片!更不幸的是,整個網際網路也幫不了我:搜這些晶片手冊只能找到沒有手冊的手冊網站,和賣晶片的中國廠商……
那麼,沒有最重要的晶片手冊,就意味著我們的計劃擱淺了麼?
連線JTAG
幸運的是,總有些辦法找到除了晶片手冊以外的有用資訊。我就搜到這麼一個。
我找的是HDDGuru論壇上一個叫Dejan的人做的連線線。Dejan不知怎麼把他硬碟控制器的內部快閃記憶體廢掉了,然後想知道有沒有辦法,要麼從外部快閃記憶體啟動控制器,要麼重寫一下內部快閃記憶體。過了五天,沒人回應他,但是這哥們很有創造力:他又發了個帖子說他找到了JTAG口的管腳。這真是個重大發現:JTAG介面可以用來控制控制器。你可以用它啟動控制器、重啟、修改記憶體、設定斷點等等。然後Dejan發現瞭如何關掉控制器的啟動ROM,找到了硬碟一個串列埠,然後試圖恢復他的快閃記憶體ROM。後來他又提了一些關於更新快閃記憶體的過程,最後消失在茫茫人海中了。
這些都是有用的資訊:至少我知道了西部資料的控制器是ARM核心的,有JTAG介面。這些硬碟通常有串列埠,雖然沒有使用但是可以用來除錯程式。有了這些,我應該有足夠的資訊可以開始破解了。
嗯,這些是我的準備工作:
那個紅色的是一塊FT2232H的小板,大概30歐元,很便宜,可以用來進行JTAG除錯,串列埠,還有SPI通訊。把它連到硬碟的JTAG口,還有串列埠上。硬碟直接連到我電腦主機板的SATA口上,還有外部ATX電源。我用OpenOCD來驅動JTAG介面。
現在的問題是:這玩意真能工作麼?Dejan用的是88i6745控制器的2.5” 250G硬碟,他檢測到的是ARM9核心。我找的是88i6745控制器的3.5” 2TB硬碟,有不同的格式因素,而且有點新。幸運的是,OpenOCD可以自動檢測JTAG連線的裝置。如下所示:
這我就有點搞不懂了……我本來估計會有一個tap,就是單獨的ARM核心……可這裡竟然有三個tap……難道這個片子有三個ARM核心?
一番研究後,我發現這個晶片真的是有三個核心。兩個是Feroceon的核心,是比較牛逼的類似ARM9的核心,還有一個是Crotex-M3核心,比較小,相比更像微控制器的核心。鼓搗了一陣(以及後來的研究)發現這些控制器各自有不同的功能:
- Feroceon 1 處理對磁碟的物理讀寫操作
- Feroceon 2 處理SATA介面
- Feroceon 2 同時處理快取以及將邏輯塊定址翻譯成柱面/磁頭/扇區
- Cortex-M3 貌似啥都不管?我給他關掉硬碟也沒啥問題。
現在從哪個核心開始破解呢?我的目標是通過使用修改的硬碟韌體來影響系統的安全。最簡單的方法,同時也可能是最難檢測的方法就是直接修改資料。這種方法不需要修改磁碟上的資料,韌體可以使自己隱身不可見。為此,我需要找到一個合適的核心來進行監聽:我需要一個能在從硬碟到SATA線的傳輸過程中接觸到資料的核心,同時可以被操縱在磁碟和SATA線纜之間修改資料。
現在,資料是如何從硬碟碟片上送到SATA藉口上的呢?憑黑客的直覺我推測:
如果處理器工作在150MHz,使用標準的記憶體複製,它們就只能達到150*23/2=2.4Gbp的速率,而實際情況要比這個少很多。硬碟的速度是6Gbps,所以肯定有些加速硬體參與其中。最可能的加速硬體應該就是使用DMA(直接訪問記憶體)。那就意味著資料直接從磁頭讀回來放進記憶體,沒有處理器的參與。SATA口也是一樣:處理器只指明資料在哪裡,DMA會直接從記憶體中讀資料。
如果是這樣的話,DMA引擎指向的記憶體會在哪呢?硬碟的快取是個好地方:資料從磁碟讀出來總是要放進快取的,所以當讀取磁碟的時候馬上去那裡複製也就說的通了。我之前發現第二個Feroceon負責管理快取;於是它就成了我的首選目標。
就這樣,我推斷資料通過DMA來讀寫,不需要任何CPU動作。現在的問題是:既然CPU不會在正常操作中接觸資料,那麼CPU能不能(非正常地)接觸到資料呢?為了解答這個問題,我首先使用JTAG連線,用了一些反彙編,來看看Feroceon2號的記憶體。
如你所見,記憶體圖有些零碎。RAM中有一些小塊散落著,還有一些IO空間和IRQ空間,以及一塊內部啟動的ROM。還有一塊64MB的資料段,我猜這個是用作快取的DRAM。一起來看看是不是這樣。首先,我把硬碟載入到我的電腦上,在硬碟上的一個檔案裡寫入「Hello world」。現在看看我是否能從64MB的記憶體中找到這個字串。
沒錯,找到了!看起來Feroceon2號可以讀取快取,並對這塊64MB的DRAM進行了地址對映。
注入程式碼
當然了,如果我想要在快取裡修改資料,我可不能每次都完全掃描整個64MB的快取:我需要知道快取是如何工作的。為此,我需要進行反彙編並理解硬碟的韌體,至少要明白快取的函式。
對韌體進行反彙編,可不是個簡單的活。首先,程式碼混合了ARM和Thumb指令,如果你沒有自動切換兩種指令的反彙編器就很令人抓狂了。而且,沒有那些能使反彙編更簡單的資訊了:一般程式都被寫好了,當有東西出錯總會彈出類似「Couldn’t open logfile!」的資訊。這些資訊對於瞭解程式碼功能有很大幫助。而這個韌體,一條資訊都沒有:你得自己看程式碼來知道程式碼在做什麼。程式碼庫好像有點老,而且有些時候反彙編的感覺就像給程式碼加了很多特性,把所有事情都搞得更復雜。
當然,也有幾件事使得反彙編相對簡單些。首先呢,西部資料沒有故意混淆程式碼:沒有在指令中間用些跳轉的招數。還有,因為JTAG介面的存在,你可以干預程式碼的執行,設定斷點,或者直接修改,讓你非常容易地知道程式在做什麼。
我看了很久程式碼,試著去理解,有時候用偵錯程式驗證我猜的對不對,最後我找到了快取系統的核心程式碼:在RAM中的一個表,我稱之為「快取描述符表」。
快取描述表的每一項描述了快取中的一個塊。它包含了可能在快取中的磁碟扇區的起始LBA、快取中存有多少硬碟資料、一些標明瞭快取項的狀態標誌符,還有一個標明瞭快取資料在記憶體中未知的數。
現在,快取描述符表的祕密還沒有被揭開,我能否在資料送出SATA口之前截斷磁碟讀取碼?為此,我需要在磁碟控制器上執行我自己的程式碼。不僅如此,我還需要確定程式碼能否在正確的時間執行:如果它修改快取太早,資料還沒進去;如果太晚的話,資料已經送到PC了。
我的方法是繫結在一個已存在的任務上。我破解的是Feroceon2號,這個CPU負責所有的SATA傳送,所以肯定有個服務是負責設定SATA硬體去快取中讀取資料。如果我找到這個服務,我就可能在它之前執行我的程式碼。
在看了很多程式碼,設定了很多斷點,修改了很多次之後,我最終找到了某個符合條件的服務。我通過連線讓這個服務在執行前先執行我的程式碼。這是原來的程式碼:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
000167BE ; r0 - slot in sata_req 000167BE sub_0_167BE: 000167BE PUSH {R4-R7,LR} 000167C0 MOVS R7, R0 000167C2 LSLS R1, R0, #4 000167C4 LDR R0, =sata_req 000167C6 SUB SP, SP, #0x14 000167C8 ADDS R6, R1, R0 000167CA LDRB R1, [R6,#0xD] 000167CC LDR R2, =stru_0_40028DC 000167CE STR R1, [SP,#0x28+var_1C] 000167D0 LDRB R0, [R6,#(off_0_FFE3F108+2 - 0xFFE3F0FC)] 000167D2 LDRB R5, [R6,#(off_0_FFE3F108 - 0xFFE3F0FC)] 000167D4 LSLS R0, R0, #4 |
這是改成連線到我的程式碼之後:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
000167BE ; r0 - slot in sata_req 000167BE sub_0_167BE: 000167BE PUSH {R4-R7,LR} 000167C0 MOVS R7, R0 000167C2 LD R6, =hookedAddr 000167C4 BX R6 000167C6 .dw checksumFix 000167C8 .dd hookedAddr 000167CC LDR R2, =stru_0_40028DC 000167CE STR R1, [SP,#0x28+var_1C] 000167D0 LDRB R0, [R6,#(off_0_FFE3F108+2 - 0xFFE3F0FC)] 000167D2 LDRB R5, [R6,#(off_0_FFE3F108 - 0xFFE3F0FC)] 000167D4 LSLS R0, R0, #4 ... FFE3F000 PUSH {R0-R12, LR} FFE3F004 BX changeThingsInCache FFE3F008 POP {R0-R12, LR} FFE3F00C LSLS R1, R0, #4 FFE3F010 LDR R0, =sata_req FFE3F014 SUB SP, SP, #0x14 FFE3F018 ADDS R6, R1, R0 FFE3F01C LDRB R1, [R6,#0xD] FFE3F020 BX 0x167CC |
如你所見,原來的指令被跳轉到的新程式碼替代了,新程式碼放在本來沒用到的地址0xFFE3F000,然後又加了一句,保證程式碼域的校驗和有效。如果沒這麼做的話,硬碟會嘗試從碟片上讀取備份,那可不是我想要的。跳轉的程式碼執行了一個服務,叫做「changeThingsInCache」然後執行修改程式碼本該做的指令。最後接著執行本來的服務好像什麼也沒發生過一樣。
現在我要寫的就是修改快取資料的服務。首先做個測試,我決定用一個下面的用虛擬碼寫的服務:
1 2 3 4 5 6 7 8 9 |
void hook() { foreach (cache_struct in cache_struct_table) { if (is_valid(cache_struct)) { foreach (sector in cache_struct.sectors) { sector[0]=0x12345678; } } } } |
這一小段程式碼會在每次呼叫的時候用0x12345678代替快取中每個扇區的前四個位元組,所以如果我把這個上傳到硬碟的話,我在我看到的每個扇區前面都會看到這個數字。我通過JTAG上傳了程式碼……
然後你看:
一勞永逸
當然了,我可以將韌體完全破解,但是每次硬碟啟動都需要用JTAG修改RAM,這就得不償失了。我得讓它保持不變,也就是說,我要把我的修改存在某個地方,每次硬碟啟動都會帶上這段修改程式。
我選的地方是快閃記憶體。我大概也可以放在磁碟本身的保留扇區上,但是如果我一旦弄錯的話,我就沒法恢復我的硬碟了。快閃記憶體晶片只是一個八個腳的標準件,所以我可以輕鬆地摘下來,刷掉快閃記憶體再裝回去。為此,我把它焊下來然後放到萬用板上,這樣我就可以在程式設計器和硬碟之間輕鬆切換了。
現在,應該在快閃記憶體裡寫什麼呢?很幸運的是,晶片中儲存的格式已經找到了:它包含了多塊資料,還有一個表在最開始描述了這些資料。這個表描述了快閃記憶體中程式碼塊的位置,如何壓縮的(如果壓縮了的話),程式碼塊應該在放在RAM的什麼位置,而且在最後的地址中是一個執行指標,標記了啟動器應該跳到什麼地方去執行程式。
不幸的是,我不能修改快閃記憶體中的程式碼;我想加鉤子的地方的資料被某種不知道的壓縮演算法壓縮了,我就不能修改了。然而我能做的是增加一個額外的程式碼塊,修改執行地址這樣這個程式碼塊就可以在其他之前執行了。這樣一來就簡單多了。當我的程式碼塊執行的時候,我就可以在已經解壓的程式碼中加入我的鉤子了。
當然,我得反彙編,再重編譯快閃記憶體的二進位制程式碼。我為此做了個小工具,非常俗地起名為「fwtool」。這個小工具可以讀出快閃記憶體中的很多資料塊,並把頭翻譯成文字檔案以方便修改。接著你就可以修改,刪除或者加上程式碼,然後重新編譯成一個韌體,準備刷回去。我用它把我的程式碼加到映象中,再刷回到晶片裡,把晶片裝回硬碟,啟動備份的檔案,然後:
結果並不新鮮:就是我之前做過的。唯一的變化就是我不用JTAG就能辦到了。
刷軟體
雖然快閃記憶體這邊有了很大進展,我還是不能開始我的黑客指令碼:我相信不會有任何一個伺服器公司會接受這些帶有反彙編又重彙編的晶片的硬碟。我需要想個辦法能讓晶片不從板子上摘下來就可以刷韌體,最好是能直接在硬碟安裝的電腦上刷。
西部資料的韌體升級工具為此提供了可能性:這個工具簡單地在DOS環境下的把新韌體寫進快閃記憶體和服務區——也就是保留扇區。根據網上資料,這個工具使用的所謂「Vendor Specific Commands」命令。也有一些其他的工具可以修改韌體:比如,有一種概念驗證性的程式碼,可以使用未使用的保留扇區來隱藏資料。最後有一組工具叫做「idle3-tools」可以通過修改韌體中的位元組來修改硬碟的閒置行為。這個程式碼同樣使用VSC,通過Linux系統的SCSI(小型計算機系統介面)直通IOCTLS(輸入輸出控制系統)這種「正式」的途徑來修改程式碼。我需要「借用」它的原始碼,修改一下然後整合到我的fwtool裡面。在胡亂猜了一陣VSC引數之後,fwtool突然可以讀寫電腦上硬碟的快閃記憶體晶片了。
有了這個工具,我的攻擊基本完成了,如果一個黑帽子黑客獲得了一個帶有這樣硬碟驅動器的伺服器的最高許可權,他就可以使用fwtool遠端獲取硬碟快閃記憶體,修改然後刷回去。最終,主機的主人會發現我用他的主機為非作歹,然後可能會重灌系統,斷掉黑客原來進入主機的路。
但是有了這個破解了的韌體,攻擊者可以操縱硬碟在新安裝的系統裡繼續為非作歹。首先他需要觸發行為,這需要事先在硬碟裡寫入一個破解韌體需要的某個特定字串。這個字串可以在任何一個檔案中:攻擊者可以向伺服器上傳一個帶有程式碼的.jpeg檔案。他也可以通過向伺服器傳送在URL中追加了特定程式碼的檔案請求來實現。這最終會在伺服器的記錄檔案中結束,觸發利用。
接下來,被破解的硬碟韌體就開始搗亂了。比如,他會等待主機讀出/etc/shadow中的檔案,其中儲存了Unix/Linux的所有密碼,然後立即修改成攻擊者之前寫進去的一些東西。當攻擊者之後嘗試用他自己的密碼登入系統的時候,主機會根據修改過的/etc/shadow判斷密碼,攻擊者就可以再次輕鬆登入。
這是我做的演示。你可以看見我沒能成功登入主機的根使用者。然後我啟動破解,給它一個代替密碼的雜湊值,也就是密碼「test123」。因為Linux系統把影子檔案快取了(如同所有最近存取的檔案),我需要製造很多硬碟活動把快取清出去;這樣,當我再次登入的時候,Linux系統會再次讀取磁碟上的影子檔案。最終,快取已清空,我可以用假的「test123」密碼登入根使用者了。
其他用法
當然了,恢復伺服器中清除的隱祕登入方法並不是我研究成果的唯一用法。這同樣可以用於防禦目的。
例如,你可以做一個不可複製的硬碟:如果扇區的讀取模式是隨機的話,像正常的作業系統讀取檔案系統,硬碟會正常工作。如果硬碟是有序的讀取,像硬碟複製裝置那樣的話,硬碟會篡改資料,無法複製出原來的內容。
硬碟控制器作為一個通用控制器也是有的玩的。你手裡的是三個效能不錯的CPU核心,連著一個相當大的RAM。還有一個UART作為串列埠,至少兩個SPI介面:一個連線到快閃記憶體ROM,一個連到電機控制器。你可以通過升級外部快閃記憶體晶片來給處理器載入程式碼,或者甚至在啟動載入器上用串列埠載入。為了演示晶片的能力,我在硬碟上移植了一個相當普及的軟體。這個demo只是概念驗證性的,串列埠是唯一工作的外圍裝置,而且沒有使用者空間。雖然如此, 我還是很驕傲的宣稱我在我的硬碟控制器上裝了一個Linux。在頂端,是標準的命令列(硬碟載入在/mnt下),低端是我在硬碟串列埠上的輸出。
在此多解釋一下這是怎麼工作的:核心和啟動都封裝成每塊大小都是一個扇區的一個個的包,包的前面帶有特殊字串和編號數字。通過從磁碟讀取資料,核心和啟動最終會進入快取。寫入特殊字串「HD, Inx!」最終觸發了修改過的韌體,在快取中搜尋所有扇區,重編譯核心然後啟動。但是一個沒有記憶體控制單元的核心也需要特殊格式的使用者空間。我不能把這個也編譯了,所以核心最終因為找不到init來執行而崩潰。
結論
是的,就是這樣。雖然硬碟控制器如同一個不知其究竟的野獸,它仍能通過逆向工程加以瞭解,併為其寫出程式碼以執行。對控制器的未知,使得通用破解充滿難度,令我懷疑這東西是不是永遠不會出現一個惡意的韌體補丁:相比對每個伺服器的每個硬碟韌體進行逆向工程加以破解,還是找一個0day漏洞更加簡單吧。
我還希望證實一個壞掉的硬碟仍然能夠使用。當硬碟的機械部分壞掉的時候,PCB仍然帶有可用的嵌入式系統,其效能相當不俗,尤其是壞的硬碟基本都不要錢就能拿到。
開放安全工程的原始碼什麼的太惡劣了。我想開放程式碼,但是我不想為由此產生的大量的「永久破解」的伺服器負責……我決定做個妥協:你可以在這裡下載程式碼,但是我移除了影子替代部分的程式碼。注意:反正我不負責讓整個過程完全可執行;黑客,你自己來吧。