The Moose and Squirrel Files

October 22, 2009

Detecting when Altiris Bootworks is Installed

Filed under: Code — Tags: , , , — networknerd @ 6:14 am

When installing Checkpoint full disk encryption we ran into some problems on computers with Altiris Bootworks still installed. Normally Bootworks can be detected through a registry key, and uninstalled if the key is present. However we found a number of computers with Bootworks were missing the key.

The quick solution to detect bootworks was to read the bootsector of the disk and look for some identifying strings. The code below shows how to read from a physical disk.  Note this has only been tested in Windows XP, and you require admin privileges.  The code below was called from a startup script so privilege wasn’t an issue.

When reading from a physical disk we need to seek, read and write in multiples of sector size and on sector boundaries. See Microsoft KB article 100027.  I use WMI to get the number of bytes per sector for the drive.

I found the signature by extracting the bootsector using a copy of dcfldd that was compiled for cygwin. I dumped it to file using the command below.
dcfldd if=”\\\\.\\physicaldrive0 of =”bootsec.bin” count=1

The file bootsec.bin can then be opened using good old debug to get the hex/ascii display

The same result can be achieved by booting to a linux live cd and using the command below.

 dd if=/dev/sda count=1 | hexdump -C

 Listing 1


using System;
using System.IO;
using System.Management;
using System.Runtime.InteropServices;
using Microsoft.Win32.SafeHandles;

namespace bwcheck
{
    class Program
    {
        [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError=true)]
        internal static extern SafeFileHandle CreateFile(string lpFileName, int dwDesiredAccess, int dwShareMode,
            IntPtr lpSecurityAttributes, uint dwCreationDisposition, uint dwFlagsAndAttributes, SafeFileHandle hTemplateFile);

        internal const int GENERIC_READ = unchecked((int)0x80000000);
        internal const int OPEN_EXISTING = 3;
        internal const int FILE_ATTRIBUTE_NORMAL = 0x80;
        const String SIGNATURE = "Altiris EBootMastr";
        const int SEEKOFFSET = 3;
        const int LENGTH_TO_READ = 18;   // LENGTH_TO_READ = SIGNATURE.Length;
        const int RETCODE_SUCCESS = 0;
        const int RETCODE_IOERROR = 1;
        const int RETCODE_BADSIGNATURE = 2;
        const int RETCODE_HIT_EOF = 3;
// NB The where clause requires additional escaping even with the @string literal
        const String WMIQRY = @"Select * from win32_diskdrive where Name='\\\\.\\PhysicalDrive0'";
        const String WMI_NS = @"\\.\root\cimv2";
        //const String WMIQRY = "Select * from win32_diskdrive ";

        public static int Main(string[] args)
        {

            // TODO: Implement Functionality Here
            int retcode = RETCODE_SUCCESS;
            SafeFileHandle h = null;
            h = CreateFile("\\\\.\\PhysicalDrive0",
                            GENERIC_READ, 0, IntPtr.Zero, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL,
                            new SafeFileHandle(IntPtr.Zero, true));

            if (! h.IsInvalid ) {
                try {
                    //Find the bytes per sector for the disk
                    //We must read, write and seek in multiples of sector size (ref: http://support.microsoft.com/kb/q100027)
                    //This is true even when we convert the handle to a filestream.
                    ManagementObjectSearcher objSearch = new ManagementObjectSearcher(WMI_NS, WMIQRY);
                    int bytespersector = 0;
                    foreach (ManagementObject objResult in objSearch.Get()){
                        bytespersector = Convert.ToInt32(objResult["BytesPerSector"]);
                    }
                    FileStream fstream = new FileStream(h, FileAccess.Read);
                    // Read from stream
                    Byte[] chunk = new Byte[bytespersector];
                    int bytesRead;
                    int bytesTotal = 0;
                    int bytesToRead =  bytespersector;
                    while (bytesToRead > 0) {
                        bytesRead = fstream.Read(chunk,bytesTotal,bytesToRead);
                        if (bytesRead == 0) {
                            break; //end of filestream condition
                        }
                        bytesToRead -= bytesRead;
                        bytesTotal += bytesRead;
                    }
                    if (bytesToRead > 0 ) {
                        retcode = RETCODE_HIT_EOF;
                    }
                    System.Text.ASCIIEncoding enc = new System.Text.ASCIIEncoding();
                    String s = enc.GetString(chunk,SEEKOFFSET,LENGTH_TO_READ);
                    Console.WriteLine("{0}", s);
                    if (! s.Equals(SIGNATURE)){
                        retcode = RETCODE_BADSIGNATURE;
                    }

                } catch (Exception e) {
                    Console.WriteLine(e.ToString());
                    retcode = RETCODE_IOERROR;
                }
            }
            else
            {
                // get error code and throw
                int error = Marshal.GetLastWin32Error();
                Console.WriteLine("Last WIN32 Error: {0}", error);
                retcode = RETCODE_IOERROR;
            }
            return retcode;
        }
    }
}

September 24, 2008

Re-Imaging Computers in 802.1x Networks – Part 5

Filed under: Code, Network — Tags: , , , , — networknerd @ 8:24 pm

