[Libguestfs] [Hivex] [PATCH v3] Report last-modified time of hive root and nodes

Alex‎ Nelson ajnelson at cs.ucsc.edu
Sat Aug 13 06:03:37 UTC 2011


The infrastructure for modified-time reporting has been essentially
unused.  These changes report the registry time by treating the
time fields as Windows filetime fields stored in little-Endian
(which means they can be treated as a single 64-bit little-Endian
integer).

This patch adds to the hivex ABI:

 * int64_t hivex_last_modified (hive_h *)
 * int64_t hivex_node_timestamp (hive_h *, hive_node_h)

These two functions return the hive's last-modified time and
a particular node's last-modified time, respectively.  Credit
to Richard Jones for the ABI suggestion, and for the tip on
Microsoft's filetime time span.

hivexml employs these two functions to produce mtime elements
for a hive and all of its nodes, producing ISO-8601 formatted
time.

Signed-off-by: Alex Nelson <ajnelson at cs.ucsc.edu>
---
 generator/generator.ml |   20 +++++++++++++
 lib/hivex.c            |   46 +++++++++++++++++++++++++++++--
 xml/hivexml.c          |   71 +++++++++++++++++++++++++++++++++++++++++++++++-
 3 files changed, 133 insertions(+), 4 deletions(-)

diff --git a/generator/generator.ml b/generator/generator.ml
index de911f1..370472f 100755
--- a/generator/generator.ml
+++ b/generator/generator.ml
@@ -158,6 +158,13 @@ but instead are lost.  See L<hivex(3)/WRITING TO HIVE FILES>.";
     "\
 Return root node of the hive.  All valid hives must contain a root node.";
 
+  "last_modified", (RInt64, [AHive]),
+  "return the modification time of the root node of the hive",
+  "\
+Return the modification time of the root node of the hive.  The
+returned value is equivalent in type and caveats as
+hivex_node_timestamp.";
+
   "node_name", (RString, [AHive; ANode "node"]),
     "return the name of the node",
     "\
@@ -170,6 +177,16 @@ only know the \"real\" name of the root node by knowing which registry
 file this hive originally comes from, which is knowledge that is
 outside the scope of this library.";
 
+  "node_timestamp", (RInt64, [AHive; ANode "node"]),
+    "return the modification time of the node",
+    "\
+Return the modification time of the node.  Output is an Endian-correct
+64-bit signed number.
+
+Though Windows' filetime struct looks like it should be an unsigned
+64-bit int, Microsoft only guarantees FILETIME through 9999-12-31, due
+to conversion limitations with .Net's DateTime.";
+
   "node_children", (RNodeList, [AHive; ANode "node"]),
     "return children of node",
     "\
@@ -708,6 +725,9 @@ typedef size_t hive_value_h;
 # define HIVEX_NO_KEY ENOENT
 #endif
 
