JOVANA
Explore Library Glossary Getting Started Three Levels Fields How it works Mission
Join the mission
All guides

威脅:惡意軟體、緩衝區溢位與權限提升

第 1、2 篇導覽建好了防守方的機制——最小權限、存取矩陣、能力與存取控制清單。現在來認識攻擊方。這篇導覽具體地走過威脅的全貌:惡意軟體究竟是什麼、一行草率的 C 程式碼如何被緩衝區溢位劫持,以及攻擊者如何透過權限提升把一個立足點變成完全的控制。

從防守到進攻:威脅的全貌

前兩篇導覽給了你防守方的工具箱。第 1 篇把保護(作業系統內部的機制,阻止一個行程不小心踩到另一個)和安全保護與安全——抵禦蓄意的對手)分了開來,並種下了最小權限原則。第 2 篇把存取矩陣化為真實的機制,配上能力與存取控制清單。這一切都是那座堡壘。這篇導覽介紹想翻牆進來的人——以及最關鍵的,他們想要什麼。攻擊者所做的一切都瞄準三個目標之一,也就是 CIA 三要素

CIA 三要素是對「安全」究竟是什麼意思最簡潔而誠實的概括。機密性(Confidentiality)是讓祕密維持祕密——你的密碼、你的私人檔案、共享伺服器上另一位租戶的資料。完整性(Integrity)是確保資料不會在你背後被竄改——一支銀行 App 絕不能讓一筆 100 的轉帳悄悄變成 10000。可用性(Availability)是讓服務根本能繼續運轉——勒索軟體把你鎖在外面,或是大量流量把網站灌垮,兩者都在攻擊可用性,即使沒有任何祕密外洩。這篇導覽裡的每一種攻擊,都可以讀作對這三者之一的一步棋。替目標命名,正是讓你能夠推敲一個防禦究竟有沒有對到真正風險的關鍵。

惡意軟體:一份對敵意程式碼的田野指南

惡意軟體單純就是寫來造成傷害的軟體——但這個家族有不同的物種,而差別是機制上的,不只是標籤。病毒是依附在一個宿主程式(或文件)上的程式碼;它只在有人執行了被感染的宿主時才會傳播,像一種需要有人來握手的流感。相對地,蠕蟲(worm)會自我繁殖:它完全不需要人類幫忙就把自己複製到網路各處,這正是為什麼蠕蟲能在幾小時內席捲全球。那些有名的——Morris、Blaster、WannaCry——之所以是蠕蟲,正因為它們不等你來點擊。

特洛伊木馬(trojan)走第三條路:它假扮成你想要的東西——一款免費遊戲、一個盜版 App、一個你看影片所需的「解碼器」——好讓你親手把它渴望的權限交給它。這裡完全沒有什麼高明的漏洞利用;訣竅是社交的、瞄準人的,而它之所以毀滅性十足,正因為是你自願執行了它。rootkit(根工具組)則是終局:一段一旦取得高權限、便把自己藏起來、不讓本該偵測到它的作業系統發現的程式碼——它去竄改核心裡那份執行中行程的清單,讓自己的行程從不現身,或是攔截列檔的呼叫,讓自己的檔案隱形。rootkit 與其說是一種入侵手法,不如說是一種賴著不走的方式。

注意這些是多麼乾淨地對映到你早已擁有的作業系統概念上。病毒利用的是「執行一支程式就等於把你的權限交給它」這個事實(回想程式不是行程——危險只在那個沉睡的檔案變成一個帶著你權利的執行中行程時才出現)。蠕蟲利用的是一個在網路上聆聽的服務。rootkit 則必須抵達核心模式才能令人信服地說謊,這正是為什麼每一種防禦終究都在乎誰被允許跨進核心。這套分類學不是冷知識;它告訴你每一種威脅住在系統的哪裡,因而也告訴你該把守衛擺在哪裡。

緩衝區溢位:一行草率的程式碼如何把控制權拱手讓出

緩衝區溢位是計算領域最有名的低階臭蟲,值得完整地理解,因為它解釋了第 5 篇的一半內容。場景看起來人畜無害:一支程式在它的堆疊上保留一塊固定大小的緩衝區——譬如一個 64 位元組的陣列,本來要拿來放使用者名稱——接著把輸入複製進去,卻不檢查長度。硬體不會阻止你。如果輸入是 200 位元組,那 200 位元組全會被寫入,而超過緩衝區末端的那 136 位元組,會覆寫掉堆疊上緊鄰著它的任何東西。這個臭蟲,就是一個被漏掉的長度檢查。

