nano heapo.c
Enter this code:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main()
{
char* name1 = (char *) malloc(20);
char* name2 = (char *) malloc(20);
printf("Enter name1: ");
scanf("%s", name1);
printf("Enter name2: ");
scanf("%s", name2);
printf("\n");
printf("name1 address: %p ; name2 address: %p\n\n", name1, name2);
printf("name1: \"%s\"\n\n", name1);
printf("name2: \"%s\"\n\n", name2);
}
Save the file with Ctrl+X,
Y, Enter.
Execute these commands to compile the program and run it:
gcc -o heapo heapo.c
./heapo
Enter this name1, which is 20 characters long:
AAAAAAAAAAAAAAAAAAAA
Enter this name2, which is 20 characters long:
BBBBBBBBBBBBBBBBBBBB
The program works as expected,
printing out the correct values of the names,
as shown below:
Execute this command to run the program again:
./heapo
Enter this name1, which is 40 characters long:
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
Enter this name2, which is 40 characters long:
BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB
The names overwrite the heap metadata
and turn into bizarre nonsense,
as shown below:
This is very cruel to the developer. No warning or error message appears. C allows an attacker to craft input that will write to memory locations that were not allocated for that purpose.
nano rheap.rs
Enter this code:
fn main() {
let mut name1 = Box::new(String::new());
let mut name2 = Box::new(String::new());
println!("name1 address: {:p}. name2 address: {:p}.", &name1, &name2);
println!("name1 contains {}. name2 contains {}. ", name1, name2);
println!("Enter name1: ");
let _num = std::io::stdin().read_line(&mut name1).unwrap();
println!("Enter name2: ");
let _num = std::io::stdin().read_line(&mut name2).unwrap();
println!("name1 address: {:p}. name2 address: {:p}.", &name1, &name2);
println!("name1 contains {}. name2 contains {}. ", name1, name2);
}
Save the file with Ctrl+X,
Y, Enter.
Execute these commands to compile the program and run it:
rustc -g rheap.rs
./rheap
Enter this name1, which is 20 characters long:
AAAAAAAAAAAAAAAAAAAA
Enter this name2, which is 20 characters long:
BBBBBBBBBBBBBBBBBBBB
The program works as expected,
printing out the correct values of the names,
as shown below:
Execute this command to run the program again:
./rheap
Enter this name1, which is 40 characters long:
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
Enter this name2, which is 40 characters long:
BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB
The program works as expected,
printing out the correct values of the names,
as shown below. There is no overflow.
gdb -q rheap
list 1,15
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 14
run
Enter this name1, which is 20 characters long:
AAAAAAAAAAAAAAAAAAAA
Enter this name2, which is 20 characters long:
BBBBBBBBBBBBBBBBBBBB
The program halts at the breakpoint.
Execute these commands to examine the strings:
print name1
print name2
The debugger shows the two pointers,
which are 0x20 = 32 bytes apart,
as shown below.
To see more information about how the strings are stored, execute these commands:
print *name1
print *name2
As shown below, the actual strings
are also stored at addresses
which are 0x20 = 32 bytes apart.
Execute these commands to run the program again:
run
y
Enter this name1, which is 40 characters long:
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
Enter this name2, which is 40 characters long:
BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB
The program halts at the breakpoint.
Execute these commands to examine the strings:
print name1
print name2
print *name1
print *name2
As shown below, the actual strings
are now stored at addresses
which are 0x40 = 64 bytes apart.
Rust has automatically reallocated space so no overflow occurs.
R 20.1: File Information (10 pts)
Execute these commands:The flag is covered by a green rectangle in the image below.
q y file rheap
nano dangp.c
Enter this code:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main()
{
char* name1 = (char *) malloc(20);
printf("Enter name1: ");
scanf("%s", name1);
printf("name1 address: %p ; value %s\n", name1, name1);
free(name1);
char* name2 = (char *) malloc(20);
printf("Enter name2: ");
scanf("%s", name2);
printf("name2 address: %p ; value %s\n", name2, name2);
printf("name1 address: %p ; value %s\n", name1, name1);
}
Save the file with Ctrl+X,
Y, Enter.
Execute these commands to compile the program and run it:
gcc -o dangp dangp.c
./dangp
Enter this name1:
A
Enter this name2:
B
Because of the errant "free" command,
name1 and name2 both end up pointing
to the same address, so the value
entered into name2 overwrites name1.
This happens because the "free" command does not erase the "name1" pointer. It remains "dangling"--available for use, even though the memory it points to is no longer properly reserved and may be re-used for a different purpose.
Like other memory corruption vulnerabilities, this is a serious security flaw and responsible for many current exploits.
nano rheapp.rs
In Rust, there is no "free" command.
Instead, it automatically frees heap
objects when they "fall out of scope".
To see how that works, enter this code:
fn main() {
{
let mut name1 = Box::new(String::new());
println!("Enter name1: ");
let _num = std::io::stdin().read_line(&mut name1).unwrap();
println!("name1 address: {:p} ; contents {}.", &name1, name1);
}
let mut name2 = Box::new(String::new());
println!("Enter name2: ");
let _num = std::io::stdin().read_line(&mut name2).unwrap();
println!("name2 address: {:p} ; contents {}.", &name2, name2);
println!("name1 contents {}.", name1);
}
Save the file with Ctrl+X,
Y, Enter.
Execute this command to compile the program:
rustc rheapp.rs
The compiler rejects the code
with an error message,
as shown below.
It won't allow a developer to make the mistake of using "name1" after it is no longer allocated.
R 20.2: Error (5 pts)
The flag is covered by a green rectangle in the image above.
nano cnoleak.c
Enter this code:
#include <stdio.h>
#include <stdlib.h>
void noleak()
{
char * p = malloc(1000);
free(p);
return;
}
int main()
{
int i, j;
for (i=0; i<10; i++) {
for (j=0; j<1000; j++) noleak();
printf("Press Enter to Continue\n");
getchar();
}
}
Save the file with Ctrl+X,
Y, Enter.
Execute these commands to compile the program and run it:
gcc -o cnoleak cnoleak.c
./cnoleak
Open a second SSH window and execute this command:
watch "pmap $(pgrep leak)"
The total memory consumed by the "cnoleak"
process is shown at the bottom of the list
and updated every 2 seconds,
as shown below.
In the first SSH window, you see a "Press Enter to Continue" message.
Press Enter a few times, watching the other window. The total memory usage does not change, as shown below.
This is because the memory allocated with alloc() is freed each time with free().
Press Ctrl+C in both windows to terminate the running processes.
nano cleak.c
Enter this code:
#include <stdio.h>
#include <stdlib.h>
void leak()
{
char * p = malloc(1000);
return;
}
int main()
{
int i, j;
for (i=0; i<10; i++) {
for (j=0; j<1000; j++) leak();
printf("Press Enter to Continue\n");
getchar();
}
}
Save the file with Ctrl+X,
Y, Enter.
Execute these commands to compile the program and run it:
gcc -o cleak cleak.c
./cleak
In the second SSH window,
execute this command:
watch "pmap $(pgrep leak)"
In the first SSH window, you see a
"Press Enter to Continue" message.
Press Enter a few times, watching the other window. The total memory usage increases each time, as shown below.
This is a memory leak. Memory is allocated but never freed, so the memory usage keeps increasing.
Press Ctrl+C in both windows to terminate the running processes.
nano rnoleak.rs
In Rust, there is no "free" command.
Instead, it automatically frees heap
objects when they "fall out of scope".
To see how that works, enter this code:
use std::io::{stdin, Read};
fn noleak() {
let _a = Box::new([0.0f64; 1000]);
}
fn main() {
for _i in 1..11 {
for _j in 1..1001 {
noleak();
}
println!("Press Enter to Continue:");
stdin().read(&mut [0]).unwrap();
}
}
Save the file with Ctrl+X,
Y, Enter.
Execute these commands to compile and run the program:
rustc rnoleak.rs
./rnoleak
In the second SSH window,
execute this command:
watch "pmap $(pgrep leak)"
In the first SSH window, you see a
"Press Enter to Continue" message.
Press Enter a few times, watching the other window. The list of memory segments is longer than it was for the C program, but the total memory usage is constant, as shown below.
There is no memory leak, because Rust automatically frees the memory each time the function returns.
Press Ctrl+C in both windows to terminate the running processes.
nano rleak.rs
Rust's "forget" function can cause
memory leaks, because it drops a value
without running its "destructor", which
would have freed its memory.
To see how that works, enter this code:
use std::io::{stdin, Read};
use std::mem;
fn leak() {
let a = Box::new([0.0f64; 1000]);
mem::forget(a);
}
fn main() {
for _i in 1..11 {
for _j in 1..1001 {
leak();
}
println!("Press Enter to Continue:");
stdin().read(&mut [0]).unwrap();
}
}
Save the file with Ctrl+X,
Y, Enter.
Execute these commands to compile and run the program:
rustc rleak.rs
./rleak
In the second SSH window,
execute this command:
watch "pmap $(pgrep leak)"
In the first SSH window, you see a
"Press Enter to Continue" message.
Press Enter a few times, watching the other window. The total memory usage increases each time, is constant, as shown below.
Press Ctrl+C in both windows to terminate the running processes.
R 20.3: Stat (10 pts)
Execute this command:The flag is covered by a green rectangle in the image below.
stat rleak
nano dupp.c
Enter this code:
#include <stdio.h>
#include <stdlib.h>
int main()
{
int * ptr1;
ptr1 = (int*)malloc(sizeof(int));
int * ptr2 = ptr1;
*ptr1 = 1;
printf("ptr1 points to address %p and contains %d\n", ptr1, *ptr1);
printf("ptr2 points to address %p and contains %d\n\n", ptr2, *ptr2);
printf("Changing *ptr1 to 2.\n");
*ptr1 = 2;
printf("ptr1 points to address %p and contains %d\n", ptr1, *ptr1);
printf("ptr2 points to address %p and contains %d\n", ptr2, *ptr2);
}
Save the file with Ctrl+X,
Y, Enter.
Execute these commands to compile the program and run it:
gcc -o dupp dupp.c
./dupp
As shown below, changing *ptr1 also changes
*ptr2. That is confusing and can easily
cause developers to make errors, such as
double-free or dangling pointers.
nano copy.rs
Enter this code:
fn main() {
let a = 1;
println!("a is stored at address {:p} and contains {}", &a, a);
let b = a;
println!("b is stored at address {:p} and contains {}", &b, b);
println!("a is stored at address {:p} and contains {}\n", &a, a);
println!("Changing b to 2\n");
let b = 2;
println!("b is stored at address {:p} and contains {}", &b, b);
println!("a is stored at address {:p} and contains {}", &a, a);
}
Save the file with Ctrl+X,
Y, Enter.
Execute this command to compile and run the program:
rustc copy.rs
./copy
Rust creates a new object for b
with a different address, and copies
the contents of a into it.
This happens because an integer on the stack is a "primitive object" that consumes few resources.
Rusts avoids duplicate pointers with "ownership".
Execute this command to create a new Rust program named own.rs:
nano own.rs
Enter this code:
fn main() {
let a = Box::from(1i8);
println!("a is stored at address {:p} and contains {}", &a, a);
let b = a;
println!("b is stored at address {:p} and contains {}", &b, b);
println!("a is stored at address {:p} and contains {}", &a, a);
}
Save the file with Ctrl+X,
Y, Enter.
Execute this command to compile the program:
rustc own.rs
Rust rejects this code
with a "value borrowed here
after move" error, as shown below.
The "let a" statement allocates some memory and assigns it to a, so a owns it.
The "let b = a" statement transfers ownership of that memory to b, and a can no longer use it.
So the attempt to print a fails.
R 20.4: Error (10 pts)
The flag is covered by a green rectangle in the image above
Posted 5-30-2020