Welcome back to another edition of Programming Thursdays! Today, we’re diving into a topic that’s near and dear to my heart: concurrency in Rust. Whether you’re a seasoned red teamer or a pen tester looking to sharpen your coding skills, this article will provide you with an in-depth look at how Rust handles concurrent and parallel programming. We’ll cover the basics of Rust, explore how it can be used in the context of pen testing and red teaming, and compare its pros and cons with other languages. Let’s get started!

Introduction to Rust

Rust is a systems programming language that promises memory safety without garbage collection. It was created by Mozilla and has rapidly gained popularity due to its performance, reliability, and concurrency capabilities. For red teamers and pen testers, Rust offers the ability to write safe and efficient code that can be deployed in a variety of environments.

Key features of Rust:

  • Memory Safety: Rust’s ownership system ensures that memory errors such as null pointer dereferencing and buffer overflows are caught at compile time.
  • Concurrency: Rust’s concurrency model leverages its ownership system to prevent data races, making concurrent programming safer and more manageable.
  • Performance: Rust’s performance is comparable to C and C++, making it suitable for low-level programming tasks.

Let’s start by looking at some basic concurrency constructs in Rust.

Concurrency Basics

Threads

Rust provides native support for threads, allowing you to create and manage threads easily. The std::thread module provides the necessary tools.

use std::thread;

fn main() {
    let handle = thread::spawn(|| {
        for i in 1..10 {
            println!("Hello from thread: {}", i);
        }
    });

    for i in 1..10 {
        println!("Hello from main: {}", i);
    }

    handle.join().unwrap();
}

In this example, we create a new thread using thread::spawn. The main thread continues to run while the spawned thread executes its code. We use handle.join().unwrap() to wait for the spawned thread to finish.

Channels

Channels are a way to communicate between threads. Rust’s std::sync::mpsc module provides a multi-producer, single-consumer channel for message passing.

use std::sync::mpsc;
use std::thread;
use std::time::Duration;

fn main() {
    let (tx, rx) = mpsc::channel();

    thread::spawn(move || {
        let val = String::from("hello");
        tx.send(val).unwrap();
    });

    let received = rx.recv().unwrap();
    println!("Got: {}", received);
}

Here, we create a channel using mpsc::channel(). The transmitter (tx) is moved to the spawned thread, which sends a message. The main thread receives the message using rx.recv().

Mutexes and Locks

Mutexes are used to ensure that only one thread can access a resource at a time. Rust’s std::sync::Mutex provides mutual exclusion.

use std::sync::{Arc, Mutex};
use std::thread;

fn main() {
    let counter = Arc::new(Mutex::new(0));
    let mut handles = vec![];

    for _ in 0..10 {
        let counter = Arc::clone(&counter);
        let handle = thread::spawn(move || {
            let mut num = counter.lock().unwrap();
            *num += 1;
        });
        handles.push(handle);
    }

    for handle in handles {
        handle.join().unwrap();
    }

    println!("Result: {}", *counter.lock().unwrap());
}

In this example, we wrap the counter in an Arc (atomic reference count) and a Mutex. Each thread increments the counter, and the mutex ensures that the increments are performed safely.

Advanced Concurrency Techniques

Rayon

Rayon is a data parallelism library for Rust that makes it easy to convert sequential computations into parallel ones.

use rayon::prelude::*;

fn main() {
    let v: Vec<i32> = (0..100).collect();
    let sum: i32 = v.par_iter().sum();
    println!("Sum: {}", sum);
}

With Rayon, we can convert a vector into a parallel iterator using par_iter(), and then perform operations like sum() in parallel.

Async Programming

Rust’s async programming model allows you to write asynchronous code using async and await. The tokio and async-std crates provide runtimes for executing async code.

use tokio::time::{sleep, Duration};

#[tokio::main]
async fn main() {
    let handle = tokio::spawn(async {
        sleep(Duration::from_secs(1)).await;
        println!("Hello from async task");
    });

    handle.await.unwrap();
    println!("Hello from main");
}

