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

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

#include <stdio.h>

int __declspec(dllimport) foo(int arg);

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

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

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

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

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

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

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

        if ((Characteristics & 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 & 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, но, к сожалению, не позволяет использовать его вместе с библиотеками задержки импорта GNU dlltool.

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

Другие вопросы по тегам