Как узнать, что приложение завершило обработку файла?

#assembly #binary #reverse-engineering #fuzzing

#сборка #двоичный #обратный инжиниринг #фаззинг

Вопрос:

Я пытаюсь автоматизировать инструментирование, которое я выполняю в приложении, но проблема в том, что я имею дело с приложениями, которые не завершаются сами по себе после обработки. Для примера возьмем любой pdfviewer / reader, если я открою файл, файл отобразится, и я увижу, что приложение обработало файл.

Под обработкой файла приложением я подразумеваю, что файл был успешно отображен приложением.

Приложением может быть любой графический просмотрщик pdf, например Adobe Reader, xpdf, foxitreader или любой просмотрщик изображений, например gpicview, и т.д. Форматы файлов могут быть любого типа, а не какого-либо определенного формата файла.

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

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

Но проблема здесь в том, как идентифицировать этот базовый блок?

Комментарии:

1. Я думал, что для больших файлов pdf отображение использует только частичное чтение файла, передавая остальные данные по мере необходимости, когда пользователь прокручивает вниз… Таким образом, работа с файлом не завершена, пока приложение не будет закрыто.

Ответ №1:

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

Убедитесь, что вы ждете достаточно долго, чтобы определить разницу между заблокированным вводом-выводом на диске и заблокированным в ожидании ввода пользователем. (В Unix-подобных ОС это разница между режимом ожидания на диске и Sleep, как показано в D vs. S в таких материалах, как top список процессов.)

Если вы не хотите полагаться на ОС в определении перехода диска в режим ожидания по сравнению с обычным переходом в режим ожидания, просто подождите в несколько раз дольше, чем максимальное время обслуживания запроса дискового ввода-вывода (~ = в несколько раз больше задержки на диске, ниже, если тестируемый процесс является единственным процессом, выполняющим ввод-вывод). Если процесс «черного ящика» не использовал процессорное время за этот интервал, можно предположить, что загрузка завершена и файл отображается на экране.

Конечно, как указывает @Ped7g, возможно, оно проанализировало не весь файл. Оно может загружать его лениво, по требованию, например, когда пользователь просматривает большой PDF-файл. Наблюдение за процессорным временем должно быть разумным способом определить, когда процесс завершил обновление после программной отправки ему команды перелистывания страницы.

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


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

В большинстве операционных систем есть некоторые средства, позволяющие процессам отслеживать другие процессы. В Linux основным является API ptrace. Такие команды, как strace , используют это для отслеживания системных вызовов. Я полагаю, что в Windows есть нечто подобное, и я предполагаю, что OS X тоже.

Таким образом, вы можете искать системный вызов open () в PDF, чтобы найти правильный fd, затем искать системные вызовы mmap, read() и close() в нем. Если read() возвращает 0, это значение EOF. Если оно закрыто без mmap, процесс с ним завершен (если только оно не откроет его снова или не использует dup () или dup2 () по какой-либо причине).

Вы могли бы проанализировать текстовый вывод strace или самостоятельно использовать ptrace API.


В качестве альтернативы, в Linux вы можете посмотреть положение файла в /proc/<PID>/fdinfo/<FD> . Другие операционные системы, вероятно, имеют аналогичные средства для просмотра позиции открытых файловых дескрипторов / файловых дескрипторов.

Например, так получилось, что у меня evince открыт файл PDF. В `/proc/

 $ ll /proc/4241/fd
...
lr-x------ 1 peter peter 64 Oct 21 06:43 14 -> /f/p/docs/agner_fog.microarchitecture.pdf    # is anyone really surprised this is the PDF I had open?  :P
...
$ ls -lL /proc/4241/fd/14       # follow the symlink to see the file size
-rw-rw-r-- 1 peter peter 2078709 Feb  4  2016 /proc/4241/fd/14

$ m /proc/4241/fdinfo/14        # alias for less
pos:    2078709
flags:  0100000
mnt_id: 49
  

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

Комментарии:

1. Обязательно ли, чтобы close или EOF указывали, что приложение завершило обработку, предположим, я прочитал файл в буфере, а затем для отображения данных я использую этот буфер. Под обработкой файла я не имею в виду, что файл был прочитан полностью. Я имею в виду, что он был успешно отображен.

2. @user2823667: Да, это хороший момент. Вероятно, сочетание этого и ожидание прекращения использования процессорного времени — ваш лучший выбор. Просто подождите, пока оно не прочитает файл и не будет использовать какое-либо значительное количество процессорного времени в течение ~ 40 мс или около того, в зависимости от задержки вашего дискового ввода-вывода. (Избегайте ложных срабатываний при ожидании ввода-вывода).

3. @user2823667: Если вы можете отследить, использовал ли какой-либо из его потоков весь свой временной интервал, или они просто проснулись, чтобы обработать событие GUI и сразу же вернулись в спящий режим, я думаю, это было бы то, на что нужно обратить внимание.

4. Что, если приложение преобразует файл в mmap, закрывает его и только после этого обрабатывает? Вы не можете (легко) проверить, какие обращения к файлу выполняет приложение, когда это всего лишь часть его адресного пространства.

5. @ShacharShemesh: Хороший момент, просмотр файлового дескриптора в целом ненадежен. Ожидание того, что загрузка процессора процесса приблизится к нулю, дольше, чем в несколько раз превышает задержку вашего дискового ввода-вывода, всегда должно работать. Если вы хотите написать больше кода, чтобы, возможно, в некоторых случаях определять с большей уверенностью (и, таким образом, не ждать так долго, чтобы убедиться, что процесс сейчас простаивает), вы могли бы использовать strace для определения того, что процесс закрыл файл, не отображая его. Но я думаю, что на самом деле лучше всего посмотреть на загрузку процессора процессом. Я собираюсь отредактировать свой ответ, чтобы поместить это в качестве первого предложения.