X-Git-Url: http://git.annexia.org/?a=blobdiff_plain;f=lib%2Fhivex.c;h=df313bf17b68524769a02e4d9f57b489bf26abc0;hb=9d276628f3a76f02e487038440cbc3c153d7c273;hp=fedbb6cee041bcc92537e87e3fd798e36c429d7c;hpb=00da6d769e4e736ebc6dca2e634fae15c0a0c3d2;p=hivex.git diff --git a/lib/hivex.c b/lib/hivex.c index fedbb6c..df313bf 100644 --- a/lib/hivex.c +++ b/lib/hivex.c @@ -30,25 +30,22 @@ #include #include #include -#include #include #include +#ifdef HAVE_MMAP +#include +#else +/* On systems without mmap (and munmap), use a replacement function. */ +#include "mmap.h" +#endif + #include "c-ctype.h" #include "full-read.h" #include "full-write.h" -#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) - #include "hivex.h" +#include "hivex-internal.h" #include "byte_conversions.h" /* These limits are in place to stop really stupid stuff and/or exploits. */ @@ -60,51 +57,12 @@ static char *windows_utf16_to_utf8 (/* const */ char *input, size_t len); static size_t utf16_string_len_in_bytes_max (const char *str, size_t len); -struct hive_h { - char *filename; - int fd; - size_t size; - int msglvl; - int writable; - - /* Registry file, memory mapped if read-only, or malloc'd if writing. */ - union { - char *addr; - struct ntreg_header *hdr; - }; - - /* Use a bitmap to store which file offsets are valid (point to a - * used block). We only need to store 1 bit per 32 bits of the file - * (because blocks are 4-byte aligned). We found that the average - * block size in a registry file is ~50 bytes. So roughly 1 in 12 - * bits in the bitmap will be set, making it likely a more efficient - * structure than a hash table. - */ - char *bitmap; -#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) \ - (((off) & 3) == 0 && \ - (off) >= 0x1000 && \ - (off) < (h)->size && \ - BITMAP_TST((h)->bitmap,(off))) - - /* 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. */ - - /* For writing. */ - size_t endblocks; /* Offset to next block allocation (0 - if not allocated anything yet). */ -}; - /* NB. All fields are little endian. */ struct ntreg_header { 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 */ @@ -173,7 +131,7 @@ struct ntreg_nk_record { 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 */ @@ -293,9 +251,16 @@ hivex_open (const char *filename, int flags) if (h->filename == NULL) goto error; +#ifdef O_CLOEXEC h->fd = open (filename, O_RDONLY | O_CLOEXEC); +#else + h->fd = open (filename, O_RDONLY); +#endif if (h->fd == -1) goto error; +#ifndef O_CLOEXEC + fcntl (h->fd, F_SETFD, FD_CLOEXEC); +#endif struct stat statbuf; if (fstat (h->fd, &statbuf) == -1) @@ -359,6 +324,9 @@ hivex_open (const char *filename, int flags) 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); @@ -367,6 +335,8 @@ hivex_open (const char *filename, int flags) " 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" @@ -374,6 +344,7 @@ hivex_open (const char *filename, int flags) " 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, @@ -571,6 +542,30 @@ hivex_root (hive_h *h) return ret; } +size_t +hivex_node_struct_length (hive_h *h, hive_node_h node) +{ + if (!IS_VALID_BLOCK (h, node) || !BLOCK_ID_EQ (h, node, "nk")) { + errno = EINVAL; + return 0; + } + + struct ntreg_nk_record *nk = (struct ntreg_nk_record *) (h->addr + node); + size_t name_len = le16toh (nk->name_len); + /* -1 to avoid double-counting the first name character */ + size_t ret = name_len + sizeof (struct ntreg_nk_record) - 1; + int used; + size_t seg_len = block_len (h, node, &used); + if (ret > seg_len) { + if (h->msglvl >= 2) + fprintf (stderr, "hivex_node_struct_length: returning EFAULT because" + " node name is too long (%zu, %zu)\n", name_len, seg_len); + errno = EFAULT; + return 0; + } + return ret; +} + char * hivex_node_name (hive_h *h, hive_node_h node) { @@ -608,6 +603,43 @@ hivex_node_name (hive_h *h, hive_node_h node) 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 %zu: %" 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. @@ -1138,8 +1170,22 @@ hivex_node_get_value (hive_h *h, hive_node_h node, const char *key) return ret; } -char * -hivex_value_key (hive_h *h, hive_value_h value) +size_t +hivex_value_struct_length (hive_h *h, hive_value_h value) +{ + size_t key_len; + + errno = 0; + key_len = hivex_value_key_len (h, value); + if (key_len == 0 && errno != 0) + return 0; + + /* -1 to avoid double-counting the first name character */ + return key_len + sizeof (struct ntreg_vk_record) - 1; +} + +size_t +hivex_value_key_len (hive_h *h, hive_value_h value) { if (!IS_VALID_BLOCK (h, value) || !BLOCK_ID_EQ (h, value, "vk")) { errno = EINVAL; @@ -1148,24 +1194,40 @@ hivex_value_key (hive_h *h, hive_value_h value) struct ntreg_vk_record *vk = (struct ntreg_vk_record *) (h->addr + value); - /* AFAIK the key is always plain ASCII, so no conversion to UTF-8 is - * necessary. However we do need to nul-terminate the string. - */ - /* vk->name_len is unsigned, 16 bit, so this is safe ... However * we have to make sure the length doesn't exceed the block length. */ - size_t len = le16toh (vk->name_len); + size_t ret = le16toh (vk->name_len); size_t seg_len = block_len (h, value, NULL); - if (sizeof (struct ntreg_vk_record) + len - 1 > seg_len) { + if (sizeof (struct ntreg_vk_record) + ret - 1 > seg_len) { if (h->msglvl >= 2) - fprintf (stderr, "hivex_value_key: returning EFAULT" + fprintf (stderr, "hivex_value_key_len: returning EFAULT" " because key length is too long (%zu, %zu)\n", - len, seg_len); + ret, seg_len); errno = EFAULT; - return NULL; + return 0; + } + return ret; +} + +char * +hivex_value_key (hive_h *h, hive_value_h value) +{ + if (!IS_VALID_BLOCK (h, value) || !BLOCK_ID_EQ (h, value, "vk")) { + errno = EINVAL; + return 0; } + struct ntreg_vk_record *vk = (struct ntreg_vk_record *) (h->addr + value); + + /* AFAIK the key is always plain ASCII, so no conversion to UTF-8 is + * necessary. However we do need to nul-terminate the string. + */ + errno = 0; + size_t len = hivex_value_key_len (h, value); + if (len == 0 && errno != 0) + return NULL; + char *ret = malloc (len + 1); if (ret == NULL) return NULL; @@ -1195,6 +1257,66 @@ hivex_value_type (hive_h *h, hive_value_h value, hive_type *t, size_t *len) return 0; } +hive_value_h +hivex_value_data_cell_offset (hive_h *h, hive_value_h value, size_t *len) +{ + if (!IS_VALID_BLOCK (h, value) || !BLOCK_ID_EQ (h, value, "vk")) { + errno = EINVAL; + return 0; + } + + if (h->msglvl >= 2) + fprintf (stderr, "hivex_value_data_cell_offset: value=0x%zx\n", value); + struct ntreg_vk_record *vk = (struct ntreg_vk_record *) (h->addr + value); + + size_t data_len; + int is_inline; + + data_len = le32toh (vk->data_len); + is_inline = !!(data_len & 0x80000000); + data_len &= 0x7fffffff; + + if (h->msglvl >= 2) + fprintf (stderr, "hivex_value_data_cell_offset: is_inline=%d\n", is_inline); + + if (h->msglvl >= 2) + fprintf (stderr, "hivex_value_data_cell_offset: data_len=%zx\n", data_len); + + if (is_inline && data_len > 4) { + errno = ENOTSUP; + return 0; + } + + if (is_inline) { + /* There is no other location for the value data. */ + if (len) + *len = 0; + return 0; + } else { + if (len) + *len = data_len + 4; /* Include 4 header length bytes */ + } + + if (h->msglvl >= 2) + fprintf (stderr, "hivex_value_data_cell_offset: Proceeding with indirect data.\n"); + + size_t data_offset = le32toh (vk->data_offset); + data_offset += 0x1000; /* Add 0x1000 because everything's off by 4KiB */ + if (!IS_VALID_BLOCK (h, data_offset)) { + if (h->msglvl >= 2) + fprintf (stderr, "hivex_value_data_cell_offset: returning EFAULT because data " + "offset is not a valid block (0x%zx)\n", + data_offset); + errno = EFAULT; + return 0; + } + + if (h->msglvl >= 2) + fprintf (stderr, "hivex_value_data_cell_offset: data_offset=%zx\n", data_offset); + + return data_offset; +} + char * hivex_value_value (hive_h *h, hive_value_h value, hive_type *t_rtn, size_t *len_rtn) @@ -2264,7 +2386,7 @@ hivex_node_add_child (hive_h *h, hive_node_h parent, const char *name) 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