Доступ к неэкспортируемым функциям DLL

Модераторы: Hawk, Romeo, Absurd, DeeJayC, WinMain

Аватара пользователя
WinMain
Сообщения: 929
Зарегистрирован: 14 янв 2005, 10:30
Откуда: Москва
Контактная информация:

mc-black, скажи как знающий человек, если в DLL или в ЕХЕ- файле реализована некая функция (без разницы, экспортируемая она или нет), то нельзя ли каким-то образом извлечь исполняемый код этой функции и связанные с ней данные, а потом встроить их в своё приложение? А самой DLL потом вообще не пользоваться. На сколько такое вообще реально?
Я знаю, есть даже утилита dll2lib.exe, которая нечто подобное делает. Она извлекает исполняемый код динамической библиотеки и сохраняет его в статическом lib-файле. Потом этот lib-файл можно включать в собственный проект. Правда, перенос ресурсов из одного модуля в другой не реализован, а без них и сами функции могут оказаться бесполезными.
Аватара пользователя
mc-black
Сообщения: 250
Зарегистрирован: 08 май 2008, 16:09
Откуда: Россия, Нижний Новгород
Контактная информация:

WinMain, dll2lib работает только с экспортируемыми функциями dll, создает статическую библиотеку ВСЕЙ dll. Пишут, что утилита не всегда работает - сам не пробовал. ИМХО несоразмеренно сложно "выдрать" функцию из exe/dll так, чтобы она потянула за собой все, что ей потребуется. А потребоваться функции может: другая функция (из этого же или из другого PE-файла), место под глобальные, статические переменные, переменные, которые берутся по ссылке внутри функции, импорты на внешние функции, которых нет в основной программе и может что-то еще. Кроме того, летит вся адресация, которую нужно как-то восстанавливать в программе. Если имеем пакер/криптор, то вообще засада. Короче задача не для одиночки и голыми руками не сделать. Я не исключаю возможность того, что для этого уже созданы какие-то инструменты о которых мне неизвестно, но в любом случае они не смогут работать в 100% случаев.

Если представить простейшую функцию типа "Hello World!", без заморочек, в чужой программе, надо бы было открыть в отладчике или дизассемблере, найти ее. Ассемблерный кусок листинга привести чуть в порядок, оформить этот кусок как dll и откомпилировать MASM. Компилятор дает при этом obj формат coff, который можно прилинковать к своей программе; линковщик дополнительно дает lib и dll - можно линковать lib к своей программе - это второй путь. Третий вариант при помощью lib.exe сделать из obj библиотеку импорта. Представь - пришлось бы фактически переписать всю функцию заново на ассемблере, вникая во все нюансы ее работы. По-моему, ненужный бесполезный труд.

Проще, как писал, загрузить левую библиотеку LoadLibrary, получить адрес чего-то внутри нее из экспортируемых GetProcAddress, смещением получать адрес функции, класть в стек параметры и вызывать функцию по адресу в ассемблерной вставке. Может можно обойтись и без GetProcAddress, кажется это называется смещением по ординалу. В формате PE и их образов недостаточно силен, кстати, не надо думать, мое мнение в этом вопросе авторитетно )
--------------------------------------------------------------------------------
Добавленное сообщение
--------------------------------------------------------------------------------
Опыт такого рода достаточен у людей, которые пробовали свои силы в написании разного рода пакеров, только они работают с PE-файлами взятыми целиком. И я полагаю, они на этот счет скажут, что это крайне затруднительно сделать.
--------------------------------------------------------------------------------
Добавленное сообщение
--------------------------------------------------------------------------------
Интересно почитать следующие обсуждения этого вопроса:
первое и второе
На заказ: VBA, Excel mc-black@yandex.ru
Аватара пользователя
Decoder
Сообщения: 308
Зарегистрирован: 19 фев 2008, 23:11
Откуда: Moscow

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

Код: Выделить всё

#include "stdafx.h"
#include <tchar.h>

BOOL PrintText(LPCTSTR szText)
{
    static HANDLE hConsole = ::GetStdHandle(STD_OUTPUT_HANDLE);
    //
    DWORD dw(0);
    return ::WriteConsole(hConsole, szText, ::lstrlen(szText), &dw, NULL);
}

