diff --git a/README.md b/README.md index 5b6bd1387..910f7c3d2 100644 --- a/README.md +++ b/README.md @@ -20,8 +20,9 @@ Code-level optimization, parameter optimization, and individual modules, such as vscan filefuzz, have been rewritten for these integrated projects. In principle, do not repeat the wheel, unless there are bugs, problems - Cross-platform: based on golang implementation, lightweight, highly customizable, open source, supports Linux, windows, mac os, etc. -- Support [22] password blasting, support custom dictionary, open by "priorityNmap": true +- Support [23] password blasting, support custom dictionary, open by "priorityNmap": true * RDP + * VNC * SSH * Socks5 * rsh-spx diff --git a/README_CN.md b/README_CN.md index 258d5971c..95e67f17d 100644 --- a/README_CN.md +++ b/README_CN.md @@ -11,7 +11,7 @@

# 特性 -Vulnerabilities Scan;15000+PoC漏洞扫描;22种应用弱口令爆破;7000+Web指纹;146种协议90000+规则Port扫描;Fuzz、HW打点、BugBounty神器... +Vulnerabilities Scan;15000+PoC漏洞扫描;[ 23 ] 种应用弱口令爆破;7000+Web指纹;146种协议90000+规则Port扫描;Fuzz、HW打点、BugBounty神器...

image