+#include <time.h>
+#define TIMESTAMP_BUF_LEN 32
+
 /* Pre-defined types. */
 enum hive_type {
 ";
diff --git a/lib/hivex.c b/lib/hivex.c
index fedbb6c..1e77831 100644
--- a/lib/hivex.c
+++ b/lib/hivex.c
@@ -93,6 +93,7 @@ struct hive_h {
   /* Fields from the header, extracted from little-endianness hell. */
   size_t rootoffs;              /* Root key offset (always an nk-block). */
   size_t endpages;              /* Offset of end of pages. */
+  int64_t last_modified;        /* mtime of base block. */
 
   /* For writing. */
   size_t endblocks;             /* Offset to next block allocation (0
@@ -104,7 +105,7 @@ struct ntreg_header {
   char magic[4];                /* "regf" */
   uint32_t sequence1;
   uint32_t sequence2;
-  char last_modified[8];
+  int64_t last_modified;
   uint32_t major_ver;           /* 1 */
   uint32_t minor_ver;           /* 3 */
   uint32_t unknown5;            /* 0 */
@@ -173,7 +174,7 @@ struct ntreg_nk_record {
   int32_t seg_len;              /* length (always -ve because used) */
   char id[2];                   /* "nk" */
   uint16_t flags;
-  char timestamp[8];
+  int64_t timestamp;
   uint32_t unknown1;
   uint32_t parent;              /* offset of owner/parent */
   uint32_t nr_subkeys;          /* number of subkeys */
@@ -359,6 +360,9 @@ hivex_open (const char *filename, int flags)
     goto error;
   }
 
+  /* Last-modified time. */
+  h->last_modified = le64toh ((int64_t) h->hdr->last_modified);
+
   if (h->msglvl >= 2) {
     char *name = windows_utf16_to_utf8 (h->hdr->name, 64);
 
@@ -367,6 +371,8 @@ hivex_open (const char *filename, int flags)
              "  file version             %" PRIu32 ".%" PRIu32 "\n"
              "  sequence nos             %" PRIu32 " %" PRIu32 "\n"
              "    (sequences nos should match if hive was synched at shutdown)\n"
+             "  last modified            \n"
+             "    (decimal, 100 ns)      %" PRIu64 "\n"
              "  original file name       %s\n"
              "    (only 32 chars are stored, name is probably truncated)\n"
              "  root offset              0x%x + 0x1000\n"
@@ -374,6 +380,7 @@ hivex_open (const char *filename, int flags)
              "  checksum                 0x%x (calculated 0x%x)\n",
              major_ver, le32toh (h->hdr->minor_ver),
              le32toh (h->hdr->sequence1), le32toh (h->hdr->sequence2),
+             h->last_modified,
              name ? name : "(conversion failed)",
              le32toh (h->hdr->offset),
              le32toh (h->hdr->blocks), h->size,
@@ -608,6 +615,39 @@ hivex_node_name (hive_h *h, hive_node_h node)
   return ret;
 }
 
+int64_t
+hivex_timestamp_check (hive_h *h, hive_node_h node, int64_t timestamp)
+{
+  if (timestamp < 0) {
+    fprintf (stderr, "Negative time reported at %z:  %" PRIi64 "\n", node, timestamp);
+    errno = EINVAL;
+    return -1;
+  }
+  return timestamp;
+}
+
+int64_t
+hivex_last_modified (hive_h *h)
+{
+  return hivex_timestamp_check (h, 0, h->last_modified);
+}
+
+int64_t
+hivex_node_timestamp (hive_h *h, hive_node_h node)
+{
+  int64_t ret;
+
+  if (!IS_VALID_BLOCK (h, node) || !BLOCK_ID_EQ (h, node, "nk")) {
+    errno = EINVAL;
+    return -1;
+  }
+
+  struct ntreg_nk_record *nk = (struct ntreg_nk_record *) (h->addr + node);
+
+  ret = le64toh (nk->timestamp);
+  return hivex_timestamp_check (h, node, ret);
+}
+
 #if 0
 /* I think the documentation for the sk and classname fields in the nk
  * record is wrong, or else the offset field is in the wrong place.
@@ -2264,7 +2304,7 @@ hivex_node_add_child (hive_h *h, hive_node_h parent, const char *name)
   nk->sk = htole32 (parent_sk_offset - 0x1000);
 
   /* Inherit parent timestamp. */
-  memcpy (nk->timestamp, parent_nk->timestamp, sizeof (parent_nk->timestamp));
+  nk->timestamp = parent_nk->timestamp;
 
   /* What I found out the hard way (not documented anywhere): the
    * subkeys in lh-records must be kept sorted.  If you just add a
diff --git a/xml/hivexml.c b/xml/hivexml.c
index 90cb22b..c68a3d2 100644
--- a/xml/hivexml.c
+++ b/xml/hivexml.c
@@ -64,6 +64,8 @@ static struct hivex_visitor visitor = {
   .value_other = value_other
 };
 
+char * filetime_to_8601 (int64_t windows_ticks);
+
 #define XML_CHECK(proc, args)                                           \
   do {                                                                  \
     if ((proc args) == -1) {                                            \
@@ -124,6 +126,20 @@ main (int argc, char *argv[])
   XML_CHECK (xmlTextWriterStartDocument, (writer, NULL, "utf-8", NULL));
   XML_CHECK (xmlTextWriterStartElement, (writer, BAD_CAST "hive"));
 
+  int64_t hive_mtime = hivex_last_modified (h);
+  if (hive_mtime < 0)
+    goto skip_mtime;
+  char *timebuf = filetime_to_8601 (hive_mtime);
+  if (timebuf == NULL)
+    goto skip_mtime;
+  XML_CHECK (xmlTextWriterStartElement, (writer, BAD_CAST "mtime"));
+  XML_CHECK (xmlTextWriterWriteString, (writer, BAD_CAST timebuf));
+  XML_CHECK (xmlTextWriterEndElement, (writer));
+  free (timebuf);
+  timebuf = NULL;
+
+skip_mtime:
+
   if (hivex_visit (h, &visitor, sizeof visitor, writer, visit_flags) == -1) {
     perror (argv[optind]);
     exit (EXIT_FAILURE);
@@ -141,13 +157,66 @@ main (int argc, char *argv[])
   exit (EXIT_SUCCESS);
 }
 
+#define WINDOWS_TICK 10000000LL
+#define SEC_TO_UNIX_EPOCH 11644473600LL
+/**
+ * Convert Windows filetime to ISO 8601 format.
+ * Source for filetime->time_t conversion:  http://stackoverflow.com/questions/6161776/convert-windows-filetime-to-second-in-unix-linux/6161842#6161842
+ * Source for time_t->char* conversion:  Fiwalk version 0.6.14's fiwalk.cpp.
+ * @param windows_ticks Expected to not have any remaining Endian issues.
+ * 
+ * Caller is responsible for freeing non-null returned buffer.
+ */
+char *
+filetime_to_8601 (int64_t windows_ticks)
+{
+  char *ret = calloc (1 + TIMESTAMP_BUF_LEN, sizeof (char));
+  if (ret == NULL) {
+    goto error_other;
+  }
+  uint64_t nanos = windows_ticks % WINDOWS_TICK;
+  time_t tt = (windows_ticks / WINDOWS_TICK - SEC_TO_UNIX_EPOCH);
+  struct tm time_tm;
+  if (gmtime_r (&tt, &time_tm) == NULL) {
+    fprintf (stderr, "filetime_to_8601: Error running gmtime_r on timestamp (decimal hundreds of ns: %" PRIu64 ").\n", windows_ticks);
+    goto error_cleanup;
+  }
+  if (strftime (ret, TIMESTAMP_BUF_LEN, "%FT%TZ", &time_tm) == 0)
+    goto error_cleanup;
+  return ret;
+
+error_cleanup:
+  free (ret);
+  ret = NULL;
+error_other:
+  return NULL;
+}
+
 static int
 node_start (hive_h *h, void *writer_v, hive_node_h node, const char *name)
 {
+  int ret = 0;
+  int64_t last_modified;
+  char *timebuf;
+
   xmlTextWriterPtr writer = (xmlTextWriterPtr) writer_v;
   XML_CHECK (xmlTextWriterStartElement, (writer, BAD_CAST "node"));
   XML_CHECK (xmlTextWriterWriteAttribute, (writer, BAD_CAST "name", BAD_CAST name));
-  return 0;
+
+  last_modified = hivex_node_timestamp (h, node);
+  if (last_modified < 0)
+    goto skip_mtime;
+  timebuf = filetime_to_8601 (last_modified);
+  if (!timebuf)
+    goto skip_mtime;
+  XML_CHECK (xmlTextWriterStartElement, (writer, BAD_CAST "mtime"));
+  XML_CHECK (xmlTextWriterWriteString, (writer, BAD_CAST timebuf));
+  XML_CHECK (xmlTextWriterEndElement, (writer));
+  free (timebuf);
+  timebuf = NULL;
+
+skip_mtime:
+  return ret;
 }
 
 static int
-- 
1.7.6






More information about the Libguestfs mailing list