Introduction to Rust

Purpose

Rust is a multi-paradigm, general purpose programming language that can be used safely due to it's validity of memory checking before execution. It is similar to C but much safer to use as we will see in this project.

You will need the following to complete this assignment:

  • Debian 11 VM
  • Expectations

    ****Need to add here.****

    Please be sure to create a Google Doc that contains screenshots with captions indicating what you are accomplishing. This will be used to help show that you completed the project and can be used as a reference later on for you. This will also be your submission for this project. The number of screenshots is to be determined but typically it should match the page count of the project.

    Directions

    One of the first things upon booting up your Debian platform is to install Rust. Do this using the following:
    sudo apt update
    sudo apt install curl build-essential gdb -y
    sudo apt install rustc -y
    
    Press Enter to accept the default installation. Execute this command to update the PATH. **Note it may not be needed but is here for future use cases**
    source $HOME/.cargo/env
    

    Now let's do the classic Hello World example to validate that everything is correct. In order to do this, you will need to set up a directory, followed by a source code file.

    mkdir HelloWorld-App
    cd HelloWorld-App
    nano Hello.rs
    

    In your source file, add the following lines of code:

    fn
    main(){
       println!("Rust says Hello!");
    }
    

    Save your file using Ctrl+X, Y and then Enter. Now you will need to compile your source code and then run it. Enter the following:

    rustc Hello.rs
    ./Hello
    
    It should run without errors as shown below:

    Now that the basics are out of the way, let's see how Rust handles data types beginning with Integers. Enter the following:

    cd
    mkdir Int-App
    cd Int-App
    nano Int.rs
    

    In your new script enter the following:

    fn main() {
       let i = 1;  
       let j:u32 = -5;
       println!("i is {}", i);
       println!("j is {}", j);
    }
    

    From here compile it like before and run it. What happens?

    Take a screenshot here showing your results.

    Hmmm.. this is peculiar. Let's explore why. Visit the following website: Data types in Rust

    There should be one character that needs changing to get the desired output.

    Find that character and make the change to get the program to work. Screenshot your updated code and show the program successfully running.

    Next we need to look at how to add values. Look at the following:

    cd
    mkdir Add-App
    cd Add-App
    nano Add.rs
    

    Now add the following code in:

    fn main() {
       let a = 1;  
       a = a + 1;
       println!("a is {}", a);
    }
    

    Again this code has problems. Use this for reference: Variables in Rust

    Figure out the issue in the code. There is a keyword (3 letters to be precise) that is missing to a single line. Find it, and add it in. Screenshot your updated code and show the program successfully running.

    From here, lets look at C and why C has flaws with memory allocations. Enter the following:

    cd
    nano ovfl.c
    

    Enter the following 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);
       }
    }
    

    Now compile it and run it using the following:

    gcc -o ovfl ovfl.c
    ./ovfl
    

    What happens with the buffer in the code? (ie: Look at the number count for the unsigned characters). Document what you believe to be occurring through screenshots. Should the user be warned?

    Alright lets create a similar program in Rust. Enter the following:

    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);
       }
    }
    

    Compile it like you've done before and run it using the following commands:

    rustc Ov.rs
    ./Ov
    

    What do you notice that is different about this one? Take a screenshot and note the difference.

    Let's compare a few other areas between Rust and C beginning with String Overflows.

    Lets start with a String in C. Type in the following:

    cd
    nano strofl.c

    Enter the code below for a few simple string inputs.

    #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);   
    }
    

    Compile it and run it. It should ask for a new value. See below for an example. Enter the letter D two times. What happens with string 2 as it relates to string 1? Now re-run the program again and enter eight D's. Does anything different happen with the buffers for the string? Take a screenshot of your results when you enter in eight D's into the prompt.

    Just as before, lets try this string in Rust. Enter the following code:

    cd
    mkdir Str-App
    cd Str-App
    nano Str.rs
    

    Code is below.

    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 and compile it using the following commands.

    rustc -g Str.rs
    ./Str
    

    Please note the -g. This tells rust when it compiles to bring forth the debugging information.

    The program should ask for values and repeat what you did above by entering in eight D's. What happens with the strings? Is there any cross contamination of the strings ie: overflows?

    Take a screenshot of your results.

    Now lets dive deeper into the stored values of the strings in memory so we can have a better understanding of how these buffer and memory overflows work. Enter the following:

    gdb -q Str
    list
    

    You should now see the source code for the rust file called str. Each of the numbers along the side are the lines of code that we just entered and more appropriately what we can use to set a breakpoint. Just as the name implies, a breakpoint provides a 'break' to system and makes it easier to debug an application. Lets see in action.

    break 7
    run
    

    As you can see from the image above, the strings are stored in two unique places. Yours will more than likely be different so please note your locations and adjust accordingly. Let's look at the memory allocations a little closer. Enter the following changing the memory below to match your memory allocations.

    x/2x 0x7fffffffdd00
    x/2x 0x7fffffffdd18
    

    The x/2x command is looking at the memory address, and more specifically the two places where pointer records are stored to find the letters A and B or x41 and x42 respectively. A pointer is a variable whose value is the address of another variable. Lets dive deeper here. Lets look at how our two strings store information.

    print s1
    print s2
    

    As you the image shows, you should be able to see how AAAA and BBBB are stored in each string. Previously we added 8 characters when prompted. Remember adding 8 D's? Lets see how that works. Enter the following:

    break 10
    continue
    

    When prompted to enter in new values, enter in your eight D's as before. And now that we have new information, lets' look at the new strings.

    print s1
    print s2
    

    What happened with the characters? Did the D's impact both strings?

    Take a screenshot showing your buffers with the additional data entered in.

    Let's find out if we can corrupt the buffer between the strings. Enter the following:

    run
    y
    continue
    

    When prompted enter in the character D 40 more times. Again have the debugger provide you details of the strings by entering the following:

    print s1
    print s2
    

    What happened with the buffers? Did String 1 overflow into String 2? How does this impact memory corruption?

    Take a screenshot showing the new buffers with memory being allocated incorrectly. Note the cap numbers.

    So far we have seen how memory buffers can be overflowed with information which can cause errors. Now let's move onto Command Injection which allows a user to input unsanitized commands into a system for execution purposes. Let's begin in C. To close the previous debugger, type in quit, press the y key and then Enter to end that session.

    Type the following:

    cd
    nano cmdinj.c
    

    The following is a simple code for a basic command injection:

    #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);
    }
    

    Now let's compile it and run it.

    gcc -o cmdinj cmdinj.c
    ./cmdinj
    
    It should prompt you for an IP address if everything is correct. Enter 8.8.4.4. This is a common public DNS server that should response to basic pings. Note if that pings aren't received due to restrictions on the firewall, use your Windows Server 2019 address. It should start with a 10.0.0.xxx where the xxx is a number from 1 to 255. Take a screenshot showing a working ping.

    Now lets test what else this can do. Run the program again and lets append the previous input. Note that I am using my internal IP address for my host system (Windows Server 2019).

    8.8.4.4;hostname;date
    

    Yikes, this is a problem and a classic example of a command injection. As you can see it attempted to ping the server, then find the hosthame of the system and display the current date.

    Now let's see how Rust's package manager can help us. Cargo (earlier in the project) is designed to help users with developing 'crates' or packages for Rust. Enter the following:

    cd
    cargo new cmdinjrs
    cd cmdinjrs
    nano Cargo.toml
    

    The TOML file is a configuration file for rust and in particular for the crate (packages and our dependencies). Unlike C where we had to use include for our packages, this lets us tidy that up. Enter the following under dependencies.

    cmd_lib = "0.6.9"
    

    From here, exit/close the file, save it to continue. Ctrl+X, Y, Enter. Now you need to change directories into the src folder.

    cd src
    nano main.rs
    

    From here lets modify the classic example of Hello World with our custom script. Enter the following:

    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);
    }
    

    To run your program type in cargo run. It will compile with warnings but no error messages. Let's try our IP address from before.

    8.8.4.4
    

    It should complete a ping of the address entered. Now let's try command injection with Rust to see what we get.

    Run the program again cargo run and then try the multiple commands again.

    8.8.4.4;hostname;date
    

    What happened? Did it perform each command? If so, did it provide more context surrounding the commands?

    Take a screenshot of your results.

    This concludes this project on the introduction to Rust.


    New Project created April 2022

    References
    What is rustc?
    Command-line arguments
    Rust Tutorial
    GDB Cheat Sheet
    What are Pointers
    Crates.io-All crates