@@ -20,8 +20,9 @@ Vulnerabilities Scan;15000+PoC漏洞扫描;22种应用弱口令爆破;7000 并对这些集成的项目进行代码级别优化、参数优化,个别模块,如 vscan filefuzz部分进行了重写 原则上不重复造轮子,除非存在bug、问题 - 跨平台:基于golang实现,轻量级、高度可定制、开源,支持Linux、windows、mac os等 -- 支持【22】种密码爆破,支持自定义字典, 通过 "priorityNmap": true 开启 +- 支持[ 23 ] 种密码爆破,支持自定义字典, 通过 "priorityNmap": true 开启 * RDP + * VNC * SSH * Socks5 * rsh-spx diff --git a/pkg/hydra/cracker.go b/pkg/hydra/cracker.go index df4a677ef..961a6c4a5 100644 --- a/pkg/hydra/cracker.go +++ b/pkg/hydra/cracker.go @@ -18,6 +18,7 @@ import ( "github.com/hktalent/scan4all/pkg/hydra/socks5" "github.com/hktalent/scan4all/pkg/hydra/ssh" "github.com/hktalent/scan4all/pkg/hydra/telnet" + "github.com/hktalent/scan4all/pkg/hydra/vnc" "github.com/hktalent/scan4all/pkg/hydra/winrm" "github.com/hktalent/scan4all/pkg/kscan/core/slog" "github.com/hktalent/scan4all/pkg/kscan/lib/gotelnet" @@ -66,6 +67,13 @@ func Socks5Cracker(i interface{}) interface{} { } return nil } +func VncCracker(i interface{}) interface{} { + info := i.(AuthInfo) + if ok, _ := vnc.Check(info.IPAddr, info.Auth.Username, info.Auth.Password, info.Port); ok { + return info + } + return nil +} func sshCracker(i interface{}) interface{} { info := i.(AuthInfo) diff --git a/pkg/hydra/doNmapResult.go b/pkg/hydra/doNmapResult.go index d5bd91c25..b06f56302 100644 --- a/pkg/hydra/doNmapResult.go +++ b/pkg/hydra/doNmapResult.go @@ -84,7 +84,7 @@ func DoParseXml(s string, bf *bytes.Buffer) { m1[ip] = append(xx09, []string{szPort, service}) } if os.Getenv("NoPOC") != "true" { - if "socks5" == service { + if "socks5" == service || "vnc" == service { CheckWeakPassword(ip, service, port) } else if "445" == szPort && service == "microsoft-ds" || "135" == szPort && service == "msrpc" { util.PocCheck_pipe <- &util.PocCheck{ diff --git a/pkg/hydra/hydra.go b/pkg/hydra/hydra.go index 24de3ac58..f11166e3a 100644 --- a/pkg/hydra/hydra.go +++ b/pkg/hydra/hydra.go @@ -22,7 +22,7 @@ var ( CustomAuthMap *AuthList // rtsp://admin:admin@192.168.0.111:554/0x8b6c42 // rtsp: 554, 5554,8554 - ProtocolList = strings.Split("rdp,ssh,rsh-spx,mysql,mssql,oracle,postgresql,redis,ftp,mongodb,mongod,smb,telnet,snmp,wap-wsp,router,winrm,pop3,socks5", ",") + ProtocolList = strings.Split("rdp,ssh,rsh-spx,mysql,mssql,oracle,postgresql,redis,ftp,mongodb,mongod,smb,telnet,snmp,wap-wsp,router,winrm,pop3,socks5,vnc", ",") ) func NewCracker(info *AuthInfo, isAuthUpdate bool, threads int) *Cracker { @@ -82,6 +82,8 @@ func (c *Cracker) Run() { c.Pool.Function = postgresqlCracker case "socks5": c.Pool.Function = Socks5Cracker + case "vnc": + c.Pool.Function = VncCracker case "ldap", "rsh-spx", "ssh": c.Pool.Function = sshCracker case "telnet": diff --git a/pkg/hydra/loadDicts.go b/pkg/hydra/loadDicts.go index fdadcfaad..5902e2815 100644 --- a/pkg/hydra/loadDicts.go +++ b/pkg/hydra/loadDicts.go @@ -145,6 +145,7 @@ func init() { } md["pop3"] = md["ssh"] md["socks5"] = md["ssh"] + md["vnc"] = md["ssh"] md["rsh-spx"] = md["ssh"] md["snmp"] = &PPDict{ Username: util.GetVal4File("snmp_user", snmp_user), diff --git a/pkg/hydra/vnc/checkvnc.go b/pkg/hydra/vnc/checkvnc.go new file mode 100644 index 000000000..53227133a --- /dev/null +++ b/pkg/hydra/vnc/checkvnc.go @@ -0,0 +1,20 @@ +package vnc + +import ( + "fmt" + "net" +) + +func Check(Host, Username, Password string, Port int) (bool, error) { + nc, err := net.Dial("tcp", fmt.Sprintf("%s:%d", Host, Port)) + if err != nil { + return false, err + } + cc1, err := Client(nc, &ClientConfig{Auth: []ClientAuth{&PasswordAuth{Password: Password}}}) + if err != nil { + return false, err + } else { + cc1.Close() + return true, nil + } +} diff --git a/pkg/hydra/vnc/client.go b/pkg/hydra/vnc/client.go new file mode 100644 index 000000000..42f59bfaf --- /dev/null +++ b/pkg/hydra/vnc/client.go @@ -0,0 +1,478 @@ +package vnc + +import ( + "bytes" + "encoding/binary" + "fmt" + "io" + "net" + "unicode" +) + +type ClientConn struct { + c net.Conn + config *ClientConfig + + // If the pixel format uses a color map, then this is the color + // map that is used. This should not be modified directly, since + // the data comes from the server. + ColorMap [256]Color + + // Encodings supported by the client. This should not be modified + // directly. Instead, SetEncodings should be used. + Encs []Encoding + + // Width of the frame buffer in pixels, sent from the server. + FrameBufferWidth uint16 + + // Height of the frame buffer in pixels, sent from the server. + FrameBufferHeight uint16 + + // Name associated with the desktop, sent from the server. + DesktopName string + + // The pixel format associated with the connection. This shouldn't + // be modified. If you wish to set a new pixel format, use the + // SetPixelFormat method. + PixelFormat PixelFormat +} + +// A ClientConfig structure is used to configure a ClientConn. After +// one has been passed to initialize a connection, it must not be modified. +type ClientConfig struct { + // A slice of ClientAuth methods. Only the first instance that is + // suitable by the server will be used to authenticate. + Auth []ClientAuth + + // Exclusive determines whether the connection is shared with other + // clients. If true, then all other clients connected will be + // disconnected when a connection is established to the VNC server. + Exclusive bool + + // The channel that all messages received from the server will be + // sent on. If the channel blocks, then the goroutine reading data + // from the VNC server may block indefinitely. It is up to the user + // of the library to ensure that this channel is properly read. + // If this is not set, then all messages will be discarded. + ServerMessageCh chan<- ServerMessage + + // A slice of supported messages that can be read from the server. + // This only needs to contain NEW server messages, and doesn't + // need to explicitly contain the RFC-required messages. + ServerMessages []ServerMessage +} + +func Client(c net.Conn, cfg *ClientConfig) (*ClientConn, error) { + conn := &ClientConn{ + c: c, + config: cfg, + } + + if err := conn.handshake(); err != nil { + conn.Close() + return nil, err + } + + go conn.mainLoop() + + return conn, nil +} + +func (c *ClientConn) Close() error { + return c.c.Close() +} + +// CutText tells the server that the client has new text in its cut buffer. +// The text string MUST only contain Latin-1 characters. This encoding +// is compatible with Go's native string format, but can only use up to +// unicode.MaxLatin values. +// +// See RFC 6143 Section 7.5.6 +func (c *ClientConn) CutText(text string) error { + var buf bytes.Buffer + + // This is the fixed size data we'll send + fixedData := []interface{}{ + uint8(6), + uint8(0), + uint8(0), + uint8(0), + uint32(len(text)), + } + + for _, val := range fixedData { + if err := binary.Write(&buf, binary.BigEndian, val); err != nil { + return err + } + } + + for _, char := range text { + if char > unicode.MaxLatin1 { + return fmt.Errorf("Character '%s' is not valid Latin-1", char) + } + + if err := binary.Write(&buf, binary.BigEndian, uint8(char)); err != nil { + return err + } + } + + dataLength := 8 + len(text) + if _, err := c.c.Write(buf.Bytes()[0:dataLength]); err != nil { + return err + } + + return nil +} + +// Requests a framebuffer update from the server. There may be an indefinite +// time between the request and the actual framebuffer update being +// received. +// +// See RFC 6143 Section 7.5.3 +func (c *ClientConn) FramebufferUpdateRequest(incremental bool, x, y, width, height uint16) error { + var buf bytes.Buffer + var incrementalByte uint8 = 0 + + if incremental { + incrementalByte = 1 + } + + data := []interface{}{ + uint8(3), + incrementalByte, + x, y, width, height, + } + + for _, val := range data { + if err := binary.Write(&buf, binary.BigEndian, val); err != nil { + return err + } + } + + if _, err := c.c.Write(buf.Bytes()[0:10]); err != nil { + return err + } + + return nil +} + +// KeyEvent indiciates a key press or release and sends it to the server. +// The key is indicated using the X Window System "keysym" value. Use +// Google to find a reference of these values. To simulate a key press, +// you must send a key with both a down event, and a non-down event. +// +// See 7.5.4. +func (c *ClientConn) KeyEvent(keysym uint32, down bool) error { + var downFlag uint8 = 0 + if down { + downFlag = 1 + } + + data := []interface{}{ + uint8(4), + downFlag, + uint8(0), + uint8(0), + keysym, + } + + for _, val := range data { + if err := binary.Write(c.c, binary.BigEndian, val); err != nil { + return err + } + } + + return nil +} + +// PointerEvent indicates that pointer movement or a pointer button +// press or release. +// +// The mask is a bitwise mask of various ButtonMask values. When a button +// is set, it is pressed, when it is unset, it is released. +// +// See RFC 6143 Section 7.5.5 +func (c *ClientConn) PointerEvent(mask ButtonMask, x, y uint16) error { + var buf bytes.Buffer + + data := []interface{}{ + uint8(5), + uint8(mask), + x, + y, + } + + for _, val := range data { + if err := binary.Write(&buf, binary.BigEndian, val); err != nil { + return err + } + } + + if _, err := c.c.Write(buf.Bytes()[0:6]); err != nil { + return err + } + + return nil +} + +// SetEncodings sets the encoding types in which the pixel data can +// be sent from the server. After calling this method, the encs slice +// given should not be modified. +// +// See RFC 6143 Section 7.5.2 +func (c *ClientConn) SetEncodings(encs []Encoding) error { + data := make([]interface{}, 3+len(encs)) + data[0] = uint8(2) + data[1] = uint8(0) + data[2] = uint16(len(encs)) + + for i, enc := range encs { + data[3+i] = int32(enc.Type()) + } + + var buf bytes.Buffer + for _, val := range data { + if err := binary.Write(&buf, binary.BigEndian, val); err != nil { + return err + } + } + + dataLength := 4 + (4 * len(encs)) + if _, err := c.c.Write(buf.Bytes()[0:dataLength]); err != nil { + return err + } + + c.Encs = encs + + return nil +} + +// SetPixelFormat sets the format in which pixel values should be sent +// in FramebufferUpdate messages from the server. +// +// See RFC 6143 Section 7.5.1 +func (c *ClientConn) SetPixelFormat(format *PixelFormat) error { + var keyEvent [20]byte + keyEvent[0] = 0 + + pfBytes, err := writePixelFormat(format) + if err != nil { + return err + } + + // Copy the pixel format bytes into the proper slice location + copy(keyEvent[4:], pfBytes) + + // Send the data down the connection + if _, err := c.c.Write(keyEvent[:]); err != nil { + return err + } + + // Reset the color map as according to RFC. + var newColorMap [256]Color + c.ColorMap = newColorMap + + return nil +} + +const pvLen = 12 // ProtocolVersion message length. + +func parseProtocolVersion(pv []byte) (uint, uint, error) { + var major, minor uint + + if len(pv) < pvLen { + return 0, 0, fmt.Errorf("ProtocolVersion message too short (%v < %v)", len(pv), pvLen) + } + + l, err := fmt.Sscanf(string(pv), "RFB %d.%d\n", &major, &minor) + if l != 2 { + return 0, 0, fmt.Errorf("error parsing ProtocolVersion.") + } + if err != nil { + return 0, 0, err + } + + return major, minor, nil +} + +func (c *ClientConn) handshake() error { + var protocolVersion [pvLen]byte + + // 7.1.1, read the ProtocolVersion message sent by the server. + if _, err := io.ReadFull(c.c, protocolVersion[:]); err != nil { + return err + } + + maxMajor, maxMinor, err := parseProtocolVersion(protocolVersion[:]) + if err != nil { + return err + } + if maxMajor < 3 { + return fmt.Errorf("unsupported major version, less than 3: %d", maxMajor) + } + if maxMinor < 8 { + return fmt.Errorf("unsupported minor version, less than 8: %d", maxMinor) + } + + // Respond with the version we will support + if _, err = c.c.Write([]byte("RFB 003.008\n")); err != nil { + return err + } + + // 7.1.2 Security Handshake from server + var numSecurityTypes uint8 + if err = binary.Read(c.c, binary.BigEndian, &numSecurityTypes); err != nil { + return err + } + + if numSecurityTypes == 0 { + return fmt.Errorf("no security types: %s", c.readErrorReason()) + } + + securityTypes := make([]uint8, numSecurityTypes) + if err = binary.Read(c.c, binary.BigEndian, &securityTypes); err != nil { + return err + } + + clientSecurityTypes := c.config.Auth + if clientSecurityTypes == nil { + clientSecurityTypes = []ClientAuth{new(ClientAuthNone)} + } + + var auth ClientAuth +FindAuth: + for _, curAuth := range clientSecurityTypes { + for _, securityType := range securityTypes { + if curAuth.SecurityType() == securityType { + // We use the first matching supported authentication + auth = curAuth + break FindAuth + } + } + } + + if auth == nil { + return fmt.Errorf("no suitable auth schemes found. server supported: %#v", securityTypes) + } + + // Respond back with the security type we'll use + if err = binary.Write(c.c, binary.BigEndian, auth.SecurityType()); err != nil { + return err + } + + if err = auth.Handshake(c.c); err != nil { + return err + } + + // 7.1.3 SecurityResult Handshake + var securityResult uint32 + if err = binary.Read(c.c, binary.BigEndian, &securityResult); err != nil { + return err + } + + if securityResult == 1 { + return fmt.Errorf("security handshake failed: %s", c.readErrorReason()) + } + + // 7.3.1 ClientInit + var sharedFlag uint8 = 1 + if c.config.Exclusive { + sharedFlag = 0 + } + + if err = binary.Write(c.c, binary.BigEndian, sharedFlag); err != nil { + return err + } + + // 7.3.2 ServerInit + if err = binary.Read(c.c, binary.BigEndian, &c.FrameBufferWidth); err != nil { + return err + } + + if err = binary.Read(c.c, binary.BigEndian, &c.FrameBufferHeight); err != nil { + return err + } + + // Read the pixel format + if err = readPixelFormat(c.c, &c.PixelFormat); err != nil { + return err + } + + var nameLength uint32 + if err = binary.Read(c.c, binary.BigEndian, &nameLength); err != nil { + return err + } + + nameBytes := make([]uint8, nameLength) + if err = binary.Read(c.c, binary.BigEndian, &nameBytes); err != nil { + return err + } + + c.DesktopName = string(nameBytes) + + return nil +} + +// mainLoop reads messages sent from the server and routes them to the +// proper channels for users of the client to read. +func (c *ClientConn) mainLoop() { + defer c.Close() + + // Build the map of available server messages + typeMap := make(map[uint8]ServerMessage) + + defaultMessages := []ServerMessage{ + new(FramebufferUpdateMessage), + new(SetColorMapEntriesMessage), + new(BellMessage), + new(ServerCutTextMessage), + } + + for _, msg := range defaultMessages { + typeMap[msg.Type()] = msg + } + + if c.config.ServerMessages != nil { + for _, msg := range c.config.ServerMessages { + typeMap[msg.Type()] = msg + } + } + + for { + var messageType uint8 + if err := binary.Read(c.c, binary.BigEndian, &messageType); err != nil { + break + } + + msg, ok := typeMap[messageType] + if !ok { + // Unsupported message type! Bad! + break + } + + parsedMsg, err := msg.Read(c, c.c) + if err != nil { + break + } + + if c.config.ServerMessageCh == nil { + continue + } + + c.config.ServerMessageCh <- parsedMsg + } +} + +func (c *ClientConn) readErrorReason() string { + var reasonLen uint32 + if err := binary.Read(c.c, binary.BigEndian, &reasonLen); err != nil { + return "" + } + + reason := make([]uint8, reasonLen) + if err := binary.Read(c.c, binary.BigEndian, &reason); err != nil { + return "" + } + + return string(reason) +} diff --git a/pkg/hydra/vnc/client_auth.go b/pkg/hydra/vnc/client_auth.go new file mode 100644 index 000000000..601e470ed --- /dev/null +++ b/pkg/hydra/vnc/client_auth.go @@ -0,0 +1,124 @@ +package vnc + +import ( + "net" + + "crypto/des" + "encoding/binary" +) + +// A ClientAuth implements a method of authenticating with a remote server. +type ClientAuth interface { + // SecurityType returns the byte identifier sent by the server to + // identify this authentication scheme. + SecurityType() uint8 + + // Handshake is called when the authentication handshake should be + // performed, as part of the general RFB handshake. (see 7.2.1) + Handshake(net.Conn) error +} + +// ClientAuthNone is the "none" authentication. See 7.2.1 +type ClientAuthNone byte + +func (*ClientAuthNone) SecurityType() uint8 { + return 1 +} + +func (*ClientAuthNone) Handshake(net.Conn) error { + return nil +} + +// PasswordAuth is VNC authentication, 7.2.2 +type PasswordAuth struct { + Password string +} + +func (p *PasswordAuth) SecurityType() uint8 { + return 2 +} + +func (p *PasswordAuth) Handshake(c net.Conn) error { + randomValue := make([]uint8, 16) + if err := binary.Read(c, binary.BigEndian, &randomValue); err != nil { + return err + } + + crypted, err := p.encrypt(p.Password, randomValue) + + if err != nil { + return err + } + + if err := binary.Write(c, binary.BigEndian, &crypted); err != nil { + return err + } + + return nil +} + +func (p *PasswordAuth) reverseBits(b byte) byte { + var reverse = [256]int{ + 0, 128, 64, 192, 32, 160, 96, 224, + 16, 144, 80, 208, 48, 176, 112, 240, + 8, 136, 72, 200, 40, 168, 104, 232, + 24, 152, 88, 216, 56, 184, 120, 248, + 4, 132, 68, 196, 36, 164, 100, 228, + 20, 148, 84, 212, 52, 180, 116, 244, + 12, 140, 76, 204, 44, 172, 108, 236, + 28, 156, 92, 220, 60, 188, 124, 252, + 2, 130, 66, 194, 34, 162, 98, 226, + 18, 146, 82, 210, 50, 178, 114, 242, + 10, 138, 74, 202, 42, 170, 106, 234, + 26, 154, 90, 218, 58, 186, 122, 250, + 6, 134, 70, 198, 38, 166, 102, 230, + 22, 150, 86, 214, 54, 182, 118, 246, + 14, 142, 78, 206, 46, 174, 110, 238, + 30, 158, 94, 222, 62, 190, 126, 254, + 1, 129, 65, 193, 33, 161, 97, 225, + 17, 145, 81, 209, 49, 177, 113, 241, + 9, 137, 73, 201, 41, 169, 105, 233, + 25, 153, 89, 217, 57, 185, 121, 249, + 5, 133, 69, 197, 37, 165, 101, 229, + 21, 149, 85, 213, 53, 181, 117, 245, + 13, 141, 77, 205, 45, 173, 109, 237, + 29, 157, 93, 221, 61, 189, 125, 253, + 3, 131, 67, 195, 35, 163, 99, 227, + 19, 147, 83, 211, 51, 179, 115, 243, + 11, 139, 75, 203, 43, 171, 107, 235, + 27, 155, 91, 219, 59, 187, 123, 251, + 7, 135, 71, 199, 39, 167, 103, 231, + 23, 151, 87, 215, 55, 183, 119, 247, + 15, 143, 79, 207, 47, 175, 111, 239, + 31, 159, 95, 223, 63, 191, 127, 255, + } + + return byte(reverse[int(b)]) +} + +func (p *PasswordAuth) encrypt(key string, bytes []byte) ([]byte, error) { + keyBytes := []byte{0, 0, 0, 0, 0, 0, 0, 0} + + if len(key) > 8 { + key = key[:8] + } + + for i := 0; i < len(key); i++ { + keyBytes[i] = p.reverseBits(key[i]) + } + + block, err := des.NewCipher(keyBytes) + + if err != nil { + return nil, err + } + + result1 := make([]byte, 8) + block.Encrypt(result1, bytes) + result2 := make([]byte, 8) + block.Encrypt(result2, bytes[8:]) + + crypted := append(result1, result2...) + + return crypted, nil +} diff --git a/pkg/hydra/vnc/pixel_format.go b/pkg/hydra/vnc/pixel_format.go new file mode 100644 index 000000000..ff8edbff5 --- /dev/null +++ b/pkg/hydra/vnc/pixel_format.go @@ -0,0 +1,151 @@ +package vnc + +import ( + "bytes" + "encoding/binary" + "io" +) + +// PixelFormat describes the way a pixel is formatted for a VNC connection. +// +// See RFC 6143 Section 7.4 for information on each of the fields. +type PixelFormat struct { + BPP uint8 + Depth uint8 + BigEndian bool + TrueColor bool + RedMax uint16 + GreenMax uint16 + BlueMax uint16 + RedShift uint8 + GreenShift uint8 + BlueShift uint8 +} + +func readPixelFormat(r io.Reader, result *PixelFormat) error { + var rawPixelFormat [16]byte + if _, err := io.ReadFull(r, rawPixelFormat[:]); err != nil { + return err + } + + var pfBoolByte uint8 + brPF := bytes.NewReader(rawPixelFormat[:]) + if err := binary.Read(brPF, binary.BigEndian, &result.BPP); err != nil { + return err + } + + if err := binary.Read(brPF, binary.BigEndian, &result.Depth); err != nil { + return err + } + + if err := binary.Read(brPF, binary.BigEndian, &pfBoolByte); err != nil { + return err + } + + if pfBoolByte != 0 { + // Big endian is true + result.BigEndian = true + } + + if err := binary.Read(brPF, binary.BigEndian, &pfBoolByte); err != nil { + return err + } + + if pfBoolByte != 0 { + // True Color is true. So we also have to read all the color max & shifts. + result.TrueColor = true + + if err := binary.Read(brPF, binary.BigEndian, &result.RedMax); err != nil { + return err + } + + if err := binary.Read(brPF, binary.BigEndian, &result.GreenMax); err != nil { + return err + } + + if err := binary.Read(brPF, binary.BigEndian, &result.BlueMax); err != nil { + return err + } + + if err := binary.Read(brPF, binary.BigEndian, &result.RedShift); err != nil { + return err + } + + if err := binary.Read(brPF, binary.BigEndian, &result.GreenShift); err != nil { + return err + } + + if err := binary.Read(brPF, binary.BigEndian, &result.BlueShift); err != nil { + return err + } + } + + return nil +} + +func writePixelFormat(format *PixelFormat) ([]byte, error) { + var buf bytes.Buffer + + // Byte 1 + if err := binary.Write(&buf, binary.BigEndian, format.BPP); err != nil { + return nil, err + } + + // Byte 2 + if err := binary.Write(&buf, binary.BigEndian, format.Depth); err != nil { + return nil, err + } + + var boolByte byte + if format.BigEndian { + boolByte = 1 + } else { + boolByte = 0 + } + + // Byte 3 (BigEndian) + if err := binary.Write(&buf, binary.BigEndian, boolByte); err != nil { + return nil, err + } + + if format.TrueColor { + boolByte = 1 + } else { + boolByte = 0 + } + + // Byte 4 (TrueColor) + if err := binary.Write(&buf, binary.BigEndian, boolByte); err != nil { + return nil, err + } + + // If we have true color enabled then we have to fill in the rest of the + // structure with the color values. + if format.TrueColor { + if err := binary.Write(&buf, binary.BigEndian, format.RedMax); err != nil { + return nil, err + } + + if err := binary.Write(&buf, binary.BigEndian, format.GreenMax); err != nil { + return nil, err + } + + if err := binary.Write(&buf, binary.BigEndian, format.BlueMax); err != nil { + return nil, err + } + + if err := binary.Write(&buf, binary.BigEndian, format.RedShift); err != nil { + return nil, err + } + + if err := binary.Write(&buf, binary.BigEndian, format.GreenShift); err != nil { + return nil, err + } + + if err := binary.Write(&buf, binary.BigEndian, format.BlueShift); err != nil { + return nil, err + } + } + + return buf.Bytes()[0:16], nil +} diff --git a/pkg/hydra/vnc/pointer.go b/pkg/hydra/vnc/pointer.go new file mode 100644 index 000000000..f4f66fc77 --- /dev/null +++ b/pkg/hydra/vnc/pointer.go @@ -0,0 +1,87 @@ +package vnc + +import ( + "encoding/binary" + "io" +) + +type ButtonMask uint8 + +// All available button mask components. +const ( + ButtonLeft ButtonMask = 1 << iota + ButtonMiddle + ButtonRight + Button4 + Button5 + Button6 + Button7 + Button8 +) + +type Color struct { + R, G, B uint16 +} + +// An Encoding implements a method for encoding pixel data that is +// sent by the server to the client. +type Encoding interface { + // The number that uniquely identifies this encoding type. + Type() int32 + + // Read reads the contents of the encoded pixel data from the reader. + // This should return a new Encoding implementation that contains + // the proper data. + Read(*ClientConn, *Rectangle, io.Reader) (Encoding, error) +} + +// RawEncoding is raw pixel data sent by the server. +// +// See RFC 6143 Section 7.7.1 +type RawEncoding struct { + Colors []Color +} + +func (*RawEncoding) Type() int32 { + return 0 +} + +func (*RawEncoding) Read(c *ClientConn, rect *Rectangle, r io.Reader) (Encoding, error) { + bytesPerPixel := c.PixelFormat.BPP / 8 + pixelBytes := make([]uint8, bytesPerPixel) + + var byteOrder binary.ByteOrder = binary.LittleEndian + if c.PixelFormat.BigEndian { + byteOrder = binary.BigEndian + } + + colors := make([]Color, int(rect.Height)*int(rect.Width)) + + for y := uint16(0); y < rect.Height; y++ { + for x := uint16(0); x < rect.Width; x++ { + if _, err := io.ReadFull(r, pixelBytes); err != nil { + return nil, err + } + + var rawPixel uint32 + if c.PixelFormat.BPP == 8 { + rawPixel = uint32(pixelBytes[0]) + } else if c.PixelFormat.BPP == 16 { + rawPixel = uint32(byteOrder.Uint16(pixelBytes)) + } else if c.PixelFormat.BPP == 32 { + rawPixel = byteOrder.Uint32(pixelBytes) + } + + color := &colors[int(y)*int(rect.Width)+int(x)] + if c.PixelFormat.TrueColor { + color.R = uint16((rawPixel >> c.PixelFormat.RedShift) & uint32(c.PixelFormat.RedMax)) + color.G = uint16((rawPixel >> c.PixelFormat.GreenShift) & uint32(c.PixelFormat.GreenMax)) + color.B = uint16((rawPixel >> c.PixelFormat.BlueShift) & uint32(c.PixelFormat.BlueMax)) + } else { + *color = c.ColorMap[rawPixel] + } + } + } + + return &RawEncoding{colors}, nil +} diff --git a/pkg/hydra/vnc/server_messages.go b/pkg/hydra/vnc/server_messages.go new file mode 100644 index 000000000..c0aae5665 --- /dev/null +++ b/pkg/hydra/vnc/server_messages.go @@ -0,0 +1,192 @@ +package vnc + +import ( + "encoding/binary" + "fmt" + "io" +) + +// A ServerMessage implements a message sent from the server to the client. +type ServerMessage interface { + // The type of the message that is sent down on the wire. + Type() uint8 + + // Read reads the contents of the message from the reader. At the point + // this is called, the message type has already been read from the reader. + // This should return a new ServerMessage that is the appropriate type. + Read(*ClientConn, io.Reader) (ServerMessage, error) +} + +// FramebufferUpdateMessage consists of a sequence of rectangles of +// pixel data that the client should put into its framebuffer. +type FramebufferUpdateMessage struct { + Rectangles []Rectangle +} + +// Rectangle represents a rectangle of pixel data. +type Rectangle struct { + X uint16 + Y uint16 + Width uint16 + Height uint16 + Enc Encoding +} + +func (*FramebufferUpdateMessage) Type() uint8 { + return 0 +} + +func (*FramebufferUpdateMessage) Read(c *ClientConn, r io.Reader) (ServerMessage, error) { + // Read off the padding + var padding [1]byte + if _, err := io.ReadFull(r, padding[:]); err != nil { + return nil, err + } + + var numRects uint16 + if err := binary.Read(r, binary.BigEndian, &numRects); err != nil { + return nil, err + } + + // Build the map of encodings supported + encMap := make(map[int32]Encoding) + for _, enc := range c.Encs { + encMap[enc.Type()] = enc + } + + // We must always support the raw encoding + rawEnc := new(RawEncoding) + encMap[rawEnc.Type()] = rawEnc + + rects := make([]Rectangle, numRects) + for i := uint16(0); i < numRects; i++ { + var encodingType int32 + + rect := &rects[i] + data := []interface{}{ + &rect.X, + &rect.Y, + &rect.Width, + &rect.Height, + &encodingType, + } + + for _, val := range data { + if err := binary.Read(r, binary.BigEndian, val); err != nil { + return nil, err + } + } + + enc, ok := encMap[encodingType] + if !ok { + return nil, fmt.Errorf("unsupported encoding type: %d", encodingType) + } + + var err error + rect.Enc, err = enc.Read(c, rect, r) + if err != nil { + return nil, err + } + } + + return &FramebufferUpdateMessage{rects}, nil +} + +// SetColorMapEntriesMessage is sent by the server to set values into +// the color map. This message will automatically update the color map +// for the associated connection, but contains the color change data +// if the consumer wants to read it. +// +// See RFC 6143 Section 7.6.2 +type SetColorMapEntriesMessage struct { + FirstColor uint16 + Colors []Color +} + +func (*SetColorMapEntriesMessage) Type() uint8 { + return 1 +} + +func (*SetColorMapEntriesMessage) Read(c *ClientConn, r io.Reader) (ServerMessage, error) { + // Read off the padding + var padding [1]byte + if _, err := io.ReadFull(r, padding[:]); err != nil { + return nil, err + } + + var result SetColorMapEntriesMessage + if err := binary.Read(r, binary.BigEndian, &result.FirstColor); err != nil { + return nil, err + } + + var numColors uint16 + if err := binary.Read(r, binary.BigEndian, &numColors); err != nil { + return nil, err + } + + result.Colors = make([]Color, numColors) + for i := uint16(0); i < numColors; i++ { + + color := &result.Colors[i] + data := []interface{}{ + &color.R, + &color.G, + &color.B, + } + + for _, val := range data { + if err := binary.Read(r, binary.BigEndian, val); err != nil { + return nil, err + } + } + + // Update the connection's color map + c.ColorMap[result.FirstColor+i] = *color + } + + return &result, nil +} + +// Bell signals that an audible bell should be made on the client. +// +// See RFC 6143 Section 7.6.3 +type BellMessage byte + +func (*BellMessage) Type() uint8 { + return 2 +} + +func (*BellMessage) Read(*ClientConn, io.Reader) (ServerMessage, error) { + return new(BellMessage), nil +} + +// ServerCutTextMessage indicates the server has new text in the cut buffer. +// +// See RFC 6143 Section 7.6.4 +type ServerCutTextMessage struct { + Text string +} + +func (*ServerCutTextMessage) Type() uint8 { + return 3 +} + +func (*ServerCutTextMessage) Read(c *ClientConn, r io.Reader) (ServerMessage, error) { + // Read off the padding + var padding [1]byte + if _, err := io.ReadFull(r, padding[:]); err != nil { + return nil, err + } + + var textLength uint32 + if err := binary.Read(r, binary.BigEndian, &textLength); err != nil { + return nil, err + } + + textBytes := make([]uint8, textLength) + if err := binary.Read(r, binary.BigEndian, &textBytes); err != nil { + return nil, err + } + + return &ServerCutTextMessage{string(textBytes)}, nil +} diff --git a/test/vnc/test.go b/test/vnc/test.go new file mode 100644 index 000000000..d0f83c1e2 --- /dev/null +++ b/test/vnc/test.go @@ -0,0 +1,22 @@ +package main + +import ( + "github.com/hktalent/scan4all/pkg/hydra/vnc" + "log" + "net" +) + +func main() { + nc, err := net.Dial("tcp", "192.168.0.100:5900") + if err != nil { + log.Println(err) + return + } + + cc1, err := vnc.Client(nc, &vnc.ClientConfig{Auth: []vnc.ClientAuth{&vnc.PasswordAuth{Password: "test"}}}) + if err != nil { + log.Println(err) + } else { + cc1.Close() + } +}