X-Git-Url: http://git.annexia.org/?p=libguestfs.git;a=blobdiff_plain;f=hivex%2Fhivex.c;h=a7603007edc5c05f919649d4f39aa33eb98e2f7f;hp=b522ccf8622dd8d7bdef7d0fd734d91a27287227;hb=1e4614ca8f8d2b3d758fef733f1294c611502b94;hpb=43a8e107ce8ad6eac99da82366bdf63b401e3d31 diff --git a/hivex/hivex.c b/hivex/hivex.c index b522ccf..a760300 100644 --- a/hivex/hivex.c +++ b/hivex/hivex.c @@ -23,6 +23,8 @@ #include #include #include +#include +#include #include #include #include @@ -38,6 +40,16 @@ #include #endif +#define STREQ(a,b) (strcmp((a),(b)) == 0) +#define STRCASEEQ(a,b) (strcasecmp((a),(b)) == 0) +//#define STRNEQ(a,b) (strcmp((a),(b)) != 0) +//#define STRCASENEQ(a,b) (strcasecmp((a),(b)) != 0) +#define STREQLEN(a,b,n) (strncmp((a),(b),(n)) == 0) +//#define STRCASEEQLEN(a,b,n) (strncasecmp((a),(b),(n)) == 0) +//#define STRNEQLEN(a,b,n) (strncmp((a),(b),(n)) != 0) +//#define STRCASENEQLEN(a,b,n) (strncasecmp((a),(b),(n)) != 0) +//#define STRPREFIX(a,b) (strncmp((a),(b),strlen((b))) == 0) + #if __BYTE_ORDER == __LITTLE_ENDIAN #ifndef be32toh #define be32toh(x) __bswap_32 (x) @@ -74,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; @@ -96,14 +111,15 @@ struct hive_h { #define BITMAP_SET(bitmap,off) (bitmap[(off)>>5] |= 1 << (((off)>>2)&7)) #define BITMAP_CLR(bitmap,off) (bitmap[(off)>>5] &= ~ (1 << (((off)>>2)&7))) #define BITMAP_TST(bitmap,off) (bitmap[(off)>>5] & (1 << (((off)>>2)&7))) -#define IS_VALID_BLOCK(h,off) \ +#define IS_VALID_BLOCK(h,off) \ (((off) & 3) == 0 && \ (off) >= 0x1000 && \ (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). */ + size_t endpages; /* Offset of end of pages. */ /* Stats. */ size_t pages; /* Number of hbin pages read. */ @@ -115,18 +131,37 @@ struct hive_h { /* 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 { @@ -144,7 +179,7 @@ struct ntreg_hbin_block { } __attribute__((__packed__)); #define BLOCK_ID_EQ(h,offs,eqid) \ - (strncmp (((struct ntreg_hbin_block *)((h)->addr + (offs)))->id, (eqid), 2) == 0) + (STREQLEN (((struct ntreg_hbin_block *)((h)->addr + (offs)))->id, (eqid), 2)) static size_t block_len (hive_h *h, size_t blkoff, int *used) @@ -219,7 +254,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__)); @@ -229,6 +266,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; @@ -236,11 +276,15 @@ hivex_open (const char *filename, int flags) h->msglvl = flags & HIVEX_OPEN_MSGLVL_MASK; const char *debug = getenv ("HIVEX_DEBUG"); - if (debug && strcmp (debug, "1") == 0) + if (debug && STREQ (debug, "1")) h->msglvl = 2; if (h->msglvl >= 2) - printf ("hivex_open: created handle %p\n", h); + 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) @@ -257,7 +301,7 @@ hivex_open (const char *filename, int flags) goto error; if (h->msglvl >= 2) - printf ("hivex_open: mapped file at %p\n", h->addr); + fprintf (stderr, "hivex_open: mapped file at %p\n", h->addr); /* Check header. */ if (h->hdr->magic[0] != 'r' || @@ -270,28 +314,62 @@ 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; -#if 0 /* Doesn't work. */ /* Header checksum. */ - uint32_t *daddr = h->addr; + uint32_t *daddr = (uint32_t *) h->addr; size_t i; uint32_t sum = 0; for (i = 0; i < 0x1fc / 4; ++i) { - sum += le32toh (*daddr); + sum ^= le32toh (*daddr); daddr++; } + 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) - printf ("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). @@ -307,23 +385,23 @@ 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++; + 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') { - /* This error is seemingly common in uncorrupt registry files. */ - /* - fprintf (stderr, "hivex: %s: ignoring trailing garbage at end of file (at %zu, after %zu pages)\n", + fprintf (stderr, "hivex: %s: trailing garbage at end of file (at 0x%zx, after %zu pages)\n", filename, off, h->pages); - */ - break; + errno = ENOTSUP; + goto error; } if (h->msglvl >= 2) - printf ("hivex_open: page at %zu\n", off); + fprintf (stderr, "hivex_open: page at 0x%zx\n", off); + h->pages++; if (le32toh (page->offset_next) <= sizeof (struct ntreg_hbin_page) || (le32toh (page->offset_next) & 3) != 0) { @@ -357,9 +435,9 @@ hivex_open (const char *filename, int flags) } if (h->msglvl >= 2) - printf ("hivex_open: %s block id %d,%d at %zu%s\n", - used ? "used" : "free", block->id[0], block->id[1], blkoff, - is_root ? " (root)" : ""); + fprintf (stderr, "hivex_open: %s block id %d,%d at 0x%zx%s\n", + used ? "used" : "free", block->id[0], block->id[1], blkoff, + is_root ? " (root)" : ""); if (is_root && !used) bad_root_block = 1; @@ -391,12 +469,13 @@ hivex_open (const char *filename, int flags) } if (h->msglvl >= 1) - printf ("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); + 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); return h; @@ -408,6 +487,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; @@ -422,6 +502,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; @@ -460,7 +541,7 @@ hivex_node_name (hive_h *h, hive_node_h node) size_t seg_len = block_len (h, node, NULL); if (sizeof (struct ntreg_nk_record) + len - 1 > seg_len) { if (h->msglvl >= 2) - printf ("hivex_node_name: returning EFAULT because node name is too long (%zu, %zu)\n", + fprintf (stderr, "hivex_node_name: returning EFAULT because node name is too long (%zu, %zu)\n", len, seg_len); errno = EFAULT; return NULL; @@ -556,8 +637,8 @@ hivex_node_children (hive_h *h, hive_node_h node) subkey_lf += 0x1000; if (!IS_VALID_BLOCK (h, subkey_lf)) { if (h->msglvl >= 2) - printf ("hivex_node_children: returning EFAULT because subkey_lf is not a valid block (%zu)\n", - subkey_lf); + fprintf (stderr, "hivex_node_children: returning EFAULT because subkey_lf is not a valid block (%zu)\n", + subkey_lf); errno = EFAULT; return NULL; } @@ -577,8 +658,8 @@ hivex_node_children (hive_h *h, hive_node_h node) size_t nr_subkeys_in_lf = le16toh (lf->nr_keys); if (h->msglvl >= 2) - printf ("hivex_node_children: nr_subkeys_in_nk = %zu, nr_subkeys_in_lf = %zu\n", - nr_subkeys_in_nk, nr_subkeys_in_lf); + fprintf (stderr, "hivex_node_children: nr_subkeys_in_nk = %zu, nr_subkeys_in_lf = %zu\n", + nr_subkeys_in_nk, nr_subkeys_in_lf); if (nr_subkeys_in_nk != nr_subkeys_in_lf) { errno = ENOTSUP; @@ -588,8 +669,8 @@ hivex_node_children (hive_h *h, hive_node_h node) size_t len = block_len (h, subkey_lf, NULL); if (8 + nr_subkeys_in_lf * 8 > len) { if (h->msglvl >= 2) - printf ("hivex_node_children: returning EFAULT because too many subkeys (%zu, %zu)\n", - nr_subkeys_in_lf, len); + fprintf (stderr, "hivex_node_children: returning EFAULT because too many subkeys (%zu, %zu)\n", + nr_subkeys_in_lf, len); errno = EFAULT; return NULL; } @@ -607,8 +688,8 @@ hivex_node_children (hive_h *h, hive_node_h node) subkey += 0x1000; if (!IS_VALID_BLOCK (h, subkey)) { if (h->msglvl >= 2) - printf ("hivex_node_children: returning EFAULT because subkey is not a valid block (%zu)\n", - subkey); + fprintf (stderr, "hivex_node_children: returning EFAULT because subkey is not a valid block (0x%zx)\n", + subkey); errno = EFAULT; free (ret); return NULL; @@ -631,8 +712,8 @@ hivex_node_children (hive_h *h, hive_node_h node) offset += 0x1000; if (!IS_VALID_BLOCK (h, offset)) { if (h->msglvl >= 2) - printf ("hivex_node_children: returning EFAULT because ri-offset is not a valid block (%zu)\n", - offset); + fprintf (stderr, "hivex_node_children: returning EFAULT because ri-offset is not a valid block (0x%zx)\n", + offset); errno = EFAULT; return NULL; } @@ -648,8 +729,8 @@ hivex_node_children (hive_h *h, hive_node_h node) } if (h->msglvl >= 2) - printf ("hivex_node_children: nr_subkeys_in_nk = %zu, counted = %zu\n", - nr_subkeys_in_nk, count); + fprintf (stderr, "hivex_node_children: nr_subkeys_in_nk = %zu, counted = %zu\n", + nr_subkeys_in_nk, count); if (nr_subkeys_in_nk != count) { errno = ENOTSUP; @@ -669,8 +750,8 @@ hivex_node_children (hive_h *h, hive_node_h node) offset += 0x1000; if (!IS_VALID_BLOCK (h, offset)) { if (h->msglvl >= 2) - printf ("hivex_node_children: returning EFAULT because ri-offset is not a valid block (%zu)\n", - offset); + fprintf (stderr, "hivex_node_children: returning EFAULT because ri-offset is not a valid block (0x%zx)\n", + offset); errno = EFAULT; return NULL; } @@ -688,8 +769,8 @@ hivex_node_children (hive_h *h, hive_node_h node) subkey += 0x1000; if (!IS_VALID_BLOCK (h, subkey)) { if (h->msglvl >= 2) - printf ("hivex_node_children: returning EFAULT because indirect subkey is not a valid block (%zu)\n", - subkey); + fprintf (stderr, "hivex_node_children: returning EFAULT because indirect subkey is not a valid block (0x%zx)\n", + subkey); errno = EFAULT; free (ret); return NULL; @@ -724,7 +805,7 @@ hivex_node_get_child (hive_h *h, hive_node_h node, const char *nname) for (i = 0; children[i] != 0; ++i) { name = hivex_node_name (h, children[i]); if (!name) goto error; - if (strcasecmp (name, nname) == 0) { + if (STRCASEEQ (name, nname)) { ret = children[i]; break; } @@ -749,10 +830,9 @@ hivex_node_parent (hive_h *h, hive_node_h node) hive_node_h ret = le32toh (nk->parent); ret += 0x1000; - printf ("parent = %zu\n", ret); if (!IS_VALID_BLOCK (h, ret)) { if (h->msglvl >= 2) - printf ("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; @@ -773,7 +853,7 @@ hivex_node_values (hive_h *h, hive_node_h node) size_t nr_values = le32toh (nk->nr_values); if (h->msglvl >= 2) - printf ("hivex_node_values: nr_values = %zu\n", nr_values); + fprintf (stderr, "hivex_node_values: nr_values = %zu\n", nr_values); /* Deal with the common "no values" case quickly. */ hive_node_h *ret; @@ -796,8 +876,8 @@ hivex_node_values (hive_h *h, hive_node_h node) vlist_offset += 0x1000; if (!IS_VALID_BLOCK (h, vlist_offset)) { if (h->msglvl >= 2) - printf ("hivex_node_values: returning EFAULT because value list is not a valid block (%zu)\n", - vlist_offset); + fprintf (stderr, "hivex_node_values: returning EFAULT because value list is not a valid block (0x%zx)\n", + vlist_offset); errno = EFAULT; return NULL; } @@ -808,8 +888,8 @@ hivex_node_values (hive_h *h, hive_node_h node) size_t len = block_len (h, vlist_offset, NULL); if (4 + nr_values * 4 > len) { if (h->msglvl >= 2) - printf ("hivex_node_values: returning EFAULT because value list is too long (%zu, %zu)\n", - nr_values, len); + fprintf (stderr, "hivex_node_values: returning EFAULT because value list is too long (%zu, %zu)\n", + nr_values, len); errno = EFAULT; return NULL; } @@ -825,8 +905,8 @@ hivex_node_values (hive_h *h, hive_node_h node) value += 0x1000; if (!IS_VALID_BLOCK (h, value)) { if (h->msglvl >= 2) - printf ("hivex_node_values: returning EFAULT because value is not a valid block (%zu)\n", - value); + fprintf (stderr, "hivex_node_values: returning EFAULT because value is not a valid block (0x%zx)\n", + value); errno = EFAULT; free (ret); return NULL; @@ -855,7 +935,7 @@ hivex_node_get_value (hive_h *h, hive_node_h node, const char *key) for (i = 0; values[i] != 0; ++i) { name = hivex_value_key (h, values[i]); if (!name) goto error; - if (strcasecmp (name, key) == 0) { + if (STRCASEEQ (name, key)) { ret = values[i]; break; } @@ -889,8 +969,8 @@ hivex_value_key (hive_h *h, hive_value_h value) size_t seg_len = block_len (h, value, NULL); if (sizeof (struct ntreg_vk_record) + len - 1 > seg_len) { if (h->msglvl >= 2) - printf ("hivex_value_key: returning EFAULT because key length is too long (%zu, %zu)\n", - len, seg_len); + fprintf (stderr, "hivex_value_key: returning EFAULT because key length is too long (%zu, %zu)\n", + len, seg_len); errno = EFAULT; return NULL; } @@ -952,8 +1032,8 @@ hivex_value_value (hive_h *h, hive_value_h value, len &= 0x7fffffff; if (h->msglvl >= 2) - printf ("hivex_value_value: value=%zu, t=%d, len=%zu\n", - value, t, len); + fprintf (stderr, "hivex_value_value: value=0x%zx, t=%d, len=%zu\n", + value, t, len); if (t_rtn) *t_rtn = t; @@ -980,8 +1060,8 @@ hivex_value_value (hive_h *h, hive_value_h value, data_offset += 0x1000; if (!IS_VALID_BLOCK (h, data_offset)) { if (h->msglvl >= 2) - printf ("hivex_value_value: returning EFAULT because data offset is not a valid block (%zu)\n", - data_offset); + fprintf (stderr, "hivex_value_value: returning EFAULT because data offset is not a valid block (0x%zx)\n", + data_offset); errno = EFAULT; free (ret); return NULL; @@ -989,10 +1069,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) - printf ("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; @@ -1031,9 +1111,12 @@ windows_utf16_to_utf8 (/* const */ char *input, size_t len) size_t r = iconv (ic, &inp, &inlen, &outp, &outlen); if (r == (size_t) -1) { if (errno == E2BIG) { + size_t prev = outalloc; /* Try again with a larger output buffer. */ free (out); outalloc *= 2; + if (outalloc < prev) + return NULL; goto again; } else { @@ -1261,8 +1344,8 @@ hivex__visit_node (hive_h *h, hive_node_h node, if (!BITMAP_TST (unvisited, node)) { if (h->msglvl >= 2) - printf ("hivex__visit_node: contains cycle: visited node %zu already\n", - node); + fprintf (stderr, "hivex__visit_node: contains cycle: visited node 0x%zx already\n", + node); errno = ELOOP; return skip_bad ? 0 : -1; @@ -1416,8 +1499,8 @@ hivex__visit_node (hive_h *h, hive_node_h node, for (i = 0; children[i] != 0; ++i) { if (h->msglvl >= 2) - printf ("hivex__visit_node: %s: visiting subkey %d (%zu)\n", - name, i, children[i]); + 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) goto error;