Richard W.M. Jones
2010-Feb-03 18:29 UTC
[Libguestfs] [PATCH 0/12] Add support for writing to hive files
This patch series adds support for some simple operations on hive files, and I've now tested and verified that those operations work correctly. All except for the last patch (12/12) are ready to be committed. The last patch is WIP. Rich. -- Richard Jones, Virtualization Group, Red Hat http://people.redhat.com/~rjones virt-df lists disk usage of guests without needing to install any software inside the virtual machine. Supports Linux and Windows. http://et.redhat.com/~rjones/virt-df/
Richard W.M. Jones
2010-Feb-03 18:31 UTC
[Libguestfs] [PATCH 1/12] Move htole*/le*toh macros into a separate header file.
-- 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 b252683d6ddeb6976133ac33ff84c7580e5076e1 Mon Sep 17 00:00:00 2001From: Richard Jones <rjones at redhat.com> Date: Wed, 3 Feb 2010 17:41:15 +0000 Subject: [PATCH 01/12] Move htole*/le*toh macros into a separate header file. This allows us to reuse these macros in hivexsh later. --- hivex/Makefile.am | 3 +- hivex/byte_conversions.h | 89 ++++++++++++++++++++++++++++++++++++++++++++++ hivex/hivex.c | 43 +--------------------- 3 files changed, 93 insertions(+), 42 deletions(-) create mode 100644 hivex/byte_conversions.h diff --git a/hivex/Makefile.am b/hivex/Makefile.am index ae7dbac..1adbbd8 100644 --- a/hivex/Makefile.am +++ b/hivex/Makefile.am @@ -21,7 +21,8 @@ lib_LTLIBRARIES = libhivex.la libhivex_la_SOURCES = \ hivex.c \ - hivex.h + hivex.h \ + byte_conversions.h libhivex_la_LDFLAGS = -version-info 0:0:0 libhivex_la_CFLAGS = \ diff --git a/hivex/byte_conversions.h b/hivex/byte_conversions.h new file mode 100644 index 0000000..84e9e2d --- /dev/null +++ b/hivex/byte_conversions.h @@ -0,0 +1,89 @@ +/* Useful byte conversion macros, not available on all platforms. + * Copyright (C) 2009-2010 Red Hat Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License. + * + * This library 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 + * Lesser General Public License for more details. + */ + +#ifndef hivex_byteorder_h +#define hivex_byteorder_h + +#ifdef HAVE_ENDIAN_H +#include <endian.h> +#endif +#ifdef HAVE_BYTESWAP_H +#include <byteswap.h> +#endif + +#if __BYTE_ORDER == __LITTLE_ENDIAN +#ifndef be32toh +#define be32toh(x) __bswap_32 (x) +#endif +#ifndef htobe32 +#define htobe32(x) __bswap_32 (x) +#endif +#ifndef be64toh +#define be64toh(x) __bswap_64 (x) +#endif +#ifndef htobe64 +#define htobe64(x) __bswap_64 (x) +#endif +#ifndef le16toh +#define le16toh(x) (x) +#endif +#ifndef htole16 +#define htole16(x) (x) +#endif +#ifndef le32toh +#define le32toh(x) (x) +#endif +#ifndef htole32 +#define htole32(x) (x) +#endif +#ifndef le64toh +#define le64toh(x) (x) +#endif +#ifndef htole64 +#define htole64(x) (x) +#endif +#else /* __BYTE_ORDER == __BIG_ENDIAN */ +#ifndef be32toh +#define be32toh(x) (x) +#endif +#ifndef htobe32 +#define htobe32(x) (x) +#endif +#ifndef be64toh +#define be64toh(x) (x) +#endif +#ifndef htobe64 +#define htobe64(x) (x) +#endif +#ifndef le16toh +#define le16toh(x) __bswap_16 (x) +#endif +#ifndef htole16 +#define htole16(x) __bswap_16 (x) +#endif +#ifndef le32toh +#define le32toh(x) __bswap_32 (x) +#endif +#ifndef htole32 +#define htole32(x) __bswap_32 (x) +#endif +#ifndef le64toh +#define le64toh(x) __bswap_64 (x) +#endif +#ifndef htole64 +#define htole64(x) __bswap_64 (x) +#endif +#endif /* __BYTE_ORDER == __BIG_ENDIAN */ + +#endif /* hivex_byteorder_h */ diff --git a/hivex/hivex.c b/hivex/hivex.c index d2c450f..4cf3ad9 100644 --- a/hivex/hivex.c +++ b/hivex/hivex.c @@ -1,5 +1,5 @@ /* hivex - Windows Registry "hive" extraction library. - * Copyright (C) 2009 Red Hat Inc. + * Copyright (C) 2009-2010 Red Hat Inc. * Derived from code by Petter Nordahl-Hagen under a compatible license: * Copyright (c) 1997-2007 Petter Nordahl-Hagen. * Derived from code by Markus Stephany under a compatible license: @@ -33,12 +33,6 @@ #include <sys/mman.h> #include <sys/stat.h> #include <assert.h> -#ifdef HAVE_ENDIAN_H -#include <endian.h> -#endif -#ifdef HAVE_BYTESWAP_H -#include <byteswap.h> -#endif #define STREQ(a,b) (strcmp((a),(b)) == 0) #define STRCASEEQ(a,b) (strcasecmp((a),(b)) == 0) @@ -50,41 +44,8 @@ //#define STRCASENEQLEN(a,b,n) (strncasecmp((a),(b),(n)) != 0) //#define STRPREFIX(a,b) (strncmp((a),(b),strlen((b))) == 0) -#if __BYTE_ORDER == __LITTLE_ENDIAN -#ifndef be32toh -#define be32toh(x) __bswap_32 (x) -#endif -#ifndef be64toh -#define be64toh(x) __bswap_64 (x) -#endif -#ifndef le16toh -#define le16toh(x) (x) -#endif -#ifndef le32toh -#define le32toh(x) (x) -#endif -#ifndef le64toh -#define le64toh(x) (x) -#endif -#else -#ifndef be32toh -#define be32toh(x) (x) -#endif -#ifndef be64toh -#define be64toh(x) (x) -#endif -#ifndef le16toh -#define le16toh(x) __bswap_16 (x) -#endif -#ifndef le32toh -#define le32toh(x) __bswap_32 (x) -#endif -#ifndef le64toh -#define le64toh(x) __bswap_64 (x) -#endif -#endif - #include "hivex.h" +#include "byte_conversions.h" static char *windows_utf16_to_utf8 (/* const */ char *input, size_t len); -- 1.6.5.2
Richard W.M. Jones
2010-Feb-03 18:31 UTC
[Libguestfs] [PATCH 2/12] Misc documentation and gitignore update.
-- Richard Jones, Virtualization Group, Red Hat http://people.redhat.com/~rjones virt-p2v converts physical machines to virtual machines. Boot with a live CD or over the network (PXE) and turn machines into Xen guests. http://et.redhat.com/~rjones/virt-p2v -------------- next part -------------->From 91b2aea7960f783d4e79d00def91278eca84d7b0 Mon Sep 17 00:00:00 2001From: Richard Jones <rjones at redhat.com> Date: Wed, 3 Feb 2010 17:44:39 +0000 Subject: [PATCH 02/12] Misc documentation and gitignore update. --- hivex/README | 4 +++- m4/.gitignore | 1 + 2 files changed, 4 insertions(+), 1 deletions(-) diff --git a/hivex/README b/hivex/README index 5e7d21f..7bed47b 100644 --- a/hivex/README +++ b/hivex/README @@ -18,12 +18,14 @@ This library was derived from several sources: . NTREG registry reader/writer library by Petter Nordahl-Hagen (LGPL v2.1 licensed library and program) - . http://home.eunet.no/pnordahl/ntpasswd/WinReg.txt + . http://pogostick.net/~pnh/ntpasswd/WinReg.txt . dumphive (a BSD-licensed Pascal program by Markus Stephany) . http://www.sentinelchicken.com/data/TheWindowsNTRegistryFileFormat.pdf . editreg program from Samba - this program was removed in later versions of Samba, so you have to go back in the source repository to find it (GPLv2+) + . http://amnesia.gtisc.gatech.edu/~moyix/suzibandit.ltd.uk/MSc/ + . reverse engineering the format (see hivex/tools/visualizer.ml) Like NTREG, this library only attempts to read Windows NT registry files (ie. not Windows 3.1 or Windows 95/98/ME). See the link above diff --git a/m4/.gitignore b/m4/.gitignore index 01c8e2d..3f1a5a4 100644 --- a/m4/.gitignore +++ b/m4/.gitignore @@ -143,3 +143,4 @@ xsize.m4 /strtoll.m4 /strtoul.m4 /strtoull.m4 +/xstrtol.m4 -- 1.6.5.2
Richard W.M. Jones
2010-Feb-03 18:32 UTC
[Libguestfs] [PATCH 3/12] Document that this flag is clear for default keys.
-- Richard Jones, Virtualization Group, Red Hat http://people.redhat.com/~rjones virt-df lists disk usage of guests without needing to install any software inside the virtual machine. Supports Linux and Windows. http://et.redhat.com/~rjones/virt-df/ -------------- next part -------------->From 8210ca2e4acc7f0e9b7c2e56867210ef4f34faae Mon Sep 17 00:00:00 2001From: Richard Jones <rjones at redhat.com> Date: Wed, 3 Feb 2010 17:45:20 +0000 Subject: [PATCH 03/12] Document that this flag is clear for default keys. --- hivex/hivex.c | 3 ++- 1 files changed, 2 insertions(+), 1 deletions(-) diff --git a/hivex/hivex.c b/hivex/hivex.c index 4cf3ad9..e0118af 100644 --- a/hivex/hivex.c +++ b/hivex/hivex.c @@ -218,7 +218,8 @@ struct ntreg_vk_record { uint32_t data_type; /* type of the data */ uint16_t flags; /* bit 0 set => key name ASCII, bit 0 clr => key name UTF-16. - Only seen ASCII here in the wild. */ + Only seen ASCII here in the wild. + NB: this is CLEAR for default key. */ uint16_t unknown2; char name[1]; /* key name follows here */ } __attribute__((__packed__)); -- 1.6.5.2
Richard W.M. Jones
2010-Feb-03 18:32 UTC
[Libguestfs] [PATCH 4/12] hivexsh: Change handling of prompt argument to rl_gets()
-- Richard Jones, Virtualization Group, Red Hat http://people.redhat.com/~rjones virt-df lists disk usage of guests without needing to install any software inside the virtual machine. Supports Linux and Windows. http://et.redhat.com/~rjones/virt-df/ -------------- next part -------------->From 20c5d154ff71cecf379c2d2f2af7b63695c05d6b Mon Sep 17 00:00:00 2001From: Richard Jones <rjones at redhat.com> Date: Wed, 3 Feb 2010 17:48:37 +0000 Subject: [PATCH 04/12] hivexsh: Change handling of prompt argument to rl_gets() Make the result of isatty into a global variable (is_tty). Change the rl_gets() function so it takes the prompt string instead of a "display prompt?" flag. rl_gets() then consults the global to find out if it should display the prompt at all. --- hivex/hivexsh.c | 19 ++++++++++--------- 1 files changed, 10 insertions(+), 9 deletions(-) diff --git a/hivex/hivexsh.c b/hivex/hivexsh.c index 1cecaad..d7d00bb 100644 --- a/hivex/hivexsh.c +++ b/hivex/hivexsh.c @@ -57,8 +57,9 @@ #include "hivex.h" static int quit = 0; +static int is_tty; static hive_h *h = NULL; -static char *prompt_string = NULL; /* Prompt string. */ +static char *prompt_string = NULL; /* Normal prompt string. */ static char *loaded = NULL; /* Basename of loaded file, if any. */ static hive_node_h cwd; /* Current node. */ static int open_flags = 0; /* Flags used when loading a hive file. */ @@ -69,7 +70,7 @@ static void set_prompt_string (void); static void initialize_readline (void); static void cleanup_readline (void); static void add_history_line (const char *); -static char *rl_gets (int prompt); +static char *rl_gets (const char *prompt_string); static void sort_strings (char **strings, int len); static int dispatch (char *cmd, char *args); static int cmd_cd (char *path); @@ -128,10 +129,10 @@ main (int argc, char *argv[]) } /* Main loop. */ + is_tty = isatty (0); initialize_readline (); - int prompt = isatty (0); - if (prompt) + if (is_tty) printf (_( "\n" "Welcome to hivexsh, the hivex interactive shell for examining\n" @@ -142,7 +143,7 @@ main (int argc, char *argv[]) "\n")); while (!quit) { - char *buf = rl_gets (prompt); + char *buf = rl_gets (prompt_string); if (!buf) { quit = 1; printf ("\n"); @@ -192,7 +193,7 @@ main (int argc, char *argv[]) got_command: /*printf ("command: '%s' args: '%s'\n", cmd, args)*/; int r = dispatch (cmd, args); - if (!prompt && r == -1) + if (!is_tty && r == -1) exit (EXIT_FAILURE); } @@ -268,11 +269,11 @@ print_node_path (hive_node_h node, FILE *fp) static char *line_read = NULL; static char * -rl_gets (int prompt) +rl_gets (const char *prompt_string) { #ifdef HAVE_LIBREADLINE - if (prompt) { + if (is_tty) { if (line_read) { free (line_read); line_read = NULL; @@ -291,7 +292,7 @@ rl_gets (int prompt) static char buf[8192]; int len; - if (prompt) + if (is_tty) printf ("%s", prompt_string); line_read = fgets (buf, sizeof buf, stdin); -- 1.6.5.2
Richard W.M. Jones
2010-Feb-03 18:33 UTC
[Libguestfs] [PATCH 5/12] hivexsh: Only print final \n when interactive.
-- Richard Jones, Virtualization Group, Red Hat http://people.redhat.com/~rjones virt-df lists disk usage of guests without needing to install any software inside the virtual machine. Supports Linux and Windows. http://et.redhat.com/~rjones/virt-df/ -------------- next part -------------->From 305a09bfbce16cbdb6f55586a2c8cda7501aefee Mon Sep 17 00:00:00 2001From: Richard Jones <rjones at redhat.com> Date: Wed, 3 Feb 2010 17:50:51 +0000 Subject: [PATCH 05/12] hivexsh: Only print final \n when interactive. When hivexsh was called non-interactively, it would print an annoying extra line. Only print this line if we are being used interactively. --- hivex/hivexsh.c | 3 ++- 1 files changed, 2 insertions(+), 1 deletions(-) diff --git a/hivex/hivexsh.c b/hivex/hivexsh.c index d7d00bb..847954c 100644 --- a/hivex/hivexsh.c +++ b/hivex/hivexsh.c @@ -146,7 +146,8 @@ main (int argc, char *argv[]) char *buf = rl_gets (prompt_string); if (!buf) { quit = 1; - printf ("\n"); + if (is_tty) + printf ("\n"); break; } -- 1.6.5.2
Richard W.M. Jones
2010-Feb-03 18:33 UTC
[Libguestfs] [PATCH 6/12] hivexsh: Change some exit(1) -> exit(EXIT_FAILURE)
-- Richard Jones, Virtualization Group, Red Hat http://people.redhat.com/~rjones virt-df lists disk usage of guests without needing to install any software inside the virtual machine. Supports Linux and Windows. http://et.redhat.com/~rjones/virt-df/ -------------- next part -------------->From 0855b6444ae390e52eb5a48a7af32cc2d798dc27 Mon Sep 17 00:00:00 2001From: Richard Jones <rjones at redhat.com> Date: Wed, 3 Feb 2010 17:52:05 +0000 Subject: [PATCH 06/12] hivexsh: Change some exit(1) -> exit(EXIT_FAILURE) --- hivex/hivexsh.c | 4 ++-- 1 files changed, 2 insertions(+), 2 deletions(-) diff --git a/hivex/hivexsh.c b/hivex/hivexsh.c index 847954c..01a5ddc 100644 --- a/hivex/hivexsh.c +++ b/hivex/hivexsh.c @@ -220,7 +220,7 @@ set_prompt_string (void) fp = open_memstream (&ptr, &size); if (fp == NULL) { perror ("open_memstream"); - exit (1); + exit (EXIT_FAILURE); } if (h) { @@ -570,7 +570,7 @@ cmd_ls (char *args) char **names = calloc (len, sizeof (char *)); if (names == NULL) { perror ("malloc"); - exit (1); + exit (EXIT_FAILURE); } int ret = -1; -- 1.6.5.2
Richard W.M. Jones
2010-Feb-03 18:33 UTC
[Libguestfs] [PATCH 7/12] Tools for analyzing and reverse engineering hive files.
-- 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 d2f6064200473d156fa1da70e5b87267dde6b22e Mon Sep 17 00:00:00 2001From: Richard Jones <rjones at redhat.com> Date: Wed, 3 Feb 2010 17:35:53 +0000 Subject: [PATCH 07/12] Tools for analyzing and reverse engineering hive files. This commit is not of general interest. It contains the tools which I used to reverse engineer the hive format and to test changes. Keeping these with the rest of the code is useful in case in future we encounter a hive file that we fail to modify. Note that the tools are not compiled by default. You have to compile each explicitly with: make -C hivex/tools <toolname>.opt --- .gitignore | 1 + configure.ac | 1 + hivex/Makefile.am | 2 + hivex/tools/Makefile.am | 54 +++ hivex/tools/clearheaderfields.ml | 112 +++++ hivex/tools/fillemptyhbins.ml | 74 ++++ hivex/tools/truncatefile.ml | 112 +++++ hivex/tools/visualizer.ml | 845 +++++++++++++++++++++++++++++++++++++ hivex/tools/visualizer_NT_time.ml | 30 ++ hivex/tools/visualizer_utils.ml | 160 +++++++ 10 files changed, 1391 insertions(+), 0 deletions(-) create mode 100644 hivex/tools/Makefile.am create mode 100644 hivex/tools/clearheaderfields.ml create mode 100644 hivex/tools/fillemptyhbins.ml create mode 100644 hivex/tools/truncatefile.ml create mode 100644 hivex/tools/visualizer.ml create mode 100644 hivex/tools/visualizer_NT_time.ml create mode 100644 hivex/tools/visualizer_utils.ml diff --git a/.gitignore b/.gitignore index 49daccd..899e973 100644 --- a/.gitignore +++ b/.gitignore @@ -84,6 +84,7 @@ hivex/*.1 hivex/*.3 hivex/hivexsh hivex/hivexml +hivex/tools/*.opt html/guestfish.1.html html/guestfs.3.html html/guestmount.1.html diff --git a/configure.ac b/configure.ac index fda4773..134cebd 100644 --- a/configure.ac +++ b/configure.ac @@ -737,6 +737,7 @@ AC_CONFIG_FILES([Makefile gnulib/lib/Makefile gnulib/tests/Makefile hivex/Makefile + hivex/tools/Makefile fuse/Makefile ocaml/META perl/Makefile.PL]) AC_OUTPUT diff --git a/hivex/Makefile.am b/hivex/Makefile.am index 1adbbd8..5624b16 100644 --- a/hivex/Makefile.am +++ b/hivex/Makefile.am @@ -15,6 +15,8 @@ # along with this program; if not, write to the Free Software # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +SUBDIRS = tools + EXTRA_DIST = hivex.pod hivexml.pod hivexget.pod hivexsh.pod LICENSE lib_LTLIBRARIES = libhivex.la diff --git a/hivex/tools/Makefile.am b/hivex/tools/Makefile.am new file mode 100644 index 0000000..b744352 --- /dev/null +++ b/hivex/tools/Makefile.am @@ -0,0 +1,54 @@ +# libguestfs +# Copyright (C) 2009 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. + +# OCaml Windows Registry visualizer. This was used while reverse +# engineering the hive format, and is not normally compiled. If you +# do with to compile it, you'll need ocaml-bitstring-devel and +# ocaml-extlib-devel. Also you'll need a collection of hive files +# from Windows machines to experiment with. +# +# We use '-w y' (disable unused variable warnings) because these +# warnings aren't very reliable with heavily preprocessed code like +# that produced by bitstring. + +EXTRA_DIST = \ + visualizer.ml \ + visualizer_utils.ml \ + visualizer_NT_time.ml \ + clearheaderfields.ml \ + fillemptyhbins.ml \ + truncatefile.ml + +visualizer.opt: visualizer_utils.ml visualizer_NT_time.ml visualizer.ml + ocamlfind ocamlopt -w y \ + -package bitstring,bitstring.syntax,extlib \ + -syntax camlp4 -linkpkg $^ -o $@ + +fillemptyhbins.opt: fillemptyhbins.ml + ocamlfind ocamlopt -w y \ + -package bitstring,bitstring.syntax,extlib \ + -syntax camlp4 -linkpkg $^ -o $@ + +clearheaderfields.opt: visualizer_utils.ml clearheaderfields.ml + ocamlfind ocamlopt -w y \ + -package bitstring,bitstring.syntax,extlib \ + -syntax camlp4 -linkpkg $^ -o $@ + +truncatefile.opt: visualizer_utils.ml truncatefile.ml + ocamlfind ocamlopt -w y \ + -package bitstring,bitstring.syntax,extlib \ + -syntax camlp4 -linkpkg $^ -o $@ diff --git a/hivex/tools/clearheaderfields.ml b/hivex/tools/clearheaderfields.ml new file mode 100644 index 0000000..d055553 --- /dev/null +++ b/hivex/tools/clearheaderfields.ml @@ -0,0 +1,112 @@ +(* Windows Registry reverse-engineering tool. + * Copyright (C) 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., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + *) + +open Bitstring +open ExtString +open Printf +open Visualizer_utils + +let () + if Array.length Sys.argv <> 2 then ( + eprintf "Error: missing argument. +Usage: %s hivefile +" Sys.executable_name; + exit 1 + ) + +let filename = Sys.argv.(1) + +(* Load the file. *) +let bits = bitstring_of_file filename + +(* Split into header + data at the 4KB boundary. *) +let header, data = takebits (4096 * 8) bits, dropbits (4096 * 8) bits + +(* Read the header fields. *) +let seq, last_modified, major, minor, unknown1, unknown2, + root_key, end_pages, unknown3, fname + bitmatch header with + | { "regf" : 4*8 : string; + seq1 : 4*8 : littleendian; + seq2 : 4*8 : littleendian; + last_modified : 64 : bitstring; + major : 4*8 : littleendian; + minor : 4*8 : littleendian; + unknown1 : 4*8 : littleendian; + unknown2 : 4*8 : littleendian; + root_key : 4*8 : littleendian; + end_pages : 4*8 : littleendian; + unknown3 : 4*8 : littleendian; + fname : 64*8 : string; + unknownguid1 : 16*8 : bitstring; + unknownguid2 : 16*8 : bitstring; + unknown4 : 4*8 : littleendian; + unknownguid3 : 16*8 : bitstring; + unknown5 : 4*8 : string; + unknown6 : 340*8 : bitstring; + csum : 4*8 + : littleendian, save_offset_to (crc_offset), + check (assert (crc_offset = 0x1fc * 8); true); + unknown7 : (0x1000-0x200)*8 : bitstring } -> + seq1, last_modified, major, minor, unknown1, unknown2, + root_key, end_pages, unknown3, fname + | {_} -> assert false + +(* Create a new header, but with unknown fields cleared. Do it in + * two parts, first creating everything up to the checksum, then + * calculating the checksum and appending checksum and the final + * field. + *) +let header + let zeroguid = zeroes_bitstring (16*8) in + let before_csum + BITSTRING { + "regf" : 4*8 : string; + seq : 4*8 : littleendian; + seq : 4*8 : littleendian; + last_modified : 64 : bitstring; + major : 4*8 : littleendian; + minor : 4*8 : littleendian; + unknown1 : 4*8 : littleendian; + unknown2 : 4*8 : littleendian; + root_key : 4*8 : littleendian; + end_pages : 4*8 : littleendian; + unknown3 : 4*8 : littleendian; + fname : 64*8 : string; + zeroguid : 16*8 : bitstring; + zeroguid : 16*8 : bitstring; + 0_l : 4*8 : littleendian; + zeroguid : 16*8 : bitstring; + 0_l : 4*8 : littleendian; + zeroes_bitstring (340*8) : 340*8 : bitstring + } in + assert (bitstring_length before_csum = 0x1fc * 8); + let csum = bitstring_fold_left_int32_le Int32.logxor 0_l before_csum in + let csum_and_after + BITSTRING { + csum : 4*8 : littleendian; + zeroes_bitstring ((0x1000-0x200)*8) : (0x1000-0x200)*8 : bitstring + } in + let new_header = concat [before_csum; csum_and_after] in + assert (bitstring_length header = bitstring_length new_header); + new_header + +(* Write it. *) +let () + let file = concat [header; data] in + bitstring_to_file file filename diff --git a/hivex/tools/fillemptyhbins.ml b/hivex/tools/fillemptyhbins.ml new file mode 100644 index 0000000..14eae96 --- /dev/null +++ b/hivex/tools/fillemptyhbins.ml @@ -0,0 +1,74 @@ +(* Windows Registry reverse-engineering tool. + * Copyright (C) 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., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + *) + +open Bitstring +open ExtString +open Printf + +let () + if Array.length Sys.argv <> 3 then ( + eprintf "Error: missing argument. +Usage: %s hivefile startoffset +" Sys.executable_name; + exit 1 + ) + +let filename = Sys.argv.(1) +let offset = int_of_string Sys.argv.(2) + +(* Load the file. *) +let bits = bitstring_of_file filename + +(* Split into header + data at the 4KB boundary. *) +let header, data = takebits (4096 * 8) bits, dropbits (4096 * 8) bits + +(* Overwrite everything after @offset, so ... *) +let nrpages = (bitstring_length data / 8 - offset) / 4096 +let data = takebits (offset * 8) data + +(* Create the empty pages. They're not all the same because each + * page contains its own page_offset. + *) +let pages + let noblock + let seg_len = 4096 - 32 in + let zeroes = zeroes_bitstring ((seg_len - 4) * 8) in + BITSTRING { + Int32.of_int seg_len : 4*8 : littleendian; + zeroes : (seg_len - 4) * 8 : bitstring + } in + let zeroes = zeroes_bitstring (20*8) in + let rec loop page_offset i + if i < nrpages then ( + let page + BITSTRING { + "hbin" : 4*8 : string; + Int32.of_int page_offset : 4*8 : littleendian; + 4096_l : 4*8 : littleendian; (* page length *) + zeroes : 20*8 : bitstring; + noblock : (4096 - 32) * 8 : bitstring + } in + page :: loop (page_offset + 4096) (i+1) + ) else [] + in + loop offset 0 + +(* Write it. *) +let () + let file = concat (header :: data :: pages) in + bitstring_to_file file filename diff --git a/hivex/tools/truncatefile.ml b/hivex/tools/truncatefile.ml new file mode 100644 index 0000000..b519f7a --- /dev/null +++ b/hivex/tools/truncatefile.ml @@ -0,0 +1,112 @@ +(* Windows Registry reverse-engineering tool. + * Copyright (C) 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., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + *) + +open Bitstring +open ExtString +open Printf +open Visualizer_utils + +let () + if Array.length Sys.argv <> 3 then ( + eprintf "Error: missing argument. +Usage: %s hivefile endpages +" Sys.executable_name; + exit 1 + ) + +let filename = Sys.argv.(1) +let new_end_pages = int_of_string Sys.argv.(2) + +(* Load the file. *) +let bits = bitstring_of_file filename + +(* Split into header + data at the 4KB boundary. *) +let header, data = takebits (4096 * 8) bits, dropbits (4096 * 8) bits + +(* Truncate the file data. *) +let data = takebits (new_end_pages * 8) data + +(* Read the header fields. *) +let seq, last_modified, major, minor, unknown1, unknown2, + root_key, end_pages, unknown3, fname + bitmatch header with + | { "regf" : 4*8 : string; + seq1 : 4*8 : littleendian; + seq2 : 4*8 : littleendian; + last_modified : 64 : bitstring; + major : 4*8 : littleendian; + minor : 4*8 : littleendian; + unknown1 : 4*8 : littleendian; + unknown2 : 4*8 : littleendian; + root_key : 4*8 : littleendian; + end_pages : 4*8 : littleendian; + unknown3 : 4*8 : littleendian; + fname : 64*8 : string; + unknownguid1 : 16*8 : bitstring; + unknownguid2 : 16*8 : bitstring; + unknown4 : 4*8 : littleendian; + unknownguid3 : 16*8 : bitstring; + unknown5 : 4*8 : string; + unknown6 : 340*8 : bitstring; + csum : 4*8 + : littleendian, save_offset_to (crc_offset), + check (assert (crc_offset = 0x1fc * 8); true); + unknown7 : (0x1000-0x200)*8 : bitstring } -> + seq1, last_modified, major, minor, unknown1, unknown2, + root_key, end_pages, unknown3, fname + | {_} -> assert false + +(* Create a new header, with endpages updated. *) +let header + let zeroguid = zeroes_bitstring (16*8) in + let before_csum + BITSTRING { + "regf" : 4*8 : string; + seq : 4*8 : littleendian; + seq : 4*8 : littleendian; + last_modified : 64 : bitstring; + major : 4*8 : littleendian; + minor : 4*8 : littleendian; + unknown1 : 4*8 : littleendian; + unknown2 : 4*8 : littleendian; + root_key : 4*8 : littleendian; + Int32.of_int new_end_pages : 4*8 : littleendian; + unknown3 : 4*8 : littleendian; + fname : 64*8 : string; + zeroguid : 16*8 : bitstring; + zeroguid : 16*8 : bitstring; + 0_l : 4*8 : littleendian; + zeroguid : 16*8 : bitstring; + 0_l : 4*8 : littleendian; + zeroes_bitstring (340*8) : 340*8 : bitstring + } in + assert (bitstring_length before_csum = 0x1fc * 8); + let csum = bitstring_fold_left_int32_le Int32.logxor 0_l before_csum in + let csum_and_after + BITSTRING { + csum : 4*8 : littleendian; + zeroes_bitstring ((0x1000-0x200)*8) : (0x1000-0x200)*8 : bitstring + } in + let new_header = concat [before_csum; csum_and_after] in + assert (bitstring_length header = bitstring_length new_header); + new_header + +(* Write it. *) +let () + let file = concat [header; data] in + bitstring_to_file file filename diff --git a/hivex/tools/visualizer.ml b/hivex/tools/visualizer.ml new file mode 100644 index 0000000..437b90d --- /dev/null +++ b/hivex/tools/visualizer.ml @@ -0,0 +1,845 @@ +(* Windows Registry reverse-engineering tool. + * Copyright (C) 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., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * For existing information on the registry format, please refer + * to the following documents. Note they are both incomplete + * and inaccurate in some respects. + * + * http://www.sentinelchicken.com/data/TheWindowsNTRegistryFileFormat.pdf + * http://pogostick.net/~pnh/ntpasswd/WinReg.txt + *) + +open Bitstring +open ExtString +open Printf +open Visualizer_utils +open Visualizer_NT_time + +let () + if Array.length Sys.argv <> 2 then ( + eprintf "Error: missing argument. +Usage: %s hivefile > out +where + 'hivefile' is the input hive file from a Windows machine + 'out' is an output file where we will write all the keys, + values etc for extended debugging purposes. +Errors, inconsistencies and unexpected fields in the hive file +are written to stderr. +" Sys.executable_name; + exit 1 + ) + +let filename = Sys.argv.(1) +let basename = Filename.basename filename + +(* Load the file. *) +let bits = bitstring_of_file filename + +(* Split into header + data at the 4KB boundary. *) +let header, data = takebits (4096 * 8) bits, dropbits (4096 * 8) bits + +(* Define a persistent pattern which matches the header fields. By + * using persistent patterns, we can reuse them later in the + * program. + *) +let bitmatch header_fields + { "regf" : 4*8 : string; + seq1 : 4*8 : littleendian; + seq2 : 4*8 : littleendian; + last_modified : 64 + : littleendian, bind (nt_to_time_t last_modified); + major : 4*8 : littleendian; + minor : 4*8 : littleendian; + + (* "Type". Contains 0. *) + unknown1 : 4*8 : littleendian; + + (* "Format". Contains 1. *) + unknown2 : 4*8 : littleendian; + + root_key : 4*8 + : littleendian, bind (get_offset root_key); + end_pages : 4*8 + : littleendian, bind (get_offset end_pages); + + (* "Cluster". Contains 1. *) + unknown3 : 4*8 : littleendian; + + filename : 64*8 : string; + + (* All three GUIDs here confirmed in Windows 7 registries. In + * Windows <= 2003 these GUID fields seem to contain junk. + * + * If you write zeroes to the GUID fields, load and unload in Win7 + * REGEDIT, then Windows 7 writes some random GUIDs. + * + * Also (on Win7) unknownguid1 == unknownguid2. unknownguid3 is + * different. + *) + unknownguid1 : 16*8 : bitstring; + unknownguid2 : 16*8 : bitstring; + + (* Wrote zero to unknown4, loaded and unloaded it in Win7 REGEDIT, + * and it still contained zero. In existing registries it seems to + * contain random junk. + *) + unknown4 : 4*8 : littleendian; + unknownguid3 : 16*8 : bitstring; + + (* If you write zero to unknown5, load and unload it in REGEDIT, + * Windows 7 puts the string "rmtm" here. Existing registries also + * seen containing this string. However on older Windows it can + * be all zeroes. + *) + unknown5 : 4*8 : string; + + (* This seems to contain junk from other parts of the registry. I + * wrote zeroes here, loaded and unloaded it in Win7 REGEDIT, and + * it still contained zeroes. + *) + unknown6 : 340*8 : bitstring; + csum : 4*8 + : littleendian, save_offset_to (crc_offset), + check (assert (crc_offset = 0x1fc * 8); true); + unknown7 : (0x1000-0x200)*8 : bitstring } + +let fprintf_header chan bits + bitmatch bits with + | { :header_fields } -> + fprintf chan + "HD %6ld %6ld %s %ld.%ld %08lx %08lx %s %s %08lx %s %s %s %08lx %s %s %s %08lx %s\n" + seq1 seq2 (print_time last_modified) major minor + unknown1 unknown2 + (print_offset root_key) (print_offset end_pages) + unknown3 (print_utf16 filename) + (print_guid unknownguid1) (print_guid unknownguid2) + unknown4 (print_guid unknownguid3) unknown5 + (print_bitstring unknown6) + csum (print_bitstring unknown7) + +(* Parse the header and check it. *) +let root_key, end_pages + bitmatch header with + | { :header_fields } -> + fprintf_header stdout header; + + if major <> 1_l then + eprintf "HD hive file major <> 1 (major.minor = %ld.%ld)\n" + major minor; + if seq1 <> seq2 then + eprintf "HD hive file sequence numbers should match (%ld <> %ld)\n" + seq1 seq2; + if unknown1 <> 0_l then + eprintf "HD unknown1 field <> 0 (%08lx)\n" unknown1; + if unknown2 <> 1_l then + eprintf "HD unknown2 field <> 1 (%08lx)\n" unknown2; + if unknown3 <> 1_l then + eprintf "HD unknown3 field <> 1 (%08lx)\n" unknown3; + if not (equals unknownguid1 unknownguid2) then + eprintf "HD unknownguid1 <> unknownguid2 (%s, %s)\n" + (print_guid unknownguid1) (print_guid unknownguid2); + (* We think this is junk. + if unknown4 <> 0_l then + eprintf "HD unknown4 field <> 0 (%08lx)\n" unknown4; + *) + if unknown5 <> "rmtm" && unknown5 <> "\000\000\000\000" then + eprintf "HD unknown5 field <> \"rmtm\" & <> zeroes (%s)\n" unknown5; + (* We think this is junk. + if not (is_zero_bitstring unknown6) then + eprintf "HD unknown6 area is not zero (%s)\n" + (print_bitstring unknown6); + *) + if not (is_zero_bitstring unknown7) then + eprintf "HD unknown7 area is not zero (%s)\n" + (print_bitstring unknown7); + + root_key, end_pages + | {_} -> + failwithf "%s: this doesn't look like a registry hive file\n" basename + +(* Define persistent patterns to match page and block fields. *) +let bitmatch page_fields + { "hbin" : 4*8 : string; + page_offset : 4*8 + : littleendian, bind (get_offset page_offset); + page_size : 4*8 + : littleendian, check (Int32.rem page_size 4096_l = 0_l), + bind (Int32.to_int page_size); + + (* In the first hbin in the file these fields contain something. + * In subsequent hbins these fields are all zero. + * + * From existing hives (first hbin only): + * + * unknown1 unknown2 unknown5 + * 00 00 00 00 00 00 00 00 9C 77 3B 02 6A 7D CA 01 00 00 00 00 + * 00 00 00 00 00 00 00 00 50 3A 15 07 B5 9B CA 01 00 00 00 00 + * 00 00 00 00 00 00 00 00 57 86 90 D4 9A 58 CA 01 00 00 00 00 + * 00 00 00 00 00 00 00 00 52 3F 90 9D CF 7C CA 01 00 00 00 00 + * 00 00 00 00 00 00 00 00 E8 86 C1 17 BD 06 CA 01 00 00 00 00 + * 00 00 00 00 00 00 00 00 4A 77 CE 7A CF 7C CA 01 00 00 00 00 + * 00 00 00 00 00 00 00 00 E4 EA 23 FF 69 7D CA 01 00 00 00 00 + * 00 00 00 00 00 00 00 00 50 13 BA 8D A2 9A CA 01 00 00 00 00 + * 00 00 00 00 00 00 00 00 0E 07 93 13 BD 06 CA 01 00 00 00 00 + * 00 00 00 00 00 00 00 00 9D 55 D0 B3 99 58 CA 01 00 00 00 00 + * 00 00 00 00 00 00 00 00 46 AC FF 8B CF 7C CA 01 00 00 00 00 + * 00 00 00 00 00 00 00 00 80 29 2D 02 6A 7D CA 01 00 00 00 00 + * 00 00 00 00 00 00 00 00 90 8D 36 07 B5 9B CA 01 00 00 00 00 + * 00 00 00 00 00 00 00 00 5C 9B 8B B8 6A 06 CA 01 00 00 00 00 + * 00 00 00 00 00 00 00 00 85 9F BB 99 9A 58 CA 01 00 00 00 00 + * 00 00 00 00 00 00 00 00 BE 3D 21 02 6A 7D CA 01 00 00 00 00 + * 00 00 00 00 00 00 00 00 70 53 09 07 B5 9B CA 01 00 00 00 00 + * 00 00 00 00 00 00 00 00 5B 62 42 B6 9A 58 CA 01 00 00 00 00 + * 01 00 00 00 00 00 00 00 B2 46 9B 9E CF 7C CA 01 00 00 00 00 + * 01 00 00 00 00 00 00 00 CA 88 EE 1A BD 06 CA 01 00 00 00 00 + * + * From the above we worked out that fields 3 and 4 are an NT + * timestamp, which seems to be "last modified" (when REGEDIT + * unloads a hive it updates this timestamp even if nothing + * has been changed). + *) + unknown1 : 4*8 : littleendian; (* usually zero, occasionally 1 *) + unknown2 : 4*8 : littleendian; (* always zero? *) + last_modified : 64 + : littleendian, + bind (if page_offset = 0 then nt_to_time_t last_modified + else ( + assert (last_modified = 0_L); + 0. + ) + ); + (* The "B.D." document said this field contains the page size, but + * this is not true. This misinformation has been copied to the + * sentinelchicken documentation too. + *) + unknown5 : 4*8 : littleendian; (* always zero? *) + + (* Now the blocks in this page follow. *) + blocks : (page_size - 32) * 8 : bitstring; + + rest : -1 : bitstring } + +let fprintf_page chan bits + bitmatch bits with + | { :page_fields } -> + fprintf chan "HB %s %08x %08lx %08lx %s %08lx\n" + (print_offset page_offset) + page_size unknown1 unknown2 + (if page_offset = 0 then print_time last_modified + else string_of_float last_modified) unknown5 + +let bitmatch block_fields + { seg_len : 4*8 + : littleendian, bind (Int32.to_int seg_len); + block_data : (abs seg_len - 4) * 8 : bitstring; + rest : -1 : bitstring } + +let fprintf_block chan block_offset bits + bitmatch bits with + | { :block_fields } -> + fprintf chan "BL %s %s %d\n" + (print_offset block_offset) + (if seg_len < 0 then "used" else "free") + (if seg_len < 0 then -seg_len else seg_len) + +(* Iterate over the pages and blocks. In the process we will examine + * each page (hbin) header. Also we will build block_list which is a + * list of (block offset, length, used flag, data). + *) +let block_list = ref [] +let () + let rec loop_over_pages data data_offset + if data_offset < end_pages then ( + bitmatch data with + | { rest : -1 : bitstring } when bitstring_length rest = 0 -> () + + | { :page_fields } -> + fprintf_page stdout data; + + assert (page_offset = data_offset); + + if data_offset = 0 then ( (* first hbin only *) + if unknown1 <> 0_l then + eprintf "HB %s unknown1 field <> 0 (%08lx)\n" + (print_offset page_offset) unknown1; + if unknown2 <> 0_l then + eprintf "HB %s unknown2 field <> 0 (%08lx)\n" + (print_offset page_offset) unknown2; + if unknown5 <> 0_l then + eprintf "HB %s unknown5 field <> 0 (%08lx)\n" + (print_offset page_offset) unknown5 + ) else ( (* subsequent hbins *) + if unknown1 <> 0_l || unknown2 <> 0_l || unknown5 <> 0_l then + eprintf "HB %s unknown fields <> 0 (%08lx %08lx %08lx)\n" + (print_offset page_offset) + unknown1 unknown2 unknown5; + if last_modified <> 0. then + eprintf "HB %s last_modified <> 0. (%g)\n" + (print_offset page_offset) last_modified + ); + + (* Loop over the blocks in this page. *) + loop_over_blocks blocks (data_offset + 32); + + (* Loop over rest of the pages. *) + loop_over_pages rest (data_offset + page_size) + + | {_} -> + failwithf "%s: invalid hbin at offset %s\n" + basename (print_offset data_offset) + ) else ( + (* Reached the end of the official hbins in this file, BUT the + * file can be larger than this and might contain stuff. What + * does it contain after the hbins? We think just junk, but + * we're not sure. + *) + if not (is_zero_bitstring data) then ( + eprintf "Junk in file after end of pages:\n"; + let rec loop data data_offset + bitmatch data with + | { rest : -1 : bitstring } when bitstring_length rest = 0 -> () + | { :page_fields } -> + eprintf "\tjunk hbin %s 0x%08x\n" + (print_offset data_offset) page_size; + loop rest (data_offset + page_size); + | { _ } -> + eprintf "\tother junk %s %s\n" + (print_offset data_offset) (print_bitstring data) + in + loop data data_offset + ) + ) + and loop_over_blocks blocks block_offset + bitmatch blocks with + | { rest : -1 : bitstring } when bitstring_length rest = 0 -> () + + | { :block_fields } -> + assert (block_offset mod 8 = 0); + + fprintf_block stdout block_offset blocks; + + let used, seg_len + if seg_len < 0 then true, -seg_len else false, seg_len in + + let block = block_offset, (seg_len, used, block_data) in + block_list := block :: !block_list; + + (* Loop over the rest of the blocks in this page. *) + loop_over_blocks rest (block_offset + seg_len) + + | {_} -> + failwithf "%s: invalid block near offset %s\n" + basename (print_offset block_offset) + in + loop_over_pages data 0 + +(* Turn the block_list into a map so we can quickly look up a block + * from its offset. + *) +let block_list = !block_list +let block_map + List.fold_left ( + fun map (block_offset, block) -> IntMap.add block_offset block map + ) IntMap.empty block_list +let lookup fn offset + try + let (_, used, _) as block = IntMap.find offset block_map in + if not used then + failwithf "%s: %s: lookup: free block %s referenced from hive tree" + basename fn (print_offset offset); + block + with Not_found -> + failwithf "%s: %s: lookup: unknown block %s referenced from hive tree" + basename fn (print_offset offset) + +(* Use this to mark blocks that we've visited. If the hive contains + * no unreferenced blocks, then by the end this should just contain + * free blocks. + *) +let mark_visited, is_not_visited, unvisited_blocks + let v = ref block_map in + let mark_visited offset = v := IntMap.remove offset !v + and is_not_visited offset = IntMap.mem offset !v + and unvisited_blocks () = !v in + mark_visited, is_not_visited, unvisited_blocks + +(* Define persistent patterns to match nk-records, vk-records and + * sk-records, which are the record types that we especially want to + * analyze later. Other blocks types (eg. value lists, lf-records) + * have no "spare space" so everything is known about them and we don't + * store these. + *) +let bitmatch nk_fields + { "nk" : 2*8 : string; + (* Flags stored in the file as a little endian word, hence the + * unusual ordering: + *) + virtmirrored : 1; + predefinedhandle : 1; keynameascii : 1; symlinkkey : 1; + cannotbedeleted : 1; isroot : 1; ismountpoint : 1; isvolatile : 1; + unknownflag8000 : 1; unknownflag4000 : 1; + unknownflag2000 : 1; unknownflag1000 : 1; + unknownflag0800 : 1; unknownflag0400 : 1; + virtualstore : 1; virttarget : 1; + timestamp : 64 : littleendian, bind (nt_to_time_t timestamp); + unknown1 : 4*8 : littleendian; + parent : 4*8 : littleendian, bind (get_offset parent); + nr_subkeys : 4*8 : littleendian, bind (Int32.to_int nr_subkeys); + nr_subkeys_vol : 4*8; + subkeys : 4*8 : littleendian, bind (get_offset subkeys); + subkeys_vol : 4*8; + nr_values : 4*8 : littleendian, bind (Int32.to_int nr_values); + vallist : 4*8 : littleendian, bind (get_offset vallist); + sk : 4*8 : littleendian, bind (get_offset sk); + 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 + * 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. + *) + max_subkey_name_len : 2*8 : littleendian; + unknown2 : 2*8 : littleendian; + (* 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. + *) + unknown3 : 4*8 : littleendian; + (* sentinelchicken.com says: maximum number of bytes in a value + * name, however that does not seem to be correct. We think it is + * the maximum number of bytes in the UTF16-LE encoded version of + * the value names (since value 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. + *) + max_vk_name_len : 4*8 : littleendian, bind (Int32.to_int max_vk_name_len); + (* sentinelchicken.com says: maximum value data size, and this + * agrees with my observations. It is the largest data size (not + * seg_len, but vk.data_len) for any value in this key. We think + * that this field is a historical max, so eg if a maximally sized + * value is deleted then this field is not reduced. Certainly + * max_vk_data_len >= the measured maximum in all the hives that we + * have observed. + *) + max_vk_data_len : 4*8 : littleendian, bind (Int32.to_int max_vk_data_len); + unknown6 : 4*8 : littleendian; + name_len : 2*8 : littleendian; + classname_len : 2*8 : littleendian; + name : name_len * 8 : string } + +let fprintf_nk chan nk + let (_, _, bits) = lookup "fprintf_nk" nk in + 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" + (print_offset nk) + (if unknownflag8000 then "8" else ".") + (if unknownflag4000 then "4" else ".") + (if unknownflag2000 then "2" else ".") + (if unknownflag1000 then "1" else ".") + (if unknownflag0800 then "8" else ".") + (if unknownflag0400 then "4" else ".") + (if virtualstore then "s" else ".") + (if virttarget then "t" else ".") + (if virtmirrored then "m" else ".") + (if predefinedhandle then "P" else ".") + (if keynameascii then "A" else ".") + (if symlinkkey then "S" else ".") + (if cannotbedeleted then "N" else ".") + (if isroot then "R" else ".") + (if ismountpoint then "M" else ".") + (if isvolatile then "V" else ".") + (print_time timestamp) + unknown1 (print_offset parent) nr_subkeys nr_subkeys_vol + (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 + name_len classname_len name + +type data_t = Inline of bitstring | Offset of int +let bitmatch vk_fields + { "vk" : 2*8 : string; + name_len : 2*8 : littleendian; + (* No one documents the important fact that data_len can have the + * top bit set (randomly or is it meaningful?). The length can + * also be 0 (or 0x80000000) if the data type is NONE. + *) + data_len : 4*8 + : littleendian, bind ( + let data_len = Int32.logand data_len 0x7fff_ffff_l in + Int32.to_int data_len + ); + (* Inline data if len <= 4, offset otherwise. + * + * The data itself depends on the type field. + * + * For REG_SZ type, the data always seems to be NUL-terminated, which + * means because these strings are often UTF-16LE, that the string will + * end with \0\0 bytes. The termination bytes are included in data_len. + * + * For REG_MULTI_SZ, see + * http://blogs.msdn.com/oldnewthing/archive/2009/10/08/9904646.aspx + *) + data : 4*8 + : bitstring, bind ( + if data_len <= 4 then + Inline (takebits (data_len*8) data) + else ( + let offset + bitmatch data with { offset : 4*8 : littleendian } -> offset in + let offset = get_offset offset in + Offset offset + ) + ); + t : 4*8 : littleendian, bind (Int32.to_int t); + (* Flags, stored as a little-endian word: *) + unknown1 : 7; + nameisascii : 1; (* Clear for default [zero-length] name, always set + * otherwise in registries that we found. Perhaps this + * is really "nameisdefault" flag? + *) + unknown2 : 8; + (* Unknown field, usually contains something. *) + unknown3 : 2*8 : littleendian; + name : name_len * 8 : string } + +let fprintf_vk chan vk + let (_, _, bits) = lookup "fprintf_vk" vk in + bitmatch bits with + | { :vk_fields } -> + let real_data + match data with + | Inline data -> data + | Offset offset -> + let (_, _, bits) = lookup "fprintf_vk (data)" offset in + bits in + fprintf chan "VK %s %s %d %s%s %s %08x %s %08x %08x\n" + (print_offset vk) + name data_len + (match data with + | Inline _ -> "" + | Offset offset -> "["^print_offset offset^"]") + (print_bitstring real_data) + (print_vk_type t) + unknown1 (if nameisascii then "A" else "L") + unknown2 unknown3 + +let bitmatch sk_fields + { "sk" : 2*8 : string; + unknown1 : 2*8 : littleendian; + sk_next : 4*8 : littleendian, bind (get_offset sk_next); + sk_prev : 4*8 : littleendian, bind (get_offset sk_prev); + refcount : 4*8 : littleendian, bind (Int32.to_int refcount); + sec_len : 4*8 : littleendian, bind (Int32.to_int sec_len); + sec_desc : sec_len * 8 : bitstring } + +let fprintf_sk chan sk + let (_, _, bits) = lookup "fprintf_sk" sk in + bitmatch bits with + | { :sk_fields } -> + fprintf chan "SK %s %04x %s %s %d %d\n" + (print_offset sk) unknown1 + (print_offset sk_next) (print_offset sk_prev) + refcount sec_len + (* print_bitstring sec_desc -- suppress this *) + +(* Store lists of records we encounter (lists of offsets). *) +let nk_records = ref [] +and vk_records = ref [] +and sk_records = ref [] + +(* Functions to visit each block, starting at the root. Each block + * that we visit is printed. + *) +let rec visit_nk ?(nk_is_root = false) nk + let (_, _, bits) = lookup "visit_nk" nk in + mark_visited nk; + (bitmatch bits with + | { :nk_fields } -> + fprintf_nk stdout nk; + + nk_records := nk :: !nk_records; + + (* Check the isroot flag is only set on the root node. *) + assert (isroot = nk_is_root); + + if unknownflag8000 then + eprintf "NK %s unknownflag8000 is set\n" (print_offset nk); + if unknownflag4000 then + eprintf "NK %s unknownflag4000 is set\n" (print_offset nk); + if unknownflag2000 then + eprintf "NK %s unknownflag2000 is set\n" (print_offset nk); + if unknownflag1000 then + eprintf "NK %s unknownflag1000 is set\n" (print_offset nk); + if unknownflag0800 then + eprintf "NK %s unknownflag0800 is set\n" (print_offset nk); + if unknownflag0400 then + 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 unknown3 <> 0_l then + eprintf "NK %s unknown3 <> 0 (%08lx)\n" (print_offset nk) unknown3; + if unknown6 <> 0_l then + eprintf "NK %s unknown6 <> 0 (%08lx)\n" (print_offset nk) unknown6; + + (* -- common, assume it's not an error + if classname = -1 then + eprintf "NK %s has no classname\n" (print_offset nk); + if classname_len = 0 then + eprintf "NK %s has zero-length classname\n" (print_offset nk); + *) + if sk = -1 then + eprintf "NK %s has no sk-record\n" (print_offset nk); + if name_len = 0 then + eprintf "NK %s has zero-length name\n" (print_offset nk); + + (* Visit the values first at this node. *) + let max_data_len, max_name_len + if vallist <> -1 then + visit_vallist nr_values vallist + else + 0, 0 in + + if max_vk_data_len < max_data_len then + eprintf "NK %s nk.max_vk_data_len (%d) < actual max data_len (%d)\n" + (print_offset nk) max_vk_data_len max_data_len; + + if max_vk_name_len < max_name_len * 2 then + eprintf "NK %s nk.max_vk_name_len (%d) < actual max name_len * 2 (%d)\n" + (print_offset nk) max_vk_name_len (max_name_len * 2); + + (* Visit the subkeys of this node. *) + if subkeys <> -1 then ( + 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" + basename nr_subkeys counted (print_offset subkeys); + + if max_subkey_name_len < max_name_len * 2 then + eprintf "NK %s nk.max_subkey_name_len (%d) < actual max name_len * 2 (%d)\n" + (print_offset nk) max_subkey_name_len (max_name_len * 2); + ); + + (* Visit the sk-record and classname. *) + if sk <> -1 then + visit_sk sk; + if classname <> -1 then + visit_classname classname classname_len; + + | {_} -> + failwithf "%s: invalid nk block at offset %s\n" + basename (print_offset nk) + ) + +and visit_vallist nr_values vallist + let (seg_len, _, bits) = lookup "visit_vallist" vallist in + mark_visited vallist; + printf "VL %s %d %d\n" (print_offset vallist) nr_values seg_len; + visit_values_in_vallist nr_values vallist bits + +and visit_values_in_vallist nr_values vallist bits + if nr_values > 0 then ( + bitmatch bits with + | { rest : -1 : bitstring } when bitstring_length rest = 0 -> + assert (nr_values = 0); + 0, 0 + + | { value : 4*8 : littleendian, bind (get_offset value); + rest : -1 : bitstring } -> + let data_len, name_len = visit_vk value in + let max_data_len, max_name_len + visit_values_in_vallist (nr_values-1) vallist rest in + max max_data_len data_len, max max_name_len name_len + + | {_} -> + failwithf "%s: invalid offset in value list at %s\n" + basename (print_offset vallist) + ) else 0, 0 + +and visit_vk vk + let (_, _, bits) = lookup "visit_vk" vk in + mark_visited vk; + + (bitmatch bits with + | { :vk_fields } -> + fprintf_vk stdout vk; + + if unknown1 <> 0 then + eprintf "VK %s unknown1 flags set (%02x)\n" + (print_offset vk) unknown1; + if unknown2 <> 0 then + eprintf "VK %s unknown2 flags set (%02x)\n" + (print_offset vk) unknown2; + if unknown3 <> 0 then + eprintf "VK %s unknown3 flags set (%04x)\n" + (print_offset vk) unknown3; + + (* Note this is common for default [ie. zero-length] key names. *) + if not nameisascii && name_len > 0 then + eprintf "VK %s has non-ASCII name flag set (name is %s)\n" + (print_offset vk) (print_binary_string name); + + vk_records := vk :: !vk_records; + (match data with + | Inline data -> () + | Offset offset -> + let _ = lookup "visit_vk (data)" offset in + mark_visited offset + ); + + data_len, name_len + + | {_} -> + failwithf "%s: invalid vk block at offset %s\n" + basename (print_offset vk) + ) + +(* Visits subkeys, recursing through intermediate lf/lh/ri structures, + * and returns the number of subkeys actually seen. + *) +and visit_subkeys subkeys + let (_, _, bits) = lookup "visit_subkeys" subkeys in + mark_visited subkeys; + (bitmatch bits with + | { ("lf"|"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 subkeys len rest + + | { "ri" : 2*8 : string; + len : 2*8 : littleendian; + rest : len*4*8 : bitstring } -> + printf "RI %s %d\n" (print_offset subkeys) len; + visit_subkeys_in_ri_list subkeys len rest + + (* In theory you can have an li-record here, but we've never + * seen one. + *) + + | { "nk" : 2*8 : string } -> + visit_nk subkeys; + let name_len = name_len_of_nk subkeys in + 1, name_len + + | {_} -> + failwithf "%s: invalid subkey node found at %s\n" + basename (print_offset subkeys) + ) + +and visit_subkeys_in_lf_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 + + | { offset : 4*8 : littleendian, bind (get_offset offset); + _ (* 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 + + | {_} -> + failwithf "%s: invalid subkey in lf/lh list at %s\n" + basename (print_offset subkeys_top) + ) 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 + + | { 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 + + | {_} -> + failwithf "%s: invalid subkey in ri list at %s\n" + basename (print_offset subkeys_top) + ) else 0, 0 + +and name_len_of_nk nk + let (_, _, bits) = lookup "name_len_of_nk" nk in + bitmatch bits with + | { :nk_fields } -> name_len + +and visit_sk sk + let (_, _, bits) = lookup "visit_sk" sk in + if is_not_visited sk then ( + mark_visited sk; + (bitmatch bits with + | { :sk_fields } -> + fprintf_sk stdout sk; + + if unknown1 <> 0 then + eprintf "SK %s unknown1 <> 0 (%04x)\n" (print_offset sk) unknown1; + + sk_records := sk :: !sk_records + + | {_} -> + failwithf "%s: invalid sk-record at %s\n" + basename (print_offset sk) + ) + ) + +and visit_classname classname classname_len + let (seg_len, _, bits) = lookup "visit_classname" classname in + mark_visited classname; + assert (seg_len >= classname_len); + printf "CL %s %s\n" (print_offset classname) (print_bitstring bits) + +let () + visit_nk ~nk_is_root:true root_key + +(* Now after visiting all the blocks, are there any used blocks which + * are unvisited? If there are any then that would indicate either (a) + * that the hive contains unreferenced blocks, or (b) that there are + * referenced blocks that we did not visit because we don't have a full + * understanding of the hive format. + * + * Windows 7 registries often contain a few of these -- not clear + * how serious they are, but don't fail here. + *) +let () + let unvisited = unvisited_blocks () in + IntMap.iter ( + fun offset block -> + match block with + | (_, false, _) -> () (* ignore unused blocks *) + | (seg_len, true, _) -> + eprintf "used block %s (length %d) is not referenced\n" + (print_offset offset) seg_len + ) unvisited diff --git a/hivex/tools/visualizer_NT_time.ml b/hivex/tools/visualizer_NT_time.ml new file mode 100644 index 0000000..a752112 --- /dev/null +++ b/hivex/tools/visualizer_NT_time.ml @@ -0,0 +1,30 @@ +(* Windows Registry reverse-engineering tool. + * Copyright (C) 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., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * For existing information on the registry format, please refer + * to the following documents. Note they are both incomplete + * and inaccurate in some respects. + *) + +(* Convert an NT file timestamp to time_t. See: + * http://blogs.msdn.com/oldnewthing/archive/2003/09/05/54806.aspx + * http://support.microsoft.com/kb/167296 + *) +let nt_to_time_t t + let t = Int64.sub t 116444736000000000L in + let t = Int64.div t 10000000L in + Int64.to_float t diff --git a/hivex/tools/visualizer_utils.ml b/hivex/tools/visualizer_utils.ml new file mode 100644 index 0000000..df9c789 --- /dev/null +++ b/hivex/tools/visualizer_utils.ml @@ -0,0 +1,160 @@ +(* Windows Registry reverse-engineering tool. + * Copyright (C) 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., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * For existing information on the registry format, please refer + * to the following documents. Note they are both incomplete + * and inaccurate in some respects. + *) + +open ExtString +open Printf + +let failwithf fs = ksprintf failwith fs + +(* Useful function to convert unknown bitstring fragments into + * printable strings. + *) +let rec print_bitstring bits + let str = Bitstring.string_of_bitstring bits in + print_binary_string str +and print_binary_string str + let rec printable = function + | '\x00' -> "\\0" | '\x01' -> "\\1" | '\x02' -> "\\2" | '\x03' -> "\\3" + | '\x04' -> "\\4" | '\x05' -> "\\5" | '\x06' -> "\\6" | '\x07' -> "\\7" + | ('\x08'..'\x31' as c) + | ('\x7f'..'\xff' as c) -> sprintf "\\x%02x" (Char.code c) + | ('\x32'..'\x7e' as c) -> String.make 1 c + and repeat str = function + | n when n <= 0 -> "" + | n -> str ^ repeat str (n-1) + in + let chars = String.explode str in + let rec loop acc = function + | [] -> List.rev acc + | x :: xs -> + let rec loop2 i = function + | y :: ys when x = y -> loop2 (i+1) ys + | ys -> i, ys + in + let count, ys = loop2 1 xs in + let acc = (count, x) :: acc in + loop acc ys + in + let frags = loop [] chars in + let frags + List.map (function + | (nr, x) when nr <= 4 -> repeat (printable x) nr + | (nr, x) -> sprintf "%s<%d times>" (printable x) nr + ) frags in + "\"" ^ String.concat "" frags ^ "\"" + +(* Convert an offset from the file to an offset. The only special + * thing is that 0xffffffff in the file is used as a kind of "NULL + * pointer". We map these null values to -1. + *) +let get_offset = function + | 0xffffffff_l -> -1 + | i -> Int32.to_int i + +(* Print an offset. *) +let print_offset = function + | -1 -> "NULL" + | i -> sprintf "@%08x" i + +(* Print time. *) +let print_time t + let tm = Unix.gmtime t in + sprintf "%04d-%02d-%02d %02d:%02d:%02d" + (tm.Unix.tm_year + 1900) (tm.Unix.tm_mon + 1) tm.Unix.tm_mday + tm.Unix.tm_hour tm.Unix.tm_min tm.Unix.tm_sec + +(* Print UTF16LE. *) +let print_utf16 str + let n = String.length str in + if n land 1 <> 0 then + print_binary_string str + else ( + let rec loop i + if i < n-1 then ( + let c1 = Char.code (str.[i]) in + let c2 = Char.code (str.[i+1]) in + if c1 <> 0 || c2 <> 0 then ( + (* Well, this doesn't print non-7bit-ASCII ... *) + let c + if c2 = 0 then String.make 1 (Char.chr c1) + else sprintf "\\u%04d" (c2 * 256 + c1) in + c :: loop (i+2) + ) else [] + ) else [] + in + let frags = loop 0 in + "L\"" ^ String.concat "" frags ^ "\"" + ) + +(* A map of int -> anything. *) +module IntMap = Map.Make (struct type t = int let compare = compare end) + +(* Print registry vk-record type field. *) +let print_vk_type = function + | 0 -> "NONE" + | 1 -> "SZ" + | 2 -> "EXPAND_SZ" + | 3 -> "BINARY" + | 4 -> "DWORD" + | 5 -> "DWORD_BIG_ENDIAN" + | 6 -> "LINK" + | 7 -> "MULTI_SZ" + | 8 -> "RESOURCE_LiST" + | 9 -> "FULL_RESOURCE_DESCRIPTOR" + | 10 -> "RESOURCE_REQUIREMENTS_LIST" + | 11 -> "QWORD" + | i -> sprintf "UNKNOWN_VK_TYPE_%d" i + +(* XXX We should write a more efficient version of this and + * push it into the bitstring library. + *) +let is_zero_bitstring bits + let len = Bitstring.bitstring_length bits in + let zeroes = Bitstring.zeroes_bitstring len in + 0 = Bitstring.compare bits zeroes + +let is_zero_guid = is_zero_bitstring + +(* http://msdn.microsoft.com/en-us/library/aa373931(VS.85).aspx + * Endianness of GUIDs is not clear from the MSDN documentation, + * so this is just a guess. + *) +let print_guid bits + bitmatch bits with + | { data1 : 4*8 : littleendian; + data2 : 2*8 : littleendian; + data3 : 2*8 : littleendian; + data4_1 : 2*8 : littleendian; + data4_2 : 6*8 : littleendian } -> + sprintf "%08lX-%04X-%04X-%04X-%012LX" data1 data2 data3 data4_1 data4_2 + | { _ } -> + assert false + +(* Fold over little-endian 32-bit integers in a bitstring. *) +let rec bitstring_fold_left_int32_le f a bits + bitmatch bits with + | { i : 4*8 : littleendian; + rest : -1 : bitstring } -> + bitstring_fold_left_int32_le f (f a i) rest + | { rest : -1 : bitstring } when Bitstring.bitstring_length rest = 0 -> a + | { _ } -> + invalid_arg "bitstring_fold_left_int32_le: length not a multiple of 32 bits" -- 1.6.5.2
Richard W.M. Jones
2010-Feb-03 18:34 UTC
[Libguestfs] [PATCH 8/12] hivex: Add HIVEX_OPEN_WRITE flag to allow hive to be opened for writing.
-- Richard Jones, Virtualization Group, Red Hat http://people.redhat.com/~rjones Read my programming blog: http://rwmj.wordpress.com Fedora now supports 80 OCaml packages (the OPEN alternative to F#) http://cocan.org/getting_started_with_ocaml_on_red_hat_and_fedora -------------- next part -------------->From 68facb8fd19bf240a19a15e2a31ba53cbfcd4320 Mon Sep 17 00:00:00 2001From: Richard Jones <rjones at redhat.com> Date: Mon, 18 Jan 2010 11:08:56 +0000 Subject: [PATCH 08/12] hivex: Add HIVEX_OPEN_WRITE flag to allow hive to be opened for writing. If this flag is omitted (as in the case for all existing callers) then the hive is still opened read-only. We add a 'writable' flag to the hive handle, and we change the way that the hive file (data) is stored. The data is still mmapped if the file is opened read-only, since that is more efficient and allows us to handle larger hives. However if we need to write to the file then we have to read it all into memory, since if we had to extend the file we need to realloc that data. Note the manpage section L</WRITING TO HIVE FILES> comes in a later commit. --- bootstrap | 1 + hivex/Makefile.am | 8 ++++---- hivex/README | 3 --- hivex/hivex.c | 44 ++++++++++++++++++++++++++++++++++---------- hivex/hivex.h | 4 +++- hivex/hivex.pod | 15 +++++++++++---- 6 files changed, 53 insertions(+), 22 deletions(-) diff --git a/bootstrap b/bootstrap index 7010eca..e743a4b 100755 --- a/bootstrap +++ b/bootstrap @@ -60,6 +60,7 @@ modules=' arpa_inet c-ctype closeout +full-read full-write gitlog-to-changelog gnu-make diff --git a/hivex/Makefile.am b/hivex/Makefile.am index 5624b16..312cc3c 100644 --- a/hivex/Makefile.am +++ b/hivex/Makefile.am @@ -26,9 +26,9 @@ libhivex_la_SOURCES = \ hivex.h \ byte_conversions.h -libhivex_la_LDFLAGS = -version-info 0:0:0 -libhivex_la_CFLAGS = \ - $(WARN_CFLAGS) $(WERROR_CFLAGS) +libhivex_la_LDFLAGS = -version-info 0:0:0 $(LTLIBINTL) $(LTLIBTHREAD) +libhivex_la_CFLAGS = $(WARN_CFLAGS) $(WERROR_CFLAGS) +libhivex_la_CPPFLAGS = -I$(top_srcdir)/gnulib/lib bin_PROGRAMS = hivexml hivexsh bin_SCRIPTS = hivexget @@ -36,7 +36,7 @@ bin_SCRIPTS = hivexget hivexml_SOURCES = \ hivexml.c -hivexml_LDADD = libhivex.la $(LIBXML2_LIBS) +hivexml_LDADD = libhivex.la $(LIBXML2_LIBS) ../gnulib/lib/libgnu.la hivexml_CFLAGS = \ -I$(top_srcdir)/src \ -DLOCALEBASEDIR=\""$(datadir)/locale"\" \ diff --git a/hivex/README b/hivex/README index 7bed47b..3f7f018 100644 --- a/hivex/README +++ b/hivex/README @@ -5,9 +5,6 @@ Copyright (C) 2009-2010 Red Hat Inc. This is a self-contained library for reading Windows Registry "hive" binary files. -It is totally dedicated to reading the files and doesn't deal with -writing or modifying them in any way. - Unlike many other tools in this area, it doesn't use the textual .REG format for output, because parsing that is as much trouble as parsing the original binary format. Instead it makes the file available diff --git a/hivex/hivex.c b/hivex/hivex.c index e0118af..44f2998 100644 --- a/hivex/hivex.c +++ b/hivex/hivex.c @@ -34,6 +34,12 @@ #include <sys/stat.h> #include <assert.h> +#include "full-read.h" + +#ifndef O_CLOEXEC +#define O_CLOEXEC 0 +#endif + #define STREQ(a,b) (strcmp((a),(b)) == 0) #define STRCASEEQ(a,b) (strcasecmp((a),(b)) == 0) //#define STRNEQ(a,b) (strcmp((a),(b)) != 0) @@ -54,8 +60,9 @@ struct hive_h { int fd; size_t size; int msglvl; + int writable; - /* Memory-mapped (readonly) registry file. */ + /* Registry file, memory mapped if read-only, or malloc'd if writing. */ union { char *addr; struct ntreg_header *hdr; @@ -260,11 +267,12 @@ hivex_open (const char *filename, int flags) if (h->msglvl >= 2) fprintf (stderr, "hivex_open: created handle %p\n", h); + h->writable = !!(flags & HIVEX_OPEN_WRITE); h->filename = strdup (filename); if (h->filename == NULL) goto error; - h->fd = open (filename, O_RDONLY); + h->fd = open (filename, O_RDONLY | O_CLOEXEC); if (h->fd == -1) goto error; @@ -274,12 +282,21 @@ hivex_open (const char *filename, int flags) h->size = statbuf.st_size; - h->addr = mmap (NULL, h->size, PROT_READ, MAP_SHARED, h->fd, 0); - if (h->addr == MAP_FAILED) - 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; - if (h->msglvl >= 2) - fprintf (stderr, "hivex_open: mapped file at %p\n", h->addr); + if (h->msglvl >= 2) + fprintf (stderr, "hivex_open: mapped file at %p\n", 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; + } /* Check header. */ if (h->hdr->magic[0] != 'r' || @@ -471,8 +488,12 @@ hivex_open (const char *filename, int flags) int err = errno; if (h) { free (h->bitmap); - if (h->addr && h->size && h->addr != MAP_FAILED) - munmap (h->addr, h->size); + 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); @@ -488,7 +509,10 @@ hivex_close (hive_h *h) int r; free (h->bitmap); - munmap (h->addr, h->size); + if (!h->writable) + munmap (h->addr, h->size); + else + free (h->addr); r = close (h->fd); free (h->filename); free (h); diff --git a/hivex/hivex.h b/hivex/hivex.h index fe5c128..56718b4 100644 --- a/hivex/hivex.h +++ b/hivex/hivex.h @@ -69,9 +69,11 @@ enum hive_type { typedef enum hive_type hive_type; +/* Bitmask of flags passed to hivex_open. */ #define HIVEX_OPEN_VERBOSE 1 #define HIVEX_OPEN_DEBUG 2 -#define HIVEX_OPEN_MSGLVL_MASK 3 +#define HIVEX_OPEN_MSGLVL_MASK (HIVEX_OPEN_VERBOSE|HIVEX_OPEN_DEBUG) +#define HIVEX_OPEN_WRITE 4 extern hive_h *hivex_open (const char *filename, int flags); extern int hivex_close (hive_h *h); diff --git a/hivex/hivex.pod b/hivex/hivex.pod index 7e41d0c..5a58144 100644 --- a/hivex/hivex.pod +++ b/hivex/hivex.pod @@ -13,8 +13,7 @@ hivex - Windows Registry "hive" extraction library libhivex is a library for extracting the contents of Windows Registry "hive" files. It is designed to be secure against buggy or malicious -registry files, and to have limited functionality (writing or -modifying these files is not in the scope of this library). +registry files. Unlike many other tools in this area, it doesn't use the textual .REG format for output, because parsing that is as much trouble as parsing @@ -32,8 +31,7 @@ L<hivexget(1)>). Opens the hive named C<filename> for reading. Flags is an ORed list of the open flags (or C<0> if you don't -want to pass any flags). Currently the only -flags defined are: +want to pass any flags). These flags are defined: =over 4 @@ -49,6 +47,12 @@ itself. This is also selected if the C<HIVEX_DEBUG> environment variable is set to 1. +=item HIVEX_OPEN_WRITE + +Open the hive for writing. If omitted, the hive is read-only. + +See L</WRITING TO HIVE FILES>. + =back C<hivex_open> returns a hive handle. On error this returns NULL and @@ -58,6 +62,9 @@ sets C<errno> to indicate the error. Close a hive handle and free all associated resources. +Note that any uncommitted writes are I<not> committed by this call, +but instead are lost. See L</WRITING TO HIVE FILES>. + Returns 0 on success. On error this returns -1 and sets errno. =back -- 1.6.5.2
Richard W.M. Jones
2010-Feb-03 18:34 UTC
[Libguestfs] [PATCH 9/12] hivex: Begin implementation of writing to hives.
-- 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 db16e70917f967f33b3c92eda777c9801e080ccf Mon Sep 17 00:00:00 2001From: Richard Jones <rjones at redhat.com> Date: Wed, 3 Feb 2010 17:59:03 +0000 Subject: [PATCH 09/12] hivex: Begin implementation of writing to hives. This implements hivex_node_set_values which is used to delete the (key, value) pairs at a node and optionally replace them with a new set. This also implements hivex_commit which is used to commit changes to hives back to disk. --- hivex/hivex.c | 384 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ hivex/hivex.h | 12 ++ hivex/hivex.pod | 125 ++++++++++++++++++ 3 files changed, 521 insertions(+), 0 deletions(-) diff --git a/hivex/hivex.c b/hivex/hivex.c index 44f2998..af36868 100644 --- a/hivex/hivex.c +++ b/hivex/hivex.c @@ -35,6 +35,7 @@ #include <assert.h> #include "full-read.h" +#include "full-write.h" #ifndef O_CLOEXEC #define O_CLOEXEC 0 @@ -88,6 +89,10 @@ struct hive_h { /* Fields from the header, extracted from little-endianness hell. */ size_t rootoffs; /* Root key offset (always an nk-block). */ size_t endpages; /* Offset of end of pages. */ + + /* For writing. */ + size_t endblocks; /* Offset to next block allocation (0 + if not allocated anything yet). */ }; /* NB. All fields are little endian. */ @@ -520,6 +525,10 @@ hivex_close (hive_h *h) return r; } +/*---------------------------------------------------------------------- + * Reading. + */ + hive_node_h hivex_root (hive_h *h) { @@ -1399,6 +1408,10 @@ hivex_value_qword (hive_h *h, hive_value_h value) return ret; } +/*---------------------------------------------------------------------- + * Visiting. + */ + int hivex_visit (hive_h *h, const struct hivex_visitor *visitor, size_t len, void *opaque, int flags) @@ -1642,3 +1655,374 @@ hivex__visit_node (hive_h *h, hive_node_h node, free_strings (strs); return ret; } + +/*---------------------------------------------------------------------- + * Writing. + */ + +/* Allocate an hbin (page), extending the malloc'd space if necessary, + * and updating the hive handle fields (but NOT the hive disk header + * -- the hive disk header is updated when we commit). This function + * also extends the bitmap if necessary. + * + * 'allocation_hint' is the size of the block allocation we would like + * to make. Normally registry blocks are very small (avg 50 bytes) + * and are contained in standard-sized pages (4KB), but the registry + * can support blocks which are larger than a standard page, in which + * case it creates a page of 8KB, 12KB etc. + * + * Returns: + * > 0 : offset of first usable byte of new page (after page header) + * 0 : error (errno set) + */ +static size_t +allocate_page (hive_h *h, size_t allocation_hint) +{ + /* In almost all cases this will be 1. */ + size_t nr_4k_pages + 1 + (allocation_hint + sizeof (struct ntreg_hbin_page) - 1) / 4096; + assert (nr_4k_pages >= 1); + + /* 'extend' is the number of bytes to extend the file by. Note that + * hives found in the wild often contain slack between 'endpages' + * and the actual end of the file, so we don't always need to make + * the file larger. + */ + ssize_t extend = h->endpages + nr_4k_pages * 4096 - h->size; + + if (h->msglvl >= 2) { + fprintf (stderr, "allocate_page: current endpages = 0x%zx, current size = 0x%zx\n", + h->endpages, h->size); + fprintf (stderr, "allocate_page: extending file by %zd bytes (<= 0 if no extension)\n", + extend); + } + + if (extend > 0) { + size_t oldsize = h->size; + size_t newsize = h->size + extend; + char *newaddr = realloc (h->addr, newsize); + if (newaddr == NULL) + return 0; + + size_t oldbitmapsize = 1 + oldsize / 32; + size_t newbitmapsize = 1 + newsize / 32; + char *newbitmap = realloc (h->bitmap, newbitmapsize); + if (newbitmap == NULL) { + free (newaddr); + return 0; + } + + h->addr = newaddr; + h->size = newsize; + h->bitmap = newbitmap; + + memset (h->addr + oldsize, 0, newsize - oldsize); + memset (h->bitmap + oldbitmapsize, 0, newbitmapsize - oldbitmapsize); + } + + size_t offset = h->endpages; + h->endpages += nr_4k_pages * 4096; + + if (h->msglvl >= 2) + fprintf (stderr, "allocate_page: new endpages = 0x%zx, new size = 0x%zx\n", + h->endpages, h->size); + + /* Write the hbin header. */ + struct ntreg_hbin_page *page + (struct ntreg_hbin_page *) (h->addr + offset); + page->magic[0] = 'h'; + page->magic[1] = 'b'; + page->magic[2] = 'i'; + page->magic[3] = 'n'; + page->offset_first = htole32 (offset - 0x1000); + page->page_size = htole32 (nr_4k_pages * 4096); + memset (page->unknown, 0, sizeof (page->unknown)); + + if (h->msglvl >= 2) + fprintf (stderr, "allocate_page: new page at 0x%zx\n", offset); + + /* Offset of first usable byte after the header. */ + return offset + sizeof (struct ntreg_hbin_page); +} + +/* Allocate a single block, first allocating an hbin (page) at the end + * of the current file if necessary. NB. To keep the implementation + * simple and more likely to be correct, we do not reuse existing free + * blocks. + * + * seg_len is the size of the block (this INCLUDES the block header). + * The header of the block is initialized to -seg_len (negative to + * indicate used). id[2] is the block ID (type), eg. "nk" for nk- + * record. The block bitmap is updated to show this block as valid. + * The rest of the contents of the block will be zero. + * + * Returns: + * > 0 : offset of new block + * 0 : error (errno set) + */ +static size_t +allocate_block (hive_h *h, size_t seg_len, const char id[2]) +{ + if (!h->writable) { + errno = EROFS; + return 0; + } + + if (seg_len < 4) { + /* The caller probably forgot to include the header. Note that + * value lists have no ID field, so seg_len == 4 would be possible + * for them, albeit unusual. + */ + if (h->msglvl >= 2) + fprintf (stderr, "allocate_block: refusing too small allocation (%zu), returning ERANGE\n", + seg_len); + errno = ERANGE; + return 0; + } + + /* Refuse really large allocations. */ + if (seg_len > 1000000) { + if (h->msglvl >= 2) + fprintf (stderr, "allocate_block: refusing large allocation (%zu), returning ERANGE\n", + seg_len); + errno = ERANGE; + return 0; + } + + /* Round up allocation to multiple of 8 bytes. All blocks must be + * on an 8 byte boundary. + */ + seg_len = (seg_len + 7) & ~7; + + /* Allocate a new page if necessary. */ + if (h->endblocks == 0 || h->endblocks + seg_len > h->endpages) { + size_t newendblocks = allocate_page (h, seg_len); + if (newendblocks == 0) + return 0; + h->endblocks = newendblocks; + } + + size_t offset = h->endblocks; + + if (h->msglvl >= 2) + fprintf (stderr, "allocate_block: new block at 0x%zx, size %zu\n", + offset, seg_len); + + struct ntreg_hbin_block *blockhdr + (struct ntreg_hbin_block *) (h->addr + offset); + + blockhdr->seg_len = htole32 (- (int32_t) seg_len); + if (id[0] && id[1] && seg_len >= 6) { + blockhdr->id[0] = id[0]; + blockhdr->id[1] = id[1]; + } + + h->endblocks += seg_len; + + /* If there is space after the last block in the last page, then we + * have to put a dummy free block header here to mark the rest of + * the page as free. + */ + ssize_t rem = h->endpages - h->endblocks; + if (rem > 0) { + if (h->msglvl >= 2) + fprintf (stderr, "allocate_block: marking remainder of page free starting at 0x%zx, size %zd\n", + h->endblocks, rem); + + assert (rem >= 4); + + blockhdr = (struct ntreg_hbin_block *) (h->addr + h->endblocks); + blockhdr->seg_len = htole32 ((int32_t) rem); + } + + return offset; +} + +/* '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)); + + struct ntreg_hbin_block *blockhdr + (struct ntreg_hbin_block *) (h->addr + offset); + + size_t seg_len = block_len (h, offset, NULL); + blockhdr->seg_len = htole32 (seg_len); + + BITMAP_CLR (h->bitmap, offset); +} + +/* Delete all existing values at this node. */ +static int +delete_values (hive_h *h, hive_node_h node) +{ + assert (h->writable); + + hive_value_h *values; + size_t *blocks; + if (get_values (h, node, &values, &blocks) == -1) + return -1; + + size_t i; + for (i = 0; blocks[i] != 0; ++i) + mark_block_unused (h, blocks[i]); + + free (blocks); + + for (i = 0; values[i] != 0; ++i) { + struct ntreg_vk_record *vk + (struct ntreg_vk_record *) (h->addr + values[i]); + + size_t len; + len = le32toh (vk->data_len); + if (len == 0x80000000) /* special case */ + len = 4; + len &= 0x7fffffff; + + if (len > 4) { /* non-inline, so remove data block */ + size_t data_offset = le32toh (vk->data_offset); + data_offset += 0x1000; + mark_block_unused (h, data_offset); + } + + /* remove vk record */ + mark_block_unused (h, values[i]); + } + + free (values); + + struct ntreg_nk_record *nk = (struct ntreg_nk_record *) (h->addr + node); + nk->nr_values = htole32 (0); + nk->vallist = htole32 (0xffffffff); + + return 0; +} + +int +hivex_commit (hive_h *h, const char *filename, int flags) +{ + if (flags != 0) { + errno = EINVAL; + return -1; + } + + if (!h->writable) { + errno = EROFS; + return -1; + } + + filename = filename ? : h->filename; + int fd = open (filename, O_WRONLY|O_CREAT|O_TRUNC|O_NOCTTY, 0666); + if (fd == -1) + return -1; + + /* Update the header fields. */ + uint32_t sequence = le32toh (h->hdr->sequence1); + sequence++; + h->hdr->sequence1 = htole32 (sequence); + h->hdr->sequence2 = htole32 (sequence); + /* XXX Ought to update h->hdr->last_modified. */ + h->hdr->blocks = htole32 (h->endpages - 0x1000); + + /* Recompute header checksum. */ + uint32_t sum = header_checksum (h); + h->hdr->csum = htole32 (sum); + + if (h->msglvl >= 2) + fprintf (stderr, "hivex_commit: new header checksum: 0x%x\n", sum); + + if (full_write (fd, h->addr, h->size) != h->size) { + int err = errno; + close (fd); + errno = err; + return -1; + } + + if (close (fd) == -1) + return -1; + + return 0; +} + +int +hivex_node_set_values (hive_h *h, hive_node_h node, + size_t nr_values, const hive_set_value *values, + int flags) +{ + if (!h->writable) { + errno = EROFS; + return -1; + } + + if (!IS_VALID_BLOCK (h, node) || !BLOCK_ID_EQ (h, node, "nk")) { + errno = EINVAL; + return -1; + } + + /* Delete all existing values. */ + if (delete_values (h, node) == -1) + return -1; + + if (nr_values == 0) + return 0; + + /* Allocate value list node. Value lists have no id field. */ + static const char nul_id[2] = { 0, 0 }; + size_t seg_len + sizeof (struct ntreg_value_list) + (nr_values - 1) * sizeof (uint32_t); + size_t vallist_offs = allocate_block (h, seg_len, nul_id); + if (vallist_offs == 0) + return -1; + + struct ntreg_nk_record *nk = (struct ntreg_nk_record *) (h->addr + node); + nk->nr_values = htole32 (nr_values); + nk->vallist = htole32 (vallist_offs - 0x1000); + + struct ntreg_value_list *vallist + (struct ntreg_value_list *) (h->addr + vallist_offs); + + size_t i; + for (i = 0; i < nr_values; ++i) { + /* Allocate vk record to store this (key, value) pair. */ + static const char vk_id[2] = { 'v', 'k' }; + seg_len = sizeof (struct ntreg_vk_record) + strlen (values[i].key); + size_t vk_offs = allocate_block (h, seg_len, vk_id); + if (vk_offs == 0) + return -1; + + vallist->offset[i] = htole32 (vk_offs - 0x1000); + + struct ntreg_vk_record *vk = (struct ntreg_vk_record *) (h->addr + vk_offs); + size_t name_len = strlen (values[i].key); + 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); + vk->flags = name_len == 0 ? 0 : 1; + + if (values[i].len <= 4) /* Store data inline. */ + memcpy (&vk->data_offset, values[i].value, values[i].len); + else { + size_t offs = allocate_block (h, values[i].len + 4, nul_id); + if (offs == 0) + return -1; + memcpy (h->addr + offs + 4, values[i].value, values[i].len); + vk->data_offset = htole32 (offs - 0x1000); + } + + if (name_len * 2 > le32toh (nk->max_vk_name_len)) + 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); + } + + return 0; +} diff --git a/hivex/hivex.h b/hivex/hivex.h index 56718b4..6a3cb3a 100644 --- a/hivex/hivex.h +++ b/hivex/hivex.h @@ -110,6 +110,18 @@ struct hivex_visitor { extern int hivex_visit (hive_h *h, const struct hivex_visitor *visitor, size_t len, void *opaque, int flags); 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); + +struct hive_set_value { + char *key; + hive_type t; + size_t len; + char *value; +}; +typedef struct hive_set_value hive_set_value; + +extern int hivex_node_set_values (hive_h *h, hive_node_h node, size_t nr_values, const hive_set_value *values, int flags); + #ifdef __cplusplus } #endif diff --git a/hivex/hivex.pod b/hivex/hivex.pod index 5a58144..5df75aa 100644 --- a/hivex/hivex.pod +++ b/hivex/hivex.pod @@ -326,6 +326,127 @@ starts at C<node>. =back +=head2 WRITING TO HIVE FILES + +The hivex library supports making limited modifications to hive files. +We have tried to implement this very conservatively in order to reduce +the chance of corrupting your registry. However you should be careful +and take back-ups, since Microsoft has never documented the hive +format, and so it is possible there are nuances in the +reverse-engineered format that we do not understand. + +To be able to modify a hive, you must pass the C<HIVEX_OPEN_WRITE> +flag to C<hivex_open>, otherwise any write operation will return with +errno C<EROFS>. + +The write operations shown below do not modify the on-disk file +immediately. You must call C<hivex_commit> in order to write the +changes to disk. If you call C<hivex_close> without committing then +any writes are discarded. + +Hive files internally consist of a "memory dump" of binary blocks +(like the C heap), and some of these blocks can be unused. The hivex +library never reuses these unused blocks. Instead, to ensure +robustness in the face of the partially understood on-disk format, +hivex only allocates new blocks after the end of the file, and makes +minimal modifications to existing structures in the file to point to +these new blocks. This makes hivex slightly less disk-efficient than +it could be, but disk is cheap, and registry modifications tend to be +very small. + +When deleting nodes, it is possible that this library may leave +unreachable live blocks in the hive. This is because certain parts of +the hive disk format such as security (sk) records and big data (db) +records and classname fields are not well understood (and not +documented at all) and we play it safe by not attempting to modify +them. Apart from wasting a little bit of disk space, it is not +thought that unreachable blocks are a problem. + +=over 4 + +=item int hivex_commit (hive_h *h, const char *filename, int flags); + +Commit (write) any changes which have been made. + +C<filename> is the new file to write. If C<filename == NULL> then we +overwrite the original file (ie. the file name that was passed to +C<hivex_open>). C<flags> is not used, always pass 0. + +Returns 0 on success. On error this returns -1 and sets errno. + +Note this does not close the hive handle. You can perform further +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_set_value + +The typedef C<hive_set_value> is used in conjunction with the +C<hivex_node_set_values> call described below. + + struct hive_set_value { + char *key; /* key - a UTF-8 encoded ASCIIZ string */ + hive_type t; /* type of value field */ + size_t len; /* length of value field in bytes */ + char *value; /* value field */ + }; + typedef struct hive_set_value hive_set_value; + +To set the default value for a node, you have to pass C<key = "">. + +Note that the C<value> field is just treated as a list of bytes, and +is stored directly in the hive. The caller has to ensure correct +encoding and endianness, for example converting dwords to little +endian. + +The correct type and encoding for values depends on the node and key +in the registry, the version of Windows, and sometimes even changes +between versions of Windows for the same key. We don't document it +here. Often it's not documented at all. + +=item int hivex_node_set_values (hive_h *h, hive_node_h node, size_t nr_values, const hive_set_value *values, int flags); + +This call can be used to set all the (key, value) pairs stored in C<node>. + +C<node> is the node to modify. C<values> is an array of (key, value) +pairs. There should be C<nr_values> elements in this array. C<flags> +is not used, always pass 0. + +Any existing values stored at the node are discarded, and their +C<hive_value_h> handles become invalid. Thus you can remove all +values stored at C<node> by passing C<nr_values = 0>. + +Returns 0 on success. On error this returns -1 and sets errno. + +Note that this library does not offer a way to modify just a single +key at a node. We don't implement a way to do this efficiently. + +=back + +=head3 WRITE OPERATIONS WHICH ARE NOT SUPPORTED + +=over 4 + +=item * + +Changing the root node. + +=item * + +Creating a new hive file from scratch. This is impossible at present +because not all fields in the header are understood. + +=item * + +Modifying or deleting single values at a node. + +=item * + +Modifying security key (sk) records or classnames. These are not +well understood. + +=back + =head1 THE STRUCTURE OF THE WINDOWS REGISTRY Note: To understand the relationship between hives and the common @@ -452,6 +573,10 @@ Registry contains cycles. Field in the registry out of range. +=item EROFS + +Tried to write to a registry which is not opened for writing. + =back =head1 ENVIRONMENT VARIABLES -- 1.6.5.2
Richard W.M. Jones
2010-Feb-03 18:35 UTC
[Libguestfs] [PATCH 10/12] hivexsh: Add 'setval' and 'commit' commands.
-- 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 c6fa2b912e47df21bc6a64b2f1b0bcf17cbd0a0f Mon Sep 17 00:00:00 2001From: Richard Jones <rjones at redhat.com> Date: Wed, 3 Feb 2010 18:04:31 +0000 Subject: [PATCH 10/12] hivexsh: Add 'setval' and 'commit' commands. This adds the 'setval' and 'commit' commands to the hivex shell. Also adds some example scripts showing use of these. --- hivex/Makefile.am | 5 +- hivex/example1 | 40 ++++++++ hivex/example2 | 47 +++++++++ hivex/example3 | 53 +++++++++++ hivex/hivexsh.c | 271 ++++++++++++++++++++++++++++++++++++++++++++++++++++- hivex/hivexsh.pod | 79 ++++++++++++++++ 6 files changed, 491 insertions(+), 4 deletions(-) create mode 100755 hivex/example1 create mode 100755 hivex/example2 create mode 100755 hivex/example3 diff --git a/hivex/Makefile.am b/hivex/Makefile.am index 312cc3c..987cfc9 100644 --- a/hivex/Makefile.am +++ b/hivex/Makefile.am @@ -32,6 +32,7 @@ libhivex_la_CPPFLAGS = -I$(top_srcdir)/gnulib/lib bin_PROGRAMS = hivexml hivexsh bin_SCRIPTS = hivexget +noinst_SCRIPTS = example1 example2 example3 hivexml_SOURCES = \ hivexml.c @@ -44,7 +45,9 @@ hivexml_CFLAGS = \ $(WARN_CFLAGS) $(WERROR_CFLAGS) hivexsh_SOURCES = \ - hivexsh.c + hivexsh.c \ + hivex.h \ + byte_conversions.h hivexsh_LDADD = libhivex.la ../gnulib/lib/libgnu.la $(LIBREADLINE) hivexsh_CFLAGS = \ diff --git a/hivex/example1 b/hivex/example1 new file mode 100755 index 0000000..5b1313f --- /dev/null +++ b/hivex/example1 @@ -0,0 +1,40 @@ +#!/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 + +# Example program which loads and saves a hive. +# +# The intention of this example is just to check that we can do this +# without corrupting the hive (header etc). +# +# NB: The copy of the hive will not be absolutely identical. The +# sequence numbers in the header will change. If we implement the +# last modified field in the header, then that and the checksum will +# also change. + +if [ $# -ne 2 ]; then + echo "$0 input output" + exit 1 +fi + +d=`dirname $0` + +$d/hivexsh -w <<EOF +load $1 +commit $2 +EOF \ No newline at end of file diff --git a/hivex/example2 b/hivex/example2 new file mode 100755 index 0000000..8d27546 --- /dev/null +++ b/hivex/example2 @@ -0,0 +1,47 @@ +#!/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 + +# Example program which modifies a hive. +# +# This program removes any existing (key, value) pairs at the root +# node and replaces them with some example values. +# +# You can load the modified hive using another tool to see the +# changes. eg. Using Windows regedit, select HKLM and then in the +# File menu choose "Load Hive ...". Point to the update hive, and +# then give a key (eg. "test1"). The modified hive will be loaded +# under HKLM\test1 and the values can be inspected there. After +# inspecting the changes, unload the hive using File -> Unload Hive. +# +# Don't replace the original Windows hive, else you'll break things :-) + +if [ $# -ne 0 ]; then + echo "$0: no arguments required" + exit 1 +fi + +d=`dirname $0` + +$d/hivexsh -w <<EOF +load $d/t/minimal +setval 1 +@ +string:Root +commit /tmp/modified +EOF \ No newline at end of file diff --git a/hivex/example3 b/hivex/example3 new file mode 100755 index 0000000..b482e41 --- /dev/null +++ b/hivex/example3 @@ -0,0 +1,53 @@ +#!/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 + +# Example program which modifies a hive. +# +# This program removes any existing (key, value) pairs at the root +# node and replaces them with some example values. +# +# You can load the modified hive using another tool to see the +# changes. eg. Using Windows regedit, select HKLM and then in the +# File menu choose "Load Hive ...". Point to the update hive, and +# then give a key (eg. "test1"). The modified hive will be loaded +# under HKLM\test1 and the values can be inspected there. After +# inspecting the changes, unload the hive using File -> Unload Hive. +# +# Don't replace the original Windows hive, else you'll break things :-) + +if [ $# -ne 0 ]; then + echo "$0: no arguments required" + exit 1 +fi + +d=`dirname $0` + +$d/hivexsh -w <<EOF +load $d/t/minimal +setval 4 +@ +string:Root +A +string:abcd +B +dword:0x12345678 +C +string:dcbadcbadcbaabcd +commit /tmp/modified +EOF \ No newline at end of file diff --git a/hivex/hivexsh.c b/hivex/hivexsh.c index 01a5ddc..6f33f41 100644 --- a/hivex/hivexsh.c +++ b/hivex/hivexsh.c @@ -50,11 +50,13 @@ //#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 "c-ctype.h" +#include "xstrtol.h" #include "hivex.h" +#include "byte_conversions.h" static int quit = 0; static int is_tty; @@ -72,18 +74,21 @@ static void cleanup_readline (void); static void add_history_line (const char *); 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_cd (char *path); static int cmd_close (char *path); +static int cmd_commit (char *path); static int cmd_help (char *args); static int cmd_load (char *hivefile); static int cmd_ls (char *args); static int cmd_lsval (char *args); +static int cmd_setval (char *args); static void usage (void) { - fprintf (stderr, "hivexsh [-df] [hivefile]\n"); + fprintf (stderr, "hivexsh [-dfw] [hivefile]\n"); exit (EXIT_FAILURE); } @@ -99,7 +104,7 @@ main (int argc, char *argv[]) set_prompt_string (); - while ((c = getopt (argc, argv, "df")) != EOF) { + while ((c = getopt (argc, argv, "dfw")) != EOF) { switch (c) { case 'd': open_flags |= HIVEX_OPEN_DEBUG; @@ -107,6 +112,9 @@ main (int argc, char *argv[]) case 'f': filename = optarg; break; + case 'w': + open_flags |= HIVEX_OPEN_WRITE; + break; default: usage (); } @@ -370,6 +378,17 @@ sort_strings (char **strings, int len) } static int +get_xdigit (char c) +{ + switch (c) { + case '0'...'9': return c - '0'; + case 'a'...'f': return c - 'a' + 10; + case 'A'...'F': return c - 'A' + 10; + default: return -1; + } +} + +static int dispatch (char *cmd, char *args) { if (STRCASEEQ (cmd, "help")) @@ -395,10 +414,14 @@ dispatch (char *cmd, char *args) return cmd_cd (args); else if (STRCASEEQ (cmd, "close") || STRCASEEQ (cmd, "unload")) return cmd_close (args); + else if (STRCASEEQ (cmd, "commit")) + return cmd_commit (args); else if (STRCASEEQ (cmd, "ls")) return cmd_ls (args); else if (STRCASEEQ (cmd, "lsval")) return cmd_lsval (args); + else if (STRCASEEQ (cmd, "setval")) + return cmd_setval (args); else { fprintf (stderr, _("hivexsh: unknown command '%s', use 'help' for help summary\n"), cmd); @@ -478,6 +501,20 @@ cmd_close (char *args) } static int +cmd_commit (char *path) +{ + if (STREQ (path, "")) + path = NULL; + + if (hivex_commit (h, path, 0) == -1) { + perror ("hivexsh: commit"); + return -1; + } + + return 0; +} + +static int cmd_cd (char *path) { if (STREQ (path, "")) { @@ -779,3 +816,231 @@ cmd_lsval (char *key) perror ("hivexsh: lsval"); return -1; } + +static int +cmd_setval (char *nrvals_str) +{ + strtol_error xerr; + + /* Parse number of values. */ + long nrvals; + xerr = xstrtol (nrvals_str, NULL, 0, &nrvals, ""); + if (xerr != LONGINT_OK) { + fprintf (stderr, _("%s: %s: invalid integer parameter (%s returned %d)\n"), + "setval", "nrvals", "xstrtol", xerr); + return -1; + } + if (nrvals < 0 || nrvals > 1000) { + fprintf (stderr, _("%s: %s: integer out of range\n"), + "setval", "nrvals"); + return -1; + } + + struct hive_set_value *values + calloc (nrvals, sizeof (struct hive_set_value)); + if (values == NULL) { + perror ("calloc"); + exit (EXIT_FAILURE); + } + + int ret = -1; + + /* 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. */ + char *buf = rl_gets (" key> "); + if (!buf) { + fprintf (stderr, _("hivexsh: setval: unexpected end of input\n")); + quit = 1; + goto error; + } + + /* Note that buf will be overwritten by the next call to rl_gets. */ + if (STREQ (buf, "@")) + values[i].key = strdup (""); + else + values[i].key = strdup (buf); + if (values[i].key == NULL) { + perror ("strdup"); + exit (EXIT_FAILURE); + } + + /* Read value. */ + buf = rl_gets ("value> "); + if (!buf) { + fprintf (stderr, _("hivexsh: setval: unexpected end of input\n")); + quit = 1; + goto error; + } + + if (STREQ (buf, "none")) { + values[i].t = hive_t_none; + values[i].len = 0; + } + else if (STRPREFIX (buf, "string:")) { + buf += 7; + values[i].t = hive_t_string; + int nr_chars = strlen (buf); + values[i].len = 2 * (nr_chars + 1); + values[i].value = malloc (values[i].len); + if (!values[i].value) { + perror ("malloc"); + exit (EXIT_FAILURE); + } + for (j = 0; j <= /* sic */ nr_chars; ++j) { + if (buf[j] & 0x80) { + fprintf (stderr, _("hivexsh: string(utf16le): only 7 bit ASCII strings are supported for input\n")); + goto error; + } + values[i].value[2*j] = buf[j]; + values[i].value[2*j+1] = '\0'; + } + } + else if (STRPREFIX (buf, "expandstring:")) { + buf += 13; + values[i].t = hive_t_string; + int nr_chars = strlen (buf); + values[i].len = 2 * (nr_chars + 1); + values[i].value = malloc (values[i].len); + if (!values[i].value) { + perror ("malloc"); + exit (EXIT_FAILURE); + } + for (j = 0; j <= /* sic */ nr_chars; ++j) { + if (buf[j] & 0x80) { + fprintf (stderr, _("hivexsh: string(utf16le): only 7 bit ASCII strings are supported for input\n")); + goto error; + } + values[i].value[2*j] = buf[j]; + values[i].value[2*j+1] = '\0'; + } + } + else if (STRPREFIX (buf, "dword:")) { + buf += 6; + values[i].t = hive_t_dword; + values[i].len = 4; + values[i].value = malloc (4); + if (!values[i].value) { + perror ("malloc"); + exit (EXIT_FAILURE); + } + long n; + xerr = xstrtol (buf, NULL, 0, &n, ""); + if (xerr != LONGINT_OK) { + fprintf (stderr, _("%s: %s: invalid integer parameter (%s returned %d)\n"), + "setval", "dword", "xstrtol", xerr); + goto error; + } + if (n < 0 || n > UINT32_MAX) { + fprintf (stderr, _("%s: %s: integer out of range\n"), + "setval", "dword"); + goto error; + } + uint32_t u32 = htole32 (n); + memcpy (values[i].value, &u32, 4); + } + else if (STRPREFIX (buf, "qword:")) { + buf += 6; + values[i].t = hive_t_qword; + values[i].len = 8; + values[i].value = malloc (8); + if (!values[i].value) { + perror ("malloc"); + exit (EXIT_FAILURE); + } + long long n; + xerr = xstrtoll (buf, NULL, 0, &n, ""); + if (xerr != LONGINT_OK) { + fprintf (stderr, _("%s: %s: invalid integer parameter (%s returned %d)\n"), + "setval", "dword", "xstrtoll", xerr); + goto error; + } +#if 0 + if (n < 0 || n > UINT64_MAX) { + fprintf (stderr, _("%s: %s: integer out of range\n"), + "setval", "dword"); + goto error; + } +#endif + uint64_t u64 = htole64 (n); + memcpy (values[i].value, &u64, 4); + } + else if (STRPREFIX (buf, "hex:")) { + /* Read the type. */ + buf += 4; + size_t len = strcspn (buf, ":"); + char *nextbuf; + if (buf[len] == '\0') /* "hex:t" */ + nextbuf = &buf[len]; + else { /* "hex:t:..." */ + buf[len] = '\0'; + nextbuf = &buf[len+1]; + } + + long t; + xerr = xstrtol (buf, NULL, 0, &t, ""); + if (xerr != LONGINT_OK) { + fprintf (stderr, _("%s: %s: invalid integer parameter (%s returned %d)\n"), + "setval", "hex", "xstrtol", xerr); + goto error; + } + if (t < 0 || t > UINT32_MAX) { + fprintf (stderr, _("%s: %s: integer out of range\n"), + "setval", "hex"); + goto error; + } + values[i].t = t; + + /* Read the hex data. */ + buf = nextbuf; + + /* The allocation length is an overestimate, but it doesn't matter. */ + values[i].value = malloc (1 + strlen (buf) / 2); + if (!values[i].value) { + perror ("malloc"); + exit (EXIT_FAILURE); + } + values[i].len = 0; + + while (*buf) { + int c = 0; + + for (j = 0; *buf && j < 2; buf++) { + if (c_isxdigit (*buf)) { /* NB: ignore non-hex digits. */ + c <<= 4; + c |= get_xdigit (*buf); + j++; + } + } + + if (j == 2) values[i].value[values[i].len++] = c; + else if (j == 1) { + fprintf (stderr, _("hivexsh: setval: trailing garbage after hex string\n")); + goto error; + } + } + } + else { + fprintf (stderr, + _("hivexsh: setval: cannot parse value string, please refer to the man page hivexsh(1) for help: %s\n"), + buf); + goto error; + } + } + + ret = hivex_node_set_values (h, cwd, nrvals, values, 0); + + error: + /* Free values array. */ + for (i = 0; i < nrvals; ++i) { + free (values[i].key); + free (values[i].value); + } + free (values); + + return ret; +} diff --git a/hivex/hivexsh.pod b/hivex/hivexsh.pod index d13c70b..e7e8d94 100644 --- a/hivex/hivexsh.pod +++ b/hivex/hivexsh.pod @@ -61,6 +61,18 @@ script, use: #!/usr/bin/hivexsh -f +=item B<-w> + +If this option is given, then writes are allowed to the hive +(see L</commit> command below, and the discussion of +modifying hives in L<hivex(3)/WRITING TO HIVE FILES>). + +B<Important Note:> Even if you specify this option, nothing is written +to a hive unless you call the L</commit> command. If you exit the +shell without committing, all changes will be discarded. + +If this option is not given, then write commands are disabled. + =back =head1 COMMANDS @@ -94,6 +106,19 @@ C<..> may be used to go to the parent directory. Close the currently loaded hive. +If you modified the hive, all uncommitted writes are lost when you +call this command (or if the shell exits). You have to call C<commit> +to write changes. + +=item B<commit> [newfile] + +Commit changes to the hive. If the optional C<newfile> parameter is +supplied, then the hive is written to that file, else the original +file is overwritten. + +Note that you have to specify the C<-w> flag, otherwise no writes are +allowed. + =item B<exit> | B<quit> Exit the shell. @@ -116,6 +141,60 @@ argument is given then all pairs are displayed. If C<key> is given, then the value of the named key is displayed. If C<@> is given, then the value of the default key is displayed. +=item B<setval> nrvals + +This command replaces all (key, value) pairs at the current node with +the values in subsequent input. C<nrvals> is the number of values +(ie. (key, value) pairs), and any existing values at this node are +deleted. So C<setval 0> just deletes any values at the current node. + +The command reads 2 * nrvals lines of input, with each pair of +lines of input corresponding to a key and a value to add. + +For example, the following setval command replaces whatever is at the +current node with two (key, value) pairs. The default key is set to +the UTF16-LE-encoded string "abcd". The other value is named +"ANumber" and is a little-endian DWORD 0x12345678. + + setval 2 + @ + string:abcd + ANumber + dword:12345678 + +The first line of each pair is the key (the special key C<@> means +the default key, but you can also use a blank line). + +The second line of each pair is the value, which has a special format +C<type:value> with possible types summarized in the table below: + + none No data is stored, and the type is set to 0. + + string:abc "abc" is stored as a UTF16-LE-encoded + string (type 1). Note that only 7 bit + ASCII strings are supported as input. + + expandstring:... Same as string but with type 2. + + dword:0x01234567 A DWORD (type 4) with the hex value + 0x01234567. You can also use decimal + or octal numbers here. + + qword:0x0123456789abcdef + A QWORD (type 11) with the hex value + 0x0123456789abcdef. You can also use + decimal or octal numbers here. + + hex:<type>:<hexbytes> + hex:1:41,00,42,00,43,00,44,00,00,00 + This is the generic way to enter any + value. <type> is the integer value type. + <hexbytes> is a list of pairs of hex + digits which are treated as bytes. + (Any non-hex-digits here are ignored, + so you can separate bytes with commas + or spaces if you want). + =back =head1 EXAMPLE -- 1.6.5.2
Richard W.M. Jones
2010-Feb-03 18:37 UTC
[Libguestfs] [PATCH 11/12] hivex: Minimal registry example.
Although this is an 8K binary file that has no source as such, I think it is worth including this file because it will allow us to write automated tests of hivexsh. The reason it is necessary for testing is because we cannot create a hive from scratch (the format is not sufficiently documented to allow us to do that). Therefore all tests have to start from an existing hive, and this means we either have to supply some minimal hive like this, or get the user to supply a hive file. Rich. -- Richard Jones, Virtualization Group, Red Hat http://people.redhat.com/~rjones virt-top is 'top' for virtual machines. Tiny program with many powerful monitoring features, net stats, disk stats, logging, etc. http://et.redhat.com/~rjones/virt-top -------------- next part -------------->From 89ad8b8596461996636555f289e4b77ce3004d95 Mon Sep 17 00:00:00 2001From: Richard Jones <rjones at redhat.com> Date: Wed, 3 Feb 2010 18:09:52 +0000 Subject: [PATCH 11/12] hivex: Minimal registry example. This is the smallest registry you can make and still have it load correctly in Windows regedit. --- configure.ac | 1 + hivex/Makefile.am | 2 +- hivex/t/Makefile.am | 18 ++++++++++++++++++ hivex/t/README | 9 +++++++++ hivex/t/minimal | Bin 0 -> 8192 bytes 5 files changed, 29 insertions(+), 1 deletions(-) create mode 100644 hivex/t/Makefile.am create mode 100644 hivex/t/README create mode 100755 hivex/t/minimal diff --git a/configure.ac b/configure.ac index 134cebd..820b236 100644 --- a/configure.ac +++ b/configure.ac @@ -737,6 +737,7 @@ AC_CONFIG_FILES([Makefile gnulib/lib/Makefile gnulib/tests/Makefile hivex/Makefile + hivex/t/Makefile hivex/tools/Makefile fuse/Makefile ocaml/META perl/Makefile.PL]) diff --git a/hivex/Makefile.am b/hivex/Makefile.am index 987cfc9..e11b95a 100644 --- a/hivex/Makefile.am +++ b/hivex/Makefile.am @@ -15,7 +15,7 @@ # along with this program; if not, write to the Free Software # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. -SUBDIRS = tools +SUBDIRS = t tools EXTRA_DIST = hivex.pod hivexml.pod hivexget.pod hivexsh.pod LICENSE diff --git a/hivex/t/Makefile.am b/hivex/t/Makefile.am new file mode 100644 index 0000000..217d7ca --- /dev/null +++ b/hivex/t/Makefile.am @@ -0,0 +1,18 @@ +# libguestfs +# Copyright (C) 2009 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. + +EXTRA_DIST = minimal diff --git a/hivex/t/README b/hivex/t/README new file mode 100644 index 0000000..b7e297a --- /dev/null +++ b/hivex/t/README @@ -0,0 +1,9 @@ +This directory contains tests for the hivex library. + +'minimal' is a valid registry containing a single root nk (with +associated sk) which was created by chopping out everything possible +from a Windows 2003 software hive and then doing lots of hand edits on +the result. There is no "source" for it as such, it is just a +hand-crafted binary blob. + +- Richard W.M. Jones 2010-01-23. diff --git a/hivex/t/minimal b/hivex/t/minimal new file mode 100755 index 0000000000000000000000000000000000000000..3f4ee58c0adc1c3a73da8fed459304fd1083fce7 GIT binary patch literal 8192 zcmeI0y-EX75QWc17h&;d5sL^GD+X-D+AjD at 1Ti8}5K<}rgvB7y2iVdFu(1eO_#Rq{ zm5<;9_yiWNbM6|<MgtZWDb6f+XXfnOxx?4(Z5^MUzyr8muV30 at _s<?v-xiH=#30P# zI?DFcv4?G}n!iE|4ICkM at i{KcuVEb<Shkui)VNnRkOUqMmfi<)FbyRFA|L`HAOa#F z0wN#+A|L`H at b3i94jWC^)c)@~*0~2 at ewU_cGhRS6zjDjF0cwvUd}n5#oiSsOe0k=* zHflntRNC3C)~bt@&3%4<`bg8u7)UJU;sG=E at -TeEBy=YNQ>GX2hQK^f#I$8z!6klV zZkBOSq>%j3f2p7AQ}1-EFJmItKl-i at yZbxcrgQb#to|gY2sp8g->voSo2wn-#@;jX geSpe1t7IY|0wN#+A|L`HAOa#F0wN#+BJei?pOc0_k^lez literal 0 HcmV?d00001 -- 1.6.5.2
Richard W.M. Jones
2010-Feb-03 18:38 UTC
[Libguestfs] [PATCH 12/12 (NOT FOR REVIEW)] hivex: Implement adding and deleting child nodes.
WIP ... -- Richard Jones, Virtualization Group, Red Hat http://people.redhat.com/~rjones virt-top is 'top' for virtual machines. Tiny program with many powerful monitoring features, net stats, disk stats, logging, etc. http://et.redhat.com/~rjones/virt-top -------------- next part -------------->From 520c38b211a35cb77d8bec98aa4d77dfb8042aad Mon Sep 17 00:00:00 2001From: Richard Jones <rjones at redhat.com> Date: Wed, 3 Feb 2010 18:10:38 +0000 Subject: [PATCH 12/12] hivex: Implement adding and deleting child nodes. --- hivex/hivex.c | 145 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ hivex/hivex.h | 4 ++ hivex/hivex.pod | 18 +++++++ 3 files changed, 167 insertions(+), 0 deletions(-) diff --git a/hivex/hivex.c b/hivex/hivex.c index af36868..f197cd3 100644 --- a/hivex/hivex.c +++ b/hivex/hivex.c @@ -1952,6 +1952,151 @@ 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 + +#if 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) +{ + hive_node_h *unused; + size_t *blocks; + if (get_children (h, node, &unused, &blocks) == -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; + + /* XXX We should decrement the refcount. Deleting the SK is tricky + * because we don't yet know about the sk_prev and sk_next fields. + + mark_block_unused (node->sk); + */ + + /* XXX Should do this: + if (node->classname != -1) + mark_block_unused (node->classname); + */ + + /* 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) == -1) + return -1; + + 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 + node); + size_t nr_subkeys_in_nk = le32toh (nk->nr_subkeys); + nk->nr_subkeys = htole32 (nr_subkeys_in_nk - 1); + + return 0; +} +#endif + 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..296a1a5 100644 --- a/hivex/hivex.h +++ b/hivex/hivex.h @@ -111,6 +111,10 @@ 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); +#if 0 +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); +#endif 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 -- 1.6.5.2
Reasonably Related Threads
- [PATCH REBASED] Remove main loop
- [PATCH 0/3] 3 small code fixes
- [PATCH 0/4] Fix RHBZ#597112 (get-e2uuid command)
- [PATCH 0/4] Allow QEMU if=... (block device emulation) to be overridden
- [PATCH 0/2] Use link-local addresses when communicating between appliance and host (RHBZ#588763)