# # $Date: 2006/05/11 18:09:24 $ # # Case Management methods, including the windows and functions to read the # config files # # Brian Carrier [carrier@sleuthkit.org] # Copyright (c) 2001-2005 by Brian Carrier. All rights reserved # # This file is part of the Autopsy Forensic Browser (Autopsy) # # Autopsy 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. # # Autopsy 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 Autopsy; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA # # THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR IMPLIED # WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF # MERCHANTABILITY AND FITNESS FOR ANY PARTICULAR PURPOSE. # IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, # INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES # (INCLUDING, BUT NOT LIMITED TO, LOSS OF USE, DATA, OR PROFITS OR # BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, # WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR # OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF # ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. package Caseman; # If the order of these views are changed, then the order of the main # function may have to be as well # Case Views $Caseman::CASE_NEW = 1; $Caseman::CASE_NEW_DOIT = 2; $Caseman::CASE_OPEN = 3; $Caseman::CASE_OPEN_LOG = 4; $Caseman::CASE_DETAILS = 5; # $Caseman::CASE_DEL = 6; my $CASE_MAX = 5; # Host Views $Caseman::HOST_ADD = 7; $Caseman::HOST_ADD_DOIT = 8; $Caseman::HOST_OPEN = 9; $Caseman::HOST_OPEN_LOG = 10; $Caseman::HOST_DETAILS = 11; # $Caseman::HOST_DEL = 12; my $HOST_MAX = 11; # Image Views $Caseman::IMG_ADD = 13; $Caseman::IMG_ADD_PREP = 14; $Caseman::IMG_ADD_DOIT = 15; $Caseman::VOL_OPEN = 16; $Caseman::VOL_OPEN_LOG = 17; $Caseman::VOL_DETAILS = 18; $Caseman::IMG_DEL = 19; $Caseman::VOL_MAKESTR = 20; $Caseman::VOL_MAKEDLS = 21; my $IMG_MAX = 21; # Module Variables # %vol2par - Volume to parent volume (vol to img, str to vol) # %vol2start - Starting sector of volume in image # %vol2end - ending sector of volume in image # %vol2cat - The big picture type of volume (part, disk, strings, dls) # %vol2ftype - The file system type (fat, dos, ntfs etc.) # %vol2itype - The image file type (could be for a parent image) # %vol2dtype- the disk type # %mod2vol; # Mapping for image, given the strings or dls # %vol2mnt; # Mapping for mount point, given the vol name # %vol2str; # Mapping for ASCII strings file, given the vol name # %vol2uni; # Mapping for Unicode strings file, given the vol name # %vol2dls; # Mapping for dls file, given the vol name # %vol2path - full file path of volume # %vol2sname - short name of volume sub main { # By default, show the case open window $Args::args{'view'} = $Args::enc_args{'view'} = $Caseman::CASE_OPEN unless (exists $Args::args{'view'}); Args::check_view(); my $view = Args::get_view(); # The only live function is for the open img if ($::LIVE == 1) { Args::check_inv(); if ($view == $Caseman::VOL_OPEN) { return vol_open(); } Args::check_vol('vol'); # Args::check_ftype(); # Args::check_mnt(); if ($view == $Caseman::VOL_OPEN_LOG) { return vol_open_log(); } else { Print::print_check_err( "Invalid Live Analysis Case Management View"); } return 0; } # Case functions if ($view <= $CASE_MAX) { if ($view == $Caseman::CASE_OPEN) { return case_open(); } elsif ($view == $Caseman::CASE_NEW) { return case_new(); } Args::check_case(); $::case_dir = "$::LOCKDIR/" . Args::get_case() . "/"; $::case_dir =~ s/\/\//\//g; if ($view == $Caseman::CASE_OPEN_LOG) { return case_open_log(); } elsif ($view == $Caseman::CASE_NEW_DOIT) { return case_new_doit(); } elsif ($view == $Caseman::CASE_DETAILS) { return case_details(); } } Args::check_case(); $::case_dir = "$::LOCKDIR/" . Args::get_case() . "/"; $::case_dir =~ s/\/\//\//g; # Host functions if ($view <= $HOST_MAX) { if ($view == $Caseman::HOST_OPEN) { return host_open(); } elsif ($view == $Caseman::HOST_ADD) { return host_add(); } Args::check_host(); $::host_dir = "$::case_dir" . Args::get_host() . "/"; $::host_dir =~ s/\/\//\//g; if ($view == $Caseman::HOST_ADD_DOIT) { return host_add_doit(); } Caseman::read_host_config(); if ($view == $Caseman::HOST_OPEN_LOG) { return host_open_log(); } elsif ($view == $Caseman::HOST_DETAILS) { return host_details(); } } Args::check_host(); $::host_dir = "$::case_dir" . Args::get_host() . "/"; $::host_dir =~ s/\/\//\//g; Caseman::read_host_config(); Args::check_inv(); if ($view <= $IMG_MAX) { if ($view == $Caseman::VOL_OPEN) { return vol_open(); } elsif ($view == $Caseman::IMG_ADD) { return img_add(); } elsif ($view == $Caseman::IMG_ADD_PREP) { return img_add_prep(); } elsif ($view == $Caseman::IMG_ADD_DOIT) { return img_add_doit(); } Args::check_vol('vol'); if ($view == $Caseman::VOL_OPEN_LOG) { return vol_open_log(); } elsif ($view == $Caseman::VOL_DETAILS) { return vol_details(); } # elsif ($view == $Caseman::IMG_DEL) { # return img_del(); # } elsif ($view == $Caseman::VOL_MAKESTR) { return vol_makestr(); } elsif ($view == $Caseman::VOL_MAKEDLS) { return vol_makedls(); } } Print::print_check_err("Invalid Case Management View"); } #################################################################### # General menu Functions sub print_menu_tabs { if ($::LIVE == 1) { print "

Live Analysis Mode

\n"; } print "\n\n" . "\n" . "\n" . "\n\n" . "
"; my $view = Args::get_view(); # Case Gallery Tab if ($view == $Caseman::CASE_OPEN) { print "\"Case\n"; } elsif ($::LIVE == 1) { print "\"Case\n"; } else { print "" . "\"Case\n"; } print ""; # Host Gallery Tab # Current if ($view == $Caseman::HOST_OPEN) { print "\"Host\n"; } # Link elsif (($view == $Caseman::VOL_OPEN) && ($::LIVE == 0)) { print "" . "\"Host\n"; } # Non-link else { print "\"Host\n"; } print ""; # Host Manager Tab # Current if ($view == $Caseman::VOL_OPEN) { print "\"Host\n"; } # non-link else { print "\"Host\n"; } print "
\n"; } #################################################################### # Case Functions # if no args are passed, return case config using args{'case'}, # else use the case value passed # # Case config: # In case directory with case_name.case sub case_config_fname { if (scalar(@_) == 1) { my $c = shift; return "$::LOCKDIR/" . "$c/case.aut"; } else { return "$::LOCKDIR/" . "$Args::args{'case'}/case.aut"; } } # Read case config and save it to $Caseman::cvals sub read_case_config { return if ($::LIVE == 1); my $case; if (scalar(@_) == 1) { $case = shift; } else { $case = Args::get_case(); } my $fname = case_config_fname($case); %Caseman::cvals = (); open CONFIG, "<$fname" or die "Can't open case config file ($fname)"; while () { next if ((/^\#/) || (/^\s+$/)); s/^\s+//; s/\s+$//; $Caseman::cvals{$1} = $2 if (/^(\S+)\s+(.*)$/); } close(CONFIG); $Caseman::cvals{'desc'} = "None Provided" unless (exists $Caseman::cvals{'desc'}); $Caseman::cvals{'created'} = "unknown" unless (exists $Caseman::cvals{'created'}); } sub case_new { Print::print_html_header("Create A New Case"); print <
\"New


1. Case Name: The name of this investigation. It can contain only letters, numbers, and symbols.
  
 
2. Description: An optional, one line description of this case.
  
 
3. Investigator Names: The optional names (with no spaces) of the investigators for this case.
  a. b.
  c. d.
  e. f.
  g. h.
  i. j.


\"Help\"
EOF Print::print_html_footer(); return; } # Create the directory and case configuration file # Gets the input from CASE_NEW sub case_new_doit { Print::print_html_header("Creating Case: $Args::args{'case'}"); my $case = $Args::args{'case'}; print "

Creating Case: $case

\n"; # Make the directory if (-d "$::case_dir") { Print::print_err("Error: $::case_dir already exists
" . "Please remove the directory and its contents and try again" . "

" . "\"Ok\"\n"); } unless (mkdir "$::case_dir", $::MKDIR_MASK) { Print::print_err("Error making directory $::case_dir: $!"); } print "Case directory ($::case_dir) created
\n"; Print::log_case_info("Case $case created"); my $fname = Caseman::case_config_fname(); open CASE_CONFIG, ">$fname" or die "Can't open case config: $fname"; print CASE_CONFIG "# Autopsy case config file\n" . "# Case: $case\n\n" . "created " . localtime() . "\n"; if ((exists $Args::args{'desc'}) && ($Args::args{'desc'} ne "")) { Print::print_err("Invalid Description
\n" . "Use the browser's back button to fix") if ($Args::args{'desc'} =~ /\n/); print CASE_CONFIG "desc $Args::args{'desc'}\n"; } print CASE_CONFIG "images $::IMGDIR\n"; print CASE_CONFIG "data $::DATADIR\n"; print CASE_CONFIG "log $::LOGDIR\n"; print CASE_CONFIG "reports $::REPDIR\n"; close CASE_CONFIG; print "Configuration file ($fname) created
\n"; my $iname = investig_fname(); open INVES, ">>$iname" or die "Can't open investigators file: $iname"; my @invs; if ( (exists $Args::args{'inv1'}) && ($Args::args{'inv1'} ne "") && ($Args::args{'inv1'} =~ /^\s*($::REG_INVESTIG)\s*$/o)) { print INVES "$1\n"; push @invs, $1; } if ( (exists $Args::args{'inv2'}) && ($Args::args{'inv2'} ne "") && ($Args::args{'inv2'} =~ /^\s*($::REG_INVESTIG)\s*$/o)) { print INVES "$1\n"; push @invs, $1; } if ( (exists $Args::args{'inv3'}) && ($Args::args{'inv3'} ne "") && ($Args::args{'inv3'} =~ /^\s*($::REG_INVESTIG)\s*$/o)) { print INVES "$1\n"; push @invs, $1; } if ( (exists $Args::args{'inv4'}) && ($Args::args{'inv4'} ne "") && ($Args::args{'inv4'} =~ /^\s*($::REG_INVESTIG)\s*$/o)) { print INVES "$1\n"; push @invs, $1; } if ( (exists $Args::args{'inv5'}) && ($Args::args{'inv5'} ne "") && ($Args::args{'inv5'} =~ /^\s*($::REG_INVESTIG)\s*$/o)) { print INVES "$1\n"; push @invs, $1; } if ( (exists $Args::args{'inv6'}) && ($Args::args{'inv6'} ne "") && ($Args::args{'inv6'} =~ /^\s*($::REG_INVESTIG)\s*$/o)) { print INVES "$1\n"; push @invs, $1; } if ( (exists $Args::args{'inv7'}) && ($Args::args{'inv7'} ne "") && ($Args::args{'inv7'} =~ /^\s*($::REG_INVESTIG)\s*$/o)) { print INVES "$1\n"; push @invs, $1; } if ( (exists $Args::args{'inv8'}) && ($Args::args{'inv8'} ne "") && ($Args::args{'inv8'} =~ /^\s*($::REG_INVESTIG)\s*$/o)) { print INVES "$1\n"; push @invs, $1; } if ( (exists $Args::args{'inv9'}) && ($Args::args{'inv9'} ne "") && ($Args::args{'inv9'} =~ /^\s*($::REG_INVESTIG)\s*$/o)) { print INVES "$1\n"; push @invs, $1; } if ( (exists $Args::args{'inv10'}) && ($Args::args{'inv10'} ne "") && ($Args::args{'inv10'} =~ /^\s*($::REG_INVESTIG)\s*$/o)) { print INVES "$1\n"; push @invs, $1; } close(INVES); Print::log_session_info("Case $case created"); print "

