網(wǎng)絡(luò)安全編程:Winsock編程基礎(chǔ)
網(wǎng)絡(luò)攻防是一個(gè)比較大的話題,比如端口掃描、SQL Injection掃描、數(shù)據(jù)包嗅探、網(wǎng)絡(luò)密碼猜解、后門(mén)、木馬等知識(shí)的基礎(chǔ)技術(shù)。這些技術(shù)在入侵剖析中是比較常見(jiàn)的技術(shù)。
在學(xué)習(xí)掃描器、嗅探器、木馬等知識(shí)之前,首先必須學(xué)習(xí)網(wǎng)絡(luò)編程的基礎(chǔ)知識(shí),網(wǎng)絡(luò)編程的基礎(chǔ)是深入學(xué)習(xí)網(wǎng)絡(luò)的起步,沒(méi)有基礎(chǔ)知識(shí),掃描、嗅探都是空談。
01 網(wǎng)絡(luò)基礎(chǔ)知識(shí)
各計(jì)算機(jī)之間通過(guò)互聯(lián)網(wǎng)進(jìn)行通信主要依賴(lài)TCP/IP。該協(xié)議是一個(gè)4層協(xié)議,由上至下分別是應(yīng)用層、傳輸層、網(wǎng)際層和鏈路層。TCP/IP的下層協(xié)議總是為上層協(xié)議服務(wù),下層協(xié)議的細(xì)節(jié)對(duì)于上層協(xié)議來(lái)說(shuō)是透明的。分層設(shè)計(jì)的好處是,每一層的功能比較明確,而且修改某一層的實(shí)現(xiàn)不會(huì)影響其他層。TCP/IP在每層協(xié)議中都定義了非常多的不同的協(xié)議,比如網(wǎng)際層的協(xié)議ICMP、IGMP等,傳輸層的TCP、UDP等。在眾多協(xié)議中,最具代表性的協(xié)議是TCP和IP,因此,互聯(lián)網(wǎng)協(xié)議被稱(chēng)為T(mén)CP/IP族(千萬(wàn)別認(rèn)為T(mén)CP和IP就是互聯(lián)網(wǎng)協(xié)議的全部)。
IP協(xié)議是“Internet Protocol”的簡(jiǎn)稱(chēng),它是為計(jì)算機(jī)網(wǎng)絡(luò)相互連接進(jìn)行通信而設(shè)計(jì)的協(xié)議。在IP協(xié)議中最重要的就是IP地址,IP地址是用來(lái)在網(wǎng)絡(luò)上唯一標(biāo)識(shí)計(jì)算機(jī)主機(jī)的地址?;ヂ?lián)網(wǎng)中沒(méi)有兩個(gè)機(jī)器有相同的IP地址,因此它是用來(lái)標(biāo)識(shí)一臺(tái)網(wǎng)絡(luò)主機(jī)的。所有的IP地址都是32位長(zhǎng),它用點(diǎn)分十進(jìn)制法來(lái)表示,比如“10.10.30.12”。IP地址指定的不是主機(jī),而是網(wǎng)絡(luò)接口設(shè)備。因此,一臺(tái)主機(jī)有兩個(gè)網(wǎng)絡(luò)接口,那么就會(huì)有兩個(gè)IP地址。通常情況下,對(duì)于一臺(tái)普通主機(jī)只有一個(gè)網(wǎng)絡(luò)接口設(shè)備,也就只有一個(gè)IP地址,比如個(gè)人使用的PC通常只有一個(gè)IP地址;而對(duì)于服務(wù)器或者網(wǎng)絡(luò)設(shè)備(交換機(jī)、路由器等)來(lái)說(shuō),則會(huì)有多個(gè)網(wǎng)絡(luò)接口設(shè)備,每個(gè)網(wǎng)絡(luò)接口設(shè)備都會(huì)有一個(gè)IP地址,那么對(duì)于路由器這種網(wǎng)絡(luò)設(shè)備來(lái)說(shuō)就會(huì)有多個(gè)IP地址。
IP地址被分為5類(lèi),分別是A類(lèi)、B類(lèi)、C類(lèi)、D類(lèi)和E類(lèi)。各類(lèi)IP地址的范圍如表1所示。
表1 各類(lèi)IP地址的范圍
IP工作在TCP/IP 4層協(xié)議的“網(wǎng)際層”,網(wǎng)際層最主要的工作是將數(shù)據(jù)包進(jìn)行路由。這里所說(shuō)IP是一種被路由協(xié)議,也就是在進(jìn)行路由的過(guò)程中,IP協(xié)議會(huì)被路由協(xié)議用到。真正進(jìn)行數(shù)據(jù)包選路的協(xié)議(其實(shí)就是路由的算法,數(shù)據(jù)包如何進(jìn)行轉(zhuǎn)發(fā)的算法)被稱(chēng)為路由協(xié)議,具體的路由協(xié)議有RIP、OSPF、BGP等。對(duì)于入門(mén)而言,只要了解了IP地址是什么,IP地址的作用是什么即可。
傳輸層主要有兩大協(xié)議,分別是TCP協(xié)議和UDP協(xié)議。
TCP是“Transmission Control Protocol”的簡(jiǎn)稱(chēng),其意思為傳輸控制協(xié)議。TCP是一種面向連接的、可靠的通信協(xié)議。TCP協(xié)議是IP協(xié)議的上層協(xié)議,IP服務(wù)于TCP。
UDP是“User Datagram Protocol”的簡(jiǎn)稱(chēng),其意思為用戶數(shù)據(jù)報(bào)協(xié)議。UDP是一種無(wú)連接的傳輸層協(xié)議,提供面向事務(wù)的簡(jiǎn)單不可靠信息傳送服務(wù)。
傳輸層是為應(yīng)用層提供服務(wù)的,應(yīng)用層的協(xié)議一部分是基于TCP的,比如FTP、HTTP,而一部分是基于UDP的,比如DNS。IP層提供了IP地址用來(lái)標(biāo)識(shí)網(wǎng)絡(luò)主機(jī),而傳輸層提供了端口用來(lái)標(biāo)識(shí)主機(jī)中的進(jìn)程。確定了IP地址和端口號(hào),就確定了網(wǎng)絡(luò)上的主機(jī)及主機(jī)上通信的進(jìn)程。
傳輸層提供了標(biāo)識(shí)通信進(jìn)程的端口號(hào)。按照協(xié)議劃分,端口分為T(mén)CP端口和UDP端口,TCP端口和UDP端口各有65536個(gè)。對(duì)于應(yīng)用程序而言,一般使用大于1024的端口號(hào),因?yàn)樾∮?024的端口號(hào)屬于保留端口。Internet上的很多服務(wù)都是用了小于1024的端口號(hào)。為了避免沖突,程序員自己編寫(xiě)的應(yīng)用程序不要使用小于1024的端口號(hào)。同一協(xié)議的端口不能沖突,比如Web服務(wù)器占用了主機(jī)TCP的80端口,那么另外的程序就不可以再使用TCP的80端口。常用的端口號(hào)如表2所示。
表2 常用端口號(hào)舉例
除了小于1024的端口號(hào)外,還有一些比較知名的端口號(hào),比如MS SQL Server的端口號(hào)是1433,Windows的遠(yuǎn)程桌面端口號(hào)是3389等。程序員在編寫(xiě)自己的網(wǎng)絡(luò)應(yīng)用程序時(shí),要避免與這些常用的端口沖突。
02 面向連接協(xié)議與非面向連接協(xié)議所使用的函數(shù)
1. 面向連接的協(xié)議
在面向連接的協(xié)議中,兩臺(tái)計(jì)算機(jī)之間在進(jìn)行數(shù)據(jù)收發(fā)前,必須先在兩者之間建立一個(gè)通信信道,以確保兩臺(tái)計(jì)算機(jī)之間存在一條路徑可以互相溝通。在數(shù)據(jù)傳輸完畢后,切斷這條通信信道。該種方式相當(dāng)于打電話,用戶在手機(jī)上撥10086,當(dāng)客服人員接聽(tīng)后,用戶就可以開(kāi)始通話,通話完畢后就可以掛電話了。
面向連接的協(xié)議使用的是TCP,服務(wù)器與客戶端建立通信信道所需要的基本W(wǎng)insock函數(shù)如下。
服務(wù)器端函數(shù):
- socket()->bind()->listen()->accept()->send()/recv()->closesocket()
客戶端函數(shù):
- socket()->connet()->send()/recv()->closesocket()
2. 非面向連接的協(xié)議
在非面向連接的協(xié)議中,發(fā)送端只要直接將要發(fā)送的數(shù)據(jù)傳出即可,不需要理會(huì)接收端是否能夠收到數(shù)據(jù)。而接收端在接收到數(shù)據(jù)時(shí),也不會(huì)響應(yīng)信息通知發(fā)送給發(fā)送端。該種方式就相當(dāng)于寫(xiě)信,將寫(xiě)好的信放到信箱中,但是卻不能保證收信人真的能夠收到這封信件。
非面向連接的協(xié)議使用的是UDP,服務(wù)器與客戶端通信所需要的基本W(wǎng)insock函數(shù)如下:
服務(wù)器端函數(shù):
- socket()->bind()->sendto()/recvfrom()->closesocket()
客戶端函數(shù):
- socket()->sendto()/recvfrom()->closesocket()
03 Winsock網(wǎng)絡(luò)編程知識(shí)
Winsock是Windows下網(wǎng)絡(luò)編程的基礎(chǔ),下面介紹Winsock的常用函數(shù)。
1. Winsock的初始化與釋放
在使用Winsock相關(guān)函數(shù)時(shí)需要對(duì)Winsock庫(kù)進(jìn)行初始化,而在使用完成后需要對(duì)Winsock庫(kù)進(jìn)行釋放。完成Winsock庫(kù)的初始化和釋放的函數(shù)如下。
Winsock庫(kù)的初始化函數(shù)的定義:
- int WSAStartup(WORD wVersionRequested, LPWSADATA lpWSAData);
該函數(shù)的第1個(gè)參數(shù)wVersionRequested是需要初始化Winsock庫(kù)的版本號(hào)。第2個(gè)參數(shù)lpWSAData是一個(gè)指向WSADATA的指針。該函數(shù)的返回值為0,說(shuō)明函數(shù)調(diào)用成功。如果函數(shù)調(diào)用失敗,則返回其他值。在程序的開(kāi)始處調(diào)用該初始化函數(shù),在程序中就可以使用Winsock相關(guān)的所有API函數(shù)。
Winsock庫(kù)的釋放函數(shù)的定義:
- int WSACleanup (void);
該函數(shù)沒(méi)有參數(shù),在程序的結(jié)束處直接調(diào)用該函數(shù),即可釋放Winsock庫(kù)。
初始化與釋放Winsock庫(kù)的代碼示例如下:
- WORD wVersionRequested;
- WSADATA wsaData;
- int err;
- wVersionRequested = MAKEWORD( 2, 2 );
- err = WSAStartup( wVersionRequested, &wsaData );
- if ( err != 0 )
- {
- return -1;
- }
- if ( LOBYTE( wsaData.wVersion ) != 2 ||
- HIBYTE( wsaData.wVersion ) != 2 )
- {
- WSACleanup( );
- return -1;
- }
- // ……
- WSACleanup();
2. 套接字的創(chuàng)建與關(guān)閉
套接字用于根據(jù)指定的協(xié)議類(lèi)型來(lái)分配一個(gè)套接字描述符。該描述符主要用在客戶端和服務(wù)器端進(jìn)行通信連接,當(dāng)套接字使用完畢時(shí)應(yīng)該關(guān)閉套接字以釋放資源。創(chuàng)建套接字與關(guān)閉套接字的函數(shù)為socket()和closesocket()。
創(chuàng)建套接字的函數(shù)定義如下:
- SOCKET socket(int af, int type, int protocol);
socket()函數(shù)共有3個(gè)參數(shù),第1個(gè)參數(shù)af用來(lái)指定地址族,在Windows下可以使用的參數(shù)值有多個(gè),但是真正可以使用的只有兩個(gè),分別是AF_INET和PF_INET。這兩個(gè)宏在Winsock2.h下的定義是相同的,分別如下:
- #define AF_INET 2 /* internetwork: UDP, TCP, etc. */
- /*
- * Protocol families, same as address families for now.
- */
- #define PF_INET AF_INET
以上兩個(gè)定義都摘自Winsock2.h頭文件。從定義中可以看出,PF_INET和AF_INET是相同的。看PF_INET宏定義上面的注釋?zhuān)珹F表示地址族(Address Family),而PF表示協(xié)議族(Protocol Family)。對(duì)于Windows來(lái)說(shuō),兩者相同;對(duì)于Unix/Linux來(lái)說(shuō),兩者是不相同的。一般情況下,在調(diào)用socket()函數(shù)時(shí)應(yīng)該使用PF_INET,而在設(shè)置地址時(shí)使用AF_INET。FP_INET上面的那句注釋?zhuān)瑯右彩浅鲎訵insock2.h頭文件中。“Protocol families,same as address families for now.”,也就是說(shuō),目前PF和AF是相同的。注釋中說(shuō)目前是相同的,可能這樣定義是為以后預(yù)留的,為了保持良好的兼容性。調(diào)用socket()函數(shù)時(shí),應(yīng)該使用PF_INET宏,而盡量避免或不去使用AF_INET宏。
socket()函數(shù)的第 2 個(gè)參數(shù) type 是指定新套接字描述符的類(lèi)型。這里可以使用的值通常有3個(gè),分別是 SOCK_STREAM、SOCK_DGRAM 和 SOCK_RAW,分別表示流套接字、數(shù)據(jù)包套接字和原始協(xié)議接口。
socket()函數(shù)的第 3 個(gè)參數(shù) protocol 用來(lái)指定應(yīng)用程序所使用的通信協(xié)議,這里可以選擇使用 IPPROTO_TCP、IPPROTO_UDP、IPPROTO_ICMP 等協(xié)議,這個(gè)參數(shù)的值根據(jù)第 2 個(gè)參數(shù)的值進(jìn)行選擇。第2 個(gè)參數(shù)如果使用SOCK_STREAM,那么第3 個(gè)參數(shù)應(yīng)該使用IPPROTO_TCP;如果第 3 個(gè)參數(shù)使用了 SOCK_DGRAM,那么第 3 個(gè)參數(shù)應(yīng)該使用 IPPROTO_UDP。為了書(shū)寫(xiě)簡(jiǎn)單,如果第 2 個(gè)參數(shù)是 SOCK_STREAM 或 SOCK_DGRAM,那么第 3 個(gè)參數(shù)可以默認(rèn)為 0。如果第 2 個(gè)參數(shù)指定的是 SOCK_RAW,那么第 3 個(gè)參數(shù)就必須指定,而不能使用 0 值。
socket()函數(shù)調(diào)用成功返回值為一個(gè)新的套接字描述符,如果調(diào)用失敗,則返回 INVALID_SOCKET。調(diào)用失敗后,想要知道調(diào)用失敗的原因,那么緊接著調(diào)用 WSAGetLastError()函數(shù)得到錯(cuò)誤碼。
所有的Winsock函數(shù)出錯(cuò)后,都可以調(diào)用WSAGetLastError()函數(shù)得到錯(cuò)誤碼,但是WSAStartup()不能通過(guò)WSAGetLastError()得到錯(cuò)誤碼,因?yàn)閃SAStartup()未調(diào)用成功,不能調(diào)用WSAGetLastError()函數(shù)。
關(guān)閉套接字的函數(shù)定義如下:
- int closesocket(SOCKET s);
closesocket()函數(shù)的參數(shù)是 socket()函數(shù)創(chuàng)建的套接字描述符。
對(duì)于WSAStartup()/WSACleanup()和socket()/closesocket()這樣的函數(shù),最好保持成對(duì)出現(xiàn)。也就是說(shuō),在寫(xiě)完一個(gè)函數(shù)時(shí),立刻寫(xiě)出另外一個(gè)函數(shù)的調(diào)用,以免忘記資源的釋放。
3. 面向連接協(xié)議的函數(shù)
bind()、listen()、accept()、connect()、send()和recv(),這些函數(shù)是常用的面向連接的函數(shù),它們都是Winsock面向連接的最基本的函數(shù)。下面介紹幾個(gè)函數(shù)的使用方法。
通過(guò)socket()函數(shù)可以創(chuàng)建一個(gè)新的套接字描述符,但是它只是一個(gè)描述符,它為網(wǎng)絡(luò)的一些資源做準(zhǔn)備。要真正在網(wǎng)絡(luò)上進(jìn)行通信,需要本地的地址與本地的端口號(hào)信息。當(dāng)然,本地地址與端口號(hào)信息要去套接字描述符進(jìn)行關(guān)聯(lián)進(jìn)行綁定。在Winsock函數(shù)中,使用bind()函數(shù)完成套接字與地址端口信息的綁定。bind()函數(shù)的定義如下:
- int bind(SOCKET s, const struct sockaddr FAR *name, int namelen);
該函數(shù)有3個(gè)參數(shù),第1個(gè)參數(shù)s是新創(chuàng)建的套接字描述符,也就是用socket()函數(shù)創(chuàng)建的描述符,第2個(gè)參數(shù)name是一個(gè)sockaddr的結(jié)構(gòu)體,提供套接字一個(gè)地址和端口信息,第3個(gè)參數(shù)namelen是sockaddr結(jié)構(gòu)體的大小。
其中第2個(gè)參數(shù)中的sockaddr結(jié)構(gòu)體定義如下:
- struct sockaddr {
- u_short sa_family;
- char sa_data[14];
- };
該結(jié)構(gòu)體共有16字節(jié),在該結(jié)構(gòu)體之前所使用的結(jié)構(gòu)體為sockaddr_in,該結(jié)構(gòu)體的定義如下:
- struct sockaddr_in {
- short sin_family;
- u_short sin_port;
- struct in_addr sin_addr;
- char sin_zero[8];
- };
sockaddr結(jié)構(gòu)體是為了保持各個(gè)特定協(xié)議之間的結(jié)構(gòu)體兼容性而設(shè)計(jì)的。為bind()函數(shù)指定地址和端口時(shí),向sockaddr_in結(jié)構(gòu)體填充相應(yīng)的內(nèi)容,而調(diào)用函數(shù)時(shí)應(yīng)該使用sockaddr結(jié)構(gòu)體。
在sockaddr_in結(jié)構(gòu)體中,還有一個(gè)結(jié)構(gòu)體in_addr,該結(jié)構(gòu)體在winsock2.h中的定義如下:
- struct in_addr {
- union {
- struct { u_char s_b1,s_b2,s_b3,s_b4; } S_un_b;
- struct { u_short s_w1,s_w2; } S_un_w;
- u_long S_addr;
- } S_un;
- };
該結(jié)構(gòu)體中是一個(gè)共用體S_un,包含兩個(gè)結(jié)構(gòu)體變量和1個(gè)u_long類(lèi)型變量。一般使用的IP地址是使用點(diǎn)分十進(jìn)制表示的,而in_addr結(jié)構(gòu)體中卻沒(méi)有提供用來(lái)保存點(diǎn)分十進(jìn)制表示IP地址的數(shù)據(jù)類(lèi)型,這時(shí)需要使用轉(zhuǎn)換函數(shù),把點(diǎn)分十進(jìn)制表示的IP地址轉(zhuǎn)換成in_addr結(jié)構(gòu)體可以接受的類(lèi)型。這里使用的轉(zhuǎn)換函數(shù)是inet_addr(),該函數(shù)的定義如下:
- unsigned long inet_addr(const char FAR *cp);
該函數(shù)是將點(diǎn)分十進(jìn)制表示IP地址轉(zhuǎn)換成unsigned long類(lèi)型的數(shù)值。該函數(shù)的參數(shù)cp是指向點(diǎn)分十進(jìn)制IP地址的字符指針。同時(shí)該函數(shù)有一個(gè)逆函數(shù),是將unsigned long型的數(shù)值型IP地址轉(zhuǎn)換為點(diǎn)分十進(jìn)制的IP地址字符串,該函數(shù)的定義如下:
- char FAR * inet_ntoa(struct in_addr in);
sockaddr_in 結(jié)構(gòu)體中的 sin_port 表示端口,這個(gè)端口需要使用大尾方式字節(jié)序存儲(chǔ)(大尾方式和小尾方式是兩種不同的存儲(chǔ)方式)。在 Intel X86 架構(gòu)下,數(shù)值存儲(chǔ)方式默認(rèn)都是小尾方式字節(jié)序,而 TCP/IP 的數(shù)值存儲(chǔ)方式都是大尾方式的字節(jié)序。為了實(shí)現(xiàn)方便的轉(zhuǎn)換,winsock2.h中提供了方便的函數(shù),即 htons()和 htonl()兩個(gè)函數(shù),并且提供了它們的逆函數(shù) ntohs()和 ntohl()。
htons()和 htonl()函數(shù)的定義分別如下:
- u_short htons(u_short hostshort);
- u_long htonl(u_long hostlong);
ntohs()和 ntohl()函數(shù)的定義分別如下:
- u_short ntohs(u_short netshort);
- u_long ntohl(u_long netlong);
這4個(gè)函數(shù)中,前兩個(gè)函數(shù)是將主機(jī)字節(jié)序轉(zhuǎn)換為網(wǎng)絡(luò)字節(jié)序(host to network),后兩個(gè)函數(shù)是將網(wǎng)絡(luò)字節(jié)序轉(zhuǎn)換為主機(jī)字節(jié)序(network to host)。在有些架構(gòu)系統(tǒng)下,主機(jī)字節(jié)序和網(wǎng)絡(luò)字節(jié)序是相同的,那樣轉(zhuǎn)換函數(shù)不進(jìn)行任何轉(zhuǎn)換,但是為了代碼的移植性,還是會(huì)進(jìn)行轉(zhuǎn)換函數(shù)的調(diào)用。
具體bind()函數(shù)的使用方法如下:
- // 創(chuàng)建套接字
- SOCKET sLisent = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
- // 對(duì) sockaddr_in 結(jié)構(gòu)體填充地址、端口等信息
- struct sockaddr_in ServerAddr;
- ServerAddr.sin_family = AF_INET;
- ServerAddr.sin_addr.S_un.S_addr = inet_addr("10.10.30.12");
- ServerAddr.sin_port = htons(1234);
- // 綁定套接字與地址信息
- bind(sLisent, (SOCKADDR *)&ServerAddr, sizeof(ServerAddr));
對(duì)于服務(wù)器端的地址可以指定為INADDR_ANY宏,表示“任意地址”或“所有地址”。當(dāng)客戶端發(fā)起連接時(shí),服務(wù)器操作系統(tǒng)接收到客戶端的連接,根據(jù)網(wǎng)絡(luò)的配置情況會(huì)自動(dòng)選擇一個(gè)IP地址和客戶端進(jìn)行通信。
當(dāng)套接字與地址端口信息綁定以后,就需要讓端口進(jìn)行監(jiān)聽(tīng),當(dāng)端口處于監(jiān)聽(tīng)狀態(tài)以后就可以接受其他主機(jī)的連接了。監(jiān)聽(tīng)端口和接受連接請(qǐng)求的函數(shù)分別為listen()和accept()。
監(jiān)聽(tīng)端口的函數(shù)定義如下:
- int listen(SOCKET s, int backlog);
該函數(shù)有兩個(gè)參數(shù),第1個(gè)參數(shù)s是指定要監(jiān)聽(tīng)的套接字描述符,第2個(gè)參數(shù)backlog是允許進(jìn)入請(qǐng)求連接隊(duì)列的個(gè)數(shù),backlog的最大值由系統(tǒng)指定,在winsock2.h中,其最大值由SOMAXCONN表示,該值的定義如下:
- #define SOMAXCONN 0x7fffffff
接受連接請(qǐng)求的函數(shù)定義如下:
- SOCKET accept(SOCKET s, struct sockaddr FAR *addr, int FAR *addrlen);
該函數(shù)從連接請(qǐng)求隊(duì)列中獲得連接信息,創(chuàng)建新的套接字描述符,獲取客戶端地址。新創(chuàng)建的套接字用于和客戶端進(jìn)行通信,在服務(wù)器和客戶端通信完成后,該套接字也需要使用closesocket()函數(shù)進(jìn)行關(guān)閉,以釋放相應(yīng)的資源。該函數(shù)有3個(gè)參數(shù),第1個(gè)參數(shù)s是處于監(jiān)聽(tīng)的套接字描述符,第2個(gè)參數(shù)addr是一個(gè)指向sockaddr結(jié)構(gòu)體的指針,用來(lái)返回客戶端的地址信息,第3個(gè)參數(shù)addrlen是一個(gè)指向int型的指針變量,用來(lái)傳入sockaddr結(jié)構(gòu)體的大小。
上面介紹的是面向連接的服務(wù)器端的函數(shù),完成了一系列服務(wù)器應(yīng)有的基本的動(dòng)作,具體如下。
① bind()函數(shù)將套接字描述符與地址信息進(jìn)行綁定。
② listen()函數(shù)將綁定過(guò)套接字描述符置于監(jiān)聽(tīng)狀態(tài)。
③ accept()函數(shù)獲取連接隊(duì)列中的連接信息,創(chuàng)建新的套接字描述符,以便與客戶端通信。
面向連接的客戶端只需要完成與服務(wù)器的連接這樣一個(gè)動(dòng)作就可以實(shí)現(xiàn)和服務(wù)器端的通信了。創(chuàng)建套接字描述符后,使用connect()函數(shù)就可以完成與服務(wù)器的連接。
connect()函數(shù)的定義如下:
- int connect(SOCKET s, const struct sockaddr FAR *name, int namelen);
該函數(shù)的作用是將套接字進(jìn)行連接。該函數(shù)有3個(gè)參數(shù),第1個(gè)參數(shù)s表示創(chuàng)建好的套接字描述符,第2個(gè)參數(shù)name是指向sockaddr結(jié)構(gòu)體的指針,sockaddr結(jié)構(gòu)體中保存了服務(wù)器的IP地址和端口號(hào),第3個(gè)參數(shù)namelen是指定sockaddr結(jié)構(gòu)體的長(zhǎng)度。
當(dāng)客戶端使用connect()函數(shù)與服務(wù)器連接后,客戶端和服務(wù)器就可以進(jìn)行通信了。通信時(shí)主要就是信息的發(fā)送和信息的接收。這里介紹的函數(shù)有兩個(gè),分別是send()和recv()。
發(fā)送函數(shù)send()的定義如下:
- int send(SOCKET s, const char FAR *buf, int len, int flags);
該函數(shù)有4個(gè)參數(shù),第1個(gè)參數(shù)s是套接字描述符,該套接字描述符對(duì)于服務(wù)器端而言,使用的是accept()函數(shù)返回的套接字描述符,對(duì)于客戶端而言,使用的是socket()函數(shù)創(chuàng)建的套接字描述符,第2個(gè)參數(shù)buf是發(fā)送消息的緩沖區(qū),第3個(gè)參數(shù)len是緩沖區(qū)的長(zhǎng)度,第4個(gè)參數(shù)flags通常賦為0值。
接收函數(shù)recv()的定義如下:
- int recv(SOCKET s, char FAR *buf, int len, int flags);
該函數(shù)有4個(gè)參數(shù)。該函數(shù)的使用方法與send()函數(shù)的使用方法相同,這里不再進(jìn)行介紹。
從send()和recv()兩個(gè)函數(shù)的名稱(chēng)來(lái)看分別是發(fā)送和接受的意思,但是實(shí)際上對(duì)于數(shù)據(jù)的發(fā)送和接收依靠的是網(wǎng)絡(luò)協(xié)議來(lái)完成的,send()函數(shù)和recv()函數(shù)只是完成了將數(shù)據(jù)從網(wǎng)絡(luò)協(xié)議所使用的緩沖區(qū)中進(jìn)行拷貝的一個(gè)動(dòng)作。
4. 非面向連接協(xié)議的函數(shù)
在面向連接的TCP中,服務(wù)器端將套接字描述符與地址進(jìn)行綁定后,需要將端口進(jìn)行監(jiān)聽(tīng),等待接受客戶端的連接請(qǐng)求,而在客戶端則需要連接服務(wù)器,完成這些步驟就可以保證面向連接的TCP的可靠傳輸,在調(diào)用connect()函數(shù)的過(guò)程中也完成了TCP的“三次握手”的過(guò)程。非面向連接的UDP協(xié)議在開(kāi)發(fā)上基本與面向連接TCP的協(xié)議相同。在非面向連接的UDP開(kāi)發(fā)中,服務(wù)器端不需要對(duì)端口進(jìn)行監(jiān)聽(tīng),也就不需要等待接受客戶端的連接請(qǐng)求,而客戶端也不需要完成與服務(wù)器端的連接。中間的“三次握手”過(guò)程也就省略了,這樣UDP相對(duì)于TCP來(lái)講就顯得不可靠了,但是在效率方面卻要快于TCP。
在非面向連接協(xié)議開(kāi)發(fā)中,服務(wù)器端不再需要調(diào)用listen()、accept()函數(shù),客戶端不再需要調(diào)用connect()函數(shù)。而服務(wù)器和客戶端的通信函數(shù)使用sendto()和recvfrom()函數(shù)即可。
sendto()函數(shù)的定義如下:
- int sendto(
- SOCKET s,
- const char FAR *buf,
- int len,
- int flags,
- const struct sockaddr FAR *to,
- int tolen
- );
該函數(shù)是來(lái)用在UDP通信雙方進(jìn)行發(fā)送數(shù)據(jù)的函數(shù),該函數(shù)有6個(gè)參數(shù),第1個(gè)參數(shù)s是套接字描述符,第2個(gè)參數(shù)buf是要發(fā)送數(shù)據(jù)的緩沖區(qū),第3個(gè)參數(shù)len是指定第2個(gè)參數(shù)的長(zhǎng)度,第4個(gè)參數(shù)通常賦0值,第5個(gè)參數(shù)to是一個(gè)指向sockaddr結(jié)構(gòu)體的指針,這里給出接收消息的地址信息,第6個(gè)參數(shù)tolen是指定第5個(gè)參數(shù)的長(zhǎng)度。
recvfrom()函數(shù)的定義如下:
- int recvfrom(
- SOCKET s,
- char FAR* buf,
- int len,
- int flags,
- struct sockaddr FAR *from,
- int FAR *fromlen
- );
該函數(shù)是用來(lái)在UDP通信雙方進(jìn)行接收數(shù)據(jù)的函數(shù)。該函數(shù)的用法與sendto()相同,這里不再進(jìn)行介紹。
sendto()函數(shù)和recvfrom()函數(shù)的功能與send()函數(shù)和recv()函數(shù)類(lèi)似,它們都是用于向網(wǎng)絡(luò)協(xié)議緩沖區(qū)進(jìn)行數(shù)據(jù)復(fù)制的函數(shù),并不是真正的去完成數(shù)據(jù)的發(fā)送和接收的。
04 字節(jié)順序
字節(jié)序的存在是由于不同架構(gòu)CPU在訪問(wèn)數(shù)據(jù)時(shí)所采取的順序不同,在計(jì)算機(jī)內(nèi)存中對(duì)數(shù)值的存儲(chǔ)有一定的標(biāo)準(zhǔn),而該標(biāo)準(zhǔn)隨著系統(tǒng)架構(gòu)的不同而不同。了解字節(jié)存儲(chǔ)順序?qū)τ谀嫦蚬こ淌且豁?xiàng)基礎(chǔ)的知識(shí),在動(dòng)態(tài)分析程序的時(shí)候,往往需要觀察內(nèi)存數(shù)據(jù)的變化情況,如果不了解字節(jié)存儲(chǔ)順序,那么可能會(huì)迷失在內(nèi)存的汪洋大海中而無(wú)法繼續(xù)逆向航行。
1. 字節(jié)序基礎(chǔ)
通常情況下,數(shù)值在內(nèi)存中存儲(chǔ)的方式有兩種,一種是大尾方式(大尾字節(jié)序就是網(wǎng)絡(luò)字節(jié)序),另一種是小尾方式。
先來(lái)看一個(gè)簡(jiǎn)單的例子,比如0x01020304這樣一個(gè)數(shù)值,如果用大尾方式存儲(chǔ),其存儲(chǔ)方式為01 02 03 04,而用小尾方式存儲(chǔ)則是04 03 02 01。這樣表示也許不直觀,用表格的形式展示其具體的區(qū)別,如表3所示。
表3 字節(jié)順序的對(duì)比表
從表中可以得到如下結(jié)論。
大尾存儲(chǔ)方式:內(nèi)存高位地址存放數(shù)據(jù)低位字節(jié)數(shù)據(jù),內(nèi)存低位地址存放數(shù)據(jù)高位字節(jié)數(shù)據(jù);
小尾存儲(chǔ)方式:內(nèi)存高位地址存放數(shù)據(jù)高位字節(jié)數(shù)據(jù),內(nèi)存低位地址存放數(shù)據(jù)低位字節(jié)數(shù)據(jù)。
2. 主機(jī)字節(jié)序與網(wǎng)絡(luò)字節(jié)序
主機(jī)字節(jié)序與網(wǎng)絡(luò)字節(jié)序是相對(duì)的概念。
所謂主機(jī)字節(jié)序,是指主機(jī)在存儲(chǔ)數(shù)據(jù)時(shí)的字節(jié)順序,主機(jī)字節(jié)序根據(jù)系統(tǒng)架構(gòu)的不同而不同。通常情況下,Windows操作系統(tǒng)兼容的CPU為小尾方式,而UNIX操作系統(tǒng)所兼容的CPU多為大尾方式。因此,主機(jī)字節(jié)序并非固定的字節(jié)序,需要根據(jù)不同的系統(tǒng)架構(gòu)進(jìn)行確定。
所謂網(wǎng)絡(luò)字節(jié)序,是指網(wǎng)絡(luò)傳輸相關(guān)協(xié)議所規(guī)定的字節(jié)傳輸順序,TCP/IP所使用的字節(jié)序?yàn)榇笪卜绞健?/p>
3. 字節(jié)序相關(guān)函數(shù)
涉及字節(jié)序常用的相關(guān)函數(shù)有htons()、htonl()、ntohs()和ntohl()。這4個(gè)函數(shù)的定義分別如下:
- u_short htons(u_short hostshort);
- u_long htonl(u_long hostlong);
- u_short ntohs(u_short netshort);
- u_long ntohl(u_long netlong);
在Windows下,使用以上4個(gè)轉(zhuǎn)換函數(shù)會(huì)改變值的大小,因?yàn)槠湓趦?nèi)存中的存放方式改變了。如果在UNIX系統(tǒng)下,使用以上4個(gè)轉(zhuǎn)換函數(shù)是不會(huì)發(fā)生任何改變的。無(wú)論是何種系統(tǒng),在進(jìn)行網(wǎng)絡(luò)開(kāi)始時(shí)都需要調(diào)用這些函數(shù)進(jìn)行轉(zhuǎn)換,因?yàn)檫@樣做可以有效的保證在網(wǎng)絡(luò)中傳輸?shù)拇_實(shí)是網(wǎng)絡(luò)字節(jié)序。
4. 編程判斷主機(jī)字節(jié)序
“編程判斷主機(jī)字節(jié)序”是很多殺毒軟件公司或者是安全開(kāi)發(fā)職位的一道面試題,因?yàn)檫@題比較基礎(chǔ)。這里給出對(duì)于該題目的實(shí)現(xiàn)方法。完成該題目有兩種方法,第1種方法是“取值比較法”,第2種方法是“直接轉(zhuǎn)換比較法”。
方法一:取值比較法
所謂取值比較法,首先定義一個(gè)4字節(jié)的十六進(jìn)制數(shù)。因?yàn)槭褂谜{(diào)試器查看內(nèi)存最直觀的就是十六進(jìn)制值,所以定義十六進(jìn)制數(shù)是一個(gè)操作起來(lái)比較直觀的方法。而后通過(guò)指針?lè)绞饺〕鲞@個(gè)十六進(jìn)制數(shù)在“內(nèi)存”中的某一字節(jié),最后和實(shí)際數(shù)值中相對(duì)應(yīng)的數(shù)進(jìn)行比較。由于字節(jié)序的問(wèn)題,內(nèi)存中的某字節(jié)與實(shí)際數(shù)值中對(duì)應(yīng)的字節(jié)可能不同,這樣就可以確定字節(jié)序了。
代碼如下:
- int main(int argc, char* argv[])
- {
- DWORD dwSmallNum = 0x01020304;
- if ( *(BYTE *)&dwSmallNum == 0x04 )
- {
- printf("Small Sequence. \r\n");
- }
- else
- {
- printf("Big Sequence. \r\n");
- }
- return 0;
- }
以上代碼中,定義了0x01020304這個(gè)十六進(jìn)制數(shù),其在小尾方式內(nèi)存中的存儲(chǔ)順序?yàn)?4 03 02 01。取*(BYTE *)&dwSmallNum內(nèi)存中低地址位的值,如果是小尾方式的話,那么低地址位存儲(chǔ)的值為0x04,如果是大尾方式則為0x01。
方法二:直接轉(zhuǎn)換比較法
所謂直接轉(zhuǎn)換比較法,是利用字節(jié)序轉(zhuǎn)換函數(shù)將所定義的值進(jìn)行轉(zhuǎn)換,然后用轉(zhuǎn)換后的值和原值進(jìn)行比較。如果原值與轉(zhuǎn)換后的值相同,說(shuō)明為大尾方式,否則為小尾方式。
代碼如下:
- int main(int argc, char* argv[])
- {
- DWORD dwSmallNum = 0x01020304;
- if ( dwSmallNum == htonl(dwSmallNum) )
- {
- printf("Big Sequence. \r\n");
- }
- else
- {
- printf("Small Sequence. \r\n");
- }
- return 0;
- }
這種方法比較直接,如果轉(zhuǎn)換后的結(jié)果與原值相等,就說(shuō)明是大尾方式,因?yàn)檗D(zhuǎn)換后的結(jié)果是網(wǎng)絡(luò)字節(jié)序,網(wǎng)絡(luò)字節(jié)序等同于大尾方式。
關(guān)于字節(jié)序的內(nèi)容大家一定要自行調(diào)試體會(huì)一下,因?yàn)樵诰W(wǎng)絡(luò)開(kāi)發(fā)中只需要進(jìn)行簡(jiǎn)單的轉(zhuǎn)換即可,不需要過(guò)多的關(guān)心它的細(xì)節(jié)。而如果是做逆向工程時(shí),在內(nèi)存中要進(jìn)行數(shù)據(jù)的查找時(shí),這時(shí)字節(jié)序的知識(shí)會(huì)使用到了。