remotethrguard

борьба с удаленными потоками\\ или противостояние с малварью продолжается

крис касперски, ака мыщъх, a.k.a. nezumi, no-email

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

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

Внедрение в существующие файлы чревато еще более далеко идущими последствиями. Система блокирует запись в запущенные файлы и малаври приходится извращаться не по детски, чтобы преодолеть это ограничение (hint: переименовать файл, например, explorer.exe в _explorer_.exe — система не запрещает, — скопировать _explorer_.exe в explorer.exe и тут же внедрить в последний вредоносный код, после чего «убить» процесс _explorer_.exe, чтобы система перезапустила его вновь, открывая уже зараженный explorer.exe). Только это все равно не поможет, ибо не пройдет и секунды как возмутиться SFC, ответственная за контроль целостности системных файлов. Теоретически ее можно отключить (или обновить эталонную копию explorer.exe), но это даже не обсуждается, поскольку не вариант. Ну поборем мы SFC, так при очередном обновлении explorer.exe программа windows update сделает беременные глаза: что, это, мол, за китайские новости? Не знаю я такого файла и обновлять его не буду!!! Короче, трогать файлы на диске это неправильно.

Более продвинутая малварь внедряется прямо в адресное пространство доверенных процессов (т. е. таких процессов, которым заведомо разрешен выход в сеть по меньшей мере по одному порту, например, Outlook Express, IE, FireFox, Opera, etc). Такая малварь живет только в оперативном памяти, не оставляя на диске никаких следов, и умирает сразу же после перезагрузки, что существенно затрудняет ее обнаружение. А на счет смерти можно не волноваться, ибо если компьютер подключен к сети, мальварь будет приходить через «дыры» вновь и вновь до тех пор пока жертва их не залатает. Но свежие дыры появляются регулярно, а вот заплатки зачастую не скачиваются годами…

Существует по меньше мере два способа внедрения вредоносного кода в постороннее адресное пространство. Первый — получаем дескриптор процесса-жертвы вызовом OpenProcess, выделяем в нем немного памяти посредством VirtualAllocEx (не забыв установить ей атрибуты «исполняемый регион», поскольку на машинах с активным аппаратным DEP выполнение кода в области данных невозможно), копируем туда вредоносный код через WriteProcessMemory, после чего с помощью недокументированных функций тем или иным образом добываем дескриптор основного потока, «замораживаем» его (SuspendThread), получаем регистровый контекст (GetThreadContext), запоминаем его и перебрасываем регистр-указатель команд (EIP) на начало вредоносного кода, обновляем регистровый контекст (SetThreadContext) и «размораживаем» поток (ResumeThread), передавая управление на свой вредоносный код, делающий все, что «задумано» и возвращающий управление по оригинальному EIP. Все! Программа изнасилована! Лежит себе, широко раскинув ноги и даже не трепыхается. А что трепыхаться-то? Девственность ведь уже потеряна, причем давно…

Достоинство этого способа в том, что он работает как на 9x (95, 98, Me), так и на NT (W2K, XP, Server 2003, Виста, Longhorn). А недостаток — в его заметности. Последовательность вызовов типа OpenProcess\VirtualAllocEx\WriteProcessMemory\SuspendThread\GetThreadContext\SetThreadContext\ResumeThread практически никогда не встречается в честных программах и выдает малварь с головой, копытами и хвостом.

А вот другой подход — получаем дескриптор процесса-жертвы вызовом OpenProcess, выделяем в нем память посредством VirtualAllocEx, копируем туда вредоносный код через WriteProcessMemory и создаем удаленный поток с помощью API-функции CreateRemoteThread (причем, как показано в одноименной врезке, без VirtualAllocEx и WriteProcessMemory в некоторых случаях можно обойтись).

Достоинства такого подхода в технической простоте реализации и высокой скрытности, поскольку удаленные потоки активно создаются многими честными программами. Вот и попробуй определи — кто создал удаленный поток и зачем! (но мы все-таки определим).

Долгое время этот способ оставался на вторых ролях, поскольку, удаленные потоки существуют только в NT, а на 9x вместо функции CreateRemoteThread вставлена «заглушка», всегда возвращающая ошибку. Однако, за последние несколько лет весовая доля NT-подобных систем отхватила свыше 90% рынка осей, и на совместимость с 9x все махнули рукой, в результате чего создание удаленных потоков стало _основным_ способом внедрения малвари в доверенные процессы, о борьбе с которой мы сейчас и поговорим.

Загружаем soft-ice (именно soft-ice, OllyDbg и другие отладчики не прокатят), нажимаем <Ctrl-D> и, дождавшись появления на экране черного прямоугольника окна, устанавливаем точку останова на API-функцию CreateRemoteThread, после чего выходим из soft-ice на хрен:

# установка точки останова на CreateRemoteThread

:bpxCreateRemoteThread

# выход из soft-ice

:x

Листинг 1 установка точки останова на CreateRemoteThread в soft-ice

Теперь мы сможем контролировать вызовы CreateRemoteThread и при каждом таком вызове отладчик будет послушно всплывать, отображая состояние стека, регистров, памяти, передавая нам в руки штурвал. Штурвал — это штука такая, которой рулят. Еще она называется клавиатурой. Мышь отдыхает, уткнувшись копчиком в коврик. Однояйцовая, с давно нечищеным гениталием. Ну и зачем она нужна такая?

