Помогите, пожалуйста: создание потока из драйвера

Тема в разделе "WASM.NT.KERNEL", создана пользователем NDIS, 23 ноя 2008.

  1. NDIS

    NDIS NDIS

    Публикаций:
    0
    Регистрация:
    16 авг 2007
    Сообщения:
    41
    Адрес:
    Москва
    Привет!
    Вот возникла задача создать поток из ядра в произвольном процессе. Чтобы было понятнее, нужно реализовать аналог RtlCreateUserThread(), ибо сама RtlCreateUserThread(), к сожалению, не экспортируется. Сейчас делаю так:

    0. Вешаю callback на создание процессов и пытаюсь внедриться в каждый из них.
    1. Создаю системный поток - PsCreateSystemThread().
    2. В потоке открываю процесс по ID и получаю его хендл.
    3. Создаю стек потока. Примерно так:

    Код (Text):
    1. typedef struct _INITIAL_TEB
    2. {
    3.     PVOID                   PreviousStackBase;
    4.     PVOID                   PreviousStackLimit;
    5.     PVOID                   StackBase;
    6.     PVOID                   StackLimit;
    7.     PVOID                   StackAllocationBase;
    8.     PVOID                   StackCommit;
    9.     PVOID                   StackCommitMax;
    10.     PVOID                   StackReserved;
    11. }
    12. INITIAL_TEB, *PINITIAL_TEB;
    13.  
    14. ...
    15.  
    16.     PBYTE pStack = NULL;
    17.     SIZE_T RegionSize = 0;
    18.  
    19.     if (CommittedStackSize == 0) CommittedStackSize = PAGE_SIZE;
    20.     if (MaximumStackSize == 0) MaximumStackSize = MM_ALLOCATION_GRANULARITY;
    21.  
    22.    if (CommittedStackSize >= MaximumStackSize)
    23.     {
    24.         MaximumStackSize = ROUND_UP (CommittedStackSize, (1024 * 1024));
    25.     }
    26.  
    27.     CommittedStackSize = ROUND_UP (CommittedStackSize, PAGE_SIZE);
    28.     MaximumStackSize = ROUND_UP (MaximumStackSize, MM_ALLOCATION_GRANULARITY);
    29.  
    30.     pStack = NULL;
    31.  
    32.     status = ZwAllocateVirtualMemory (
    33.         hProcess,
    34.         (PVOID*) &pStack,
    35.         0,
    36.         &MaximumStackSize,
    37.         MEM_RESERVE,
    38.         PAGE_READWRITE);
    39.  
    40.     if (! NT_SUCCESS (status)) { ... }
    41.  
    42.     pInitialTeb -> PreviousStackBase = NULL;
    43.     pInitialTeb -> PreviousStackLimit = NULL;
    44.     pInitialTeb -> StackAllocationBase = pStack;
    45.     pInitialTeb -> StackBase = pStack + MaximumStackSize;
    46.  
    47.     pStack += (MaximumStackSize - CommittedStackSize);
    48.  
    49.     if (MaximumStackSize > CommittedStackSize)
    50.     {
    51.         pStack -= PAGE_SIZE;
    52.         CommittedStackSize += PAGE_SIZE;
    53.     }
    54.  
    55.     status = ZwAllocateVirtualMemory (
    56.         hProcess,
    57.         (PVOID*) &pStack,
    58.         0,
    59.         &CommittedStackSize,
    60.         MEM_COMMIT,
    61.         PAGE_READWRITE);
    62.  
    63.     if (! NT_SUCCESS (status)) { ... }
    64.  
    65.     pInitialTeb -> StackLimit = pStack;
    В итоге в pInitialTeb -> StackBase имеем начало стека.

    4. Затем создаю контекст:

    Код (Text):
    1.     PCONTEXT pContext = NULL;
    2.     PCONTEXT pMappedContext = NULL;
    3.     SIZE_T uRegionSize = sizeof (CONTEXT);
    4.     PMDL pMdl = NULL;
    5.  
    6.     pContext = (PCONTEXT) ExAllocatePool (NonPagedPool, sizeof (CONTEXT));
    7.     if (! pContext) { ... }
    8.  
    9.     pMdl = IoAllocateMdl (pContext, sizeof (CONTEXT), FALSE, FALSE, NULL);
    10.     if (! pMdl) { ... }
    11.  
    12.     MmBuildMdlForNonPagedPool (pMdl);
    13.  
    14.     pMappedContext = (PCONTEXT) MmMapLockedPagesSpecifyCache (pMdl, UserMode, MmCached, NULL, 0, NormalPagePriority);
    15.     if (! pMappedContext) { ... }
    16.  
    17.     pContext -> Eax = 0;
    18.     pContext -> Ebx = 0;
    19.     pContext -> Ecx = 0;
    20.     pContext -> Edx = 0;
    21.     pContext -> Esi = 0;
    22.     pContext -> Edi = 0;
    23.     pContext -> Ebp = 0;
    24.  
    25.     pContext -> SegGs = 0;
    26.     pContext -> SegFs = 0x38;
    27.     pContext -> SegEs = 0x23;
    28.     pContext -> SegDs = 0x23;
    29.     pContext -> SegSs = 0x23;
    30.     pContext -> SegCs = 0x1B;
    31.  
    32.     pContext -> EFlags = 0x3000;
    33.  
    34.     pContext -> Esp = (ULONG) pStackBase;
    35.     pContext -> Eip = (ULONG) pStartAddress;
    36.  
    37.     pContext -> ContextFlags = CONTEXT_FULL;
    38.  
    39.     pContext -> Esp -= sizeof (pParameter);
    40.  
    41.     status = NtWriteVirtualMemory (
    42.         hProcess,
    43.         (PVOID) pContext -> Esp,
    44.         (PVOID) &pParameter,
    45.         sizeof (pParameter),
    46.         NULL);
    47.  
    48.     if (! NT_SUCCESS (status)) { ... }
    49.  
    50.     pContext -> Esp -= sizeof (pParameter);
    pMappedContext в итоге передаётся в NtCreateThread().

    5. Записываю код в а.п. целевого процесса. Пока пишу тупо однобайтовый ret, чтобы хотя бы проверить работоспособность кода:

    Код (Text):
    1.     PBYTE pCode = NULL;
    2.     BYTE Code [1] = { 0xC3 /* ret */ };
    3.     SIZE_T uRegionSize = PAGE_SIZE;
    4.  
    5.     status = ZwAllocateVirtualMemory (
    6.         hProcess,
    7.         &pCode,
    8.         0,
    9.         &uRegionSize,
    10.         MEM_RESERVE | MEM_COMMIT,
    11.         PAGE_EXECUTE_READWRITE);
    12.  
    13.     if (! NT_SUCCESS (status)) { ... }
    14.  
    15.     status = NtWriteVirtualMemory (
    16.         hProcess,
    17.         pCode,
    18.         Code,
    19.         sizeof (Code),
    20.         NULL);
    21.  
    22.     if (! NT_SUCCESS (status)) { ... }
    6. Создаю поток:

    Код (Text):
    1.     CLIENT_ID ClientId = {0};
    2.     HANDLE hThread = NULL;
    3.     OBJECT_ATTRIBUTES ThreadAttributes = {0};
    4.  
    5.     InitializeObjectAttributes (
    6.         &ThreadAttributes,
    7.         NULL,
    8.         OBJ_KERNEL_HANDLE,
    9.         NULL,
    10.         NULL);
    11.    
    12.     status = NtCreateThread (
    13.         &hThread,
    14.         THREAD_ALL_ACCESS,
    15.         &ThreadAttributes,
    16.         hProcess,
    17.         &ClientId,
    18.         pContext,
    19.         pInitialTeb,
    20.         FALSE);
    21.  
    22.     if (! NT_SUCCESS (status)) { ... }
    7. Задержка примерно полсекунды.
    8. Завершение потока - NtTerminateThread().
    9. Освобождение стека, кода и контекста.

    Обратите внимание, что на шаге 6 поток создаётся в подвешенном состоянии и не начинает исполнятся. При этом, заметьте, все вызовы во всех случаях отрабатывают успешно, возвращая STATUS_SUCCESS.

    Что ожидалось в случае, если всё было бы запрограммировано правильно: приложения бы падали с access violation, т.к. записанная инструкция ret возвращала бы управление на невалидный адрес, по-хорошему если, то в потоке нужно вызывать что-то вроде NtTerminateThread (NtCurrentThread ());, но это пока тестовый код, думаю, сейчас это делать необязательно. Сейчас в результате получаю крайне странное поведение. На Windows Server 2003 SP1 процессы запускаются и сразу же тихо падают, хотя по идее код ещё даже не начал выполнятся! На Windows XP SP3 всё работает, вроде бы, как и полагается - процессы запускаются и выполняются далее нормально.

    Далее если я таки запускаю поток после создания, то ситуация немного меняется. На Windows XP примерно в 10% случаев иногда приложения таки запускаются, а в остальном вылазит вот такое сообщение об ошибке: "Ошибка при инициализации приложения (0xC0000142). Для выхода из приложения нажмите кнопку ОК.". На Windows Server 2003 по-прежнему либо тихо падает сразу при запуске либо тоже самое сообщение об ошибке. Код 0xC0000142 - это

    Код (Text):
    1. // {DLL Initialization Failed}
    2. // Initialization of the dynamic link library %hs failed. The process is terminating abnormally.
    3. //
    4. #define STATUS_DLL_INIT_FAILED           ((NTSTATUS)0xC0000142L)
    В WinDbg ставил бряк на виртуальный адрес кода, записанного на шаге 5, и бряк ни разу не сработал, значит код вообще не вызывается. С чем это может быть связано - не понимаю. Вроде всё сделал как вот тут советуют - http://www.wasm.ru/forum/viewtopic.php?id=24967 и всё равно не пашет. Может кто знает, что не так?
     
  2. CrystalIC

    CrystalIC New Member

    Публикаций:
    0
    Регистрация:
    26 июл 2008
    Сообщения:
    500
    Трудно сказать почему не работает, могу предположить.
    Ядро при создании потока ставит в его очередь событие, поток начнёт его обрабатывать - с этой точки он и начнёт исполняться в юзермоде. Это KiUserApcDispatcher(), а событие LdrInitializeThunk():
    Код (Text):
    1. PspUserThreadStartup(
    2.     [...]
    3.     KiInitializeUserApc (PspGetBaseExceptionFrame (Thread),
    4.                              PspGetBaseTrapFrame (Thread),
    5.                              PspSystemDll.LoaderInitRoutine,    ;LdrInitializeThunk
    6.                              NULL,
    7.                              PspSystemDll.DllBase,
    8.                              NULL);
    В XPSP2 немного иначе, но это не меняет сути. Именно этот код и будет выполнен вначале.
    Код (Text):
    1. KiUserApcDispatcher:
    2.     lea edi,dword ptr [esp+10]
    3.     pop eax
    4.     call eax
    5.     push 1
    6.     push edi
    7.     call ZwContinue
    В стеке будут параметры, переданные в KiInitializeUserApc. Тоесть в регистр Eax будет загружен указатель на LdrInitializeThunk(), а эта функция весьма тяжёлая, именно из неё поток обходит все EntryPoint модулей с причиной DLL_THREAD_ATTACH и подгружает kernel32.dll. Затем только по возврату из этой функции(если успешно прошло) поток перейдёт посредством NtContinue на код, указатель на который был в регистре Eip контекста. Так как гдето возникает ошибка, возврат из процедуры не происходит, поэтому не срабатывает бряк. Следует поставить точку останова ниже, например в самом начале на KiUserApcDispatcher() и посмотреть состояние контекста потока и стека.
     
  3. zorro

    zorro New Member

    Публикаций:
    0
    Регистрация:
    8 ноя 2008
    Сообщения:
    33
    NDIS, вы тут очепятались: pContext -> SegFs = 0x38;
     
  4. CrystalIC

    CrystalIC New Member

    Публикаций:
    0
    Регистрация:
    26 июл 2008
    Сообщения:
    500
    zorro
    Это не важно, RPL = 0, но в 7-м дескрипторе GDT DPL = 3, поэтому юзермодный код свободно обращатсо может, темболее что первый сервис перезагрузит селектор в 0x3B.
     
  5. NDIS

    NDIS NDIS

    Публикаций:
    0
    Регистрация:
    16 авг 2007
    Сообщения:
    41
    Адрес:
    Москва
    Э, ну вообще-то у меня так и написано вроде как. Или там должно быть не 0x38 ?
     
  6. wasm_test

    wasm_test wasm test user

    Публикаций:
    0
    Регистрация:
    24 ноя 2006
    Сообщения:
    5.582
    ну указано то место с неточностью)) если бы должно было быть 38, то не указывали бы.
    У 38 уровень привилегий в селекторе 0, а у 3B - 3. Хорошо хоть индексы одинаковые
    но как сказал CrystalIC, это не важно
     
  7. CrystalIC

    CrystalIC New Member

    Публикаций:
    0
    Регистрация:
    26 июл 2008
    Сообщения:
    500
    Great
    Из селектора игнорируетсо если PL_GDT > PL_SEL, проверь сам.

    Great: исправил у себя "ошибка" на "неточность", но по-моему, ты слишком придираешься к словам )
     
  8. CrystalIC

    CrystalIC New Member

    Публикаций:
    0
    Регистрация:
    26 июл 2008
    Сообщения:
    500
    NDIS
    Ну что брякался на KiUserApcDispatcher() ?
    Что в стеке и контекст потока какой ?
     
  9. wasm_test

    wasm_test wasm test user

    Публикаций:
    0
    Регистрация:
    24 ноя 2006
    Сообщения:
    5.582
    В асе топикстартера просил бряки на NtTerminateThread + call stack, бряк на KiUserExceptionDispatcher + call stack. Это в тему того, что в 2003 сервер у него молча вылетают новоиспеченные процессы после загрузки дрова.
    В XP у него заработало, как выяснилось, если его чудные махинации с reserve/commit заменить на полный коммит всего стека.
     
  10. asd

    asd New Member

    Публикаций:
    0
    Регистрация:
    12 мар 2005
    Сообщения:
    952
    Адрес:
    Russia
    NDIS
    Можешь сделать так:
    1. Внедрить код в ап целевого процесса
    2. В kernel32 поменять в таблице импорта указатели на ф-ии из ntdll на свой код. При первом же вызове ф-ий ntdll из kernel32 внедрённый код получит управление. Из него уже и запускай поток обычными юзермодными ф-ми.
     
  11. wasm_test

    wasm_test wasm test user

    Публикаций:
    0
    Регистрация:
    24 ноя 2006
    Сообщения:
    5.582
    asd
    Хорошая кстати мысль.)

    Но с топикстартером мы вроде разобрались, вроде поток запускается.) нативный, правда.
     
  12. CrystalIC

    CrystalIC New Member

    Публикаций:
    0
    Регистрация:
    26 июл 2008
    Сообщения:
    500
    Эм.. Второй пост наверно я для себя запостил)
     
  13. wasm_test

    wasm_test wasm test user

    Публикаций:
    0
    Регистрация:
    24 ноя 2006
    Сообщения:
    5.582
    CrystalIC
    ?

    пробема была у него в том, что он забыл сделать аттач к процессу когда проецировал в юзермод
     
  14. katrus

    katrus New Member

    Публикаций:
    0
    Регистрация:
    7 мар 2007
    Сообщения:
    612
    Понравилась идея! Только один вопрос - как из драйвера найти таблицу импорта?
     
  15. asd

    asd New Member

    Публикаций:
    0
    Регистрация:
    12 мар 2005
    Сообщения:
    952
    Адрес:
    Russia
    К примеры из приложения, которое грузит драйвер, передать ему базу kernel32
     
  16. katrus

    katrus New Member

    Публикаций:
    0
    Регистрация:
    7 мар 2007
    Сообщения:
    612
    Не всегда есть это приложение из которого драйвер загрузился.
     
  17. katrus

    katrus New Member

    Публикаций:
    0
    Регистрация:
    7 мар 2007
    Сообщения:
    612
    Еще один момент. В качестве отправной точки пытаюсь вызвать RtlCreateUserThread (адрес подсмотрел в IDA). Вызов происходит в обработчике DeviceControlIo. RtlCreateUserThread возвращает 0 (т.е., все в порядке), но на самом деле ничего не происходит. Чего еще не хватает?
    Код (Text):
    1. RtlCreateUserThread(NtCurrentProcess(),
    2.                  NULL,
    3.                  FALSE,
    4.                  0,
    5.                  0,
    6.                  0,
    7.                  (PVOID)0x00418B20, // функция вызывающая MessageBox
    8.                  NULL,
    9.                  NULL,
    10.                  NULL);