Alex Nelson
2011-Aug-13 06:03 UTC
[Libguestfs] [Hivex] [PATCH v3] 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 <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
Richard W.M. Jones
2011-Aug-13 06:27 UTC
[Libguestfs] [Hivex] [PATCH v3] Report last-modified time of hive root and nodes
Looks good. I'll apply it later on today. Thanks, Rich. -- Richard Jones, Virtualization Group, Red Hat http://people.redhat.com/~rjones virt-top is 'top' for virtual machines. Tiny program with many powerful monitoring features, net stats, disk stats, logging, etc. http://et.redhat.com/~rjones/virt-top
Richard W.M. Jones
2011-Aug-13 08:46 UTC
[Libguestfs] [Hivex] [PATCH v3] Report last-modified time of hive root and nodes
I have modified the code quite a lot before committing it. Please take a look: http://git.annexia.org/?p=hivex.git;a=commitdiff;h=e0b21193257f9784b28e931525f2a382a74775c6 Thanks for your contribution, Rich. -- Richard Jones, Virtualization Group, Red Hat http://people.redhat.com/~rjones libguestfs lets you edit virtual machines. Supports shell scripting, bindings from many languages. http://libguestfs.org
Possibly Parallel Threads
- [Hivex][PATCH v2] Report last-modified time of hive root and nodes
- [hivex] [PATCH 1/2] hivex: Expose hive major and minor version
- [PATCH] Report last-modified time of hive root and nodes
- [PATCH] hivexml: Add root attribute to the root node
- [hivex] [PATCH 2/2] hivex: Expose embedded hive file name