Exploiting. Part 1: Applications

We discuss exploiting concents and then focus on application exploiting (i.e. runtime/binary application exploiting).

Slides for this session:

Cheatsheet

Before you start, enable support for x32 binaries with:

# allow the execution of x32 binaries on x64 Linux
sudo dpkg --add-architecture i386
sudo apt-get update
sudo apt-get install libc6:i386 libncurses5:i386 libstdc++6:i386
# also allow the compilation of x32 binaries on x64 Linux
sudo apt-get install gcc-multilib

To disassemble a binary, use:

objdump -M intel -d <name-of-the-binary> | less

To disassemble a raw shellcode, use:

objdump -D -b binary -m i386 -M intel <file-containing-raw-shellcode>

Compute a payload, write it to a file and then send the payload to a binary:

python3 -c 'import sys; sys.stdout.buffer.write(b"<your-payload>")' > /tmp/payload
cat /tmp/payload - | ./<vulnerable-binary>

Same outcome, without writing the payload to the file:

cat <((python3 -c 'import sys; sys.stdout.buffer.write(b"<your-payload>")')) - | ./<vulnerable-binary>

To send the payload to a remote server, use:

cat <((python3 -c 'import sys; sys.stdout.buffer.write(b"<your-payload>")')) - | nc <IP> <PORT>

Caution: Use sys.stdout.buffer.write() instead of print() when working with raw bytes in Python3. For details, see this stackoverflow question.

Lab Key Takeaways

  1. x32 Linux system call convention: place the system call number in eax, then its arguments, in order, in ebx, ecx, edx, esi, edi, and ebp, then invoke int 0x80.

  2. On x32 Linux, function call parameters are pushed on the stack from the rightmost to the leftmost as they appear in C code.

  3. On x32 Linux, system call parameters are copied into registers (they are not pushed into registers).

  4. Based on points 2 and 3 above: on x32 Linux function calling convention is different than system call calling convention.

  5. x64 Linux system call convention: place the system call number in rax, then its arguments, in order, in rdi, rsi, rdx, r10, r8, and r9, then invoke syscall.

  6. On x64 Linux, the x64 Linux function call convention states that parameters are pushed into rdi, rsi, rdx, rcx, r8, r9 respectively.

  7. Based on points 5 and 6 above: in terms of parameter order on x64, the only difference between function calling and syscall calling is the overlap between r10 and rcx. See the question about r10 and rcx registers.

  8. A list of both x32 and x64 syscalls is Linux system call convention.

Tasks

Download the session task archive.

  1. Open the session task archive and access the buffer-overflow/ subfolder. Exploit the vuln executable to jump to the warcraft(), diablo() and then starcraft() functions, one at a time. When calling starcraft() you are only interested in printing the message via the puts() call.

    The steps are:

    1. Use nm or objdump to find the addresses of warcraft(), diablo() and the puts() call inside of starcraft().

    2. Identify the location and the length of the buffer overflow.

    3. Craft payloads to exploit the buffer overflow.

    4. Send each payload to the binary.

  2. Open the session task archive and access the shellcode/ subfolder. The hello-shellcode/vuln binary is compiled from hello-shellcode/vuln.c using make. The gen-hello-shellcode/ subdirectory contains boilerplate code to help you easily compile shellcodes.

    See the assembly source code in gen-hello-shellcode/shellcode.S, and then use make to assemble it as a raw file called shellcode.bin. You can disassemble the raw file by using objdump -D -b binary -m i386 -M intel shellcode.bin and see that it matches shellcode.S. You can list the contents of shellcode.bin in hexadecimal by using make print.

    Your goal is to write shellcodes in asm, assemble the shellcodes and send the shellcodes to the vuln binary.

    At each subchallenge, perform the following:

    1. Craft your shellcode inside the right asm source file.

    2. Compile and print your shellcode.

    3. Send the raw shellcode to the vuln binary to exploit it.

    The subchallenges are:

    1. The gen-hello-shellcode/shellcode-mundi.S file is a copy of gen-hello-shellcode/shellcode.S. Update the gen-hello-shellcode/shellcode-mundi.S shellcode to write Salut, Mundi! to standard output. Then compile the shellcode with make print-mundi and send it to the vuln binary.

    2. Now make the vuln binary exit properly (without printing Segmentation fault) after running your shellcode. To do so, update the gen-hello-shellcode/print-exit-shellcode.S source to also run exit(0) after printing Salut, Mundi!. Then compile the shellcode with make print-exit-print and send it to the vuln binary.

      The system call ID of exit() is on the Linux system calls page or inside of /usr/include/asm/unistd_32.h. The system call ID is stored in the eax register and parameters are stored in ebx, ecx, edx, esi and edi.

      Check information on Linux system calls and Linux system call convention.

  1. Printing is boring. Spawning shells is fun. Create a new shellcode that spawns a shell. To do so, update the gen-hello-shellcode/execve-shellcode.S shellcode to spawn a shell by running execve("/bin/sh", 0, 0). Then compile the shellcode with make execve-print and then send it to the vuln binary.

    If you are out of ideas for the shellcode, take a look at this one. Check a more complicated one if things are too easy. For the curious, more fun shellcodes are found on shell-storm.org.

    After you get a working shellcode, test the same payload on the remote server. Use nc alongside python3 to generate and send the payload to the remote server. The address is 141.85.224.104:30000.

    For a better understanding of what the execve shellcode does, go through the article Demystifying the Execve Shellcode (Stack Method).

  1. (BONUS) Create a shellcode that reads a fixed-length message from standard input and then prints it and exits properly. Update the gen-hello-shellcode/read-print-exit-shellcode.S source to read a fixed length string onto the stack and print it to standard output. Then compile the shellcode with make read-print-exit-print and send it to the vuln binary.

  1. Open the session task archive and access the ret-to-libc/ subfolder. Your goal is to create shells by calling system(). Do this in three different ways:

    1. Call system("/bin/bash") from function enroth().

    2. Comment out enroth() and call system("sh") using the call to system() in function erathia() and the sh string already in the executable.

    3. Also comment out erathia() and call system("sh") by using the address of system() in the standard C library. Disable ASLR by using echo 0 | sudo tee /proc/sys/kernel/randomize_va_space.

  2. Open the session task archive and access the integer-overflow/ subfolder. Provide the proper input to the intover executable to create a shell. Craft a working payload locally and then submit it to the remote server. The address is 141.85.224.104:30001