SEED Labs – Return-to-libc Attack Lab 1

Return-to-libc Attack Lab

This work is licensed under a Creative Commons AttributionNonCommercial-ShareAlike 4.0 InternationalLicense. If you remix, transform, or build upon the material, this copyright notice must be left intact, orreproduced in a way that is reasonable to the medium in which the work is being re-published.

1 Overview

The learning objective of this lab is for students to gain the firsthand experience on an interesting variantof buffer-overflow attack; this attack can bypass an existing protection scheme currently implemented inmajor Linux operating systems. A common way to exploit a buffer-overflow vulnerability is to overflow thebuffer with a malicious shellcode, and then cause the vulnerable program to jump to the shellcode stored inthe stack. To prevent these types of attacks, some operating systems allow programs to make their stacksnon-executable; therefore, jumping to the shellcode causes the program to fail.Unfortunately, the above protection scheme is not fool-proof. There exists a variant of buffer-overflowattacks called Return-to-libc, which does not need an executable stack; it does not even use shellcode.Instead, it causes the vulnerable program to jump to some existing code, such as the system() function inthe libc library, which is already loaded into a process’s memory space.In this lab, students are given a program with a buffer-overflow vulnerability; their task is to developa Return-to-libc attack to exploit the vulnerability and finally to gain the root privilege. In addition to theattacks, students will be guided to walk through some protection schemes implemented in Ubuntu to counterbuffer-overflow attacks. This lab covers the following topics:

• Buffer overflow vulnerability
• Stack layout in a function invocation and Non-executable stack
• Return-to-libc attack and Return-Oriented Programming (ROP)
Readings and videos. Detailed coverage of the return-to-libc attack can be found in the following:
• Chapter 5 of the SEED Book, Computer & Internet Security: A Hands-on Approach, 2nd Edition, byWenliang Du. See details at https://www.handsonsecurity.net.
• Section 5 of the SEED Lecture at Udemy, Computer Security: A Hands-on Approach, by Wenliang

Du. See details at https://www.handsonsecurity.net/video.html.
Lab environment. This lab has been tested on the SEED Ubuntu 20.04VM. You can download a pre-builtimage from the SEED website, and run the SEED VM on your own computer. However, most of the SEEDlabs can be conducted on the cloud, and you can follow our instruction to create a SEED VM on the cloud.Note for instructors. Instructors can customize this lab by choosing a value for the buffer size in thevulnerable program. See Section 2.3 for details.SEED Labs – Return-to-libc Attack Lab 2

2 Environment Setup

2.1 Note on x86 and x64 Architectures

The return-to-libc attack on the x64 machines (64-bit) is much more difficult than that on the x86 machines(32-bit). Although the SEED Ubuntu 20.04 VM is a 64-bit machine, we decide to keep using the 32-bitprograms (x64 is compatible with x86, so 32-bit programs can stillrun on x64 machines). In the future, wemay introduce a 64-bit version for this lab. Therefore, in this lab, when we compileprograms using gcc,we always use the -m32 flag, which means compiling the program into 32-bit binary.

2.2 Turning off countermeasures

You can execute the lab tasks using our pre-built Ubuntu virtual machines. Ubuntu and other Linux distributions have implemented several security mechanisms to make the buffer-overflow attack difficult. Tosimplify our attacks, we need to disable them first.
Address Space Randomization. Ubuntu and several other Linux-based systems use address space randomization to randomize the starting address of heap and stack, making guessing the exact addresses difficult. Guessing addresses is one of the critical steps of bufferoverflow attacks. In this lab, we disable thisfeature using the following command:

$ sudo sysctl -w kernel.randomize_va_space=0
The StackGuard Protection Scheme. The gcc compiler implements a security mechanism called StackGuard to prevent buffer overflows. In the presence of this protection, buffer overflow attacks do not work.
We can disable this protection during the compilation using the -fno-stack-protector option. For example,
to compile a program example.c with StackGuard disabled, we can do the following:
$ gcc -m32 -fno-stack-protector example.c
Non-Executable Stack. Ubuntu used to allow executable stacks, but this has now changed. The binary
images of programs (and shared libraries) must declare whether they require executable stacks or not, i.e.,
they need to mark a field in the program header. Kernel or dynamic linker uses this marking to decide
whether to make the stack of this running program executable or non-executable. This marking is done
automatically by the recent versions of gcc, and by default, stacks are set to be non-executable. To change
that, use the following option when compiling programs:
For executable stack:
$ gcc -m32 -z execstack -o test test.c
For non-executable stack:
$ gcc -m32 -z noexecstack -o test test.c
Because the objective of this lab is to show that the non-executable stack protection does not work, you
should always compile your program using the "-z noexecstack" option in this lab.

