Welcome to The Passion Of Code Laboratory!!!Статьи

API-функции и их адреса

Автор: Bill Prisoner / TPOC

Общий обзор и история

Создавая приложения в операционных системах Windows программисты вызывают так называемые функции API для последующего вызова системных сервисов операционной системы. Я имею ввиду приложения пользовательского режима. Для пользовательского режима существует четыре вида приложений – 1) процессы поддержки системы, 2) процессы сервисов, 3) пользовательские приложения, 4) подсистемы окружения. Процессы сервисов(services processes) и пользовательские приложения(user applications) не могут вызывать системные сервисы напрямую, вместо этого они используют функции подсистем окружения(environment subsystems). Всего на данный момент в операционной системе Windows 2000 поддерживается 3 подсистемы – Win32, POSIX, OS/2. Win32 – единственная их них которая является обязательной подсистемой, остальные вызываются по требованию. Win32 API(Application Programming Interface) – набор документированных функций для программирования пользовательских приложений. Интерфейс Win32 API представляет собой по сути дела набор DLL(Dinamic Link Library) которые содержат экспортируемые функции. Вот эти DLL – Kernel32.dll, Advapi32.dll, User32.dll, Gdi32.dll. Эти функции в свою очередь вызывают системные сервисы режима ядра. Чтобы использовать системный сервис поток пользовательского приложения через соответствующую API функцию переходит режим ядра с помощью специальной команды процессора. Операционная система перехватывает эту команду, обнаруживает запрос системного сервиса, проверяет аргументы для системной функции и выполняет нужную подпрограмму уже в режиме ядра, а затем поток переключается обратно в пользовательский режим. Так происходит вызов системных функций из пользовательского режима. К примеру возьмём вызов известной всем API функции MessageBox. Эта функция при её вызове вызывает другую API функцию – MessageBoxEx, та ещё какую-нибудь и т.д. После нескольких таких вызовов вызывается функция режима ядра RtlEnterCriticalSecion из библиотеки ntdll.dll. Ntdll.dll это библиотека исполнительной системы и как раз служит для поддержки подсистем окружения. Т.к. исполнительная система работает режиме ядра, то и вызов соответсвующей функции происходит в режиме ядра. Для функций в операционной системе Windows 2000(это относится к всему семейству OS Windows) определены соглашения по их именованию. Общий формат выглядит так:

<префикс><операция><обьект>, где <префикс> - внутренний компонент экспортирующий процедуру, <операция> - название операции выполняемой над обьектом, <обьект> - название объекта, над которым проводиться операция. Список префиксов приведён в таблице 1.
 

Префикс

Компонент
CcДиспетчер кэша
CmДиспетчер конфигурации
ExПодпрограммы поддержки исполнительной системы
FsRtlБиблиотека(периода выполнения) драйвера файловой системы
HalHAL
IoДиспетчер ввода-вывода
KeЯдро
LpcLPC
LsaЛокальная аутентификация
NtСистемные сервисы Windows
ObДиспетчер обьектов
PoДиспетчер электропитания
PpДиспетчер Plug and Play
PsПоддержка процессов
RtlСтандартная библиотека(периода выполнения)
SeЗащита
WmtWMI
ZwЗеркальная точка входа для системных сервисов, устанавливающих предыдущий режим доступа к ядру


Таблица 1. Общеупотребительные префиксы

 

Интересно, что поначалу Win32 API не планировался как основной интерфейс разработки приложений и в Windows NT основным интерфейсом планировался OS/2 Presentation Manager API, т.к. Windows NT – как было поначалу задумано (в 1989 году) это операционная система должна была заменить операционную систему OS/2. Но после выхода Windows 3.0 и её огромного успеха на рынке специалисты из Microsoft решили изменить курс и перенацелили проект Windows NT как будущую замену продуктов семейства Windows. До этого существовал только интерфейс Win16 API.

Вызов API функций

Программируя в Windows в user mode, т.е. в третьем кольце процессора кодер может использовать API-функции. Дальше вы узнаете как вызывать API-функции и некоторые тонкости связанные с вызовом API функций. Просмотрите этот малюсенький отрывок кода:

.386; директива препроцессору использовать инструкции процессора Intel 80386.
model flat,stdcall;flat – плоская модель памяти, stdcall – способ передачи параметров функциям(последний ;параметр кладётся в стек первым)


option casemap:none;указывает препроцессору различать регистр
includelib \masm32\lib\user32.lib;вставить библиотеку импорта
includelib \masm32\lib\kernel32.lib;вставить библиотеку импорта


EXTERN ExitProcess@4:NEAR;указываем какие функции мы будем импортировать из других модулей
EXTERN MessageBoxA@16:NEAR;указываем какие функции мы будем импортировать из других модулей


.data
Mes db “API – is cool!”,0;Строка в памяти

.code
begin:


push 0;uType
push offset Mes;lpCaption
push offset Mes;lpText
push 0;hWnd
call MessageBoxA@16;Вызов API-функции

push 0;ExitCode
call ExitProcess@4;Вызов API-функции


end begin

Вообще существует два стиля передачи параметров в подпрограмму – C и Pascal. В данном случае стиль передачи – это как кладутся параметры в стек и кто выравнивает стек. В Си передаче параметры кладутся в стек справа-налево, т.е. первый параметр кладётся в стек последним, и мы сами должны выравнить стек. Например,

push 0;uType
push offset Mes
push offset Mes
push 0;hWnd
call MessageBoxA
add esp,16;мы сами выравниваем(приводим в начальное положение) Stack Point

Не забывайте что стек растёт в сторону младших адресов, поэтому мы складываем.
В паскаль-передаче всё с точностью наоборот. Например,