This is the final post in this series on re-imaging in 802.1x networks. It ties all the other posts together and contains the complete altiris re-imaging script in one listing.  Although I haven’t covered it here, a post-image script is also required to set the switch port back to using dot1x after the image is dropped from the computer and joined to the domain to get it’s authentication credentials.

Altiris scripting
The observant reader would have noticed that additional information is required before we can perform the previous five steps in a script. The mac address of the computer, the re-imaging vlan and the management IP address of the switch are also required. Variables are provided by altiris which help obtain the additional information. The mac address is provided straight up as %NIC1MACADDR%. The management IP address of the switch and re-imaging vlan aren’t directly available. Altiris has no knowledge of these items. However networks built to a standard allow calculation of the remaining two parameters. The computers ip address provided in the %NIC1IPADDR% variable is used for this calculation. The example network was built to the design standard below.

  • Floor vlans will be allocated in the range 100 – 299 with 10 vlans being reserved per floor.
  • Floor ip addresses will be allocated in the range 192.168.32.0 – 192.168.191.255 with 8 class C networks reserved per floor.
  • The first three networks and vlans per floor will be allocated to authenticated computers, guest/auth-fail computers and re-imaging vlan respectively.
  • The fourth network and vlan will be reserved for future IP telephony projects.
  • The fifth network will be allocated to switch management IP addresses with all others reserved for future use.
  • Switch management vlans will be allocated in the range 300 – 350.
  • Edge switch management address will start at 192.168.x.11

The ip address of the first network on a floor is calculated by masking the last three bits in the third octet of the computers ip address (%NIC1IPADDR%). The fifth network on each floor is reserved for switch management. Adding 4 to the third octet gives the switch management network. Assuming the last octet of the switch management IP addresses are also kept consistent, the address can be completed by simply changing the fourth octet to the standard value. Refer to the getSwitchMgmtAddr() function in listing 1.

The vlan of the first network on a floor is calculated using a similar technique. The vlan in which the computers mac address was found is divided, using integer division, by the number of vlans per floor. The result is then multiplied by the number of vlans per floor. The third network and vlan are reserved for re-imaging. Adding 2 to the first vlan on the floor will give the re-imaging vlan.

As an example, assume that the computer to be re-imaged has an ip address of 192.168.42.157 and its mac address was found in vlan 112 (probably due to a failed re-image job). Masking the last three bits of the third octet gives the first network on the floor.
00101010 (42)
AND 11111000 (248)
= 00101000 (40)
The management network is found by adding 4 to the third octet and gives 192.168.44.0/24, and the switch management ip address will be 192.168.44.11. The first vlan on a floor is calculated as (112\10) * 10 = 110. The re-imaging vlan is found by adding 2 to give 112. Note that the use of integer division- denoted by \ rather than / – means that remainders are ignored.

Not every network is the way we would design it with hindsight. Networks often grow in odd ways. You may have inherited a flat network that won’t lend itself to this kind of calculation. In this case you can simply build an array of switch management ip addresses and loop through steps one and two for each switch until the bridgeport on which the mac address appears is found on a non-trunking port. Then continue with steps three to five.

The script in listing 1 should be easy to customise for your environment. Pay particular attention to the constants defined at the beginning, the regular expression patterns used to match the output from the snmp commands, and the snmp commands. If you aren’t familiar with regular expressions take a look at “Mastering Regular expressions” by Jeffrey Friedl.

With snmp and a modicum of scripting know-how you can now have dot1x security without fearing an uprising of angry helpdesk staff.

References

How To Add, Modify, and Remove VLANs on a Catalyst Using SNMP. (October 26, 2005).
     Retrieved 11 November, 2006, from
     http://www.cisco.com/en/US/tech/tk648/tk362/technologies_tech_note09186a00801c6035.shtml

Using SNMP to Find a Port Number from a MAC Address on a Catalyst Switch. (October 26, 2005).
     Retrieved 11 November, 2006, from
     http://www.cisco.com/en/US/tech/tk648/tk362/technologies_tech_note09186a00801c9199.shtml

How To Get Dynamic CAM Entries (CAM Table) for Catalyst Switches Using SNMP. (October 26, 2005).
     Retrieved 11 November, 2006, from
     http://www.cisco.com/en/US/tech/tk648/tk362/technologies_tech_note09186a0080094a9b.shtml

How to Get VLAN Information From a Catalyst Using SNMP. (October 26, 2005).
     Retrieved 11 November, 2006, from
     http://www.cisco.com/en/US/tech/tk648/tk362/technologies_configuration_example09186a008015773e.shtml

SNMP Community String Indexing. (October 26, 2005).
     Retrieved 11 November, 2006, from
     http://www.cisco.com/en/US/tech/tk648/tk362/technologies_tech_note09186a00801576ff.shtml

IEEE Standard for Local and metropolitan area networks—Port-Based Network Access Control. (2004).
     Retrieved 11 November, 2006, from
     http://standards.ieee.org/getieee802/download/802.1X-2004.pdf

Friedl, J. (2002). Mastering Regular Expressions (Second ed.): O'Reilly Media Inc.
 (more...)

