none
x224 check in Powershell RRS feed

  • Question

  • Hi there, I've been digging around for an effective way of monitoring an RDP port that goes further than just checking that the port is open. Nagios runs a Python script that builds an x224 connection. I want to perform the same test with a Powershell script. I'm really looking to keep the solution simple - that is Powershell only, I'm not really interested in calling the Python script from Powershell or compiling the script. Does Powershell have the necessary libraries and would anyone be willing to rewrite the Python script below in Powershell? Thanks.

    # coding=utf-8
    #!/usr/bin/env python3
     
    # This Nagios plugin may be used to check the health of an RDP server, such
    # as a Windows host offering remote desktop. Typically, a "strange" RDP
    # response is a good indication of a Windows host is having trouble (while
    # it is still responding to ping).
     
    # It seems that the RDP protocol is based on a protocol called X.224,
    # and this plugin only goes as far as checking very basic X.224
    # protocol operations. Hence, the somewhat strange name of the plugin.
     
    # Example of a check command definition, using this plugin:
    # define command{
    # command_name    check_x224
    # command_line    /usr/local/nagios/check_x224 -H $HOSTADDRESS$
    # }
    #
    # A corresponding service definition might look like:
    # define service{
    # service_description             Remote desktop
    # check_command                   check_x224
    # host_name                       somename.example.com
    # use                             generic-service
    # }
     
    # Author: Troels Arvin <tra@sst.dk>
    # Last modified: 2012-12-08.
     
    # Copyright (c) 2011, Danish National Board of Health.
    # All rights reserved.
    #
    # Redistribution and use in source and binary forms, with or without
    # modification, are permitted provided that the following conditions are met:
    #     * Redistributions of source code must retain the above copyright
    #       notice, this list of conditions and the following disclaimer.
    #     * Redistributions in binary form must reproduce the above copyright
    #       notice, this list of conditions and the following disclaimer in the
    #       documentation and/or other materials provided with the distribution.
    #     * Neither the name of the  the Danish National Board of Health nor the
    #       names of its contributors may be used to endorse or promote products
    #       derived from this software without specific prior written permission.
    #
    # THIS SOFTWARE IS PROVIDED BY the Danish National Board of Health ''AS IS'' AND ANY
    # EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
    # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
    # DISCLAIMED. IN NO EVENT SHALL the Danish National Board of Health BE LIABLE FOR ANY
    # DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
    # (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
    # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
    # ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
    # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
    # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     
    # References:
    # TPKT:  http://www.itu.int/rec/T-REC-T.123/
    # X.224: http://www.itu.int/rec/T-REC-X.224/en
     
    default_rdp_port = 3389
    default_warning_sec = 3
    default_critical_sec = 50
     
    def do_conn(hostname, port, setup_payload, teardown_payload):
        try:
            s = socket.socket()
            t1 = time.time()
     
            # connect
            s.connect((hostname, port))
            sent_bytes = s.send(setup_payload)
            if sent_bytes != len(setup_payload):
                print('Could not send RDP setup payload')
                sys.exit(2)
            setup_received = s.recv(1024)
            t2 = time.time()
     
            # disconnect
            sent_bytes = s.send(teardown_payload)
            if sent_bytes != len(teardown_payload):
                print('x224 CRITICAL: Could not send RDP teardown payload')
                sys.exit(2)
            s.close()
     
            elapsed = t2 - t1
     
            l_setup_received = len(setup_received)
            l_expected_short = 11
            l_expected_long = 19
            if l_setup_received != l_expected_short and l_setup_received != l_expected_long:
                print('x224 CRITICAL: RDP response of unexpected length (%d)' % l_setup_received)
                sys.exit(2)
        except socket.error as e:
            if e.errno == -2:
                print("x224 UNKNOWN: Could not resolve hostname '%s': %s" % (hostname, e))
                sys.exit(3)
            print('x224 CRITICAL: Could not set up connection on port %d: %s' % (port, e))
            sys.exit(2)
        except Exception as e:
            print('x224 CRITICAL: Problem communicating with RDP server: %s' % e)
            sys.exit(2)
        return elapsed, setup_received
     
    # wrapping in gigantic try-block to be able to return 3 if something
    # unexpected goes wrong
    try:
        import os
        import sys
        import getopt
        import socket
        import struct
        import time
     
        this_script = os.path.basename(__file__)
     
        def usage():
            print("""Usage: %s [-h|--help] -H hostname [-p|--port port] [-w|--warning seconds] [-c|--critical seconds]

        port            : tcp port to connect to; default: %d 
        warning seconds : number of seconds that an RDP response may take without
                          emitting a warning; default: %d
        critical seconds: number of seconds that an RDP response may take without
                          emitting status=critical; default: %d""" % (
                this_script, default_rdp_port, default_warning_sec, default_critical_sec))
            sys.exit(3)
     
        try:
            options, args = getopt.getopt(
                sys.argv[1:],
                "hw:c:H:p:",
                [
                    'help',
                    'warning=',
                    'critical=',
                    'port='
                ]
            )
        except getopt.GetoptError:
            usage()
            sys.exit(3)
     
        warning_sec = default_warning_sec
        critical_sec = default_critical_sec
        rdp_port = default_rdp_port
        hostname = ''
     
        for name, value in options:
            if name in ("-h", "--help"):
                usage()
            if name == '-H':
                hostname = value
            if name in ('-p', '--port'):
                try:
                    rdp_port = int(value)
                except Exception:
                    print("Unable to convert port to integer\n")
                    usage()
            if name in ("-w", "--warning"):
                try:
                    warning_sec = int(value)
                except Exception:
                    print("Unable to convert warning_sec to integer\n")
                    usage()
            if name in ("-c", "--critical"):
                try:
                    critical_sec = int(value)
                except Exception:
                    print("Unable to convert critical_sec to integer\n")
                    usage()
     
        if rdp_port < 0:
            print('port number (%d) negative' % rdp_port)
            usage()
     
        if hostname == '':
            print('Hostname (-H) not indicated')
            usage()
     
        if warning_sec > critical_sec:
            print('warning seconds (%d) may not be greater than critical_seconds (%d)' % (warning_sec, critical_sec))
            usage()
     
        # make sure that we don't give up before critical sec has had a chance to elapse
        socket.setdefaulttimeout(critical_sec + 2)
     
        setup_x224_cookie = "Cookie: mstshash=\r\n".encode('ascii')
        setup_x224_rdp_neg_data = struct.pack(  # little-endian here, it seems ?
                                                '<BBHI',
                                                1,  # type
                                                0,  # flags
                                                8,  # length
                                                3,  # TLS + CredSSP
        )
        setup_x224_header = struct.pack(
            '!BBHHB',
            len(setup_x224_cookie) + 6 + 8,  # length,  1 byte
            #  6: length of this header, excluding length byte
            #  8: length of setup_x224_rdp_neg_data (static)
            224,  # code,    1 byte (224 = 0xe0 = connection request)
            0,  # dst-ref, 1 short
            0,  # src-ref, 1 short
            0  # class,   1 byte
        )
        setup_x224 = setup_x224_header + setup_x224_cookie + setup_x224_rdp_neg_data
     
        tpkt_total_len = len(setup_x224) + 4
        # 4 is the static size of a tpkt header
        setup_tpkt_header = struct.pack(
            '!BBH',
            3,  # version,  1 byte
            0,  # reserved, 1 byte
            tpkt_total_len  # len,      1 short
        )
     
        setup_payload = setup_tpkt_header + setup_x224
     
        #print('Len of cookie: %d'       % len(setup_x224_cookie))
        #print('Len of rdp_neg_data: %d' % len(setup_x224_rdp_neg_data))
        #print('Len of header: %d'       % len(setup_x224_header))
        #print('Len of setup_x224: %d'   % len(setup_x224))
        #print('tpkt_total_len: %d'   % tpkt_total_len)
     
        teardown_payload = struct.pack(
            '!BBHBBBBBBB',
            3,  # tpkt version,  1 byte
            0,  # tpkt reserved, 1 byte
            11,  # tpkt len,      1 short
            6,  # x224 len,      1 byte
            128,  # x224 code,     1 byte
            0,  # x224 ?,        1 byte
            0,  # x224 ?,        1 byte
            0,  # x224 ?,        1 byte
            0,  # x224 ?,        1 byte
            0  # x224 ?,        1 byte
        )
     
        elapsed, rec = do_conn(hostname, rdp_port, setup_payload, teardown_payload)
     
        if elapsed > critical_sec:
            print('x224 CRITICAL: RDP connection setup time (%f) was longer than (%d) seconds' % (elapsed, critical_sec))
            sys.exit(2)
        if elapsed > warning_sec:
            print('x224 WARNING: RDP connection setup time (%f) was longer than (%d) seconds' % (elapsed, warning_sec))
            sys.exit(1)
     
        rec_tpkt_header = {}
        rec_x224_header = {}
        rec_nego_resp = {}
     
        # Older Windows hosts will return with a short answer
        if len(rec) == 11:
            rec_tpkt_header['version'], \
                rec_tpkt_header['reserved'], \
                rec_tpkt_header['length'], \
                rec_x224_header['length'], \
                rec_x224_header['code'], \
                rec_x224_header['dst_ref'], \
                rec_x224_header['src_ref'], \
                rec_x224_header['class'], \
                = struct.unpack('!BBHBBHHB', rec)
        else:
            # Newer Windows hosts will return with a longer answer
            rec_tpkt_header['version'], \
                rec_tpkt_header['reserved'], \
                rec_tpkt_header['length'], \
                rec_x224_header['length'], \
                rec_x224_header['code'], \
                rec_x224_header['dst_ref'], \
                rec_x224_header['src_ref'], \
                rec_x224_header['class'], \
                rec_nego_resp['type'], \
                rec_nego_resp['flags'], \
                rec_nego_resp['length'], \
                rec_nego_resp['selected_proto'] \
                = struct.unpack('!BBHBBHHBBBHI', rec)
     
        if rec_tpkt_header['version'] != 3:
            print('x224 CRITICAL: Unexpected version-value(%d) in TPKT response' % rec_tpkt_header['version'])
            sys.exit(2)
     
        # 13 = binary 00001101; corresponding to 11010000 shifted four times
        # dst_ref=0 and class=0 was asked for in the connection setup
        if (rec_x224_header['code'] >> 4) != 13 or \
                rec_x224_header['dst_ref'] != 0 or \
                rec_x224_header['class'] != 0:
            print('x224 CRITICAL: Unexpected element(s) in X.224 response')
            sys.exit(2)
     
    except struct.error as e:
        print('x224 CRITICAL: Could not decode RDP response: %s' % e)
        sys.exit(2)
    except SystemExit as e:
        # Special case which is needed in order to convert the return code
        # from other exception handlers.
        sys.exit(int(str(e)))
    except Exception as e:
        # At this point, we don't know what's going on, so let's
        # not output the details of the error into something which
        # would appear in the Nagios web interface. Do print the details
        # on stderr, though, to ease debugging.
        print('x224 UNKNOWN: An unhandled error occurred')
        sys.stderr.write('Unhandled error: %s' % sys.exc_info()[1])
        sys.exit(3)
     
    print('x224 OK. Connection setup time: %f sec.|time=%fs;%d;%d;0' % (elapsed, elapsed, warning_sec, critical_sec))
    sys.exit(0)

    Monday, February 16, 2015 11:52 PM

