最近给自己的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