Top.Mail.Ru
Мастера DELPHI, Delphi programming community Титульная страница Поиск, карта сайта Написать письмо 
| Новости | Поиск | FAQ | Статьи | Книги | Новости VCL |
| Форумы ЧАТ | Орешник | KOL и MCK |
Форумы -» Основная («Начинающим»)/ Базы / WinAPI / Компоненты / Сети / Media / Игры / Corba и COM / KOL / FreePascal / .Net / Прочее / RSDN.ORG

« предыдущая ветвь | форум | следующая ветвь »

Ожидание окончания "инициализации процесса".


Страницы:   1 2

Riply ©   (2007-05-10 22:07:00) 

Здавствуйте !
Пытаюсь запустить процесс и тут же в него "внедриться" :)
Делаю, примерно так:
if ProcessCreate(ExeName, ..., Proc_Info) then
try
 if WaitForInputIdle(Proc_Info.hProcess, INFINITE) = WAIT_OBJECT_0 then
  begin
   // Здесь внедряемся и что-то делаем, например перехватываем API.
  end;
finally
 ProcessInfo_CloseHandles(@Proc_Info);
end
else Result := GetLastError;

Все работает, но мне не совсем нравиться :(
Во-первых: в отношении консольного процесса Help какой-то двусмысленный:
с одной стороны:
"until the specified process has finished its initializationand is waiting for user input",
а с другой:
"If this process is a console application or does not have a message queue,
WaitForInputIdle returns immediately".
Объясните мне пожалуйста WaitForInputIdle (в случае, например, консоли)
дожидается "the specified process has finished its initialization", или "returns immediately" ?
И еще: меня не устраивает ожидание по TimeOut - интервалу.
Как можно организовать одновременное ожидание WaitForInputIdle и моего StopWaitEvent ?
P.S.
Я понимаю, что это можно сделать с помощью, например RegisterWaitForSingleObject,
но не заводить же пул потоков ради перехвата
одной единственной маленькой и несчастной функции :)


Германн   (2007-05-14 02:48:00) [21]


> Riply ©   (14.05.07 01:28) [19]
>
...
> Пойду переделывать.


Когда-нибудь, пусть даже и в мемуарах, опиши свой "метод ненаучного тыка". Со всеми подробностями и деталями. Может кому пригодится?
:-)


Riply   (2007-05-14 01:28:00) [22]

> [16] Leonid Troyanovsky © (14.05.07 00:03)
>Тут, видимо, и следует использовать технику "двойного APC",
>которую упоминал Slava.
>Т.е., это, наверное, QUAPC, посылаемый вызывающему потоку
>(который передал в качестве параметра свой tid),
>и ожидающему своей очереди в an alertable state.
Что-то начинает становиться сложновато для меня :(
Не смогла понять, приведенную выше цитату.
>Кста, в том, что APC вызывается в контексте первичного
>потока, есть и свои преимущества, т.е., синхронизаций
>при подменах не потребуется.
Имеется ввиду организация перехвата прямо из APC ?
Мне кажется, что это не так просто. Ведь, если я правильно понимаю,
то в нашей трэд-функции даже блоки try/finally нельзя использовать.
Очень сложно будет написать такой код. Или я ошибаюсь ?

>[17] Leonid Troyanovsky © (14.05.07 00:07)
>DH можно вызвать в вызывающем потоке - так потребуется
>передать лишь один параметр.
А ведь и правда !
Мне же пришлось вместе с ProcessID еще передавать адреса
самой DuplicateHandle, CloseHandle, OpenProcess и GetCurrentProcess.
Вот что значит торопиться.
Пойду переделывать.


Leonid Troyanovsky   (2007-05-14 00:16:00) [23]


> Riply ©   (13.05.07 21:43) [14]

VirtualProtectEx, IMHO, не стоит пренебрегать, бо,
в скором времени, MS может запретит исполнение кода в -rw.

--
Regards, LVT.


Leonid Troyanovsky   (2007-05-14 00:07:00) [24]


> Riply ©   (13.05.07 23:47) [15]

> Ответ прост - передать в тред-функцию hEvent и свой ProcessID,
>  а в ней использовать DuplicateHandle.

DH можно вызвать в вызывающем потоке - так потребуется
передать лишь один параметр.

--
Regards, LVT.


Leonid Troyanovsky   (2007-05-14 00:03:00) [25]


> Riply ©   (13.05.07 21:43) [14]

>           WaitForSingleObject(Proc_Info.hProcess, aTimeOut);
>  // ??? а вот здесь не знаю что и делать.

Тут, видимо, и следует использовать технику "двойного APC",
которую упоминал Slava.
Т.е., это, наверное, QUAPC, посылаемый вызывающему потоку
(который передал в качестве параметра свой tid),
и ожидающему своей очереди в an alertable state.
Также можно передать в параметре значение хендла event
(+ DuplicateHandle) для оповещения вызывающего потока.

Так как эти функции из kernel32, то их адреса можно
также передать из вызывающего потока.

Кста, в том, что APC вызывается в контексте первичного
потока, есть и свои преимущества, т.е., синхронизаций
при подменах не потребуется.

--
Regards, LVT.


Riply   (2007-05-13 23:47:00) [26]

>[14] Riply ©   (13.05.07 21:43)
>А как быть дальше ? (На строчке, помеченной "???")
Получилось :)
Ответ прост - передать в тред-функцию hEvent и свой ProcessID, а в ней использовать DuplicateHandle.
Теперь надо тестировать. Не очень мне нравиться этим заниматься :)


