/* * Parse a V 2.0 USN record. * Returns 0 on success, 1 otherwise */ static uint8_t parse_v2_record(const unsigned char *buf, TSK_USN_RECORD_HEADER *header, TSK_USN_RECORD_V2 *record, TSK_ENDIAN_ENUM endian) { uint64_t timestamp = 0; uint16_t name_offset = 0, name_length = 0; record->refnum = tsk_getu48(endian, &buf[8]); record->refnum_seq = tsk_getu16(endian, &buf[14]); record->parent_refnum = tsk_getu48(endian, &buf[16]); record->parent_refnum_seq = tsk_getu16(endian, &buf[22]); record->usn = tsk_getu64(endian, &buf[24]); /* Convert NT timestamp into Unix */ timestamp = tsk_getu64(endian, &buf[32]); record->time_sec = nt2unixtime(timestamp); record->time_nsec = nt2nano(timestamp); record->reason = tsk_getu32(endian, &buf[40]); record->source_info = tsk_getu32(endian, &buf[44]); record->security = tsk_getu32(endian, &buf[48]); record->attributes = tsk_getu32(endian, &buf[52]); /* Extract file name */ name_length = tsk_getu16(endian, &buf[56]); name_offset = tsk_getu16(endian, &buf[58]); return parse_fname(&buf[name_offset], name_length, record, endian); }
static uint8_t ntfs_dent_copy(NTFS_INFO * ntfs, ntfs_idxentry * idxe, TSK_FS_NAME * fs_name) { ntfs_attr_fname *fname = (ntfs_attr_fname *) & idxe->stream; TSK_FS_INFO *fs = (TSK_FS_INFO *) & ntfs->fs_info; UTF16 *name16; UTF8 *name8; int retVal; int i; fs_name->meta_addr = tsk_getu48(fs->endian, idxe->file_ref); fs_name->meta_seq = tsk_getu16(fs->endian, idxe->seq_num); name16 = (UTF16 *) & fname->name; name8 = (UTF8 *) fs_name->name; retVal = tsk_UTF16toUTF8(fs->endian, (const UTF16 **) &name16, (UTF16 *) ((uintptr_t) name16 + fname->nlen * 2), &name8, (UTF8 *) ((uintptr_t) name8 + fs_name->name_size), TSKlenientConversion); if (retVal != TSKconversionOK) { *name8 = '\0'; if (tsk_verbose) tsk_fprintf(stderr, "Error converting NTFS name to UTF8: %d %" PRIuINUM, retVal, fs_name->meta_addr); } /* Make sure it is NULL Terminated */ if ((uintptr_t) name8 > (uintptr_t) fs_name->name + fs_name->name_size) fs_name->name[fs_name->name_size] = '\0'; else *name8 = '\0'; /* Clean up name */ i = 0; while (fs_name->name[i] != '\0') { if (TSK_IS_CNTRL(fs_name->name[i])) fs_name->name[i] = '^'; i++; } if (tsk_getu64(fs->endian, fname->flags) & NTFS_FNAME_FLAGS_DIR) fs_name->type = TSK_FS_NAME_TYPE_DIR; else fs_name->type = TSK_FS_NAME_TYPE_REG; fs_name->flags = 0; return 0; }
// @@@ Should make a_idxe const and use internal pointer in function loop static TSK_RETVAL_ENUM ntfs_proc_idxentry(NTFS_INFO * a_ntfs, TSK_FS_DIR * a_fs_dir, uint8_t a_is_del, ntfs_idxentry * a_idxe, uint32_t a_idxe_len, uint32_t a_used_len) { uintptr_t endaddr, endaddr_alloc; TSK_FS_NAME *fs_name; TSK_FS_INFO *fs = (TSK_FS_INFO *) & a_ntfs->fs_info; if ((fs_name = tsk_fs_name_alloc(NTFS_MAXNAMLEN_UTF8, 0)) == NULL) { return TSK_ERR; } if (tsk_verbose) tsk_fprintf(stderr, "ntfs_proc_idxentry: Processing index entry: %" PRIu64 " Size: %" PRIu32 " Len: %" PRIu32 "\n", (uint64_t) ((uintptr_t) a_idxe), a_idxe_len, a_used_len); /* Sanity check */ if (a_idxe_len < a_used_len) { tsk_error_reset(); tsk_errno = TSK_ERR_FS_ARG; snprintf(tsk_errstr, TSK_ERRSTR_L, "ntfs_proc_idxentry: Allocated length of index entries is larger than buffer length"); return TSK_ERR; } /* where is the end of the buffer */ endaddr = ((uintptr_t) a_idxe + a_idxe_len); /* where is the end of the allocated data */ endaddr_alloc = ((uintptr_t) a_idxe + a_used_len); /* cycle through the index entries, based on provided size */ while (((uintptr_t) & (a_idxe->stream) + sizeof(ntfs_attr_fname)) < endaddr) { ntfs_attr_fname *fname = (ntfs_attr_fname *) & a_idxe->stream; if (tsk_verbose) tsk_fprintf(stderr, "ntfs_proc_idxentry: New IdxEnt: %" PRIu64 " $FILE_NAME Entry: %" PRIu64 " File Ref: %" PRIu64 " IdxEnt Len: %" PRIu16 " StrLen: %" PRIu16 "\n", (uint64_t) ((uintptr_t) a_idxe), (uint64_t) ((uintptr_t) fname), (uint64_t) tsk_getu48(fs->endian, a_idxe->file_ref), tsk_getu16(fs->endian, a_idxe->idxlen), tsk_getu16(fs->endian, a_idxe->strlen)); /* perform some sanity checks on index buffer head * and advance by 4-bytes if invalid */ if ((tsk_getu48(fs->endian, a_idxe->file_ref) > fs->last_inum) || (tsk_getu48(fs->endian, a_idxe->file_ref) < fs->first_inum) || (tsk_getu16(fs->endian, a_idxe->idxlen) <= tsk_getu16(fs->endian, a_idxe->strlen)) || (tsk_getu16(fs->endian, a_idxe->idxlen) % 4) || (tsk_getu16(fs->endian, a_idxe->idxlen) > a_idxe_len)) { a_idxe = (ntfs_idxentry *) ((uintptr_t) a_idxe + 4); continue; } /* do some sanity checks on the deleted entries */ if ((tsk_getu16(fs->endian, a_idxe->strlen) == 0) || (((uintptr_t) a_idxe + tsk_getu16(fs->endian, a_idxe->idxlen)) > endaddr_alloc)) { /* name space checks */ if ((fname->nspace != NTFS_FNAME_POSIX) && (fname->nspace != NTFS_FNAME_WIN32) && (fname->nspace != NTFS_FNAME_DOS) && (fname->nspace != NTFS_FNAME_WINDOS)) { a_idxe = (ntfs_idxentry *) ((uintptr_t) a_idxe + 4); if (tsk_verbose) tsk_fprintf(stderr, "ntfs_proc_idxentry: Skipping because of invalid name space\n"); continue; } if ((tsk_getu64(fs->endian, fname->alloc_fsize) < tsk_getu64(fs->endian, fname->real_fsize)) || (fname->nlen == 0) || (*(uint8_t *) & fname->name == 0)) { a_idxe = (ntfs_idxentry *) ((uintptr_t) a_idxe + 4); if (tsk_verbose) tsk_fprintf(stderr, "ntfs_proc_idxentry: Skipping because of reported file sizes, name length, or NULL name\n"); continue; } if ((is_time(tsk_getu64(fs->endian, fname->crtime)) == 0) || (is_time(tsk_getu64(fs->endian, fname->atime)) == 0) || (is_time(tsk_getu64(fs->endian, fname->mtime)) == 0)) { a_idxe = (ntfs_idxentry *) ((uintptr_t) a_idxe + 4); if (tsk_verbose) tsk_fprintf(stderr, "ntfs_proc_idxentry: Skipping because of invalid times\n"); continue; } } /* For all fname entries, there will exist a DOS style 8.3 * entry. We don't process those because we already processed * them before in their full version. If the type is * full POSIX or WIN32 that does not satisfy DOS, then a * type NTFS_FNAME_DOS will exist. If the name is WIN32, * but already satisfies DOS, then a type NTFS_FNAME_WINDOS * will exist * * Note that we could be missing some info from deleted files * if the windows version was deleted and the DOS wasn't... * * @@@ This should be added to the shrt_name entry of TSK_FS_NAME. The short * name entry typically comes after the long name */ if (fname->nspace == NTFS_FNAME_DOS) { if (tsk_verbose) tsk_fprintf(stderr, "ntfs_proc_idxentry: Skipping because of name space: %d\n", fname->nspace); goto incr_entry; } /* Copy it into the generic form */ if (ntfs_dent_copy(a_ntfs, a_idxe, fs_name)) { if (tsk_verbose) tsk_fprintf(stderr, "ntfs_proc_idxentry: Skipping because error copying dent_entry\n"); goto incr_entry; } /* * Check if this entry is deleted * * The final check is to see if the end of this entry is * within the space that the idxallocbuf claimed was valid OR * if the parent directory is deleted */ if ((a_is_del == 1) || (tsk_getu16(fs->endian, a_idxe->strlen) == 0) || (((uintptr_t) a_idxe + tsk_getu16(fs->endian, a_idxe->idxlen)) > endaddr_alloc)) { fs_name->flags = TSK_FS_NAME_FLAG_UNALLOC; } else { fs_name->flags = TSK_FS_NAME_FLAG_ALLOC; } if (tsk_verbose) tsk_fprintf(stderr, "ntfs_proc_idxentry: Entry Details of %s: Str Len: %" PRIu16 " Len to end after current: %" PRIu64 " flags: %x\n", fs_name->name, tsk_getu16(fs->endian, a_idxe->strlen), (uint64_t) (endaddr_alloc - (uintptr_t) a_idxe - tsk_getu16(fs->endian, a_idxe->idxlen)), fs_name->flags); if (tsk_fs_dir_add(a_fs_dir, fs_name)) { tsk_fs_name_free(fs_name); return TSK_ERR; } incr_entry: /* the theory here is that deleted entries have strlen == 0 and * have been found to have idxlen == 16 * * if the strlen is 0, then guess how much the indexlen was * before it was deleted */ /* 16: size of idxentry before stream * 66: size of fname before name * 2*nlen: size of name (in unicode) */ if (tsk_getu16(fs->endian, a_idxe->strlen) == 0) { a_idxe = (ntfs_idxentry *) ((((uintptr_t) a_idxe + 16 + 66 + 2 * fname->nlen + 3) / 4) * 4); } else { a_idxe = (ntfs_idxentry *) ((uintptr_t) a_idxe + tsk_getu16(fs->endian, a_idxe->idxlen)); } } /* end of loop of index entries */ tsk_fs_name_free(fs_name); return TSK_OK; }
/* * copy the index (directory) entry into the generic structure * * uses the global variables 'dirs' and 'depth' * * Returns 1 on eror and 0 on success */ static uint8_t ntfs_dent_copy(NTFS_INFO * ntfs, NTFS_DINFO * dinfo, ntfs_idxentry * idxe, TSK_FS_DENT * fs_dent) { ntfs_attr_fname *fname = (ntfs_attr_fname *) & idxe->stream; TSK_FS_INFO *fs = (TSK_FS_INFO *) & ntfs->fs_info; UTF16 *name16; UTF8 *name8; int retVal; int i; fs_dent->inode = tsk_getu48(fs->endian, idxe->file_ref); name16 = (UTF16 *) & fname->name; name8 = (UTF8 *) fs_dent->name; retVal = tsk_UTF16toUTF8(fs->endian, (const UTF16 **) &name16, (UTF16 *) ((uintptr_t) name16 + fname->nlen * 2), &name8, (UTF8 *) ((uintptr_t) name8 + fs_dent->name_max), TSKlenientConversion); if (retVal != TSKconversionOK) { *name8 = '\0'; if (tsk_verbose) tsk_fprintf(stderr, "Error converting NTFS name to UTF8: %d %" PRIuINUM, retVal, fs_dent->inode); } /* Make sure it is NULL Terminated */ if ((uintptr_t) name8 > (uintptr_t) fs_dent->name + fs_dent->name_max) fs_dent->name[fs_dent->name_max] = '\0'; else *name8 = '\0'; /* Clean up name */ i = 0; while (fs_dent->name[i] != '\0') { if (TSK_IS_CNTRL(fs_dent->name[i])) fs_dent->name[i] = '^'; i++; } /* copy the path data */ fs_dent->path = dinfo->dirs; fs_dent->pathdepth = dinfo->depth; /* Get the actual inode */ if (fs_dent->fsi != NULL) tsk_fs_inode_free(fs_dent->fsi); if (NULL == (fs_dent->fsi = fs->inode_lookup(fs, fs_dent->inode))) { if (tsk_verbose) { tsk_fprintf(stderr, "ntfs_dent_copy: error looking up inode: %" PRIuINUM "\n", fs_dent->inode); tsk_error_print(stderr); tsk_error_reset(); } } if (tsk_getu64(fs->endian, fname->flags) & NTFS_FNAME_FLAGS_DIR) fs_dent->ent_type = TSK_FS_DENT_TYPE_DIR; else fs_dent->ent_type = TSK_FS_DENT_TYPE_REG; fs_dent->flags = 0; return 0; }
/** * Process a lsit of index entries and call the callback for * each. * * @param list_seen List of directories that have already been analyzed * @param idxe Buffer with index entries to process * @param idxe_len Length of idxe buffer (in bytes) * @param used_len Length of data as reported by idexlist header (everything * after which and less then idxe_len is considered deleted) * @param flags (All we care about is ALLOC and UNALLOC) * @param action Callback * @param ptr Pointer to data to pass to callback * * @returns 1 to stop, 0 on success, and -1 on error */ static int ntfs_dent_idxentry(NTFS_INFO * ntfs, NTFS_DINFO * dinfo, TSK_LIST ** list_seen, ntfs_idxentry * idxe, uint32_t idxe_len, uint32_t used_len, int flags, TSK_FS_DENT_TYPE_WALK_CB action, void *ptr) { uintptr_t endaddr, endaddr_alloc; TSK_FS_DENT *fs_dent; TSK_FS_INFO *fs = (TSK_FS_INFO *) & ntfs->fs_info; if ((fs_dent = tsk_fs_dent_alloc(NTFS_MAXNAMLEN_UTF8, 0)) == NULL) { return -1; } if (tsk_verbose) tsk_fprintf(stderr, "ntfs_dent_idxentry: Processing index entry: %" PRIu64 " Size: %" PRIu32 " Len: %" PRIu32 " Flags: %x\n", (uint64_t) ((uintptr_t) idxe), idxe_len, used_len, flags); /* Sanity check */ if (idxe_len < used_len) { tsk_error_reset(); tsk_errno = TSK_ERR_FS_INODE_INT; snprintf(tsk_errstr, TSK_ERRSTR_L, "ntfs_dent_idxentry: Allocated length of index entries is larger than buffer length"); return 1; } /* where is the end of the buffer */ endaddr = ((uintptr_t) idxe + idxe_len); /* where is the end of the allocated data */ endaddr_alloc = ((uintptr_t) idxe + used_len); /* cycle through the index entries, based on provided size */ while (((uintptr_t) & (idxe->stream) + sizeof(ntfs_attr_fname)) < endaddr) { ntfs_attr_fname *fname = (ntfs_attr_fname *) & idxe->stream; if (tsk_verbose) tsk_fprintf(stderr, "ntfs_dent_idxentry: New IdxEnt: %" PRIu64 " $FILE_NAME Entry: %" PRIu64 " File Ref: %" PRIu64 " IdxEnt Len: %" PRIu16 " StrLen: %" PRIu16 "\n", (uint64_t) ((uintptr_t) idxe), (uint64_t) ((uintptr_t) fname), (uint64_t) tsk_getu48(fs->endian, idxe->file_ref), tsk_getu16(fs->endian, idxe->idxlen), tsk_getu16(fs->endian, idxe->strlen)); /* perform some sanity checks on index buffer head * and advance by 4-bytes if invalid */ if ((tsk_getu48(fs->endian, idxe->file_ref) > fs->last_inum) || (tsk_getu48(fs->endian, idxe->file_ref) < fs->first_inum) || (tsk_getu16(fs->endian, idxe->idxlen) <= tsk_getu16(fs->endian, idxe->strlen)) || (tsk_getu16(fs->endian, idxe->idxlen) % 4) || (tsk_getu16(fs->endian, idxe->idxlen) > idxe_len)) { idxe = (ntfs_idxentry *) ((uintptr_t) idxe + 4); continue; } /* do some sanity checks on the deleted entries */ if ((tsk_getu16(fs->endian, idxe->strlen) == 0) || (((uintptr_t) idxe + tsk_getu16(fs->endian, idxe->idxlen)) > endaddr_alloc)) { /* name space checks */ if ((fname->nspace != NTFS_FNAME_POSIX) && (fname->nspace != NTFS_FNAME_WIN32) && (fname->nspace != NTFS_FNAME_DOS) && (fname->nspace != NTFS_FNAME_WINDOS)) { idxe = (ntfs_idxentry *) ((uintptr_t) idxe + 4); if (tsk_verbose) tsk_fprintf(stderr, "ntfs_dent_idxentry: Skipping because of invalid name space\n"); continue; } if ((tsk_getu64(fs->endian, fname->alloc_fsize) < tsk_getu64(fs->endian, fname->real_fsize)) || (fname->nlen == 0) || (*(uint8_t *) & fname->name == 0)) { idxe = (ntfs_idxentry *) ((uintptr_t) idxe + 4); if (tsk_verbose) tsk_fprintf(stderr, "ntfs_dent_idxentry: Skipping because of reported file sizes, name length, or NULL name\n"); continue; } if ((is_time(tsk_getu64(fs->endian, fname->crtime)) == 0) || (is_time(tsk_getu64(fs->endian, fname->atime)) == 0) || (is_time(tsk_getu64(fs->endian, fname->mtime)) == 0)) { idxe = (ntfs_idxentry *) ((uintptr_t) idxe + 4); if (tsk_verbose) tsk_fprintf(stderr, "ntfs_dent_idxentry: Skipping because of invalid times\n"); continue; } } /* For all fname entries, there will exist a DOS style 8.3 * entry. We don't process those because we already processed * them before in their full version. If the type is * full POSIX or WIN32 that does not satisfy DOS, then a * type NTFS_FNAME_DOS will exist. If the name is WIN32, * but already satisfies DOS, then a type NTFS_FNAME_WINDOS * will exist * * Note that we could be missing some info from deleted files * if the windows version was deleted and the DOS wasn't... * * @@@ This should be added to the shrt_name entry of TSK_FS_DENT. The short * name entry typically comes after the long name */ if (fname->nspace == NTFS_FNAME_DOS) { if (tsk_verbose) tsk_fprintf(stderr, "ntfs_dent_idxentry: Skipping because of name space: %d\n", fname->nspace); goto incr_entry; } /* Copy it into the generic form */ if (ntfs_dent_copy(ntfs, dinfo, idxe, fs_dent)) { if (tsk_verbose) tsk_fprintf(stderr, "ntfs_dent_idxentry: Skipping because error copying dent_entry\n"); goto incr_entry; } /* * Check if this entry is deleted * * The final check is to see if the end of this entry is * within the space that the idxallocbuf claimed was valid */ if ((tsk_getu16(fs->endian, idxe->strlen) == 0) || (((uintptr_t) idxe + tsk_getu16(fs->endian, idxe->idxlen)) > endaddr_alloc)) { /* we know deleted entries with an inode of 0 are not legit because * that is the MFT value. Free it so it does not confuse * people with invalid data */ if ((fs_dent->inode == 0) && (fs_dent->fsi)) { tsk_fs_inode_free(fs_dent->fsi); fs_dent->fsi = NULL; } fs_dent->flags = TSK_FS_DENT_FLAG_UNALLOC; } else { fs_dent->flags = TSK_FS_DENT_FLAG_ALLOC; } if (tsk_verbose) tsk_fprintf(stderr, "ntfs_dent_idxentry: Entry Details of %s: Str Len: %" PRIu16 " Len to end after current: %" PRIu64 " flags: %x\n", fs_dent->name, tsk_getu16(fs->endian, idxe->strlen), (uint64_t) (endaddr_alloc - (uintptr_t) idxe - tsk_getu16(fs->endian, idxe->idxlen)), fs_dent->flags); if ((flags & fs_dent->flags) == fs_dent->flags) { int retval = action(fs, fs_dent, ptr); if (retval == TSK_WALK_STOP) { tsk_fs_dent_free(fs_dent); return 1; } else if (retval == TSK_WALK_ERROR) { tsk_fs_dent_free(fs_dent); return -1; } } /* Recurse if we need to */ if ((fs_dent->flags & TSK_FS_DENT_FLAG_ALLOC) && (flags & TSK_FS_DENT_FLAG_RECURSE) && (!TSK_FS_ISDOT(fs_dent->name)) && (fs_dent->fsi) && ((fs_dent->fsi->mode & TSK_FS_INODE_MODE_FMT) == TSK_FS_INODE_MODE_DIR) && (fs_dent->inode)) { int depth_added = 0; /* Make sure we do not get into an infinite loop */ if (0 == tsk_list_find(*list_seen, fs_dent->inode)) { if (tsk_list_add(list_seen, fs_dent->inode)) { tsk_fs_dent_free(fs_dent); return -1; } if ((dinfo->depth < MAX_DEPTH) && (DIR_STRSZ > strlen(dinfo->dirs) + strlen(fs_dent->name))) { dinfo->didx[dinfo->depth] = &dinfo->dirs[strlen(dinfo->dirs)]; strncpy(dinfo->didx[dinfo->depth], fs_dent->name, DIR_STRSZ - strlen(dinfo->dirs)); strncat(dinfo->dirs, "/", DIR_STRSZ); depth_added = 1; } dinfo->depth++; if (ntfs_dent_walk_lcl(&(ntfs->fs_info), dinfo, list_seen, fs_dent->inode, flags, action, ptr)) { if (tsk_verbose) tsk_fprintf(stderr, "Error recursing into directory\n"); tsk_error_reset(); } dinfo->depth--; if (depth_added) *dinfo->didx[dinfo->depth] = '\0'; } } /* end of recurse */ incr_entry: /* the theory here is that deleted entries have strlen == 0 and * have been found to have idxlen == 16 * * if the strlen is 0, then guess how much the indexlen was * before it was deleted */ /* 16: size of idxentry before stream * 66: size of fname before name * 2*nlen: size of name (in unicode) */ if (tsk_getu16(fs->endian, idxe->strlen) == 0) { idxe = (ntfs_idxentry *) ((((uintptr_t) idxe + 16 + 66 + 2 * fname->nlen + 3) / 4) * 4); } else { idxe = (ntfs_idxentry *) ((uintptr_t) idxe + tsk_getu16(fs->endian, idxe->idxlen)); } } /* end of loop of index entries */ tsk_fs_dent_free(fs_dent); return 0; }