We must now create a host for this case.\n"; print "

\n" . "\n" . "\n" . "\n"; if (scalar @invs == 0) { print "
\n"; Print::print_html_footer(); return; } # Open a Case # This provides a form with a list of options sub case_open { Print::print_html_header("Open A Case"); # Read the directories of the Evidence Locker into an array # Verify that there is a config file in the directory my @cases; opendir CASES, $::LOCKDIR or die "Can't open $::LOCKDIR directory: $!"; foreach my $c (readdir CASES) { next if (($c eq '.') || ($c eq '..')); my $cfile = Caseman::case_config_fname($c); push @cases, $c if ((-d "$::LOCKDIR/$c") && (-e "$cfile")); } closedir CASES; print "

"; # Were there any cases? if (scalar @cases == 0) { print "No cases exist in $::LOCKDIR

\n" . "Select the New Case button below to make one
\n"; } else { print "Select the case to open or create a new one
\n"; print_menu_tabs(); print "\n"; print "\n" . "\n" . "\n" . Args::make_hidden() . "" . "" . "\n"; my $first = 0; foreach my $c (@cases) { print ""; Caseman::read_case_config($c); print "" . "" . "\n"; } print "
NameDescription 
" . "$c$Caseman::cvals{'desc'}" . "" . "details
\n"; } print "

" . "\n" . "\n"; # Print the OK button if there were cases if (scalar @cases != 0) { print "\n\n"; } # Print a 'New Case' Button print "" . # Print a Menu Button "
" . "" . "" . "
\n" . "\n" . "\n" . "\n" . "
" . "
\n" . "\n" . "
\n"; print "\n" . "\n" . "" . "\n" . "\n" . "
 " . "\"Help\"" . " 
"; Print::print_html_footer(); return; } # Log that a given case was opened and then proceed to open a host sub case_open_log { Print::log_session_info("Case $Args::args{'case'} opened"); Print::log_case_info("Case $Args::args{'case'} opened"); $Args::args{'view'} = $Args::enc_args{'view'} = $Caseman::HOST_OPEN; host_open(); } # Display Case Details sub case_details { Print::print_html_header("Details of $Args::args{'case'}"); read_case_config(); $Caseman::cvals{'desc'} = " " unless (exists $Caseman::cvals{'desc'}); $Caseman::cvals{'created'} = " " unless (exists $Caseman::cvals{'created'}); print "

" . "
" . "\"Case" . "


\n" . "\n" . " " . "\n" . # Description " " . "\n" . " " . "\n"; # Display the valid investigators my @invs = read_invest(); my $cnt = 0; print " \n" if (scalar @invs > 0); foreach my $i (@invs) { if ($cnt == 0) { print " "; $cnt++; } else { print " "; } print "\n"; } print "
Name:$Args::args{'case'}
Description:$Caseman::cvals{'desc'}
Created:$Caseman::cvals{'created'}
 
Investigators:
 $i
\n" . "

