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@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-secon... + * 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