Richard W.M. Jones
2010-Feb-04 13:36 UTC
[Libguestfs] [PATCH 0/3] Add support for recursively deleting nodes
This set adds support for recursively deleting a node and everything under it (ie. all subkeys, values, SKs etc). I've checked this works, both using my own reverse-engineering tools and using Windows 7 regedit. Example: $ hivexsh -w <<EOF load software cd \Microsoft del commit EOF Rich. -- Richard Jones, Virtualization Group, Red Hat http://people.redhat.com/~rjones New in Fedora 11: Fedora Windows cross-compiler. Compile Windows programs, test, and build Windows installers. Over 70 libraries supprt'd http://fedoraproject.org/wiki/MinGW http://www.annexia.org/fedora_mingw
Richard W.M. Jones
2010-Feb-04 13:42 UTC
[Libguestfs] [PATCH 1/3] hivex: Don't die on valid registries which have bad declared data lengths.
This is a cleanup, not related to this series, but you need it if you want to test the new functionality using the 'software' registry from a recent Windows machine. Rich. -- Richard Jones, Virtualization Group, Red Hat http://people.redhat.com/~rjones New in Fedora 11: Fedora Windows cross-compiler. Compile Windows programs, test, and build Windows installers. Over 70 libraries supprt'd http://fedoraproject.org/wiki/MinGW http://www.annexia.org/fedora_mingw -------------- next part -------------->From 01ba5f80ee02405f2a558e735e32957a710fdd5b Mon Sep 17 00:00:00 2001From: Richard Jones <rjones at redhat.com> Date: Thu, 4 Feb 2010 13:26:04 +0000 Subject: [PATCH 1/3] hivex: Don't die on valid registries which have bad declared data lengths. Some apparently valid registries contain value data length declarations which exceed the allocated block size for the value. Previously the code would return EFAULT for such registries. However since these appear to be otherwise valid registries, turn this into a warning and just use the allocated block size as the data length (in other words, truncate the value). --- hivex/hivex.c | 13 ++++++++----- 1 files changed, 8 insertions(+), 5 deletions(-) diff --git a/hivex/hivex.c b/hivex/hivex.c index af36868..6a9d509 100644 --- a/hivex/hivex.c +++ b/hivex/hivex.c @@ -1186,15 +1186,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; -- 1.6.5.2
Richard W.M. Jones
2010-Feb-04 13:43 UTC
[Libguestfs] [PATCH 2/3] hivex: Add flags argument to internal get_children() function.
-- Richard Jones, Virtualization Group, Red Hat http://people.redhat.com/~rjones libguestfs lets you edit virtual machines. Supports shell scripting, bindings from many languages. http://et.redhat.com/~rjones/libguestfs/ See what it can do: http://et.redhat.com/~rjones/libguestfs/recipes.html -------------- next part -------------->From e14d001fc929307cf017408856be6b753908ab43 Mon Sep 17 00:00:00 2001From: Richard Jones <rjones at redhat.com> Date: Thu, 4 Feb 2010 13:24:27 +0000 Subject: [PATCH 2/3] hivex: Add flags argument to internal get_children() function. When we later call get_children to visit the intermediate ri/lf/lh records, we have already deleted the subkey nk-records, so checking that those nk-records are still valid is not very helpful. This commit adds a flag to turn these checks off. --- hivex/hivex.c | 35 +++++++++++++++++++++-------------- 1 files changed, 21 insertions(+), 14 deletions(-) diff --git a/hivex/hivex.c b/hivex/hivex.c index 6a9d509..0b4ba2e 100644 --- a/hivex/hivex.c +++ b/hivex/hivex.c @@ -683,9 +683,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; @@ -766,12 +769,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; @@ -844,12 +849,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; @@ -878,7 +885,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); -- 1.6.5.2
Richard W.M. Jones
2010-Feb-04 13:44 UTC
[Libguestfs] [PATCH 3/3] hivex: Implement deleting child nodes.
There's a little bit of #if 0-out dead code in this patch. Just ignore that ... Rich. -- Richard Jones, Virtualization Group, Red Hat http://people.redhat.com/~rjones libguestfs lets you edit virtual machines. Supports shell scripting, bindings from many languages. http://et.redhat.com/~rjones/libguestfs/ See what it can do: http://et.redhat.com/~rjones/libguestfs/recipes.html -------------- next part -------------->From aef0d618b08dc9fb27fb3a729dd8926cff26ae0b Mon Sep 17 00:00:00 2001From: Richard Jones <rjones at redhat.com> Date: Wed, 3 Feb 2010 18:10:38 +0000 Subject: [PATCH 3/3] hivex: Implement deleting child nodes. --- hivex/example4 | 34 ++++++++ hivex/hivex.c | 226 +++++++++++++++++++++++++++++++++++++++++++++++++++++ hivex/hivex.h | 2 + hivex/hivex.pod | 18 ++++ hivex/hivexsh.c | 29 +++++++ hivex/hivexsh.pod | 8 ++ 6 files changed, 317 insertions(+), 0 deletions(-) create mode 100755 hivex/example4 diff --git a/hivex/example4 b/hivex/example4 new file mode 100755 index 0000000..85fd552 --- /dev/null +++ b/hivex/example4 @@ -0,0 +1,34 @@ +#!/bin/bash - +# Copyright (C) 2009-2010 Red Hat Inc. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +set -e + +# This program deletes the whole \Microsoft tree from a software hive. + +if [ $# -ne 2 ]; then + echo "$0 software software.new" + exit 1 +fi + +d=`dirname $0` + +$d/hivexsh -w <<EOF +load $1 +cd \Microsoft +del +commit $2 +EOF \ No newline at end of file diff --git a/hivex/hivex.c b/hivex/hivex.c index 0b4ba2e..c8257a3 100644 --- a/hivex/hivex.c +++ b/hivex/hivex.c @@ -236,6 +236,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) { @@ -1861,6 +1872,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); @@ -1962,6 +1976,218 @@ hivex_commit (hive_h *h, const char *filename, int flags) return 0; } +#if 0 +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 -1; + } + + if (name == NULL) { + errno = EINVAL; + return -1; + } + + + + + + +} +#endif + +/* 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, diff --git a/hivex/hivex.h b/hivex/hivex.h index 6a3cb3a..f4ce834 100644 --- a/hivex/hivex.h +++ b/hivex/hivex.h @@ -111,6 +111,8 @@ extern int hivex_visit (hive_h *h, const struct hivex_visitor *visitor, size_t l extern int hivex_visit_node (hive_h *h, hive_node_h node, const struct hivex_visitor *visitor, size_t len, void *opaque, int flags); extern int hivex_commit (hive_h *h, const char *filename, int flags); +extern hive_node_h hivex_node_add_child (hive_h *h, hive_node_h parent, const char *name); +extern int hivex_node_delete_child (hive_h *h, hive_node_h node); struct hive_set_value { char *key; diff --git a/hivex/hivex.pod b/hivex/hivex.pod index 5df75aa..34ff253 100644 --- a/hivex/hivex.pod +++ b/hivex/hivex.pod @@ -379,6 +379,24 @@ operations on the hive after committing, including making more modifications. If you no longer wish to use the hive, call C<hivex_close> after this. +=item hive_node_h hivex_node_add_child (hive_h *h, hive_node_h parent, const char *name); + +Add a new child node named C<name> to the existing node C<parent>. +The new child initially has no subnodes and contains no keys or +values. The parent must not have an existing child called C<name>, so +if you want to overwrite an existing child, call +C<hivex_node_delete_child> first. + +Returns the node handle. On error this returns 0 and sets errno. + +=item int hivex_node_delete_child (hive_h *h, hive_node_h node); + +Delete the node C<node>. All values at the node and all subnodes are +deleted (recursively). The C<node> handle and the handles of all +subnodes become invalid. You cannot delete the root node. + +Returns 0 on success. On error this returns -1 and sets errno. + =item hive_set_value The typedef C<hive_set_value> is used in conjunction with the diff --git a/hivex/hivexsh.c b/hivex/hivexsh.c index 6f33f41..ceb1153 100644 --- a/hivex/hivexsh.c +++ b/hivex/hivexsh.c @@ -79,6 +79,7 @@ static int dispatch (char *cmd, char *args); static int cmd_cd (char *path); static int cmd_close (char *path); static int cmd_commit (char *path); +static int cmd_del (char *args); static int cmd_help (char *args); static int cmd_load (char *hivefile); static int cmd_ls (char *args); @@ -416,6 +417,8 @@ dispatch (char *cmd, char *args) return cmd_close (args); else if (STRCASEEQ (cmd, "commit")) return cmd_commit (args); + else if (STRCASEEQ (cmd, "del")) + return cmd_del (args); else if (STRCASEEQ (cmd, "ls")) return cmd_ls (args); else if (STRCASEEQ (cmd, "lsval")) @@ -1044,3 +1047,29 @@ cmd_setval (char *nrvals_str) return ret; } + +static int +cmd_del (char *args) +{ + if (STRNEQ (args, "")) { + fprintf (stderr, _("hivexsh: '%s' command should not be given arguments\n"), + "del"); + return -1; + } + + if (cwd == hivex_root (h)) { + fprintf (stderr, _("hivexsh: del: the root node cannot be deleted\n")); + return -1; + } + + hive_node_h new_cwd = hivex_node_parent (h, cwd); + + if (hivex_node_delete_child (h, cwd) == -1) { + perror ("del"); + return -1; + } + + cwd = new_cwd; + set_prompt_string (); + return 0; +} diff --git a/hivex/hivexsh.pod b/hivex/hivexsh.pod index e7e8d94..277e3ae 100644 --- a/hivex/hivexsh.pod +++ b/hivex/hivexsh.pod @@ -119,6 +119,14 @@ file is overwritten. Note that you have to specify the C<-w> flag, otherwise no writes are allowed. +=item B<del> + +Delete the current node and everything beneath it. The current +directory is moved up one level (as if you did C<cd ..>) after +this command. + +You cannot delete the root node. + =item B<exit> | B<quit> Exit the shell. -- 1.6.5.2