為什麼那是災難性的、而不只是當機?因為緊鄰緩衝區的是什麼東西。在呼叫堆疊上,就在一個函式的區域緩衝區之後,住著返回位址——CPU 在函式結束時會跳回去的地方。堆疊朝著較低的位址生長,而一次複製卻朝較高的位址寫,所以溢出緩衝區就一路行軍、正撞進那個返回位址。控制了輸入的攻擊者就控制了那些被覆寫的位元組,因而也控制了程式接下來跳到哪裡。把返回位址覆寫成攻擊者自己那些位元組的位址,於是當函式返回時,CPU 就開始執行攻擊者的程式碼。

  stack grows DOWN (toward lower addresses)
  copy writes UP (toward higher addresses) ->

  higher addresses
  +----------------------+
  | return address       |  <-- (4) overflow overwrites THIS
  +----------------------+
  | saved frame pointer  |  <-- (3) ...then this
  +----------------------+
  | buf[63]              |  <-- (2) ...keeps going past the end
  |   ...   (64 bytes)   |
  | buf[0]               |  <-- (1) copy starts here
  +----------------------+
  lower addresses

  Normal return:  jump to caller
  Hijacked:       jump to attacker's bytes -> attacker code runs
一次以堆疊為基礎的緩衝區溢位。寫過那 64 位元組緩衝區,就向上行軍進入被儲存的框架指標、然後是返回位址;控制了返回位址,你就控制了 CPU 接下來去哪裡。

誠實面對根本原因,因為它形塑了修補方式。溢位之所以可能,純粹是因為——在經典情形下——堆疊把資料(緩衝區)和控制資訊(返回位址)放在同一塊可寫、可執行的區域裡,而那個複製常式又信任了輸入的長度。那是兩個假設,而第 5 篇的防禦各攻其一:堆疊金絲雀在緩衝區和返回位址之間放一個祕密值,好在返回之前就偵測到溢位;DEP/NX 則把堆疊標記成不可執行,使得就算注入了位元組也無法當成程式碼跑起來。溢位不是魔法——它是對一條邊界的違反,而作業系統如今會幫忙強制這條邊界。

權限提升:把一個立足點變成整串鑰匙

緩衝區溢位給了攻擊者程式碼執行的能力——但是以誰的身分?通常是以那支有漏洞的程式所屬的使用者或服務,而那也許權力很小。攻擊者的下一步就是權限提升:把有限的存取變成更多。它有兩種風味。垂直提升是從一個低權限帳號爬到高權限的——一個普通使用者變成 root 或 Administrator,或是任何使用者模式行程闖進核心模式水平提升則是橫向移動:仍然是個普通使用者,但現在以另一個普通使用者的身分行動、讀取對方的檔案。兩者都是同一條邊界的失守,而那正是存取矩陣本該強制的。

在無權那一側的程式碼,怎麼搆到有權的那一側?經典的垂直路徑是濫用一支被刻意允許帶著提升後權限執行的程式——Unix 上的 setuid 程式,或是一個系統服務。舉例來說,改密碼的工具必須去編輯一個 root 所擁有的檔案,所以即使是你啟動它,它也以 root 身分執行。如果這樣一支受信任卻有臭蟲的程式有緩衝區溢位,攻擊者注入的程式碼就會繼承它的 root 權限。這就是最小權限的黑暗鏡像:越多程式「以防萬一」地帶著額外權力,攻擊者能去敲的門就越多。最小權限不是官僚作風——每一個被拿掉的權限,就是少一個可被提升的目標。

  1. 進來。攻擊者利用一個漏洞——譬如一個面向網路的服務裡的緩衝區溢位——以那個服務的低權限帳號執行程式碼。這就是立足點。
  2. 四下張望。從裡頭,攻擊者盤點這個帳號能碰到什麼:所有人都可讀的祕密、設錯的檔案權限(一個被留成 rwxrwxrwx 的設定檔)、一支 setuid 執行檔、一個過時的核心。
  3. 提升。攻擊者濫用其中之一——利用那支 setuid 執行檔的臭蟲,或是一個透過系統呼叫可搆到的核心臭蟲——來取得 root,或溜進核心模式。如今他們握有那串鑰匙。
  4. 賴著並隱藏。有了高權限,攻擊者安裝一個 rootkit 以挺過重開機並維持隱形,完成從一個未經檢查的輸入到完全掌控這台機器的旅程。

為什麼核心是那個獎品——以及可信運算基底

每一個提升的故事都終結在同一個地方:核心,或是 root——在大多數系統上 root 幾乎一樣好用。回想前面幾階的雙模式運作——CPU 要嘛在受限的使用者模式、要嘛在無所不能的核心模式裡執行,而兩者之間唯一合法的門就是系統呼叫。那一道門正是為什麼它被守得那麼嚴,也正是為什麼攻擊者對它念茲在茲:核心模式裡的程式碼可以讀任何記憶體、改寫那份藏惡意軟體的行程清單、停掉本該逮到它的那些檢查。機器上沒有更高的權威可以申訴。攻下核心,你就贏得了一切。

這正是為什麼搞安全的人對可信運算基底(trusted computing base, TCB)念念不忘:它是你被迫完全信任的所有硬體與軟體的集合,因為只要其中任何一部分被攻陷,其他防禦就誰也救不了你。核心在 TCB 裡。啟動它的韌體也在(這正是為什麼安全啟動很重要——見第 5 篇),以 root 身分執行的那些特權服務也在。最重要的那條設計守則直接由此而來:讓 TCB 盡可能地小。TCB 裡的每一行程式碼,都是一行其臭蟲會變成全面攻陷的程式碼。你文字編輯器裡的臭蟲是個小麻煩;核心裡的臭蟲則是這場遊戲的終結。