- 37
- 15
Lazy_importer
C++ (header only) библиотека для сокрытия импорта системных (и не только) DLL и вызовов из них.
C++ (header only) библиотека для сокрытия импорта системных (и не только) DLL и вызовов из них.
Введение:
Для начала необходимо ответить на вопрос: Зачем скрывать импорты DLL и(или) вызовы из них?
Простой ответ: чтобы усложнить жизнь ревёрсеру ваших программ.
Более раскрытый ответ: чтобы запутать ревёрсера во время "вскрытия" вашей программы, лишив его информации об используемых системных (и не только) библиотеках, о вызывах из них. Данная информация играет одну из ключевых ролей в ревёрсе программы, примеры её использования различны, начиная от банальных хуков функций внутри сторонних длл, заканчивая использованием дыр в этих же библиотеках (например пустые пространства кода внутри сегментов длл, для последующего встраивания туда шелл-кода).
Преимущества:
- Не выделяет виртуальную память для какой-либо функции (или при загрузке DLL).
- В исполняемом виде представляет из себя достаточно простой asm-код.
- Не оставляет defined strings при импорте/использовании функций библиотеки.
- Легковстраиваемая в любой проект (представляет из себя одиночный hpp файл).
- Интуитивно понятные названия inlined-функций.
Использование:
Вызов любой из функций осуществляется при помощи скрытого метода LI_FN, он динамичен, принимает аргументы и возвращает значения оригинальной функции.
Принцип работы заключается в извлечении оригинальной функции из библиотеки, с последующим встраиванием функции в непосредственный код программы, предварительно подвергнув её hash-morping`у. При традиционном использовании, функция импортируется из определенной библиотеки (указанной в PE-заголовках) и не находится напрямую в исполняемом коде.
C++:
// Использование скрытым методом, не отображается в разделе импортов PE-заголовков.
LI_FN(OutputDebugStringA)("string");
// Оригинальное использование, отобразится в любом дебаггере при изучении PE-заголовков,
// в разделе импорты.
OutputDebugStringA("string");
Лучше всего это демонстрируется при reverse-engineering`е исполняемого кода.
При традиционном способе всё отлично проявляется.
При использовании LI_FN, импортированная функция никак не отображается, и при исполнении представляет из себя примерно подобный код
Код:
for ( i = *(_QWORD **)(*(_QWORD *)(__readgsqword(0x60u) + 24) + 16i64); ; i = (_QWORD *)*i )
{
v1 = i[6];
v2 = *(unsigned int *)(*(signed int *)(v1 + 60) + v1 + 136);
v3 = (_DWORD *)(v2 + v1);
if ( v2 + v1 != v1 )
{
LODWORD(v4) = v3[6];
if ( (_DWORD)v4 )
break;
}
LABEL_8:
;
}
while ( 1 )
{
v4 = (unsigned int)(v4 - 1);
v5 = -2128831035;
v6 = (char *)(v1 + *(unsigned int *)((unsigned int)v3[8] + 4 * v4 + v1));
v7 = *v6;
v8 = (signed __int64)(v6 + 1);
if ( v7 )
{
do
{
++v8;
v5 = 16777619 * (v5 ^ v7);
v7 = *(_BYTE *)(v8 - 1);
}
while ( v7 );
if ( v5 == -973690651 )
break;
}
if ( !(_DWORD)v4 )
goto LABEL_8;
}
((void (__fastcall *)(const char *))(v1
+ *(unsigned int *)(v1
+ (unsigned int)v3[7]
+ 4i64 * *(unsigned __int16 *)(v1 + (unsigned int)v3[9] + 2 * v4))))("hello world");
LI_FN помимо обычного использования, позволяет различными способами оптимизировать ваш код и имеет для этого определенные суб-параметры.
C++:
// cached параметр вычисляет результат выполнения при первом использовании,
// и в дальнейшем возвращает сохраненное значение.
LI_FN(OutputDebugStringA)("string").cached()
// safe параметр позволяет безопасно использовать вызовы функций без unhandled exception,
// т.к при неверном выполнении функции, она попросту возвратит значение 0, не вызывая краш.
LI_FN(OutputDebugStringA)("string").safe()
// in параметр в отличие от других, позволяет принудительно указать источник функции,
// таким источник может выступать предварительно загруженный модуль.
// В данном примере, функция вызывается непосредственно из библиотеки Kernel32.DLL,
// тогда как без использования параметра in, программа попытается найти требуемую
// функцию в уже загруженных иных библиотеках.
LI_FN(OutputDebugStringA)("string").in("Kernel32.dll")
// Остальные параметры представляют из себя комбинацию описанных выше
LI_FN(OutputDebugStringA)("string").safe_cached()
LI_FN(OutputDebugStringA)("string").in_cached("Kernel32.dll")
LI_FN(OutputDebugStringA)("string").in_safe("Kernel32.dll")
LI_FN(OutputDebugStringA)("string").in_safe_cached("Kernel32.dll")
Но, если функция не найдена ни в одной из импортированных библиотек, что в этом случае?
Параметр in в таком случае так-же не поможет, вам необходимо предварительно загрузить требуемые библиотеки при помощи стандартных способов.
Если же не загрузить их, ваш исполняемый код вызовет краш и программа завершится.
C++:
// Предварительно загружаем требуемую нам библиотеки скрытым способом
LI_FN("LoadLibraryA")("Kernel32.DLL");
// Теперь, мы можем использовать любую из функций внутри этой библиотеки
LI_FN("OutputDebugStringA")("string").in(LI_MODULE("Kernel32.DLL").get())
При использовании LI_FN важно помнить о дефайнах/макросах функций.
Например OutputDebugString является обёрткой для двух различных функций - OutputDebugStringA и OutputDebugStringW в зависимости от используемой многобайтовой или иной кодировки.
При вызове дефайна OutputDebugString напрямую, ваш исполняемый код так-же вызовет краш.
В таком случае необходимо вызывать функцию напрямую, избегая различные обёртки.
C++:
// crash!!!
LI_FN(OutputDebugString)("string")
// всё ok!
LI_FN(OutputDebugStringA)("string")
// всё ok!
LI_FN(OutputDebugStringW)(L"string")
Не стоит забывать и о APISET-библиотеках, такой является Ole32.DLL. В данном случае необходимо вызывать функции из библиотеки combase.dll.
C++:
// crash!!!
// apiset библиотека
LI_FN(LoadLibraryA)("Ole32.DLL");
LI_FN(CoCreateGuid).in(LI_MODULE("Ole32.DLL").get())(&guid);
// всё ok!
LI_FN(LoadLibraryA)("combase.dll");
LI_FN(CoCreateGuid).in(LI_MODULE("combase.dll").get())(&guid);
Предирективы:
Lazy_Importer позволяет указать различные дефайны для эффективного и правильного использования своих фунций.
C++:
// позволяет импортировать библиотеки вне зависимости от регистра в их названии,
// без использования данной предирективы, следует соблюдать регистр, иначе последует краш.
#define LAZY_IMPORTER_CASE_INSENSITIVE
// заменяет get() на параметр safe()
#define LAZY_IMPORTER_CACHE_OPERATOR_PARENS
// добавляет более жесткие ограничения при поиске функции из импортированных библиотек.
#define LAZY_IMPORTER_HARDENED_MODULE_CHECKS
Библиотека: https://github.com/JustasMasiulis/lazy_importer
Автор этой блестящей библиотеки: JustasMasiulis и сообщество GitHub
C++:
// Загружаем библиотеку libcurl.dll
const char* libraryPath = (Core::getCurrentDirectory() + XorStr("\\libcurl.dll")).c_str();
HMODULE library = LI_FN(LoadLibraryA)(libraryPath);
FILE* fp;
long httpStatusCode = 0;
CURL* curl;
// Инициализация CURL.
curl = LI_FN(curl_easy_init).get()();
// Если CURL готов к работе.
if (curl)
{
setDefaultCURLOptions(curl);
// Открываем файл вывода.
fp = fopen(out, XorStr("wb"));
LI_FN(curl_easy_setopt).get()(curl, CURLOPT_URL, url);
LI_FN(curl_easy_setopt).get()(curl, CURLOPT_WRITEFUNCTION, Core::curlWriteToFile);
LI_FN(curl_easy_setopt).get()(curl, CURLOPT_WRITEDATA, fp);
CURLcode result = LI_FN(curl_easy_perform).get()(curl);
LI_FN(curl_easy_getinfo).get()(curl, CURLINFO_RESPONSE_CODE, &httpStatusCode);
if (result == CURLE_OK && httpStatusCode == 200)
{
// Процедуры очистки и закрытия файла.
LI_FN(curl_easy_cleanup).get()(curl);
fclose(fp);
return true;
}
else
{
Network::lastError = httpStatusCode;
LI_FN(curl_easy_cleanup).get()(curl);
fclose(fp);
return false;
}
}
LI_FN(curl_easy_cleanup).get()(curl);
Последнее редактирование: