Blog. Just Blog

Перехват ввода и вывода консольных программ

Версия для печати Добавить в Избранное Отправить на E-Mail | Категория: Образ мышления: Assembler | Автор: ManHunter
Перехват ввода и вывода консольных программ бывает нужен, когда требуется получить результат их работы для обработки в нашем приложении. Также мы получаем возможность передавать консольным программам собственные данные. Как обычно в FASM'е готовых решений нет, пришлось разбираться самому и портировать с языков высокого уровня. Технически перехват ввода и вывода консоли выполняется с использованием специальных структур, называемых "Pipe". По принципу действия они и вправду похожи на трубы: в один конец информация "вливается", из другого "выливается", а перехват является просто подключением нашего "крана" к тому или иному концу трубы. Для перехвата требуется переопределить стандартные дескрипторы ввода и вывода консольного приложения на наши. Создать новые дескрипторы можно при помощи функции CreatePipe, а затем прописать в структуру STARTUPINFO запускаемого приложения. После этого новые дескрипторы будут доступны для чтения и записи как обычный файл.

В сегменте данных родительского приложения требуется определить следующие переменные и структуры:
  1. ; Сегмент данных
  2. section '.data' data readable writeable
  3.  
  4. ; Данные для перехвата консоли
  5. newstdin      dd ?  ; Новый дескриптор стандартного ввода
  6. newstdout     dd ?  ; Новый дескриптор стандартного вывода
  7. read_stdout   dd ?  ; Дескриптор для использования ReadFile
  8. write_stdin   dd ?  ; Дескриптор для использования WriteFile
  9. bytestoread   dd ?  ; Всего байт в буфере консоли
  10. available     dd ?  ; Счетчик байт, доступных для чтения из консоли
  11.  
  12. ; Эта структура по умолчанию не определена, сделаем это сами
  13. struct SECURITY_ATTRIBUTES
  14.        nLength               dd ?
  15.        lpSecurityDescriptor  dd ?
  16.        bInheritHandle        dd ?
  17. ends
  18.  
  19. ; Описание структур для запуска консольной программы и настройки дескрипторов
  20. sinfo      STARTUPINFO
  21. sattr      SECURITY_ATTRIBUTES
  22. pinfo      PROCESS_INFORMATION
  23.  
  24. ; Дополнительно зарезервируем буфер для чтения информации
  25. buff   rb 1024
Буфер большого размера для чтения данных лучше не использовать, вполне достаточно 1 килобайта. Количество байт, доступных для чтения из консоли, можно получить при помощи функции PeekNamedPipe. Обратите внимание, что фактически данные из консоли при этом не забираются, это надо будет сделать при помощи функции чтения файла ReadFile. Вот пример кода перехватчика вывода консоли.
  1. ; Сегмент кода
  2. section '.code' code readable executable
  3.         ; Заполнить структуру SECURITY_ATTRIBUTES
  4.         mov     [sattr.nLength],sizeof.SECURITY_ATTRIBUTES
  5.         mov     [sattr.lpSecurityDescriptor],NULL
  6.         mov     [sattr.bInheritHandle],TRUE
  7.  
  8.         ; Создать новые дескрипторы для консольного ввода и вывода
  9.         invoke  CreatePipe,newstdin,write_stdin,sattr,NULL
  10.         invoke  CreatePipe,read_stdout,newstdout,sattr,NULL
  11.  
  12.         ; Заполнить структуру STARTUPINFO данными процесса
  13.         invoke  GetStartupInfo,sinfo
  14.         ; Установить флаги: использовать собственные дескрипторы
  15.         ; ввода-вывода и возможность изменять видимость окна процесса
  16.         mov     [sinfo.dwFlags],STARTF_USESTDHANDLES+STARTF_USESHOWWINDOW
  17.         mov     [sinfo.wShowWindow],SW_HIDE
  18.         mov     eax,[newstdout]
  19.         ; Установить наш дескриптор для стандартного вывода и ошибок
  20.         mov     [sinfo.hStdOutput],eax
  21.         mov     [sinfo.hStdError],eax
  22.         mov     eax,[newstdin]
  23.         ; Установить наш дескриптор для стандартного ввода
  24.         mov     [sinfo.hStdInput],eax
  25.  
  26.         ; Запустить консольное приложение в режиме suspended
  27.         invoke  CreateProcess, NULL, fname, NULL, NULL, TRUE,\
  28.                 CREATE_SUSPENDED+NORMAL_PRIORITY_CLASS+CREATE_NEW_CONSOLE,\
  29.                 NULL,NULL,sinfo,pinfo
  30.  
  31.         ; Тут можно выполнить какие-то промежуточные действия, например
  32.         ; спросить у пользователя куда сохранять файл, или проверить
  33.         ; доступность каких-либо данных
  34.  
  35.         ; Отпустить замороженный процесс на выполнение
  36.         invoke  ResumeThread,[pinfo.hThread]
  37. wait_for_finish:
  38.         ; Проверить активен ли еще запущенный процесс
  39.         invoke  GetExitCodeProcess,[pinfo.hProcess],tmp
  40.         cmp     [tmp],STILL_ACTIVE
  41.         jne     finish
  42.  
  43.         ; Небольшая пауза чтобы не грузить систему
  44.         invoke  Sleep,10
  45.  
  46.         ; Прочитать данные из буфера консоли без удаления
  47.         invoke  PeekNamedPipe,[read_stdout],buff,1023,bytestoread,available,NULL
  48.         ; Если консоль еще ничего не вывела, то продолжать ждать результат
  49.         cmp     [bytestoread],0
  50.         je      wait_for_finish
  51. read_data_from_pipe:
  52.         ; Прочитать данные из буфера консоли и записать в наш файл
  53.         invoke  ReadFile,[read_stdout],buff,1023,bytestoread,NULL
  54.  
  55.         ; Теперь в буфере buff содержатся данные из консоли. Их можно
  56.         ; проанализировать, вывести в форму оконного приложения, сохранить
  57.         ; в файл или просто проигнорировать
  58.  
  59.         ; Дополнительная проверка если весь вывод был меньше размера буфера
  60.         cmp     [available],1023
  61.         jna     wait_for_finish
  62.  
  63.         ; Продолжать чтение до окончания данных
  64.         cmp     [bytestoread],1023
  65.         je      read_data_from_pipe
  66.         jmp     wait_for_finish
  67. finish:
  68.         ; Завершить обработку
