 |

23.02.2023, 00:19
|
|
Познающий
Регистрация: 14.02.2023
Сообщений: 41
С нами:
1709244
Репутация:
13
|
|
Lazy_importer
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`е исполняемого кода.
При традиционном способе всё отлично проявляется.
1677095224124.png0Z0SK0 · 22 Фев 2023 в 23:19' data-fancybox="lb-post-1261067" data-lb-caption-extra-html="" data-lb-sidebar-href="" data-single-image="1" data-src="https://www.blast.hk/attachments/190991/" style="cursor: pointer;" title="1677095224124.png">
При использовании 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
)
;
|
|
|

23.02.2023, 00:40
|
|
Познающий
Регистрация: 07.01.2023
Сообщений: 56
С нами:
1763923
Репутация:
13
|
|
опять небезопасный способ!
да ты надоел
|
|
|

23.02.2023, 00:41
|
|
Познающий
Регистрация: 14.02.2023
Сообщений: 41
С нами:
1709244
Репутация:
13
|
|
Сообщение от AeSiK256
опять небезопасный способ!
да ты надоел
Укажи где именно.
|
|
|

23.02.2023, 00:44
|
|
Познающий
Регистрация: 07.01.2023
Сообщений: 56
С нами:
1763923
Репутация:
13
|
|
Сообщение от 0Z0SK0
Укажи где именно.
рантайм дебаг = F
|
|
|

23.02.2023, 00:45
|
|
Познающий
Регистрация: 14.02.2023
Сообщений: 41
С нами:
1709244
Репутация:
13
|
|
Сообщение от AeSiK256
рантайм дебаг = F
Не совсем понимаю о чем ты, и как это относится к теме.
|
|
|

23.02.2023, 03:53
|
|
Познавший АНТИЧАТ
Регистрация: 18.09.2017
Сообщений: 1,044
С нами:
4553429
Репутация:
153
|
|
Сообщение от 0Z0SK0
Не совсем понимаю о чем ты, и как это относится к теме.
Я так понимаю что если у тебя какая-то ошибка выскочит или краш на пустом месте и тебе будет нужно искать причину чтобы это исправить. Ты попросту её не найдешь так как runtime-debug -> press F или это будет очень сложно сделать.
|
|
|

23.02.2023, 04:07
|
|
Познающий
Регистрация: 14.02.2023
Сообщений: 41
С нами:
1709244
Репутация:
13
|
|
Сообщение от Scar_
Я так понимаю что если у тебя какая-то ошибка выскочит или краш на пустом месте и тебе будет нужно искать причину чтобы это исправить. Ты попросту её не найдешь так как runtime-debug -> press F или это будет очень сложно сделать.
Для этого предусмотрены методы safe и nt_safe для LI_FN вызова, и тоже самое для LI_MODULE.
Возвращающие 0 при неправильном выполнении функции, минуя появление undefined behavior.
Сообщение от Спойлер
https://github.com/JustasMasiulis/lazy_importer/blob/master/include/lazy_importer.hpp:
Код:
struct
safe_module_enumerator
{
using
value_type
=
const
detail
::
win
::
LDR_DATA_TABLE_ENTRY_T
;
value_type
*
value
;
value_type
*
head
;
LAZY_IMPORTER_FORCEINLINE
safe_module_enumerator
(
)
noexcept
:
safe_module_enumerator
(
ldr_data_entry
(
)
)
{
}
LAZY_IMPORTER_FORCEINLINE
safe_module_enumerator
(
const
detail
::
win
::
LDR_DATA_TABLE_ENTRY_T
*
ldr
)
noexcept
:
value
(
ldr
->
load_order_next
(
)
)
,
head
(
value
)
{
}
LAZY_IMPORTER_FORCEINLINE
void
reset
(
)
noexcept
{
value
=
head
->
load_order_next
(
)
;
}
LAZY_IMPORTER_FORCEINLINE
bool
next
(
)
noexcept
{
value
=
value
->
load_order_next
(
)
;
return
value
!=
head
&&
value
->
DllBase
;
}
}
;
|
|
|

24.02.2023, 23:57
|
|
Новичок
Регистрация: 13.02.2022
Сообщений: 12
С нами:
2236305
Репутация:
28
|
|
Сообщение от 0Z0SK0
Более раскрытый ответ: чтобы запутать ревёрсера во время "вскрытия" вашей программы, лишив его информации об используемых системных (и не только) библиотеках, о вызывах из них.
Увы, но реверсеру данная библиотека ничем не мешает, более того, она оставляет важный паттерн за собой из-за которого можно отследить все дальнейшие вызовы, а именно - обращение к PEB.
Более подробно как находить все вызовы импортов от LazyImport можно прочитать у меня в статье -> https://yougame.biz/threads/247347/
Хорошим аналогом для этой библиотеки может послужить любой коммерческий протектор, тот же VMProtect или Code Virtualizer с лёгкой вм, которые не будут так открыто палить ваш вызов
|
|
|
|
 |
|
Здесь присутствуют: 1 (пользователей: 0 , гостей: 1)
|
|
|
|