Add windowskext integration, update related packages

This commit is contained in:
Daniel
2019-04-26 11:33:24 +02:00
parent a702cd4824
commit 78a0b3c1fb
33 changed files with 979 additions and 690 deletions

View File

@@ -1,154 +1,130 @@
package windowskext
import (
"errors"
"encoding/binary"
"fmt"
"net"
"github.com/google/gopacket"
"github.com/google/gopacket/layers"
"github.com/tevino/abool"
"github.com/Safing/portbase/log"
"github.com/Safing/portmaster/network/packet"
"github.com/tevino/abool"
)
func (wd *WinDivert) Packets(packets chan packet.Packet) error {
go wd.packetHandler(packets)
return nil
// VerdictRequest is the request structure from the Kext.
type VerdictRequest struct {
id uint32 /* ID from RegisterPacket */
processID uint64 /* Process ID. Nice to have*/
direction uint8
ipV6 uint8 /* True: IPv6, False: IPv4 */
protocol uint8 /* Protocol */
_ uint8
localIP [4]uint32 /* Source Address */
remoteIP [4]uint32 /* Destination Address */
localPort uint16 /* Source Port */
remotePort uint16 /* Destination port */
compartmentId uint32
interfaceIndex uint32
subInterfaceIndex uint32
packetSize uint32
}
func (wd *WinDivert) packetHandler(packets chan packet.Packet) {
// Handler transforms received packets to the Packet interface.
func Handler(packets chan packet.Packet) {
if !ready.IsSet() {
return
}
defer close(packets)
for {
if !wd.valid.IsSet() {
if !ready.IsSet() {
return
}
packetData, packetAddress, err := wd.Recv()
packetInfo, err := RecvVerdictRequest()
if err != nil {
log.Warningf("failed to get packet from windivert: %s", err)
log.Warningf("failed to get packet from windows kext: %s", err)
continue
}
ipHeader, tpcUdpHeader, payload, err := parseIpPacket(packetData)
if err != nil {
log.Warningf("failed to parse packet from windivert: %s", err)
log.Warningf("failed packet payload (%d): %s", len(packetData), string(packetData))
if packetInfo == nil {
continue
}
// log.Tracef("packet: %+v", packetInfo)
// New Packet
new := &Packet{
windivert: wd,
packetData: packetData,
packetAddress: packetAddress,
verdictSet: abool.NewBool(false),
verdictRequest: packetInfo,
verdictSet: abool.NewBool(false),
}
new.IPHeader = ipHeader
new.TCPUDPHeader = tpcUdpHeader
new.Payload = payload
if packetAddress.Direction == directionInbound {
new.Direction = packet.InBound
info := new.Info()
info.Direction = packetInfo.direction > 0
info.InTunnel = false
info.Protocol = packet.IPProtocol(packetInfo.protocol)
// IP version
if packetInfo.ipV6 == 1 {
info.Version = packet.IPv6
} else {
new.Direction = packet.OutBound
info.Version = packet.IPv4
}
// IPs
if info.Version == packet.IPv4 {
// IPv4
if info.Direction {
// Inbound
info.Src = convertIPv4(packetInfo.remoteIP)
info.Dst = convertIPv4(packetInfo.localIP)
} else {
// Outbound
info.Src = convertIPv4(packetInfo.localIP)
info.Dst = convertIPv4(packetInfo.remoteIP)
}
} else {
// IPv6
if info.Direction {
// Inbound
info.Src = convertIPv6(packetInfo.remoteIP)
info.Dst = convertIPv6(packetInfo.localIP)
} else {
// Outbound
info.Src = convertIPv6(packetInfo.localIP)
info.Dst = convertIPv6(packetInfo.remoteIP)
}
}
// Ports
if info.Direction {
// Inbound
info.SrcPort = packetInfo.remotePort
info.DstPort = packetInfo.localPort
} else {
// Outbound
info.SrcPort = packetInfo.localPort
info.DstPort = packetInfo.remotePort
}
packets <- new
}
}
func parseIpPacket(packetData []byte) (ipHeader *packet.IPHeader, tpcUdpHeader *packet.TCPUDPHeader, payload []byte, err error) {
var parsedPacket gopacket.Packet
if len(packetData) == 0 {
return nil, nil, nil, errors.New("empty packet")
}
switch packetData[0] >> 4 {
case 4:
parsedPacket = gopacket.NewPacket(packetData, layers.LayerTypeIPv4, gopacket.DecodeOptions{Lazy: true, NoCopy: true})
if ipv4Layer := parsedPacket.Layer(layers.LayerTypeIPv4); ipv4Layer != nil {
ipv4, _ := ipv4Layer.(*layers.IPv4)
ipHeader = &packet.IPHeader{
Version: 4,
Protocol: packet.IPProtocol(ipv4.Protocol),
Tos: ipv4.TOS,
TTL: ipv4.TTL,
Src: ipv4.SrcIP,
Dst: ipv4.DstIP,
}
} else {
var err error
if errLayer := parsedPacket.ErrorLayer(); errLayer != nil {
err = errLayer.Error()
}
return nil, nil, nil, fmt.Errorf("failed to parse IPv4 packet: %s", err)
}
case 6:
parsedPacket = gopacket.NewPacket(packetData, layers.LayerTypeIPv6, gopacket.DecodeOptions{Lazy: true, NoCopy: true})
if ipv6Layer := parsedPacket.Layer(layers.LayerTypeIPv6); ipv6Layer != nil {
ipv6, _ := ipv6Layer.(*layers.IPv6)
ipHeader = &packet.IPHeader{
Version: 6,
Protocol: packet.IPProtocol(ipv6.NextHeader),
Tos: ipv6.TrafficClass,
TTL: ipv6.HopLimit,
Src: ipv6.SrcIP,
Dst: ipv6.DstIP,
}
} else {
var err error
if errLayer := parsedPacket.ErrorLayer(); errLayer != nil {
err = errLayer.Error()
}
return nil, nil, nil, fmt.Errorf("failed to parse IPv6 packet: %s", err)
}
default:
return nil, nil, nil, errors.New("unknown IP version")
}
switch ipHeader.Protocol {
case packet.TCP:
if tcpLayer := parsedPacket.Layer(layers.LayerTypeTCP); tcpLayer != nil {
tcp, _ := tcpLayer.(*layers.TCP)
tpcUdpHeader = &packet.TCPUDPHeader{
SrcPort: uint16(tcp.SrcPort),
DstPort: uint16(tcp.DstPort),
Checksum: tcp.Checksum,
}
} else {
var err error
if errLayer := parsedPacket.ErrorLayer(); errLayer != nil {
err = errLayer.Error()
}
return nil, nil, nil, fmt.Errorf("could not parse TCP layer: %s", err)
}
case packet.UDP:
if udpLayer := parsedPacket.Layer(layers.LayerTypeUDP); udpLayer != nil {
udp, _ := udpLayer.(*layers.UDP)
tpcUdpHeader = &packet.TCPUDPHeader{
SrcPort: uint16(udp.SrcPort),
DstPort: uint16(udp.DstPort),
Checksum: udp.Checksum,
}
} else {
var err error
if errLayer := parsedPacket.ErrorLayer(); errLayer != nil {
err = errLayer.Error()
}
return nil, nil, nil, fmt.Errorf("could not parse UDP layer: %s", err)
}
}
if appLayer := parsedPacket.ApplicationLayer(); appLayer != nil {
payload = appLayer.Payload()
}
if errLayer := parsedPacket.ErrorLayer(); errLayer != nil {
return nil, nil, nil, errLayer.Error()
}
return
func convertIPv4(input [4]uint32) net.IP {
return net.IPv4(
uint8(input[0]>>24&0xFF),
uint8(input[0]>>16&0xFF),
uint8(input[0]>>8&0xFF),
uint8(input[0]&0xFF),
)
}
func convertIPv6(input [4]uint32) net.IP {
addressBuf := make([]byte, 16)
for i := 0; i < 4; i++ {
binary.BigEndian.PutUint32(addressBuf[i:i+3], input[i])
}
return net.IP(addressBuf)
}

View File

@@ -1,61 +1,206 @@
package windowskext
import (
"errors"
"fmt"
"sync"
"syscall"
"unsafe"
"github.com/Safing/portmaster/network"
"github.com/tevino/abool"
"golang.org/x/sys/windows"
)
// Package errors
var (
ErrKextNotReady = errors.New("the windows kernel extension (driver) is not ready to accept commands")
kext *WinKext
kextLock sync.RWMutex
ready = abool.NewBool(false)
)
// WinKext holds the DLL handle.
type WinKext struct {
dll *windows.DLL
sync.RWMutex
dll *windows.DLL
driverPath string
init *windows.Proc
start *windows.Proc
stop *windows.Proc
recvVerdictRequest *windows.Proc
valid *abool.AtomicBool
setVerdict *windows.Proc
getPayload *windows.Proc
}
type VerdictRequest struct {
ID uint32
ProcessID uint32
Direction bool
IPv6 bool
Protocol uint8
SrcIP [4]uint32
DstIP [4]uint32
SrcPort uint16
DstPort uint16
}
// Init initializes the DLL and the Kext (Kernel Driver).
func Init(dllPath, driverPath string) error {
func New(dllLocation string) (*WinKext, error) {
new := &WinKext{
driverPath: driverPath,
}
new := &WinKext{}
var err error
// load dll
new.dll, err = windows.LoadDLL(dllLocation)
new.dll, err = windows.LoadDLL(dllPath)
if err != nil {
return nil, err
return err
}
// load functions
new.init, err = new.dll.FindProc("PortmasterInit")
if err != nil {
return fmt.Errorf("could not find proc PortmasterStart in dll: %s", err)
}
new.start, err = new.dll.FindProc("PortmasterStart")
if err != nil {
return fmt.Errorf("could not find proc PortmasterStart in dll: %s", err)
}
new.stop, err = new.dll.FindProc("PortmasterStop")
if err != nil {
return fmt.Errorf("could not find proc PortmasterStop in dll: %s", err)
}
new.recvVerdictRequest, err = new.dll.FindProc("PortmasterRecvVerdictRequest")
if err != nil {
return nil, fmt.Errorf("could not find proc PortmasterRecvVerdictRequest: %s", err)
return fmt.Errorf("could not find proc PortmasterRecvVerdictRequest in dll: %s", err)
}
new.setVerdict, err = new.dll.FindProc("PortmasterSetVerdict")
if err != nil {
return fmt.Errorf("could not find proc PortmasterSetVerdict in dll: %s", err)
}
new.getPayload, err = new.dll.FindProc("PortmasterGetPayload")
if err != nil {
return fmt.Errorf("could not find proc PortmasterGetPayload in dll: %s", err)
}
return new, nil
// initialize dll/kext
rc, _, lastErr := new.init.Call()
if rc != windows.NO_ERROR {
return formatErr(lastErr)
}
// set kext
kextLock.Lock()
defer kextLock.Unlock()
kext = new
return nil
}
func (kext *WinKext) RecvVerdictRequest() (*VerdictRequest, error) {
// Start intercepting.
func Start() error {
kextLock.Lock()
defer kextLock.Unlock()
// convert to C string
charArray := make([]byte, len(kext.driverPath)+1)
copy(charArray, []byte(kext.driverPath))
charArray[len(charArray)-1] = 0 // force NULL byte at the end
rc, _, lastErr := kext.start.Call(
uintptr(unsafe.Pointer(&charArray[0])),
)
if rc != windows.NO_ERROR {
return formatErr(lastErr)
}
ready.Set()
return nil
}
// Stop intercepting.
func Stop() error {
kextLock.Lock()
defer kextLock.Unlock()
if !ready.IsSet() {
return ErrKextNotReady
}
ready.UnSet()
rc, _, lastErr := kext.stop.Call()
if rc != windows.NO_ERROR {
return formatErr(lastErr)
}
return nil
}
// RecvVerdictRequest waits for the next verdict request from the kext. If a timeout is reached, both *VerdictRequest and error will be nil.
func RecvVerdictRequest() (*VerdictRequest, error) {
kextLock.RLock()
defer kextLock.RUnlock()
if !ready.IsSet() {
return nil, ErrKextNotReady
}
new := &VerdictRequest{}
rc, _, lastErr := kext.recvVerdictRequest.Call(
uintptr(unsafe.Pointer(new)),
)
if rc != 0 {
return nil, lastErr
if rc == 13 /* ERROR_INVALID_DATA */ {
return nil, nil
}
return nil, formatErr(lastErr)
}
return new, nil
}
// SetVerdict sets the verdict for a packet and/or connection.
func SetVerdict(packetID uint32, verdict network.Verdict) error {
kextLock.RLock()
defer kextLock.RUnlock()
if !ready.IsSet() {
return ErrKextNotReady
}
rc, _, lastErr := kext.setVerdict.Call(
uintptr(packetID),
uintptr(verdict),
)
if rc != windows.NO_ERROR {
return formatErr(lastErr)
}
return nil
}
// GetPayload returns the payload of a packet.
func GetPayload(packetID uint32, packetSize uint32) ([]byte, error) {
kextLock.RLock()
defer kextLock.RUnlock()
if !ready.IsSet() {
return nil, ErrKextNotReady
}
buf := make([]byte, packetSize)
rc, _, lastErr := kext.getPayload.Call(
uintptr(packetID),
uintptr(unsafe.Pointer(&buf[0])),
uintptr(unsafe.Pointer(&packetSize)),
)
if rc != windows.NO_ERROR {
return nil, formatErr(lastErr)
}
if packetSize == 0 {
return nil, errors.New("windows kext did not return any data")
}
if packetSize < uint32(len(buf)) {
return buf[:packetSize], nil
}
return buf, nil
}
func formatErr(err error) error {
sysErr, ok := err.(syscall.Errno)
if ok {
return fmt.Errorf("%s [0x%X]", err, uintptr(sysErr))
}
return err
}

View File

@@ -1,55 +1,108 @@
package windowskext
import (
"sync"
"github.com/tevino/abool"
"github.com/Safing/portbase/log"
"github.com/Safing/portmaster/network"
"github.com/Safing/portmaster/network/packet"
)
// Packet represents an IP packet.
type Packet struct {
packet.PacketBase
kextID uint32
packetData []byte
verdictRequest *VerdictRequest
verdictSet *abool.AtomicBool
verdictSet *abool.AtomicBool
payloadLoaded bool
lock sync.Mutex
}
// GetPayload returns the full raw packet.
func (pkt *Packet) GetPayload() ([]byte, error) {
pkt.lock.Lock()
defer pkt.lock.Unlock()
if !pkt.payloadLoaded {
pkt.payloadLoaded = true
payload, err := GetPayload(pkt.verdictRequest.id, pkt.verdictRequest.packetSize)
if err != nil {
log.Errorf("windowskext: failed to load payload %s", err)
return nil, packet.ErrFailedToLoadPayload
}
pkt.Payload = payload
}
if len(pkt.Payload) == 0 {
return nil, packet.ErrFailedToLoadPayload
}
return pkt.Payload, nil
}
// Accept accepts the packet.
func (pkt *Packet) Accept() error {
if pkt.verdictSet.SetToIf(false, true) {
return pkt.windivert.Send(pkt.packetData, pkt.packetAddress)
return SetVerdict(pkt.verdictRequest.id, -network.VerdictAccept)
}
return nil
}
// Block blocks the packet.
func (pkt *Packet) Block() error {
if pkt.verdictSet.SetToIf(false, true) {
// TODO: implement blocking mechanism
return nil
return SetVerdict(pkt.verdictRequest.id, -network.VerdictBlock)
}
return nil
}
// Drop drops the packet.
func (pkt *Packet) Drop() error {
if pkt.verdictSet.SetToIf(false, true) {
return SetVerdict(pkt.verdictRequest.id, -network.VerdictDrop)
}
return nil
}
// PermanentAccept permanently accepts connection (and the current packet).
func (pkt *Packet) PermanentAccept() error {
return pkt.Accept()
if pkt.verdictSet.SetToIf(false, true) {
return SetVerdict(pkt.verdictRequest.id, network.VerdictAccept)
}
return nil
}
// PermanentBlock permanently blocks connection (and the current packet).
func (pkt *Packet) PermanentBlock() error {
return pkt.Block()
if pkt.verdictSet.SetToIf(false, true) {
return SetVerdict(pkt.verdictRequest.id, network.VerdictBlock)
}
return nil
}
// PermanentDrop permanently drops connection (and the current packet).
func (pkt *Packet) PermanentDrop() error {
return pkt.Drop()
if pkt.verdictSet.SetToIf(false, true) {
return SetVerdict(pkt.verdictRequest.id, network.VerdictDrop)
}
return nil
}
// RerouteToNameserver permanently reroutes the connection to the local nameserver (and the current packet).
func (pkt *Packet) RerouteToNameserver() error {
if pkt.verdictSet.SetToIf(false, true) {
return SetVerdict(pkt.verdictRequest.id, network.VerdictRerouteToNameserver)
}
return nil
}
// RerouteToTunnel permanently reroutes the connection to the local tunnel entrypoint (and the current packet).
func (pkt *Packet) RerouteToTunnel() error {
if pkt.verdictSet.SetToIf(false, true) {
return SetVerdict(pkt.verdictRequest.id, network.VerdictRerouteToTunnel)
}
return nil
}

View File

@@ -0,0 +1,26 @@
package main
import (
"fmt"
"unsafe"
)
const integerSize int = int(unsafe.Sizeof(0))
func isBigEndian() bool {
var i int = 0x1
bs := (*[integerSize]byte)(unsafe.Pointer(&i))
if bs[0] == 0 {
return true
} else {
return false
}
}
func main() {
if isBigEndian() {
fmt.Println("System is Big Endian (Network Byte Order): uint16 0x1234 is 0x1234 in memory")
} else {
fmt.Println("System is Little Endian (Host Byte Order): uint16 0x1234 is 0x3412 in memory")
}
}

View File

@@ -0,0 +1,110 @@
package main
import (
"fmt"
"os"
"os/signal"
"syscall"
"github.com/Safing/portbase/log"
"github.com/Safing/portmaster/firewall/interception/windowskext"
"github.com/Safing/portmaster/network/packet"
)
var (
packets chan packet.Packet
)
func main() {
// check parameter count
if len(os.Args) < 3 {
fmt.Printf("usage: %s <dll> <sys>", os.Args[0])
os.Exit(1)
}
// check parameters
for i := 1; i < 3; i++ {
if _, err := os.Stat(os.Args[i]); err != nil {
fmt.Printf("could not access %s: %s", os.Args[i], err)
os.Exit(2)
}
}
// logging
log.Start()
log.Info("starting Portmaster Windows Kext Test Program")
// init
err := windowskext.Init(os.Args[1], os.Args[2])
if err != nil {
panic(err)
}
// start
err = windowskext.Start()
if err != nil {
panic(err)
}
packets = make(chan packet.Packet, 1000)
go windowskext.Handler(packets)
go handlePackets()
// catch interrupt for clean shutdown
signalCh := make(chan os.Signal)
signal.Notify(
signalCh,
os.Interrupt,
syscall.SIGHUP,
syscall.SIGINT,
syscall.SIGTERM,
syscall.SIGQUIT,
)
<-signalCh
fmt.Println(" <INTERRUPT>")
log.Warning("program was interrupted, shutting down")
// stop
err = windowskext.Stop()
if err != nil {
fmt.Printf("error stopping: %s\n", err)
}
log.Info("shutdown complete")
log.Shutdown()
os.Exit(0)
}
func handlePackets() {
for {
pkt := <-packets
if pkt == nil {
log.Infof("stopped handling packets")
return
}
log.Infof("received packet: %s", pkt)
data, err := pkt.GetPayload()
if err != nil {
log.Errorf("failed to get payload: %s", err)
} else {
log.Infof("payload is: %x", data)
}
// reroute dns requests to nameserver
if pkt.IsOutbound() && !pkt.Info().Src.Equal(pkt.Info().Dst) && pkt.Info().DstPort == 53 {
log.Infof("rerouting %s", pkt)
pkt.RerouteToNameserver()
continue
}
// accept all
log.Infof("accepting %s", pkt)
pkt.PermanentAccept()
}
}