Win32 Perl Programming: The Administrator's Handbook. Chapter 3 - Tools





3.00/5 (2 votes)
Mar 13, 2001

118988
This chapter covers an assortment of scripts simplify and lessen the workload of an administrator.
![]() |
|
Chapter 3 - Tools
Every good administrator assembles a collection of tools that she simply cannot do without. These tools perform specific tasks that enable her to quickly perform her job. A perfect example of such a utility is the popular XCACLS.EXE program that comes with the Windows NT Resource Kit.
This utility (and its little brother CACLS.EXE) dumps any permissions that are applied to a file or directory. Without such a tool, you are doomed to open an Explorer window, select the object in question, right-click, select the Properties context menu, and then go to the Security tab on the resulting property page. If this procedure doesn't sound like very much work, you have not administered a network of thousands of machines with various security permissions applied to various directories and files.
This chapter covers an assortment of scripts that were created for my own toolkit. To be sure, I wrote each of these scripts to simplify and lessen my workload as an administrator. I included these scripts only for the sake of demonstrating how you can leverage Win32 Perl to solve problems that administrators face every day. When you scrutinize the code, it becomes obvious that there is almost no Win32 task that cannot be managed with a bit of Perl coding.
These scripts were not written to be simple or to be tutorials but instead to get into the nitty-gritty of task solving. I recommend that you use a good Win32 API reference while looking at the code that makes up these scripts; in particular, check out Microsoft's MSDN library (http://msdn.microsoft.com/) and, of course, my other book, Win32 Perl Programming: The Standard Extensions (New Riders Publishing).
The File System
The Win32 operating system makes copious use of its file system. As an administrator, you have the burden of managing this important resource. Your job covers the entire range of administrative tasks, ranging from securing files and directories to creating directories and updating outdated files. Many different tools and utilities available from various companies can help you administer your machine's file system. However, you will not be able to easily find some tools.
Many of the existing tools are confusing because they reflect aspects of the file system that are not commonly known, such as alternate data streams. Some tools are not used simply because administrators have no idea that they exist, such as tools used to discover hidden directory shares on remote machines. The following sections focus on file system-related tools that can save you a great deal of work.
Discovering Shared Directories
Ever since Windows for Workgroups was released, sharing directories
for others to connect to has been quite simple. From a Windows NT/2000 command
line, you can easily share a directory by using the net share
command. The following line shares a temporary directory
with the name TempDir
:
net share TempDir=c:\temp
Alternatively, you can share directories from the File Manager, the Explorer, and the Service Manager GUI applications.
The Win32 world permits you to hide
shared directories-that is, share a directory that nobody can see when browsing
the network. The only way to access the directory is to know it has been shared
and explicitly access the hidden share. You hide directories by appending a
dollar sign ($) to the end of the share name. For example, the following
command line creates a hidden shared directory called MyHiddenShare$
:
net share MyHiddenShare$=c:\temp
Now, only users who know about this hidden share can access it.
You can see which directories your machine is sharing by using this command line:
net share
This line displays all the shared directories on the local machine. However, there is no easy way to discover shared directories on a remote machine. You can always open an Explorer window for a particular machine, but this solution is bulky and displays only nonhidden directories.
Let's say that you need to see what shares are available on some remote machine. Explorer won't help you in this case. You could walk over to the machine, log on, and issue the net share command, but this method is not very convenient, especially if the machine is across town or across the country. And assuming that you don't have some type of Telnet daemon installed, this problem is quite serious.
At this point, you can turn to the DumpShares.pl script (shown in Example 3.1). The actual core of the script revolves around line 11, where a call is made into the Win32::Lanman extension to get a list of shared directory names.
As long as you can physically connect to the remote machine, you can see all its shared directories, regardless of whether or not you are granted administrative privileges on the remote machine. If you lack admin privileges on the remote machine, the shared directory's paths are not visible.
The script takes in any number of remote machine names and tries to resolve each passed-in machine name. If nothing is passed in, the local machine's shared directories are displayed.
Example 3.1 Displaying All Shared Directories on a Remote Machine
01. use Win32; 02. use Win32::Lanman; 03. 04. push( @ARGV, Win32::NodeName() ) if( ! scalar @ARGV ); 05. foreach my $Machine ( @ARGV ) 06. { 07. my @List; 08. $Machine =~ s#^[\\/]*#\\\\#; 09. 10. print "\nShare list for ‘$Machine'\n"; 11. if( Win32::Lanman::NetShareEnum( $Machine, \@List ) ) 12. { 13. foreach my $Share ( @List ) 14. { 15. my( $Remark, $Path, $NetName ); 16. 17. $NetName = $Share->{netname}; 18. if( "" ne $Share->{remark} ) 19. { 20. $Remark = "($Share->{remark})"; 21. } 22. if( "" ne $Share->{path} ) 23. { 24. $Path = $Share->{path}; 25. } 26. else 27. { 28. $Path = "No permission to display"; 29. } 30. push( @ShareList, { name => $NetName, 31. remark => $Remark, 32. path => $Path } ); 33. } 34. $~ = Share_Header; 35. write; 36. 37. $~ = Share_Info; 38. $iCount = 0; 39. 40. foreach $Share ( sort( { 41. lc $a->{name} cmp lc $b->{name} 42. } @ShareList ) ) 43. { 44. $iCount++; 45. write; 46. } 47. } 48. else 49. { 50. print "...not available : "; 51. print Win32::FormatMessage( Win32::Lanman::GetLastError() ); 52. } 53. 54. print "\n"; 55. } 56. 57. format Share_Info = 58. @>>> @<<<<<<<<<<<<<<<<< ^<<<<<<<<<<<<<<<<<<<<<< ^<<<<<<<<<<<<<<<<<<<<<<< 59. $iCount, $Share->{name}, $Share->{remark}, $Share->{path} 60. ~ ^<<<<<<<<<<<<<<<<<<<<<< ^<<<<<<<<<<<<<<<<<<<<<<< 61. $Share->{remark}, $Share->{path} 62. ~ ^<<<<<<<<<<<<<<<<<<<<<< ^<<<<<<<<<<<<<<<<<<<<<<< 63. $Share->{remark}, $Share->{path} 64. . 65. 66. format Share_Header = 67. @||| @<<<<<<<<<<<<<<<<< @<<<<<<<<<<<<<<<<<<<<<< @<<<<<<<<<<<<<<<<<<<<<<< 68. "Num", "Share Name", "Comment", "Path" 69. ---- ------------------ ----------------------- ------------------------ 70. .
Volume Information
For an administrator, discovering details of hard drives on remote machines can be essential. Determining how large the volume is, as well as how much space is available, is an important part of drive management. Example 3.2 retrieves such necessary information and other relevant data, such as the volume name, file system type (NTFS, FAT, FAT32, and so on), serial number, and the volume's flags. The flags indicate what features are available for the drive. The flags and their descriptions are listed in Table 3.1.
Table 3.1 Volume Flags
Flag Description
FS_CASE_IS_PRESERVED | The file system preserves the case of filenames when it places a name on disk. |
FS_CASE_SENSITIVE | The file system supports case-sensitive file names. |
FS_UNICODE_STORED_ON_DISK | The file system supports Unicode in filenames as they appear on disk. |
FS_PERSISTENT_ACLS | The file system preserves and enforces Access Control Lists (ACLs-also known as permissions). For example, NTFS preserves and enforces ACLs, but FAT does not. |
FS_FILE_COMPRESSION | The file system supports file-based compression. |
FS_VOL_IS_COMPRESSED | The specified volume is a compressed volume-for example, a DoubleSpace volume. |
FILE_NAMED_STREAMS | The file system supports named streams. |
FILE_SUPPORTS_ENCRYPTION | The file system supports the Encrypted File System (EFS). |
FILE_SUPPORTS_OBJECT_IDS | The file system supports object identifiers. |
FILE_SUPPORTS_REPARSE_POINTS | The file system supports reparse points. This capability allows for features such as using NTFS links and moving infrequently accessed data to long-term storage such as tape backup. |
FILE_SUPPORTS_SPARSE_FILES | The file system supports sparse files. Sparse files have disk storage allocated only for the parts of the file that contain data. Therefore, large blocks of 0s are not stored to disk to conserve drive storage space. |
FILE_VOLUME_QUOTAS | The file system supports space allocation limits on a per user basis. |
The script accepts a list of drive letters or UNCs like this:
perl volume.pl c:\ \\server\share
In this case, the script displays volume information for both the
local machine's C:\
drive as well as for the volume that is
shared by the UNC \\server\share
. Figure 3.1 illustrates the
output from this code. If no parameter is passed in, information regarding the
volume for the current directory is displayed.
The code uses a Win32 API function that was introduced with Windows NT 4.0 and Windows 95 OEM Service Release 2 (OSR2). It will most likely fail on platforms earlier than this.
C:\>perl volume.pl c:\ \\server\share\ c:\: FS Serial Volume Name Flags ----- --------- ------------------------------ ---------------------------- NTFS DCF6-AF23 This is my test volume FS_CASE_SENSITIVE Total Size: 4,301,788,672 FS_CASE_IS_PRESERVED Avail Size: 726,140,928 FS_UNICODE_STORED_ON_DISK FILE_VOLUME_QUOTAS FS_FILE_COMPRESSION FS_PERSISTENT_ACLS FILE_SUPPORTS_OBJECT_IDS FILE_SUPPORTS_SPARSE_FILES FILE_SUPPORTS_REPARSE_POINTS FILE_SUPPORTS_ENCRYPTION \\server\share\: FS Serial Volume Name Flags ----- --------- ------------------------------ ---------------------------- NTFS 8063-FA56 New Volume FS_CASE_SENSITIVE Total Size: 18,202,226,688 FS_CASE_IS_PRESERVED Avail Size: 12,755,464,192 FS_UNICODE_STORED_ON_DISK FILE_VOLUME_QUOTAS FS_FILE_COMPRESSION FS_PERSISTENT_ACLS FILE_SUPPORTS_OBJECT_IDS FILE_SUPPORTS_SPARSE_FILES FILE_SUPPORTS_REPARSE_POINTS FILE_SUPPORTS_ENCRYPTION
Figure 3.1 Displaying volume information.
Example
3.2 Retrieving Volume Information
01. use Win32::API::Prototype; 02. 03. ApiLink( ‘kernel32.dll', ‘BOOL GetVolumeInformation( 04. LPCTSTR lpRootPathName, 05. LPTSTR lpVolumeNameBuffer, 06. DWORD nVolumeNameSize, 07. LPDWORD lpVolumeSerialNumber, 08. LPDWORD lpMaximumComponentLength, 09. LPDWORD lpFileSystemFlags, 10. LPTSTR lpFileSystemNameBuffer, 11. DWORD nFileSystemNameSize )' ) 12. || die "Can not link to GetVolumeInformation()"; 13. ApiLink( ‘kernel32.dll', ‘BOOL GetDiskFreeSpaceEx( 14. LPCTSTR lpDirectoryName, 15. PVOID lpFreeBytesAvailable, 16. PVOID lpTotalNumberOfBytes, 17. PVOID lpTotalNumberOfFreeBytes )' ) 18. || die "Wrong version of NT"; 19. 20. %FLAGS = ( 21. 0x00000001 => ‘FS_CASE_SENSITIVE', 22. 0x00000002 => ‘FS_CASE_IS_PRESERVED', 23. 0x00000004 => ‘FS_UNICODE_STORED_ON_DISK', 24. 0x00000008 => ‘FS_PERSISTENT_ACLS', 25. 0x00000010 => ‘FS_FILE_COMPRESSION', 26. 0x00000020 => ‘FILE_VOLUME_QUOTAS', 27. 0x00000040 => ‘FILE_SUPPORTS_SPARSE_FILES', 28. 0x00000080 => ‘FILE_SUPPORTS_REPARSE_POINTS', 29. 0x00008000 => ‘FS_VOL_IS_COMPRESSED', 30. 0x00000000 => ‘FILE_NAMED_STREAMS', 31. 0x00020000 => ‘FILE_SUPPORTS_ENCRYPTION', 32. 0x00010000 => ‘FILE_SUPPORTS_OBJECT_IDS', 33. ); 34. 35. push( @ARGV, "\\" ) if( 0 == scalar @ARGV ); 36. 37. foreach my $Path ( @ARGV ) 38. { 39. my $dwVolSize = 256; 40. my $szVolName = NewString( $dwVolSize ); 41. my $pdwSerialNum = pack( "L", 0 ); 42. my $pdwMaxLength = pack( "L", 0 ); 43. my $pdwFlags = pack( "L", 0 ); 44. my $dwFSNameSize = 256; 45. my $szFSName = NewString( $dwFSNameSize ); 46. 47. $Path .= "\\" unless( $Path =~ /\\$/ ); 48. next unless( -d $Path ); 49. $Path = NewString( $Path ); 50. 51. print "\n$Path:\n"; 52. $~ = DumpVol; 53. if( GetVolumeInformation( $Path, $szVolName, 54. $dwVolSize, $pdwSerialNum, 55. $pdwMaxLength, $pdwFlags, 56. $szFSName, $dwFSNameSize ) ) 57. { 58. local %Vol = ( 59. path => $Path, 60. volume_name => CleanString( $szVolName ), 61. serial_num => sprintf( "\U%04x-%04x", 62. reverse( unpack( "S2", 63. $pdwSerialNum ) ) ), 64. max_length => unpack( "L", $pdwMaxLength ), 65. flags => DecodeFlags( unpack( "L", $pdwFlags ) ), 66. fs_name => CleanString( $szFSName ) 67. ); 68. my $pFree = pack( "L2", 0, 0 ); 69. my $pTotal = pack( "L2", 0, 0 ); 70. my $pTotalFree = pack( "L2", 0, 0 ); 71. if( GetDiskFreeSpaceEx( $Path, $pFree, $pTotal, $pTotalFree ) ) 72. { 73. $Vol{free} = FormatNumber( MakeLargeInt( unpack( "L2", 74. $pTotalFree ) ) ); 75. $Vol{total} = FormatNumber( MakeLargeInt( unpack( "L2", 76. $pTotal ) ) ); 77. } 78. write; 79. } 80. } 81. 82. sub MakeLargeInt 83. { 84. my( $Low, $High ) = @_; 85. return( $High * ( 1 + 0xFFFFFFFF ) + $Low ); 86. } 87. 88. sub DecodeFlags 89. { 90. my( $Flags ) = @_; 91. my $String = ""; 92. foreach my $Key ( keys( %FLAGS ) ) 93. { 94. if( $Flags & $Key ) 95. { 96. $String .= sprintf( "%- 30s\n", $FLAGS{$Key} ); 97. } 98. } 99. return( $String ); 100. } 101. 102. sub FormatNumber 103. { 104. my( $Num ) = @_; 105. {} while( $Num =~ s/^(-?\d+)(\d{3})/$1,$2/ ); 106. return( $Num ); 107. } 108. 109. format DumpVol = 110. FS Serial Volume Name Flags 111. ----- --------- --------------------------- ---------------------------- 112. @<<<< @<<<<<<<< @<<<<<<<<<<<<<<<<<<<<<<<<<< ^<<<<<<<<<<<<<<<<<<<<<<<<<<< 113. $Vol{fs_name}, $Vol{serial_num}, $Vol{volume_name}, $Vol{flags} 114. Total Size: @<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< ^<<<<<<<<<<<<<<<<<<<<<<<<<<< 115. $Vol{total} $Vol{flags 116. Avail Size: @<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< ^<<<<<<<<<<<<<<<<<<<<<<<<<<< 117. $Vol{free} $Vol{flags} 118. ~ ^<<<<<<<<<<<<<<<<<<<<<<<<<<< 119. $Vol{flags} 120. ~ ^<<<<<<<<<<<<<<<<<<<<<<<<<<< 121. $Vol{flags} 122. ~ ^<<<<<<<<<<<<<<<<<<<<<<<<<<< 123. $Vol{flags} 124. ~ ^<<<<<<<<<<<<<<<<<<<<<<<<<<< 125. $Vol{flags} 126. ~ ^<<<<<<<<<<<<<<<<<<<<<<<<<<< 127. $Vol{flags} 128. ~ ^<<<<<<<<<<<<<<<<<<<<<<<<<<< 129. $Vol{flags} 130. ~ ^<<<<<<<<<<<<<<<<<<<<<<<<<<< 131. $Vol{flags} 132. ~ ^<<<<<<<<<<<<<<<<<<<<<<<<<<< 133. $Vol{flags} 134. ~ ^<<<<<<<<<<<<<<<<<<<<<<<<<<< 135. $Vol{flags} 136. .
Creating Hard File Links
UNIX users could always create hard links between files, and starting with Windows 2000, so can users of the Win32 platform. Previous versions of Windows NT were able to create links by utilizing a POSIX subsystem; however, with Windows 2000 the link functionality is built directly into the Win32 API. Unfortunately, these links are not as rich as their UNIX brethren. You can create links only for files but not directories. And like UNIX's hard links, they must be located on the same volume. This means that a link must reside on the same hard drive as the file it is linked to. Win32, however, does not have the capability to symbolically link across volumes. The closest equivalent is a shortcut, but that is interpreted by an application, not the operating system (therefore, you cannot rely on all programs to understand how to use them).
This hard-linking capability is supported only on NTFS volumes. Example 3.3 creates such hard links. The script takes in two parameters like this:
perl ln.pl c:\temp\MyExistingFile.txt c:\MyNewLink.txt
The first parameter is the existing file, and the second parameter is the path to where the new linked file will be created. This new link file will be (for all practical purposes) the same file as specified in the first parameter. Editing either of these files results in both files being modified. This makes sense because you really have only one file, just two separate file system entries for it.
After a hard link has been created on an NTFS volume, it is literally linked to the original file. Even if you move the original or linked file, they both still point to the same data. Additionally, you can modify the content of either file, and it updates the same data. This can cause concurrency problems if you are accessing multiple files all linked together. Because the file is only a link to the original file, it can be deleted without affecting the original file.
Example 3.3 Creating Hard Links
01. use Win32::API::Prototype; 02. 03. if( 2 != scalar @ARGV ) 04. { 05. print "Syntax:\n $0 <File to link to> <New Link File>\n"; 06. exit(); 07. } 08. 09. my( $NewLink, $File ) = @ARGV; 10. ApiLink( ‘kernel32.dll', 11. ‘BOOL CreateHardLink( LPCTSTR lpFileName, 12. LPCTSTR lpExistingFileName, 13. LPSECURITY_ATTRIBUTES lpSa )' ) 14. || die "This version of Windows does not support CreateHardLink()"; 15. if( CreateHardLink( $File, $NewLink, 0 ) ) 16. { 17. print "Link successfully created.\n"; 18. } 19. else 20. { 21. print "Failed to create link.\nError: "; 22. print Win32::FormatMessage( Win32::GetLastError() ), "\n"; 23. }
Creating Directories
Creating
directories is not a new concept. All disk-based operating systems such as
UNIX, Win32, DOS, and Macintosh have the concept of some file-based container
(for example, a directory or folder). You must create a container so that you
can move objects such as files and other directories into it. You usually do so
by using a command such as md
or mkdir
. Win32 exposes
this functionality through its Win32 API CreateDirectory()
and CreateDirectoryEx()
functions. It is also available from a DOS box when you use the mkdir
or md
command. These techniques, however, fail to create intermediary directories.
For example, if you run
md Dir1\Dir2
and Dir1
does not already exist, then Dir2
will not be created.
Tip
On the Windows NT and 2000 platforms, if command extensions are enabled, the DOS box
md
andmkdir
commands create any intermediary directories. For more information, runcmd.exe /h
.
Even Win32
Perl fails to create any intermediary directories-most likely because it simply
calls into the C: library's mkdir()
function,
which, in turn, calls the Win32 API's CreateDirectory()
function.
Example 3.4
creates as many directories as it needs in order to create the passed-in
directory. There is nothing Win32-specific about this particular script;
therefore, it could run on any platform that runs Perl. This script accepts
only one parameter, which must be either a full path (for example, C
),
a relative path (for example, :
\temp\Dir1\Dir2..\Dir1\Dir2
), or a UNC (for example, \\server\share\Dir1\Dir2
).
To use this script, just pass in the name of the path to create-for example,
perl makedir.pl \\server\share\dir1\dir2 Example 3.4 Creating Intermediary Directories 01. $Dir = shift @ARGV || die "Syntax: $0 <Dir>"; 02. $Result = 1; 03. ( $Root, $Path ) = ( $Dir =~ /^(\w:\\?|\\\\.+?\\.+?\\|\\)?(.*)$/ ); 04. print "Creating directory ‘$Dir'...\n"; 05. 06. if( -d $Dir ) 07. { 08. print "Directory already exists.\n"; 09. exit; 10. } 11. 12. @DirList = split( /\\/, $Path ); 13. $Path = $Root; 14. 15. while( $Result && scalar @DirList ) 16. { 17. $Path .= ( shift @DirList ) . "\\"; 18. 19. next if( -d $Path ); 20. $Result = mkdir( $Path, 0777 ); 21. } 22. 23. print ( ( $Result )? "Success" : "Failure (Error: $!)" ), "\n";
NTFS Streams
One of the greatest joys of using the NTFS file system is the ability to create multiple streams in a file. However, most administrators, let alone users, are not even aware of this capability.
Just like any file system, an NTFS file contains data. However, unlike other file systems, in this one the data is stored in what is called an NTFS stream. This stream is simple data that is associated with the file by means of a name. This is very similar to the way a Perl hash works. A stream name is much like a hash key. Data is associated with a hash key, just as data can be associated with a file stream name. By default, all NTFS files have only one stream, the default stream, which is the place data is stored. However, NTFS allows additional streams to be created in a file. Because each stream has a name (with the exception of the default stream), you access the stream by name.
Thinking of multiple data segments for a single file may be mind-bending-but only because it is not common. Think of multiple streams as you would a tar file, which is basically an archive of multiple data files. You could create one file with multiple streams, each stream consisting of data from different files. This would enable you to create a log file for, say, a Web server that consists of only one file. But each day the Web server creates a new log, it creates a new stream in the log file. The end result is that you could have an entire year's worth of Web server log entries consolidated into one convenient file. Then you can easily copy all the logged data from one place to another because you are copying only one file.
A Windows NT/2000 machine can store Macintosh files on its NTFS drives this way. By using alternate data streams, the operating system can store both the Macintosh data and resource forks in one file.
To access an NTFS stream you must refer to the NTFS file as you normally would, but you use a colon followed by the stream name. The format is
path\file_name:stream_name
Here's an example:
C:\temp\MyTestFile.txt:MyFirstStream
Perl can read and write to such NTFS streams using the normal file functions. You can test this capability by running Example 3.5 and Example 3.6 on an NTFS drive. Example 3.5 creates a text file with two streams: the main data stream and an alternative stream.
Example 3.6 reads back the data from both streams in the text file. If you examine the resulting text file, you will notice that the file size reflects only the data in the default data stream. If you were unaware that the file contained multiple streams, there would normally be no way to discover this fact. As you will see, Win32 Perl can be used to discover such alternate data streams.
Example 3.5 Creating a Test File with Multiple Streams
01. $File = "StreamTest.txt"; 02. $Stream = "AltDataStream"; 03. if( open( STREAM1, "> $File" ) ) 04. { 05. print "Writing to the main data stream.\n"; 06. print STREAM1 "\tWelcome to the main data stream.\n"; 07. close( STREAM1 ); 08. 09. if( open( STREAM2, "> $File:$Stream" ) ) 10. { 11. print "Writing to alternative stream ‘$Stream'.\n"; 12. print STREAM2 "\tWelcome to the alternative data stream ‘$Stream'\n"; 13. close( STREAM2 ); 14. } 15. else 16. { 17. print "Error: $!\n"; 18. } 19. } 20. else 21. { 22. print "Error: $!\n"; 23. }
Example 3.6 Reading Multiple Streams from a Test File
01. $File = "StreamTest.txt"; 02. $Stream = "AltDataStream"; 03. if( open( STREAM1, "< $File" ) ) 04. { 05. print "This is the main data stream for $File.\n"; 06. while( my $Line = <STREAM1> ) 07. { 08. print $Line; 09. } 10. close( STREAM1 ); 11. 12. if( open( STREAM2, "< $File:$Stream" ) ) 13. { 14. print "\n\nThis is the data stream $File:$Stream:\n"; 15. while( my $Line = <STREAM2> ) 16. { 17. print $Line; 18. } 19. close( STREAM2 ); 20. } 21. else 22. { 23. print "Error: $!\n"; 24. } 25. } 26. else 27. { 28. print "Error: $!\n"; 29. }
On Windows NT/2000, the more command can be coaxed into displaying alternate data streams. You do so by redirecting the input into the more command from a data stream like this:
more < test.txt:MyStream
Tip
For some reason, most programs do not permit you to enter valid NTFS pathnames that include alternate streams. For example, an attempt to load
test.txt:MyStream
into Notepad, Microsoft Word, or various other programs will fail.
Example 3.7 is a replacement for the DOS type command. This script functions the same way, except that it can display NTFS data streams-something that type cannot do.
The script accepts any number of paths to display. This includes paths with wildcards. For example,
perl streamdump.pl myfile.txt:altStream1 c:\temp\*.txt
Example 3.7 Displaying NTFS Alternate Data Stream Content
01. foreach my $Mask ( @ARGV ) 02. { 03. my @List; 04. 05. # Must be careful since glob() does not support 06. # paths with embedded spaces. 07. if( @List = glob( $Mask ) ) 08. { 09. push( @FileList, @List ); 10. } 11. else 12. { 13. push( @FileList, $Mask ); 14. } 15. } 16. foreach my $Path ( @FileList ) 17. { 18. print $Path, "\n"; 19. print "-" x length( $Path ), "\n"; 20. if( open( FILE, "< $Path" ) ) 21. { 22. my $Buffer; 23. 24. binmode( FILE ); 25. while( read( FILE, $Buffer, 1024 ) ) 26. { 27. print $Buffer; 28. } 29. close( FILE ); 30. } 31. else 32. { 33. print "\tError: $!\n"; 34. } 35. print "\n"; 36. }
NTFS, Alternative Data Streams, and File Sizes
Interestingly enough, the reported size of a file does not reflect the contents of alternative data streams. For example, if you create a 32-byte file and add an alternative data stream that is 500,000 bytes in size, the file still reports only 32 bytes. This makes it even more impractical for an administrator to determine exactly how much space a file truly takes up.
Just to help complicate NTFS size reporting issues, due to the nature of NTFS, a directory listing may report unexpected available disk space values. For example, if you create this text file
C:\>echo Big Old Test > test.txt
a directory listing shows how large the file is and how much space is available on the disk:
C:\>dir test.txt Volume in drive C is unnamed Volume Serial Number is DCF6-AF23 Directory of C:\ 06/01/2000 09:14a 15 test.txt 1 File(s) 15 bytes 0 Dir(s) 592,346,112 bytes free
If you rewrite the file to a different file size,
C:\>echo This is really a test > test.txt
a directory listing yields
C:\>dir test.txt Volume in drive C is unnamed Volume Serial Number is DCF6-AF23 Directory of C:\ 06/01/2000 09:14a 24 test.txt 1 File(s) 17 bytes 0 Dir(s) 592,346,112 bytes free
Notice that even though the file size changes, the amount of available drive space does not because NTFS calculates two different size values for each file: how large the file is and how much disk space the file takes up. In this case, the file size is either 15 or 24 bytes. However, the file takes up (at least) one sector of disk space. And each sector (on this particular volume) is made up of 1 kilobyte (1024 bytes) of space. Therefore, it takes 1024 bytes of disk space to store only 15 bytes of data. When NTFS computes the available disk space, that amount is based on the amount of unallocated disk space. Because disk space is taken up in 1KB blocks, the available free space is always rounded down to a multiple of 1024.
Enumerating NTFS Streams
The one problem with NTFS streams is that there is no way to
enumerate the streams in a file. This problem is rather tragic, considering how
powerful NTFS streams can be. Basically, this means that if you forget which
streams you have added to a file, there is no way to discover them. This
situation is akin to removing the dir
command from the DOS box: How would
you possibly discover which files are in a directory? Yes, bizarre as it
sounds, this is the reality of Win32's lack of proper support for NTFS streams.
The good news is that you can use a hack to enumerate streams. This
solution involves reading the file as backup software does, using the Win32
API's BackupRead()
function.
The Streams.pl script (see Example 3.8) displays the names and sizes of all streams in an NTFS file. The script accepts any number of parameters from the command line. Each parameter is a path to a file that is to be examined for alternate streams. The file path can contain wildcards.
Example 3.8 Enumerating Streams in an NTFS File
01. use Win32::API::Prototype; 02. 03. $OPEN_EXISTING = 3; 04. $GENERIC_READ = 0x80000000; 05. $BACKUP_DATA = 0x00000001; 06. $BACKUP_ALTERNATE_DATA = 0x00000004; 07. 08. ApiLink( ‘kernel32.dll', 09. ‘HANDLE CreateFile( LPCTSTR pszPath, 10. DWORD dwAccess, 11. DWORD dwShareMode, 12. PVOID SecurityAttributes, 13. DWORD dwCreationDist, 14. DWORD dwFlags, 15. HANDLE hTemplate )' ) 16. || die "Can not locate CreateFile()"; 17. ApiLink( ‘kernel32.dll', 18. ‘BOOL CloseHandle( HANDLE hFile )' ) 19. || die "Can not locate CloseHandle()"; 20. ApiLink( ‘kernel32.dll', 21. ‘BOOL BackupRead( HANDLE hFile, 22. LPBYTE pBuffer, 23. DWORD dwBytesToRead, 24. LPDWORD pdwBytesRead, 25. BOOL bAbort, 26. BOOL bProcessSecurity, 27. LPVOID *ppContext)' ) 28. || die "Can not locate BackupRead()"; 29. ApiLink( ‘kernel32.dll', 30. ‘BOOL BackupSeek( HANDLE hFile, 31. DWORD dwLowBytesToSeek, 32. DWORD dwHighBytesToSeek, 33. LPDWORD pdwLowByteSeeked, 34. LPDWORD pdwHighByteSeeked, 35. LPVOID *pContext )' ) 36. || die "Can not create BackupSeek()"; 37. 38. # Generate a list of all files to process 39. # therefore we have to expand any wildcards 40. foreach my $Mask ( @ARGV ) 41. { 42. foreach my $Path ( glob( $Mask ) ) 43. { 44. push( @Files, $Path ) if( -f $Path ); 45. } 46. } 47. 48. foreach my $File ( @Files ) 49. { 50. print "$File\n"; 51. 52. $hFile = CreateFile( $File, 53. $GENERIC_READ, 54. 0, 55. undef, 56. $OPEN_EXISTING, 57. 0, 58. 0 ) || die "Can not open the file ‘$File'\n"; 59. 60. # If CreateFile() failed $hFile is a negative value 61. if( 0 < $hFile ) 62. { 63. my $iStreamCount = 0; 64. my $iTotalSize = 0; 65. my $pBytesRead = pack( "L", 0 ); 66. my $pContext = pack( "L", 0 ); 67. my $pStreamIDStruct = pack( "L5", 0,0,0,0,0,0 ); 68. 69. while( BackupRead( $hFile, 70. $pStreamIDStruct, 71. length( $pStreamIDStruct ), 72. $pBytesRead, 73. 0, 74. 0, 75. $pContext ) ) 76. { 77. my $BytesRead = unpack( "L", $pBytesRead ); 78. my $Context = unpack( "L", $pContext ); 79. my %Stream; 80. my( $pSeekBytesLow, $pSeekBytesHigh ) = ( pack( "L", 0 ), 81. pack( "L", 0 ) ); 82. my $StreamName = ""; 83. my $StreamSize; 84. 85. # No more data to read 86. last if( 0 == $BytesRead ); 87. 88. @Stream{ id, attributes, 89. size_low, size_high, 90. name_size } = unpack( "L5", $pStreamIDStruct ); 91. 92. if( $BACKUP_ALTERNATE_DATA == $Stream{id} ) 93. { 94. $StreamName = NewString( $Stream{name_size} ); 95. if( BackupRead( $hFile, 96. $StreamName, 97. $Stream{name_size}, 98. $pBytesRead, 99. 0, 100. 0, 101. $pContext ) ) 102. { 103. my $String = CleanString( $StreamName, 1 ); 104. $String =~ s/^:(.*?):.*$/$1/; 105. $StreamName = $String; 106. } 107. } 108. elsif( $BACKUP_DATA == $Stream{id} ) 109. { 110. $StreamName = "<Main Data Stream>"; 111. } 112. $StreamSize = MakeLargeInt( $Stream{size_low}, $Stream{size_high} ); 113. $iTotalSize += $StreamSize; 114. 115. printf( " % 3d) %s (%s bytes)\n", 116. ++$iStreamCount, 117. $StreamName, 118. FormatNumber( $StreamSize ) ) if( "" ne $StreamName ); 119. 120. # Move to next stream... 121. if( ! BackupSeek( $hFile, 122. $Stream{size_low}, 123. $Stream{size_high}, 124. $pSeekBytesLow, 125. $pSeekBytesHigh, 126. $pContext ) ) 127. { 128. last; 129. } 130. $pBytesRead = pack( "L2", 0 ); 131. $pStreamIDStruct = pack( "L5", 0,0,0,0,0 ); 132. } 133. printf( " Total file size: %s bytes\n", 134. FormatNumber( $iTotalSize ) ); 135. # Abort the backup reading. Win32 API claims we MUST do this. 136. BackupRead( $hFile, undef, 0, 0, 1, 0, $pContext ); 137. CloseHandle( $hFile ); 138. } 139. print "\n"; 140. } 141. 142. sub FormatNumber 143. { 144. my( $Num ) = @_; 145. {} while( $Num =~ s/^(-?\d+)(\d{3})/$1,$2/ ); 146. return( $Num ); 147. } 148. 149. sub MakeLargeInt 150. { 151. my( $Low, $High ) = @_; 152. return( $High * ( 1 + 0xFFFFFFFF ) + $Low ); 153. }
Discovering File Versions
Many binary files, such as programs and DLLs, can have version information branded into them. Such branding is not required but is incredibly helpful. When you install software that has updated binary files, it usually checks the version information on files to make sure it is not overwriting a file with an older version.
Figure 3.2 shows all the version information on a given binary file. This particular display is useful in determining not only what version the file is, but also what platform the file was created for (NT, Win32, Win16, DOS).
C:\>perl filever.pl c:\winnt\system32\AUTPRX32.DLL 1) c:\winnt\system32\AUTPRX32.DLL Comments............January 15, 1997 CompanyName.........Microsoft Corporation FileDescription.....Microsoft Remote Automation Proxy for Windows NT(TM) Operating System FileVersion.........5.00.3715 InternalName........AUTPRX32.DLL LangID..............0x0409 Language............English (United States) LegalCopyright......Copyright ¬ Microsoft Corp. 1995. LegalTrademarks.....Microsoft« is a registered trademark of Microsoft Corporation. Windows(TM) is a trademark of Microsoft Corporation ProductName.........Microsoft Remote Automation ProductVersion......5.00.3715 Internal Info: DateStamp.......0 Debug...........0 FileType........dll FileVersion.....5.0.37.15 OS..............nt Patched.........0 PreRelease......0 ProductVersion..5.0.37.15 SpecialBuild....0
Figure 3.2 Displaying version information on a DLL file.
Example 3.9 accepts any number of parameters, each representing a path to a binary file. The paths can include wildcards and can be UNCs to access version information residing on remote machines.
Example 3.9 Displaying File Version Information
01. use Win32::AdminMisc; 02. 03. my $iCount = 0; 04. if( 0 == scalar @ARGV ) 05. { 06. Syntax(); 07. exit; 08. } 09. 10. foreach my $File ( @ARGV ) 11. { 12. my %Info; 13. 14. $iCount++; 15. print "$iCount) $File "; 16. if( -f $File ) 17. { 18. if( Win32::AdminMisc::GetFileInfo( $File, \%Info ) ) 19. { 20. print "\n"; 21. foreach my $Attr ( sort( keys( %Info ) ) ) 22. { 23. next if( ref $Info{$Attr} ); 24. Display( $Attr, $Info{$Attr} ); 25. } 26. 27. if( $Info{FileInfo} ) 28. { 29. Display( "Internal Info:" ); 30. foreach my $Attr ( sort( keys( %{$Info{FileInfo}} ) ) ) 31. { 32. next if( ref $Info{FileInfo}->{$Attr} ); 33. Display( " $Attr", $Info{FileInfo}->{$Attr} ); 34. } 35. } 36. } 37. else 38. { 39. print "unable to get version.\n"; 40. } 41. } 42. else 43. { 44. print "unable to locate file.\n"; 45. } 46. 47. } 48. 49. sub Display 50. { 51. my( $Attr, $Data ) = @_; 52. 53. print "\t$Attr"; 54. if( "" ne $Data ) 55. { 56. print "." x ( 20 - length( $Attr ) ), "$Data"; 57. } 58. print "\n"; 59. } 60. 61. sub Syntax 62. { 63. my( $Line ) = "-" x length( $0 ); 64. 65. print <<EOT; 66. 67. $0 68. $Line 69. Display version information on a particular file. 70. 71. Syntax: 72. perl $0 File[, File2[, ... ]]] 73. 74. EOT 75. }
Permissions
Win32 security is, in part, based on permissions. Permissions, which are placed on securable objects (such as files, directories, Registry keys, and network shares), designate which users have access to perform actions. The topic of permissions is quite involved and can easily take up an entire book by itself, so I won't go into detail on how they work. For this type of information, you can consult any number of different resources, including http://www.roth.net/perl/perms/security/.
Displaying Permissions
UNIX machines have always been capable of listing who has
permissions on files and directories. You get this listing by using the ls
command. Win32's equivalent to ls
is dir
, but it does not
display any permission information.
Example 3.10 displays permission information on any path passed in on the command line. This script accepts a number of parameters representing paths to securable objects, as in this example:
perl perm.pl c:\temp\*.txt \\server\HKLM\Software\ActiveState \\server2\Temp$
This example displays the permissions for all text files in the c:\temp
directory as well as the HKEY_LOCAL_MACHINE\Software\ActiveState
Registry
key on the machine \\server
and the Temp$
share on the
machine \\server2
.
Example 3.10 displays the permissions in the form of an array of letters. Each letter indicates the type of permission that the specified user has been granted. Table 3.2 describes these letters.
Table 3.2 Permissions by Letter
Letter Permission
R | User is allowed to open the object for reading. |
r | User is not allowed to open the object for reading. |
W | User is allowed to open the file for writing. |
w | User is not allowed to open the file for writing. |
D | User is allowed to delete the object. |
d | User is not allowed to delete the object. |
X | User is allowed to execute the object. When this permission is applied to a directory or Registry key, the user is allowed to access its contents. |
x | User is not allowed to execute the object. When this permission is applied to a directory or Registry key, the user is not allowed to access its contents. |
P | User is allowed to change the object's permissions. |
p | User is not allowed to change the object's permissions. |
O | User is allowed to take ownership of the object. |
o | User is not allowed to take ownership of the object. |
A | User has been granted all permissions. This permission may be set in addition to other flags. |
a | User has been denied all permissions. This permission may be set in addition to other flags. |
Figure 3.3 shows the permissions on a specific directory (C:\temp
).
The script first prints the name of the owner and group of the object (in this
case, a directory). Next, it prints each user and group account that has been
granted or denied access to the object. The type of object that the permission
relates to follows the account name. In this example, the Administrator account
is granted directory permissions, and the Perltest7 account has
been granted both directory and file permissions.
The permissions are then displayed. This seven-letter string
indicates which permissions are granted, denied, or not addressed. A capital
letter indicates that the related permission has been explicitly granted, and a
lowercase letter indicates that the permission has been explicitly denied. You
can see that Perltest7 has been granted read and execute but denied write
permissions on files in the C:\temp
directory. Additionally, the
account has been denied permission to take ownership of the directory. On the
other hand, the Administrator account has been granted all permissions. The
letter A means that the account has been
granted all permissions. This
means that there was no need to explicitly grant the read, write, execute, and
delete permissions. Notice that the Joel account has been granted all access
(just like the Administrator); however, the delete permission has been
explicitly denied. Because explicitly denied access always overrides allowed
access, the user has full control over the directory, except that he cannot
delete it.
C:\>perl perm.pl c:\temp Permissions for ‘c:\temp': Owner: BUILTIN\Administrators Group: BUILTIN\Domain Admins Administrator (Directory) RWXD--A Joel (Directory) RWXd--A Perltest7 (Directory) -----o- Perltest7 (File) RwX----
Figure 3.3 Displaying permissions on a directory.
Notes on Files and Directories
For files and directory paths, wildcards are acceptable; however, for Registry keys, they are not.
You can specify files and directories on remote machines by using a UNC. However, if you're specifying a UNC's root directory, make sure that you terminate the path with a backslash like this:
\\server\share\
If you do not append the backslash, the permissions of the network share itself are referenced.
Notes on Shared Directories and Printers
Shared directory and printer pathnames must not end with a backslash. If you specify a shared directory path with an ending backslash, the permissions for the shared directory's root directory are displayed, not the permissions for the network share itself. An attempt to reference a printer with an ending backslash simply fails.
Also, note that shared directories have neither an owner nor a group.
Notes on Registry Keys
You specify Registry keys with a full Registry key path like this:
HKEY_LOCAL_MACHINE\Software
You can abbreviate the Registry root by shortening it like this:
HKLM\Software
You also can specify Registry keys on remote machines by prepending the machine name like this:
\\server\HKEY_LOCAL_MACHINE\Software
or
\\server\HKLM\Software
Note
When accessing remote registries, only the
HKEY_USERS
andHKEY_LOCAL_MACHINE
hives are accessible.
Example 3.10 Displaying Permission Information
01. use Win32::Perms; 02. 03. # The %PERM hash maps a permission type to its position in the 04. # permission string array. Notice that there is no 0 position 05. # since that is reserved for unhandled permission types (and 06. # we won't print it). 07. %PERM = ( 08. R => 1, 09. W => 2, 10. X => 3, 11. D => 4, 12. P => 5, 13. O => 6, 14. A => 7, 15. ); 16. 17. %MAP = ( 18. ‘FILE_READ_DATA' => ‘R', 19. ‘GENERIC_READ' => ‘R', 20. ‘KEY_READ' => ‘R', 21. ‘DIR_READ' => ‘R', 22. 23. ‘FILE_WRITE_DATA' => ‘W', 24. ‘KEY_WRITE' => ‘W', 25. ‘GENERIC_WRITE' => ‘W', 26. ‘FILE_APPEND_DATA' => ‘W', 27. ‘DIR_ADD_SUBDIR' => ‘W', 28. ‘DIR_ADD_FILE' => ‘W', 29. 30. ‘DELETE' => ‘D', 31. ‘FILE_DELETE_CHILD' => ‘D', 32. 33. ‘FILE_EXECUTE' => ‘X', 34. ‘FILE_TRAVERSE' => ‘X', 35. ‘GENERIC_EXECUTE' => ‘X', 36. ‘DIR_TRAVERSE' => ‘X', 37. ‘DIR_EXECUTE' => ‘X', 38. 39. ‘CHANGE_PERMISSION' => ‘P', 40. 41. ‘TAKE_OWNERSHIP' => ‘O', 42. 43. ‘FILE_ALL_ACCESS' => ‘A', 44. ‘GENERIC_ALL' => ‘A', 45. ‘DIR_ALL_ACCESS' => ‘A', 46. ‘STANDARD_RIGHTS_ALL' => ‘A', 47. ); 48. 49. push( @ARGV, "*.*" ) unless( scalar @ARGV ); 50. foreach my $Mask ( @ARGV ) 51. { 52. my( @List ) = glob( $Mask ); 53. if( ! scalar @List ) 54. { 55. push( @List, $Mask ); 56. } 57. foreach my $Path ( @List ) 58. { 59. print "\nPermissions for ‘$Path':\n"; 60. ReportPerms( $Path ); 61. print "\n\n"; 62. } 63. } 64. 65. sub ReportPerms 66. { 67. my( $Path ) = @_; 68. my( $Acct, @List ); 69. my( $Perm ) = new Win32::Perms( $Path ); 70. my( %PermList ) = (); 71. my( $MaxAcctLength ) = 1; 72. 73. if( ! $Perm ) 74. { 75. print "Can not obtain permissions for ‘$Path'\n"; 76. return; 77. }; 78. 79. printf( " Owner: %s\n Group: %s\n", 80. $Perm->Owner(), 81. $Perm->Group() ); 82. $Perm->Dump( \@List ); 83. foreach $Acct ( @List ) 84. { 85. my( $PermMask ); 86. my( $Mask, @M, @F ); 87. my( $DaclType ); 88. my $bAllowAccess = ( "Deny" ne $Acct->{Access} ); 89. my $String; 90. my $Account; 91. 92. next if( $Acct->{Entry} ne "DACL" ); 93. 94. if( "" eq $Acct->{Account} ) 95. { 96. $Account = $Acct->{SID}; 97. } 98. else 99. { 100. $Account = "$Acct->{Domain}\\" if( "" ne $Acct->{Domain} ); 101. $Account .= $Acct->{Account}; 102. } 103. if( length( $Account ) > $MaxAcctLength ) 104. { 105. $MaxAcctLength = length( $Account ) 106. } 107. $iTotal++; 108. DecodeMask( $Acct, \@M, \@F ); 109. foreach $Mask ( @M ) 110. { 111. $PermMask |= 2**$PERM{ $MAP{$Mask} }; 112. } 113. $DaclType = $Acct->{ObjectName}; 114. if( 2 == $Acct->{ObjectType} ) 115. { 116. # We have either a file or directory. Therefore we need to 117. # figure out if this DACL represents an object (file) or 118. # a container (dir)... 119. if( $Acct->{Flag} & DIR ) 120. { 121. $DaclType = "Directory"; 122. } 123. else 124. { 125. $DaclType = "File"; 126. } 127. } 128. if( ! defined $PermList{$Account}->{$DaclType} ) 129. { 130. # Create the permission string array. The first element in the 131. # array must be blank since all unhandled permissions will default 132. # to that position (and we won't print it). 133. my $TempHash = [ 134. " ", 135. split( //, "-" x scalar( keys( %PERM ) ) ) 136. ]; 137. $PermList{$Account}->{$DaclType} = $TempHash; 138. 139. } 140. foreach $Mask ( keys( %PERM ) ) 141. { 142. if( $PermMask & 2**$PERM{$Mask} ) 143. { 144. $String = $PermList{$Account}->{$DaclType}; 145. # If we already have a denied permission then skip this step 146. # since denied access overrides any explicitly allowed access 147. if( $String->[$PERM{$Mask}] !~ /[a-z]/ ) 148. { 149. my $TempMask = $Mask; 150. $TempMask = lc $Mask if( 0 == $bAllowAccess ); 151. $String->[$PERM{$Mask}] = $TempMask ; 152. } 153. } 154. } 155. } 156. 157. if( ! $iTotal ) 158. { 159. # There are no DACL entries therefore... 160. print "\t Everyone has full permissions.\n"; 161. } 162. else 163. { 164. foreach my $Permission ( sort( keys( %PermList ) ) ) 165. { 166. foreach my $DaclType ( sort( keys( %{$PermList{$Permission}} ) ) ) 167. { 168. my $String = $PermList{$Permission}->{$DaclType}; 169. printf( " % " . $MaxAcctLength . "s % -11s %s\n", 170. $Permission, 171. "($DaclType)", 172. join( ‘', @$String ) ); 173. } 174. } 175. } 176. }
Displaying Verbose Permissions
Example 3.10 in the "Displaying Permissions" section displays the permissions for various securable objects, such as files and directories. That particular script distills the complex world of permissions into a few very basic and simple permission sets. Using that script, you can easily get an idea of which users have which permissions. However, to display the permissions in a simple way, many assumptions and interpretations must be made, which can leave out details that may be critical for administrators to successfully maintain true security.
Example 3.11 displays the full set of permissions that an object has. No interpretations are made, and all permissions, flags, and types are displayed in their full, confusing glory. If you're not familiar with the different permissions that can be set on an object, the display may be more than just confusing.
Figure 3.4 shows the output this script
produces. To be sure, it is not for the faint at heart. This particular example
is a permission dump for the same c:\temp
directory shown in Figure 3.3.
Unlike Example 3.10, this script displays all
the permission information. Some of these permissions may not make much sense,
but they are documented in the Win32 API SDK (http://msdn.microsoft.com/).
C:\>perl vperm.pl c:\temp Permissions for ‘c:\temp': Owner: BUILTIN\Administrators Group: BUILTIN\Domain Admins Account Permissions Flags ------------------------------ ---------------------- ----------------------- CREATOR OWNER DELETE INHERIT_ONLY_ACE Deny Access OBJECT_INHERIT_ACE (Directory) CONTAINER_INHERIT_ACE ROTH.NET\Perltest7 FILE_WRITE_DATA INHERIT_ONLY_ACE Deny Access FILE_APPEND_DATA OBJECT_INHERIT_ACE (File) FILE_WRITE_EA FILE_WRITE_ATTRIBUTES ROTH.NET\Perltest7 DIR_ADD_SUBDIR OBJECT_INHERIT_ACE Deny Access TAKE_OWNERSHIP CONTAINER_INHERIT_ACE (Directory) CHANGE_PERMISSION CREATOR OWNER DIR_READ INHERIT_ONLY_ACE Allow Access DIR_ADD_FILE OBJECT_INHERIT_ACE (Directory) DIR_ADD_SUBDIR CONTAINER_INHERIT_ACE DIR_READ_EATTRIB DIR_WRITE_EATTRIB DIR_EXECUTE DIR_DEL_SUBDIR DIR_READ_ATTRIB DIR_WRITE_ATTRIB READ_CONTROL TAKE_OWNERSHIP ROTH.NET\Administrator DIR_READ OBJECT_INHERIT_ACE Allow Access DIR_ADD_FILE CONTAINER_INHERIT_ACE (Directory) DIR_ADD_SUBDIR DIR_READ_EATTRIB DIR_WRITE_EATTRIB DIR_EXECUTE DIR_DEL_SUBDIR DIR_READ_ATTRIB DIR_WRITE_ATTRIB STANDARD_RIGHTS_ALL ROTH.NET\Perltest7 FILE_READ_DATA INHERIT_ONLY_ACE Allow Access FILE_READ_EA OBJECT_INHERIT_ACE (File) FILE_EXECUTE FILE_READ_ATTRIBUTES READ_CONTROL SYNCHRONIZE
Figure 3.4 The true permissions placed on a file.
Example 3.11 accepts any number of paths
on the command line. Each path can contain wildcards. Additionally, an optional
/F
switch is accepted; it reports the so-called friendly
permissions. Most administrators are familiar with these permissions, such as FULL_CONTROL
,
CHANGE
,
and READ.
Such friendly permissions are, however, far from complete and do not
necessarily explain the true nature of the permissions. For example, no
friendly permission describes that a file has both READ
and DELETE
access.
Refer to the "Displaying Permissions" section to learn how to address different paths.
Example 3.11 Displaying the Full Details of an Object's Permissions
01. use Getopt::Long; 02. use Win32::Perms; 03. 04. # Turn off aggressive domain controller lookups 05. Win32::Perms::LookupDC( 0 ); 06. Configure( \%Config ); 07. if( $Config{help} ) 08. { 09. Syntax(); 10. exit(); 11. } 12. 13. foreach my $Mask ( @{$Config{masks}} ) 14. { 15. my( @List ) = glob( $Mask ); 16. if( ! scalar @List ) 17. { 18. push( @List, $Mask ); 19. } 20. foreach $Path ( @List ) 21. { 22. print "\nPermissions for ‘$Path':\n"; 23. ReportPerms( $Path ); 24. print "\n\n"; 25. } 26. } 27. 28. sub ReportPerms 29. { 30. my( $Path ) = @_; 31. my( $Acct, @List ); 32. my( $Perm ) = new Win32::Perms( $Path ); 33. 34. if( ! $Perm ) 35. { 36. print "Can not obtain permissions for ‘$Path'\n"; 37. return; 38. }; 39. 40. printf( " Owner: %s\n Group: %s\n", $Perm->Owner(), $Perm->Group() ); 41. $Perm->Dump( \@List ); 42. $~ = PermissionHeader; 43. write; 44. foreach $Acct ( @List ) 45. { 46. my( $PermMask ); 47. my( @String ) = split( //, "-" x scalar( keys( %PERM ) ) ); 48. my( $Mask, @Permissions, @Friendly ); 49. local $DaclType; 50. local $Access = $Acct->{Access}; 51. local $Account; 52. 53. $~ = Permission; 54. 55. next if( $Acct->{Entry} ne "DACL" ); 56. $iTotal++; 57. 58. DecodeMask( $Acct, \@Permissions, \@Friendly ); 59. DecodeFlag( $Acct, \@Flag ); 60. # Pad each flag and permission with spaces so that they correctly 61. # break into one permission/flag per line when printed. This is a 62. # trick when working with write/formats 63. $Flags = join( " " x 10 . "\n", @Flag ); 64. $Perms = join( " " x 10 . "\n", 65. ( $Config{friendly} )? @Friendly : @Permissions ); 66. if( "" eq $Acct->{Account} ) 67. { 68. $Account = $Acct->{SID}; 69. } 70. else 71. { 72. $Account = "$Acct->{Domain}\\" if( "" ne $Acct->{Domain} ); 73. $Account .= $Acct->{Account}; 74. } 75. $DaclType = $Acct->{ObjectName}; 76. if( 2 == $Acct->{ObjectType} ) 77. { 78. # We have either a file or directory. Therefore we need to 79. # figure out if this DACL represents an object (file) or 80. # a container (dir)... 81. if( $Acct->{Flag} & DIR ) 82. { 83. $DaclType = "Directory"; 84. } 85. else 86. { 87. $DaclType = "File"; 88. } 89. } 90. write; 91. print "\n"; 92. } 93. 94. if( ! $iTotal ) 95. { 96. print "\t Everyone has full permissions.\n"; 97. } 98. } 99. 100. sub Configure 101. { 102. my( $Config ) = @_; 103. my $Result; 104. 105. Getopt::Long::Configure( "prefix_pattern=(-|\/)" ); 106. $Result = GetOptions( $Config, 107. qw( 108. friendly|f 109. help|?|h 110. ) 111. ); 112. push( @{$Config->{masks}}, @ARGV ); 113. $Config->{help} = 1 unless( $Result && scalar @{$Config->{masks}} ); 114. } 115. 116. sub Syntax 117. { 118. my( $Script ) = ( $0 =~ /([^\\\/]*?)$/ ); 119. my( $Line ) = "-" x length( $Script ); 120. 121. print <<EOT; 122. 123. $Script 124. $Line 125. Displays verbose permissions set on securable objects. 126. 127. Syntax: 128. perl $Script [-f] Path [ Path2 ... ] 129. -f..........Show "friendly" permissions. 130. Path........The path to a securable object. 131. This path can consist of ? and * wildcards. 132. 133. EOT 134. } 135. 136. format PermissionHeader = 137. @||||||||||||||||||||||||| @||||||||||||||||||||| @|||||||||||||||||||||| 138. "Account", "Permissions", "Flags" 139. -------------------------- ---------------------- ----------------------- 140. . 141. 142. format Permission = 143. ^<<<<<<<<<<<<<<<<<<<<<<<<< ^<<<<<<<<<<<<<<<<<<<<< ^<<<<<<<<<<<<<<<<<<<<<< 144. $Account, $Perms, $Flags 145. ^<<<<<<<<<<<<<<<<<<<<<<<<< ^<<<<<<<<<<<<<<<<<<<<< ^<<<<<<<<<<<<<<<<<<<<<< 146. $Account, $Perms, $Flags 147. @<<<<<<<<<<<<<< ^<<<<<<<<<<<<<<<<<<<<< ^<<<<<<<<<<<<<<<<<<<<<< 148. "$Access Access", $Perms, $Flags 149. @<<<<<<<<<<<<<< ^<<<<<<<<<<<<<<<<<<<<< ^<<<<<<<<<<<<<<<<<<<<<< 150. "($DaclType)", $Perms, $Flags 151. ~ ^<<<<<<<<<<<<<<<<<<<<< ^<<<<<<<<<<<<<<<<<<<<<< 152. $Perms, $Flags 153. ~ ^<<<<<<<<<<<<<<<<<<<<< ^<<<<<<<<<<<<<<<<<<<<<< 154. $Perms, $Flags 155. ~ ^<<<<<<<<<<<<<<<<<<<<< ^<<<<<<<<<<<<<<<<<<<<<< 156. $Perms, $Flags 157. ~ ^<<<<<<<<<<<<<<<<<<<<< ^<<<<<<<<<<<<<<<<<<<<<< 158. $Perms, $Flags 159. ~ ^<<<<<<<<<<<<<<<<<<<<< ^<<<<<<<<<<<<<<<<<<<<<< 160. $Perms, $Flags 161. ~ ^<<<<<<<<<<<<<<<<<<<<< ^<<<<<<<<<<<<<<<<<<<<<< 162. $Perms, $Flags 163. ~ ^<<<<<<<<<<<<<<<<<<<<< ^<<<<<<<<<<<<<<<<<<<<<< 164. $Perms, $Flags 165. ~ ^<<<<<<<<<<<<<<<<<<<<< ^<<<<<<<<<<<<<<<<<<<<<< 166. $Perms, $Flags 167. .
Ownership
Almost all securable objects have an owner. The owner is an account (either a user or group account) that has ultimate access over the file. The owner of an object can never be denied access to it. He can read, write, delete, and modify permissions, as well as do anything else that is possible.
To discover who is the owner of a particular object (file, directory, or Registry key), you need to locate the security information for the object. For files and directories, you typically use the Explorer program to select the object's properties and then select the Security tab on the displayed property page. For a Registry key, you display the key's permission information from within the REGEDT32.EXE program.
Ownership of an object was designed such that it could only be taken, not given. For example, an administrator could become the owner of a file (take ownership) so that she could usurp permissions placed on the file. However, because she could not assign ownership to another user (give ownership), it would be obvious as to who had bypassed security because the administrator would remain the owner until someone else took ownership. Because an administrator must grant a user account with the privilege to take ownership (the SE_TAKE_OWNERSHIP_NAME privilege to be precise), this paradigm was deemed safe.
It did not take long, however, for programmers to discover that backup software somehow had to be able to restore a file with its original owner intact. That meant that there was a way to give ownership! The technique for doing so lies in the ability for a process to act like backup software. This, of course, requires that the user has a special privilege (SE_RESTORE_NAME).
All this leads to Example 3.12, which enables you not only to discover who is the owner for a given object but also to change the object's owner (provided that you have permission to do so). Any user can use this script to view who is the owner, but a user requires the SE_RESTORE_NAME privilege to change the ownership of a secured object.
This script accepts a number of parameters representing paths to
securable objects. To set the owner on the specified objects, you pass in an -s
switch followed by the account name of the new owner. Consider these two
examples:
perl owner.pl c:\temp\*.txt \\server\HKLM\Software\ActiveState perl owner.pl -s JOEL c:\temp\*.txt \\server\HKLM\Software\ActiveState
The first example displays the owner for all text files in the c:\temp
directory as well as the HKEY_LOCAL_MACHINE\Software\ActiveState
Registry
key on the machine \\server
.
The second example is essentially the same as the first example, except that it first sets all the objects' owners to Joel and then displays who is the owner. If the user running the script has the appropriate privileges, Joel is displayed as the new owner for each object.
Tip
When referring to an account name, you can pass in the name of an account and have Win32 decide if the account is a domain or local machine account. Alternatively, you can explicitly specify what domain or machine the account belongs to.
To specify a domain account, refer to it by specifying the domain followed by a backslash and account name as in
MyDomain\joel "Some Domain\Domain Admins"To specify a particular machine account, pass in the server name beginning with double backslashes. Then append a backslash and account name as in
\\Server1\joel "\\Server1\Domain Admins"
Refer to the notes in the "Displaying Permissions" section for information regarding how to address different paths.
Example 3.12 Retrieving and Setting the Owner of a Securable Object
01. use Getopt::Long; 02. use Win32::Perms; 03. 04. # Turn off intense domain lookups 05. Win32::Perms::LookupDC( 0 ); 06. 07. Configure( \%Config ); 08. if( $Config{help} ) 09. { 10. Syntax(); 11. exit(); 12. } 13. 14. $~ = OwnerHeader; 15. write; 16. $~ = Owner; 17. foreach $Mask ( @{$Config{masks}} ) 18. { 19. my( @List ) = glob( $Mask ); 20. if( ! scalar @List ) 21. { 22. push( @List, $Mask ); 23. } 24. foreach $Path ( @List ) 25. { 26. my $Perm; 27. 28. if( ! ( $Perm = new Win32::Perms( $Path ) ) ) 29. { 30. $Owner = "Can't get permissions.\n"; 31. } 32. else 33. { 34. if( "" ne $Config{owner} ) 35. { 36. $Perm->Owner( $Config{owner} ); 37. $Perm->Set(); 38. } 39. $Owner = $Perm->Owner(); 40. } 41. write; 42. } 43. } 44. 45. sub Configure 46. { 47. my( $Config ) = @_; 48. my $Result; 49. 50. Getopt::Long::Configure( "prefix_pattern=(-|\/)" ); 51. $Result = GetOptions( $Config, 52. qw( 53. owner|o|s=s 54. help|?|h 55. ) 56. ); 57. push( @{$Config->{masks}}, @ARGV ); 58. $Config->{help} = 1 unless( $Result && scalar @{$Config->{masks}} ); 59. } 60. 61. sub Syntax 62. { 63. my( $Script ) = ( $0 =~ /([^\\\/]*?)$/ ); 64. my( $Line ) = "-" x length( $Script ); 65. 66. print <<EOT; 67. 68. $Script 69. $Line 70. Displays (and sets) the owner of securable objects. 71. 72. Syntax: 73. perl $Script [-s Account] Path [ Path2 ... ] 74. -s Account..Set the owner to the specified account. 75. Path........The path to a securable object. 76. This path can consist of ? and * wildcards. 77. 78. EOT 79. } 80. 81. format OwnerHeader = 82. @<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< @<<<<<<<<<<<<<<<<<<<<<<<<<<< 83. "Path", "Owner" 84. -------------------------------------- ---------------------------- 85. . 86. 87. format Owner = 88. @<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< @<<<<<<<<<<<<<<<<<<<<<<<<<<< 89. $Path, $Owner 90. .
DCOM Configurations
Most users never know that their programs communicate with each other using COM. They paste spreadsheets inside their word processing documents without ever knowing that this capability is made possible by object linking and embedding (OLE) technology, which is based on COM. However, you need to be vigilant regarding security because recent versions of Windows have enabled Distributed COM (DCOM). This technology enables one computer to communicate with a program running on another machine. This capability is ideal for administration purposes but potentially dangerous if renegade hackers break into a machine.
Windows provides the DCOMCNFG.EXE utility, which is used to configure DCOM security. This tool enables you to select who has access to a computer via DCOM. It has two problems, though: it is GUI-based, and it runs only on a local machine.
Because the program is GUI-based, getting a quick overview of how a machine's DCOM is configured is difficult. Instead, you must move from tab to tab clicking on buttons. Doing so is not only annoying but a time burden as well.
Because the utility reports only the DCOM configuration for the machine it is running on, its use is quite limited. This limitation requires that you walk to each server, log on, and then run the utility if you want to check your server farm.
Example 3.13 displays a machine's DCOM configuration quickly and without any GUI. You can also check a remote machine's DCOM configuration by passing in the machine's name when running the script.
Note
Every machine that is a member of a domain has a machine account (similar to user account) in the domain. When the machine is booted up and comes online, it logs into the domain using the machine account and a password. When examining a machine's DCOM configuration, you might find references to user, group, and machine accounts.
Figure 3.5 shows the output of Example 3.13. Notice that the Guest account has been explicitly denied in all the security-related configurations. The script shows whether access was granted or denied.
C:\>perl dcom.pl \\server1 DCOM settings for \\Server1: DCOM is enabled. COM Internet Services are enabled. The following accounts are allowed to launch COM applications: Allow NT AUTHORITY\SYSTEM Allow NT AUTHORITY\INTERACTIVE Deny SERVER1\Guest Allow BUILTIN\Administrators Allow SERVER1\IUSR_SERVER1 Allow SERVER1\IWAM_SERVER1 Accounts with access to this machine via DCOM: Allow NT AUTHORITY\SYSTEM Allow NT AUTHORITY\INTERACTIVE Deny SERVER1\Guest Allow BUILTIN\Administrators Allow SERVER1\IUSR_SERVER1 Allow SERVER1\IWAM_SERVER1 Allow BUILTIN\Administrators Accounts with access to COM/OLE configuration: Allow BUILTIN\Administrators Allow CREATOR OWNER Deny SERVER1\Guest Allow Everyone Allow BUILTIN\Power Users Allow NT AUTHORITY\SYSTEM Allow BUILTIN\Users The following protocols are permitted for use with DCOM: ncacn_ip_tcp ncacn_spx ncacn_nb_nb ncacn_nb_ipx
Figure 3.5 Examining a DCOM configuration.
Example
3.13 Displaying DCOM Configurations
01. use Win32::Registry; 02. use Win32::Perms; 03. 04. # Prevent aggressive domain controller lookups 05. Win32::Perms::LookupDC( 0 ); 06. %PATH = ( 07. ole => ‘SOFTWARE\Microsoft\Ole', 08. rpc => ‘SOFTWARE\Microsoft\RPC', 09. ); 10. 11. $Perm = new Win32::Perms() || die "Cannot create Permissions object"; 12. if( scalar @ARGV ) 13. { 14. $Machine = "\\\\" . shift @ARGV; 15. } 16. else 17. { 18. $Machine = "\\\\" . Win32::NodeName(); 19. } 20. $Machine =~ s/^\\+/\\\\/; 21. $HKEY_LOCAL_MACHINE->Connect( $Machine, $Root ) 22. || die "Can not connect"; 23. print "\nDCOM settings for $Machine:\n"; 24. if( $Root->Open( $PATH{ole}, $Key ) ) 25. { 26. my( $Flag, $Type, $Value, $Sd ); 27. 28. undef $Value; 29. $Key->QueryValueEx( "EnableDCOM", $Type, $Value ); 30. print " DCOM is ", ( "y" eq lc $Value )? "":"not ", "enabled.\n"; 31. 32. $Key->QueryValueEx( "EnableDCOMHTTP", $Type, $Value ); 33. print " COM Internet Services are ", 34. ( "y" eq lc $Value )? "":"not ", "enabled.\n"; 35. 36. print "\n The following accounts are allowed to " 37. . "launch COM applications:\n"; 38. if( $Key->QueryValueEx( "DefaultLaunchPermission", $Type, $Sd ) ) 39. { 40. DisplayPerms( $Sd ) if( REG_BINARY == $Type ); 41. } 42. 43. print "\n Accounts with access to this machine via DCOM:\n"; 44. if( $Key->QueryValueEx( "DefaultAccessPermission", $Type, $Sd ) ) 45. { 46. DisplayPerms( $Sd ) if( REG_BINARY == $Type ); 47. } 48. 49. print "\n Accounts with access to COM/OLE configuration:\n"; 50. DisplayPerms( "$Machine\\HKEY_CLASSES_ROOT" ); 51. 52. print "\n The following protocols are permitted " 53. . "for use with DCOM:\n"; 54. if( $Root->Open( $PATH{rpc}, $Key2 ) ) 55. { 56. my $MString; 57. if( $Key2->QueryValueEx( "DCOM Protocols", $Type, $MString ) ) 58. { 59. foreach my $Protocol ( split( "\x00", $MString ) ) 60. { 61. print " $Protocol\n"; 62. } 63. } 64. $Key2->Close(); 65. } 66. $Key->Close(); 67. } 68. $Root->Close(); 69. 70. sub DisplayPerms 71. { 72. my( $Sd ) = @_; 73. my @List; 74. 75. $Perm->Import( $Sd ); 76. $Perm->Dump( \@List ); 77. 78. foreach my $Ace ( @List ) 79. { 80. my $Account; 81. if( "" ne $Ace->{Account} ) 82. { 83. $Account = "$Ace->{Domain}\\" if( "" ne $Ace->{Domain} ); 84. $Account .= $Ace->{Account}; 85. } 86. else 87. { 88. $Account = $Ace->{SID}; 89. } 90. 91. # Object Type of 1 represents a DACL 92. next if( 1 != $Ace->{ObjectType} ); 93. 94. printf( " %-6s %-30s\n", $Ace->{Access}, $Account ); 95. } 96. return; 97. }
Registry
Win32 platforms make use of the Registry to store configuration information. Programs and system services alike populate Registry keys with values that represent various settings of some sort. Information on services and system drivers are stored along with details on how they are loaded and where their files are located. The Registry is where you can find COM component configurations, TCP settings, and auto logon settings, just to name a few.
Regfind
The Registry is a vast wasteland of configuration information. It's so vast, in fact, that locating anything held therein can be quite daunting. Because the Registry is a database of configuration data, you would hope that you could search it and efficiently locate necessary tidbits of information. However, you would be disappointed to learn that this is not possible. The Win32 API does not have any mechanism that enables such searching. Considering that this database, even on a notebook, can grow several tens of megabytes in size, locating data is comparable to finding a needle in a haystack.
The lack of such search functionality is probably due, in part, to the lack of any real need. After all, the repository of information was designed for processes to hold configuration data-what used to be held in .INI files. The presumption here is that software would know where it stored data in the Registry, so it would never need to look for it. This presumption, however, is based on the notion that software behaves itself.
All too often when you uninstall software, it leaves, intact, traces of itself, such as COM registration information. Under these circumstances, searching the Registry for a given GUID or string can be quite valuable. To give another, but altogether pertinent, example, often, for some odd reason, some software moves a window to a location just off of the screen. Such repositioning of a window may occur if you are using dual monitors but have to temporarily resort to using only one. This could also occur if, from time to time, you use a higher resolution display. Either way, every time the application is run, the window the user interacts with is located where it cannot be seen. Because most users do not know where, say, Notepad may locate its configuration store in the Registry, a search becomes quite important.
Example 3.14 performs such searches. When you pass in Registry
paths and search strings, the script scans the Registry and displays its
findings as it proceeds. You can pass in multiple paths and multiple search
strings. All you need to do is specify each path with a -p
switch-as many of them as necessary. Then pass in as many strings as required.
You can locate each path on a remote machine by prepending the machine name to the path. You can also abbreviate the Registry root keys as in Table 3.3.
Table 3.3 Registry Roots and Their Abbreviations
Registry Root Abbreviation
HKEY_LOCAL_MACHINE | HKLM |
HKEY_CURRENT_USER | HKCR |
HKEY_CLASSES_ROOT | HKCU |
HKEY_USERS | HKU |
HKEY_CURRENT_CONFIG | HKCC |
Consider this example:
perl regfind.pl -p HKEY_LOCAL_MACHINE\Software -p \\server1\HKLM\Software winamp "windows media"
This particular instance searches the HKEY_LOCAL_MACHINE\Software
Registry
paths on the current machine as well as on \\Server1
. The
Registry is searched for the string winamp
or "windows media
". The
search is not case sensitive, so WinAmp
also triggers a match. The search
traverses the entire specified Registry paths. Therefore, it could take some
time to complete.
Output is printed to STDOUT
, but information about how the
search is proceeding is printed to STDERR
. This way, you can redirect output
to a file while still observing the findings of the search.
Example 3.14 Locating Strings in the Registry
01. use Getopt::Long; 02. use Win32::Registry; 03. 04. %HIVE_LIST = ( 05. HKEY_LOCAL_MACHINE => $HKEY_LOCAL_MACHINE, 06. HKEY_CURRENT_USER => $HKEY_CURRENT_USER, 07. HKEY_CLASSES_ROOT => $HKEY_CLASSES_ROOT, 08. HKEY_USERS => $HKEY_USERS, 09. HKEY_CURRENT_CONFIG => $HKEY_CURRENT_CONFIG, 10. ); 11. %HIVE_LIST_ABBR = ( 12. HKLM => $HKEY_LOCAL_MACHINE, 13. HKCU => $HKEY_CURRENT_USER, 14. HKCR => $HKEY_CLASSES_ROOT, 15. HKU => $HKEY_USERS, 16. HKCC => $HKEY_CURRENT_CONFIG, 17. ); 18. foreach my $Key ( keys( %HIVE_LIST ) ) 19. { 20. $HIVE_LIST_REVERSE{$HIVE_LIST{$Key}} = $Key; 21. } 22. $iTotalKeys = $iTotalMatch = 0; 23. Configure( \%Config ); 24. if( $Config{help} ) 25. { 26. Syntax(); 27. exit(); 28. } 29. 30. while( my $Path = shift @{$Config{paths}} ) 31. { 32. my( $Temp, $Machine, $Hive ) = ( $Path =~ /^((\\\\.*?)\\)?(.*)/ ); 33. my( $Hive, $Temp2, $Path ) = ( $Hive =~ /^([^\\]*)(\\(.*))?/ ); 34. my $Root = OpenHive( $Hive, $Machine ); 35. if( 0 != $Root ) 36. { 37. my @Keys; 38. my @List; 39. 40. if( "" ne $Machine ) 41. { 42. $Hive = "$Machine\\$Hive"; 43. } 44. 45. if( $Root->Open( $Path, $Key ) ) 46. { 47. $Path = "\\$Path" if( "" ne $Path ); 48. print "Scanning $Hive$Path\n"; 49. ProcessKey( $Key, "$Hive$Path", $KeyName ); 50. } 51. else 52. { 53. print "Unable to open $Hive\\$Path\n"; 54. } 55. $Root->Close(); 56. } 57. else 58. { 59. # Can not connect. 60. } 61. } 62. 63. print STDERR "\n-------------------\n"; 64. print STDERR "Total values checked: $iTotalKeys\n"; 65. print STDERR "Total values matching criteria: $iTotalMatch\n"; 66. 67. sub OpenHive 68. { 69. my( $Hive, $Machine ) = @_; 70. my $Root; 71. my $HiveKey = $HIVE_LIST_ABBR{uc $Hive}; 72. 73. if( "Win32::Registry" ne ref $HiveKey ) 74. { 75. $HiveKey = $HIVE_LIST{uc $Hive} 76. } 77. 78. if( "" ne $Machine ) 79. { 80. if( ! $HiveKey->Connect( $Machine, $Root ) ) 81. { 82. print STDERR "Could not connect to ‘$Machine'\n"; 83. $Root = 0; 84. } 85. } 86. else 87. { 88. $Root = $HiveKey; 89. } 90. return( $Root ); 91. } 92. 93. sub ProcessKey 94. { 95. my( $Key, $Path, $KeyName ) = @_; 96. my $TempKey; 97. 98. if( $Key->Open( $KeyName, $TempKey ) ) 99. { 100. my( $SubKey, $Value, @Keys, %Values, $iCount ); 101. my $ThisPath = $Path; 102. $ThisPath .= "\\$KeyName" if( "" ne $KeyName ); 103. if( $TempKey->GetValues( \%Values ) ) 104. { 105. foreach my $Value ( sort( keys( %Values ) ) ) 106. { 107. my( $Name, $Type, $Data ) = @{$Values{$Value}}; 108. $iTotalKeys++; 109. printf( STDERR "% 10s keys checked; %s matched.\r", 110. FormatNumber( $iTotalKeys ), 111. FormatNumber( $iTotalMatch ) ); 112. foreach my $Target ( @{$Config{target}} ) 113. { 114. if( "$Name\x00$Data" =~ /$Target->{find}/i ) 115. { 116. if( REG_BINARY == $Type ) 117. { 118. $Data = "<Binary Data>"; 119. } 120. print STDERR " " x 60, "\r"; 121. printf( STDOUT " % d) %s\\%s: (%s) ‘%s'\n", 122. ++$iTotalMatch, 123. $ThisPath, 124. $Value, 125. ValueType( $Type ), 126. $Data ); 127. } 128. } 129. } 130. } 131. $TempKey->GetKeys( \@Keys ); 132. 133. foreach $SubKey ( sort( @Keys ) ) 134. { 135. ProcessKey( $TempKey, $ThisPath, $SubKey ); 136. } 137. $TempKey->Close(); 138. } 139. } 140. 141. sub ValueType 142. { 143. my( $Type ) = @_; 144. my( $ValueType ); 145. 146. if( REG_SZ == $Type ) 147. { 148. $ValueType = "REG_SZ"; 149. } 150. elsif( REG_EXPAND_SZ == $Type ) 151. { 152. $ValueType = "REG_EXPAND_SZ"; 153. } 154. elsif( REG_MULTI_SZ == $Type ) 155. { 156. $ValueType = "REG_MULTI_SZ"; 157. } 158. elsif( REG_DWORD == $Type ) 159. { 160. $ValueType = "REG_DWORD"; 161. } 162. elsif( REG_BINARY == $Type) 163. { 164. $ValueType = "REG_BINARY"; 165. } 166. else 167. { 168. $ValueType = "Unknown Type"; 169. } 170. return( $ValueType ); 171. } 172. 173. sub FormatNumber 174. { 175. my( $Number ) = @_; 176. while( $Number =~ s/^(-?\d+)(\d{3})/$1,$2/ ){}; 177. return( $Number ); 178. } 179. 180. sub Configure 181. { 182. my( $Config ) = @_; 183. my $Result; 184. 185. Getopt::Long::Configure( "prefix_pattern=(-|\/)" ); 186. $Result = GetOptions( $Config, 187. qw( 188. paths|p=s@ 189. help|?|h 190. ) 191. ); 192. foreach my $Target ( @ARGV ) 193. { 194. push( @{$Config->{target}}, { find => $Target } ); 195. } 196. if( ! scalar @{$Config->{paths}} ) 197. { 198. push( @{$Config->{paths}}, ‘HKLM' ); 199. } 200. $Config->{help} = 1 unless( scalar @{$Config->{target}} ); 201. $Config->{help} = 1 unless( $Result ); 202. return; 203. } 204. 205. sub Syntax 206. { 207. my( $Script ) = ( $0 =~ m#([^\\/]+)$# ); 208. my $Line = "-" x length( $Script ); 209. print << "EOT"; 210. 211. $Script 212. $Line 213. Locates specified strings in the registry. 214. Syntax: $Script [-p <path>]<find> [<find2> ...] 215. Path..........Registry path to look into such as 216. HKEY_LOCAL_MACHINE or HKEY_CURRENT_USER 217. Abbreviations are allowed (eg. HKLM). 218. Default: HKEY_LOCAL_MACHINE 219. Find..........String to search for. 220. 221. Remote registries are allowed by prepending a machine name 222. such as: \\\\server1\\hklm\\software 223. 224. EOT 225. }
Setting Autologon
There is a little-known ability to configure a Windows NT/2000 machine to autologon. This means that when a machine boots up, it automatically logs on as a specific user. You can set this autologon by manipulating some settings in the Registry. Using such a configuration is a terrific way to autoconfigure a machine.
Say that you need to configure 100 brand new machines. You could create an administrator-like account with a special logon script (covered in Chapter 6, "Logon Scripts"). The logon script would perform any functions that it needs to properly configure the machine-creating proper directories, copying files from a master file server, installing ODBC drivers and other software packages, and so on. Often, however, many programs need to reboot the computer before they can finish installing. Autologon handles this task. Before the logon script finishes, it sets the necessary information in the Registry and reboots the computer. The computer reboots and logs on automatically using the specified user ID. That user ID causes a logon script to run; the script recognizes the state of the machine and finishes whatever it needs to do. Finally, the logon script removes the autologon settings and either logs off or reboots again.
Using this particular example, all you need to do is walk over to each machine and log on with the special account. You do so 100 times, and you're finished. Consider this to be a poor man's version of Microsoft's Systems Management Server (SMS).
The dark side of autologon is that a username and password are added into the Registry, and they can be discovered with relative ease. The information is not encoded, so anyone with permission to view the Registry key can see both the username and password. You can remedy this by applying permissions so that only administrators and the System accounts have read access on the key and can both change permissions and take ownership.
Example 3.15 turns on or off the autologon feature, depending on what you pass into the script. The syntax is
perl setlogon.pl [-m Machine] [-clear] [<Domain\User | User> <Password>]
The switches are described in Table 3.4. If a username is specified, a password must be specified as well. The user account's domain should be specified but does not need to be. However, a logon problem may occur if another user account in the local machine's user database has the same name as one in the default domain.
Table 3.4 Autologon Switches
Switch Description
-m Machine | This switch specifies for which machine to configure autologon. If this switch is not specified, the default is the local machine. |
clear | This switch clears the autologon configuration. The username, domain, and password are cleared, and autologon is turned off. |
User | The username of the account to log on automatically. |
Domain | The domain of the account to log on automatically. This switch is optional. |
Password | The password of the account to log on automatically. |
Example 3.15 Setting the Autologon User Account
01. use Getopt::Long; 02. use Win32::Registry; 03. 04. $PATH = "Software\\Microsoft\\Windows NT\\CurrentVersion\\Winlogon"; 05. Configure( \%Config ); 06. if( $Config{help} ) 07. { 08. Syntax(); 09. exit(); 10. } 11. 12. my $Root = $HKEY_LOCAL_MACHINE; 13. my $Key; 14. 15. if( "" ne $Config{machine} ) 16. { 17. print "Connecting to $Config{machine}...\n"; 18. if( ! $Root->Connect( $Config{machine}, $Root ) ) 19. { 20. print "Can not connect to $Machine\n"; 21. exit(); 22. } 23. } 24. 25. if( $Root->Create( $PATH, $Key ) ) 26. { 27. foreach my $Param ( keys( %{$Config{params}} ) ) 28. { 29. my $Result = $Key->SetValueEx( $Param, 30. 0, 31. REG_SZ, 32. $Config{params}->{$Param} ); 33. print "Setting $Param to ‘$Config{params}->{$Param}': ", 34. ( $Result )? "success":"failure", "\n"; 35. } 36. $Key->Close(); 37. } 38. else 39. { 40. print "Unable to open the $PATH key.\n"; 41. } 42. 43. if( "" ne $Config{machine} ) 44. { 45. $Root->Close(); 46. } 47. 48. sub Configure 49. { 50. my( $Config ) = @_; 51. my $Result; 52. my $Params = { 53. DefaultDomainName => "", 54. DefaultUserName => "", 55. DefaultPassword => "", 56. AutoAdminLogon => 0, 57. }; 58. 59. Getopt::Long::Configure( "prefix_pattern=(-|\/)" ); 60. $Result = GetOptions( $Config, 61. qw( 62. machine|m=s 63. clear|c 64. help|?|h 65. ) 66. ); 67. $Config->{help} = 1 unless( $Result ); 68. 69. if( ! $Config->{clear} ) 70. { 71. $Params->{DefaultDomainName} = shift @ARGV; 72. if( "" ne $Params->{DefaultDomainName} ) 73. { 74. ( $Params->{DefaultDomainName}, 75. $Params->{DefaultUserName} ) = ( $Params->{DefaultDomainName} 76. =~ /((.*)\\)?(.*)$/ )[1..3]; 77. } 78. $Params->{DefaultPassword} = shift @ARGV; 79. $Params->{AutoAdminLogon} = 1; 80. } 81. else 82. { 83. $Params->{AutoAdminLogon} = 0; 84. } 85. if( ( "" eq $Params->{DefaultUserName} ) 86. && ( ! $Config->{clear} ) ) 87. { 88. $Config->{help} = 1; 89. } 90. $Config->{params} = $Params; 91. return; 92. } 93. 94. sub Syntax 95. { 96. my( $Script ) = ( $0 =~ m#([^\\/]+)$# ); 97. my $Line = "-" x length( $Script ); 98. my $WhiteSpace = " " x length( $Script ); 99. print << "EOT"; 100. 101. $Script 102. $Line 103. This utility will set the default logon information such as 104. Userid, Domain, Password and whether or not the computer 105. automatically logs on the default user. 106. 107. Syntax: $Script [-m Machine] <Domain\\User|User> <Password> 108. $WhiteSpace -clear [-m Machine] 109. 110. User............The userid to autologon as 111. Domain..........The domain of the autologon userid 112. Password........The autologon userid's password 113. -m Machine......Specifies what machine to set/unset. 114. -clear..........Remove the domain, username and password 115. 116. EOT 117. }
TCP/IP Configuration
You can configure multiple network cards on Windows machines. You also can bind multiple TCP/IP addresses to a given network card, which can make it difficult to track which computer has what IP address. All this information is held in the Registry on each computer. Being able to track this information, however, can be quite tedious.
Example 3.16 displays a machine's TCP/IP configuration, including addresses procured by means of DHCP. All addresses for all network cards are displayed. The information from other, remote machines can be displayed in addition to the local machine.
The script takes a single parameter that represents which machine to query. If no machine name is passed in, the local machine is assumed. For example,
perl tcpinfo.pl \\server1
In this case, the TCP configuration information is obtained for the
machine \\server1
.
Example 3.16 Displaying a Machine's TCP/IP Configuration
01. use Win32::Registry; 02. 03. %KeyName = ( 04. serviceroot => ‘System\CurrentControlSet\Services', 05. tcplink => ‘Tcpip\Linkage', 06. tcplink_disabled => ‘Tcpip\Linkage\Disabled', 07. win2k_tcplink => ‘Tcpip\Parameters\Interfaces', 08. deviceparam_tcp => ‘Parameters\Tcpip', 09. ); 10. 11. $Root = $HKEY_LOCAL_MACHINE; 12. 13. if( $Machine = $ARGV[0] ) 14. { 15. $HKEY_LOCAL_MACHINE->Connect( $Machine, $Root ) 16. || die "Could not connect to the registry on ‘$Machine'\n"; 17. } 18. 19. if( $Root->Open( $KeyName{serviceroot}, $ServiceRoot ) ) 20. { 21. # First check if this is Win2k... 22. if( $ServiceRoot->Open( $KeyName{win2k_tcplink}, $Links ) ) 23. { 24. my @Interfaces; 25. if( $Links->GetKeys( \@Interfaces ) ) 26. { 27. foreach my $DeviceName ( @Interfaces ) 28. { 29. push( @Devices, [ 30. "$KeyName{win2k_tcplink}\\$DeviceName", 31. $DeviceName 32. ] ); 33. } 34. } 35. $Links->Close(); 36. } 37. # Get the device names of the cards tcp is bound to... 38. else 39. { 40. if( $ServiceRoot->Open( $KeyName{tcplink}, $Links ) ) 41. { 42. my( $Data ); 43. if( $Links->QueryValueEx( "Bind", $DataType, $Data ) ) 44. { 45. $Data =~ s/\n/ /gs; 46. $Data =~ s/\\Device\\//gis; 47. $Data =~ s/^\s+(.*)\s+$/$1/gs; 48. foreach my $DeviceName ( split( /\c@+/, $Data ) ) 49. { 50. push( @Devices, 51. [ 52. "$DeviceName\\$KeyName{deviceparam_tcp}", 53. $DeviceName 54. ] ); 55. } 56. } 57. $Links->Close(); 58. } 59. # Get the device names of cards that tcp is bound to but disabled... 60. if( $ServiceRoot->Open( $KeyName{tcplink_disabled}, $Links ) ) 61. { 62. my( $Data ); 63. 64. if( $Links->QueryValueEx( "Bind", $DataType, $Data ) ) 65. { 66. $Data =~ s/\s+//gs; 67. $Data =~ s/\\Device\\//gis; 68. foreach my $DeviceName ( split( /\c@+/, $Data ) ) 69. { 70. push( @Devices, 71. [ 72. "$DeviceName\\$KeyName{deviceparam_tcp}", 73. $DeviceName 74. ] ); 75. } 76. } 77. $Links->Close(); 78. } 79. } 80. 81. foreach my $Device ( @Devices ) 82. { 83. my( $DeviceTCPKey ); 84. my( $Path ) = $Device->[0]; 85. my( $DeviceName ) = $Device->[1]; 86. 87. if( $ServiceRoot->Open( $Path, $DeviceTCPKey ) ) 88. { 89. my %Hash; 90. my( @IP, @Subnet, @Gateway, @DHCP, @DNS ); 91. my( $Data, $DataType, $Domain ); 92. 93. # Get the domain... 94. $DeviceTCPKey->QueryValueEx( "Domain", $DataType, $Domain ); 95. 96. # Get the IP addresses... 97. if( $DeviceTCPKey->QueryValueEx( "IPAddress", 98. $DataType, 99. $Data ) ) 100. { 101. $Data =~ s/\s+//g; 102. $Data =~ s/0\.0\.0\.0//g; 103. $Data =~ s/\c@+/\c@/g; 104. push( @IP, split( /\c@/, $Data ) ); 105. push( @DHCP, ( "no" ) x scalar split( /\c@/, $Data ) ); 106. } 107. 108. # Get the Subnet masks... 109. if( $DeviceTCPKey->QueryValueEx( "SubnetMask", 110. $DataType, 111. $Data ) ) 112. { 113. $Data =~ s/\s+//g; 114. $Data =~ s/0\.0\.0\.0//g; 115. $Data =~ s/\c@+/\c@/g; 116. push( @Subnet, split( /\c@/, $Data ) ); 117. } 118. # Get the default gateways... 119. if( $DeviceTCPKey->QueryValueEx( "DefaultGateway", 120. $DataType, 121. $Data ) ) 122. { 123. $Data =~ s/\s+//g; 124. $Data =~ s/0\.0\.0\.0//g; 125. $Data =~ s/\c@+/\c@/g; 126. push( @Gateway, split( /\c@/, $Data ) ); 127. } 128. # Get the name servers... 129. if( $DeviceTCPKey->QueryValueEx( "NameServer", 130. $DataType, 131. $Data ) ) 132. { 133. $Data =~ s/0\.0\.0\.0//g; 134. push( @DNS, split( /\s+/, $Data ) ); 135. } 136. 137. # Query DHCP stats 138. if( $DeviceTCPKey->QueryValueEx( "EnableDHCP", 139. $DataType, 140. $Data ) ) 141. { 142. $Hash{dhcp} = (( $Data )? "yes":"no" ); 143. 144. # Get the DHCP domain... 145. $DeviceTCPKey->QueryValueEx( "DhcpDomain", 146. $DataType, 147. $Domain ); 148. 149. # Get the DHCP IP Addresses... 150. if( $DeviceTCPKey->QueryValueEx( "DHCPIPAddress", 151. $DataType, 152. $Data ) ) 153. { 154. $Data =~ s/\s+//g; 155. $Data =~ s/0\.0\.0\.0//g; 156. $Data =~ s/\c@+/\c@/g; 157. push( @IP, split( /\c@/, $Data ) ); 158. push( @DHCP, ( "yes" ) x scalar split( /\c@/, $Data ) ); 159. } 160. 161. # Get the DHCP subnet masks... 162. if( $DeviceTCPKey->QueryValueEx( "DHCPSubnetMask", 163. $DataType, 164. $Data ) ) 165. { 166. $Data =~ s/\s+//g; 167. $Data =~ s/0\.0\.0\.0//g; 168. $Data =~ s/\c@+/\c@/g; 169. push( @Subnet, split( /\c@/, $Data ) ); 170. } 171. 172. # Get the DHCP gateways... 173. if( $DeviceTCPKey->QueryValueEx( "DHCPDefaultGateway", 174. $DataType, 175. $Data ) ) 176. { 177. $Data =~ s/\s+//g; 178. $Data =~ s/0\.0\.0\.0//g; 179. $Data =~ s/\c@+/\c@/g; 180. push( @Gateway, split( /\c@/, $Data ) ); 181. } 182. 183. # Get the DHCP name servers... 184. if( $DeviceTCPKey->QueryValueEx( "DHCPNameServer", 185. $DataType, 186. $Data ) ) 187. { 188. $Data =~ s/0\.0\.0\.0//g; 189. push( @DNS, split( /\s+/, $Data ) ); 190. } 191. } 192. next if( 0 == scalar @IP ); 193. $Hash{name} = $DeviceName; 194. $Hash{ip} = join( " " x 8, @IP ); 195. $Hash{subnet} = join( " " x 8, @Subnet ); 196. $Hash{gateway} = join( " " x 8, @Gateway ); 197. $Hash{dhcp} = join( " " x 8, @DHCP ); 198. $Hash{dns} = join( " " x 8, @DNS ); 199. $Hash{domain} = $Domain; 200. 201. # Push our newfound data onto the stack... 202. push( @TCPConfig, \%Hash ); 203. 204. $DeviceTCPKey->Close(); 205. } 206. } 207. print "The machine $Machine has the following IP addresses:\n"; 208. foreach $IP ( @TCPConfig ) 209. { 210. print "\nInterface: $IP->{name}\n"; 211. print "Domain: $IP->{domain}\n"; 212. 213. $~ = TCPHeader; 214. write; 215. $~ = TCPDump; 216. write; 217. } 218. $ServiceRoot->Close(); 219. } 220. 221. format TCPHeader = 222. @<<< @<<<<<<<<<<<<<< @<<<<<<<<<<<<<< @<<<<<<<<<<<<<< @<<<<<<<<<<<<<< 223. "DHCP", "IP Address", "Subnet Mask", "Gateway", "DNS" 224. ---- --------------- --------------- --------------- --------------- 225. . 226. 227. format TCPDump = 228. ^<<< ^<<<<<<<<<<<<<< ^<<<<<<<<<<<<<< ^<<<<<<<<<<<<<< ^<<<<<<<<<<<<<< 229. $IP->{dhcp}, $IP->{ip}, $IP->{subnet}, $IP->{gateway}, $IP->{dns} 230. ~ ^<<< ^<<<<<<<<<<<<<< ^<<<<<<<<<<<<<< ^<<<<<<<<<<<<<< ^<<<<<<<<<<<<<< 231. $IP->{dhcp}, $IP->{ip}, $IP->{subnet}, $IP->{gateway}, $IP->{dns} 232. ~ ^<<< ^<<<<<<<<<<<<<< ^<<<<<<<<<<<<<< ^<<<<<<<<<<<<<< ^<<<<<<<<<<<<<< 233. $IP->{dhcp}, $IP->{ip}, $IP->{subnet}, $IP->{gateway}, $IP->{dns} 234. ~ ^<<< ^<<<<<<<<<<<<<< ^<<<<<<<<<<<<<< ^<<<<<<<<<<<<<< ^<<<<<<<<<<<<<< 235. $IP->{dhcp}, $IP->{ip}, $IP->{subnet}, $IP->{gateway}, $IP->{dns} 236. ~ ^<<< ^<<<<<<<<<<<<<< ^<<<<<<<<<<<<<< ^<<<<<<<<<<<<<< ^<<<<<<<<<<<<<< 237. $IP->{dhcp}, $IP->{ip}, $IP->{subnet}, $IP->{gateway}, $IP->{dns} 238. ~ ^<<< ^<<<<<<<<<<<<<< ^<<<<<<<<<<<<<< ^<<<<<<<<<<<<<< ^<<<<<<<<<<<<<< 239. $IP->{dhcp}, $IP->{ip}, $IP->{subnet}, $IP->{gateway}, $IP->{dns} 240. ~ ^<<< ^<<<<<<<<<<<<<< ^<<<<<<<<<<<<<< ^<<<<<<<<<<<<<< ^<<<<<<<<<<<<<< 241. $IP->{dhcp}, $IP->{ip}, $IP->{subnet}, $IP->{gateway}, $IP->{dns} 242. ~ ^<<< ^<<<<<<<<<<<<<< ^<<<<<<<<<<<<<< ^<<<<<<<<<<<<<< ^<<<<<<<<<<<<<< 243. $IP->{dhcp}, $IP->{ip}, $IP->{subnet}, $IP->{gateway}, $IP->{dns} 244. .
Conclusion
There are dozens of different tools that an administrator can use to make her job easier. Tools ranging from open source code to commercial shrink-wrapped packages provide the necessary utilities to accomplish an administrative goal.
This chapter demonstrates Perl's remarkable capability to exercise the Win32 API to accomplish tasks. We have uncovered ways to use Perl to manage the Win32 file system, permissions, and the Registry. The tools discussed are useful and help define techniques that can be modified to target other tasks.