Skip to content

Commit

Permalink
Merge pull request #2 from ndbeals/pageant-pipe
Browse files Browse the repository at this point in the history
Implemented Pageant named pipe proxying
  • Loading branch information
ndbeals authored Aug 7, 2020
2 parents 60b5274 + 308b710 commit a319566
Show file tree
Hide file tree
Showing 6 changed files with 140 additions and 36 deletions.
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,10 @@ go build -ldflags -H=windowsgui


## Usage
Run the executable `winssh-pageant.exe`. There is only one (optional) flag:
Run the executable `winssh-pageant.exe`. There are two (optional) flags:

- `--sshpipe` - name of the windows openssh agent pipe, default is `"\\.\pipe\ssh-pageant"`
- `--no-pageant-pipe` - disable pageant named pipe proxying


### Task Scheduler
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,5 @@ go 1.14
require (
github.com/Microsoft/go-winio v0.4.14
github.com/lxn/win v0.0.0-20191128105842-2da648fda5b4
golang.org/x/sys v0.0.0-20200728102440-3e129f6d46b1
golang.org/x/sys v0.0.0-20200802091954-4b90ce9b60b3
)
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,5 @@ golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200728102440-3e129f6d46b1 h1:sIky/MyNRSHTrdxfsiUSS4WIAMvInbeXljJz+jDjeYE=
golang.org/x/sys v0.0.0-20200728102440-3e129f6d46b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200802091954-4b90ce9b60b3 h1:qDJKu1y/1SjhWac4BQZjLljqvqiWUhjmDMnonmVGDAU=
golang.org/x/sys v0.0.0-20200802091954-4b90ce9b60b3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
10 changes: 7 additions & 3 deletions internal/sshagent/agent.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,13 @@ import (
"github.com/Microsoft/go-winio"
)

const (
AgentMaxMessageLength = 1<<14 - 1
)

