第二章、 背景知識
2.3 攔截系統呼叫(System call interception)
在現代的作業系統中,所有的系統資源都是由作業系統集中管理,每個想要 存取系統資源的程式,都必須透過系統呼叫向作業系統提出要求,然後等待作業 系統將資源分配給它。在本節中,我們將介紹攔截系統呼叫的相關技術。
2.3.1 可載入核心模組(Loadable Kernel Module)
在討論如何在核心中攔截系統呼叫之前,我們先介紹要如何在核心中加入自 己需要的功能。我們可以將模組(module)視為一個程式,但是它可以被動態載到 核心裡成為核心的一部分。載到核心裡的模組具有跟核心一樣的權力。可以存取 任何核心的資料結構。雖然Linux已是一個完整的系統,但為了增加新的功能,
它允許新增新的device driver,file system等。使用可載入核心模組(Loadable Kernel Module,簡稱LKM)便是擴充核心功能的一個最簡便的方法。
每個模組至少都必須提供init_module()和cleanup_module()這兩個基本的函 式,前者是用來初始化這個模組,在模組載入核心時會被呼叫。後者是用來結束 這個模組的工作,在從核心移除模組時會被呼叫。
圖4是從“Linux Device Drivers"書中所引用的一張圖。如圖所示,系統管 理者可使用“insmod"指令動態地將一個模組載入核心,此“insmod"指令會呼 叫模組中的“init_module( )"函式,作初始化的動作,並且向核心註冊此模組所 提供的功能函式。如此一來,此模組所提供的功能函式便可以為核心所用。當模 組提供的功能不再需要時,系統管理者可使用“rmmod"指令動態地將模組從系 統中移除。
圖 4 核心載入模組
2.3.2 核心層之系統呼叫攔截
當一個程式想要存取系統資源時,它會向作業系統發出一個系統呼叫,此時 系統呼叫的號碼會存入暫存器中,然後便由作業系統取得程式的控制權。當作業 系統完成此次系統呼叫的工作後,程式的控制權便再回到要求存取資源的程式,
如圖5所示,系統呼叫表(system call table)中存著指向處理該系統呼叫函式的指 標。
正常狀況下,系統呼叫的處理流程如圖5的藍色路徑,作業系統會根據系統 呼叫表中對應的函式指標,呼叫處理該系統呼叫的函式,完成後直接由處理系統 呼叫的函式傳回結果給呼叫系統呼叫的程式。
圖 5 正常系統呼叫處理流程
若系統呼叫表被修改,系統呼叫的處理便由原本直接執行核心的函式,改為 指到其他攔截者函式,如圖6中的橙色路徑。
圖 6 攔截系統呼叫下的處理流程
通常在攔截系統呼叫時,最後都會呼叫原本核心中處理系統呼叫的函式,以 保持一致性。攔截系統呼叫可由攔截者函式做一些動作,例如檢查參數等,最後 再呼叫原本核心中的函式來處理此次系統呼叫。
在核心層攔截系統呼叫,利用LKM和device driver的技術,我們可以在LKM
中直接修改system call table的內容,將每個指到原本核心中處理系統呼叫函式的 指標,改為指向我們實作在LKM中的攔截函式。圖7是一個簡單的核心層系統呼 叫攔截的例子。這個例子中實作了一個假的exit函式來取代原本的exit函式,然後 在模組的init_module()函式裡,將系統呼叫表中指向原本exit函式的指標,改為指 向假的exit,並且在模組的cleanup_module()函式裡,把system call table復原,即 把指標指回原本的exit函式。如此當這個模組被載入時,任何程式呼叫exit這個系 統呼叫,都會被導到假的exit函式,而當這個模組被移除時,便會將system call table復原,使得exit這個系統呼叫會導到原本的exit函式。
圖 7 核心層系統呼叫攔截範例