別看這是封包這一問題,但是涉及的技術(shù)范圍很廣范,實(shí)現(xiàn)的方式也很多(比如說APIHOOK,VXD,Winsock2都可以實(shí)現(xiàn)),在這里我們不可能每種技術(shù)和方法都涉及,所以我在這里以Winsock2技術(shù)作詳細(xì)講解,就算作拋磚引玉。
由于大多數(shù)讀者對(duì)封包類編程不是很了解,我在這里就簡(jiǎn)單介紹一下相關(guān)知識(shí):
APIHooK:
由于Windows的把內(nèi)核提供的功能都封裝到API里面,所以大家要實(shí)現(xiàn)功能就必須通過API,換句話說就是我們要想捕獲數(shù)據(jù)封包,就必須先要得知道并且捕獲這個(gè)API,從API里面得到封包信息。
VXD:
直接通過控制VXD驅(qū)動(dòng)程序來實(shí)現(xiàn)封包信息的捕獲,不過VXD只能用于win9X。
winsock2:
winsock是Windows網(wǎng)絡(luò)編程接口,winsock工作在應(yīng)用層,它提供與底層傳輸協(xié)議無關(guān)的高層數(shù)據(jù)傳輸編程接口,winsock2是winsock2.0提供的服務(wù)提供者接口,但只能在win2000下用。
好了,我們開始進(jìn)入winsock2封包式編程吧。
在封包編程里面我準(zhǔn)備分兩個(gè)步驟對(duì)大家進(jìn)行講解:1、封包的捕獲,2、封包的發(fā)送。
首先我們要實(shí)現(xiàn)的是封包的捕獲:
Delphi的封裝的winsock是1.0版的,很自然winsock2就用不成。如果要使用winsock2我們要對(duì)winsock2在Delphi里面做一個(gè)接口,才可以使用winsock2。
1、如何做winsock2的接口?
1)我們要先定義winsock2.0所用得到的類型,在這里我們以WSA_DATA類型做示范,大家可以舉一仿三的來實(shí)現(xiàn)winsock2其他類型的封裝。
我們要知道WSA_DATA類型會(huì)被用于WSAStartup(wVersionRequired: word; var WSData: TWSAData): Integer;,大家會(huì)發(fā)現(xiàn)WSData是引用參數(shù),在傳入?yún)?shù)時(shí)傳的是變量的地址,所以我們對(duì)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的函數(shù),在此我們也是以WSAStartup為例做函數(shù)引入:
function WSAStartup(wVersionRequired: word; var WSData: TWSAData): Integer; stdcall; implementation const WinSocket2 = 'WS2_32.DLL'; function WSAStartup; external winsocket name 'WSAStartup'; |
通過以上方法,我們便可以對(duì)winsock2做接口,下面我們就可以用winsock2做封包捕獲了,不過首先要有一塊網(wǎng)卡。因?yàn)樯婕暗秸谶\(yùn)作的網(wǎng)絡(luò)游戲安全問題,所以我們?cè)谶@里以IP數(shù)據(jù)包為例做封包捕獲,如果下面的某些數(shù)據(jù)類型您不是很清楚,請(qǐng)您查閱MSDN:
1)我們要起動(dòng)WSA,這時(shí)個(gè)要用到的WSAStartup函數(shù),用法如下:
INTEGER WSAStartup( wVersionRequired: word, WSData: TWSA_DATA ); |
2)使用socket函數(shù)得到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類型,跟據(jù)我們的網(wǎng)卡IP給Sock_ADDR類型附值,然后我們使用bind函數(shù)來綁定我們的網(wǎng)卡,Bind函數(shù)用法如下:
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'); //這里你自己的網(wǎng)卡的IP地址,而inet_addr這個(gè)函數(shù)是winsock2的函數(shù)。 bind(m_hSocket, LocalAddr, sizeof(LocalAddr)); |
4)用WSAIoctl來注冊(cè)WSA的輸入輸出組件,其用法如下:
INTEGER WSAIoctl(s:INTEGER, dwIoControlCode : INTEGER, lpvInBuffer :INTEGER, cbInBuffer : INTEGER, lpvOutBuffer : INTEGER, cbOutBuffer: INTEGER, lpcbBytesReturned : INTEGER, lpOverlapped : INTEGER, lpCompletionRoutine : INTEGER ); |
5)下面做死循環(huán),在死循環(huán)塊里,來實(shí)現(xiàn)數(shù)據(jù)的接收。但是徇環(huán)中間要用Sleep()做延時(shí),不然程序會(huì)出錯(cuò)。
6)在循環(huán)塊里,用recv函數(shù)來接收數(shù)據(jù),recv函數(shù)用法如下:
INTEGER recv (s : INTEGER, buffer:Array[0..4095] of byte, length : INTEGER, flags : INTEGER, ); |
7)在buffer里就是我們接收回來的數(shù)據(jù)了,如果我們想要知道數(shù)據(jù)是什么地方發(fā)來的,那么,我們要定義一定IP包結(jié)構(gòu),用CopyMemory()把IP信息從buffer里面讀出來就可以了,不過讀出來的是十六進(jìn)制的數(shù)據(jù)需要轉(zhuǎn)換一下。
看了封包捕獲的全過程序,對(duì)你是不是有點(diǎn)起發(fā),然而在這里要告訴大家的是封包的獲得是很容易的,但是許多游戲的封包都是加密的,如果你想搞清楚所得到的是什么內(nèi)容還需要自己進(jìn)行封包解密。