"\
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<time_t> see:
+L<http://stackoverflow.com/questions/6161776/convert-windows-filetime-to-second-in-unix-linux/6161842#6161842>";
+
"node_name", (RString, [AHive; ANode "node"]),
"return the name of the node",
"\
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<time_t> see:
+L<http://stackoverflow.com/questions/6161776/convert-windows-filetime-to-second-in-unix-linux/6161842#6161842>";
+
"node_children", (RNodeList, [AHive; ANode "node"]),
"return children of node",
"\
/* 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
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 */
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 */
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);
" 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"
" 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,
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.
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
#include <inttypes.h>
#include <unistd.h>
#include <errno.h>
+#include <time.h>
#include <libxml/xmlwriter.h>
//#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);
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);
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;
}