R 10: Basics, Overflows, and Injection (35 pts)

What you need

Purpose

To learn the basics of Rust, and understand how it avoids several security problems of C.

Installing Rust

On your Debian 10 server, execute these commands:
sudo apt update
sudo apt install curl build-essential gdb -y
sudo apt install rustc -y

Hello, World!

Execute these commands to prepare a working directory and start editing a Rust source code file:
mkdir HelloWorld-App
cd HelloWorld-App
nano Hello.rs
Enter this code, as shown below.
fn
main(){
   println!("Rust says Hello!");
}

Save the file with Ctrl+X, Y, Enter.

Execute these commands to compile and run the program:

rustc Hello.rs
./Hello
The program runs, as shown below.

Integer Data Types

Execute these commands to create a new program:
cd
mkdir Int-App
cd Int-App
nano Int.rs
Enter this code:
fn main() {
   let i = 1;  
   let j:u32 = -5;
   println!("i is {}", i);
   println!("j is {}", j);
}
Execute this command to compile it:
rustc Int.rs
This program has errors. Figure out the problem. This tutorial should help:

Rust - Data Types

R 10.1: Int (5 pts)

Change one character in the "Int.rs" program to fix it and produce this desired output:

The flag is the complete fixed line.

Adding Numbers

Execute these commands to create a new Rust program:
cd
mkdir Add-App
cd Add-App
nano Add.rs
fn main() {
   let a = 1;  
   a = a + 1;
   println!("a is {}", a);
}
Save the file with Ctrl+X, Y, Enter.

Execute this command to compile it:

rustc Add.rs
This program has errors. Figure out the problem. This tutorial should help:

Rust - Variables

R 10.2: Add (5 pts)

Add one three-letter keyword to a single line in the "Add.rs" program to fix it and produce this desired output:

The flag is the complete fixed line.

Integer Overflow in C

Execute this commands to create a new C program named ovc.c:
nano ovc.c
Enter this code:
#include <stdio.h>

int main()
   {
   unsigned char x = 230;
   int i;

   for (i = 1; i < 10; i++)
      {
      x = x + i;
      printf("x is %d\n",x);
   }
}
Save the file with Ctrl+X, Y, Enter.

Execute these commands to compile the program and run it:

gcc -o ovc ovc.c
./ovc
As shown below, the integer overflows when it exceeds 255 and assumes incorrect low values. It does not warn the developer, so a bug like this can easily end up in distributed software.

Integer Overflow in Rust

Execute these commands to create a new Rust program named Ov.rs:
cd
mkdir Ov-App
cd Ov-App
nano Ov.rs
Enter this code:
fn main() {
   let mut x:u8 = 230;

   for y in 1..10{ 
   	  x = x + y;
      println!("x is {}",x);
   }
}
Save the file with Ctrl+X, Y, Enter.

Execute these commands to compile and run it:

rustc Ov.rs
./Ov

R 10.3: Overflow (5 pts)

Compile the file and run it.

As shown below, Rust does not allow the integer to assume an incorrect value. Instead, it stops and alerts the developer with an error message.

The flag is covered by a green rectangle in the image below.

String Overflow in C

Execute this commands to create a new C program named strovc.c:
nano strovc.c
Enter this code:
#include <stdio.h>

int main()
   {
   char s1[5] = "AAAA";
   char s2[5] = "BBBB";

   printf("String 1 address: %p.  String 2 address: %p.\n", s1, s2);
   printf("String 1 contains %s.  String 2 contains %s.\n", s1, s2);

   printf("Enter new value for String 2:\n");
   scanf("%s", s2);

   printf("String 1 address: %p.  String 2 address: %p.\n", s1, s2);
   printf("String 1 contains %s.  String 2 contains %s.\n", s1, s2);   
}
Save the file with Ctrl+X, Y, Enter.

Execute these commands to compile the program and run it:

gcc -o strovc strovc.c
./strovc
Note: if the address of String 1 is larger than the address of String 2, change the program to enter a new value for String 1.

The program asks for a new value. Enter eight "C" characters, as shown below.

The eight-letter value in String 2 overflows into String 1. As before, C just lets this awful thing happen without stopping or warning the developer.

This is the notorious "buffer overflow" vulnerability responsible for many viruses and other security problems.

String Storage in Rust

Execute these commands to create a new Rust program named Ov.rs:
cd
mkdir Str-App
cd Str-App
nano Str.rs
Enter this code:
fn main() {
    let mut s1 = String::from("AAAA");
    let mut s2 = String::from("BBBB");

    println!("String 1 address: {:p}. String 2 address: {:p}.", &s1, &s2);
    println!("String 1 contains {}.  String 2 contains {}. ", s1, s2);
    println!("Enter new value for String 1: ");
    let num = std::io::stdin().read_line(&mut s1).unwrap();
    println!("{} bytes read", num);
    println!("String 1 address: {:p}. String 2 address: {:p}.", &s1, &s2);
    println!("String 1 contains {}.  String 2 contains {}. ", s1, s2);
}
Save the file with Ctrl+X, Y, Enter.

