]> Untitled Git - articles.git/blob
d7cef9427e6f47c3e572c722ec4b4492caa8f4f7
[articles.git] /
1 ## Perl
2
3 Perl -- интерпретируемый язык предназначенный для работы с текстовой информацией.
4 Во втором исследовании мне захотелось выяснить, можно ли использовать natch для быстрого нахождения точек входа для исследования незнакомого программного проекта.
5 С внутренностями Perl'а я знаком крайне поверхностно, и стало интересно можно ли при помощи natch быстро найти что-то новое, например выяснить, какой вызов отвечает за первоначальный разбор синтаксических выражений, когда мы вызываем команду `eval` передавая ей строку содержащую фрагмент программы. 
6 Надо каким-то образом пометить содержимое этой строки и посмотреть как ее содержимое будет распространяться по графу вызовов.
7
8 ### План эксперемента
9
10 * Собрать perl с отладочной информацией
11 * На простом примере убедиться что отслеживание помеченных данных работает:
12   * Пометить данные в файле;
13   * В Perl-скрипте считать содержимое помеченного файла в переменную;
14   * В том же скрипте вывести значение переменной на стандартный вывод;
15   * Пронаблюдать движение данных через внутренние структуры и вызовы Perl.
16 * Аналогичным способом отследить попадание помеченных данных в оператор языка Perl -- `eval`:
17   * В некий файл записать несколько perl-операторов выполняющих какое-либо осмысленное действие;
18   * Пометить данные в этом файле;
19   * В Perl-скрипте считать данные из помеченного файла в переменную;
20   * Выполнить операцию `eval` для содержимого вышеупомянутой переменной;
21   * Пронаблюдать стек вызовов по которому будут передаваться помеченные данные и найти вызов отвечающий за работу оператора `eval`.
22
23 ### Сборка и установка
24
25 Для кастомной сборки интерпретатора Perl воспользуемся утилитой `perlbrew` специально предназначенной для организации одновременной работы с несколькими версиями интерпретатора Perl, включая возможность автоматической сборки нужной версии из исходников.
26
27 Все инструкции по сборке приведены на примере дистрибутива Debian 12. 
28
29 #### Установка зависимостей
30
31 Список зависимостей необходимых для сборки интерпретатора Perl естественным образом присутствует в мета-информации пакета исходников `perl` входящего в Debain. Для установки необходимых зависимостей воспользуемся штатным механизмом установки зависимостей необходимых для сборки пакета исходников:
32
33 ```bash
34 $ sudo apt-get build-dep perl
35 ```
36
37 #### Подготовка к сборке
38
39 Установим пакет `perlbrew` из состава дистрибутива Debian
40
41 ```bash
42 $ sudo apt-get install perlbrew
43 ```
44
45 и проведем инициализацию его окружения
46
47 ```bash
48 $ perlbrew init
49
50 perlbrew root (~/perl5/perlbrew) is initialized.
51
52 [....]
53
54 Happy brewing!
55 ```
56
57 #### Сборка
58
59 Получим список доступных для сборки версий Perl
60
61 ```bash
62 $ perlbrew available
63
64    perl-5.40.1   
65    perl-5.38.3   
66 [......]
67    perl-5.10.1   
68     perl-5.8.9   
69     perl-5.6.2   
70 ```
71
72 Как мы видим на момент проведения эксперимента самой свежей версией была `perl-5.40.1`.
73 Будем использовать именно ее в своей работе.
74
75 Узнав номер желаемой версии даем команду на выполнение сборки
76
77 ```bash
78 $ perlbrew install -DEBUGGING=both -v perl-5.40.1 --as=5.40.1-dbg
79 ```
80
81 Здесь: опцией `-v perl-5.40.1` мы указываем какую версию Perl мы желаем собрать, опция `-DEBUGGING=both` будет передана программе конфигурации сборочной системе Perl и являет собой инструкцию провести сборку с отладочной информацией, которая будет нам необходима при проведении taint-анализа. 
82 Опцией `--as=5.40.1-dbg` мы задаем кастомное имя сборки, добавив суффикс `-dbg` к имени по умолчанию, чтобы было визуально отличить сборку с отладочной информацией от обычной сборки.
83
84 Процесс сборки замет какое-то не нулевое время.
85
86 Бинарные файлы получившиеся в результате сборки будут установлены в директорию `~/perl5/perlbrew/perls/perl-5.40.1-dbg/bin`.
87 (Это знание нам пригодиться в дальнейшем при проведении taint-анализа
88
89 #### Проверка
90
91 После того как сбора успешно завершится, имя сборки появится в списке доступных для использования сборок Perl:
92
93 ```bash
94 $ perlbrew list
95   5.40.1-dbg 
96 ```
97
98 Проверим, что мы можем воспользоваться новой сборкой. Для начала убедимся, что по умолчанию нам доступен системный Perl:
99
100 ```bash
101 $ perl -v
102
103 This is perl 5, version 36, subversion 0 (v5.36.0) built for x86_64-linux-gnu-thread-multi
104 [......]
105 ```
106
107 Далее используя утилиту `perlbrew` переключимся на нашу кастомную сборку Perl
108
109 ```bash
110 $ perlbrew use 5.40.1-dbg
111
112 A sub-shell is launched with 5.40.1-dbg as the activated perl. Run 'exit' to finish it.
113 ```
114
115 И убедимся, что теперь по умолчанию используется Perl другой версии
116
117 ```bash
118 $ perl -v
119
120 This is perl 5, version 40, subversion 1 (v5.40.1) built for x86_64-linux
121 ```
122
123 ### Эксперимент №1
124 ### Отслеживание помеченных данных проходящих через скаляр
125
126 #### Подготовка эксперимента
127
128 В домашней директории создадим директорию `perl` и в ней файл `in1` с содержимым `Hello, World!`
129
130 ```bash
131 $ mkdir ~/perl
132 $ echo Hello, World! >~/perl/in1
133 ```
134
135 В той же директории создадим файл `test_scalar.pl` с содержимым:
136
137 ```perl
138 #!/usr/bin/perl
139
140 use strict;
141
142 print "Perl is $]!\n";
143
144 open F, "in1";
145 my $s = <F>;
146 close F;
147 chomp $s;
148 print "** $s **\n";
149 ```
150 Сделать это можно например так:
151
152 ```bash
153 $ editor ~/perl/test_scalar.pl
154 # Вводим или вставляем из буфера программу программу
155
156 $ chmod +x $ editor ~/perl/test_scalar.pl 
157 ```
158
159 Помимо содержимого файла `in1` приведенный выше скрипт выводит версию интерпретатора Perl на котором он исполняется, что позволит в процессе проведения эксперимента убедится, что используется нужная сборка интерпретатора Perl.
160
161 Проверяем работоспособность созданного скрипта:
162
163 ```bash
164
165 $ perlbrew use 5.40.1-dbg
166 $ cd ~/perl
167 $ perl test_scalar.pl
168 ```
169
170 Скрипт должен вывести следующие:
171
172 ```
173 Perl is 5.040001!
174 ** Hello, World! **
175 ```
176
177 Обращаю внимание на необходимость явным образом запускать интепретатор и передавать ему имя файла со скриптом в качестве параметра, поскольку Ша-Банг (#!) по умолчанию указываемый в скрипте указывает на системный перл, perlbrew не в силах этого изменить, он может только добавить в пути поиска из переменной PATH путь к нужной сборке perl. Таким образом при явном вызове интерпретатора будет использоваться кастомная сборка Perl, a при запуске скрипта без указания интерпретатора -- системная.
178
179 #### Создание проекта natch
180
181 При создании проекта natch, уже на host-машине следуем штатной инструкции, в качестве источника помеченных данных указываем файл `in1` из домашней директории на виртуалке. 
182 В моем случае `/home/nataraj/perl/in1`. В качестве директории содержащий дополнительные модули с отладочной информацией указать место куда были установлены бинарные файлы собранные `perlbrew` (cм. выше).
183 В моем случае это `/home/nataraj/perl5/perlbrew/perls/perl-5.40.1-dbg/bin`.
184
185 #### Запись трассы
186
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.
195
196 #### Генерация отчета
197
198 Далее следуя штатной инструкции при помощи команды `natch replay` снимаем трассу между первым и вторым снепшотом и загружаем ее в snatch.
199
200 #### Анализ результатов
201
202 ##### Ресурсы
203
204 ![...](2. Perl.imgs/scalar_resources_1.png)
205
206 Если заглянуть в раздел "Ресурсы" можно увидеть, что в юзер-спейсе помеченные данные проходили только через бинарную программу perl и библиотеку libc. Данные были прочитаны из файла `/home/nataraj/perl/in1`
207
208 ![...](2. Perl.imgs/scalar_resources_2.png)
209
210 И записаны в устройство виртуального терминала `/dev/tty1`
211
212 ![...](2. Perl.imgs/scalar_resources_3.png)
213
214 что вполне отвечает нашим ожиданиям.
215
216 ##### Дерево вызовов
217
218 ![...](2. Perl.imgs/scalar_callgraph.png)
219
220 Если посмотреть на дерево вызовов, анатомия интерпретатора Perl становится в общем и целом понятной
221
222 `perl_run` и `Perl_runops_debug` очень похожи на вызовы оборачивающие все исполнение скрипта
223
224 `Perl_do_readline` вероятнее всего реализация операции чтения из файла `$s = <F>;`. В вызове `Perl_sv_gets` префикс `sv_` по конвенции обозначает работу со скалярным значением, это я знаю...
225
226 `Perl_pp_schop` -- реализация вызова `chomp $s;`
227
228 `Perl_pp_multiconcat` вероятнее всего занимается подстановкой значений в строковой литерал, являющейся аргументом оператора `print`
229
230 `Perl_pp_print` по всей видимости является реализацией оператора `print`
231
232 ##### Выводы
233
234 В принципе Natch не является профильным инструментом для исследования архитектуры сложных проектов, однако может быть использован и для этой цели.
235 Результат получается красивый и наглядный, и при наличии навыка его получение не требует больших усилий.
236
237 ### Эксперимент №2
238 ### Отслеживание помеченных данных передаваемых в оператор `eval`
239
240 #### Подготовка эксперимента
241
242 Для исследования перемещения помеченных данных через оператор `eval` запишем в файл `in2` небольшую perl-программу:
243
244 ```bash
245 $ echo 'my $aaa = "Hello, World!";' >~/perl/in2
246 $ echo 'print $aaa;' >>~/perl/in2
247 ```
248
249 И так же создадим perl-скрипт `test_eval.pl` который будет читать содержимое файла `in2` и выполнять его при помощи оператора `eval`:
250
251 ```perl
252 #!/usr/bin/perl
253
254 use strict;
255
256 print "Perl is $]!\n";
257
258 open F, "in2";
259 $/ = "";
260 my $s = <F>;
261 close F;
262
263 print "** ";
264 eval $s;
265 print " **\n";
266
267 ```
268
269 Сделать это можно например так:
270
271 ```bash
272 $ editor ~/perl/test_eval.pl
273 # Вводим или вставляем из буфера программу программу
274
275 $ chmod +x $ editor ~/perl/test_eval.pl 
276 ```
277
278 Проверяем работоспособность созданного скрипта:
279
280 ```bash
281
282 $ perlbrew use 5.40.1-dbg
283 $ cd ~/perl
284 $ perl test_eval.pl
285 ```
286
287 Скрипт должен вывести следующие:
288
289 ```
290 Perl is 5.040001!
291 ** Hello, World! **
292 ```
293
294 #### Проведение эксперимента
295
296 Проведение эксперимента для случая `eval` ни чем не отличается от предыдущего варианта эксперимента с загрузкой помеченных данных в скалярную переменную.
297 В данном случае необходимо будет пометить данные в файле `in2` вместо `in1` и при записи трассы выполнить скрипт `test_eval.pl` вместо `test_scalar.pl`
298
299 #### Анализ результатов
300
301 ##### Ресурсы
302
303 ![...](2. Perl.imgs/eval_resources_1.png)
304
305 Так же как и в предыдущем эксперименте мы видим что в юзер-спейсе помеченные данные проходят только через процесс perl и библиотеку libc
306
307 Читаются помеченные данные из входного файла `in2`
308
309 ![...](2. Perl.imgs/eval_resources_2.png)
310
311 И на устройство `/dev/tty1` из всех помеченных данных выводится только содержимое строкового литерала из программы из файла `in2`.
312
313 ![...](2. Perl.imgs/eval_resources_3.png)
314
315 Все более или менее ожидаемо.
316
317 ##### Дерево вызовов
318
319 ![...](2. Perl.imgs/eval_callgraph.png)
320
321 Дерево вызовов во втором эксперименте получается сильно более развесистое чем в первом.
322
323 В начале мы видим все тот же `Perl_do_readline` отвечающий за чтение данных из файла.
324
325 Далее мы видим пару `Perl_pp_entereval`, `Perl_pp_leaveeval` и `Perl_pp_print` между ними.
326
327 По всей видимости первые два обрамляют собой оный `eval` а вызов `print` является содержательной частью внутри `eval`'а исполняющейся.
328
329 Так же интересным вызовом внутри `Perl_pp_entereval` является `S_doeval_compile` в котором судя по названию происходит компиляция строки в исполняемый байт-код Perl, и судя по префиксам `yy` в его внутренних вызовах разбор проводится при помощи Yacc-парсера. 
330 Префикс `yy` традиционно используется для Yacc-related кода.
331
332 Функция `Perl_pp_entereval` так же выглядит крайне перспективно в смысле фаззинга.
333 Вероятнее всего она сама по себе состояние интерпретатора не меняет.
334 Главное корректно освободить все структуры которые она создала в процессе компиляции. 
335
336 ##### Выводы
337
338 Поставленная задача решена полностью, базовая анатомия работы оператора `eval` полностью установлена.
339 Далее при реальном исследовании работы интерпретатора мы бы перешли к исследованию исходного кода интерпретатора и постановке натурных экспериментов.
340 Часть касающаяся демонстрации возможностей Natch на этом этапе можно считать завершенной.  
341
342