Вызов неэкспортированной функции в DLL - PullRequest
10 голосов
/ 27 мая 2010

У меня есть программа, которая загружает библиотеки DLL, и мне нужно вызвать одну из неэкспортированных функций, которые она содержит. Есть ли способ, которым я могу сделать это, с помощью поиска в отладчике или иным образом? Прежде чем кто-нибудь спросит, да, у меня есть прототипы и прочее для функций.

Ответы [ 6 ]

10 голосов
/ 27 мая 2010

Да, хотя бы, но это не очень хорошая идея.

В C / C ++ все указатели функций - это адреса в памяти. Поэтому, если вы каким-то образом сможете найти адрес этой функции, вы можете вызвать ее.

Позвольте мне задать несколько вопросов, откуда вы знаете, что эта DLL содержит эту функцию? У вас есть исходный код? В противном случае я не знаю, как вы могли бы точно знать, что эта функция существует или ее безопасно вызывать. Но если у вас есть исходный код, тогда просто выставьте функцию. Если разработчик DLL не предоставил эту функцию, он никогда не ожидает, что вы вызовете ее, и может изменить / удалить реализацию в любое время.

Помимо предупреждений, вы можете найти адрес функции, если у вас есть символы отладки, или MAP-файл , где вы можете найти смещение в DLL. Если у вас нет ничего, кроме DLL, то нет способа узнать, где эта функция существует в DLL - она ​​не хранится в самой DLL.

Получив смещение, вы можете вставить его в код следующим образом:

const DWORD_PTR funcOffset = 0xDEADBEEF;
typedef void (*UnExportedFunc)();

....
void CallUnExportedFunc() {
     // This will get the DLL base address (which can vary)
     HMODULE hMod = GetModuleHandle("My.dll"); 
     // Calcualte the acutal address 
     DWORD_PTR funcAddress = (DWORD_PTR)hMod + funcOffset;
     // Cast the address to a function poniter
     UnExportedFunc func = (UnExportedFunc)funcAddress;
     // Call the function
     func();
}

Также следует понимать, что смещение этой функции ИЗМЕНИТСЯ КАЖДЫЙ РАЗ, когда DLL перестраивается, так что это очень хрупко, и позвольте мне повторить, что не очень хорошая идея.

8 голосов
/ 17 ноября 2012

Я понимаю, что этот вопрос довольно старый, но shf301 имеет правильную идею здесь. Единственное, что я хотел бы добавить, это реализовать поиск по шаблону в целевой библиотеке. Если у вас есть IDA или OllyDbg , вы можете найти функцию и просмотреть двоичные / шестнадцатеричные данные, которые окружают начальный адрес этой функции.

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


Способ, которым вы реализуете поиск по двоичному шаблону, выглядит так:

bool bCompare(const PBYTE pData, const PBYTE bMask, const PCHAR szMask)
{
    for(;*szMask;++szMask,++pData,++bMask)
            if(*szMask=='x' && *pData!=*bMask)  
                    return 0;
    return (*szMask) == NULL;
}

DWORD FindPattern(DWORD dwAddress, DWORD dwLen, PBYTE bMask, PCHAR szMask)
{
    for(DWORD i=0; i<dwLen; i++)
            if (bCompare((PBYTE)(dwAddress+i),bMask,szMask))  
                    return (DWORD)(dwAddress+i);
    return 0;
}


Пример использования:

typedef void (*UnExportedFunc)();

//...
void CallUnExportedFunc()
{
    // This will get the DLL base address (which can vary)
    HMODULE hMod = GetModuleHandleA( "My.dll" );

    // Get module info
    MODULEINFO modinfo = { NULL, };
    GetModuleInformation( GetCurrentProcess(), hMod, &modinfo, sizeof(modinfo) );

    // This will search the module for the address of a given signature
    DWORD dwAddress = FindPattern(
        hMod, modinfo.SizeOfImage,
        (PBYTE)"\xC7\x06\x00\x00\x00\x00\x89\x86\x00\x00\x00\x00\x89\x86",
        "xx????xx????xx"
    );

    // Calculate the acutal address 
    DWORD_PTR funcAddress = (DWORD_PTR)hMod + dwAddress;

    // Cast the address to a function poniter
    UnExportedFunc func = (UnExportedFunc)funcAddress;

    // Call the function
    func();
}


