|
|
|
|
@ -10,6 +10,7 @@ from __future__ import annotations
|
|
|
|
|
|
|
|
|
|
import contextlib
|
|
|
|
|
from asyncio import CancelledError
|
|
|
|
|
from typing import Iterator
|
|
|
|
|
|
|
|
|
|
import pwnlib.asm
|
|
|
|
|
import pwnlib.shellcraft
|
|
|
|
|
@ -22,9 +23,6 @@ import pwndbg.aglib.vmmap
|
|
|
|
|
from pwndbg.dbg import BreakpointLocation
|
|
|
|
|
from pwndbg.dbg import ExecutionController
|
|
|
|
|
|
|
|
|
|
if pwndbg.dbg.is_gdblib_available():
|
|
|
|
|
import pwndbg.gdblib.prompt
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _get_syscall_return_value():
|
|
|
|
|
"""
|
|
|
|
|
@ -46,7 +44,6 @@ async def exec_syscall(
|
|
|
|
|
arg3=None,
|
|
|
|
|
arg4=None,
|
|
|
|
|
arg5=None,
|
|
|
|
|
disable_breakpoints=False,
|
|
|
|
|
):
|
|
|
|
|
"""
|
|
|
|
|
Tries executing the given syscall in the context of the inferior.
|
|
|
|
|
@ -60,41 +57,12 @@ async def exec_syscall(
|
|
|
|
|
async with exec_shellcode(
|
|
|
|
|
ec,
|
|
|
|
|
syscall_bin,
|
|
|
|
|
restore_context=True,
|
|
|
|
|
disable_breakpoints=disable_breakpoints,
|
|
|
|
|
):
|
|
|
|
|
return _get_syscall_return_value()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@contextlib.asynccontextmanager
|
|
|
|
|
async def exec_shellcode(
|
|
|
|
|
ec: ExecutionController, blob, restore_context=True, disable_breakpoints=False
|
|
|
|
|
):
|
|
|
|
|
"""
|
|
|
|
|
Tries executing the given blob of machine code in the current context of the
|
|
|
|
|
inferior, optionally restoring the values of the registers as they were
|
|
|
|
|
before the shellcode ran, as a means to allow for execution of the inferior
|
|
|
|
|
to continue uninterrupted. The value of the program counter is always
|
|
|
|
|
restored.
|
|
|
|
|
|
|
|
|
|
Additionally, the caller may specify an object to be called before the
|
|
|
|
|
context is restored, so that information stored in the registers after the
|
|
|
|
|
shellcode finishes can be retrieved. The return value of that call will be
|
|
|
|
|
returned by this function.
|
|
|
|
|
|
|
|
|
|
# Safety
|
|
|
|
|
Seeing as this function injects code directly into the inferior and runs it,
|
|
|
|
|
the caller must be careful to inject code that will (1) terminate and (2)
|
|
|
|
|
not cause the inferior to misbehave. Otherwise, it is fairly easy to crash
|
|
|
|
|
or currupt the memory in the inferior.
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
register_set = pwndbg.lib.regs.reg_sets[pwndbg.aglib.arch.name]
|
|
|
|
|
preserve_set = register_set.gpr + register_set.args + (register_set.pc, register_set.stack)
|
|
|
|
|
|
|
|
|
|
registers = {reg: pwndbg.aglib.regs[reg] for reg in preserve_set}
|
|
|
|
|
starting_address = registers[register_set.pc]
|
|
|
|
|
|
|
|
|
|
@contextlib.contextmanager
|
|
|
|
|
def _ctx_code(starting_address: int, blob) -> Iterator[None]:
|
|
|
|
|
# Make sure the blob fits in the rest of the space we have in this page.
|
|
|
|
|
#
|
|
|
|
|
# NOTE: Technically, we could actually use anything from the whole page to
|
|
|
|
|
@ -116,47 +84,76 @@ async def exec_shellcode(
|
|
|
|
|
existing_code = pwndbg.aglib.memory.read(starting_address, len(blob))
|
|
|
|
|
pwndbg.aglib.memory.write(starting_address, blob)
|
|
|
|
|
|
|
|
|
|
# The continue we use here will trigger an event that would get the context
|
|
|
|
|
# prompt to show, regardless of the circumstances. We don't want that, so
|
|
|
|
|
# we preserve the state of the context skip.
|
|
|
|
|
if pwndbg.dbg.is_gdblib_available():
|
|
|
|
|
would_skip_context = pwndbg.gdblib.prompt.context_shown
|
|
|
|
|
try:
|
|
|
|
|
yield
|
|
|
|
|
finally:
|
|
|
|
|
pwndbg.aglib.memory.write(starting_address, existing_code)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@contextlib.contextmanager
|
|
|
|
|
def _ctx_registers() -> Iterator[int]:
|
|
|
|
|
register_set = pwndbg.lib.regs.reg_sets[pwndbg.aglib.arch.name]
|
|
|
|
|
preserve_set = register_set.gpr + register_set.args + (register_set.pc, register_set.stack)
|
|
|
|
|
|
|
|
|
|
uncached_regs = pwndbg.dbg.selected_frame().regs()
|
|
|
|
|
registers = {reg: int(uncached_regs.by_name(reg)) for reg in preserve_set}
|
|
|
|
|
starting_address = registers[register_set.pc]
|
|
|
|
|
|
|
|
|
|
# Execute.
|
|
|
|
|
target_address = starting_address + len(blob)
|
|
|
|
|
try:
|
|
|
|
|
yield starting_address
|
|
|
|
|
finally:
|
|
|
|
|
# Restore the code and the program counter and, if requested, the rest of
|
|
|
|
|
# the registers.
|
|
|
|
|
setattr(pwndbg.aglib.regs, register_set.pc, starting_address)
|
|
|
|
|
for reg, val in registers.items():
|
|
|
|
|
setattr(pwndbg.aglib.regs, reg, val)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async def _execute_until_addr(ec: ExecutionController, target_address: int) -> None:
|
|
|
|
|
with pwndbg.dbg.selected_inferior().break_at(
|
|
|
|
|
BreakpointLocation(target_address), internal=True
|
|
|
|
|
) as bp:
|
|
|
|
|
while True:
|
|
|
|
|
try:
|
|
|
|
|
await ec.cont(bp)
|
|
|
|
|
await ec.cont_selected_thread(bp)
|
|
|
|
|
break
|
|
|
|
|
except CancelledError:
|
|
|
|
|
if disable_breakpoints:
|
|
|
|
|
# We probably hit another breakpoint, but in this mode we're
|
|
|
|
|
# supposed to ignore any breakpoints that aren't the one we put
|
|
|
|
|
# at the end of the range, so just retry.
|
|
|
|
|
continue
|
|
|
|
|
# We probably hit another breakpoint, but in this mode we're
|
|
|
|
|
# supposed to ignore any breakpoints that aren't the one we put
|
|
|
|
|
# at the end of the range, so just retry.
|
|
|
|
|
continue
|
|
|
|
|
|
|
|
|
|
# We hit an external break, and we haven't been told to ignore it.
|
|
|
|
|
raise
|
|
|
|
|
assert pwndbg.dbg.selected_frame().pc() == target_address, "Target address is incorrect"
|
|
|
|
|
|
|
|
|
|
# Restore the state of the context skip.
|
|
|
|
|
if pwndbg.dbg.is_gdblib_available():
|
|
|
|
|
pwndbg.gdblib.prompt.context_shown = would_skip_context
|
|
|
|
|
|
|
|
|
|
# Make sure we're in the right place.
|
|
|
|
|
assert pwndbg.aglib.regs.pc == target_address
|
|
|
|
|
@contextlib.asynccontextmanager
|
|
|
|
|
async def exec_shellcode(ec: ExecutionController, blob):
|
|
|
|
|
"""
|
|
|
|
|
Tries executing the given blob of machine code in the current context of the
|
|
|
|
|
inferior, optionally restoring the values of the registers as they were
|
|
|
|
|
before the shellcode ran, as a means to allow for execution of the inferior
|
|
|
|
|
to continue uninterrupted. The value of the program counter is always
|
|
|
|
|
restored.
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
# Give the caller a chance to collect information from the environment
|
|
|
|
|
# before any of the context gets restored.
|
|
|
|
|
yield
|
|
|
|
|
finally:
|
|
|
|
|
# Restore the code and the program counter and, if requested, the rest of
|
|
|
|
|
# the registers.
|
|
|
|
|
pwndbg.aglib.memory.write(starting_address, existing_code)
|
|
|
|
|
setattr(pwndbg.aglib.regs, register_set.pc, starting_address)
|
|
|
|
|
if restore_context:
|
|
|
|
|
for reg, val in registers.items():
|
|
|
|
|
setattr(pwndbg.aglib.regs, reg, val)
|
|
|
|
|
Additionally, the caller may specify an object to be called before the
|
|
|
|
|
context is restored, so that information stored in the registers after the
|
|
|
|
|
shellcode finishes can be retrieved. The return value of that call will be
|
|
|
|
|
returned by this function.
|
|
|
|
|
|
|
|
|
|
# Safety
|
|
|
|
|
Seeing as this function injects code directly into the inferior and runs it,
|
|
|
|
|
the caller must be careful to inject code that will (1) terminate and (2)
|
|
|
|
|
not cause the inferior to misbehave. Otherwise, it is fairly easy to crash
|
|
|
|
|
or currupt the memory in the inferior.
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
with _ctx_registers() as starting_address:
|
|
|
|
|
target_address = starting_address + len(blob)
|
|
|
|
|
with _ctx_code(starting_address, blob):
|
|
|
|
|
try:
|
|
|
|
|
with pwndbg.dbg.ctx_suspend_events(pwndbg.dbg_mod.EventType.SUSPEND_ALL):
|
|
|
|
|
await _execute_until_addr(ec, target_address)
|
|
|
|
|
|
|
|
|
|
yield
|
|
|
|
|
finally:
|
|
|
|
|
pass
|
|
|
|
|
|