This shows you the differences between two versions of the page.
Both sides previous revision Previous revision Next revision | Previous revision | ||
session:09 [2019/07/04 15:36] Razvan Deaconescu |
session:09 [2020/07/19 12:49] (current) |
||
---|---|---|---|
Line 1: | Line 1: | ||
- | = 0x09. Defense Mechanisms | + | ====== 0x08. Return Oriented Programming ====== |
- | == Resources | + | ===== Resources |
- | [[http:// | + | [[https:// |
- | [[https://security.cs.pub.ro/summer-school/res/arc/09-defense-mechanisms-skel.zip|Activities archive]] | + | [[https://github.com/hexcellents/sss-exploit|Session' |
- | == Tutorials | + | [[https:// |
- | The previous sessions ([[: | ||
- | <note warning> | + | === PLT and GOT === |
- | The tasks today are designed for 32 bit executables. Make sure you compile with the '' | + | |
- | The binaries in the tasks archive are already compiled as such. | + | |
- | </ | + | |
- | + | ||
- | === Executable Space Protection | + | |
- | + | ||
- | The **executable space protection** is an instance of the **principle of least privilege**, | + | |
- | + | ||
- | The mechanism can be (and was) implemented in many different ways, the most common in Linux being: | + | |
- | + | ||
- | **NX bit:** This is the easiest method, and involves an extra bit added to each page table entry that specifies if the memory page should be executable or not. This is current implementation in 64-bit processors where page table entries are 8-bytes wide. | + | |
- | + | ||
- | **Physical Address Extension (PAE):** Besides the main feature that allows access to more than 4GB of memory, the PAE extension for 32-bit processor also adds a NX bit in its page table entries. | + | |
- | + | ||
- | **Emulation: | + | |
- | + | ||
- | < | + | |
- | This security feature gets in the way of **just-in-time (JIT)** compilers, which need to produce and write code at runtime, and that is later executed. Since a JIT compiler cannot run in this kind of secured environment, | + | |
- | + | ||
- | * Slides: [[http:// | + | |
- | * Paper: [[http:// | + | |
- | + | ||
- | </ | + | |
- | + | ||
- | There are of course other implementations in different hardening-oriented projects such as: OpenBSD [[http:// | + | |
- | + | ||
- | ==== Walk-through | + | |
- | + | ||
- | The Linux kernel provides support for managing memory protections in the '' | + | |
- | + | ||
- | <note important> | + | |
- | PaX has a protection option that restricts the use of '' | + | |
- | </ | + | |
- | + | ||
- | Let's start by deactivating ASLR, which is going to be discussed in the following section of this tutorial, and only focus on the NX protection: | + | |
- | + | ||
- | <code bash> | + | |
- | ~$ sudo bash -c 'echo 0 > / | + | |
- | </ | + | |
- | + | ||
- | Let's first compile an extremely simple C application: | + | |
- | + | ||
- | <code c> | + | |
- | int main() { | + | |
- | while (1); | + | |
- | } | + | |
- | </ | + | |
- | + | ||
- | <code bash> | + | |
- | ~$ CFLAGS=' | + | |
- | </ | + | |
- | + | ||
- | As presented in [[session: | + | |
- | + | ||
- | < | + | |
- | Program Headers: | + | |
- | Type | + | |
- | PHDR | + | |
- | INTERP | + | |
- | [Requesting program interpreter: | + | |
- | LOAD | + | |
- | LOAD | + | |
- | DYNAMIC | + | |
- | NOTE | + | |
- | GNU_EH_FRAME | + | |
- | GNU_STACK | + | |
- | GNU_RELRO | + | |
- | + | ||
- | | + | |
- | Segment Sections... | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | </ | + | |
- | + | ||
- | + | ||
- | Check the '' | + | |
- | + | ||
- | Next we are interested in seeing calls to '' | + | |
- | + | ||
- | <code bash> | + | |
- | ~$ strace -e mmap2, | + | |
- | </ | + | |
- | + | ||
- | <code text> | + | |
- | [ Process PID=11198 runs in 32 bit mode. ] | + | |
- | mmap2(0x8048000, | + | |
- | mmap2(0x8049000, | + | |
- | mmap2(NULL, 4096, PROT_READ|PROT_WRITE, | + | |
- | mmap2(NULL, 8192, PROT_READ|PROT_WRITE, | + | |
- | mmap2(NULL, 156324, PROT_READ, MAP_PRIVATE, | + | |
- | mmap2(NULL, 1763964, PROT_READ|PROT_EXEC, | + | |
- | mmap2(0xf7fcd000, | + | |
- | mmap2(0xf7fd0000, | + | |
- | mmap2(NULL, 4096, PROT_READ|PROT_WRITE, | + | |
- | mprotect(0xf7fcd000, | + | |
- | mprotect(0x8049000, | + | |
- | mprotect(0x56575000, | + | |
- | </ | + | |
- | + | ||
- | We can observe a '' | + | |
- | + | ||
- | <note important> | + | |
- | Note that the **stack** is not explicitly allocated by the loader. The kernel will keep increasing it each time a page fault is triggered without calling '' | + | |
- | </ | + | |
- | + | ||
- | We can dump all memory mappings of the still running process as follows: | + | |
- | + | ||
- | <code bash> | + | |
- | ~$ ps u | grep / | + | |
- | ... | + | |
- | ~$ cat / | + | |
- | </ | + | |
- | + | ||
- | <note important> | + | |
- | Make sure to use the PID of the loader process, and not the '' | + | |
- | </ | + | |
- | + | ||
- | <code bash> | + | |
- | ~$ cat / | + | |
- | </ | + | |
- | + | ||
- | <code text> | + | |
- | 08048000-08049000 r-xp 00000000 00:22 5769082 | + | |
- | 08049000-0804a000 r--p 00000000 00:22 5769082 | + | |
- | 0804a000-0804b000 rw-p 00001000 00:22 5769082 | + | |
- | 56555000-56575000 r-xp 00000000 08:05 827365 | + | |
- | 56575000-56576000 r--p 0001f000 08:05 827365 | + | |
- | 56576000-56577000 rw-p 00020000 08:05 827365 | + | |
- | f7e23000-f7e24000 rw-p 00000000 00:00 0 | + | |
- | f7e24000-f7fcd000 r-xp 00000000 08:05 823395 | + | |
- | f7fcd000-f7fcf000 r--p 001a9000 08:05 823395 | + | |
- | f7fcf000-f7fd0000 rw-p 001ab000 08:05 823395 | + | |
- | f7fd0000-f7fd3000 rw-p 00000000 00:00 0 | + | |
- | f7ffa000-f7ffd000 rw-p 00000000 00:00 0 | + | |
- | f7ffd000-f7ffe000 r-xp 00000000 00:00 0 [vdso] | + | |
- | fffdd000-ffffe000 rw-p 00000000 00:00 0 [stack] | + | |
- | </ | + | |
- | + | ||
- | ==== Bypassing NX | + | |
- | + | ||
- | **ret-to-plt/ | + | |
- | + | ||
- | **mprotect().** If the application is using '' | + | |
- | + | ||
- | **Return Oriented Programming (ROP).** This is a generalization of the ret-to-* approach that makes use of existing code to execute almost anything. As this is probably one of the most common types of attacks, it will be discussed in depth in a future section. | + | |
- | + | ||
- | === Address Space Layout Randomization | + | |
- | + | ||
- | Address Space Layout Randomization (ASLR) is a security feature that maps different memory regions of an executable at random addresses. This prevents buffer overflow-based attacks that rely on known addresses such as the stack (for calling into shellcode), or dynamically linked libraries (for calling functions that were not already linked with the target binary). Usually, the sections that are randomly mapped are: the stack, the heap, the VDSO page, and the dynamic libraries. The code section can also be randomly mapped for [[http:// | + | |
- | + | ||
- | <note important> | + | |
- | Linux allows 3 options for its ASLR implementation that can be configured using the ''/ | + | |
- | * **0**: deactivated | + | |
- | * **1**: random stack, vdso, libraries; heap is after code section; random code section (only for PIE-linked binaries) | + | |
- | * **2**: random heap too | + | |
- | + | ||
- | </ | + | |
- | + | ||
- | Let's reactivate ASLR after the previous section of the tutorial: | + | |
- | + | ||
- | <code bash> | + | |
- | ~$ sudo bash -c 'echo 2 > / | + | |
- | </ | + | |
- | + | ||
- | We can easily demonstrate the effects on shared libraries by running '' | + | |
- | + | ||
- | ==== PLT and GOT | + | |
ASLR is not the only feature that prevents the compiler and the linker from solving some relocations before the binary is actually running. Shared libraries can also be combined in different ways, so the first time you actually know the address of a shared library is while the loader is running. The ASLR feature is orthogonal to this - the loader could choose to assign address to libraries in a round-robin fashion, or could use ASLR to assign them randomly. | ASLR is not the only feature that prevents the compiler and the linker from solving some relocations before the binary is actually running. Shared libraries can also be combined in different ways, so the first time you actually know the address of a shared library is while the loader is running. The ASLR feature is orthogonal to this - the loader could choose to assign address to libraries in a round-robin fashion, or could use ASLR to assign them randomly. | ||
Line 275: | Line 100: | ||
What's going on here? What's actually happening is //lazy binding// — by convention when the dynamic linker loads a library, it will put an identifier and resolution function into known places in the GOT. Therefore, what happens is roughly this: on the first call of a function, it falls through to call the default stub, it simply jumps to the next instruction. The identifier is pushed on the stack, the dynamic linker is called, which at that point has enough information to figure out "hey, this program is trying to find the function foo". It will go ahead and find it, and then patch the address into the GOT such that the next time the original PLT entry is called, it will load the actual address of the function, rather than the lookup stub. Ingenious! | What's going on here? What's actually happening is //lazy binding// — by convention when the dynamic linker loads a library, it will put an identifier and resolution function into known places in the GOT. Therefore, what happens is roughly this: on the first call of a function, it falls through to call the default stub, it simply jumps to the next instruction. The identifier is pushed on the stack, the dynamic linker is called, which at that point has enough information to figure out "hey, this program is trying to find the function foo". It will go ahead and find it, and then patch the address into the GOT such that the next time the original PLT entry is called, it will load the actual address of the function, rather than the lookup stub. Ingenious! | ||
- | === Bypassing ASLR | ||
- | **Bruteforce.** If you are able to inject payloads multiple times without crashing the application, | + | ==== Return Oriented Programming ==== |
- | **NOP sled.** In the case of shellcodes, a longer NOP sled will maximize the chances of jumping inside it and eventually reaching the exploit code even if the stack address is randomized. This is not very useful when we are interested in jumping to '' | + | {{ : |
- | **jmp esp.** This will basically jump into the stack, no matter where it is mapped. It's actually a very rudimentary form of Return Oriented Programming which is going to be discussed | + | === Motivation === |
+ | In the previous sessions we discussed | ||
+ | < | ||
+ | RET + 0x00: addr of system | ||
+ | RET + 0x04: | ||
+ | RET + 0x08: | ||
+ | </ | ||
- | **Restrict entropy.** There are various ways of reducing the entropy | + | However, what happens when you need to call multiple functions? Say you need to call f1() and then f2(0xAB, 0xCD)? The payload should be: |
+ | < | ||
+ | RET + 0x00: | ||
+ | RET + 0x04: | ||
+ | RET + 0x08: JUNK (return address after f2 finishes: we don't care about what happens after the 2 functions are called) | ||
+ | RET + 0x0c: 0xAB (param1 of f2) | ||
+ | RET + 0x10: 0xCD (param2 of f2) | ||
+ | </ | ||
+ | What about if we need to call f1(0xAB, 0xCD) and then f2(0xEF, 0x42) ? | ||
+ | < | ||
+ | RET + 0x00: addr of f1 | ||
+ | RET + 0x04: addr of f2 (return address after f1 finishes) | ||
+ | RET + 0x08: 0xAB (param1 of f1) | ||
+ | RET + 0x0c: 0xCD (param2 of f1) but this should also be 0xEF (param1 of f2) | ||
+ | RET + 0x10: 0x42 (param2 | ||
+ | </ | ||
- | **Information leak.** The most effective way of bypassing ASLR is by using an information leak vulnerability that exposes randomized address, or at least parts of them. You can also dump parts of libraries (e.g., | + | This kind of conflict can be resolved |
- | === Tools: ltrace | + | === NOP analogy === |
+ | While '' | ||
+ | Let's explore an example: | ||
+ | <code c> | ||
+ | int main() | ||
+ | { | ||
+ | char a[16]; | ||
+ | read(0, a, 100); | ||
- | We are going to use '' | + | return 0; |
+ | } | ||
+ | </ | ||
+ | This code obviously suffers from a stack buffer overflow. The offset | ||
+ | Remember the NOP sled concept from previous sessions? These were long chains of NOP instructions (" | ||
+ | Since we can't add any new code to the program (NX is enabled) how could we simulate the effect of a NOP sled? Easy! Using return instructions! | ||
+ | < | ||
+ | # objdump | ||
+ | | ||
+ | | ||
+ | | ||
+ | | ||
+ | | ||
+ | | ||
+ | | ||
+ | | ||
+ | </ | ||
+ | Any and all of these addresses will be ok. The payload could be the following: | ||
+ | < | ||
+ | RET + 0x00: | ||
+ | RET + 0x04: | ||
+ | RET + 0x08: | ||
+ | RET + 0x0c: | ||
+ | RET + 0x10: | ||
+ | ..... | ||
+ | </ | ||
+ | The original ret (in the normal code flow) will pop RET+0x00 off the stack and jump to it. When it gets popped the stack is automatically increased by 4 (on to the next value). The instruction at '' | ||
+ | That payload is not the only option. We don't really care which '' | ||
< | < | ||
- | python -c 'print " | + | RET + 0x00: |
+ | RET + 0x04: | ||
+ | RET + 0x08: | ||
+ | RET + 0x0c: | ||
+ | RET + 0x10: | ||
+ | ..... | ||
</ | </ | ||
+ | Notice the addresses are different but because they all point to a '' | ||
+ | |||
+ | <note warning> | ||
+ | Take a moment to fully understand what is happening here. Run your own program and step through the payload to see this in action before proceeding. | ||
+ | Follow along using this skeleton to generate the payloads. | ||
+ | </ | ||
+ | <file python skel.py> | ||
+ | # | ||
+ | import struct, sys | ||
+ | |||
+ | def dw(i): | ||
+ | return struct.pack("< | ||
+ | |||
+ | #TODO update count for your prog | ||
+ | pad_count_to_ret = 100 | ||
+ | payload = " | ||
+ | |||
+ | #TODO figure out the rop chain | ||
+ | payload += dw(0xcafebeef) | ||
+ | payload += dw(0xdeadc0de) | ||
+ | |||
+ | |||
+ | sys.stdout.write(payload) | ||
+ | |||
+ | </ | ||
+ | |||
+ | |||
+ | === Gadgets & ROP chains === | ||
+ | Now that we have a sort of neutral primitive equivalent to a NOP let's actually do something useful. | ||
+ | The building blocks of ROP payloads are called gadgets. These are blocks of instructions that end with a ' | ||
+ | Here are some ' | ||
+ | < | ||
+ | 0x8048443: pop ebp; ret | ||
+ | 0x80484a7: pop edi; pop ebp; ret | ||
+ | 0x8048441: mov ebp,esp; pop ebp; ret | ||
+ | 0x80482da: pop eax; pop ebx; leave; ret | ||
+ | 0x80484c3: pop ecx; pop ebx; leave; ret | ||
+ | </ | ||
+ | |||
+ | By carefully stitching such gadgets on the stack we can bring code execution to almost any context we want. | ||
+ | As an example let's say we would like to load 0x41424344 into eax and 0x61626364 into ebx. The payload should look like: | ||
+ | < | ||
+ | RET + 0x00: | ||
+ | RET + 0x04: | ||
+ | RET + 0x08: | ||
+ | RET + 0x0c: | ||
+ | </ | ||
+ | * First the ret addr is popped from the stack and execution goes there. | ||
+ | * At '' | ||
+ | * At '' | ||
+ | * At '' | ||
+ | * At '' | ||
+ | |||
+ | We have now seen how gadgets can be useful if we want the CPU to achieve a certain state. This is particularly useful on other architectures such as ARM and x86_64 where functions do not take parameters from the stack but from registers. | ||
+ | As an example, if we want to call f1(0xAB, 0xCD, 0xEF) on x86_64 we first need to know the calling convention for the first three parameters: | ||
+ | * 1st param: RDI | ||
+ | * 2nd param: RSI | ||
+ | * 3rd param: RDX | ||
+ | Next we would need gadgets for each. Let's assume these 2 scenarios: | ||
+ | Scenario 1: | ||
+ | < | ||
+ | 0x400124: | ||
+ | 0x400235: | ||
+ | 0x400440: | ||
+ | |||
+ | Payload: | ||
+ | RET + 0x00: | ||
+ | RET + 0x08: val of RDI (0xAB) | ||
+ | RET + 0x10: val of RSI (0xCD) | ||
+ | RET + 0x18: | ||
+ | RET + 0x20: val of RDX | ||
+ | RET + 0x28: f1 | ||
+ | </ | ||
+ | |||
+ | Scenario 2: | ||
+ | < | ||
+ | 0x400125: | ||
+ | 0x400252: | ||
+ | 0x400235: | ||
+ | 0x400440: | ||
+ | |||
+ | Payload: | ||
+ | RET + 0x00: | ||
+ | RET + 0x08: val of RDI (0xAB) | ||
+ | RET + 0x10: | ||
+ | RET + 0x18: val of RSI (0xCD) | ||
+ | RET + 0x20: | ||
+ | RET + 0x28: val of RDX | ||
+ | RET + 0x30: f1 | ||
+ | </ | ||
+ | Notice that because the architecture is 64 bits wide, the values on the stack are not dwords but qwords (quad words: 8 bytes wide) | ||
+ | |||
+ | |||
+ | The second use of gadgets is to clear the stack. Remember the issue we had in the **Motivation** section? Let's solve it using gadgets. | ||
+ | We need to call f1(0xAB, 0xCD) and then f2(0xEF, 0x42). Our initial solution was: | ||
+ | < | ||
+ | RET + 0x00: addr of f1 | ||
+ | RET + 0x04: addr of f2 (return address after f1 finishes) | ||
+ | RET + 0x08: 0xAB (param1 of f1) | ||
+ | RET + 0x0c: 0xCD (param2 of f1) but this should also be 0xEF (param1 of f2) | ||
+ | RET + 0x10: 0x42 (param2 of f2) | ||
+ | </ | ||
+ | |||
+ | The problem is that those parameters of f1 are getting in the way of calling f2. We need to find a **pop pop ret** gadget. The actual registers are not important. | ||
+ | |||
+ | < | ||
+ | RET + 0x00: addr of f1 | ||
+ | RET + 0x04: addr of (pop eax, pop ebx, ret) | ||
+ | RET + 0x08: 0xAB (param1 of f1) | ||
+ | RET + 0x0c: 0xCD (param2 of f1) | ||
+ | RET + 0x10: addr of f2 | ||
+ | RET + 0x14: JUNK | ||
+ | RET + 0x18: 0xEF (param1 of f2) | ||
+ | RET + 0x1c: 0x42 (param2 of f2) | ||
+ | </ | ||
+ | Now we can even call the next function f3 if we repeat the trick: | ||
+ | < | ||
+ | RET + 0x00: addr of f1 | ||
+ | RET + 0x04: addr of (pop eax, pop ebx, ret) | ||
+ | RET + 0x08: 0xAB (param1 of f1) | ||
+ | RET + 0x0c: 0xCD (param2 of f1) | ||
+ | RET + 0x10: addr of f2 | ||
+ | RET + 0x14: addr of (pop eax, pop ebx, ret) | ||
+ | RET + 0x18: 0xEF (param1 of f2) | ||
+ | RET + 0x1c: 0x42 (param2 of f2) | ||
+ | RET + 0x20: addr of f3 | ||
+ | </ | ||
+ | |||
+ | |||
+ | ==== Some useful ninja tricks ==== | ||
+ | |||
+ | === Memory spraying === | ||
+ | Let's take the following prog: | ||
+ | <code c> | ||
+ | int main() | ||
+ | { | ||
+ | int x, y ,z; | ||
+ | char a,b,c; | ||
+ | char buf[23]; | ||
+ | read(0, buf, 100); | ||
+ | |||
+ | return 0; | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | A fairly simple overflow, right? How fast can you figure out the offset to the return address? How much padding do you need ? | ||
+ | There is a shortcut that you can use to figure this out in under 30 seconds without looking at the assembly. | ||
+ | |||
+ | A [[ https:// | ||
+ | |||
+ | Peda can help you do this. Here's how: | ||
+ | <code bash> | ||
+ | gdb-peda$ help pattern_create | ||
+ | Generate a cyclic pattern | ||
+ | Usage: | ||
+ | pattern_create size [file] | ||
+ | |||
+ | gdb-peda$ pattern_create 100 | ||
+ | ' | ||
+ | |||
+ | gdb-peda$ help pattern_offset | ||
+ | Search for offset of a value in cyclic pattern | ||
+ | Usage: | ||
+ | pattern_offset value | ||
+ | |||
+ | gdb-peda$ pattern_offset AA8A | ||
+ | AA8A found at offset: 76 | ||
+ | </ | ||
+ | |||
+ | Things can even get more complex: if you insert such patterns as input to the program you can search for signs of where it got placed using peda. Here's how to figure out the offset to the return address in 3 commands for the previous program as promised: | ||
+ | <code bash> | ||
+ | # gdb -q ./a | ||
+ | Reading symbols from ./a...(no debugging symbols found)...done. | ||
+ | gdb-peda$ pattern_create 200 | ||
+ | ' | ||
+ | gdb-peda$ run | ||
+ | AAAaAA0AABAAbAA1AACAAcAA2AADAAdAA3AAEAAeAA4AAFAAfAA5AAGAAgAA6AAHAAhAA7AAIAAiAA8AAJAAjAA9AAKAAkAALAAlAAMAAmAANAAnAAOAAoAAPAApAAQAAqAARAArAASAAsAATAAtAAUAAuAAVAAvAAWAAwAAXAAxAAYAAyAAZAAzAaaAa0AaBAabAa1A | ||
+ | |||
+ | Program received signal SIGSEGV, Segmentation fault. | ||
+ | [----------------------------------registers-----------------------------------] | ||
+ | EAX: 0x0 | ||
+ | EBX: 0xf7f97e54 --> 0x1a6d5c | ||
+ | ECX: 0xffffcd49 (" | ||
+ | EDX: 0x64 (' | ||
+ | ESI: 0x0 | ||
+ | EDI: 0x0 | ||
+ | EBP: 0x41334141 (' | ||
+ | ESP: 0xffffcd70 (" | ||
+ | EIP: 0x41414541 (' | ||
+ | EFLAGS: 0x10207 (CARRY PARITY adjust zero sign trap INTERRUPT direction overflow) | ||
+ | [-------------------------------------code-------------------------------------] | ||
+ | Invalid $PC address: 0x41414541 | ||
+ | [------------------------------------stack-------------------------------------] | ||
+ | 0000| 0xffffcd70 (" | ||
+ | 0004| 0xffffcd74 (" | ||
+ | 0008| 0xffffcd78 (" | ||
+ | 0012| 0xffffcd7c (" | ||
+ | 0016| 0xffffcd80 (" | ||
+ | 0020| 0xffffcd84 (" | ||
+ | 0024| 0xffffcd88 (" | ||
+ | 0028| 0xffffcd8c (" | ||
+ | 0032| 0xffffcd90 (" | ||
+ | 0036| 0xffffcd94 (" | ||
+ | 0040| 0xffffcd98 (" | ||
+ | 0044| 0xffffcd9c (" | ||
+ | 0048| 0xffffcda0 (" | ||
+ | 0052| 0xffffcda4 (" | ||
+ | 0056| 0xffffcda8 (" | ||
+ | 0060| 0xffffcdac --> 0x6c (' | ||
+ | |||
+ | [------------------------------------------------------------------------------] | ||
+ | Legend: code, data, rodata, value | ||
+ | Stopped reason: SIGSEGV | ||
+ | 0x41414541 in ?? () | ||
+ | |||
+ | |||
+ | |||
+ | gdb-peda$ pattern_search | ||
+ | Registers contain pattern buffer: | ||
+ | EIP+0 found at offset: 35 | ||
+ | EBP+0 found at offset: 31 | ||
+ | Registers point to pattern buffer: | ||
+ | [ECX] --> offset 0 - size ~100 | ||
+ | [ESP] --> offset 39 - size ~61 | ||
+ | Pattern buffer found at: | ||
+ | 0xffffcd49 : offset | ||
+ | 0xffffd1c6 : offset 23424 - size 4 ($sp + 0x456 [277 dwords]) | ||
+ | 0xffffd1d8 : offset 22930 - size 4 ($sp + 0x468 [282 dwords]) | ||
+ | 0xffffd276 : offset 48535 - size 4 ($sp + 0x506 [321 dwords]) | ||
+ | References to pattern buffer found at: | ||
+ | 0xffffcd20 : 0xffffcd49 ($sp + -0x50 [-20 dwords]) | ||
+ | 0xffffcd34 : 0xffffcd49 ($sp + -0x3c [-15 dwords]) | ||
+ | |||
+ | </ | ||
+ | |||
+ | |||
+ | === Vulnerable function identification === | ||
+ | As you can see from above, the base pointer gets trashed so backtracing is not possible | ||
+ | <code bash> | ||
+ | gdb-peda$ bt | ||
+ | #0 0x41414541 in ?? () | ||
+ | #1 0x34414165 in ?? () | ||
+ | #2 0x41464141 in ?? () | ||
+ | #3 0x41416641 in ?? () | ||
+ | </ | ||
+ | If this program was larger you wouldn' | ||
+ | You can set a breakpoint on all declared functions (if the program has not been stripped) using **rbreak** and then ignoring them: | ||
+ | <code bash> | ||
+ | gdb-peda$ rbreak | ||
+ | Breakpoint 1 at 0x80482d4 | ||
+ | < | ||
+ | Breakpoint 2 at 0x8048310 | ||
+ | < | ||
+ | Breakpoint 3 at 0x8048320 | ||
+ | < | ||
+ | Breakpoint 4 at 0x8048330 | ||
+ | < | ||
+ | Breakpoint 5 at 0x8048340 | ||
+ | < | ||
+ | Breakpoint 6 at 0x8048370 | ||
+ | < | ||
+ | Breakpoint 7 at 0x804843f | ||
+ | < | ||
+ | Breakpoint 8 at 0x8048470 | ||
+ | < | ||
+ | Breakpoint 9 at 0x80484e0 | ||
+ | < | ||
+ | Breakpoint 10 at 0x80484e4 | ||
+ | < | ||
+ | |||
+ | |||
+ | gdb-peda$ commands | ||
+ | Type commands for breakpoint(s) 1-10, one per line. | ||
+ | End with a line saying just " | ||
+ | > | ||
+ | >end | ||
+ | |||
+ | |||
+ | gdb-peda$ run | ||
+ | Starting program: / | ||
+ | warning: the debug information found in "/ | ||
+ | |||
+ | warning: Could not load shared library symbols for linux-gate.so.1. | ||
+ | Do you need "set solib-search-path" | ||
+ | |||
+ | Breakpoint 4, 0x08048330 in __libc_start_main@plt () | ||
+ | |||
+ | Breakpoint 8, 0x08048470 in __libc_csu_init () | ||
+ | |||
+ | Breakpoint 6, 0x08048370 in __x86.get_pc_thunk.bx () | ||
+ | |||
+ | Breakpoint 1, 0x080482d4 in _init () | ||
+ | |||
+ | Breakpoint 6, 0x08048370 in __x86.get_pc_thunk.bx () | ||
+ | |||
+ | Breakpoint 7, 0x0804843f in main () | ||
+ | |||
+ | Breakpoint 2, 0x08048310 in read@plt () | ||
+ | |||
+ | AAAaAA0AABAAbAA1AACAAcAA2AADAAdAA3AAEAAeAA4AAFAAfAA5AAGAAgAA6AAHAAhAA7 | ||
+ | |||
+ | Program received signal SIGSEGV, Segmentation fault. | ||
+ | 0x41414541 in ?? () | ||
+ | </ | ||
+ | |||
+ | |||
+ | === ROP payload debugging === | ||
+ | When you know what the offending function is, disassemble it and break on " | ||
+ | <code bash> | ||
+ | gdb-peda$ pdis main | ||
+ | Dump of assembler code for function main: | ||
+ | | ||
+ | | ||
+ | | ||
+ | | ||
+ | | ||
+ | | ||
+ | | ||
+ | | ||
+ | | ||
+ | | ||
+ | | ||
+ | | ||
+ | End of assembler dump. | ||
+ | gdb-peda$ b *0x08048467 | ||
+ | Breakpoint 1 at 0x8048467 | ||
+ | |||
+ | |||
+ | AAAaAA0AABAAbAA1AACAAcAA2AADAAdAA3AAEAAeAA4AAFAAfA | ||
+ | [----------------------------------registers-----------------------------------] | ||
+ | EAX: 0x0 | ||
+ | EBX: 0xf7f97e54 --> 0x1a6d5c | ||
+ | ECX: 0xffffcd49 (" | ||
+ | EDX: 0x64 (' | ||
+ | ESI: 0x0 | ||
+ | EDI: 0x0 | ||
+ | EBP: 0x41334141 (' | ||
+ | ESP: 0xffffcd6c (" | ||
+ | EIP: 0x8048467 (< | ||
+ | EFLAGS: 0x203 (CARRY parity adjust zero sign trap INTERRUPT direction overflow) | ||
+ | [-------------------------------------code-------------------------------------] | ||
+ | | ||
+ | | ||
+ | | ||
+ | | ||
+ | | ||
+ | | ||
+ | | ||
+ | => 0x8048467 < | ||
+ | | ||
+ | | ||
+ | | ||
+ | | ||
+ | | ||
+ | | ||
+ | | ||
+ | | ||
+ | [------------------------------------stack-------------------------------------] | ||
+ | 0000| 0xffffcd6c --> 0xf7e333e0 (< | ||
+ | 0004| 0xffffcd70 --> 0x80484cf (< | ||
+ | 0008| 0xffffcd74 --> 0xf7f56be6 ("/ | ||
+ | 0012| 0xffffcd78 --> 0xf7e25c00 (< | ||
+ | |||
+ | |||
+ | gdb-peda$ patto AEAAeAA4AAFAAfA | ||
+ | AEAAeAA4AAFAAfA found at offset: 35 | ||
+ | </ | ||
+ | |||
+ | Then you can break on all called functions or step as needed to see if the payload is doing what you want it to. | ||
+ | |||
+ | |||
+ | === checksec in peda === | ||
+ | <code bash> | ||
+ | gdb-peda$ checksec | ||
+ | CANARY | ||
+ | FORTIFY | ||
+ | NX : ENABLED | ||
+ | PIE : disabled | ||
+ | RELRO : Partial | ||
+ | </ | ||
+ | |||
+ | |||
+ | === gadget finding in peda === | ||
+ | Apart from **objdump** which only finds aligned instructions, | ||
+ | <code bash> | ||
+ | gdb-peda$ start | ||
+ | .... | ||
+ | gdb-peda$ dumprop | ||
+ | Warning: this can be very slow, do not run for large memory range | ||
+ | Writing ROP gadgets to file: a-rop.txt ... | ||
+ | 0x8048467: ret | ||
+ | 0x804835d: iret | ||
+ | 0x804838f: repz ret | ||
+ | 0x80483be: ret 0xeac1 | ||
+ | 0x80483a9: leave; ret | ||
+ | 0x80485b4: inc ecx; ret | ||
+ | 0x80484cf: pop ebp; ret | ||
+ | 0x80482f5: pop ebx; ret | ||
+ | 0x80484df: nop; repz ret | ||
+ | 0x80483a8: ror cl,1; ret | ||
+ | 0x804838e: add dh,bl; ret | ||
+ | 0x80483e5: ror cl,cl; ret | ||
+ | 0x8048465: add cl,cl; ret | ||
+ | 0x804840b: leave; repz ret | ||
+ | 0x8048371: sbb al,0x24; ret | ||
+ | 0x80485b3: adc al,0x41; ret | ||
+ | 0x8048370: mov ebx,[esp]; ret | ||
+ | 0x80484de: nop; nop; repz ret | ||
+ | 0x80483a7: call eax; leave; ret | ||
+ | 0x80483e4: call edx; leave; ret | ||
+ | 0x804840a: add ecx,ecx; repz ret | ||
+ | 0x80484ce: pop edi; pop ebp; ret | ||
+ | </ | ||
+ | |||
+ | Something finer is: | ||
+ | <code bash> | ||
+ | gdb-peda$ asmsearch "pop ? ; ret" | ||
+ | 0x080482f5 : (5bc3) pop | ||
+ | 0x080484cf : (5dc3) pop | ||
+ | 0x080484f6 : (5bc3) pop | ||
+ | |||
+ | gdb-peda$ asmsearch "pop ? ; pop ? ; ret" | ||
+ | 0x080484ce : (5f5dc3) pop | ||
+ | |||
+ | gdb-peda$ asmsearch "call ?" | ||
+ | 0x080483a7 : (ffd0) call | ||
+ | 0x080483e4 : (ffd2) call | ||
+ | 0x0804842f : (ffd0) call | ||
+ | |||
+ | </ | ||
+ | |||
+ | === Anti-anti-debugging and others === | ||
+ | There can be various annoyances in binaries: **ptrace** calls for anti-debugging, | ||
+ | These can all be deactivated using **unptrace** (for ptrace) and **deactive** in peda. | ||
+ | |||
- | If you are more comfortable with another tool feel free to use it. Keep in mind that sometimes addresses change when you are using '' | + | ===== Challenges ===== |
- | === 00. Tutorial - Bypass NX Stack with return-to-libc | + | ==== 00. Tutorial - Bypass NX Stack with return-to-libc |
Go to the '' | Go to the '' | ||
Line 303: | Line 622: | ||
In the previous sessions we used stack overflow vulnerabilities to inject new code into a running process (on its stack) and redirect execution to it. This attack is easily defeated by making the stack, together with any other memory page that can be modified, non-executable. This is achieved by setting the NX bit in the page table. | In the previous sessions we used stack overflow vulnerabilities to inject new code into a running process (on its stack) and redirect execution to it. This attack is easily defeated by making the stack, together with any other memory page that can be modified, non-executable. This is achieved by setting the NX bit in the page table. | ||
- | We will try to bypass this protection for the '' | + | We will try to bypass this protection for the '' |
< | < | ||
- | echo 0 | sudo tee /proc/sys/ | + | setarch $(uname -m) -R /bin/bash |
</ | </ | ||
- | |||
- | We will enable it back later. | ||
Let's take a look at the program headers and confirm that the stack is no longer executable. We only have read and write (RW) permissions for the stack area. | Let's take a look at the program headers and confirm that the stack is no longer executable. We only have read and write (RW) permissions for the stack area. | ||
Line 315: | Line 632: | ||
<note important> | <note important> | ||
The auth binary requires the '' | The auth binary requires the '' | ||
+ | |||
+ | You can find '' | ||
</ | </ | ||
Line 383: | Line 702: | ||
</ | </ | ||
- | == Challenges | ||
- | === 01. Challenge - ret-to-libc | + | ==== 01. Challenge - ret-to-libc |
Looks good! Let's get serious and do something useful with this. | Looks good! Let's get serious and do something useful with this. | ||
Line 408: | Line 726: | ||
</ | </ | ||
- | === 02. Challenge - no-ret-control | + | ==== 02. Challenge - no-ret-control |
Go to the '' | Go to the '' | ||
Line 416: | Line 734: | ||
Alter the execution of '' | Alter the execution of '' | ||
- | === 03. Challenge - ret-to-plt | + | ==== 03. Challenge - ret-to-plt |
Go to the '' | Go to the '' | ||
- | - '' | + | '' |
- | - **(bonus)** The process should SEGFAULT after printing the second (constant) number. Make it exit cleanly (the exit code does not matter, just no SIGSEGV). | + | |
- | Example | + | Your task is to build an exploit that makes the application always print the **same second random number**. That is the first printed random number is whatever, but the second printed random number will always be the same, for all runs. In the sample |
<code text> | <code text> | ||
Line 470: | Line 787: | ||
sys.stdout.write(payload) | sys.stdout.write(payload) | ||
- | |||
</ | </ | ||
- | === 04. Challenge - colors | + | **Bonus**: The process should SEGFAULT after printing the second (constant) number. Make it exit cleanly (the exit code does not matter, just no SIGSEGV). |
- | Go to the '' | ||
- | <note important> | + | ==== 04. Challenge |
- | //Hint//: If you are going to use an inline python command, stdin will get closed and the new shell will have nothing to read. Use cat to concatenate your attack string with stdin like this: '' | + | |
- | </ | + | |
- | ===== 04.a. | + | This task requires you to construct a payload using gadgets and calling the functions inside such that it will print |
+ | < | ||
+ | Hello! | ||
+ | stage A!stage B! | ||
+ | </ | ||
+ | Make it also print the messages in reverse order: | ||
+ | < | ||
+ | Hello! | ||
+ | stage B!stage A! | ||
+ | </ | ||
+ | ==== Bonus Challenge - Echo service | ||
+ | This task is a network service that can be exploited. Run it locally and try to exploit it. You'll find that if you call system("/ | ||
- | Exploit | + | So you will need to do the equivalent of the following in a ROP chain: |
+ | <code c> | ||
+ | dup2(sockfd, 1); | ||
+ | dup2(sockfd, | ||
+ | system("/ | ||
+ | </ | ||
- | ===== 04.b. | ||
- | ASLR still disabled. Call '' | + | Exploit it first with ASLR disabled and then enabled. |
- | + | ||
- | ===== 04.c. | + | |
- | + | ||
- | Again, | + | |
- | + | ||
- | + | ||
- | === 05. Challenge - bruteforce | + | |
- | + | ||
- | Continue working in the '' | + | |
- | + | ||
- | Try the previous exploit with ASLR enabled. You can rerun the binary multiple times. | + | |
- | + | ||
- | <note important> | + | |
- | Figure out how addresses look like using '' | + | |
- | </ | + | |
- | + | ||
- | < | + | |
- | The ASLR entropy on 32-bit systems if pretty low, which makes this bruteforce attack feasible. On 64-bit platforms you will need an information leak, and a 2-stage exploit. We are going to discuss this in a future session. | + | |
- | </ | + | |
- | + | ||
- | === 06. Challenge - mprotect | + | |
- | + | ||
- | Continue working in the '' | + | |
- | + | ||
- | Using any of the 2 binaries, try to call '' | + | |
- | + | ||
- | <note important> | + | |
- | To make your life easier, you can disable ASLR. The purpose of this task is to bypass NX, and not ASLR. | + | |
- | </ | + | |
- | + | ||
- | <note important> | + | |
- | //Hint//: The '' | + | |
- | </ | + |