Re-Imaging Computers in 802.1x Networks – Part 4

Filed under: Code, Network — Tags: , , , , — networknerd @ 9:13 am

Finding the interface index
The bridgeport number is converted to an interface index by concatenating it to
dot1dBasePortIfIndex and once again performing an snmp get operation.

G:\usr\bin>snmpget.exe -OnqU -v 2c -c public@100 192.168.36.11 .1.3.6.1.2.1.17.1.4.1.2.108
.1.3.6.1.2.1.17.1.4.1.2.108 11002

The interface index returned is 11002, and once again we can extract it from the output using a
regular expression as shown in the getIFIndex() function in listing 1.

Setting Dot1x port control
To allow the computer to connect to the network without a supplicant, the port is first placed into
forced authorised mode. The interface index is concatenated to
dot1xAuthAuthControlledPortControl and an snmp set operation is used with an integer
argument of 3 (FORCEAUTHORISED). The same process can be used to return the switch port
to normal operation by specifying an argument of 2 (AUTO). This operation is performed by the
setPortControl() function in listing 2.

G:\usr\bin>snmpset.exe -v 2c -c private 192.168.36.11 .1.0.8802.1.1.1.1.2.1.1.6.11002 i 3
iso.0.8802.1.1.1.1.2.1.1.6.11002 = INTEGER: 3
G:\usr\bin>snmpset.exe -v 2c -c private 192.168.36.11 .1.0.8802.1.1.1.1.2.1.1.6.11002 i 2
iso.0.8802.1.1.1.1.2.1.1.6.11002 = INTEGER: 2

Setting the vlan
Strictly speaking, changing the vlan is not required to perform the re-imaging operation.
Restricting access on the re-imaging vlan provides a small amount of protection against the risk
of leaving the switch port in the force authorised state if the re-imaging job fails or is cancelled.
This operation is performed by the setVlan() function in listing 3.

G:\usr\bin>snmpget -OnqUe -v 2c –c public 192.168.36.11 vmVlan.11002
.1.3.6.1.4.1.9.9.68.1.2.2.1.2.11002 100
G:\usr\bin>snmpset -OnqUe -v 2c –c private 192.168.36.11 vmVlan.11002 i 102
.1.3.6.1.4.1.9.9.68.1.2.2.1.2.11002 102
G:\usr\bin>snmpget -OnqUe -v 2c –c public 192.168.36.11 vmVlan.11002
.1.3.6.1.4.1.9.9.68.1.2.2.1.2.11002 102
G:\usr\bin>snmpset -OnqUe -v 2c -c private 192.168.36.11 vmVlan.11002 i 100
.1.3.6.1.4.1.9.9.68.1.2.2.1.2.11002 100

Listing 1


const DOT1DBASEPORTIFINDEX = " .1.3.6.1.2.1.17.1.4.1.2."
const SNMPGETCMD = "f:\usr\bin\snmpget.exe -OnqUe -v 2c -c "
const SNMPREADV = " public@" 'need community name and vlan for some info

'************************************************************************
'FUNCTION:                                                              *
' getIFIndex(strAgent, intVlan, intBridgePort)                          *
'                                                                       *
'Purpose:                                                               *
' convert a bridgeport value to an interface index suitable for         *
' use with the setvlan() and setportcontrol() functions                 *
'                                                                       *
'Inputs:                                                                *
' strAgent: management IP address of the switch                         *
' intVlan : the vlan specific instance of the forwarding table          *
' intBridgePort: bridgeport value returned from getBridgePort()         *
'                                                                       *
'Returns:                                                               *
' String containing the interface index, or an empty string on          *
' failure                                                               *
'                                                                       *
'Calls:                                                                 *
' SNMPGETCMD - constant defining the path to an external                *
' program and options used to perform an snmp get                       *
'                                                                       *
'Comments:                                                              *
' Uses community string indexing to reference the per vlan mib          *
' instance.                                                             *
' Reference cisco Document ID: 44800                                    *
' "Using SNMP to Find a Port Number from a MAC Address on a             *
' Catalyst Switch" viewed at                                            *
' http://www.cisco.com/en/US/tech/tk648/tk362/                          *
' technologies_tech_note09186a00801c9199.shtml                          *
' on 16/11/2006                                                         *
'************************************************************************
function getIFIndex(strAgent, intVlan, intBridgePort)
dim WshShell, oExec
dim re 'as regexp
dim matches
dim match
dim tempstr, stroutput
Set WshShell = CreateObject("WScript.Shell")
Set oExec = WshShell.Exec(SNMPGETCMD & SNMPREADV & intVlan & " " & _
strAgent & " " & DOT1DBASEPORTIFINDEX & intBridgePort)
Do while Not oExec.StdOut.AtEndOfStream
  stroutput = oExec.StdOut.readall
Loop
Do While oExec.Status <> 1
  WScript.Sleep 100
Loop
set re = new regexp
re.global = True
re.multiline = True
'Pattern to capture the last digits of the snmp output
'output lines from SNMPCMD should look like
' ".1.3.6.1.2.1.17.1.4.1.2.108 11002"
re.pattern = "^" & trim(DOT1DBASEPORTIFINDEX) & intBridgePort & _
                 "\s+(\d+)$"
