Пример #1
0
hive_value_h
hivex_value_data_cell_offset (hive_h *h, hive_value_h value, size_t *len)
{
  if (!IS_VALID_BLOCK (h, value) || !block_id_eq (h, value, "vk")) {
    SET_ERRNO (EINVAL, "invalid block or not an 'vk' block");
    return 0;
  }

  DEBUG (2, "value=0x%zx", value);
  struct ntreg_vk_record *vk =
    (struct ntreg_vk_record *) ((char *) h->addr + value);

  size_t data_len;
  int is_inline;

  data_len = le32toh (vk->data_len);
  is_inline = !!(data_len & 0x80000000);
  data_len &= 0x7fffffff;

  DEBUG (2, "is_inline=%d", is_inline);

  DEBUG (2, "data_len=%zx", data_len);

  if (is_inline && data_len > 4) {
    SET_ERRNO (ENOTSUP, "inline data with declared length (%zx) > 4", data_len);
    return 0;
  }

  if (is_inline) {
    /* There is no other location for the value data. */
    if (len)
      *len = 0;
    errno = 0;
    return 0;
  } else {
    if (len)
      *len = data_len + 4;  /* Include 4 header length bytes */
  }

  DEBUG (2, "proceeding with indirect data");

  size_t data_offset = le32toh (vk->data_offset);
  data_offset += 0x1000;  /* Add 0x1000 because everything's off by 4KiB */
  if (!IS_VALID_BLOCK (h, data_offset)) {
    SET_ERRNO (EFAULT, "data offset is not a valid block (0x%zx)", data_offset);
    return 0;
  }

  DEBUG (2, "data_offset=%zx", data_offset);

  return data_offset;
}
Пример #2
0
char *
hivex_value_key (hive_h *h, hive_value_h value)
{
  if (!IS_VALID_BLOCK (h, value) || !block_id_eq (h, value, "vk")) {
    SET_ERRNO (EINVAL, "invalid block or not an 'vk' block");
    return 0;
  }

  struct ntreg_vk_record *vk =
    (struct ntreg_vk_record *) ((char *) h->addr + value);

  size_t flags = le16toh (vk->flags);
  size_t len = le16toh (vk->name_len);

  size_t seg_len = block_len (h, value, NULL);
  if (sizeof (struct ntreg_vk_record) + len - 1 > seg_len) {
    SET_ERRNO (EFAULT, "key length is too long (%zu, %zu)", len, seg_len);
    return NULL;
  }
  if (flags & 0x01) {
    return _hivex_windows_latin1_to_utf8 (vk->name, len);
  } else {
    return _hivex_windows_utf16_to_utf8 (vk->name, len);
  }
}
Пример #3
0
/* Insert node into existing lf/lh-record at position.
 * This allocates a new record and marks the old one as unused.
 */
static size_t
insert_lf_record (hive_h *h, size_t old_offs, size_t posn,
                  const char *name, hive_node_h node)
{
  assert (IS_VALID_BLOCK (h, old_offs));

  /* Work around C stupidity.
   * http://www.redhat.com/archives/libguestfs/2010-February/msg00056.html
   */
  int test = block_id_eq (h, old_offs, "lf") || block_id_eq (h, old_offs, "lh");
  assert (test);

  struct ntreg_lf_record *old_lf =
    (struct ntreg_lf_record *) ((char *) h->addr + old_offs);
  size_t nr_keys = le16toh (old_lf->nr_keys);

  if (nr_keys == UINT16_MAX) {
    SET_ERRNO (EOVERFLOW,
               "cannot extend record because it already contains the maximum number of subkeys (%zu)",
               nr_keys);
    return 0;
  }
  nr_keys++; /* in new record ... */

  size_t seg_len = sizeof (struct ntreg_lf_record) + (nr_keys-1) * 8;

  /* Copy the old_lf->id in case it moves during allocate_block. */
  char id[2];
  memcpy (id, old_lf->id, sizeof id);

  size_t new_offs = allocate_block (h, seg_len, id);
  if (new_offs == 0)
    return 0;

  /* old_lf could have been invalidated by allocate_block. */
  old_lf = (struct ntreg_lf_record *) ((char *) h->addr + old_offs);

  struct ntreg_lf_record *new_lf =
    (struct ntreg_lf_record *) ((char *) h->addr + new_offs);
  new_lf->nr_keys = htole16 (nr_keys);

  /* Copy the keys until we reach posn, insert the new key there, then
   * copy the remaining keys.
   */
  size_t i;
  for (i = 0; i < posn; ++i)
    new_lf->keys[i] = old_lf->keys[i];

  new_lf->keys[i].offset = htole32 (node - 0x1000);
  calc_hash (new_lf->id, name, new_lf->keys[i].hash);

  for (i = posn+1; i < nr_keys; ++i)
    new_lf->keys[i] = old_lf->keys[i-1];

  /* Old block is unused, return new block. */
  mark_block_unused (h, old_offs);
  return new_offs;
}
Пример #4
0
/* Insert node into existing li-record at position.  Pretty much the
 * same as insert_lf_record above, but the record layout is a bit
 * different.
 */