// QueryAgent provides a way to query the named windows openssh agent pipe
func QueryAgent(pipeName string, buf []byte, agentMaxMessageLength int) (result []byte, err error) {
if len(buf) > agentMaxMessageLength {
func QueryAgent(pipeName string, buf []byte) (result []byte, err error) {
if len(buf) > AgentMaxMessageLength {
return nil, fmt.Errorf("Message too long")
}

Expand All @@ -25,7 +29,7 @@ func QueryAgent(pipeName string, buf []byte, agentMaxMessageLength int) (result
}

reader := bufio.NewReader(conn)
res := make([]byte, agentMaxMessageLength)
res := make([]byte, AgentMaxMessageLength)

l, err = reader.Read(res)
if err != nil {
Expand Down
24 changes: 6 additions & 18 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,36 +3,24 @@ package main
import (
"flag"
"fmt"
"syscall"

"github.com/lxn/win"
)

var (
sshPipe = flag.String("sshpipe", `\\.\pipe\openssh-ssh-agent`, "Named pipe for Windows OpenSSH agent")
sshPipe = flag.String("sshpipe", `\\.\pipe\openssh-ssh-agent`, "Named pipe for Windows OpenSSH agent")
noPageantPipe = flag.Bool("no-pageant-pipe", false, "Toggle pageant named pipe proxying")
)

func main() {
flag.Parse()

inst := win.GetModuleHandle(nil)
atom := registerPageantWindow(inst)
if atom == 0 {
fmt.Println(fmt.Errorf("RegisterClass failed: %d", win.GetLastError()))
return
// Start a proxy/redirector for the pageant named pipes
if !*noPageantPipe {
go pipeProxy()
}

// CreateWindowEx
pageantWindow := win.CreateWindowEx(win.WS_EX_APPWINDOW,
syscall.StringToUTF16Ptr(wndClassName),
syscall.StringToUTF16Ptr(wndClassName),
0,
0, 0,
0, 0,
0,
0,
inst,
nil)
pageantWindow := createPageantWindow()
if pageantWindow == 0 {
fmt.Println(fmt.Errorf("CreateWindowEx failed: %v", win.GetLastError()))
return
Expand Down
135 changes: 122 additions & 13 deletions pageant.go
Original file line number Diff line number Diff line change
@@ -1,31 +1,47 @@
package main

import (
"bufio"
"crypto/sha256"
"fmt"
"io"
"log"
"net"
"os/user"
"strings"
"syscall"
"unsafe"

"encoding/binary"

"github.com/Microsoft/go-winio"
"github.com/lxn/win"
"golang.org/x/sys/windows"

"encoding/hex"

"github.com/ndbeals/winssh-pageant/internal/security"
"github.com/ndbeals/winssh-pageant/internal/sshagent"
)

var (
crypt32 = syscall.NewLazyDLL("crypt32.dll")
procCryptProtectMemory = crypt32.NewProc("CryptProtectMemory")

modkernel32 = syscall.NewLazyDLL("kernel32.dll")
procOpenFileMappingA = modkernel32.NewProc("OpenFileMappingA")
)

const (
// windows consts
FILE_MAP_ALL_ACCESS = 0xf001f
CRYPTPROTECTMEMORY_BLOCK_SIZE = 16
CRYPTPROTECTMEMORY_CROSS_PROCESS = 1
FILE_MAP_ALL_ACCESS = 0xf001f

// Pageant consts
agentMaxMessageLength = 1<<14 - 1
agentCopyDataID = 0x804e50ba
wndClassName = "Pageant"
agentPipeName = `\\.\pipe\pageant.%s.%s`
agentCopyDataID = 0x804e50ba
wndClassName = "Pageant"
)

// copyDataStruct is used to pass data in the WM_COPYDATA message.
Expand All @@ -36,6 +52,15 @@ type copyDataStruct struct {
lpData uintptr
}

func openFileMap(dwDesiredAccess uint32, bInheritHandle uint32, mapNamePtr uintptr) (windows.Handle, error) {
mapPtr, _, err := procOpenFileMappingA.Call(uintptr(dwDesiredAccess), uintptr(bInheritHandle), mapNamePtr)
if err != nil && err.Error() == "The operation completed successfully." {
err = nil
}

return windows.Handle(mapPtr), err
}

func registerPageantWindow(hInstance win.HINSTANCE) (atom win.ATOM) {
var wc win.WNDCLASSEX
wc.Style = 0
Expand All @@ -55,14 +80,27 @@ func registerPageantWindow(hInstance win.HINSTANCE) (atom win.ATOM) {
return win.RegisterClassEx(&wc)
}

func openFileMap(dwDesiredAccess uint32, bInheritHandle uint32, mapNamePtr uintptr) (windows.Handle, error) {
mapPtr, _, err := procOpenFileMappingA.Call(uintptr(dwDesiredAccess), uintptr(bInheritHandle), mapNamePtr)

if err != nil && err.Error() == "The operation completed successfully." {
err = nil
func createPageantWindow() win.HWND {
inst := win.GetModuleHandle(nil)
atom := registerPageantWindow(inst)
if atom == 0 {
fmt.Println(fmt.Errorf("RegisterClass failed: %d", win.GetLastError()))
return 0
}

return windows.Handle(mapPtr), err
// CreateWindowEx
pageantWindow := win.CreateWindowEx(win.WS_EX_APPWINDOW,
syscall.StringToUTF16Ptr(wndClassName),
syscall.StringToUTF16Ptr(wndClassName),
0,
0, 0,
0, 0,
0,
0,
inst,
nil)

return pageantWindow
}

func wndProc(hWnd win.HWND, message uint32, wParam uintptr, lParam uintptr) uintptr {
Expand Down Expand Up @@ -98,15 +136,16 @@ func wndProc(hWnd win.HWND, message uint32, wParam uintptr, lParam uintptr) uint
}
defer windows.UnmapViewOfFile(sharedMemory)

sharedMemoryArray := (*[agentMaxMessageLength]byte)(unsafe.Pointer(sharedMemory))
sharedMemoryArray := (*[sshagent.AgentMaxMessageLength]byte)(unsafe.Pointer(sharedMemory))

size := binary.BigEndian.Uint32(sharedMemoryArray[:4]) + 4
// size += 4
if size > agentMaxMessageLength {
if size > sshagent.AgentMaxMessageLength {
return 0
}

result, err := sshagent.QueryAgent(*sshPipe, sharedMemoryArray[:size], agentMaxMessageLength)
// result, err := sshagent.QueryAgent(*sshPipe, sharedMemoryArray[:size], sshagent.AgentMaxMessageLength)
result, err := sshagent.QueryAgent(*sshPipe, sharedMemoryArray[:size])
copy(sharedMemoryArray[:], result)
// success
return 1
Expand All @@ -115,3 +154,73 @@ func wndProc(hWnd win.HWND, message uint32, wParam uintptr, lParam uintptr) uint

return win.DefWindowProc(hWnd, message, wParam, lParam)
}

func capiObfuscateString(realname string) string {
cryptlen := len(realname) + 1
cryptlen += CRYPTPROTECTMEMORY_BLOCK_SIZE - 1
cryptlen /= CRYPTPROTECTMEMORY_BLOCK_SIZE
cryptlen *= CRYPTPROTECTMEMORY_BLOCK_SIZE

cryptdata := make([]byte, cryptlen)
copy(cryptdata, realname)

pDataIn := uintptr(unsafe.Pointer(&cryptdata[0]))
cbDataIn := uintptr(cryptlen)
dwFlags := uintptr(CRYPTPROTECTMEMORY_CROSS_PROCESS)
// pageant ignores errors
procCryptProtectMemory.Call(pDataIn, cbDataIn, dwFlags)

hash := sha256.Sum256(cryptdata)
return hex.EncodeToString(hash[:])
}

func pipeProxy() {
currentUser, err := user.Current()
pipeName := fmt.Sprintf(agentPipeName, strings.Split(currentUser.Username, `\`)[1], capiObfuscateString(wndClassName))
listener, err := winio.ListenPipe(pipeName, nil)

if err != nil {
log.Fatal(err)
}
defer listener.Close()

for {
conn, err := listener.Accept()
if err != nil {
log.Println(err)
return
}

go pipeListen(conn)
}
}

func pipeListen(pageantConn net.Conn) {
defer pageantConn.Close()
reader := bufio.NewReader(pageantConn)

for {
lenBuf := make([]byte, 4)
_, err := io.ReadFull(reader, lenBuf)
if err != nil {
return
}

bufferLen := binary.BigEndian.Uint32(lenBuf)
readBuf := make([]byte, bufferLen)
_, err = io.ReadFull(reader, readBuf)
if err != nil {
return
}

result, err := sshagent.QueryAgent(*sshPipe, append(lenBuf, readBuf...))
if err != nil {
return
}

_, err = pageantConn.Write(result)
if err != nil {
return
}
}
}

0 comments on commit a319566

Please sign in to comment.