BOOL SayHello() // передаваемая функция
{
    return PrintText(_T("Всем привет!\r\n"));
}

BOOL SayGoodBye() // передаваемая функция
{
    return PrintText(_T("Всем пока!\r\n"));
}

FARPROC fpFuncs[] = 
{
    (FARPROC)SayHello,
    (FARPROC)SayGoodBye
};

extern "C"
__declspec(dllexport)
FARPROC* DllGetProcTable() // экспортируемая функция
{
    return fpFuncs;
}

BOOL APIENTRY DllMain( HMODULE hModule,
                       DWORD  ul_reason_for_call,
                       LPVOID lpReserved)
{
    return TRUE;
}
Дальше идёт код приложения, которое загружает эту DLL, импортирует из неё единственную функцию и получает через неё доступ к другим (передаваемым) функциям...

Код: Выделить всё

#include "stdafx.h"

typedef BOOL (*SAYHELLO)(void);
typedef BOOL (*SAYGOODBYE)(void);
typedef FARPROC* (*GETPROCTABLE)(void);

int _tmain(int argc, _TCHAR* argv[])
{
    HMODULE hModule = ::LoadLibrary(_T("FreeLib.dll"));
    if (hModule != NULL)
    {
        GETPROCTABLE DllGetProcTable = 
            (GETPROCTABLE)::GetProcAddress(hModule, "DllGetProcTable");
        //
        FARPROC* lpFuncs = DllGetProcTable();
        SAYHELLO fnSayHello = (SAYHELLO)lpFuncs[0];
        SAYGOODBYE fnSayGoodBye = (SAYGOODBYE)lpFuncs[1];
        // вызов полученных функций...
        fnSayHello();
        fnSayGoodBye();
        //
        ::FreeLibrary(hModule);
    }
    getc(stdin);
    return 0;
}
После запуска приложения на экране появляются строки
Всем привет!
Всем пока!
Как видите, ничего сложного в этом нет.
Аватара пользователя
mc-black
Сообщения: 250
Зарегистрирован: 08 май 2008, 16:09
Откуда: Россия, Нижний Новгород
Контактная информация:

Decoder,
поздравляю, как видишь STDCALL в стиле API - не единственный путь вызова из DLL. Существуют более продвинутые техники: можно грузить DLL и вообще отказаться от всех таблиц импорта в вызывающей программе, связанных с вызываемой DLL, вплоть до отказа использования в своей программе таблиц импорта на LoadLibrary, GetProcAddress - вообще от всех импортов! Правда у меня нет уверенности, что компилятор автоматом не создаст какие-то таблицу для своих нужд, типа для CRT или просто для запуска. Если тема интересна, рекомендую погуглить насчет NoImport, NoImportEngine и т.п. Все сводится к нахождению базы kernel32, поиску в ней адреса LoadLibrary, GetProcAddress и к получению после этого адресов всего и вся. Этим пользуются системы защиты, пакеры и крипторы, эта техника при определенных условиях позволяет уменьшить размер откомпилированной программы и усложнить исследование программы под отладчиком.
На заказ: VBA, Excel mc-black@yandex.ru
Аватара пользователя
WinMain
Сообщения: 929
Зарегистрирован: 14 янв 2005, 10:30
Откуда: Москва
Контактная информация:

Похвально, Decoder.
Твои успехи впечатляют, наблюдается явный прогресс.
Аватара пользователя
Decoder
Сообщения: 308
Зарегистрирован: 19 фев 2008, 23:11
Откуда: Moscow

Приятно всё-таки услышать похвалу в свой адрес, значит не зря старался.
Фактически я только воплотил в коде ту идею, которую мне WinMain предложил...
WinMain писал(а): Если же тебе по каким-то причинам (на то должны быть реальные причины) нужно вообще отказаться от экспорта функций из DLL, то придётся самому реализовать альтернативный способ передачи адресов вызываемых функций в основное приложение.
WinMain, ты бы не мог мне по-подробнее рассказать про "альтернативные" способы? А то я покопался в интернете, но пока ничего вразумительного так и не нашёл.
Аватара пользователя
WinMain
Сообщения: 929
Зарегистрирован: 14 янв 2005, 10:30
Откуда: Москва
Контактная информация:

Официально документированного способа, альтернативного стандартному экспорту/импорту как бы не существует, поэтому неудивительно, что тебе не удалось его отыскать. Но это не означает, что его нельзя самому придумать. Основная сложность здесь вот какая: в языке С++ нет такого описателя переменных, который бы делал их видимыми из другого модуля, загруженного приложением. Область видимости глобальных переменных в языке С++ ограничена рамками одного проекта, а каждый исполняемый модуль или динамическая бибилиотека создаются в отдельных проектах. Так же нет возможности в С++ создавать объекты или массивы с присвоенным им текстовым именем, чтобы можно было создать объект в одном месте программы, а обратиться к нему по имени из другого места программы без использования глобальных переменных.
Но с помощью системных возможностей Windows кое-что можно сделать.
Простейший способ - использовать функции SetEnvironmentVariable и GetEnvironmentVariable, с помощью которых можно передавать адреса функций в виде текстовых строк внутри одного процесса.
Ещё можно в приложении с графическим интерфейсом через оконные сообщения передавать данные. Но лучше не связываться с оконными сообщениями, если это не касается непосредственно самого интерфейса.
Могу так же предложить для обмена данными использовать управляемую память процесса, так называемую кучу (heap), в которой можно выделить блок памяти из какой-то одной части приложения, а обратиться к нему из другой его части, даже из динамической библиотеки. Доступ к основной куче процесса можно получить с помощью функции GetProcessHeap(), а для выделения блока памяти в ней используется функция HeapAlloc(). Таким образом, при загрузке DLL ты можешь создать массив в основной куче процесса, поместить в него адреса функций, а потом из приложения обратиться к этому массиву, прочитать данные из него и по этим адресам вызывать нужные тебе функции.
Аватара пользователя
Decoder
Сообщения: 308
Зарегистрирован: 19 фев 2008, 23:11
Откуда: Moscow

Спасибо, WinMain. Идея интересная, только мне непонятно, каким образом я отыщу в куче именно "свой" массив данных. Функция HeapAlloc ничего, кроме указателя на массив, не возвращает. А если функция, создающая массив, и функция, читающая эти данные, находятся в разных модулях, то как им обратиться к одному и тому же массиву?
Аватара пользователя
mc-black
Сообщения: 250
Зарегистрирован: 08 май 2008, 16:09
Откуда: Россия, Нижний Новгород
Контактная информация:

WinMain,
Простейший способ - использовать функции SetEnvironmentVariable и GetEnvironmentVariable, с помощью которых можно передавать адреса функций в виде текстовых строк внутри одного процесса.
Если передавать текст, то может больше подходят Mutex? Еще близко к теме Atoms - используются для DDE - динамического обмена данными, может и их суда каким-то образом можно задействовать?
На заказ: VBA, Excel mc-black@yandex.ru
Аватара пользователя
WinMain
Сообщения: 929
Зарегистрирован: 14 янв 2005, 10:30
Откуда: Москва
Контактная информация:

Использование способов, которые применяются при обмене данными между приложениями, для внутрипроцессного решения будет избыточным и только усложнит реализацию. Здесь нужен-то всего один массив, через который будут передаваться адреса функций и какие-то другие значения.
Другое дело, каким способом распознать в памяти процесса именно тот самый массив, к которому нужно обратиться?
Суть в том, что куча устроена по принципу связанного списка, т.е. от одного блока памяти можно переходить к другому и так далее по цепочке с помощью функции HeapWalk(). В основной куче процесса могут находиться десятки и даже сотни других массивов, созданных самим процессом.
Поэтому, когда создашь свой массив в куче процесса, необходимо будет пометить его определённой сигнатурой, чтобы потом приложение могло по этой сигнатуре найти его среди других массивов. Для этого нужно в начало массива записать некое слово длиной 4-8 символов. Потом отступить ещё 4-8 байт и записать передаваемые данные, в нашем случае это адреса вызываемых функций.
Приложение, которое будет искать массив в основной куче процесса, будет по очереди сравнивать начальные данные каждого массива с заданной сигнатурой. Если они совпадут, значит это и есть "твой" массив. Дальше читай из него данные и вызывай функции...
Ответить