char * hivex_value_key (hive_h *h, hive_value_h value) { if (!IS_VALID_BLOCK (h, value) || !block_id_eq (h, value, "vk")) { SET_ERRNO (EINVAL, "invalid block or not an 'vk' block"); return 0; } struct ntreg_vk_record *vk = (struct ntreg_vk_record *) ((char *) h->addr + value); size_t flags = le16toh (vk->flags); size_t len = le16toh (vk->name_len); size_t seg_len = block_len (h, value, NULL); if (sizeof (struct ntreg_vk_record) + len - 1 > seg_len) { SET_ERRNO (EFAULT, "key length is too long (%zu, %zu)", len, seg_len); return NULL; } if (flags & 0x01) { return _hivex_windows_latin1_to_utf8 (vk->name, len); } else { return _hivex_windows_utf16_to_utf8 (vk->name, len); } }
size_t BufferArray::write(size_t pos, const void * src, size_t len) { const char * c_src(static_cast<const char *>(src)); if (pos > mLen || 0 == len) return 0; size_t result(0), offset(0); const int block_limit(mBlocks.size()); int block_start(findBlock(pos, &offset)); if (block_start >= 0) { // Some or all of the write will be on top of // existing data. do { Block & block(*mBlocks[block_start]); size_t block_limit(block.mUsed - offset); size_t block_len((std::min)(block_limit, len)); memcpy(&block.mData[offset], c_src, block_len); result += block_len; c_src += block_len; len -= block_len; offset = 0; ++block_start; } while (len && block_start < block_limit); } // Something left, see if it will fit in the free // space of the last block. if (len && ! mBlocks.empty()) { Block & last(*mBlocks.back()); if (last.mUsed < last.mAlloced) { // Some will fit... const size_t copy_len((std::min)(len, (last.mAlloced - last.mUsed))); memcpy(&last.mData[last.mUsed], c_src, copy_len); last.mUsed += copy_len; result += copy_len; llassert_always(last.mUsed <= last.mAlloced); mLen += copy_len; c_src += copy_len; len -= copy_len; } } if (len) { // Some or all of the remaining write data will // be an append. result += append(c_src, len); } return result; }
size_t BufferArray::read(size_t pos, void * dst, size_t len) { char * c_dst(static_cast<char *>(dst)); if (pos >= mLen) return 0; size_t len_limit(mLen - pos); len = (std::min)(len, len_limit); if (0 == len) return 0; size_t result(0), offset(0); const int block_limit(mBlocks.size()); int block_start(findBlock(pos, &offset)); if (block_start < 0) return 0; do { Block & block(*mBlocks[block_start]); size_t block_limit(block.mUsed - offset); size_t block_len((std::min)(block_limit, len)); memcpy(c_dst, &block.mData[offset], block_len); result += block_len; len -= block_len; c_dst += block_len; offset = 0; ++block_start; } while (len && block_start < block_limit); return result; }
/* 'offset' must point to a valid, used block. This function marks * the block unused (by updating the seg_len field) and invalidates * the bitmap. It does NOT do this recursively, so to avoid creating * unreachable used blocks, callers may have to recurse over the hive * structures. Also callers must ensure there are no references to * this block from other parts of the hive. */ static void mark_block_unused (hive_h *h, size_t offset) { assert (h->writable); assert (IS_VALID_BLOCK (h, offset)); DEBUG (2, "marking 0x%zx unused", offset); struct ntreg_hbin_block *blockhdr = (struct ntreg_hbin_block *) ((char *) h->addr + offset); size_t seg_len = block_len (h, offset, NULL); blockhdr->seg_len = htole32 (seg_len); BITMAP_CLR (h->bitmap, offset); }
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")) { SET_ERRNO (EINVAL, "invalid block or not an 'vk' block"); return 0; } struct ntreg_vk_record *vk = (struct ntreg_vk_record *) ((char *) h->addr + value); /* 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 seg_len = block_len (h, value, NULL); if (sizeof (struct ntreg_vk_record) + len - 1 > seg_len) { SET_ERRNO (EFAULT, "key length is too long (%zu, %zu)", len, seg_len); return 0; } return _hivex_utf8_strlen (vk->name, len, ! (le16toh (vk->flags) & 0x01)); }
int _hivex_get_values (hive_h *h, hive_node_h node, hive_value_h **values_ret, size_t **blocks_ret) { if (!IS_VALID_BLOCK (h, node) || !block_id_eq (h, node, "nk")) { SET_ERRNO (EINVAL, "invalid block or not an 'nk' block"); return -1; } struct ntreg_nk_record *nk = (struct ntreg_nk_record *) ((char *) h->addr + node); size_t nr_values = le32toh (nk->nr_values); DEBUG (2, "nr_values = %zu", nr_values); offset_list values, blocks; _hivex_init_offset_list (h, &values); _hivex_init_offset_list (h, &blocks); /* Deal with the common "no values" case quickly. */ if (nr_values == 0) goto ok; /* Arbitrarily limit the number of values we will ever deal with. */ if (nr_values > HIVEX_MAX_VALUES) { SET_ERRNO (ERANGE, "nr_values > HIVEX_MAX_VALUES (%zu > %d)", nr_values, HIVEX_MAX_VALUES); goto error; } /* Preallocate space for the values. */ if (_hivex_grow_offset_list (&values, nr_values) == -1) goto error; /* Get the value list and check it looks reasonable. */ size_t vlist_offset = le32toh (nk->vallist); vlist_offset += 0x1000; if (!IS_VALID_BLOCK (h, vlist_offset)) { SET_ERRNO (EFAULT, "value list is not a valid block (0x%zx)", vlist_offset); goto error; } if (_hivex_add_to_offset_list (&blocks, vlist_offset) == -1) goto error; struct ntreg_value_list *vlist = (struct ntreg_value_list *) ((char *) h->addr + vlist_offset); size_t len = block_len (h, vlist_offset, NULL); if (4 + nr_values * 4 > len) { SET_ERRNO (EFAULT, "value list is too long (%zu, %zu)", nr_values, len); goto error; } size_t i; for (i = 0; i < nr_values; ++i) { hive_node_h value = le32toh (vlist->offset[i]); value += 0x1000; if (!IS_VALID_BLOCK (h, value)) { SET_ERRNO (EFAULT, "value is not a valid block (0x%zx)", value); goto error; } if (_hivex_add_to_offset_list (&values, value) == -1) goto error; } ok: *values_ret = _hivex_return_offset_list (&values); *blocks_ret = _hivex_return_offset_list (&blocks); if (!*values_ret || !*blocks_ret) goto error; return 0; error: _hivex_free_offset_list (&values); _hivex_free_offset_list (&blocks); return -1; }
char * hivex_value_value (hive_h *h, hive_value_h value, hive_type *t_rtn, size_t *len_rtn) { if (!IS_VALID_BLOCK (h, value) || !block_id_eq (h, value, "vk")) { SET_ERRNO (EINVAL, "invalid block or not an 'vk' block"); return NULL; } struct ntreg_vk_record *vk = (struct ntreg_vk_record *) ((char *) h->addr + value); hive_type t; size_t len; int is_inline; t = le32toh (vk->data_type); len = le32toh (vk->data_len); is_inline = !!(len & 0x80000000); len &= 0x7fffffff; DEBUG (2, "value=0x%zx, t=%u, len=%zu, inline=%d", value, t, len, is_inline); if (t_rtn) *t_rtn = t; if (len_rtn) *len_rtn = len; if (is_inline && len > 4) { SET_ERRNO (ENOTSUP, "inline data with declared length (%zx) > 4", len); return NULL; } /* Arbitrarily limit the length that we will read. */ if (len > HIVEX_MAX_VALUE_LEN) { SET_ERRNO (ERANGE, "data length > HIVEX_MAX_VALUE_LEN (%zu > %d)", len, HIVEX_MAX_VALUE_LEN); return NULL; } char *ret = malloc (len); if (ret == NULL) return NULL; if (is_inline) { memcpy (ret, (char *) &vk->data_offset, len); return ret; } size_t data_offset = le32toh (vk->data_offset); data_offset += 0x1000; if (!IS_VALID_BLOCK (h, data_offset)) { SET_ERRNO (EFAULT, "data offset is not a valid block (0x%zx)", data_offset); free (ret); return NULL; } /* Check that the declared size isn't larger than the block its in. * * XXX Some apparently valid registries are seen to have this, * so turn this into a warning and substitute the smaller length * instead. */ size_t blen = block_len (h, data_offset, NULL); if (len <= blen - 4 /* subtract 4 for block header */) { char *data = (char *) h->addr + data_offset + 4; memcpy (ret, data, len); return ret; } else { if (!IS_VALID_BLOCK (h, data_offset) || !block_id_eq (h, data_offset, "db")) { SET_ERRNO (EINVAL, "declared data length is longer than the block and " "block is not a db block (data 0x%zx, data len %zu)", data_offset, len); free (ret); return NULL; } struct ntreg_db_record *db = (struct ntreg_db_record *) ((char *) h->addr + data_offset); size_t blocklist_offset = le32toh (db->blocklist_offset); blocklist_offset += 0x1000; size_t nr_blocks = le16toh (db->nr_blocks); if (!IS_VALID_BLOCK (h, blocklist_offset)) { SET_ERRNO (EINVAL, "blocklist is not a valid block " "(db block 0x%zx, blocklist 0x%zx)", data_offset, blocklist_offset); free (ret); return NULL; } struct ntreg_value_list *bl = (struct ntreg_value_list *) ((char *) h->addr + blocklist_offset); size_t i, off; for (i = off = 0; i < nr_blocks; ++i) { size_t subblock_offset = le32toh (bl->offset[i]); subblock_offset += 0x1000; if (!IS_VALID_BLOCK (h, subblock_offset)) { SET_ERRNO (EINVAL, "subblock is not valid " "(db block 0x%zx, block list 0x%zx, data subblock 0x%zx)", data_offset, blocklist_offset, subblock_offset); free (ret); return NULL; } int32_t seg_len = block_len(h, subblock_offset, NULL); struct ntreg_db_block *subblock = (struct ntreg_db_block *) ((char *) h->addr + subblock_offset); int32_t sz = seg_len - 8; /* don't copy the last 4 bytes */ if (off + sz > len) { sz = len - off; } memcpy (ret + off, subblock->data, sz); off += sz; } if (off != *len_rtn) { DEBUG (2, "warning: declared data length " "and amount of data found in sub-blocks differ " "(db block 0x%zx, data len %zu, found data %zu)", data_offset, *len_rtn, off); *len_rtn = off; } return ret; } }
hive_h * 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; h->msglvl = flags & HIVEX_OPEN_MSGLVL_MASK; const char *debug = getenv ("HIVEX_DEBUG"); if (debug && STREQ (debug, "1")) h->msglvl = 2; DEBUG (2, "created handle %p", h); h->writable = !!(flags & HIVEX_OPEN_WRITE); h->unsafe = !!(flags & HIVEX_OPEN_UNSAFE); h->filename = strdup (filename); if (h->filename == NULL) goto error; #ifdef O_CLOEXEC h->fd = open (filename, O_RDONLY | O_CLOEXEC | O_BINARY); #else h->fd = open (filename, O_RDONLY | O_BINARY); #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) goto error; h->size = statbuf.st_size; if (h->size < 0x2000) { SET_ERRNO (EINVAL, "%s: file is too small to be a Windows NT Registry hive file", filename); goto error; } if (!h->writable) { h->addr = mmap (NULL, h->size, PROT_READ, MAP_SHARED, h->fd, 0); if (h->addr == MAP_FAILED) goto error; DEBUG (2, "mapped file at %p", h->addr); } else { h->addr = malloc (h->size); if (h->addr == NULL) goto error; if (full_read (h->fd, h->addr, h->size) < h->size) goto error; /* We don't need the file descriptor along this path, since we * have read all the data. */ if (close (h->fd) == -1) goto error; h->fd = -1; } /* Check header. */ if (h->hdr->magic[0] != 'r' || h->hdr->magic[1] != 'e' || h->hdr->magic[2] != 'g' || h->hdr->magic[3] != 'f') { SET_ERRNO (ENOTSUP, "%s: not a Windows NT Registry hive file", filename); goto error; } /* Check major version. */ uint32_t major_ver = le32toh (h->hdr->major_ver); if (major_ver != 1) { SET_ERRNO (ENOTSUP, "%s: hive file major version %" PRIu32 " (expected 1)", filename, major_ver); goto error; } h->bitmap = calloc (1 + h->size / 32, 1); if (h->bitmap == NULL) goto error; /* Header checksum. */ uint32_t sum = header_checksum (h); if (sum != le32toh (h->hdr->csum)) { SET_ERRNO (EINVAL, "%s: bad checksum in hive header", filename); goto error; } for (int t=0; t<nr_recode_types; t++) { gl_lock_init (h->iconv_cache[t].mutex); h->iconv_cache[t].handle = NULL; } /* Last modified time. */ h->last_modified = le64toh ((int64_t) h->hdr->last_modified); if (h->msglvl >= 2) { char *name = _hivex_recode (h, utf16le_to_utf8, h->hdr->name, 64, NULL); 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" " last modified %" PRIi64 "\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" " 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), h->last_modified, 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; DEBUG (2, "root offset = 0x%zx", 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 * we read to a hash table so pointers will only reference the start * of valid blocks. */ size_t off; struct ntreg_hbin_page *page; for (off = 0x1000; off < h->size; off += le32toh (page->page_size)) { if (off >= h->endpages) break; page = (struct ntreg_hbin_page *) ((char *) h->addr + off); if (page->magic[0] != 'h' || page->magic[1] != 'b' || page->magic[2] != 'i' || page->magic[3] != 'n') { if (!h->unsafe) { SET_ERRNO (ENOTSUP, "%s: trailing garbage at end of file " "(at 0x%zx, after %zu pages)", filename, off, pages); goto error; } DEBUG (2, "page not found at expected offset 0x%zx, " "seeking until one is found or EOF is reached", off); int found = 0; while (off < h->size) { off += 0x1000; if (off >= h->endpages) break; page = (struct ntreg_hbin_page *) ((char *) h->addr + off); if (page->magic[0] == 'h' && page->magic[1] == 'b' && page->magic[2] == 'i' && page->magic[3] == 'n') { DEBUG (2, "found next page by seeking at 0x%zx", off); found = 1; break; } } if (!found) { DEBUG (2, "page not found and end of pages section reached"); break; } } size_t page_size = le32toh (page->page_size); DEBUG (2, "page at 0x%zx, size %zu", 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) { SET_ERRNO (ENOTSUP, "%s: page size %zu at 0x%zx, bad registry", filename, page_size, off); goto error; } if (off + page_size > h->size) { SET_ERRNO (ENOTSUP, "%s: page size %zu at 0x%zx extends beyond end of file, bad registry", filename, page_size, off); goto error; } size_t page_offset = le32toh(page->offset_first) + 0x1000; if (page_offset != off) { SET_ERRNO (ENOTSUP, "%s: declared page offset (0x%zx) does not match computed " "offset (0x%zx), bad registry", filename, page_offset, off); goto error; } /* Read the blocks in this page. */ size_t blkoff; struct ntreg_hbin_block *block; size_t seg_len; for (blkoff = off + 0x20; blkoff < off + page_size; blkoff += seg_len) { blocks++; int is_root = blkoff == h->rootoffs; if (is_root) seen_root_block = 1; block = (struct ntreg_hbin_block *) ((char *) h->addr + blkoff); int used; seg_len = block_len (h, blkoff, &used); /* https://gcc.gnu.org/bugzilla/show_bug.cgi?id=78665 */ #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wstrict-overflow" if (seg_len <= 4 || (seg_len & 3) != 0) { #pragma GCC diagnostic pop if (is_root || !h->unsafe) { SET_ERRNO (ENOTSUP, "%s, the block at 0x%zx has invalid size %" PRIu32 ", bad registry", filename, blkoff, le32toh (block->seg_len)); goto error; } else { DEBUG (2, "%s: block at 0x%zx has invalid size %" PRIu32 ", skipping", filename, blkoff, le32toh (block->seg_len)); break; } } if (h->msglvl >= 2) { unsigned char *id = (unsigned char *) block->id; int id0 = id[0], id1 = id[1]; fprintf (stderr, "%s: %s: " "%s block id %d,%d (%c%c) at 0x%zx size %zu%s\n", "hivex", __func__, used ? "used" : "free", id0, id1, c_isprint (id0) ? id0 : '.', c_isprint (id1) ? id1 : '.', blkoff, 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) { used_blocks++; used_size += seg_len; /* Root block must be an nk-block. */ if (is_root && (block->id[0] != 'n' || block->id[1] != 'k')) bad_root_block = 1; /* Note this blkoff is a valid address. */ BITMAP_SET (h->bitmap, blkoff); } } } if (!seen_root_block) { SET_ERRNO (ENOTSUP, "%s: no root block found", filename); goto error; } if (bad_root_block) { SET_ERRNO (ENOTSUP, "%s: bad root block (free or not nk)", filename); goto error; } DEBUG (1, "successfully read Windows Registry hive file:\n" " pages: %zu [sml: %zu, lge: %zu]\n" " blocks: %zu [sml: %zu, avg: %zu, lge: %zu]\n" " blocks used: %zu\n" " bytes used: %zu", pages, smallest_page, largest_page, blocks, smallest_block, blocks_bytes / blocks, largest_block, used_blocks, used_size); return h; error:; int err = errno; if (h) { free (h->bitmap); if (h->addr && h->size && h->addr != MAP_FAILED) { if (!h->writable) munmap (h->addr, h->size); else free (h->addr); } if (h->fd >= 0) close (h->fd); free (h->filename); free (h); } errno = err; return NULL; }