Обратный инжиниринг сценария Perl на основе дампа ядра - PullRequest
11 голосов
/ 26 августа 2010

Сервер друга (да, правда. Не мой) был взломан, и мы обнаружили бинарный файл perl, выполняющий некоторый бот-код. Мы не смогли найти сам скрипт (вероятно, eval'ed как полученный по сети), но нам удалось создать дамп ядра процесса perl.

Запуск строк в ядре дал нам некоторые подсказки (имена хостов, имена пользователей / пароли), но не исходный код скрипта.

Мы хотели бы знать, на что способен скрипт, поэтому мы бы хотели перепроектировать Perl-код, который работал внутри этого интерпретатора Perl.

Поиск, ближайший к де-компилятору Perl, который я нашел, - это модуль B :: Deparse, который, кажется, идеально подходит для преобразования байт-кода деревьев разбора обратно в читаемый код.

Теперь, как мне заставить B :: Deparse работать с дампом ядра? Или, в качестве альтернативы, как я могу перезапустить программу из ядра, загрузить B :: Deparse и выполнить ее?

Любые идеи приветствуются.

Ответы [ 3 ]

7 голосов
/ 31 августа 2010

ysth попросил меня прокомментировать ваш вопрос в IRC. Я сделал целый куча вещей "разборка" скомпилированных Perl и прочее (только посмотрите мой CPAN страница [http://search.cpan.org/~jjore]).

Perl компилирует ваш источник в дерево OP* структур, которые иногда есть указатели C на SV*, которые являются значениями perl. Ваше ядро В дампе теперь есть куча этих OP* и SV* спрятанных.

Наилучшим из возможных миров был бы такой Perl-модуль, как B :: Deparse сделайте работу по пониманию информации за вас. Это работает с использованием легкого интерфейса для памяти Perl в B::OP и B::SV классы (задокументированы в B , perlguts и perlhack ). Это нереально для вас, потому что объект B::* просто указатель в память с аксессорами для декодирования структуры для нашего использовать. Рассмотрим:

require Data::Dumper;
require Scalar::Util;
require B;

my $value = 'this is a string';

my $sv      = B::svref_2object( \ $value );
my $address = Scalar::Util::refaddr( \ $value );

local $Data::Dumper::Sortkeys = 1;
local $Data::Dumper::Purity   = 1;
print Data::Dumper::Dumper(
  {
    address => $address,
    value   => \ $value,
    sv      => $sv,
    sv_attr => {
      CUR           => $sv->CUR,
      LEN           => $sv->LEN,
      PV            => $sv->PV,
      PVBM          => $sv->PVBM,
      PVX           => $sv->PVX,
      as_string     => $sv->as_string,
      FLAGS         => $sv->FLAGS,
      MAGICAL       => $sv->MAGICAL,
      POK           => $sv->POK,
      REFCNT        => $sv->REFCNT,
      ROK           => $sv->ROK,
      SvTYPE        => $sv->SvTYPE,
      object_2svref => $sv->object_2svref,
    },
  }
);

, который при запуске показал, что объект B::PV (это ISA B::SV) на самом деле просто интерфейс для представления в памяти скомпилированная строка this is a string.

$VAR1 = {
          'address' => 438506984,
          'sv' => bless( do{\(my $o = 438506984)}, 'B::PV' ),
          'sv_attr' => {
                         'CUR' => 16,
                         'FLAGS' => 279557,
                         'LEN' => 24,
                         'MAGICAL' => 0,
                         'POK' => 1024,
                         'PV' => 'this is a string',
                         'PVBM' => 'this is a string',
                         'PVX' => 'this is a string',
                         'REFCNT' => 2,
                         'ROK' => 0,
                         'SvTYPE' => 5,
                         'as_string' => 'this is a string',
                         'object_2svref' => \'this is a string'
                       },
          'value' => do{my $o}
        };
$VAR1->{'value'} = $VAR1->{'sv_attr'}{'object_2svref'};

Это, однако, означает, что любой B::*, использующий код, должен действительно работать на живую память. Тай МакКвин думал, что он помнил отладчик C, который может полностью восстановить рабочий процесс с учетом дампа ядра. Мой gdb не может. gdb может позволить вам сбросить содержимое вашего OP* и SV* структуры. Скорее всего, вы просто прочитали бы интерпретировать структуру вашей программы. Вы могли бы, если хотите, использовать gdb чтобы выгрузить структуры, затем синтетически создать B::* объекты который вел себя в интерфейсе, как будто они были обычными и используют B::Deparse на этом. В корне наш депарсер и прочий отладочный дамп инструменты в основном объектно-ориентированные, так что вы можете просто "обмануть" их создание кучи поддельных B::* классов и предметов.

Вы можете найти чтение метода coderef2text класса B :: Deparse поучительно. Он принимает ссылку на функцию, приводит ее к B::CV объект и использует его для ввода в метод deparse_sub:

require B;
require B::Deparse;
sub your_function { ... }

my $cv = B::svref_2object( \ &your_function );
my $deparser = B::Deparse->new;
print $deparser->deparse_sub( $cv );

Более подробные сведения о OP* и связанных с ними идеях см. В обновленном PerlGuts Illustrated и Optree Guts .

2 голосов
/ 26 августа 2010

Я сомневаюсь, что есть инструмент, который делает это из коробки, так что ...

  1. Найдите исходный код версии Perl, с которой вы работали. Это должно помочь вам понять структуру памяти интерпретатора Perl. Это также поможет вам выяснить, есть ли здесь способ использовать ярлык (например, если байт-коду предшествует легко найти заголовок в памяти или что-то в этом роде).

  2. Загрузите двоичный файл + дамп ядра в отладчике, вероятно, gdb

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

Как только вы получите байт-код, B :: Deparse сможет получить что-то более читаемое.

0 голосов
/ 26 августа 2010

Ну, undump превратит этот дамп ядра обратно в двоичный исполняемый файл (если вы можете найти рабочую версию). Затем вы сможете загрузить его в perl и -MO=Deparse it.

...