hivex: page 'offset_next' field is really 'page_size'.
[libguestfs.git] / hivex / hivex.c
index 546ef18..fd65f8c 100644 (file)
@@ -23,6 +23,8 @@
 #include <stdio.h>
 #include <stdlib.h>
 #include <stdint.h>
+#include <stddef.h>
+#include <inttypes.h>
 #include <string.h>
 #include <fcntl.h>
 #include <unistd.h>
@@ -30,6 +32,7 @@
 #include <iconv.h>
 #include <sys/mman.h>
 #include <sys/stat.h>
+#include <assert.h>
 #ifdef HAVE_ENDIAN_H
 #include <endian.h>
 #endif
 
 #include "hivex.h"
 
+static char *windows_utf16_to_utf8 (/* const */ char *input, size_t len);
+
 struct hive_h {
+  char *filename;
   int fd;
   size_t size;
   int msglvl;
@@ -111,37 +117,51 @@ struct hive_h {
    (off) < (h)->size &&                     \
    BITMAP_TST((h)->bitmap,(off)))
 
-  /* Fields from the header, extracted from little-endianness. */
+  /* Fields from the header, extracted from little-endianness hell. */
   size_t rootoffs;              /* Root key offset (always an nk-block). */
-
-  /* Stats. */
-  size_t pages;                 /* Number of hbin pages read. */
-  size_t blocks;                /* Total number of blocks found. */
-  size_t used_blocks;           /* Total number of used blocks found. */
-  size_t used_size;             /* Total size (bytes) of used blocks. */
+  size_t endpages;              /* Offset of end of pages. */
 };
 
 /* NB. All fields are little endian. */
 struct ntreg_header {
   char magic[4];                /* "regf" */
-  uint32_t unknown1;
-  uint32_t unknown2;
+  uint32_t sequence1;
+  uint32_t sequence2;
   char last_modified[8];
-  uint32_t unknown3;            /* 1 */
-  uint32_t unknown4;            /* 3 */
+  uint32_t major_ver;           /* 1 */
+  uint32_t minor_ver;           /* 3 */
   uint32_t unknown5;            /* 0 */
   uint32_t unknown6;            /* 1 */
   uint32_t offset;              /* offset of root key record - 4KB */
-  uint32_t blocks;              /* size in bytes of data (filesize - 4KB) */
+  uint32_t blocks;              /* pointer AFTER last hbin in file - 4KB */
   uint32_t unknown7;            /* 1 */
-  char name[0x1fc-0x2c];
-  uint32_t csum;                /* checksum: sum of 32 bit words 0-0x1fb. */
+  /* 0x30 */
+  char name[64];                /* original file name of hive */
+  char unknown_guid1[16];
+  char unknown_guid2[16];
+  /* 0x90 */
+  uint32_t unknown8;
+  char unknown_guid3[16];
+  uint32_t unknown9;
+  /* 0xa8 */
+  char unknown10[340];
+  /* 0x1fc */
+  uint32_t csum;                /* checksum: xor of dwords 0-0x1fb. */
+  /* 0x200 */
+  char unknown11[3528];
+  /* 0xfc8 */
+  char unknown_guid4[16];
+  char unknown_guid5[16];
+  char unknown_guid6[16];
+  uint32_t unknown12;
+  uint32_t unknown13;
+  /* 0x1000 */
 } __attribute__((__packed__));
 
 struct ntreg_hbin_page {
   char magic[4];                /* "hbin" */
   uint32_t offset_first;        /* offset from 1st block */
-  uint32_t offset_next;         /* offset of next (relative to this) */
+  uint32_t page_size;           /* size of this page (multiple of 4KB) */
   char unknown[20];
   /* Linked list of blocks follows here. */
 } __attribute__((__packed__));