Riply   (2007-05-13 21:43:00) [27]

> [13] Leonid Troyanovsky ©   (13.05.07 11:01)
>Я немного поразмышлял над сказанным by Slava Usov,
>и пришел к выводу, что вот такая схема может быть
>весьма интересна:
Спасибо, за подсказку. Т.к. являюсь "оставляю заинтересованным лицом",
попробовала что-то изобразить и совсем запуталась :(
Делаю, примерно, так:
(Прошу не обращать внимание на попытки обращения к
возможно уже несуществующему процессу. Я там все исправлю... Потом.. Может быть..  :)
Это наша тестовая тред-функция:
function _RemoteThreadBase(pRem_Info: Pointer): DWord; stdcall;
var
 _MessageBoxA: function(hWnd: HWND; lpText, lpCaption: PChar; uType: UINT): Integer; stdcall;
begin
Result := 0;
with PRemoteThreadInfo(pRem_Info)^ do
 begin
  RetResult := 1;
  if hUser32 = 0 then hUser32 := _GetModuleHandleA(User32_Name);
  if hUser32 <> 0 then
   begin
    RetResult := 2;
    @_MessageBoxA := _GetProcAddress(hUser32, MessageBoxA_Name);
    if @_MessageBoxA <> nil then _MessageBoxA(0, MessageBoxA_Name, User32_Name, 0) else RetResult := 4;
   end
  else LastErr := _GetLastError;
 end;
end;

Сама попытка реализации:
function Proc_CreateAndWaitInitialization(const FName: string; aTimeOut: DWord): DWord;
var
Rem_Info: TRemoteThreadInfo;
Proc_Info: PROCESS_INFORMATION;
cbCodeSize: DWord;
pRemoteData, pRemoteThread: Pointer;
BaseThreadCode: TByte_Array;
begin
if Rem_Info.InitSelf then // Заполняет Rem_Info адресами функций из kernel32 и прочей ерундой :)
 begin
  FillChar(Proc_Info, SizeOf(PROCESS_INFORMATION), 0);
  if Process_Create(FName, '', ExtractFileDir(FName), CREATE_SUSPENDED, SW_SHOWDEFAULT, Proc_Info) then
   try
    pRemoteData := nil;
    if Proc_WriteMemory(Proc_Info.hProcess, @Rem_Info, SizeOf(TRemoteThreadInfo), MEM_COMMIT or MEM_RESERVE, PAGE_EXECUTE_READWRITE, pRemoteData) then
     try
      pRemoteThread := nil;
      cbCodeSize := _RemoteThreadBaseSize; // вычисляет размер кода нашей тред-функции
      SetLength(BaseThreadCode, cbCodeSize);
      CopyMemory(@BaseThreadCode[0], @_RemoteThreadBase, cbCodeSize);
      if Proc_WriteMemory(Proc_Info.hProcess, @BaseThreadCode[0], cbCodeSize, MEM_COMMIT or MEM_RESERVE, PAGE_EXECUTE_READWRITE, pRemoteThread) then
       try
        if QueueUserAPC(pRemoteThread, Proc_Info.hThread, DWord(pRemoteData)) then
         begin
          if ResumeThread(Proc_Info.hThread) <> MAXDWORD then Result := ERROR_SUCCESS else Result := GetLastError;
          WaitForSingleObject(Proc_Info.hProcess, aTimeOut); // ??? а вот здесь не знаю что и делать.
         end
        else Result := GetLastError;
       finally
        VirtualFreeEx(Proc_Info.hProcess, pRemoteThread, 0, MEM_RELEASE);
       end
      else Result := GetLastError
     finally
      VirtualFreeEx(Proc_Info.hProcess, pRemoteData, 0, MEM_RELEASE);
     end
    else Result := GetLastError
   finally
    ResumeThread(Proc_Info.hThread);
    ProcessInfo_CloseHandles(@Proc_Info);
   end
  else Result := GetLastError
 end
else Result := ERROR_BAD_ARGUMENTS;
end;

С грехом пополам удалось добиться запука целевого процесса и нашей нити в нем.
А как быть дальше ? (На строчке, помеченной "???")
Как нам дождаться завершения работы тред-функции ? Если бы мы ее создавали с помощью RemoteThread,
то все понятно - у нас был бы хэндл. А так где его взять ? Вытаскивать в тред-функцию что-то типа
PostThreadMessage очень некузяво, ибо придется подгружать доп. библиотеку (как с MessageBox),
да еще и организовывать нить ожидающую ответа.
Очень хочеться что-нибудь типа глобального(?) Event-а,
чтобы можно было просто использовать какую-нибудь из WaitFor... Как это можно сделать ?


Leonid Troyanovsky   (2007-05-13 11:01:00) [28]


> Riply ©   (12.05.07 14:09) [12]

> Пойду изучать ссылки.

Я немного поразмышлял над сказанным by Slava Usov,
и пришел к выводу, что вот такая схема может быть
весьма интересна:

var
  pi: TProcessInformation;
  si: TStartupInfo;
  p : Pointer;
  d:DWord;
begin
  Fillchar(si, SizeOf(si),0);
  ..

  CreateProcess(
       nil,
       'имя.exe',
       nil,
       nil,
       False,
       CREATE_SUSPENDED,
       nil,
       nil,
       si,
       pi
   );
   p:= VirtualAllocEx(pi.hProcess,..);
   {и пишем туда код  процедуры с таким прототипом:  
   procedure APCProc(dwParam: Dword); stdcall;}
   VirtualProtectEx(..);

   QueueUserAPC(p,  pi.hThread,    {параметр} ); // вот оно

   ResumeThread(pi.hThread);
   CloseHandle(pi.hThread);
   CloseHandle(pi.hProcess);
end;

Требования к коду APCProc такие же, как в случае с CRT.
APCProc должна сработать сразу после инициализации процесса.

Ну, а интересного в этой схеме то, что она, видимо, может
сработать даже в 9х (если подыскать замену VirtualAllocEx).

Правда, проверять мне все это влом,
оставляю заинтересованным лицам :)

