Richard Jones
2010-Feb-05 13:06 UTC
[Libguestfs] [PATCH 01/14] hivexsh: Document some peculiarities of the "cd" command.
--- hivex/hivexsh.pod | 9 ++++++++- 1 files changed, 8 insertions(+), 1 deletions(-) diff --git a/hivex/hivexsh.pod b/hivex/hivexsh.pod index 277e3ae..9336798 100644 --- a/hivex/hivexsh.pod +++ b/hivex/hivexsh.pod @@ -100,7 +100,14 @@ or even: Path elements (node names) are matched case insensitively, and characters like space, C<*>, and C<?> have I<no> special significance. -C<..> may be used to go to the parent directory. +C<cd ..> may be used to go to the parent directory. + +C<cd> without any arguments prints the current path. + +Be careful with C<cd \> since the readline library has an undocumented +behaviour where it will think the final backslash is a continuation +(it reads the next line of input and appends it). Put a single space +after the backslash. =item B<close> | B<unload> -- 1.6.5.2
Richard Jones
2010-Feb-05 13:06 UTC
[Libguestfs] [PATCH 02/14] hivex: Some missing le32toh endianness conversions.
--- hivex/hivex.c | 4 ++-- 1 files changed, 2 insertions(+), 2 deletions(-) diff --git a/hivex/hivex.c b/hivex/hivex.c index c8257a3..ce3e14c 100644 --- a/hivex/hivex.c +++ b/hivex/hivex.c @@ -803,7 +803,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) @@ -839,7 +839,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) -- 1.6.5.2
Richard Jones
2010-Feb-05 13:06 UTC
[Libguestfs] [PATCH 03/14] hivex: Documentation update.
ntreg_lf_record can have id "lf" (old-style hashes) or "lh" (new- style hashes). --- hivex/hivex.c | 2 +- 1 files changed, 1 insertions(+), 1 deletions(-) diff --git a/hivex/hivex.c b/hivex/hivex.c index ce3e14c..b106a10 100644 --- a/hivex/hivex.c +++ b/hivex/hivex.c @@ -195,7 +195,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 */ -- 1.6.5.2
Richard Jones
2010-Feb-05 13:06 UTC
[Libguestfs] [PATCH 04/14] hivex: More debug messages.
--- hivex/hivex.c | 11 ++++++++++- 1 files changed, 10 insertions(+), 1 deletions(-) diff --git a/hivex/hivex.c b/hivex/hivex.c index b106a10..6b4b9d8 100644 --- a/hivex/hivex.c +++ b/hivex/hivex.c @@ -735,7 +735,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; @@ -813,6 +813,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; } @@ -849,6 +852,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; } @@ -876,6 +882,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); -- 1.6.5.2
Richard Jones
2010-Feb-05 13:06 UTC
[Libguestfs] [PATCH 05/14] hivex: allocate_block should update valid block bitmap.
The internal allocate_block() function wasn't updating the bitmap, so if you revisited a block which you had allocated in the same session, you could get an EFAULT error. --- hivex/hivex.c | 2 ++ 1 files changed, 2 insertions(+), 0 deletions(-) diff --git a/hivex/hivex.c b/hivex/hivex.c index 6b4b9d8..2a094b8 100644 --- a/hivex/hivex.c +++ b/hivex/hivex.c @@ -1847,6 +1847,8 @@ allocate_block (hive_h *h, size_t seg_len, const char id[2]) 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 -- 1.6.5.2
Richard Jones
2010-Feb-05 13:06 UTC
[Libguestfs] [PATCH 06/14] hivexsh: cd command: fix error handling
The error behaviour of hivex_node_get_child is subtle, so the 'cd' command wouldn't always report errors correctly. This fixes it. --- hivex/hivexsh.c | 8 ++++++-- 1 files changed, 6 insertions(+), 2 deletions(-) diff --git a/hivex/hivexsh.c b/hivex/hivexsh.c index ceb1153..39ca634 100644 --- a/hivex/hivexsh.c +++ b/hivex/hivexsh.c @@ -559,10 +559,14 @@ cmd_cd (char *path) continue; } + errno = 0; new_cwd = hivex_node_get_child (h, new_cwd, elem); if (new_cwd == 0) { - fprintf (stderr, _("hivexsh: cd: subkey '%s' not found\n"), - elem); + if (errno) + perror ("hivexsh: cd"); + else + fprintf (stderr, _("hivexsh: cd: subkey '%s' not found\n"), + elem); return -1; } } -- 1.6.5.2
Richard Jones
2010-Feb-05 13:07 UTC
[Libguestfs] [PATCH 07/14] hivexsh: lsval: Remove stray quotation mark.
--- hivex/hivexsh.c | 2 +- 1 files changed, 1 insertions(+), 1 deletions(-) diff --git a/hivex/hivexsh.c b/hivex/hivexsh.c index 39ca634..1bd3b8b 100644 --- a/hivex/hivexsh.c +++ b/hivex/hivexsh.c @@ -784,7 +784,7 @@ cmd_lsval (char *key) case hive_t_dword: case hive_t_dword_be: { int32_t j = hivex_value_dword (h, values[i]); - printf ("dword:%08" PRIx32 "\"", j); + printf ("dword:%08" PRIx32, j); break; } -- 1.6.5.2
Richard Jones
2010-Feb-05 13:07 UTC
[Libguestfs] [PATCH 08/14] hivexsh: del command: Fix error message.
--- hivex/hivexsh.c | 2 +- 1 files changed, 1 insertions(+), 1 deletions(-) diff --git a/hivex/hivexsh.c b/hivex/hivexsh.c index 1bd3b8b..d5d9ada 100644 --- a/hivex/hivexsh.c +++ b/hivex/hivexsh.c @@ -1069,7 +1069,7 @@ cmd_del (char *args) hive_node_h new_cwd = hivex_node_parent (h, cwd); if (hivex_node_delete_child (h, cwd) == -1) { - perror ("del"); + perror ("hivexsh: del"); return -1; } -- 1.6.5.2
Richard Jones
2010-Feb-05 13:07 UTC
[Libguestfs] [PATCH 09/14] hivex: Check hash fields in lf/lh records.
--- hivex/tools/visualizer.ml | 81 ++++++++++++++++++++++++++++++++++----------- 1 files changed, 61 insertions(+), 20 deletions(-) diff --git a/hivex/tools/visualizer.ml b/hivex/tools/visualizer.ml index da79bee..bc447b7 100644 --- a/hivex/tools/visualizer.ml +++ b/hivex/tools/visualizer.ml @@ -636,7 +636,7 @@ let rec visit_nk ?(nk_is_root = false) nk (* Visit the subkeys of this node. *) if subkeys <> -1 then ( - let counted, max_name_len = visit_subkeys subkeys in + let counted, max_name_len, _ = visit_subkeys subkeys in if counted <> nr_subkeys then failwithf "%s: incorrect count of subkeys (%d, counted %d) in subkey list at %s\n" @@ -728,11 +728,17 @@ and visit_subkeys subkeys let (_, _, bits) = lookup "visit_subkeys" subkeys in mark_visited subkeys; (bitmatch bits with - | { ("lf"|"lh") : 2*8 : string; + | { "lf" : 2*8 : string; len : 2*8 : littleendian; (* number of subkeys of this node *) rest : len*8*8 : bitstring } -> printf "LF %s %d\n" (print_offset subkeys) len; - visit_subkeys_in_lf_list subkeys len rest + visit_subkeys_in_lf_list false subkeys len rest + + | { "lh" : 2*8 : string; + len : 2*8 : littleendian; (* number of subkeys of this node *) + rest : len*8*8 : bitstring } -> + printf "LF %s %d\n" (print_offset subkeys) len; + visit_subkeys_in_lf_list true subkeys len rest | { "ri" : 2*8 : string; len : 2*8 : littleendian; @@ -746,55 +752,90 @@ and visit_subkeys subkeys | { "nk" : 2*8 : string } -> visit_nk subkeys; - let name_len = name_len_of_nk subkeys in - 1, name_len + let name, name_len = name_of_nk subkeys in + 1, name_len, name | {_} -> failwithf "%s: invalid subkey node found at %s\n" basename (print_offset subkeys) ) -and visit_subkeys_in_lf_list subkeys_top len bits +and visit_subkeys_in_lf_list newstyle_hash subkeys_top len bits if len > 0 then ( bitmatch bits with | { rest : -1 : bitstring } when bitstring_length rest = 0 -> assert (len = 0); - 0, 0 + 0, 0, "" | { offset : 4*8 : littleendian, bind (get_offset offset); - _ (* hash *) : 4*8 : bitstring; + hash : 4*8 : bitstring; rest : -1 : bitstring } -> - let c1, name_len1 = visit_subkeys offset in - let c2, name_len2 = visit_subkeys_in_lf_list subkeys_top (len-1) rest in - c1 + c2, max name_len1 name_len2 + let c1, name_len1, name = visit_subkeys offset in + + check_hash offset newstyle_hash hash name; + + let c2, name_len2, _ + visit_subkeys_in_lf_list newstyle_hash subkeys_top (len-1) rest in + c1 + c2, max name_len1 name_len2, "" | {_} -> failwithf "%s: invalid subkey in lf/lh list at %s\n" basename (print_offset subkeys_top) - ) else 0, 0 + ) else 0, 0, "" and visit_subkeys_in_ri_list subkeys_top len bits if len > 0 then ( bitmatch bits with | { rest : -1 : bitstring } when bitstring_length rest = 0 -> assert (len = 0); - 0, 0 + 0, 0, "" | { offset : 4*8 : littleendian, bind (get_offset offset); rest : -1 : bitstring } -> - let c1, name_len1 = visit_subkeys offset in - let c2, name_len2 = visit_subkeys_in_ri_list subkeys_top (len-1) rest in - c1 + c2, max name_len1 name_len2 + let c1, name_len1, _ = visit_subkeys offset in + let c2, name_len2, _ + visit_subkeys_in_ri_list subkeys_top (len-1) rest in + c1 + c2, max name_len1 name_len2, "" | {_} -> failwithf "%s: invalid subkey in ri list at %s\n" basename (print_offset subkeys_top) - ) else 0, 0 + ) else 0, 0, "" + +and check_hash offset newstyle_hash hash name + if not newstyle_hash then ( + (* Old-style lf record hash the first four bytes of the name + * as the has. + *) + let len = String.length name in + let name_bits + if len >= 4 then + bitstring_of_string (String.sub name 0 4) + else ( + let zeroes = zeroes_bitstring ((4-len)*8) in + concat [bitstring_of_string name; zeroes] + ) in + if not (equals hash name_bits) then + eprintf "LF incorrect hash for name %s, expected %s, actual %s\n" + name (print_bitstring name_bits) (print_bitstring hash) + ) else ( + (* New-style lh record has a proper hash. *) + let actual = bitmatch hash with { hash : 4*8 : littleendian } -> hash in + let h = ref 0_l in + String.iter ( + fun c -> + h := Int32.mul !h 37_l; + h := Int32.add !h (Int32.of_int (Char.code (Char.uppercase c))) + ) name; + if actual <> !h then + eprintf "LH incorrect hash for name %s, expected 0x%08lx, actual 0x%08lx\n" + name !h actual + ) -and name_len_of_nk nk - let (_, _, bits) = lookup "name_len_of_nk" nk in +and name_of_nk nk + let (_, _, bits) = lookup "name_of_nk" nk in bitmatch bits with - | { :nk_fields } -> name_len + | { :nk_fields } -> name, name_len and visit_sk sk let (_, _, bits) = lookup "visit_sk" sk in -- 1.6.5.2
Richard Jones
2010-Feb-05 13:07 UTC
[Libguestfs] [PATCH 10/14] hivex: More debugging around nk 'unknown2' field.
--- hivex/tools/visualizer.ml | 36 ++++++++++++++++++++++++++---------- 1 files changed, 26 insertions(+), 10 deletions(-) diff --git a/hivex/tools/visualizer.ml b/hivex/tools/visualizer.ml index bc447b7..d4a339e 100644 --- a/hivex/tools/visualizer.ml +++ b/hivex/tools/visualizer.ml @@ -408,19 +408,27 @@ let bitmatch nk_fields classname : 4*8 : littleendian, bind (get_offset classname); (* sentinelchicken.com says this is a single 32 bit field * containing maximum number of bytes in a subkey name, however - * that does not seem to be correct. We think it is two 16 bit + * that does not seem to be correct. We think it is several * fields, the first being the maximum number of bytes in the * UTF16-LE encoded version of the subkey names, (since subkey * names are usually ASCII, that would be max length of names * 2). * This is a historical maximum, so it can be greater than the * current maximum name field. * - * The second field is often non-zero, but the purpose is unknown. - * In the hives we examined it had values 0, 1, 0x20, 0x21, 0xa0, - * 0xa1, 0xe1, suggesting some sort of flags. + * The remaining fields are often non-zero, but the purpose is + * unknown. + * + * In the hives we examined the other fields had values as + * follows: + * userflags: 0, 2, 0xa, 0xe + * virtcontrolflags: 0, 1 + * debug: always 0 *) max_subkey_name_len : 2*8 : littleendian; - unknown2 : 2*8 : littleendian; + unknown2_userflags : 4; + unknown2_virtcontrolflags : 4; + unknown2_debug : 8; + (* sentinelchicken.com says: maximum subkey CLASSNAME length, * however that does not seem to be correct. In hives I looked * at, it has value 0, 0xc, 0x10, 0x18, 0x1a, 0x28. @@ -453,7 +461,7 @@ let fprintf_nk chan nk bitmatch bits with | { :nk_fields } -> fprintf chan - "NK %s %s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s %s %08lx %s %d %ld %s %08lx %d %s %s %s %d %04x %08lx %d %d %08lx %d %d %s\n" + "NK %s %s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s %s %08lx %s %d %ld %s %08lx %d %s %s %s %d %x %x %x %08lx %d %d %08lx %d %d %s\n" (print_offset nk) (if unknownflag8000 then "8" else ".") (if unknownflag4000 then "4" else ".") @@ -476,8 +484,9 @@ let fprintf_nk chan nk (print_offset subkeys) subkeys_vol nr_values (print_offset vallist) (print_offset sk) (print_offset classname) - max_subkey_name_len unknown2 unknown3 - max_vk_name_len max_vk_data_len unknown6 + max_subkey_name_len + unknown2_userflags unknown2_virtcontrolflags unknown2_debug + unknown3 max_vk_name_len max_vk_data_len unknown6 name_len classname_len name type data_t = Inline of bitstring | Offset of int @@ -601,8 +610,15 @@ let rec visit_nk ?(nk_is_root = false) nk eprintf "NK %s unknownflag0400 is set\n" (print_offset nk); if unknown1 <> 0_l then eprintf "NK %s unknown1 <> 0 (%08lx)\n" (print_offset nk) unknown1; - if unknown2 <> 0 then - eprintf "NK %s unknown2 <> 0 (%04x)\n" (print_offset nk) unknown2; + if unknown2_userflags <> 0 then + eprintf "NK %s unknown2_userflags <> 0 (%x)\n" + (print_offset nk) unknown2_userflags; + if unknown2_virtcontrolflags <> 0 then + eprintf "NK %s unknown2_virtcontrolflags <> 0 (%x)\n" + (print_offset nk) unknown2_virtcontrolflags; + if unknown2_debug <> 0 then + eprintf "NK %s unknown2_debug <> 0 (%x)\n" + (print_offset nk) unknown2_debug; if unknown3 <> 0_l then eprintf "NK %s unknown3 <> 0 (%08lx)\n" (print_offset nk) unknown3; if unknown6 <> 0_l then -- 1.6.5.2
Richard Jones
2010-Feb-05 13:07 UTC
[Libguestfs] [PATCH 11/14] hivex: Complete the implementation of adding child nodes.
--- hivex/example5 | 54 ++++++++++ hivex/hivex.c | 287 +++++++++++++++++++++++++++++++++++++++++++++++++++- hivex/hivex.pod | 19 +++- hivex/hivexsh.c | 16 +++- hivex/hivexsh.pod | 14 +++ 5 files changed, 378 insertions(+), 12 deletions(-) create mode 100755 hivex/example5 diff --git a/hivex/example5 b/hivex/example5 new file mode 100755 index 0000000..ccf711c --- /dev/null +++ b/hivex/example5 @@ -0,0 +1,54 @@ +#!/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 script adds a new node under \Microsoft in an existing 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 +add TestNode +cd TestNode +add Test1 +add Test2 +add Test3 +add Test4 +add Test5 +cd Test1 +setval 2 +@ +string:This is the default key of Test1 +ThisIsTest1 +dword:0x12345678 +cd .. +cd Test5 +setval 2 +@ +string:This is the default key of Test5 +ThisIsTest5 +dword:0x87654321 +commit $2 +EOF \ No newline at end of file diff --git a/hivex/hivex.c b/hivex/hivex.c index 2a094b8..148d837 100644 --- a/hivex/hivex.c +++ b/hivex/hivex.c @@ -34,6 +34,7 @@ #include <sys/stat.h> #include <assert.h> +#include "c-ctype.h" #include "full-read.h" #include "full-write.h" @@ -49,7 +50,7 @@ //#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" @@ -1987,7 +1988,117 @@ hivex_commit (hive_h *h, const char *filename, int flags) return 0; } -#if 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) { @@ -1998,21 +2109,185 @@ hivex_node_add_child (hive_h *h, hive_node_h parent, const char *name) if (!IS_VALID_BLOCK (h, parent) || !BLOCK_ID_EQ (h, parent, "nk")) { errno = EINVAL; - return -1; + return 0; } - if (name == NULL) { + if (name == NULL || strlen (name) == 0) { errno = EINVAL; - return -1; + 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; } -#endif /* Decrement the refcount of an sk-record, and if it reaches zero, * unlink it from the chain and delete it. diff --git a/hivex/hivex.pod b/hivex/hivex.pod index 34ff253..275eb42 100644 --- a/hivex/hivex.pod +++ b/hivex/hivex.pod @@ -383,9 +383,12 @@ C<hivex_close> after this. 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. +values. The sk-record (security descriptor) is inherited from +the parent. + +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. @@ -460,8 +463,10 @@ Modifying or deleting single values at a node. =item * -Modifying security key (sk) records or classnames. These are not -well understood. +Modifying security key (sk) records or classnames. +Previously we did not understand these records. However now they +are well-understood and we could add support if it was required +(but nothing much really uses them). =back @@ -591,6 +596,10 @@ Registry contains cycles. Field in the registry out of range. +=item EEXIST + +Registry key already exists. + =item EROFS Tried to write to a registry which is not opened for writing. diff --git a/hivex/hivexsh.c b/hivex/hivexsh.c index d5d9ada..9c54536 100644 --- a/hivex/hivexsh.c +++ b/hivex/hivexsh.c @@ -76,6 +76,7 @@ static char *rl_gets (const char *prompt_string); static void sort_strings (char **strings, int len); static int get_xdigit (char c); static int dispatch (char *cmd, char *args); +static int cmd_add (char *name); static int cmd_cd (char *path); static int cmd_close (char *path); static int cmd_commit (char *path); @@ -411,7 +412,9 @@ dispatch (char *cmd, char *args) return -1; } - if (STRCASEEQ (cmd, "cd")) + if (STRCASEEQ (cmd, "add")) + return cmd_add (args); + else if (STRCASEEQ (cmd, "cd")) return cmd_cd (args); else if (STRCASEEQ (cmd, "close") || STRCASEEQ (cmd, "unload")) return cmd_close (args); @@ -1077,3 +1080,14 @@ cmd_del (char *args) set_prompt_string (); return 0; } + +static int +cmd_add (char *name) +{ + hive_node_h node = hivex_node_add_child (h, cwd, name); + if (node == 0) { + perror ("hivexsh: add"); + return -1; + } + return 0; +} diff --git a/hivex/hivexsh.pod b/hivex/hivexsh.pod index 9336798..a31d9e0 100644 --- a/hivex/hivexsh.pod +++ b/hivex/hivexsh.pod @@ -79,6 +79,20 @@ If this option is not given, then write commands are disabled. =over 4 +=item B<add> name + +Add a subkey named C<name> below the current node. The name may +contain spaces and punctuation characters, and does not need to be +quoted. + +The new key will have no subkeys and no values (see C<setval>). + +There must be no existing subkey called C<name>, or this command will +fail. To replace an existing subkey, delete it first like this: + + cd name + del + =item B<cd> path Change to the subkey C<path>. Use Windows-style backslashes to -- 1.6.5.2
Richard Jones
2010-Feb-05 13:07 UTC
[Libguestfs] [PATCH 12/14] hivexsh: Remove unused variable.
This removes an unused variable left over by commit ab608f3948d903af64e814b2e67949a1a71d93a4. --- hivex/hivexsh.c | 1 - 1 files changed, 0 insertions(+), 1 deletions(-) diff --git a/hivex/hivexsh.c b/hivex/hivexsh.c index 9c54536..00f33ae 100644 --- a/hivex/hivexsh.c +++ b/hivex/hivexsh.c @@ -858,7 +858,6 @@ cmd_setval (char *nrvals_str) /* Read nrvals * 2 lines of input, nrvals * (key, value) pairs, as * explained in the man page. */ - int prompt = isatty (0) ? 2 : 0; int i, j; for (i = 0; i < nrvals; ++i) { /* Read key. */ -- 1.6.5.2
Richard Jones
2010-Feb-05 13:07 UTC
[Libguestfs] [PATCH 13/14] hivex: Make limits into macros.
--- hivex/hivex.c | 14 ++++++++++---- hivex/hivexsh.c | 4 +++- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/hivex/hivex.c b/hivex/hivex.c index 148d837..5da50ea 100644 --- a/hivex/hivex.c +++ b/hivex/hivex.c @@ -55,6 +55,12 @@ #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 { @@ -719,7 +725,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; } @@ -989,7 +995,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; } @@ -1188,7 +1194,7 @@ hivex_value_value (hive_h *h, hive_value_h value, *len_rtn = len; /* Arbitrarily limit the length that we will read. */ - if (len > 1000000) { + if (len > HIVEX_MAX_VALUE_LEN) { errno = ERANGE; return NULL; } @@ -1812,7 +1818,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); diff --git a/hivex/hivexsh.c b/hivex/hivexsh.c index 00f33ae..6d83f0d 100644 --- a/hivex/hivexsh.c +++ b/hivex/hivexsh.c @@ -58,6 +58,8 @@ #include "hivex.h" #include "byte_conversions.h" +#define HIVEX_MAX_VALUES 1000 + static int quit = 0; static int is_tty; static hive_h *h = NULL; @@ -840,7 +842,7 @@ cmd_setval (char *nrvals_str) "setval", "nrvals", "xstrtol", xerr); return -1; } - if (nrvals < 0 || nrvals > 1000) { + if (nrvals < 0 || nrvals > HIVEX_MAX_VALUES) { fprintf (stderr, _("%s: %s: integer out of range\n"), "setval", "nrvals"); return -1; -- 1.6.5.2
Richard Jones
2010-Feb-05 13:07 UTC
[Libguestfs] [PATCH 14/14] hivex: Documentation and cleanups.
--- hivex/hivex.c | 3 ++- 1 files changed, 2 insertions(+), 1 deletions(-) diff --git a/hivex/hivex.c b/hivex/hivex.c index 5da50ea..6752da0 100644 --- a/hivex/hivex.c +++ b/hivex/hivex.c @@ -1849,7 +1849,7 @@ 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]; } @@ -2547,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); -- 1.6.5.2
Reasonably Related Threads
- [PATCH hivex 00/19] Fix read/write handling of li-records.
- [PATCH 0/2] Fix errors found by Clang static analyzer
- -Woverlength-strings / assert unhelpful interaction
- [PATCH hivex] maint: split long lines
- [PATCH v4 0/5] hivex: handle corrupted hives better.