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
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.
go get pcap on Windows
- Install go_amd64 (add go binaries to your PATH). I assume you have a Go environment ready to go.
- Install MinGW x64 via Win-Builds like I have written about before.
- Add
C:\mingw\x64\bin
to PATH. - Install npcap.
- Download Winpcap developer's pack and extract it to
C:\
. So you will haveC:\WpdPack
. - Find
wpcap.dll
andpacket.dll
inC:\Windows\System32
and copy them somewhere. - Run
gendef
(fromMinGW
) on both files. - Generate static library 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
- Copy
libwpcap.a
andlibpacket.a
toc:\WpdPack\Lib\x64
. - Finally
go get github.com/google/gopacket/pcap
.
Reading pcaps
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:
Opening a pcap File
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
.
Setting Filters
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
.
Layers
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.
IPv4 Layer
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.
Creating an ICMP Message in Go
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:
|
|
Getting ICMP Payload
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
.