Издательский дом ООО "Гейм Лэнд"ЖУРНАЛ ХАКЕР #101, МАЙ 2007 г.

Ваяем radmin

Андрей «LittleBudda» Шкрыль

Хакер, номер #101, стр. 120

(ShkrylAndrei@rambler.ru, www.vr-online.ru)

Написать программу для удаленного администрирования? Легко!

Как же интересно наблюдать на своем мониторе за тем, что делает твой сосед по отделу, и понимать, что под его грозным видом скрывается человек с трогательной привязанностью к сайтам знакомств и любовью к фотографиям пятничной тематики! Однако речь в этой статье пойдет совсем не о вуайеризме…

Зачем писать свой radmin? Не поверишь, но многие антивирусы теперь предупреждают пользователя не только о запуске (!), но даже о наличии оригинального radmin’а на жестком диске, не без оснований считая его программой как минимум подозрительной.

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

Приступим

В этом материале мы рассмотрим создание несколько усеченного и безвозмездного аналога radmin’а. Кстати, мы испытали его в рабочих условиях и узнали много нового о трудовых буднях Никитоса. Оказывается, он, как и робот Бендер, интересуется кулинарными онлайн-передачами! Надеюсь, со временем он угостит нас своей ядовитой стряпней ;).

Итак, приступим! Первым делом разработаем серверную часть. Размести на форме следующие компоненты (смотри рисунок):

  • TIdTCPServer (закладка Indy Servers) - как видишь, для работы с сетью мы будем использовать компоненты INDY;
  • два TEdit - в них вводятся IP-адрес сервера и номер порта, по которому будет происходить соединение;
  • TButton с текстом «Включить сервер» - с помощью нее ты будешь запускать сервер, давая тем самым возможность подключаться к своей машине. В конце концов, мы же все-таки должны оставить пользователю право решать, хочет он, чтобы за ним подсматривали, или нет ;);
  • TMemo - в нем будет вестись лог всех осуществляемых на сервере операций;
  • TImage - к нему нам потребуется две картинки, которые можно найти в исходниках программы, находящейся на диске. Зовут эти графические изображения eye1.bmp и eye2.bmp, а олицетворять они будут соответственно включенное и выключенное состояние сервера. Размести компоненты на форме.

Кодим сервер

Для начала создай обработчик для кнопки «Включить…», который будет задавать IP-адрес и номер порта для приема команд сервером. Делается это через свойство Bindings, оно является контейнером для всех сокетов, связанных с сервером (смотри врезку «Активизируем сервер»). Далее необходимо активизировать сервер посредством установки свойства Active компонента TIdTCPServer в True, при этом кнопка «Включить сервер» превращается в «Выключить сервер» и меняется картинка в TImage. Благодаря этому пользователь сможет визуально наблюдать состояние сервера. Листинг обработчика ты найдешь в исходниках программы на DVD, прилагающемуся к журналу.

Также нам потребуется специальная процедура Log - она отвечает за журналирование действий, осуществляемых сервером. Объяви ее в секции public формы следующим образом:

procedure TForm1.Log(S:string);

Реализация ее очень проста:

Memo1.Lines.Add(TimeToStr(Time)+' '+S);

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

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

Объяви в секции public формы процедуру GET_SCREEN. Ее реализация представлена ниже:

procedure TForm1.GET_SCREEN;

var

Desktop: TCanvas;

B: TBitmap;

W, H :Integer;

PNG: TPNGObject;

Kursor:TPoint;

TempRect:TRect;

begin

GetCursorPos(Kursor);

W:=Screen.Width;

H:=screen.Height;

TempRect:=Rect(Kursor.x,Kursor.y,Kursor.x+10,Kursor.y+10);

B:=TBitmap.Create;

B.Width:=W;

B.Height:=H;

Desktop:=TCanvas.Create;

try

with Desktop do

Handle := GetWindowDC(GetDesktopWindow);

with B.Canvas do

begin

Brush.Color:=clGreen;

CopyRect(Rect (0, 0, w, h),

DeskTop,

Rect(0, 0, w, h));

FillRect(TempRect);

end;

PNG := TPNGObject.Create;

PNG.Assign(B);

