Files
honeydany/app/services/vnc.go
T

119 lines
4.1 KiB
Go

package services
import (
"bufio"
"encoding/binary"
"net"
"strings"
"time"
)
// NewVNCHandler implements a minimal RFB handshake and then fails auth to keep interaction realistic.
func NewVNCHandler(log LoggerFunc) Handler {
return func(conn net.Conn) {
remote := conn.RemoteAddr().String()
conn.SetDeadline(time.Now().Add(20 * time.Second))
// Send server protocol version banner
serverVer := "RFB 003.008\n"
_, _ = conn.Write([]byte(serverVer))
r := bufio.NewReader(conn)
clientVer, _ := r.ReadString('\n')
clientVer = strings.TrimSpace(clientVer)
if clientVer == "" {
return
}
log(Record{Timestamp: Now(), RemoteAddr: remoteIP(remote), RemotePort: remotePort(remote), Service: "vnc", Details: map[string]string{"event":"version","client":clientVer}})
// Offer two security types: None (1) and VNC Authentication (2)
_, _ = conn.Write([]byte{2, 1, 2})
// Read client's selected security type (1 byte)
b, _ := r.ReadByte()
// If client chose None (1), send SecurityResult OK and minimal ServerInit
if b == 1 {
// OK result
_, _ = conn.Write([]byte{0, 0, 0, 0})
// ClientInit (read but ignore)
_, _ = r.ReadByte()
// Send ServerInit: framebuffer width/height, pixel format, name length + name
// Width=800, Height=600
srv := make([]byte, 0, 24)
srv = append(srv, 0x03, 0x20) // width 800
srv = append(srv, 0x02, 0x58) // height 600
// Pixel format (16 bytes): 32bpp, 24 depth, big endian=0, trueColor=1, max red/green/blue, shifts
srv = append(srv, 32, 24, 0, 1, 0x00, 0xff, 0x00, 0xff, 0x00, 0xff, 16, 8, 0)
// pad 3 bytes
srv = append(srv, 0, 0, 0)
// Name length and name
name := []byte("Ubuntu VNC")
nameLen := []byte{0, 0, byte(len(name) >> 8), byte(len(name) & 0xff)}
// Actually RFB uses 32-bit name length big-endian
nameLen = []byte{0, 0, 0, byte(len(name))}
srv = append(srv, nameLen...)
srv = append(srv, name...)
_, _ = conn.Write(srv)
// Enter a simple loop to keep client engaged: respond to FramebufferUpdateRequest (type=3)
for {
// Read message type
mt, err := r.ReadByte()
if err != nil { return }
switch mt {
case 0: // SetPixelFormat: skip 3 pad + 16 bytes
skip := make([]byte, 19)
if _, err := r.Read(skip); err != nil { return }
case 2: // SetEncodings: 1 pad + int16 num + list
pad, _ := r.ReadByte(); _ = pad
Hdr := make([]byte, 2)
if _, err := r.Read(Hdr); err != nil { return }
nEnc := int(binary.BigEndian.Uint16(Hdr))
encs := make([]byte, nEnc*4)
if _, err := r.Read(encs); err != nil { return }
case 3: // FramebufferUpdateRequest
req := make([]byte, 9)
if _, err := r.Read(req); err != nil { return }
// incremental := req[0]
// x,y,w,h (big-endian)
// Respond with one small RAW rectangle 64x32 at 0,0
reply := make([]byte, 0, 4+12+64*32*4)
// message-type=0 (FramebufferUpdate) + pad
reply = append(reply, 0, 0)
// number-of-rectangles = 1
reply = append(reply, 0, 1)
// x=0,y=0,w=64,h=32
rb := make([]byte, 12)
// x,y
binary.BigEndian.PutUint16(rb[0:2], 0)
binary.BigEndian.PutUint16(rb[2:4], 0)
// w,h
binary.BigEndian.PutUint16(rb[4:6], 64)
binary.BigEndian.PutUint16(rb[6:8], 32)
// encoding RAW = 0
binary.BigEndian.PutUint32(rb[8:12], 0)
reply = append(reply, rb...)
// pixel data (32bpp). Simple grey fill (e.g., 0x202020ff little-endian if needed)
px := make([]byte, 64*32*4)
for i := 0; i < len(px); i += 4 {
// BGRA for many viewers when little endian flag=0; we keep it neutral
px[i+0] = 0x20
px[i+1] = 0x20
px[i+2] = 0x20
px[i+3] = 0xFF
}
reply = append(reply, px...)
if _, err := conn.Write(reply); err != nil { return }
default:
// Unknown or unhandled; try to keep reading, but bail on error
}
}
return
}
// Otherwise (e.g., VNC Auth), send failure
_, _ = conn.Write([]byte{0, 0, 0, 1})
reason := []byte("Authentication failed")
msg := append([]byte{0, 0, 0, byte(len(reason))}, reason...)
_, _ = conn.Write(msg)
}
}