" . "\"Ok\""; Print::print_html_footer(); return 0; } #################################################################### # Host Functions # if no args are passed, return host config using args{'host'}, # else use the host value passed sub host_config_fname { if (scalar(@_) == 1) { my $h = shift; return "$::case_dir" . "$h/host.aut"; } else { return "$::host_dir" . "host.aut"; } } # Converts the original single image host config to the volume-based config sub convert_host_config { return if ($::LIVE == 1); my $host = Args::get_host(); Print::log_host_info("Converting host config files"); print STDERR "Converting host config file to new format\n"; # The file to convert my $cfile = host_config_fname(); unless (open(FILE, $cfile)) { Print::print_check_err("Error opening $cfile"); } my $tmpcfile = "$::host_dir" . "host-convert.aut"; unless (open(FILE_TMP, ">>$tmpcfile")) { Print::print_check_err("Error opening $tmpcfile"); } my $img_cnt = 0; my $vol_cnt = 0; $img2vol{'qazwsxedc'} = ""; # stores the image path to partition / file name $img2vol2{'qazwsxedc'} = ""; # stores the image path to file name (no partitions) while () { if ((/^\#/) || (/^\s+$/)) { print FILE_TMP $_; next; } # remove whitespace s/^\s+//; s/\s+$//; # normal file system image entry # # 'image images/hda1.dd openbsd /usr if (/^image\s+($::REG_IMG)\s+([\w\-]+)\s+([\w\-\_\.\/:\\]+)$/o) { my $i = $1; my $t = $2; my $r = $3; # Add trailing / to original mount point if needed if (($r !~ /.*?\/$/) && ($r !~ /.*?\\$/)) { $r .= '/'; } my $vnum = "vol" . $vol_cnt; my $inum = "img" . $img_cnt; $img2vol{$i} = $vnum; $img2vol2{$i} = $inum; print FILE_TMP "image $inum raw $i\n"; print FILE_TMP "part $vnum $inum 0 0 $t $r\n"; $img_cnt++; $vol_cnt++; } # swap # swap images/hda3.dd elsif (/^swap\s+($::REG_IMG)\s*$/o) { my $i = $1; my $vnum = "vol" . $vol_cnt; my $inum = "img" . $img_cnt; $img2vol{$i} = $vnum; $img2vol2{$i} = $inum; print FILE_TMP "image $inum raw $i\n"; print FILE_TMP "part $vnum $inum 0 0 swap\n"; $img_cnt++; $vol_cnt++; } # raw # raw images/hda3.dd elsif (/^raw\s+($::REG_IMG)\s*$/o) { my $i = $1; $img2vol{$i} = "vol" . $vol_cnt; $img2vol2{$i} = "img" . $img_cnt; print FILE_TMP "image img" . $img_cnt . " raw $i\n"; print FILE_TMP "part vol" . $vol_cnt . " img" . $img_cnt . " 0 0 raw\n"; $img_cnt++; $vol_cnt++; } # entry for a strings or dls file # # strings data/hda1.str images/hda1.dd elsif (/^strings\s+($::REG_IMG)\s+($::REG_IMG)$/o) { my $i = $1; my $o = $2; if (exists $img2vol{$o}) { my $vname = $img2vol{$o}; print FILE_TMP "strings vol" . $vol_cnt . " $vname $i\n"; $img2vol{$i} = "vol" . $vol_cnt; $img2vol2{$i} = "vol" . $vol_cnt; } else { print STDERR "Error: Volume for strings $o not found
\n"; } $vol_cnt++; } # entry for a strings or dls file # # unistrings data/hda1.str images/hda1.dd elsif (/^unistrings\s+($::REG_IMG)\s+($::REG_IMG)$/o) { my $i = $1; my $o = $2; if (exists $img2vol{$o}) { my $vname = $img2vol{$o}; print FILE_TMP "unistrings vol" . $vol_cnt . " $vname $i\n"; $img2vol{$i} = "vol" . $vol_cnt; $img2vol2{$i} = "vol" . $vol_cnt; } else { print STDERR "Error: Volume for unicode strings $o not found
\n"; } $vol_cnt++; } # dls entry # dls data/image.dls [images/image.dd] elsif (/^dls\s+($::REG_IMG)\s*($::REG_IMG)$/o) { my $i = $1; my $o = $2; $img2vol{$i} = "vol" . $vol_cnt; if (exists $img2vol{$o}) { my $vname = $img2vol{$o}; print FILE_TMP "dls vol" . $vol_cnt . " $vname $i\n"; $img2vol{$i} = "vol" . $vol_cnt; $img2vol2{$i} = "vol" . $vol_cnt; } else { print STDERR "Error: Volume for dls $o not found
\n"; } $vol_cnt++; } # body data/body.txt elsif (/^body\s+($::REG_IMG)$/o) { my $i = $1; print FILE_TMP "body vol" . $vol_cnt . " $i\n"; $img2vol{$i} = "vol" . $vol_cnt; $img2vol2{$i} = "vol" . $vol_cnt; $vol_cnt++; } # timeline data/timeline.txt elsif (/^timeline\s+($::REG_IMG)$/o) { my $i = $1; print FILE_TMP "timeline vol" . $vol_cnt . " $i\n"; $img2vol{$i} = "vol" . $vol_cnt; $img2vol2{$i} = "vol" . $vol_cnt; $vol_cnt++; } # timezone XYZ elsif (/^timezone\s+($::REG_ZONE_ARGS)$/o) { print FILE_TMP "$_\n"; } # timeskew XYZ elsif (/^timeskew\s+($::REG_SKEW)$/o) { print FILE_TMP "$_\n"; } # desc XYZ elsif (/^desc\s+(.*)$/) { print FILE_TMP "$_\n"; } # hash databases elsif (/^alert_db\s+'(.*)'$/) { print FILE_TMP "$_\n"; } elsif (/^exclude_db\s+'(.*)'$/) { print FILE_TMP "$_\n"; } else { my $msg = "Error: invalid entry in $cfile:$." . "
\n" . "image path fs_type mnt_point
\n" . "strings path orig_img
\n" . "dls path [orig_img]
\n" . "body path
\n" . "timeline path
\n" . "timezone TZ
\n" . "desc DESCRIPTION
\n"; Print::print_check_err($msg); } } close(FILE); close(FILE_TMP); unless (rename $cfile, $cfile . ".bak") { print STDERR "Error backing up original host config file\n"; } unless (rename $tmpcfile, $cfile) { print STDERR "Error renaming new host config file\n"; } Notes::convert(\%img2vol); Hash::convert(\%img2vol2); } # reads host config file and sets global hash values for images and other sub read_host_config { if ($::LIVE == 1) { %Caseman::mod2vol = (); %Caseman::vol2str = (); %Caseman::vol2uni = (); %Caseman::vol2dls = (); $Caseman::tz = ""; $Caseman::ts = 0; $Caseman::host_desc = ""; $Caseman::exclude_db = ""; $Caseman::alert_db = ""; return; } my $host = Args::get_host(); my $cfile = host_config_fname(); restart: unless (open(FILE, $cfile)) { Print::print_check_err("Error opening $cfile"); } %Caseman::vol2mnt = (); %Caseman::vol2ftype = (); %Caseman::vol2dtype = (); %Caseman::vol2cat = (); %Caseman::mod2vol = (); %Caseman::vol2str = (); %Caseman::vol2uni = (); %Caseman::vol2dls = (); %Caseman::vol2par = (); %Caseman::vol2start = (); %Caseman::vol2end = (); $Caseman::vol2path = (); $Caseman::vol2sname = (); $Caseman::tz = ""; $Caseman::ts = 0; $Caseman::host_desc = ""; $Caseman::exclude_db = ""; $Caseman::alert_db = ""; while () { next if ((/^\#/) || (/^\s+$/)); # remove whitespace s/^\s+//; s/\s+$//; # old file system image entry # # 'image images/hda1.dd openbsd /usr if (/^image\s+($::REG_IMG)\s+([\w\-]+)\s+([\w\-\_\.\/:\\]+)$/o) { close(FILE); convert_host_config(); goto restart; } elsif ( /^image\s+($::REG_INAME)\s+($::REG_IMGTYPE)\s+($::REG_IMG_CONFIG)$/o ) { my $me = $1; my $t = $2; my $i = $3; unless ((-e "$::host_dir$i") || ((-l "$::host_dir$i") && (-e readlink "$::host_dir$i"))) { Print::print_check_err( "Error: image $i in ${host}.host:$. not found: " . "$::host_dir" . "$i
Edit the config file and refresh your browser" . "
(Or your version of Perl does not support large files)" ); } if (exists $Caseman::vol2path{$me}) { $Caseman::vol2path{$me} .= " \'$::host_dir" . "$i\'"; } else { $Caseman::vol2path{$me} = "\'$::host_dir" . "$i\'"; $Caseman::vol2sname{$me} = $i; $Caseman::vol2sname{$me} = $1 if ($i =~ /\/($::REG_FILE)$/); $Caseman::vol2par{$me} = ""; $Caseman::vol2cat{$me} = "image"; $Caseman::vol2itype{$me} = $t; $Caseman::vol2ftype{$me} = ""; $Caseman::vol2start{$me} = 0; $Caseman::vol2end{$me} = 0; } } elsif ( /^part\s+($::REG_VNAME)\s+($::REG_INAME)\s+(\d+)\s+(\d+)\s+($::REG_FTYPE)\s*([\w\-\_\.\/:\\]+)?$/o ) { my $par = $2; my $me = $1; my $s = $3; my $e = $4; my $t = $5; my $r = $6; # Not defined for swap and raw unless (exists $Fs::addr_unit{$t}) { Print::print_check_err( "Error: unknown type: $t in host config: $." . "
Edit the file and refresh your browser"); } if (exists $Caseman::vol2itype{$par}) { $Caseman::vol2itype{$me} = $Caseman::vol2itype{$par}; } else { Print::print_check_err( "Error: Image $par for partition $me was not found in config: $." . "
Edit the file and refresh your browser"); } $Caseman::vol2ftype{$me} = $t; $Caseman::vol2cat{$me} = "part"; $Caseman::vol2start{$me} = $s; $Caseman::vol2end{$me} = $e; # Add trailing / to original mount point if needed if ((defined $r) && ($r !~ /.*?\/$/) && ($r !~ /.*?\\$/)) { $r .= '/'; } $Caseman::vol2mnt{$me} = $r; $Caseman::vol2par{$me} = $par; $Caseman::vol2path{$me} = $Caseman::vol2path{$par}; $Caseman::vol2sname{$me} = $Caseman::vol2sname{$par} . "-" . $s . "-" . $e; } elsif (/^disk\s+($::REG_VNAME)\s+($::REG_INAME)\s+($::REG_FTYPE)?$/o) { my $par = $2; my $me = $1; my $t = $3; unless (exists $Vs::type{$t}) { Print::print_check_err( "Error: unknown volume system type: $t in host config: $." . "
Edit the file and refresh your browser"); } if (exists $Caseman::vol2itype{$par}) { $Caseman::vol2itype{$me} = $Caseman::vol2itype{$par}; } else { Print::print_check_err( "Error: Image $par for disk $me was not found in config: $." . "
Edit the file and refresh your browser"); } $Caseman::vol2ftype{$me} = "raw"; $Caseman::vol2dtype{$me} = $t; $Caseman::vol2cat{$me} = "disk"; $Caseman::vol2start{$me} = 0; $Caseman::vol2end{$me} = 0; $Caseman::vol2mnt{$me} = ""; $Caseman::vol2par{$me} = $par; $Caseman::vol2path{$me} = $Caseman::vol2path{$par}; $Caseman::vol2sname{$me} = $Caseman::vol2sname{$par} . "-disk"; } # entry for a strings or dls file # # strings data/hda1.str volX elsif (/^strings\s+($::REG_VNAME)\s+($::REG_VNAME)\s+($::REG_IMG)$/o) { my $i = $3; my $par = $2; my $me = $1; unless ((-e "$::host_dir$i") || ((-l "$::host_dir$i") && (-e readlink "$::host_dir$i"))) { Print::print_check_err("Error: strings file not found: " . "$::host_dir$i
Edit host config in $::host_dir and refresh your browser" ); } unless (exists $Caseman::vol2cat{$par}) { Print::print_check_err( "Error: Volume $par for strings $me was not found in config: $." . "
Edit the file and refresh your browser"); } $Caseman::vol2ftype{$me} = "strings"; $Caseman::vol2cat{$me} = "mod"; $Caseman::vol2itype{$me} = "raw"; $Caseman::mod2vol{$me} = $par; $Caseman::vol2str{$par} = $me; $Caseman::vol2par{$me} = $par; $Caseman::vol2path{$me} = "\'$::host_dir" . "$i\'"; $Caseman::vol2start{$me} = 0; $Caseman::vol2end{$me} = 0; $Caseman::vol2sname{$me} = $i; $Caseman::vol2sname{$me} = $1 if ($i =~ /\/($::REG_FILE)$/); } # entry for a strings or dls file # # unistrings data/hda1.str volX elsif (/^unistrings\s+($::REG_VNAME)\s+($::REG_VNAME)\s+($::REG_IMG)$/o) { my $i = $3; my $par = $2; my $me = $1; unless ((-e "$::host_dir$i") || ((-l "$::host_dir$i") && (-e readlink "$::host_dir$i"))) { Print::print_check_err("Error: Unicode strings file not found: " . "$::host_dir$i
Edit host config in $::host_dir and refresh your browser" ); } unless (exists $Caseman::vol2cat{$par}) { Print::print_check_err( "Error: Volume $par for unistrings $me was not found in config: $." . "
Edit the file and refresh your browser"); } $Caseman::vol2ftype{$me} = "strings"; $Caseman::vol2cat{$me} = "mod"; $Caseman::vol2itype{$me} = "raw"; $Caseman::mod2vol{$me} = $par; $Caseman::vol2uni{$par} = $me; $Caseman::vol2par{$me} = $par; $Caseman::vol2path{$me} = "\'$::host_dir" . "$i\'"; $Caseman::vol2start{$me} = 0; $Caseman::vol2end{$me} = 0; $Caseman::vol2sname{$me} = $i; $Caseman::vol2sname{$me} = $1 if ($i =~ /\/($::REG_FILE)$/); } # dls entry # dls themname myname elsif (/^dls\s+($::REG_VNAME)\s+($::REG_VNAME)\s+($::REG_IMG)$/o) { my $i = $3; my $par = $2; my $me = $1; unless ( (-e "$::host_dir$i") || ( (-l "$::host_dir$i") && (-e readlink "$::host_dir$i")) ) { Print::print_check_err("Error: dls file not found: " . "$::host_dir$i
Edit host config in $::host_dir and refresh your browser" ); } unless (exists $Caseman::vol2cat{$par}) { Print::print_check_err( "Error: Volume $par for dls $me was not found in config: $." . "
Edit the file and refresh your browser"); } $Caseman::vol2ftype{$me} = "dls"; $Caseman::vol2cat{$me} = "mod"; $Caseman::vol2itype{$me} = "raw"; $Caseman::vol2mnt{$me} = ""; $Caseman::vol2par{$me} = $par; $Caseman::mod2vol{$me} = $par; $Caseman::vol2dls{$par} = $me; $Caseman::vol2path{$me} = "\'$::host_dir" . "$i\'"; $Caseman::vol2start{$me} = 0; $Caseman::vol2end{$me} = 0; $Caseman::vol2sname{$me} = $i; $Caseman::vol2sname{$me} = $1 if ($i =~ /\/($::REG_FILE)$/); } # body data/body.txt elsif (/^body\s+($::REG_VNAME)\s+($::REG_IMG)$/o) { my $me = $1; my $i = $2; unless ((-e "$::host_dir$i") || ((-l "$::host_dir$i") && (-e readlink "$::host_dir$i"))) { Print::print_check_err("Error: body file not found: " . "$::host_dir$i
Edit host config in $::host_dir and refresh your browser" ); } $Caseman::vol2cat{$me} = "timeline"; $Caseman::vol2ftype{$me} = "body"; $Caseman::vol2itype{$me} = "raw"; $Caseman::vol2path{$me} = "\'$::host_dir" . "$i\'"; $Caseman::vol2start{$me} = 0; $Caseman::vol2end{$me} = 0; $Caseman::vol2sname{$me} = $i; $Caseman::vol2sname{$me} = $1 if ($i =~ /\/($::REG_FILE)$/); } # timeline data/timeline.txt elsif (/^timeline\s+($::REG_VNAME)\s+($::REG_IMG)$/o) { my $me = $1; my $i = $2; unless ((-e "$::host_dir$i") || ((-l "$::host_dir$i") && (-e readlink "$::host_dir$i"))) { Print::print_check_err("Error: timeline file not found: " . "$::host_dir$i
Edit host config in $::host_dir and refresh your browser" ); } $Caseman::vol2cat{$me} = "timeline"; $Caseman::vol2ftype{$me} = "timeline"; $Caseman::vol2itype{$me} = "raw"; # We do not add the quotes to the path for timeline because it is opened only by the Perl code and it doesn't like the quotes $Caseman::vol2path{$me} = "$::host_dir" . "$i"; $Caseman::vol2start{$me} = 0; $Caseman::vol2end{$me} = 0; $Caseman::vol2sname{$me} = $i; $Caseman::vol2sname{$me} = $1 if ($i =~ /\/($::REG_FILE)$/); } # timezone XYZ elsif (/^timezone\s+($::REG_ZONE_ARGS)$/o) { $Caseman::tz = "\'$1\'"; } # timeskew XYZ elsif (/^timeskew\s+($::REG_SKEW)$/o) { $Caseman::ts = "\'$1\'"; } # desc XYZ elsif (/^desc\s+(.*)$/) { $Caseman::host_desc = "$1"; } # hash databases elsif (/^alert_db\s+'(.*)'$/) { $Caseman::alert_db = "$1"; } elsif (/^exclude_db\s+'(.*)'$/) { $Caseman::exclude_db = "$1"; } else { my $msg = "Error: invalid entry in $cfile:$." . "
\n" . "$_
\n"; Print::print_check_err($msg); } } close(FILE); } # Add a new image entry to the host config and return its id sub add_img_host_config { my $type = shift; my $other = shift; my $id = shift; my $read = host_config_fname(); my $write = $read . "-" . rand(); unless (open(READ, "<$read")) { Print::print_check_err("Error opening $read"); } unless (open(WRITE, ">$write")) { Print::print_check_err("Error opening $write"); } my $maxcnt = 0; while () { s/^\s+//; s/\s+$//; if (/^\w+\s+img(\d+)\s+.*$/) { $maxcnt = $1 if ($1 > $maxcnt); } print WRITE "$_\n"; } $maxcnt++; if ($id eq "") { $id = "img" . $maxcnt; } print WRITE "$type $id $other\n"; Print::log_host_info("Image added: $type $id $other"); close(READ); close(WRITE); unless (rename $write, $read) { print STDERR "Error renaming temp host config file\n"; } return $id; } # Add a new volume entry to the host config and return its id sub add_vol_host_config { my $type = shift; my $other = shift; my $read = host_config_fname(); my $write = $read . "-" . rand(); unless (open(READ, "<$read")) { Print::print_check_err("Error opening $read"); } unless (open(WRITE, ">$write")) { Print::print_check_err("Error opening $write"); } # We want to find the max count ... not just the unused # ones because we could end up reusing one and that could # mess up the notes my $maxcnt = 0; while () { s/^\s+//; s/\s+$//; if (/^\w+\s+vol(\d+)\s+.*$/) { $maxcnt = $1 if ($1 > $maxcnt); } print WRITE "$_\n"; } $maxcnt++; print WRITE "$type vol" . $maxcnt . " $other\n"; Print::log_host_info("Volume added: $type vol" . $maxcnt . " $other"); close(READ); close(WRITE); unless (rename $write, $read) { print STDERR "Error renaming temp host config file\n"; } return "vol" . $maxcnt; } # Delete anentry from the host config # DOES NOT WORK RIGHT NOW sub del_host_config_BLAH { return; my $type = shift; my $other = shift; my $read = host_config_fname(); my $write = $read . "-" . rand(); unless (open(READ, "<$read")) { Print::print_check_err("Error opening $read"); } unless (open(WRITE, ">$write")) { Print::print_check_err("Error opening $write"); } while () { s/^\s+//; s/\s+$//; print WRITE "$_\n" unless ((/^(\w+)\s+($::REG_IMG)/o) && ($2 eq $img) && ($1 ne 'desc') && ($1 ne 'timezone')); } if ($type ne "") { if (defined $ref) { print WRITE "$type $img $ref\n"; } else { print WRITE "$type $img\n"; } } close(READ); close(WRITE); unless (rename $write, $read) { print STDERR "Error renaming temp host config file\n"; } return; } # Given the image and md5, it is saved to the MD5.txt file and any other # references to the image are removed # # if $md5 is "", then nothing is written sub update_md5 { my $vol = shift; my $md5 = shift; $md5 =~ tr/[a-f]/[A-F]/; my $read = "$::host_dir/md5.txt"; my $write = $read . "-" . rand(); unless (open(WRITE, ">$write")) { Print::print_check_err("Error opening $write"); } if (-e "$read") { unless (open(READ, "<$read")) { Print::print_check_err("Error opening $read"); } while () { s/^\s+//; s/\s+$//; print WRITE "$_\n" unless ((/^$::REG_MD5\s+($::REG_VNAME)/o) && ($1 eq $vol)); } close(READ); } print WRITE "$md5 $vol\n" if ($md5 ne ""); close(WRITE); unless (rename $write, $read) { print STDERR "Error renaming temp MD5 hash file\n"; } return; } sub host_add { Print::print_html_header("Add A New Host To $Args::args{'case'}"); print "Case: $Args::args{'case'}

\n"; print "

" . "\"Add" . "


\n"; print "\n" . "\n" . "\n" . "\n" . Args::make_hidden() . "" . # Host name "" . "" . "\n" . # Description "" . "" . "\n" . # Empty line "" . # Timezone "" . "" . "\n" . # Timeskew "" . "" . "\n" . # Spacer "" . # Alert "" . "" . "\n" . # Ignore "" . "" . "\n" . # Spacer "" . "
 
1. Host Name: The name of the computer being investigated. It can contain only letters, numbers, and symbols.
   
2. Description: An optional one-line description or note about this computer.
   " . "
 
3. Time zone: An optional timezone value (i.e. EST5EDT). If not given, it defaults to the local setting. A list of time zones can be found in the help files.
   " . "
4. Timeskew Adjustment: An optional value to describe how many seconds this computer's clock was out of sync. For example, if the computer was 10 seconds fast, then enter -10 to compensate.
   " . "" . "
 
5. Path of Alert Hash Database: An optional hash database of known bad files.
   " . "" . "
6. Path of Ignore Hash Database: An optional hash database of known good files.
   " . "" . "
 
\n"; if (exists $Args::args{'inv'}) { print "\n"; } # Ok Button print "

\n" . "\n" . # Cancel Button "\n" . # Help Button "\n" . "
" . "\n" . "" . "
\n" . "\n" . "\n" . "\n" . "\n" . "
" . "\"Help\"" . "
"; Print::print_html_footer(); return 0; } # Make the directories and config files for a host sub host_add_doit { Args::check_tz() if ((exists $Args::args{'tz'}) && ($Args::args{'tz'} ne "")); Args::check_ts(); Print::print_html_header( "Adding Host $Args::args{'host'} to $Args::args{'case'}"); print "

Adding host: $Args::args{'host'} to " . "case $Args::args{'case'}

\n"; # Do some sanity checks before we start making the directories and such if ( (exists $Args::args{'alert_db'}) && ($Args::args{'alert_db'} ne "")) { unless ($Args::args{'alert_db'} =~ /^$::REG_HASHDB$/o) { print "Invalid Alert Database path\n" . "

" . "\"Ok\"\n"; return 1; } unless (-e "$Args::args{'alert_db'}") { print "Alert Database Not Found: $Args::args{'alert_db'}" . "

" . "\"Ok\"\n"; return 1; } } if ( (exists $Args::args{'exclude_db'}) && ($Args::args{'exclude_db'} ne "")) { unless ($Args::args{'exclude_db'} =~ /^$::REG_HASHDB$/o) { print "Invalid Exclude Database path\n" . "

" . "\"Ok\"\n"; return 1; } unless (-e "$Args::args{'exclude_db'}") { print "Exclude Database Not Found: $Args::args{'exclude_db'}" . "

" . "\"Ok\"\n"; return 1; } } # Make the directory if (-d "$::host_dir") { Print::print_err("Error: $::host_dir already exists
" . "Please remove the directory and its contents and try again" . "

" . "\"Ok\"\n"); } unless (mkdir "$::host_dir", $::MKDIR_MASK) { Print::print_err("Error making directory $::host_dir: $!"); } print "Host Directory ($::host_dir) created

\n"; Print::log_case_info("Host $Args::args{'host'} added to case"); # Images directory unless (mkdir "$::host_dir" . "$::IMGDIR", $::MKDIR_MASK) { rmdir "$::host_dir"; Print::print_err("Error making $::host_dir" . "$::IMGDIR"); } # Output Directory unless (mkdir "$::host_dir" . "$::DATADIR", $::MKDIR_MASK) { rmdir "$::host_dir" . "$::IMGDIR"; rmdir "$::host_dir"; Print::print_err("Error making $::host_dir" . "$::DATADIR"); } # Log Directory unless (mkdir "$::host_dir" . "$::LOGDIR", $::MKDIR_MASK) { rmdir "$::host_dir" . "$::DATADIR"; rmdir "$::host_dir" . "$::IMGDIR"; rmdir "$::host_dir"; Print::print_err("Error making $::host_dir" . "$::LOGDIR"); } # Reports directory unless (mkdir "$::host_dir" . "$::REPDIR", $::MKDIR_MASK) { rmdir "$::host_dir" . "$::LOGDIR"; rmdir "$::host_dir" . "$::DATADIR"; rmdir "$::host_dir" . "$::IMGDIR"; rmdir "$::host_dir"; Print::print_err("Error making $::host_dir" . "$::REPDIR"); } # Make a directory for mounting the image in loopback unless (mkdir "$::host_dir" . "mnt", $::MKDIR_MASK) { rmdir "$::host_dir" . "$::REPDIR"; rmdir "$::host_dir" . "$::LOGDIR"; rmdir "$::host_dir" . "$::DATADIR"; rmdir "$::host_dir" . "$::IMGDIR"; rmdir "$::host_dir"; Print::print_err("Error making $::host_dir" . "mnt"); } Print::log_host_info( "Host $Args::args{'host'} added to case $Args::args{'case'}"); # Create config file my $fname = Caseman::host_config_fname(); open HOST_CONFIG, ">$fname" or die "Can't open host config: $fname"; print HOST_CONFIG "# Autopsy host config file\n" . "# Case: $Args::args{'case'} Host: $Args::args{'host'}\n" . "# Created: " . localtime() . "\n\n"; if ((exists $Args::args{'desc'}) && ($Args::args{'desc'} ne "")) { Print::print_err("Invalid Description
\n" . "Use the browser's back button to fix") if ($Args::args{'desc'} =~ /\n/); print HOST_CONFIG "desc $Args::args{'desc'}\n"; } print HOST_CONFIG "timezone " . Args::get_tz() . "\n" if ((exists $Args::args{'tz'}) && ($Args::args{'tz'} ne "")); print HOST_CONFIG "timeskew " . Args::get_ts() . "\n"; if ( (exists $Args::args{'alert_db'}) && ($Args::args{'alert_db'} ne "")) { # Index it if it is not unless (-e "$Args::args{'alert_db'}-md5.idx") { print "Alert Database has not been indexed - it will be as an md5sum file
\n"; print "


\n"; Hash::index_md5sum($Args::args{'alert_db'}); print "
\n"; } # only print it if it was successful print HOST_CONFIG "alert_db \'$Args::args{'alert_db'}\'\n" if (-e "$Args::args{'alert_db'}-md5.idx"); } if ( (exists $Args::args{'exclude_db'}) && ($Args::args{'exclude_db'} ne "")) { # Index it if it is not unless (-e "$Args::args{'exclude_db'}-md5.idx") { print "Exclude Database has not been indexed - it will be as an md5sum file
\n"; print "
\n"; Hash::index_md5sum($Args::args{'exclude_db'}); print "
\n"; } # only print it if it was successful print HOST_CONFIG "exclude_db \'$Args::args{'exclude_db'}\'\n" if (-e "$Args::args{'exclude_db'}-md5.idx"); } close HOST_CONFIG; print "Configuration file ($fname) created

\n"; print "We must now import an image file for this host\n"; print "

" . "\"Add\n"; Print::print_html_footer(); return 0; } # Open a host in the given case sub host_open { Print::print_html_header("Open Host In $Args::args{'case'}"); # Create an array of directories in the case, verifying that there is # a config file my @hosts; opendir HOSTS, $::case_dir or die "Can't open $::case_dir directory: $!"; foreach my $h (readdir HOSTS) { next if (($h eq '.') || ($h eq '..')); my $hfile = Caseman::host_config_fname($h); push @hosts, $h if ((-d "$::case_dir" . "$h") && (-e "$hfile")); } closedir HOSTS; print "Case: $Args::args{'case'}

\n"; print "
"; if (scalar @hosts == 0) { print "No hosts have been added to the case yet" . "

Select the Add Host button below to create one.
\n"; } else { print "Select the host to open or create a new one
\n"; print_menu_tabs(); print "\n"; print "\n" . "\n" . "\n" . Args::make_hidden() . "" . "\n"; my $first = 0; foreach my $h (@hosts) { print ""; my $fname = Caseman::host_config_fname($h); open CONFIG, "<$fname" or die "Can't open host config file ($fname)"; my $desc = "None Provided"; while () { s/^\s+//; s/\s+$//; if (/^desc\s+(.*)$/) { $desc = $1; last; } } close CONFIG; print "" . "\n"; } print "
NameDescription 
" . " $h $desc" . "details
\n"; # Display pulldown of investigators my @invs = read_invest(); if (scalar @invs == 0) { print "\n"; } else { print "
Investigator (for reports only): "; my $cur_inv = ""; $cur_inv = $Args::args{'inv'} if (exists $Args::args{'inv'}); print "\n"; } } print "

\n" . "\n"; # Make a table for the buttons. The table will either be 3 or 2 # entries wide, depending on if there is an 'Ok' button or not unless (scalar @hosts == 0) { print "\n"; } # Add Host print "" . # Close Button "
" . "\n" . "\n" . "
\n" . "\n" . "\n" . "\n" . "\n" . "
" . "
\n" . "\n" . "\n" . "\n" . "
\n"; print "\n" . "" . "\n" . "
 " . "\"Help\"" . " 
