《C++黑客编程解密》02
作者:互联网
网络编程
TCP 服务端函数:
- socket()
- bind()
- listen()
- accept()
- send() / recv()
- closesocket()
TCP 客户端函数:
- socket()
- connect()
- send() / recv()
- closesocket()
UDP 服务端:
- socket()
- bind()
- sendto() / recvfrom()
- closesocket()
UDP 客户端:
- socket()
- sendto() / recvfrom()
- closesocket()
winsock 常用函数
1. Winsock 初始化与释放
使用 Winsock 函数前要初始化,使用后要释放。
// 初始化,返回0成功
int PASCAL FAR WSAStartup(
_In_ WORD wVersionRequired, // 需要初始化 Winsock 库的版本号,例如 2.2
_Out_ LPWSADATA lpWSAData // 一个指向 WSADATA 的指针
);
// 释放
int PASCAL FAR WSACleanup(void);
示例:
#include <Windows.h>
int main(int argc, char* argv[])
{
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();
return 0;
}
启动时可能保存:error LNK2019: 无法解析的外部符号 WSAStartup,函数 main 中引用了该符号
解决方法:向 项目的属性页 -> 链接器 -> 输入 -> 附加依赖项
添加 WS2_32.lib
Psapi.lib
2. 套接字的创建与关闭
// 使用 SOCKET_STREAM 时第三个参数可默认为0,默认使用 IPPROTO_TCP
// 使用 SOCKET_DGRAM 时第三个参数可默认为0,默认使用 IPPROTO_UDP
// 成功返回套接字,失败返回 INVALID_SOCKET,调用 WSAGetLastError() 获得错误码
SOCKET socket(
int af, // PF_INET(首选) AF_INET 地址族和协议组二者windows下相等,linux下不同
int type, // SOCKET_STREAM(流套接字) SOCKET_DGRAM(数据包套接字) SOCKET_RAW(原始协议接口)
int protocol // 指定通信协议,IPPROTO_TCP IPPROTO_UDP IPPROTO_ICMP 等。
);
int closesocket(SOCKET s);
3. 面向连接协议的函数
绑定地址和端口:
int PASCAL FAR bind (
_In_ SOCKET s,
_In_reads_bytes_(namelen) const struct sockaddr FAR *addr,
_In_ int namelen
);
// 此结构用于不同协议间兼容,调用函数时将其他结构体转型为这个类型
struct sockaddr {
u_short sa_family; /* address family */
char sa_data[14]; /* up to 14 bytes of direct address */
};
// 具体使用的地址
struct sockaddr_in {
short sin_family;
u_short sin_port; // port 大端方式存储
struct in_addr sin_addr; // ip
char sin_zero[8];
};
typedef struct in_addr {
union {
struct { UCHAR s_b1,s_b2,s_b3,s_b4; } S_un_b;
struct { USHORT s_w1,s_w2; } S_un_w;
ULONG S_addr;
} S_un;
#define s_addr S_un.S_addr /* can be used for most tcp & ip code */
#define s_host S_un.S_un_b.s_b2 // host on imp
#define s_net S_un.S_un_b.s_b1 // network
#define s_imp S_un.S_un_w.s_w2 // imp
#define s_impno S_un.S_un_b.s_b4 // imp #
#define s_lh S_un.S_un_b.s_b3 // logical host
} IN_ADDR, *PIN_ADDR, FAR *LPIN_ADDR;
// in_addr 没有使用点分十进制保存IP,需要用转换函数
// 点分十进制 转为 unsigned long
unsigned long PASCAL FAR inet_addr (_In_z_ const char FAR * cp);
// 前一个函数的逆函数
char FAR * PASCAL FAR inet_ntoa (_In_ struct in_addr in);
// winsock 中大小端转换
// 主机字节序转为网络字节序
u_short PASCAL FAR htons (_In_ u_short hostshort);
u_long PASCAL FAR htonl ( _In_ u_long hostlong);
// 网络字节序转为主机字节序
u_short PASCAL FAR ntohs (_In_ u_short netshort);
u_long PASCAL FAR ntohl (_In_ u_long netlong);
bind函数使用示例:
SOCKET sLisent = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
struct sockaddr_in ServerAddr;
ServerAddr.sin_family = AF_INET;
ServerAddr.sin_addr.S_un.S_addr = inet_addr("0.0.0.0");
// ServerAddr.sin_addr.S_un.S_addr = INADDR_ANY; 表示 任意地址/所有地址
ServerAddr.sin_port = htons(1234);
bind(sLisent, (SOCKADDR *)&ServerAddr, sizeof(ServerAddr));
用于连接的函数:
int PASCAL FAR listen (
_In_ SOCKET s,
_In_ int backlog // 连接队列长度 最大值为 #define SOMAXCONN 0x7fffffff
);
SOCKET PASCAL FAR accept (
_In_ SOCKET s,
_Out_writes_bytes_opt_(*addrlen) struct sockaddr FAR *addr, // 指向sockaddr的指针,返回客户端地址
_Inout_opt_ int FAR *addrlen // 传入结构体大小
);
int PASCAL FAR connect (
_In_ SOCKET s,
_In_reads_bytes_(namelen) const struct sockaddr FAR *name, // 连接目标的地址
_In_ int namelen
);
int PASCAL FAR send (
_In_ SOCKET s,
_In_reads_bytes_(len) const char FAR * buf,
_In_ int len,
_In_ int flags // 通常为 0
);
int PASCAL FAR recv (
_In_ SOCKET s,
_Out_writes_bytes_to_(len, return) __out_data_source(NETWORK) char FAR * buf,
_In_ int len,
_In_ int flags
);
4. 非面向连接协议的函数
int PASCAL FAR sendto (
_In_ SOCKET s,
_In_reads_bytes_(len) const char FAR * buf,
_In_ int len,
_In_ int flags, // 一般为0
_In_reads_bytes_opt_(tolen) const struct sockaddr FAR *to, // 目标地址
_In_ int tolen); // 前者长度
int PASCAL FAR recvfrom (
_In_ SOCKET s,
_Out_writes_bytes_to_(len, return) __out_data_source(NETWORK) char FAR * buf,
_In_ int len,
_In_ int flags,
_Out_writes_bytes_to_opt_(*fromlen, *fromlen) struct sockaddr FAR * from,
_Inout_opt_ int FAR * fromlen);
TCP 示例:
服务端:
#include <Windows.h>
#include <iostream>
#include <stdio.h>
int main(int argc, char* argv[])
{
WSADATA wsaData;
WSAStartup(MAKEWORD(2, 2), &wsaData);
SOCKET sLisent = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
SOCKADDR_IN ServerAddr;
ServerAddr.sin_family = AF_INET;
ServerAddr.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
ServerAddr.sin_port = htons(1234);
bind(sLisent, (SOCKADDR*) & ServerAddr, sizeof(ServerAddr));
listen(sLisent, SOMAXCONN);
SOCKADDR_IN ClientAddr;
int nSize = sizeof(ClientAddr);
SOCKET sClient = accept(sLisent, (SOCKADDR*) & ClientAddr, &nSize);
printf("client ip = %s:%d", inet_ntoa(ClientAddr.sin_addr), ntohs(ClientAddr.sin_port));
char szMsg[MAXBYTE] = { 0 };
strcpy(szMsg, "hello");
send(sClient, szMsg, sizeof(szMsg), 0);
recv(sClient, szMsg, sizeof(szMsg), 0);
WSACleanup();
return 0;
}
客户端:
#include <Windows.h>
#include <iostream>
#include <stdio.h>
int main(int argc, char* argv[])
{
WSADATA wsaData;
WSAStartup(MAKEWORD(2, 2), &wsaData);
SOCKET sServer = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
SOCKADDR_IN ServerAddr;
ServerAddr.sin_family = AF_INET;
ServerAddr.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
ServerAddr.sin_port = htons(1234);
connect(sServer, (SOCKADDR*)&ServerAddr, sizeof(ServerAddr));
char szMsg[MAXBYTE] = { 0 };
recv(sServer, szMsg, sizeof(szMsg), 0);
send(sServer, szMsg, sizeof(szMsg) + sizeof(char), 0);
WSACleanup();
return 0;
}
UCP 示例:
服务端:
#include <Windows.h>
#include <iostream>
#include <stdio.h>
int main(int argc, char* argv[])
{
WSADATA wsaData;
WSAStartup(MAKEWORD(2, 2), &wsaData);
SOCKET sServer = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
SOCKADDR_IN ServerAddr;
ServerAddr.sin_family = AF_INET;
ServerAddr.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
ServerAddr.sin_port = htons(1234);
bind(sServer, (SOCKADDR*)&ServerAddr, sizeof(ServerAddr));
char szMsg[MAXBYTE] = { 0 };
SOCKADDR_IN ClientAddr;
int nSize = sizeof(ClientAddr);
recvfrom(sServer, szMsg, MAXBYTE, 0, &ClientAddr, &nSize);
sendto(sServer, szMsg, strlen(szMsg) + sizeof(char), 0, (SOCKADDR*) & ClientAddr, nSize);
WSACleanup();
return 0;
}
客户端:
#include <Windows.h>
#include <iostream>
#include <stdio.h>
int main(int argc, char* argv[])
{
WSADATA wsaData;
WSAStartup(MAKEWORD(2, 2), &wsaData);
SOCKET sClient = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
SOCKADDR_IN ServerAddr;
ServerAddr.sin_family = AF_INET;
ServerAddr.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
ServerAddr.sin_port = htons(1234);
bind(sClient, (SOCKADDR*)&ServerAddr, sizeof(ServerAddr));
char szMsg[MAXBYTE] = { 0 };
strcpy(szMsg, "hello");
int nSize = sizeof(ServerAddr);
sendto(sClient, szMsg, strlen(szMsg) + sizeof(char), 0, (SOCKADDR*)&ServerAddr, nSize);
nSize = sizeof(ServerAddr);
recvfrom(sClient, szMsg, MAXBYTE, 0, &ServerAddr, &nSize);
WSACleanup();
return 0;
}
字节顺序
大端方式:内存高位地址放地位数据
小端方式:内存高位地址放高位数据
主机字节序由cpu决定,TCP/IP 协议规定使用大端方式。
// winsock 中大小端转换,内存中字节序改变,windows中数值也改变
// 主机字节序转为网络字节序
u_short PASCAL FAR htons (_In_ u_short hostshort);
u_long PASCAL FAR htonl ( _In_ u_long hostlong);
// 网络字节序转为主机字节序
u_short PASCAL FAR ntohs (_In_ u_short netshort);
u_long PASCAL FAR ntohl (_In_ u_long netlong);
判断主机字节序
方法一:
将 WORD 转为 BYTE类型,比较是等于低字节还是高字节
#include <stdio.h>
int main()
{
DWORD dwSmallNum = 0x01020304;
if (*(BYTE*)&dwSmallNum == 0x04) {
printf("small sequence");
} else {
printf("big sequence");
}
}
方法二:
使用 windows 的字节序转换函数
int main()
{
DWORD dwSmallNum = 0x01020304;
if (dwSmallNum == htonl(dwSmallNum)) {
printf("big sequence");
} else {
printf("small sequence");
}
}
非阻塞模式
阻塞模式下Winsock会将线程阻塞,非阻塞模式的Winsock不会发生等待情况。在异步模式下,当一个函数执行后会立刻返回,即使时操作没有完成也会返回;当函数执行完成时,会以某种方式通知应用程序。
设置WInsock工作模式:
// 该函数将套接字绑定到一个窗口,当socket有网络事件发生时,向绑定窗口发送响应消息
int PASCAL FAR WSAAsyncSelect(
_In_ SOCKET s, // 要改为非阻塞模式的套接字
_In_ HWND hWnd, // 发生网络事件时接收消息的窗口
_In_ u_int wMsg, // 发送的自定义消息,比如 WM_USER+1
_In_ long lEvent // 通知码
);
/*
常用通知码:
FD_READ 收到数据包
FD_ACCEPT 监听中套接字有连接请求
FD_CONNECT 成功连接到对方
FD_CLOSE 连接被关闭
*/
原始套接字开发
当socket()函数第2个参数为 SOCK_RAW
时使用的是原始套接字类型,第3个参数可取 IPPROTO_IP
IPPROTO_ICMP
IPPROTO_TCP
IPPROTO
IPPROTO_RAW
。使用前4种类型,当发送数据时系统自动添加上IP首部并设置IP首部的上层协议字段(如果有 IP_HDRINCL 选项则不自动添加IP首部);当接收到数据时,系统不会讲IP首部移除,需要自行处理。如果使用 IPPROTO_RAW ,那么系统讲数据包直接送到网络层发送数据,并要程序自己构造IP首部字段。
Ping 命令构造:
依赖ICMP协议,ICMP位于IP协议之上。
ICMP协议的类型码和代码根据不同情况取不同值。Ping命令类型码用到两个值0和8,代码取值都为0。
类型码为0,代码为0,表示回显应答;
类型码为8,代码为0,表示请求回显。
自定义 IMCP 数据结构如下:
struct icmp_header
{
unsigned char icmp_type; // 消息类型
unsigned char icmp_code; // 代码
unsigned short icmp_checksum; // 校验和
unsigned short icmp_id; // 标识此请求的ID,通常用进程ID
unsigned short icmp_sequence; // 序列号
unsigned long icmp_timestamp; // 时间戳
};
例子:
icmp
#include <Windows.h>
#include <stdio.h>
// 不加此行会报错
#pragma comment(lib,"ws2_32.lib")
#define ICMP_HEADER_SIZE sizeof(icmp_header)
#define ICMP_ECHO_REQUEST 0x08
#define ICMP_ECHO_REPLY 0x00
struct icmp_header
{
unsigned char icmp_type; // 消息类型
unsigned char icmp_code; // 代码
unsigned short icmp_checksum; // 校验和
unsigned short icmp_id; // 标识此请求的ID,通常用进程ID
unsigned short icmp_sequence; // 序列号
unsigned long icmp_timestamp; // 时间戳
};
// 计算校验和
unsigned short chsum(struct icmp_header* picmp, int len)
{
long sum = 0;
unsigned short* pusicmp = (unsigned short*)picmp;
while (len > 1)
{
sum += *(pusicmp++);
if (sum & 0x80000000)
{
sum = (sum & 0xffff) + (sum >> 16);
}
len -= 2;
}
if (len)
{
sum += (unsigned short)*(unsigned char*)pusicmp;
}
while (sum >> 16)
{
sum = (sum & 0xffff) + (sum >> 16);
}
return (unsigned short)~sum;
}
BOOL MyPing(char* szDestIp)
{
BOOL bRet = TRUE;
WSADATA wsaData;
int nTimeOut = 1000;
char szBuff[ICMP_HEADER_SIZE + 32] = { 0 };
icmp_header* pIcmp = (icmp_header*)szBuff;
char icmp_data[32] = { 0 };
WSAStartup(MAKEWORD(2, 2), &wsaData);
SOCKET s = socket(PF_INET, SOCK_RAW, IPPROTO_ICMP);
// 设置超时
setsockopt(s, SOL_SOCKET, SO_RCVTIMEO, (char const*)&nTimeOut, sizeof(nTimeOut));
// 设置目的地
sockaddr_in dest_addr;
dest_addr.sin_family = AF_INET;
dest_addr.sin_addr.S_un.S_addr = inet_addr(szDestIp);
dest_addr.sin_port = htons(0);
// 构造ICMP封包
pIcmp->icmp_type = ICMP_ECHO_REQUEST;
pIcmp->icmp_code = 0;
pIcmp->icmp_id = GetCurrentProcessId();
pIcmp->icmp_sequence = 0;
pIcmp->icmp_timestamp = 0;
pIcmp->icmp_checksum = 0;
// 拷贝数据,这里数据可以任意
memcpy((szBuff + ICMP_HEADER_SIZE), "abcoerijeriiiiiiiisdijfasrfnjedr", 32);
// 计算校验和
pIcmp->icmp_checksum = chsum((struct icmp_header*)szBuff, sizeof(szBuff));
sockaddr_in from_addr;
char szRecvBuff[1024];
int nLen = sizeof(from_addr);
sendto(s, szBuff, sizeof(szBuff), 0, (SOCKADDR*)&dest_addr, sizeof(SOCKADDR));
recvfrom(s, szRecvBuff, MAXBYTE, 0, (SOCKADDR*)&from_addr, &nLen);
if (lstrcmp(inet_ntoa(from_addr.sin_addr), szDestIp))
{
bRet = FALSE;
}
else
{
struct icmp_header* pIcmp1 = (icmp_header*)(szRecvBuff + 20);
printf("%s\n", inet_ntoa(from_addr.sin_addr));
}
return bRet;
}
int main()
{
MyPing((char *)"127.0.0.1");
}
标签:02,addr,int,C++,char,FAR,黑客,ServerAddr,icmp 来源: https://www.cnblogs.com/zhh567/p/16532859.html