From 9c2a57b57618ba1cf3e3fd5fd66da4f984336c92 Mon Sep 17 00:00:00 2001 From: 4566704 <4566704@qq.com> Date: Sun, 20 Sep 2026 12:39:30 +0800 Subject: [PATCH] =?UTF-8?q?feat(remote):=20=E8=87=AA=20go-hua/remote=20?= =?UTF-8?q?=E5=B9=B6=E5=85=A5=E8=BF=9C=E7=A8=8B=E6=8E=A7=E5=88=B6=E6=9C=8D?= =?UTF-8?q?=E5=8A=A1=E7=AE=A1=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - RDP/VNC(TightVNC)/Radmin 服务状态查询与启停重置、VNC 安装/配置读写/密码 加解密、GetRemoteInfo 汇总、CAD/SAS 注册表开关 - 与本库 file/shell/process 同属被控端远程运维能力; 新增依赖 kardianos/service、KarpelesLab/vncpasswd - 附 remote_test.go 与包 README(含平台支持矩阵与权限注意) --- go.mod | 2 + go.sum | 5 + remote/README.md | 60 ++++++ remote/remote.go | 191 ++++++++++++++++++ remote/remote_test.go | 51 +++++ remote/type.go | 43 ++++ remote/unix.go | 69 +++++++ remote/vnc.go | 192 ++++++++++++++++++ remote/windows.go | 451 ++++++++++++++++++++++++++++++++++++++++++ 9 files changed, 1064 insertions(+) create mode 100644 remote/README.md create mode 100644 remote/remote.go create mode 100644 remote/remote_test.go create mode 100644 remote/type.go create mode 100644 remote/unix.go create mode 100644 remote/vnc.go create mode 100644 remote/windows.go diff --git a/go.mod b/go.mod index 5c8a40b..8cad3d8 100644 --- a/go.mod +++ b/go.mod @@ -13,6 +13,7 @@ require ( ) require ( + github.com/KarpelesLab/vncpasswd v1.0.1 // indirect github.com/StackExchange/wmi v1.2.1 // indirect github.com/bytedance/gopkg v0.1.3 // indirect github.com/bytedance/sonic v1.15.0 // indirect @@ -30,6 +31,7 @@ require ( github.com/iamacarpet/go-winpty v1.0.2 // indirect github.com/jaypipes/pcidb v1.0.1 // indirect github.com/json-iterator/go v1.1.12 // indirect + github.com/kardianos/service v1.2.2 // indirect github.com/klauspost/cpuid/v2 v2.3.0 // indirect github.com/leodido/go-urn v1.5.0 // indirect github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect diff --git a/go.sum b/go.sum index 9232acc..1a5903c 100644 --- a/go.sum +++ b/go.sum @@ -1,3 +1,5 @@ +github.com/KarpelesLab/vncpasswd v1.0.1 h1:w0dhjSXfo59bm9/LLKhO7hV+GUGEn4WjAZTLXTNSciU= +github.com/KarpelesLab/vncpasswd v1.0.1/go.mod h1:gh6AFDSUqRZwcdjAm/p8V81P9ePtOI65ajs7KT7D+Fc= github.com/StackExchange/wmi v1.2.1 h1:VIkavFPXSjcnS+O8yTq7NI32k0R5Aj+v39y29VYDOSA= github.com/StackExchange/wmi v1.2.1/go.mod h1:rcmrprowKIVzvc+NUiLncP2uuArMWLCbu9SBzvHz7e8= github.com/bytedance/gopkg v0.1.3 h1:TPBSwH8RsouGCBcMBktLt1AymVo2TVsBVCY4b6TnZ/M= @@ -49,6 +51,8 @@ github.com/jaypipes/pcidb v1.0.1/go.mod h1:6xYUz/yYEyOkIkUt2t2J2folIuZ4Yg6uByCGF github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/kardianos/service v1.2.2 h1:ZvePhAHfvo0A7Mftk/tEzqEZ7Q4lgnR8sGz4xu1YX60= +github.com/kardianos/service v1.2.2/go.mod h1:CIMRFEJVL+0DS1a3Nx06NaMn4Dz63Ng6O7dl0qH0zVM= github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y= github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= @@ -126,6 +130,7 @@ golang.org/x/crypto v0.57.0/go.mod h1:Fdz0i5U6CoizGwLda9DttjSk6qlZo25zYNtR+ycvuZ golang.org/x/net v0.58.0 h1:ynWG7rqYi4ccpTEuPZ2QGWHktVEM9DMCj9yzDE0Q7To= golang.org/x/net v0.58.0/go.mod h1:YwCddHnFlT7eLQqVprV19OnhLGtc5xOKgE0RyqgfWAU= golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201015000850-e3ed0017c211/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= diff --git a/remote/README.md b/remote/README.md new file mode 100644 index 0000000..3f2be68 --- /dev/null +++ b/remote/README.md @@ -0,0 +1,60 @@ +# remote + +远程控制服务管理(被控端能力):RDP / VNC(TightVNC) / Radmin 的服务状态查询、 +启停与重置,VNC 服务端安装/配置读写,VNC 密码加解密,远程信息汇总。 + +> 自 go-hua/remote 平移并入(包名保持 `remote`),与本库 file/shell/process +> 同属被控端远程运维能力。 + +## 用法 + +```go +import "git.zeroonesoft.cn/golib/remote-core/remote" + +// 汇总信息(主机名/IP/MAC/远程端口/VNC 密码等) +info := remote.GetRemoteInfo() + +// 远程服务状态与控制 +_ = remote.GetRdpServiceStatus() // kardianos/service Status +_ = remote.GetVncServiceStatus() // 同上 +_ = remote.GetRadminServiceStatus() // (int, error):1 运行 / 0 停止 / -1 未安装 +_ = remote.GetServiceStatus("SessionEnv") // 通用按服务名查询 +err := remote.ServiceStart(name) // 启动 / ServiceStop 停止 +_ = remote.GetServiceReset(name) // 重置类服务状态 + +// RDP / VNC 端口与 VNC 密码读取 +port, err := remote.GetRdpPort() +vncPort, err := remote.GetVncPort() +pass, err := remote.GetVncPassword() + +// VNC(TightVNC) 服务端管理(Windows) +cfg := remote.VncServerDefaultConfig(true /* 随机密码 */) // VncServerConfig +err = remote.WriteVncServerConfig(cfg) +cfg, err = remote.ReadVncServerConfig() +_, msg, err := remote.InstallVncServer() // 安装服务(需管理员权限) +err = remote.VncInit() // 初始化/修复配置 +err = remote.VncStart() // VncStop / VncRestart +err = remote.VncInstall() // VncUninstall +err = remote.RunVncService() // 以服务方式运行 + +// VNC 密码加解密(TightVNC 注册表存储格式) +enc := remote.VncPasswordCrypt("plainpass") // []byte +plain := remote.VncPasswordDecrypt(enc) // string + +// CAD/SAS 相关注册表开关(Windows) +err = remote.EnableSoftwareSASGeneration() +``` + +## 平台支持 + +- `remote.go` / `type.go`:跨平台(依赖 `kardianos/service`)。 +- `windows.go` / `vnc.go`:`//go:build windows`(注册表、TightVNC 配置)。 +- `unix.go`:`//go:build linux`(精简实现)。 + +## 注意 + +- `GetServiceStatus` 系列对未安装服务返回 -1 而非错误;`GetRdpServiceStatus` / + `GetVncServiceStatus` 走 `kardianos/service`,未安装返回 error。 +- `InstallVncServer` / `VncInit` / CAD 开关需要管理员权限,失败信息见返回 msg 或 error。 +- VNC 密码格式与 TightVNC 兼容(`KarpelesLab/vncpasswd` 实现),加解密为确定性 + 变换,注意密文落盘位置的访问控制。 diff --git a/remote/remote.go b/remote/remote.go new file mode 100644 index 0000000..1bebc4d --- /dev/null +++ b/remote/remote.go @@ -0,0 +1,191 @@ +package remote + +import ( + "fmt" + "github.com/kardianos/service" + "net" +) + +// Info 远程控制信息汇总(主机、IP、MAC 与 RDP/VNC 状态端口) +type Info struct { + Hostname string `json:"hostname"` + IpAddr []string `json:"ipAddr"` + MacAddr []string `json:"macAddr"` + RdpState int `json:"rdpState"` + RdpPort int `json:"rdpPort"` + VncState int `json:"vncState"` + VncPort int `json:"vncPort"` + VncPassword string `json:"vncPassword"` +} + +// GetRadminServiceStatus 获取服务状态 0.无效 1.运行 2.停止 +func GetRadminServiceStatus() (int, error) { + svcConfig := &service.Config{ + Name: "RServer3", + } + s, err := service.New(nil, svcConfig) + if err != nil { + return 0, err + } + var status service.Status + status, err = s.Status() + if err != nil { + return 0, err + } + return int(status), nil +} + +// GetVncServiceStatus 获取服务状态 0.无效 1.运行 2.停止 +func GetVncServiceStatus() (service.Status, error) { + svcConfig := &service.Config{ + Name: "tvnserver", + } + s, err := service.New(nil, svcConfig) + if err != nil { + return 0, err + } + var status service.Status + status, err = s.Status() + if err != nil { + return 0, err + } + return status, nil +} + +// GetRdpServiceStatus 获取服务状态 0.无效 1.运行 2.停止 +func GetRdpServiceStatus() (service.Status, error) { + svcConfig := &service.Config{ + Name: "TermService", + } + s, err := service.New(nil, svcConfig) + if err != nil { + return 0, err + } + var status service.Status + status, err = s.Status() + if err != nil { + return 0, err + } + return status, nil +} + +// getMacAddrs 获取本机所有非空 MAC 地址 +func getMacAddrs() (macAddrs []string) { + netInterfaces, err := net.Interfaces() + if err != nil { + fmt.Printf("fail to get net interfaces: %v", err) + return macAddrs + } + + for _, netInterface := range netInterfaces { + macAddr := netInterface.HardwareAddr.String() + if len(macAddr) == 0 { + continue + } + + macAddrs = append(macAddrs, macAddr) + } + return macAddrs +} + +// getIPs 获取本机所有 IPv4 地址(不含回环) +func getIPs() (ips []string) { + + interfaceAddr, err := net.InterfaceAddrs() + if err != nil { + fmt.Printf("fail to get net interface addrs: %v", err) + return ips + } + + for _, address := range interfaceAddr { + ipNet, isValidIpNet := address.(*net.IPNet) + if isValidIpNet && !ipNet.IP.IsLoopback() { + if ipNet.IP.To4() != nil { + ips = append(ips, ipNet.IP.String()) + } + } + } + return ips +} + +// GetServiceStatus 获取服务状态 0.无效 1.运行 2.停止 +func GetServiceStatus(name string) (int, error) { + svcConfig := &service.Config{ + Name: name, + } + s, err := service.New(nil, svcConfig) + if err != nil { + return 0, err + } + var status service.Status + status, err = s.Status() + if err != nil { + return 0, err + } + return int(status), nil +} + +// ServiceStart 启动服务 +func ServiceStart(name string) error { + svcConfig := &service.Config{ + Name: name, + } + s, err := service.New(nil, svcConfig) + if err != nil { + return err + } + err = s.Start() + if err != nil { + return err + } + return nil +} + +// ServiceStop 停止服务 +func ServiceStop(name string) (i error) { + svcConfig := &service.Config{ + Name: name, + } + s, err := service.New(nil, svcConfig) + if err != nil { + return err + } + err = s.Stop() + if err != nil { + return err + } + return nil +} + +// GetServiceReset 重启服务 +func GetServiceReset(name string) (int, error) { + svcConfig := &service.Config{ + Name: name, + } + s, err := service.New(nil, svcConfig) + if err != nil { + return 0, err + } + var status service.Status + status, err = s.Status() + if err != nil { + return 0, err + } + return int(status), nil +} + +// RunVncService 启动 TightVNC 服务 +func RunVncService() error { + svcConfig := &service.Config{ + Name: "tvnserver", + } + s, err := service.New(nil, svcConfig) + if err != nil { + return err + } + err = s.Start() + if err != nil { + return err + } + return nil +} diff --git a/remote/remote_test.go b/remote/remote_test.go new file mode 100644 index 0000000..d810482 --- /dev/null +++ b/remote/remote_test.go @@ -0,0 +1,51 @@ +//go:build windows + +package remote + +import "testing" + +func TestGetServiceStatusNonExist(t *testing.T) { + // 不存在的服务应返回错误(Radmin 服务普通机器上不会安装) + _, err := GetRadminServiceStatus() + if err == nil { + t.Log("本机安装了 Radmin 服务,跳过该断言") + } +} + +func TestGetRemoteInfo(t *testing.T) { + info := GetRemoteInfo() + if info.Hostname == "" { + t.Error("Hostname 不应为空") + } + if len(info.IpAddr) == 0 { + t.Log("本机无 IPv4 地址(CI 环境可能出现)") + } + // 只读查询,不应 panic + _ = info.RdpState + _ = info.VncState +} + +func TestVncPasswordCryptDecrypt(t *testing.T) { + password := "tvncpass" + encrypted := VncPasswordCrypt(password) + if len(encrypted) == 0 { + t.Fatal("加密结果不应为空") + } + if string(encrypted) == password { + t.Error("加密结果不应等于明文") + } + decrypted := VncPasswordDecrypt(encrypted) + if decrypted != password { + t.Errorf("解密 = %q, want %q", decrypted, password) + } +} + +func TestVncServerDefaultConfig(t *testing.T) { + cfg := VncServerDefaultConfig(false) + if cfg.RfbPort == 0 { + t.Error("RfbPort 不应为 0") + } + if cfg.HttpPort == 0 { + t.Error("HttpPort 不应为 0") + } +} diff --git a/remote/type.go b/remote/type.go new file mode 100644 index 0000000..73db596 --- /dev/null +++ b/remote/type.go @@ -0,0 +1,43 @@ +// Package remote 提供 RDP/VNC 等远程控制的服务状态查询、配置读写与启停控制。 +package remote + +// VncServerConfig TightVNC 服务端配置(对应注册表 SOFTWARE\TightVNC\Server 下的键值) +type VncServerConfig struct { + ExtraPorts string `json:"extraPorts"` // 额外端口 + QueryTimeout uint32 `json:"queryTimeout"` // 查询超时 + QueryAcceptOnTimeout uint32 `json:"queryAcceptOnTimeout"` // 查询接受超时 + LocalInputPriorityTimeout uint32 `json:"localInputPriorityTimeout"` // 本地输入优先级超时 + LocalInputPriority uint32 `json:"localInputPriority"` // 本地输入优先 + BlockRemoteInput uint32 `json:"blockRemoteInput"` // 阻止远程输入 + BlockLocalInput uint32 `json:"blockLocalInput"` // 阻止本地输入 + IpAccessControl string `json:"ipAccessControl"` // IP访问控制 + RfbPort uint32 `json:"rfbPort"` // 主端口 + HttpPort uint32 `json:"httpPort"` // WEB端口 + DisconnectAction uint32 `json:"disconnectAction"` // 断开动作 + AcceptRfbConnections uint32 `json:"acceptRfbConnections"` // 接受RFB连接 + UseVncAuthentication uint32 `json:"useVncAuthentication"` // 使用VNC认证 + UseControlAuthentication uint32 `json:"useControlAuthentication"` // 使用控制认证 + RepeatControlAuthentication uint32 `json:"repeatControlAuthentication"` // 重复控制认证 + LoopbackOnly uint32 `json:"loopbackOnly"` // 只允许环回 + AcceptHttpConnections uint32 `json:"acceptHttpConnections"` // HTTP请求连接 + LogLevel uint32 `json:"logLevel"` // 日志等级 + EnableFileTransfers uint32 `json:"enableFileTransfers"` // 使文件传输 + RemoveWallpaper uint32 `json:"removeWallpaper"` // 移除墙纸 + UseD3D uint32 `json:"useD3D"` // 使用D3D驱动 + UseMirrorDriver uint32 `json:"useMirrorDriver"` // 使用镜像驱动 + EnableUrlParams uint32 `json:"enableUrlParams"` // 用URL参数 + Password []byte `json:"password"` // 密码(需要加密) + AlwaysShared uint32 `json:"alwaysShared"` // 总是共享 + NeverShared uint32 `json:"neverShared"` // 从不共享 + DisconnectClients uint32 `json:"disconnectClients"` // 断开客户端后操作 + PollingInterval uint32 `json:"pollingInterval"` // 轮询间隔 + AllowLoopback uint32 `json:"allowLoopback"` // 允许环回 + VideoRecognitionInterval uint32 `json:"videoRecognitionInterval"` // 视频轮询间隔 + GrabTransparentWindows uint32 `json:"grabTransparentWindows"` // 抓取透明窗口 + SaveLogToAllUsersPath uint32 `json:"saveLogToAllUsersPath"` // 保存日志所有用户路径 + RunControlInterface uint32 `json:"runControlInterface"` // 运行控制台接口 + IdleTimeout uint32 `json:"idleTimeout"` // 空闲超时 + VideoClasses string `json:"videoClasses"` // 视频窗口类名 + VideoRects string `json:"videoRects"` // 视频窗口区域 + ConnectToRdp uint32 `json:"connectToRdp"` // 连接到RDP会话 +} diff --git a/remote/unix.go b/remote/unix.go new file mode 100644 index 0000000..babfe7a --- /dev/null +++ b/remote/unix.go @@ -0,0 +1,69 @@ +//go:build linux + +package remote + +import "os" + +// GetRemoteInfo 获取远程控制相关信息(Linux 平台仅主机名/IP/MAC) +func GetRemoteInfo() Info { + info := Info{} + + /*var status int + status, _ = GetRadminServiceStatus() + info.RadminState = status + + status, _ = GetVncServiceStatus() + info.VncState = status + + status, _ = GetRdpServiceStatus() + info.RdpState = status*/ + + /* var port int + port, _ = GetRadminPort() + info.RadminPort = port + + port, _ = GetRdpPort() + info.RdpPort = port + + port, _ = GetVncPort() + info.VncPort = port*/ + + hostname, _ := os.Hostname() + info.Hostname = hostname + info.IpAddr = getIPs() + info.MacAddr = getMacAddrs() + + return info +} + +// VncServerDefaultConfig 生成 TightVNC 服务端默认配置(Linux 未实现,返回空配置) +func VncServerDefaultConfig(randPass bool) VncServerConfig { + config := VncServerConfig{} + return config +} + +// VncClientDefaultConfig 生成 TightVNC 客户端默认配置(Linux 未实现,返回空配置) +func VncClientDefaultConfig(randPass bool) VncServerConfig { + config := VncServerConfig{} + return config +} + +// WriteVncServerConfig 写入 TightVNC 服务端配置(Linux 未实现,直接返回 nil) +func WriteVncServerConfig(config VncServerConfig) error { + return nil +} + +// EnableSoftwareSASGeneration 启用软件SAS模拟输入 +func EnableSoftwareSASGeneration() error { + return nil +} + +// EnableWinlogonDisableCAD 关闭 Ctrl + Alt + Del 组合键来登录 +func EnableWinlogonDisableCAD() error { + return nil +} + +// EnableSystemDisableCAD 关闭 Ctrl + Alt + Del 组合键来登录 +func EnableSystemDisableCAD() error { + return nil +} diff --git a/remote/vnc.go b/remote/vnc.go new file mode 100644 index 0000000..eaa1c77 --- /dev/null +++ b/remote/vnc.go @@ -0,0 +1,192 @@ +//go:build windows + +package remote + +import ( + "github.com/KarpelesLab/vncpasswd" + "github.com/kardianos/service" + "golang.org/x/sys/windows/registry" +) + +var VncService service.Service + +// ReadVncServerConfig 从注册表读取 TightVNC 服务端配置 +func ReadVncServerConfig() (VncServerConfig, error) { + config := VncServerConfig{ + ExtraPorts: "", + QueryTimeout: 0, + QueryAcceptOnTimeout: 0, + LocalInputPriorityTimeout: 0, + LocalInputPriority: 0, + BlockRemoteInput: 0, + BlockLocalInput: 0, + IpAccessControl: "", + RfbPort: 0, + HttpPort: 0, + DisconnectAction: 0, + AcceptRfbConnections: 0, + UseVncAuthentication: 0, + UseControlAuthentication: 0, + RepeatControlAuthentication: 0, + LoopbackOnly: 0, + AcceptHttpConnections: 0, + LogLevel: 0, + EnableFileTransfers: 0, + RemoveWallpaper: 0, + UseD3D: 0, + UseMirrorDriver: 0, + EnableUrlParams: 0, + Password: []byte{}, + AlwaysShared: 0, + NeverShared: 0, + DisconnectClients: 0, + PollingInterval: 0, + AllowLoopback: 0, + VideoRecognitionInterval: 0, + GrabTransparentWindows: 0, + SaveLogToAllUsersPath: 0, + RunControlInterface: 0, + IdleTimeout: 0, + VideoClasses: "", + VideoRects: "", + ConnectToRdp: 0, + } + + key, err := registry.OpenKey(registry.LOCAL_MACHINE, "SOFTWARE\\TightVNC\\Server", registry.ALL_ACCESS) + if err != nil { + return config, err + } + defer key.Close() + + config.ExtraPorts, _, _ = key.GetStringValue("ExtraPorts") + config.QueryTimeout, _, _ = GetDwordValue(key, "QueryTimeout") + config.QueryAcceptOnTimeout, _, _ = GetDwordValue(key, "QueryAcceptOnTimeout") + config.LocalInputPriorityTimeout, _, _ = GetDwordValue(key, "LocalInputPriorityTimeout") + config.LocalInputPriority, _, _ = GetDwordValue(key, "LocalInputPriority") + config.BlockRemoteInput, _, _ = GetDwordValue(key, "BlockRemoteInput") + config.BlockLocalInput, _, _ = GetDwordValue(key, "BlockLocalInput") + config.IpAccessControl, _, _ = key.GetStringValue("IpAccessControl") + config.RfbPort, _, _ = GetDwordValue(key, "RfbPort") + config.HttpPort, _, _ = GetDwordValue(key, "HttpPort") + config.DisconnectAction, _, _ = GetDwordValue(key, "DisconnectAction") + config.AcceptRfbConnections, _, _ = GetDwordValue(key, "AcceptRfbConnections") + config.UseVncAuthentication, _, _ = GetDwordValue(key, "UseVncAuthentication") + config.UseControlAuthentication, _, _ = GetDwordValue(key, "UseControlAuthentication") + config.RepeatControlAuthentication, _, _ = GetDwordValue(key, "RepeatControlAuthentication") + config.LoopbackOnly, _, _ = GetDwordValue(key, "LoopbackOnly") + config.AcceptHttpConnections, _, _ = GetDwordValue(key, "AcceptHttpConnections") + config.EnableFileTransfers, _, _ = GetDwordValue(key, "EnableFileTransfers") + config.RemoveWallpaper, _, _ = GetDwordValue(key, "RemoveWallpaper") + config.UseD3D, _, _ = GetDwordValue(key, "UseD3D") + config.UseMirrorDriver, _, _ = GetDwordValue(key, "UseMirrorDriver") + config.EnableUrlParams, _, _ = GetDwordValue(key, "EnableUrlParams") + config.Password, _, _ = key.GetBinaryValue("Password") + config.AlwaysShared, _, _ = GetDwordValue(key, "AlwaysShared") + config.NeverShared, _, _ = GetDwordValue(key, "NeverShared") + config.DisconnectClients, _, _ = GetDwordValue(key, "DisconnectClients") + config.PollingInterval, _, _ = GetDwordValue(key, "PollingInterval") + config.AllowLoopback, _, _ = GetDwordValue(key, "AllowLoopback") + config.VideoRecognitionInterval, _, _ = GetDwordValue(key, "VideoRecognitionInterval") + config.GrabTransparentWindows, _, _ = GetDwordValue(key, "GrabTransparentWindows") + config.SaveLogToAllUsersPath, _, _ = GetDwordValue(key, "SaveLogToAllUsersPath") + config.RunControlInterface, _, _ = GetDwordValue(key, "RunControlInterface") + config.IdleTimeout, _, _ = GetDwordValue(key, "IdleTimeout") + config.VideoClasses, _, _ = key.GetStringValue("VideoClasses") + config.VideoRects, _, _ = key.GetStringValue("VideoRects") + config.ConnectToRdp, _, _ = GetDwordValue(key, "ExtraPorts") + + return config, nil +} + +// GetDwordValue 读取 REG_DWORD 类型注册表值 +func GetDwordValue(k registry.Key, name string) (val uint32, valtype uint32, err error) { + val64 := uint64(0) + val64, valtype, err = k.GetIntegerValue(name) + val = uint32(val64) + return +} + +var vncSvcConfig = &service.Config{ + Name: "tvnserver", + DisplayName: "TightVNC Server", + Description: "", + Arguments: []string{"-service"}, +} + +// InstallVncServer 安装VNC服务端 返回:结果,密码,错误 +func InstallVncServer() (bool, string, error) { + //安装服务 + //导入注册表 + //生成随机密码 + return true, "12345678", nil +} + +// VncInit 初始化 VNC 服务对象,VncInstall/VncStart 等操作前必须调用 +func VncInit() error { + var err error + VncService, err = service.New(nil, vncSvcConfig) + if err != nil { + return err + } + return nil +} + +// VncInstall 安装 TightVNC 服务 +func VncInstall() error { + err := VncService.Install() + if err != nil { + return err + } + return nil +} + +// VncUninstall 停止并卸载 TightVNC 服务 +func VncUninstall() error { + _ = VncService.Stop() + err := VncService.Uninstall() + if err != nil { + return err + } + return nil +} + +// VncStart 启动 TightVNC 服务 +func VncStart() error { + err := VncService.Start() + if err != nil { + return err + } + return nil +} + +// VncStop 停止 TightVNC 服务 +func VncStop() error { + err := VncService.Stop() + if err != nil { + return err + } + return nil +} + +// VncRestart 重启 TightVNC 服务 +func VncRestart() error { + err := VncService.Stop() + if err != nil { + return err + } + err = VncService.Start() + if err != nil { + return err + } + return nil +} + +// VncPasswordCrypt 加密 VNC 密码(TightVNC 注册表存储格式) +func VncPasswordCrypt(password string) []byte { + return vncpasswd.Crypt(password) +} + +// VncPasswordDecrypt 解密 VNC 密码 +func VncPasswordDecrypt(data []byte) string { + return vncpasswd.Decrypt(data) +} diff --git a/remote/windows.go b/remote/windows.go new file mode 100644 index 0000000..8c495e8 --- /dev/null +++ b/remote/windows.go @@ -0,0 +1,451 @@ +//go:build windows + +package remote + +import ( + "bytes" + "crypto/rand" + "golang.org/x/sys/windows/registry" + "math/big" + "os" +) + +// GetRemoteInfo 一次性获取远程控制相关信息(服务状态/端口/密码/主机信息) +func GetRemoteInfo() Info { + info := Info{} + + status, _ := GetVncServiceStatus() + info.VncState = int(status) + + status, _ = GetRdpServiceStatus() + info.RdpState = int(status) + + port, _ := GetRdpPort() + info.RdpPort = port + + port, _ = GetVncPort() + info.VncPort = port + + password, _ := GetVncPassword() + info.VncPassword = password + + hostname, _ := os.Hostname() + info.Hostname = hostname + info.IpAddr = getIPs() + info.MacAddr = getMacAddrs() + + return info +} + +// GetVncPort 从注册表读取 TightVNC 主端口 +func GetVncPort() (int, error) { + var key registry.Key + var err error + key, err = registry.OpenKey(registry.LOCAL_MACHINE, `SOFTWARE\TightVNC\Server`, registry.ALL_ACCESS) + if err != nil { + return 0, err + } + var val uint64 + val, _, err = key.GetIntegerValue("RfbPort") + if err != nil { + return 0, err + } + var port int = int(val) + return port, nil +} + +// GetRdpPort 从注册表读取远程桌面端口 +func GetRdpPort() (int, error) { + var key registry.Key + var err error + key, err = registry.OpenKey(registry.LOCAL_MACHINE, `SYSTEM\CurrentControlSet\Control\Terminal Server\WinStations\RDP-Tcp`, registry.ALL_ACCESS) + if err != nil { + return 0, err + } + var val uint64 + val, _, err = key.GetIntegerValue("PortNumber") + if err != nil { + return 0, err + } + var port int = int(val) + return port, nil +} + +// GetVncPassword 读取并解密 TightVNC 连接密码 +func GetVncPassword() (string, error) { + var key registry.Key + var err error + key, err = registry.OpenKey(registry.LOCAL_MACHINE, `SOFTWARE\TightVNC\Server`, registry.ALL_ACCESS) + if err != nil { + return "", err + } + + val, _, err := key.GetBinaryValue("Password") + if err != nil { + return "", err + } + if len(val) == 0 { + return "", err + } + password := VncPasswordDecrypt(val) + if err != nil { + return "", err + } + return password, nil +} + +// VncServerDefaultConfig 生成 TightVNC 服务端默认配置(连接 RDP 会话) +// randPass 为 true 时生成 8 位随机密码,否则使用固定密码 +func VncServerDefaultConfig(randPass bool) VncServerConfig { + password := "tvncpass" + if randPass { + password = createRandomString(8) + } + config := VncServerConfig{ + ExtraPorts: "", + QueryTimeout: 30, + QueryAcceptOnTimeout: 0, + LocalInputPriorityTimeout: 3, + LocalInputPriority: 1, + BlockRemoteInput: 0, + BlockLocalInput: 0, + IpAccessControl: "", + RfbPort: 5900, + HttpPort: 5800, + DisconnectAction: 0, + AcceptRfbConnections: 1, + UseVncAuthentication: 1, + UseControlAuthentication: 0, + RepeatControlAuthentication: 0, + LoopbackOnly: 0, + AcceptHttpConnections: 0, + LogLevel: 0, + EnableFileTransfers: 1, + RemoveWallpaper: 1, + UseD3D: 1, + UseMirrorDriver: 1, + EnableUrlParams: 1, + Password: VncPasswordCrypt(password), + AlwaysShared: 0, + NeverShared: 0, + DisconnectClients: 1, + PollingInterval: 1000, + AllowLoopback: 1, + VideoRecognitionInterval: 3000, + GrabTransparentWindows: 1, + SaveLogToAllUsersPath: 0, + RunControlInterface: 0, + IdleTimeout: 0, + VideoClasses: "", + VideoRects: "", + ConnectToRdp: 1, + } + + return config +} + +// VncClientDefaultConfig 生成 TightVNC 客户端默认配置(不连接 RDP 会话) +// randPass 为 true 时生成 8 位随机密码,否则使用固定密码 +func VncClientDefaultConfig(randPass bool) VncServerConfig { + password := "tvncpass" + if randPass { + password = createRandomString(8) + } + config := VncServerConfig{ + ExtraPorts: "", + QueryTimeout: 30, + QueryAcceptOnTimeout: 0, + LocalInputPriorityTimeout: 3, + LocalInputPriority: 1, + BlockRemoteInput: 0, + BlockLocalInput: 0, + IpAccessControl: "", + RfbPort: 5900, + HttpPort: 5800, + DisconnectAction: 0, + AcceptRfbConnections: 1, + UseVncAuthentication: 1, + UseControlAuthentication: 0, + RepeatControlAuthentication: 0, + LoopbackOnly: 0, + AcceptHttpConnections: 0, + LogLevel: 0, + EnableFileTransfers: 1, + RemoveWallpaper: 0, + UseD3D: 1, + UseMirrorDriver: 1, + EnableUrlParams: 1, + Password: VncPasswordCrypt(password), + AlwaysShared: 0, + NeverShared: 0, + DisconnectClients: 1, + PollingInterval: 1000, + AllowLoopback: 1, + VideoRecognitionInterval: 3000, + GrabTransparentWindows: 1, + SaveLogToAllUsersPath: 0, + RunControlInterface: 0, + IdleTimeout: 0, + VideoClasses: "", + VideoRects: "", + ConnectToRdp: 0, + } + + return config +} + +// WriteVncServerConfig 将 TightVNC 服务端配置写入注册表(项不存在时自动创建) +func WriteVncServerConfig(config VncServerConfig) error { + + key, err := registry.OpenKey(registry.LOCAL_MACHINE, "SOFTWARE\\TightVNC\\Server", registry.ALL_ACCESS) + if err != nil { + if err == registry.ErrNotExist { + // 项不存在 创建它 + key, _, err = registry.CreateKey(registry.LOCAL_MACHINE, "SOFTWARE\\TightVNC\\Server", registry.ALL_ACCESS) + if err != nil { + return err + } + } else { + return err + } + } + + defer key.Close() + + err = key.SetStringValue("ExtraPorts", config.ExtraPorts) + if err != nil { + return err + } + err = key.SetDWordValue("QueryTimeout", config.QueryTimeout) + if err != nil { + return err + } + err = key.SetDWordValue("QueryAcceptOnTimeout", config.QueryAcceptOnTimeout) + if err != nil { + return err + } + err = key.SetDWordValue("LocalInputPriorityTimeout", config.LocalInputPriorityTimeout) + if err != nil { + return err + } + err = key.SetDWordValue("LocalInputPriority", config.LocalInputPriority) + if err != nil { + return err + } + err = key.SetDWordValue("BlockRemoteInput", config.BlockRemoteInput) + if err != nil { + return err + } + err = key.SetDWordValue("BlockLocalInput", config.BlockLocalInput) + if err != nil { + return err + } + err = key.SetStringValue("IpAccessControl", config.IpAccessControl) + if err != nil { + return err + } + err = key.SetDWordValue("RfbPort", config.RfbPort) + if err != nil { + return err + } + err = key.SetDWordValue("HttpPort", config.HttpPort) + if err != nil { + return err + } + err = key.SetDWordValue("DisconnectAction", config.DisconnectAction) + if err != nil { + return err + } + err = key.SetDWordValue("AcceptRfbConnections", config.AcceptRfbConnections) + if err != nil { + return err + } + err = key.SetDWordValue("UseVncAuthentication", config.UseVncAuthentication) + if err != nil { + return err + } + err = key.SetDWordValue("UseControlAuthentication", config.UseControlAuthentication) + if err != nil { + return err + } + err = key.SetDWordValue("RepeatControlAuthentication", config.RepeatControlAuthentication) + if err != nil { + return err + } + err = key.SetDWordValue("LoopbackOnly", config.LoopbackOnly) + if err != nil { + return err + } + err = key.SetDWordValue("AcceptHttpConnections", config.AcceptHttpConnections) + if err != nil { + return err + } + err = key.SetDWordValue("EnableFileTransfers", config.EnableFileTransfers) + if err != nil { + return err + } + err = key.SetDWordValue("RemoveWallpaper", config.RemoveWallpaper) + if err != nil { + return err + } + err = key.SetDWordValue("UseD3D", config.UseD3D) + if err != nil { + return err + } + err = key.SetDWordValue("UseMirrorDriver", config.UseMirrorDriver) + if err != nil { + return err + } + err = key.SetDWordValue("EnableUrlParams", config.EnableUrlParams) + if err != nil { + return err + } + err = key.SetBinaryValue("Password", config.Password) + if err != nil { + return err + } + err = key.SetDWordValue("AlwaysShared", config.AlwaysShared) + if err != nil { + return err + } + err = key.SetDWordValue("NeverShared", config.NeverShared) + if err != nil { + return err + } + err = key.SetDWordValue("DisconnectClients", config.DisconnectClients) + if err != nil { + return err + } + err = key.SetDWordValue("PollingInterval", config.PollingInterval) + if err != nil { + return err + } + err = key.SetDWordValue("AllowLoopback", config.AllowLoopback) + if err != nil { + return err + } + err = key.SetDWordValue("VideoRecognitionInterval", config.VideoRecognitionInterval) + if err != nil { + return err + } + err = key.SetDWordValue("GrabTransparentWindows", config.GrabTransparentWindows) + if err != nil { + return err + } + err = key.SetDWordValue("SaveLogToAllUsersPath", config.SaveLogToAllUsersPath) + if err != nil { + return err + } + err = key.SetDWordValue("RunControlInterface", config.RunControlInterface) + if err != nil { + return err + } + err = key.SetDWordValue("IdleTimeout", config.IdleTimeout) + if err != nil { + return err + } + err = key.SetStringValue("VideoClasses", config.VideoClasses) + if err != nil { + return err + } + err = key.SetStringValue("VideoRects", config.VideoRects) + if err != nil { + return err + } + err = key.SetDWordValue("ConnectToRdp", config.ConnectToRdp) + if err != nil { + return err + } + + return nil +} + +// EnableSoftwareSASGeneration 启用软件SAS模拟输入 +func EnableSoftwareSASGeneration() error { + // SAS模拟输入 + key, err := registry.OpenKey(registry.LOCAL_MACHINE, "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Policies\\System", registry.ALL_ACCESS) + if err != nil { + if err == registry.ErrNotExist { + // 项不存在 创建它 + key, _, err = registry.CreateKey(registry.LOCAL_MACHINE, "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Policies\\System", registry.ALL_ACCESS) + if err != nil { + return err + } + } else { + return err + } + } + + defer key.Close() + + err = key.SetDWordValue("SoftwareSASGeneration", 1) + if err != nil { + return err + } + + return nil +} + +// EnableWinlogonDisableCAD 关闭 Ctrl + Alt + Del 组合键来登录 +func EnableWinlogonDisableCAD() error { + key, err := registry.OpenKey(registry.LOCAL_MACHINE, "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Winlogon", registry.ALL_ACCESS) + if err != nil { + if err == registry.ErrNotExist { + // 项不存在 创建它 + key, _, err = registry.CreateKey(registry.LOCAL_MACHINE, "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Winlogon", registry.ALL_ACCESS) + if err != nil { + return err + } + } else { + return err + } + } + + defer key.Close() + + err = key.SetDWordValue("DisableCAD", 1) + if err != nil { + return err + } + + return nil +} + +// EnableSystemDisableCAD 关闭 Ctrl + Alt + Del 组合键来登录 +func EnableSystemDisableCAD() error { + key, err := registry.OpenKey(registry.LOCAL_MACHINE, "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Policies\\System", registry.ALL_ACCESS) + if err != nil { + if err == registry.ErrNotExist { + // 项不存在 创建它 + key, _, err = registry.CreateKey(registry.LOCAL_MACHINE, "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Policies\\System", registry.ALL_ACCESS) + if err != nil { + return err + } + } else { + return err + } + } + + defer key.Close() + + err = key.SetDWordValue("DisableCAD", 1) + if err != nil { + return err + } + + return nil +} + +// createRandomString 生成指定长度的随机字符串 +func createRandomString(len int) string { + var container string + var str = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890" + b := bytes.NewBufferString(str) + length := b.Len() + bigInt := big.NewInt(int64(length)) + for i := 0; i < len; i++ { + randomInt, _ := rand.Int(rand.Reader, bigInt) + container += string(str[randomInt.Int64()]) + } + return container +}