feat: v2.1.0 核心重构与功能增强

## 架构重构
- 全局变量消除,迁移至 Config/State 对象
- SMB 插件融合(smb/smb2/smbghost/smbinfo)
- 服务探测重构,实现 Nmap 风格 fallback 机制
- 输出系统重构,TXT 实时刷盘 + 双写机制
- i18n 框架升级至 go-i18n

## 性能优化
- 正则表达式预编译
- 内存优化 map[string]struct{}
- 并发指纹匹配
- SOCKS5 连接复用
- 滑动窗口调度 + 自适应线程池

## 新功能
- Web 管理界面
- 多格式 POC 适配(xray/afrog)
- 增强指纹库(3139条)
- Favicon hash 指纹识别
- 插件选择性编译(Build Tags)
- fscan-lab 靶场环境
- 默认端口扩展(62→133)

## 构建系统
- 添加 no_local tag 支持排除本地插件
- 多版本构建:fscan/fscan-nolocal/fscan-web
- CI 添加 snapshot 模式支持仅测试构建

## Bug 修复
- 修复 120+ 个问题,包括 RDP panic、批量扫描漏报、
  JSON 输出格式、Redis 检测、Context 超时等

## 测试增强
- 单元测试覆盖率 74-100%
- 并发安全测试
- 集成测试(Web/端口/服务/SSH/ICMP)
This commit is contained in:
ZacharyZcR
2026-01-11 20:16:23 +08:00
parent 6b13b2e84f
commit 71b92d4408
948 changed files with 92335 additions and 24630 deletions
+328
View File
@@ -0,0 +1,328 @@
// +build ignore
// addins.go
package plugin
import (
"strings"
"sync/atomic"
"syscall"
"unsafe"
)
var (
openHandleSeq uint32
freerdpClient2 = syscall.NewLazyDLL("freerdp-client2.dll")
winpr2 = syscall.NewLazyDLL("winpr2.dll")
freerdp2 = syscall.NewLazyDLL("freerdp2.dll")
)
var (
virtualChannelEntry = freerdpClient2.NewProc("VirtualChannelEntry")
virtualChannelEntryEx = freerdpClient2.NewProc("VirtualChannelEntryEx")
)
func VirtualChannelEntryEx(ex *ChannelEntryPointsEx, pInitHandle interface{}) (err error) {
r0, _, ec := virtualChannelEntryEx.Call(uintptr(unsafe.Pointer(ex)),
uintptr(unsafe.Pointer(&pInitHandle)))
if r0 == 0 {
err = error(ec)
}
return
}
func ChannelsClientLoadEx(cs *rdpChannels) {
var client ChannelClientData
client.entryEx = VIRTUALCHANNELENTRYEX(VirtualChannelEntryEx)
cs.clientDataList = append(cs.clientDataList, client)
cs.clientDataCount++
var init ChannelInitData
init.channels = cs
init.openDataMap = make(map[uint32]*ChannelOpenData)
cs.initDataList = append(cs.initDataList, init)
cs.initDataCount++
ex := NewChannelEntryPointsEx()
ex.PVirtualChannelInitEx = VIRTUALCHANNELINITEX(RdpVirtualChannelInitEx)
ex.PVirtualChannelOpenEx = VIRTUALCHANNELOPENEX(RdpVirtualChannelOpenEx)
ex.PVirtualChannelCloseEx = VIRTUALCHANNELCLOSEEX(RdpVirtualChannelCloseEx)
ex.PVirtualChannelWriteEx = VIRTUALCHANNELWRITEEX(RdpVirtualChannelWriteEx)
client.entryEx(ex, uintptr(unsafe.Pointer(&init)))
}
type ChannelClientData struct {
//PVIRTUALCHANNELENTRY entry;
entryEx VIRTUALCHANNELENTRYEX
// pChannelInitEventProc *CHANNEL_INIT_EVENT_FN
pChannelInitEventProcEx CHANNEL_INIT_EVENT_EX_FN
pInitHandle interface{}
lpUserParam interface{}
}
type ChannelOpenData struct {
name string
OpenHandle uint32
options uint32
flags int
pInterface interface{}
channels *rdpChannels
lpUserParam interface{}
//pChannelOpenEventProc *CHANNEL_OPEN_EVENT_FN
pChannelOpenEventProcEx *CHANNEL_OPEN_EVENT_EX_FN
}
type ChannelInitData struct {
channels *rdpChannels
pInterface interface{}
openDataMap map[uint32]*ChannelOpenData
}
type rdpChannels struct {
clientDataCount int
clientDataList []ChannelClientData
openDataCount int
openDataList []ChannelOpenData
initDataCount int
initDataList []ChannelInitData
/* control for entry into MyVirtualChannelInit */
can_call_init bool
/* true once freerdp_channels_post_connect is called */
connected bool
/* used for locating the channels for a given instance */
//freerdp* instance;
//wMessageQueue* queue;
//DrdynvcClientContext* drdynvc;
//CRITICAL_SECTION channelsLock;
}
type ChannelOpenEvent struct {
Data interface{}
DataLength uint32
UserData interface{}
pChannelOpenData *ChannelOpenData
}
func RdpVirtualChannelInitEx(lpUserParam interface{}, clientContext interface{},
pInitHandle interface{}, pChannel []ChannelDef,
channelCount int, versionRequested uint32,
pChannelInitEventProcEx CHANNEL_INIT_EVENT_EX_FN) uint {
var (
//rdpSettings* settings;
pChannelInitData *ChannelInitData
pChannelClientData *ChannelClientData
channels *rdpChannels
)
if pInitHandle == nil {
return CHANNEL_RC_BAD_INIT_HANDLE
}
if pChannel == nil {
return CHANNEL_RC_BAD_CHANNEL
}
if (channelCount <= 0) || pChannelInitEventProcEx == nil {
return CHANNEL_RC_INITIALIZATION_ERROR
}
pChannelInitData = pInitHandle.(*ChannelInitData)
//WINPR_ASSERT(pChannelInitData);
channels = pChannelInitData.channels
//WINPR_ASSERT(channels);
if !channels.can_call_init {
return CHANNEL_RC_NOT_IN_VIRTUALCHANNELENTRY
}
if (channels.openDataCount + channelCount) > 30 {
return CHANNEL_RC_TOO_MANY_CHANNELS
}
if channels.connected {
return CHANNEL_RC_ALREADY_CONNECTED
}
if versionRequested != VIRTUAL_CHANNEL_VERSION_WIN2000 {
}
for i := range pChannel {
pChannelDef := &pChannel[i]
if getChannelOpenDataByName(channels, pChannelDef.Name) == nil {
return CHANNEL_RC_BAD_CHANNEL
}
}
pChannelClientData = &channels.clientDataList[channels.clientDataCount]
pChannelClientData.pChannelInitEventProcEx = pChannelInitEventProcEx
pChannelClientData.pInitHandle = pInitHandle
pChannelClientData.lpUserParam = lpUserParam
channels.clientDataCount++
//WINPR_ASSERT(channels->instance);
//WINPR_ASSERT(channels->instance->context);
//settings = channels.instance.context.settings
//WINPR_ASSERT(settings);
for i := range pChannel {
pChannelDef := &pChannel[i]
var pChannelOpenData ChannelOpenData
//WINPR_ASSERT(pChannelOpenData)
pChannelOpenData.OpenHandle = atomic.AddUint32(&openHandleSeq, 1)
pChannelOpenData.channels = channels
pChannelOpenData.lpUserParam = lpUserParam
if _, ok := pChannelInitData.openDataMap[pChannelOpenData.OpenHandle]; ok {
return CHANNEL_RC_INITIALIZATION_ERROR
}
pChannelInitData.pInterface = clientContext
pChannelOpenData.flags = 1
pChannelOpenData.name = pChannelDef.Name
pChannelOpenData.options = pChannelDef.Options
pChannelInitData.openDataMap[pChannelOpenData.OpenHandle] = &pChannelOpenData
channels.openDataList = append(channels.openDataList, pChannelOpenData)
channels.openDataCount++
/*
if settings.ChannelCount < 30 {
channel := freerdp_settings_get_pointer_array_writable(
settings, FreeRDP_ChannelDefArray, settings.ChannelCount)
channel.name = pChannelDef.Name
channel.options = pChannelDef.Options
settings.ChannelCount++
}*/
channels.openDataCount++
}
return CHANNEL_RC_OK
}
func getChannelOpenDataByName(channel *rdpChannels, name string) *ChannelOpenData {
for _, v := range channel.openDataList {
if strings.EqualFold(name, v.name) {
return &v
}
}
return nil
}
func RdpVirtualChannelOpenEx(pInitHandle interface{}, pOpenHandle *uint32, pChannelName string,
pChannelOpenEventProcEx *CHANNEL_OPEN_EVENT_EX_FN) uint {
pChannelInitData := pInitHandle.(*ChannelInitData)
channels := pChannelInitData.channels
pInterface := pChannelInitData.pInterface
if pOpenHandle == nil {
return CHANNEL_RC_BAD_CHANNEL_HANDLE
}
if pChannelOpenEventProcEx == nil {
return CHANNEL_RC_BAD_PROC
}
if !channels.connected {
return CHANNEL_RC_NOT_CONNECTED
}
pChannelOpenData := getChannelOpenDataByName(channels, pChannelName)
if pChannelOpenData == nil {
return CHANNEL_RC_UNKNOWN_CHANNEL_NAME
}
if pChannelOpenData.flags == 2 {
return CHANNEL_RC_ALREADY_OPEN
}
pChannelOpenData.flags = 2 /* open */
pChannelOpenData.pInterface = pInterface
pChannelOpenData.pChannelOpenEventProcEx = pChannelOpenEventProcEx
*pOpenHandle = pChannelOpenData.OpenHandle
return CHANNEL_RC_OK
}
func RdpVirtualChannelCloseEx(pInitHandle interface{}, openHandle uint32) uint {
if pInitHandle == nil {
return CHANNEL_RC_BAD_INIT_HANDLE
}
pChannelInitData := pInitHandle.(*ChannelInitData)
pChannelOpenData := pChannelInitData.openDataMap[openHandle]
if pChannelOpenData == nil {
return CHANNEL_RC_BAD_CHANNEL_HANDLE
}
if pChannelOpenData.flags != 2 {
return CHANNEL_RC_NOT_OPEN
}
pChannelOpenData.flags = 0
return CHANNEL_RC_OK
}
func RdpVirtualChannelWriteEx(pInitHandle interface{}, openHandle uint32,
pData interface{}, dataLength uint32,
pUserData interface{}) uint {
//wMessage message;
if pInitHandle == nil {
return CHANNEL_RC_BAD_INIT_HANDLE
}
pChannelInitData := pInitHandle.(*ChannelInitData)
channels := pChannelInitData.channels
if channels == nil {
return CHANNEL_RC_BAD_CHANNEL_HANDLE
}
pChannelOpenData := pChannelInitData.openDataMap[openHandle]
if pChannelOpenData == nil {
return CHANNEL_RC_BAD_CHANNEL_HANDLE
}
if !channels.connected {
return CHANNEL_RC_NOT_CONNECTED
}
if pData == nil {
return CHANNEL_RC_NULL_DATA
}
if dataLength == 0 {
return CHANNEL_RC_ZERO_LENGTH
}
if pChannelOpenData.flags != 2 {
return CHANNEL_RC_NOT_OPEN
}
pChannelOpenEvent := new(ChannelOpenEvent)
if pChannelOpenEvent == nil {
return CHANNEL_RC_NO_MEMORY
}
pChannelOpenEvent.Data = pData
pChannelOpenEvent.DataLength = dataLength
pChannelOpenEvent.UserData = pUserData
pChannelOpenEvent.pChannelOpenData = pChannelOpenData
/*message.context = channels;
message.id = 0;
message.wParam = pChannelOpenEvent;
message.lParam = NULL;
message.Free = channel_queue_message_free;
if (!MessageQueue_Dispatch(channels->queue, &message))
{
free(pChannelOpenEvent);
return CHANNEL_RC_NO_MEMORY;
}*/
return CHANNEL_RC_OK
}
+304
View File
@@ -0,0 +1,304 @@
package plugin
import "C"
import (
"bytes"
"fmt"
"unsafe"
"github.com/shadow1ng/fscan/mylib/grdp/glog"
"github.com/shadow1ng/fscan/mylib/grdp/core"
"github.com/shadow1ng/fscan/mylib/grdp/emission"
)
const (
CHANNEL_RC_OK = 0
CHANNEL_RC_ALREADY_INITIALIZED = 1
CHANNEL_RC_NOT_INITIALIZED = 2
CHANNEL_RC_ALREADY_CONNECTED = 3
CHANNEL_RC_NOT_CONNECTED = 4
CHANNEL_RC_TOO_MANY_CHANNELS = 5
CHANNEL_RC_BAD_CHANNEL = 6
CHANNEL_RC_BAD_CHANNEL_HANDLE = 7
CHANNEL_RC_NO_BUFFER = 8
CHANNEL_RC_BAD_INIT_HANDLE = 9
CHANNEL_RC_NOT_OPEN = 10
CHANNEL_RC_BAD_PROC = 11
CHANNEL_RC_NO_MEMORY = 12
CHANNEL_RC_UNKNOWN_CHANNEL_NAME = 13
CHANNEL_RC_ALREADY_OPEN = 14
CHANNEL_RC_NOT_IN_VIRTUALCHANNELENTRY = 15
CHANNEL_RC_NULL_DATA = 16
CHANNEL_RC_ZERO_LENGTH = 17
CHANNEL_RC_INVALID_INSTANCE = 18
CHANNEL_RC_UNSUPPORTED_VERSION = 19
CHANNEL_RC_INITIALIZATION_ERROR = 20
)
const (
VIRTUAL_CHANNEL_VERSION_WIN2000 = 1
)
const (
CHANNEL_EVENT_INITIALIZED = 0
CHANNEL_EVENT_CONNECTED = 1
CHANNEL_EVENT_V1_CONNECTED = 2
CHANNEL_EVENT_DISCONNECTED = 3
CHANNEL_EVENT_TERMINATED = 4
CHANNEL_EVENT_REMOTE_CONTROL_START = 5
CHANNEL_EVENT_REMOTE_CONTROL_STOP = 6
CHANNEL_EVENT_ATTACHED = 7
CHANNEL_EVENT_DETACHED = 8
CHANNEL_EVENT_DATA_RECEIVED = 10
CHANNEL_EVENT_WRITE_COMPLETE = 11
CHANNEL_EVENT_WRITE_CANCELLED = 12
)
const (
CHANNEL_OPTION_INITIALIZED = 0x80000000
CHANNEL_OPTION_ENCRYPT_RDP = 0x40000000
CHANNEL_OPTION_ENCRYPT_SC = 0x20000000
CHANNEL_OPTION_ENCRYPT_CS = 0x10000000
CHANNEL_OPTION_PRI_HIGH = 0x08000000
CHANNEL_OPTION_PRI_MED = 0x04000000
CHANNEL_OPTION_PRI_LOW = 0x02000000
CHANNEL_OPTION_COMPRESS_RDP = 0x00800000
CHANNEL_OPTION_COMPRESS = 0x00400000
CHANNEL_OPTION_SHOW_PROTOCOL = 0x00200000
CHANNEL_OPTION_REMOTE_CONTROL_PERSISTENT = 0x00100000
)
type ChannelDef struct {
Name string
Options uint32
}
type CHANNEL_INIT_EVENT_EX_FN func(lpUserParam interface{},
pInitHandle interface{}, event uint, pData uintptr, dataLength uint)
type VIRTUALCHANNELINITEX func(lpUserParam interface{}, clientContext interface{},
pInitHandle interface{}, pChannel []ChannelDef,
channelCount int, versionRequested uint32,
pChannelInitEventProcEx CHANNEL_INIT_EVENT_EX_FN) uint
type CHANNEL_OPEN_EVENT_EX_FN func(lpUserParam uintptr,
openHandle uint32, event uint,
pData uintptr, dataLength uint32, totalLength uint32, dataFlags uint32)
type VIRTUALCHANNELOPENEX func(pInitHandle interface{}, pOpenHandle *uint32,
pChannelName string,
pChannelOpenEventProcEx *CHANNEL_OPEN_EVENT_EX_FN) uint
type VIRTUALCHANNELCLOSEEX func(pInitHandle interface{}, openHandle uint32) uint
type VIRTUALCHANNELWRITEEX func(pInitHandle interface{}, openHandle uint32, pData interface{},
dataLength uint32, pUserData interface{}) uint
type ChannelEntryPointsEx struct {
CbSize uint32
ProtocolVersion uint32
PVirtualChannelInitEx VIRTUALCHANNELINITEX
PVirtualChannelOpenEx VIRTUALCHANNELOPENEX
PVirtualChannelCloseEx VIRTUALCHANNELCLOSEEX
PVirtualChannelWriteEx VIRTUALCHANNELWRITEEX
}
func NewChannelEntryPointsEx() *ChannelEntryPointsEx {
e := &ChannelEntryPointsEx{}
e.CbSize = uint32(unsafe.Sizeof(e))
e.ProtocolVersion = VIRTUAL_CHANNEL_VERSION_WIN2000
return e
}
type VIRTUALCHANNELENTRYEX func(pEntryPointsEx *ChannelEntryPointsEx,
pInitHandle interface{}) error
/*
type ChannelEntryPoints struct {
CbSize uint32
ProtocolVersion uint32
PVirtualChannelInit PVIRTUALCHANNELINIT
PVirtualChannelOpen PVIRTUALCHANNELOPEN
PVirtualChannelClose PVIRTUALCHANNELCLOSE
PVirtualChannelWrite PVIRTUALCHANNELWRITE
}
typedef VOID VCAPITYPE CHANNEL_INIT_EVENT_FN(LPVOID pInitHandle,
UINT event, LPVOID pData, UINT dataLength);
typedef CHANNEL_INIT_EVENT_FN* PCHANNEL_INIT_EVENT_FN;
typedef VOID VCAPITYPE CHANNEL_OPEN_EVENT_FN(DWORD openHandle, UINT event,
LPVOID pData, UINT32 dataLength, UINT32 totalLength, UINT32 dataFlags);
typedef CHANNEL_OPEN_EVENT_FN* PCHANNEL_OPEN_EVENT_FN;
typedef UINT VCAPITYPE VIRTUALCHANNELINIT(LPVOID* ppInitHandle, PCHANNEL_DEF pChannel,
INT channelCount, ULONG versionRequested,
PCHANNEL_INIT_EVENT_FN pChannelInitEventProc);
typedef VIRTUALCHANNELINIT* PVIRTUALCHANNELINIT;
typedef UINT VCAPITYPE VIRTUALCHANNELOPEN(LPVOID pInitHandle, LPDWORD pOpenHandle,
PCHAR pChannelName,
PCHANNEL_OPEN_EVENT_FN pChannelOpenEventProc);
typedef VIRTUALCHANNELOPEN* PVIRTUALCHANNELOPEN;
typedef UINT VCAPITYPE VIRTUALCHANNELCLOSE(DWORD openHandle);
typedef VIRTUALCHANNELCLOSE* PVIRTUALCHANNELCLOSE;
typedef UINT VCAPITYPE VIRTUALCHANNELWRITE(DWORD openHandle, LPVOID pData, ULONG dataLength,
LPVOID pUserData);
typedef VIRTUALCHANNELWRITE* PVIRTUALCHANNELWRITE;
typedef UINT VCAPITYPE VIRTUALCHANNELINITEX(LPVOID lpUserParam, LPVOID clientContext,
LPVOID pInitHandle, PCHANNEL_DEF pChannel,
INT channelCount, ULONG versionRequested,
PCHANNEL_INIT_EVENT_EX_FN pChannelInitEventProcEx);
typedef VIRTUALCHANNELINITEX* PVIRTUALCHANNELINITEX;
typedef UINT VCAPITYPE VIRTUALCHANNELOPENEX(LPVOID pInitHandle, LPDWORD pOpenHandle,
PCHAR pChannelName,
PCHANNEL_OPEN_EVENT_EX_FN pChannelOpenEventProcEx);
typedef VIRTUALCHANNELOPENEX* PVIRTUALCHANNELOPENEX;
typedef UINT VCAPITYPE VIRTUALCHANNELCLOSEEX(LPVOID pInitHandle, DWORD openHandle);
typedef VIRTUALCHANNELCLOSEEX* PVIRTUALCHANNELCLOSEEX;
typedef UINT VCAPITYPE VIRTUALCHANNELWRITEEX(LPVOID pInitHandle, DWORD openHandle, LPVOID pData,
ULONG dataLength, LPVOID pUserData);
typedef VIRTUALCHANNELWRITEEX* PVIRTUALCHANNELWRITEEX;
*/
// static channel name
const (
CLIPRDR_SVC_CHANNEL_NAME = "cliprdr" //剪切板
RDPDR_SVC_CHANNEL_NAME = "rdpdr" //设备重定向(打印机,磁盘,端口,智能卡等)
RDPSND_SVC_CHANNEL_NAME = "rdpsnd" //音频输出
RAIL_SVC_CHANNEL_NAME = "rail" //远程应用
DRDYNVC_SVC_CHANNEL_NAME = "drdynvc" //动态虚拟通道
REMDESK_SVC_CHANNEL_NAME = "remdesk" //远程协助
)
const (
RDPGFX_DVC_CHANNEL_NAME = "Microsoft::Windows::RDS::Graphics" //图形扩展
)
var StaticVirtualChannels = map[string]int{
CLIPRDR_SVC_CHANNEL_NAME: CHANNEL_OPTION_INITIALIZED | CHANNEL_OPTION_ENCRYPT_RDP |
CHANNEL_OPTION_COMPRESS_RDP | CHANNEL_OPTION_SHOW_PROTOCOL,
RDPDR_SVC_CHANNEL_NAME: CHANNEL_OPTION_INITIALIZED | CHANNEL_OPTION_ENCRYPT_RDP | CHANNEL_OPTION_COMPRESS_RDP,
RDPSND_SVC_CHANNEL_NAME: CHANNEL_OPTION_INITIALIZED | CHANNEL_OPTION_ENCRYPT_RDP |
CHANNEL_OPTION_COMPRESS_RDP | CHANNEL_OPTION_SHOW_PROTOCOL,
RAIL_SVC_CHANNEL_NAME: CHANNEL_OPTION_INITIALIZED | CHANNEL_OPTION_ENCRYPT_RDP |
CHANNEL_OPTION_COMPRESS_RDP | CHANNEL_OPTION_SHOW_PROTOCOL,
}
const (
CHANNEL_CHUNK_LENGTH = 1600
CHANNEL_FLAG_FIRST = 0x01
CHANNEL_FLAG_LAST = 0x02
CHANNEL_FLAG_SHOW_PROTOCOL = 0x10
)
type ChannelTransport interface {
GetType() (string, uint32)
Sender(core.ChannelSender)
Process(s []byte)
}
type ChannelClient struct {
ChannelDef
t ChannelTransport
}
type Channels struct {
emission.Emitter
channels map[string]ChannelClient
transport core.Transport
buff *bytes.Buffer
channelSender core.ChannelSender
}
func NewChannels(t core.Transport) *Channels {
c := &Channels{
Emitter: *emission.NewEmitter(),
channels: make(map[string]ChannelClient, 20),
transport: t,
buff: &bytes.Buffer{},
}
t.On("channel", c.process)
return c
}
func (c *Channels) SetChannelSender(f core.ChannelSender) {
c.channelSender = f
}
func (c *Channels) Register(t ChannelTransport) {
name, option := t.GetType()
_, ok := c.channels[name]
if ok {
glog.Warn("Already register channel:", name)
return
}
t.Sender(c)
c.channels[name] = ChannelClient{ChannelDef{name, option}, t}
}
func (c *Channels) SendToChannel(channel string, s []byte) (int, error) {
cli, ok := c.channels[channel]
if !ok {
glog.Warn("No register channel:", channel)
return 0, fmt.Errorf("No register channel: %s", channel)
}
idx := 0
ln := len(s)
b := &bytes.Buffer{}
for ln > 0 {
var flag uint32 = 0
if cli.Options&CHANNEL_OPTION_SHOW_PROTOCOL != 0 {
flag |= CHANNEL_FLAG_SHOW_PROTOCOL
}
if idx == 0 {
flag |= CHANNEL_FLAG_FIRST
}
var ss []byte
if ln > CHANNEL_CHUNK_LENGTH {
ss = s[idx : idx+CHANNEL_CHUNK_LENGTH]
idx += CHANNEL_CHUNK_LENGTH
} else {
flag |= CHANNEL_FLAG_LAST
ss = s[idx : idx+ln]
}
glog.Debug("len:", len(ss), "flag:", flag)
ln -= len(ss)
b.Reset()
core.WriteUInt32LE(uint32(len(s)), b)
core.WriteUInt32LE(flag, b)
b.Write(ss)
c.channelSender.SendToChannel(channel, b.Bytes())
}
return ln, nil
}
func (c *Channels) process(channel string, s []byte) {
cli, ok := c.channels[channel]
if !ok {
glog.Warn("No found channel:", channel)
return
}
r := bytes.NewReader(s)
ln, _ := core.ReadUInt32LE(r)
flags, _ := core.ReadUInt32LE(r)
glog.Debugf("channel:%s length: %d, flags: %d", channel, ln, flags)
if flags&CHANNEL_FLAG_FIRST == 0 || flags&CHANNEL_FLAG_LAST == 0 {
if flags&CHANNEL_FLAG_FIRST != 0 {
c.buff.Reset()
}
b, _ := core.ReadBytes(r.Len(), r)
c.buff.Write(b)
if flags&CHANNEL_FLAG_LAST == 0 {
return
}
s = c.buff.Bytes()
} else {
s, _ = core.ReadBytes(r.Len(), r)
}
cli.t.Process(s)
}
+159
View File
@@ -0,0 +1,159 @@
package drdynvc
import (
"bytes"
"encoding/hex"
"io"
"github.com/shadow1ng/fscan/mylib/grdp/core"
"github.com/shadow1ng/fscan/mylib/grdp/glog"
"github.com/shadow1ng/fscan/mylib/grdp/plugin"
)
const (
ChannelName = plugin.DRDYNVC_SVC_CHANNEL_NAME
ChannelOption = plugin.CHANNEL_OPTION_INITIALIZED |
plugin.CHANNEL_OPTION_ENCRYPT_RDP
)
const (
MAX_DVC_CHANNELS = 20
)
const (
DYNVC_CREATE_REQ = 0x01
DYNVC_DATA_FIRST = 0x02
DYNVC_DATA = 0x03
DYNVC_CLOSE = 0x04
DYNVC_CAPABILITIES = 0x05
DYNVC_DATA_FIRST_COMPRESSED = 0x06
DYNVC_DATA_COMPRESSED = 0x07
DYNVC_SOFT_SYNC_REQUEST = 0x08
DYNVC_SOFT_SYNC_RESPONSE = 0x09
)
type ChannelClient struct {
}
type DvcClient struct {
w core.ChannelSender
channels map[string]ChannelClient
}
func NewDvcClient() *DvcClient {
return &DvcClient{
channels: make(map[string]ChannelClient, 100),
}
}
func (c *DvcClient) LoadAddin(f core.ChannelSender) {
}
type DvcHeader struct {
cmd uint8
sp uint8
cbChId uint8
}
func readHeader(r io.Reader) *DvcHeader {
value, _ := core.ReadUInt8(r)
cmd := (value & 0xf0) >> 4
sp := (value & 0x0c) >> 2
cbChId := (value & 0x03) >> 0
return &DvcHeader{cmd, sp, cbChId}
}
func (h *DvcHeader) serialize(channelId uint32) []byte {
b := &bytes.Buffer{}
core.WriteUInt8((h.cmd<<4)|(h.sp<<2)|h.cbChId, b)
if h.cbChId == 0 {
core.WriteUInt8(uint8(channelId), b)
} else if h.cbChId == 1 {
core.WriteUInt16LE(uint16(channelId), b)
} else {
core.WriteUInt32LE(channelId, b)
}
return b.Bytes()
}
func (c *DvcClient) Send(s []byte) (int, error) {
glog.Debug("len:", len(s), "data:", hex.EncodeToString(s))
name, _ := c.GetType()
return c.w.SendToChannel(name, s)
}
func (c *DvcClient) Sender(f core.ChannelSender) {
c.w = f
}
func (c *DvcClient) GetType() (string, uint32) {
return ChannelName, ChannelOption
}
func (c *DvcClient) Process(s []byte) {
glog.Debug("recv:", hex.EncodeToString(s))
r := bytes.NewReader(s)
hdr := readHeader(r)
glog.Infof("dvc: Cmd=0x%x, Sp=%d CbChId=%d all=%d", hdr.cmd, hdr.sp, hdr.cbChId, r.Len())
b, _ := core.ReadBytes(r.Len(), r)
switch hdr.cmd {
case DYNVC_CAPABILITIES:
glog.Info("DYNVC_CAPABILITIES")
c.processCapsPdu(hdr, b)
case DYNVC_CREATE_REQ:
glog.Info("DYNVC_CREATE_REQ")
c.processCreateReq(hdr, b)
case DYNVC_DATA_FIRST:
glog.Info("DYNVC_DATA_FIRST")
case DYNVC_DATA:
glog.Info("DYNVC_DATA")
case DYNVC_CLOSE:
glog.Info("DYNVC_CLOSE")
default:
glog.Errorf("type 0x%x not supported", hdr.cmd)
}
}
func (c *DvcClient) processCreateReq(hdr *DvcHeader, s []byte) {
r := bytes.NewReader(s)
channelId := readDvcId(r, hdr.cbChId)
name, _ := core.ReadBytes(r.Len(), r)
channelName := string(name)
glog.Infof("Server requests channelId=%d, name=%s", channelId, channelName)
//response
b := &bytes.Buffer{}
b.Write(hdr.serialize(channelId))
core.WriteUInt32LE(0, b)
c.Send(b.Bytes())
}
func readDvcId(r io.Reader, cbLen uint8) (id uint32) {
switch cbLen {
case 0:
i, _ := core.ReadUInt8(r)
id = uint32(i)
case 1:
i, _ := core.ReadUint16LE(r)
id = uint32(i)
default:
id, _ = core.ReadUInt32LE(r)
}
return
}
func (c *DvcClient) processCapsPdu(hdr *DvcHeader, s []byte) {
r := bytes.NewReader(s)
core.ReadUInt8(r)
ver, _ := core.ReadUint16LE(r)
glog.Infof("Server supports dvc=%d", ver)
hdr.cmd = DYNVC_CAPABILITIES
hdr.cbChId = 0
hdr.sp = 0
b := &bytes.Buffer{}
core.WriteUInt16LE(0x0050, b)
core.WriteUInt16LE(ver, b)
c.Send(b.Bytes())
}
+451
View File
@@ -0,0 +1,451 @@
// rail.go
package rail
import (
"bytes"
"encoding/hex"
"github.com/shadow1ng/fscan/mylib/grdp/core"
"github.com/shadow1ng/fscan/mylib/grdp/glog"
"github.com/shadow1ng/fscan/mylib/grdp/plugin"
)
const (
ChannelName = plugin.RAIL_SVC_CHANNEL_NAME
ChannelOption = plugin.CHANNEL_OPTION_INITIALIZED | plugin.CHANNEL_OPTION_ENCRYPT_RDP |
plugin.CHANNEL_OPTION_COMPRESS_RDP | plugin.CHANNEL_OPTION_SHOW_PROTOCOL
)
const (
TS_RAIL_ORDER_EXEC = 0x0001
TS_RAIL_ORDER_ACTIVATE = 0x0002
TS_RAIL_ORDER_SYSPARAM = 0x0003
TS_RAIL_ORDER_SYSCOMMAND = 0x0004
TS_RAIL_ORDER_HANDSHAKE = 0x0005
TS_RAIL_ORDER_NOTIFY_EVENT = 0x0006
TS_RAIL_ORDER_WINDOWMOVE = 0x0008
TS_RAIL_ORDER_LOCALMOVESIZE = 0x0009
TS_RAIL_ORDER_MINMAXINFO = 0x000A
TS_RAIL_ORDER_CLIENTSTATUS = 0x000B
TS_RAIL_ORDER_SYSMENU = 0x000C
TS_RAIL_ORDER_LANGBARINFO = 0x000D
TS_RAIL_ORDER_GET_APPID_REQ = 0x000E
TS_RAIL_ORDER_GET_APPID_RESP = 0x000F
TS_RAIL_ORDER_TASKBARINFO = 0x0010
TS_RAIL_ORDER_LANGUAGEIMEINFO = 0x0011
TS_RAIL_ORDER_COMPARTMENTINFO = 0x0012
TS_RAIL_ORDER_HANDSHAKE_EX = 0x0013
TS_RAIL_ORDER_ZORDER_SYNC = 0x0014
TS_RAIL_ORDER_CLOAK = 0x0015
TS_RAIL_ORDER_POWER_DISPLAY_REQUEST = 0x0016
TS_RAIL_ORDER_SNAP_ARRANGE = 0x0017
TS_RAIL_ORDER_GET_APPID_RESP_EX = 0x0018
TS_RAIL_ORDER_EXEC_RESULT = 0x0080
)
type RailClient struct {
w core.ChannelSender
DesktopWidth uint16
DesktopHeight uint16
RemoteApplicationProgram string
ShellWorkingDirectory string
RemoteApplicationCmdLine string
}
func NewClient() *RailClient {
return &RailClient{
DesktopWidth: 800,
DesktopHeight: 600,
RemoteApplicationProgram: "calc",
ShellWorkingDirectory: "/tmp",
}
}
type RailPDUHeader struct {
OrderType uint16 `struc:"little"`
OrderLength uint16 `struc:"little"`
}
func NewRailPDUHeader(mType, ln uint16) *RailPDUHeader {
return &RailPDUHeader{
OrderType: mType,
OrderLength: ln,
}
}
func (h *RailPDUHeader) serialize() []byte {
b := &bytes.Buffer{}
core.WriteUInt16LE(h.OrderType, b)
core.WriteUInt16LE(h.OrderLength, b)
return b.Bytes()
}
func (c *RailClient) sendData(mType uint16, ln int, s []byte) {
glog.Debug(ln, ":ln:", len(s), "data:", hex.EncodeToString(s))
header := NewRailPDUHeader(mType, uint16(ln))
b := &bytes.Buffer{}
core.WriteBytes(header.serialize(), b)
core.WriteBytes(s, b)
c.Send(b.Bytes())
}
func (c *RailClient) Send(s []byte) (int, error) {
glog.Debug("len:", len(s), "data:", hex.EncodeToString(s))
name, _ := c.GetType()
return c.w.SendToChannel(name, s)
}
func (c *RailClient) Sender(f core.ChannelSender) {
c.w = f
}
func (c *RailClient) GetType() (string, uint32) {
return ChannelName, ChannelOption
}
func (c *RailClient) Process(s []byte) {
glog.Debug("recv:", hex.EncodeToString(s))
r := bytes.NewReader(s)
msgType, _ := core.ReadUint16LE(r)
length, _ := core.ReadUint16LE(r)
glog.Infof("rail: type=0x%x length=%d, all=%d", msgType, length, r.Len())
b, _ := core.ReadBytes(int(length), r)
glog.Info("b:", hex.EncodeToString(b))
switch msgType {
case TS_RAIL_ORDER_HANDSHAKE:
glog.Info("TS_RAIL_ORDER_HANDSHAKE")
c.processOrderHandshake(b)
case TS_RAIL_ORDER_SYSPARAM:
glog.Info("TS_RAIL_ORDER_SYSPARAM")
c.processOrderSysparam(b)
case TS_RAIL_ORDER_EXEC_RESULT:
glog.Info("TS_RAIL_ORDER_EXEC_RESULT")
c.processExecResult(b)
default:
glog.Errorf("type 0x%x not supported", msgType)
}
}
func (c *RailClient) processOrderHandshake(b []byte) {
r := bytes.NewReader(b)
buildNumber, _ := core.ReadUInt32LE(r)
glog.Info("buildNumber:", buildNumber)
//send client info
c.sendClientStatus()
//send client systemparam
c.sendClientSystemparam()
//send client execute
c.sendClientExecute()
}
const (
TS_RAIL_CLIENTSTATUS_ALLOWLOCALMOVESIZE = 0x00000001
TS_RAIL_CLIENTSTATUS_AUTORECONNECT = 0x00000002
TS_RAIL_CLIENTSTATUS_ZORDER_SYNC = 0x00000004
TS_RAIL_CLIENTSTATUS_WINDOW_RESIZE_MARGIN_SUPPORTED = 0x00000010
TS_RAIL_CLIENTSTATUS_HIGH_DPI_ICONS_SUPPORTED = 0x00000020
TS_RAIL_CLIENTSTATUS_APPBAR_REMOTING_SUPPORTED = 0x00000040
TS_RAIL_CLIENTSTATUS_POWER_DISPLAY_REQUEST_SUPPORTED = 0x00000080
TS_RAIL_CLIENTSTATUS_GET_APPID_RESPONSE_EX_SUPPORTED = 0x00000100
TS_RAIL_CLIENTSTATUS_BIDIRECTIONAL_CLOAK_SUPPORTED = 0x00000200
)
func (c *RailClient) sendClientStatus() {
glog.Info("Send client Status")
var flags uint32 = TS_RAIL_CLIENTSTATUS_ALLOWLOCALMOVESIZE
//if (settings->AutoReconnectionEnabled)
//clientStatus.flags |= TS_RAIL_CLIENTSTATUS_AUTORECONNECT;
flags |= TS_RAIL_CLIENTSTATUS_ZORDER_SYNC
flags |= TS_RAIL_CLIENTSTATUS_WINDOW_RESIZE_MARGIN_SUPPORTED
flags |= TS_RAIL_CLIENTSTATUS_APPBAR_REMOTING_SUPPORTED
flags |= TS_RAIL_CLIENTSTATUS_POWER_DISPLAY_REQUEST_SUPPORTED
flags |= TS_RAIL_CLIENTSTATUS_BIDIRECTIONAL_CLOAK_SUPPORTED
b := &bytes.Buffer{}
core.WriteUInt32LE(flags, b)
c.sendData(TS_RAIL_ORDER_CLIENTSTATUS, 4, b.Bytes())
}
const (
SPI_SET_SCREEN_SAVE_ACTIVE = 0x00000011
SPI_SET_SCREEN_SAVE_SECURE = 0x00000077
)
const (
/*Bit mask values for SPI_ parameters*/
SPI_MASK_SET_DRAG_FULL_WINDOWS = 0x00000001
SPI_MASK_SET_KEYBOARD_CUES = 0x00000002
SPI_MASK_SET_KEYBOARD_PREF = 0x00000004
SPI_MASK_SET_MOUSE_BUTTON_SWAP = 0x00000008
SPI_MASK_SET_WORK_AREA = 0x00000010
SPI_MASK_DISPLAY_CHANGE = 0x00000020
SPI_MASK_TASKBAR_POS = 0x00000040
SPI_MASK_SET_HIGH_CONTRAST = 0x00000080
SPI_MASK_SET_SCREEN_SAVE_ACTIVE = 0x00000100
SPI_MASK_SET_SET_SCREEN_SAVE_SECURE = 0x00000200
SPI_MASK_SET_CARET_WIDTH = 0x00000400
SPI_MASK_SET_STICKY_KEYS = 0x00000800
SPI_MASK_SET_TOGGLE_KEYS = 0x00001000
SPI_MASK_SET_FILTER_KEYS = 0x00002000
)
const (
SPI_SET_DRAG_FULL_WINDOWS = 0x00000025
SPI_SET_KEYBOARD_CUES = 0x0000100B
SPI_SET_KEYBOARD_PREF = 0x00000045
SPI_SET_MOUSE_BUTTON_SWAP = 0x00000021
SPI_SET_WORK_AREA = 0x0000002F
SPI_DISPLAY_CHANGE = 0x0000F001
SPI_TASKBAR_POS = 0x0000F000
SPI_SET_HIGH_CONTRAST = 0x00000043
SPI_SETCARETWIDTH = 0x00002007
SPI_SETSTICKYKEYS = 0x0000003B
SPI_SETTOGGLEKEYS = 0x00000035
SPI_SETFILTERKEYS = 0x00000033
)
type TsFilterKeys struct {
Flags uint32
WaitTime uint32
DelayTime uint32
RepeatTime uint32
BounceTime uint32
}
type RailHighContrast struct {
flags uint32
colorSchemeLength uint32
colorScheme string
}
type Rectangle16 struct {
left uint16
top uint16
right uint16
bottom uint16
}
type RailSysparamOrder struct {
param uint32
params uint32
dragFullWindows uint8
keyboardCues uint8
keyboardPref uint8
mouseButtonSwap uint8
workArea Rectangle16
displayChange Rectangle16
taskbarPos Rectangle16
highContrast RailHighContrast
caretWidth uint32
stickyKeys uint32
toggleKeys uint32
filterKeys TsFilterKeys
setScreenSaveActive uint8
setScreenSaveSecure uint8
}
func (c *RailClient) sendClientSystemparam() {
glog.Info("Send client Systemparam")
var sp RailSysparamOrder
sp.params = 0
sp.params |= SPI_MASK_SET_HIGH_CONTRAST
sp.highContrast.colorScheme = ""
sp.highContrast.colorSchemeLength = 0
sp.highContrast.flags = 0x7E
sp.params |= SPI_MASK_SET_MOUSE_BUTTON_SWAP
sp.mouseButtonSwap = 0
sp.params |= SPI_MASK_SET_KEYBOARD_PREF
sp.keyboardPref = 0
sp.params |= SPI_MASK_SET_DRAG_FULL_WINDOWS
sp.dragFullWindows = 0
sp.params |= SPI_MASK_SET_KEYBOARD_CUES
sp.keyboardCues = 0
sp.params |= SPI_MASK_SET_WORK_AREA
sp.workArea.left = 0
sp.workArea.top = 0
sp.workArea.right = c.DesktopWidth
sp.workArea.bottom = c.DesktopHeight
if sp.params&SPI_MASK_SET_HIGH_CONTRAST != 0 {
sp.param = SPI_SET_HIGH_CONTRAST
c.sendOneClientSysparam(&sp)
}
if sp.params&SPI_MASK_TASKBAR_POS != 0 {
sp.param = SPI_TASKBAR_POS
c.sendOneClientSysparam(&sp)
}
if sp.params&SPI_MASK_SET_MOUSE_BUTTON_SWAP != 0 {
sp.param = SPI_SET_MOUSE_BUTTON_SWAP
c.sendOneClientSysparam(&sp)
}
if sp.params&SPI_MASK_SET_KEYBOARD_PREF != 0 {
sp.param = SPI_SET_KEYBOARD_PREF
c.sendOneClientSysparam(&sp)
}
if sp.params&SPI_MASK_SET_DRAG_FULL_WINDOWS != 0 {
sp.param = SPI_SET_DRAG_FULL_WINDOWS
c.sendOneClientSysparam(&sp)
}
if sp.params&SPI_MASK_SET_KEYBOARD_CUES != 0 {
sp.param = SPI_SET_KEYBOARD_CUES
c.sendOneClientSysparam(&sp)
}
if sp.params&SPI_MASK_SET_WORK_AREA != 0 {
sp.param = SPI_SET_WORK_AREA
glog.Debug("SPI_SET_WORK_AREA")
c.sendOneClientSysparam(&sp)
}
}
func (c *RailClient) sendOneClientSysparam(sp *RailSysparamOrder) {
length := 0
b := &bytes.Buffer{}
core.WriteUInt32LE(sp.param, b)
switch sp.param {
case SPI_SET_DRAG_FULL_WINDOWS:
core.WriteUInt8(sp.dragFullWindows, b)
case SPI_SET_KEYBOARD_CUES:
core.WriteUInt8(sp.keyboardCues, b)
case SPI_SET_KEYBOARD_PREF:
core.WriteUInt8(sp.keyboardPref, b)
case SPI_SET_MOUSE_BUTTON_SWAP:
core.WriteUInt8(sp.mouseButtonSwap, b)
case SPI_SET_WORK_AREA:
core.WriteUInt16LE(sp.workArea.left, b)
core.WriteUInt16LE(sp.workArea.top, b)
core.WriteUInt16LE(sp.workArea.right, b)
core.WriteUInt16LE(sp.workArea.bottom, b)
case SPI_DISPLAY_CHANGE:
core.WriteUInt16LE(sp.displayChange.left, b)
core.WriteUInt16LE(sp.displayChange.top, b)
core.WriteUInt16LE(sp.displayChange.right, b)
core.WriteUInt16LE(sp.displayChange.bottom, b)
case SPI_TASKBAR_POS:
core.WriteUInt16LE(sp.taskbarPos.left, b)
core.WriteUInt16LE(sp.taskbarPos.top, b)
core.WriteUInt16LE(sp.taskbarPos.right, b)
core.WriteUInt16LE(sp.taskbarPos.bottom, b)
case SPI_SET_HIGH_CONTRAST:
core.WriteUInt32LE(sp.highContrast.flags, b)
core.WriteUInt32LE(sp.highContrast.colorSchemeLength, b)
data := core.UnicodeEncode(sp.highContrast.colorScheme)
core.WriteBytes(data, b)
case SPI_SETFILTERKEYS:
core.WriteUInt32LE(sp.filterKeys.Flags, b)
core.WriteUInt32LE(sp.filterKeys.WaitTime, b)
core.WriteUInt32LE(sp.filterKeys.DelayTime, b)
core.WriteUInt32LE(sp.filterKeys.RepeatTime, b)
core.WriteUInt32LE(sp.filterKeys.BounceTime, b)
case SPI_SETSTICKYKEYS:
core.WriteUInt32LE(sp.stickyKeys, b)
case SPI_SETCARETWIDTH:
core.WriteUInt32LE(sp.caretWidth, b)
case SPI_SETTOGGLEKEYS:
core.WriteUInt32LE(sp.toggleKeys, b)
case SPI_MASK_SET_SET_SCREEN_SAVE_SECURE:
core.WriteUInt8(sp.setScreenSaveSecure, b)
case SPI_MASK_SET_SCREEN_SAVE_ACTIVE:
core.WriteUInt8(sp.setScreenSaveActive, b)
default:
glog.Error("ERROR_BAD_ARGUMENTS")
return
}
c.sendData(TS_RAIL_ORDER_SYSPARAM, length+b.Len(), b.Bytes())
}
type RailExecOrder struct {
flags uint16
RemoteApplicationProgram string
RemoteApplicationWorkingDir string
RemoteApplicationArguments string
}
func (c *RailClient) sendClientExecute() {
glog.Info("Send Client Execute")
var exec RailExecOrder
//exec.flags = TS_RAIL_EXEC_FLAG_EXPAND_ARGUMENTS
exec.RemoteApplicationProgram = c.RemoteApplicationProgram
exec.RemoteApplicationWorkingDir = c.ShellWorkingDirectory
exec.RemoteApplicationArguments = c.RemoteApplicationCmdLine
program := core.UnicodeEncode(exec.RemoteApplicationProgram)
workdir := core.UnicodeEncode(exec.RemoteApplicationWorkingDir)
arguments := core.UnicodeEncode(exec.RemoteApplicationArguments)
length := 4
b := &bytes.Buffer{}
core.WriteUInt16LE(exec.flags, b)
core.WriteUInt16LE(uint16(len(program)), b)
core.WriteUInt16LE(uint16(len(workdir)), b)
core.WriteUInt16LE(uint16(len(arguments)), b)
core.WriteBytes(program, b)
core.WriteBytes(workdir, b)
core.WriteBytes(arguments, b)
length += b.Len()
c.sendData(TS_RAIL_ORDER_EXEC, length, b.Bytes())
}
func (c *RailClient) processOrderSysparam(b []byte) {
r := bytes.NewReader(b)
systemParam, _ := core.ReadUInt32LE(r)
body, _ := core.ReadUInt8(r)
glog.Infof("systemParam:0x%x, body:%d", systemParam, body)
}
const (
//The Client Execute request was successful and the requested application or file has been launched.
RAIL_EXEC_S_OK = 0x0000
//The Client Execute request could not be satisfied because the server is not monitoring the current input desktop.
RAIL_EXEC_E_HOOK_NOT_LOADED = 0x0001
//The Execute request could not be satisfied because the request PDU was malformed.
RAIL_EXEC_E_DECODE_FAILED = 0x0002
//The Client Execute request could not be satisfied because the requested application was blocked by policy from being launched on the server.
RAIL_EXEC_E_NOT_IN_ALLOWLIST = 0x0003
//The Client Execute request could not be satisfied because the application or file path could not be found.
RAIL_EXEC_E_FILE_NOT_FOUND = 0x0005
//The Client Execute request could not be satisfied because an unspecified error occurred on the server.
RAIL_EXEC_E_FAIL = 0x0006
//The Client Execute request could not be satisfied because the remote session is locked.
RAIL_EXEC_E_SESSION_LOCKED = 0x0007
)
func (c *RailClient) processExecResult(b []byte) {
r := bytes.NewReader(b)
flags, _ := core.ReadUint16LE(r)
execResult, _ := core.ReadUint16LE(r)
rawResult, _ := core.ReadUInt32LE(r)
core.ReadUint16LE(r)
exeOrFileLength, _ := core.ReadUint16LE(r)
exeOrFile, _ := core.ReadBytes(r.Len(), r)
glog.Info("flags:", flags, "execResult:", execResult, "rawResult:", rawResult)
glog.Info("length:", exeOrFileLength, "file:", core.UnicodeDecode(exeOrFile))
}
+9
View File
@@ -0,0 +1,9 @@
package rdpgfx
import (
"github.com/shadow1ng/fscan/mylib/grdp/plugin"
)
const (
ChannelName = plugin.RDPGFX_DVC_CHANNEL_NAME
)