RtlCreateUserThread и LoadLibrary

Тема в разделе "WASM.NT.KERNEL", создана пользователем katrus, 12 янв 2009.

  1. katrus

    katrus New Member

    Публикаций:
    0
    Регистрация:
    7 мар 2007
    Сообщения:
    612
    Это сложо? Есть примеры?

    Бррр. Слово "создать" три раза подряд. Не понял кто кого создает.

    P.S. кстати, черм АРС принципиально хуже для инжекта? Какие у него могут быть подводные камни?
     
  2. n0name

    n0name New Member

    Публикаций:
    0
    Регистрация:
    5 июн 2004
    Сообщения:
    4.336
    Адрес:
    Russia
    один вызов CsrClientCallServer.
     
  3. katrus

    katrus New Member

    Публикаций:
    0
    Регистрация:
    7 мар 2007
    Сообщения:
    612
    То есть созданный поток может сделать OpenProcess и OpenThread самому себе и вызвать CsrClientCallServer? Вот, что я попробовал:
    Код (Text):
    1. struct MSG {
    2.     PORT_MESSAGE            PortMessage;
    3.     CSRSS_MESSAGE           CsrssMessage;
    4.     PROCESS_INFORMATION     ProcessInformation;
    5.     CLIENT_ID               Debugger;
    6.     ULONG                   CreationFlags;
    7.     ULONG                   VdmInfo[2];
    8. } csrmsg;
    9.  
    10. csrmsg.ProcessInformation.hProcess = hProcess;
    11. csrmsg.ProcessInformation.hThread = hThread;
    12. csrmsg.ProcessInformation.dwProcessId = pid;
    13. csrmsg.ProcessInformation.dwThreadId = tid;
    14.  
    15. CsrClientCallServer(&csrmsg, 0, 0x10000, 0x24);
    CsrClientCallServer возвращает STATUS_ILLEGAL_FUNCTION = 0xC00000AF
     
  4. x64

    x64 New Member

    Публикаций:
    0
    Регистрация:
    29 июл 2008
    Сообщения:
    1.370
    Адрес:
    Россия
    Я, честно говоря, так и не понял - тебе из памяти нужно DLL загрузить или с диска? Ну я на всякий случай напишу для обоих случаев. Подробностей не будет, только направление для дальнейшего ковыряния. Сей замечательный вопрос решается прекрасно в два этапа. Если кратко:

    1. Загрузить DLL в адресное пространство процесса

    Здесь есть несколько способов, упомяну о каждом, разделяя способы для загрузки из памяти и для загрузки с диска.

    1.1. Загрузка DLL из памяти

    1.1.1. Загрузка из памяти вручную из системного потока

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

    1.1.2. Загрузка из памяти вручную из APC

    Если же больше нравится APC (я бы не рекомендовал, но уж если очень хочется...), то следует использовать такие функции как KeInitializeApc() и KeInsertQueueApc(), при чём в качестве RundownRoutine следует указать функцию очистки, в качестве KernelRoutine функцию, которая будет выполняться в режиме ядра, но уже в контексте целевого процесса. Задача последней - проделать всё то, что и в случае кода из п. 1.1.1, только с поправкой на то, что мы находимся уже в контексте целевого процесса.

    1.1.3. Загрузка DLL из памяти с использованием виртуального диска

    В случае, если нужно загрузить DLL с виртуального диска, можно реализовать виртуальный RAM-диск (в памяти который). Его можно написать двумя способами: либо на базе одной из существующих файловых систем, либо написать некое подобие своей.

    1.1.3.1. Загрузка DLL из памяти с помощью стандартной файловой системы

    В первом случае придёться зарегистрировать собственный девайс типа FILE_DEVICE_DISK и реализовать все необходимые обработчики типа IRP_MJ_*; для виртуального диска, по моему опыту, достаточно IRP_MJ_CREATE, IRP_MJ_CLOSE, IRP_MJ_READ, IRP_MJ_WRITE и IRP_MJ_DEVICE_CONTROL (для собственной файловой системы тоже самое, только без IRP_MJ_DEVICE_CONTROL). Если свою файловую систему писать не хочется, тогда достаточно выделить блок памяти (например, в nonpaged-пуле размером в 1 МБ) и сформировать загрузочный сектор для FAT16, таким образом при первом же вызове IopCreateFile() (т.е. при первой же попытке открытия файла на этом томе) наш том будет примонтирован драйвером fastfat.sys, т.е. с одной стороны девайс формально будет считаться файловым томом fastfat'а, но с другой все запросы на чтение/запись будут приходить в наш драйвер, т.к. фактически девайс тома принадлежит нам. Тут самое главное реализовать обработчики IRP_MJ_READ и IRP_MJ_WRITE - читать/писать данные из/в наш буфер в памяти.

    1.1.3.2. Загрузка DLL из памяти с помощью собственной файловой системы

    Во втором случае, например, если мы не хотим, чтобы система и другие приложения знали о нашей активности (как-никак к нашему девайсу могут прицепиться файловые фильтры других приложений, ведь он есть не что иное как обычный виндовый логический диск), мы можем написать некое подобие своей файловой системы. Для этого мы также должны создать девайс, но типа FILE_DEVICE_UNKNOWN, здесь мы по прежнему должны обрабатывать запросы на чтение/запись и другие. Единственное, что чтобы I/O менеджер признал нас за файловую систему (в частности, при создании executable-секции), потребуют дополнительные телодвижения вроде инициализации структур менеджера кэша на лету и т.п. Более подробно можно исходники fastfat и cdfs посмотреть, ну или меня спросить, хотя первое быстрее, конечно.

    1.2. Загрузка DLL с диска

    Опять же, есть два метода.

    1.2.1. Загрузка DLL с диска через APC

    С реализацией виртуального диска, думаю, понятно. Остался последний вопрос - как заставить систему загрузить файл с диска (не важно, реального или виртуального) ? Тут, наверно, проще всего воспользоваться опять же APC, при чём двумя. Первая - kernel APC, её задача в получении адреса LoadLibrary() в текущем (он же целевой) процессе и запуске user APC с точкой входа на найденную LoadLibrary(), вторая - пользовательская, её точка входа аккурат на LoadLibrary() будет, думаю, объяснять её задачу не нужно. Последний момент, который здесь нужно упомянуть, это путь к DLL. Он будет вида \\?\GLOBALROOT\Device\DeviceName\mydll.dll, например, \\?\GLOBALROOT\Device\MyVirtualDrive\mydll.dll для виртуального диска, и \\?\GLOBALROOT\Device\HarddiskVolume1\Windows\System32\mydll.dll для реального логического диска. С этим всё.

    1.2.2. Загрузка DLL с диска через реализацию удалённых потоков

    Второй способ загрузки заключается в реализации аналога функции CreateRemoteThread(), необходимую для использования метода удалённых потоков. Метод этот, думаю, всем знаком ещё по режиму пользователя, здесь всё тоже самое - выделение памяти в целевом процессе для кода и параметров, и прочее, единственно, что сама реализация CreateRemoteThread() будет другой. Подробнее см. п.2.

    2. Вызвать DllMain загруженной DLL

    Этот шаг нужен только если мы грузим DLL вручную из памяти. Кроме того, в этом разделе содержится информация, необходимая для реализации метода удалённых потоков.

    Предположим, что наша DLL формата Win32, а не Native (т.е. импортирует функции не только ntdll.dll, но также kernel32.dll и другие). Тогда нам понадобится поток пользовательского режима, который и вызовет DllMain() с флагом DLL_PROCESS_ATTACH (и другими, при необходимости). Здесь есть один нюанс: бывает, что в DllMain() нужно сделать некоторые действия, типа CreateThread() или что-нибудь в этим духе. Проблема в том, что если мы вызовем DllMain(), а значит и CreateThread(), из потока, не зарегистрированного в CSRSS, то прилетит птица обломинго и сделать наша DLL практически ничего не сможет. Лично мне такой расклад крайне не нравится, поэтому рекомендую использовать следующий workaround. А именно: вызывать DllMain() нужно в потоке, который CSRSS тоже пропустит. Таким потоком может быть либо уже зарегистрированный в CSRSS поток, либо поток, работающий в контексте самого CSRSS (угу, свои потоки оно не трогает никогда и позволяет им любые шалости, даже если они полностью Native, а не Win32). Поскольку искать зарегистрированные потоки - дело неблагодарное да и не нужное, в общем, рекомендую делать заход на DllMain() в два этапа:

    1. Создать Native-поток в CSRSS.
    2. Из него уже вызывать CreateRemoteThread() на shell-код в целевом процессе.

    Фактически, этот способ позволяет реализовать аналог CreateRemoteThread() в ядре. При этом разумеется, у нас должно быть два шел-кода - для потока в CSRSS и для потока в целевом процессе, который непосредственно и вызовет DllMain(). Задача потока в CSRSS простая - вызывать CreateRemoteThread() с точкой входа в целевом процессе, затем дождаться завершения нового потока по WaitForSingleObject(), закрыть его хендл и завершиться по ExitThread(). Вызов ExitThread() обязателен, потому что Native-потоки не могут просто сделать ret, ибо им некуда возвращаться и никто их не прибьёт. Такое могут позволить себе только Win32-потоки, возвращающие управление в kernel32!BaseThreadStart(), которая и прибивает поток.

    Создание Native-потока следует выполнять с помощью функции NtCreateThread(), при этом придётся самому формировать стек потока и структуру INITIAL_TEB. Для этого можно воспользоваться функциями ZwAllocateVirtualMemory(); освобождать стек придётся также самому по завершению потока с помощью ZwFreeVirtualMemory(). Адрес NtCreateThread() можно взять из таблицы SDT, по индексу, считанному из ntdll.dll. Вызов NtCreateThread() следует осуществлять из системного потока, чтобы previous mode было равно KernelMode и не возникало проблем с правами.

    Отмечу ещё, что вся приведённая информация актуальна как для Windows XP и Server 2003, так и для Windows Vista; в Windows 7, вероятно, тоже будет работать, т.к. у них с Vista почти одинаковое ядро. Проверено, и неоднократно.

    P.S.
    Ну вот что за копибара, хотел пару строчек отписать, а получилась почти статья...
     
  5. RET

    RET Well-Known Member

    Публикаций:
    17
    Регистрация:
    5 янв 2008
    Сообщения:
    789
    Адрес:
    Jabber: darksys@sj.ms
    Вообще удаленные Native-потоки нужно создавать при помощи RtlCreateUserThread, или Nt/ZwCreateThread (собирая структуры TEB и stack вручную). При создании потока нужно указать флаг CreateSuspended=TRUE, после чего заполнить сруктуры (кроме ThreadHandle & ClientID нужно занести мессаги серверу, примерно так на XPSP2: CsrClientCallServer(&CSRSSMSG,0,0x10001,0x0C); и вызвать LPC-функцию CsrClientCallServer. После чего разморозить поток Nt/ZwResumeThread и дождаться его завершения по Nt/ZwWaitForSingleObject (если нужно- потом освободить память).При этом все вызовы из точки входа dll (DllMain нужно указать линкеру в качестве точки входа) будут выполняться. Как заметил выше x64 ExitThread в шелл-коде обязателен, можно использовать Zw/NtTerminateThread.