@@ -228,7 +248,9 @@ struct ntreg_vk_record {
   uint32_t data_len;
   uint32_t data_offset;         /* pointer to the data (or data if inline) */
   hive_type data_type;          /* type of the data */
-  uint16_t unknown1;            /* possibly always 1 */
+  uint16_t flags;               /* bit 0 set => key name ASCII,
+                                   bit 0 clr => key name UTF-16.
+                                   Only seen ASCII here in the wild. */
   uint16_t unknown2;
   char name[1];                 /* key name follows here */
 } __attribute__((__packed__));
@@ -238,6 +260,9 @@ hivex_open (const char *filename, int flags)
 {
   hive_h *h = NULL;
 
+  assert (sizeof (struct ntreg_header) == 0x1000);
+  assert (offsetof (struct ntreg_header, csum) == 0x1fc);
+
   h = calloc (1, sizeof *h);
   if (h == NULL)
     goto error;
@@ -251,6 +276,10 @@ hivex_open (const char *filename, int flags)
   if (h->msglvl >= 2)
     fprintf (stderr, "hivex_open: created handle %p\n", h);
 
+  h->filename = strdup (filename);
+  if (h->filename == NULL)
+    goto error;
+
   h->fd = open (filename, O_RDONLY);
   if (h->fd == -1)
     goto error;
@@ -279,6 +308,16 @@ hivex_open (const char *filename, int flags)
     goto error;
   }
 
+  /* Check major version. */
+  uint32_t major_ver = le32toh (h->hdr->major_ver);
+  if (major_ver != 1) {
+    fprintf (stderr,
+             "hivex: %s: hive file major version %" PRIu32 " (expected 1)\n",
+             filename, major_ver);
+    errno = ENOTSUP;
+    goto error;
+  }
+
   h->bitmap = calloc (1 + h->size / 32, 1);
   if (h->bitmap == NULL)
     goto error;
@@ -292,24 +331,53 @@ hivex_open (const char *filename, int flags)
     daddr++;
   }
 
-#if 0                           /* Doesn't work. */
   if (sum != le32toh (h->hdr->csum)) {
     fprintf (stderr, "hivex: %s: bad checksum in hive header\n", filename);
     errno = EINVAL;
     goto error;
   }
-#endif
+
+  if (h->msglvl >= 2) {
+    char *name = windows_utf16_to_utf8 (h->hdr->name, 64);
+
+    fprintf (stderr,
+             "hivex_open: header fields:\n"
+             "  file version             %" PRIu32 ".%" PRIu32 "\n"
+             "  sequence nos             %" PRIu32 " %" PRIu32 "\n"
+             "    (sequences nos should match if hive was synched at shutdown)\n"
+             "  original file name       %s\n"
+             "    (only 32 chars are stored, name is probably truncated)\n"
+             "  root offset              0x%x + 0x1000\n"
+             "  end of last page         0x%x + 0x1000 (total file size 0x%zx)\n"
+             "  checksum                 0x%x (calculated 0x%x)\n",
+             major_ver, le32toh (h->hdr->minor_ver),
+             le32toh (h->hdr->sequence1), le32toh (h->hdr->sequence2),
+             name ? name : "(conversion failed)",
+             le32toh (h->hdr->offset),
+             le32toh (h->hdr->blocks), h->size,
+             le32toh (h->hdr->csum), sum);
+    free (name);
+  }
 
   h->rootoffs = le32toh (h->hdr->offset) + 0x1000;
+  h->endpages = le32toh (h->hdr->blocks) + 0x1000;
 
   if (h->msglvl >= 2)
-    fprintf (stderr, "hivex_open: root offset = %zu\n", h->rootoffs);
+    fprintf (stderr, "hivex_open: root offset = 0x%zx\n", h->rootoffs);
 
   /* We'll set this flag when we see a block with the root offset (ie.
    * the root block).
    */
   int seen_root_block = 0, bad_root_block = 0;
 
