茵蒂克丝
基础概念
IP 地址
IP 是英文 Internet Protocol (网络之间互连的协议)的缩写,也就是为计算机网络相互连接进行通信而设计的协议。任一系统,只要遵守 IP协议就可以与因特网互连互通。
所谓IP地址就是给每个遵循tcp/ip协议连接在Internet上的主机分配的一个32bit地址。按照TCP/IP协议规定,IP地址用二进制来表示,每个IP地址长32bit,比特换算成字节,就是4个字节。为了方便人们的使用,IP地址经常被写成十进制的形式,中间使用符号 “.” 分开不同的字节,如:192.168.1.1。在因特网中,主机的标识便是它ip地址。
常见的获取服务器ip的方法是使用系统自带的ping命令。例,打开cmd输入:
ping www.baidu.com
其中的 115.239.210.27 就是baidu服务器的地址了
服务端与客户端
服务端与客户端在计算机的世界里,凡是提供服务的一方我们称为服务端(Server),而接受服务的另一方我们称作客户端(Client)。
网站提供网页数据的服务,使用者接受网站所提供的数据服务,所以使用者在这里就是客户端,响应使用者要求的网站即称为服务端。
不过客户端及服务端的关系不见得一定建立在两台分开的机器上,同一台机器中也有这种主从关系的存在,提供服务的服务端及接受服务的客户端也有可能都在同一台机器上。
例如我们在提供网页的服务器上执行浏览器浏览本机所提供的网页,这样在同一台机器上就同时扮演服务端及客户端。
Socket
socket的英文原义是“孔”或“插座”。作为4BDS UNIX的进程通信机制,取后一种意思。通常也称作"套接字",用于描述IP地址和端口,是一个通信链的句柄。
Scoket程勋分为服务端与客户端,服务端程序监听端口,等待客户端程序的连接。客户端程序发起连接,等待服务端的相应。客户端想要连接上服务端的话,需要 知道服务端的ip地址。
例如,客户想要访问百度网站的首页,通过浏览器访问http://www.baidu.com。浏览器发出请求之后,先是DNS服务器将百度的域名解析成ip地址之后,访问到ip地址为115.239.210.27服务器的80端口(http协议默认在80端口),请求发送后,百度的服务器作为响应将页面的源代码发送至客户端(通过浏览器右键->源代码,或者ctrl+u可以看到服务器返回的页面源代码),随后浏览器将源代码渲染成页面。这样用户就看到了百度网站的首页。
头文件和库文件
在windows系统上,当前的Windows Socket接口是Windows Sockets 2,所有接口函数都是有Ws2_32.dll导出。在进行程序设计时,祥光的数据类型、结构定义、函数声明等卫浴头文件winsock2.h中,编译时需要包含此文件,并且连接到库Ws2_32.lib。
常用函数
常见的socket函数
WSAStartup初始化Ws2_32.lib库 WSACleanup关闭Ws2_32.lib库 Socket创建socket closesocket关闭socket sockaddr和sockaddr_in结构体socket地址 bind绑定 listen监听 accept接收 connect连接 send发送 recv获取返回WSAStartup () 函数
WSAStartup函数的功能使加载Ws2_32.dll等Socket程序运行的环境。在程序初始化后,Socket程序运行所依赖的动态链接库不一定已经在家,WSAStartup保证了Socket 动态链接库的加载。
1
2
3
4
|
int
WSAStartup(
__in
WORD
wVersionRequested,
__out LPWSADATA lpWSAData
);
|
参数一wVersionRequested
Socket程序库的版本,一般使用MAKEWORD(2,2)宏生成。
参数二lpWSAData
输出参数,指向 WSADATA 结构的指针,用于返回Socket库初始化的信息。
返回值 用来判断程序是否成功加载
WSACleanup () 函数
与 WSAStartup 相反, WSACleanup 释放Ws2_32.dll,函数无参数。
Socket () 函数
Socket函数的功能是建立一个绑定到制定协议和传输类型的Socket。
1
2
3
4
5
|
SOCKET WSAAPI socket(
__in
int
af,
__in
int
type,
__in
int
protocol
);
|
参数一af
address family的缩写,制定该Socket使用哪一种类型的网络地址。可以是IPv4(AF_INET),也可以是IPv6(AF_INET6)、蓝牙(AF_BTM)、NetBios(AF_NETBIOS)等。
参数二type
设置传输类型,常见类型有SOCK_STREAM、SOCK_DGRAM、SOCK_RAM、SOCK_SEQPACKET等。SOCK_STREAM类型是基于连接的(TCP),所收的数据时数据流形式的,传输层的数据包已经经过处理。SOCK_DGRAM是基于报文(UDP)。如果制定为SOCK_RAM,那么可以建立原始套接字,所收到的数据是以数据包(包括包头)的形式存在的。
参数三protocol
设置传输协议,常用的协议为IPPROTO_TCP和IPPROTO_UDP。
colsesocket() 函数
与 socket 函数对应,用于关闭 socket
1
2
3
|
int
WSAAPI closesocket(
__in SOCKET s
);
|
参数一s
指定要关闭的socket
sockaddr和sockaddr_in结构体
sockaddr、SOCKADDR、sockaddr_in、SOCKADDR_IN这 几个结构用于表示地址很端口。在IPv4下,这几个结构体是可以通用的。
1
2
3
4
5
6
7
8
9
10
|
typedef
struct
sockaddr {
#if (_WIN32_WINNT < 0x0600)
u_short sa_family;
#else
ADDRESS_FAMILY sa_family;
// Address family.
#endif //(_WIN32_WINNT < 0x0600)
CHAR
sa_data[14];
// Up to 14 bytes of direct address.
} SOCKADDR, *PSOCKADDR, FAR *LPSOCKADDR;
|
1
2
3
4
5
6
7
8
9
10
11
12
|
typedef
struct
sockaddr_in {
#if(_WIN32_WINNT < 0x0600)
short
sin_family;
#else //(_WIN32_WINNT < 0x0600)
ADDRESS_FAMILY sin_family;
#endif //(_WIN32_WINNT < 0x0600)
USHORT
sin_port;
IN_ADDR sin_addr;
CHAR
sin_zero[8];
} SOCKADDR_IN, *PSOCKADDR_IN;
|
bind() 函数
服务器端,将Socket与网络地址和端口绑定起来,函数原型如下:
1
2
3
4
5
|
int
WSAAPI bind(
__in SOCKET s,
__in_bcount(namelen)
const
struct
sockaddr FAR * name,
__in
int
namelen
);
|
参数一s
要绑定的socket
参数二name
要绑定的网络地址结构体
参数三namelen
name参数所指向的结构体的大小
listen() 函数
设置 socket 的状态为监听,使客户端程序可以进行连接,函数原型:
1
2
3
4
|
int
WSAAPI listen(
__in SOCKET s,
__in
int
backlog
);
|
参数一s
绑定的socket
参数二backlog
指定最大的连接数
accept() 函数
接受客户端的连接
1
2
3
4
5
|
SOCKET WSAAPI accept(
__in SOCKET s,
__out_bcount_opt(*addrlen)
struct
sockaddr FAR * addr,
__inout_opt
int
FAR * addrlen
);
|
参数一s
正在监听的socket,该 socket 使用 listen() 设置。
参数二addr (可选)
指针,指向一缓冲区,其中接收为通讯层所知的连接实体的地址。Addr参数的实际格式由套接口创建时所产生的地址族确定。
参数三addrlen (可选)
指针,指向存有addr地址长度的整形数。
返回:
成功:非负描述字
失败:-1
accept 默认会阻塞进程,直到有客户端建立连接后返回,它返回的是连接用的 socket 。如果 accept 成功返回,则服务器与客户已经正确建立连接了,此时服务器通过 accept 返回的 socket 来完成与客户的通信。
connect() 函数
connect函数的功能使与服务器建立连接。这个函数只能由客户端程序调用。
1
2
3
4
5
|
int
WSAAPI connect(
__in SOCKET s,
__in_bcount(namelen)
const
struct
sockaddr FAR * name,
__in
int
namelen
);
|
参数一s
由socket函数建立的套接字。
参数二name
指向sockaddr结构体指针,包括了所要连接的服务端的地址和端口等。
参数三namelen
sockaddr结构的长度
send() 函数
向连接的另一端发送数据,不论是客户还是服务器应用程序都用send函数来向 TCP 连接的另一端发送数据。
1
2
3
4
5
6
|
int
WSAAPI send(
__in SOCKET s,
__in_bcount(len)
const
char
FAR * buf,
__in
int
len,
__in
int
flags
);
|
参数一s
指定发送端socket描述符。
参数二*buf
指明存放要发送的数据的缓冲区。
参数三len
指明实际要发送的数据的字节数。
参数四flags
指明发送的方法,常见宏MSG_DONTROUTE、MSG_OOB等,一般置0。
将*buf指向的字符串发送至客户端
recv() 函数
不论是客户还是服务器应用程序都用 recv 函数 从 TCP 连接的另一端接收数据。
1
2
3
4
5
6
|
int
WSAAPI recv(
__in SOCKET s,
__out_bcount_part(len,
return
) __out_data_source(NETWORK)
char
FAR * buf,
__in
int
len,
__in
int
flags
);
|
参数一s
指定接收端socket描述符;
参数二*buf
指明一个缓冲区,该缓冲区用来存放recv函数接收到的数据;
参数三len
指明buf的长度;
参数四flags
指明接收的方法,常见宏MSG_DONTROUTE、MSG_OOB等,一般置0。
获取到客户端返回的字符串并将其写入到buf中
简单的 socket 通信示例
服务端(Server)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
|
#include <Winsock2.h>
#include <stdio.h>
#pragma comment(lib, "ws2_32.lib")
void
main()
{
int
err;
// 错误信息
int
len;
char
sendBuf[100];
// 发送至客户端的字符串
char
recvBuf[100];
// 接受客户端返回的字符串
SOCKET sockServer;
// 服务端 Socket
SOCKADDR_IN addrServer;
// 服务端地址
SOCKET sockClient;
// 客户端 Scoket
SOCKADDR_IN addrClient;
// 客户端地址
WSADATA wsaData;
// winsock 结构体
WORD
wVersionRequested;
// winsock 的版本
// 配置 Windows Socket版本
wVersionRequested = MAKEWORD( 2, 2 );
// 初始化 Windows Socket
err = WSAStartup( wVersionRequested, &wsaData );
if
( err != 0 )
{
// 启动错误,程序结束
return
;
}
/*
* 确认WinSock DLL支持2.2
* 请注意如果DLL支持的版本大于2.2至2.2
* 它仍然会返回wVersion2.2的版本
*/
if
( LOBYTE( wsaData.wVersion ) != 2 || HIBYTE( wsaData.wVersion ) != 2 )
{
// 启动错误,程序结束
WSACleanup();
// 终止Winsock 2 DLL (Ws2_32.dll) 的使用
return
;
}
// 定义服务器端socket
sockServer = socket(AF_INET, SOCK_STREAM, 0);
// 设置服务端 socket
addrServer.sin_addr.S_un.S_addr = htonl(INADDR_ANY);
// 本机IP
addrServer.sin_family = AF_INET;
// 协议类型是INET
addrServer.sin_port = htons(6000);
// 绑定端口6000
// 将服务器端socket绑定在本地端口
bind(sockServer, (SOCKADDR *)&addrServer,
sizeof
(SOCKADDR));
// Listen 监听端口
listen(sockServer, 5);
// 5 为等待连接数目
printf
(
"服务器已启动:\n监听中...\n"
);
len =
sizeof
(SOCKADDR);
// accept 会阻塞进程,直到有客户端连接上来为止
sockClient = accept(sockServer, (SOCKADDR *)&addrClient, &len);
// 当客户端连接上来时, 拼接如下字符串
sprintf
(sendBuf,
"欢迎 ip: %s 的用户连接, 这里是 Lellansin 的服务器\n"
, inet_ntoa(addrClient.sin_addr));
// 向客户端发送字符串
send(sockClient, sendBuf,
strlen
(sendBuf) + 1, 0);
// 获取客户端返回的数据
recv(sockClient, recvBuf, 100, 0);
// 打印客户端返回的数据
printf
(
"%s\n"
, recvBuf);
// 关闭socket
closesocket(sockClient);
getchar
();
}
|
启动后防火墙有阻挡提示,这里接受就可以了。另外下图是原来博主测试时截的图,跟上面代码的输出部分稍稍有些区别。
客户端(Client)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
|
#include <Winsock2.h>
#include <stdio.h>
#pragma comment(lib, "ws2_32.lib")
void
main()
{
int
err;
char
*message;
char
recvBuf[100];
SOCKET sockClient;
// 客户端 Scoket
SOCKADDR_IN addrServer;
// 服务端地址
WSADATA wsaData;
WORD
wVersionRequested;
wVersionRequested = MAKEWORD( 2, 2 );
err = WSAStartup( wVersionRequested, &wsaData );
if
( err != 0 )
{
return
;
}
if
( LOBYTE( wsaData.wVersion ) != 2 || HIBYTE( wsaData.wVersion ) != 2 )
{
// 启动错误,程序结束
WSACleanup( );
return
;
}
// 新建客户端 scoket
sockClient = socket(AF_INET, SOCK_STREAM, 0);
// 定义要连接的服务端地址
addrServer.sin_addr.S_un.S_addr = inet_addr(
"127.0.0.1"
);
// 目标IP (127.0.0.1是本机地址)
addrServer.sin_family = AF_INET;
// 协议类型是INET
addrServer.sin_port = htons(6000);
// 连接端口1234
// 让 sockClient 连接到 服务端
connect(sockClient, (SOCKADDR *)&addrServer,
sizeof
(SOCKADDR));
// 从服务端获取数据
recv(sockClient, recvBuf, 100, 0);
// 打印数据
printf
(
"%s\n"
, recvBuf);
message =
"服务端你好~"
;
// 发送数据到服务端
send(sockClient, message,
strlen
(message) + 1, 0);
// 关闭socket
closesocket(sockClient);
WSACleanup();
getchar
();
// 暂停
}
|
客户端与服务端更多通信,例子:《木马,你好!(二)最简单的木马》
简单的端口扫描程序
hostent结构体
host entry的缩写,该结构记录主机的信息,包括主机名、别名、地址类型、地址长度和地址列表。之所以主机的地址是一个列表的形式,原因是当一个主机有多个网络接口时会有多个地址。
1
2
3
4
5
6
7
8
|
struct
hostent {
char
FAR * h_name;
/* 主机正式名称 */
char
FAR * FAR * h_aliases;
/* 主机别名 */
short
h_addrtype;
/* 地址类型 */
short
h_length;
/* 地址长度 */
char
FAR * FAR * h_addr_list;
/* 主机网络地址指针 */
#define h_addr h_addr_list[0] /* 指向第一个地址 */
};
|
代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
|
#include <winsock2.h>
#include <stdio.h>
#include <stdlib.h>
#pragma comment(lib,"ws2_32.lib")
void
Help(
void
);
int
main(
int
argc,
char
*argv[])
{
WORD
wVersion = MAKEWORD(2, 0);
WSADATA wsaData;
//sockaddr_in结构体
struct
sockaddr_in
sin
;
int
iFromPort;
int
iToPort;
int
iNowPort;
char
*cHost;
HOSTENT *host_entry;
char
*host_name;
char
host_address[256];
SOCKET s;
int
iOpenPort = 0;
int
port[256], i;
if
(argc != 4)
{
Help();
return
-1;
}
iFromPort =
atoi
(argv[2]);
iToPort =
atoi
(argv[3]);
host_name = argv[1];
if
(iFromPort > iToPort || iFromPort < 0 || iFromPort > 65535 || iToPort < 0 || iToPort > 65535)
{
printf
(
"起始端口不能大于结束端口,且范围为:1--65535 "
);
return
0;
}
if
( WSAStartup(wVersion , &wsaData) )
{
printf
(
"初始化失败!"
);
return
-1;
}
// 根据用户输入的域名来获取服务器主机信息
host_entry = gethostbyname(host_name);
if
(host_entry != 0)
{
wsprintf( host_address,
"%d.%d.%d.%d"
,
(host_entry->h_addr_list[0][0] & 0x00ff),
(host_entry->h_addr_list[0][1] & 0x00ff),
(host_entry->h_addr_list[0][2] & 0x00ff),
(host_entry->h_addr_list[0][3] & 0x00ff)
);
printf
(
"\n主机名称:%s 主机地址:%s \n"
, host_name, host_address);
}
cHost = host_address;
printf
(
"======= 开始扫描 ======= \n"
);
for
(iNowPort = iFromPort; iNowPort <= iToPort; iNowPort++)
{
s = socket(AF_INET, SOCK_STREAM, 0);
if
(s == INVALID_SOCKET)
{
printf
(
"create socket() failed!"
);
WSACleanup();
}
sin
.sin_family = AF_INET;
sin
.sin_port = htons(iNowPort);
sin
.sin_addr.S_un.S_addr = inet_addr(cHost);
if
(connect(s, (
struct
sockaddr *)&
sin
,
sizeof
(
sin
)) == SOCKET_ERROR)
{
printf
(
"%s -> %d:未开放 \n"
, cHost, iNowPort);
closesocket(s);
}
else
{
printf
(
"%s -> %d:开放 \n"
, cHost, iNowPort);
port[iOpenPort] = iNowPort;
iOpenPort ++;
closesocket(s);
}
}
printf
(
"======= 扫描结果 ======= \n"
);
printf
(
"主机:%s 扫描到%d个端口,分别是: \n"
, host_name, iOpenPort);
for
( i = 0; i < iOpenPort; i++)
{
printf
(
"%d "
, port[i]);
}
//关闭socket
closesocket(s);
WSACleanup();
return
0;
}
//帮助函数
void
Help()
{
printf
(
"Usage: \n"
);
printf
(
"Port.exe <TargetIP> <BeginPort> <EndPort> \n"
);
printf
(
"Example: \n"
);
printf
(
"scan.exe 127.0.0.1 100 200 "
);
}
|