\n"; Print::print_html_footer(); return 0; } # Log that a given host was opened and then proceed to open an image sub host_open_log { unless ((exists $Args::args{'inv'}) && ($Args::args{'inv'} ne "")) { my @invs = read_invest(); if (scalar @invs == 0) { $Args::args{'inv'} = $Args::enc_args{'inv'} = 'unknown'; } else { Print::print_html_header("Missing Investigator"); print "
An investigator must be selected

\n" . "

\n" . "\n" . "\n" . Args::make_hidden(); print "Select one of the following:"; print "

\n" . "\n" . "

\n"; Print::print_html_footer(); return 0; } } Args::check_inv(); Print::log_case_info( "Host $Args::args{'host'} opened by $Args::args{'inv'}"); Print::log_host_info( "Host $Args::args{'host'} opened by $Args::args{'inv'}"); Print::log_host_inv("Host $Args::args{'host'} opened"); $Args::args{'view'} = $Args::enc_args{'view'} = $Caseman::VOL_OPEN; vol_open(); } # Provide details about the configuration of a host. This window is # a link from the HOST_OPEN window sub host_details { Print::print_html_header( "Details of $Args::args{'case'}:$Args::args{'host'}"); print "Case: $Args::args{'case'}

" . "
" . "\"Host" . "


\n" . "\n" . # Name "" . "\n" . # Description "" . "\n" . # Timezone "" . "\n" . # Timeskew "" . "\n" . "\n" . # Actual Directory "" . "\n" . "\n" . # Alert Database "" . "\n" . # Exclude Database "" . "\n" . "
Name:$Args::args{'host'}
Description:" . (($Caseman::host_desc ne "") ? $Caseman::host_desc : " ") . "
Time zone: $Caseman::tz
Timeskew:$Caseman::ts
 
Directory:$::host_dir
 
Alert Hash Database:" . (($Caseman::alert_db ne "") ? $Caseman::alert_db : " ") . "
Exclude Hash Database:" . (($Caseman::exclude_db ne "") ? $Caseman::exclude_db : " ") . "
\n"; # Final Button print "

\n" . "\n" . "\n" . Args::make_hidden() . "\n
"; Print::print_html_footer(); return; } # Read the investigators file and return a sorted list sub read_invest { my $fname = investig_fname(); open INVES, "<$fname" or return; my @investigs; while () { chomp; s/^\s+//; s/\s+$//; push @investigs, $1 if (/^($::REG_INVESTIG)$/o); } close(INVES); sort { lc($a) cmp lc($b) } @investigs; } # File of investigators name in list sub investig_fname { return "$::case_dir" . "investigators.txt"; } #################################################################### # Image Functions # Types of modes for fname (i.e. can we overwrite it if it exists) my $FNAME_MODE_INIT = 0; my $FNAME_MODE_OVER = 1; my $MD5_NOTHING = 1; my $MD5_CALC = 2; my $MD5_ADD = 3; my $IMG_ADD_SYM = 1; my $IMG_ADD_COPY = 2; my $IMG_ADD_MOVE = 3; # Open an image that has been configured sub vol_open { Print::print_html_header( "Open Image In $Args::args{'case'}:$Args::args{'host'}"); print "Case: $Args::args{'case'}
\n"; print "Host: $Args::args{'host'}
\n"; print "
\n"; # the images have been loaded from reading the host config file in # autopsy_main if (scalar(keys %Caseman::vol2ftype) == 0) { print "No images have been added to this host yet

\n" . "Select the Add Image File button below to add one\n"; goto EGRESS; } if ($::LIVE == 1) { print "Select a volume to analyze.
\n"; } else { print "Select a volume to analyze or add a new image file.
\n"; } print_menu_tabs(); print "\n"; # We want to sort, so rearrange the hash my %mnt2vol; my %par2disk; # Cycle through each image we read from the host config foreach my $i (keys %Caseman::vol2cat) { if ($Caseman::vol2cat{$i} eq "disk") { $mnt2vol{"1disk--AUTOPSY--$i"} = $i; } elsif ($Caseman::vol2cat{$i} eq "part") { if ( ($Caseman::vol2ftype{$i} eq "raw") || ($Caseman::vol2ftype{$i} eq "swap")) { $mnt2vol{"2$Caseman::vol2ftype{$i}--AUTOPSY--$i"} = $i; } else { $mnt2vol{"2$Caseman::vol2mnt{$i}--AUTOPSY--$i"} = $i; } } } # sort via parent volume, then starting location, # and then mount point (which includes the name) my @mnt = sort { ($Caseman::vol2par{$mnt2vol{$a}} cmp $Caseman::vol2par{$mnt2vol{$b}}) or ($Caseman::vol2start{$mnt2vol{$a}} <=> $Caseman::vol2start{$mnt2vol{$b}}) or (lc($a) cmp lc($b)) } keys %mnt2vol; # It is possible to have only the dls image and not the original # We need to search for those now because they will not be in the # list that we just made (which are arranged via mount point) my @orphan_dls; # cycle through each image and check its type and original foreach my $k (keys %Caseman::vol2ftype) { if ( ($Caseman::vol2ftype{$k} eq "dls") && (!exists $Caseman::mod2vol{$k})) { push @orphan_dls, $k; } } print "\n" . "\n" . "\n" . Args::make_hidden() . "" . "" . "" . # vol name "\n"; my $prev_par = ""; for (my $i = 0; $i <= $#mnt; $i++) { my $vol = $mnt2vol{$mnt[$i]}; if ($Caseman::vol2par{$vol} ne $prev_par) { print "\n" if ($i != 0); $prev_par = $Caseman::vol2par{$vol}; } # Mount Point # If we have the dummy string at the end of the duplicate # entry, then take it off and print the original $mnt[$i] = $1 if ($mnt[$i] =~ /^\d(.*?)--AUTOPSY--$::REG_VNAME$/o); print "" . "" . ""; # image name and ftype print ""; if ($::LIVE == 0) { print "" . "\n"; } else { print "\n"; } } # If we are done with the regular images and have some orphan # dls images, print them my @sort = sort @orphan_dls; for (my $i = 0; $i <= $#sort; $i++) { print "\n"; } # Begin Button print "
 mountnamefs type

$mnt[$i]$Caseman::vol2sname{$vol}$Caseman::vol2ftype{$vol}details
 
  ( unalloc)$Caseman::vol2sname{$sort[$i]}$Caseman::vol2ftype{$sort[$i]}
\n"; EGRESS: print "

" . "\n"; # Ok Button if (scalar(keys %Caseman::vol2ftype) == 0) { print "\n"; } else { print "\n"; } # Image Add Button if ($::LIVE == 0) { print "\n" . # Cancel Button ""; } else { print "\n"; } # Help Button print "\n" . "
 
" . "\n" . "" . "
\n" . "\n" . "\n" . Args::make_hidden() . "
" . "
\n" . "\n" . "\n" . "\n" . "\n" . "
  
 " . "\"Help\"" . " 
\n"; # Other features that can be done on a host if ($::LIVE == 0) { print "

" . "\n" . "\n"; # Timeline of file activity print "\n"; # verify the integrity of the images print "\n" . # Hashdatabases "\n" . "
" . "" . "" . "" . "" . "" . "
\n"; # Notes if ($::USE_NOTES == 1) { print "\n" . "\n" . "\n" . "\n" . "\n" . "
" . "" . "" . "" . "
\n"; } # If LIVE } else { print "
\n" . "\"Logo\"

\n"; } Print::print_html_footer(); return 0; } # Log in the host log that a given image was opened by what user # then open the main window sub vol_open_log { # These will be stopped in the func during LIVE Print::log_host_info( "Image $Args::args{'vol'} opened by $Args::args{'inv'}"); Print::log_host_inv("$Args::args{'vol'}: volume opened"); $Args::args{'mod'} = $Args::enc_args{'mod'} = $::MOD_FRAME; $Args::args{'view'} = $Args::enc_args{'view'} = $Frame::IMG_FRAME; Frame::main(); } # Menu to add a new image to the host # # The list of new images is determined by looking in the images directory # and seeing which is not yet configured # sub img_add { Print::print_html_header( "Add Image To $Args::args{'case'}:$Args::args{'host'}"); print "Case: $Args::args{'case'}
\n" . "Host: $Args::args{'host'}
\n"; print "

\n" . "\n" . "\n" . Args::make_hidden(); print < \"Add


 
1. Location
Enter the full path (starting with /) to the image file.
If the image is split (either raw or EnCase), then enter '*' for the extension.
  
 
2. Type
Please select if this image file is for a disk or a single partition.
   Disk Partition  
 
3. Import Method
To analyze the image file, it must be located in the evidence locker. It can be imported from its current location using a symbolic link, by copying it, or by moving it. Note that if a system failure occurs during the move, then the image could become corrupt.
   Symlink Copy Move
 

EOF1 print "\n" . "\n" . # HELP " \n" . "\n" . "
 
\n" . "
\n" . " \n" . " \n" . Args::make_hidden() . "
\n" . "
\n" . " \n" . " \"Help\"\n" . "
\n"; Print::print_html_footer(); return 0; } # List the images from the glob - called from img_add_prep if spl_conf is not set sub img_add_split_conf { my $img_path = Args::get_img_path_wild(); my $img_type = $Args::args{'imgtype'}; print "


Split Image Confirmation

\n"; my @spl_img = glob($img_path); if (scalar(@spl_img) == 0) { print "No images were found at this location ($img_path)
\n" . "Use the back button and fix the path
\n"; return; } print < If this is not the correct order, then you should change the naming convention.
Press the Next button at the bottom of the page if this is correct.

EOF1 my $a = 0; foreach $i (@spl_img) { # We need to do this when we analyze the image, so do it here too to show # what will be analyzed $i = $1 if ($i =~ /^($::REG_IMG_PATH)$/); print "\n"; $a++; } my $vs = ""; $vs = "&vstype=$Args::args{'vstype'}" if (exists $Args::args{'vstype'}); # Print the Ok Button print "
 
$a$i


\n" . "" . "\n" . "
" . "\"Next\"" . "\"cancel\"" . "
\n"; return 0; } # Run the autodetect stuff and get confirmation from the user sub img_add_prep { Args::check_img_path_wild(); Args::check_sort(); unless ((exists $Args::args{'imgtype'}) && ($Args::args{'imgtype'} =~ /^\w+$/)) { Print::print_check_err("Invalid image type"); } Print::print_html_header("Collecting details on new image file"); my $img_path = Args::get_img_path_wild(); my $img_type = $Args::args{'imgtype'}; my $spl_conf = 0; $spl_conf = 1 if ((exists $Args::args{'spl_conf'}) && ($Args::args{'spl_conf'} == 1)); # If we have a wildcard then it is a split image, so we verify it first and # then make a string of the images so we can test it. if ($img_path =~ /[\*\?]/) { if ($spl_conf == 0) { return img_add_split_conf(); } else { $img_tmp = ""; foreach my $i (glob($img_path)) { if ($i =~ /^($::REG_IMG_PATH)$/) { $img_tmp .= "\"$1\" "; } } $img_path = $img_tmp; } } else { unless ((-f $img_path) || (-d $img_path) || (-l $img_path) || (-b $img_path) || (-c $img_path)) { Print::print_err("Image file not found ($img_path)"); } $img_path = "\"$img_path\""; } # Get the image type local *OUT; Exec::exec_pipe(*OUT, "'$::TSKDIR/img_stat' -t $img_path"); my $itype = Exec::read_pipe_line(*OUT); if (defined $itype) { chomp $itype; $itype = $1 if ($itype =~ /^(\w+)$/); } else { print "The image format type could not be determined for this image file
\n"; return; } close(OUT); # The plan here is to collect the needed info and then we print it my $conflict = 0; my $cnt = 0; $start[0] = ""; $end[0] = ""; $type[0] = ""; $desc[0] = ""; $active[0] = ""; my $vstype = ""; my $vstype_flag = ""; if ($img_type eq "disk") { my $out; if ( (exists $Args::args{'vstype'}) && ($Args::args{'vstype'} =~ /^(\w+)$/)) { $vstype = $Args::args{'vstype'}; $vstype_flag = "-t $vstype"; } # Get the type else { Exec::exec_pipe(*OUT, "'$::TSKDIR/mmstat' -i $itype $img_path"); $vstype = Exec::read_pipe_line(*OUT); close(OUT); chomp $vstype if (defined $vstype); if ( (!defined $vstype) || ($vstype =~ /^Error determining/) || ($vstype eq "") || (!exists $Vs::type{$vstype}) || ($vstype !~ /^\w+$/)) { print "\n" . "\n" . "\n" . "\n" . "\n" . "\n" . "\n" . "\n" . Args::make_hidden() . "\n" . "\n" . "" . "" . "" . "
Warning: Autopsy could not determine the volume system type for the disk image (i.e. the type of partition table).
\n" . "Please select the type from the list below or reclassify the image as a volume image instead of as a disk image.
 
