From aef0d618b08dc9fb27fb3a729dd8926cff26ae0b Mon Sep 17 00:00:00 2001 From: Richard Jones Date: Wed, 3 Feb 2010 18:10:38 +0000 Subject: [PATCH] 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(+) 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 <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 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 to the existing node C. +The new child initially has no subnodes and contains no keys or +values. The parent must not have an existing child called C, so +if you want to overwrite an existing child, call +C 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. All values at the node and all subnodes are +deleted (recursively). The C 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 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 + +Delete the current node and everything beneath it. The current +directory is moved up one level (as if you did C) after +this command. + +You cannot delete the root node. + =item B | B Exit the shell. -- 1.8.3.1