Нужна помощь в ускорении моей программы Perl - PullRequest
1 голос
/ 14 апреля 2011

Хорошо, я работаю над поиском эксплойтов, чтобы работать с корнями изменений. Моя проблема заключается в том, что при поиске большого количества строк в большом количестве файлов I.E. htdocs, это занимает больше времени, чем мне бы хотелось, я уверен, что некоторые продвинутые авторы Perl могут помочь мне немного ускорить процесс. Вот часть моей программы, которую я хотел бы улучшить.

sub sStringFind {
  if (-B $_ ) {
  }else{
   open FH, '<', $_ ;
   my @lines = <FH>;
   foreach $fstring(@lines) {
    if ($fstring =~ /sendraw|portscan|stunshell|Bruteforce|fakeproc|sub google|sub alltheweb|sub uol|sub bing|sub altavista|sub ask|sub yahoo|virgillio|filestealth|IO::Socket::INET|\/usr\/sbin\/bjork|\/usr\/local\/apache\/bin\/httpd|\/sbin\/syslogd|\/sbin\/klogd|\/usr\/sbin\/acpid|\/usr\/sbin\/cron|\/usr\/sbin\/httpd|irc\.byroe\.net|milw0rm|tcpflooder/) {
     push(@huhFiles, "$_");
   }
  }
 }
}
#End suspicious string find.
find(\&sStringFind, "$cDir/www/htdocs");
for(@huhFiles) {
 print "$_\n";
}

Возможно, немного хеширования? Не уверен, что не очень хорошо с Perl Atm. Любая помощь приветствуется, спасибо, ребята.

Ответы [ 9 ]

1 голос
/ 14 апреля 2011

Итак, под «хэшированием» я полагаю, вы имеете в виду выполнение контрольной суммы на уровне файла или строки, чтобы вам не приходилось проверять ее снова?

Основная проблема, контрольная сумма или нет, вы все еще должны прочитать каждую строку каждого файла либо для сканирования, либо для хеширования. Таким образом, это принципиально не меняет ваш алгоритм, а просто изменяет константы.

Если у вас много повторяющихся файлов, проверка контрольных сумм на уровне файлов может сэкономить вам много времени. Если вы этого не сделаете, это потратит много времени.

cost = (checksum_cost * num_files) + (regex_cost * lines_per(unique_files))

Проверка контрольных сумм на уровне строк - это сложность между стоимостью регулярного выражения и стоимостью контрольной суммы. Если дубликатов не так много, вы проигрываете. Если ваша контрольная сумма слишком дорогая, вы проигрываете. Вы можете написать это так:

cost = (checksum_cost * total_lines) + (regex_cost * (total_lines - duplicate_lines))

Я бы начал с выяснения, какой процент файлов и строк являются дубликатами. Это так просто, как:

$line_frequency{ checksum($line) }++

, а затем посмотреть на процент, где частота >= 2. Этот процент представляет собой увеличение производительности максимум , которое вы увидите при проверке контрольной суммы. Если это 50%, вы увидите увеличение только на 50%. Это предполагает, что стоимость контрольной суммы равна 0, а это не так, так что вы увидите меньше. Если контрольная сумма стоит вдвое меньше, чем регулярное выражение, вы увидите только 25%.

Вот почему я рекомендую grep. Он будет перебирать файлы и строки быстрее, чем Perl может атаковать фундаментальную проблему: вы должны прочитать каждый файл и каждую строку.

То, что вы можете сделать, это не просматривать каждый файл каждый раз. Простая вещь, которую нужно сделать, это запомнить последний раз, когда вы сканировали, и посмотреть на время изменения каждого файла. Это не изменилось, и ваше регулярное выражение не изменилось, не проверяйте это снова. Более надежная версия будет хранить контрольные суммы каждого файла, в случае, если файл был изменен во время изменения. Если все ваши файлы меняются не очень часто, это приведет к большой победе.

# Write a timestamp file at the top of the directory you're scanning
sub set_last_scan_time {
    my $dir = shift;

    my $file = "$dir/.last_scan";
    open my $fh, ">", $file or die "Can't open $file for writing: $!";
    print $fh time;

    return
}

# Read the timestamp file
sub get_last_scan_time {
    my $dir = shift;

    my $file = "$dir/.last_scan";

    return 0 unless -e $file;

    open my $fh, "<", $file or die "Can't open $file: $!";
    my $time = <$fh>;
    chomp $time;

    return $time;
}

