3 Perl -- интерпретируемый язык предназначенный для работы с текстовой информацией.
4 Во втором исследовании мне захотелось выяснить, можно ли использовать natch для быстрого нахождения точек входа для исследования незнакомого программного проекта.
5 С внутренностями Perl'а я знаком крайне поверхностно, и стало интересно можно ли при помощи natch быстро найти что-то новое, например выяснить, какой вызов отвечает за первоначальный разбор синтаксических выражений, когда мы вызываем команду `eval` передавая ей строку содержащую фрагмент программы.
6 Надо каким-то образом пометить содержимое этой строки и посмотреть как ее содержимое будет распространяться по графу вызовов.
10 * Собрать perl с отладочной информацией
11 * На простом примере убедиться что отслеживание помеченных данных работает:
12 * Пометить данные в файле;
13 * В Perl-скрипте считать содержимое помеченного файла в переменную;
14 * В том же скрипте вывести значение переменной на стандартный вывод;
15 * Пронаблюдать движение данных через внутренние структуры и вызовы Perl.
16 * Аналогичным способом отследить попадание помеченных данных в оператор языка Perl -- `eval`:
17 * В некий файл записать несколько perl-операторов выполняющих какое-либо осмысленное действие;
18 * Пометить данные в этом файле;
19 * В Perl-скрипте считать данные из помеченного файла в переменную;
20 * Выполнить операцию `eval` для содержимого вышеупомянутой переменной;
21 * Пронаблюдать стек вызовов по которому будут передаваться помеченные данные и найти вызов отвечающий за работу оператора `eval`.
23 ### Сборка и установка
25 Для кастомной сборки интерпретатора Perl воспользуемся утилитой `perlbrew` специально предназначенной для организации одновременной работы с несколькими версиями интерпретатора Perl, включая возможность автоматической сборки нужной версии из исходников.
27 Все инструкции по сборке приведены на примере дистрибутива Debian 12.
29 #### Установка зависимостей
31 Список зависимостей необходимых для сборки интерпретатора Perl естественным образом присутствует в мета-информации пакета исходников `perl` входящего в Debain. Для установки необходимых зависимостей воспользуемся штатным механизмом установки зависимостей необходимых для сборки пакета исходников:
34 $ sudo apt-get build-dep perl
37 #### Подготовка к сборке
39 Установим пакет `perlbrew` из состава дистрибутива Debian
42 $ sudo apt-get install perlbrew
45 и проведем инициализацию его окружения
50 perlbrew root (~/perl5/perlbrew) is initialized.
59 Получим список доступных для сборки версий Perl
72 Как мы видим на момент проведения эксперимента самой свежей версией была `perl-5.40.1`.
73 Будем использовать именно ее в своей работе.
75 Узнав номер желаемой версии даем команду на выполнение сборки
78 $ perlbrew install -DEBUGGING=both -v perl-5.40.1 --as=5.40.1-dbg
81 Здесь: опцией `-v perl-5.40.1` мы указываем какую версию Perl мы желаем собрать, опция `-DEBUGGING=both` будет передана программе конфигурации сборочной системе Perl и являет собой инструкцию провести сборку с отладочной информацией, которая будет нам необходима при проведении taint-анализа.
82 Опцией `--as=5.40.1-dbg` мы задаем кастомное имя сборки, добавив суффикс `-dbg` к имени по умолчанию, чтобы было визуально отличить сборку с отладочной информацией от обычной сборки.
84 Процесс сборки замет какое-то не нулевое время.
86 Бинарные файлы получившиеся в результате сборки будут установлены в директорию `~/perl5/perlbrew/perls/perl-5.40.1-dbg/bin`.
87 (Это знание нам пригодиться в дальнейшем при проведении taint-анализа
91 После того как сбора успешно завершится, имя сборки появится в списке доступных для использования сборок Perl:
98 Проверим, что мы можем воспользоваться новой сборкой. Для начала убедимся, что по умолчанию нам доступен системный Perl:
103 This is perl 5, version 36, subversion 0 (v5.36.0) built for x86_64-linux-gnu-thread-multi
107 Далее используя утилиту `perlbrew` переключимся на нашу кастомную сборку Perl
110 $ perlbrew use 5.40.1-dbg
112 A sub-shell is launched with 5.40.1-dbg as the activated perl. Run 'exit' to finish it.
115 И убедимся, что теперь по умолчанию используется Perl другой версии
120 This is perl 5, version 40, subversion 1 (v5.40.1) built for x86_64-linux
124 ### Отслеживание помеченных данных проходящих через скаляр
126 #### Подготовка эксперимента
128 В домашней директории создадим директорию `perl` и в ней файл `in1` с содержимым `Hello, World!`
132 $ echo Hello, World! >~/perl/in1
135 В той же директории создадим файл `test_scalar.pl` с содержимым:
142 print "Perl is $]!\n";
150 Сделать это можно например так:
153 $ editor ~/perl/test_scalar.pl
154 # Вводим или вставляем из буфера программу программу
156 $ chmod +x $ editor ~/perl/test_scalar.pl
159 Помимо содержимого файла `in1` приведенный выше скрипт выводит версию интерпретатора Perl на котором он исполняется, что позволит в процессе проведения эксперимента убедится, что используется нужная сборка интерпретатора Perl.
161 Проверяем работоспособность созданного скрипта:
165 $ perlbrew use 5.40.1-dbg
167 $ perl test_scalar.pl
170 Скрипт должен вывести следующие:
177 Обращаю внимание на необходимость явным образом запускать интепретатор и передавать ему имя файла со скриптом в качестве параметра, поскольку Ша-Банг (#!) по умолчанию указываемый в скрипте указывает на системный перл, perlbrew не в силах этого изменить, он может только добавить в пути поиска из переменной PATH путь к нужной сборке perl. Таким образом при явном вызове интерпретатора будет использоваться кастомная сборка Perl, a при запуске скрипта без указания интерпретатора -- системная.
179 #### Создание проекта natch
181 При создании проекта natch, уже на host-машине следуем штатной инструкции, в качестве источника помеченных данных указываем файл `in1` из домашней директории на виртуалке.
182 В моем случае `/home/nataraj/perl/in1`. В качестве директории содержащий дополнительные модули с отладочной информацией указать место куда были установлены бинарные файлы собранные `perlbrew` (cм. выше).
183 В моем случае это `/home/nataraj/perl5/perlbrew/perls/perl-5.40.1-dbg/bin`.
187 - Запускаем штатным образом запись трассы используя команду `natch record`;
188 - В появившемся окне виртуальной машины логинимся в виртуалку;
189 - Переходим в директорию с подготовленными для исследования файлами: `cd perl`;
190 - Используя `perlbrew` переключаем интерпретатор Perl на собранный нами: `perlbrew use 5.40.1-dbg`;
191 - В консоли Natch делаем первый снапшот (см. штатный tutorial);
192 - В окне виртуальной машины запускаем скрипт подготовленный для исследования `perl test_scalar.pl`;
193 - В консоли Natch делаем второй снапшот;
194 - Завершаем работу Natch.
196 #### Генерация отчета
198 Далее следуя штатной инструкции при помощи команды `natch replay` снимаем трассу между первым и вторым снепшотом и загружаем ее в snatch.
200 #### Анализ результатов
204 
206 Если заглянуть в раздел "Ресурсы" можно увидеть, что в юзер-спейсе помеченные данные проходили только через бинарную программу perl и библиотеку libc. Данные были прочитаны из файла `/home/nataraj/perl/in1`
208 
210 И записаны в устройство виртуального терминала `/dev/tty1`
212 
214 что вполне отвечает нашим ожиданиям.
218 
220 Если посмотреть на дерево вызовов, анатомия интерпретатора Perl становится в общем и целом понятной
222 `perl_run` и `Perl_runops_debug` очень похожи на вызовы оборачивающие все исполнение скрипта
224 `Perl_do_readline` вероятнее всего реализация операции чтения из файла `$s = <F>;`. В вызове `Perl_sv_gets` префикс `sv_` по конвенции обозначает работу со скалярным значением, это я знаю...
226 `Perl_pp_schop` -- реализация вызова `chomp $s;`
228 `Perl_pp_multiconcat` вероятнее всего занимается подстановкой значений в строковой литерал, являющейся аргументом оператора `print`
230 `Perl_pp_print` по всей видимости является реализацией оператора `print`
234 В принципе Natch не является профильным инструментом для исследования архитектуры сложных проектов, однако может быть использован и для этой цели.
235 Результат получается красивый и наглядный, и при наличии навыка его получение не требует больших усилий.
238 ### Отслеживание помеченных данных передаваемых в оператор `eval`
240 #### Подготовка эксперимента
242 Для исследования перемещения помеченных данных через оператор `eval` запишем в файл `in2` небольшую perl-программу:
245 $ echo 'my $aaa = "Hello, World!";' >~/perl/in2
246 $ echo 'print $aaa;' >>~/perl/in2
249 И так же создадим perl-скрипт `test_eval.pl` который будет читать содержимое файла `in2` и выполнять его при помощи оператора `eval`:
256 print "Perl is $]!\n";
269 Сделать это можно например так:
272 $ editor ~/perl/test_eval.pl
273 # Вводим или вставляем из буфера программу программу
275 $ chmod +x $ editor ~/perl/test_eval.pl
278 Проверяем работоспособность созданного скрипта:
282 $ perlbrew use 5.40.1-dbg
287 Скрипт должен вывести следующие:
294 #### Проведение эксперимента
296 Проведение эксперимента для случая `eval` ни чем не отличается от предыдущего варианта эксперимента с загрузкой помеченных данных в скалярную переменную.
297 В данном случае необходимо будет пометить данные в файле `in2` вместо `in1` и при записи трассы выполнить скрипт `test_eval.pl` вместо `test_scalar.pl`
299 #### Анализ результатов
303 
305 Так же как и в предыдущем эксперименте мы видим что в юзер-спейсе помеченные данные проходят только через процесс perl и библиотеку libc
307 Читаются помеченные данные из входного файла `in2`
309 
311 И на устройство `/dev/tty1` из всех помеченных данных выводится только содержимое строкового литерала из программы из файла `in2`.
313 
315 Все более или менее ожидаемо.
319 
321 Дерево вызовов во втором эксперименте получается сильно более развесистое чем в первом.
323 В начале мы видим все тот же `Perl_do_readline` отвечающий за чтение данных из файла.
325 Далее мы видим пару `Perl_pp_entereval`, `Perl_pp_leaveeval` и `Perl_pp_print` между ними.
327 По всей видимости первые два обрамляют собой оный `eval` а вызов `print` является содержательной частью внутри `eval`'а исполняющейся.
329 Так же интересным вызовом внутри `Perl_pp_entereval` является `S_doeval_compile` в котором судя по названию происходит компиляция строки в исполняемый байт-код Perl, и судя по префиксам `yy` в его внутренних вызовах разбор проводится при помощи Yacc-парсера.
330 Префикс `yy` традиционно используется для Yacc-related кода.
332 Функция `Perl_pp_entereval` так же выглядит крайне перспективно в смысле фаззинга.
333 Вероятнее всего она сама по себе состояние интерпретатора не меняет.
334 Главное корректно освободить все структуры которые она создала в процессе компиляции.
338 Поставленная задача решена полностью, базовая анатомия работы оператора `eval` полностью установлена.
339 Далее при реальном исследовании работы интерпретатора мы бы перешли к исследованию исходного кода интерпретатора и постановке натурных экспериментов.
340 Часть касающаяся демонстрации возможностей Natch на этом этапе можно считать завершенной.