Files
fscan/mylib/grdp/login/screen.go
T
ZacharyZcR 25a9776fb9 feat: RDP使用NLA仅验证模式,避免挤掉已登录用户
- 添加ErrNLAAuthSuccess标志用于NLA验证成功信号
- tpkt层支持nlaAuthOnly模式,验证成功后不建立完整会话
- x224层正确传播NLA验证结果
- rdpCrack改用NlaAuth进行凭据验证
2026-01-15 00:21:25 +08:00

705 lines
18 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package login
import (
"bufio"
"bytes"
"errors"
"fmt"
"github.com/shadow1ng/fscan/mylib/grdp/core"
"github.com/shadow1ng/fscan/mylib/grdp/glog"
"github.com/shadow1ng/fscan/mylib/grdp/protocol/nla"
"github.com/shadow1ng/fscan/mylib/grdp/protocol/pdu"
"github.com/shadow1ng/fscan/mylib/grdp/protocol/sec"
"github.com/shadow1ng/fscan/mylib/grdp/protocol/t125"
"github.com/shadow1ng/fscan/mylib/grdp/protocol/tpkt"
"github.com/shadow1ng/fscan/mylib/grdp/protocol/x224"
"golang.org/x/net/context"
"golang.org/x/net/proxy"
"image"
"image/color"
"image/draw"
"image/jpeg"
"log"
"math/rand"
"net"
"net/url"
"os"
"strings"
"time"
)
var (
Socks5Proxy string = ""
LogLever glog.LEVEL = glog.NONE
OutputDir string
)
func init() {
}
func RdpConn(host, domain, user, password string, timeout int64, rdpProtocol uint32) (bool, error) {
g := NewClient(host, LogLever)
status, err, reconnectProtocol := g.ScreenShot(domain, user, password, timeout, rdpProtocol)
if status == true {
return true, err
} else {
if reconnectProtocol != rdpProtocol {
glog.Info("reconnect with protocol:", reconnectProtocol)
return RdpConn(host, domain, user, password, timeout, reconnectProtocol)
} else {
return status, err
}
}
}
func RdpCrack(host, domain, user, password string, timeout int64, rdpProtocol uint32) (bool, error) {
g := NewClient(host, LogLever)
status, err, reconnectProtocol := g.Crack(domain, user, password, timeout, rdpProtocol)
if status == true {
return true, err
} else {
if reconnectProtocol != rdpProtocol {
glog.Info("reconnect with protocol:", reconnectProtocol)
return RdpCrack(host, domain, user, password, timeout, reconnectProtocol)
} else {
return status, err
}
}
}
// NlaAuth 仅进行NLA认证验证,不建立RDP会话,不会挤掉已登录用户
// 返回: (认证成功, 错误信息)
func NlaAuth(host, domain, user, password string, timeout int64) (bool, error) {
g := NewClient(host, LogLever)
return g.NlaAuthOnly(domain, user, password, timeout)
}
type Client struct {
Host string // ip:port
tpkt *tpkt.TPKT
x224 *x224.X224
mcs *t125.MCSClient
sec *sec.Client
pdu *pdu.Client
}
func NewClient(host string, logLevel glog.LEVEL) *Client {
glog.SetLevel(logLevel)
logger := log.New(os.Stdout, "", 0)
glog.SetLogger(logger)
return &Client{
Host: host,
}
}
func WrapperTcpWithTimeout(network, address string, timeout time.Duration) (net.Conn, error) {
local_ip := "0.0.0.0"
net_ip := net.ParseIP(local_ip)
if net_ip == nil {
net_ip = net.ParseIP("0.0.0.0")
}
local_addr := &net.TCPAddr{
IP: net_ip, // 替换为你想要使用的本地IP地址
}
d := &net.Dialer{Timeout: timeout, LocalAddr: local_addr}
return WrapperTCP(network, address, d)
}
func WrapperTCP(network, address string, forward *net.Dialer) (net.Conn, error) {
//get conn
var conn net.Conn
if Socks5Proxy == "" {
var err error
conn, err = forward.Dial(network, address)
if err != nil {
return nil, err
}
} else {
dailer, err := Socks5Dailer(forward)
if err != nil {
return nil, err
}
conn, err = dailer.Dial(network, address)
if err != nil {
// fmt.Println(err)
return nil, err
}
}
timeout := forward.Timeout
if err := conn.SetWriteDeadline(time.Now().Add(timeout * 6)); err != nil {
return nil, err
}
if err := conn.SetReadDeadline(time.Now().Add(timeout * 6)); err != nil {
return nil, err
}
return conn, nil
}
func Socks5Dailer(forward *net.Dialer) (proxy.Dialer, error) {
u, err := url.Parse(Socks5Proxy)
if err != nil {
return nil, err
}
if strings.ToLower(u.Scheme) != "socks5" {
return nil, errors.New("Only support socks5")
}
address := u.Host
var auth proxy.Auth
var dailer proxy.Dialer
if u.User.String() != "" {
auth = proxy.Auth{}
auth.User = u.User.Username()
password, _ := u.User.Password()
auth.Password = password
dailer, err = proxy.SOCKS5("tcp", address, &auth, forward)
} else {
dailer, err = proxy.SOCKS5("tcp", address, nil, forward)
}
if err != nil {
return nil, err
}
return dailer, nil
}
type Bitmap struct {
DestLeft int `json:"destLeft"`
DestTop int `json:"destTop"`
DestRight int `json:"destRight"`
DestBottom int `json:"destBottom"`
Width int `json:"width"`
Height int `json:"height"`
BitsPerPixel int `json:"bitsPerPixel"`
IsCompress bool `json:"isCompress"`
Data []byte `json:"data"`
}
func Bpp(BitsPerPixel uint16) (pixel int) {
switch BitsPerPixel {
case 15:
pixel = 1
case 16:
pixel = 2
case 24:
pixel = 3
case 32:
pixel = 4
default:
glog.Error("-------------------------------------Bpp func. invalid bitmap data format")
}
return
}
func BitmapDecompress(bitmap *pdu.BitmapData) []byte {
return core.Decompress(bitmap.BitmapDataStream, int(bitmap.Width), int(bitmap.Height), Bpp(bitmap.BitsPerPixel))
}
func ToRGBA(pixel int, i int, data []byte) (r, g, b, a uint8) {
a = 255
switch pixel {
case 1:
rgb555 := core.Uint16BE(data[i], data[i+1])
r, g, b = core.RGB555ToRGB(rgb555)
case 2:
rgb565 := core.Uint16BE(data[i], data[i+1])
r, g, b = core.RGB565ToRGB(rgb565)
case 3, 4:
fallthrough
default:
r, g, b = data[i+2], data[i+1], data[i]
}
return
}
// NlaAuthOnly 仅进行NLA认证验证凭据,不建立RDP会话
// 这样不会挤掉已登录的用户
func (g *Client) NlaAuthOnly(domain, user, pwd string, timeout int64) (bool, error) {
conn, err := WrapperTcpWithTimeout("tcp", g.Host, time.Duration(timeout)*time.Second)
if err != nil {
return false, fmt.Errorf("[dial err] %v", err)
}
defer conn.Close()
g.tpkt = tpkt.New(core.NewSocketLayer(conn), nla.NewNTLMv2(domain, user, pwd))
g.x224 = x224.New(g.tpkt)
// 设置NLA仅验证模式
g.tpkt.SetNLAAuthOnly(true)
// 使用 PROTOCOL_HYBRID (NLA) 协议
g.x224.SetRequestedProtocol(x224.PROTOCOL_HYBRID)
// 用于接收结果的通道
resultChan := make(chan error, 1)
// 监听错误事件(包括 ErrNLAAuthSuccess)
g.x224.On("error", func(err error) {
resultChan <- err
})
// 监听连接事件(不应该发生在 auth-only 模式)
g.x224.On("connect", func(protocol uint32) {
resultChan <- fmt.Errorf("unexpected connect in auth-only mode")
})
// 发起连接
err = g.x224.Connect()
if err != nil {
return false, err
}
// 等待结果或超时
select {
case err := <-resultChan:
if err == tpkt.ErrNLAAuthSuccess {
return true, nil
}
return false, err
case <-time.After(time.Duration(timeout*3) * time.Second):
return false, fmt.Errorf("NLA auth timeout")
}
}
func (g *Client) ProbeOSInfo(host, domain, user, pwd string, timeout int64, rdpProtocol uint32) (info map[string]any) {
start := time.Now()
exitFlag := make(chan bool)
info = make(map[string]any)
targetSlice := strings.Split(g.Host, ":")
ip := targetSlice[0]
conn, err := WrapperTcpWithTimeout("tcp", g.Host, time.Duration(timeout)*time.Second)
if err != nil {
return
}
defer conn.Close()
glog.Info(conn.LocalAddr().String())
g.tpkt = tpkt.New(core.NewSocketLayer(conn), nla.NewNTLMv2(domain, user, pwd))
g.x224 = x224.New(g.tpkt)
g.mcs = t125.NewMCSClient(g.x224)
g.sec = sec.NewClient(g.mcs)
g.pdu = pdu.NewClient(g.sec)
g.sec.SetUser(user)
g.sec.SetPwd(pwd)
g.sec.SetDomain(domain)
g.tpkt.SetFastPathListener(g.sec)
g.sec.SetFastPathListener(g.pdu)
g.pdu.SetFastPathSender(g.tpkt)
g.sec.SetChannelSender(g.mcs)
g.tpkt.On("os_info", func(infoMap map[string]any) {
glog.Debug("[+] callback, get os info ........................")
for k, v := range infoMap {
glog.Debugf("%s: %s\n", k, v)
}
info = infoMap
g.pdu.Emit("done")
})
g.x224.SetRequestedProtocol(rdpProtocol) //x224.PROTOCOL_SSL , x224.PROTOCOL_RDP , x224.PROTOCOL_HYBRID , x224.PROTOCOL_HYBRID_EX
g.x224.On("reconnect", func(protocol uint32) {
info["reconn"] = protocol
g.pdu.Emit("close")
exitFlag <- true
})
err = g.x224.Connect()
if err != nil {
info["err"] = err.Error()
return
}
glog.Info("wait connect ok")
g.pdu.On("error", func(e error) {
err = e
glog.Error("error", e)
g.pdu.Emit("done")
})
g.pdu.On("close", func() {
err = errors.New("close")
glog.Info("on close")
g.pdu.Emit("done")
})
g.pdu.On("success", func() {
glog.Debugf("===============login success %s===============", ip)
err = nil
g.pdu.Emit("done")
})
g.pdu.On("ready", func() {
err = nil
glog.Debug("on ready")
})
g.pdu.On("bitmap", func(rectangles []pdu.BitmapData) {
})
g.pdu.On("done", func() {
glog.Debug("done信号触发")
exitFlag <- true
})
ctx, cancel := context.WithTimeout(context.Background(), time.Duration(timeout*3)*time.Second)
defer cancel()
loop:
for {
select {
case <-time.After(time.Second * time.Duration(timeout)): //
break loop
case <-exitFlag:
break loop
case <-ctx.Done():
glog.Debug("总超时已达到,退出")
break loop
}
}
glog.Debug("循环结束,总时间过去了:", time.Since(start))
return info
}
func (g *Client) ScreenShot(domain, user, pwd string, timeout int64, rdpProtocol uint32) (status bool, err error, reconnProtocol uint32) {
//glog.SetLevel(glog.ERROR)
reconnProtocol = rdpProtocol
pic_length := 1280 //1280
pic_width := 800 //800
needReconnect := false
isScreenOK := false
refresh := make(chan bool)
exitFlag := make(chan bool)
start := time.Now()
now := start
screenImage := image.NewRGBA(image.Rect(0, 0, pic_length, pic_width))
targetSlice := strings.Split(g.Host, ":")
ip := targetSlice[0]
port := targetSlice[1]
status = false
conn, err := WrapperTcpWithTimeout("tcp", g.Host, time.Duration(timeout)*time.Second)
if err != nil {
return status, fmt.Errorf("[dial err] %v", err), reconnProtocol
}
defer conn.Close()
glog.Info(conn.LocalAddr().String())
g.tpkt = tpkt.New(core.NewSocketLayer(conn), nla.NewNTLMv2(domain, user, pwd))
g.x224 = x224.New(g.tpkt)
g.mcs = t125.NewMCSClient(g.x224)
g.sec = sec.NewClient(g.mcs)
g.pdu = pdu.NewClient(g.sec)
g.sec.SetUser(user)
g.sec.SetPwd(pwd)
g.sec.SetDomain(domain)
//g.sec.SetClientAutoReconnect()
g.tpkt.SetFastPathListener(g.sec)
g.sec.SetFastPathListener(g.pdu)
g.pdu.SetFastPathSender(g.tpkt)
g.sec.SetChannelSender(g.mcs)
g.tpkt.On("os_info", func(info map[string]any) {
glog.Debug("[+] callback, get os info ........................")
for k, v := range info {
glog.Debugf("%s: %s\n", k, v)
}
})
g.x224.SetRequestedProtocol(rdpProtocol) //x224.PROTOCOL_SSL , x224.PROTOCOL_RDP , x224.PROTOCOL_HYBRID , x224.PROTOCOL_HYBRID_EX
g.x224.On("reconnect", func(protocol uint32) {
needReconnect = true
reconnProtocol = protocol
glog.Info("need reconnect with protocol:", protocol)
g.pdu.Emit("close")
exitFlag <- true
})
g.x224.On("more_timeout", func() {
timeout += 18 //如果是PROTOCOL_RDP协议,可以适当延长超时时间
})
err = g.x224.Connect()
if err != nil {
return status, fmt.Errorf("[x224 connect err] %v", err), reconnProtocol
}
glog.Info("wait connect ok")
g.pdu.On("error", func(e error) {
err = e
glog.Error("error", e)
g.pdu.Emit("done")
})
g.pdu.On("close", func() {
err = errors.New("close")
glog.Info("on close")
g.pdu.Emit("done")
})
g.pdu.On("success", func() {
glog.Debugf("===============login success %s===============", ip)
status = true
err = nil
g.pdu.Emit("done")
})
g.pdu.On("ready", func() {
err = nil
glog.Debug("on ready")
//g.pdu.Emit("done")
})
g.pdu.On("bitmap", func(rectangles []pdu.BitmapData) {
now = time.Now()
// 发送一个鼠标事件,作用是与服务器保持联系不要断开
rand.New(rand.NewSource(time.Now().UnixNano()))
randomNumber := rand.Intn(1000) + 10 // 10到1000之间的随机数
mouseX := 60 + randomNumber
mouseY := 10 + randomNumber
pevent := &pdu.PointerEvent{}
pevent.PointerFlags |= pdu.PTRFLAGS_MOVE
pevent.XPos = uint16(mouseX)
pevent.YPos = uint16(mouseY)
g.pdu.SendInputEvents(pdu.INPUT_EVENT_MOUSE, []pdu.InputEventsInterface{pevent})
glog.Debug("on update bitmap:", len(rectangles))
bs := make([]Bitmap, 0)
for _, v := range rectangles {
IsCompress := v.IsCompress()
data := v.BitmapDataStream
if IsCompress {
data = BitmapDecompress(&v)
IsCompress = false
}
b := Bitmap{int(v.DestLeft), int(v.DestTop), int(v.DestRight), int(v.DestBottom),
int(v.Width), int(v.Height), Bpp(v.BitsPerPixel), IsCompress, data}
bs = append(bs, b)
}
var (
pixel int
i int
r, g, b, a uint8
)
for _, bm := range bs {
i = 0
pixel = bm.BitsPerPixel
m := image.NewRGBA(image.Rect(0, 0, bm.Width, bm.Height))
for y := 0; y < bm.Height; y++ {
for x := 0; x < bm.Width; x++ {
r, g, b, a = ToRGBA(pixel, i, bm.Data)
c := color.RGBA{R: r, G: g, B: b, A: a}
i += pixel
m.Set(x, y, c)
}
}
draw.Draw(screenImage, screenImage.Bounds().Add(image.Pt(bm.DestLeft, bm.DestTop)), m, m.Bounds().Min, draw.Src)
}
// Encode to jpeg.
//var imageBuf bytes.Buffer
//err = jpeg.Encode(&imageBuf, screenImage, nil)
//
//if err != nil {
// glog.Info("trans bitmap to jpeg err:", err)
//}
// Write to file.
//fo, err := os.Create(fmt.Sprintf("img/%s-%d.jpg", ip, index))
//if err != nil {
// panic(err)
//}
//index += 1
//fw := bufio.NewWriter(fo)
//fw.Write(imageBuf.Bytes())
isScreenOK = true
refresh <- true
})
g.pdu.On("done", func() {
glog.Debug("done信号触发")
exitFlag <- true
})
ctx, cancel := context.WithTimeout(context.Background(), time.Duration(timeout*6)*time.Second)
defer cancel()
loop:
for {
select {
case <-time.After(time.Second * time.Duration(timeout*3)): //
glog.Debug("本次获取bitmap超时了, 距离上次获取到图像过去了:", time.Since(now))
break loop
case <-refresh:
continue loop
case <-exitFlag:
break loop
case <-ctx.Done():
glog.Debug("总超时已达到,退出")
break loop
}
}
glog.Debug("循环结束,总时间过去了:", time.Since(start))
// 认证结果由 success 事件回调设置,不在此处覆盖
if needReconnect {
return status, err, reconnProtocol
} else if isScreenOK {
glog.Info("get screen ok")
// Encode to jpeg.
var imageBuf bytes.Buffer
encodeErr := jpeg.Encode(&imageBuf, screenImage, nil)
if encodeErr != nil {
glog.Error("Failed to encode screenshot:", encodeErr)
return status, err, reconnProtocol
}
// Write to file.
saveDate := time.Now().Format("2006_01_02_15_04_05")
fo, writeErr := os.Create(fmt.Sprintf("%s/%s_%s_%s.jpg", OutputDir, ip, port, saveDate))
if writeErr != nil {
glog.Error("Can not create rdp screenshot file:", writeErr)
} else {
defer fo.Close()
fw := bufio.NewWriter(fo)
_, writeErr := fw.Write(imageBuf.Bytes())
if writeErr != nil {
glog.Error("Can not write rdp screenshot file:", writeErr)
} else {
fw.Flush()
}
}
}
return status, err, reconnProtocol
}
func (g *Client) Crack(domain, user, pwd string, timeout int64, rdpProtocol uint32) (status bool, err error, reconnProtocol uint32) {
//glog.SetLevel(glog.ERROR)
reconnProtocol = rdpProtocol
refresh := make(chan bool)
exitFlag := make(chan bool)
start := time.Now()
now := start
targetSlice := strings.Split(g.Host, ":")
ip := targetSlice[0]
status = false
conn, err := WrapperTcpWithTimeout("tcp", g.Host, time.Duration(timeout)*time.Second)
if err != nil {
return status, fmt.Errorf("[dial err] %v", err), reconnProtocol
}
defer conn.Close()
glog.Info(conn.LocalAddr().String())
g.tpkt = tpkt.New(core.NewSocketLayer(conn), nla.NewNTLMv2(domain, user, pwd))
g.x224 = x224.New(g.tpkt)
g.mcs = t125.NewMCSClient(g.x224)
g.sec = sec.NewClient(g.mcs)
g.pdu = pdu.NewClient(g.sec)
g.sec.SetUser(user)
g.sec.SetPwd(pwd)
g.sec.SetDomain(domain)
//g.sec.SetClientAutoReconnect()
g.tpkt.SetFastPathListener(g.sec)
g.sec.SetFastPathListener(g.pdu)
g.pdu.SetFastPathSender(g.tpkt)
g.sec.SetChannelSender(g.mcs)
g.tpkt.On("os_info", func(info map[string]any) {
glog.Debug("[+] callback, get os info ........................")
for k, v := range info {
glog.Debugf("%s: %s\n", k, v)
}
})
g.x224.SetRequestedProtocol(rdpProtocol) //x224.PROTOCOL_SSL , x224.PROTOCOL_RDP , x224.PROTOCOL_HYBRID , x224.PROTOCOL_HYBRID_EX
g.x224.On("reconnect", func(protocol uint32) {
reconnProtocol = protocol
glog.Info("need reconnect with protocol:", protocol)
g.pdu.Emit("close")
exitFlag <- true
})
g.x224.On("more_timeout", func() {
timeout += 18 //如果是PROTOCOL_RDP协议,可以适当延长超时时间
})
err = g.x224.Connect()
if err != nil {
return status, fmt.Errorf("[x224 connect err] %v", err), reconnProtocol
}
glog.Info("wait connect ok")
g.pdu.On("error", func(e error) {
err = e
glog.Error("error", e)
g.pdu.Emit("done")
})
g.pdu.On("close", func() {
err = errors.New("close")
glog.Info("on close")
g.pdu.Emit("done")
})
g.pdu.On("success", func() {
glog.Debugf("===============login success %s===============", ip)
status = true
err = nil
g.pdu.Emit("done")
})
g.pdu.On("ready", func() {
err = nil
glog.Debug("on ready")
//g.pdu.Emit("done")
})
g.pdu.On("bitmap", func(rectangles []pdu.BitmapData) {
now = time.Now()
// 发送一个鼠标事件,作用是与服务器保持联系不要断开
rand.New(rand.NewSource(time.Now().UnixNano()))
randomNumber := rand.Intn(1000) + 10 // 10到1000之间的随机数
mouseX := 60 + randomNumber
mouseY := 10 + randomNumber
pevent := &pdu.PointerEvent{}
pevent.PointerFlags |= pdu.PTRFLAGS_MOVE
pevent.XPos = uint16(mouseX)
pevent.YPos = uint16(mouseY)
g.pdu.SendInputEvents(pdu.INPUT_EVENT_MOUSE, []pdu.InputEventsInterface{pevent})
glog.Debug("on update bitmap:", len(rectangles))
refresh <- true
})
g.pdu.On("done", func() {
glog.Debug("done信号触发")
exitFlag <- true
})
ctx, cancel := context.WithTimeout(context.Background(), time.Duration(timeout*6)*time.Second)
defer cancel()
loop:
for {
select {
case <-time.After(time.Second * time.Duration(timeout*3)): //
glog.Debug("本次获取bitmap超时了, 距离上次获取到图像过去了:", time.Since(now))
break loop
case <-refresh:
continue loop
case <-exitFlag:
break loop
case <-ctx.Done():
glog.Debug("总超时已达到,退出")
break loop
}
}
glog.Debug("循环结束,总时间过去了:", time.Since(start))
// 认证结果由 success 事件回调设置,不在此处覆盖
return status, err, reconnProtocol
}