Использование WinApi в определенных ЯП

Привет, читатель! В данной статье пойдет речь о том, как разобраться в странных записях с msdn’а, вроде такой:


HANDLE WINAPI CreateFile(
  _In_      LPCTSTR lpFileName,
  _In_      DWORD dwDesiredAccess,
  _In_      DWORD dwShareMode,
  _In_opt_  LPSECURITY_ATTRIBUTES lpSecurityAttributes,
  _In_      DWORD dwCreationDisposition,
  _In_      DWORD dwFlagsAndAttributes,
  _In_opt_  HANDLE hTemplateFile
);

 

 

Это не сложно! Статью я решил разбить на:

1) Общий обзор — буду рассказывать о том, как microsoft записывает прототипы функций у себя на сайте ( msdn ).

2) обзор для Delphi, C++, Assembler — буду рассказывать о том, как использовать это в определенных языках программирования.

Начнем.

Общий обзор

Для начала, ссылку на сам msdn: https://msdn.microsoft.com/

Нас интересует эта страница: https://msdn.microsoft.com/en-us/library/windows/desktop/ff818516%28v=vs.85%29.aspx

На этой странице вы сможете найти нужные вам структуры и функции. А я сейчас вам расскажу, как в них разобраться. Для примера возьмем функцию, которую использовали в прошлой статье: MessageBox: https://msdn.microsoft.com/en-us/library/windows/desktop/ms645505%28v=vs.85%29.aspx


int WINAPI MessageBox(
  _In_opt_  HWND hWnd,
  _In_opt_  LPCTSTR lpText,
  _In_opt_  LPCTSTR lpCaption,
  _In_      UINT uType
);

 

 

Итак, первая строка: int WINAPI MessageBox

Означает, что MessageBox возвращает значение типа int. Значение int — целочисленное значение. Аналог integer’а в Delphi. Занимает 4 или 2 байта ( зависит от системы ), очень часто именно 4 байта. В современных компах именно 4 байта, так что про 2 можете забыть.

имеет конвенцию вызовов stdcall (winapi). Это важно для ассемблерщиков, кодеры на высокоуровневых языках могут не въезжать в это. Ассемблерщики поймут.

А если все же не поймут, то конвенция вызовов ( информация для тех, кто кодит на ассемблере ) — это правило, по которым в функцию передают параметры. stdcall означает, что аргументы передаются через стек в обратном порядке, стек очищает вызываемая функция. Существует еще множество: pascal, cdecl, stdcall самые распространенные. Конвенцию pascal в WinAPI не встречал, а вот cdecl есть одна часто используемая функция: wsprintf. cdecl означает, что параметры в функцию передаются через стек, стек очищает вызывающая функция. Это что касается x86 системы, в x64 системе распространен fastcall, где аргументы передаются через регистры, а если регистров не хватает — через стек.

MessageBox — имя функции.

Далее: _In_opt_  HWND hWnd

_In_opt_ означает, что данный аргумент принимается на вход.

Бывают еще следующие:

_out_opt_ и _in_out_opt_

Первый означает, что это аргумент на выход, т.е в переданную переменную будет что-то положено, именно поэтому, если у нас _out_opt_ передаем АДРЕС переменной. Если вы не знаете, как получить адрес переменной, то почитайте про указатели в своем языке программирования.

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

Следующее слово: HWND — означает, какой тип нужно передать. HWND — это хэндл окна, сокращенно от Handle of WiNDow ( ладно, забудьте, это я сам придумал, но, скорее всего, отсюда :D ).

Хэндл — это уникальный идентификатор какого-либо объекта.

hWnd — имя переменной, но не обращайте внимание, оно никакой роли не играет и используется во внутренней работе функции.

Теперь вы знаете, как понять прототип функции.

Где взять подробную информацию?

Подробную информацию можно взять в описании функции, в том же MSDN’е, давайте посмотрим страницу описания MessageBox: https://msdn.microsoft.com/en-us/library/windows/desktop/ms645505%28v=vs.85%29.aspx