tempstr = ""
set matches = re.execute(stroutput)
for each match in matches
  tempstr = match.submatches(0)
next
getIFIndex = tempstr
end function

Listing 2


const FORCEUNAUTHORISED = 1
const AUTO = 2
const FORCEAUTHORISED = 3
const dot1xAuthAuthControlledPortControl = ".1.0.8802.1.1.1.1.2.1.1.6."
const SNMPSETCMD = "f:\usr\bin\snmpset.exe -v 2c -c "
const SNMPWRITE = " private "

'************************************************************************
'FUNCTION:                                                              *
' setPortControl(strAgent,intIFIndex, intPortControl)                   *
'                                                                       *
'Purpose:                                                               *
' sets the PaeControlledPortControl value which controls whether        *
' dot1x authentication is required.                                     *
'                                                                       *
'Inputs:                                                                *
' strAgent: management IP address of the switch                         *
' intIFIndex: port interface index returned from getIFIndex()           *
' intPortControl : the control values of the authenticator PAE          *
' controlled port. Allowed values are                                   *
' forceUnauthorized(1), auto(2),forceAuthorized(3)                      *
'                                                                       *
'Returns:                                                               *
' Integer, 0 if successful or a positive value on failure.              *
'                                                                       *
'Calls:                                                                 *
' SNMPSETCMD - constant defining the path to an external                *
' program and options used to perform an snmp set                       *
'                                                                       *
'Comments:                                                              *
' Reference IEEE Std 802.1X-2001                                        *
' "IEEE Standard for Local and metropolitan area networks—              *
' Port-Based Network Access Control"                                    *
' viewed at                                                             *
' http://standards.ieee.org/getieee802/download/802.1X-2001.pdf         *
' on 16/11/2006                                                         *
'************************************************************************
function setPortControl(strAgent,intIFIndex, intPortControl)
dim WshShell, oExec
dim stroutput
if (intPortControl < FORCEUNAUTHORISED or _
  intPortControl > FORCEAUTHORISED) then
  setPortControl = 1
  exit function
end if
Set WshShell = CreateObject("WScript.Shell")
Set oExec = WshShell.Exec(SNMPSETCMD & SNMPWRITE & " " & strAgent & _
                                      " " & dot1xAuthAuthControlledPortControl & intIFIndex &_
                                      " i " & intPortControl)
Do while Not oExec.StdOut.AtEndOfStream
  stroutput = oExec.StdOut.readall
  Loop
Do While oExec.Status <> 1
  WScript.Sleep 100
Loop
setPortControl = instr(1, stroutput, "Error")
end function

Listing 3


const VMVLAN = ".1.3.6.1.4.1.9.9.68.1.2.2.1.2."
const SNMPSETCMD = "f:\usr\bin\snmpset.exe -v 2c -c "
const SNMPWRITE = " private "

'************************************************************************
'FUNCTION:                                                              *
' setVlan(strAgent,intIFIndex, intVlan)                                 *
'                                                                       *
'Purpose:                                                               *
' set the port specified by the interface index suitable to             *
' the specified vlan                                                    *
'                                                                       *
'Inputs:                                                                *
' strAgent: management IP address of the switch                         *
' intIFIndex: port interface index returned from getIFIndex()           *
' intVlan : the vlan to which the port should be configured             *
'                                                                       *
'Returns:                                                               *
' Integer, 0 if successful or a positive value on failure.              *
'                                                                       *
'Calls:                                                                 *
' SNMPSETCMD - constant defining the path to an external                *
' program and options used to perform an snmp set                       *
'                                                                       *
'Comments:                                                              *
' CISCO-VTP-MIB is cisco specific.                                      *
' Reference cisco Document ID: 45080                                    *
' "How To Add, Modify, and Remove VLANs on a Catalyst Using SNMP"       *
' viewed at                                                             *
' http://www.cisco.com/en/US/tech/tk648/tk362/                          *
' technologies_tech_note09186a00801c6035.shtml                          *
' on 16/11/2006                                                         *
'************************************************************************
function setVlan(strAgent,intIFIndex, intVlan)
dim WshShell, oExec
dim stroutput
Set WshShell = CreateObject("WScript.Shell")
Set oExec = WshShell.Exec(SNMPSETCMD & SNMPWRITE & " " & strAgent & _
" " & VMVLAN & intIFIndex & " i " & intVlan)
Do while Not oExec.StdOut.AtEndOfStream
  stroutput = oExec.StdOut.readall
Loop
Do While oExec.Status <> 1
  WScript.Sleep 100
Loop
setVlan = instr(1, stroutput, "Error")
end function

September 21, 2008

Re-Imaging Computers in 802.1x Networks – Part 3

Filed under: Code, Network — Tags: , , , , — networknerd @ 10:53 am

Finding the Bridgeport number
Some MIBs, such as the BRIDGE-MIB, have a separate instance for each VLAN configured on the switch. Mac addresses in the forwarding table are examined using community string indexing to reference the instance maintained for each vlan. The community string is formatted as read_community@vlan_number.
Armed with a list of vlans and the mac address of the computer the bridgeport number can now be found. Hexadecimal digits of the mac address are converted to decimal and concatenated to the dot1dTpFdbPort OID. An snmp get operation is performed using this OID for each vlan in the list until an instance of the OID is found, or the list of vlans is exhausted. The bridgeport number and the vlan configured for the port are returned when an instance of this OID is found.

For example, a computer whose mac address is 00-40-CA-69-34-EE, has a corresponding OID of dot1dTpFdbPort.0.64.202.105.52.238 (.1.3.6.1.2.1.17.4.3.1.2.0.64.202.105.52.238).

G:\usr\bin>snmpget.exe -OnqU -v 2c -c public@1 192.168.36.11 .1.3.6.1.2.1.17.4.3.1.2.0.64.202.105.52.238
.1.3.6.1.2.1.17.4.3.1.2.0.64.202.105.52.238 No Such Instance currently exists at this OID
G:\usr\bin>snmpget.exe -OnqU -v 2c -c public@100 192.168.36.11.1.3.6.1.2.1.17.4.3.1.2.0.64.202.105.52.238
.1.3.6.1.2.1.17.4.3.1.2.0.64.202.105.52.238 108

The mac address was learnt on bridgeport 108, which is configured in vlan 100. The bridgeport
is easily extracted from the output using a regular expression as shown in the getBridgePort()
function in listing 1.

const SNMPSETCMD = "f:\usr\bin\snmpset.exe -v 2c -c "
const SNMPREADV = " public@" 'need community name and vlan for some info
const DOT1DTPFDBPORT = " .1.3.6.1.2.1.17.4.3.1.2"

'************************************************************************
'FUNCTION:                                                              *
' getBridgePort(strAgent, intVlan, strmac)                              *
'                                                                       *
'Purpose:                                                               *
' examine the switch forwarding tables for the specified mac            *
' address in the specified vlan                                         *
'                                                                       *
'Inputs:                                                                *
' strAgent: management IP address of the switch                         *
' intVlan : the vlan specific instance of the forwarding table          *
' strmac : Mac address string in the format 0040CA6934EE                *
'                                                                       *
'Returns:                                                               *
' String containing the bridgeport if found or an empty string          *
'                                                                       *
'Calls:                                                                 *
' SNMPGETCMD - constant defining the path to an external                *
' program and options used to perform an snmp get                       *
'                                                                       *
'Comments:                                                              *
' Uses community string indexing to reference the per vlan mib          *
' instance.                                                             *
' Reference cisco Document ID: 44800                                    *
' "Using SNMP to Find a Port Number from a MAC Address on a             *
' Catalyst Switch" viewed at                                            *
' http://www.cisco.com/en/US/tech/tk648/tk362/                          *
' technologies_tech_note09186a00801c9199.shtml                          *
' on 16/11/2006                                                         *
'************************************************************************
function getBridgePort(strAgent, intVlan, strmac)
dim WshShell, oExec
dim re 'as regexp
dim matches
dim match
dim tempstr, stroutput
Set WshShell = CreateObject("WScript.Shell")
Set oExec = WshShell.Exec(SNMPGETCMD & SNMPREADV & intVlan & " " & _
strAgent & " " & DOT1DTPFDBPORT & mac2oid(strmac))
Do while Not oExec.StdOut.AtEndOfStream
  stroutput = oExec.StdOut.readall
Loop
Do While oExec.Status <> 1
  WScript.Sleep 100
Loop
set re = new regexp
re.global = True
re.multiline = True
'output lines from SNMPCMD should look like
' ".1.3.6.1.2.1.17.4.3.1.2.0.64.202.105.52.238 108"
'Pattern to capture the snmpget output
re.pattern = "^" & trim(DOT1DTPFDBPORT) & mac2oid(strmac) & "\s+(\d+)$"
tempstr = ""
set matches = re.execute(stroutput)
for each match in matches
  tempstr = match.submatches(0)
next
getBridgePort = tempstr
end function

'************************************************************************
'FUNCTION:                                                              *
' mac2OID(strmac)                                                       *
'Purpose:                                                               *
' Convert mac address string to a decimal string for snmp queries.      *
'                                                                       *
'Inputs:                                                                *
' strmac: Mac address string in the format 0040CA6934EE                 *
'                                                                       *
'Returns:                                                               *
' snmp OID string of the form .0.64.202.105.52.238 or an empty          *
' string ("") if an error occured.                                      *
'                                                                       *
'Calls:                                                                 *
' Hex2Dec                                                               *
'                                                                       *
'Comments:                                                              *
' No error checking is performed on the input character set.            *
' The input string is validated by length only.                         *
'************************************************************************
function mac2OID(strmac)
dim intOctet
dim arrOctet
dim strOID
strOID = ""
if len(strmac) = 12 then
  for intOctet = 1 to 11 step 2
    strOID = strOID & "." & Hex2Dec(mid(strmac,intOctet,2))
  next
end if
mac2OID = strOID
end function

