-
Notifications
You must be signed in to change notification settings - Fork 353
RDP module #644
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
Seanstoppable
wants to merge
2
commits into
zmap:master
Choose a base branch
from
Seanstoppable:ssmith/rdp
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
RDP module #644
Changes from all commits
Commits
Show all changes
2 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| package modules | ||
|
|
||
| import rdp "github.com/zmap/zgrab2/modules/rdp" | ||
|
|
||
| func init() { | ||
| rdp.RegisterModule() | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,255 @@ | ||
| // Package rdp provides a zgrab2 module that scans for Remote Desktop Protocol. | ||
| // Default port: TCP 3389 | ||
| package rdp | ||
|
|
||
| import ( | ||
| "bytes" | ||
| "context" | ||
| "encoding/binary" | ||
| "errors" | ||
| "fmt" | ||
| "net" | ||
| "reflect" | ||
| "strings" | ||
|
|
||
| log "github.com/sirupsen/logrus" | ||
|
|
||
| "github.com/zmap/zgrab2" | ||
| ) | ||
|
|
||
| // Flags holds the command-line configuration for the scan module. | ||
| // Populated by the framework. | ||
| type Flags struct { | ||
| zgrab2.BaseFlags | ||
| zgrab2.TLSFlags | ||
| } | ||
|
|
||
| // Module implements the zgrab2.Module interface. | ||
| type Module struct { | ||
| } | ||
|
|
||
| // Scanner implements the zgrab2.Scanner interface. | ||
| type Scanner struct { | ||
| config *Flags | ||
| dialerGroupConfig *zgrab2.DialerGroupConfig | ||
| } | ||
|
|
||
| // RegisterModule registers the zgrab2 module. | ||
| func RegisterModule() { | ||
| var module Module | ||
| _, err := zgrab2.AddCommand("rdp", "rdp", module.Description(), 102, &module) | ||
| if err != nil { | ||
| log.Fatal(err) | ||
| } | ||
| } | ||
|
|
||
| // NewFlags returns a default Flags object. | ||
| func (module *Module) NewFlags() any { | ||
| return new(Flags) | ||
| } | ||
|
|
||
| // NewScanner returns a new Scanner instance. | ||
| func (module *Module) NewScanner() zgrab2.Scanner { | ||
| return new(Scanner) | ||
| } | ||
|
|
||
| // Description returns an overview of this module. | ||
| func (module *Module) Description() string { | ||
| return "Probe for Remote Desktop Protocol" | ||
| } | ||
|
|
||
| // Validate checks that the flags are valid. | ||
| // On success, returns nil. | ||
| // On failure, returns an error instance describing the error. | ||
| func (flags *Flags) Validate(_ []string) error { | ||
| return nil | ||
| } | ||
|
|
||
| // Help returns the module's help string. | ||
| func (flags *Flags) Help() string { | ||
| return "" | ||
| } | ||
|
|
||
| // Init initializes the Scanner. | ||
| func (scanner *Scanner) Init(flags zgrab2.ScanFlags) error { | ||
| f, _ := flags.(*Flags) | ||
| scanner.config = f | ||
| scanner.dialerGroupConfig = &zgrab2.DialerGroupConfig{ | ||
| TransportAgnosticDialerProtocol: zgrab2.TransportTCP, | ||
| BaseFlags: &f.BaseFlags, | ||
| TLSEnabled: true, | ||
| TLSFlags: &f.TLSFlags, | ||
| } | ||
| return nil | ||
| } | ||
|
|
||
| // InitPerSender initializes the scanner for a given sender. | ||
| func (scanner *Scanner) InitPerSender(senderID int) error { | ||
| return nil | ||
| } | ||
|
|
||
| // GetName returns the Scanner name defined in the Flags. | ||
| func (scanner *Scanner) GetName() string { | ||
| return scanner.config.Name | ||
| } | ||
|
|
||
| // GetTrigger returns the Trigger defined in the Flags. | ||
| func (scanner *Scanner) GetTrigger() string { | ||
| return scanner.config.Trigger | ||
| } | ||
|
|
||
| // Protocol returns the protocol identifier of the scan. | ||
| func (scanner *Scanner) Protocol() string { | ||
| return "rdp" | ||
| } | ||
|
|
||
| func (scanner *Scanner) GetDialerGroupConfig() *zgrab2.DialerGroupConfig { | ||
| return scanner.dialerGroupConfig | ||
| } | ||
|
|
||
| // GetScanMetadata returns any metadata on the scan itself from this module. | ||
| func (scanner *Scanner) GetScanMetadata() any { | ||
| return nil | ||
| } | ||
|
|
||
| // Scan probes for rdp services. | ||
| // 1. Connect to TCP port | ||
| // 2. Send a NTLM negotiate packet | ||
| // 7. Return the output | ||
| func (scanner *Scanner) Scan(ctx context.Context, dialGroup *zgrab2.DialerGroup, target *zgrab2.ScanTarget) (zgrab2.ScanStatus, any, error) { | ||
| conn, err := dialGroup.Dial(ctx, target) | ||
| if err != nil { | ||
| return zgrab2.TryGetScanStatus(err), nil, err | ||
| } | ||
| scanStatus, result, err := GetBanner(conn) | ||
| if result != nil { | ||
| if tlsConn, ok := conn.(*zgrab2.TLSConnection); ok { | ||
| result.TLSLog = tlsConn.GetLog() | ||
| } | ||
| } | ||
| return scanStatus, result, err | ||
| } | ||
|
|
||
| func GetBanner(connection net.Conn) (zgrab2.ScanStatus, *RDPResult, error) { | ||
|
|
||
| result := new(RDPResult) | ||
|
|
||
| _, err := connection.Write(NTLM_NEGOTIATE_BLOB) | ||
| responseBytes, readErr := zgrab2.ReadAvailable(connection) | ||
| if err != nil { | ||
| return zgrab2.TryGetScanStatus(err), nil, err | ||
| } | ||
| if readErr != nil { | ||
| return zgrab2.TryGetScanStatus(readErr), nil, readErr | ||
| } | ||
|
|
||
| prefixOffset := bytes.Index(responseBytes, NTLM_PREFIX) | ||
| if prefixOffset == -1 { | ||
| return zgrab2.SCAN_PROTOCOL_ERROR, nil, errors.New("not a valid NTLMSSP response") | ||
| } | ||
|
|
||
| if len(responseBytes) < prefixOffset+NTLM_RESPONSE_LENGTH { | ||
| return zgrab2.SCAN_PROTOCOL_ERROR, nil, fmt.Errorf("invalid response length %d", len(responseBytes)) | ||
| } | ||
|
|
||
| var responseData NTLMSecurityBlob | ||
| responseBytes = responseBytes[prefixOffset:] | ||
| responseBuf := bytes.NewBuffer(responseBytes) | ||
|
|
||
| err = binary.Read(responseBuf, binary.LittleEndian, &responseData) | ||
| if err != nil { | ||
| return zgrab2.TryGetScanStatus(err), nil, err | ||
| } | ||
|
|
||
| // 0x2 is the response message type to our request. If we don't have it, we don't know how to handle | ||
| if responseData.MessageType != 0x2 { | ||
| return zgrab2.SCAN_PROTOCOL_ERROR, nil, fmt.Errorf("unexpected message type %d", responseData.MessageType) | ||
| } | ||
|
|
||
| if responseData.Reserved != 0 { | ||
| return zgrab2.SCAN_PROTOCOL_ERROR, nil, fmt.Errorf("reserved value is not zero %d", responseData.Reserved) | ||
| } | ||
|
|
||
| if !reflect.DeepEqual(responseData.Version[4:], []byte{0, 0, 0, 0xF}) { | ||
| return zgrab2.SCAN_PROTOCOL_ERROR, nil, errors.New("unknown OS info structure in NTLM handshake") | ||
| } | ||
|
|
||
| var versionData OSVersion | ||
| versionBuf := bytes.NewBuffer(responseData.Version[:4]) | ||
| err = binary.Read(versionBuf, binary.LittleEndian, &versionData) | ||
| if err != nil { | ||
| return zgrab2.SCAN_PROTOCOL_ERROR, nil, errors.New("unable to parse version data") | ||
| } | ||
| result.OSVersion = fmt.Sprintf("%d.%d.%d", | ||
| versionData.MajorVersion, | ||
| versionData.MinorVersion, | ||
| versionData.BuildNumber) | ||
|
|
||
| // Parse: DomainName | ||
| targetNameLen := int(responseData.DomainNameLen) | ||
| if targetNameLen > 0 { | ||
| startIndex := int(responseData.DomainNameBufferOffset) | ||
| endIndex := startIndex + targetNameLen | ||
| targetName := strings.ReplaceAll(string(responseBytes[startIndex:endIndex]), "\x00", "") | ||
| result.TargetName = targetName | ||
| } | ||
|
|
||
| targetInfoLen := int(responseData.TargetInfoLen) | ||
| if targetInfoLen > 0 { | ||
| startIndex := int(responseData.TargetInfoBufferOffset) | ||
| if startIndex+targetInfoLen > len(responseBytes) { | ||
| return zgrab2.SCAN_PROTOCOL_ERROR, result, errors.New("invalid TargetInfoLen value") | ||
| } | ||
|
|
||
| var avItem *AVItem | ||
| currentIndex := startIndex | ||
|
|
||
| avItem, err = readAvItem(responseBytes, startIndex, currentIndex, targetInfoLen) | ||
| if err != nil { | ||
| return zgrab2.SCAN_PROTOCOL_ERROR, result, err | ||
| } | ||
|
|
||
| for avItem.Id != AV_EOL { | ||
| avLength := AV_ITEM_LENGTH + int(avItem.Length) | ||
| if field, exists := NTLM_AV_ID_VALUES[avItem.Id]; exists { | ||
| avValue := string(responseBytes[currentIndex+AV_ITEM_LENGTH : currentIndex+avLength]) | ||
| value := strings.ReplaceAll(avValue, "\x00", "") | ||
| switch field { | ||
| case "netbios_computer_name": | ||
| result.NetBIOSComputerName = value | ||
| case "netbios_domain_name": | ||
| result.NetBIOSDomainName = value | ||
| case "fqdn": | ||
| result.DNSComputerName = value | ||
| case "dns_domain_name": | ||
| result.DNSDomainName = value | ||
| case "dns_forest_name": | ||
| result.ForestName = value | ||
| } | ||
| } | ||
| currentIndex += avLength | ||
| avItem, err = readAvItem(responseBytes, startIndex, currentIndex, targetInfoLen) | ||
| if err != nil { | ||
| return zgrab2.SCAN_PROTOCOL_ERROR, result, err | ||
| } | ||
| } | ||
| } | ||
| return zgrab2.SCAN_SUCCESS, result, nil | ||
| } | ||
|
|
||
| func readAvItem(responseBytes []byte, startIndex int, currentIndex int, targetInfoLen int) (*AVItem, error) { | ||
| var avItem AVItem | ||
| nextIndex := currentIndex + AV_ITEM_LENGTH | ||
| if nextIndex > startIndex+targetInfoLen { | ||
| return nil, errors.New("invalid AV Item list") | ||
| } | ||
| if nextIndex > len(responseBytes) { | ||
| return nil, errors.New("invalid AV Item list") | ||
| } | ||
| avItemBuf := bytes.NewBuffer(responseBytes[currentIndex:nextIndex]) | ||
| err := binary.Read(avItemBuf, binary.LittleEndian, &avItem) | ||
| if err != nil { | ||
| return nil, err | ||
| } | ||
| return &avItem, nil | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,86 @@ | ||
| package rdp | ||
|
|
||
| import "github.com/zmap/zgrab2" | ||
|
|
||
| /* | ||
| * Adapted from https://github.com/nmap/nmap/blob/136e1c6ed771119d3d0aa2629efc5dbc783f946d/scripts/rdp-ntlm-info.nse#L79 | ||
| */ | ||
|
|
||
| var NTLM_NEGOTIATE_BLOB = []byte{ | ||
| 0x30, 0x37, 0xA0, 0x03, 0x02, 0x01, 0x60, 0xA1, 0x30, 0x30, 0x2E, 0x30, 0x2C, 0xA0, 0x2A, 0x04, 0x28, | ||
| 0x4e, 0x54, 0x4c, 0x4d, 0x53, 0x53, 0x50, 0x00, // Identifer, NTLMSSP | ||
| 0x01, 0x00, 0x00, 0x00, //NTLM Negotiate (01) | ||
| // Negotiate Flags | ||
| 0xB7, 0x82, 0x08, 0xE2, //Flags (NEGOTIATE_SIGN_ALWAYS | NEGOTIATE_NTLM | NEGOTIATE_SIGN | REQUEST_TARGET | NEGOTIATE_UNICODE) | ||
| // Domain Name Fields | ||
| 0x00, 0x00, // DomainNameLen | ||
| 0x00, 0x00, // DomainNameMaxLen | ||
| 0x00, 0x00, 0x00, 0x00, // DomainNameBufferOffset | ||
| 0x00, 0x00, // WorkstationLen | ||
| 0x00, 0x00, // WorkstationMaxLen | ||
| 0x00, 0x00, 0x00, 0x00, // WorkstationBufferOffset | ||
| // Version | ||
| 0x0A, // Major Version | ||
| 0x00, // Minor Version | ||
| 0x63, 0x45, // Build # | ||
| 0x00, 0x00, 0x00, // Reserved | ||
| 0x0F, //NTLMRevision = 5 = NTLMSSP_REVISION_W2K3 | ||
| } | ||
|
|
||
| var NTLM_PREFIX = []byte{'N', 'T', 'L', 'M', 'S', 'S', 'P', 0} | ||
|
|
||
| var NTLM_AV_ID_VALUES = map[uint16]string{ | ||
| 1: "netbios_computer_name", | ||
| 2: "netbios_domain_name", | ||
| 3: "fqdn", | ||
| 4: "dns_domain_name", | ||
| 5: "dns_forest_name", | ||
| 6: "flags", | ||
| 7: "timestamp", | ||
| 8: "restrictions", | ||
| 9: "target_ame", | ||
| 10: "channel_bindings", | ||
| } | ||
|
|
||
| const NTLM_RESPONSE_LENGTH = 56 | ||
|
|
||
| type NTLMSecurityBlob struct { | ||
| Signature [8]byte | ||
| MessageType uint32 | ||
| DomainNameLen uint16 | ||
| DomainNameMaxLen uint16 | ||
| DomainNameBufferOffset uint32 | ||
| NegotiateFlags uint32 | ||
| ServerChallenge uint64 | ||
| Reserved uint64 | ||
| TargetInfoLen uint16 | ||
| TargetInfoMaxLen uint16 | ||
| TargetInfoBufferOffset uint32 | ||
| Version [8]byte | ||
| } | ||
|
|
||
| type OSVersion struct { | ||
| MajorVersion byte | ||
| MinorVersion byte | ||
| BuildNumber uint16 | ||
| } | ||
|
|
||
| type RDPResult struct { | ||
| OSVersion string `json:"os_version,omitempty"` | ||
| TargetName string `json:"target_name,omitempty"` | ||
| NetBIOSComputerName string `json:"netbios_computer_name,omitempty"` | ||
| NetBIOSDomainName string `json:"netbios_domain_name,omitempty"` | ||
| DNSComputerName string `json:"dns_computer_name,omitempty"` | ||
| DNSDomainName string `json:"dns_domain_name,omitempty"` | ||
| ForestName string `json:"forest_name,omitempty"` | ||
| TLSLog *zgrab2.TLSLog `json:"tls,omitempty"` | ||
| } | ||
|
|
||
| const AV_ITEM_LENGTH = 4 | ||
|
|
||
| const AV_EOL = 0 | ||
|
|
||
| type AVItem struct { | ||
| Id uint16 | ||
| Length uint16 | ||
| } | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I believe this is the RDP Version based on testing a couple known public IPs
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is actually os_version.
Example:
10.0.17763 is:
https://learn.microsoft.com/en-us/windows/uwp/whats-new/windows-10-build-17763