《C++黑客编程解密》02

2022/8/8 14:22:47

本文主要是介绍《C++黑客编程解密》02,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

网络编程

TCP 服务端函数:

  1. socket()
  2. bind()
  3. listen()
  4. accept()
  5. send() / recv()
  6. closesocket()

TCP 客户端函数:

  1. socket()
  2. connect()
  3. send() / recv()
  4. closesocket()

UDP 服务端:

  1. socket()
  2. bind()
  3. sendto() / recvfrom()
  4. closesocket()

UDP 客户端:

  1. socket()
  2. sendto() / recvfrom()
  3. 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");
}

 



这篇关于《C++黑客编程解密》02的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!


扫一扫关注最新编程教程