newgrdp

module
v0.0.0-...-fdaf21d Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 31, 2026 License: GPL-3.0

README

newgrdp — Modern Go RDP Client

Go Version License Status

A modern, production-ready pure-Go implementation of the Microsoft RDP (Remote Desktop Protocol) client, focused on authentication and programmatic access. This is a heavily reworked fork of the original icodeface/grdp/x90skysn3k/grdp line, with substantial improvements to error handling, performance, safety, and protocol compatibility.

Repository: https://github.com/sayetars/newgrdp Go import path: github.com/sayetars/newgrdp


Table of contents


Highlights

  • Full NLA / CredSSP / NTLMv2 authentication (Windows 11 24H2+ compatible, including MIC + MsvAvTargetName SPN handling)
  • Three security protocols: PROTOCOL_RDP (Standard), PROTOCOL_SSL (TLS), PROTOCOL_HYBRID (CredSSP/NLA). PROTOCOL_HYBRID_EX is detected and reported as unsupported.
  • context.Context everywhere — deadlines and cancellation propagate through the entire stack (Client → X224 → TPKT → SocketLayer → net.Conn)
  • No goroutine leaksStartReadBytes uses a watcher that closes the socket on ctx.Done(), unblocking in-progress reads
  • Rich, structured errors*core.RDPError carries Kind, Message, Code (NTSTATUS / X224 failure code), and Wrapped cause; helpers TranslateNTStatus and TranslateX224Failure provide human-readable descriptions for ~30 NTSTATUS codes and all 6 X224 negotiation failures
  • Zero-copy I/OZeroCopyReader / ZeroCopyWriter + BufferPool eliminate per-call allocations on hot paths (0 allocs/op, verified by benchmarks)
  • Index-based Cursor — alternative to CVAL/CVAL2 for byte-slice parsing without slice-header churn
  • NTLM hash cacheNTOWFv2 results are cached across calls, giving ~100× speedup for brute-force scanners that retry the same credentials against multiple hosts
  • Safe RLE decoder — no unsafe.Pointer, no closure-based loops, bounds-checked writes (33 inline for loops replace the old REPEAT pattern)
  • Configurable X224 cookie — the old hardcoded mstshash=test is now overrideable via Setting.Cookie (helps avoid IDS fingerprinting)
  • Certificate verificationProprietaryServerCertificate.Verify() actually validates the RSA/SHA-1 self-signature (was a no-op returning true); X509CertificateChain.Verify() parses each cert in the chain
  • FIPS advertisementClientSecurityData.FIPS flag lets the client advertise FIPS-compliant encryption to FIPS-only servers
  • Modern client identificationClientBuild=7601 (Win 7 SP1) and extended EarlyCapabilityFlags (DYNVC_GFX, HEARTBEAT, DYNAMIC_TIME_ZONE, STATUS_INFO, 32-bpp, ERRINFO) so modern servers don't fall back to slow rendering paths

Compatibility

Windows Server version NLA (CredSSP) TLS Standard RDP Notes
2008 / 2008 R2 ✅ (if enabled) For 2008 with NLA off, use full Login not LoginAuthOnly
2012 / 2012 R2 CredSSP v5+ SHA-256 binding
2016
2019
2022 TLS capped at 1.2 (FreeRDP-compatible)
2025 / 2026 NTLM path (Azure AD / RDSTLS not supported)
Windows 11 24H2+ targets MIC + MsvAvTargetName already implemented

Not supported: PROTOCOL_HYBRID_EX, RDSTLS, Azure AD authentication, RDP 8.x graphics pipeline (RDPGFX/RemoteFX/H.264), UDP transport, dynamic virtual channels, audio/clipboard/drive redirection.

Installation

go get github.com/sayetars/newgrdp

Requires Go 1.23 or later.

Quick start

The rdpcheck CLI

Build and run the included credential-checker:

go build -o rdpcheck ./cmd/rdpcheck

./rdpcheck -host 192.168.1.100:3389 -user administrator -pass 'P@ssw0rd!' -timeout 10s

Sample output on success:

OK (1.234s): authentication successful

Sample output on failure (with the new structured error reporting):