Disk Image Volume Image
Volume System Type (disk image only):  
 


\n" . "" . "\n"; return; } $vstype = $1 if ($vstype =~ /^(\w+)$/); $vstype_flag = "-t $vstype"; } # Run 'mmls' on the image Exec::exec_pipe(*OUT, "'$::TSKDIR/mmls' -i $itype $vstype_flag -r $img_path"); # cycle through results and add each to table with file system type my $part_type = ""; while ($_ = Exec::read_pipe_line(*OUT)) { last if (/^Error determining partition/); if (/^\d+:\s+[\d:]+\s+(\d+)\s+(\d+)\s+\d+\s+(\S.*)$/) { $start[$cnt] = $1; $end[$cnt] = $2; $desc[$cnt] = $3; $active[$cnt] = 1; } elsif ((/^DOS Partition/) || (/^BSD Disk/) || (/^Sun VTOC/) || (/^MAC Partition/) || (/^GUID Partition/)) { $part_type = $_; #print " \n"; #print "$_\n"; next; } elsif (/^Sector:/) { #print "$_\n"; next; } else { next; } # Skip the BSD partition for the full disk next if ( ($part_type =~ /^BSD Disk/) && ($start[$cnt] == 0) && ($desc[$cnt] =~ /^Unused/)); # Skip if this is an extended DOS partition next if ( ($part_type =~ /^DOS Partition/) && ($desc[$cnt] =~ /Extended \(/)); # Get rid of the leading 0s $start[$cnt] = $1 if ($start[$cnt] =~ /^0+([1-9]\d*)$/); $end[$cnt] = $1 if ($end[$cnt] =~ /^0+([1-9]\d*)$/); # Do we already have this partition? my $i; for ($i = 0; $i < $cnt; $i++) { next if ($active[$i] == 0); if ($start[$i] == $start[$cnt]) { $conflict = 1; if ($end[$i] == $end[$cnt]) { last; } #The previous was the BSD partition - skip it */ if ( ($desc[$i] =~ /^FreeBSD \(0xA5\)/) || ($desc[$i] =~ /^OpenBSD \(0xA6\)/) || ($desc[$i] =~ /^NetBSD \(0xA9\)/)) { $active[$i] = 0; # if the current one is the BSD partition for # the full partition/disk then skip it if ($desc[$cnt] =~ /^Unused /) { $active[$cnt] = 0; } } } # Do we start inside of another? if (($start[$i] > $start[$cnt]) && ($end[$i] < $start[$cnt])) { $conflict = 1; } # Do we end inside of another? elsif (($start[$i] < $end[$cnt]) && ($end[$i] > $end[$cnt])) { $conflict = 1; } } if (($end[$i] == $end[$cnt]) && ($i != $cnt)) { next; } local *OUT2; my $out2; # Run 'fstat -t' on the image Exec::exec_pipe(*OUT2, "'$::TSKDIR/fsstat' -o $start[$cnt] -i $itype -t $img_path"); $type[$cnt] = Exec::read_pipe_line(*OUT2); close(OUT2); if ( (!exists $type[$cnt]) || (!defined $type[$cnt]) || ($type[$cnt] =~ /^Cannot determine/) || ($type[$cnt] eq "")) { $type[$cnt] = "Unknown"; } chomp $type[$cnt]; $cnt++; } close(OUT); if ($conflict == 1) { print "Warning: Conflicts in the partitions were detected.
The full mmls output is given at the bottom of the page\n" . " \n"; } } # If a volume, then run fsstat on it elsif ($img_type eq "volume") { # Run 'fstat -t' on the image Exec::exec_pipe(*OUT, "'$::TSKDIR/fsstat' -t -i $itype $img_path"); $type[0] = Exec::read_pipe_line(*OUT); close(OUT); if ( (!defined $type[0]) || ($type[0] =~ /^Cannot determine/) || ($type[0] eq "")) { $type[0] = "Unknown"; print "Warning: The file system type of the volume image file could not be determined.
\n" . "If this is a disk image file, return to the previous page and change the type.

\n"; } chomp $type[0]; $start[0] = 0; $end[0] = 0; $active[0] = 1; $desc[0] = $type[0]; $cnt++; close(OUT); } else { Print::print_err("Unknown image type: $img_type"); } my $sname = $img_path; $sname = "$::IMGDIR/" . "$1" if ($sname =~ /\/($::REG_FILE)\"$/); # Now that we have the information about the partitions and disks, print the fields print <

Image File Details

EOF1 # We do not currently offer integrity options for non-raw files if (($itype eq "raw") || ($itype eq "split")) { print < EOF1b } else { print "\n"; } print <

File System Details

Local Name: $sname
Data Integrity: An MD5 hash can be used to verify the integrity of the image. (With split images, this hash is for the full image file)
   Ignore the hash value for this image.
   Calculate the hash value for this image.
   Add the following MD5 hash value for this image:
       
         Verify hash after importing?
EOF1c print Args::make_hidden(); print "\n" if ($vstype ne ""); my $idx = 1; my $ms_cnt = 0; my @ms_name = ("C:", "D:", "E:", "F:", "G:", "H:", "I:", "J:"); for (my $i = 0; $i < $cnt; $i++) { next if ($active[$i] == 0); print "\n"; if ($cnt > 1) { print "\n"; } else { print "\n"; } unless (($start[$i] == 0) && ($end[$i] == 0)) { print "\n"; } print "" . "\n" . "\n" . "\n" . "\n"; $idx++; } print "
Analysis of the image file shows the following partitions:
 
Partition $idx (Type: $desc[$i])
  Add to case? " . "
  Sector Range: " . "$start[$i] to $end[$i]" . "
  Mount Point: File System Type:
 
\n"; print <
EOF2 print Args::make_hidden(); print <
\"Help\"
EOF3 if ($img_type eq "disk") { print "

For your reference, the mmls output was the following:
\n"; # Run 'mmls' on the image Exec::exec_pipe(*OUT, "'$::TSKDIR/mmls' $vstype_flag -r $img_path"); # cycle through results and add each to table with file system type while ($_ = Exec::read_pipe_line(*OUT)) { print "$_
\n"; } } Print::print_html_footer(); return 0; } # Add the image to the configuration by adding it to the host config file # and the md5.txt file if that data was provided sub img_add_doit { Args::check_num_img(); Args::check_img_path_wild(); Args::check_sort(); Args::check_do_md5(); my $num_img = Args::get_num_img(); my $img_path = Args::get_img_path_wild(); my $import_type = Args::get_sort(); Print::print_html_header("Add a new image to an Autopsy Case"); my $err = 0; my $add_num = 0; $start[0] = 0; $end[0] = 0; $ftype[0] = ""; $mnt[0] = ""; # We need a string with all images in it for the hashes and file system testing my $img_path_full; if ($img_path =~ /[\*\?]/) { $img_path_full = ""; foreach my $i (glob($img_path)) { if ($i =~ /^($::REG_IMG_PATH)$/) { $img_path_full .= "\"$1\" "; } } } else { $img_path_full = "\"$img_path\""; } # Get the image type local *OUT; Exec::exec_pipe(*OUT, "'$::TSKDIR/img_stat' -t $img_path_full"); my $itype = Exec::read_pipe_line(*OUT); if (defined $itype) { chomp $itype; $itype = $1 if ($itype =~ /^(\w+)$/); } else { print "The image format type could not be determined for this image file
\n"; return; } close(OUT); # Check the hash of the image if that is the plan my $do_md5 = Args::get_do_md5(); my $act_md5 = ""; unless ($do_md5 == $MD5_NOTHING) { # Do we need to calculate an MD5? if ( ($do_md5 == $MD5_CALC) || ( ($do_md5 == $MD5_ADD) && (exists $Args::args{'ver_md5'}) && ($Args::args{'ver_md5'} == 1)) ) { print "

Calculating MD5 (this could take a while)
\n"; $act_md5 = Hash::calc_md5_split($img_path_full); unless ($act_md5 =~ /^$::REG_MD5$/o) { print "Error calculating MD5: $act_md5
\n"; return 1; } print "Current MD5: $act_md5
\n"; } # And md5 value was given so we can add it to the md5.txt file if (($do_md5 == $MD5_ADD) && (exists $Args::args{'md5'})) { my $md5 = $Args::args{'md5'}; unless ($md5 =~ /^$::REG_MD5$/o) { if ($md5 eq "") { print "MD5 value missing
\n"; } else { print "Invalid MD5 value (32 numbers or letters a-f)
\n"; } print "

" . "\"Back\"\n"; return 1; } $md5 =~ tr/[a-f]/[A-F]/; # They also want us to validate the MD5 if ( (exists $Args::args{'ver_md5'}) && ($Args::args{'ver_md5'} == 1)) { if ($act_md5 eq $md5) { print "Integrity Check Passed
\n"; Print::log_host_info("Integrity check passed on new image"); } else { print "" . "Integrity Check Failed

\n" . "Provided: $md5
\n" . "Image not added to case
\n"; Print::log_host_info("Integrity check failed on new image"); return 1; } } # set the act_md5 value to what was given and verified $act_md5 = $md5; } # We will add the MD5 to the config file after we get its ID } # Proces the image arguments to make sure they are all there and test the # file system type print "Testing partitions
\n"; for (my $i = 0; $i <= $num_img; $i++) { next unless ((exists $Args::args{"yes-" . $i}) && ($Args::args{"yes-" . $i} == 1)); if ( (exists $Args::args{"start-" . $i}) && ($Args::args{"start-" . $i} =~ /^(\d+)$/)) { $start[$add_num] = $1; } else { print "Missing starting address for partition $i
\n"; $err = 1; last; } if ( (exists $Args::args{"end-" . $i}) && ($Args::args{"end-" . $i} =~ /^(\d+)$/)) { $end[$add_num] = $1; } else { print "Missing ending address for partition $i
\n"; $err = 1; last; } if ( (exists $Args::args{"mnt-" . $i}) && ($Args::args{"mnt-" . $i} =~ /^($::REG_MNT)$/)) { $mnt[$add_num] = $1; } else { print "Missing mount point for partition $i
\n"; $err = 1; last; } if ( (exists $Args::args{"ftype-" . $i}) && ($Args::args{"ftype-" . $i} =~ /^($::REG_FTYPE)$/)) { $ftype[$add_num] = $1; } else { print "Missing file system type for partition $i
\n"; $err = 1; last; } # Test the File System if (($ftype[$add_num] ne 'swap') && ($ftype[$add_num] ne 'raw')) { local *OUT; my $out; # Run 'fsstat' and see if there is any output - else there was # an error and the data went to STDERR Exec::exec_pipe(*OUT, "'$::TSKDIR/fsstat' -o $start[$add_num] -i $itype -f $ftype[$add_num] $img_path_full" ); unless (read(OUT, $out, 1)) { print "

