Корректно записать привязку функции отложенной загрузки в исполняемый файл образа (dlltool)

#windows #visual-studio #llvm #binutils #dlltool

#Windows #visual-studio #llvm #binutils #dlltool

Вопрос:

Я изучал конвейер отложенной загрузки (delayimp) как возможный серверный сервер для отсутствующей функциональности RPATH в Windows, на следующем примере:

 #include <stdio.h>

int __declspec(dllimport) foo(int arg);

int main(int argc, char* argv[])
{
    printf("foo() = %dn", foo(foo(argc)));
    return 0;
}
  

И GNU, и LLVM аналогично реализуют отложенную загрузку с помощью «dlltool» (тем не менее, dlltool от LLVM, похоже, слился с «ld-link»). По сути, задача, выполняемая в LLVM lld/COFF/DLL.cpp или BinUtil dlltool.c , состоит из двух частей:

  1. Сгенерировать заглушку таблицы переходов для функции отложенной загрузки (см. Пример ниже)
  2. Сгенерируйте trampoline, который должен развернуть код __delayLoadHelper2 (см. Пример ниже)

После успешной привязки __delayLoadHelper2 , похоже, записывает разрешенный адрес функции прямо в раздел исполняемого кода:

 extern "C"
FARPROC WINAPI
__delayLoadHelper2(
    PCImgDelayDescr     pidd,
    FARPROC *           ppfnIATEntry
    ) {
...
SetEntryHookBypass:
    *ppfnIATEntry = pfnRet; // access violation
...
}
  

Для модификации исполняемого образа Microsoft разработала несколько необычных функций, которые временно добавляют разрешения на запись в соответствующую область памяти.

Теперь вопрос в следующем: код, который нужно изменить, находится в заглушке таблицы переходов, которая переходит в раздел «.idata», и ему не удается получить разрешения на запись:

         if ((Characteristics amp; IMAGE_SCN_MEM_WRITE) == 0) {

            //
            // This delay load helper module does not support merging the delay
            // load section to a read only section because memory management
            // would not guarantee that there is commit available - and thus a
            // low memory failure path where the delay load failure hook could
            // not be safely invoked (the delay load section would still be
            // read only) might be encountered.
            //
            // It is a build time configuration problem to produce such a
            // binary so abort here and now so that the problem can be
            // identified amp; fixed.
            //

/* Exception thrown at 0x000000013F3B3F3F in dlltool_test_executable.exe: 0xC0000005: Access violation reading */
            __fastfail(FAST_FAIL_DLOAD_PROTECTION_FAILURE);
        }
  

Итак, в настоящее время жесткая привязка не работает и выдает «нарушение прав доступа на запись». Мне интересно, какую двоичную конфигурацию я здесь упускаю?

Моя тестовая конфигурация: LLVM, исходящий из github, BinUtils, исходящий из git, MSVC2019, Windows 7.

 $ cat trampoline.s
# Import trampoline
        .section        .text
        .global __tailMerge_C__Users_marcusmae_dlltool_build_import_test_lib
__tailMerge_C__Users_marcusmae_dlltool_build_import_test_lib:
        pushq %rcx
        pushq %rdx
        pushq %r8
        pushq %r9
        subq  $40, %rsp
        movq  %rax, %rdx
        leaq  __DELAY_IMPORT_DESCRIPTOR_C__Users_marcusmae_dlltool_build_import_test_lib(%rip), %rcx
        call __delayLoadHelper2
        addq  $40, %rsp
        popq %r9
        popq %r8
        popq %rdx
        popq %rcx
        jmp *%rax

# DELAY_IMPORT_DESCRIPTOR
.section        .text$2
.global __DELAY_IMPORT_DESCRIPTOR_C__Users_marcusmae_dlltool_build_import_test_lib
__DELAY_IMPORT_DESCRIPTOR_C__Users_marcusmae_dlltool_build_import_test_lib:
        .long 1 # grAttrs
        .rva    __C__Users_marcusmae_dlltool_build_import_test_lib_iname        # rvaDLLName
        .rva    __DLL_HANDLE_C__Users_marcusmae_dlltool_build_import_test_lib   # rvaHmod
        .rva    __IAT_C__Users_marcusmae_dlltool_build_import_test_lib  # rvaIAT
        .rva    __INT_C__Users_marcusmae_dlltool_build_import_test_lib  # rvaINT
        .long   0       # rvaBoundIAT
        .long   0       # rvaUnloadIAT
        .long   0       # dwTimeStamp

