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

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

В сегменте данных родительского приложения требуется определить следующие переменные и структуры:
; Сегмент данных
section '.data' data readable writeable 
 
; Данные для перехвата консоли
newstdin      dd ?  ; Новый дескриптор стандартного ввода
newstdout     dd ?  ; Новый дескриптор стандартного вывода
read_stdout   dd ?  ; Дескриптор для использования ReadFile
write_stdin   dd ?  ; Дескриптор для использования WriteFile
bytestoread   dd ?  ; Всего байт в буфере консоли
available     dd ?  ; Счетчик байт, доступных для чтения из консоли
 
; Эта структура по умолчанию не определена, сделаем это сами
struct SECURITY_ATTRIBUTES
       nLength               dd ?
       lpSecurityDescriptor  dd ?
       bInheritHandle        dd ?
ends
 
; Описание структур для запуска консольной программы и настройки дескрипторов
sinfo         STARTUPINFO
sattr         SECURITY_ATTRIBUTES
pinfo         PROCESS_INFORMATION
 
; Дополнительно зарезервируем буфер для чтения информации
buff   rb 1024

Буфер большого размера для чтения данных лучше не использовать, вполне достаточно 1 килобайта. Количество байт, доступных для чтения из консоли, можно получить при помощи функции PeekNamedPipe. Обратите внимание, что фактически данные из консоли при этом не забираются, это надо будет сделать при помощи функции чтения файла ReadFile. Вот пример кода перехватчика вывода консоли.
; Сегмент кода
section '.code' code readable executable
        ; Заполнить структуру SECURITY_ATTRIBUTES
        mov     [sattr.nLength],sizeof.SECURITY_ATTRIBUTES
        mov     [sattr.lpSecurityDescriptor],NULL
        mov     [sattr.bInheritHandle],TRUE
 
        ; Создать новые дескрипторы для консольного ввода и вывода
        invoke  CreatePipe,newstdin,write_stdin,sattr,NULL
        invoke  CreatePipe,read_stdout,newstdout,sattr,NULL
 
        ; Заполнить структуру STARTUPINFO данными процесса
        invoke  GetStartupInfo,sinfo
        ; Установить флаги: использовать собственные дескрипторы
        ; ввода-вывода и возможность изменять видимость окна процесса
        mov     [sinfo.dwFlags],STARTF_USESTDHANDLES+STARTF_USESHOWWINDOW
        mov     [sinfo.wShowWindow],SW_HIDE
        mov     eax,[newstdout]
        ; Установить наш дескриптор для стандартного вывода и ошибок
        mov     [sinfo.hStdOutput],eax
        mov     [sinfo.hStdError],eax
        mov     eax,[newstdin]
        ; Установить наш дескриптор для стандартного ввода
        mov     [sinfo.hStdInput],eax
 
        ; Запустить консольное приложение в режиме suspended
        invoke  CreateProcess, NULL, fname, NULL, NULL, TRUE,\
                CREATE_SUSPENDED+NORMAL_PRIORITY_CLASS+CREATE_NEW_CONSOLE,\
                NULL,NULL,sinfo,pinfo
 
        ; Тут можно выполнить какие-то промежуточные действия, например
        ; спросить у пользователя куда сохранять файл, или проверить
        ; доступность каких-либо данных
 
        ; Отпустить замороженный процесс на выполнение
        invoke  ResumeThread,[pinfo.hThread]
wait_for_finish:
        ; Проверить активен ли еще запущенный процесс
        invoke  GetExitCodeProcess,[pinfo.hProcess],tmp
        cmp     [tmp],STILL_ACTIVE
        jne     finish
 
        ; Небольшая пауза чтобы не грузить систему
        invoke  Sleep,10
 
        ; Прочитать данные из буфера консоли без удаления
        invoke  PeekNamedPipe,[read_stdout],buff,1023,bytestoread,available,NULL
        ; Если консоль еще ничего не вывела, то продолжать ждать результат
        cmp     [bytestoread],0
        je      wait_for_finish
read_data_from_pipe:
        ; Прочитать данные из буфера консоли и записать в наш файл
        invoke  ReadFile,[read_stdout],buff,1023,bytestoread,NULL
 
        ; Теперь в буфере buff содержатся данные из консоли. Их можно
        ; проанализировать, вывести в форму оконного приложения, сохранить
        ; в файл или просто проигнорировать
 
        ; Дополнительная проверка если весь вывод был меньше размера буфера
        cmp     [available],1023
        jna     wait_for_finish
 
        ; Продолжать чтение до окончания данных
        cmp     [bytestoread],1023
        je      read_data_from_pipe
        jmp     wait_for_finish
finish:
        ; Завершить обработку

Если не требуется обработка данных из консольного приложения, то можно сразу перенаправить его, например, на дескриптор открытого файла или принтер. Для передачи данных в поток консольного приложения надо отправлять их через функцию WriteFile на дескриптор, описанный в этом примере как write_stdin.

В аттаче рабочий пример программы, перехватывающей консольный вывод и сохраняющей его в файл console.txt. В примере выполняется команда "cmd /c dir c:\", выводящая список файлов и каталогов в корне диска C:
Теги:
Перехват ввода
Добавлено: 10 Апреля 2018 20:39:52 Добавил: Андрей Ковальчук Нравится 0
Добавить
Комментарии:
Нету комментариев для вывода...