Configuring /bin/sh. In Ubuntu 20.04, the /bin/sh symbolic linkpoints to the /bin/dash shell.The dash shell has a countermeasure that prevents itself from being executed in a Set-UID process. IfSEED Labs – Return-to-libc Attack Lab 3dash is executed in a Set-UID process, it immediately changes the effective user ID to the process’s real
user ID, essentially dropping its privilege.Since our victim program is a Set-UID program, and our attack uses the system() function to
run a command of our choice. This function does not run our command directly; it invokes /bin/shto run our command. Therefore, the countermeasure in /bin/dash immediately drops the Set-UIDprivilege before executing our command, making our attack more difficult. Todisable this protection, welink /bin/sh to another shell that does not have such a countermeasure. We have installed a shell programcalled zsh in our Ubuntu 16.04 VM. We use the followingcommands to link /bin/sh to zsh:

$ sudo ln -sf /bin/zsh /bin/sh
It should be noted that the countermeasure implemented in dash can be circumvented. We will do that
in a later task.
2.3 The Vulnerable Program
Listing 1: The vulnerable program (retlib.c)
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#ifndef BUF_SIZE
#define BUF_SIZE 12
#endif
int bof(char *str)
{
char buffer[BUF_SIZE];
unsigned int *framep;
// Copy ebp into framep
asm("movl %%ebp, %0" : "=r" (framep));
/* print out information for experiment purpose */
printf("Address of buffer[] inside bof(): 0x%.8x\n", (unsigned)buffer);
printf("Frame Pointer value inside bof(): 0x%.8x\n", (unsigned)framep);
strcpy(buffer, str); ➞buffer overflow!
return 1;
}
int main(int argc, char **argv)
{
char input[1000];
FILE *badfile;
badfile = fopen("badfile", "r");
int length = fread(input, sizeof(char), 1000, badfile);
printf("Address of input[] inside main(): 0x%x\n", (unsigned int) input);
printf("Input size: %d\n", length);
SEED Labs – Return-to-libc Attack Lab 4
bof(input);
printf("(ˆ_ˆ)(ˆ_ˆ) Returned Properly (ˆ_ˆ)(ˆ_ˆ)\n");
return 1;
}
// This function will be used in the optional task
void foo(){
static int i = 1;
printf("Function foo() is invoked %d times\n", i++);
return;
}

The above program has a buffer overflow vulnerability. It first reads an input up to 1000 bytes froma file called badfile. It then passes the input data to the bof() function, which copies the input to its
internal buffer using strcpy(). However, the internal buffer’s sizeis less than 1000, so here is potentialbuffer-overflow vulnerability.
This program is a root-owned Set-UID program, so if a normal user canexploit this buffer overflowvulnerability, the user might be able to get a root shell. It should be noted that the program gets its inputfrom a file called badfile, which is provided by users.Therefore, we can construct the file in a way suchthat when the vulnerable program copies the file contents into its buffer, a root shell can be spawned.Compilation. Let us first compile the code and turn it into a root-owned Set-UID program. Do not forgetto include the -fno-stack-protector option (for turning off the StackGuard protection) and the "-znoexecstack" option (for turning on the non-executable stack protection). It should also be noted thatchanging ownership must be done before turning on the Set-UID bit, because ownership changes causethe Set-UID bit to be turned off. All thesecommands are included in the provided Makefile.
// Note: N should be replaced by the value set by the instructor

$ gcc -m32 -DBUF_SIZE=N -fno-stack-protector -z noexecstack -o retlib retlib.c
$ sudo chown root retlib
$ sudo chmod 4755 retlib
For instructors. To prevent students from using the solutions from the past (or from those posted on the
Internet), instructors can change the value for BUF SIZE by requiring students to compile the code using
a different BUF SIZE value. Without the -DBUF SIZE option, BUF SIZE is set to the default value 12
(defined in the program). When this value changes, the layout of the stack will change, and the solution
will be different. Students should ask their instructors for the value of N. The value of N can be set in the
provided Makefile and N can be from 10 to 800.

3 Lab Tasks

3.1 Task 1: Finding out the Addresses of libc Functions
In Linux, when a program runs, the libc library will be loaded into memory. When the memory addressrandomization is turned off, for the same program, the library is always loaded in the same memory address
(for different programs, the memory addresses of the libc library may be different). Therefore, we caneasily find out the address of system() using a debugging tool such as gdb. Namely, we can debug theSEED Labs – Return-to-libc Attack Lab 5target program retlib. Even though the program is a root-owned Set-UID program, we can still debugit, except that the privilege will be dropped (i.e., the effective user ID will be the same as the real user ID).Inside gdb, we need to type the run command to execute the target program once, otherwise, the librarycode will not be loaded. We use the p command (or print) to print out the address of the system() andexit() functions (we will need exit() later on).

