I recently needed to analyze a series of VMware image snapshots, as discussed in a recent post. Aside from the more obvious need to review differences on the filesystems on the VM’s virtual disks, I had to validate and explain some of the contents of the snapshot database itself. This database, contained in the “*.vmsd” file, consists of text describing the various other files VMware creates during the snapshot process, as well as other useful metadata. The VMware documentation contained some basic details about this file, and I found other posts explaining the role of the file in the snapshot process.
However, none of these explained the syntax VMware uses in this file to document the time a snapshot was created. There are two fields created in the file upon snapshot creation: “createTimeHigh” and “createTimeLow”, but their format was not readily obvious. I didn’t want to rely solely on filesystem metadata, especially since these values exist in the VMSD file, possibly representing more trustworthy time values. After testing and invalidating of a number of theories, I’m glad to share that I found the structure used for these fields.
First, I created a fairly basic snapshot tree of a VM and included its GUI screenshot and VMSD contents below.
.encoding = "UTF-8" snapshot.lastUID = "41" snapshot.current = "41" snapshot.mru0.uid = "41" snapshot.mru1.uid = "12" snapshot.mru2.uid = "38" snapshot0.uid = "12" snapshot0.filename = "Network-Forensics-Snapshot12.vmsn" snapshot0.displayName = "base snap" snapshot0.description = "Network Forensics VMware Image" snapshot0.createTimeHigh = "300237" snapshot0.createTimeLow = "-1952402371" snapshot0.numDisks = "1" snapshot0.disk0.fileName = "Vanilla Ubuntu 10.04 Desktop (i386)-cl1.vmdk" snapshot0.disk0.node = "scsi0:0" snapshot1.uid = "35" snapshot1.filename = "Network-Forensics-Snapshot35.vmsn" snapshot1.parent = "12" snapshot1.displayName = "test snap 1" snapshot1.type = "2" snapshot1.createTimeHigh = "306294" snapshot1.createTimeLow = "-549817432" snapshot1.numDisks = "1" snapshot1.disk0.fileName = "Vanilla Ubuntu 10.04 Desktop (i386)-cl1-000001.vmdk" snapshot1.disk0.node = "scsi0:0" snapshot2.uid = "41" snapshot2.filename = "Network-Forensics-Snapshot41.vmsn" snapshot2.parent = "12" snapshot2.displayName = "test snap 3" snapshot2.type = "1" snapshot2.createTimeHigh = "311461" snapshot2.createTimeLow = "1251062316" snapshot2.numDisks = "1" snapshot2.disk0.fileName = "Vanilla Ubuntu 10.04 Desktop (i386)-cl1-000004.vmdk" snapshot2.disk0.node = "scsi0:0" snapshot3.uid = "38" snapshot3.filename = "Network-Forensics-Snapshot38.vmsn" snapshot3.parent = "35" snapshot3.displayName = "test snap 2" snapshot3.type = "1" snapshot3.createTimeHigh = "317531" snapshot3.createTimeLow = "1266853093" snapshot3.numDisks = "1" snapshot3.disk0.fileName = "Vanilla Ubuntu 10.04 Desktop (i386)-cl1-000002.vmdk" snapshot3.disk0.node = "scsi0:0" snapshot.numSnapshots = "4"
As you can see, each snapshot is detailed with a set of “snapshot<x>.*” entries in the VMSD file. Most of these, including parental hierachy and VMDK allocation are pretty straightforward. However, where the GUI displays human-readable creation times, they are limited to one-minute resolution and don’t readily appear in the VMSD file. The “createTimeHigh” and “createTimeLow” values appeared relevant. However, the “createTimeHigh” value incremented at times which did not correspond to any known boundaries (day, hour, etc) and the “createTimeLow” value was represented as both positive and negative numbers in different cases.
After a bit of testing, I was able to determine the relationship of these values, and reliably calculate the time the VMSD file indicated the user created each snapshot. Although in hindsight, these seem pretty obvious, the lack of documentation from VMware combined with the need for forensic certainty of the values led to a somewhat long validation process.
These values combine to form a 64-bit, millisecond-accurate UNIX-epoch timestamp. “createTimeHigh” is the highest-order 32-bit value from the final timestamp, and “createTimeLow” is the lowest-order 32-bit value. However, “createTimeLow” is stored as a signed integer, and must be converted accordingly before it can be incorporated into the complete timestamp value. It stands to reason that “createTimeHigh” may be represented in the same way, but we have until sometime after the year 290,300 before that becomes a concern.
Python code to convert these values to an human-readable timestamp is below:
#!/usr/bin/python from datetime import datetime from struct import unpack, pack createTimeHigh=300237 createTimeLow=-1952402371 combinedTimeMsec = float( (createTimeHigh * 2**32) + unpack('I', pack('i', createTimeLow)) ) combinedTimeSec = combinedTimeMsec / 1000000 timestamp = datetime.fromtimestamp(combinedTimeSec) print 'createTimeHigh = %s' % (createTimeHigh) print 'createTimeLow = %s' % (createTimeLow) print 'Human Time = %s' % (timestamp.strftime('%Y-%m-%d %H:%M:%S'))
Given the createTime* values in the sample VMSD file, we calculate the following:
|Snapshot Name||createTimeHigh||createTimeLow||Human-readable Timestamp|
|base snap||300237||-1952402371||2010-11-11 16:20:38|
|test snap 1||306294||-549817432||2011-09-08 20:00:58|
|test snap 3||311461||1251062316||2012-05-22 15:47:40|
|test snap 2||317531||1266853093||2013-03-20 09:35:27|
As you can see from the screenshot, the results are consistent and include improved time precision. In my research, the file creation time for the snapshot’s VMSN file is also equal to this value, however a variety of system conditions (CPU load and disk I/O to name a few) could cause this to be off by a second or so. VMDK file timestamps are subject to file system timestamp changes through normal use or deliberate manipulation. A savvy user could also manually manipulate the createTimeHigh and createTimeLow values in the VMSD file.
By calculating the time value in the VMSD file, we can use an additional data source to cross-reference other findings on the system with regard to the VM’s snapshot tree.