FAIL (1.234s)
  summary: [auth] CredSSP server returned an error code
  stage  : tpkt.nla.errorCode
  host   : 192.168.1.100:3389
  user   : administrator
  code   : 0xC000006D (STATUS_LOGON_FAILURE — wrong username or password ...)
  cause  : CredSSP server error: NTSTATUS 0xC000006D (STATUS_LOGON_FAILURE — ...)
  hint   : wrong password — verify the credentials

Use -plain for a one-line summary suitable for scripting, and -v for trace-level logging.

Usage examples

Quick credential check (NLA only)

Performs CredSSP/NLA authentication only — no MCS/SEC/PDU setup. Fastest path for credential validation. Requires the server to support NLA.

package main

import (
    "context"
    "fmt"
    "time"

    "github.com/sayetars/newgrdp/client"
    "github.com/sayetars/newgrdp/glog"
)

func main() {
    s := client.NewSetting()
    s.LogLevel = glog.INFO

    c := client.NewClient("192.168.1.100:3389", "admin", "password",
        client.TC_RDP, s)

    ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
    defer cancel()

    if err := c.LoginAuthOnly(ctx); err != nil {
        fmt.Println("FAIL:", err)
        return
    }
    fmt.Println("OK: authentication successful")
}
Full session login (synchronous)

Use LoginSyncContext when you need to send input or receive bitmaps immediately after Login returns. It blocks until the full MCS/SEC/PDU handshake completes:

ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()

if err := c.LoginSyncContext(ctx); err != nil {
    log.Fatal(err)
}
defer c.Close()

// Session is fully established — safe to send input and receive bitmaps.
c.OnBitmap(func(bmps []client.Bitmap) { /* render */ })
c.KeyDown(0x1C, "ENTER")  // send Enter key

LoginContext (async) returns after X224 + NLA; LoginSyncContext blocks until the session is ready (Demand Active / Confirm Active / Synchronize / Control / Font Map all done).

Full session login (asynchronous)

Use LoginContext when you need the full RDP session (bitmap updates, input events). This negotiates MCS, SEC, and PDU layers after NLA.

c := client.NewClient("192.168.1.100:3389", "user", "pass",
    client.TC_RDP, nil)

ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
defer cancel()

if err := c.LoginContext(ctx); err != nil {
    log.Fatal(err)
}
defer c.Close()

c.OnSuccess(func() { fmt.Println("connected") })
c.OnError(func(e error) { fmt.Println("error:", e) })
c.OnBitmap(func(bmps []client.Bitmap) {
    // ... render bmps[i].Data (width × height, bmps[i].BitsPerPixel/8 bytes per pixel)
})
Domain authentication

The DOMAIN\user and DOMAIN/user formats are parsed automatically:

c := client.NewClient("host:3389", `CORP\administrator`, "password",
    client.TC_RDP, nil)
Connection reuse after cancellation

Context cancellation uses a deadline-based approach (setting a near-zero deadline on the socket), which unblocks in-progress I/O with a timeout error — without closing the connection. After cancellation, you can reset the deadline and reuse the connection:

// Cancel an in-progress operation.
cancel()

// The Read/Write that was in progress returned a net.Error with Timeout()==true.
// The connection is still open. Reset the deadline to reuse it:
sl := /* the *core.SocketLayer */
if err := sl.ResetDeadline(); err != nil {
    // connection was actually closed (e.g. by the remote peer) — dial a new one
    return
}
// Now issue a new Read/Write with a fresh context.

This is critical for high-scale scanners that cancel many connections: unlike the old close-based cancellation, there's no reconnection cost when a context is cancelled mid-operation.

s := client.NewSetting()
s.Width = 1920
s.Height = 1080
s.Cookie = "admin"             // sent as "Cookie: mstshash=admin" (avoids IDS fingerprinting)
s.TLSVerify = true             // verify server TLS certificate
s.VerifyServer = true          // verify CredSSP PubKeyAuth (MITM protection)
s.RequestedProtocol = 0x2      // PROTOCOL_HYBRID (NLA)
// s.RequestedProtocol = 0x1   // PROTOCOL_SSL
// s.RequestedProtocol = 0x0   // PROTOCOL_RDP (Standard)
s.AuthOnly = true              // Login() will skip MCS/SEC/PDU (same as LoginAuthOnly)