Answers

  • Any suggestion of a library or command to build binary data like struct.pack is doing?

    You would need to use C#, C++ or VB.Net to create the API calls and the required structures and enums.

    I recommend doing it I all in C# and then moving it over to PowerShell.  PowerShell has no native support for bit level structures.

    Here is an actual example of how to do this in PowerSHell:

    $code=@'
    using System;
    using System.Runtime.InteropServices;
    
    public class TokenManipulator{
        
        [DllImport("advapi32.dll", ExactSpelling = true, SetLastError = true)]
        internal static extern bool AdjustTokenPrivileges(
                    IntPtr htok,
                    bool disall,
                    ref TokPriv1Luid newst,
                    int len, 
                    IntPtr prev, 
                    IntPtr relen
        );
            
        [DllImport("kernel32.dll", ExactSpelling = true)]
        internal static extern IntPtr GetCurrentProcess();
        
        [DllImport("advapi32.dll", ExactSpelling = true, SetLastError = true)]
        internal static extern bool OpenProcessToken(IntPtr h, int acc, ref IntPtr phtok);
        
        [DllImport("advapi32.dll", SetLastError = true)]
        internal static extern bool LookupPrivilegeValue(string host, string name,ref long pluid);
        
        [StructLayout(LayoutKind.Sequential, Pack = 1)]
        internal struct TokPriv1Luid{
            public int Count;
            public long Luid;
            public int Attr;
        }
        
        internal const int SE_PRIVILEGE_DISABLED = 0x00000000;
        internal const int SE_PRIVILEGE_ENABLED = 0x00000002;
        internal const int TOKEN_QUERY = 0x00000008;
        internal const int TOKEN_ADJUST_PRIVILEGES = 0x00000020;
        
        public static bool AddPrivilege(string privilege){
            try{
                bool retVal;
                TokPriv1Luid tp;
                IntPtr hproc = GetCurrentProcess();
                IntPtr htok = IntPtr.Zero;
                retVal = OpenProcessToken(hproc, TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, ref htok);
                tp.Count = 1;
                tp.Luid = 0;
                tp.Attr = SE_PRIVILEGE_ENABLED;
                retVal = LookupPrivilegeValue(null, privilege, ref tp.Luid);
                retVal = AdjustTokenPrivileges(htok, false, ref tp, 0, IntPtr.Zero, IntPtr.Zero);
                return retVal;
            }
            catch (Exception ex){
                throw ex;
            }
        }
        
        public static bool RemovePrivilege(string privilege){
            try{
                bool retVal;
                TokPriv1Luid tp;
                IntPtr hproc = GetCurrentProcess();
                IntPtr htok = IntPtr.Zero;
                retVal = OpenProcessToken(hproc, TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, ref htok);
                tp.Count = 1;
                tp.Luid = 0;
                tp.Attr = SE_PRIVILEGE_DISABLED;
                retVal = LookupPrivilegeValue(null, privilege, ref tp.Luid);
                retVal = AdjustTokenPrivileges(htok, false, ref tp, 0, IntPtr.Zero, IntPtr.Zero);
                return retVal;
            }
           
            catch(Exception ex){
                throw ex;
            }
        }
    }
    '@
    
    add-type $code
    PS C:\scripts> [TokenManipulator]
    
    IsPublic IsSerial Name                                     BaseType
    -------- -------- ----                                     --------
    True     False    TokenManipulator                         System.Object
    
    
    PS C:\scripts> [TokenManipulator]::AddPrivilege
    
    OverloadDefinitions
    -------------------
    static bool AddPrivilege(string privilege)
    
    


    ¯\_(ツ)_/¯

    • Marked as answer by Clint_R Wednesday, February 18, 2015 8:26 PM
    Tuesday, February 17, 2015 9:43 PM

