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(a)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-se...
+ * 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