Эта тема требующая подробного рассмотрения. Не буду описывать подробно работу загрузчика, опишу лишь главные моменты в его использовании при манипуляциях контекстом потока, которые здесь часто спрашивают. Итак матчасть. - Ощий механизм вызова загрузчика вновь созданными потоками. Поток начинает исполнять стартап-APC, её ставит в очередь потока ядро, диспетчер KiUserApcDispatcher(), APC LdrInitializeThunk(). Прототип диспетчера: Код (Text): ;++ ; ; VOID ; KiUserApcDispatcher ( ; IN PKNORMAL_ROUTINE NormalRoutine, ; IN PVOID NormalContext, ; IN PVOID SystemArgument1, ; IN PVOID SystemArgument2, ; IN CONTEXT ContinueContext ; ) ; ; Routine Description: ; ; This routine is entered on return from kernel mode to deliver an APC ; in user mode. The context frame for this routine was built when the ; APC interrupt was processed and contains the entire machine state of ; the current thread. The specified APC routine is called and then the ; machine state is restored and execution is continued. ; ; Arguments: ; ; NormalRoutine - Supplies that address of the function that is to be called. ; ; NormalContext] - Supplies the normal context parameter that was specified ; when the APC was initialized. ; ; SystemArgument1 - Supplies the first argument that was provied by the ; executive when the APC was queued. ; ; SystemArgument2 - Supplies the second argument that was provided by ; the executive when the APC was queued. ; ; ContinueContext - Context record to pass to Continue call. ; ; ; Return Value: ; ; None. ; ;-- cPublicProc _KiUserApcDispatcher ,5 lea edi, [esp+16] ; (edi)->context frame pop eax ; (eax)->specified function call eax ; call the specified function ; 1 - set alert argument true ; ebp - addr of context frame ; execute system service to continue stdCall _ZwContinue, <edi, 1> stdENDP _KiUserApcDispatcher Для стартап апк параметры следующие: NormalRoutine = @LdrInitializeThunk(), NormalContext = NULL, SystemArgument1 - база загрузки ntdll.dll, SystemArgument2 = NULL. Прототип LdrInitializeThunk(): Код (Text): VOID LdrInitializeThunk( IN PVOID NormalContext, IN PVOID SystemArgument1, IN PVOID SystemArgument2 ) Где NormalContext = NULL, SystemArgument1 - база загрузки ntdll, SystemArgument2 = NULL. Тоесть при входе в LdrInitializeThunk() в стеке будет структура: Код (Text): LDR_THUNK_FRAME struct ReturnAddress PVOID ? ; ~KiUserApcDispatcher() NormalContext PCONTEXT ? ; NULL NtdllHandle PVOID ? Reserved DWORD ? Context CONTEXT <> LDR_THUNK_FRAME ends На время обработки APC контекст находится в стеке и при возврате из APC он будет загружен в процессор посредством сервиса NtContinue, причём доставка APC разрешена для этого сервиса. Далее в любой момент, пока поток выполняет код загрузчика доступ к контексту может быть выполнен посредством поиска последнего фрейма в цепочке стековых фреймов(SFC). Код NT построен таким образом, что в любой момент времени регистр Ebp контекста адресует конец SFC, это также верно если поток простаивает(исполнение его отложено шедулером, либо он выполняет обработку сервиса/ISR). После отработки APC и загрузки контекста в процессор выполняется переход на стаб http://www.wasm.ru/forum/viewtopic.php?pid=284176#p284176. В конце отработки загрузчика выполняется очистка очереди APC потока посредством вызова NtTestAlert, при этом все APC доставляются при освобождённой кс LdrpLoaderLock. Каждое критичное действие, а в общем и всякое обращение к загрузчику выполняется синхронно с остальными потоками. Для этого служит критическая секция LdrpLoaderLock. Ссылка на неё находится в PEB, так как она находится в ntdll, а этот модуль во всех процессах проецируется на одинаковые адреса, то во всех процессах адрес этой кс одинаков и для доступа к ней достаточно определить адрес её в текущем процессе. У критических секций есть свойство позволяющее избежать деадлоков, а именно допустимы повторные входы потока в критическую секцию. При этом увеличивается счётчик захватов кс, далее поток работает нормально. Этот факт позволяет загружать модуля из DllMain(). Из этого следуют важные выводы: o Невозможно из юзермода изменить стартап APC, но можно поставить в очередь пользовательские апк. Для перехвата потока на первой инструкции диспетчера можно выполнить редирект на диспетчер исключений http://www.wasm.ru/forum/viewtopic.php?pid=366509#p366509. o Пользовательские APC доставляются после отработки загрузчика и освобождения кс LdrpLoaderLock. При этом возможен вызов загрузчика например для загрузки модулей. o Если поставить из первой APC есчо одну, то она будет доставлена при вызове сервиса NtContinue, до перехода на стартовый адрес потока/модуля. o Стартовый адрес потока/модуля находится в контексте. Контекст находится в конце SFC(описано выше), http://www.wasm.ru/forum/viewtopic.php?id=38284. o Из этой APC может быть запущен поток, который тутже начнёт исполняться. Иначе при создании потока ранее, чем будет освобождена кс поток будет ждать её освобождение, причём ожидание не алертабельно и пока поток ждёт, APC в его контексте не может быть доставлена. Поток выйдет из ожидания как только первый поток покинет загрузчик, освободив кс. o При завершении потока захватившего эту кс деадлока не произойдёт, так как ядро её освободит. - LdrInitializeThunk() в начале работы загружает ссылку на контекст в LDR_THUNK_FRAME. После этого выполняется переход на загрузчик LdrpInitialize(). Первое его действие - инициализация куков в InitSecurityCookie(). Куки это несколько переменных(__security_cookie и пр.) содержащих рандомное значение и служат для защиты стековых фреймов от переполнений(в начале работы функций куки сохраняются в стеке, в конце работы функции извлекаются из стека и сравниваются с оригинальным значением, если они не совпадают то возникло переполнение буфера и генерируется исключение STATUS_STACK_BUFFER_OVERRUN в __report_gsfailure()). Хак с использованием куков для захвата потока описан тут http://files.virustech.org/indy/Code/IDP/Test/Other/Cookie/. - После инициализации куков начинает исполняться непосредственно загрузчик в __LdrpInitialize(). Если поток первый в процессе, то инициализируется окружение: кс, читаются ключи загрузчика, создаётся база данных, подгружается kernel32.dll, импорт, инициализируется хип и пр. После этого вызывается LdrpInitializeThread(), как и в случае если поток не первый(загрузчик инициализирован). Эта функция выполняет инициализацию TLS, активацию SXS и нотификацию модулей с причиной DLL_PROCESS_ATTACH. - Важные действия загрузчик может логгировать в виде отладочного вывода, он имеет префикс "LDR". За этого отвевают некоторые переменные, достаточны следующие: o ShowSnaps - переменная включает основное логгирование. Загружается из конфига в реестре, как флаг FLG_SHOW_LDR_SNAPS. Поиск и использование этой переменной можно посмотреть тут: http://files.virustech.org/indy/Code/IDP/Test/Other/Snaps/ http://files.virustech.org/indy/Code/LDR/Exts/ http://www.wasm.ru/forum/viewtopic.php?pid=391581#p391581 http://www.wasm.ru/forum/viewtopic.php?id=37208 o ShowErrors - включает логгирование ошибок. По большей части эти две переменные эквивалентны. o LdrpShowInitRoutines - включает логгирование нотификаций. Механизм отладочного вывода это доставка исключений на отладочный порт DBG_PRINTEXCEPTION_C, либо локально в процессе при его отсутствии. Этот механизм позволяет отслеживать и управлять загрузчиком на самом низком уровне. - Некоторые важные переменные: o LdrpInLdrInit - установлена в TRUE если выполняется инициализация загрузчика. Сбрасывается сразу перед освобождением кс LdrpLoaderLock. o LdrpShutdownInProgress - устанавливается в TRUE когда выполняется завершение процесса. Это вызов LdrShutdownProcess(), при этом кс захвачена. o LdrpLdrDatabaseIsSetup - устанавливается в TRUE после завершения инициализации загрузчика. o LdrpShutdownThreadId - TID потока, завершающего процесс. o LdrpLoadedDllHandleCache - загрузочный кеш. Сюда сохраняется ссылка на описатель последнего модуля(LDR_DATA_TABLE_ENTRY), для которого была выполнена валидация хэндла. o LdrpGetModuleHandleCache - тоже, но для кеширования поиска описателя по имени в базе данных. o LdrpImageEntry - ссылка на описатель экзешника. o LdrpNtDllDataTableEntry - ссылка на описатель ntdll.dll. o RtlpStkNtdllStart/RtlpStkNtdllEnd - база и лимит проекции ntdll. o RtlpStkDllRanges - битмапа, используется для быстрой проверки принадлежности адреса модулю в RtlpStkIsPointerInDllRange(). Незаменимый хидер тут http://files.virustech.org/indy/wrk/ntldr.h