use File::Slurp 'read_file';
use File::stat;

my $last_scan_time = get_last_scan_time($dir);

# Place the regex outside the routine just to make things tidier.
my $regex = qr{this|that|blah|...};
my @huhFiles;
sub scan_file {
    # Only scan text files
    return unless -T $_;

    # Don't bother scanning if it hasn't changed
    return if stat($_)->mtime < $last_scan_time;

    push(@huhFiles, $_) if read_file($_) =~ $regex;
}

# Set the scan time to before you start so if anything is edited
# while you're scanning you'll catch it next time.
set_last_scan_time($dir);

find(\&scan_file, $dir);
1 голос
/ 14 апреля 2011

У меня есть несколько вещей, которые я бы сделал, чтобы улучшить производительность.

Во-первых, вы должны предварительно скомпилировать свое регулярное выражение.В общем, я делаю это так: my @ items = qw (foo bar baz);# Обычно я извлекаю это из конфигурационного файла my $ regex = '^'.присоединиться к "|", @items.'$';#В качестве примера.Я тоже много снимаю.$ regex = qr ($ regex) i;

Во-вторых, как уже упоминалось, вы должны читать файлы по одной строке за раз.Из того, что я видел, больше всего работает оперативная память, а не процессор.

В-третьих, если у вас не хватает одного процессора и у вас много файлов для работы, разделите приложение на вызывающую и приемную при помощиfork (), так что вы можете обрабатывать несколько файлов одновременно, используя более одного процессора.Вы можете записать в общий файл, а когда закончите, проанализируйте это.

Наконец, следите за использованием вашей памяти - много раз, добавление файла позволяет вам сохранить то, что в памяти, намного меньше.

Я должен обработать большие дампы данных, используя 5.8 и 5.10, и это работает для меня.

1 голос
/ 14 апреля 2011

В отличие от других ответов, я бы предложил выполнять регулярное выражение один раз для каждого файла целиком, а не один раз для каждой строки.

use File::Slurp 'read_file';
        ...
    if (-B $_ ) {
    }else{
        if ( read_file("$_") =~ /sendraw|portscan|stunshell|Bruteforce|fakeproc|sub google|sub alltheweb|sub uol|sub bing|sub altavista|sub ask|sub yahoo|virgillio|filestealth|IO::Socket::INET|\/usr\/sbin\/bjork|\/usr\/local\/apache\/bin\/httpd|\/sbin\/syslogd|\/sbin\/klogd|\/usr\/sbin\/acpid|\/usr\/sbin\/cron|\/usr\/sbin\/httpd|irc\.byroe\.net|milw0rm|tcpflooder/) {
            push(@huhFiles, $_);
        }
    }

Убедитесь, что вы используете хотя бы perl5.10.1.

1 голос
/ 14 апреля 2011

Вы не делаете ничего, что вызовет явную проблему с производительностью, поэтому вам придется смотреть за пределы Perl.Используйте grep.Это должно быть намного быстрее.

open my $grep, "-|", "grep", "-l", "-P", "-I", "-r", $regex, $dir;
my @files = <$grep>;
chomp @files;

-l вернет только имена файлов, которые соответствуют.-P будет использовать Perl-совместимые регулярные выражения.-r сделает это рекурсивным через файлы.-I будет игнорировать двоичные файлы.Убедитесь, что у grep вашей системы есть все эти опции.

0 голосов
/ 14 апреля 2011

Не читайте все строки одновременно. Читайте по одной строке за раз, а затем, когда вы найдете совпадение в файле, выйдите из цикла и прекратите чтение из этого файла.

Кроме того, не интерполируйте, когда вам не нужно. Вместо

push(@huhFiles, "$_");

сделать

push(@huhFiles, $_);

Это не будет проблемой скорости, но это лучший стиль кодирования.

0 голосов
/ 14 апреля 2011

Просто работаю из вашего кода:

#!/usr/bin/perl

# it looks awesome to use strict
use strict;
# using warnings is beyond awesome
use warnings;
use File::Find;

my $keywords = qr[sendraw|portscan|stunshell|Bruteforce|fakeproc|sub google|sub alltheweb|sub uol|sub bing|sub altavista|sub ask|sub yahoo|virgillio|filestealth|IO::Socket::INET|\/usr\/sbin\/bjork|\/usr\/local\/apache\/bin\/httpd|\/sbin\/syslogd|\/sbin\/klogd|\/usr\/sbin\/acpid|\/usr\/sbin\/cron|\/usr\/sbin\/httpd|irc\.byroe\.net|milw0rm|tcpflooder];

