Консольная DLL и gui приложение

Тема в разделе "WASM.WIN32", создана пользователем yunus, 11 янв 2005.

  1. yunus

    yunus New Member

    Публикаций:
    0
    Регистрация:
    18 дек 2004
    Сообщения:
    11
    Адрес:
    Russia
    Есть чужая dll выводящая текст на консоль. Цепляется к оконному приложению. Подскажите, будте любезны, как перехватить консольный вывод.

    Пытался использовать треды. В один прописал - SetStdHandle и поставил pipe. В другом бесконечный цикл с PeekNamedPipe ну и затем ReadFile. Вызываю функцию dll. Она кидает текст - и текст ИНОГДА (редко) отлавливается.

    Может можно проще как нибудь ?
     
  2. vinnie_pooh

    vinnie_pooh New Member

    Публикаций:
    0
    Регистрация:
    30 июн 2004
    Сообщения:
    98
    Я поковырял этот вопрос, вот что получилось:

    1. В GUI-приложении пишем процедуру, которая будет принимать во втором параметре строку (как WriteConsole).

    2. В Иде узнаем адрес этой процедуры (напр. 40111Eh).

    3. В Dll-ке находим вызов WriteConsole (что-то типа call [????????]) и вставляем вместо него код команд mov eax, 40111Eh / call eax.

    В результате вызывается наша процедура вместо WriteConsole со строкой во втором параметре. К сожалению, call [????????] занимает 6 байт, а mov eax, 40111Eh / call eax - 7, поэтому процедура перед возвратом в DLL должна сама выполнить затертую команду. У меня она выглядит так:
    Код (Text):
    1. QuasiWriteConsole   proc    near
    2.     mov eax,[esp + 8]       ;адрес строки
    3.     push    0
    4.     push    offset the_title
    5.     push    eax
    6.     push    0
    7.     call    MessageBox
    8.  
    9.     pop eax         ;EAX: адрес возврата
    10.     add esp,5 * 4       ;очистить стек от параметров
    11.     pop edi         ;эта команда была у меня в DLL-ке после WriteConsole
    12.                     ;(а может быть и не однобайтная!)
    13.     jmp eax         ;возврат
    14. QuasiWriteConsole   endp


    Результат в аттаче.



    [​IMG] _969651039__GuiApp.rar



    PS. А проще и лучше править адрес WriteConsole в таблице импорта у DLL-ки
     
  3. yunus

    yunus New Member

    Публикаций:
    0
    Регистрация:
    18 дек 2004
    Сообщения:
    11
    Адрес:
    Russia
    vinnie_pooh



    Мысль править DDL-ку мне понравилась, но применить оказалось не судьба. гадкая dll состряпана в Delphi и WriteConsole не использует в принципе (смотрел и таблицу импорта и дебагом пробегал).

    То есть таблицу импорта править не удасться.



    Хуже того, в pascal свой Write при вызове которого генерируется нечто подобное -


    Код (Text):
    1. MyTest.pas.32: Writeln('Init Test');
    2. 00B158C8 A13464B100       mov eax,[$00b16434]
    3. 00B158CD BAEC58B100       mov edx,$00b158ec
    4. 00B158D2 E8CDEEFEFF       call @Write0LString
    5. 00B158D7 E868D8FEFF       call @WriteLn
    6. 00B158DC E8F7D1FEFF       call @_IOTest




    то есть целых три call. При том в каждом конкретном случае генерируется различный код (различные типы параметров, проверка ошибки ввода-вывода и т.п.)

    То есть и первый вариант найти все вызовы и поменять на нужное оказывается слишком хлопотным. Вызовов до чертиков а искать их придется практически в ручную (во всяком случае я не знаю - какой шаблон поиска задать)

    Так что вопрос остается открытым
     
  4. bogrus

    bogrus Active Member

    Публикаций:
    0
    Регистрация:
    24 окт 2003
    Сообщения:
    1.338
    Адрес:
    ukraine




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

    leo Active Member

    Публикаций:
    0
    Регистрация:
    4 авг 2004
    Сообщения:
    2.542
    Адрес:
    Russia
    Ежели "гадкая dll состряпана в Delphi", то возможно и править ее не надо, т.к. в дельфях есть легальный способ перенаправления вывода.

    Вывод на консоль идет как и в любой текстовый файл, но вместо произвольной файловой переменной f используется глобальная переменная Output, инициализируемая при загрузке модуля System. Все что нужно для write\writeln содержится в самой переменной Output, поэтому достаточно до нее добраться и можно перенаправить вывод куда угодно, заменив соответствующие поля TTextRec(Output).

    Например, можно заменить TTextRec(Output).Handle на pipe. Но лучше задать фиктивный валидный хэндл и подменить указатель на функцию вывода строки TTextRec(Output).InOutFunc.

    Тип TTextRec описан в модуле SysUtils:
    Код (Text):
    1. TTextRec = record
    2.     Handle: Integer;     //хэндл
    3.     Mode: Integer;       //режим вывода = fmOutput = $D7B2
    4.     BufSize: Cardinal;   //размер буфера строки
    5.     BufPos: Cardinal;    //для записи - число символов в буфере
    6.     BufEnd: Cardinal;    //для чтения - число прочитанных байт
    7.     BufPtr: PChar;       //указатель на буфер строки
    8.     OpenFunc: Pointer;   //функция открытия файла
    9.     InOutFunc: Pointer;  //функция ввода-вывода строки
    10.     FlushFunc: Pointer;  //функция выталкивания остатка буфера
    11.     CloseFunc: Pointer;  //функция закрытия файла
    12.     UserData: array[1..32] of Byte; //не используется
    13.     Name: array[0..259] of Char; //имя файла; для консоли = ''
    14.     Buffer: TTextBuf; //=array[0..127] of Char; буфер строки по умолчанию
    15. end;
    16. const
    17.     fmClosed = $D7B0;
    18.     fmInput  = $D7B1;
    19.     fmOutput = $D7B2;
    Все функции имеют один прототип:

    function DeviceFunc(var F: TTextRec):integer == DeviceFunc(FP:pointer):integer, где FP - указатель на TTextRec; тип вызова register, т.е. на входе в EAX сидит указатель на F.

    Если OK, то Result = 0, иначе это код ошибки IOResult.



    Для перехвата записи строки нужно в своем приложении изменить поля переменной Output dll:

    1) на всякий случай проверить и закрыть Handle (хотя при инициализации он = 0 и открывается только при первой записи)

    2) задать валидное значение Handle (<> 0 и INVALID_HANDLE)

    3) установить Mode = fmOutput

    4) определить функцию InOutFunc, которая будет копировать BufPos символов из буфера BufPtr и занулять BufPos (вместо BufPtr можно использовать Buffer, т.к. для консоли BufPtr = указателю на Buffer)

    5) остальные функции заменить на пустышку, которая просто возвращвет 0 в eax.



    Как найти указатель на Output ? Он передается в EAX во все writeXX, т.е. в приведенном примере это mov eax,[$00b16434]. Косвенная ссылка - это дельфийские прибамбасы, наверняка сама структура Output сидит в .BSS, а ссылка на нее в .DATA
     
  6. yunus

    yunus New Member

    Публикаций:
    0
    Регистрация:
    18 дек 2004
    Сообщения:
    11
    Адрес:
    Russia
    leo

    Спасибо за совет, так я и сделал. Единственно что пришлось добавить - указать размер буфера (BufSize) равным 1. Без этого вызов функции InOutFunc произходил только при закрытии программы. А так, насколько я понял, буфер при выводе каждого символа заполняется и принудительно вызывает фунцию сброса данных.
     
  7. leo

    leo Active Member

    Публикаций:
    0
    Регистрация:
    4 авг 2004
    Сообщения:
    2.542
    Адрес:
    Russia
    yunus

    Да, ты прав. Покопался я в сорцах (..\rtl\sys\assign.asm и writestr.asm) и пришел к выводу, что при обычном write символы просто добавляются в буфер, а запись в файл производится в следующих случаях:

    1) при переполнении буфера: BufSize-BufPtr < числа добавляемых символов

    2) при закрытии файла close(F); в данном сл. Output закрывается при завершении программы

    3) при writeln вызывается FlushFunc

    Поэтому можно попробовать установить FlushFunc = InOutFunc, а BufSize задать порядка 4-8 (на случай write, т.к. если вывод идет по writeln, то и 128 можно оставить).

    А то копировать по одному символу ИМХО как-то "не изящно" (но с другой стороны - надежно).