I was trying to solve a challenge where the "hidden data" were in ICMP echo payloads. I decided to do it in Go but there were some hiccups on the way. Here are my notes in case (most likely) future me or someone else needs to do the same.
Code is in my clone at:
gopacket is the official Go library for packet manipulation.
It also supports reading and writing pcap files through gopacket/pcap
.
I started following this tutorial from dev dungeon (skipped
the capturing part because I have a pcap file in hand). We need to go get
both
gopacket
and gopacket/pcap
.
go get github.com/google/gopacket/pcap
won't work on Windows. I searched
around and found an answer on Stack Overflow.
I got it to work with some modification.
Update February 2020: Seems like this is not an issue anymore. Running the go
get
command above worked for me on Windows 10.
C:\mingw\x64\bin
to PATH.C:\
. So you will have C:\WpdPack
.wpcap.dll
and packet.dll
in C:\Windows\System32
and copy them somewhere.gendef
(from MinGW
) on both files.dlltool --as-flags=--64 -m i386:x86-64 -k --output-lib libwpcap.a --input-def wpcap.def
dlltool --as-flags=--64 -m i386:x86-64 -k --output-lib libpacket.a --input-def packet.def
libwpcap.a
and libpacket.a
to c:\WpdPack\Lib\x64
.go get github.com/google/gopacket/pcap
.Following the tutorial I started making code snippets to do what I wanted. Most code is based on the tutorial.
Gopacket godoc and source are also your friends:
This one shows how to open a pcap file and print the packets.
|
|
But I got the following error:
$ go run go-pcap-test1.go
2017/12/02 15:48:46 bad dump file format
exit status 1
Seems like the original file was in pcapng
format which is not supported by
gopacket
. Converting the file to pcap
worked. The new file is named
conv.pcap
.
Reading everything in the pcap file is good but not what we want. We want to set
a filter and only read certain packets. This can be done with
handle.SetBPFFilter(filter)
in which filter
is a string containing a filter
in BPF syntax. We just pass the filter icmp
:
|
|
This code only reads packets of type icmp
.
gopacket
is based on layers. You can get each layer from raw packet data
(either from the pcap file or just bytes). Layers are in
github.com/google/gopacket/layers
. We are interested in IPv4 pings so I used
ipLayer := packet.Layer(layers.LayerTypeIPv4)
.
Now ipLayer
is a *layers.IPv4
(don't worry about it being a pointer) and we
can print it with fmt.Printf("%+v", ipLayer)
to get:
|
|
Remember those are printed in decimal (bytes are just uint8 in go) and not hex. Personally I prefer printing in hex because it's easier for me to read ASCII-Hex.
At this point you would think we could just do ipLayer.Payload
and read it but
we get:
ipLayer.Payload undefined (type gopacket.Layer has no field or method Payload)
But if we print the type with %T
we get *layers.IPv4
and when we print it
with %+v
we can see the Payload
field.
What we have is an interface and the compiler does not know it's going to be
populated by *layers.IPv4
at runtime. We need to cast the packet to
*layers.IPv4
manually. Then we can access Payload
:
|
|
Which results in
|
|
For more info see section Pointers to Known Layers in gopacket docs.
So we mostly got everything, the payload is some headers and then base64 encoded
data. We could just discard the first 8 (header) + 9 ($$START$$
) and grab what
we want. But let's do things properly.
We can create an icmp
message from the IPv4 layer payload.
First we need go get golang.org/x/net/icmp
and then:
|
|
ProtocolICMP
and ProtocolIPv6ICMP
are defined in
golang.org/x/net/internal/iana
. It's an internal package and we cannot use it
directly. Instead I have copied the constants directly in my code.
The result is *icmp.Message:
|
|
We are interested in Body
of type MessageBody which
is again an interface. If we print the value and type we get:
|
|
But again we need to cast it to *icmp.Echo
before we can get the Data
field
which contains the payload.
|
|
Now we have the payload:
$$START$$SGFtIHNoYW5rIHJ1bXAsIG51bGxhIG5vbiBhbGNhdHJhIHV0IGRlc2VydW50IG1pbmltIGJvdWRp
This is base64 encoded and we can decode it after removing $$START$$
:
Ham shank rump, nulla non alcatra ut deserunt minim boudi
The rest is easy. Complete code for this section is in pcap-3.go
.