Platform Security: Exercise 4

In this exercise, you will experiment with Linux memory protection facilities, and see how to create stack pointer collisions that might be used to attack Pointer Authentication-based defences.

Part 0: Exercise 3 Feedback

Since there was no feedback session for the last exercise, the first part of this exercise will be to reflect on your solutions as usual within your assigned groups, and answer the following questions:

  1. You were asked to create a socket of type SOCK_SEQPACKET. What types of Unix domain socket are available on Linux, and what are the differences between them?

  2. How else might a container be given access to file(s) outside the container, without passing a file descriptor over a domain socket?

Include your reflection on your solution and the answer to these questions in your submission for this exercise.

Part 1: Memory protection

In this part, you will create memory mappings using the mmap(2) system call, and use the mprotect(2) system call to modify the memory protection parameters.

The mmap(2) system call can be used to create new virtual memory mappings (i.e. to make regions of the process’s virtual address space point somewhere). These mappings are made at the level of a page of memory (normally 4KiB), and several types of mapping are possible:

Begin by writing a program that uses mmap(2) to create a 4096-byte anonymous mapping (i.e. one page) with only read permissions.

Hint: Have a look in utils.c from Exercise 2.

Now, you will try to copy some code into the memory mapping and execute it. For the sake of simplicity, we will use some so-called shellcode from here: https://github.com/offensive-security/exploitdb/blob/master/shellcodes/linux_x86-64/46907.c.

Historical note: shellcode was a chunk of code that was inserted into a program’s address space, and then executed by e.g. using a stack overflow vulnerability to cause a function to return to the start of the shellcode. This code needed to be small so that it could fit into the space between the start of the buffer and the return address pointer, so its functionality was limited; a common choice was to execute a local or remote shell that would allow the attacker to run arbitrary commands and continue their attack. Inserting and executing shellcode in the traditional way is generally no longer possible, thanks to the W^X policies that you are experimenting with here, but similar results can be achieved using return-oriented programming.

Take the shellcode string from the link above, and try to execute it as in the code example (but don’t use their compiler flags, which disable several security features). Does it work? If not, why not? Remove the call to the shellcode string before continuing to the next step.

Next, copy the shellcode into the memory mapping that you created using mmap(2) (Hint: memcpy will be helpful here). Try running the program; it should crash. This is because the memory mapping was created read-only. Change the call to mmap(2) so that you can successfully copy the shellcode into your memory mapping.

Now, we can finally call the shellcode, like in the example, but using the pointer to your new memory mapping. Try to do so, and verify that the program crashes. This is because your code should now be readable and writable, but not executable. Without violating W^X (that is, without ever making your memory mapping both writable and executable at the same time), modify your code so that the shellcode is executable, and running the program takes you to a new shell:

lachlan@lachlangunn> ./shell
$

Hint: The mprotect(2) syscall will be helpful here.