NTLDR: APC, CTX.

Тема в разделе "WASM.WIN32", создана пользователем Clerk, 15 авг 2010.

  1. Clerk

    Clerk Забанен

    Публикаций:
    0
    Регистрация:
    4 янв 2008
    Сообщения:
    6.689
    Адрес:
    РБ, Могилёв
    Эта тема требующая подробного рассмотрения. Не буду описывать подробно работу загрузчика, опишу лишь главные моменты в его использовании при манипуляциях контекстом потока, которые здесь часто спрашивают. Итак матчасть.
    -
    Ощий механизм вызова загрузчика вновь созданными потоками.
    Поток начинает исполнять стартап-APC, её ставит в очередь потока ядро, диспетчер KiUserApcDispatcher(), APC LdrInitializeThunk(). Прототип диспетчера:
    Код (Text):
    1. ;++
    2. ;
    3. ; VOID
    4. ; KiUserApcDispatcher (
    5. ;    IN PKNORMAL_ROUTINE NormalRoutine,
    6. ;    IN PVOID NormalContext,
    7. ;    IN PVOID SystemArgument1,
    8. ;    IN PVOID SystemArgument2,
    9. ;    IN CONTEXT ContinueContext
    10. ;    )
    11. ;
    12. ; Routine Description:
    13. ;
    14. ;    This routine is entered on return from kernel mode to deliver an APC
    15. ;    in user mode. The context frame for this routine was built when the
    16. ;    APC interrupt was processed and contains the entire machine state of
    17. ;    the current thread. The specified APC routine is called and then the
    18. ;    machine state is restored and execution is continued.
    19. ;
    20. ; Arguments:
    21. ;
    22. ;    NormalRoutine - Supplies that address of the function that is to be called.
    23. ;
    24. ;    NormalContext] - Supplies the normal context parameter that was specified
    25. ;       when the APC was initialized.
    26. ;
    27. ;    SystemArgument1 - Supplies the first argument that was provied by the
    28. ;       executive when the APC was queued.
    29. ;
    30. ;    SystemArgument2 - Supplies the second argument that was provided by
    31. ;       the executive when the APC was queued.
    32. ;
    33. ;    ContinueContext - Context record to pass to Continue call.
    34. ;
    35. ;
    36. ; Return Value:
    37. ;
    38. ;    None.
    39. ;
    40. ;--
    41. cPublicProc _KiUserApcDispatcher ,5
    42.  
    43.         lea     edi, [esp+16]           ; (edi)->context frame
    44.         pop     eax                     ; (eax)->specified function
    45.         call    eax                     ; call the specified function
    46.  
    47. ; 1 - set alert argument true
    48. ; ebp - addr of context frame
    49. ; execute system service to continue
    50.         stdCall   _ZwContinue, <edi, 1>
    51.  
    52. stdENDP _KiUserApcDispatcher
    Для стартап апк параметры следующие:
    NormalRoutine = @LdrInitializeThunk(), NormalContext = NULL, SystemArgument1 - база загрузки ntdll.dll, SystemArgument2 = NULL.
    Прототип LdrInitializeThunk():
    Код (Text):
    1. VOID
    2. LdrInitializeThunk(
    3.    IN PVOID NormalContext,
    4.    IN PVOID SystemArgument1,
    5.    IN PVOID SystemArgument2
    6.    )
    Где NormalContext = NULL, SystemArgument1 - база загрузки ntdll, SystemArgument2 = NULL. Тоесть при входе в LdrInitializeThunk() в стеке будет структура:
    Код (Text):
    1. LDR_THUNK_FRAME struct
    2.    ReturnAddress   PVOID ?  ; ~KiUserApcDispatcher()
    3.    NormalContext   PCONTEXT ?   ; NULL
    4.    NtdllHandle        PVOID ?
    5.    Reserved          DWORD ?
    6.    Context            CONTEXT <>
    7. 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
     
    M0rg0t и Cytrus нравится это.