From e0b21193257f9784b28e931525f2a382a74775c6 Mon Sep 17 00:00:00 2001 From: Alex Nelson Date: Fri, 12 Aug 2011 23:03:37 -0700 Subject: Report last-modified time of hive root and nodes 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 A lot of code cleanup by RWMJ. --- generator/generator.ml | 18 +++++++++++++ lib/hivex.c | 49 +++++++++++++++++++++++++++++++++--- xml/hivexml.c | 68 ++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 132 insertions(+), 3 deletions(-) diff --git a/generator/generator.ml b/generator/generator.ml index de911f1..943039c 100755 --- a/generator/generator.ml +++ b/generator/generator.ml @@ -158,6 +158,15 @@ but instead are lost. See L."; "\ Return root node of the hive. All valid hives must contain a root node."; + "last_modified", (RInt64, [AHive]), + "return the modification time from the header of the hive", + "\ +Return the modification time from the header of the hive. + +The returned value is a Windows filetime. +To convert this to a Unix C see: +L"; + "node_name", (RString, [AHive; ANode "node"]), "return the name of the node", "\ @@ -170,6 +179,15 @@ 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. + +The returned value is a Windows filetime. +To convert this to a Unix C see: +L"; + "node_children", (RNodeList, [AHive; ANode "node"]), "return children of node", "\ diff --git a/lib/hivex.c b/lib/hivex.c index fedbb6c..dceea73 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 %" PRIu64 "\n" + " (Windows filetime, x 100 ns since 1601-01-01)\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,42 @@ hivex_node_name (hive_h *h, hive_node_h node) return ret; } +static int64_t +timestamp_check (hive_h *h, hive_node_h node, int64_t timestamp) +{ + if (timestamp < 0) { + if (h->msglvl >= 2) + fprintf (stderr, "hivex: timestamp_check: " + "negative time reported at %z: %" PRIi64 "\n", node, timestamp); + errno = EINVAL; + return -1; + } + + return timestamp; +} + +int64_t +hivex_last_modified (hive_h *h) +{ + return 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 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 +2307,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..2967ac9 100644 --- a/xml/hivexml.c +++ b/xml/hivexml.c @@ -25,6 +25,7 @@ #include #include #include +#include #include @@ -39,6 +40,8 @@ //#define N_(str) str #endif +static char *filetime_to_8601 (int64_t windows_ticks); + /* Callback functions. */ static int node_start (hive_h *, void *, hive_node_h, const char *name); static int node_end (hive_h *, void *, hive_node_h, const char *name); @@ -124,6 +127,17 @@ 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) { + char *timebuf = filetime_to_8601 (hive_mtime); + if (timebuf) { + XML_CHECK (xmlTextWriterStartElement, (writer, BAD_CAST "mtime")); + XML_CHECK (xmlTextWriterWriteString, (writer, BAD_CAST timebuf)); + XML_CHECK (xmlTextWriterEndElement, (writer)); + free (timebuf); + } + } + if (hivex_visit (h, &visitor, sizeof visitor, writer, visit_flags) == -1) { perror (argv[optind]); exit (EXIT_FAILURE); @@ -141,12 +155,66 @@ main (int argc, char *argv[]) exit (EXIT_SUCCESS); } +/* Convert Windows filetime to ISO 8601 format. + * 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. + * + * The caller should free the returned buffer. + */ + +#define WINDOWS_TICK 10000000LL +#define SEC_TO_UNIX_EPOCH 11644473600LL +#define TIMESTAMP_BUF_LEN 32 + +static char * +filetime_to_8601 (int64_t windows_ticks) +{ + char *ret; + time_t t; + struct tm *tm; + + t = windows_ticks / WINDOWS_TICK - SEC_TO_UNIX_EPOCH; + tm = gmtime (&t); + if (tm == NULL) + return NULL; + + ret = malloc (TIMESTAMP_BUF_LEN); + if (ret == NULL) { + perror ("malloc"); + exit (EXIT_FAILURE); + } + + if (strftime (ret, TIMESTAMP_BUF_LEN, "%FT%TZ", tm) == 0) { + perror ("strftime"); + exit (EXIT_FAILURE); + } + + return ret; +} + static int node_start (hive_h *h, void *writer_v, hive_node_h node, const char *name) { + 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)); + + last_modified = hivex_node_timestamp (h, node); + if (last_modified >= 0) { + timebuf = filetime_to_8601 (last_modified); + if (timebuf) { + XML_CHECK (xmlTextWriterStartElement, (writer, BAD_CAST "mtime")); + XML_CHECK (xmlTextWriterWriteString, (writer, BAD_CAST timebuf)); + XML_CHECK (xmlTextWriterEndElement, (writer)); + free (timebuf); + } + } + return 0; } -- cgit