Доступ к неэкспортируемым функциям DLL
Модераторы: Hawk, Romeo, Absurd, DeeJayC, WinMain
mc-black, скажи как знающий человек, если в DLL или в ЕХЕ- файле реализована некая функция (без разницы, экспортируемая она или нет), то нельзя ли каким-то образом извлечь исполняемый код этой функции и связанные с ней данные, а потом встроить их в своё приложение? А самой DLL потом вообще не пользоваться. На сколько такое вообще реально?
Я знаю, есть даже утилита dll2lib.exe, которая нечто подобное делает. Она извлекает исполняемый код динамической библиотеки и сохраняет его в статическом lib-файле. Потом этот lib-файл можно включать в собственный проект. Правда, перенос ресурсов из одного модуля в другой не реализован, а без них и сами функции могут оказаться бесполезными.
Я знаю, есть даже утилита 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-файлами взятыми целиком. И я полагаю, они на этот счет скажут, что это крайне затруднительно сделать.
--------------------------------------------------------------------------------
Добавленное сообщение
--------------------------------------------------------------------------------
Интересно почитать следующие обсуждения этого вопроса:
первое и второе
Если представить простейшую функцию типа "Hello World!", без заморочек, в чужой программе, надо бы было открыть в отладчике или дизассемблере, найти ее. Ассемблерный кусок листинга привести чуть в порядок, оформить этот кусок как dll и откомпилировать MASM. Компилятор дает при этом obj формат coff, который можно прилинковать к своей программе; линковщик дополнительно дает lib и dll - можно линковать lib к своей программе - это второй путь. Третий вариант при помощью lib.exe сделать из obj библиотеку импорта. Представь - пришлось бы фактически переписать всю функцию заново на ассемблере, вникая во все нюансы ее работы. По-моему, ненужный бесполезный труд.
Проще, как писал, загрузить левую библиотеку LoadLibrary, получить адрес чего-то внутри нее из экспортируемых GetProcAddress, смещением получать адрес функции, класть в стек параметры и вызывать функцию по адресу в ассемблерной вставке. Может можно обойтись и без GetProcAddress, кажется это называется смещением по ординалу. В формате PE и их образов недостаточно силен, кстати, не надо думать, мое мнение в этом вопросе авторитетно )
--------------------------------------------------------------------------------
Добавленное сообщение
--------------------------------------------------------------------------------
Опыт такого рода достаточен у людей, которые пробовали свои силы в написании разного рода пакеров, только они работают с PE-файлами взятыми целиком. И я полагаю, они на этот счет скажут, что это крайне затруднительно сделать.
--------------------------------------------------------------------------------
Добавленное сообщение
--------------------------------------------------------------------------------
Интересно почитать следующие обсуждения этого вопроса:
первое и второе
На заказ: VBA, Excel mc-black@yandex.ru
Всех рад поблагодарить за участие в обсуждении данного вопроса.
Как и обещал, готов предоставить отчёт о результатах своего эксперимента.
Вот код динамической библиотеки, которая экспортирует всего одну функцию и передаёт через неё указатели на другие функции...Дальше идёт код приложения, которое загружает эту DLL, импортирует из неё единственную функцию и получает через неё доступ к другим (передаваемым) функциям...
После запуска приложения на экране появляются строки
Как и обещал, готов предоставить отчёт о результатах своего эксперимента.
Вот код динамической библиотеки, которая экспортирует всего одну функцию и передаёт через неё указатели на другие функции...
Код: Выделить всё
#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;
}
Код: Выделить всё
#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 и к получению после этого адресов всего и вся. Этим пользуются системы защиты, пакеры и крипторы, эта техника при определенных условиях позволяет уменьшить размер откомпилированной программы и усложнить исследование программы под отладчиком.
поздравляю, как видишь STDCALL в стиле API - не единственный путь вызова из DLL. Существуют более продвинутые техники: можно грузить DLL и вообще отказаться от всех таблиц импорта в вызывающей программе, связанных с вызываемой DLL, вплоть до отказа использования в своей программе таблиц импорта на LoadLibrary, GetProcAddress - вообще от всех импортов! Правда у меня нет уверенности, что компилятор автоматом не создаст какие-то таблицу для своих нужд, типа для CRT или просто для запуска. Если тема интересна, рекомендую погуглить насчет NoImport, NoImportEngine и т.п. Все сводится к нахождению базы kernel32, поиску в ней адреса LoadLibrary, GetProcAddress и к получению после этого адресов всего и вся. Этим пользуются системы защиты, пакеры и крипторы, эта техника при определенных условиях позволяет уменьшить размер откомпилированной программы и усложнить исследование программы под отладчиком.
На заказ: VBA, Excel mc-black@yandex.ru
Похвально, Decoder.
Твои успехи впечатляют, наблюдается явный прогресс.
Твои успехи впечатляют, наблюдается явный прогресс.
Приятно всё-таки услышать похвалу в свой адрес, значит не зря старался.
Фактически я только воплотил в коде ту идею, которую мне WinMain предложил...
Фактически я только воплотил в коде ту идею, которую мне WinMain предложил...
WinMain, ты бы не мог мне по-подробнее рассказать про "альтернативные" способы? А то я покопался в интернете, но пока ничего вразумительного так и не нашёл.WinMain писал(а): Если же тебе по каким-то причинам (на то должны быть реальные причины) нужно вообще отказаться от экспорта функций из DLL, то придётся самому реализовать альтернативный способ передачи адресов вызываемых функций в основное приложение.
Официально документированного способа, альтернативного стандартному экспорту/импорту как бы не существует, поэтому неудивительно, что тебе не удалось его отыскать. Но это не означает, что его нельзя самому придумать. Основная сложность здесь вот какая: в языке С++ нет такого описателя переменных, который бы делал их видимыми из другого модуля, загруженного приложением. Область видимости глобальных переменных в языке С++ ограничена рамками одного проекта, а каждый исполняемый модуль или динамическая бибилиотека создаются в отдельных проектах. Так же нет возможности в С++ создавать объекты или массивы с присвоенным им текстовым именем, чтобы можно было создать объект в одном месте программы, а обратиться к нему по имени из другого места программы без использования глобальных переменных.
Но с помощью системных возможностей Windows кое-что можно сделать.
Простейший способ - использовать функции SetEnvironmentVariable и GetEnvironmentVariable, с помощью которых можно передавать адреса функций в виде текстовых строк внутри одного процесса.
Ещё можно в приложении с графическим интерфейсом через оконные сообщения передавать данные. Но лучше не связываться с оконными сообщениями, если это не касается непосредственно самого интерфейса.
Могу так же предложить для обмена данными использовать управляемую память процесса, так называемую кучу (heap), в которой можно выделить блок памяти из какой-то одной части приложения, а обратиться к нему из другой его части, даже из динамической библиотеки. Доступ к основной куче процесса можно получить с помощью функции GetProcessHeap(), а для выделения блока памяти в ней используется функция HeapAlloc(). Таким образом, при загрузке DLL ты можешь создать массив в основной куче процесса, поместить в него адреса функций, а потом из приложения обратиться к этому массиву, прочитать данные из него и по этим адресам вызывать нужные тебе функции.
Но с помощью системных возможностей Windows кое-что можно сделать.
Простейший способ - использовать функции SetEnvironmentVariable и GetEnvironmentVariable, с помощью которых можно передавать адреса функций в виде текстовых строк внутри одного процесса.
Ещё можно в приложении с графическим интерфейсом через оконные сообщения передавать данные. Но лучше не связываться с оконными сообщениями, если это не касается непосредственно самого интерфейса.
Могу так же предложить для обмена данными использовать управляемую память процесса, так называемую кучу (heap), в которой можно выделить блок памяти из какой-то одной части приложения, а обратиться к нему из другой его части, даже из динамической библиотеки. Доступ к основной куче процесса можно получить с помощью функции GetProcessHeap(), а для выделения блока памяти в ней используется функция HeapAlloc(). Таким образом, при загрузке DLL ты можешь создать массив в основной куче процесса, поместить в него адреса функций, а потом из приложения обратиться к этому массиву, прочитать данные из него и по этим адресам вызывать нужные тебе функции.
Спасибо, WinMain. Идея интересная, только мне непонятно, каким образом я отыщу в куче именно "свой" массив данных. Функция HeapAlloc ничего, кроме указателя на массив, не возвращает. А если функция, создающая массив, и функция, читающая эти данные, находятся в разных модулях, то как им обратиться к одному и тому же массиву?
- mc-black
- Сообщения: 250
- Зарегистрирован: 08 май 2008, 16:09
- Откуда: Россия, Нижний Новгород
- Контактная информация:
WinMain,
Если передавать текст, то может больше подходят Mutex? Еще близко к теме Atoms - используются для DDE - динамического обмена данными, может и их суда каким-то образом можно задействовать?Простейший способ - использовать функции SetEnvironmentVariable и GetEnvironmentVariable, с помощью которых можно передавать адреса функций в виде текстовых строк внутри одного процесса.
На заказ: VBA, Excel mc-black@yandex.ru
Использование способов, которые применяются при обмене данными между приложениями, для внутрипроцессного решения будет избыточным и только усложнит реализацию. Здесь нужен-то всего один массив, через который будут передаваться адреса функций и какие-то другие значения.
Другое дело, каким способом распознать в памяти процесса именно тот самый массив, к которому нужно обратиться?
Суть в том, что куча устроена по принципу связанного списка, т.е. от одного блока памяти можно переходить к другому и так далее по цепочке с помощью функции HeapWalk(). В основной куче процесса могут находиться десятки и даже сотни других массивов, созданных самим процессом.
Поэтому, когда создашь свой массив в куче процесса, необходимо будет пометить его определённой сигнатурой, чтобы потом приложение могло по этой сигнатуре найти его среди других массивов. Для этого нужно в начало массива записать некое слово длиной 4-8 символов. Потом отступить ещё 4-8 байт и записать передаваемые данные, в нашем случае это адреса вызываемых функций.
Приложение, которое будет искать массив в основной куче процесса, будет по очереди сравнивать начальные данные каждого массива с заданной сигнатурой. Если они совпадут, значит это и есть "твой" массив. Дальше читай из него данные и вызывай функции...
Другое дело, каким способом распознать в памяти процесса именно тот самый массив, к которому нужно обратиться?
Суть в том, что куча устроена по принципу связанного списка, т.е. от одного блока памяти можно переходить к другому и так далее по цепочке с помощью функции HeapWalk(). В основной куче процесса могут находиться десятки и даже сотни других массивов, созданных самим процессом.
Поэтому, когда создашь свой массив в куче процесса, необходимо будет пометить его определённой сигнатурой, чтобы потом приложение могло по этой сигнатуре найти его среди других массивов. Для этого нужно в начало массива записать некое слово длиной 4-8 символов. Потом отступить ещё 4-8 байт и записать передаваемые данные, в нашем случае это адреса вызываемых функций.
Приложение, которое будет искать массив в основной куче процесса, будет по очереди сравнивать начальные данные каждого массива с заданной сигнатурой. Если они совпадут, значит это и есть "твой" массив. Дальше читай из него данные и вызывай функции...