Execute these commands to compile and run it. Don't omit the "-g" switch--we'll need it later for debugging.

rustc -g Str.rs
./Str
The program compiles with a warning but no errors, as shown below.

When the program asks for a new value, enter eight "C" characters.

Notice these things:

Using gdb to Examine the Stored Strings

Execute these commands to debug the Rust executable and list its source code:
gdb -q Str
list
The debugger starts, and shows the Rust source code, as shown below.

Execute these commands to set a breakpoint and start running the program:

break 7
run
The program initializes the strings and halts at the breakpoint, as shown below.

Execute these commands to examine memory where the strings are stored. You may have to adjust the addresses to match the addresses shown on your system:

x/2x 0x7fffffffdfc0
x/2x 0x7fffffffdfd8
The letters "A" and "B" are not stored here--they would appear as "41" and "42" hex values.

Instead, pointers are stored here, as shown below.

Execute these commands to examine the details of how the strings are stored:

print s1
print s2
The debugger shows the two pointers, outlined in green in the image below.

Execute these commands to see the stored strings, replacing the addresses with the correct addresses on your system:

x/4x 0x5555555a69d0
x/4x 0x5555555a69f0
The "AAAA" and "BBBB" letters are visible as ASCII hex codes 41414141 and 42424242, outlined in red in the image below.

Adding 8 Characters to String 1

Execute these commands to set another breakpoint and continue running the program:
break 10
continue
When the program asks for a new value, enter eight "C" characters.

Execute these commands to examine the details of how the strings are stored, correcting the addresses as you did before:

print s1
print s2
x/4x 0x5555555a69d0
x/4x 0x5555555a69f0
Both strings are still stored in the same locations, and even though String 1 is longer, it hasn't overwritten String 2, as shown below.

Adding 30 Characters to String 1

Execute these commands to restart the program and continue through the first breakpoint:
run
y
continue
When the program asks for a new value, enter thirty "C" characters.

Execute these commands to examine the details of how the strings are stored, adjusting the addresses to match the new values of the pointers:

print s1
print s2
x/4x 0x5555555a8a20
x/4x 0x5555555a69f0
As shown below, String 2 is unchanged, but String 1 is now stored at a different address, so it can hold its longer value without overflowing into the memory used by String 2.

This is much safer. The developer doesn't need to remember how long the string is--it just resizes automatically as needed, and never corrupts memory used by some other variable.

R 10.4: String Storage (10 pts)

The flag is covered by a green rectangle in the image above.

Command Injection in C

Execute these commands to create a new C program named cmdi.c:
cd
nano cmdi.c
Enter this code:
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

int main()
   {
   char ip[30];
   char cmd[40];

   printf("IP:");
   scanf("%s", ip);
   strcpy(cmd, "ping -c 1 ");
   strcat(cmd, ip);
   system(cmd);
}
Save the file with Ctrl+X, Y, Enter.

Execute these commands to compile the program and run it:

gcc -o cmdi cmdi.c
./cmdi
The program asks for an IP address. Enter this IP:
8.8.8.8
The program sends a ping to that address, as shown below.

Run the program again, entering this address:

8.8.8.8;date
As shown below, the program performs a ping and then executes the "date" command. This is another common flaw: command injection.

Using cargo in Rust

Cargo is Rust's package manager. We'll use it to add packages (called "crates") to apps.

Execute these commands to create a new Rust package named cmdi and edit its "Manifest":

cd
cargo new cmdinj
cd cmdinj
nano Cargo.toml
Add this line at the bottom, as shown below.
cmd_lib = "0.6.5"

Save the file with Ctrl+X, Y, Enter.

Execute this command to edit the package's source code:

nano src/main.rs
Cargo creates a "Hello, world!" program. Replace it with this code:
use cmd_lib::{run_cmd};

fn main() {
    let mut ip = String::new();

    println!("Enter IP: ");
    let _num = std::io::stdin().read_line(&mut ip).unwrap();
    run_cmd!("ping -c 1 {}", ip);
}

Save the file with Ctrl+X, Y, Enter.

Execute this command to compile and run it:

cargo run
The program compiles with a warning but no errors, as shown below, and runs, asking for an IP address. Enter:
8.8.8.8
The program performs a "ping", as shown below.

Command Injection in Rust

Execute this command to compile and run the program again:
cargo run
Enter this IP:
8.8.8.8;date
The program performs a "ping", and then displays the date, as shown below.

Rust does not prevent command injection vulnerabilities.

R 10.5: Lock File (10 pts)

Execute this command:
cat Cargo.lock
The flag is covered by a green rectangle in the image below.

Sources

Rust Tutorial
Debugging Rust
Dependencies
Macro cmd_lib::run_cmd
The Cargo Book
Fearless Security: Memory Safety

Posted 5-30-2020
R 10.3 instructions fixed 11-15-20
R 10.2 instructions fixed 4-26-2021
Rust install instructions updated 8-3-2021
Updated with different gdb commands 6-22-22
Minor updates 11-22-22
Video and note about string pointer order added 4-20-23