Partition $i is not a $ftype[$add_num] file system
\n"; $err = 1; last; } close(OUT); } $add_num++; } # Go back if we got an error if ($err == 1) { print "Use the browser's back button to fix the data
\n"; return 1; } ################################################## # Copy the images and add them to the config file if ($import_type == $IMG_ADD_SYM) { Print::print_err("ERROR: /bin/ln missing") unless (-x '/bin/ln'); print "Linking image(s) into evidence locker
\n"; } elsif ($import_type == $IMG_ADD_COPY) { Print::print_err("ERROR: /bin/cp missing") unless (-x '/bin/cp'); print "Copying image(s) into evidence locker (this could take a little while)
\n"; } elsif ($import_type == $IMG_ADD_MOVE) { Print::print_err("ERROR: /bin/mv missing") unless (-x '/bin/mv'); print "Moving image(s) into evidence locker
\n"; } else { Print::print_err("Invalid Import Type: $import_type
\n"); } my $imgid = ""; foreach my $i (glob($img_path)) { # remove the tainting $i = $1 if ($i =~ /^($::REG_IMG_PATH)$/); # Deterine the local (target) name my $img = ""; if ($i =~ /\/($::REG_FILE)$/) { $img = "$::IMGDIR/$1"; } else { Print::print_err("Error Parsing Image Path ($i)
\n"); } # Get the full path of the destination my $img_dst = "$::host_dir" . "$img"; if ((-e "$img_dst") || (-l "$img_dst")) { Print::print_err( "An image by the same name already exists in the Host directory ($img)
\n" . "Use the browser's back button to fix the name or delete the existing file." ); } my $orig_size = (stat("$i"))[7]; # Copy, Move, or link it if ($import_type == $IMG_ADD_SYM) { Print::log_host_info( "Sym Linking image $img_path into $Args::args{'case'}:$Args::args{'host'}" ); Exec::exec_sys("/bin/ln -s '$i' '$img_dst'"); } elsif ($import_type == $IMG_ADD_COPY) { Print::log_host_info( "Copying image $img_path into $Args::args{'case'}:$Args::args{'host'}" ); Exec::exec_sys("/bin/cp '$i' '$img_dst'"); } elsif ($import_type == $IMG_ADD_MOVE) { Print::log_host_info( "Moving image $img_path into $Args::args{'case'}:$Args::args{'host'}" ); Exec::exec_sys("/bin/mv '$i' '$img_dst'"); } my $new_size = (stat("$img_dst"))[7]; if ($new_size != $orig_size) { Print::print_err( "Original image size ($orig_size) is not the same as the destination size ($new_size)" ); } # Add the disk and partition images to the config file $imgid = Caseman::add_img_host_config("image", "$itype $img", $imgid); } print "Image file added with ID $imgid
\n"; # AFM files have raw files that we also need to copy # This approach is not the best, since it may copy more than # is needed if ($itype eq "afm") { my $afm_base_path = ""; if ($img_path =~ /^(.*?)\.afm/i) { $afm_base_path = $1; $afm_base_path .= ".[0-9][0-9][0-9]"; } else { Print::print_err( "Error parsing out base name of AFM file $img_path"); } print "BASE: $afm_base_path
\n"; my $copied = 0; foreach my $i (glob($afm_base_path)) { $copied++; # remove the tainting $i = $1 if ($i =~ /^($::REG_IMG_PATH)$/); # Deterine the local (target) name my $img = ""; if ($i =~ /\/($::REG_FILE)$/) { $img = "$::IMGDIR/$1"; } else { Print::print_err("Error Parsing Image Path ($i)
\n"); } # Get the full path of the destination my $img_dst = "$::host_dir" . "$img"; if ((-e "$img_dst") || (-l "$img_dst")) { Print::print_err( "An image by the same name already exists in the Host directory ($img) (AFM import)
\n" . "Use the browser's back button to fix the name or delete the existing file." ); } my $orig_size = (stat("$i"))[7]; # Copy, Move, or link it if ($import_type == $IMG_ADD_SYM) { Print::log_host_info( "Sym Linking image $img_path into $Args::args{'case'}:$Args::args{'host'}" ); Exec::exec_sys("/bin/ln -s '$i' '$img_dst'"); } elsif ($import_type == $IMG_ADD_COPY) { Print::log_host_info( "Copying image $img_path into $Args::args{'case'}:$Args::args{'host'}" ); Exec::exec_sys("/bin/cp '$i' '$img_dst'"); } elsif ($import_type == $IMG_ADD_MOVE) { Print::log_host_info( "Moving image $img_path into $Args::args{'case'}:$Args::args{'host'}" ); Exec::exec_sys("/bin/mv '$i' '$img_dst'"); } my $new_size = (stat("$img_dst"))[7]; if ($new_size != $orig_size) { Print::print_err( "Original image size ($orig_size) is not the same as the destination size ($new_size) after AFM import" ); } } if ($copied == 0) { Print::print_err( "No AFM raw files were found with the same base name and a numeric extension" ); } else { print "$copied AFM raw files imported
\n"; } } Caseman::update_md5("$imgid", "$act_md5") unless (($do_md5 == $MD5_NOTHING) || ($imgid eq "")); # Add a disk entry if the image is of a disk unless (($add_num == 1) && ($end[0] == 0) && ($start[0] == 0)) { unless ((exists $Args::args{'vstype'}) && ($Args::args{'vstype'} =~ /^(\w+)$/)) { Print::print_err("Missing Volume System Type"); } my $vstype = $Args::args{'vstype'}; my $diskid = Caseman::add_vol_host_config("disk", "$imgid $vstype"); print "

Disk image (type $vstype) added with ID $diskid\n"; } # Add the file system / partition entries for (my $i = 0; $i < $add_num; $i++) { my $volid = Caseman::add_vol_host_config("part", "$imgid $start[$i] $end[$i] $ftype[$i] $mnt[$i]"); print "

Volume image ($start[$i] to $end[$i] - $ftype[$i] - $mnt[$i]) added with ID $volid\n"; } print <

\"Ok\" \"Ok\"
EOF Print::print_html_footer(); return 0; } # Display details of image based on config values # provides links to remove the config of the image and to get the file # system details sub vol_details { Print::print_html_header("Details of $Args::args{'vol'}"); Args::get_unitsize(); my $vol = Args::get_vol('vol'); my $mnt = $Caseman::vol2mnt{$vol}; my $ftype = $Caseman::vol2ftype{$vol}; print "
" . "\"Image" . "


\n" . "\n" . " \n" . # Name " " . "\n" . "" . "\n" . "" . "\n" . "" . "\n" # Mount . " " . "\n" . # Type " " . "\n"; # Host Directory print " \n" # Strings File . " \n" . " " . "\n" . # Unicode Strings File " " . "\n"; if (($ftype ne "raw") && ($ftype ne "swap")) { # dls file print " " . "\n"; # Strings of dls print " " . "\n"; # Unicodde Strings of dls print " " . "\n"; } print " \n" . "
 
Name:$Caseman::vol2sname{$vol}
Volume Id:$vol
Parent Volume Id:$Caseman::vol2par{$vol}
Image File Format:$Caseman::vol2itype{$vol}
Mounting Point:$mnt
File System Type:$ftype
 
External Files
ASCII Strings:" . ( (exists $Caseman::vol2str{$vol}) ? $Caseman::vol2sname{$Caseman::vol2str{$vol}} : " " ) . "
Unicode Strings:" . ( (exists $Caseman::vol2uni{$vol}) ? $Caseman::vol2sname{$Caseman::vol2uni{$vol}} : " " ) . "
Unallocated $Fs::addr_unit{$ftype}s:" . ( (exists $Caseman::vol2dls{$vol}) ? $Caseman::vol2sname{$Caseman::vol2dls{$vol}} : " " ) . "
ASCII Strings of Unallocated:" . ( ( (exists $Caseman::vol2dls{$vol}) && (exists $Caseman::vol2str{$Caseman::vol2dls{$vol}}) ) ? $Caseman::vol2sname{$Caseman::vol2str{$Caseman::vol2dls{$vol}}} : " " ) . "
Unicode Strings of Unallocated:" . ( ( (exists $Caseman::vol2dls{$vol}) && (exists $Caseman::vol2uni{$Caseman::vol2dls{$vol}}) ) ? $Caseman::vol2sname{$Caseman::vol2uni{$Caseman::vol2dls{$vol}}} : " " ) . "
 
\n\n"; } # Strings File if ( (!(exists $Caseman::vol2str{$vol})) || (!(exists $Caseman::vol2uni{$vol}))) { print "\n" . "\n"; } if (($ftype eq 'dls') || ($ftype eq 'swap') || ($ftype eq 'raw')) { # Place holder for types that have no notion of unallocated } # Unallocated Space File elsif (!(exists $Caseman::vol2dls{$vol})) { print "\n"; } if ( (!(exists $Caseman::vol2str{$vol})) || (!(exists $Caseman::vol2uni{$vol})) || (!(exists $Caseman::vol2dls{$vol})) || ( (exists $Caseman::vol2dls{$vol}) && ( (!(exists $Caseman::vol2str{$Caseman::vol2dls{$vol}})) || (!(exists $Caseman::vol2uni{$Caseman::vol2dls{$vol}}))) ) ) { print "

Extract Strings of
Entire Volume

" . "Extracting the ASCII and Unicode strings from a file system will " . "make keyword searching faster.

\n" . "
\n" . "Generate MD5? " . "

" . "\n" . "\n" . "\n" . Args::make_hidden(); if (!(exists $Caseman::vol2str{$vol})) { print "ASCII: \n"; } if (!(exists $Caseman::vol2uni{$vol})) { print " Unicode: \n"; } print "

\n
 

Extract Unallocated $Fs::addr_unit{$ftype}s

" . "Extracting the unallocated data in a file system allows " . "more focused keyword searches and data recovery.

\n" . "(Note: This Does Not Include Slack Space)
\n" . "
\n"; print "Generate MD5? " . "

" . "\n" . "\n" . "\n" . Args::make_hidden() . "\n
\n"; } # strings of 'dls' elsif ((!(exists $Caseman::vol2str{$Caseman::vol2dls{$vol}})) || (!(exists $Caseman::vol2uni{$Caseman::vol2dls{$vol}}))) { print "

Extract Strings of
Unallocated $Fs::addr_unit{$ftype}s

" . "Extracting the ASCII strings from the unallocated data will make " . "keyword searching faster.

\n" . "
\n" . "Generate MD5? " . "

" . "\n" . "\n" . "\n" . "\n" . Args::make_hidden(); if (!(exists $Caseman::vol2str{$Caseman::vol2dls{$vol}})) { print "ASCII: \n"; } if (!(exists $Caseman::vol2uni{$Caseman::vol2dls{$vol}})) { print " Unicode: \n"; } print "