--
Regards, LVT.


Riply   (2007-05-12 14:09:00) [29]

> [9] Leonid Troyanovsky © (12.05.07 10:33)
> По Рихтеру - это CreateRemoteThread?
Да. Мне этот способ очень нравится: "просто и со вкусом" :)
>Стоп-стоп.
>Сначала была речь про консольные приложения, ну,
>а если уж хуки, то WaitForInputIdle хватит вполне.
Хуки я использовала только, как тест закончена инициализация или нет.
Реально же работать с ними не очень удобно.
>И все же, какая поставлена цель?
>В самом общем случае тема не так уж и проста,
Ну, разумеется, цель была поставлена в "самом общем случае".
Мы же не хухры-мухры, а с Delphimaster.ru :)
Интересовала возможность "спокойной работы" с любым процессом
или обход некоторых ограничений налагаемых на DllMain - функцию.
Да и само "определения окончания инициализации процесса" имеет довольно широкую область применения.
Например, как тест, я пыталась загружать библиотеку, использующею SysUtils из AppInit_DLLs.
Эксперимент, в котором, подопытная DLL дожидается (разумеется, надуманным способом)
окончания инициализации целевого процесса и, только после этого,
инициализирует сама себя(некузяво выразилась, но не знаю, как сказать по-другому) прошел удачно.
А мне кажется, что уж если мы нормально грузимся из AppInit_DLLs,
то любой другой вид загрузки пройдет "на ура". :)
>Ну, тогда, видимо, можно запустить suspended,
>а по готовности потока отладчика - resume.
>Хотя, IMHO, это все излишне.
"это все излишне" - всегда приятно слышать, т.к. меньше головной боли :))
>Ну, если отлаживаемый процесс изменяет свою работу
>в зависимости от присутствия отладчика - то вносим.
>Но, это явно необычные приложения.
Боюсь, что этих "необычных приложений" гораздо больше, чем нам кажется :(
Сейчас все кому не лень защищают свои программы в т.ч. и от отладчика :)
P.S.
Пойду изучать ссылки.