$ touch badfile
$ gdb -q retlib ➙Use "Quiet" mode
Reading symbols from ./retlib...
(No debugging symbols found in ./retlib)
gdb-peda$ break main
Breakpoint 1 at 0x1327
gdb-peda$ run
......
Breakpoint 1, 0x56556327 in main ()
gdb-peda$ p system
$1 = {<text variable, no debug info>} 0xf7e12420 <system>
gdb-peda$ p exit
$2 = {<text variable, no debug info>} 0xf7e04f80 <exit>
gdb-peda$ quit
It should be noted that even for the same program, if we change it from a Set-UID program to a
non-Set-UID program, the libc library may not be loaded into the same location. Therefore, when we
debug the program, we need to debug the target Set-UID program; otherwise, the address we get may be
incorrect.
Running gdb in batch mode. If you prefer to run gdb in a batch mode, you can put the gdb commands
in a file, and then ask gdb to execute the commands from this file:
$ cat gdb_command.txt
break main
run
p system
p exit
quit
$ gdb -q -batch -x gdb_command.txt ./retlib
...
Breakpoint 1, 0x56556327 in main ()
$1 = {<text variable, no debug info>} 0xf7e12420 <system>
$2 = {<text variable, no debug info>} 0xf7e04f80 <exit>

3.2 Task 2: Putting the shell string in the memory
Our attack strategy is to jump to the system() function and get it to execute an arbitrary command.Since we would like to get a shell prompt, we want the system() function to execute the"/bin/sh"program. Therefore, the command string "/bin/sh" must be put in the memory first and we have to knowits address (this address needs to be passed to the system() function). There are many ways to achievethese goals; we choose a method that uses environment variables. Students areencouraged to use otherapproaches.When we execute a program from a shell prompt, the shell actually spawns a child process to execute theprogram, and all the exported shell variables become theenvironment variables of the child process. ThisSEED Labs – Return-to-libc Attack Lab 6creates an easy way for us to put some arbitrary string in the child process’s memory. Let us define a new
shell variable MYSHELL, and let it contain the string "/bin/sh". From the following commands, we canverify that the string gets into the child process, and it is printed out by the env command runninginsidethe child process.

$ export MYSHELL=/bin/sh
$ env | grep MYSHELL
MYSHELL=/bin/sh
We will use the address of this variable as an argument to system() call. The location of this variable
in the memory can be found out easily using the following program:
void main(){
char* shell = getenv("MYSHELL");
if (shell)
printf("%x\n", (unsigned int)shell);
}

Compile the code above into a binary called prtenv. If the address randomization is turned off, youwill find out that the same address is printed out. When you run the vulnerable program retlib inside the
same terminal, the address of the environment variable will be thesame. You can verify that by putting thecode above inside retlib.c. However, the length of the program name does make adifference. That’swhy we choose 6 characters for the program nameprtenv to match the length of retlib.

3.3 Task 3: Launching the Attack

We are ready to create the content of badfile. Since the content involves some binary data (e.g., theaddress of the libc functions), we can use Python to do the construction. We provide a skeleton of thecode in the following, with the essential parts left for you to fill out.

#!/usr/bin/env python3
import sys
# Fill content with non-zero values
content = bytearray(0xaa for i in range(300))
X = 0
sh_addr = 0x00000000 # The address of "/bin/sh"
content[X:X+4] = (sh_addr).to_bytes(4,byteorder=’little’)
Y = 0
system_addr = 0x00000000 # The address of system()
content[Y:Y+4] = (system_addr).to_bytes(4,byteorder=’little’)
Z = 0
exit_addr = 0x00000000 # The address of exit()
content[Z:Z+4] = (exit_addr).to_bytes(4,byteorder=’little’)
# Save content to a file
with open("badfile", "wb") as f:
f.write(content)

You need to figure out the three addresses and the values for X, Y,and Z. If your values are incorrect,SEED Labs – Return-to-libc Attack Lab 7your attack might not work. In your report, you need to describe how you decide the values for X, Y and Z.Either show us your reasoning or, if you use a trial-and-error approach, show your trials.A note regarding gdb. If you use gdb to figure out the values for X, Y, and Z, it should be noted that thegdb behavior in Ubuntu 20.04 is slightly different from that inUbuntu 16.04. In particular

5 Submission

You need to submit a detailed lab report, with screenshots, todescribe what you have done and what youhave observed. You also need to provide explanation to the observations that are interesting or surprising.Please also list the important code snippets followed by explanation. Simply attaching code without anyexplanation will not receive credits.
WX:codehelp


心软的红豆
1 声望0 粉丝