別看這是封包這一問題,但是涉及的技術范圍很廣范,實現的方式也很多(比如說APIHOOK,VXD,Winsock2都可以實現),在這里我們不可能每種技術和方法都涉及,所以我在這里以Winsock2技術作詳細講解,就算作拋磚引玉。
由于大多數讀者對封包類編程不是很了解,我在這里就簡單介紹一下相關知識:
APIHooK:
由于Windows的把內核提供的功能都封裝到API里面,所以大家要實現功能就必須通過API,換句話說就是我們要想捕獲數據封包,就必須先要得知道并且捕獲這個API,從API里面得到封包信息。
VXD:
直接通過控制VXD驅動程序來實現封包信息的捕獲,不過VXD只能用于win9X。
winsock2:
winsock是Windows網絡編程接口,winsock工作在應用層,它提供與底層傳輸協議無關的高層數據傳輸編程接口,winsock2是winsock2.0提供的服務提供者接口,但只能在win2000下用。
好了,我們開始進入winsock2封包式編程吧。
在封包編程里面我準備分兩個步驟對大家進行講解:1、封包的捕獲,2、封包的發送。
首先我們要實現的是封包的捕獲:
Delphi的封裝的winsock是1.0版的,很自然winsock2就用不成。如果要使用winsock2我們要對winsock2在Delphi里面做一個接口,才可以使用winsock2。
1、如何做winsock2的接口?
1)我們要先定義winsock2.0所用得到的類型,在這里我們以WSA_DATA類型做示范,大家可以舉一仿三的來實現winsock2其他類型的封裝。
我們要知道WSA_DATA類型會被用于WSAStartup(wVersionRequired: word; var WSData: TWSAData): Integer;,大家會發現WSData是引用參數,在傳入參數時傳的是變量的地址,所以我們對WSA_DATA做以下封裝:
| const WSADESCRIPTION_LEN = 256; WSASYS_STATUS_LEN = 128; type PWSA_DATA = ^TWSA_DATA; WSA_DATA = record wVersion: Word; wHighVersion: Word; szDescription: array[0..WSADESCRIPTION_LEN] of Char; szSystemStatus: array[0..WSASYS_STATUS_LEN] of Char; iMaxSockets: Word; iMaxUdpDg: Word; lpVendorInfo: PChar; end; TWSA_DATA = WSA_DATA; |
2)我們要從WS2_32.DLL引入winsock2的函數,在此我們也是以WSAStartup為例做函數引入:
| function WSAStartup(wVersionRequired: word; var WSData: TWSAData): Integer; stdcall; implementation const WinSocket2 = 'WS2_32.DLL'; function WSAStartup; external winsocket name 'WSAStartup'; |
通過以上方法,我們便可以對winsock2做接口,下面我們就可以用winsock2做封包捕獲了,不過首先要有一塊網卡。因為涉及到正在運作的網絡游戲安全問題,所以我們在這里以IP數據包為例做封包捕獲,如果下面的某些數據類型您不是很清楚,請您查閱MSDN:
1)我們要起動WSA,這時個要用到的WSAStartup函數,用法如下:
| INTEGER WSAStartup( wVersionRequired: word, WSData: TWSA_DATA ); |
2)使用socket函數得到socket句柄,m_hSocket:=Socket(AF_INET, SOCK_RAW, IPPROTO_IP); 用法如下:
| INTEGER socket(af: Integer, Struct: Integer, protocol: Integer ); m_hSocket:=Socket(AF_INET, SOCK_RAW, IPPROTO_IP); |
在程序里m_hSocket為socket句柄,AF_INET,SOCK_RAW,IPPROTO_IP均為常量。
3)定義SOCK_ADDR類型,跟據我們的網卡IP給Sock_ADDR類型附值,然后我們使用bind函數來綁定我們的網卡,Bind函數用法如下:
| Type IN_ADDR = record S_addr : PChar; End; Type TSOCK_ADDR = record sin_family: Word; sin_port: Word; sin_addr : IN_ADDR sin_zero: array[0..7] of Char; End; var LocalAddr:TSOCK_ADDR; LocalAddr.sin_family: = AF_INET; LocalAddr.sin_port: = 0; LocalAddr.sin_addr.S_addr: = inet_addr('192.168.1.1'); //這里你自己的網卡的IP地址,而inet_addr這個函數是winsock2的函數。 bind(m_hSocket, LocalAddr, sizeof(LocalAddr)); |
4)用WSAIoctl來注冊WSA的輸入輸出組件,其用法如下:
| INTEGER WSAIoctl(s:INTEGER, dwIoControlCode : INTEGER, lpvInBuffer :INTEGER, cbInBuffer : INTEGER, lpvOutBuffer : INTEGER, cbOutBuffer: INTEGER, lpcbBytesReturned : INTEGER, lpOverlapped : INTEGER, lpCompletionRoutine : INTEGER ); |
5)下面做死循環,在死循環塊里,來實現數據的接收。但是徇環中間要用Sleep()做延時,不然程序會出錯。
6)在循環塊里,用recv函數來接收數據,recv函數用法如下:
| INTEGER recv (s : INTEGER, buffer:Array[0..4095] of byte, length : INTEGER, flags : INTEGER, ); |
7)在buffer里就是我們接收回來的數據了,如果我們想要知道數據是什么地方發來的,那么,我們要定義一定IP包結構,用CopyMemory()把IP信息從buffer里面讀出來就可以了,不過讀出來的是十六進制的數據需要轉換一下。
看了封包捕獲的全過程序,對你是不是有點起發,然而在這里要告訴大家的是封包的獲得是很容易的,但是許多游戲的封包都是加密的,如果你想搞清楚所得到的是什么內容還需要自己進行封包解密。
