Commit d074d35
2026-01-10 21:27:33
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