Leonid Troyanovsky   (2007-05-12 12:23:00) [30]


> Leonid Troyanovsky ©   (12.05.07 10:33) [9]

> Кста,  в этом треде отметились не самые последние эксперты

См. также
http://groups.google.com/group/microsoft.public.win32.programmer.kernel/browse_thread/thread/1a2ec9ccd98d3979/77e944f5640757f3#77e944f5640757f3

--
Regards, LVT.


Leonid Troyanovsky   (2007-05-12 10:49:00) [31]


> Riply ©   (11.05.07 23:16) [8]

> Допустим, мы ждем загрузки ntdll.dll.
> А вдруг она успеет загрузиться до первого вызова WaitForDebugEvent
> ?
> Как можно узнать, что она уже там и ждать нет смысла ?

Ты ж стартуешь процесс в отладочном режиме.
Или ты не уверена в готовности потока отладчика?
Ну, тогда, видимо, можно запустить suspended,
а по готовности потока отладчика - resume.
Хотя, IMHO, это все излишне.


> Меня еще смущает, что мы вынуждены стартовать процесс DEBUG_.
> .. флагами.
> Не сказывается ли это на его "нормальной работе", т.е. вносим
> ли мы, какие то коррективы в его поведение ?

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

Кста, установка хуков - это тоже отладочная техника,
т.е. тоже меняет поведение.

> Попытка стартовать его "нормально" и использовать DebugActiveProcess

IMHO, сразу отлаживать предпочтительней, он полностью под контролем.

--
Regards, LVT.


Leonid Troyanovsky   (2007-05-12 10:33:00) [32]


> Riply ©   (11.05.07 23:16) [8]

> Если внедряться "по Рихтеру", по после события загрузки
> ntdll.dll или
> "искомой библиотеки"(если она не ntdll.dll) у меня ошибок
> не возникало.

По Рихтеру - это CreateRemoteThread?

> Но, если использовать хуки, то наступает Ваш случай:

Стоп-стоп.
Сначала была речь про консольные приложения, ну,
а если уж хуки, то WaitForInputIdle хватит вполне.

И все же, какая поставлена цель?
В самом общем случае тема не так уж и проста,
см., например
http://groups.google.com/group/microsoft.public.win32.programmer.kernel/browse_thread/thread/a2a16a5753f49252/c49a244670a56d2b#c49a244670a56d2b

Кста,  в этом треде отметились не самые последние эксперты
по подобным вопросам.

--
Regards, LVT.


Riply   (2007-05-11 23:16:00) [33]

