hivexsh: Add 'setval' and 'commit' commands.
authorRichard Jones <rjones@redhat.com>
Wed, 3 Feb 2010 18:04:31 +0000 (18:04 +0000)
committerRichard Jones <rjones@redhat.com>
Fri, 19 Feb 2010 15:00:41 +0000 (15:00 +0000)
This adds the 'setval' and 'commit' commands to the hivex shell.

Also adds some example scripts showing use of these.

hivex/Makefile.am
hivex/example1 [new file with mode: 0755]
hivex/example2 [new file with mode: 0755]
hivex/example3 [new file with mode: 0755]
hivex/hivexsh.c
hivex/hivexsh.pod

index 312cc3c..987cfc9 100644 (file)
@@ -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 (executable)
index 0000000..5b1313f
--- /dev/null
@@ -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 (executable)
index 0000000..8d27546
--- /dev/null
@@ -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 (executable)
index 0000000..b482e41
--- /dev/null
@@ -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
index 01a5ddc..6f33f41 100644 (file)
 //#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;
+}
index d13c70b..e7e8d94 100644 (file)
@@ -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