0

I am attempting to work on a project involving ICMP packets. I want to create a straightforward Golang program that would help ICMP Tunneling, a method of passing data over ICMP packets to get around security measures like firewalls. The issue is that I've never been able to make Dial, ListenIP, or ListenPacket's IP/Protocol parsing work. Even if it attempted to run the same code as other people and worked flawlessly for them.

Information:

  • Go version: go version go1.17.5 windows/amd64
  • OS: Windows 10, lastest version
  • IDE: LiteIDE
  • Destination IP: 192.168.1.5 (my PC's local IP)
  • Destination Port: any for now, simply trying out RAW packets.

Here's what I've tried:

How to use Golang to compose raw TCP packet (using gopacket) and send it via raw socket - even if I followed the solution's instructions There was usually an issue processing IP4 and TCP in the network. ListenPacket and ListenIP delivered an error message such as "unexpected type" or "invalid host." I tired using the two following formats: ip:port and ip.

Here is my version of the GitHub code - because many dependencies were missing, I had to modify the code to make it function. Because of this tutorial, I know this code works.

Here is my version of the GitHub code

/*
Copyright 2013-2014 Graham King

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.

For full license details see <http://www.gnu.org/licenses/>.
*/

package main

import (
    "bytes"
    "encoding/binary"
    "log"
    "math/rand"
    "net"
)

const (
    FIN = 1  // 00 0001
    SYN = 2  // 00 0010
    RST = 4  // 00 0100
    PSH = 8  // 00 1000
    ACK = 16 // 01 0000
    URG = 32 // 10 0000
)

type TCPHeader struct {
    Source      uint16
    Destination uint16
    SeqNum      uint32
    AckNum      uint32
    DataOffset  uint8 // 4 bits
    Reserved    uint8 // 3 bits
    ECN         uint8 // 3 bits
    Ctrl        uint8 // 6 bits
    Window      uint16
    Checksum    uint16 // Kernel will set this if it's 0
    Urgent      uint16
    Options     []TCPOption
}

type TCPOption struct {
    Kind   uint8
    Length uint8
    Data   []byte
}

// Parse packet into TCPHeader structure
func NewTCPHeader(data []byte) *TCPHeader {
    var tcp TCPHeader
    r := bytes.NewReader(data)
    binary.Read(r, binary.BigEndian, &tcp.Source)
    binary.Read(r, binary.BigEndian, &tcp.Destination)
    binary.Read(r, binary.BigEndian, &tcp.SeqNum)
    binary.Read(r, binary.BigEndian, &tcp.AckNum)

    var mix uint16
    binary.Read(r, binary.BigEndian, &mix)
    tcp.DataOffset = byte(mix >> 12)  // top 4 bits
    tcp.Reserved = byte(mix >> 9 & 7) // 3 bits
    tcp.ECN = byte(mix >> 6 & 7)      // 3 bits
    tcp.Ctrl = byte(mix & 0x3f)       // bottom 6 bits

    binary.Read(r, binary.BigEndian, &tcp.Window)
    binary.Read(r, binary.BigEndian, &tcp.Checksum)
    binary.Read(r, binary.BigEndian, &tcp.Urgent)

    return &tcp
}

func (tcp *TCPHeader) HasFlag(flagBit byte) bool {
    return tcp.Ctrl&flagBit != 0
}

func (tcp *TCPHeader) Marshal() []byte {

    buf := new(bytes.Buffer)
    binary.Write(buf, binary.BigEndian, tcp.Source)
    binary.Write(buf, binary.BigEndian, tcp.Destination)
    binary.Write(buf, binary.BigEndian, tcp.SeqNum)
    binary.Write(buf, binary.BigEndian, tcp.AckNum)

    var mix uint16
    mix = uint16(tcp.DataOffset)<<12 | // top 4 bits
        uint16(tcp.Reserved)<<9 | // 3 bits
        uint16(tcp.ECN)<<6 | // 3 bits
        uint16(tcp.Ctrl) // bottom 6 bits
    binary.Write(buf, binary.BigEndian, mix)

    binary.Write(buf, binary.BigEndian, tcp.Window)
    binary.Write(buf, binary.BigEndian, tcp.Checksum)
    binary.Write(buf, binary.BigEndian, tcp.Urgent)

    for _, option := range tcp.Options {
        binary.Write(buf, binary.BigEndian, option.Kind)
        if option.Length > 1 {
            binary.Write(buf, binary.BigEndian, option.Length)
            binary.Write(buf, binary.BigEndian, option.Data)
        }
    }

    out := buf.Bytes()

    // Pad to min tcp header size, which is 20 bytes (5 32-bit words)
    pad := 20 - len(out)
    for i := 0; i < pad; i++ {
        out = append(out, 0)
    }

    return out
}

// TCP Checksum
func Csum(data []byte, srcip, dstip [4]byte) uint16 {

    pseudoHeader := []byte{
        srcip[0], srcip[1], srcip[2], srcip[3],
        dstip[0], dstip[1], dstip[2], dstip[3],
        0,                  // zero
        6,                  // protocol number (6 == TCP)
        0, byte(len(data)), // TCP length (16 bits), not inc pseudo header
    }

    sumThis := make([]byte, 0, len(pseudoHeader)+len(data))
    sumThis = append(sumThis, pseudoHeader...)
    sumThis = append(sumThis, data...)
    //fmt.Printf("% x\n", sumThis)

    lenSumThis := len(sumThis)
    var nextWord uint16
    var sum uint32
    for i := 0; i+1 < lenSumThis; i += 2 {
        nextWord = uint16(sumThis[i])<<8 | uint16(sumThis[i+1])
        sum += uint32(nextWord)
    }
    if lenSumThis%2 != 0 {
        //fmt.Println("Odd byte")
        sum += uint32(sumThis[len(sumThis)-1])
    }

    // Add back any carry, and any carry from adding the carry
    sum = (sum >> 16) + (sum & 0xffff)
    sum = sum + (sum >> 16)

    // Bitwise complement
    return uint16(^sum)
}

func main() {
    packet := TCPHeader{
        Source:      0xaa47, // Random ephemeral port
        Destination: 80,
        SeqNum:      rand.Uint32(),
        AckNum:      0,
        DataOffset:  5,      // 4 bits
        Reserved:    0,      // 3 bits
        ECN:         0,      // 3 bits
        Ctrl:        2,      // 6 bits (000010, SYN bit set)
        Window:      0xaaaa, // size of your receive window
        Checksum:    0,      // Kernel will set this if it's 0
        Urgent:      0,
        Options:     []TCPOption{},
    }

    data := packet.Marshal()
    packet.Checksum = Csum(data, [4]byte{192, 168, 1, 5}, [4]byte{192, 168, 1, 5})
    data = packet.Marshal()

    conn, err := net.Dial("ip4:tcp", "192.168.1.5:555")
    if err != nil {
        log.Fatalf("Dial: %s\n", err)
    }

    conn.Write(data)
}

I'm using LiteIDE, and when I build and execute the Go produced exe, I get the following error:

2022/08/25 13:17:10 Dial: dial ip4:tcp 192.168.1.5: connect: An invalid argument was supplied.

This error is caused by line 178. I tried changing it to "192.168.1.5:555" (or any port), but now I get the following error:

2022/08/25 13:23:06 Dial: dial ip4:tcp: lookup 192.168.1.5:555: no such host

I'm also listening for incoming packets with ncat from nmap.org and Wireshark. Please keep in mind that I'm still studying Golang and in-depth networking. I understand networking at a basic level, but I'm attempting to become more comfortable with the more in-depth aspects of it (aka creating, manipulating and injecting RAW packets). I'm deliberately attempting something difficult since that's how I learn.

James Z
  • 12,209
  • 10
  • 24
  • 44
3vil.Tux
  • 1
  • 1
  • 2
    In MS Windows (starting XP SP2) it is not allowed to use raw sockets with TCP: https://learn.microsoft.com/en-us/windows/win32/winsock/tcp-ip-raw-sockets-2#:~:text=A%20call%20to%20the%20bind%20function%20with%20a%20raw%20socket%20for%20the%20IPPROTO_TCP%20protocol%20is%20not%20allowed. If you try other protocol, instead of "ip4:tcp", the program doesn't fail. – Pak Uula Aug 25 '22 at 12:20
  • Consider using libpcap to inject / capture ethernet frames and parse IP/TCP headers yourself. – Pak Uula Aug 25 '22 at 12:23
  • @PakUula Thanks for your comment! I see, I didn't know it was forbidden on Windows, I did try to change it to ICMP instead (even if it wasn't gonna work for obvious reasons) and the code at least ran perfectly fine. Regarding libpcap, I haven't found any libpcap library for Golang... unless I'm missing something? – 3vil.Tux Aug 26 '22 at 00:36
  • You are welcome ;) I worked with Pcap using C, can't tell how good is golang binding. I found one: https://pkg.go.dev/github.com/google/gopacket/pcap Still I can tell there is one gotcha - to guess the proper device name for `pcap.OpenLive` . It is not `eth0`-like, instead it is smth like `\Device\NPF_{13044533-0543-4AF5-9E3C-85EBBC7C04BB}`. See e.g. https://forum.golangbridge.org/t/soved-gopacket-pcap-and-windows-device-names/15856/2 – Pak Uula Aug 26 '22 at 02:53

1 Answers1

0

Please to try: conn, err := net.Dial("ip4:tcp", "192.168.1.5")

Unico
  • 607
  • 5
  • 6