c := client.NewClient("host:3389", "user", "pass", client.TC_RDP, s)
Registering event handlers before Login

OnBitmap, OnSuccess, OnError, OnReady, and OnClose can all be called before Login. Handlers registered before the PDU layer exists are buffered internally and replayed against it once Login creates it. This makes the API order-insensitive — you don't need to worry about whether Login has completed before registering handlers.

c := client.NewClient("host:3389", "user", "pass", client.TC_RDP, nil)

// All of these are safe before Login:
c.OnSuccess(func() { fmt.Println("session established") })
c.OnBitmap(func(bmps []client.Bitmap) {
    // ... render bmps[i].Data
})
c.OnError(func(e error) { fmt.Println("error:", e) })

err := c.LoginContext(ctx)
Inspecting the negotiated protocol

After Login completes, SelectedProtocol() tells you which security layer the server actually chose (useful when RequestedProtocol=0 allowed the server to pick):

err := c.LoginContext(ctx)
// ...
switch c.SelectedProtocol() {
case 0: // PROTOCOL_RDP — Standard RDP Security
case 1: // PROTOCOL_SSL — TLS
case 2: // PROTOCOL_HYBRID — CredSSP / NLA
}
Manual bitmap decompression

If you capture raw pdu.BitmapData (e.g. via a custom PDU listener) and need to decode the RLE-compressed bitmap stream yourself, use the exported BitmapDecompress:

raw := <-rawBitmapChannel
pixels := client.BitmapDecompress(&raw) // []byte, width*height*Bpp
Connection reuse (TCP+TLS reuse for multiple credentials)

client.AuthSession lets you dial a TCP+TLS connection once and try multiple credentials on it — without re-doing the X224 negotiation or TLS handshake on every attempt. This is the fastest path for brute-force tools when the server keeps the connection open after failed attempts.

sess := client.NewAuthSession(client.NewSetting())
defer sess.Close()

// Dial once: TCP + X224 + TLS (no NLA yet).
if err := sess.Dial(ctx, "10.0.0.1:3389"); err != nil {
    return err
}

for _, cred := range creds {
    err := sess.TryAuth(ctx, cred.User, cred.Pass)
    if err == nil {
        fmt.Println("valid:", cred)
        break
    }
    if sess.IsClosed() {
        // Server closed the connection — re-dial.
        if err := sess.Dial(ctx, "10.0.0.1:3389"); err != nil {
            return err
        }
    }
}
TLS session resumption (faster reconnects)

For scenarios where the server closes the connection after each auth attempt (the common case), Setting.TLSSessionCache enables TLS session ticket reuse across connections — saving ~75ms per connection (the full TLS handshake becomes an abbreviated one):

s := client.NewSetting()
// Share this cache across all clients connecting to the same target.
s.TLSSessionCache = tls.NewClientSessionCache(1000)

// Now each LoginAuthOnly/Login benefits from a faster TLS handshake
// (after the first connection establishes the session).
for _, cred := range creds {
    c := client.NewClient("10.0.0.1:3389", cred.User, cred.Pass,
        client.TC_RDP, s)
    err := c.LoginAuthOnly(ctx)
    c.Close()
}
Programmatic error handling
err := c.LoginAuthOnly(ctx)
if err != nil {
    var rdpErr *core.RDPError
    if errors.As(err, &rdpErr) {
        switch rdpErr.Kind {
        case core.ErrKindAuth:
            // Wrong credentials — check rdpErr.Code for the NTSTATUS
            switch rdpErr.Code {
            case 0xC000006D: // STATUS_LOGON_FAILURE
            case 0xC0000234: // STATUS_ACCOUNT_LOCKED_OUT
            case 0xC0000072: // STATUS_ACCOUNT_DISABLED
            }
        case core.ErrKindTimeout:
            // Context deadline exceeded
        case core.ErrKindProtocol:
            // X224 negotiation failure — rdpErr.Code is the failure code (1..6)
        case core.ErrKindNetwork:
            // TCP dial / connection reset
        }
    }
}

The client package also provides helper predicates:

