1. Этот сайт использует файлы cookie. Продолжая пользоваться данным сайтом, Вы соглашаетесь на использование нами Ваших файлов cookie. Узнать больше.
  2. Вы находитесь в сообществе Rubukkit. Мы - администраторы серверов Minecraft, разрабатываем собственные плагины и переводим на различные языки плагины наших коллег из других стран.
    Скрыть объявление
  3. Данный раздел создан исключительно для релизов! Вопросы по лаунчеру или обвязке задавайте ТОЛЬКО в соответсвующей теме автора. Любые другие темы будут удалены, а авторы понесут наказание.

Скрыть объявление
В преддверии глобального обновления, мы проводим исследования, которые помогут нам сделать опыт пользования форумом ещё удобнее. Помогите нам, примите участие!

Лаунчер [C++17|Avn2]: Пишем защиту вместе! #7 Виртуализация на службе у читера

Тема в разделе "Веб-обвязки и лаунчеры", создана пользователем HoShiMin, 16 мар 2019.

  1. Автор темы
    HoShiMin

    HoShiMin Старожил Пользователь

    Баллы:
    173
    #4: Защищаемся от инжектов через APC

    С основными методами инжекта разобрались, начинаются более сложные.
    Пусть инжект через APC используется достаточно редко, однако, он реализован в таких инжекторах, как ExtremeInjector и Xenos - и потому, его нельзя игнорировать.

    Для начала разберёмся, что такое APC.
    APC - Asynchronous Procedure Call - механизм асинхронного вызова процедур, реализованный в ядре Windows. Основное его предназначение - межпроцессное и межпоточное взаимодействие (RPC), организация очереди задач для пула потоков, а также, исходя из названия, поддержка асинхронных вызовов функций.

    У каждого потока есть своя собственная очередь APC, которую тот опустошает, исполняя накопившиеся APC, когда переходит в специальное Alertable-состояние. APC не могут быть доставлены потоку, пока тот не находится в Alertable-состоянии, и накапливаются в очереди.

    Чтобы перейти в Alertable-состояние, поток должен уйти в ожидание с помощью функций SleepEx, WaitForSingleObjectEx, WaitForMultipleObjectsEx или MsgWaitForMultipleObjectsEx, передав TRUE в параметр bAlertable. В этом случае поток уходит в Alertable-ожидание и исполняет накопленные APC, пока ожидаемый объект (в случае [Msg]WaitFor***-функций) не перейдёт в сигнальное состояние, после чего поток выходит из Alertable-состояния и продолжает исполнение в обычном режиме.

    Ядро может форсировать доставку юзермодных APC потокам, которые НЕ находятся в Alertable-состоянии. Пример такого кода в драйвере: ЗДЕСЬ.

    Также, поток может сам очистить свою очередь APC, выполнив их специальной функцией NtTestAlert.

    Чтобы добавить APC в очередь конкретного потока, существует функция QueueUserAPC в библиотеке kernel32.dll, которая сводится к сервису NtQueueApcThread в ntdll.dll.

    Пример инжекта через APC похож на способ с CreateRemoteThread(LoadLibrary), только, вместо создания нового потока, мы используем уже существующий, заставляя его подгружать нашу библиотеку:
    Код:
    #include <Windows.h>
    
    int main()
    {
        CHAR LibPath[] = "C:\\Folder\\Library.dll"; // Путь к дллке, которую хотим подгрузить
        DWORD ProcessId = 1234; // ID целевого процесса
        DWORD ThreadId = 5678; // ID целевого потока в целевом процессе
    
        // Открываем целевой процесс:
        HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, ProcessId);
    
        // Открываем целевой поток:
        HANDLE hThread = OpenThread(THREAD_ALL_ACCESS, FALSE, ThreadId);
    
        // Получаем адрес функции LoadLibrary, которую выполним в чужом процессе:
        PVOID pLoadLibrary = GetProcAddress(GetModuleHandle(L"kernel32"), "LoadLibraryA");
    
        // Выделяем в целевом процессе память под путь к дллке:
        PVOID pRemoteLibPath = VirtualAllocEx(
            hProcess,
            NULL,
            sizeof(LibPath),
            MEM_RESERVE | MEM_COMMIT,
            PAGE_READWRITE
        );
    
        // Записываем путь в память процесса:
        DWORD Written = 0;
        WriteProcessMemory(hProcess, pRemoteLibPath, LibPath, sizeof(LibPath), &Written);
    
        // Добавляем APC в очередь потока:
        QueueUserAPC((PAPCFUNC)pLoadLibrary, hThread, (ULONG_PTR)pRemoteLibPath);
    
        CloseHandle(hThread);
        CloseHandle(hProcess);
    }
    
    Этим кодом мы добавили в очередь потока APC с точкой входа на функции LoadLibrary, в качестве аргумента которой задали путь к библиотеке, которую хотим подгрузить. Осталось дождаться, пока поток перейдёт в Alertable-состояние и выполнит нашу LoadLibrary. Однако, стоит помнить, что поток может вообще не переходить в Alertable-состояние (тогда наш APC не выполнится никогда), поэтому нужно специально искать потоки, которые вызывают SleepEx или [Msg]WaitFor***Ex-функции.

    Чтобы понять, как защищаться от этого типа инжектов, рассмотрим, как происходит доставка APC потоку. Напишем простой код доставки APC самому себе и посмотрим, кто и откуда вызывает нашу процедуру.
    Код:
    #include <Windows.h>
    #include <winternl.h>
    
    #pragma comment(lib, "ntdll.lib")
    
    #define NtCurrentThread() ((HANDLE)-2)
    
    extern "C" NTSYSAPI NTSTATUS NTAPI NtQueueApcThread(
        IN HANDLE ThreadHandle,
        IN PIO_APC_ROUTINE ApcRoutine,
        IN OPTIONAL PVOID ApcRoutineContext,
        IN OPTIONAL PIO_STATUS_BLOCK ApcStatusBlock,
        IN ULONG ApcReserved
    );
    
    extern "C" NTSYSAPI NTSTATUS NTAPI NtTestAlert();
    
    static VOID NTAPI ApcRoutine(
        IN PVOID ApcContext,
        IN PIO_STATUS_BLOCK IoStatusBlock,
        IN ULONG Reserved
    ) {
        __debugbreak(); // Остановимся здесь
    }
    
    int main()
    {
        // Ставим APC в очередь сами себе:
        NtQueueApcThread(
            NtCurrentThread(),
            ApcRoutine,
            NULL,
            NULL,
            0
        );
    
        // Очищаем очередь APC:
        NtTestAlert();
    
        return 0;
    }
    
    Посмотрим трейс потока:
    upload_2019-3-26_22-14-29.png

    Нас интересует процедура KiUserApcDispatcher. Это юзермодный каллбэк, который вызывает ядро для доставки APC нашему потоку.

    upload_2019-3-26_22-31-46.png

    Рассмотрим подробнее, что происходило с потоком:
    1. Добавили APC с точкой входа на нашей функции ApcRoutine в очередь APC нашего потока с помощью функции NtQueueApcThread
    1. Ушли в ядро на сервисе NtTestAlert, переведя поток в Alertable-состояние
    2. Ядро стало доставлять накопившиеся APC нашему потоку (в данном случае, в очереди стоит только APC с точкой входа на нашей функции ApcRoutine)
    3. Для доставки APC наш поток из ядра вызывает юзермодный каллбэк KiUserApcDispatcher, в котором происходит вызов заданной нами функции
    4. APC отрабатывает и поток возвращается на то место, где прервал работу, с помощью сервиса NtContinue - таким образом, выходим из NtTestAlert.

    Для фильтрации APC перехватим KiUserApcDispatcher, чтобы анализировать точки входа и отсекать недоверенные. Здесь нас ждёт небольшая трудность: так как KiUserApcDispatcher - не обычная функция, а каллбэк из ядра, все аргументы передаются в стеке, что противоречит принятому в х64 соглашению fastcall. На чистом C/C++ мы не сможем реализовать корректный обработчик, т.к. компилятор не знает, как ему обрабатывать переданные аргументы, поэтому напишем прослойку на ассемблере, оборачивающую аргументы в понятный сишному коду вид с последующим вызовом обработчика на C/C++. Прототипы KiUserApcDispatcher на х32 и х64 отличаются:
    Код:
    // Для х64:
    __declspec(noreturn)
    VOID NTAPI KiUserApcDispatcher(CONTEXT Context);
    
    // Для x32:
    __declspec(noreturn)
    VOID NTAPI KiUserApcDispatcher(
        PVOID NormalRoutine,
        PVOID SystemArgument1,
        PVOID SystemArgument2,
        CONTEXT Context
    );
    
    На x64 адрес APC хранится в поле Context.P4Home; аргумент - в поле Context.P1Home.
    На х32 под адрес APC и аргумент выделены специальные аргументы - NormalRoutine и SystemArgument соответственно.
    Всё остальное содержимое структуры Context - контекст, который следует восстановить, чтобы вернуть поток на место, где его прервали для обработки APC.
    Таким образом, если хотим отменить вызов APC и сразу вернуться на продолжение оригинального кода, достаточно вызвать NtContinue(&Context).

    Итак, напишем ассемблерную прослойку, приводящую аргументы на стеке в понятный сишному коду вид и вызовем сишный обработчик, в котором безусловно отменим все APC:
    upload_2019-3-26_22-58-45.png
    И сишный обработчик:
    Код:
    static BOOL IsApcAllowed(PVOID ApcRoutine)
    {
        // Здесь должны быть проверки, но для примера запретим все APC:
        return FALSE;
    }
    
    [[noreturn]]
    static inline VOID DiscardApc(PCONTEXT Context)
    {
        NtContinue(Context, FALSE);
    }
    
    extern "C" {
        VOID NTAPI KiApcStub(); // Импортируем прослойку из ассемблерного кода
    
    #ifdef _AMD64_
        // Адрес оригинального диспетчера, полученный после хука:
        VOID (NTAPI *OriginalApcDispatcher)(CONTEXT Context) = NULL;
         
        VOID NTAPI ApcHandler(PCONTEXT Context)
        {
            // ApcRoutine = Context->P4Home, Arg = Context->P1Home:
            if (!IsApcAllowed(reinterpret_cast<PVOID>(Context->P4Home)))
                DiscardApc(Context);
        }
    #else
        // Адрес оригинального диспетчера, полученный после хука:
        VOID(NTAPI* OriginalApcDispatcher)(
            PVOID NormalRoutine,   // ApcProc
            PVOID SystemArgument1, // Argument
            PVOID SystemArgument2,
            CONTEXT Context
        ) = NULL;
    
        VOID NTAPI ApcHandler(PVOID ApcRoutine, PVOID Arg, PCONTEXT Context)
        {
            if (!IsApcAllowed(ApcRoutine))
                DiscardApc(Context);
        }
    #endif
    
    Всё, что остаётся - перехватить KiUserApcDispatcher любой библиотекой для хуков, в качестве хука задать ассемблерный KiApcStub и записать адрес оригинального диспетчера в переменную OriginalApcDispatcher.

    KiApcStub вызовет наш ApcHandler, в котором мы можем провести полноценную фильтрацию и, при необходимости, отменить вызов APC, вернув контекст потока обратно функцией NtContinue(&Context).
     
  2. Автор темы
    HoShiMin

    HoShiMin Старожил Пользователь

    Баллы:
    173
    #5: Защита от угона контекста

    От угона контекста защититься нельзя.

    [​IMG]


    Для начала, рассмотрим, что это такое.
    Контекст - это состояние потока в определённый момент времени: значения всех регистров, содержимое стека, а также, адрес следующей инструкции, которую должен выполнить процессор на следующем шаге.

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

    Так как контекст подменяется внутри ядра, нет ни одного способа это обнаружить или предотвратить.

    Напишем класс, позволяющий выполнять шеллы в 64х-битных процессах:
    Код:
    #include <Windows.h>
    
    class Shell {
    #pragma pack(push, 1)
        typedef struct _SHELL_MAPPING {
            const unsigned char Reserved0[145];
            const void* Arg;
            const unsigned char Reserved1[2];
            const void* Func;
            const unsigned char Reserved2[147];
        } SHELL_MAPPING, *PSHELL_MAPPING;
    #pragma pack(pop)
        unsigned char Code[310] = {
            /* +000 */ 0x9C, // pushfq
            /* +001 */ 0x50, // push rax
            /* +002 */ 0x51, // push rcx
            /* +003 */ 0x52, // push rdx
            /* +004 */ 0x53, // push rbx
            /* +005 */ 0x54, // push rsp
            /* +006 */ 0x55, // push rbp
            /* +007 */ 0x56, // push rsi
            /* +008 */ 0x57, // push rdi
            /* +009 */ 0x41, 0x50, // push r8
            /* +011 */ 0x41, 0x51, // push r9
            /* +013 */ 0x41, 0x52, // push r10
            /* +015 */ 0x41, 0x53, // push r11
            /* +017 */ 0x41, 0x54, // push r12
            /* +019 */ 0x41, 0x55, // push r13
            /* +021 */ 0x41, 0x56, // push r14
            /* +023 */ 0x41, 0x57, // push r15
    
            /* +025 */ 0x48, 0x81, 0xEC, 0x00, 0x01, 0x00, 0x00, // sub rsp, 16 * sizeof(OWORD)
            /* +032 */ 0x0F, 0x29, 0x04, 0x24,                               // movaps [rsp + 0  * sizeof(OWORD)], xmm0
            /* +036 */ 0x0F, 0x29, 0x4C, 0x24, 0x10,                         // movaps [rsp + 1  * sizeof(OWORD)], xmm1
            /* +041 */ 0x0F, 0x29, 0x54, 0x24, 0x20,                         // movaps [rsp + 2  * sizeof(OWORD)], xmm2
            /* +046 */ 0x0F, 0x29, 0x5C, 0x24, 0x30,                         // movaps [rsp + 3  * sizeof(OWORD)], xmm3
            /* +051 */ 0x0F, 0x29, 0x64, 0x24, 0x40,                         // movaps [rsp + 4  * sizeof(OWORD)], xmm4
            /* +056 */ 0x0F, 0x29, 0x6C, 0x24, 0x50,                         // movaps [rsp + 5  * sizeof(OWORD)], xmm5
            /* +061 */ 0x0F, 0x29, 0x74, 0x24, 0x60,                         // movaps [rsp + 6  * sizeof(OWORD)], xmm6
            /* +066 */ 0x0F, 0x29, 0x7C, 0x24, 0x70,                         // movaps [rsp + 7  * sizeof(OWORD)], xmm7
            /* +071 */ 0x44, 0x0F, 0x29, 0x84, 0x24, 0x80, 0x00, 0x00, 0x00, // movaps [rsp + 8  * sizeof(OWORD)], xmm8
            /* +080 */ 0x44, 0x0F, 0x29, 0x8C, 0x24, 0x90, 0x00, 0x00, 0x00, // movaps [rsp + 9  * sizeof(OWORD)], xmm9
            /* +089 */ 0x44, 0x0F, 0x29, 0x94, 0x24, 0xA0, 0x00, 0x00, 0x00, // movaps [rsp + 10 * sizeof(OWORD)], xmm10
            /* +098 */ 0x44, 0x0F, 0x29, 0x9C, 0x24, 0xB0, 0x00, 0x00, 0x00, // movaps [rsp + 11 * sizeof(OWORD)], xmm11
            /* +107 */ 0x44, 0x0F, 0x29, 0xA4, 0x24, 0xC0, 0x00, 0x00, 0x00, // movaps [rsp + 12 * sizeof(OWORD)], xmm12
            /* +116 */ 0x44, 0x0F, 0x29, 0xAC, 0x24, 0xD0, 0x00, 0x00, 0x00, // movaps [rsp + 13 * sizeof(OWORD)], xmm13
            /* +125 */ 0x44, 0x0F, 0x29, 0xB4, 0x24, 0xE0, 0x00, 0x00, 0x00, // movaps [rsp + 14 * sizeof(OWORD)], xmm14
            /* +134 */ 0x44, 0x0F, 0x29, 0xBC, 0x24, 0xF0, 0x00, 0x00, 0x00, // movaps [rsp + 15 * sizeof(OWORD)], xmm15
    
            /* +143 */ 0x48, 0xB9, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // mov rcx, Arg
            /* +153 */ 0x48, 0xB8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // mov rax, Func
            /* +163 */ 0xFF, 0xD0, // call rax
    
            /* +165 */ 0x44, 0x0F, 0x29, 0xBC, 0x24, 0xF0, 0x00, 0x00, 0x00, // movaps xmm15, [rsp + 15 * sizeof(OWORD)]
            /* +174 */ 0x44, 0x0F, 0x29, 0xB4, 0x24, 0xE0, 0x00, 0x00, 0x00, // movaps xmm14, [rsp + 14 * sizeof(OWORD)]
            /* +183 */ 0x44, 0x0F, 0x29, 0xAC, 0x24, 0xD0, 0x00, 0x00, 0x00, // movaps xmm13, [rsp + 13 * sizeof(OWORD)]
            /* +192 */ 0x44, 0x0F, 0x29, 0xA4, 0x24, 0xC0, 0x00, 0x00, 0x00, // movaps xmm12, [rsp + 12 * sizeof(OWORD)]
            /* +201 */ 0x44, 0x0F, 0x29, 0x9C, 0x24, 0xB0, 0x00, 0x00, 0x00, // movaps xmm11, [rsp + 11 * sizeof(OWORD)]
            /* +210 */ 0x44, 0x0F, 0x29, 0x94, 0x24, 0xA0, 0x00, 0x00, 0x00, // movaps xmm10, [rsp + 10 * sizeof(OWORD)]
            /* +219 */ 0x44, 0x0F, 0x29, 0x8C, 0x24, 0x90, 0x00, 0x00, 0x00, // movaps xmm9,  [rsp + 9 * sizeof(OWORD)]
            /* +228 */ 0x44, 0x0F, 0x29, 0x84, 0x24, 0x80, 0x00, 0x00, 0x00, // movaps xmm8,  [rsp + 8 * sizeof(OWORD)]
            /* +237 */ 0x0F, 0x29, 0x7C, 0x24, 0x70,                         // movaps xmm7,  [rsp + 7 * sizeof(OWORD)]
            /* +242 */ 0x0F, 0x29, 0x74, 0x24, 0x60,                         // movaps xmm6,  [rsp + 6 * sizeof(OWORD)]
            /* +247 */ 0x0F, 0x29, 0x6C, 0x24, 0x50,                         // movaps xmm5,  [rsp + 5 * sizeof(OWORD)]
            /* +252 */ 0x0F, 0x29, 0x64, 0x24, 0x40,                         // movaps xmm4,  [rsp + 4 * sizeof(OWORD)]
            /* +257 */ 0x0F, 0x29, 0x5C, 0x24, 0x30,                         // movaps xmm3,  [rsp + 3 * sizeof(OWORD)]
            /* +262 */ 0x0F, 0x29, 0x54, 0x24, 0x20,                         // movaps xmm2,  [rsp + 2 * sizeof(OWORD)]
            /* +267 */ 0x0F, 0x29, 0x4C, 0x24, 0x10,                         // movaps xmm1,  [rsp + 1 * sizeof(OWORD)]
            /* +272 */ 0x0F, 0x29, 0x04, 0x24,                               // movaps xmm0,  [rsp + 0 * sizeof(OWORD)]
            /* +276 */ 0x48, 0x81, 0xC4, 0x00, 0x01, 0x00, 0x00, // add rsp, 16 * sizeof(OWORD)
    
            /* +283 */ 0x41, 0x5F, // pop r15
            /* +285 */ 0x41, 0x5E, // pop r14
            /* +287 */ 0x41, 0x5D, // pop r13
            /* +289 */ 0x41, 0x5C, // pop r12
            /* +291 */ 0x41, 0x5B, // pop r11
            /* +293 */ 0x41, 0x5A, // pop r10
            /* +295 */ 0x41, 0x59, // pop r9
            /* +297 */ 0x41, 0x58, // pop r8
            /* +299 */ 0x5F, // pop rdi
            /* +300 */ 0x5E, // pop rsi
            /* +301 */ 0x5D, // pop rbp
            /* +302 */ 0x5C, // pop rsp
            /* +303 */ 0x5B, // pop rbx
            /* +304 */ 0x5A, // pop rdx
            /* +305 */ 0x59, // pop rcx
            /* +306 */ 0x58, // pop rax
            /* +307 */ 0x9D, // popfq
            /* +308 */ 0xC3, // ret
            /* +309 */ 0xCC, // int 3h (breakpoint)
        };
        static_assert(sizeof(SHELL_MAPPING) == sizeof(Code), "sizeof(SHELL_MAPPING) != sizeof(Code)");
    
    public:
        Shell(LPCVOID FuncAddr, LPCVOID Arg)
        {
            auto Mapping = reinterpret_cast<PSHELL_MAPPING>(Code);
            Mapping->Func = FuncAddr;
            Mapping->Arg = Arg;
        }
    
        BOOL Execute(DWORD ProcessId, DWORD ThreadId)
        {
            HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, ProcessId);
            HANDLE hThread = OpenThread(THREAD_ALL_ACCESS, FALSE, ThreadId);
            if (!hProcess || !hThread) {
                if (hProcess) CloseHandle(hProcess);
                if (hThread) CloseHandle(hThread);
                return FALSE;
            }
              
            PVOID CodeBuf = VirtualAllocEx(hProcess, NULL, sizeof(Code), MEM_RESERVE | MEM_COMMIT, PAGE_EXECUTE_READWRITE);
            WriteProcessMemory(hProcess, CodeBuf, Code, sizeof(Code), NULL);
       
            SuspendThread(hThread);
    
            CONTEXT Context = {};
            Context.ContextFlags = CONTEXT_ALL;
            GetThreadContext(hThread, &Context);
    
            Context.Rsp -= sizeof(PVOID);
            WriteProcessMemory(hProcess, reinterpret_cast<PVOID>(Context.Rsp), &Context.Rip, sizeof(Context.Rip), NULL);
    
            Context.Rip = reinterpret_cast<DWORD64>(CodeBuf);
            SetThreadContext(hThread, &Context);
       
            ResumeThread(hThread);
    
            CloseHandle(hThread);
            CloseHandle(hProcess);
    
            return TRUE;
        }
    };
    
    В коде выше мы, несмотря на кажущуюся громоздкость, делаем простые вещи:
    1. Генерируем шелл, который:
    - Сохраняет состояние регистров
    - Вызывает заданную функцию с прототипом "VOID WINAPI Func(PVOID Arg)" с заданным аргументом
    - Восстанавливает состояние регистров
    - Возвращается по адресу, который лежит на вершине стека (в п.2 мы его туда положим)
    2. Вызываем шелл из заданного потока, подменяя его контекст:
    - Выделяем в процессе память и записываем туда наш шелл
    - Замораживаем целевой поток
    - Получаем его контекст
    - На вершину стека кладём адрес следующей инструкции, чтобы шелл после выполнения вернулся на то место, откуда мы угнали поток - таким образом, выполнение потока продолжится, как ни в чём не бывало
    - Подменяем адрес следующей инструкции на адрес нашего шелла
    - Размораживаем поток
    - Поток начинает исполнять шелл, сохраняет на стеке все регистры, вызывает заданную нами функцию, затем восстанавливает регистры и возвращается обратно на ту точку, откуда мы угнали поток, продолжая его нормальное исполнение

    Что мы можем этому противопоставить?
    Для начала, следует научиться отличать свою память от чужой: поставить фильтры на выделение исполняемой памяти - перехватить NtAllocateVirtualMemory, NtFreeVirtualMemory и NtProtectVirtualMemory, чтобы точно знать, какую память выделил наш процесс (эта память пройдёт через наши фильтры), а какую память выделили со стороны (этой памяти в списке известных нам регионов не будет, т.к. она не пройдёт через фильтры).
    Используя этот фильтр, мы можем периодически (например, по таймеру) проверять всю память процесса и искать неизвестные исполняемые регионы.
    Другой вариант - перехватить функции, которые теоретически могут быть вызваны из шелла (например, LoadLibrary или NtContinue, которая может быть использована для возврата из шелла на оригинальный код потока), чтобы проверять стактрейс потока: если увидим фрейм, который лежит не в модуле и не в известной нам памяти - считаем это за инжект.

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

    Однако, существует ещё один способ из разряда "клиент хочет странного": написать визор - бинарный транслятор, который будет исполнять машинный код в отдельном буфере или в анклаве. Это даст возможность сделать всю память процесса неисполняемой (за исключением системных точек входа внутри ntdll - Ki*** и некоторые Ldr***/Rtl***-функции) и шелл на первой же API-функции вылетит с ошибкой доступа. Этот способ сложный и выходит далеко за рамки этой статьи.
     
    Последнее редактирование: 31 мар 2019
  3. SergK35

    SergK35 Активный участник Пользователь

    Баллы:
    76
    Имя в Minecraft:
    Sergk35
    Данную защиту уже можно ставить на играбельный проект ? Есть ли какие-нибдьь эксперементабельные фичи, который стоит пока что не включать ?
     
  4. Автор темы
    HoShiMin

    HoShiMin Старожил Пользователь

    Баллы:
    173
    Пока нигде не тестировалось, дождись вайтлистов и поддержки правил блокировок, чтобы пропускать инжекты от валидных библиотек
     
  5. alexandrage

    alexandrage Старожил Пользователь

    Баллы:
    173
    Ага ага, когда можно будет маскировать читы под валидные либы:D. А то ни разу ж не интересно.
     
  6. Автор темы
    HoShiMin

    HoShiMin Старожил Пользователь

    Баллы:
    173
    Читы читами, но главное не защититься от всего на свете, а ничего при этом не сломать. А что до читов - замаскировать под валидную либу уже сложнее, чем просто беспрепятственно инжектить любым паблик-инжектором, это уже отсечёт какой-то процент читоделов.
     
  7. alexandrage

    alexandrage Старожил Пользователь

    Баллы:
    173
    Ага, читы станут платными, как под ваймворлд.
     
  8. Автор темы
    HoShiMin

    HoShiMin Старожил Пользователь

    Баллы:
    173
    А это даже хорошо, обе стороны в плюсе: читоделам прибыль и отваливаются читеры с пабликами, а модераторы добавляют новые приватки в блэклист (мы же знаем, кто в нас инжектит, нам нужно лишь решить, доверенный это софт или нет), и всё по кругу.
     
  9. SergK35

    SergK35 Активный участник Пользователь

    Баллы:
    76
    Имя в Minecraft:
    Sergk35
    Когда примерно стоит ожидать ? =)
     
  10. Автор темы
    HoShiMin

    HoShiMin Старожил Пользователь

    Баллы:
    173
    Нууууууууу... Я сейчас отложил эйвон, делаю плагинчик на транспорт. Так шо, как только - так сразу)
     
  11. HorizonInGames

    HorizonInGames Старожил Пользователь

    Баллы:
    103
    Exit code -1073741511
    Гугл машет прощайнескучай
     
  12. Автор темы
    HoShiMin

    HoShiMin Старожил Пользователь

    Баллы:
    173
    Экстрасенсов тут нет - при каких условиях, какое приложение, какой софт установлен, есть ли антивирусы или фаерволл, встроенная или дискретная видеокарта и какого производителя, стоят ли фирменные драйвера, какая версия операционки, какие параметры включены, что в AvnLog?

    Сам по себе Exit code вообще ни о чём не говорит.
     
    Последнее редактирование: 19 апр 2019
  13. SergK35

    SergK35 Активный участник Пользователь

    Баллы:
    76
    Имя в Minecraft:
    Sergk35
    Есть какие-нибудь новости на счет обновлений ? :rolleyes:
     
  14. Автор темы
    HoShiMin

    HoShiMin Старожил Пользователь

    Баллы:
    173
    Сделал систему репортов и базовую проверку исполняемых секций, но ещё нет биндингов к джаве.
    Сейчас в доработке:
    - Проверка только нужных модулей, чтобы не тратить ресурсы на ненужные дллки
    - Проверка памяти на предмет неизвестных участков
    - Собственно, биндинги к джаве и API

    Может, на выходных\праздниках что-то появится
     
  15. SergK35

    SergK35 Активный участник Пользователь

    Баллы:
    76
    Имя в Minecraft:
    Sergk35
    =)
     
  16. HorizonInGames

    HorizonInGames Старожил Пользователь

    Баллы:
    103
    Так понятнее?
    Windows 7 x32
    wrapper32.png
     
  17. Автор темы
    HoShiMin

    HoShiMin Старожил Пользователь

    Баллы:
    173
    Дичь какая.
    В документации никаких ограничений на версию Windows для этой функции не накладывается.
    https://docs.microsoft.com/en-us/windows/desktop/api/winnt/nf-winnt-rtlpctofileheader
    Или проблема в твоей kernel32.dll, или в доках, где действительно забыли указать, что эта функция доступна с какой-то более новой версии Windows (однако, исходя из её предназначения, она должна быть доступна, как минимум с Win 2000).

    Если скинешь свои kernel32.dll, kernelbase.dll и ntdll.dll, которые лежат в C:\Windows\System32, будет совсем здорово и поможет понять, действительно ли этой функции там нет и, если так, что используется вместо неё.
     
  18. HorizonInGames

    HorizonInGames Старожил Пользователь

    Баллы:
    103
    Кину вечером в лс.
    Может, данные о пк поспособствуют чему-то?
    [​IMG]
     
  19. Автор темы
    HoShiMin

    HoShiMin Старожил Пользователь

    Баллы:
    173
    > By AlexSoft
    Возможно, из-за неоригинальной сборки...
    Импорты всё равно поправлю (их можно вытащить не из kernel32, а из ntdll), но всё равно интересно, почему у тебя в k32 их нет, хотя по докам быть должны (например, у меня на Win10 x64 они есть)

    upload_2019-5-29_13-5-45.png
     
  20. HorizonInGames

    HorizonInGames Старожил Пользователь

    Баллы:
    103
    Кинул в лс.
    Тащемта первый запуск был вполне нормальный. А вот когда второй раз запустил враппер, все полетело к черту.
    После этого авангвард отклеивается на постоянной основе.
     

Поделиться этой страницей