本文首先向讀者講解了Linux下進程地址空間的佈局以及進程堆棧幀的結構,然後在此基礎上介紹了Linux下緩衝區溢出攻擊的原理及對策。
從邏輯上講進程的堆棧是由多個堆棧幀構成的,其中每個堆棧幀都對應一個函數調用。當函數調用發生時,新的堆棧幀被壓入堆棧;當函數返回時,相應的堆棧幀從堆棧中彈出。儘管堆棧幀結構的引入爲在高級語言中實現函數或過程這樣的概念提供了直接的硬件支持,但是由於將函數返回地址這樣的重要數據保存在程序員可見的堆棧中,因此也給系統安全帶來了極大的隱患。
歷史上最著名的緩衝區溢出攻擊可能要算是
根據綠盟科技提供的漏洞報告,2002年共發現各種操作系統和應用程序的漏洞1830個,其中緩衝區溢出漏洞有432個,佔總數的23.6%. 而綠盟科技評出的2002年嚴重程度、影響範圍最大的十個安全漏洞中,和緩衝區溢出相關的就有6個。
在讀者閱讀本文之前有一點需要說明,文中所有示例程序的編譯運行環境爲gcc
|
如果讀者使用的是較高版本的gcc或bash的話,運行文中示例程序的結果可能會與這裏給出的結果不盡相符,具體原因將在相應章節中做出解釋。
Linux下緩衝區溢出攻擊實例
爲了引起讀者的興趣,我們不妨先來看一個Linux下的緩衝區溢出攻擊實例。
|
圖1 攻擊程序exe.c
|
圖2 攻擊對象toto.c
將上面兩個程序分別編譯爲可執行程序,並且將toto改爲屬主爲root的setuid程序:
|
OK,看看接下來會發生什麼。首先別忘了用whoami命令驗證一下我們現在的身份。其實Linux繼承了UNIX的一個習慣,即普通用戶的命令提示符是以$開始的,而超級用戶的命令提示符是以#開始的。
|
第一次一般不會成功,但是我們可以準確得知系統的漏洞所在――0xbffffc38,第二次必然一擊斃命。當我們在新創建的shell下再次執行 whoami命令時,我們的身份已經是root了!由於在所有UNIX系統下黑客攻擊的最高目標就是對root權限的追求,因此可以說系統已經被攻破了。
這裏我們模擬了一次Linux下緩衝區溢出攻擊的典型案例。toto的屬主爲root,並且具有setuid屬性,通常這種程序是緩衝區溢出的典型攻擊目標。普通用戶wy通過其含有惡意攻擊代碼的程序exe向具有缺陷的toto發動了一次緩衝區溢出攻擊,並由此獲得了系統的root權限。有一點需要說明的是,如果讀者使用的是較高版本的bash的話,即使通過緩衝區溢出攻擊exe得到了一個新的shell,在看到whoami命令的結果後您可能會發現您的權限並沒有改變,具體原因我們將在本文最後一節做出詳細的解釋。不過爲了一睹爲快,您可以先使用本文代碼包中所帶的exe_pro.c作爲攻擊程序,而不是圖1中的exe.c。
Linux下進程地址空間的佈局及堆棧幀的結構
要想了解Linux下緩衝區溢出攻擊的原理,我們必須首先掌握Linux下進程地址空間的佈局以及堆棧幀的結構。
任何一個程序通常都包括代碼段和數據段,這些代碼和數據本身都是靜態的。程序要想運行,首先要由操作系統負責爲其創建進程,並在進程的虛擬地址空間中爲其代碼段和數據段建立映射。光有代碼段和數據段是不夠的,進程在運行過程中還要有其動態環境,其中最重要的就是堆棧。圖3所示爲Linux下進程的地址空間佈局:
圖3 Linux下進程地址空間的佈局
首先,execve(2)會負責爲進程代碼段和數據段建立映射,真正將代碼段和數據段的內容讀入內存是由系統的缺頁異常處理程序按需完成的。另外, execve(2)還會將bss段清零,這就是爲什麼未賦初值的全局變量以及static變量其初值爲零的原因。進程用戶空間的最高位置是用來存放程序運行時的命令行參數及環境變量的,在這段地址空間的下方和bss段的上方還留有一個很大的空洞,而作爲進程動態運行環境的堆棧和堆就棲身其中,其中堆棧向下伸展,堆向上伸展。
知道了堆棧在進程地址空間中的位置,我們再來看一看堆棧中都存放了什麼。相信讀者對C語言中的函數這樣的概念都已經很熟悉了,實際上堆棧中存放的就是與每個函數對應的堆棧幀。當函數調用發生時,新的堆棧幀被壓入堆棧;當函數返回時,相應的堆棧幀從堆棧中彈出。典型的堆棧幀結構如圖4所示。
堆棧幀的頂部爲函數的實參,下面是函數的返回地址以及前一個堆棧幀的指針,最下面是分配給函數的局部變量使用的空間。一個堆棧幀通常都有兩個指針,其中一個稱爲堆棧幀指針,另一個稱爲棧頂指針。前者所指向的位置是固定的,而後者所指向的位置在函數的運行過程中可變。因此,在函數中訪問實參和局部變量時都是以堆棧幀指針爲基址,再加上一個偏移。對照圖4可知,實參的偏移爲正,局部變量的偏移爲負。
圖4 典型的堆棧幀結構
介紹了堆棧幀的結構,我們再來看一下在Intel i386體系結構上堆棧幀是如何實現的。圖5和圖6分別是一個簡單的C程序及其編譯後生成的彙編程序。
|
圖5 一個簡單的C程序example1.c
|
圖6 example1.c編譯後生成的彙編程序example1.s
這裏我們着重關心一下與函數function對應的堆棧幀形成和銷燬的過程。從圖5中可以看到,function是在main中被調用的,三個實參的值分別爲1、2、3。由於C語言中函數傳參遵循反向壓棧順序,所以在圖6中32至34行三個實參從右向左依次被壓入堆棧。接下來35行的call指令除了將控制轉移到function之外,還要將call的下一條指令addl的地址,也就是function函數的返回地址壓入堆棧。下面就進入 function函數了,首先在第9行將main函數的堆棧幀指針ebp保存在堆棧中並在第10行將當前的棧頂指針esp保存在堆棧幀指針ebp中,最後在第11行爲function函數的局部變量buffer[14]和sum在堆棧中分配空間。至此,函數function的堆棧幀就構建完成了,其結構如圖7所示。
圖7 函數function的堆棧幀
讀者不妨回過頭去與圖4對比一下。這裏有幾點需要說明。首先,在Intel i386體系結構下,堆棧幀指針的角色是由ebp扮演的,而棧頂指針的角色是由esp扮演的。另外,函數function的局部變量buffer[14] 由14個字符組成,其大小按說應爲14字節,但是在堆棧幀中卻爲其分配了16個字節。這是時間效率和空間效率之間的一種折衷,因爲Intel i386是32位的處理器,其每次內存訪問都必須是4字節對齊的,而高30位地址相同的4個字節就構成了一個機器字。因此,如果爲了填補buffer [14]留下的兩個字節而將sum分配在兩個不同的機器字中,那麼每次訪問sum就需要兩次內存操作,這顯然是無法接受的。還有一點需要說明的是,正如我們在本文前言中所指出的,如果讀者使用的是較高版本的gcc的話,您所看到的函數function對應的堆棧幀可能和圖7所示有所不同。上面已經講過,爲函數function的局部變量buffer[14]和sum在堆棧中分配空間是通過在圖6中第11行對esp進行減法操作完成的,而sub指令中的20 正是這裏兩個局部變量所需的存儲空間大小。但是在較高版本的gcc中,sub指令中出現的數字可能不是20,而是一個更大的數字。應該說這與優化編譯技術有關,在較高版本的gcc中爲了有效運用目前流行的各種優化編譯技術,通常需要在每個函數的堆棧幀中留出一定額外的空間。
下面我們再來看一下在函數function中是如何將a、b、c的和賦給sum的。前面已經提過,在函數中訪問實參和局部變量時都是以堆棧幀指針爲基址,再加上一個偏移,而Intel i386體系結構下的堆棧幀指針就是ebp,爲了清楚起見,我們在圖7中標出了堆棧幀中所有成分相對於堆棧幀指針ebp的偏移。這下圖6中12至16的計算就一目瞭然了,8(%ebp)、12(%ebp)、16(%ebp)和-20(%ebp)分別是實參a、b、c和局部變量sum的地址,幾個簡單的 add指令和mov指令執行後sum中便是a、b、c三者之和了。另外,在gcc編譯生成的彙編程序中函數的返回結果是通過eax傳遞的,因此在圖6中第 17行將sum的值拷貝到eax中。
最後,我們再來看一下函數function執行完之後與其對應的堆棧幀是如何彈出堆棧的。圖6中第21行的leave指令將堆棧幀指針ebp拷貝到 esp中,於是在堆棧幀中爲局部變量buffer[14]和sum分配的空間就被釋放了;除此之外,leave指令還有一個功能,就是從堆棧中彈出一個機器字並將其存放到ebp中,這樣ebp就被恢復爲main函數的堆棧幀指針了。第22行的ret指令再次從堆棧中彈出一個機器字並將其存放到指令指針 eip中,這樣控制就返回到了第36行main函數中的addl指令處。addl指令將棧頂指針esp加上12,於是當初調用函數function之前壓入堆棧的三個實參所佔用的堆棧空間也被釋放掉了。至此,函數function的堆棧幀就被完全銷燬了。前面剛剛提到過,在gcc編譯生成的彙編程序中通過 eax傳遞函數的返回結果,因此圖6中第38行將函數function的返回結果保存在了main函數的局部變量i中。
Linux下緩衝區溢出攻擊的原理
明白了Linux下進程地址空間的佈局以及堆棧幀的結構,我們再來看一個有趣的例子。
|
圖8 一個奇妙的程序example2.c
在main函數中,局部變量x的初值首先被賦爲0,然後調用與x毫無關係的function函數,最後將x的值改爲1並打印出來。結果是多少呢,如果我告訴你是0你相信嗎?閒話少說,還是趕快來看看函數function都動了哪些手腳吧。這裏的function函數與圖5中的function相比只是多了一個指針變量ret以及兩條對ret進行操作的語句,就是它們使得main函數最後打印的結果變成了0。對照圖7可知,地址buffer + 20處保存的正是函數function的返回地址,第7行的語句將函數function的返回地址加了10。這樣會達到什麼效果呢?看一下main函數對應的彙編程序就一目瞭然了。
|
|
圖9 example2.c中main函數對應的彙編程序
地址爲0x
當然,圖8所示只是一個示例性的程序,通過修改保存在堆棧幀中的函數的返回地址,我們改變了程序正常的控制流。圖8中程序的運行結果可能會使很多讀者感到新奇,但是如果函數的返回地址被修改爲指向一段精心安排好的惡意代碼,那時你又會做何感想呢?緩衝區溢出攻擊正是利用了在某些體系結構下函數的返回地址被保存在程序員可見的堆棧中這一缺陷,修改函數的返回地址,使得一段精心安排好的惡意代碼在函數返回時得以執行,從而達到危害系統安全的目的。
說到緩衝區溢出就不能不提shellcode,shellcode讀者已經在圖1中見過了,其作用就是生成一個shell。下面我們就來一步步看一下這段令人眼花繚亂的程序是如何得來的。首先要說明一下,Linux下的系統調用都是通過int $0x80中斷實現的。在調用int $0x80之前,eax中保存了系統調用號,而系統調用的參數則保存在其它寄存器中。圖10所示是直接利用系統調用實現的Hello World程序。
|
圖10 直接利用系統調用實現的Hello World程序hello.c
將其編譯鏈接生成可執行程序hello:
|
有興趣的讀者可以將這個hello的大小和我們當初在第一節C語言課上學過的Hello World程序的大小比較一下,看看能不能用C語言寫出更小的Hello World程序。圖10中的_syscall3和_syscall1都是定義於/usr/include/asm/unistd.h中的宏,該文件中定義了以__NR_開頭的各種系統調用的所對應的系統調用號以及_syscall0到_syscall6六個宏,分別用於參數個數爲0到6的系統調用。由此可知,Linux系統中系統調用所允許的最大參數個數就是6個,比如mmap(2)。另外,仔細閱讀syscall0到_syscall6六個宏的定義不難發現,系統調用號是存放在寄存器eax中的,而系統調用可能會用到的6個參數依次存放在寄存器ebx、ecx、edx、esi、edi和ebp中。
清楚了系統調用的使用規則,我先來看一下如何在Linux下生成一個shell。應該說這是非常簡單的任務,使用execve(2)系統調用即可,如圖11所示。
|
圖11 shellcode.c在Linux下生成一個shell
在shellcode.c中一共用到了兩個系統調用,分別是execve(2)和_exit(2)。查看 /usr/include/asm/unistd.h文件可以得知,與其相應的系統調用號__NR_execve和__NR_exit分別爲11和1。按照前面剛剛講過的系統調用規則,在Linux下生成一個shell並結束退出需要以下步驟:
· 在內存中存放一個以'//0'結束的字符串"/bin/sh";
· 將字符串"/bin/sh"的地址保存在內存中的某個機器字中,並且後面緊接一個值爲0的機器字,這裏相當於設置好了圖11中name[2]中的兩個指針;
· 將execve(2)的系統調用號11裝入eax寄存器;
· 將字符串"/bin/sh"的地址裝入ebx寄存器;
· 將第2步中設好的字符串"/bin/sh"的地址的地址裝入ecx寄存器;
· 將第2步中設好的值爲0的機器字的地址裝入edx寄存器;
· 執行int $0x80,這裏相當於調用execve(2);
· 將_exit(2)的系統調用號1裝入eax寄存器;
· 將退出碼0裝入ebx寄存器;
· 執行int $0x80,這裏相當於調用_exit(2)。
於是我們就得到了圖12所示的彙編程序。
|
圖12
使用execve(2)和_exit(2)系統調用生成shell的彙編程序shellcodeasm.c
這裏第4行的jmp指令和第17行的call指令使用的都是IP相對尋址方式,第14行至第16行對應於_exit(2)系統調用,由於它比較簡單,我們着重看一下調用execve(2)的過程。首先第4行的jmp指令執行之後控制就轉移到了第17行的call指令處,在call指令的執行過程中除了將控制轉移到第5行的pop指令外,還會將其下一條指令的地址壓入堆棧。然而由圖12可知,call指令後面並沒有後續的指令,而是存放了字符串 "/bin/sh",於是實際被壓入堆棧的便成了字符串"/bin/sh"的地址。第5行的pop指令將剛剛壓入堆棧的字符串地址彈出到esi寄存器中。接下來的三條指令首先將esi中的字符串地址保存在字符串"/bin/sh"之後的機器字中,然後又在字符串"/bin/sh"的結尾補了個'//0',最後將0寫入內存中合適的位置。第9行至第12行按圖13所示正確設置好了寄存器eax、ebx、ecx和edx的值,在第13行就可以調用execve (2)了。但是在編譯shellcodeasm.c之後,你會發現程序無法運行。原因就在於圖13中所示的所有數據都存放在代碼段中,而在Linux下存放代碼的頁面是不可寫的,於是當我們試圖使用圖12中第6行的mov指令進行寫操作時,頁面異常處理程序會向運行我們程序的進程發送一個SIGSEGV信號,這樣我們的終端上便會出現Segmentation fault的提示信息。
圖13調用execve(2)之前各寄存器的設置
解決的辦法很簡單,既然不能對代碼段進行寫操作,我們就把圖12中的代碼挪到可寫的數據段或堆棧段中。可是一段可執行的代碼在數據段中應該怎麼表示呢?其實,內存中存放着的無非是0和1這樣的比特,當我們的程序將其用作代碼時這些比特就成了代碼,而當我們的程序將其用作數據時這些比特又成了數據。我們先來看一下圖12中的代碼在內存中是如何存放的,通過gdb中的x命令可以很容易的做到這一點,如圖14所示。
|
圖14 通過gdb中的x命令查看圖12中的代碼在內存中對應的數據
從jmp指令的起始地址0x
|
圖15 shellcode的改進方案
|
圖16 最終的shellcode彙編程序shellcodeasm2.c
同樣,按照上面的方法再次查看內存中的shellcode代碼,如圖16所示。我們在圖16中再次列出了圖1 用到過的shellcode,有興趣的讀者不妨比較一下。
|
圖17 shellcode的來歷
我猜當你看到這裏時一定也像我當初一樣已經熱血沸騰、迫不及待了吧?那就趕快來試一下吧。
|
圖18 通過程序testsc.c驗證我們的shellcode
將testsc.c編譯成可執行程序,再運行testsc就可以看到shell了!
|
圖19描繪了testsc.c程序所作的一切,相信有了前面那麼長的鋪墊,讀者在看到圖19時應該已經沒有困難了。
圖19 程序testsc.c的控制流程
下面我們該回頭看看本文開頭的那個Linux下緩衝區溢出攻擊實例了。攻擊程序exe.c利用了系統中存在漏洞的程序toto.c,通過以下步驟向系統發動了一次緩衝區溢出攻擊:
· 通過命令行參數argv[2]得到toto.c程序中緩衝區buffer[96]的地址,並將該地址填充到large_string[128]中;
· 將我們已經準備好的shellcode拷貝到large_string[128]的開頭;
· 通過環境變量KIRIKA將我們的shellcode注射到buffer[96]中;
· 當toto.c程序中的main函數返回時,buffer[96]中的shellcode得以運行;由於toto的屬主爲root,並且具有setuid屬性,因此我們得到的shell便具有了root權限。
程序exe.c的控制流程與圖19所示程序testsc.c的控制流程非常相似,唯一的不同在於這次我們的shellcode是寄宿在toto運行時的堆棧裏,而不是在數據段中。之所以不能再將shellcode放在數據段中是因爲當我們在程序exe.c中調用execle(3) 運行toto時,進程整個地址空間的映射會根據toto程序頭部的描述信息重新設置,而原來的地址空間中數據段的內容已經不能再訪問了,因此在程序 exe.c中shellcode是通過環境變量來傳遞的。
怎麼樣,是不是感覺傳說中的黑客不再像你想象的那樣神祕了?暫時不要妄下結論,在上面的緩衝區溢出攻擊實例中,攻擊程序exe之所以能夠準確的將 shellcode注射到toto的buffer[96]中,關鍵在於我們在toto程序中打印出了buffer[96]在堆棧中的起始地址。當然,在實際的系統中,不要指望有像toto這樣家有醜事還自揭瘡疤的事情發生。
Linux下防禦緩衝區溢出攻擊的對策
瞭解了緩衝區溢出攻擊的原理,接下來要做的顯然就是要找出克敵之道。這裏,我們主要介紹一種非常簡單但是又比較流行的方法――Libsafe。
在標準C庫中存在着很多像strcpy(3)這種用於處理字符串的函數,它們將一個字符串拷貝到另一個字符串中。對於何時停止拷貝,這些函數通常只有一個判斷標準,即是否遇上了'//0'字符。然而這個唯一的標準顯然是不夠的。我們在上一節剛剛分析過的Linux下緩衝區溢出攻擊實例正是利用 strcpy(3)對系統實施了攻擊,而strcpy(3)的缺陷就在於在拷貝字符串時沒有將目的字符串的大小這一因素考慮進來。像這樣的函數還有很多,比如strcat、gets、scanf、sprintf等等。統計數據表明,在已經發現的緩衝區溢出攻擊案例中,肇事者多是這些函數。正是基於上述事實,Avaya實驗室推出了Libsafe。
在現在的Linux系統中,程序鏈接時所使用的大多都是動態鏈接庫。動態鏈接庫本身就具有很多優點,比如在庫升級之後,系統中原有的程序既不需要重新編譯也不需要重新鏈接就可以使用升級後的動態鏈接庫繼續運行。除此之外,Linux還爲動態鏈接庫的使用提供了很多靈活的手段,而預載 (preload)機制就是其中之一。在Linux下,預載機制是通過環境變量LD_PRELOAD的設置提供的。簡單來說,如果系統中有多個不同的動態鏈接庫都實現了同一個函數,那麼在鏈接時優先使用環境變量LD_PRELOAD中設置的動態鏈接庫。這樣一來,我們就可以利用Linux提供的預載機制將上面提到的那些存在安全隱患的函數替換掉,而Libsafe正是基於這一思想實現的。
圖20所示的testlibsafe.c是一段非常簡單的程序,字符串buf2[16]中首先被寫滿了'A',然後再通過strcpy(3)將其拷貝到buf1[8]中。由於buf2[16]比buf1[8]要大,顯然會發生緩衝區溢出,而且很容易想到,由於'A'的二進制表示爲0x41,所以 main函數的返回地址被改爲了0x41414141。這樣當main返回時就會發生Segmentation fault。
|
圖20 測試Libsafe
|
下面我們就來看一看Libsafe是如何保護我們免遭緩衝區溢出攻擊的。首先,在系統中安裝Libsafe,本文的附件中提供了其2.0版的安裝包。
|
至此安裝還沒有結束,接下來還要正確設置環境變量LD_PRELOAD。
|
下面就可以來試試看了。
|
可以看到,Libsafe正確檢測到了由strcpy()函數導致的緩衝區溢出,其uid、euid和pid,以及進程運行時的Call stack也被一併列出。另外,這些信息不光是在終端上顯示,還會被記錄到系統日誌中,這樣系統管理員就可以掌握潛在的攻擊來源並及時加以防範。
那麼,有了Libsafe我們就可以高枕無憂了嗎?千萬不要有這種天真的想法,在計算機安全領域入侵與反入侵的較量永遠都不會停止。其實 Libsafe爲我們提供的保護可以被輕易的破壞掉。由於Libsafe的實現依賴於Linux系統爲動態鏈接庫所提供的預載機制,因此對於使用靜態鏈接庫的具有緩衝區溢出漏洞的程序Libsafe也就無能爲力了。
|
如果在使用gcc編譯時加上-static選項,那麼鏈接時使用的便是靜態鏈接庫。在系統已經安裝了Libsafe的情況下,可以看到testlibsafe_static再次產生了Segmentation fault。
另外,正如我們在本文前言中所指出的那樣,如果讀者使用的是較高版本的bash的話,那麼即使您在運行攻擊程序exe之後得到了一個新的 shell,您可能會發現並沒有得到您所期望的root權限。其實這正是的高版本bash的改進之一。由於近十年來緩衝區溢出攻擊屢見不鮮,而且大部分的攻擊對象都是系統中屬主爲root的setuid程序,以藉此獲得root權限。因此以root權限運行系統中的程序是十分危險的。爲此,在新的 POSIX.1標準中增加了一個名爲seteuid(2)的系統調用,其作用在於改變進程的effective uid。而新版本的bash也都紛紛採用了這一技術,在bash啓動運行之初首先通過調用seteuid(getuid())將bash的運行權限恢復爲進程屬主的權限,這樣就出現了我們在高版本bash中運行攻擊程序exe所看到的結果。那麼高版本的bash就已經無懈可擊了嗎?其實不然,只要在通過 execve(2)創建shell之前先調用setuid(0)將進程的uid也改爲0,bash的這一改進也就徒勞無功了。也就是說,你所要做的就是遵照前面所講的系統調用規則將setuid(0)加入到shellcode中,而新版shellocde的這一改進只需要很少的工作量。附件中的 shellcodeasm3.c和exe_pro.c告訴了你該如何去做。
安全有兩種不同的表現形式,一種是如果你所使用的系統在安全上存在漏洞,但是黑客們對此一無所知,那麼你可以暫且認爲你的系統是安全的;另一種是黑客和你都發現了系統中的安全漏洞,但是你會想方設法將漏洞彌補上,使你的系統真正無懈可擊。你想要的是哪一種呢?聖經上的一句話給出了這個問題的答案,而這句話也被刻在了美國中央情報局大廳的牆壁上:“你應當瞭解真相,真相會使你自由。”
[1] Aleph One. Smashing The Stack For Fun And Profit.
[2] Pierre-Alain FAYOLLE, Vincent GLAUME. A Buffer Overflow Study -- Attacks & Defenses.
[3] Taeho Oh. Advanced buffer overflow exploit.
[4] 綠盟科技(nsfocus). NSFOCUS 2002年十大安全漏洞, 2002, http://www.nsfocus.net/index.php?act=sec_bug&do=top_ten
[5] 王卓威。基於系統行爲模式的緩衝區溢出攻擊檢測技術。
[6] developerWorks上的《使您的軟件運行起來:防止緩衝區溢出》爲您列出了標準C庫中所有存在安全隱患的函數以及對這些函數的使用建議。
[7] 毛德操,胡希明的《Linux內核源代碼情景分析》向讀者介紹了Linux下嵌入式彙編語言的語法。
[8] W.Richard Stevens的《Advanced Programming in the UNIX Environment》爲您詳細介紹了uid和effective uid的概念以及setuid(2)和seteuid(2)等相關函數的用法。
[9] Joel Scambray, Stuart McClure, George Kurtz的《Hacking Exposed》向讀者介紹了網絡安全的方方面面,從而使讀者對網絡安全有更多的瞭解,知道如何去加強安全性。
[10] Intel. Intel Architecture Software Developer's Manual. Intel Corporation.