All replies

  • What is the problem with using Pythin if it is available and works.

    You would need an advanced developers license and probably federal certification to convert that to PowerShell.

    It can be done but why?  Why not just use the Windows RDS SDK to build a solution.  I am sure that is where the Piething code came from.


    ¯\_(ツ)_/¯

    Tuesday, February 17, 2015 12:50 AM
  • Here is a PowerShell comprehensive test with exciting sounds.

    http://irl33t.com/blog/2011/03/powershell-script-connect-rdp-ps1


    ¯\_(ツ)_/¯


    • Edited by jrv Tuesday, February 17, 2015 12:58 AM
    Tuesday, February 17, 2015 12:58 AM
  • Well, it's not available. Python would need to be installed in the environment to run this script? I would prefer not to introduce any dependencies or complexities to monitoring.

    I'm open to other suggestions, this is the only example I have found of any RDS type monitor checking further than just an open port. And we have experienced issues in our environment where the port is open but RDS is not setting up a session.

    Tuesday, February 17, 2015 12:59 AM
  • Here is the documentation. Between that and the Python code you should be able to script it in about 3 to 4 hours.

    https://msdn.microsoft.com/en-us/library/aa383459(v=vs.85).aspx


    ¯\_(ツ)_/¯


    • Edited by jrv Tuesday, February 17, 2015 1:02 AM
    Tuesday, February 17, 2015 1:02 AM
  • Thanks, I'll take a look. The script you posted is much the same as all other Powershell solutions (except the exciting sounds) - it doesn't actually test RDS, just the open port, then if successful fires up mstsc for a user, this cannot return a success/fail as far as I can see.
    Tuesday, February 17, 2015 1:07 AM
  • If you are good at low-level line programming you should be abe to convert the Python.  It is actually pretty easy but will take time to test.  Starting with SDK samples might be faster.

    ¯\_(ツ)_/¯

    Tuesday, February 17, 2015 1:12 AM
  • Thank you. I'm not good at low-level programming. Which is why I ended up here :)
    Tuesday, February 17, 2015 1:30 AM
  • With all due respect, this isn't a "translate this python script into equivalent PowerShell code for me for free" forum. I agree with jrv that you keep what's working. Keep in mind that there are ways of packaging python into an executable on Windows that don't require a python installation.

    One example: http://www.py2exe.org/


    -- Bill Stewart [Bill_Stewart]


    Tuesday, February 17, 2015 2:58 PM
    Moderator
  • Thanks Bill, I thought it was a forum where I could ask for help, which is exactly what I asked. I'm pretty sure all the posts here are asking for free help. I did explain that I didn't want to compile the script, I'm using it for monitoring and the platform has no functionality to deliver an executable, plus this introduces complexities around UAC. In my mind monitoring should be as simple as possible and so I'm focusing on keeping everything in Powershell. I had a go at converting it myself and couldn't find the equivalent commands. At some point someone had a need for something and created a Python script, now I have a need for something else. With all due respect, putting in a work around to make something work that's not right for the job is not a proper solution. If you don't want to help, that's fine. Maybe someone else will.
    Tuesday, February 17, 2015 9:13 PM
  • I think you miss the point.  No one is going to write this for you. It is not a trivial two or three lines.  It is a development project.


    ¯\_(ツ)_/¯

    Tuesday, February 17, 2015 9:15 PM
  • Any suggestion of a library or command to build binary data like struct.pack is doing?
    Tuesday, February 17, 2015 9:18 PM
  • As already explained, you will need to translate this code yourself if you are not willing to create a standalone python executable for Win32 (which would, by far, be the simplest solution).

    You will need to read the python documentation, such as http://docs.python.org/2/library/struct.html, and then you will need to go through the .NET class library documentation (link is for 2.0) and find the equivalent classes and methods. As noted, this is not trivial, and it is highly unlikely anyone has the resources to do all of this work for you for free.


    -- Bill Stewart [Bill_Stewart]

    Tuesday, February 17, 2015 9:36 PM
    Moderator
  • Any suggestion of a library or command to build binary data like struct.pack is doing?

    You would need to use C#, C++ or VB.Net to create the API calls and the required structures and enums.

    I recommend doing it I all in C# and then moving it over to PowerShell.  PowerShell has no native support for bit level structures.

    Here is an actual example of how to do this in PowerSHell:

    $code=@'
    using System;
    using System.Runtime.InteropServices;
    
    public class TokenManipulator{
        
        [DllImport("advapi32.dll", ExactSpelling = true, SetLastError = true)]
        internal static extern bool AdjustTokenPrivileges(
                    IntPtr htok,
                    bool disall,
                    ref TokPriv1Luid newst,
                    int len, 
                    IntPtr prev, 
                    IntPtr relen
        );
            
        [DllImport("kernel32.dll", ExactSpelling = true)]
        internal static extern IntPtr GetCurrentProcess();
        
        [DllImport("advapi32.dll", ExactSpelling = true, SetLastError = true)]
        internal static extern bool OpenProcessToken(IntPtr h, int acc, ref IntPtr phtok);
        
        [DllImport("advapi32.dll", SetLastError = true)]
        internal static extern bool LookupPrivilegeValue(string host, string name,ref long pluid);
        
        [StructLayout(LayoutKind.Sequential, Pack = 1)]
        internal struct TokPriv1Luid{
            public int Count;
            public long Luid;
            public int Attr;
        }
        
        internal const int SE_PRIVILEGE_DISABLED = 0x00000000;
        internal const int SE_PRIVILEGE_ENABLED = 0x00000002;
        internal const int TOKEN_QUERY = 0x00000008;
        internal const int TOKEN_ADJUST_PRIVILEGES = 0x00000020;
        
        public static bool AddPrivilege(string privilege){
            try{
                bool retVal;
                TokPriv1Luid tp;
                IntPtr hproc = GetCurrentProcess();
                IntPtr htok = IntPtr.Zero;
                retVal = OpenProcessToken(hproc, TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, ref htok);
                tp.Count = 1;
                tp.Luid = 0;
                tp.Attr = SE_PRIVILEGE_ENABLED;
                retVal = LookupPrivilegeValue(null, privilege, ref tp.Luid);
                retVal = AdjustTokenPrivileges(htok, false, ref tp, 0, IntPtr.Zero, IntPtr.Zero);
                return retVal;
            }
            catch (Exception ex){
                throw ex;
            }
        }
        
        public static bool RemovePrivilege(string privilege){
            try{
                bool retVal;
                TokPriv1Luid tp;
                IntPtr hproc = GetCurrentProcess();
                IntPtr htok = IntPtr.Zero;
                retVal = OpenProcessToken(hproc, TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, ref htok);
                tp.Count = 1;
                tp.Luid = 0;
                tp.Attr = SE_PRIVILEGE_DISABLED;
                retVal = LookupPrivilegeValue(null, privilege, ref tp.Luid);
                retVal = AdjustTokenPrivileges(htok, false, ref tp, 0, IntPtr.Zero, IntPtr.Zero);
                return retVal;
            }
           
            catch(Exception ex){
                throw ex;
            }
        }
    }
    '@
    
    add-type $code
    PS C:\scripts> [TokenManipulator]
    
    IsPublic IsSerial Name                                     BaseType
    -------- -------- ----                                     --------
    True     False    TokenManipulator                         System.Object
    
    
    PS C:\scripts> [TokenManipulator]::AddPrivilege
    
    OverloadDefinitions
    -------------------
    static bool AddPrivilege(string privilege)
    
    


    ¯\_(ツ)_/¯

    • Marked as answer by Clint_R Wednesday, February 18, 2015 8:26 PM
    Tuesday, February 17, 2015 9:43 PM
  • Ok great, I'll read up on it. Thanks a lot.
    Tuesday, February 17, 2015 9:49 PM
  • Correct; if there's no .NET class equivalency, you'll have to go down the Win32 API  route (unmanaged code, P/Invoke).

    If you're not familiar with Win32 API programming (which is C-based), this will, in all likelihood, be very slow going unless you have some programming/development background.

    I would recommend building a standalone executable, which would be (by far) the easiest solution.


    -- Bill Stewart [Bill_Stewart]

    Tuesday, February 17, 2015 10:19 PM
    Moderator