static size_t
insert_li_record (hive_h *h, size_t old_offs, size_t posn,
                  const char *name, hive_node_h node)
{
  assert (IS_VALID_BLOCK (h, old_offs));
  assert (block_id_eq (h, old_offs, "li"));

  struct ntreg_ri_record *old_li =
    (struct ntreg_ri_record *) ((char *) h->addr + old_offs);
  size_t nr_offsets = le16toh (old_li->nr_offsets);

  if (nr_offsets == UINT16_MAX) {
    SET_ERRNO (EOVERFLOW,
               "cannot extend record because it already contains the maximum number of subkeys (%zu)",
               nr_offsets);
    return 0;
  }
  nr_offsets++; /* in new record ... */

  size_t seg_len = sizeof (struct ntreg_ri_record) + (nr_offsets-1) * 4;

  /* Copy the old_li->id in case it moves during allocate_block. */
  char id[2];
  memcpy (id, old_li->id, sizeof id);

  size_t new_offs = allocate_block (h, seg_len, id);
  if (new_offs == 0)
    return 0;

  /* old_li could have been invalidated by allocate_block. */
  old_li = (struct ntreg_ri_record *) ((char *) h->addr + old_offs);

  struct ntreg_ri_record *new_li =
    (struct ntreg_ri_record *) ((char *) h->addr + new_offs);
  new_li->nr_offsets = htole16 (nr_offsets);

  /* Copy the offsets until we reach posn, insert the new offset
   * there, then copy the remaining offsets.
   */
  size_t i;
  for (i = 0; i < posn; ++i)
    new_li->offset[i] = old_li->offset[i];

  new_li->offset[i] = htole32 (node - 0x1000);

  for (i = posn+1; i < nr_offsets; ++i)
    new_li->offset[i] = old_li->offset[i-1];

  /* Old block is unused, return new block. */
  mark_block_unused (h, old_offs);
  return new_offs;
}
Пример #5
0
size_t
hivex_node_nr_values (hive_h *h, hive_node_h node)
{
  if (!IS_VALID_BLOCK (h, node) || !block_id_eq (h, node, "nk")) {
    SET_ERRNO (EINVAL, "invalid block or not an 'nk' block");
    return 0;
  }

  struct ntreg_nk_record *nk =
    (struct ntreg_nk_record *) ((char *) h->addr + node);

  size_t nr_values = le32toh (nk->nr_values);

  return nr_values;
}
Пример #6
0
/* '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));

  DEBUG (2, "marking 0x%zx unused", offset);

  struct ntreg_hbin_block *blockhdr =
    (struct ntreg_hbin_block *) ((char *) h->addr + offset);

  size_t seg_len = block_len (h, offset, NULL);
  blockhdr->seg_len = htole32 (seg_len);

  BITMAP_CLR (h->bitmap, offset);
}
Пример #7
0
/* Decrement the refcount of an sk-record, and if it reaches zero,
 * unlink it from the chain and delete it.
 */
