#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
+#include <stddef.h>
+#include <inttypes.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#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;
/* 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__));
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__));
{
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;
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;
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;
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)
+ if (h->msglvl >= 2) {
+ char *name = windows_utf16_to_utf8 (h->hdr->name, 64);
+
fprintf (stderr,
"hivex_open: header fields:\n"
- " root offset - 4KB 0x%x\n"
- " blocks (file size - 4KB) 0x%x (real file size 0x%zx)\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 = 0x%zx\n", h->rootoffs);
*/
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
*/
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 0x%zx, 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 0x%zx\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;
}
/* 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)
}
if (h->msglvl >= 2)
- fprintf (stderr, "hivex_open: %s block id %d,%d at 0x%zx%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'))
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;
munmap (h->addr, h->size);
if (h->fd >= 0)
close (h->fd);
+ free (h->filename);
free (h);
}
errno = err;
free (h->bitmap);
munmap (h->addr, h->size);
r = close (h->fd);
+ free (h->filename);
free (h);
return r;
/* 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;