Внизу, после прототипа функции мы можем найти подробное описание всех аргументов функции и возвращаемого значения.

Итак, давайте теперь подробно о популярных типах:

Что такое LPCTSTR

LPCTSTR — LP означает, что это указатель, т.е передаем мы адрес. константа, т.е строка не изменится. T — ASCII кодировка, почему T — не спрашивайте, не знаю, бывают еще Unicode строки, тогда будет LPCWSTR.

Str — указывает на то,  что у нас строка.

Что такое HANDLE

Handle — хэндл, я уже говорил о нем, является уникальным описателем объекта. Используя хэндл мы можем обращаться к ранее открытым файлам, процессам, созданным потоком и так далее.

Что такое UINT

UINT — сокращенно от Unsigned int, т.е беззнаковое целое.

Как понять приставку LP

LP означает, что передается указатель на что либо, другими словами, адрес переменной.

Например, LPHANDLE — указатель на переменную типа handle

Или LPINT — указатель на переменную типа int

О другом

Также бывают структуры: какие-либо структуированные данные: например, PROCESSENTRY32, содержит большое количество инфы о процессе.

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

Обзор для Delphi

Итак, приступим к делфям. Хоть я их не люблю, но…

Итак, в делфи LPCTSTR — это pansichar, или по другому, pchar. Чтобы передать строку какую-либо функции из переменной типа string, нужно это привести к типу pchar.

pchar(str);

НЕПРАВИЛЬНЫЙ пример на MessageBox

program hello;

uses Windows;

var a:string = 'Hello, World!';

begin

MessageBox(0, a, a, 0);

end.


 

 

Выдаст ошибку:

[Error] Project1.dpr(10): Incompatible types: ‘String’ and ‘PAnsiChar’

Как надо:

program hello;

uses
  Windows;

var a:string = 'Hello, World!';

begin

MessageBox(0, pchar(a), pchar(a), 0);

end.

Обзор для C/C++


#include <windows.h>

#pragma comment(linker, "/ENTRY:main /SUBSYSTEM:WINDOWS")

int main()
{
  char Hello[] = "Hello, World!";
  MessageBox(0, Hello, Hello, 0);
  return 0;
}

Ведь строка — это массив символов!

#pragma comment(linker, «/ENTRY:main /SUBSYSTEM:WINDOWS») означает, что точка входа у нас будет в main, а подсистема будет WINDOWS. Это нужно, чтобы не создавалось лишних окон, вроде консольного окна.

Обзор для Assembler

Юзать буду, как всегда, FASM. Хоть я больше люблю MASM, но FASM более доступен новичку.


include 'win32ax.inc'

section '.code' code readable executable

body db 'Hello, World!',0

title db 'Hi! I am a title',0

start:

xor ebx, ebx

push ebx

push title

push body

push ebx

call [MessageBoxA]

push 0

call [ExitProcess]

.end start

xor ebx, ebx — аннулируем ebx, в нем будет 0. Это быстрее чем mov ebx, 0 и оптимальнее заносить push ebx, нежели push 0 ( что равносильно push 00000000 )

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

Но! В FASM’е и MASM’е есть специальный макрос: invoke.

Он позволяет вызывать функции привычно, давайте посмотрим:


include 'win32ax.inc'

section '.code' code readable executable

body db 'Hello, World!',0

title db 'Hi! I am a title',0

start:

invoke MessageBoxA, 0, body, title, 0

invoke ExitProcess, 0

.end start

макрос invoke сам передаст аргументы, как того требует функция, если надо очистить стек — очистит. При компиляции это все заменится на привычные нам push и call

 

Что такое постфиксы A и W к функциям?

В примерах на ассемблере я использую MessageBoxA, почему?

Дело в том, что нет такой функции MessageBox, есть только MessageBoxA и MessageBoxW

Отличие в том, что первая принимает ASCII строку, а другая Unicode строку.

Те функции, которые не требуют себе строку будут выглядить обычно: CreateThread, например. А вот требующие, например, wsprintf иначе: wsprintfA/wsprintfW

