This shows you the differences between two versions of the page.
cns:labs:lab-03 [2019/10/14 11:27] dennis.plosceanu [Off-by-one Overflow] |
cns:labs:lab-03 [2022/10/24 19:05] (current) mihai.dumitru2201 [2. Overflow a Pointer] |
||
---|---|---|---|
Line 1: | Line 1: | ||
====== Lab 03 - The Stack. Buffer Management ====== | ====== Lab 03 - The Stack. Buffer Management ====== | ||
- | |||
- | ===== Resources ===== | ||
- | |||
- | *[[https://eli.thegreenplace.net/2011/09/06/stack-frame-layout-on-x86-64|x64 stack frame tutorial]] | ||
- | *[[https://eli.thegreenplace.net/2011/02/04/where-the-top-of-the-stack-is-on-x86/|x32 stack frame tutorial]] | ||
- | * ''%%python -c 'print "A" * 42' | ./l33tb1n%%'' | ||
- | * objdump | ||
- | * GDB | ||
- | * strace | ||
- | * ltrace | ||
- | |||
- | ===== Lab Support Files ===== | ||
- | |||
- | We will use this [[http://elf.cs.pub.ro/oss/res/labs/lab-03.tar.gz|lab archive]] throughout the lab. | ||
- | |||
- | Please download the lab archive an then unpack it using the commands below: | ||
- | <code bash> | ||
- | student@mjolnir:~$ wget http://elf.cs.pub.ro/oss/res/labs/lab-03.tar.gz | ||
- | student@mjolnir:~$ tar xzf lab-03.tar.gz | ||
- | </code> | ||
- | |||
- | After unpacking we will get the ''lab-03/'' folder that we will use for the lab: | ||
- | <code bash> | ||
- | student@mjolnir:~$ cd lab-03/ | ||
- | student@mjolnir:~/lab-03$ ls | ||
- | asm-function-call extra off-by-one overflow-address overflow-ptr | ||
- | </code> | ||
===== Intro ===== | ===== Intro ===== | ||
Line 34: | Line 7: | ||
The subregion of the stack holding data pertaining to a function call is named a **stack frame**. The compiler will generate code to resize the stack right at the beginning of the function (prologue), and will restore the previous stack size when a function returns (epilogue). Usually, the function caller is also placing arguments on the stack. | The subregion of the stack holding data pertaining to a function call is named a **stack frame**. The compiler will generate code to resize the stack right at the beginning of the function (prologue), and will restore the previous stack size when a function returns (epilogue). Usually, the function caller is also placing arguments on the stack. | ||
- | General functions of a stack frame: | + | The stack is generally used to: |
- store function arguments (for 32 architecture) | - store function arguments (for 32 architecture) | ||
- store local variables | - store local variables | ||
- store metadata: previous frame pointer, and return address | - store metadata: previous frame pointer, and return address | ||
- | Operations: | ||
- | - resize, by adjusting the **stack pointer** register | ||
- | - push (store a value at the top of the stack) | ||
- | - pop (retrieve a value from the top) | ||
- | - peek (same as pop, but the value can still be retrieved by a future instruction) | ||
Depending on architecture, the stack may grow downwards or upwards (i.e., from high memory addresses, from low memory addresses). The most common is the former, which means that decreasing the stack pointer will allocate a new memory region on the stack, while increasing the stack pointer will free it. | Depending on architecture, the stack may grow downwards or upwards (i.e., from high memory addresses, from low memory addresses). The most common is the former, which means that decreasing the stack pointer will allocate a new memory region on the stack, while increasing the stack pointer will free it. | ||
Line 112: | Line 80: | ||
===== Tasks ===== | ===== Tasks ===== | ||
- | ==== Assembly Function Calls ==== | + | All content necessary for the CNS laboratory tasks can be found in [[cns:resources:repo|the CNS public repository]]. |
- | Enter the ''asm-function-call/'' subfolder in the lab archive folder. Check the source code so far. Compile it and run it:<code> | + | ==== 1. Assembly Function Calls ==== |
- | student@host:~/lab-03/skel/asm-function-call$ ls | + | |
+ | Enter the ''01-asm-function-call/'' subfolder in the lab archive folder. Check the source code so far. Compile it and run it:<code> | ||
+ | student@host:~/cns/labs/03-stack-buffer-management/01-asm-function-call $ ls | ||
Makefile function_call.asm | Makefile function_call.asm | ||
- | student@host:~/lab-03/skel/asm-function-call$ make | + | student@host:~~/cns/labs/03-stack-buffer-management/01-asm-function-call $ make |
nasm -f elf64 -o function_call.o function_call.asm | nasm -f elf64 -o function_call.o function_call.asm | ||
gcc function_call.o -o function_call | gcc function_call.o -o function_call | ||
- | student@host:~/lab-03/skel/asm-function-call$ ls | + | student@host:~/cns/labs/03-stack-buffer-management/01-asm-function-call $ ls |
Makefile function_call function_call.asm function_call.o | Makefile function_call function_call.asm function_call.o | ||
- | student@host:~/lab-03/skel/asm-function-call$ ./function_call | + | student@host:~/cns/labs/03-stack-buffer-management/01-asm-function-call $ ./function_call |
Sum(100) is 5050 | Sum(100) is 5050 | ||
</code> | </code> | ||
- | The program calls the external function ''printf'' by using arguments on the stack. And prints out the sum of the first ''100'' integers. | + | The program calls the external function ''printf'' by using arguments in registers. And prints out the sum of the first ''100'' integers. |
- | <note important> | ||
- | Pay attention to the way the ''printf'' function arguments are transmitted. First the last function argument then the next one and so forth. | ||
- | </note> | ||
- | |||
- | === Task === | ||
Update the current assembly implementation such that the user inputs the number to which the sum will be computed. Use ''scanf'' call for this. Print a message such as //Please insert your number: //, before using ''scanf''. | Update the current assembly implementation such that the user inputs the number to which the sum will be computed. Use ''scanf'' call for this. Print a message such as //Please insert your number: //, before using ''scanf''. | ||
Line 139: | Line 104: | ||
You'll have to define a string for the new ''printf'' call (for printing the intro message) and one for the ''scanf'' call, for reading the message. | You'll have to define a string for the new ''printf'' call (for printing the intro message) and one for the ''scanf'' call, for reading the message. | ||
- | Read the number into the global ''num'' variable. You need to pass the address of the variable on the stack. Simply use<code> | + | Read the number into the global ''num'' variable. You need to pass the address of the variable in ''rsi''. Simply use |
- | push num | + | <code> |
+ | mov rsi, num | ||
</code> | </code> | ||
</note> | </note> | ||
- | ==== Overflow a Pointer ==== | + | ==== 2. Overflow a Pointer ==== |
- | Enter the ''owerflow-ptr/'' subfolder in the lab archive folder. Check the source code so far. Compile it and run it:<code> | + | Enter the ''02-owerflow-ptr/'' subfolder in the lab archive folder. Check the source code so far. Compile it and run it:<code> |
- | student@host:~/lab-03/overflow-ptr$ ls | + | student@host:~/cns/labs/03-stack-buffer-management/02-overflow-ptr $ ls |
Makefile overflow_ptr.c | Makefile overflow_ptr.c | ||
- | student@host:~/lab-03/overflow-ptr$ make | + | student@host:~/cns/labs/03-stack-buffer-management/02-overflow-ptr $ make |
gcc -Wall -Wextra -Wno-unused-function -g -O0 -fno-stack-protector -no-pie -c -o overflow_ptr.o overflow_ptr.c | gcc -Wall -Wextra -Wno-unused-function -g -O0 -fno-stack-protector -no-pie -c -o overflow_ptr.o overflow_ptr.c | ||
gcc overflow_ptr.o -o overflow_ptr -no-pie | gcc overflow_ptr.o -o overflow_ptr -no-pie | ||
- | student@host:~/lab-03/overflow-ptr$ ls | + | student@host:~/cns/labs/03-stack-buffer-management/02-overflow-ptr $ ls |
Makefile overflow_ptr overflow_ptr.c overflow_ptr.o | Makefile overflow_ptr overflow_ptr.c overflow_ptr.o | ||
- | student@host:~/lab-03/overflow-ptr$ ./overflow_ptr | + | student@host:~/cns/labs/03-stack-buffer-management/02-overflow-ptr $ ./overflow_ptr |
Provide buffer input: aaaa | Provide buffer input: aaaa | ||
Dumb number value is 0x12345678. | Dumb number value is 0x12345678. | ||
Line 165: | Line 131: | ||
In the code we see that we use the ''fgets'' function to read ''64'' bytes in a buffer that is only ''32'' bytes wide. We will overwrite certain values. The aim is to eventually overwrite the ''f_ptr'' function pointer located above the ''buffer'' array. | In the code we see that we use the ''fgets'' function to read ''64'' bytes in a buffer that is only ''32'' bytes wide. We will overwrite certain values. The aim is to eventually overwrite the ''f_ptr'' function pointer located above the ''buffer'' array. | ||
- | === Tutorial === | + | === a. Tutorial === |
Let's first automate the delivery of input to the buffer by using Python. Let's write 16 bytes of ''A'' characters:<code> | Let's first automate the delivery of input to the buffer by using Python. Let's write 16 bytes of ''A'' characters:<code> | ||
- | python -c 'print 16*"A"' | ./overflow_ptr | + | python -c 'import sys; sys.stdout.buffer.write(16*b"A")' | ./overflow_ptr |
Provide buffer input: Dumb number value is 0x12345678. | Provide buffer input: Dumb number value is 0x12345678. | ||
Buffer is AAAAAAAAAAAAAAAA | Buffer is AAAAAAAAAAAAAAAA | ||
Line 177: | Line 143: | ||
Let's now increase the number of bytes we are writing to 30, then 35, then 36:<code> | Let's now increase the number of bytes we are writing to 30, then 35, then 36:<code> | ||
- | student@host:~/lab-03/overflow-ptr$ python -c 'print 30*"A"' | ./overflow_ptr | + | student@host:~/cns/labs/03-stack-buffer-management/02-overflow-ptr $ python -c 'import sys; sys.stdout.buffer.write(30*b"A")' | ./overflow_ptr |
Provide buffer input: Dumb number value is 0x12345678. | Provide buffer input: Dumb number value is 0x12345678. | ||
Buffer is AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA | Buffer is AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA | ||
Knock, knock! Who's there? Recursion. Recursion who? Knock, knock! | Knock, knock! Who's there? Recursion. Recursion who? Knock, knock! | ||
- | student@host:~/lab-03/overflow-ptr$ python -c 'print 35*"A"' | ./overflow_ptr | + | student@host:~/cns/labs/03-stack-buffer-management/02-overflow-ptr $ python -c 'import sys; sys.stdout.buffer.write(35*b"A")' | ./overflow_ptr |
Provide buffer input: Dumb number value is 0x12345600. | Provide buffer input: Dumb number value is 0x12345600. | ||
Buffer is AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA | Buffer is AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA | ||
Knock, knock! Who's there? Recursion. Recursion who? Knock, knock! | Knock, knock! Who's there? Recursion. Recursion who? Knock, knock! | ||
- | student@host:~/lab-03/overflow-ptr$ python -c 'print 36*"A"' | ./overflow_ptr | + | student@host:~/cns/labs/03-stack-buffer-management/02-overflow-ptr $ python -c 'import sys; sys.stdout.buffer.write(36*b"A")' | ./overflow_ptr |
Provide buffer input: Dumb number value is 0x1234000a. | Provide buffer input: Dumb number value is 0x1234000a. | ||
Buffer is AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA | Buffer is AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA | ||
Line 201: | Line 167: | ||
Now let's try to write more, let's go one byte after the ''dumb_number variable'' by writing 39 bytes: 36 bytes for the buffer, 3 bytes for the ''dumb_number'' variable, 1 byte for the newline and one byte for the NUL-byte going further than the ''dumb_number'' variable:<code> | Now let's try to write more, let's go one byte after the ''dumb_number variable'' by writing 39 bytes: 36 bytes for the buffer, 3 bytes for the ''dumb_number'' variable, 1 byte for the newline and one byte for the NUL-byte going further than the ''dumb_number'' variable:<code> | ||
- | student@host:~/lab-03/overflow-ptr$ python -c 'print 39*"A"' | ./overflow_ptr | + | student@host:~/cns/labs/03-stack-buffer-management/02-overflow-ptr $ python -c 'import sys; sys.stdout.buffer.write(39*b"A")' | ./overflow_ptr |
Provide buffer input: Dumb number value is 0x0a414141. | Provide buffer input: Dumb number value is 0x0a414141. | ||
Buffer is AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA | Buffer is AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA | ||
Line 207: | Line 173: | ||
Let's see what happens if we overwrite more data, we write ''41'' bytes:<code> | Let's see what happens if we overwrite more data, we write ''41'' bytes:<code> | ||
- | student@host:~/lab-03/overflow-ptr$ python -c 'print 41*"A"' | ./overflow_ptr | + | student@host:~/cns/labs/03-stack-buffer-management/02-overflow-ptr $ python -c 'import sys; sys.stdout.buffer.write(41*b"A")' | ./overflow_ptr |
Provide buffer input: Dumb number value is 0x41414141. | Provide buffer input: Dumb number value is 0x41414141. | ||
Buffer is AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA | Buffer is AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA | ||
Segmentation fault | Segmentation fault | ||
- | student@host:~/lab-03/overflow-ptr$ dmesg | + | student@host:~/cns/labs/03-stack-buffer-management/02-overflow-ptr $ dmesg |
[...] | [...] | ||
[11400.357883] overflow_ptr[13573]: segfault at a41 ip 0000000000000a41 sp 00007ffd94ac96f8 error 14 in overflow_ptr[400000+1000] | [11400.357883] overflow_ptr[13573]: segfault at a41 ip 0000000000000a41 sp 00007ffd94ac96f8 error 14 in overflow_ptr[400000+1000] | ||
Line 220: | Line 186: | ||
We now see that we've overwritten three bytes of the ''f_ptr'' function pointer that we jump to: ''0x00'' (the NUL byte), ''0x0a'' (the newline), and ''0x41'' (one of the 41 ''A'' characters we've written). | We now see that we've overwritten three bytes of the ''f_ptr'' function pointer that we jump to: ''0x00'' (the NUL byte), ''0x0a'' (the newline), and ''0x41'' (one of the 41 ''A'' characters we've written). | ||
- | Let's see how we could write some random hex data. Let's overwrite the ''dumb_number'' value with ''0x87654321'', that is the reverse of how it currently is. We will write ''32'' bytes of ''A'' and another eight properly arranged bytes to overwrite the ''dumb_number'' variable:<code> | + | Let's see how we could write some random hex data. Let's overwrite the ''dumb_number'' value with ''0x87654321'', that is the reverse of how it currently is. We will write ''32'' bytes of ''A'' and another eight properly arranged bytes to overwrite the ''dumb_number'' variable: |
- | $ python -c 'print 32*"A" + "\x00\x00\x00\x00\x21\x43\x65\x87"' | ./overflow_ptr | + | <code> |
+ | |||
+ | $ python -c 'import sys; sys.stdout.buffer.write(32*b"A" + b"\x21\x43\x65\x87\x00\x00\x00\x00")' | ./overflow_ptr | ||
Provide buffer input: Dumb number value is 0x87654321. | Provide buffer input: Dumb number value is 0x87654321. | ||
Line 232: | Line 200: | ||
Note the format for generating hex bytes in Python. And note that, since we use little endian, the order of the bytes is the other way we see them when printed. The print message tells us that the ''dumb_number'' variable is using the expected value. | Note the format for generating hex bytes in Python. And note that, since we use little endian, the order of the bytes is the other way we see them when printed. The print message tells us that the ''dumb_number'' variable is using the expected value. | ||
- | === Task === | + | === b. Task === |
Let's get ready for some real action. Find out the address of the ''hidden_function'' and make the program call it by overwriting the ''f_ptr'' function pointer with that address. | Let's get ready for some real action. Find out the address of the ''hidden_function'' and make the program call it by overwriting the ''f_ptr'' function pointer with that address. | ||
Line 245: | Line 213: | ||
</note> | </note> | ||
- | === Bonus 1=== | + | === c. Bonus 1=== |
Make it such that when calling the ''hidden_function'' you maintain the value of ''0x12345678'' for the ''dumb_number'' variable. That's the value that gets printed. | Make it such that when calling the ''hidden_function'' you maintain the value of ''0x12345678'' for the ''dumb_number'' variable. That's the value that gets printed. | ||
- | === Bonus 2=== | + | === d. Bonus 2=== |
Make it such that you would call both the ''hidden_function'' and the ''visible_function''. | Make it such that you would call both the ''hidden_function'' and the ''visible_function''. | ||
Line 261: | Line 229: | ||
</code> | </code> | ||
</note> | </note> | ||
- | ==== Off-by-one Overflow ==== | + | ==== 3. Off-by-one Overflow ==== |
- | Enter the ''off-by-one/'' subfolder in the lab archive folder. Check the source code so far. Compile it and run it. | + | Enter the ''03-off-by-one/'' subfolder in the lab archive folder. Check the source code so far. Compile it and run it. |
Analyze the binary and source code file and spot the bug. | Analyze the binary and source code file and spot the bug. | ||
- | === Task === | + | === a. Task === |
Trigger a ''SIGSEGV'' signal when ''opfunc()'' is called by changing the function pointer's value in GDB. | Trigger a ''SIGSEGV'' signal when ''opfunc()'' is called by changing the function pointer's value in GDB. | ||
Line 274: | Line 242: | ||
Then, change attack input string and force a call to ''bad_func()''. | Then, change attack input string and force a call to ''bad_func()''. | ||
- | === Bonus === | + | === b. Bonus === |
Same as before, but make it display %%"Very bad."%% | Same as before, but make it display %%"Very bad."%% | ||
- | ==== Overflow an Address [3p] ==== | + | ==== 4. Overflow an Address ==== |
- | Enter the ''overflow-address/'' subfolder in the lab archive folder. Check the source code so far. Compile it and run it. | + | Enter the ''04-overflow-address/'' subfolder in the lab archive folder. Check the source code so far. Compile it and run it. |
Analyze the binary and source code file and spot the bug. Trigger a ''SIGSEGV'' with a long enough input. | Analyze the binary and source code file and spot the bug. Trigger a ''SIGSEGV'' with a long enough input. | ||
- | === Task === | + | === a. Task === |
Make the program call ''bad_func()''. | Make the program call ''bad_func()''. | ||
- | === Bonus [1p] === | + | === b. Bonus 1 === |
Also call ''really_bad_func()'' right after exiting ''bad_func''. | Also call ''really_bad_func()'' right after exiting ''bad_func''. | ||
- | === Bonus [1p] === | + | === c. Bonus 2 === |
You are now calling the 2 bad functions sequentially, but ''SIGSEGV'' is triggered right after this. Avoid the ''SIGSEGV'' and exit gracefully. | You are now calling the 2 bad functions sequentially, but ''SIGSEGV'' is triggered right after this. Avoid the ''SIGSEGV'' and exit gracefully. | ||
+ | ===== Resources ===== | ||
+ | *[[https://eli.thegreenplace.net/2011/09/06/stack-frame-layout-on-x86-64|x64 stack frame tutorial]] | ||
+ | *[[https://eli.thegreenplace.net/2011/02/04/where-the-top-of-the-stack-is-on-x86/|x86 stack frame tutorial]] | ||
+ | *[[http://security.cs.pub.ro/hexcellents/wiki/|Hexcellents - A collection of binary exploitation resources]] | ||
+ | * ''%%python -c 'import sys; sys.stdout.buffer.write(b"A" * 42 + b"\x44\x33\x22\x11")' | ./l33tb1n%%'' | ||
+ | * objdump | ||
+ | * GDB | ||
+ | * strace | ||
+ | * ltrace |