WireGuard / wgctrl-go

Package wgctrl enables control of WireGuard interfaces on multiple platforms.
https://godoc.org/golang.zx2c4.com/wireguard/wgctrl
MIT License
753 stars 84 forks source link

internal/wguser: support Windows port's pipe interface #36

Closed mdlayher closed 5 years ago

mdlayher commented 5 years ago

The Windows port uses the userspace protocol but uses special pipes to pass configuration rather than a UNIX socket: "\\\\.\\pipe\\WireGuard\\"+name. There should be a way to detect all of these pipes and configure them.

Jason also recommends looking at https://github.com/Microsoft/go-winio.

zx2c4 commented 5 years ago

https://git.zx2c4.com/WireGuard/tree/contrib/examples/windows-pipe/enumerate-and-get.c will help.

mdlayher commented 5 years ago

This CL should let us complete the implementation of this: https://go-review.googlesource.com/c/sys/+/176625.

The actual part where we find and enumerate pipes is done, just need to put the appropriate token code in place to access it now.

zx2c4 commented 5 years ago

Pending the merging of that CL, here's some working code:

package main

import (
    "errors"
    "fmt"
    "github.com/Microsoft/go-winio"
    "golang.org/x/sys/windows"
    "net"
    "runtime"
    "strings"
    "unsafe"
)

func openPipe(tunnelName string) (net.Conn, error) {
    runtime.LockOSThread()
    defer func() {
        windows.RevertToSelf()
        runtime.UnlockOSThread()
    }()
    privileges := windows.Tokenprivileges{
        PrivilegeCount: 1,
        Privileges: [1]windows.LUIDAndAttributes{
            {
                Attributes: windows.SE_PRIVILEGE_ENABLED,
            },
        },
    }
    err := windows.LookupPrivilegeValue(nil, windows.StringToUTF16Ptr("SeDebugPrivilege"), &privileges.Privileges[0].Luid)
    if err != nil {
        return nil, err
    }
    err = windows.ImpersonateSelf(windows.SecurityImpersonation)
    if err != nil {
        return nil, err
    }
    thread, _ := windows.GetCurrentThread()
    var threadToken windows.Token
    err = windows.OpenThreadToken(thread, windows.TOKEN_ADJUST_PRIVILEGES, false, &threadToken)
    if err != nil {
        return nil, err
    }
    defer threadToken.Close()
    err = windows.AdjustTokenPrivileges(threadToken, false, &privileges, uint32(unsafe.Sizeof(privileges)), nil, nil)
    if err != nil {
        return nil, err
    }

    processes, err := windows.CreateToolhelp32Snapshot(windows.TH32CS_SNAPPROCESS, 0)
    if err != nil {
        return nil, err
    }
    defer windows.CloseHandle(processes)

    processEntry := windows.ProcessEntry32{Size: uint32(unsafe.Sizeof(windows.ProcessEntry32{}))}
    pid := uint32(0)
    for err = windows.Process32First(processes, &processEntry); err == nil; err = windows.Process32Next(processes, &processEntry) {
        if strings.ToLower(windows.UTF16ToString(processEntry.ExeFile[:])) == "winlogon.exe" {
            pid = processEntry.ProcessID
            break
        }
    }
    if pid == 0 {
        return nil, errors.New("Unable to find winlogon.exe process")
    }

    winlogonProcess, err := windows.OpenProcess(windows.PROCESS_QUERY_INFORMATION, false, pid)
    if err != nil {
        return nil, err
    }
    defer windows.CloseHandle(winlogonProcess)
    var winlogonToken windows.Token
    err = windows.OpenProcessToken(winlogonProcess, windows.TOKEN_IMPERSONATE|windows.TOKEN_DUPLICATE, &winlogonToken)
    if err != nil {
        return nil, err
    }
    defer winlogonToken.Close()
    var duplicatedToken windows.Token
    err = windows.DuplicateTokenEx(winlogonToken, 0, nil, windows.SecurityImpersonation, windows.TokenImpersonation, &duplicatedToken)
    if err != nil {
        return nil, err
    }
    defer duplicatedToken.Close()
    err = windows.SetThreadToken(nil, duplicatedToken)
    if err != nil {
        return nil, err
    }
    return winio.DialPipe(`\\.\pipe\WireGuard\`+tunnelName, nil)
}

func main() {
    var findData windows.Win32finddata
    findHandle, err := windows.FindFirstFile(windows.StringToUTF16Ptr(`\\.\pipe\*`), &findData)
    defer windows.CloseHandle(findHandle)
    if err != nil {
        fmt.Println(err)
        return
    }
    for {
        name := windows.UTF16ToString(findData.FileName[:])
        for strings.HasPrefix(name, `WireGuard\`) {
            name = name[10:]
            fmt.Printf("name=%s\n", name)
            conn, err := openPipe(name)
            if err != nil {
                fmt.Println(err)
                break
            }
            defer conn.Close()
            conn.Write([]byte("get=1\n\n"))
            var resp [0x10000]byte
            n, err := conn.Read(resp[:])
            if err != nil {
                fmt.Println(err)
                break
            }
            fmt.Print(string(resp[:n]))
            break
        }
        err = windows.FindNextFile(findHandle, &findData)
        if err != nil {
            if err == windows.ERROR_NO_MORE_FILES {
                break
            }
            fmt.Println(err)
            break
        }
    }
}