Компилятор C/C++ и Delphi сам подбирает нужную, исходя из настроек. По умолчанию обычно стоит A, поэтому я их и не прописывал. Но, вы можете попробовать — компилятор все равно скомпилирует все верно.

 

 

Введение в системное программирование

Введение

Системное программирование — создание системных программ под различные операционные системы ( ОС ), либо написание самих ОС. Например, системным программным обеспечением ( ПО ) под Linux является: демоны, различные скрипты для настройки системы, вредоносные программы, будь то руткиты, или бэкдоры. Под Windows: службы, драйвера, в общем, все, что взаимодействует плотно с системой. Говоря в общем случае: системное ПО — это такое ПО, которое непосредственно использует возможности операционной системы. В данном блоге мы будем изучать системное программирование под Windows.

Как устроена Windows?

Буду краток. В папке Windows находятся служебные файлы, в папке System32 системные утилиты и библиотеки. Что такое системная библиотека, например: kernel32.dll,  user32.dll, advapi32? Данная библиотека содержит в себе различные функции, которые могут быть вызваны любой программой. Например, функция ExitProcess из kernel32.dll служит для завершения собственного процесса, функция MessageBox из user32.dll для отображения какого-либо сообщения на экране. С этими функциями мы будем работать вплотную, функции из таких библиотек называются Windows API, или сокращенно WinApi. API — это программный интерфейс, его можно встретить где угодно, будь то API скайпа, API вконтакте, или API какого-либо интернет-сервиса. Windows тоже имеет свой API, о котором мы уже сказали.

Что же происходит при вызове APi функций?

Давайте посмотрим: Допустим, мы вызываем ExitProcess. Эта функция является всего-лишь оболочкой, для более низкоуровневых функций. В нашем случае, вызывая ExitProcess уже сама функция вызывает RtlExitUserProcess из ntdll.dll, а та, в свою очередь, ZwTerminateProcess, которая уже напрямую ( либо через FastSysCall, либо sysenter, об этом позже ) вызывает системное прерывание, недоступное из третьего кольца ( об этом потом ).  В общем случае, «путь» вызовов выглядит так: ExitProcess -> RtlExitUserProcess -> ZwTerminateProcess Вы спросите: а почему сразу не вызвать? Можно, мы можем вызвать хоть ZwTerminateProcess, вопрос лишь в удобстве. Функции ExitProcess мы передаем лишь один параметр: код завершения, а ZwTerminateProcess требует даже хэндл процесса ( о хэндлах потом ).

Что такое Zw/Nt функции?

Их называют Native функции ( NTAPI ). В User-mode ( третье кольцо, о них читайте дальше ) Zw и Nt функции одинаковы, так что различие между ними в 0-м кольце не буду вводить, так как мы его не будем трогать. Причины опять же смотрите позже :) Эти функции находятся в ntdll.dll, они плохо документированы, и Microsoft в любой момент может изменить их, даже не сообщив. Так что от использования лучше воздержаться, если только вы пишите не вредоносное ПО. Почему вредоносам лучше это использовать? Причина проста: многие мониторы ставят хуки ( перехват API функций ) на WinApi, и вызовы Nt/Zw функций они могут просто пропустить. Вредоносными программами мы тут заниматься не будем, потому что, во-первых — это не законно. Во-вторых, из полученных знаний вы сами сможете их написать. Я, конечно же, не несу ответственности за ваши действия, так как полицейский может пойти убивать, и кто понесет ответственность? Только он. Ладно, что-то отошел от темы.

Кольца защиты

