Путеводитель по написанию вирусов под Win32

Мультитредность


Когда мне сказали, что в среде Win32 это очень легко сделать, мне пришло в голову, что это можно использовать для различных целей: выполнение кода во время выполнения другого кода (тоже из нашего вируса). Это было бы очень полезно, так как сэкономит вам время :).

Ок, основное назначение мультизадачной процедуры следующее:

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

Это кажется трудноватым, но здесь есть две API-функции, которые могут нас спасти. Их имена: CreateThread и WaitForSingleObject. Давайте посмотрим, что об этих функция говорит справочник по Win32 API.

-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=

Функция CreateThread создает тред, выполняющийся внутри адресного пространства вызывающего функцию процесса.

HANDLE CreateThread( LPSECURITY_ATTRIBUTES lpThreadAttributes, // указ. на аттр. безоп. треда DWORD dwStackSize, // нач. размер стека треда в байтах LPTHREAD_START_ROUTINE lpStartAddress, // указатель на функцию треда LPVOID lpParameter, // аргументы для нового треда DWORD dwCreationFlags, // флаги создания LPDWORD lpThreadId // указатель на возвращенный идентификатор треда );

Параметры ---------

• lpThreadAttributes: указатель на структуру SECURITY_ATTRIBUTES, которая определяет, сможет ли возвращенный хэндл наследоваться дочерним процессом. Если lpThreadAttributes равен NULL, хэндл не может наследоваться.

Windows NT: поле lpSecurityDescriptor задает дескриптор безопасности нового треда. Если lpThreadAttributes равен NULL, тред получает дескриптор безопасности по умолчанию.

Windows 95: поле lpSecurityDescriptor игнорируется.

• dwStackSize: задает в байтах размер стека нового треда. Если указан 0, то размер стека будет равен размеру стека главного треда процесса. Стек автоматически выделяется в адресном пространстве процесса и освобождается, когда тред завершает свое выполнение. Обратите внимание на то, что размер стека увеличивается по необходимости. CreateThread пытается выделить указанное количество байтов, а если это не удается, возвращает ошибку.


• lpStartAddress: стартовый адрес нового треда. Обычно это адрес функции, имеющая соглашение о вызове WinAPI, которая принимает 32-х битный указатель в качестве аргумента и возвращает 32-х битный код возврата. Ее прототипом является:

DWORD WINAPI ThreadFunc( LPVOID );



• lpParameter: задает 32-х битное значение, которое будет передано треду в качестве аргумента.

• dwCreationFlags: задает дополнительные флаги, контролирующие создание треда. Если задан флаг CREATE_SUSPENDED, тред создается в замороженном состоянии и начнет свое выполнение только тогда, когда будет вызвана функция ResumeThread. Если это значение равно нулю, тред начинает выполняться немедленно после создания. На данный момент другие значения не поддерживаются.

• lpThreadId: указывает на 32-х битную переменную, которая получает идентификатор треда.

Возвращаемые значения ---------------------

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

• Если вызов функции не удастся, возвращаемое значение будет равно NULL. Чтобы получить дополнительную информацию об ошибке, вызовите GetLastError.

Windows 95: CreateThread успешно выполняется только тогда, когда она вызывается в контексте 32-х битной программы. 32-х битная DLL не может создать дополнительный тред, если эта DLL была вызвана 16-ти битной программой.

-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=

Функция WaitForSingleObject возвращает управление программе, когда случается одно из следующего:

• Указанный объект находится в сигнализирующем состоянии.

• Закончился заданный интервал времени

DWORD WaitForSingleObject( HANDLE hHandle, // хэндл ожидаемого объекта DWORD dwMilliseconds // интервал таймаута в миллисекундах );

Параметры ---------

• hHandle: идентифицирует объект.

Windows NT: хэндл должен иметь доступ типа SYNCHRONIZE.

• dwMilliseconds: задает интервал таймаута в миллисекундах. Функция возвращает управление, если заданное время закончилось, даже если объект находится в несигнализирующем состоянии. Если dwMilliseconds равно нулю, функция тестирует состояние объекта и возвращает управление немедленно. Если dwMilliseconds равно INFINITE, интервал таймаута бесконечен.



Возвращаемые значения ---------------------

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

• Если вызов функции прошел неуспешно, возвращаемое значение равно WAIT_FAILED. Чтобы получить дополнительную информацию об ошибке, вызовите GetLastError.

-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=

Если этого для вас недостаточно, или вы не понимаете ничего, что написано в описании функций, вот ASM-пример.

;---[ CUT HERE ]------------------------------------------------------------- .586p .model flat

extrn CreateThread:PROC extrn WaitForSingleObject:PROC extrn MessageBoxA:PROC extrn ExitProcess:PROC

.data tit1 db "Parent Process",0 msg1 db "Spread your wings and fly away...",0 tit2 db "Child Process",0 msg2 db "Billy's awesome bullshit!",0

lpParameter dd 00000000h lpThreadId dd 00000000h

.code

multitask: push offset lpThreadId ; lpThreadId push 00h ; dwCreationFlags push offset lpParameter ; lpParameter push offset child_process ; lpStartAddress push 00h ; dwStackSize push 00h ; lpThreadAttributes call CreateThread

; EAX = Thread handle

push 00h ; 'Parent Process' blah blah push offset tit1 push offset msg1 push 00h call MessageBoxA

push 0FFh ; Ждем бесконечно push eax ; Хэндл ожидаемого объекта (тред) call WaitForSingleObject

push 00h ; Выходим из программы call ExitProcess

child_process: push 00h ; 'Child Process' blah blah push offset tit2 push offset msg2 push 00h call MessageBoxA ret

end multitask ;---[ CUT HERE ]-------------------------------------------------------------

Если вы протестируете вышеприведенный код, вы увидите, что если вы кликните по кнопке 'Accept' в дочернем процессе, то вам придется кликнуть также по 'Accept' родительского процесса, но если вы закроете родительский процесс, оба messagebox'а будут закрыты. Если родительский процесс умирает, все порожденные им процессы (здесь и далее до конца данного подраздела Billy употребляет слово 'процесс' в значении 'тред' - прим. пер.) также умирают. Но если умрет дочерний процесс, родительский выживет.

Таким образом с помощью WaitForSingleObject вы можете контролировать оба процесса - родительский и дочерний. Представьте себе следующие возможности: поиск по директориям в поисках определенного файла (например, MIRC.INI), и в то же время генерация полиморфного декриптора и распаковка дроппера... Вау! ;)

Смотрите туториал Benny о тредах и фиберах (29A#4) (есть на http://www.wasm.ru - прим. пер.).


Содержание раздела