Загрузка DLL из памяти : redux

Тема в разделе "WASM.WIN32", создана пользователем Incidence, 26 дек 2011.

  1. Incidence

    Incidence New Member

    Публикаций:
    0
    Регистрация:
    26 дек 2011
    Сообщения:
    236
    Адрес:
    Kiev, UA
    Добрый день.

    Задача: подгрузить в процесс DLLку, которую для этого (да и вообще) нельзя сохранять на диск, и ещё и так, чтобы её настоящее имя не светилось в списке подгруженных модулей (ненастоящее можно).

    Читал тут это, это, это и это, много думал.

    Решил обойтись без KnownDlls, т.к. на это могут ругаться всякие антивирусы, да и внедрение должно происходить только в один процесс.

    Делаю так (эксперимент с целью, так сказать, proof of concept):
    1. Гружу DLLку штатно через LoadLibrary()
    2. Делаю GetModuleInformation(), узнаю по какому адресу она висит и сколько места занимает. Запоминаю это всё в буфер.
    3. Выгружаю DLLку через FreeLibrary()
    4. Создаю секцию
    Естественно, параметры тут не такие, как в LdrpFindOrMapDll(), потому что там делается memory mapping, с FILE_WRITE_EA и SEC_IMAGE. Но я ж так не могу, файла не должно быть.
    5. Делаю
    6. Пишу в получившийся view ранее сохранённый образ DLLки.
    7. Делаю ему NtUnmapViewOfSection()
    8. Ставлю хуки на NtOpenFile() и NtCreateSection()
    9. Вызываю LoadLibrary() с именем какой-нибудь безобидной системной DLLки.
    10. Когда внутри LdrpFindOrMapDll() дело доходит до NtOpenFile(), запоминаю хэндл файла.
    11. Когда внутри LdrpFindOrMapDll() дело доходит до NtCreateSection() и запомненным выше хэндлом, вместо вызова родной функции возвращаю тот хэндл секции из п.4.

    По моему чайниковскому разумению, после этого должен типа как "загрузиться" модуль с тем образом, который я предварительно записал в свою секцию.
    Однако, несмотря на то, что последующий за NtCreateSection() вызов NtMapViewOfSection() внутри LdrpFindOrMapDll() возвращает STATUS_SUCCESS, DLLка нифига не грузится и её точка входа не выполняется.

    Что же делать?
     
  2. kejcerfcrv

    kejcerfcrv New Member

    Публикаций:
    0
    Регистрация:
    16 дек 2011
    Сообщения:
    320
    Секция открывается по имени из директории KnownDlls. Директория открывается при инициализации процесса и далее описатель её хранится в системной переменной(LdrpKnownDllObjectDirectory). При открытии секции он передаётся в сервис. Можно создать свою директорию и загрузить её в переменную. Проты детектят манипуляции с этой директорией, так как возможна перезапись секции(удаление и повторное создание). Мне это тоже не нравится.

    Ну во первых очевидно что таким образом модуль не будет релоцироваться. STATUS_IMAGE_NOT_AT_BASE возвращается только для SEC_IMAGE.

    Далее в 11 нужно убрать флаг SEC_IMAGE.

    Есчо там проверка ZwQuerySection(SectionImageInformation), такая вроде при запуске процесса не даёт его из длл запустить.

    1. Включить логгер.
    2. Изменить способ загрузки. Никаких патчей недолжно быть.
     
  3. Incidence

    Incidence New Member

    Публикаций:
    0
    Регистрация:
    26 дек 2011
    Сообщения:
    236
    Адрес:
    Kiev, UA
    Хорошо.
    Делаю через KnownDlls, всё равно не получается.
    Делал так, как написано в этой теме, перевёл код на FPC дословно (для проверки работоспособности):
    Код (Text):
    1. procedure CreateKnownDllSection(const SectionHandle : PHANDLE;
    2.                                const SectionName : PSTR;
    3.                                const SectionSize : ULONG;
    4.                                const SourceAddress : PVOID;
    5.                                const SourceSize : ULONG); stdcall;
    6. const DirectoryName : AnsiString = '\KnownDlls'#$00;
    7. var ObjAttr : OBJECT_ATTRIBUTES;
    8.     LocalSectionHandle : HANDLE;
    9.     DirectoryNameUni : UNICODE_STRING;
    10.     SectionNameUni : UNICODE_STRING;
    11.     LocalSectionSize : LARGE_INTEGER;
    12.     ViewSize : ULONG;
    13.     BaseAddress : PVOID;
    14.     SectionOffset : LARGE_INTEGER;
    15. begin
    16.  Assert(RtlCreateUnicodeStringFromAsciiz(@DirectoryNameUni,PAnsiChar(DirectoryName)),'RtlCreateUnicodeStringFromAsciiz 0');
    17.  ObjAttr.Length:=SizeOf(OBJECT_ATTRIBUTES);
    18.  ObjAttr.RootDirectory:=0;
    19.  ObjAttr.ObjectName:=@DirectoryNameUni;
    20.  ObjAttr.Attributes:=0;
    21.  ObjAttr.SecurityDescriptor:=nil;
    22.  ObjAttr.SecurityQualityOfService:=nil;
    23.  LocalSectionSize.HighPart:=0;
    24.  LocalSectionSize.LowPart:=SectionSize;
    25.  SectionHandle^:=0;
    26.  if (SourceSize<=SectionSize) then
    27.   begin
    28.    Assert(NtOpenDirectoryObject(@ObjAttr.RootDirectory,DIRECTORY_CREATE_OBJECT,@ObjAttr)=STATUS_SUCCESS,'NtOpenDirectoryObject');
    29.    Assert(RtlCreateUnicodeStringFromAsciiz(@SectionNameUni,PAnsiChar(SectionName)),'RtlCreateUnicodeStringFromAsciiz 1');
    30.    ObjAttr.ObjectName:=@SectionNameUni;
    31.    Assert(NtCreateSection(@LocalSectionHandle,SECTION_ALL_ACCESS,@ObjAttr,@LocalSectionSize,PAGE_EXECUTE_READWRITE,SEC_COMMIT,0)=STATUS_SUCCESS,'NtCreateSection');
    32.    BaseAddress:=nil;
    33.    ViewSize:=0;
    34.    SectionOffset.LowPart:=0;
    35.    SectionOffset.HighPart:=0;
    36.    Assert(NtMapViewOfSection(LocalSectionHandle,NtCurrentProcess,@BaseAddress,0,0,@SectionOffset,@ViewSize,ViewShare,0,PAGE_READWRITE)=STATUS_SUCCESS,'NtMapViewOfSection');
    37.    Assert(NtWriteVirtualMemory(NtCurrentProcess,BaseAddress,SourceAddress,SourceSize,nil)=STATUS_SUCCESS,'NtWriteVirtualMemory');
    38.    Assert(NtUnmapViewOfSection(NtCurrentProcess,BaseAddress)=STATUS_SUCCESS,'NtUnmapViewOfSection');
    39.    SectionHandle^:=LocalSectionHandle;
    40.    RtlFreeUnicodeString(@SectionNameUni);
    41.    Assert(NtClose(ObjAttr.RootDirectory)=STATUS_SUCCESS,'NtClose');
    42.    RtlFreeUnicodeString(@DirectoryNameUni);
    43.   end;
    44. end;
    Далее так:
    Код (Text):
    1. CreateKnownDllSection(@hSect,MySection,DLLImage.Level,DLLImage.Ptr,DLLImage.Level);
    2.  Writeln('LoadLibrary = ',LoadLibrary(PWideChar(WideString(MySection))));
    3.  Writeln('GetLastError = ',IntToString(GetLastError,Radix_HEX,0));
    Ни один assert не выскочил, но LoadLibrary возвращает 0, GetLastError тоже 0, а в логгере так:
    STATUS_DLL_NOT_FOUND, то есть секцию загрузчик не нашёл.
    Как быть?
     
  4. Malfoy

    Malfoy New Member

    Публикаций:
    0
    Регистрация:
    2 янв 2012
    Сообщения:
    698
    Если не хотите юзать отладчик, то мб стоит взять готовое решение ?
     
  5. Incidence

    Incidence New Member

    Публикаций:
    0
    Регистрация:
    26 дек 2011
    Сообщения:
    236
    Адрес:
    Kiev, UA
    Да я бы с радостью, а что именно отлаживать, подскажите?
    LoadLibrary доходит до LdrpFindKnownDll, в нём вызывается NtOpenSection с именем DLL и возвращает STATUS_OBJECT_NAME_NOT_FOUND.
    Значит, как-то не так создаётся секция.
     
  6. Incidence

    Incidence New Member

    Публикаций:
    0
    Регистрация:
    26 дек 2011
    Сообщения:
    236
    Адрес:
    Kiev, UA
    Так. Проблема проясняется.
    Надо было вместо KnownDlls писать KnownDlls32, т.к. всё это хозяйство у меня крутится на wow64.
    Теперь похоже на то что DLL грузится нормально, но вылазит такой косяк:
    Т.е. пытается обратиться по старому адресу, когда она ещё загружена была обычным способом. Т.е. надо делать релок (вручную?). Как это делать, я пока не знаю :dntknw:
    Нашёл тут похожую задачу, буду копать.
     
  7. Malfoy

    Malfoy New Member

    Публикаций:
    0
    Регистрация:
    2 янв 2012
    Сообщения:
    698
    В идеале нужно релоцировать загрузчик, получить управление в начале его работы и выполнить маршрутизацию на релоцированную копию, в которой стабы должным образом скорректированы. Тут старый лодер на трейсе:
    indy-vx.narod.ru/Bin/Ldr.zip
    и тут есчо чтото связанное с загрузкой:
    indy-vx.narod.ru/Bin/LdrExts.zip
    indy-vx.narod.ru/Bin/LdrUpd.zip
     
  8. shchetinin

    shchetinin Member

    Публикаций:
    0
    Регистрация:
    27 май 2011
    Сообщения:
    715
    Malfoy
    Security risk detected: Trojan.Gen.2 ))
     
  9. Incidence

    Incidence New Member

    Публикаций:
    0
    Регистрация:
    26 дек 2011
    Сообщения:
    236
    Адрес:
    Kiev, UA
    Так... Мучения мои продолжаются :)
    Релок я сделал, таким образом
    Код (Text):
    1. function RelocateDLLImage(const DLLImage : Pointer;
    2.                           const DLLOriginalBase : PtrUInt;
    3.                           const DLLNewBase : PtrUInt) : LongBool; stdcall;
    4. var NTHeader : PIMAGE_NT_HEADERS;
    5.     RelEntry : PIMAGE_BASE_RELOCATION;
    6.     PatchCount : DWORD;
    7.     RelPtrOffset : PtrUInt;
    8. begin
    9.  Result:=False;
    10.  if (PIMAGE_DOS_HEADER(DLLImage)^.e_magic=IMAGE_DOS_SIGNATURE) then // Check if this is true dll with MZ
    11.   begin
    12.    NTHeader:=MemOffsetPointer(DLLImage,PIMAGE_DOS_HEADER(DLLImage)^.e_lfanew);
    13.    if (NTHeader^.Signature=IMAGE_NT_SIGNATURE) then // Check if this is PE dll
    14.     begin
    15.      // process relocation information
    16.      RelPtrOffset:=NTHeader^.OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].VirtualAddress;
    17.      RelEntry:=MemOffsetPointer(DLLImage,RelPtrOffset);
    18.      while (RelEntry<>nil) do if (RelEntry^.SizeOfBlock>0) then
    19.       begin
    20.        PatchCount:=((RelEntry^.SizeOfBlock-SizeOf(IMAGE_BASE_RELOCATION)) div SizeOf(Word));
    21.        RelEntry:=LdrProcessRelocationBlock(MemOffsetPointer(DLLImage,RelEntry^.VirtualAddress),
    22.                                            PatchCount,
    23.                                            MemOffsetPointer(RelEntry,SizeOf(IMAGE_BASE_RELOCATION)),
    24.                                            PtrInt(DLLNewBase-DLLOriginalBase));
    25.       end
    26.        else RelEntry:=nil;
    27.      // Fix ImageBase, is it necessary?
    28.      NTHeader^.OptionalHeader.ImageBase:=DLLNewBase;
    29.      // done
    30.      Result:=True;
    31.     end; // PE signature
    32.   end; // MZ signature
    33. end;
    По крайней мере, теперь когда происходит
    Вызывается она по правильному адресу и даже успевает что-то там отработать. Но потом всё равно падает по AV при доступе по старому адресу
    Код (Text):
    1. Access violation when reading [10005000] - Shift+Run/Step to pass exception to the program
    Буду выяснять, откуда берётся этот AV.
    Может есть какие-то идеи?
     
  10. Incidence

    Incidence New Member

    Публикаций:
    0
    Регистрация:
    26 дек 2011
    Сообщения:
    236
    Адрес:
    Kiev, UA
    Теперь оно падает из-за кривого FPC RTL.
    Там поддержка threadvar реализуется какой-то магией, которая только один раз при старте запоминает адреса магических функций.
    Переписал тестовую DLL на асме -- всё работает без ошибок :)
     
  11. Incidence

    Incidence New Member

    Публикаций:
    0
    Регистрация:
    26 дек 2011
    Сообщения:
    236
    Адрес:
    Kiev, UA
    На всякий случай, если кто-нибудь будет страдать той же хернёй, что и я тут.
    Способ через подстановку kernel section в KnownDlls без файла на диске, т.е. чисто в памяти -- не работает.
    Вернее, работает через раз, случайным образом.

    Причина заключается, как мне подсказал kejcerfcrv выше, в этом:
    Если подробнее, то вызов NtCreateSection() без флага SEC_IMAGE (т.к. мы берём образ из памяти) вынуждает последующие вызовы NtMapViewOfSection() относиться к этой секции просто как к куску данных, а не образу DLL.
    Удивительно, но факт -- NtMapViewOfSection() вкурсе про формат PE, и если секция была создана с SEC_IMAGE, то может самостоятельно сообщить о том, что образ отмаплен не по тому адресу, который указан в ImageBase. Более того, с этим флагом функция будет стараться мапить образ именно по указанному ImageBase и\или хотя бы по тем адресам, где он (возможно) был смаплен в этом же процессе ранее.
    Без SEC_IMAGE нифига этого не происходит. Даже повторный вызов NtMapViewOfSection() внутри LdrpFindOrMapDLL() на ту же самую секцию вернёт адрес view другой и не вызовет релоки. Иногда, совершенно случайно, адрес view возвращается тот же самый и всё работает как надо. Но редко.

    В общем, такой метод не подходит.

    В качестве временного рабочего решения я применил полу-ручную загрузку DLL. То есть, оставляя образ в памяти, не трогая kernel sections, я прямо в нём делаю релокации, обнуляю секции с UNINITIALIZED_DATA (это важно для инициализации ЯВУшных RTL) и расставляю VirtualProtect'ы секций. Если при этом следить, чтобы внедрение происходило в процесс, где все зависимые модули уже загружены и в середине кода DLL не трогать наш якобы hInstance в вызовах винапи, то тут уже можно вызывать entry point и всё работает. Загруженная таким образом DLL нигде не светится, естественно - побочный эффект :)

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

    Malfoy New Member

    Публикаций:
    0
    Регистрация:
    2 янв 2012
    Сообщения:
    698
    Incidence
    Нужно с умом делать, вы даже не удосужились посмотреть готовое, работающее через эту директорию.

    Кошерное решение это стековая маршрутизация в изменённую проекцию. Это единственно годное решение(идеальное). Всякого рода захват загрузчика и дальнейшая маршрутизация хотя и будет работать, но не соверщенна.