Ладно, не будем отвлекаться, а лучше запустим какое-нибудь приложение, например, FAR. Сразу же после появления голубых панелей на экране (ну, у кого голубых, а у мыщъх'а — черных), как тут же всплывает soft-ice, высвечивая имея функции — CreateRemoteThread в окне CODE и имя программы (FAR) в правом нижнем углу статусной строки (см. рис. 1).

Ни фига себе! Вроде бы честное приложение… и вдруг удаленные потоки. С какой это стати FAR лезет своими грязными лапами в чужие программы?! Может, у нас потому и глючит все!!! Но прежде, чем высаживаться на полное неглиже и жуткую измену (от которой не спасает даже смесь вальяны с мелатонином), попробуем все-таки разобраться, что это за гадость твориться такая. Команда «stack» (см. рис. 1) не кажет ничего интересного и потому приходится прибегать к помощи тяжелой артиллерии.

Рисунок 1 soft-ice отловил попытку создания удаленного потока честным приложением по имени FAR

Даем команду «dd» для переключения дампа в режим вывода двойных слов (см. рис. 2#1), после чего смотрим содержимое стека командой «d esp» (см. рис. 2#2), на вершине которого расположен адрес возврата из функции CreateRemoteThread в вызывающий ее код и равный в данном случае 77E9652Ch (внимание! в другой версии NT этот адрес будет отличаться!).

Смотрим – какой функции он принадлежит (см. рис. 2#3):«u 77E9652C» и… видим функцию CreateThread, создающую локальный поток внутри самого FAR'а.

Рисунок 2 раскрутка стека с целью поиска материнской функции, вызывающей CreateRemoteThread

И, действительно, дизассемблирование библиотеки KERNEL32.DLL с помощью IDA Pro или любого другого дизассемблера (но, IDA Pro все-таки круче!) доказывает (см. рис. 3), что на низком концептуальном уровне (уровне исполнительной системы, являющейся частью ядра), в NT существует _только_ функция CreateRemoteThread, а CreateThread – лишь тонкая «обертка», создающая удаленные потоки в текущей процессе (локальный поток представляет собой частный случай удаленного).

Рисунок 3 IDA Pro наглядно показывает, что локальный поток являются частным случаем удаленного

Таким образом, чтобы отбросить локальные потоки (нам совершенно неинтересные), необходимо запомнить адрес возврата из CreateRemoteThread в CreateThread, установив простейший фильтр в виде условной точки останова. Согласно синтаксисту soft-ice она задается так:

# удаляем ранее установленные точки останова

:bc *

# устанавливаем условную точку останова на CreateRemoteThread,

# игнорирующую вызовы из CreateThread (локальные потоки)

:bpx CreateRemoteThread if (esp→0 != 77E9652C)

# выходим на свободу

:x

Листинг 2 установка условной точки останова-фильтра

Теперь при запуске FAR'а soft-ice уже не всплывает, но тут же выпрыгивает при его закрытии. Как?! Еще один удаленный поток?! Смотрим на правый нижний угол статусной строки (см. рис. 4), где сейчас «поселился» CSRSS, что расшифровывается как Client/Server Runtime Sub-System – Клиент/Серверная Подсистема Времени Исполнения, чей адрес возврата равен 5FFAF3F4h.

Рисунок 4 клиент/серверная подсистема времени исполнения создает удаленные потоки в служебных целях

Просматривая карту памяти (команда «mod» в soft-ice), определяем, что адрес 5FFAF3F4h принадлежит модулю CSRSS.EXE (см. листинг 3), то есть это действительно Клиент/Серверная Подсистема Времени Исполнения. Так что все нормально.

# определяем адрес возврата из функции CreateRemoteThread

# (он лежит на вершине стека и выделен полужирным шрифтом с подчеркиванием)

:d esp

0023:00DBF440 5FFAF3F4 00000430 00DBF480 00000000 …_0………..

0023:00DBF450 77E88C27 00000002 00000000 00DBF48C '..w…………

0023:00DBF460 00DBF53C 00000000 00000001 000C0001 <……………

# определяем принадлежность адреса возврата по карте памяти

# (адрес принадлежащего к нему модуля выделен полужирным шрифтом с подчеркиванием)

:mod

hMod Base PEHeader Module Name File Name

80400000 804000C8 ntoskrnl \WINNT\System32\NTOSKRN1.EXE

5FF80000 5FF800C0 csrsrv \WINNT\system32\csrsrv.dll

5FF90000 5FF900D0 basesrv \WINNT\system32\basesrv.dll

5FFA0000 5FFA00C8 winsrv \WINNT\system32\winsrv.dll

5FFF0000 5FFF00D0 csrss \WINNT\system32\csrss.exe

77E10000 77E100D8 user32 \WINNT\system32\user32.dll

77F80000 77F800C0 ntdll \WINNT\system32\ntdll.dll

Листинг 3 разоблачение клиент/серверной подсистемы времени исполнения в soft-ice

И ведь ни фига не нормально! Не можем же мы терпеть постоянные всплытия отладчика, каждый раз разбираясь «правильно» это или «неправильно». Так что наш фильтр нуждается в совершенстве. Удаляем прежнюю точку останова и создаем новую, отбрасывающую как CreateThread, так и CSRSS (см. листинг 4).

# удаляем ранее установленные точки останова

:bc *

# устанавливаем условную точку останова на CreateRemoteThread,

# игнорирующую вызовы из CreateThread и из CSRSS.EXE

:bpx CreateRemoteThread if 1)

# выходим из soft-ice

:x

Листинг 4 установка двойного фильтра условной точки останова на CreateRemoteThread

Ага! Теперь, даже если ничего не трогать, soft-ice все равно всплывет, ругаясь на системные сервисы («service»), чье имя отображено в правом нижем углу статусной строки. Коварство NT не знает пределов!!! Удаленные потоки создает множество легальных компонентов! Попробуй, тут, на всеми ними уследи…

Ладно, парни, запарили жаловаться! Системных компонентов не так уж и много. В «стерильной» W2K SP0 (из которой выкинуты все лишние службы) их меньше десятка. Правда, удаленные потоки могут создавать так же и драйвера, но с драйверами все проще — их адрес возврата всегда больше 80000000h и для фильтрации достаточно забить всего одно условие: (esp→0 < 80000000). Если оно выполняется, отладчик всплывает. Если же нет — тихо фильтрует вызов и ждет дальше.

В конечном счете, soft-ice превращается в мощный проактивный антивирус, всплывающий только при вызове CreateRemoreThread из зловредных программ, пытающихся внедриться в доверенные приложения. Как этому помешать? Да очень просто!!! Достаточно заменить дескриптор процесса (лежащей по смещению +4 байта от регистра ESP) на ноль, обломив функции CreateRemoteThread с созданием удаленного потока (см. листинг 5).

# забиваем оригинальный дескриптор потока

:E (esp→4) 0

# выходим из soft-ice

:X

Листинг 5 блокировка создания удаленного потока

К сожалению, данный метод борьбы с малварью невозможно рекомендовать не продвинутым пользователям — вид soft-ice их приводит в ужас, граничащий с суицидом. Да и soft-ice таскать за собой накладно (и памяти он кушает будь здоров). К тому же, адреса точек вызова CreateRemoteThread могут измениться при установке очередной заплатки…

А что, если попробовать обойтись без soft-ice, написав относительно несложную программу?! Вот этим мы сейчас и займемся!

Прямой перехват CreateRemoteThread кажется очевидным решением, но это далеко не лучший и самый простой в реализации вариант. А давайте напишем простейшую DLL, с помощью которой будем отслеживать создание новых потоков!

Если динамическая библиотека имеет точку входа, то она вызывается при всяком создании/завершении потоков (а так же при загрузке/выгрузке DLL, но нам они сейчас неинтересны). Вызов происходит в контексте «подопытного» процесса _до_ окончательного формирования потока, то есть в тот момент когда можно получить идентификатор потока, прочитать его контекст и даже завершить поток вызовом TerminateThread, но сам поток еще не начал выполняться и потому зловредный код отдыхает, ну а на стеке находится… собственно говоря, гарантированно там находятся только аргументы функции DllMain, а все остальное — системно-зависимо и недокументированно. С другой стороны, user-space стек один и потому в него неизбежно попадают адреса возврата предыдущих системных функций, участвующих в подготовке потока к запуску.

Мыщъх расковырял стек до самого основания, пытаясь определить — отличается ли его содержимое в зависимости от типа создаваемого потока (локальный или удаленный) и — представьте себе — определил! В результате получилось мощное оружие против хакеров, бьющее точно в цель и укладывающее в пару десятков строчек кода на Си.

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

Рассмотрим листинг 6, совмещающий в себе DLL-монитор и тестовую программу номер один. Сделано это исключительно в целях экономии бумаги, чтобы не переводить лес на дублирующиеся листинги. Алгоритм ее работы ясен из сопутствующих комментариев:

#include <stdio.h>

#include <windows.h>

#include <process.h>

главная (и единственная) функция локального потока, получающая управления и тут же завершающая поток по return

int thread(void *x){ return 0; }

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

от Microsoft нас не поймет, а искать обходные пути дороже выйдет, чем набить одну строку (и еще 5 строк

комментария к ней, что в сумме дает 6) declspec(dllexport) char* demo() { return 0;} главная функция тестовой программы номер один (в DLL она не работает) main() { объявляем буфер для fgets char buf[666]; загружаем DLL-монитор в свое адресное пространство LoadLibrary(«KPNC.DLL»); создаем локальный поток _beginthread(thread,0,0); ждем нажатия на <ENTER> и отваливаем printf(«press enter to exit\n»);fgets(buf,66,stdin); } точка входа в DLL в тестовой программе она не работает BOOL APIENTRY DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved) { int a, *p; объявляем переменные switch (fdwReason) мониторим события { case DLL_THREAD_ATTACH: создан новый поток! распечатываем первые 20h двойных слов от вершины стека p = &a; for(a=0;a<0x20;a++) printf(«%08Xh\n»,*p++); printf(«создание потока: %08Xh,%08Xh\n», hinstDLL,lpvReserved); break; case DLL_THREAD_DETACH: поток ушел в суицид printf(«завершение потока %08Xh,%08Xh\n»,hinstDLL,lpvReserved); break; } карету мне карту, мотал я отсюда!!! return TRUE; } Листинг 6 исходный текст KPNC.c, совмещающей в себе тестовую программу с DLL-монитором Компилировать KPNC.c приходится в два прохода. Сначала создаем динамическую библиотеку-монитор (см. листинг 7), после чего генерируем исполняемый файл тестовой программы (см. листинг 8). Пара слов об используемых ключах. Ключ /LD предписывает компилятору Microsoft Visual Studio создавать динамическую библиотеку. По умолчанию (если его не указать) создается исполняемый файл. Ключ /MT (сокращение от Multi-Thread) указывает на необходимость использования многопоточной версии стандартной библиотеки Си. $cl /MT /LD KPNC.c Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 12.00.8168 for 80×86 Copyright (C) Microsoft Corp 1984-1998. All rights reserved. KPNC.c Microsoft (R) Incremental Linker Version 6.00.8168 Copyright (C) Microsoft Corp 1992-1998. All rights reserved. /out:KPNC.dll /dll /implib:KPNC.lib KPNC.obj Creating library KPNC.lib and object KPNC.exp Листинг 7 компиляция динамической библиотеки-монитора $cl /MT KPNC.c Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 12.00.8168 for 80×86 Copyright (C) Microsoft Corp 1984-1998. All rights reserved. KPNC.c Microsoft (R) Incremental Linker Version 6.00.8168 Copyright (C) Microsoft Corp 1992-1998. All rights reserved. /out:KPNC.exe KPNC.obj Creating library KPNC.lib and object KPNC.exp Листинг 8 компиляция тестовой программы номер один Теперь напишем тестовую программу номер два, создающую удаленный поток в первой программе. Для простоты идентификатор процесса определяется вручную (через «Диспетчер Задач»), а не автоматически. В остальном код программы не содержит ничего интересного (см. листинг 9): #include <stdio.h> #include <windows.h> #define PID 1368 идентификатор процесса для создания удаленного потока main() { объявляем переменные char buf[666]; DWORD id; HANDLE h; получаем дескриптор процесса для создания удаленного потока h = OpenProcess(PROCESS_ALL_ACCESS, 0, PID); создаем удаленный поток printf(«%x\n»,CreateRemoteThread(h, 0,0, 0x401481, 0, 0, &id)); ждем нажатия на клавишу и выходим printf(«press enter to exit\n»); fgets(buf,66,stdin); } Листинг 9 remotethread.c —исходный текст тестовой программы номер два Все необходимое для экспериментов уже готово. Самое время приступить к делу, засучив рукава и подтолкнув под себя хвост. Ну с хвостом мы еще разберемся, а сейчас запустим KPNC.exe на выполнение (см. рис. 5#1) и вызовем «Диспетчер Задач» нажатием <Ctrl>-<Shift>-<Esc>. Найдем в нем «KPNC.exe», запомнив идентификатор процесса, в данном случае равный 1104 (см. рис. 5#2). Подставим идентификатор в макрос «PID» программы remotethread.c (см. рис. 5#3), откомпилируем ее с ключами по умолчанию (см. рис. 5#4) и тут же запустим (см. рис. 5#5), не обращая внимание на ругательства компилятора. Тестовая программаномер один тут же грохнется (удаленный поток создается по случайному адресу), но библиотека-монитор успеет вывести на экран содержимое стека, которое нам и нужно Рисунок 5 исследование содержимого стека в момент создания нового локального и удаленного потоков Сравнение содержимого стека в случае создания локального и удаленного потоков обнаруживает одно, но очень существенное различие (см. таблицу 1). На самом дне стека, после цепочки нулей, лежит двойное слово, которое в случае локального потока указывает внутрь NTDLL.DLL (это легко определить по карте памяти приведенной в листинге 3), а вот в при удаленном потоке туда попадает указатель на user-space стек, равный 0012F788h (естественно, его значение в зависимости от ситуации может варьироваться в очень широких пределах). |локальный поток|удаленный поток| | 0062FC3Ch|0062FC3Ch| |0062FC60h|0062FC60h| |10001576h|10001576h| |10000000h|10000000h| |00000002h|00000002h| |00000000h|00000000h| |7FFDF000h|7FFDF000h| |0062FC74h|0062FC74h| |10001526h|10001526h| |0062FC80h|0062FC80h| |77F8806Ch|77F8806Ch| |10000000h|10000000h| |00000002h|00000002h| |00000000h|00000000h| |10001526h|10001526h| |7FFDF000h|7FFDF000h| |00134770h|00134770h| |0062FD1Ch|0062FD1Ch| |77F8AB2Fh|77F8AB2Fh| |10001526h|10001526h| |10000000h|10000000h| |00000002h|00000002h| |00000000h|00000000h| |7FFDF000h|7FFDF000h| |7FFDD000h|7FFDD000h| |00000000h|00000000h| |77F83A44h|77F83A44h| |0062FD30h|0062FD30h| |0062FD30h|0062FD30h| |77F8D514h|0012F788h**| |00300520h|00000000h| |00000000h|00000000h| |00000000h|00000000h| |00000000h|00000000h| |00000000h|00000000h| Таблица 1 сравнение содержимого стека в момент создания локального и удаленного потока по данным DLL-монитора Вырисовывается следующий алгоритм — спускаемся на дно стека, находим там цепочку нулей, поднимаясь вверх по которой («вверх» — это в область младших адресов) встречаем первое ненулевое двойное слово. Если оно лежит внутри NTDLL.DLL (базовый адрес загрузки которой легко получить вызовом GetModuleHandle), то все ОК, это локальный поток. В противном случае — давим поток на хрен через TerminateThread, предварительно убедившись, что мы не находимся внутри системного процесса, такого как Service.exe или CSRSS.EXE (имя определяется API-функцией GetModuleFileName). И последнее — чтобы подключить нашу динамическую библиотеку-монитор ко всем запускаемым процессам, ее достаточно прописать в следующей ветке системного реестра: HKLM\SOFTWARE\Microsoft\WindowsNT\CurrentVersion\Windows\AppInit_DLLs, после чего останется только радоваться жизни и обмывать победу над малварью свежим пивом. ===== »> врезка жизнь без WriteProcessMemory ===== Оба метода внедрения используют VirtualMemoryEx/WriteProcessMemory для выделения памяти в адресном пространстве чужого процесса и копирования туда вредоносного кода, что легко демаскирует малварь, поскольку к числу распространенных эти API-функции явно не относятся. Ну, ладно, без VirtualMemoryEx еще как-то можно обойтись, забросив код на вершину стека, но вот чем заменить WriteProcessMemory?! А вот чем!!! Помещаем зловредный код в динамическую библиотеку с неприметным названием. Находим в адресном пространстве чужого процесса функцию LoadLibrary, находящуюся в KERNEL32.DLL, которая вплоть до Висты у всех процессов располагается по тем же самым виртуальным адресам (то есть, нам достаточно прочитать адресное пространство _своего_ процесса, что никакая защита не запрещает). Теперь создаем удаленный поток внутри жертвы, указав в качестве стартового адреса потока адрес LoadLibrary, передав ей указатель на имя нашей DLL с единственным аргументом. Естественно, это имя должно присутствовать в _ее_ адресном пространстве, а потому нам опять-таки приходится сканировать внутренности KERNEL32.DLL (или, например, NTDLL.DLL), выискивая подходящую текстовую строку. Вот, собственно и все. Зловредная динамическая библиотека будет загружена в адресное пространство процесса-жертвы, DllMain получит управление и DLL может делать все, что ей заблагорассудиться. Предложенный мыщъх'ем способ легко отлавливаем такое проникновения, а защитные механизмы, основанные на WriteProcessMemory (их, кстати говоря, больше всего) – нет. ===== заключение ===== Абсолютных защит не существует и предложенный мыщъх'ем прием— не исключение. Умная малварь его сможем обойти, используя более продвинутые механизмы внедрения. Проблема меча и щита не имеет решения, но это не значит, что все щиты идут лесом. Даже плохой щит лучше чем полное отсутствие такового, к тому же, по меньшей мере 2/3 существующих зловредных программ легко ловятся DLL-монитором, в то время как разрекламированные антивирусные комплексы KAV, Dr.Web, NOD32 сидят себе в трее и даже не хрюкают. Выводы делайте сами!

1)
esp→0 != 77E9652C) && (esp→0 != 5FFAF3F4