+  /* Collect some stats. */
+  size_t pages = 0;           /* Number of hbin pages read. */
+  size_t smallest_page = SIZE_MAX, largest_page = 0;
+  size_t blocks = 0;          /* Total number of blocks found. */
+  size_t smallest_block = SIZE_MAX, largest_block = 0, blocks_bytes = 0;
+  size_t used_blocks = 0;     /* Total number of used blocks found. */
+  size_t used_size = 0;       /* Total size (bytes) of used blocks. */
+
   /* Read the pages and blocks.  The aim here is to be robust against
    * corrupt or malicious registries.  So we make sure the loops
    * always make forward progress.  We add the address of each block
@@ -318,28 +386,32 @@ hivex_open (const char *filename, int flags)
    */
   size_t off;
   struct ntreg_hbin_page *page;
-  for (off = 0x1000; off < h->size; off += le32toh (page->offset_next)) {
-    h->pages++;
+  for (off = 0x1000; off < h->size; off += le32toh (page->page_size)) {
+    if (off >= h->endpages)
+      break;
 
     page = (struct ntreg_hbin_page *) (h->addr + off);
     if (page->magic[0] != 'h' ||
         page->magic[1] != 'b' ||
         page->magic[2] != 'i' ||
         page->magic[3] != 'n') {
-      /* NB: This error is seemingly common in uncorrupt registry files. */
-      if (h->msglvl >= 2)
-        fprintf (stderr, "hivex: %s: ignoring trailing garbage at end of file (at %zu, after %zu pages)\n",
-                 filename, off, h->pages);
-      break;
+      fprintf (stderr, "hivex: %s: trailing garbage at end of file (at 0x%zx, after %zu pages)\n",
+               filename, off, pages);
+      errno = ENOTSUP;
+      goto error;
     }
 
+    size_t page_size = le32toh (page->page_size);
     if (h->msglvl >= 2)
-      fprintf (stderr, "hivex_open: page at %zu\n", off);
-
-    if (le32toh (page->offset_next) <= sizeof (struct ntreg_hbin_page) ||
-        (le32toh (page->offset_next) & 3) != 0) {
-      fprintf (stderr, "hivex: %s: pagesize %d at %zu, bad registry\n",
-               filename, le32toh (page->offset_next), off);
+      fprintf (stderr, "hivex_open: page at 0x%zx, size %zu\n", off, page_size);
+    pages++;
+    if (page_size < smallest_page) smallest_page = page_size;
+    if (page_size > largest_page) largest_page = page_size;
+
+    if (page_size <= sizeof (struct ntreg_hbin_page) ||
+        (page_size & 0x0fff) != 0) {
+      fprintf (stderr, "hivex: %s: page size %zu at 0x%zx, bad registry\n",
+               filename, page_size, off);
       errno = ENOTSUP;
       goto error;
     }
@@ -347,11 +419,11 @@ hivex_open (const char *filename, int flags)
     /* Read the blocks in this page. */
     size_t blkoff;
     struct ntreg_hbin_block *block;
-    int32_t seg_len;
+    size_t seg_len;
     for (blkoff = off + 0x20;
-         blkoff < off + le32toh (page->offset_next);
+         blkoff < off + page_size;
          blkoff += seg_len) {
-      h->blocks++;
+      blocks++;
 
       int is_root = blkoff == h->rootoffs;
       if (is_root)
@@ -368,16 +440,20 @@ hivex_open (const char *filename, int flags)
       }
 
       if (h->msglvl >= 2)
-        fprintf (stderr, "hivex_open: %s block id %d,%d at %zu%s\n",
+        fprintf (stderr, "hivex_open: %s block id %d,%d at 0x%zx size %zu%s\n",
                  used ? "used" : "free", block->id[0], block->id[1], blkoff,
-                 is_root ? " (root)" : "");
+                 seg_len, is_root ? " (root)" : "");
+
+      blocks_bytes += seg_len;
+      if (seg_len < smallest_block) smallest_block = seg_len;
+      if (seg_len > largest_block) largest_block = seg_len;
 
       if (is_root && !used)
         bad_root_block = 1;
 
       if (used) {
-        h->used_blocks++;
-        h->used_size += seg_len;
+        used_blocks++;
+        used_size += seg_len;
 
         /* Root block must be an nk-block. */
         if (is_root && (block->id[0] != 'n' || block->id[1] != 'k'))
@@ -404,11 +480,13 @@ hivex_open (const char *filename, int flags)
   if (h->msglvl >= 1)
     fprintf (stderr,
              "hivex_open: successfully read Windows Registry hive file:\n"
-             "  pages:                  %zu\n"
-             "  blocks:                 %zu\n"
-             "  blocks used:            %zu\n"
-             "  bytes used:             %zu\n",
-             h->pages, h->blocks, h->used_blocks, h->used_size);
+             "  pages:          %zu [sml: %zu, lge: %zu]\n"
+             "  blocks:         %zu [sml: %zu, avg: %zu, lge: %zu]\n"
+             "  blocks used:    %zu\n"
+             "  bytes used:     %zu\n",
+             pages, smallest_page, largest_page,
+             blocks, smallest_block, blocks_bytes / blocks, largest_block,
+             used_blocks, used_size);
 
   return h;
 
@@ -420,6 +498,7 @@ hivex_open (const char *filename, int flags)
       munmap (h->addr, h->size);
     if (h->fd >= 0)
       close (h->fd);
+    free (h->filename);
     free (h);
   }
   errno = err;
@@ -434,6 +513,7 @@ hivex_close (hive_h *h)
   free (h->bitmap);
   munmap (h->addr, h->size);
   r = close (h->fd);
+  free (h->filename);
   free (h);
 
   return r;
@@ -619,7 +699,7 @@ hivex_node_children (hive_h *h, hive_node_h node)
       subkey += 0x1000;
       if (!IS_VALID_BLOCK (h, subkey)) {
         if (h->msglvl >= 2)
-          fprintf (stderr, "hivex_node_children: returning EFAULT because subkey is not a valid block (%zu)\n",
+          fprintf (stderr, "hivex_node_children: returning EFAULT because subkey is not a valid block (0x%zx)\n",
                    subkey);
         errno = EFAULT;
         free (ret);
@@ -643,7 +723,7 @@ hivex_node_children (hive_h *h, hive_node_h node)
       offset += 0x1000;
       if (!IS_VALID_BLOCK (h, offset)) {
         if (h->msglvl >= 2)
-          fprintf (stderr, "hivex_node_children: returning EFAULT because ri-offset is not a valid block (%zu)\n",
+          fprintf (stderr, "hivex_node_children: returning EFAULT because ri-offset is not a valid block (0x%zx)\n",
                    offset);
         errno = EFAULT;
         return NULL;
@@ -681,7 +761,7 @@ hivex_node_children (hive_h *h, hive_node_h node)
       offset += 0x1000;
       if (!IS_VALID_BLOCK (h, offset)) {
         if (h->msglvl >= 2)
-          fprintf (stderr, "hivex_node_children: returning EFAULT because ri-offset is not a valid block (%zu)\n",
+          fprintf (stderr, "hivex_node_children: returning EFAULT because ri-offset is not a valid block (0x%zx)\n",
                    offset);
         errno = EFAULT;
         return NULL;
@@ -700,7 +780,7 @@ hivex_node_children (hive_h *h, hive_node_h node)
         subkey += 0x1000;
         if (!IS_VALID_BLOCK (h, subkey)) {
           if (h->msglvl >= 2)
-            fprintf (stderr, "hivex_node_children: returning EFAULT because indirect subkey is not a valid block (%zu)\n",
+            fprintf (stderr, "hivex_node_children: returning EFAULT because indirect subkey is not a valid block (0x%zx)\n",
                      subkey);
           errno = EFAULT;
           free (ret);
@@ -763,7 +843,7 @@ hivex_node_parent (hive_h *h, hive_node_h node)
   ret += 0x1000;
   if (!IS_VALID_BLOCK (h, ret)) {
     if (h->msglvl >= 2)
-      fprintf (stderr, "hivex_node_parent: returning EFAULT because parent is not a valid block (%zu)\n",
+      fprintf (stderr, "hivex_node_parent: returning EFAULT because parent is not a valid block (0x%zx)\n",
               ret);
     errno = EFAULT;
     return 0;
@@ -807,7 +887,7 @@ hivex_node_values (hive_h *h, hive_node_h node)
   vlist_offset += 0x1000;
   if (!IS_VALID_BLOCK (h, vlist_offset)) {
     if (h->msglvl >= 2)
-      fprintf (stderr, "hivex_node_values: returning EFAULT because value list is not a valid block (%zu)\n",
+      fprintf (stderr, "hivex_node_values: returning EFAULT because value list is not a valid block (0x%zx)\n",
                vlist_offset);
     errno = EFAULT;
     return NULL;
@@ -836,7 +916,7 @@ hivex_node_values (hive_h *h, hive_node_h node)
     value += 0x1000;
     if (!IS_VALID_BLOCK (h, value)) {
       if (h->msglvl >= 2)
-        fprintf (stderr, "hivex_node_values: returning EFAULT because value is not a valid block (%zu)\n",
+        fprintf (stderr, "hivex_node_values: returning EFAULT because value is not a valid block (0x%zx)\n",
                  value);
       errno = EFAULT;
       free (ret);
@@ -963,7 +1043,7 @@ hivex_value_value (hive_h *h, hive_value_h value,
   len &= 0x7fffffff;
 
   if (h->msglvl >= 2)
-    fprintf (stderr, "hivex_value_value: value=%zu, t=%d, len=%zu\n",
+    fprintf (stderr, "hivex_value_value: value=0x%zx, t=%d, len=%zu\n",
              value, t, len);
 
   if (t_rtn)
@@ -991,7 +1071,7 @@ hivex_value_value (hive_h *h, hive_value_h value,
   data_offset += 0x1000;
   if (!IS_VALID_BLOCK (h, data_offset)) {
     if (h->msglvl >= 2)
-      fprintf (stderr, "hivex_value_value: returning EFAULT because data offset is not a valid block (%zu)\n",
+      fprintf (stderr, "hivex_value_value: returning EFAULT because data offset is not a valid block (0x%zx)\n",
                data_offset);
     errno = EFAULT;
     free (ret);
@@ -1000,10 +1080,10 @@ hivex_value_value (hive_h *h, hive_value_h value,
 
   /* Check that the declared size isn't larger than the block its in. */
   size_t blen = block_len (h, data_offset, NULL);
-  if (blen < len) {
+  if (len > blen) {
     if (h->msglvl >= 2)
-      fprintf (stderr, "hivex_value_value: returning EFAULT because data is longer than its block (%zu, %zu)\n",
-               blen, len);
+      fprintf (stderr, "hivex_value_value: returning EFAULT because data is longer than its block (data 0x%zx, data len %zu, block len %zu)\n",
+               data_offset, len, blen);
     errno = EFAULT;
     free (ret);
     return NULL;
@@ -1275,7 +1355,7 @@ hivex__visit_node (hive_h *h, hive_node_h node,
 
   if (!BITMAP_TST (unvisited, node)) {
     if (h->msglvl >= 2)
-      fprintf (stderr, "hivex__visit_node: contains cycle: visited node %zu already\n",
+      fprintf (stderr, "hivex__visit_node: contains cycle: visited node 0x%zx already\n",
                node);
 
     errno = ELOOP;
@@ -1430,7 +1510,7 @@ hivex__visit_node (hive_h *h, hive_node_h node,
 
   for (i = 0; children[i] != 0; ++i) {
     if (h->msglvl >= 2)
-      fprintf (stderr, "hivex__visit_node: %s: visiting subkey %d (%zu)\n",
+      fprintf (stderr, "hivex__visit_node: %s: visiting subkey %d (0x%zx)\n",
                name, i, children[i]);
 
     if (hivex__visit_node (h, children[i], vtor, unvisited, opaque, flags) == -1)