my @huhfiles;

find sub {
        return unless -f;
        my $file = $File::Find::name;

        open my $fh, '<', $file or die "$!\n";
        local $/ = undef;
        my $contents = <$fh>;
        # modern Perl handles this but it's a good practice
        # to close the file handle after usage
        close $fh;

        if ($contents =~ $keywords) {
                push @huhfiles, $file;
        }
}, "$cDir/www/htdocs";

if (@huhfiles) {
        print join "\n", @huhfiles;
} else {
        print "No vulnerable files found\n";
}
0 голосов
/ 14 апреля 2011

Просто, чтобы добавить что-то еще.

Если вы собираете, вы используете регулярное выражение из списка поисковых терминов.Затем Regexp :: Assemble :: Compressed можно использовать для сворачивания поисковых терминов в более короткое регулярное выражение:

use Regexp::Assemble::Compressed;

my @terms = qw(sendraw portscan stunshell Bruteforce fakeproc sub google sub alltheweb sub uol sub bing sub altavista sub ask sub yahoo virgillio filestealth IO::Socket::INET /usr/sbin/bjork /usr/local/apache/bin/httpd /sbin/syslogd /sbin/klogd /usr/sbin/acpid /usr/sbin/cron /usr/sbin/httpd irc.byroe.net milw0rm tcpflooder);

my $ra = Regexp::Assemble::Compressed->new;
$ra->add("\Q${_}\E") for @terms;
my $re = $ra->re;
print $re."\n";

print "matched" if 'blah blah yahoo' =~ m{$re};

. Это приводит к:

(?-xism:(?:\/(?:usr\/(?:sbin\/(?:(?:acpi|http)d|bjork|cron)|local\/apache\/bin\/httpd)|sbin\/(?:sys|k)logd)|a(?:l(?:ltheweb|tavista)|sk)|f(?:ilestealth|akeproc)|s(?:tunshell|endraw|ub)|(?:Bruteforc|googl)e|(?:virgilli|yaho)o|IO::Socket::INET|irc\.byroe\.net|tcpflooder|portscan|milw0rm|bing|uol))
matched

Thisможет быть полезен для очень длинных списков поисковых терминов, особенно для Perl pre 5.10.

0 голосов
/ 14 апреля 2011

Как написано, ваш скрипт читает все содержимое каждого файла в @lines, а затем сканирует каждую строку.Это предполагает два улучшения: чтение строки за раз и остановка, как только строка соответствует.

Некоторые дополнительные улучшения: if (-B $_) {} else { ... } странный - если вы хотите обрабатывать только текстовые файлы, используйте тест -T.Вы должны всегда проверять возвращаемое значение open ().И в вашем push() есть бесполезное использование кавычек.Взятые вместе:

sub sStringFind {
    if (-T $_) {
        # Always - yes, ALWAYS check for failure on open()
        open(my $fh, '<', $_) or die "Could not open $_: $!";

        while (my $fstring = <$fh>) {
            if ($fstring =~ /sendraw|portscan|stunshell|Bruteforce|fakeproc|sub google|sub alltheweb|sub uol|sub bing|sub altavista|sub ask|sub yahoo|virgillio|filestealth|IO::Socket::INET|\/usr\/sbin\/bjork|\/usr\/local\/apache\/bin\/httpd \/sbin\/syslogd|\/sbin\/klogd|\/usr\/sbin\/acpid|\/usr\/sbin\/cron|\/usr\/sbin\/httpd|irc\.byro \.net|milw0rm|tcpflooder/) {
                push(@huhFiles, $_);
                last; # No need to keep checking once this file's been flagged
            }
        }
    }
}
0 голосов
/ 14 апреля 2011

Я не уверен, поможет ли это, но когда вы открываете <FH>, вы читаете весь файл в массив perl (@lines) одновременно. Вы можете повысить производительность, открыв файл и читая его построчно, а не загружая весь файл в память перед его обработкой. Однако, если ваши файлы маленькие, ваш текущий метод может быть быстрее ...

См. Эту страницу для примера: http://www.perlfect.com/articles/perlfile.shtml

Это может выглядеть примерно так (обратите внимание на скалярную $line переменную, а не массив):

open FH, '<' $_;

while ($line = <FH>)
{
    # do something with line
}

close FH;
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...