In this example, we use tokio::spawn to run an async task. The sleep function pauses the task for a duration, and await is used to wait for the task to complete.

Rust for Pen Testing and Red Teaming

Rust’s performance, safety, and concurrency features make it an excellent choice for pen testing and red teaming. Let’s explore some specific applications.

Network Scanners

Rust can be used to build efficient network scanners. Here’s a simple example using the tokio and trust-dns-resolver crates.

use tokio::net::TcpStream;
use trust_dns_resolver::TokioAsyncResolver;

#[tokio::main]
async fn main() {
    let resolver = TokioAsyncResolver::tokio_from_system_conf().unwrap();
    let response = resolver.lookup_ip("example.com").await.unwrap();

    for ip in response {
        if let Ok(_) = TcpStream::connect((ip, 80)).await {
            println!("Port 80 is open on {}", ip);
        }
    }
}

This code performs a DNS lookup and attempts to connect to port 80 on each resolved IP address.

Exploits

Rust’s low-level capabilities allow for the creation of custom exploits. Here’s an example of a buffer overflow exploit targeting a vulnerable server.

use std::net::TcpStream;
use std::io::Write;

fn main() {
    let payload = "A".repeat(1024) + "\x90\x90\x90\x90"; // NOP sled followed by shellcode
    let mut stream = TcpStream::connect("127.0.0.1:9999").unwrap();
    stream.write_all(payload.as_bytes()).unwrap();
}

This code constructs a payload with a NOP sled and shellcode, then sends it to a vulnerable server.

Payload Delivery

Rust can be used to build tools for payload delivery. Here’s an example using hyper to create a simple HTTP server that serves payloads.

use hyper::service::{make_service_fn, service_fn};
use hyper::{Body, Response, Server};

async fn handle_request(_: hyper::Request<Body>) -> Result<Response<Body>, hyper::Error> {
    let payload = b"Hello, world!";
    Ok(Response::new(Body::from(payload)))
}

#[tokio::main]
async fn main() {
    let make_svc = make_service_fn(|_| async {
        Ok::<_, hyper::Error>(service_fn(handle_request))
    });

    let addr = ([127, 0, 0, 1], 3000).into();
    let server = Server::bind(&addr).serve(make_svc);

    println!("Serving on http://{}", addr);
    server.await.unwrap();
}

This code creates an HTTP server that responds with a simple payload.

Rust vs Other Languages

When comparing Rust to other languages commonly used in pen testing and red teaming, several factors come into play:

Pros

  • Memory Safety: Rust’s ownership system eliminates many common security vulnerabilities.
  • Concurrency: Rust’s concurrency model is both safe and efficient, allowing for high-performance concurrent code.
  • Performance: Rust’s performance is on par with C and C++, making it suitable for resource-intensive tasks.
  • Ecosystem: Rust has a growing ecosystem with libraries and tools for various applications.

Cons

  • Learning Curve: Rust’s ownership system can be challenging to learn, especially for those coming from other languages.
  • Ecosystem Maturity: While growing rapidly, Rust’s ecosystem is still maturing compared to languages like Python and C.
  • Tooling: Some tools commonly used in pen testing and red teaming may not yet be available in Rust.

Comparison with Other Languages

  • Python: Python is widely used in pen testing due to its simplicity and rich ecosystem. However, it lacks the performance and safety features of Rust.
  • C/C++: C and C++ offer high performance and control, but they are prone to memory safety issues that Rust avoids. Additionally, Rust’s modern syntax and features make it more pleasant to work with.
  • Go: Go provides simplicity and good concurrency support, but Rust’s ownership model offers better safety guarantees. Rust’s performance also tends to be higher due to zero-cost abstractions.

References

Conclusion

That’s a wrap for today’s deep dive into Rust concurrency! We’ve covered the basics of concurrent programming in Rust, explored advanced techniques, and discussed how Rust can be used in pen testing and red teaming scenarios. By leveraging Rust’s safety and performance features, you can build powerful tools and exploits that are both efficient and secure. So, whether you’re scanning networks, crafting exploits, or delivering payloads, Rust has got you covered. Happy hacking!