最近给自己的c2编写命令控制、文件传输模块,需要使用到socket编程

在这做一下socket简单使用的总结

0x00 头文件及其主要函数、结构体

结构体

  1. struct sockaddr_in {
  2. sa_family_t sin_family;
  3. in_port_t sin_port;
  4. struct in_addr sin_addr;
  5. unsinged char sin_zero[8];
  6. };
类型 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

承接<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我们默认已经转为网络序

结构体

  1. struct sockaddr {
  2. short int sa_family;
  3. char sa_data[14];
  4. };
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 关闭描述符

下面展示一个最基础的C&S结构

0x01 Server

固定步骤:

获取socket标识符->填充sockaddr->bind绑定IP->listen监听端口->

accept接受请求->使用send/recv进行交互

  1. #include <stdio.h>
  2. #include <string.h>
  3. #include <sys/types.h>
  4. #include <netinet/in.h>
  5. #include <arpa/inet.h>
  6. #include <sys/socket.h>
  7. #define LISTEN_NUM 3
  8. /* 套接字描述符 */
  9. int sock_fd;
  10. /* 全局网络地址结构体 */
  11. struct sockaddr_in g_adr;
  12. int main(int argc, char *argv[])
  13. {
  14. int errno = 0;
  15. /* 获取套接字 */
  16. if ((sock_fd = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
  17. printf("[-] Get socket failed\n");
  18. errno = -1;
  19. goto err;
  20. }
  21. printf("[+] Get socket descriptor %d successfully\n", sock_fd);
  22. bzero((void *)&g_adr, sizeof(g_adr));
  23. /* 填充网络地址结构体 */
  24. g_adr.sin_family = AF_INET;
  25. g_adr.sin_port = htons(atoi(argv[1]));
  26. g_adr.sin_addr.s_addr = INADDR_ANY;
  27. if (bind(sock_fd, (struct sockaddr*)(&g_adr),
  28. sizeof(g_adr)) == -1) {
  29. printf("[-] Bind failed\n");
  30. errno = -2;
  31. goto err;
  32. }
  33. printf("[+] Bind successfully\n");
  34. /* 开始监听,设置监听数量 */
  35. if (listen(sock_fd, LISTEN_NUM) < 0) {
  36. printf("[-] Listen failed\n");
  37. errno = -3;
  38. goto err;
  39. }
  40. printf("[+] Listen successfully\n");
  41. /* 在这个无限循环中, 我们会使用accept接受来自客户端的请求 */
  42. char buffer[1024];
  43. while (1) {
  44. memset(buffer, 0, sizeof(buffer));
  45. int req_fd;
  46. struct sockaddr_in req_adr;
  47. socklen_t len = sizeof(struct sockaddr_in);
  48. bzero(&req_adr, sizeof(struct sockaddr_in));
  49. /* 用req_fd作为本次连接的socket标识符,req_adr来获取请求网络地址端口等 */
  50. req_fd = accept(sock_fd, (struct sockaddr*)(&req_adr), &len);
  51. if (req_fd < 0) {
  52. printf("[-] Accept failed\n");
  53. errno = -4;
  54. goto err;
  55. }
  56. printf("[+] Accept successfully\n\n");
  57. printf("[+] Ip: %s\n[+] Port: %u\n\n", inet_ntoa(req_adr.sin_addr), ntohs(req_adr.sin_port));
  58. /* 接收数据 */
  59. recv(req_fd, buffer, sizeof(buffer), 0);
  60. printf("[!] DATA: %s\n", buffer);
  61. /* 发送数据 */
  62. send(req_fd, buffer, sizeof(buffer), 0);
  63. close(req_fd);
  64. }
  65. err:
  66. close(sock_fd);
  67. return errno;
  68. }

0x02 Client

步骤:

获取socket标识符->填充sockaddr->connect连接服务器->read/write读写数据 进行交互

  1. #include <stdio.h>
  2. #include <string.h>
  3. #include <netinet/in.h>
  4. #include <arpa/inet.h>
  5. #include <sys/socket.h>
  6. #include <sys/types.h>
  7. #define USAGE "\
  8. Usage:\n\
  9. \tclient ip port\n\n\
  10. have fun!\n"
  11. int sock_fd;
  12. struct sockaddr_in src_adr;
  13. int main(int argc, char *argv[])
  14. {
  15. int errno = 0;
  16. /* 参数数量检查 */
  17. if (argc != 3) {
  18. printf(USAGE);
  19. errno = -1;
  20. goto err;
  21. }
  22. /* 获取socket标识符 */
  23. if ((sock_fd = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
  24. printf("[!] can't get a socket fd");
  25. errno = -2;
  26. goto err;
  27. }
  28. bzero((void *)&src_adr, sizeof(src_adr));
  29. /* 为了方便观看,这没有做容错处理 */
  30. /* 填充sockaddr_in结构体,即:设置协议、ip、端口 */
  31. src_adr.sin_family = AF_INET;
  32. src_adr.sin_port = htons(atoi(argv[2])); // 这里注意端口是short int,如果误用htonl,则可能被转换为网络序时连不上
  33. src_adr.sin_addr.s_addr = inet_addr(argv[1]);
  34. /* 发送请求,尝试建立连接*/
  35. int ret = connect(sock_fd,
  36. (struct sockaddr *)&src_adr, sizeof(struct sockaddr));
  37. if (ret < 0) {
  38. perror("[!] error: socket error\n");
  39. errno = -3;
  40. goto err;
  41. }
  42. /* 发送数据 */
  43. char data[20] = "hello shawn!\n";
  44. ssize_t write_num = write(sock_fd, (void *)data, strlen(data));
  45. printf("[+] Send data finished\n");
  46. err:
  47. close(sock_fd);
  48. return errno;
  49. }

0x03 C2模块化

socket模块化使用,粘包解决方式

下面是c2中M4ster(服务端)的函数调用草图,后续会持续优化。

其中命令执行是所有模块的基础,这里主要分析命令执行文件传输

1616593485559.png

  • 文件传输借鉴了ftp协议的实现方式,除了原先命令执行的指令通道1337外,在传输数据的时候还会动态打开4779用户文件传输
  • 主动模式
    • C->S
  • 被动模式
    • S->C

0x04 Summary

  1. 自己实现一遍,是快速上手的要点,哪怕抄一遍
  2. 后期推荐学习使用boost库,对coder来说,除了熟悉原理以外,productivity更是一个要下大功夫的提高点

Reference

[0] Structures and Types used by the Sockets Library

[1] Linux c socket编程:简单的客户端(client)和服务端(server)实现

[2] Linux C Socket简单应用

[3] sys/socke.h