client.IsAuthError(err)              // wrong credentials / account restriction?
client.IsTimeoutError(err)           // context deadline / cancellation?
client.IsNetworkError(err)           // TCP dial / connection reset?
client.IsProtocolNegotiationError(err)  // X224 negotiation phase?
client.AsRDPError(err)               // extract *core.RDPError (or nil)
client.FormatError(err)              // fully-formatted multi-line message

Zero-copy I/O (high-performance hot path)

For tight loops that parse or serialize many PDUs, use the zero-copy API instead of the per-call core.ReadUint16LE / core.WriteUInt32LE helpers:

pool := core.NewBufferPool(8192)

// Read side
zr := core.NewZeroCopyReader(conn, pool)
defer zr.Close()
typ, _   := zr.ReadUInt8()       // 0 allocs
length,_ := zr.ReadUint16LE()    // 0 allocs
flag, _  := zr.ReadUInt32LE()    // 0 allocs
body, _  := zr.ReadBytes(int(length))  // 0 allocs (slice into internal buffer)

// Write side
zw := core.NewZeroCopyWriter(conn, pool)
defer zw.Close()  // Close flushes pending data + returns buffer to pool
zw.WriteUInt32LE(0xDEADBEEF)     // 0 allocs
zw.WriteUInt16LE(0x1234)         // 0 allocs
zw.WriteBytes(payload)           // 0 allocs (large writes bypass buffer)
if err := zw.Flush(); err != nil { ... }
⚠️ Important: ReadBytes vs ReadBytesCopy

ZeroCopyReader.ReadBytes(n) returns a slice that aliases the internal buffer. It's only valid until the next Read* call. This is the source of the 0-alloc performance, but it's a footgun if misused:

// ❌ BUG: b1 and b2 alias the same internal buffer
b1, _ := zr.ReadBytes(4)
b2, _ := zr.ReadBytes(4)   // b1 is now invalidated
use(b1)                    // b1 contains b2's data

// ❌ BUG: every entry aliases the same buffer
hdrs = append(hdrs, b1)
hdrs = append(hdrs, b2)

If you need to retain the bytes beyond the next read, use ReadBytesCopy (which allocates a fresh slice) or call copy(dst, b) yourself:

// ✅ Safe: independent copy
b, _ := zr.ReadBytesCopy(4)   // allocates, but safe to retain
hdrs = append(hdrs, b)

For parsing in-memory byte slices (no io.Reader), use core.Cursor:

c := core.NewCursor(inputBytes)
for c.Remaining() >= 3 {
    op  := c.NextByte()
    val := c.ReadUint16LE()
}
Syscall batching with FillAhead

ZeroCopyReader.FillAhead() reads as much data as possible into the internal buffer in a single syscall. After calling it, many small Read* calls can be served from the in-memory buffer without any further syscalls — 1 syscall per ~8 KB of data, instead of 1 syscall per ReadUint16LE.

for {
    if err := zr.FillAhead(); err != nil { return err }
    for zr.Buffered() >= headerSize {
        typ, _ := zr.ReadUInt8()      // no syscall
        len, _ := zr.ReadUint16LE()   // no syscall
        // ... parse from in-memory buffer
    }
}
Concurrent decode pipeline

For high-throughput packet processing, core.Pipeline runs read → decode → process stages concurrently in separate goroutines, connected by buffered channels. This overlaps I/O, parsing, and processing:

p := core.NewPipeline(ctx, 4 /* channel buffer size */)
p.AddStage("reader", func(ctx, in, out) error {
    // read from socket into out
})
p.AddStage("decoder", func(ctx, in, out) error {
    for buf := range in {
        pdu, err := decodePDU(buf)
        if err != nil { return err }
        out <- pdu
    }
    return nil
})
p.AddStage("processor", func(ctx, in, out) error {
    for pdu := range in {
        if err := processPDU(pdu); err != nil { return err }
    }
    return nil
})
err := p.Run()  // blocks until all stages finish or one errors

For the common 3-stage pattern, core.NewSimplePipeline(r, decode, process, chunkSize, bufSize) manages the buffer pool internally.

NTLM hash cache

