# # $Date: 2005/03/16 00:03:40 $ # # Keyword search mode # # 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 Kwsrch; require 'search.pl'; $Kwsrch::ENTER = 1; $Kwsrch::RESULTS_FR = 2; $Kwsrch::RUN = 3; $Kwsrch::LOAD = 4; $Kwsrch::BLANK = 5; my $IMG_DETAILS = 0x80; sub main { # By default, show the main frame $Args::args{'view'} = $Args::enc_args{'view'} = $Kwsrch::ENTER unless (exists $Args::args{'view'}); Args::check_view(); my $view = Args::get_view(); if ($view == $Kwsrch::BLANK) { blank(); return 0; } # Check Basic Args Args::check_vol('vol'); # These windows don't need the meta data address if ($view == $Kwsrch::ENTER) { return enter(); } elsif ($view == $Kwsrch::RESULTS_FR) { return results_fr(); } elsif ($view == $Kwsrch::RUN) { return run(); } elsif ($view == $Kwsrch::LOAD) { return load(); } else { Print::print_check_err("Invalid Keyword Search View"); } } my $CASE_INSENS = 1; my $CASE_SENS = 0; my $REG_EXP = 1; my $STRING = 0; # Form to enter search data sub enter { my $vol = Args::get_vol('vol'); Print::print_html_header("Search on $Caseman::vol2sname{$vol}"); my $ftype = $Caseman::vol2ftype{$vol}; if ($ftype eq 'dls') { print "
NOTE: The keyword search runs " . "grep on the image.\n" . "A list of what will and " . "what will not be found is available " . "here.\n"; # Section for previous searches if ($::LIVE == 0) { my $srch_name = get_srch_fname(0); if (-e $srch_name) { print "
"; print_srch_results($srch_name); Print::print_html_footer(); return 0; } # performs actual search, saves hits to file, and calls method to print sub run { Args::check_str(); Print::print_html_header(""); 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 $orig_str = Args::get_str(); my $grep_str = $orig_str; # we will escape some values in the grep ver # Check for a search string if ($orig_str eq "") { print "You must enter a string value to search\n"; print "New Search\n
"; return 1; } my $log = ""; # Log entry string my $ascii = 0; my $unicode = 0; if ((exists $Args::args{'ascii'}) && ($Args::args{'ascii'} == 1)) { $ascii = 1; $log .= "ASCII, "; } if ((exists $Args::args{'unicode'}) && ($Args::args{'unicode'} == 1)) { $unicode = 1; $log .= "Unicode, "; } if (($ascii == 0) && ($unicode == 0)) { print "You must choose either ASCII or Unicode to search\n"; print "New Search\n
"; return 1; } my $grep_flag = ""; # Flags to pass to grep # Check if search is case insensitive my $case = 0; if ( (exists $Args::args{'srch_case'}) && ($Args::args{'srch_case'} == $CASE_INSENS)) { $grep_flag = "-i"; $case = 1; $log .= "Case Insensitive "; } # Check if search is a regular expression my $regexp = 0; if ((exists $Args::args{'regexp'}) && ($Args::args{'regexp'} == $REG_EXP)) { $grep_flag .= " -E"; $regexp = 1; $log .= "Regular Expression "; } # if not a reg-exp, we need to escape some special values that # 'grep' will misinterpret else { $grep_str =~ s/\\/\\\\/g; # \ $grep_str =~ s/\./\\\./g; # . $grep_str =~ s/\[/\\\[/g; # [ $grep_str =~ s/\^/\\\^/g; # ^ $grep_str =~ s/\$/\\\$/g; # $ $grep_str =~ s/\*/\\\*/g; # * # We need to add ' to end begin and end of the search as well if ($grep_str =~ /\'/) { $grep_str =~ s/\'/\\\'/g; # ' $grep_str = "'$grep_str'"; } $grep_str =~ s/^\-/\\\-/; # starting with - (mistakes for an arg) } Print::log_host_inv( "$Caseman::vol2sname{$vol}: ${log}search for $grep_str"); # Get the addressable unit of image my $bs = Args::get_unitsize(); # $norm_str is normalized to find the "hit" in the output my $norm_str = $orig_str; # make this lowercase if we are doing case insens $norm_str =~ tr/[A-Z]/[a-z]/ if ($case == 1); my $norm_str_len = length($norm_str); # array to pass to printing method my @results; my $name_uni = ""; my $name_asc = ""; # round 0 is for ASCII and 1 is for Unicode for (my $i = 0; $i < 2; $i++) { next if (($i == 0) && ($ascii == 0)); next if (($i == 1) && ($unicode == 0)); @results = (); local *OUT; my $hit_cnt = 0; $SIG{ALRM} = sub { if (($hit_cnt++ % 5) == 0) { print "+"; } else { print "-"; } alarm(5); }; alarm(5); if ($i == 0) { print "Searching for ASCII: "; } else { print "Searching for Unicode: "; } # if the string is less than 4 chars, then it will not be in the # strings file so it will be searched for the slow way if (length($orig_str) < 4) { my $ltmp = length($orig_str); if ($i == 0) { if ( (($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 -$ltmp | '$::GREP_EXE' $grep_flag '$grep_str'" ); } else { Exec::exec_pipe(*OUT, "'$::TSKDIR/dls' -e -f $ftype -o $offset -i $imgtype $img | '$::TSKDIR/srch_strings' -a -t d -$ltmp | '$::GREP_EXE' $grep_flag '$grep_str'" ); } } else { if ( (($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 -$ltmp | '$::GREP_EXE' $grep_flag '$grep_str'" ); } else { Exec::exec_pipe(*OUT, "'$::TSKDIR/dls' -e -f $ftype -o $offset -i $imgtype $img | '$::TSKDIR/srch_strings' -a -t d -e l -$ltmp | '$::GREP_EXE' $grep_flag '$grep_str'" ); } } } # Use the strings file if it exists elsif (($i == 0) && (defined $Caseman::vol2str{$vol})) { my $str_vol = $Caseman::vol2path{$Caseman::vol2str{$vol}}; Exec::exec_pipe(*OUT, "'$::GREP_EXE' $grep_flag '$grep_str' $str_vol"); } elsif (($i == 1) && (defined $Caseman::vol2uni{$vol})) { my $str_vol = $Caseman::vol2path{$Caseman::vol2uni{$vol}}; Exec::exec_pipe(*OUT, "'$::GREP_EXE' $grep_flag '$grep_str' $str_vol"); } # Run strings on the image first and then grep that else { if ($i == 0) { if ( (($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 | '$::GREP_EXE' $grep_flag '$grep_str'" ); } else { Exec::exec_pipe(*OUT, "'$::TSKDIR/dls' -e -f $ftype -o $offset -i $imgtype $img | '$::TSKDIR/srch_strings' -a -t d | '$::GREP_EXE' $grep_flag '$grep_str'" ); } } else { if ( (($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 | '$::GREP_EXE' $grep_flag '$grep_str'" ); } else { Exec::exec_pipe(*OUT, "'$::TSKDIR/dls' -e -f $ftype -o $offset -i $imgtype $img | '$::TSKDIR/srch_strings' -a -t d -e l | '$::GREP_EXE' $grep_flag '$grep_str'" ); } } } alarm(0); $SIG{ALRM} = 'DEFAULT'; # Cycle through the results and put them in an array while ($_ = Exec::read_pipe_line(*OUT)) { # Parse out the byte offset and hit string if (/^\s*(\d+)\s+(.+)$/) { my $off = $1; my $hit_str_orig = $2; my $idx = 0; # Make a copy that we can modify & play with my $hit_str = $hit_str_orig; $hit_str =~ tr/[A-Z]/[a-z]/ if ($case == 1); # How long was the string that we hit? my $hit_str_len = length($hit_str); # I'm not sure how to find a grep re in the hit yet, so # for now we do not get the exact offset if ($regexp) { my $b = int($off / $bs); my $o = int($off % $bs); # $hit =~ s/\n//g; push @results, "${b}|${o}|"; next; } # There could be more than one keyword in the string # so cycle through all of them my $psize = scalar(@results); while (($idx = index($hit_str, $norm_str, $idx)) > -1) { # The summary of the hit starts 5 chars before it my $sum_min = $idx - 5; $sum_min = 0 if ($sum_min < 0); # Goto 5 after, if there is still space my $sum_max = $idx + $norm_str_len + 5; $sum_max = $hit_str_len if ($sum_max > $hit_str_len); my $sum_hit = substr($hit_str_orig, $sum_min, $sum_max - $sum_min); # remove new lines $sum_hit =~ s/\n/ /g; # The actual offset for Unicode is 2 bytes per char my $tmpidx = $idx; $tmpidx *= 2 if ($i == 1); my $b = int(($off + $tmpidx) / $bs); my $o = int(($off + $tmpidx) % $bs); push @results, "${b}|${o}|$sum_hit"; # advance index to find next hit $idx++; } # If we did not find a term, then just print what # was found-this occurs bc index does not find it # sometimes. if ($psize == scalar(@results)) { my $b = int($off / $bs); my $o = int($off % $bs); # $hit =~ s/\n//g; push @results, "${b}|${o}|"; next; } } # A negative offset is common on FreeBSD with large images elsif (/^\s*(\-\d+):?\s*(.+)$/) { print "ERROR: Negative byte offset ($1) Your version of " . "strings likely does not support large files: $2\n"; } else { print "Error parsing grep result: $_\n"; } } close(OUT); print " Done"; my $cnt = scalar(@results); my $srch_name = ""; if ($::LIVE == 0) { print "Saving: "; # Find a file to save the results to $srch_name = find_srch_file(); unless (open(IDX, ">$srch_name")) { print "Error opening $srch_name\n"; return (1); } # Print the header if ($i == 0) { print IDX "$cnt|${grep_flag}|${orig_str}|ascii\n"; $name_asc = $srch_name; } else { print IDX "$cnt|${grep_flag}|${orig_str}|unicode\n"; $name_uni = $srch_name; } for (my $a = 0; $a < $cnt; $a++) { print IDX "$results[$a]\n"; } close(IDX); print " Done\n"; } if ($i == 0) { print "$cnt hits"; print "- link to results" if ($cnt > 0); print "\n"; } else { print "$cnt hits"; print "- link to results" if ($cnt > 0); print "\n"; } print "
"; if ($::LIVE == 0) { if ($ascii == 1) { print_srch_results($name_asc); } if ($unicode == 1) { print_srch_results($name_uni); } } Print::print_html_footer(); return 0; } # Args are search string, grep flags, and array of hits sub print_srch_results { if (scalar(@_) != 1) { print "Missing Args for print_srch_results()\n"; return 1; } my $srch_name = shift; my $vol = Args::get_vol('vol'); my $ftype = $Caseman::vol2ftype{$vol}; my $addr_str = $Fs::addr_unit{$ftype}; unless (open(SRCH, "$srch_name")) { print "Error opening search file: $srch_name\n"; return 1; } my @results; my $grep_str = ""; my $grep_flag = ""; my $cnt = 0; my $type = 0; # ASCII iis 0 and Unicode is 1 my $prev = -1; while () { # The first line is a header if ($. == 1) { if (/^(\d+)\|(.*?)?\|(.*)$/) { $cnt = $1; $grep_flag = $2; $grep_str = $3; $type = 0; } else { print "Error pasing header of search file: $srch_name\n"; close(SRCH); return 1; } if ($grep_str =~ /^(.*?)\|unicode$/) { $grep_str = $1; $type = 1; } elsif ($grep_str =~ /^(.*?)\|ascii$/) { $grep_str = $1; } my $grep_str_html = Print::html_encode($grep_str); print "\n"; if ($type == 0) { print "\n"; } else { print "\n"; } if ($cnt == 0) { print "$grep_str_html was not found\n"; } elsif ($cnt == 1) { print "1 occurrence of $grep_str_html was found\n"; } else { print "$cnt occurrences of $grep_str_html were found\n"; } print "Search Options:\n"; if ($type == 0) { print " ASCII\n"; } else { print " Unicode\n"; } if ($grep_flag =~ /\-i/) { print " Case Insensitive\n"; } else { print " Case Sensitive\n"; } if ($grep_flag =~ /\-E/) { print " Regular Expression\n"; } print "\n"; if ($cnt > 1000) { print "There were more than 1000 hits.\n"; print "Please revise the search to a managable amount.\n"; print "The $cnt hits can be found in: $srch_name\n"; close(SRCH); return 0; } next; } unless (/^(\d+)\|(\d+)\|(.*)?$/) { print "Error parsing results: $_\n"; close(SRCH); return 1; } my $blk = $1; my $off = $2; my $str = $3; if ($blk != $prev) { my $url = "$::PROGNAME?mod=$::MOD_DATA&view=$Data::CONT_MENU_FR&$Args::baseargs&block=$blk"; print "\n$addr_str $blk (Hex - " . "" . "Ascii"; print " - Original" if ( ($ftype eq 'dls') && (exists $Caseman::mod2vol{$vol})); print ")"; $prev = $blk; } my $occ = $. - 1; if ($str ne "") { $str = Print::html_encode($str); print "$occ: $off ($str)\n"; } else { print "$occ: $off\n"; } } close(SRCH); return 0; } # Blank Page sub blank { Print::print_html_header(""); print "\n"; Print::print_html_footer(); return 0; } 1;
The $cnt hits can be found in: $srch_name\n"; close(SRCH); return 0; } next; } unless (/^(\d+)\|(\d+)\|(.*)?$/) { print "Error parsing results: $_\n"; close(SRCH); return 1; } my $blk = $1; my $off = $2; my $str = $3; if ($blk != $prev) { my $url = "$::PROGNAME?mod=$::MOD_DATA&view=$Data::CONT_MENU_FR&$Args::baseargs&block=$blk"; print "\n$addr_str $blk (Hex - " . "" . "Ascii"; print " - Original" if ( ($ftype eq 'dls') && (exists $Caseman::mod2vol{$vol})); print ")"; $prev = $blk; } my $occ = $. - 1; if ($str ne "") { $str = Print::html_encode($str); print "$occ: $off ($str)\n"; } else { print "$occ: $off\n"; } } close(SRCH); return 0; } # Blank Page sub blank { Print::print_html_header(""); print "\n"; Print::print_html_footer(); return 0; } 1;