Всего существует 4 кольца защиты: 0-е, 1-е, 2-е, 3-е. Мы с вами тут рассмотрим только два: 0-е и 3-е. Итак, в 3-е кольце защиты, его другие названия: пользовательское пространство, пользовательский режим ( user-mode ). Ну и сленговое: юзермодное приложение, как пример, приложение которое работает в 3-м кольце защиты. В 3-м кольце защиты работают все пользовательские приложения, и системные: браузер, игры, аська там, проводник винды, svchost и так далее. Они имеют ограниченные права: не могут выполнять привилегированные команды процессора, не могут изменять системные структуры, такие как SSDT таблица. 0-е кольцо — кольцо власти, как я его называю, там можно все: от изменения ядра ОС, до повышения прав калькулятора до системных. На этом кольце работают драйвера. Их написание мы не будем рассматривать, так как оно довольно хорошо описано самим майкрософтом и с Win Vista требуется цифровая подпись для драйвера, чтобы его загрузить. Хотя буткит Rovnix каким-то образом ( подозреваю, что на этапе загрузки :)) без цифровой подписи грузил драйвера, можете погуглить.

Что происходит при запуске исполняемых файлов?

Только в общих словах: загрузчик ( не тот, который грузит ОС, а тот, который грузит PE файлы в память ), проверяет, является ли файл действительно PE файлом ( иногда можем увидеть сообщение, что приложение не является исполняемым Win32, это тогда, когда проверка прошла неудачно ). Далее он заполняет таблицу импорта ( IAT таблица ), выделяет память и грузит туда файл по определенному адресу, и создает поток на точке входа ( EntryPoint ), исключение, если есть TLS-callback`и, которые вызываются до точки входа. Подробнее можете почитать в статьях на васме: http://wasm.ru/wault/article/show/green2red02 ( не реклама ). Статья может показаться сложной, ничего страшного — тема не простая.

На кого рассчитан данный блог?

В первую очередь, на людей, знакомым с программированием на каких-либо компилируемых языках и желающих познакомиться с миром Windows, научившись использовать ее возможности по полной. На каких языках следует программировать? На тех языках, которые поддерживают вызов WinApi. Могу привести примеры, которые прекрасно подойдут: C/C++, Assembler, Pascal ( Delphi ), C#, VB Сам я знаю C++, Assembler и Pascal (Delphi) поэтому в книге преимущественно будут примеры на них.

Есть различия в WinApi на разных языках?

Ответ — НЕТ. Как уже было сказано: WinApi — это функции, которые может вызывать любая программа. Системные библиотеки то не изменятся, от того, на каком языке написана программа? К тому же, все программы, на любом компилируемом языке, превращаются в машинный код. За исключением .NET среды, там свой машинный код: p-code.  Но машинный код — он есть машинный.

Примеры создания сообщения на разных языках

Чтобы вы поняли, что различий существенных нет: только в синтаксисе языка. Я приведу простейший пример Hello, World’а. В следующих статьях научу использовать и понимать другие функции, а так же, научу пользоваться вас MSDN’м. Создание сообщения и выход из процесса в среде Delphi 7. Открываем Delphi 7. Потом: File -> New -> Other -> Console Application. Создается каркас

Удаляем {$APPTYPE CONSOLE} и добавляем в модули Windows. {$APPTYPE CONSOLE} нужен для указания того, что в SUBSYSTEM будет указан CONSOLE и тогда загрузчик создаст еще консольное окно, но нам оно не нужно. Вот приложение, создающее окно и выходящее из процесса:


program Project2;

uses
SysUtils,
Windows;

// в Windows - описание нужных нам API, но не всех.

begin
MessageBox(0, 'Hello, World!', 'Hi', MB_OK); // 0 - владелец, т.е мы. Hello, World - //сообщение, Hi - заголовок, MB_OK - тип сообщения

ExitProcess(0); // выходим из процесса
end.

C/C++


#include <windows.h>

int main()

{

MessageBox(0, "Hello, World!", "Hi", MB_OK);

ExitProcess(0);

}

Flat Assembler:


include 'win32ax.inc'

.data

mes db 'Hello, World',0

title db 'Hi' ,0

.code

start:

push 0

push title

push mes

push 0

call [MessageBoxA]

push 0

call [ExitProcess]

.end start

Как видите, разницы — почти никакой. Надеюсь вам понравилось, оставайтесь с нами!