\n

\n"; } print "

" . "\n" . # Ok "\n"; print "\n"; } else { print " \n"; } # Remove Image # THis was removed 12/03 because it causes problems because the image still # exists and config entries and ... it becomes a mess # print # "\n". # "
" . "
\n" . "\n" . "\n" . Args::make_hidden() . "
"; if (($ftype ne "raw") && ($ftype ne "swap")) { # File System Details print "
\n" . Args::make_hidden() . "\n" . "\n" . "\n" . "
". # "
\n". # "\n". # "\n". # Args::make_hidden(). # "\n". # "\n". # "
". # "
\n"; Print::print_html_footer(); return 0; } # remove the config files sub img_del { Args::check_vol('vol'); # Args::check_ftype(); Print::print_html_header( "Removing Configuration Settings for $Args::args{'vol'}"); Caseman::del_host_config("", $Args::args{'vol'}, ""); Caseman::update_md5($Args::args{'vol'}, ""); print "Settings for $Args::args{'vol'} removed from " . "$Args::args{'case'}:$Args::args{'host'}.\n" . "

NOTE: The actual file still exists in the host directory.\n"; print "

" . "\"Ok\"\n"; Print::print_html_footer(); return 0; } # Make a strings -t d file for the image to decrease the search time # Can make both ASCII and Unicode strings files sub vol_makestr { Print::print_html_header("Extracting Strings"); my $ascii = 0; my $uni = 0; my $vol = Args::get_vol('vol'); my $ftype = $Caseman::vol2ftype{$vol}; my $img = $Caseman::vol2path{$vol}; my $offset = $Caseman::vol2start{$vol}; my $imgtype = $Caseman::vol2itype{$vol}; if ((exists $Args::args{'str'}) && ($Args::args{'str'} == 1)) { if (exists $Caseman::vol2str{$vol}) { Print::print_err( "Image already has an ASCII strings file: $Caseman::vol2sname{$vol}" ); } $ascii = 1; } if ((exists $Args::args{'uni'}) && ($Args::args{'uni'} == 1)) { if (exists $Caseman::vol2uni{$vol}) { Print::print_err( "Image already has a Unicode strings file: $Caseman::vol2sname{$vol}" ); } $uni = 1; } if (($uni == 0) && ($ascii == 0)) { goto str_egress; } my $base_name = $Caseman::vol2sname{$vol}; if ($ascii == 1) { my $fname_rel = "$::DATADIR/${base_name}-$ftype.asc"; my $fname = "$::host_dir" . "$fname_rel"; if (-e "$fname") { my $i = 1; $i++ while (-e "$::host_dir" . "$::DATADIR/" . "${base_name}-$ftype-$i.asc"); $fname_rel = "$::DATADIR/${base_name}-$ftype-$i.asc"; $fname = "$::host_dir" . "$fname_rel"; } print "Extracting ASCII strings from $Caseman::vol2sname{$vol}
\n"; Print::log_host_inv( "$Caseman::vol2sname{$vol}: Saving ASCII strings to $fname_rel"); local *OUT; my $hit_cnt = 0; $SIG{ALRM} = sub { if (($hit_cnt++ % 5) == 0) { print "+"; } else { print "-"; } alarm(5); }; alarm(5); if ($ftype eq "dls") { Exec::exec_pipe(*OUT, "'$::TSKDIR/srch_strings' -a -t d $img > '$fname'"); } elsif ((($ftype eq "raw") || ($ftype eq "swap")) && ($Caseman::vol2end{$vol} != 0)) { Exec::exec_pipe(*OUT, "'$::TSKDIR/dls' -e -f $ftype -i $imgtype $img " . $Caseman::vol2start{$vol} . "-" . $Caseman::vol2end{$vol} . " | '$::TSKDIR/srch_strings' -a -t d > '$fname'"); } else { Exec::exec_pipe(*OUT, "'$::TSKDIR/dls' -e -f $ftype -o $offset -i $imgtype $img | '$::TSKDIR/srch_strings' -a -t d > '$fname'" ); } alarm(0); $SIG{ALRM} = 'DEFAULT'; print $_ while ($_ = Exec::read_pipe_line(*OUT)); close(OUT); print "
\n" if ($hit_cnt != 0); # Verify that it worked unless (open(STR, "$fname")) { print( "Error opening $fname
\n" . "Either an error occurred while generating the file or " . "no ASCII strings exist
"); goto str_uni; } # append to config my $strvol = Caseman::add_vol_host_config("strings", "$vol $fname_rel"); print "Host configuration file updated
"; $Caseman::vol2ftype{$strvol} = "strings"; $Caseman::mod2vol{$strvol} = $vol; $Caseman::vol2str{$vol} = $strvol; $Caseman::vol2cat{$strvol} = "mod"; $Caseman::vol2itype{$strvol} = "raw"; $Caseman::vol2par{$strvol} = $vol; $Caseman::vol2path{$strvol} = "$::host_dir" . "$fname_rel"; $Caseman::vol2start{$strvol} = 0; $Caseman::vol2end{$strvol} = 0; $Caseman::vol2sname{$strvol} = $fname_rel; # Calculate MD5 if ((exists $Args::args{'md5'}) && ($Args::args{'md5'} == 1)) { print "Calculating MD5 Value

\n"; my $m = Hash::int_create_wrap($strvol); print "MD5 Value: $m

\n"; } } str_uni: if ($uni == 1) { my $fname_rel = "$::DATADIR/${base_name}-$ftype.uni"; my $fname = "$::host_dir" . "$fname_rel"; if (-e "$fname") { my $i = 1; $i++ while (-e "$::host_dir" . "$::DATADIR/" . "${base_name}-$ftype-$i.uni"); $fname_rel = "$::DATADIR/${base_name}-$ftype-$i.uni"; $fname = "$::host_dir" . "$fname_rel"; } print "


\n" if ($ascii == 1); print "Extracting Unicode strings from $Caseman::vol2sname{$vol}
\n"; Print::log_host_inv( "$Caseman::vol2sname{$vol}: Saving Unicode strings to $fname_rel"); local *OUT; my $hit_cnt = 0; $SIG{ALRM} = sub { if (($hit_cnt++ % 5) == 0) { print "+"; } else { print "-"; } alarm(5); }; alarm(5); if ($ftype eq "dls") { Exec::exec_pipe(*OUT, "'$::TSKDIR/srch_strings' -a -t d -e l $img > '$fname'"); } elsif ((($ftype eq "raw") || ($ftype eq "swap")) && ($Caseman::vol2end{$vol} != 0)) { Exec::exec_pipe(*OUT, "'$::TSKDIR/dls' -e -f $ftype -i $imgtype $img " . $Caseman::vol2start{$vol} . "-" . $Caseman::vol2end{$vol} . " | '$::TSKDIR/srch_strings' -a -t d -e l > '$fname'"); } else { Exec::exec_pipe(*OUT, "'$::TSKDIR/dls' -e -f $ftype -o $offset -i $imgtype $img | '$::TSKDIR/srch_strings' -a -t d -e l > '$fname'" ); } alarm(0); $SIG{ALRM} = 'DEFAULT'; print $_ while ($_ = Exec::read_pipe_line(*OUT)); close(OUT); print "
\n" if ($hit_cnt != 0); # Verify that it worked unless (open(STR, "$fname")) { print "Error opening $fname
\n" . "Either an error occurred while generating the file or " . "no Unicode strings exist"; goto str_egress; } # append to config my $strvol = Caseman::add_vol_host_config("unistrings", "$vol $fname_rel"); print "Host configuration file updated
"; $Caseman::vol2ftype{$strvol} = "strings"; $Caseman::mod2vol{$strvol} = $vol; $Caseman::vol2uni{$vol} = $strvol; $Caseman::vol2cat{$strvol} = "mod"; $Caseman::vol2itype{$strvol} = "raw"; $Caseman::vol2par{$strvol} = $vol; $Caseman::vol2path{$strvol} = "$::host_dir" . "$fname_rel"; $Caseman::vol2start{$strvol} = 0; $Caseman::vol2end{$strvol} = 0; $Caseman::vol2sname{$strvol} = $fname_rel; # Calculate MD5 if ((exists $Args::args{'md5'}) && ($Args::args{'md5'} == 1)) { print "Calculating MD5 Value

\n"; $m = Hash::int_create_wrap($strvol); print "MD5 Value: $m

\n"; } } str_egress: my $dest_vol = $vol; # We need to return with a real image to VOL_DETAILS so check the mod $dest_vol = $Caseman::mod2vol{$vol} if (exists $Caseman::mod2vol{$vol}); print "
Image Details

\n"; print "Keyword Search\n"; Print::print_html_footer(); return 0; } sub vol_makedls { Print::print_html_header("Extracting Unallocated Space"); my $vol = Args::get_vol('vol'); my $ftype = $Caseman::vol2ftype{$vol}; my $img = $Caseman::vol2path{$vol}; my $offset = $Caseman::vol2start{$vol}; my $imgtype = $Caseman::vol2itype{$vol}; my $base_name = $Caseman::vol2sname{$vol}; $base_name = $1 if ($base_name =~ /^(.*?)\.dd$/); my $fname_rel = "$::DATADIR/${base_name}-$ftype.unalloc"; my $fname = "$::host_dir" . "$fname_rel"; if (-e "$::host_dir" . "$fname_rel") { my $i = 1; $i++ while (-e "$::host_dir" . "$::DATADIR/" . "${base_name}-$ftype-$i.unalloc"); $fname_rel = "$::DATADIR/${base_name}-$ftype-$i.unalloc"; $fname = "$::host_dir" . "$fname_rel"; } Print::log_host_inv( "$Args::args{'vol'}: Saving unallocated data to $fname_rel"); print "Extracting unallocated data from $Caseman::vol2sname{$vol}
\n"; local *OUT; my $hit_cnt = 0; $SIG{ALRM} = sub { if (($hit_cnt++ % 5) == 0) { print "+"; } else { print "-"; } alarm(5); }; alarm(5); Exec::exec_pipe(*OUT, "'$::TSKDIR/dls' -f $ftype -o $offset -i $imgtype $img > '$fname'"); alarm(0); $SIG{ALRM} = 'DEFAULT'; print "$_" while ($_ = Exec::read_pipe_line(*OUT)); close(OUT); print "
\n" if ($hit_cnt != 0); # append to config my $dlsvol = Caseman::add_vol_host_config("dls", "$vol $fname_rel"); print "Host configuration file updated
"; $Caseman::vol2ftype{$dlsvol} = "dls"; $Caseman::mod2vol{$dlsvol} = $vol; $Caseman::vol2dls{$vol} = $dlsrvol; $Caseman::vol2cat{$dlsvol} = "mod"; $Caseman::vol2itype{$dlsvol} = "raw"; $Caseman::vol2par{$dlsvol} = $vol; $Caseman::vol2path{$dlsvol} = "$::host_dir" . "$fname_rel"; $Caseman::vol2start{$dlsvol} = 0; $Caseman::vol2end{$dlsvol} = 0; $Caseman::vol2sname{$dlsvol} = $fname_rel; # Calculate MD5 if ((exists $Args::args{'md5'}) && ($Args::args{'md5'} == 1)) { print "Calculating MD5 Value
\n"; my $m = Hash::int_create_wrap($dlsvol); print "MD5 Value: $m

\n"; } print "Image Details

\n"; print "Keyword Search\n"; Print::print_html_footer(); return 0; } 1;