X-Git-Url: http://git.annexia.org/?a=blobdiff_plain;f=hivex%2Fhivex.c;h=7d26eebde4872f969f2fd01933d45516d84dd6d7;hb=a1d388f25859b1c205aebb04dcd4846aa47027a1;hp=af36868a7e8f01e5fd9fcbc92d8a1b427e14f063;hpb=f604f9b0b44c17adcf7a799db06a7fc4c758c5e9;p=libguestfs.git diff --git a/hivex/hivex.c b/hivex/hivex.c index af36868..7d26eeb 100644 --- a/hivex/hivex.c +++ b/hivex/hivex.c @@ -34,6 +34,7 @@ #include #include +#include "c-ctype.h" #include "full-read.h" #include "full-write.h" @@ -49,11 +50,17 @@ //#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) +#define STRPREFIX(a,b) (strncmp((a),(b),strlen((b))) == 0) #include "hivex.h" #include "byte_conversions.h" +/* These limits are in place to stop really stupid stuff and/or exploits. */ +#define HIVEX_MAX_SUBKEYS 10000 +#define HIVEX_MAX_VALUES 1000 +#define HIVEX_MAX_VALUE_LEN 1000000 +#define HIVEX_MAX_ALLOCATION 1000000 + static char *windows_utf16_to_utf8 (/* const */ char *input, size_t len); struct hive_h { @@ -195,7 +202,7 @@ struct ntreg_nk_record { struct ntreg_lf_record { int32_t seg_len; - char id[2]; /* "lf" */ + char id[2]; /* "lf"|"lh" */ uint16_t nr_keys; /* number of keys in this record */ struct { uint32_t offset; /* offset of nk-record for this subkey */ @@ -222,8 +229,7 @@ struct ntreg_vk_record { uint16_t name_len; /* length of name */ /* length of the data: * If data_len is <= 4, then it's stored inline. - * If data_len is 0x80000000, then it's an inline dword. - * Top bit may be set or not set at random. + * Top bit is set to indicate inline. */ uint32_t data_len; uint32_t data_offset; /* pointer to the data (or data if inline) */ @@ -236,6 +242,17 @@ struct ntreg_vk_record { char name[1]; /* key name follows here */ } __attribute__((__packed__)); +struct ntreg_sk_record { + int32_t seg_len; /* length (always -ve because used) */ + char id[2]; /* "sk" */ + uint16_t unknown1; + uint32_t sk_next; /* linked into a circular list */ + uint32_t sk_prev; + uint32_t refcount; /* reference count */ + uint32_t sec_len; /* length of security info */ + char sec_desc[1]; /* security info follows */ +} __attribute__((__packed__)); + static uint32_t header_checksum (const hive_h *h) { @@ -683,9 +700,12 @@ return_offset_list (struct offset_list *list) } /* Iterate over children, returning child nodes and intermediate blocks. */ +#define GET_CHILDREN_NO_CHECK_NK 1 + static int get_children (hive_h *h, hive_node_h node, - hive_node_h **children_ret, size_t **blocks_ret) + hive_node_h **children_ret, size_t **blocks_ret, + int flags) { if (!IS_VALID_BLOCK (h, node) || !BLOCK_ID_EQ (h, node, "nk")) { errno = EINVAL; @@ -704,7 +724,7 @@ get_children (hive_h *h, hive_node_h node, goto ok; /* Arbitrarily limit the number of subkeys we will ever deal with. */ - if (nr_subkeys_in_nk > 1000000) { + if (nr_subkeys_in_nk > HIVEX_MAX_SUBKEYS) { errno = ERANGE; goto error; } @@ -721,7 +741,7 @@ get_children (hive_h *h, hive_node_h node, subkey_lf += 0x1000; if (!IS_VALID_BLOCK (h, subkey_lf)) { if (h->msglvl >= 2) - fprintf (stderr, "hivex_node_children: returning EFAULT because subkey_lf is not a valid block (%zu)\n", + fprintf (stderr, "hivex_node_children: returning EFAULT because subkey_lf is not a valid block (0x%zx)\n", subkey_lf); errno = EFAULT; goto error; @@ -766,12 +786,14 @@ get_children (hive_h *h, hive_node_h node, for (i = 0; i < nr_subkeys_in_lf; ++i) { hive_node_h subkey = le32toh (lf->keys[i].offset); subkey += 0x1000; - if (!IS_VALID_BLOCK (h, subkey)) { - if (h->msglvl >= 2) - fprintf (stderr, "hivex_node_children: returning EFAULT because subkey is not a valid block (0x%zx)\n", - subkey); - errno = EFAULT; - goto error; + if (!(flags & GET_CHILDREN_NO_CHECK_NK)) { + if (!IS_VALID_BLOCK (h, subkey)) { + if (h->msglvl >= 2) + fprintf (stderr, "hivex_node_children: returning EFAULT because subkey is not a valid block (0x%zx)\n", + subkey); + errno = EFAULT; + goto error; + } } if (add_to_offset_list (&children, subkey) == -1) goto error; @@ -787,7 +809,7 @@ get_children (hive_h *h, hive_node_h node, /* Count total number of children. */ size_t i, count = 0; for (i = 0; i < nr_offsets; ++i) { - hive_node_h offset = ri->offset[i]; + hive_node_h offset = le32toh (ri->offset[i]); offset += 0x1000; if (!IS_VALID_BLOCK (h, offset)) { if (h->msglvl >= 2) @@ -797,6 +819,9 @@ get_children (hive_h *h, hive_node_h node, goto error; } if (!BLOCK_ID_EQ (h, offset, "lf") && !BLOCK_ID_EQ (h, offset, "lh")) { + if (h->msglvl >= 2) + fprintf (stderr, "get_children: returning ENOTSUP because ri-record offset does not point to lf/lh (0x%zx)\n", + offset); errno = ENOTSUP; goto error; } @@ -823,7 +848,7 @@ get_children (hive_h *h, hive_node_h node, * something reasonable above. */ for (i = 0; i < nr_offsets; ++i) { - hive_node_h offset = ri->offset[i]; + hive_node_h offset = le32toh (ri->offset[i]); offset += 0x1000; if (!IS_VALID_BLOCK (h, offset)) { if (h->msglvl >= 2) @@ -833,6 +858,9 @@ get_children (hive_h *h, hive_node_h node, goto error; } if (!BLOCK_ID_EQ (h, offset, "lf") && !BLOCK_ID_EQ (h, offset, "lh")) { + if (h->msglvl >= 2) + fprintf (stderr, "get_children: returning ENOTSUP because ri-record offset does not point to lf/lh (0x%zx)\n", + offset); errno = ENOTSUP; goto error; } @@ -844,12 +872,14 @@ get_children (hive_h *h, hive_node_h node, for (j = 0; j < le16toh (lf->nr_keys); ++j) { hive_node_h subkey = le32toh (lf->keys[j].offset); subkey += 0x1000; - if (!IS_VALID_BLOCK (h, subkey)) { - if (h->msglvl >= 2) - fprintf (stderr, "hivex_node_children: returning EFAULT because indirect subkey is not a valid block (0x%zx)\n", - subkey); - errno = EFAULT; - goto error; + if (!(flags & GET_CHILDREN_NO_CHECK_NK)) { + if (!IS_VALID_BLOCK (h, subkey)) { + if (h->msglvl >= 2) + fprintf (stderr, "hivex_node_children: returning EFAULT because indirect subkey is not a valid block (0x%zx)\n", + subkey); + errno = EFAULT; + goto error; + } } if (add_to_offset_list (&children, subkey) == -1) goto error; @@ -858,6 +888,9 @@ get_children (hive_h *h, hive_node_h node, goto ok; } /* else not supported, set errno and fall through */ + if (h->msglvl >= 2) + fprintf (stderr, "get_children: returning ENOTSUP because subkey block is not lf/lh/ri (0x%zx, %d, %d)\n", + subkey_lf, block->id[0], block->id[1]); errno = ENOTSUP; error: free_offset_list (&children); @@ -878,7 +911,7 @@ hivex_node_children (hive_h *h, hive_node_h node) hive_node_h *children; size_t *blocks; - if (get_children (h, node, &children, &blocks) == -1) + if (get_children (h, node, &children, &blocks, 0) == -1) return NULL; free (blocks); @@ -961,7 +994,7 @@ get_values (hive_h *h, hive_node_h node, goto ok; /* Arbitrarily limit the number of values we will ever deal with. */ - if (nr_values > 100000) { + if (nr_values > HIVEX_MAX_VALUES) { errno = ERANGE; goto error; } @@ -1117,11 +1150,7 @@ hivex_value_type (hive_h *h, hive_value_h value, hive_type *t, size_t *len) if (len) { *len = le32toh (vk->data_len); - if (*len == 0x80000000) { /* special case */ - *len = 4; - if (t) *t = hive_t_dword; - } - *len &= 0x7fffffff; + *len &= 0x7fffffff; /* top bit indicates if data is stored inline */ } return 0; @@ -1140,27 +1169,30 @@ hivex_value_value (hive_h *h, hive_value_h value, hive_type t; size_t len; + int is_inline; t = le32toh (vk->data_type); len = le32toh (vk->data_len); - if (len == 0x80000000) { /* special case */ - len = 4; - t = hive_t_dword; - } + is_inline = !!(len & 0x80000000); len &= 0x7fffffff; if (h->msglvl >= 2) - fprintf (stderr, "hivex_value_value: value=0x%zx, t=%d, len=%zu\n", - value, t, len); + fprintf (stderr, "hivex_value_value: value=0x%zx, t=%d, len=%zu, inline=%d\n", + value, t, len, is_inline); if (t_rtn) *t_rtn = t; if (len_rtn) *len_rtn = len; + if (is_inline && len > 4) { + errno = ENOTSUP; + return NULL; + } + /* Arbitrarily limit the length that we will read. */ - if (len > 1000000) { + if (len > HIVEX_MAX_VALUE_LEN) { errno = ERANGE; return NULL; } @@ -1169,8 +1201,7 @@ hivex_value_value (hive_h *h, hive_value_h value, if (ret == NULL) return NULL; - /* If length is <= 4 it's always stored inline. */ - if (len <= 4) { + if (is_inline) { memcpy (ret, (char *) &vk->data_offset, len); return ret; } @@ -1186,15 +1217,18 @@ hivex_value_value (hive_h *h, hive_value_h value, return NULL; } - /* Check that the declared size isn't larger than the block its in. */ + /* 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 */) { if (h->msglvl >= 2) - fprintf (stderr, "hivex_value_value: returning EFAULT because data is longer than its block (data 0x%zx, data len %zu, block len %zu)\n", + fprintf (stderr, "hivex_value_value: warning: declared data length is longer than the block it is in (data 0x%zx, data len %zu, block len %zu)\n", data_offset, len, blen); - errno = EFAULT; - free (ret); - return NULL; + len = blen - 4; } char *data = h->addr + data_offset + 4; @@ -1781,7 +1815,7 @@ allocate_block (hive_h *h, size_t seg_len, const char id[2]) } /* Refuse really large allocations. */ - if (seg_len > 1000000) { + if (seg_len > HIVEX_MAX_ALLOCATION) { if (h->msglvl >= 2) fprintf (stderr, "allocate_block: refusing large allocation (%zu), returning ERANGE\n", seg_len); @@ -1812,11 +1846,13 @@ allocate_block (hive_h *h, size_t seg_len, const char id[2]) (struct ntreg_hbin_block *) (h->addr + offset); blockhdr->seg_len = htole32 (- (int32_t) seg_len); - if (id[0] && id[1] && seg_len >= 6) { + if (id[0] && id[1] && seg_len >= sizeof (struct ntreg_hbin_block)) { blockhdr->id[0] = id[0]; blockhdr->id[1] = id[1]; } + BITMAP_SET (h->bitmap, offset); + h->endblocks += seg_len; /* If there is space after the last block in the last page, then we @@ -1851,6 +1887,9 @@ mark_block_unused (hive_h *h, size_t offset) assert (h->writable); assert (IS_VALID_BLOCK (h, offset)); + if (h->msglvl >= 2) + fprintf (stderr, "mark_block_unused: marking 0x%zx unused\n", offset); + struct ntreg_hbin_block *blockhdr = (struct ntreg_hbin_block *) (h->addr + offset); @@ -1882,12 +1921,12 @@ delete_values (hive_h *h, hive_node_h node) (struct ntreg_vk_record *) (h->addr + values[i]); size_t len; + int is_inline; len = le32toh (vk->data_len); - if (len == 0x80000000) /* special case */ - len = 4; + is_inline = !!(len & 0x80000000); /* top bit indicates is inline */ len &= 0x7fffffff; - if (len > 4) { /* non-inline, so remove data block */ + if (!is_inline) { /* non-inline, so remove data block */ size_t data_offset = le32toh (vk->data_offset); data_offset += 0x1000; mark_block_unused (h, data_offset); @@ -1952,6 +1991,492 @@ hivex_commit (hive_h *h, const char *filename, int flags) return 0; } +/* Calculate the hash for a lf or lh record offset. + */ +static void +calc_hash (const char *type, const char *name, char *ret) +{ + size_t len = strlen (name); + + if (STRPREFIX (type, "lf")) + /* Old-style, not used in current registries. */ + memcpy (ret, name, len < 4 ? len : 4); + else { + /* New-style for lh-records. */ + size_t i, c; + uint32_t h = 0; + for (i = 0; i < len; ++i) { + c = c_toupper (name[i]); + h *= 37; + h += c; + } + *((uint32_t *) ret) = htole32 (h); + } +} + +/* Create a completely new lh-record containing just the single node. */ +static size_t +new_lh_record (hive_h *h, const char *name, hive_node_h node) +{ + static const char id[2] = { 'l', 'h' }; + size_t seg_len = sizeof (struct ntreg_lf_record); + size_t offset = allocate_block (h, seg_len, id); + if (offset == 0) + return 0; + + struct ntreg_lf_record *lh = (struct ntreg_lf_record *) (h->addr + offset); + lh->nr_keys = htole16 (1); + lh->keys[0].offset = htole32 (node - 0x1000); + calc_hash ("lh", name, lh->keys[0].hash); + + return offset; +} + +/* Insert node into existing lf/lh-record at position. + * This allocates a new record and marks the old one as unused. + */ +static size_t +insert_lf_record (hive_h *h, size_t old_offs, size_t posn, + const char *name, hive_node_h node) +{ + assert (IS_VALID_BLOCK (h, old_offs)); + + /* Work around C stupidity. + * http://www.redhat.com/archives/libguestfs/2010-February/msg00056.html + */ + int test = BLOCK_ID_EQ (h, old_offs, "lf") || BLOCK_ID_EQ (h, old_offs, "lh"); + assert (test); + + struct ntreg_lf_record *old_lf = + (struct ntreg_lf_record *) (h->addr + old_offs); + size_t nr_keys = le16toh (old_lf->nr_keys); + + nr_keys++; /* in new record ... */ + + size_t seg_len = sizeof (struct ntreg_lf_record) + (nr_keys-1) * 8; + size_t new_offs = allocate_block (h, seg_len, old_lf->id); + if (new_offs == 0) + return 0; + + struct ntreg_lf_record *new_lf = + (struct ntreg_lf_record *) (h->addr + new_offs); + new_lf->nr_keys = htole16 (nr_keys); + + /* Copy the keys until we reach posn, insert the new key there, then + * copy the remaining keys. + */ + size_t i; + for (i = 0; i < posn; ++i) + new_lf->keys[i] = old_lf->keys[i]; + + new_lf->keys[i].offset = htole32 (node - 0x1000); + calc_hash (new_lf->id, name, new_lf->keys[i].hash); + + for (i = posn+1; i < nr_keys; ++i) + new_lf->keys[i] = old_lf->keys[i-1]; + + /* Old block is unused, return new block. */ + mark_block_unused (h, old_offs); + return new_offs; +} + +/* Compare name with name in nk-record. */ +static int +compare_name_with_nk_name (hive_h *h, const char *name, hive_node_h nk_offs) +{ + assert (IS_VALID_BLOCK (h, nk_offs)); + assert (BLOCK_ID_EQ (h, nk_offs, "nk")); + + /* Name in nk is not necessarily nul-terminated. */ + char *nname = hivex_node_name (h, nk_offs); + + /* Unfortunately we don't have a way to return errors here. */ + if (!nname) { + perror ("compare_name_with_nk_name"); + return 0; + } + + int r = strcasecmp (name, nname); + free (nname); + + return r; +} + +hive_node_h +hivex_node_add_child (hive_h *h, hive_node_h parent, const char *name) +{ + if (!h->writable) { + errno = EROFS; + return 0; + } + + if (!IS_VALID_BLOCK (h, parent) || !BLOCK_ID_EQ (h, parent, "nk")) { + errno = EINVAL; + return 0; + } + + if (name == NULL || strlen (name) == 0) { + errno = EINVAL; + return 0; + } + + if (hivex_node_get_child (h, parent, name) != 0) { + errno = EEXIST; + return 0; + } + + /* Create the new nk-record. */ + static const char nk_id[2] = { 'n', 'k' }; + size_t seg_len = sizeof (struct ntreg_nk_record) + strlen (name); + hive_node_h node = allocate_block (h, seg_len, nk_id); + if (node == 0) + return 0; + + if (h->msglvl >= 2) + fprintf (stderr, "hivex_node_add_child: allocated new nk-record for child at 0x%zx\n", node); + + struct ntreg_nk_record *nk = (struct ntreg_nk_record *) (h->addr + node); + nk->flags = htole16 (0x0020); /* key is ASCII. */ + nk->parent = htole32 (parent - 0x1000); + nk->subkey_lf = htole32 (0xffffffff); + nk->subkey_lf_volatile = htole32 (0xffffffff); + nk->vallist = htole32 (0xffffffff); + nk->classname = htole32 (0xffffffff); + nk->name_len = htole16 (strlen (name)); + strcpy (nk->name, name); + + /* Inherit parent sk. */ + struct ntreg_nk_record *parent_nk = + (struct ntreg_nk_record *) (h->addr + parent); + size_t parent_sk_offset = le32toh (parent_nk->sk); + parent_sk_offset += 0x1000; + if (!IS_VALID_BLOCK (h, parent_sk_offset) || + !BLOCK_ID_EQ (h, parent_sk_offset, "sk")) { + if (h->msglvl >= 2) + fprintf (stderr, "hivex_node_add_child: returning EFAULT because parent sk is not a valid block (%zu)\n", + parent_sk_offset); + errno = EFAULT; + return 0; + } + struct ntreg_sk_record *sk = + (struct ntreg_sk_record *) (h->addr + parent_sk_offset); + sk->refcount = htole32 (le32toh (sk->refcount) + 1); + nk->sk = htole32 (parent_sk_offset - 0x1000); + + /* Inherit parent timestamp. */ + memcpy (nk->timestamp, parent_nk->timestamp, sizeof (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 + * subkey in a non-sorted position (eg. just add it at the end) then + * Windows won't see the subkey _and_ Windows will corrupt the hive + * itself when it modifies or saves it. + * + * So use get_children() to get a list of intermediate + * lf/lh-records. get_children() returns these in reading order + * (which is sorted), so we look for the lf/lh-records in sequence + * until we find the key name just after the one we are inserting, + * and we insert the subkey just before it. + * + * The only other case is the no-subkeys case, where we have to + * create a brand new lh-record. + */ + hive_node_h *unused; + size_t *blocks; + + if (get_children (h, parent, &unused, &blocks, 0) == -1) + return 0; + free (unused); + + size_t i, j; + size_t nr_subkeys_in_parent_nk = le32toh (parent_nk->nr_subkeys); + if (nr_subkeys_in_parent_nk == 0) { /* No subkeys case. */ + /* Free up any existing intermediate blocks. */ + for (i = 0; blocks[i] != 0; ++i) + mark_block_unused (h, blocks[i]); + size_t lh_offs = new_lh_record (h, name, node); + if (lh_offs == 0) { + free (blocks); + return 0; + } + + if (h->msglvl >= 2) + fprintf (stderr, "hivex_node_add_child: no keys, allocated new lh-record at 0x%zx\n", lh_offs); + + parent_nk->subkey_lf = htole32 (lh_offs - 0x1000); + } + else { /* Insert subkeys case. */ + size_t old_offs = 0, new_offs = 0; + struct ntreg_lf_record *old_lf = NULL; + + /* Find lf/lh key name just after the one we are inserting. */ + for (i = 0; blocks[i] != 0; ++i) { + if (BLOCK_ID_EQ (h, blocks[i], "lf") || + BLOCK_ID_EQ (h, blocks[i], "lh")) { + old_offs = blocks[i]; + old_lf = (struct ntreg_lf_record *) (h->addr + old_offs); + for (j = 0; j < le16toh (old_lf->nr_keys); ++j) { + hive_node_h nk_offs = le32toh (old_lf->keys[j].offset); + nk_offs += 0x1000; + if (compare_name_with_nk_name (h, name, nk_offs) < 0) + goto insert_it; + } + } + } + + /* Insert it at the end. + * old_offs points to the last lf record, set j. + */ + assert (old_offs != 0); /* should never happen if nr_subkeys > 0 */ + j = le16toh (old_lf->nr_keys); + + /* Insert it. */ + insert_it: + if (h->msglvl >= 2) + fprintf (stderr, "hivex_node_add_child: insert key in existing lh-record at 0x%zx, posn %zu\n", old_offs, j); + + new_offs = insert_lf_record (h, old_offs, j, name, node); + if (new_offs == 0) { + free (blocks); + return 0; + } + + if (h->msglvl >= 2) + fprintf (stderr, "hivex_node_add_child: new lh-record at 0x%zx\n", + new_offs); + + /* If the lf/lh-record was directly referenced by the parent nk, + * then update the parent nk. + */ + if (le32toh (parent_nk->subkey_lf) + 0x1000 == old_offs) + parent_nk->subkey_lf = htole32 (new_offs - 0x1000); + /* Else we have to look for the intermediate ri-record and update + * that in-place. + */ + else { + for (i = 0; blocks[i] != 0; ++i) { + if (BLOCK_ID_EQ (h, blocks[i], "ri")) { + struct ntreg_ri_record *ri = + (struct ntreg_ri_record *) (h->addr + blocks[i]); + for (j = 0; j < le16toh (ri->nr_offsets); ++j) + if (le32toh (ri->offset[j] + 0x1000) == old_offs) { + ri->offset[j] = htole32 (new_offs - 0x1000); + goto found_it; + } + } + } + + /* Not found .. This is an internal error. */ + if (h->msglvl >= 2) + fprintf (stderr, "hivex_node_add_child: returning ENOTSUP because could not find ri->lf link\n"); + errno = ENOTSUP; + free (blocks); + return 0; + + found_it: + ; + } + } + + free (blocks); + + /* Update nr_subkeys in parent nk. */ + nr_subkeys_in_parent_nk++; + parent_nk->nr_subkeys = htole32 (nr_subkeys_in_parent_nk); + + /* Update max_subkey_name_len in parent nk. */ + uint16_t max = le16toh (parent_nk->max_subkey_name_len); + if (max < strlen (name) * 2) /* *2 because "recoded" in UTF16-LE. */ + parent_nk->max_subkey_name_len = htole16 (strlen (name) * 2); + + return node; +} + +/* Decrement the refcount of an sk-record, and if it reaches zero, + * unlink it from the chain and delete it. + */ +static int +delete_sk (hive_h *h, size_t sk_offset) +{ + if (!IS_VALID_BLOCK (h, sk_offset) || !BLOCK_ID_EQ (h, sk_offset, "sk")) { + if (h->msglvl >= 2) + fprintf (stderr, "delete_sk: not an sk record: 0x%zx\n", sk_offset); + errno = EFAULT; + return -1; + } + + struct ntreg_sk_record *sk = (struct ntreg_sk_record *) (h->addr + sk_offset); + + if (sk->refcount == 0) { + if (h->msglvl >= 2) + fprintf (stderr, "delete_sk: sk record already has refcount 0: 0x%zx\n", + sk_offset); + errno = EINVAL; + return -1; + } + + sk->refcount--; + + if (sk->refcount == 0) { + size_t sk_prev_offset = sk->sk_prev; + sk_prev_offset += 0x1000; + + size_t sk_next_offset = sk->sk_next; + sk_next_offset += 0x1000; + + /* Update sk_prev/sk_next SKs, unless they both point back to this + * cell in which case we are deleting the last SK. + */ + if (sk_prev_offset != sk_offset && sk_next_offset != sk_offset) { + struct ntreg_sk_record *sk_prev = + (struct ntreg_sk_record *) (h->addr + sk_prev_offset); + struct ntreg_sk_record *sk_next = + (struct ntreg_sk_record *) (h->addr + sk_next_offset); + + sk_prev->sk_next = htole32 (sk_next_offset - 0x1000); + sk_next->sk_prev = htole32 (sk_prev_offset - 0x1000); + } + + /* Refcount is zero so really delete this block. */ + mark_block_unused (h, sk_offset); + } + + return 0; +} + +/* Callback from hivex_node_delete_child which is called to delete a + * node AFTER its subnodes have been visited. The subnodes have been + * deleted but we still have to delete any lf/lh/li/ri records and the + * value list block and values, followed by deleting the node itself. + */ +static int +delete_node (hive_h *h, void *opaque, hive_node_h node, const char *name) +{ + /* Get the intermediate blocks. The subkeys have already been + * deleted by this point, so tell get_children() not to check for + * validity of the nk-records. + */ + hive_node_h *unused; + size_t *blocks; + if (get_children (h, node, &unused, &blocks, GET_CHILDREN_NO_CHECK_NK) == -1) + return -1; + free (unused); + + /* We don't care what's in these intermediate blocks, so we can just + * delete them unconditionally. + */ + size_t i; + for (i = 0; blocks[i] != 0; ++i) + mark_block_unused (h, blocks[i]); + + free (blocks); + + /* Delete the values in the node. */ + if (delete_values (h, node) == -1) + return -1; + + struct ntreg_nk_record *nk = (struct ntreg_nk_record *) (h->addr + node); + + /* If the NK references an SK, delete it. */ + size_t sk_offs = le32toh (nk->sk); + if (sk_offs != 0xffffffff) { + sk_offs += 0x1000; + if (delete_sk (h, sk_offs) == -1) + return -1; + nk->sk = htole32 (0xffffffff); + } + + /* If the NK references a classname, delete it. */ + size_t cl_offs = le32toh (nk->classname); + if (cl_offs != 0xffffffff) { + cl_offs += 0x1000; + mark_block_unused (h, cl_offs); + nk->classname = htole32 (0xffffffff); + } + + /* Delete the node itself. */ + mark_block_unused (h, node); + + return 0; +} + +int +hivex_node_delete_child (hive_h *h, hive_node_h node) +{ + if (!h->writable) { + errno = EROFS; + return -1; + } + + if (!IS_VALID_BLOCK (h, node) || !BLOCK_ID_EQ (h, node, "nk")) { + errno = EINVAL; + return -1; + } + + if (node == hivex_root (h)) { + if (h->msglvl >= 2) + fprintf (stderr, "hivex_node_delete_child: cannot delete root node\n"); + errno = EINVAL; + return -1; + } + + hive_node_h parent = hivex_node_parent (h, node); + if (parent == 0) + return -1; + + /* Delete node and all its children and values recursively. */ + static const struct hivex_visitor visitor = { .node_end = delete_node }; + if (hivex_visit_node (h, node, &visitor, sizeof visitor, NULL, 0) == -1) + return -1; + + /* Delete the link from parent to child. We need to find the lf/lh + * record which contains the offset and remove the offset from that + * record, then decrement the element count in that record, and + * decrement the overall number of subkeys stored in the parent + * node. + */ + hive_node_h *unused; + size_t *blocks; + if (get_children (h, parent, &unused, &blocks, GET_CHILDREN_NO_CHECK_NK)== -1) + return -1; + free (unused); + + size_t i, j; + for (i = 0; blocks[i] != 0; ++i) { + struct ntreg_hbin_block *block = + (struct ntreg_hbin_block *) (h->addr + blocks[i]); + + if (block->id[0] == 'l' && (block->id[1] == 'f' || block->id[1] == 'h')) { + struct ntreg_lf_record *lf = (struct ntreg_lf_record *) block; + + size_t nr_subkeys_in_lf = le16toh (lf->nr_keys); + + for (j = 0; j < nr_subkeys_in_lf; ++j) + if (le32toh (lf->keys[j].offset) + 0x1000 == node) { + for (; j < nr_subkeys_in_lf - 1; ++j) + memcpy (&lf->keys[j], &lf->keys[j+1], sizeof (lf->keys[j])); + lf->nr_keys = htole16 (nr_subkeys_in_lf - 1); + goto found; + } + } + } + if (h->msglvl >= 2) + fprintf (stderr, "hivex_node_delete_child: could not find parent to child link\n"); + errno = ENOTSUP; + return -1; + + found:; + struct ntreg_nk_record *nk = (struct ntreg_nk_record *) (h->addr + parent); + size_t nr_subkeys_in_nk = le32toh (nk->nr_subkeys); + nk->nr_subkeys = htole32 (nr_subkeys_in_nk - 1); + + if (h->msglvl >= 2) + fprintf (stderr, "hivex_node_delete_child: updating nr_subkeys in parent 0x%zx to %zu\n", + parent, nr_subkeys_in_nk); + + return 0; +} + int hivex_node_set_values (hive_h *h, hive_node_h node, size_t nr_values, const hive_set_value *values, @@ -2005,10 +2530,13 @@ hivex_node_set_values (hive_h *h, hive_node_h node, vk->name_len = htole16 (name_len); strcpy (vk->name, values[i].key); vk->data_type = htole32 (values[i].t); - vk->data_len = htole16 (values[i].len); + uint32_t len = values[i].len; + if (len <= 4) /* store it inline => set MSB flag */ + len |= 0x80000000; + vk->data_len = htole32 (len); vk->flags = name_len == 0 ? 0 : 1; - if (values[i].len <= 4) /* Store data inline. */ + if (values[i].len <= 4) /* store it inline */ memcpy (&vk->data_offset, values[i].value, values[i].len); else { size_t offs = allocate_block (h, values[i].len + 4, nul_id); @@ -2019,6 +2547,7 @@ hivex_node_set_values (hive_h *h, hive_node_h node, } if (name_len * 2 > le32toh (nk->max_vk_name_len)) + /* * 2 for UTF16-LE "reencoding" */ nk->max_vk_name_len = htole32 (name_len * 2); if (values[i].len > le32toh (nk->max_vk_data_len)) nk->max_vk_data_len = htole32 (values[i].len);