最近给自己的c2编写命令控制、文件传输模块,需要使用到socket编程
在这做一下socket简单使用的总结
0x00 头文件及其主要函数、结构体
结构体
struct sockaddr_in {sa_family_t sin_family;in_port_t sin_port;struct in_addr sin_addr;unsinged char sin_zero[8];};
| 类型 struct sockaddr_in | 成员名 | 用于保存网络地址,使用时需要被转换为sockaddr |
|---|---|---|
| sa_family_t | sin_family | 定义在sys/socket.h中 |
| in_port_t (int16_t) | sin_port | 16位整数保存端口 |
| struct in_addr (int32_t) | sin_addr | 以32位整数保存IP地址 |
| unsigned char | sin_zero[8] | 填充至sockaddr大小 |
- getsockopt()
- setsockopt()
- 获取、设置套接字属性,比如超时时间、调试信息、socket类型、缓存大小等等
- htonl()、ntohl()、htons()、ntohs()
- 用于网络字节序和主机字节序的转换
h->host,n->network,l->long,s->short
- 宏定义
- 用于connect()、sendmsg()、sendto()
- INADDR_ANY:本地地址
- INADDR_BROADCAST
- 用于connect()、sendmsg()、sendto()
承接<netinet/in.h>和<inttypes.h>
- htonl()、ntohl()、htons()、ntohs()等等在
<netinet/in.h>中的函数、类型 - uint32_t、uint16_t等在
<inttypes.h>的类型
重要函数
| 返回值类型 | 名称 | 参数 | 作用 |
|---|---|---|---|
| in_addr_t | inet_addr | const char *cp | 将字符串形式的IP4地址转为32位整数(网络序) |
| in_addr_t | inet_network | const char *cp | 同上(返回主机序,较特殊) |
| char * | inet_ntoa | struct in_addr in | 将网络字节序32位整数转为字符串形式 |
| in_addr_t | inet_netof | struct in_addr in | 返回网络地址(主机序) |
| in_addr_t | inet_lnaof | struct in_addr in | 返回主机地址(主机序) |
| struct in_addr | inet_makeaddr | in_addr_t net, in_addr_t lna | 拼接主机地址和网络地址(网络序) |
struct in_addr中的sin_addr我们默认已经转为网络序
结构体
struct sockaddr {short int sa_family;char sa_data[14];};
| struct sockaddr | ||
|---|---|---|
| sa_family_t (short int) | sa_family | 协议簇(IPv6、IPv4) |
| char | sa_data[14] | socket地址 |
函数
| 返回值 | 函数名 | 参数 | 作用 |
|---|---|---|---|
| int | socket | int domain, int type, int protocol |
创建socket标识符 |
| int | accept | int socket, struct sockaddr _address, socklen_t _address_len |
接受来此connect的连接返回一个新的socket标识符 |
| int | bind | int socket, const struct sockaddr_ address, socklen_t _address_len |
将socket标识符同IP地址绑定 |
| int | connect | int socket, const struct sockaddr *address, socklen_t address_len |
客户端发送服务请求,连接指定的Ip:port |
| int | listen | int socket, int backlog |
监听来自外部的连接 |
| ssize_t | recv | int socket, void *buffer, size_t length, int flags |
从socket标识符指定的连接接受数据 |
| ssize_t | send | int socket, void *message, size_t length, int flags |
向socket标识符指定的连接发送数据 |
| ssize_t | sendto | int sockfd, const void_ buf, size_t len, int flags const struct sockaddr _dest_addr, socklen_t addrlen |
向指定目的地发送数据,主要用于UDP数据报 |
| ssize_t | recvfrom | int sockfd, void _buf, size_t len, int flags, struct sockaddr _src_addr, socklen_t *addrlen |
接收数据,主要用于UDP数据报 |
除去上面的函数,在client(客户端)还可能会用到read和write
| 返回值 | 函数名 | 参数 | 作用 |
|---|---|---|---|
| ssize_t | write | int fd, const void *buf, size_t nbytes |
向描述符中写入内容 |
| ssize_t | read | int fd, void *buf, size_t nbyte |
从描述符中读出内容 |
| int | close | int fd | 关闭描述符 |
- write和send、read和recv之间的差距就在flags,推荐直接阅读manual
- send和sendto之间区别
下面展示一个最基础的C&S结构
0x01 Server
固定步骤:
获取socket标识符->填充sockaddr->bind绑定IP->listen监听端口->
accept接受请求->使用send/recv进行交互
#include <stdio.h>#include <string.h>#include <sys/types.h>#include <netinet/in.h>#include <arpa/inet.h>#include <sys/socket.h>#define LISTEN_NUM 3/* 套接字描述符 */int sock_fd;/* 全局网络地址结构体 */struct sockaddr_in g_adr;int main(int argc, char *argv[]){int errno = 0;/* 获取套接字 */if ((sock_fd = socket(AF_INET, SOCK_STREAM, 0)) < 0) {printf("[-] Get socket failed\n");errno = -1;goto err;}printf("[+] Get socket descriptor %d successfully\n", sock_fd);bzero((void *)&g_adr, sizeof(g_adr));/* 填充网络地址结构体 */g_adr.sin_family = AF_INET;g_adr.sin_port = htons(atoi(argv[1]));g_adr.sin_addr.s_addr = INADDR_ANY;if (bind(sock_fd, (struct sockaddr*)(&g_adr),sizeof(g_adr)) == -1) {printf("[-] Bind failed\n");errno = -2;goto err;}printf("[+] Bind successfully\n");/* 开始监听,设置监听数量 */if (listen(sock_fd, LISTEN_NUM) < 0) {printf("[-] Listen failed\n");errno = -3;goto err;}printf("[+] Listen successfully\n");/* 在这个无限循环中, 我们会使用accept接受来自客户端的请求 */char buffer[1024];while (1) {memset(buffer, 0, sizeof(buffer));int req_fd;struct sockaddr_in req_adr;socklen_t len = sizeof(struct sockaddr_in);bzero(&req_adr, sizeof(struct sockaddr_in));/* 用req_fd作为本次连接的socket标识符,req_adr来获取请求网络地址端口等 */req_fd = accept(sock_fd, (struct sockaddr*)(&req_adr), &len);if (req_fd < 0) {printf("[-] Accept failed\n");errno = -4;goto err;}printf("[+] Accept successfully\n\n");printf("[+] Ip: %s\n[+] Port: %u\n\n", inet_ntoa(req_adr.sin_addr), ntohs(req_adr.sin_port));/* 接收数据 */recv(req_fd, buffer, sizeof(buffer), 0);printf("[!] DATA: %s\n", buffer);/* 发送数据 */send(req_fd, buffer, sizeof(buffer), 0);close(req_fd);}err:close(sock_fd);return errno;}
0x02 Client
步骤:
获取socket标识符->填充sockaddr->connect连接服务器->read/write读写数据 进行交互
#include <stdio.h>#include <string.h>#include <netinet/in.h>#include <arpa/inet.h>#include <sys/socket.h>#include <sys/types.h>#define USAGE "\Usage:\n\\tclient ip port\n\n\have fun!\n"int sock_fd;struct sockaddr_in src_adr;int main(int argc, char *argv[]){int errno = 0;/* 参数数量检查 */if (argc != 3) {printf(USAGE);errno = -1;goto err;}/* 获取socket标识符 */if ((sock_fd = socket(AF_INET, SOCK_STREAM, 0)) < 0) {printf("[!] can't get a socket fd");errno = -2;goto err;}bzero((void *)&src_adr, sizeof(src_adr));/* 为了方便观看,这没有做容错处理 *//* 填充sockaddr_in结构体,即:设置协议、ip、端口 */src_adr.sin_family = AF_INET;src_adr.sin_port = htons(atoi(argv[2])); // 这里注意端口是short int,如果误用htonl,则可能被转换为网络序时连不上src_adr.sin_addr.s_addr = inet_addr(argv[1]);/* 发送请求,尝试建立连接*/int ret = connect(sock_fd,(struct sockaddr *)&src_adr, sizeof(struct sockaddr));if (ret < 0) {perror("[!] error: socket error\n");errno = -3;goto err;}/* 发送数据 */char data[20] = "hello shawn!\n";ssize_t write_num = write(sock_fd, (void *)data, strlen(data));printf("[+] Send data finished\n");err:close(sock_fd);return errno;}
0x03 C2模块化
socket模块化使用,粘包解决方式
下面是c2中M4ster(服务端)的函数调用草图,后续会持续优化。
其中命令执行是所有模块的基础,这里主要分析命令执行和文件传输。

- 文件传输借鉴了ftp协议的实现方式,除了原先命令执行的指令通道1337外,在传输数据的时候还会动态打开4779用户文件传输
- 主动模式
- C->S
- 被动模式
- S->C
0x04 Summary
- 自己实现一遍,是快速上手的要点,哪怕抄一遍
- 后期推荐学习使用boost库,对coder来说,除了熟悉原理以外,productivity更是一个要下大功夫的提高点
Reference
[0] Structures and Types used by the Sockets Library
