Commit d074d35

bryfry <bryon@fryer.io>
2026-01-10 21:27:33
init
go.mod
@@ -0,0 +1,5 @@
+module goVNC
+
+go 1.25.5
+
+require github.com/coder/websocket v1.8.14 // indirect
go.sum
@@ -0,0 +1,2 @@
+github.com/coder/websocket v1.8.14 h1:9L0p0iKiNOibykf283eHkKUHHrpG7f65OE3BhhO7v9g=
+github.com/coder/websocket v1.8.14/go.mod h1:NX3SzP+inril6yawo5CQXx8+fk145lPDC6pumgx0mVg=
main.go
@@ -0,0 +1,250 @@
+package main
+
+import (
+	"context"
+	"encoding/binary"
+	"fmt"
+	"net/http"
+	"net/url"
+	"strings"
+	"time"
+
+	"github.com/coder/websocket"
+)
+
+const (
+	_rfbVersion = "RFB 003.008\n"
+)
+
+func main() {
+	err := run()
+	if err != nil {
+		panic(err)
+	}
+}
+
+func run() error {
+	u := "wss://presidentscup.cisa.gov/ctf/ng/vnc/access/websockify"
+	cookies := "__cflb=04dToQN5TLGPYN3eV6yHVETn6mH6T16iEnZix2HQaY; _ga_NVR9SKL9J6=GS2.1.s1768084717$o1$g0$t1768084717$j60$l0$h0; _ga=GA1.1.1147073997.1768084718; _ga_CSLL4ZEK4L=GS2.1.s1768084717$o1$g0$t1768084720$j57$l0$h0; session=1c5f6578-f062-4792-bce4-4f41735cea53.ikfi-Rx5BJrTWYAElmO0UibEIwc; __cf_bm=3dUIEjS8wuALtvlWPH7L4XPmgrHiuXAPTqC_nXkWdNw-1768097791-1.0.1.1-BtJV8mTvUqeKlI1ua.Ad_7qUAyOb7o8oDZkPUnzIJ2_.L9O24FpqE2Jv5y.cFPgYvIC9ZIDrK61zkhV72NAsvEh4pDqu6PQrdCzKnQoo9.Y"
+	ua := "Mozilla/5.0 (X11; Linux x86_64; rv:140.0) Gecko/20100101 Firefox/140.0"
+
+	wsURL, err := url.Parse(u)
+	if err != nil {
+		err = fmt.Errorf("parsing url=%q: %w", u, err)
+		return err
+	}
+
+	header := http.Header{}
+	header.Add("Cookie", cookies)
+	header.Add("User-Agent", ua)
+
+	ctx := context.Background()
+
+	// Dial(ctx, wsURL, header)
+
+	c, _, err := websocket.Dial(ctx, wsURL.String(), &websocket.DialOptions{
+		HTTPHeader: header,
+	})
+	if err != nil {
+		err = fmt.Errorf("connecting to url=%q: %w", wsURL.String(), err)
+		return err
+	}
+
+	_, b, err := c.Read(ctx)
+	if err != nil {
+		err = fmt.Errorf("reading server version: %w", err)
+		return err
+	}
+	s := strings.TrimSpace(string(b))
+	fmt.Printf("server version: %s %d \n", string(s), len(b))
+	err = c.Write(ctx, websocket.MessageBinary, []byte(_rfbVersion))
+	if err != nil {
+		err = fmt.Errorf("sending client version: %w", err)
+		return err
+	}
+
+	// security handshake
+	_, b, err = c.Read(ctx)
+	if err != nil {
+		err = fmt.Errorf("reading security n: %w", err)
+		return err
+	}
+	if len(b) < 2 {
+		err = fmt.Errorf("unexpected security types message len:%d", len(b))
+		return err
+	}
+	if b[0] != 1 || b[1] != 1 {
+		err = fmt.Errorf("expected none security got count:%d first:%d", b[0], b[1])
+		return err
+	}
+	// select None security
+	err = c.Write(ctx, websocket.MessageBinary, []byte{0x01})
+	if err != nil {
+		err = fmt.Errorf("sending security choice: %w", err)
+		return err
+	}
+	_, b, err = c.Read(ctx)
+	if err != nil {
+		err = fmt.Errorf("reading security handshake result: %w", err)
+		return err
+	}
+	fmt.Printf("security handshake result: %x %d \n", b, len(b))
+
+	// init
+	err = c.Write(ctx, websocket.MessageBinary, []byte{0x01})
+	if err != nil {
+		err = fmt.Errorf("sending client init: %w", err)
+		return err
+	}
+	_, b, err = c.Read(ctx)
+	if err != nil {
+		err = fmt.Errorf("reading server init: %w", err)
+		return err
+	}
+	//skip over RFC6143 ServerInit header and get the name
+	serverName := string(b[2+2+16+4:])
+	sizeWidth := binary.BigEndian.Uint16(b[0:2])
+	sizeHeight := binary.BigEndian.Uint16(b[2:4])
+	fmt.Printf("server init: name=%q width=%d height=%d\n", serverName, sizeWidth, sizeHeight)
+
+	c.SetReadLimit(1024 * 1024 * 64)
+
+	err = SendSetEncodings(ctx, c, []int32{0}) // RAW
+	if err != nil {
+		err = fmt.Errorf("setting raw encoding: %w", err)
+		return err
+	}
+	err = SendFBURequest(ctx, c, false, sizeWidth, sizeHeight)
+
+	for {
+		_, b, err := c.Read(ctx)
+		if err != nil {
+			return err
+		}
+		fmt.Printf("drain: %d", len(b))
+		msg := make([]byte, 10)
+		msg[0] = 3
+		msg[1] = 1
+		err = c.Write(ctx, websocket.MessageBinary, msg)
+		if err != nil {
+			return err
+		}
+	}
+
+	//drainCtx, drainCancel := context.WithCancel(ctx)
+	//defer drainCancel()
+	//go func() {
+	//	err := Drain(drainCtx, c)
+	//	if err != nil {
+	//		slog.Error("drain exited",
+	//			slog.String("error", err.Error()))
+	//	}
+	//}()
+
+	////Type(ctx, c, "ls -al; pwd; whoami; w\n")
+
+	////Tap(ctx, c, uint32('t'))
+	////Tap(ctx, c, uint32('e'))
+	////Tap(ctx, c, uint32('s'))
+	////Tap(ctx, c, uint32('t'))
+	////Tap(ctx, c, 0xff0d) // Return
+
+	//scanner := bufio.NewScanner(os.Stdin)
+	//for scanner.Scan() {
+	//	line := scanner.Text() + "\n"
+	//	err = Type(ctx, c, line)
+	//	if err != nil {
+	//		return err
+	//	}
+	//}
+	//return nil
+}
+
+type vncTyper struct {
+	conn *websocket.Conn
+}
+
+func (vt vncTyper) Write(p []byte) (n int, err error) {
+	return len(p), Type(context.Background(), vt.conn, string(p))
+}
+
+func SendKey(sessionCtx context.Context, c *websocket.Conn, down bool, key uint32) error {
+	msg := make([]byte, 8)
+	msg[0] = 0x04 // KeyEvent
+	if down {
+		msg[1] = 0x01 // downkey
+	}
+
+	ctx, cancel := context.WithTimeout(sessionCtx, 1*time.Second)
+	defer cancel()
+
+	// msg[2:4] padding zeros
+	binary.BigEndian.PutUint32(msg[4:], key)
+	return c.Write(ctx, websocket.MessageBinary, msg)
+}
+
+func Tap(ctx context.Context, c *websocket.Conn, key uint32) error {
+	err := SendKey(ctx, c, true, key)
+	if err != nil {
+		return err
+	}
+	return SendKey(ctx, c, false, key)
+}
+
+func Type(ctx context.Context, c *websocket.Conn, s string) error {
+	fmt.Println("sending", s)
+	const (
+		_keyReturn = 0xff0d
+		_keyTab    = 0xff09
+	)
+	for _, ch := range s {
+		switch ch {
+		case '\n':
+			err := Tap(ctx, c, _keyReturn)
+			if err != nil {
+				return err
+			}
+			continue
+		case '\t':
+			err := Tap(ctx, c, _keyTab)
+			if err != nil {
+				return err
+			}
+			continue
+		default:
+			err := Tap(ctx, c, uint32(ch))
+			if err != nil {
+				return err
+			}
+			continue
+		}
+	}
+	return nil
+}
+
+// SetEncodings: say you support RAW only (encoding 0)
+func SendSetEncodings(ctx context.Context, c *websocket.Conn, encs []int32) error {
+	// type(1)=2, pad(1), count(2), encodings(4*N)
+	msg := make([]byte, 4+4*len(encs))
+	msg[0] = 2
+	binary.BigEndian.PutUint16(msg[2:4], uint16(len(encs)))
+	off := 4
+	for _, e := range encs {
+		binary.BigEndian.PutUint32(msg[off:off+4], uint32(e))
+		off += 4
+	}
+	return c.Write(ctx, websocket.MessageBinary, msg)
+}
+
+// FramebufferUpdateRequest: request full screen once
+func SendFBURequest(ctx context.Context, c *websocket.Conn, incremental bool, w, h uint16) error {
+	msg := make([]byte, 10)
+	msg[0] = 3 // FramebufferUpdateRequest
+	if incremental {
+		msg[1] = 1
+	}
+	// x,y = 0,0
+	binary.BigEndian.PutUint16(msg[6:8], w)
+	binary.BigEndian.PutUint16(msg[8:10], h)
+	return c.Write(ctx, websocket.MessageBinary, msg)
+}
README.md
@@ -0,0 +1,9 @@
+# `goVNC`
+
+Goals:
+ - interact with the noVNC api
+
+## Sources
+
+- https://novnc.com/noVNC/docs/API.html 
+- https://github.com/Haskely/noVNC-client