NTOWFv2 results are cached in a two-level bounded LRU — critical for brute-force / mass-scanning scenarios:

  • Level 1 (md4PasswordCache): MD4(UTF16(password)) — depends only on the password. This is the most expensive step (MD4 is sequential and not parallelizable). In brute-force with one password tried against many users, this avoids recomputing MD4 for every (user, domain) tuple.
  • Level 2 (ntowfCache): the full HMAC-MD5 result — depends on (password, user, domain). On a hit, no crypto is done at all.
Scenario Work done
Same (password, user, domain) None (~50 ns, level-2 hit)
Same password, different user/domain Only HMAC-MD5 (level-1 hit)
New password MD4 + HMAC-MD5 (both misses)

Both caches are bounded at 4096 entries (default) — no unbounded memory growth, even with a huge password list. Thread-safe for concurrent use.

// Adjust the cache size at startup (optional, default is 4096)
nla.SetNTOWFCacheSize(8192)

// ... do many NTOWFv2 calls ...

// Clear both caches when done (optional, releases memory)
nla.ClearNTOWFCache()

Setting the size to 0 effectively disables caching (every call recomputes).

Settings reference

client.Setting fields (all optional — NewSetting() provides sensible defaults):

Field Type Default Description
Width int 1024 Requested desktop width
Height int 768 Requested desktop height
RequestedProtocol uint32 0 (auto) 0=RDP, 1=SSL, 2=HYBRID(NLA), 8=HYBRID_EX
LogLevel glog.LEVEL INFO Trace/Debug/Info/Warn/Error/None
TLSMinVersion uint16 TLS 1.2 Minimum TLS version
TLSVerify bool false Verify server TLS certificate
VerifyServer bool false Verify CredSSP PubKeyAuth (MITM protection)
AuthOnly bool false Stop after NLA (skip MCS/SEC/PDU)
Cookie string "" X224 cookie (sent as Cookie: mstshash=<cookie>)
FIPS bool false Advertise FIPS-compliant encryption
core.SocketLayer methods
Method Description
SetContext(ctx) Apply context deadline + spawn cancellation watcher
SetDeadline(t) Set read/write deadline directly
ResetDeadline() Clear deadline — allows reusing the connection after cancellation
Read(b) / Write(b) Read/Write (delegates to TLS conn if StartTLS was called)
Close() Cancel watcher + close underlying connection

Error handling

All errors returned from Login* are *core.RDPError (reachable via errors.As). The Error() method returns a multi-line diagnostic block suitable for display; the structured fields are available for programmatic branching.

Error kinds
Kind Meaning
ErrKindNetwork TCP dial, DNS, connection refused
ErrKindTLS TLS handshake failures
ErrKindAuth Wrong credentials (NLA/CredSSP), account restrictions
ErrKindProtocol X224 negotiation, MCS, PDU protocol failures
ErrKindTimeout Context deadline exceeded
ErrKindSecurity Cryptographic verification failures (MAC, PubKeyAuth, MITM)
ErrKindEncoding DER/PER/struc serialization failures
ErrKindLicense RDP licensing protocol failures
NTSTATUS translation

core.TranslateNTStatus(code) returns a human-readable description for ~30 NTSTATUS codes that Microsoft SChannel / LSASS can return through CredSSP TSRequest.ErrorCode, including:

STATUS_LOGON_FAILURE, STATUS_WRONG_PASSWORD, STATUS_NO_SUCH_USER, STATUS_ACCOUNT_RESTRICTION, STATUS_ACCOUNT_DISABLED, STATUS_ACCOUNT_LOCKED_OUT, STATUS_PASSWORD_EXPIRED, STATUS_INVALID_LOGON_HOURS, STATUS_AUTHENTICATION_FIREWALL_FAILED, SEC_E_LOGON_DENIED, SEC_E_DELEGATION_POLICY, and more.

X224 failure translation

core.TranslateX224Failure(code) covers all 6 negotiation failure codes from MS-RDPBCGR §2.2.1.5.2:

Code Name Meaning
1 SSL_REQUIRED_BY_SERVER Server requires TLS
2 SSL_NOT_ALLOWED_BY_SERVER Server only allows Standard RDP Security
3 SSL_CERT_NOT_ON_SERVER Server has no valid auth certificate
4 INCONSISTENT_FLAGS Requested protocols conflict
5 HYBRID_REQUIRED_BY_SERVER Server requires NLA/CredSSP
6 SSL_WITH_USER_AUTH_REQUIRED_BY_SERVER Server requires TLS + client cert

Protocol stack

Client (Login with context)
  └─ X224    (connection request/confirm, protocol negotiation)
      └─ TPKT  (packet framing, FastPath, CredSSP TSRequest)
          └─ SocketLayer (TLS 1.2, raw TCP, context-aware cancellation)
              └─ net.Conn

Each layer propagates the context downward. The SocketLayer.SetContext spawns a watcher goroutine that closes the underlying connection when the context is cancelled — this unblocks in-progress Read/Write calls immediately, rather than waiting for the OS-level TCP timeout.

What's new vs the upstream fork

Area Upstream This fork
Silent error swallowing ReadUint16LE returned 0, nil on error All Read* helpers return the real error
RLE safety unsafe.Pointer + closure-based REPEAT + no bounds binary.LittleEndian + 33 inline loops + bounds checks
Goroutine leaks Reader goroutines blocked forever on dead sockets Watcher closes socket on ctx.Done(); verified by leak test
ReadByte panic b[0] on empty slice Guards len(b) == 0io.ErrUnexpectedEOF
Error reporting "protocol negotiation failed with code 5" "protocol negotiation failed: HYBRID_REQUIRED_BY_SERVER — the server requires NLA/CredSSP (PROTOCOL_HYBRID); set RequestedProtocol=2"
NTSTATUS reporting 4 hard-coded codes ~30 codes with human-readable hints
Allocation per read make([]byte, 2) on every ReadUint16LE ZeroCopyReader = 0 allocs/op (6× faster)
NTLM hash Recomputed on every connection Cached (sync.Map); ~100× faster on cache hit
X224 cookie Hardcoded mstshash=test Configurable via Setting.Cookie
Cert verification Verify() always returned true Real RSA/SHA-1 signature check
FIPS Not advertised ClientSecurityData.FIPS flag
Client identification ClientBuild=3790 (Win 2003) ClientBuild=7601 (Win 7 SP1) + modern EarlyCapabilityFlags
Socket cancellation Deadline only, closes connection on ctx.Done() Deadline-based cancellation — preserves connection for reuse via ResetDeadline()
Event handler timing Must register after Login (PDU nil before) Buffered + replayed — safe to register before Login
Setting.AuthOnly Defined but ignored by Login Honored by Login (delegates to LoginAuthOnly)
SelectedProtocol() Not exposed Exposed on both Client and RdpClient
BitmapDecompress Unexported (bitmapDecompress) Exported for manual bitmap decoding
HYBRID_EX Rejected with error Falls back to HYBRID (NLA without EUA)
LoginAuthOnly Concrete type assertion to *RdpClient Uses AuthOnlyClient interface (works with any Control)
Synchronous Login Not available (async only) LoginSync / LoginSyncContext blocks until full session is ready
Connection pool Not available ConnPool reuses authenticated connections (2-3x faster brute-force)
SocketLayer OnClose Not available SetOnClose callback for connection lifecycle tracking
Connection reuse Not available AuthSession — TCP+TLS reuse for multiple credential attempts
TLS session resumption Not available Setting.TLSSessionCache — saves ~75ms per connection on reconnects

Known limitations

  • No RDP 8.x graphics pipeline — RDPGFX, RemoteFX, H.264 codec, and dynamic virtual channels are not implemented. Bitmap updates use the legacy RLE decoder.
  • No UDP transport — TCP only.
  • No virtual channels — clipboard, drive redirection, audio, and printer redirection are not implemented (ClientNetworkData ships an empty channel list).
  • No RDSTLS / Azure AD authentication — only NTLM over CredSSP.
  • TLS capped at 1.2 — Windows CredSSP silently breaks PubKeyAuth verification over TLS 1.3 (same as FreeRDP).
  • FIPS advertisement onlySetting.FIPS advertises the capability but the SEC layer does not yet implement FIPS sealing; a FIPS-only server will still fail at the security-exchange stage.
  • Residual allocation in io.go helpersReadUint16LE/etc. still escape-allocate because io.Reader.Read is an interface method the compiler can't inline. Use ZeroCopyReader for 0-alloc hot paths.
  • pdu/data.go ignores some read errorslogonInfoV1/V2 discard errors from core.ReadBytes. This only affects SaveSessionInfo parsing (not on the auth-only path).