PNG.SaveToFile(ExtractFilePath(Application.ExeName)+'\'+'s.png');

finally

DeskTop.Free;

B.Free;

PNG.Free;

end;

end;

С помощью этой процедуры мы получим скриншот экрана, вместо курсора сделаем зеленый прямоугольник, для чего используем функции GetCursorPos, Rect и FillRect. Полученный графический образ мы сохраняем в файле s.png (комментарии к коду ты найдешь в исходниках программы).

Итак, мы подошли к заключительному этапу создания сервера. Сделай обработчик OnExecute компонента TIdTCPServer, в котором будет происходить анализ присланных команд от клиента (на основании их будут совершаться необходимые нам действия).

procedure TForm1.IdTCPServer1Execute(AThread: TIdPeerThread);

var

z: string;

fstream:TFileStream;

X,Y:integer;

K:TPoint;

begin

with AThread.Connection do

begin

//Читаем, что прислало нам клиентское приложение

z := ReadLn;

if SameText(Copy(z, 1, 11), 'get_screen') then

begin

Log('Запрошен скриншот');

GET_SCREEN;

fStream := TFileStream.Create(ExtractFilePath(Application.ExeName)+'\'+'s.png',

fmOpenRead + fmShareDenyNone);

//ПЕРЕДАЕМ файл клиентскому приложению

OpenWriteBuffer;

Log('Передаем файл s.png');

WriteStream(fStream);

Log('Файл передан');

CloseWriteBuffer;

FreeAndNil(fStream);

end;

...

//Обработка остальных команд

...

AThread.Connection.Disconnect;

end;

end;

Здесь приведен лишь фрагмент кода обработчика OnExecute, который отправляет скриншот экрана. Остальную часть ты найдешь в исходниках программы, расположенной на диске. Поговорим более детально о командах, которые может принимать наш сервер (с первой мы уже знакомы, это «get_screen». Остальные представлены на схеме, изображенной на рисунке.

Во время обращения к серверу каждый клиент создает свой собственный поток. Информация о нем содержится в переменной AThread, из которой мы читаем присланную команду с помощью функции ReadLn. Далее с помощью функции SameText мы осуществляем проверку того, является ли присланная команда разрешенной. Затем создаем поток и через него передаем файл клиенту, после чего рвем соединение, так как запрошенное действие выполнено.

Проектируем и пишем клиентскую часть

Я уже чувствую нетерпение, с которым ты хочешь приступить к тестированию разработанной тобой утилиты. Конечно, хочется расслабиться, откинувшись на спинку кресла (копирайт на эту фразу по-прежнему находится у инсталлятора Windows 98) и, никуда не бегая, решать (или создавать) все проблемы пользователей удаленно. Вот и отлично! Нам остался последний рывок - разработка клиентской части.

Создай новое приложения, не забыв при этом сохранить старое. Размести на форме следующие компоненты :

  • TIdTCPClient (закладка Indy Servers) - с помощью него ты будешь соединяться с сервером и посылать ему команды;
  • TGroupBox и в нем - два TEdit - в TEdit'ы ты в дальнейшем введешь IP-адрес сервера и номер порта, по которому будет происходить соединение;
  • четыре TButton с текстом «Скриншот», «Просмотр», «Управление», «Сообщение 'ПОРА РАБОТАТЬ!'», назови кнопки B_GetScr, B_Watch, B_Control, B_Message соответственно.
  • TCheckBox с текстом «Полноэкранный режим»;
  • два TTimer - один будет использоваться для работы в режиме просмотра удаленного рабочего стола, а второй - для работы в режиме управления; у обоих компонентов свойство Enabled установи в False, а свойство Interval сделай равным 400 - это частота обновления картинки экрана сервера и посылки серверу команд, заданная в миллисекундах;
  • TImage - в нем будет отображаться картинка с удаленного рабочего стола.

Приступаем к работе. Для кнопки «Скриншот» создай следующий обработчик:

procedure TForm1.B_GetScrClick(Sender: TObject);

var

s:TFileStream;

Bitmap: TBitmap;

PNG: TPNGObject;

begin

//Подключаемся к серверу

connect_to_server;

//Посылаем серверу команду «get_screen»

IdTCPClient1.WriteLn('get_screen ');

with IdTCPClient1 do

begin

if FileExists('C:\s.png') then DeleteFile('C:\s.png');

//Создаем поток

s := TFileStream.Create('C:\s.png',fmCreate);

//Пока есть соединение, читаем данные

while connected do

ReadStream(s,-1,true);

//Уничтожаем поток

FreeAndNil(s);

//Отключаемся

Disconnect;

Image1.Picture:=nil;

//Выводим полученный скриншот на экран

PNG := TPNGObject.Create;

Bitmap := TBitmap.Create;

try

PNG.LoadFromFile('C:\s.png');

Bitmap.Assign(PNG);

Image1.Picture.Bitmap.Assign(Bitmap);

finally

PNG.Free;

Bitmap.Free;

end;

end; //end - with

end;

Как видишь, тут все элементарно. С помощью собственной процедуры connect_to_server (ее реализацию ты можешь найти в исходниках) мы подключаемся к серверу. Для этого вызывается метод Connect компонента TidTCPClient и предварительно устанавливаются свойства Host и Port. Затем с помощью процедуры WriteLn мы отправляем серверу команду «get_screen» и в ответ получаем графический файл формата PNG, который сохраняем на диске C и отображаем пользователю посредством компонента TImage.

Реализацию остальных функций ты найдешь в исходниках программы, а сейчас давай просто посмотрим, как она осуществлена. Если нажать на кнопку «Просмотр», ее текст поменяется на «Отключить просмотр», а кнопки «Скриншот» и «Управление» блокируются. Далее включается таймер, который через определенные промежутки времени (я установил значение 400 мс) обращается к серверу с просьбой прислать скриншот. Если пользователь нажимает на кнопку повторно, то режим просмотра отключается. Аналогично работает и режим управления, только клиент, кроме запроса прислать скриншот, отправляет еще координаты курсора мыши (сообщения «mouse_move», «mouse_x» и «mouse_y», полученные с помощью функции GetCursorPos, которые затем и устанавливаются у получателя. Если пользователь нажимает на кнопку «Сообщение 'Пора работать!'», на сервер уходит команда «message_for_you»), которая приводит к вызову функции MessageBox и выводу поверх всех открытых окон указанного текстового сообщения.

Итак, программа разработана, пора испытывать ее в боевых условиях. Предупреждаю: если у тебя стоит Firewall, настрой его так, чтобы был возможен обмен сообщениями между двумя компьютерами сети по определенном порту (у меня выбран 6666).

Несколько слов напоследок

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

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

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

Активизация сервера

IdTCPServer1.Bindings.Add;

IdTCPServer1.Bindings.Items[0].IP:=IPadress.Text;

IdTCPServer1.Bindings.Items[0].Port:=StrToInt(PortNumber.Text);

IdTCPServer1.Active:=true;

Полезная информация

Indy представляет собой набор классов Object Pascal для работы с сетью (в частности, с интернетом). Так, класс TIdTCPConnection предоставляет базовые функции для низкоуровневой работы с сетевыми сервисами. Одним из главных достоинств Indy является встроенный механизм многопоточности (multi-threading). Стоит отметить, что компоненты Indy входят в комплект поставки Delphi и очень просты в использовании. Например, ты можешь запросто создать FTP-сервер или почтовый клиент, при этом написав минимум кода и не вникая в технологию реализации сетевых протоколов.

Несколько слов о формате PNG

В 1978 году израильские исследователи Яков Зив (Jacob Ziv) и Абрам Лемпел (Abraham Lempel) изобрели новый алгоритм компрессии данных без потерь, названный LZ78. Публикация алгоритма была открытой, любой человек мог взять и использовать его для своих целей, и в 1987 году сотрудник компании CompuServe Боб Берри (Bob Berry) разработал на основе LZW новый формат хранения изображений - Graphic Interchange Format (GIF). В декабре 1994 года было объявлено, что за использование алгоритма LZ78 нужно выплачивать лицензионные отчисления разработчикам. Таким образом, использование формата GIF стало платным. После этого была создана рабочая группа под руководством Томаса Боутела (Thomas Boutell), которая решала задачу по разработке альтернативы GIF. Новый формат получил название PNG (Portable Network Graphics).

DVD

На диске ты найдешь полные исходные коды программы, описанной в статье, а также компонент TPNGImage, предназначенный для работы с графическими файлами формата PNG.

Содержание