Это работает путем передачи базового адреса загруженной библиотеки через GetModuleHandle, указания длины (в байтах) для поиска, двоичных данных для поиска и маски, которая указывает, какие байты двоичной строки являются действительный ('x') и которые должны быть пропущены ('?'). Затем функция будет проходить через область памяти загруженного модуля в поисках совпадения. В некоторых случаях может быть более одного совпадения, и в этом случае имеет смысл сделать вашу подпись чуть более выраженной, чтобы там было только одно совпадение.

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

1 голос
/ 27 мая 2010

Если нужная функция не экспортируется, ее не будет в таблице адресов экспорта. Предполагая, что Visual Studio использовалась для создания этой DLL, и у вас есть связанный с ней файл PDB (программная база данных), вы можете использовать API-интерфейсы Microsoft DIA (доступ к интерфейсу отладки), чтобы найти нужную функцию по имени или, приблизительно, , подписью.

Как только вы получите функцию (символ) от PDB, у вас также будет свой RVA (относительный виртуальный адрес). Вы можете добавить RVA к базовому адресу загруженного модуля, чтобы определить абсолютный виртуальный адрес в памяти, где хранится функция. Затем вы можете выполнить вызов функции через этот адрес.


В качестве альтернативы, если это единовременная вещь, которую вам нужно сделать (т.е. вам не нужно программное решение), вы можете использовать windbg.exe в Средства отладки для Windows Инструментарий присоединиться к вашему процессу и узнать адрес функции, которая вас интересует. В WinDbg вы можете использовать команду x для «проверки символов» в модуле.

Например, вы можете сделать x mymodule!*foo*, чтобы увидеть все функции, имя которых содержит «foo». Пока у вас загружены символы (PDB) для вашего модуля, это также покажет вам функции неэкспорта. Используйте .hh x для получения справки по команде x.

0 голосов
/ 04 октября 2016

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

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

Не думайте, что блок машинного кода, который выглядит как нужная вам функция, на самом деле следует стандартному ABI и реализует все, что говорит исходный код.

В случае gcc он использует специальные имена для специализированных версий функции, которые не являются встроенными, но используют особый случай (например, постоянное распространение) от нескольких вызывающих абонентов.

например. в этом objdump -drwC выводе (где -C - это demangle):

42944c: e8 cf 13 0e 00, звонок 50a820 429451: 48 8b 7b 48 mov rdi, QWORD PTR [rbx + 0x48] 429455: 48 89 ee mov rsi, rbp 429458: e8 b3 10 0e 00, звонок 50a510

gcc испускает код, который вызывает два разных клона одной и той же функции, специализированной для двух разных констант времени компиляции. (Это из http://endless -sky.github.io / , который крайне нуждается в LTO, потому что даже тривиальные функции доступа для его класса позиции XY находятся в Point.cpp, а не в Point.h, поэтому они могут только быть вовлеченным в LTO.)

LTO может даже создавать .lto_priv статические версии данных: например,

mov    rcx,QWORD PTR [rip+0x412ff7]        # 83dbe0 <_ZN12_GLOBAL__N_116playerGovernmentE.lto_priv.898>

Таким образом, даже если вы найдете функцию, которая выглядит так, как вам нужно, вызов ее из другого места может нарушить предположения, которыми воспользовалась Link-Time-Optimization.

0 голосов
/ 27 мая 2010

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

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

Мне действительно нужно было сделать нечто подобное один раз, чтобы применить бинарный патч к DOS-файлу; это было исправление ошибки, и код не был под контролем ревизии, так что это был единственный способ исправить это.

Мне было бы очень интересно узнать, зачем вам это, кстати.

0 голосов
/ 27 мая 2010

Боюсь, что не существует «безопасного» способа сделать это, если указанная библиотека явно не экспортирует свой объект (class / func). Потому что вы не будете знать, где находится требуемый объект в памяти кода.

Однако, используя инструменты RE, вы можете найти смещение для интересующего объекта в библиотеке, а затем добавить его к любому известному адресу экспортируемого объекта, чтобы получить «реальную» область памяти. После этого подготовьте прототип функции и т. Д. И приведите к локальной структуре для использования.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...