.section .data
__DLL_HANDLE_C__Users_marcusmae_dlltool_build_import_test_lib:
        .long   0       # Handle
        .long   0

#Stuff for compatibility
        .section        .idata$5
        .long   0
        .long   0
__IAT_C__Users_marcusmae_dlltool_build_import_test_lib:
        .section        .idata$4
        .long   0
        .long   0
        .section        .idata$4
__INT_C__Users_marcusmae_dlltool_build_import_test_lib:
        .section        .idata$2
  
 $ objdump -d dorks00000.o

dorks00000.o:     file format pe-x86-64


Disassembly of section .text:

0000000000000000 <foo>:
   0:   ff 25 00 00 00 00       jmpq   *0x0(%rip)        # 6 <foo 0x6>
   6:   48 8d 05 00 00 00 00    lea    0x0(%rip),%rax        # d <foo 0xd>
   d:   e9 00 00 00 00          jmpq   12 <foo 0x12>
        ...
  

Ответ №1:

Итак, вы создаете структуры импорта с задержкой, используя GNU dlltool, но связываетесь с ним с помощью LLD или MS link.exe ?

Я думаю, что разница здесь заключается в том, что GNU dlltool помещает адреса, которые обновляются во время выполнения, в .idata , и GNU ld обычно ссылается .idata как доступные для записи, в то время как LLD и MS link.exe обычно доступен только для чтения .idata (и вместо этого помещает адреса, которые будут обновлены во время выполнения механизмом отложенной загрузки .data ).

В LLD есть немного дополнительного кода для извлечения .idata разделов чтения-записи из библиотек импорта GNU и объединения их с остальными, доступными только для чтения в LLD, .idata что позволяет работать обычным библиотекам импорта GNU, но, к сожалению, мешает их использованию вместе с библиотеками delayimport GNU dlltool.

Итак, с LLD просто используйте встроенный механизм импорта задержки LLD, передавая, например, -delayload:user32.dll при компоновке. Это работает при использовании библиотек импорта в стиле MSVC, но, к сожалению, не при использовании библиотек импорта в стиле GNU (библиотеки импорта, созданные GNU dlltool или GNU ld).

Комментарии:

1. Точно. Я пытаюсь объединить MS link.exe с помощью библиотек импорта, сгенерированных dlltool, потому что я хочу отложить загрузку библиотек, которые не были изначально скомпилированы для отложенной загрузки. То есть мне нужно предоставить новые библиотеки импорта. Я надеюсь выполнить это, не затрагивая логику компоновщика, поскольку библиотека импорта, естественно, является самодостаточной сущностью. Не могли бы вы, пожалуйста, любезно указать мне, где именно компоновщик LLVM подготавливает разделы «.idata» для записи? GNU dlltool просто создает INIT_SEC_DATA (IDATA5, ".idata$5", SEC_HAS_CONTENTS, 2) , что не делает его доступным для записи.

2. LLD не делает .idata разделы доступными для записи, вместо этого он помещает соответствующие данные в .data . Итак, в вашем случае вы можете захотеть изменить .idata$5 на .data$5 , и то же самое для .idata$4 — это может сработать.

3. Но для создания MS link.exe отложите загрузку библиотеки, созданной с использованием GNU ld, самым простым способом может быть заставить GNU ld создать файл def при компоновке ( -Wl,--output-def,mylib.def ), затем создайте из этого библиотеку импорта с помощью MS lib.exe ( lib.exe -machine:x64 -def:mylib.def -out:mylib.lib ), затем связать с этой библиотекой с помощью MS link.exe , но передать -delayload параметр, например link.exe [other options] mylib.lib -delayload:mylib.dll . MS link.exe для случаев отложенного импорта не требуется отдельная библиотека импорта, если это библиотека импорта в стиле MSVC.

4. Изменение .idata $ 4 и .idata $ 5 на .data $ 4 и .data $ 5 привело к новому нарушению доступа здесь: // Calculate the index for the IAT entry in the import address table // N.B. The INT entries are ordered the same as the IAT entries so // the calculation can be done on the IAT side. // const unsigned iIAT = IndexFromPImgThunkData(PCImgThunkData(ppfnIATEntry), idd.pIAT); const unsigned iINT = iIAT; PCImgThunkData pitd = amp;(idd.pINT[iINT]); dli.dlp.fImportByName = !IMAGE_SNAP_BY_ORDINAL(pitd->u1.Ordinal);

5. Затем попробуйте изменить другие разделы на . также данные; .text$2 и .idata$2 — все, кроме первого .text раздела, который фактически содержит исполняемый код.