一隅

手握三尺青锋,平尽天下不平事

ICMP隧道穿透

0x01 什么是ICMP
ICMP:Internet控制报文协议。由于IP协议并不是一个可靠的协议,它不保证数据被成功送达,那么,如何才能保证数据的可靠送达呢? 这里就需要使用到一个重要的协议模块ICMP(网络控制报文)协议。它传递差错报文以及其他需要注意的信息,经常供IP层或更高层协议(TCP或UDP)使用。所以它经常被认为是IP层的一个组成部分
是IPv4协议族中的一个子协议,用于IP主机、路由器之间传递控制消息。控制消息是在网络通不通、主机是否可达、路由是否可用等网络本身的消息。这些控制消息虽然不传输用户数据,但是对于用户数据的传递起着重要的作用

在IP数据报文中的封装如下:

不同类型的报文是由类型字段和代码字段来共同决定:

ping 使用的是ICMP协议,它发送icmp回送请求消息给目的主机。ICMP协议规定:目的主机必须返回ICMP回送应答消息给源主机。如果源主机在一定时间内收到应答,则认为主机可达。大多数的 TCP/IP 实现都在内核中直接支持Ping服务器

ping的原理是用类型码为0的ICMP发请求,受到请求的主机则用类型码为8的ICMP回应。通过计算ICMP应答报文数量和与接受与发送报文之间的时间差,判断当前的网络状态

ICMP回显请求和回显应答报文如下图所示:

0x02 ICMP隧道穿透原理

原理:

请求端的Ping工具通常会在ICMP数据包后面附加上一段随机的数据作为Payload,而响应端则会拷贝这段Payload到ICMP响应数据包中返还给请求端,用于识别和匹配Ping请求,最后一个Payload字段是可以存放任何数据的,这样就可以把IP包被封装到ICMP里(IP包被封装到ICMP里之后,体积肯定会变大,如果超出网络的MTU,内核就会用两个IP包来装)

ICMP echo请求包数据结构:

协议中共有6个字段,所以只要按字段固定长度及对应顺序填充即可
{
    "type": 8,
    "code": 0,
    "checksum": 0,
    "id": 10086,
    "seq": 1,
    "data": "hello world"
}

python构造发送ICMP包:

import random
import socket

def int_to_binary(num):
    if num >= 256:
        binary = "{:0>16b}".format(num)
        return [binary[:8], binary[8:]]
    else:
        return ["{:0>8b}".format(num)]


def int_shift(num, ensure_odd=False):
    result = [int(x, base=2) for x in int_to_binary(num)]
    if ensure_odd and len(result) == 1:
        result = [0, result[0]]
    return result


def send_ip_packet(host, port, payload):
    s = socket.socket(socket.AF_INET, socket.SOCK_RAW, socket.IPPROTO_ICMP)
    s.connect((host, port))
    return s.send(payload)

class ICMP(object):
    def __init__(self, _type, code, _id, seq, data):
        self._type = _type
        self.code = code
        self._id = _id
        self.seq = seq
        self.data = data
        self.checksum = 0
        self.packet = self.assemble_packet()
        self.binary = ['{:0>8}'.format(format(c, 'b'))
                       for c in self.packet]
        self.raw = ''.join(self.binary)

    def assemble_packet(self):
        packet = [self._type, self.code, self.checksum, self.checksum]
        for x in (self._id, self.seq):
            packet.extend(int_shift(x, ensure_odd=True))
        packet.extend([ord(c) for c in self.data])
        self.checksum = self.genernate_checksum(packet)
        checksum_shift = int_shift(self.checksum, ensure_odd=True)
        packet[2], packet[3] = checksum_shift[1], checksum_shift[0]
        return packet

    def genernate_checksum(self, packet):
        last = 0
        for i in range(len(packet) // 2):
            last += (packet[i*2+1] << 8) + packet[i*2]
        if len(packet) % 2:
            last += packet[-1]
        while (last >> 16):
            last = (last & 0xffff) + (last >> 16)
        return ~last & 0xffff

    def __str__(self):
        return ''.join([chr(x) for x in self.packet])

    def __repr__(self):
        return '{}(id={}, seq={}, data="{}", checksum={})'.format(
            self.__class__.__name__, self._id, self.seq,
            self.data, self.checksum,
        )

class Echo(ICMP):
    TYPE = 8
    CODE = 0

    def __init__(self, _id=None, seq=None, data=''):
        _id = _id or random.randint(0, 0xffff)
        seq = seq or random.randint(0, 0xffff)
        super(Echo, self).__init__(self.TYPE, self.CODE, _id, seq, data)


e = Echo(10086, 1, data="hello world")
print(repr(e))
print(send_ip_packet('10.10.10.10', 1, str(e)))

ICMP Keepalive

客户端通过发送一个ICMP包到服务器可以在NAT上创建一条NAT记录,但这个记录是有时效的。为了维持这个时效,需要客户端向服务器(也可能同时需要服务器向客户端)定时或不定时发送Keepalive包
0x03 利用

工具:icmptunnel

两台机器间,除了允许相互ping,其他的tcp/udp端口一律不允许,可利用icmp隧道进行穿透

参考:
[1] http://blog.csdn.net/u011784495/article/details/71743516
[2] https://www.s0nnet.com/archives/icmp-ping
[3] https://xiaix.me/li-yong-icmp-sui-dao-chuan-tou-fang-huo-qiang/
[4] https://oing9179.github.io/blog/2017/06/The-ICMP-Tunnel/
[5] https://github.com/leohowell/python-bullet/blob/master/networking/icmp_echo.py

Categories:  Apt 

« AutoSploit的简单分析 LiteHTTP Botnet 分析 »