v0.1 - initial commit
This commit is contained in:
+187
@@ -0,0 +1,187 @@
|
||||
//go:build windows
|
||||
|
||||
package winpwn
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"sync"
|
||||
|
||||
"golang.org/x/sys/windows"
|
||||
)
|
||||
|
||||
const (
|
||||
pipeOutBufSize = 64 * 1024
|
||||
pipeInBufSize = 64 * 1024
|
||||
)
|
||||
|
||||
// pipeConn wraps a duplex named-pipe handle opened with
|
||||
// FILE_FLAG_OVERLAPPED as an io.ReadWriteCloser, the shape Tube needs for
|
||||
// its stdin/stdout fields. The handle is genuinely asynchronous -- matching
|
||||
// how real Windows services hold their pipe ends; a synchronous duplex
|
||||
// pipe is the rarer case in production code -- but every Read/Write below
|
||||
// immediately blocks on GetOverlappedResult, so the type behaves like an
|
||||
// ordinary blocking reader/writer to the rest of the package. That keeps
|
||||
// Tube's synchronous Send/Recv contract intact while still exercising the
|
||||
// same overlapped-completion path a real target uses.
|
||||
type pipeConn struct {
|
||||
h windows.Handle
|
||||
event windows.Handle // manual-reset event reused across overlapped calls
|
||||
|
||||
closeOnce sync.Once
|
||||
closeErr error
|
||||
}
|
||||
|
||||
func newPipeConn(h windows.Handle) (*pipeConn, error) {
|
||||
ev, err := windows.CreateEvent(nil, 1 /* manual reset */, 0, nil)
|
||||
if err != nil {
|
||||
windows.CloseHandle(h)
|
||||
return nil, fmt.Errorf("CreateEvent: %w", err)
|
||||
}
|
||||
return &pipeConn{h: h, event: ev}, nil
|
||||
}
|
||||
|
||||
func (p *pipeConn) Read(b []byte) (int, error) {
|
||||
if len(b) == 0 {
|
||||
return 0, nil
|
||||
}
|
||||
var ov windows.Overlapped
|
||||
ov.HEvent = p.event
|
||||
|
||||
var n uint32
|
||||
err := windows.ReadFile(p.h, b, &n, &ov)
|
||||
if err != nil && err != windows.ERROR_IO_PENDING {
|
||||
if err == windows.ERROR_BROKEN_PIPE || err == windows.ERROR_HANDLE_EOF {
|
||||
return 0, io.EOF
|
||||
}
|
||||
return 0, err
|
||||
}
|
||||
|
||||
var transferred uint32
|
||||
if err := windows.GetOverlappedResult(p.h, &ov, &transferred, true); err != nil {
|
||||
if err == windows.ERROR_BROKEN_PIPE || err == windows.ERROR_HANDLE_EOF {
|
||||
return int(transferred), io.EOF
|
||||
}
|
||||
return int(transferred), err
|
||||
}
|
||||
if transferred == 0 {
|
||||
return 0, io.EOF
|
||||
}
|
||||
return int(transferred), nil
|
||||
}
|
||||
|
||||
func (p *pipeConn) Write(b []byte) (int, error) {
|
||||
if len(b) == 0 {
|
||||
return 0, nil
|
||||
}
|
||||
var ov windows.Overlapped
|
||||
ov.HEvent = p.event
|
||||
|
||||
var n uint32
|
||||
err := windows.WriteFile(p.h, b, &n, &ov)
|
||||
if err != nil && err != windows.ERROR_IO_PENDING {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
var transferred uint32
|
||||
if err := windows.GetOverlappedResult(p.h, &ov, &transferred, true); err != nil {
|
||||
return int(transferred), err
|
||||
}
|
||||
return int(transferred), nil
|
||||
}
|
||||
|
||||
// Close is idempotent (sync.Once) deliberately: Tube.Close calls Close once
|
||||
// via its stdin field and once via its stdout field, and stdin/stdout are
|
||||
// the same *pipeConn here -- unlike net.Conn, a raw Windows HANDLE is not
|
||||
// safe to pass to CloseHandle twice (the numeric value can be reused by an
|
||||
// unrelated object in between).
|
||||
func (p *pipeConn) Close() error {
|
||||
p.closeOnce.Do(func() {
|
||||
_ = windows.CloseHandle(p.event)
|
||||
p.closeErr = windows.CloseHandle(p.h)
|
||||
})
|
||||
return p.closeErr
|
||||
}
|
||||
|
||||
// ServePipe creates a duplex named pipe at \\.\pipe\<name>
|
||||
// (PIPE_ACCESS_DUPLEX | FILE_FLAG_OVERLAPPED -- the pattern real Windows
|
||||
// services use, not the rarer synchronous one) and blocks until exactly
|
||||
// one client connects -- the named-pipe analogue of Spawn/Remote for the
|
||||
// IPC-flavored challenges Windows uses far more than Linux pwn does.
|
||||
// Returns a *Tube wired to the connected pipe end, so Send/Recv*/
|
||||
// Interactive/SetTimeout all work exactly as they do over a process or TCP
|
||||
// socket.
|
||||
func ServePipe(name string) (*Tube, error) {
|
||||
fullName, err := windows.UTF16PtrFromString(`\\.\pipe\` + name)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid pipe name %q: %w", name, err)
|
||||
}
|
||||
|
||||
h, err := windows.CreateNamedPipe(
|
||||
fullName,
|
||||
windows.PIPE_ACCESS_DUPLEX|windows.FILE_FLAG_OVERLAPPED,
|
||||
windows.PIPE_TYPE_BYTE|windows.PIPE_READMODE_BYTE|windows.PIPE_WAIT,
|
||||
1, // maxInstances: one client, matching "spawn one challenge instance"
|
||||
pipeOutBufSize, pipeInBufSize,
|
||||
0, // default timeout
|
||||
nil,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("CreateNamedPipe: %w", err)
|
||||
}
|
||||
|
||||
conn, err := newPipeConn(h)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
Info(`Waiting for a client on \\.\pipe\%s`, name)
|
||||
|
||||
var ov windows.Overlapped
|
||||
ov.HEvent = conn.event
|
||||
err = windows.ConnectNamedPipe(conn.h, &ov)
|
||||
if err != nil && err != windows.ERROR_IO_PENDING && err != windows.ERROR_PIPE_CONNECTED {
|
||||
conn.Close()
|
||||
return nil, fmt.Errorf("ConnectNamedPipe: %w", err)
|
||||
}
|
||||
if err != windows.ERROR_PIPE_CONNECTED {
|
||||
var transferred uint32
|
||||
if err := windows.GetOverlappedResult(conn.h, &ov, &transferred, true); err != nil {
|
||||
conn.Close()
|
||||
return nil, fmt.Errorf("waiting for client connection: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
Success(`Client connected to \\.\pipe\%s`, name)
|
||||
return newTube(nil, nil, conn, conn), nil
|
||||
}
|
||||
|
||||
// DialPipe connects to a named pipe at \\.\pipe\<name> as a client (the
|
||||
// CreateFile-based counterpart to ServePipe), returning a *Tube wired to
|
||||
// it.
|
||||
func DialPipe(name string) (*Tube, error) {
|
||||
fullName, err := windows.UTF16PtrFromString(`\\.\pipe\` + name)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid pipe name %q: %w", name, err)
|
||||
}
|
||||
|
||||
h, err := windows.CreateFile(
|
||||
fullName,
|
||||
windows.GENERIC_READ|windows.GENERIC_WRITE,
|
||||
0,
|
||||
nil,
|
||||
windows.OPEN_EXISTING,
|
||||
windows.FILE_FLAG_OVERLAPPED,
|
||||
0,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf(`CreateFile(\\.\pipe\%s): %w`, name, err)
|
||||
}
|
||||
|
||||
conn, err := newPipeConn(h)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return newTube(nil, nil, conn, conn), nil
|
||||
}
|
||||
Reference in New Issue
Block a user