>[7] Leonid Troyanovsky © (10.05.07 22:44)
>> Или WaitForDebugEvent - решит все проблемы ? (Я еще не успела его посмотреть)
>Там есть на что посмотреть :)
И, правда: там много интересного :)
>Хотя, возможно, что для подмен достаточен лишь факт загрузки искомой библиотеки.
Практика показала, что это зависит от способа внедрения.
Если внедряться "по Рихтеру", по после события загрузки ntdll.dll или
"искомой библиотеки"(если она не ntdll.dll) у меня ошибок не возникало.
Но, если использовать хуки, то наступает Ваш случай:
>Насчет решения всех проблем не уверен, бо допускаю, что
>правильная инициализация знаменуется загрузкой последней
>из потребных библиотек.
К сожалению, Вы, как всегда, правы :( ("К сожалению" - в смысле, что это действительно так :)
Пыталась внедриться после загрузки следующих библиотек:
USER32.dll, ADVAPI32.dll, comctl32.dll (дальше экспериментировать уже не видела смысла).
if CreateProcess(PChar(aFileName), ..., CREATE_DEFAULT_ERROR_MODE or
                DEBUG_PROCESS or DEBUG_ONLY_THIS_PROCESS,...) then
try
 while WaitForDebugEvent(DbgEvent, INFINITE) do
  begin
   case DbgEvent.dwDebugEventCode of
    EXIT_PROCESS_DEBUG_EVENT: Break;
    LOAD_DLL_DEBUG_EVENT:
     begin
      Get_ModuleFileName(DWord(DbgEvent.LoadDll.lpBaseOfDll), LibName);
      if CompareText(LibName, WaitLibName) = 0 then
       begin
        ContinueDebugEvent(DbgEvent.dwProcessId, DbgEvent.dwThreadid, DBG_CONTINUE);
        Result := DebugActiveProcessStop(DbgEvent.dwProcessId);
//         WaitForInputIdle(Proc_Info.hProcess, INFINITE);
        InjectError := Hook_SetThread(WH_SHELL, Proc_Info.dwThreadID);
        Break;
       end;
     end;
  end;
  ContinueDebugEvent(DbgEvent.dwProcessId, DbgEvent.dwThreadid, DBG_CONTINUE);
 end;
finally
 ProcessInfo_CloseHandles(@Proc_Info);
end;

Результат один и тот же: Хуком внедриться не удается.(только используя WaitForInputIdle)
Отсюда вопрос: какого события нужно дождаться, чтобы знать, что процесс закончил свою инициализацию
(вне зависимости, от того он "have a message queue" или нет)?
И еще несколько вопросов:
Допустим, мы ждем загрузки ntdll.dll.
А вдруг она успеет загрузиться до первого вызова WaitForDebugEvent ?
Как можно узнать, что она уже там и ждать нет смысла ?
Меня еще смущает, что мы вынуждены стартовать процесс DEBUG_... флагами.
Не сказывается ли это на его "нормальной работе", т.е. вносим ли мы, какие то коррективы в его поведение ?
Попытка стартовать его "нормально" и использовать DebugActiveProcess показала,
что для этого способа, сначала нужно дождаться "правильной инициализации процесса" :)


Leonid Troyanovsky   (2007-05-10 22:44:00) [34]


> Riply ©   (10.05.07 22:37) [5]

> Или WaitForDebugEvent - решит все проблемы ? (Я еще не успела
> его посмотреть)

Там есть на что посмотреть :)

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

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

--
Regards, LVT.


Riply   (2007-05-10 22:38:00) [35]

Удалено модератором
Примечание: оффтоп


Riply   (2007-05-10 22:37:00) [36]

> [4] Leonid Troyanovsky ©   (10.05.07 22:30)
>Полную гарантию дает только страховой полис ;)
Ну, мне достаточто вероятности сбоя 1/10^39 :)
>Но, из практики WaitForInutIdle имеет смысл только
>для "оконных"  (не полноэкранных) консолей.
А чем, тогда его (WaitForInutIdle) можно заменить ?
Или WaitForDebugEvent - решит все проблемы ? (Я еще не успела его посмотреть)


Leonid Troyanovsky   (2007-05-10 22:30:00) [37]


> Riply ©   (10.05.07 22:25) [2]

> >Если консоль запущена в окне, то дожидается.
> Т.е. я могу быть уверена, что, при таком подходе,
> я случайно не начну внедрятся "раньше чем можно" ? :)

Полную гарантию дает только страховой полис ;)

Но, из практики WaitForInutIdle имеет смысл только
для "оконных"  (не полноэкранных) консолей.

--
Regards, LVT.


homm   (2007-05-10 22:29:00) [38]

Удалено модератором
Примечание: оффтоп


Riply   (2007-05-10 22:25:00) [39]

> [1] Leonid Troyanovsky ©   (10.05.07 22:18)
>Если консоль запущена в окне, то дожидается.
Т.е. я могу быть уверена, что, при таком подходе,
я случайно не начну внедрятся "раньше чем можно" ? :)
>IMHO, наверное, WaitForDebugEvent нужной длл.
Спасибо.


Leonid Troyanovsky   (2007-05-10 22:18:00) [40]


> Riply ©   (10.05.07 22:07)  

> Объясните мне пожалуйста WaitForInputIdle (в случае, например,
>  консоли)
> дожидается "the specified process has finished its initialization",
>  или "returns immediately" ?

Если консоль запущена в окне, то дожидается.

> Я понимаю, что это можно сделать с помощью, например RegisterWaitForSingleObject,

IMHO, наверное, WaitForDebugEvent нужной длл.

--
Regards, LVT.


Страницы:   1 2

Наверх

Титульная страница Поиск, карта сайта Написать письмо