1#
2# Keyword search mode
3#
4# Brian Carrier [carrier@sleuthkit.org]
5# Copyright (c) 2001-2005 by Brian Carrier.  All rights reserved
6#
7# This file is part of the Autopsy Forensic Browser (Autopsy)
8#
9# Autopsy is free software; you can redistribute it and/or modify
10# it under the terms of the GNU General Public License as published by
11# the Free Software Foundation; either version 2 of the License, or
12# (at your option) any later version.
13#
14# Autopsy is distributed in the hope that it will be useful,
15# but WITHOUT ANY WARRANTY; without even the implied warranty of
16# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17# GNU General Public License for more details.
18#
19# You should have received a copy of the GNU General Public License
20# along with Autopsy; if not, write to the Free Software
21# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
22#
23# THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR IMPLIED
24# WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF
25# MERCHANTABILITY AND FITNESS FOR ANY PARTICULAR PURPOSE.
26# IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
27# INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
28# (INCLUDING, BUT NOT LIMITED TO, LOSS OF USE, DATA, OR PROFITS OR
29# BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
30# WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
31# OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
32# ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
33
34package Kwsrch;
35
36require 'search.pl';
37
38$Kwsrch::ENTER      = 1;
39$Kwsrch::RESULTS_FR = 2;
40$Kwsrch::RUN        = 3;
41$Kwsrch::LOAD       = 4;
42$Kwsrch::BLANK      = 5;
43
44my $IMG_DETAILS = 0x80;
45
46sub main {
47
48    # By default, show the main frame
49    $Args::args{'view'} = $Args::enc_args{'view'} = $Kwsrch::ENTER
50      unless (exists $Args::args{'view'});
51
52    Args::check_view();
53    my $view = Args::get_view();
54
55    if ($view == $Kwsrch::BLANK) {
56        blank();
57        return 0;
58    }
59
60    # Check Basic Args
61    Args::check_vol('vol');
62
63    # These windows don't need the meta data address
64    if ($view == $Kwsrch::ENTER) {
65        return enter();
66    }
67    elsif ($view == $Kwsrch::RESULTS_FR) {
68        return results_fr();
69    }
70    elsif ($view == $Kwsrch::RUN) {
71        return run();
72    }
73    elsif ($view == $Kwsrch::LOAD) {
74        return load();
75    }
76    else {
77        Print::print_check_err("Invalid Keyword Search View");
78    }
79}
80
81my $CASE_INSENS = 1;
82my $CASE_SENS   = 0;
83
84my $REG_EXP = 1;
85my $STRING  = 0;
86
87# Form to enter search data
88sub enter {
89    my $vol = Args::get_vol('vol');
90
91    Print::print_html_header("Search on $Caseman::vol2sname{$vol}");
92    my $ftype = $Caseman::vol2ftype{$vol};
93
94    if ($ftype eq 'blkls') {
95        print "<center><h3>Keyword Search of Unallocated Space</h3>\n";
96    }
97    elsif ($ftype eq 'swap') {
98        print "<center><h3>Keyword Search of swap partition</h3>\n";
99    }
100    elsif ($ftype eq 'raw') {
101        print "<center><h3>Keyword Search of raw data</h3>\n";
102    }
103    elsif ($Caseman::vol2cat{$vol} eq "disk") {
104        print "<center><h3>Keyword Search of disk</h3>\n";
105    }
106    else {
107        print
108"<center><h3>Keyword Search of Allocated and Unallocated Space</h3>\n";
109    }
110
111    # @@@ Fix this - caused by writing all results to a file
112    if ($::LIVE == 1) {
113        Print::print_err(
114"Keyword searching is temporarily not available during live analysis mode"
115        );
116    }
117
118    print "<form action=\"$::PROGNAME\" method=\"get\">\n"
119      . "Enter the keyword string or expression to search for:<br> <input type=\"text\" name=\"str\"><br><br>\n"
120      . Args::make_hidden()
121      . "<input type=\"hidden\" name=\"mod\" value=\"$::MOD_KWSRCH\">\n"
122      . "<input type=\"hidden\" name=\"view\" value=\"$Kwsrch::RESULTS_FR\">\n"
123      . "<input type=\"hidden\" name=\"vol\" value=\"$vol\">\n";
124
125    print "<table width=400><tr>\n"
126      . "<td width=200 align=center><input type=\"checkbox\" name=\"ascii\" value=\"1\" CHECKED>"
127      . "ASCII \n</td>"
128      . "<td width=200 align=center><input type=\"checkbox\" name=\"unicode\" value=\"1\" CHECKED>"
129      . "Unicode</td></tr>\n"
130      . "<tr><td align=center><input type=\"checkbox\" name=\"srch_case\" value=\"$CASE_INSENS\">"
131      . "Case Insensitive</td>\n"
132      . "<td align=center><input type=\"checkbox\" name=\"regexp\" value=\"$REG_EXP\">\n"
133      . "<tt>grep</tt> Regular Expression</td></tr></table>\n"
134      . "<input type=\"image\" src=\"pict/but_search.jpg\" "
135      . "alt=\"Search\" border=\"0\">\n</form>\n";
136
137    if ($::LIVE == 0) {
138        print "<table width=600><tr>\n";
139
140        # If we are a non-blkls image and one exists - make a button to load it
141        if (($ftype ne 'blkls') && (exists $Caseman::vol2blkls{$vol})) {
142            print "<td align=center width=200>"
143              . "<form action=\"$::PROGNAME\" method=\"get\" target=\"_top\">\n"
144              . "<input type=\"hidden\" name=\"mod\" value=\"$::MOD_FRAME\">\n"
145              . "<input type=\"hidden\" name=\"submod\" value=\"$::MOD_KWSRCH\">\n"
146              . "<input type=\"hidden\" name=\"vol\" value=\"$Caseman::vol2blkls{$vol}\">\n"
147              . Args::make_hidden()
148              . "<input type=\"image\" src=\"pict/srch_b_lun.jpg\" "
149              . "alt=\"Load Unallocated Image\" border=\"0\">\n<br></form></td>\n";
150        }
151
152        # If we are a blkls and the original exists - make a button to load it
153        elsif (($ftype eq 'blkls')
154            && (exists $Caseman::mod2vol{$vol}))
155        {
156            print "<td align=center width=200>"
157              . "<form action=\"$::PROGNAME\" method=\"get\" target=\"_top\">\n"
158              . "<input type=\"hidden\" name=\"mod\" value=\"$::MOD_FRAME\">\n"
159              . "<input type=\"hidden\" name=\"submod\" value=\"$::MOD_KWSRCH\">\n"
160              . "<input type=\"hidden\" name=\"vol\" value=\"$Caseman::mod2vol{$vol}\">\n"
161              . Args::make_hidden()
162              . "<input type=\"image\" src=\"pict/srch_b_lorig.jpg\" "
163              . "alt=\"Load Original Image\" border=\"0\">\n<br></form></td>\n";
164        }
165
166        # Strings Button
167        if (   (!(exists $Caseman::vol2str{$vol}))
168            || (!(exists $Caseman::vol2uni{$vol})))
169        {
170
171            my $dest_vol = $vol;
172            $dest_vol = $Caseman::mod2vol{$vol}
173              if exists($Caseman::mod2vol{$vol});
174
175            print "<td align=center width=200>"
176              . "<form action=\"$::PROGNAME\" method=\"get\" target=\"_top\">\n"
177              . "<input type=\"hidden\" name=\"mod\" value=\"$::MOD_CASEMAN\">\n"
178              . "<input type=\"hidden\" name=\"view\" value=\"$Caseman::VOL_DETAILS\">\n"
179              . "<input type=\"hidden\" name=\"vol\" value=\"$dest_vol\">\n"
180              . Args::make_hidden()
181              . "<input type=\"image\" src=\"pict/srch_b_str.jpg\" "
182              . "alt=\"Extract Strings\" border=\"0\">\n<br></form></td>\n";
183        }
184
185        # Unallocated Space Button
186        if (   ($Fs::is_fs{$ftype})
187            && (!(exists $Caseman::vol2blkls{$vol})))
188        {
189            print "<td align=center width=200>"
190              . "<form action=\"$::PROGNAME\" method=\"get\" target=\"_top\">\n"
191              . "<input type=\"hidden\" name=\"mod\" value=\"$::MOD_CASEMAN\">\n"
192              . "<input type=\"hidden\" name=\"view\" value=\"$Caseman::VOL_DETAILS\">\n"
193              . "<input type=\"hidden\" name=\"vol\" value=\"$vol\">\n"
194              . Args::make_hidden()
195              . "<input type=\"image\" src=\"pict/srch_b_un.jpg\" "
196              . "alt=\"Extract Unallocated Space\" border=\"0\">\n<br></form></td>\n";
197        }
198
199        print "</tr></table>\n";
200    }
201
202    print "<a href=\"help/grep.html\" target=\"_blank\">"
203      . "Regular Expression Cheat Sheet</a>\n<br><br>\n";
204
205    print "<p><font color=\"red\">NOTE:</font> The keyword search runs "
206      . "<tt>grep</tt> on the image.<br>\n"
207      . "A list of what will and "
208      . "what will not be found is available "
209      . "<a href=\"help/grep_lim.html\" target=\"_blank\">here</a>.<br>\n";
210
211    # Section for previous searches
212    if ($::LIVE == 0) {
213        my $srch_name = get_srch_fname(0);
214        if (-e $srch_name) {
215            print "<hr><h3>Previous Searches</h3>\n" . "<table width=600>\n";
216            my $row_idx = 0;
217
218            # Cycle through the files
219            for (my $srch_idx = 0;; $srch_idx++) {
220
221                $srch_name = get_srch_fname($srch_idx);
222
223                last unless (-e $srch_name);
224
225                # Open the file to get the string and count
226                unless (open(SRCH, "$srch_name")) {
227                    print "Error opening search file: $srch_name\n";
228                    return 1;
229                }
230                my $prev_str = "";
231                my $prev_cnt = 0;
232
233                while (<SRCH>) {
234                    unless (/^(\d+)\|(.*?)?\|(.*)$/) {
235                        print
236                          "Error pasing header of search file: $srch_name\n";
237                        return 1;
238                    }
239                    $prev_cnt = $1;
240                    $prev_str = $3;
241                    if (length($prev_str) > 32) {
242                        $prev_str = substr($prev_str, 0, 32);
243                        $prev_str .= "...";
244                    }
245
246                    last;
247                }
248                close(SRCH);
249
250                print "<tr>\n" if ($row_idx == 0);
251
252                print "  <td align=center width=150>\n"
253                  . "<form action=\"$::PROGNAME\" method=\"get\">\n"
254                  . "<input type=\"hidden\" name=\"mod\" value=\"$::MOD_KWSRCH\">\n"
255                  . "<input type=\"hidden\" name=\"view\" value=\"$Kwsrch::RESULTS_FR\">\n"
256                  . "<input type=\"hidden\" name=\"vol\" value=\"$vol\">\n"
257                  . "<input type=\"hidden\" name=\"srchidx\" value=\"$srch_idx\">\n"
258                  . Args::make_hidden();
259
260                print "<input type=\"SUBMIT\" value=\"".Print::html_encode($prev_str)." ($prev_cnt)\">"
261                  . "<br></form>\n";
262
263                if ($row_idx == 3) {
264                    print "</tr>\n";
265                    $row_idx = 0;
266                }
267                else {
268                    $row_idx++;
269                }
270            }
271            print "</table>\n";
272        }
273    }
274
275    # Predefined expressions from search.pl
276    print "<hr><h3>Predefined Searches</h3>\n";
277    print "<table width=600>\n";
278    my $row_idx = 0;
279    my $r;
280    foreach $r (keys %Kwsrch::auto_srch) {
281
282        $Kwsrch::auto_srch_reg{$r} = 0
283          unless (defined $Kwsrch::auto_srch_reg{$r});
284        $Kwsrch::auto_srch_csense{$r} = 1
285          unless (defined $Kwsrch::auto_srch_csense{$r});
286
287        print "<tr>\n" if ($row_idx == 0);
288
289        # @@@ Make a unicode option in predefined
290
291        print "  <td align=center width=150>\n"
292          . "<form action=\"$::PROGNAME\" method=\"get\">\n"
293          . "<input type=\"hidden\" name=\"mod\" value=\"$::MOD_KWSRCH\">\n"
294          . "<input type=\"hidden\" name=\"view\" value=\"$Kwsrch::RESULTS_FR\">\n"
295          . "<input type=\"hidden\" name=\"vol\" value=\"$vol\">\n"
296          . "<input type=\"hidden\" name=\"str\" value=\"$Kwsrch::auto_srch{$r}\">\n"
297          . "<input type=\"hidden\" name=\"ascii\" value=\"1\">\n"
298          . Args::make_hidden();
299
300        if ($Kwsrch::auto_srch_reg{$r} == 1) {
301            print
302              "<input type=\"hidden\" name=\"regexp\" value=\"$REG_EXP\">\n";
303        }
304        if ($Kwsrch::auto_srch_csense{$r} == 0) {
305            print
306"<input type=\"hidden\" name=\"srch_case\" value=\"$CASE_INSENS\">\n";
307        }
308        print "<input type=\"SUBMIT\" value=\"$r\"><br></form>\n" . "  </td>\n";
309
310        if ($row_idx == 3) {
311            print "</tr>\n";
312            $row_idx = 0;
313        }
314        else {
315            $row_idx++;
316        }
317    }
318    print "</table>\n";
319
320    Print::print_html_footer();
321    return 0;
322}
323
324# MAIN WITH RESULTS
325# Page that makes frame with the results on left and data units on right
326sub results_fr {
327    my $vol = Args::get_vol('vol');
328
329    # A string was given for a new search
330    if (exists $Args::args{'str'}) {
331        Args::check_str();
332
333        Print::print_html_header_frameset(
334            "Search on $Caseman::vol2sname{$vol} for $Args::args{'str'}");
335
336        print "<frameset cols=\"35%,65%\">\n";
337
338        my $srch_case = "";
339        $srch_case = "&srch_case=$Args::args{'srch_case'}"
340          if (exists $Args::args{'srch_case'});
341
342        my $regexp = "";
343        $regexp = "&regexp=$Args::args{'regexp'}"
344          if (exists $Args::args{'regexp'});
345
346        my $ascii = "";
347        $ascii = "&ascii=$Args::args{'ascii'}"
348          if (exists $Args::args{'ascii'});
349
350        my $unicode = "";
351        $unicode = "&unicode=$Args::args{'unicode'}"
352          if (exists $Args::args{'unicode'});
353
354        # Block List
355        print "<frame src=\"$::PROGNAME?"
356          . "mod=$::MOD_KWSRCH&view=$Kwsrch::RUN&"
357          . "$Args::baseargs$srch_case$regexp&str=$Args::enc_args{'str'}$ascii$unicode\">\n";
358    }
359    elsif (exists $Args::args{'srchidx'}) {
360        Args::check_srchidx();
361
362        Print::print_html_header_frameset(
363"Search on $Caseman::vol2sname{$vol} for Index $Args::args{'srchidx'}"
364        );
365
366        print "<frameset cols=\"35%,65%\">\n";
367
368        # Block List
369        print "<frame src=\"$::PROGNAME?"
370          . "mod=$::MOD_KWSRCH&view=$Kwsrch::LOAD&"
371          . "$Args::baseargs&srchidx=$Args::enc_args{'srchidx'}\">\n";
372    }
373
374    # Block Contents
375    print "<frame src=\"$::PROGNAME?mod=$::MOD_KWSRCH&view=$Kwsrch::BLANK&"
376      . "$Args::baseargs\" name=\"content\">\n"
377      . "</frameset>\n";
378
379    Print::print_html_footer_frameset();
380    return 0;
381}
382
383# Find an empty file to save the keyword searches to
384sub find_srch_file {
385    my $vol = Args::get_vol('vol');
386
387    my $out_name = "$::host_dir" . "$::DATADIR/$Caseman::vol2sname{$vol}";
388    my $i;
389    for ($i = 0; -e "${out_name}-${i}.srch"; $i++) { }
390
391    return "${out_name}-${i}.srch";
392}
393
394# Pass the index
395# return the full path of the file returned
396sub get_srch_fname {
397    my $idx = shift;
398    my $vol = Args::get_vol('vol');
399    return "$::host_dir"
400      . "$::DATADIR"
401      . "/$Caseman::vol2sname{$vol}-${idx}.srch";
402}
403
404sub load {
405    Args::check_srchidx();
406
407    Print::print_html_header("");
408
409    if ($::LIVE == 1) {
410        print "Searches cannot be loaded during live analysis<br>\n";
411        return 1;
412    }
413
414    my $srch_name = get_srch_fname($Args::args{'srchidx'});
415
416    print "<b><a href=\"$::PROGNAME?mod=$::MOD_KWSRCH&view=$Kwsrch::ENTER&"
417      . "$Args::baseargs\" "
418      . "target=\"_parent\">New Search</a></b>\n<p>";
419
420    print_srch_results($srch_name);
421
422    Print::print_html_footer();
423    return 0;
424}
425
426# performs actual search, saves hits to file, and calls method to print
427sub run {
428    Args::check_str();
429
430    Print::print_html_header("");
431
432    my $vol     = Args::get_vol('vol');
433    my $ftype   = $Caseman::vol2ftype{$vol};
434    my $img     = $Caseman::vol2path{$vol};
435    my $offset  = $Caseman::vol2start{$vol};
436    my $imgtype = $Caseman::vol2itype{$vol};
437
438    my $orig_str = Args::get_str();
439    my $grep_str = $orig_str;       # we will escape some values in the grep ver
440
441    # Check for a search string
442    if ($orig_str eq "") {
443        print "You must enter a string value to search<br>\n";
444        print "<b><a href=\"$::PROGNAME?mod=$::MOD_KWSRCH&view=$Kwsrch::ENTER&"
445          . "$Args::baseargs\" target=\"_parent\">New Search</a></b>\n<p>";
446        return 1;
447    }
448
449    my $log = "";                   # Log entry string
450
451    my $ascii   = 0;
452    my $unicode = 0;
453
454    if ((exists $Args::args{'ascii'}) && ($Args::args{'ascii'} == 1)) {
455        $ascii = 1;
456        $log .= "ASCII, ";
457    }
458    if ((exists $Args::args{'unicode'}) && ($Args::args{'unicode'} == 1)) {
459        $unicode = 1;
460        $log .= "Unicode, ";
461    }
462
463    if (($ascii == 0) && ($unicode == 0)) {
464        print "You must choose either ASCII or Unicode to search<br>\n";
465        print "<b><a href=\"$::PROGNAME?mod=$::MOD_KWSRCH&view=$Kwsrch::ENTER&"
466          . "$Args::baseargs\" target=\"_parent\">New Search</a></b>\n<p>";
467        return 1;
468    }
469
470    my $grep_flag = "";    # Flags to pass to grep
471
472    # Check if search is case insensitive
473    my $case = 0;
474    if (   (exists $Args::args{'srch_case'})
475        && ($Args::args{'srch_case'} == $CASE_INSENS))
476    {
477        $grep_flag = "-i";
478        $case      = 1;
479        $log .= "Case Insensitive ";
480    }
481
482    # Check if search is a regular expression
483    my $regexp = 0;
484    if ((exists $Args::args{'regexp'}) && ($Args::args{'regexp'} == $REG_EXP)) {
485        $grep_flag .= " -E";
486        $regexp = 1;
487        $log .= "Regular Expression ";
488    }
489
490    # if not a reg-exp, we need to escape some special values that
491    # 'grep' will misinterpret
492    else {
493        $grep_str =~ s/\\/\\\\/g;    # \
494        $grep_str =~ s/\./\\\./g;    # .
495        $grep_str =~ s/\[/\\\[/g;    # [
496        $grep_str =~ s/\^/\\\^/g;    # ^
497        $grep_str =~ s/\$/\\\$/g;    # $
498        $grep_str =~ s/\*/\\\*/g;    # *
499             # We need to add ' to end begin and end of the search as well
500        if ($grep_str =~ /\'/) {
501            $grep_str =~ s/\'/\\\'/g;    # '
502            $grep_str = "'$grep_str'";
503        }
504        $grep_str =~ s/^\-/\\\-/;        # starting with - (mistakes for an arg)
505    }
506
507    Print::log_host_inv(
508        "$Caseman::vol2sname{$vol}: ${log}search for $grep_str");
509
510    # Get the addressable unit of image
511    my $bs = Args::get_unitsize();
512
513    # $norm_str is normalized to find the "hit" in the output
514    my $norm_str = $orig_str;
515
516    # make this lowercase if we are doing case insens
517    $norm_str =~ tr/[A-Z]/[a-z]/ if ($case == 1);
518
519    my $norm_str_len = length($norm_str);
520
521    # array to pass to printing method
522    my @results;
523
524    my $name_uni = "";
525    my $name_asc = "";
526
527    # round 0 is for ASCII and 1 is for Unicode
528    for (my $i = 0; $i < 2; $i++) {
529
530        next if (($i == 0) && ($ascii == 0));
531        next if (($i == 1) && ($unicode == 0));
532
533        @results = ();
534
535        local *OUT;
536
537        my $hit_cnt = 0;
538        $SIG{ALRM} = sub {
539            if (($hit_cnt++ % 5) == 0) {
540                print "+";
541            }
542            else {
543                print "-";
544            }
545            alarm(5);
546        };
547
548        alarm(5);
549
550        if ($i == 0) {
551            print "<b>Searching for ASCII</b>: ";
552        }
553        else {
554            print "<b>Searching for Unicode</b>: ";
555        }
556
557        # if the string is less than 4 chars, then it will not be in the
558        # strings file so it will be searched for the slow way
559        if (length($orig_str) < 4) {
560            my $ltmp = length($orig_str);
561
562            if ($i == 0) {
563                if (   (($ftype eq "raw") || ($ftype eq "swap"))
564                    && ($Caseman::vol2end{$vol} != 0))
565                {
566                    Exec::exec_pipe(*OUT,
567                            "'$::TSKDIR/blkls' -e -f $ftype -i $imgtype $img "
568                          . $Caseman::vol2start{$vol} . "-"
569                          . $Caseman::vol2end{$vol}
570                          . " | '$::TSKDIR/srch_strings' -a -t d -$ltmp | '$::GREP_EXE' $grep_flag '$grep_str'"
571                    );
572                }
573                else {
574                    Exec::exec_pipe(*OUT,
575"'$::TSKDIR/blkls' -e -f $ftype -o $offset -i $imgtype $img | '$::TSKDIR/srch_strings' -a -t d -$ltmp | '$::GREP_EXE' $grep_flag '$grep_str'"
576                    );
577                }
578            }
579
580            else {
581                if (   (($ftype eq "raw") || ($ftype eq "swap"))
582                    && ($Caseman::vol2end{$vol} != 0))
583                {
584                    Exec::exec_pipe(*OUT,
585                            "'$::TSKDIR/blkls' -e -f $ftype -i $imgtype $img "
586                          . $Caseman::vol2start{$vol} . "-"
587                          . $Caseman::vol2end{$vol}
588                          . " | '$::TSKDIR/srch_strings' -a -t d -e l -$ltmp | '$::GREP_EXE' $grep_flag '$grep_str'"
589                    );
590                }
591                else {
592                    Exec::exec_pipe(*OUT,
593"'$::TSKDIR/blkls' -e -f $ftype -o $offset -i $imgtype $img | '$::TSKDIR/srch_strings' -a -t d -e l -$ltmp | '$::GREP_EXE' $grep_flag '$grep_str'"
594                    );
595                }
596            }
597        }
598
599        # Use the strings file if it exists
600        elsif (($i == 0) && (defined $Caseman::vol2str{$vol})) {
601            my $str_vol = $Caseman::vol2path{$Caseman::vol2str{$vol}};
602            Exec::exec_pipe(*OUT,
603                "'$::GREP_EXE' $grep_flag '$grep_str' $str_vol");
604        }
605        elsif (($i == 1) && (defined $Caseman::vol2uni{$vol})) {
606            my $str_vol = $Caseman::vol2path{$Caseman::vol2uni{$vol}};
607            Exec::exec_pipe(*OUT,
608                "'$::GREP_EXE' $grep_flag '$grep_str' $str_vol");
609        }
610
611        # Run strings on the image first and then grep that
612        else {
613            if ($i == 0) {
614                if (   (($ftype eq "raw") || ($ftype eq "swap"))
615                    && ($Caseman::vol2end{$vol} != 0))
616                {
617                    Exec::exec_pipe(*OUT,
618                            "'$::TSKDIR/blkls' -e -f $ftype -i $imgtype $img "
619                          . $Caseman::vol2start{$vol} . "-"
620                          . $Caseman::vol2end{$vol}
621                          . " | '$::TSKDIR/srch_strings' -a -t d | '$::GREP_EXE' $grep_flag '$grep_str'"
622                    );
623                }
624                else {
625                    Exec::exec_pipe(*OUT,
626"'$::TSKDIR/blkls' -e -f $ftype -o $offset -i $imgtype $img | '$::TSKDIR/srch_strings' -a -t d | '$::GREP_EXE' $grep_flag '$grep_str'"
627                    );
628                }
629            }
630            else {
631                if (   (($ftype eq "raw") || ($ftype eq "swap"))
632                    && ($Caseman::vol2end{$vol} != 0))
633                {
634                    Exec::exec_pipe(*OUT,
635                            "'$::TSKDIR/blkls' -e -f $ftype -i $imgtype $img "
636                          . $Caseman::vol2start{$vol} . "-"
637                          . $Caseman::vol2end{$vol}
638                          . " | '$::TSKDIR/srch_strings' -a -t d -e l | '$::GREP_EXE' $grep_flag '$grep_str'"
639                    );
640                }
641                else {
642                    Exec::exec_pipe(*OUT,
643"'$::TSKDIR/blkls' -e -f $ftype -o $offset -i $imgtype $img | '$::TSKDIR/srch_strings' -a -t d -e l | '$::GREP_EXE' $grep_flag '$grep_str'"
644                    );
645                }
646            }
647        }
648
649        alarm(0);
650        $SIG{ALRM} = 'DEFAULT';
651
652        # Cycle through the results and put them in an array
653        while ($_ = Exec::read_pipe_line(*OUT)) {
654
655            # Parse out the byte offset and hit string
656            if (/^\s*(\d+)\s+(.+)$/) {
657                my $off          = $1;
658                my $hit_str_orig = $2;
659                my $idx          = 0;
660
661                # Make a copy that we can modify & play with
662                my $hit_str = $hit_str_orig;
663                $hit_str =~ tr/[A-Z]/[a-z]/ if ($case == 1);
664
665                # How long was the string that we hit?
666                my $hit_str_len = length($hit_str);
667
668                # I'm not sure how to find a grep re in the hit yet, so
669                # for now we do not get the exact offset
670                if ($regexp) {
671                    my $b = int($off / $bs);
672                    my $o = int($off % $bs);
673
674                    # $hit =~ s/\n//g;
675                    push @results, "${b}|${o}|";
676                    next;
677                }
678
679                # There could be more than one keyword in the string
680                # so cycle through all of them
681                my $psize = scalar(@results);
682                while (($idx = index($hit_str, $norm_str, $idx)) > -1) {
683
684                    # The summary of the hit starts 5 chars before it
685                    my $sum_min = $idx - 5;
686                    $sum_min = 0 if ($sum_min < 0);
687
688                    # Goto 5 after, if there is still space
689                    my $sum_max = $idx + $norm_str_len + 5;
690                    $sum_max = $hit_str_len if ($sum_max > $hit_str_len);
691
692                    my $sum_hit =
693                      substr($hit_str_orig, $sum_min, $sum_max - $sum_min);
694
695                    # remove new lines
696                    $sum_hit =~ s/\n/ /g;
697
698                    # The actual offset for Unicode is 2 bytes per char
699                    my $tmpidx = $idx;
700                    $tmpidx *= 2
701                      if ($i == 1);
702
703                    my $b = int(($off + $tmpidx) / $bs);
704                    my $o = int(($off + $tmpidx) % $bs);
705
706                    push @results, "${b}|${o}|$sum_hit";
707
708                    # advance index to find next hit
709                    $idx++;
710                }
711
712                # If we did not find a term, then just print what
713                # was found-this occurs bc index does not find it
714                # sometimes.
715                if ($psize == scalar(@results)) {
716
717                    my $b = int($off / $bs);
718                    my $o = int($off % $bs);
719
720                    # $hit =~ s/\n//g;
721                    push @results, "${b}|${o}|";
722                    next;
723                }
724            }
725
726            # A negative offset is common on FreeBSD with large images
727            elsif (/^\s*(\-\d+):?\s*(.+)$/) {
728                print "ERROR: Negative byte offset ($1) Your version of "
729                  . "strings likely does not support large files: $2<br>\n";
730            }
731            else {
732                print "Error parsing grep result: $_<br>\n";
733            }
734        }
735        close(OUT);
736
737        print " <b>Done</b><br>";
738        my $cnt = scalar(@results);
739
740        my $srch_name = "";
741        if ($::LIVE == 0) {
742            print "<b>Saving</b>: ";
743
744            # Find a file to save the results to
745            $srch_name = find_srch_file();
746            unless (open(IDX, ">$srch_name")) {
747                print "Error opening $srch_name\n";
748                return (1);
749            }
750
751            # Print the header
752            if ($i == 0) {
753                print IDX "$cnt|${grep_flag}|${orig_str}|ascii\n";
754                $name_asc = $srch_name;
755            }
756            else {
757                print IDX "$cnt|${grep_flag}|${orig_str}|unicode\n";
758                $name_uni = $srch_name;
759            }
760
761            for (my $a = 0; $a < $cnt; $a++) {
762                print IDX "$results[$a]\n";
763            }
764            close(IDX);
765            print " <b>Done</b><br>\n";
766        }
767        if ($i == 0) {
768            print "$cnt hits";
769            print "- <a href=\"#ascii\">link to results</a>" if ($cnt > 0);
770            print "<br>\n";
771        }
772        else {
773            print "$cnt hits";
774            print "- <a href=\"#unicode\">link to results</a>" if ($cnt > 0);
775            print "<br>\n";
776        }
777        print "<hr>\n";
778    }
779
780    print "<b><a href=\"$::PROGNAME?mod=$::MOD_KWSRCH&view=$Kwsrch::ENTER&"
781      . "$Args::baseargs\" "
782      . "target=\"_parent\">New Search</a></b>\n<p>";
783
784    if ($::LIVE == 0) {
785        if ($ascii == 1) {
786            print_srch_results($name_asc);
787        }
788
789        if ($unicode == 1) {
790            print_srch_results($name_uni);
791        }
792    }
793
794    Print::print_html_footer();
795    return 0;
796}
797
798# Args are search string, grep flags, and array of hits
799sub print_srch_results {
800
801    if (scalar(@_) != 1) {
802        print "Missing Args for print_srch_results()\n";
803        return 1;
804    }
805
806    my $srch_name = shift;
807    my $vol       = Args::get_vol('vol');
808    my $ftype     = $Caseman::vol2ftype{$vol};
809
810    my $addr_str = $Fs::addr_unit{$ftype};
811
812    unless (open(SRCH, "$srch_name")) {
813        print "Error opening search file: $srch_name\n";
814        return 1;
815    }
816
817    my @results;
818    my $grep_str  = "";
819    my $grep_flag = "";
820    my $cnt       = 0;
821    my $type      = 0;    # ASCII iis 0 and Unicode is 1
822
823    my $prev = -1;
824
825    while (<SRCH>) {
826
827        # The first line is a header
828        if ($. == 1) {
829            if (/^(\d+)\|(.*?)?\|(.*)$/) {
830                $cnt       = $1;
831                $grep_flag = $2;
832                $grep_str  = $3;
833                $type      = 0;
834            }
835            else {
836                print "Error pasing header of search file: $srch_name\n";
837                close(SRCH);
838                return 1;
839            }
840
841            if ($grep_str =~ /^(.*?)\|unicode$/) {
842                $grep_str = $1;
843                $type     = 1;
844            }
845            elsif ($grep_str =~ /^(.*?)\|ascii$/) {
846                $grep_str = $1;
847            }
848
849            my $grep_str_html = Print::html_encode($grep_str);
850            print "<hr>\n";
851            if ($type == 0) {
852                print "<a name=\"ascii\">\n";
853            }
854            else {
855                print "<a name=\"unicode\">\n";
856            }
857
858            if ($cnt == 0) {
859                print "<b><tt>$grep_str_html</tt> was not found</b><br>\n";
860            }
861            elsif ($cnt == 1) {
862                print
863"<b>1 occurrence of <tt>$grep_str_html</tt> was found</b><br>\n";
864            }
865            else {
866                print
867"<b>$cnt occurrences of <tt>$grep_str_html</tt> were found</b><br>\n";
868            }
869
870            print "Search Options:<br>\n";
871            if ($type == 0) {
872                print "&nbsp;&nbsp;ASCII<br>\n";
873            }
874            else {
875                print "&nbsp;&nbsp;Unicode<br>\n";
876            }
877
878            if ($grep_flag =~ /\-i/) {
879                print "&nbsp;&nbsp;Case Insensitive<br>\n";
880            }
881            else {
882                print "&nbsp;&nbsp;Case Sensitive<br>\n";
883            }
884            if ($grep_flag =~ /\-E/) {
885                print "&nbsp;&nbsp;Regular Expression<br>\n";
886            }
887
888            print "<hr>\n";
889
890            if ($cnt > 1000) {
891                print "There were more than <U>1000</U> hits.<br>\n";
892                print "Please revise the search to a managable amount.\n";
893                print
894                  "<p>The $cnt hits can be found in: <tt>$srch_name</tt><br>\n";
895                close(SRCH);
896                return 0;
897            }
898
899            next;
900        }
901
902        unless (/^(\d+)\|(\d+)\|(.*)?$/) {
903            print "Error parsing results: $_\n";
904            close(SRCH);
905            return 1;
906        }
907
908        my $blk = $1;
909        my $off = $2;
910        my $str = $3;
911
912        if ($blk != $prev) {
913            my $url =
914"$::PROGNAME?mod=$::MOD_DATA&view=$Data::CONT_MENU_FR&$Args::baseargs&block=$blk";
915
916            print "<br>\n$addr_str $blk (<a href=\"$url&sort=$Data::SORT_HEX\" "
917              . "target=content>Hex</a> - "
918              . "<a href=\"$url&sort=$Data::SORT_ASC\" target=content>"
919              . "Ascii</a>";
920
921            print
922" - <a href=\"$::PROGNAME?$Args::baseargs&mod=$::MOD_DATA&view=$Data::CONT_MENU_FR&"
923              . "mnt=$Args::enc_args{'mnt'}&vol=$Caseman::mod2vol{$vol}&"
924              . "btype=$Data::ADDR_BLKLS&block=$blk\" target=content>Original</a>"
925              if ( ($ftype eq 'blkls')
926                && (exists $Caseman::mod2vol{$vol}));
927
928            print ")<br>";
929            $prev = $blk;
930        }
931
932        my $occ = $. - 1;
933        if ($str ne "") {
934            $str = Print::html_encode($str);
935            print "$occ: $off (<tt>$str</tt>)<br>\n";
936        }
937        else {
938            print "$occ: $off<br>\n";
939        }
940    }
941
942    close(SRCH);
943    return 0;
944}
945
946# Blank Page
947sub blank {
948    Print::print_html_header("");
949    print "<!-- This Page Intentionally Left Blank -->\n";
950    Print::print_html_footer();
951    return 0;
952}
953
9541;
955