Если не требуется обработка данных из консольного приложения, то можно сразу перенаправить его, например, на дескриптор открытого файла или принтер. Для передачи данных в поток консольного приложения надо отправлять их через функцию WriteFile на дескриптор, описанный в этом примере как write_stdin.

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

В аттаче рабочий пример программы, перехватывающей консольный вывод и сохраняющей его в файл console.txt. В примере выполняется команда "cmd /c dir c:\", выводящая список файлов и каталогов в корне диска C:

Пример программы с исходным текстом (FASM)Пример программы с исходным текстом (FASM)

Console.Interceptor.Demo.zip (2,840 bytes)


Поделиться ссылкой ВКонтакте
Просмотров: 10830 | Комментариев: 11

Внимание! Статья опубликована больше года назад, информация могла устареть!

Комментарии

Отзывы посетителей сайта о статье
sheva740 (18.09.2015 в 07:46):
Добрый день.
Подскажи пожалуйста что не так в моей программе?
Не выводит в созданную консоль текст "123456789"
Код не поместился поэтому даю ссылку
... на васме не отвечают.
http://wasm.ru/forum/viewtopic.php?id=51280

Читать из пайпа читает, но писать туда не хочет
WriteFile() не работает (((
Где я затупил как всегда?

>Опять мимо. Хэндл [write_stdin] завернут на исполняемое приложение, а не >на вывод консоли, и он используется только для передачи каких-то данных >исполняемой ПРОГРАММЕ, а не консоли.
Я конечно медитирую над этой фразой, но тогда как вывести в новосозданную консоль "123456789"? Только через VkKeyScan() и найденный хендл окна получается?
ded (14.06.2010 в 22:09):
пардон, я хотел сказать определить новые хэндлы и структуры под новый процесс..
скажем, как то так

file     db 'cmd.exe',0
invoke GetStdHandle,STD_OUTPUT_HANDLE
mov     [sinfo1.hStdOutput],eax
invoke  CreateProcess, NULL, file, NULL, NULL, TRUE,\
        CREATE_NEW_CONSOLE,NULL,NULL,sinfo1,pinfo1
invoke  WriteFile,[sinfo1.hStdOutput],buff,1023,bytestoread,NULL
ManHunter (14.06.2010 в 21:18):
Опять мимо. Хэндл [write_stdin] завернут на исполняемое приложение, а не на вывод консоли, и он используется только для передачи каких-то данных исполняемой ПРОГРАММЕ, а не консоли.
ded (14.06.2010 в 21:13):
Вы намекаете... на 54-ю строку?
Все равно не работает :(
Хотя.. надо же, наверно, восстанавливать стантартные хэндлы ин\оут-ов, заново заполнять структуры под новый процесс.. чтоб туда чота посылать..? так ведь?
ManHunter (14.06.2010 в 02:30):
А если задействовать головной мозг? К 68-й строке в буфере будет пусто, а bytestoread=0.
ded (14.06.2010 в 02:22):
и как же вывести на консоль, то, что "выливается" из трубы?
пишем 68-й строкой:
invoke  WriteFile,[write_stdin],buff,1023,bytestoread,NULL
и ничо не наблюдаем...
ELM (23.03.2010 в 10:54):
а да, я тож с такой ерундой сталкевалсо
от биндшелл на фасмах mytechblog.net/?p=21
ManHunter (03.08.2009 в 19:56):
Поэкспериметрировал, выяснил причину. Нулевой файл создается на очень шустрых машинах. Надо видоизменить код:

wait_for_finish:
        ; Проверить активен ли еще запущенный процесс
        invoke  GetExitCodeProcess,[pinfo.hProcess],tmp
        cmp     [tmp],STILL_ACTIVE
        jne     finish

        ; Небольшая пауза чтобы не грузить систему
        invoke  Sleep,10

Чтобы сперва была проверка активности процесса, а уже потом пауза. И паузу чуть побольше. Исходники перезалил.
Dima (28.04.2009 в 21:23):
Система Хрюша сп 2, права админские
ManHunter (27.04.2009 в 08:46):
Система какая? Я проверял на WinXP SP3, на двух стационарных машинах и ноутбуке все работает. Учетная запись с правами администратора.
Dima (27.04.2009 в 08:40):
Hi
У меня отрабатывает файл но, создается пустой файл console.txt 0 bytes (((

Добавить комментарий

Заполните форму для добавления комментария
Имя*:
Текст комментария (не более 2000 символов)*:

*Все поля обязательны для заполнения.
Комментарии, содержащие рекламу, ненормативную лексику, оскорбления и т.п., а также флуд и сообщения не по теме, будут удаляться. Нарушителям может быть заблокирован доступ к сайту.
Наверх
Powered by PCL's Speckled Band Engine 0.2 RC3
© ManHunter / PCL, 2008-2024
При использовании материалов ссылка на сайт обязательна
Время генерации: 0.09 сек. / MySQL: 2 (0.0088 сек.) / Память: 4.5 Mb
Наверх