X-Git-Url: http://git.annexia.org/?p=libguestfs.git;a=blobdiff_plain;f=hivex%2Fhivex.c;h=fd65f8ccb4271740e81dc28bd4dd57004a50d310;hp=b582f300438aa97d4e74d46c878936fbf129e918;hb=f1dc74b8d605c07f4b8d4ca0b8dcf3db8c18edd1;hpb=f004c406e0ac78d7e70d13260f2eff7e0257b4d4 diff --git a/hivex/hivex.c b/hivex/hivex.c index b582f30..fd65f8c 100644 --- a/hivex/hivex.c +++ b/hivex/hivex.c @@ -23,6 +23,8 @@ #include #include #include +#include +#include #include #include #include @@ -30,6 +32,7 @@ #include #include #include +#include #ifdef HAVE_ENDIAN_H #include #endif @@ -83,7 +86,10 @@ #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; @@ -113,35 +119,49 @@ struct hive_h { /* 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,25 +331,36 @@ 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) + 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); @@ -320,6 +370,14 @@ hivex_open (const char *filename, int flags) */ 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 @@ -328,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 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; } @@ -357,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) @@ -378,16 +440,20 @@ hivex_open (const char *filename, int flags) } 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')) @@ -414,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; @@ -430,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; @@ -444,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; @@ -1010,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;