Created
November 1, 2024 21:53
-
-
Save flatz/cbb84539aeee1ade1983ee2eea499dbc to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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