Benchmarks

Run with go test -bench=. -benchmem ./core/:

Operation Old approach New approach Speedup Allocs
Read uint16 LE 22 ns, 1 alloc 3.7 ns, 0 allocs (ZeroCopyReader) 6.0× -100%
Write uint32 LE 14.6 ns, 1 alloc 7.2 ns, 0 allocs (ZeroCopyWriter) 2.0× -100%
Mixed read (1+2+4 bytes) 60 ns, 3 allocs 10.8 ns, 0 allocs (ZeroCopyReader) 5.6× -100%
NTLM NTOWFv2 (cache hit) ~5 µs ~50 ns (cached) ~100× -
RLE CVAL (per-byte, 8KB) 17.8 µs 16.8 µs (Cursor) 1.06× 0 → 0

Building & testing

# Build all packages
go build ./...

# Run all tests with the race detector
go test -race ./...

# Run benchmarks
go test -bench=. -benchmem ./core/

# Build the rdpcheck CLI
go build -o rdpcheck ./cmd/rdpcheck

Project layout

newgrdp/
├── client/                 # High-level RDP client
│   ├── client.go           # Client + Setting
│   ├── rdp.go             # RdpClient (Login, LoginAuthOnly)
│   └── errors.go          # Error helpers (IsAuthError, FormatError, ...)
├── cmd/
│   └── rdpcheck/          # CLI credential checker
├── core/                  # Core primitives
│   ├── io.go              # Read/Write helpers (legacy, allocates)
│   ├── zcreader.go        # ZeroCopyReader (0 allocs) + FillAhead
│   ├── zcwriter.go        # ZeroCopyWriter (0 allocs)
│   ├── pool.go            # BufferPool (sync.Pool)
│   ├── cursor.go          # Cursor (index-based slice reader)
│   ├── pipeline.go        # Pipeline / SimplePipeline (concurrent stages)
│   ├── socket.go          # SocketLayer (context-aware, deadline-based cancel)
│   ├── rle.go             # RLE bitmap decoder (safe, bounds-checked)
│   ├── errors.go          # RDPError + NTStatus/X224 translations
│   ├── types.go           # Transport interface, FastPathListener
│   └── util.go            # UnicodeEncode/Decode, Reverse, Random
├── emission/              # Event emitter (On/Once/Emit)
├── glog/                  # Leveled logger
├── protocol/
│   ├── lic/               # Licensing PDUs
│   ├── nla/               # CredSSP + NTLMv2 (with two-level hash cache)
│   ├── pdu/               # PDU layer (capabilities, data, orders, gdi)
│   ├── sec/               # Security layer (encrypt/decrypt, MAC)
│   ├── t125/              # MCS + GCC + BER + PER
│   ├── tpkt/              # TPKT framing + CredSSP TSRequest handling
│   └── x224/              # X224 connection negotiation
├── go.mod / go.sum
├── LICENSE
└── README.md

Credits

This library is a heavily modified fork of the original icodeface/grdp / x90skysn3k/grdp line. The protocol implementation draws from:

  • rdpy — Python RDP reference
  • node-rdpjs — Node.js RDP reference
  • gordp — Go RDP reference
  • ncrack — RDP auth module reference
  • FreeRDP — Reference for CredSSP v5+ binding hash and TLS 1.2 cap

Protocol references:

  • MS-RDPBCGR — Remote Desktop Protocol: Basic Connectivity and Graphics Remoting
  • MS-CSSP — Credential Security Support Provider
  • MS-NLMP — NT LAN Manager
  • MS-ERREF — Windows Error Codes

License

GPL-3.0 — see LICENSE.

Directories

Path Synopsis
client.go
client.go
protocol
lic
nla
pdu
sec

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL