Extracting Windows system information with the wmi_info() function

The wmi_info() function, defined on line 172, starts by defining a dictionary that will store the various types of information we query using the WMI API.

Similarly, on line 185, we create the WMI object and assign it to the variable, conn, which is what we will be specifically querying:

172 def wmi_info(outdir):
173 """
174 Gather information available through Windows Management
175 Interface. We recommend extending this script by adding
176 support for other WMI modules -- Win32_PrintJob,
177 Win32_NetworkAdapterConfiguration, Win32_Printer,
178 Win32_PnpEntity (USB).
179 :param outdir: The directory to write CSV reports to.
180 :return: Nothing.
181 """
182
183 wmi_dict = {"Users": [], "Shares": [], "Services": [],
184 "Disks": [], "Event Log": []}
185 conn = wmi.WMI()
In some of these code blocks, you will notice that we call a specific function of the conn object, but in others, we use the query() method. Note that either option is viable in some cases. For instance, instead of calling conn.Win32_UserAccount(), we could call conn.query("SELECT * from Win32_UserAccount"). The query() method gives us some additional flexibility, as we can provide additional logic to our query, which will be seen when we query for specific event log entries.

Starting with the print statement on line 190, we begin to collect information using the wmi library. Iterating through each user profile on line 191, we append various attributes of the user account to the wmi_dict users list: 

187     # See attributes for a given module like so: for user in
188 # conn.Win32_UserAccount(); user._getAttributeNames()
189
190 print("[+] Gathering information on Windows user profiles")
191 for user in conn.Win32_UserAccount():
192 wmi_dict["Users"].append({
193 "Name": user.Name, "SID": user.SID,
194 "Description": user.Description,
195 "InstallDate": user.InstallDate,
196 "Domain": user.Domain,
197 "Local Account": user.LocalAccount,
198 "Password Changeable": user.PasswordChangeable,
199 "Password Required": user.PasswordRequired,
200 "Password Expires": user.PasswordExpires,
201 "Lockout": user.Lockout
202 })

 

We start to use the query() method in the following code block to list all (*) shares on line 205. For each share, we append various details about it to the appropriate list in the wmi_dict dictionary. On line 213, we again use the query() method, this time for services, but only capture services that are currently running.

Hopefully, you can appreciate the value of the query() method, as it provides the developer with a lot of flexibility on isolating and providing data only matching specified criteria, thereby cutting out a lot of junk:

204     print("[+] Gathering information on Windows shares")
205 for share in conn.query("SELECT * from Win32_Share"):
206 wmi_dict["Shares"].append({
207 "Name": share.Name, "Path": share.Path,
208 "Description": share.Description,
209 "Status": share.Status,
210 "Install Date": share.InstallDate})
211
212 print("[+] Gathering information on Windows services")
213 for service in conn.query(
214 "SELECT * FROM Win32_Service WHERE State='Running'"):
215 wmi_dict["Services"].append({
216 "Name": service.Name,
217 "Description": service.Description,
218 "Start Mode": service.StartMode,
219 "State": service.State,
220 "Path": service.PathName,
221 "System Name": service.SystemName})

On line 224, we begin to collect details on the connected drives by iterating through each drive using the conn.Win32_DiskDrive() function. To collect all of the information we want to extract, we need to also iterate through each partition and the logical volume of each disk; hence, the additional for loops on lines 225 and 227.

Once we have the disk, partition, and logical_disk objects, we use each and append a dictionary to the appropriate list of the wmi_dict dictionary containing the various properties of each disk, partition, and volume:

223     print("[+] Gathering information on connected drives")
224 for disk in conn.Win32_DiskDrive():
225 for partition in disk.associators(
226 "Win32_DiskDriveToDiskPartition"):
227 for logical_disk in partition.associators(
228 "Win32_LogicalDiskToPartition"):
229 wmi_dict["Disks"].append({
230 "Physical Disk Name": disk.Name,
231 "Bytes Per Sector": disk.BytesPerSector,
232 "Sectors": disk.TotalSectors,
233 "Physical S/N": disk.SerialNumber,
234 "Disk Size": disk.Size,
235 "Model": disk.Model,
236 "Manufacturer": disk.Manufacturer,
237 "Media Type": disk.MediaType,
238 "Partition Name": partition.Name,
239 "Partition Desc.": partition.Description,
240 "Primary Partition": partition.PrimaryPartition,
241 "Bootable": partition.Bootable,
242 "Partition Size": partition.Size,
243 "Logical Name": logical_disk.Name,
244 "Volume Name": logical_disk.VolumeName,
245 "Volume S/N": logical_disk.VolumeSerialNumber,
246 "FileSystem": logical_disk.FileSystem,
247 "Volume Size": logical_disk.Size,
248 "Volume Free Space": logical_disk.FreeSpace})

Next, on line 253, we create a variable, wmi_query, to hold a string that we will use to extract all events with event ID 4624 from the Security event log.

Note that it was observed in testing that the script needs to be run from an elevated Command Prompt to be able to extract information from the Security event log.

Similar to the other queries, we iterate through the returned results and append various attributes to the appropriate list in the wmi_dict dictionary:

250     # Query for logon events type 4624
251 print("[+] Querying the Windows Security Event Log "
252 "for Event ID 4624")
253 wmi_query = ("SELECT * from Win32_NTLogEvent WHERE Logfile="
254 "'Security' AND EventCode='4624'")
255 for logon in conn.query(wmi_query):
256 wmi_dict["Event Log"].append({
257 "Event Category": logon.CategoryString,
258 "Event ID": logon.EventIdentifier,
259 "Time Generated": logon.TimeGenerated,
260 "Message": logon.Message})

Lastly, after extracting all of the information and storing it in the wmi_dict dictionary, we begin to make calls to the csv_writer() function to write a spreadsheet for each type of data to the output directory. Most of the values being passed into the csv_writer() are self-explanatory and include the artifact-specific data (that is, User Profiles under the Users key), the output directory, and the output filename. The last argument is an alphabetically sorted list of keys from the artifact-specific data to serve as column headers for our CSV.

You will also notice that we have a try and except block to handle writing the event log data. The reason for this, as previously discussed, is that, if the script is not run from an elevated Command Prompt, it is possible that the Event Log key will consist of an empty list:

262     csv_writer(wmi_dict["Users"], outdir, "users.csv",
263 sorted(wmi_dict["Users"][0].keys()))
264 csv_writer(wmi_dict["Shares"], outdir, "shares.csv",
265 sorted(wmi_dict["Shares"][0].keys()))
266 csv_writer(wmi_dict["Services"], outdir, "services.csv",
267 sorted(wmi_dict["Services"][0].keys()))
268 csv_writer(wmi_dict["Disks"], outdir, "disks.csv",
269 sorted(wmi_dict["Disks"][0].keys()))
270 try:
271 csv_writer(wmi_dict["Event Log"],outdir, "logonevent.csv",
272 sorted(wmi_dict["Event Log"][0].keys()))
273 except IndexError:
274 print("No Security Event Log Logon events (Event ID "
275 "4624). Make sure to run the script in an escalated "
276 "command prompt")