'************************************************************************
'FUNCTION:                                                              *
' Hex2Dec(strHex)                                                       *
'Purpose:                                                               *
' Convert a string representation of a hexadecimal number to a          *
' decimal string.                                                       *
'                                                                       *
'Inputs:                                                                *
' strHex: string containing hexadecimal characters [0-9a-fA-F]          *
'                                                                       *
'Returns:                                                               *
' string containing the input converted to decimal characters[0-9]      *
'                                                                       *
'Calls:                                                                 *
' Nothing                                                               *
'                                                                       *
'Comments:                                                              *
' No error checking is performed on the input.                          *
' Beware of overflow in CInt function. Consider modifying to Clng       *
' before using in other code.                                           *
'************************************************************************
Function Hex2Dec(strHex)
Hex2Dec = "" & CInt("&H" & strHex)
End Function

September 20, 2008

Re-Imaging Computers in 802.1x Networks – Part 2

Filed under: Code, Network — Tags: , , , , — networknerd @ 9:40 am

Obtaining a list of vlans

It’s not obvious why we’d need to obtain a list of vlans on a switch. This will become evident in the next post, but for now I will say that  Cisco uses a thing called community string indexing where a per vlan instance of an SNMP MIB is maintained on a device.  The index to the MIB is the vlan number and we’ll need to use community string indexing later on to gather more information.

There are two possible snmp object identifiers (OID’s) to query to get a list of vlans and your mileage will vary here depending on switch configuration. The vlanPortIslVlansAllowed entry from the CISCO-STACK-MIB is convenient as it returns only the vlans allowed on a specified trunk, and reduces the number of snmp operations required to find a port. The vtpVlanState entry from the CISCO-VTP-MIB returns all the vlans propagated through VTP. Vlans that are not configured on any port of the switch we are examining are included, and the number of redundant snmp operations will increase.

VlanPortIslVlansAllowed is defined as

An indication of which Virtual LANs are allowed on this Inter-Switch Link. This is an octet
string value with bits set to indicate allowed VLANs. It can be interpreted as a sum of f(x) as x
goes from 0 to 1023, where f(x) = 0 for VLAN x not allowed and f(x) = exp(2, x) for VLAN x
allowed.

For VlanPortIslVlansAllowed the return value from the query could be up to 1024 bits or 128 hexadecimal digits. The output for a switch on the example network is shown below.

G:\usr\bin>snmpget.exe -Ov -v 2c -c public 192.168.36.11 vlanPortIslVlansAllowed.1.1
Hex-STRING: 70 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 07 01 C0 70 00 00 00 00 00 00
00 00 00 00 00 00

Fortunately large number arithmetic is not required to reveal the allowed vlans. Each pair of
hexadecimal digits is converted to a binary string and concatenated to form the binary
representation of the number. Each bit that is set corresponds to a vlan. The string is reversed
using the strreverse() function and then searched for the character ‘1’ using the instr() function.
The index value returned from instr is the vlan number + 1. The vlan is obtained by subtracting 1
from the returned index and the search repeated until no more matches are found in the string.
More detail is available by referring to the enum_AllowedVLAN()  and supporting functions in listing 1.

Walking vtpVlanState returns output similar to that shown below. The last group of digits in the
OID is the vlan number and the number that follows is the operational state of the vlan. The vlan
number is easily extracted using a regular expression as shown in the enum_VLAN() function in
listing 2.

G:\usr\bin>snmpwalk.exe -OnqUe -v 2c -c public 192.168.36.11 vtpVlanState
.1.3.6.1.4.1.9.9.46.1.3.1.1.2.1.1 1
.1.3.6.1.4.1.9.9.46.1.3.1.1.2.1.100 1
.1.3.6.1.4.1.9.9.46.1.3.1.1.2.1.101 1
.1.3.6.1.4.1.9.9.46.1.3.1.1.2.1.102 1
.1.3.6.1.4.1.9.9.46.1.3.1.1.2.1.110 1
.1.3.6.1.4.1.9.9.46.1.3.1.1.2.1.111 1
.1.3.6.1.4.1.9.9.46.1.3.1.1.2.1.112 1
.1.3.6.1.4.1.9.9.46.1.3.1.1.2.1.120 1
.1.3.6.1.4.1.9.9.46.1.3.1.1.2.1.121 1
.1.3.6.1.4.1.9.9.46.1.3.1.1.2.1.122 1
.1.3.6.1.4.1.9.9.46.1.3.1.1.2.1.300 1
.1.3.6.1.4.1.9.9.46.1.3.1.1.2.1.301 1
.1.3.6.1.4.1.9.9.46.1.3.1.1.2.1.302 1
.1.3.6.1.4.1.9.9.46.1.3.1.1.2.1.1002 1
.1.3.6.1.4.1.9.9.46.1.3.1.1.2.1.1003 1
.1.3.6.1.4.1.9.9.46.1.3.1.1.2.1.1004 1
.1.3.6.1.4.1.9.9.46.1.3.1.1.2.1.1005 1

Listing 1

const vlanPortIslVlansAllowed = " .1.3.6.1.4.1.9.5.1.9.3.1.5.1.1 "
const SNMPGETCMD2 = "f:\usr\bin\snmpget.exe -Ov -v 2c -c "
'************************************************************************
'FUNCTION:                                                              *
' enum_AllowedVLAN(strAgent)                                            *
'Purpose:                                                               *
' enumerate the vlans configured on the switch.                         *
'                                                                       *
'Inputs:                                                                *
' strAgent: management IP address of the switch                         *
'                                                                       *
'Returns:                                                               *
' Array with each element containing a vlan number                      *
'                                                                       *
'Calls:                                                                 *
' SNMPGETCMD2 - constant defining the path to an external               *
' program and options used to perform an snmp get operation.            *
' fmtBinary - function to left pad a binary number with zeros           *
' ToBinary - function to convert an integer to a binary string          *
'                                                                       *
'Comments:                                                              *
' CISCO-STACK-MIB is cisco specific.                                    *
' Reference Cisco SNMP Object Navigator viewed at                       *
' http://tools.cisco.com/Support/SNMP/do/BrowseOID.do?                  *
' objectInput=vlanPortIslVlansAllowed&translate=Translate&              *
' submitValue=SUBMIT&submitClicked=true                                 *
' on 16/11/2006                                                         *
'************************************************************************
function enum_AllowedVLAN(strAgent)
dim WshShell, oExec
dim re 'as regexp
dim matches
dim match, submatch
dim tempstr, stroutput, index, vlans
Set WshShell = CreateObject("WScript.Shell")
Set oExec = WshShell.Exec(SNMPGETCMD2 & SNMPREAD & " " & _
strAgent & " " & vlanPortIslVlansAllowed)
Do while Not oExec.StdOut.AtEndOfStream
  stroutput = oExec.StdOut.readall
Loop
Do While oExec.Status <> 1
  WScript.Sleep 100
Loop
tempstr = ""
set re = new regexp
re.global = True
re.multiline = True
'Pattern to capture the hex digits representing the allowed vlans.
re.pattern = "[0-9a-fA-F]{2}"
vlans = ""
if instr(1,stroutput, "Hex-STRING:") > 0 then
  set matches = re.execute(stroutput)
  for each match in matches
    tempstr = tempstr & fmtBinary(ToBinary(cint("&H" & match)), 8)
  next
  tempstr = strreverse(tempstr)
  index = 1
  do
    index = instr(index, tempstr, "1")
    if index <> 0 then
      vlans = vlans & " " & index - 1
      index = index + 1
    end if
  loop until index = 0
end if
enum_AllowedVLAN = split(trim(vlans))
end function
'************************************************************************
'FUNCTION:                                                              *
' fmtBinary(strNumber, intLength)                                       *
'PURPOSE:                                                               *
' function to left pad a binary number with zeros                       *
'                                                                       *
'INPUTS:                                                                *
' strNumber: binary number to left pad with zeros                       *
' intLength: The desired bit length of the binary number                *
'                                                                       *
'RETURNS:                                                               *
' string containing the binary representation of the input.             *
'                                                                       *
'CALLS:                                                                 *
' Nothing                                                               *
'                                                                       *
'COMMENTS:                                                              *
'************************************************************************
function fmtBinary(strNumber, intLength)
  fmtBinary = string(intLength - len(strNumber), "0") & strNumber
end function
'************************************************************************
'FUNCTION:                                                              *
' ToBinary(intNumber)                                                   *
'                                                                       *
'PURPOSE:                                                               *
' convert an integer number to binary.                                  *
'                                                                       *
'Inputs:                                                                *
' intNumber: Number to convert to binary                                *
'                                                                       *
'Returns:                                                               *
' string containing the binary representation of the input.             *
'                                                                       *
'Calls:                                                                 *
' Nothing                                                               *
'                                                                       *
'Comments:                                                              *
' note the use of \ (integer division operator) rather than /           *
'************************************************************************
function ToBinary(intNumber)
if intNumber > 0 then
  ToBinary = ToBinary(intNumber\2) & intNumber mod 2
end if
end function

Listing2

const SNMPWALKCMD = "f:\usr\bin\snmpwalk.exe -OnqUe -v 2c -c "
const SNMPREAD = " public "
const VTPVLANSTATE = " .1.3.6.1.4.1.9.9.46.1.3.1.1.2 "
'************************************************************************
'FUNCTION:                                                              *
' enum_VLAN(strAgent)                                                   *
'Purpose:                                                               *
' enumerate the vlans configured on the switch.                         *
'                                                                       *
'Inputs:                                                                *
' strAgent: management IP address of the switch                         *
'                                                                       *
'Returns:                                                               *
' Array with each element containing a vlan number                      *
'                                                                       *
'Calls:                                                                 *
' SNMPWALKCMD - constant defining the path to an external               *
' program used to perform an snmp walk                                  *
'                                                                       *
'Comments:                                                              *
' CISCO-VTP-MIB is cisco specific.                                      *
' Reference cisco Document ID: 41003                                    *
' "How to Get VLAN Information From a Catalyst Using SNMP" viewed       *
' at http://www.cisco.com/en/US/tech/tk648/tk362/technologies_          *
' configuration_example09186a008015773e.shtml                           *
' on 16/11/2006                                                         *
'************************************************************************
function enum_VLAN(strAgent)
dim WshShell, oExec
dim re 'as regexp
dim matches
dim match
dim tempstr, stroutput
set re = new regexp
re.global = True
re.multiline = True
'output lines from SNMPCMD should look like
' ".1.3.6.1.4.1.9.9.46.1.3.1.1.2.1.125 1"
'Pattern to capture the last digits of the snmp OID
'Include VTPVLANSTATE in the pattern to minimise regex engine backtracking
re.pattern = "^" & VTPVLANSTATE & "(?:\.\d+)+\.(\d+)\s+\d+$"
Set WshShell = CreateObject("WScript.Shell")
Set oExec = WshShell.Exec(SNMPWALKCMD & SNMPREAD & " " & strAgent & " " & VTPVLANSTATE)
Do while Not oExec.StdOut.AtEndOfStream
  stroutput = oExec.StdOut.readall
Loop
Do While oExec.Status <> 1
  WScript.Sleep 100
Loop
tempstr = ""
set matches = re.execute(stroutput)
for each match in matches
  tempstr = tempstr & match.submatches(0) & " "
next
enum_VLAN = split(trim(tempstr))
end function

September 18, 2008

Re-Imaging Computers in 802.1x Networks – Part 1

Filed under: Code, Network — Tags: , , , , — networknerd @ 7:37 pm

Postings are a little slow lately because I’ve just been to my first Cisco Networkers conference.  I did meet a guy Paul at a techtorial and we were discussing 802.1x.  Paul’s problem was how to deal with re-imaging.

Fortunately I’d already tackled and documented that issue and was able to email my document to him.  Extrapolating from the fact that one person found it useful, I thought I might re-write as a series of blog posts here. At least google will index it for others to find.  If you can’t wait for the blog posts to finish, leave a comment with your email address (obfuscated) and I’ll send you the whole document.

802.1x Basics

Port-based network access control is defined by the dot1x standard as a means of authenticating
devices attached to the network edge, and preventing access to the port where authentication
fails.

Dot1x defines three roles within the port based access control system.

  • Authenticator – usually a switch port that is configured to require authentication before passing traffic.
  • Supplicant – the device that requires access to the network.  The supplicant function is performed by software, either as part of the operating system (windows xp), as an add-on provided by companies such as Juniper/Funk and Cisco/Meetinghouse, or open source such as X-supplicant.
  • Authentication Server – checks the credentials of the supplicant on behalf of the authenticator. The authentication is usually performed via radius.

Problem Definition

The preboot execution environment (PXE) is an essential part of the re-imaging process. PXE is
built into the computer BIOS to allow network boot and automatic download of software images
and configuration parameters. Unfortunately the PXE environment has no supplicant or
credentials with which to authenticate. The switch port will attempt to initiate authentication,
performing a number of retries before timing out and leaving the port in the unauthenticated
state. The computer can neither obtain a DHCP address nor perform boot server discovery to
obtain a boot image.

Possible Solutions

Our options at this point are:

  1. Move the computer to a port in a physically secure location with dot1x disabled.
  2. Have the network administrator disable dot1x on the port, and enable dot1x again when re-imaging is completed.

EDITED TO ADD: Kevin’s comment points out a third option, Mac Authentication Bypass. I write more on that after I have finished this series of posts.

Drawbacks of Option 1 – Computer relocations are extremely unpopular amongst helpdesk staff
charged with re-imaging computers. Also, this option doesn’t scale well when deploying a new
image across the whole organisation.
Drawbacks of Option 2 – Enabling and disabling dot1x on the port will almost certainly fail.
Ultimately someone will forget to enable dot1x on the port, and the number of unsecured ports
will increase over time. The dot1x security will eventually become like swiss cheese.

An automated method that is part of the re-image operations is required.  If your re-imaging program allows scripting (like Altiris in this example) then a completely automated solution is possible.

SNMP to the Rescue

The essence of the solution is to use altiris scripting to perform snmp operations during the reimaging
job. SNMP is used to temporarily disable dot1x authentication on the switch port until
the image is delivered. But the security versus utility tradeoff also comes in to play here.
Compromises made for this solution are listed below.

  1. SNMP must be enabled on the edge switches where the computers to be re-imaged connect.
  2. SNMP read and write access must be granted to the altiris deployment server.
  3. The SNMP read and write community names to the switches are stored in plain text in the altiris scripts.

The only protections we can apply are snmp access control lists to the switches, and ensuring
operating system and file system security is set appropriately on the altiris server. Re-imaging is
optionally performed in a vlan that has limited connectivity in the local network. This protects to
some extent against failure to re-enable dot1x on the switch port. Firewall rules or access control
lists on the switches are used to achieve this.

The process of configuring a port for re-imaging consists of five steps.

  1. Obtain a list of vlans on the switch.
  2. Use the mac address to identify the bridgeport number associated with the port.
  3. Use the bridgeport number to identify the interface index of the port to which the computer is connected.
  4. Use the interface index to set dot1x port control.
  5. Use the interface index to set the vlan. (Optional, only if re-imaging vlans are used)

The next series of posts will discuss each of these steps in depth. I’ll provide detail of the method used to acquire the information, as well as vbscript functions that can be used in re-imaging scripts.

Create a free website or blog at WordPress.com.