Alex Nelson
2011-Aug-10 19:07 UTC
[Libguestfs] [Hivex][PATCH v2] 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 the node_mtime function to the visitor API. Signed-off-by: Alex Nelson <ajnelson at cs.ucsc.edu> --- generator/generator.ml | 3 ++ lib/hivex.c | 95 ++++++++++++++++++++++++++++++++++++++++++++++-- xml/hivexml.c | 18 +++++++++- 3 files changed, 112 insertions(+), 4 deletions(-) diff --git a/generator/generator.ml b/generator/generator.ml index 31478cd..36615f7 100755 --- a/generator/generator.ml +++ b/generator/generator.ml @@ -772,6 +772,7 @@ struct hivex_visitor { int (*value_none) (hive_h *, void *opaque, hive_node_h, hive_value_h, hive_type t, size_t len, const char *key, const char *value); int (*value_other) (hive_h *, void *opaque, hive_node_h, hive_value_h, hive_type t, size_t len, const char *key, const char *value); int (*value_any) (hive_h *, void *opaque, hive_node_h, hive_value_h, hive_type t, size_t len, const char *key, const char *value); + int (*node_mtime) (hive_h *h, void *writer_v, hive_node_h node, const char *last_modified); }; #define HIVEX_VISIT_SKIP_BAD 1 @@ -1134,6 +1135,8 @@ all, set the function pointer to NULL. */ int (*value_any) (hive_h *, void *opaque, hive_node_h, hive_value_h, hive_type t, size_t len, const char *key, const char *value); + int (*node_mtime) (hive_h *h, void *writer_v, hive_node_h node, + const char *last_modified) }; =over 4 diff --git a/lib/hivex.c b/lib/hivex.c index fedbb6c..4f3dcb1 100644 --- a/lib/hivex.c +++ b/lib/hivex.c @@ -33,6 +33,7 @@ #include <sys/mman.h> #include <sys/stat.h> #include <assert.h> +#include <time.h> #include "c-ctype.h" #include "full-read.h" @@ -93,6 +94,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. */ + char *last_modified; /* mtime of base block. */ /* For writing. */ size_t endblocks; /* Offset to next block allocation (0 @@ -104,7 +106,7 @@ struct ntreg_header { char magic[4]; /* "regf" */ uint32_t sequence1; uint32_t sequence2; - char last_modified[8]; + uint64_t last_modified; uint32_t major_ver; /* 1 */ uint32_t minor_ver; /* 3 */ uint32_t unknown5; /* 0 */ @@ -173,7 +175,7 @@ struct ntreg_nk_record { int32_t seg_len; /* length (always -ve because used) */ char id[2]; /* "nk" */ uint16_t flags; - char timestamp[8]; + uint64_t timestamp; uint32_t unknown1; uint32_t parent; /* offset of owner/parent */ uint32_t nr_subkeys; /* number of subkeys */ @@ -265,7 +267,34 @@ header_checksum (const hive_h *h) return sum; } +#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. + */ +int +filetime_to_8601 (char *buf, int bufsize, uint64_t windows_ticks) +{ + if (buf == NULL) { + fprintf (stderr, "filetime_to_8601: Received null output buffer, unable to proceed.\n"); + return -1; + } + 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); + return -1; + } + strftime(buf, bufsize, "%FT%TZ", &time_tm); + return 0; +} + #define HIVEX_OPEN_MSGLVL_MASK (HIVEX_OPEN_VERBOSE|HIVEX_OPEN_DEBUG) +#define TIMESTAMP_BUF_LEN 32 hive_h * hivex_open (const char *filename, int flags) @@ -359,6 +388,15 @@ hivex_open (const char *filename, int flags) goto error; } + /* Last-modified time. */ + h->last_modified = (char *) calloc(1 + TIMESTAMP_BUF_LEN, sizeof(char)); + int ft_rc = filetime_to_8601(h->last_modified, TIMESTAMP_BUF_LEN, le64toh ((uint64_t) h->hdr->last_modified)); + if (ft_rc) { + fprintf (stderr, "hivex: failed to parse time value\n"); + free(h->last_modified); + h->last_modified = NULL; + } + if (h->msglvl >= 2) { char *name = windows_utf16_to_utf8 (h->hdr->name, 64); @@ -367,6 +405,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 %s\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 +414,8 @@ 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, + le64toh (h->hdr->last_modified), name ? name : "(conversion failed)", le32toh (h->hdr->offset), le32toh (h->hdr->blocks), h->size, @@ -541,6 +583,10 @@ hivex_close (hive_h *h) if (h->msglvl >= 1) fprintf (stderr, "hivex_close\n"); + if (h->last_modified) { + free (h->last_modified); + h->last_modified = NULL; + } free (h->bitmap); if (!h->writable) munmap (h->addr, h->size); @@ -608,6 +654,33 @@ hivex_node_name (hive_h *h, hive_node_h node) return ret; } +/* Caller responsible for freeing returned char* if non-null. */ +char * +hivex_node_mtime (hive_h *h, hive_node_h node) +{ + int ft_rc; + if (!IS_VALID_BLOCK (h, node) || !BLOCK_ID_EQ (h, node, "nk")) { + errno = EINVAL; + return NULL; + } + + struct ntreg_nk_record *nk = (struct ntreg_nk_record *) (h->addr + node); + + char *ret = calloc (32 + 1, sizeof(char)); + if (ret == NULL) + return ret; + ft_rc = filetime_to_8601 (ret, 32, le64toh (nk->timestamp)); + if (h->msglvl >= 2) { + fprintf(stderr, "hivex_node_mtime: nk->timestamp: %" PRIu64 "\n", nk->timestamp); + fprintf(stderr, "hivex_node_mtime: ret: %s\n", ret); + } + if (ft_rc) { + free(ret); + ret = NULL; + } + return 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. @@ -1560,6 +1633,7 @@ hivex__visit_node (hive_h *h, hive_node_h node, { int skip_bad = flags & HIVEX_VISIT_SKIP_BAD; char *name = NULL; + char *last_modified = NULL; hive_value_h *values = NULL; hive_node_h *children = NULL; char *key = NULL; @@ -1584,10 +1658,24 @@ hivex__visit_node (hive_h *h, hive_node_h node, BITMAP_CLR (unvisited, node); name = hivex_node_name (h, node); + last_modified = hivex_node_mtime (h, node); + if (h->msglvl >= 2) + fprintf(stderr, "hivex__visit_node: last_modified: %s\n", last_modified ? last_modified : "NULL" ); if (!name) return skip_bad ? 0 : -1; + + /* Report extra for hive's root node. */ + if (node == hivex_root (h)) { + //Produce mtime for hive, not present node + if (vtor->node_mtime && vtor->node_mtime (h, opaque, node, h->last_modified) == -1) + goto error; + } + if (vtor->node_start && vtor->node_start (h, opaque, node, name) == -1) goto error; + if (vtor->node_mtime && vtor->node_mtime (h, opaque, node, last_modified) == -1) + goto error; + values = hivex_node_values (h, node); if (!values) { ret = skip_bad ? 0 : -1; @@ -1764,6 +1852,7 @@ hivex__visit_node (hive_h *h, hive_node_h node, error: free (name); + free (last_modified); free (values); free (children); free (key); @@ -2264,7 +2353,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..25e8cd9 100644 --- a/xml/hivexml.c +++ b/xml/hivexml.c @@ -50,6 +50,7 @@ static int value_qword (hive_h *, void *, hive_node_h, hive_value_h, hive_type t static int value_binary (hive_h *, void *, hive_node_h, hive_value_h, hive_type t, size_t len, const char *key, const char *value); static int value_none (hive_h *, void *, hive_node_h, hive_value_h, hive_type t, size_t len, const char *key, const char *value); static int value_other (hive_h *, void *, hive_node_h, hive_value_h, hive_type t, size_t len, const char *key, const char *value); +static int node_mtime (hive_h *h, void *writer_v, hive_node_h node, const char *last_modified); static struct hivex_visitor visitor = { .node_start = node_start, @@ -61,7 +62,8 @@ static struct hivex_visitor visitor = { .value_qword = value_qword, .value_binary = value_binary, .value_none = value_none, - .value_other = value_other + .value_other = value_other, + .node_mtime = node_mtime }; #define XML_CHECK(proc, args) \ @@ -142,6 +144,20 @@ main (int argc, char *argv[]) } static int +node_mtime (hive_h *h, void *writer_v, hive_node_h node, const char *last_modified) +{ + if (!last_modified) { + fprintf(stderr, "node_mtime: last_modified came across NULL.\n"); + return -1; + } + xmlTextWriterPtr writer = (xmlTextWriterPtr) writer_v; + XML_CHECK (xmlTextWriterStartElement, (writer, BAD_CAST "mtime")); + XML_CHECK (xmlTextWriterWriteString, (writer, BAD_CAST last_modified)); + XML_CHECK (xmlTextWriterEndElement, (writer)); + return 0; +} + +static int node_start (hive_h *h, void *writer_v, hive_node_h node, const char *name) { xmlTextWriterPtr writer = (xmlTextWriterPtr) writer_v; -- 1.7.6
Richard W.M. Jones
2011-Aug-10 19:55 UTC
[Libguestfs] [Hivex][PATCH v2] Report last-modified time of hive root and nodes
On Wed, Aug 10, 2011 at 12:07:23PM -0700, Alex? Nelson wrote:> 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 the node_mtime function to the visitor API.Nearly there. See my comments below.> @@ -93,6 +94,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. */ > + char *last_modified; /* mtime of base block. */This field seems to be unused except in debugging messages? Unless you're going to use it, I'd just omit this field and all the code that generates and prints last_modified.> +#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. > + */ > +int > +filetime_to_8601 (char *buf, int bufsize, uint64_t windows_ticks)This function is an oddity. You're taking a useful thing (64 bit Windows filetime) and converting it to a string. So callers are going to need to parse this string if they want to do anything useful with it. I think it's best just to return the 64 bit Windows filetime (but with the correct endianness) and let callers deal with it. Passing back strings from a C API just seems wrong.> +/* Caller responsible for freeing returned char* if non-null. */ > +char * > +hivex_node_mtime (hive_h *h, hive_node_h node)Add this function to the API (in generator/generator.ml, 'functions'). This will ensure that Perl, OCaml etc bindings + documentation are generated automatically for the function. Don't make it return a string. Have it return the original 64 bit Windows filetime instead (ie. RInt64) with the correct endianness, and let callers deal with it. The rest seems fine to me, except that you'll need to push the filetime_to_8601 function into hivexml.c since it will now be getting a 64 bit filetime. 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
Apparently Analagous Threads
- [PATCH] Report last-modified time of hive root and nodes
- [Hivex] [PATCH v3] Report last-modified time of hive root and nodes
- [PATCH v6] hivexml: Add byte run reporting functions
- [hivex] [PATCH 8/8] hivexml: Add byte run reporting functions
- [PATCH] hivex: Add byte runs for nodes and values