push 0;hWnd
push offset Mes
push offset Mes
push 0;uType
call MessageBoxA@16

Здесь функция MessageBoxA сама выравнивает Stack Point. Но мы должны указать после имени функции и собачки – количество байт насколько выравнивать стек.
STDCALL же – это смесь Cи и Pascal передачи. Параметры кладутся в стек справа-налево и функция которую мы вызываем сама выравнивает стек. Например,

push 0;uType
push offset Mes
push offset Mes
push 0;hWnd
call MessageBoxA

Мы подключаем файлы .lib чтобы наша прога нашла точки входа в API-функции. Мы используем директиву EXTERN для указания линкеру, какие функции мы импортируем. Названия этих функций пойдут в импорт PE-файла.
Есть одно исключение в модели STDCALL. Это функция wsprintf. Если вы захотите использовать эту функцию вам придётся соответсвовать Сишному порядку вызова. Например,

push offset Mes
push offset Format
push offset Buffer
call wsprintf
add esp,12

Важно также отметить, что когда вы вызываете API-функцию ОС Windows, она не изменяет значение регистров EBX,ESI,EDI,EBP. Есть исключения для этого правила. Это исключение будет описано в Platform SDK.

Адреса API-функций

Допустим вы создали исполняемый модуль для подсистемы Win32(параметр /SUBSYSTEM:WINDOWS линковщика). И как вы думаете как находятся эти функции для их вызова. Раньше во времена DOS’а программы имели статические процедуры, т.е. при линковке объектный код процедуры вставлялся в наш executable-модуль из lib или obj файла. Теперь же lib файлы служат для того чтобы показать где искать API функции в памяти. Для всех DLL, например kernel32.dll, библиотека динамической компоновки должна всего один раз загрузиться в память, тогда как при статической линковке даже если программы используют один и тот же код подпрограммы, они содержат в себе этот код. А программа использую динамическую компоновку просто переходит на нужную точку входа в процедуру. При создании проекта в Visual C++ мы должны указать тип компоновки: статический или динамический. При динамическом налицо экономия оперативной памяти. В Windows появился новый объект называемый – объект проекция файла (file mapping object). С помощью механизма проекции файла, DLL проецируется на область разделяемой памяти(shared memory) которую могут использовать все пользовательские процессы. Обычно процесс находиться в своём собственном 32-битном адресном пространстве и не «видит» другие процессы. Но все библиотеки спроецированные на общий раздел памяти видны каждому процессу, поэтому процесс может использовать фрагменты кода этих библиотек. Когда ваш executable-модуль запускается, то загрузчик Windows использует информацию об импортируемых вашим модулем функций API. Загрузчик использует имена функций для поиска их реальных адресов в текущей операционной системе. В зависимости от операционной системы Windows(2k,9x,Me) адреса API-функций не фиксированы, т.е. они могут различаться. Если у вас есть самостоятельный exe-модуль то проблем нет, адреса вы найдете без проблем. Т.е. их найдет для вас загрузчик и заполнит таблицу адресов импортируемых функций. Если вы просто хотите получить адрес API функции, то вызовите API-функцию GetProcAddress. Она принимает хэндл DLL в которой находиться эта функция и её имя или ординал. Хэндл DLL это адрес её загрузки в памяти. Но что если у вас нет самостоятельного загрузочного модуля. К примеру это кусочек кода внедрённого в какой-то exeшник. Для того чтобы сделать этот кусочек кода переносимым на другие операционные системы Windows мы должны уметь искать адреса функций в текущей операционной системе. Мы конечно может сделать так – например вызываем AddAtomA:

call 77E693FE!

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

Получение адресов API-функций для любых платформ Windows

Когда мы запускаем executable-модуль, то OS с помощью функции CreateProcess создаёт объект процесс в памяти. CreateProcess находиться в kernel32.dll. Используя эту теорию можно найти VA(виртуальный адрес) kernel32.dll в памяти. Смотрите код:

.code
pop eax

И когда начинает выполнятся первая инструкция с стеке лежит адрес возврата из CreateProcess. И теперь в eax адрес возврата из CreateProcess и это находиться где-то в недрах kernel32.dll. А т.к. все в винде выравнивается на страницу, то чтобы найти адрес базы kernel32.dll идём вверх и ищем текст “ZM”. Потом “EP”. Если мы нашли PE-хидер, то переходим к таблице экспорта и по имени ищем имя функции GetProcAddress. Получаем индекс в массиве имён. Используем этот индекс в массиве ординалов и получаем индекс в массиве адресов функций. Далее необходимо получить хэндлы user32.dll, advapi32.dll и т.д. для передачи в GetProcAddress.

Заключение

После того как мы получили адреса API-функций можно их вызывать. Всё вышеприведённое нужно было для внедрямого кода в PE. Для того чтобы наш вирь мог вызывать API-функции. Далее можно приступить к процедуре заражения. Об основных методах заражения PE файлов мы можете прочитать в моей статье. Если в процессе чтения данной статьи вы что-то не поняли или у вас есть критические замечания, пишите bill_tpoc@mail.ru. До встречи в киберпространстве…

[C] Bill Prisoner / TPOC

Наши новости

Новые события из жизни нашей лаборатории

Статьи

Статьи и переводы лаборатории TPOC

Программы

Программы лаборатории TPOC

Релизы

Здесь мы сообщаем Вам, какие творения скоро появятся

Ссылки

Ссылки на сайты, где можно найти больше информации

Наша лаборатория

История нашей лаборатории и ее члены

 
Дата последнего обновления: 25 июля 2005 года
У вас есть предложения по нашему сайту?
Напишите сюда
Любимые сайты вирмейкеров:
(WASM)   (RSDN)