static int
delete_sk (hive_h *h, size_t sk_offset)
{
  if (!IS_VALID_BLOCK (h, sk_offset) || !block_id_eq (h, sk_offset, "sk")) {
    SET_ERRNO (EFAULT, "not an sk record: 0x%zx", sk_offset);
    return -1;
  }

  struct ntreg_sk_record *sk =
    (struct ntreg_sk_record *) ((char *) h->addr + sk_offset);

  if (sk->refcount == 0) {
    SET_ERRNO (EINVAL, "sk record already has refcount 0: 0x%zx", sk_offset);
    return -1;
  }

  sk->refcount--;

  if (sk->refcount == 0) {
    size_t sk_prev_offset = sk->sk_prev;
    sk_prev_offset += 0x1000;

    size_t sk_next_offset = sk->sk_next;
    sk_next_offset += 0x1000;

    /* Update sk_prev/sk_next SKs, unless they both point back to this
     * cell in which case we are deleting the last SK.
     */
    if (sk_prev_offset != sk_offset && sk_next_offset != sk_offset) {
      struct ntreg_sk_record *sk_prev =
        (struct ntreg_sk_record *) ((char *) h->addr + sk_prev_offset);
      struct ntreg_sk_record *sk_next =
        (struct ntreg_sk_record *) ((char *) h->addr + sk_next_offset);

      sk_prev->sk_next = htole32 (sk_next_offset - 0x1000);
      sk_next->sk_prev = htole32 (sk_prev_offset - 0x1000);
    }

    /* Refcount is zero so really delete this block. */
    mark_block_unused (h, sk_offset);
  }

  return 0;
}
Пример #8
0
/* Compare name with name in nk-record. */
static int
compare_name_with_nk_name (hive_h *h, const char *name, hive_node_h nk_offs)
{
  assert (IS_VALID_BLOCK (h, nk_offs));
  assert (block_id_eq (h, nk_offs, "nk"));

  /* Name in nk is not necessarily nul-terminated. */
  char *nname = hivex_node_name (h, nk_offs);

  /* Unfortunately we don't have a way to return errors here. */
  if (!nname) {
    perror ("compare_name_with_nk_name");
    return 0;
  }

  int r = strcasecmp (name, nname);
  free (nname);

  return r;
}
Пример #9
0
int
hivex_value_type (hive_h *h, hive_value_h value, hive_type *t, size_t *len)
{
  if (!IS_VALID_BLOCK (h, value) || !block_id_eq (h, value, "vk")) {
    SET_ERRNO (EINVAL, "invalid block or not an 'vk' block");
    return -1;
  }

  struct ntreg_vk_record *vk =
    (struct ntreg_vk_record *) ((char *) h->addr + value);

  if (t)
    *t = le32toh (vk->data_type);

  if (len) {
    *len = le32toh (vk->data_len);
    *len &= 0x7fffffff;         /* top bit indicates if data is stored inline */
  }

  return 0;
}
Пример #10
0
size_t
hivex_value_key_len (hive_h *h, hive_value_h value)
{
  if (!IS_VALID_BLOCK (h, value) || !block_id_eq (h, value, "vk")) {
    SET_ERRNO (EINVAL, "invalid block or not an 'vk' block");
    return 0;
  }

  struct ntreg_vk_record *vk =
    (struct ntreg_vk_record *) ((char *) h->addr + value);

  /* vk->name_len is unsigned, 16 bit, so this is safe ...  However
   * we have to make sure the length doesn't exceed the block length.
   */
  size_t len = le16toh (vk->name_len);

  size_t seg_len = block_len (h, value, NULL);
  if (sizeof (struct ntreg_vk_record) + len - 1 > seg_len) {
    SET_ERRNO (EFAULT, "key length is too long (%zu, %zu)", len, seg_len);
    return 0;
  }
  return _hivex_utf8_strlen (vk->name, len, ! (le16toh (vk->flags) & 0x01));
}
Пример #11
0
int
_hivex_get_values (hive_h *h, hive_node_h node,
                   hive_value_h **values_ret, size_t **blocks_ret)
{
  if (!IS_VALID_BLOCK (h, node) || !block_id_eq (h, node, "nk")) {
    SET_ERRNO (EINVAL, "invalid block or not an 'nk' block");
    return -1;
  }

  struct ntreg_nk_record *nk =
    (struct ntreg_nk_record *) ((char *) h->addr + node);

  size_t nr_values = le32toh (nk->nr_values);

  DEBUG (2, "nr_values = %zu", nr_values);

  offset_list values, blocks;
  _hivex_init_offset_list (h, &values);
  _hivex_init_offset_list (h, &blocks);

  /* Deal with the common "no values" case quickly. */
  if (nr_values == 0)
    goto ok;

  /* Arbitrarily limit the number of values we will ever deal with. */
  if (nr_values > HIVEX_MAX_VALUES) {
    SET_ERRNO (ERANGE,
               "nr_values > HIVEX_MAX_VALUES (%zu > %d)",
               nr_values, HIVEX_MAX_VALUES);
    goto error;
  }

  /* Preallocate space for the values. */
  if (_hivex_grow_offset_list (&values, nr_values) == -1)
    goto error;

  /* Get the value list and check it looks reasonable. */
  size_t vlist_offset = le32toh (nk->vallist);
  vlist_offset += 0x1000;
  if (!IS_VALID_BLOCK (h, vlist_offset)) {
    SET_ERRNO (EFAULT,
               "value list is not a valid block (0x%zx)", vlist_offset);
    goto error;
  }

  if (_hivex_add_to_offset_list (&blocks, vlist_offset) == -1)
    goto error;

  struct ntreg_value_list *vlist =
    (struct ntreg_value_list *) ((char *) h->addr + vlist_offset);

  size_t len = block_len (h, vlist_offset, NULL);
  if (4 + nr_values * 4 > len) {
    SET_ERRNO (EFAULT, "value list is too long (%zu, %zu)", nr_values, len);
    goto error;
  }

  size_t i;
  for (i = 0; i < nr_values; ++i) {
    hive_node_h value = le32toh (vlist->offset[i]);
    value += 0x1000;
    if (!IS_VALID_BLOCK (h, value)) {
      SET_ERRNO (EFAULT, "value is not a valid block (0x%zx)", value);
      goto error;
    }
    if (_hivex_add_to_offset_list (&values, value) == -1)
      goto error;
  }

 ok:
  *values_ret = _hivex_return_offset_list (&values);
  *blocks_ret = _hivex_return_offset_list (&blocks);
  if (!*values_ret || !*blocks_ret)
    goto error;
  return 0;

 error:
  _hivex_free_offset_list (&values);
  _hivex_free_offset_list (&blocks);
  return -1;
}
Пример #12
0
char *
hivex_value_value (hive_h *h, hive_value_h value,
                   hive_type *t_rtn, size_t *len_rtn)
{
  if (!IS_VALID_BLOCK (h, value) || !block_id_eq (h, value, "vk")) {
    SET_ERRNO (EINVAL, "invalid block or not an 'vk' block");
    return NULL;
  }

  struct ntreg_vk_record *vk =
    (struct ntreg_vk_record *) ((char *) h->addr + value);

  hive_type t;
  size_t len;
  int is_inline;

  t = le32toh (vk->data_type);

  len = le32toh (vk->data_len);
  is_inline = !!(len & 0x80000000);
  len &= 0x7fffffff;

  DEBUG (2, "value=0x%zx, t=%u, len=%zu, inline=%d",
         value, t, len, is_inline);

  if (t_rtn)
    *t_rtn = t;
  if (len_rtn)
    *len_rtn = len;

  if (is_inline && len > 4) {
    SET_ERRNO (ENOTSUP, "inline data with declared length (%zx) > 4", len);
    return NULL;
  }

  /* Arbitrarily limit the length that we will read. */
  if (len > HIVEX_MAX_VALUE_LEN) {
    SET_ERRNO (ERANGE, "data length > HIVEX_MAX_VALUE_LEN (%zu > %d)",
               len, HIVEX_MAX_VALUE_LEN);
    return NULL;
  }

  char *ret = malloc (len);
  if (ret == NULL)
    return NULL;

  if (is_inline) {
    memcpy (ret, (char *) &vk->data_offset, len);
    return ret;
  }

  size_t data_offset = le32toh (vk->data_offset);
  data_offset += 0x1000;
  if (!IS_VALID_BLOCK (h, data_offset)) {
    SET_ERRNO (EFAULT, "data offset is not a valid block (0x%zx)", data_offset);
    free (ret);
    return NULL;
  }

  /* Check that the declared size isn't larger than the block its in.
   *
   * XXX Some apparently valid registries are seen to have this,
   * so turn this into a warning and substitute the smaller length
   * instead.
   */
  size_t blen = block_len (h, data_offset, NULL);
  if (len <= blen - 4 /* subtract 4 for block header */) {
    char *data = (char *) h->addr + data_offset + 4;
    memcpy (ret, data, len);
    return ret;
  } else {
    if (!IS_VALID_BLOCK (h, data_offset) ||
        !block_id_eq (h, data_offset, "db")) {
      SET_ERRNO (EINVAL,
                 "declared data length is longer than the block and "
                 "block is not a db block (data 0x%zx, data len %zu)",
                 data_offset, len);
      free (ret);
      return NULL;
    }
    struct ntreg_db_record *db =
      (struct ntreg_db_record *) ((char *) h->addr + data_offset);
    size_t blocklist_offset = le32toh (db->blocklist_offset);
    blocklist_offset += 0x1000;
    size_t nr_blocks = le16toh (db->nr_blocks);
    if (!IS_VALID_BLOCK (h, blocklist_offset)) {
      SET_ERRNO (EINVAL,
                 "blocklist is not a valid block "
                 "(db block 0x%zx, blocklist 0x%zx)",
                 data_offset, blocklist_offset);
      free (ret);
      return NULL;
    }
    struct ntreg_value_list *bl =
      (struct ntreg_value_list *) ((char *) h->addr + blocklist_offset);
    size_t i, off;
    for (i = off = 0; i < nr_blocks; ++i) {
      size_t subblock_offset = le32toh (bl->offset[i]);
      subblock_offset += 0x1000;
      if (!IS_VALID_BLOCK (h, subblock_offset)) {
        SET_ERRNO (EINVAL,
                   "subblock is not valid "
                   "(db block 0x%zx, block list 0x%zx, data subblock 0x%zx)",
                   data_offset, blocklist_offset, subblock_offset);
        free (ret);
        return NULL;
      }
      int32_t seg_len = block_len(h, subblock_offset, NULL);
      struct ntreg_db_block *subblock =
        (struct ntreg_db_block *) ((char *) h->addr + subblock_offset);
      int32_t sz = seg_len - 8; /* don't copy the last 4 bytes */
      if (off + sz > len) {
        sz = len - off;
      }
      memcpy (ret + off, subblock->data, sz);
      off += sz;
    }
    if (off != *len_rtn) {
      DEBUG (2, "warning: declared data length "
             "and amount of data found in sub-blocks differ "
             "(db block 0x%zx, data len %zu, found data %zu)",
             data_offset, *len_rtn, off);
      *len_rtn = off;
    }
    return ret;
  }
}
Пример #13
0
int
hivex_node_set_values (hive_h *h, hive_node_h node,
                       size_t nr_values, const hive_set_value *values,
                       int flags)
{
  CHECK_WRITABLE (-1);

  if (!IS_VALID_BLOCK (h, node) || !block_id_eq (h, node, "nk")) {
    SET_ERRNO (EINVAL, "invalid block or not an 'nk' block");
    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 *) ((char *) h->addr + node);
  nk->nr_values = htole32 (nr_values);
  nk->vallist = htole32 (vallist_offs - 0x1000);


  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' };
    size_t recoded_name_len;
    int use_utf16;
    char* recoded_name = _hivex_encode_string (h, values[i].key, &recoded_name_len,
                                               &use_utf16);
    seg_len = sizeof (struct ntreg_vk_record) + recoded_name_len;
    size_t vk_offs = allocate_block (h, seg_len, vk_id);
    if (vk_offs == 0)
      return -1;

    /* Recalculate pointers that could have been invalidated by
     * previous call to allocate_block.
     */
    nk = (struct ntreg_nk_record *) ((char *) h->addr + node);
    struct ntreg_value_list *vallist =
      (struct ntreg_value_list *) ((char *) h->addr + vallist_offs);

    vallist->offset[i] = htole32 (vk_offs - 0x1000);

    struct ntreg_vk_record *vk =
      (struct ntreg_vk_record *) ((char *) h->addr + vk_offs);
    vk->name_len = htole16 (recoded_name_len);
    memcpy (vk->name, recoded_name, recoded_name_len);
    free (recoded_name);
    vk->data_type = htole32 (values[i].t);
    uint32_t len = values[i].len;
    if (len <= 4)               /* store it inline => set MSB flag */
      len |= 0x80000000;
    vk->data_len = htole32 (len);
    if (recoded_name_len == 0)
      vk->flags = 0;
    else
      vk->flags = htole16 (!use_utf16);

    if (values[i].len <= 4)     /* store it 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;

      /* Recalculate pointers that could have been invalidated by
       * previous call to allocate_block.
       */
      nk = (struct ntreg_nk_record *) ((char *) h->addr + node);
      /* vallist could be invalid here */
      vk = (struct ntreg_vk_record *) ((char *) h->addr + vk_offs);

      memcpy ((char *) h->addr + offs + 4, values[i].value, values[i].len);
      vk->data_offset = htole32 (offs - 0x1000);
    }

    size_t utf16_len = use_utf16 ? recoded_name_len : recoded_name_len * 2;
    if (utf16_len > le32toh (nk->max_vk_name_len))
      nk->max_vk_name_len = htole32 (utf16_len);
    if (values[i].len > le32toh (nk->max_vk_data_len))
      nk->max_vk_data_len = htole32 (values[i].len);
  }

  return 0;
}
Пример #14
0
int
hivex_node_delete_child (hive_h *h, hive_node_h node)
{
  CHECK_WRITABLE (-1);

  if (!IS_VALID_BLOCK (h, node) || !block_id_eq (h, node, "nk")) {
    SET_ERRNO (EINVAL, "invalid block or not an 'nk' block");
    return -1;
  }

  if (node == hivex_root (h)) {
    SET_ERRNO (EINVAL, "cannot delete root node");
    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 (_hivex_get_children (h, parent,
                           &unused, &blocks, GET_CHILDREN_NO_CHECK_NK)== -1)
    return -1;
  free (unused);

  size_t i, j;
  for (i = 0; blocks[i] != 0; ++i) {
    struct ntreg_hbin_block *block =
      (struct ntreg_hbin_block *) ((char *) 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;
        }
    }
  }
  free (blocks);
  SET_ERRNO (ENOTSUP, "could not find parent to child link");
  return -1;

 found:;
  free (blocks);

  struct ntreg_nk_record *nk =
    (struct ntreg_nk_record *) ((char *) h->addr + parent);
  size_t nr_subkeys_in_nk = le32toh (nk->nr_subkeys);
  nk->nr_subkeys = htole32 (nr_subkeys_in_nk - 1);

  DEBUG (2, "updating nr_subkeys in parent 0x%zx to %zu",
         parent, nr_subkeys_in_nk);

  return 0;
}
Пример #15
0
hive_node_h
hivex_node_add_child (hive_h *h, hive_node_h parent, const char *name)
{
  CHECK_WRITABLE (0);

  if (!IS_VALID_BLOCK (h, parent) || !block_id_eq (h, parent, "nk")) {
    SET_ERRNO (EINVAL, "invalid block or not an 'nk' block");
    return 0;
  }

  if (name == NULL || strlen (name) == 0) {
    SET_ERRNO (EINVAL, "name is NULL or zero length");
    return 0;
  }

  if (hivex_node_get_child (h, parent, name) != 0) {
    SET_ERRNO (EEXIST, "a child with that name exists already");
    return 0;
  }

  size_t recoded_name_len;
  int use_utf16 = 0;
  char *recoded_name =
    _hivex_encode_string (h, name, &recoded_name_len, &use_utf16);
  if (recoded_name == NULL) {
    SET_ERRNO (EINVAL, "malformed name");
    return 0;
  }

  /* Create the new nk-record. */
  static const char nk_id[2] = { 'n', 'k' };
  size_t seg_len = sizeof (struct ntreg_nk_record) + recoded_name_len;
  hive_node_h nkoffset = allocate_block (h, seg_len, nk_id);
  if (nkoffset == 0) {
    free (recoded_name);
    return 0;
  }

  DEBUG (2, "allocated new nk-record for child at 0x%zx", nkoffset);

  struct ntreg_nk_record *nk =
    (struct ntreg_nk_record *) ((char *) h->addr + nkoffset);
  if (use_utf16)
    nk->flags = htole16 (0x0000);
  else
    nk->flags = htole16 (0x0020);
  nk->parent = htole32 (parent - 0x1000);
  nk->subkey_lf = htole32 (0xffffffff);
  nk->subkey_lf_volatile = htole32 (0xffffffff);
  nk->vallist = htole32 (0xffffffff);
  nk->classname = htole32 (0xffffffff);
  nk->name_len = htole16 (recoded_name_len);
  memcpy (nk->name, recoded_name, recoded_name_len);
  free (recoded_name);

  /* Inherit parent sk. */
  struct ntreg_nk_record *parent_nk =
    (struct ntreg_nk_record *) ((char *) h->addr + parent);
  size_t parent_sk_offset = le32toh (parent_nk->sk);
  parent_sk_offset += 0x1000;
  if (!IS_VALID_BLOCK (h, parent_sk_offset) ||
      !block_id_eq (h, parent_sk_offset, "sk")) {
    SET_ERRNO (EFAULT,
               "parent sk is not a valid block (%zu)", parent_sk_offset);
    return 0;
  }
  struct ntreg_sk_record *sk =
    (struct ntreg_sk_record *) ((char *) h->addr + parent_sk_offset);
  sk->refcount = htole32 (le32toh (sk->refcount) + 1);
  nk->sk = htole32 (parent_sk_offset - 0x1000);

  /* Inherit parent timestamp. */
  nk->timestamp = parent_nk->timestamp;

  /* What I found out the hard way (not documented anywhere): the
   * subkeys in lh-records must be kept sorted.  If you just add a
   * subkey in a non-sorted position (eg. just add it at the end) then
   * Windows won't see the subkey _and_ Windows will corrupt the hive
   * itself when it modifies or saves it.
   *
   * So use get_children() to get a list of intermediate records.
   * get_children() returns these in reading order (which is sorted),
   * so we look for the lf/lh/li-records in sequence until we find the
   * key name just after the one we are inserting, and we insert the
   * subkey just before it.
   *
   * The only other case is the no-subkeys case, where we have to
   * create a brand new lh-record.
   */
  hive_node_h *unused;
  size_t *blocks;

  if (_hivex_get_children (h, parent, &unused, &blocks, 0) == -1)
    return 0;
  free (unused);

  size_t i;
  size_t nr_subkeys_in_parent_nk = le32toh (parent_nk->nr_subkeys);

  if (nr_subkeys_in_parent_nk == UINT32_MAX) {
    free (blocks);
    SET_ERRNO (EOVERFLOW,
               "too many subkeys (%zu)", nr_subkeys_in_parent_nk);
    return 0;
  }

  if (nr_subkeys_in_parent_nk == 0) { /* No subkeys case. */
    /* Free up any existing intermediate blocks. */
    for (i = 0; blocks[i] != 0; ++i)
      mark_block_unused (h, blocks[i]);
    size_t lh_offs = new_lh_record (h, name, nkoffset);
    if (lh_offs == 0) {
      free (blocks);
      return 0;
    }

    /* Recalculate pointers that could have been invalidated by
     * previous call to allocate_block (via new_lh_record).
     */
    /* nk could be invalid here */
    parent_nk = (struct ntreg_nk_record *) ((char *) h->addr + parent);

    DEBUG (2, "no keys, allocated new lh-record at 0x%zx", lh_offs);

    parent_nk->subkey_lf = htole32 (lh_offs - 0x1000);
  }
  else {                        /* Insert subkeys case. */
    if (insert_subkey (h, name, parent, nkoffset, blocks) == -1) {
      free (blocks);
      return 0;
    }

    /* Recalculate pointers that could have been invalidated by
     * previous call to allocate_block (via new_lh_record).
     */
    /* nk could be invalid here */
    parent_nk = (struct ntreg_nk_record *) ((char *) h->addr + parent);
  }

  free (blocks);

  /* Update nr_subkeys in parent nk. */
  nr_subkeys_in_parent_nk++;
  parent_nk->nr_subkeys = htole32 (nr_subkeys_in_parent_nk);

  /* Update max_subkey_name_len in parent nk. */
  size_t utf16_len = use_utf16 ? recoded_name_len : recoded_name_len * 2;
  if (le16toh (parent_nk->max_subkey_name_len) < utf16_len)
    parent_nk->max_subkey_name_len = htole16 (utf16_len);

  return nkoffset;
}