mirror of https://github.com/pwndbg/pwndbg
fix: make mprotect command truly multi-arch (#1162)
* fix: make mprotect command truly multi-arch Added register saving based on reg_sets defined for each processor architecture, additionally shellcraft is used to generate the arch-specific shellcode. Unfortunately this command is not currently tested on platforms other than x86_64. * Update pwndbg/commands/mprotect.py Co-authored-by: Disconnect3d <dominik.b.czarnota@gmail.com> * mprotect: Add parsing, alignment to the addr argument This change makes sure that the addr argument is parsed as an gdb expression (so you can use registers for example) and aligns it to the nearest page boundary. * mprotect: Clean up register saving, print the result Cleaned up saving of registers and added printing of the results, as per disconnect's sugesstions. * Simplify the test for mprotect Simplify the code and remove the useless binary * Update tests/test_mprotect.py Co-authored-by: Disconnect3d <dominik.b.czarnota@gmail.com>
This commit is contained in:
parent
c50ba4612e
commit
1c06c52b47
|
@ -11,9 +11,24 @@ import pwndbg.gdblib.file
|
|||
import pwndbg.lib.which
|
||||
import pwndbg.wrappers.checksec
|
||||
import pwndbg.wrappers.readelf
|
||||
from pwndbg.lib.regs import reg_sets
|
||||
|
||||
parser = argparse.ArgumentParser(description="Calls mprotect. x86_64 only.")
|
||||
parser.add_argument("addr", help="Page-aligned address to all mprotect on.", type=int)
|
||||
parser = argparse.ArgumentParser(
|
||||
description="""
|
||||
Calls the mprotect syscall and prints its result value
|
||||
|
||||
Note that the mprotect syscall may fail for various reasons
|
||||
(see `man mprotect`) and a non-zero error return value
|
||||
can be decoded with the `errno <value>` command.
|
||||
|
||||
Examples:
|
||||
mprotect $rsp PROT_READ|PROT_WRITE|PROT_EXEC
|
||||
mprotect some_symbol PROT_NONE
|
||||
"""
|
||||
)
|
||||
parser.add_argument(
|
||||
"addr", help="Page-aligned address to all mprotect on.", type=pwndbg.commands.sloppy_gdb_parse
|
||||
)
|
||||
parser.add_argument(
|
||||
"length",
|
||||
help="Count of bytes to call mprotect on. Needs " "to be multiple of page size.",
|
||||
|
@ -44,33 +59,42 @@ def prot_str_to_val(protstr):
|
|||
|
||||
@pwndbg.commands.ArgparsedCommand(parser)
|
||||
@pwndbg.commands.OnlyWhenRunning
|
||||
@pwndbg.commands.OnlyAmd64
|
||||
def mprotect(addr, length, prot):
|
||||
"""Only x86_64."""
|
||||
saved_rax = pwndbg.gdblib.regs.rax
|
||||
saved_rbx = pwndbg.gdblib.regs.rbx
|
||||
saved_rcx = pwndbg.gdblib.regs.rcx
|
||||
saved_rdx = pwndbg.gdblib.regs.rdx
|
||||
saved_rip = pwndbg.gdblib.regs.rip
|
||||
|
||||
prot_int = prot_str_to_val(prot)
|
||||
|
||||
shellcode_asm = pwnlib.shellcraft.syscall("SYS_mprotect", int(addr), int(length), int(prot_int))
|
||||
# generate a shellcode that executes the mprotect syscall
|
||||
shellcode_asm = pwnlib.shellcraft.syscall(
|
||||
"SYS_mprotect", int(pwndbg.lib.memory.page_align(addr)), int(length), int(prot_int)
|
||||
)
|
||||
shellcode = asm.asm(shellcode_asm)
|
||||
|
||||
saved_instruction_bytes = pwndbg.gdblib.memory.read(pwndbg.gdblib.regs.rip, len(shellcode))
|
||||
# obtain the registers that need to be saved for the current platform
|
||||
# we save the registers that are used for arguments, return value and the program counter
|
||||
current_regs = reg_sets[pwndbg.gdblib.arch.current]
|
||||
regs_to_save = current_regs.args + (current_regs.retval, current_regs.pc)
|
||||
|
||||
pwndbg.gdblib.memory.write(pwndbg.gdblib.regs.rip, shellcode)
|
||||
# save the registers
|
||||
saved_registers = {reg: pwndbg.gdblib.regs[reg] for reg in regs_to_save}
|
||||
|
||||
# save the memory which will be overwritten by the shellcode
|
||||
saved_instruction_bytes = pwndbg.gdblib.memory.read(
|
||||
saved_registers[current_regs.pc], len(shellcode)
|
||||
)
|
||||
pwndbg.gdblib.memory.write(saved_registers[current_regs.pc], shellcode)
|
||||
|
||||
# execute syscall
|
||||
gdb.execute("nextsyscall")
|
||||
gdb.execute("stepi")
|
||||
|
||||
# restore registers and memory
|
||||
pwndbg.gdblib.memory.write(saved_rip, saved_instruction_bytes)
|
||||
# get the return value
|
||||
ret = pwndbg.gdblib.regs[current_regs.retval]
|
||||
|
||||
pwndbg.gdblib.regs.rax = saved_rax
|
||||
pwndbg.gdblib.regs.rbx = saved_rbx
|
||||
pwndbg.gdblib.regs.rcx = saved_rcx
|
||||
pwndbg.gdblib.regs.rdx = saved_rdx
|
||||
pwndbg.gdblib.regs.rip = saved_rip
|
||||
print("mprotect returned %d (%s)" % (ret, current_regs.retval))
|
||||
|
||||
# restore registers and memory
|
||||
pwndbg.gdblib.memory.write(saved_registers[current_regs.pc], saved_instruction_bytes)
|
||||
|
||||
# restore the registers
|
||||
for register, value in saved_registers.items():
|
||||
setattr(pwndbg.gdblib.regs, register, value)
|
||||
|
|
|
@ -1,17 +0,0 @@
|
|||
#include <stdlib.h>
|
||||
#include <stdint.h>
|
||||
|
||||
//
|
||||
// pwnlib.asm.asm(pwnlib.shellcraft.amd64.linux.echo("mprotect_ok"), arch='amd64').hex()
|
||||
uint8_t func[] = {
|
||||
0x36, 0x38, 0x35, 0x65, 0x36, 0x65, 0x36, 0x61, 0x30, 0x31, 0x38, 0x31, 0x33, 0x34, 0x32, 0x34, 0x30, 0x31, 0x30, 0x31, 0x30, 0x31, 0x30, 0x31, 0x34, 0x38, 0x62, 0x38, 0x36, 0x64, 0x37, 0x30, 0x37, 0x32, 0x36, 0x66, 0x37, 0x34, 0x36, 0x35, 0x36, 0x33, 0x37, 0x34, 0x35, 0x30, 0x36, 0x61, 0x30, 0x31, 0x35, 0x38, 0x36, 0x61, 0x30, 0x31, 0x35, 0x66, 0x36, 0x61, 0x30, 0x62, 0x35, 0x61, 0x34, 0x38, 0x38, 0x39, 0x65, 0x36, 0x30, 0x66, 0x30, 0x35, 0x33, 0x31, 0x66, 0x66, 0x36, 0x61, 0x33, 0x63, 0x35, 0x38, 0x30, 0x66, 0x30, 0x35
|
||||
};
|
||||
|
||||
|
||||
int main() {
|
||||
|
||||
void (*f)() = (void (*)())func;
|
||||
printf("ptr = %p", f);
|
||||
// f();
|
||||
return 1;
|
||||
}
|
|
@ -3,46 +3,28 @@ import gdb
|
|||
import pwndbg
|
||||
import tests
|
||||
|
||||
MPROTECT_BINARY = tests.binaries.get("mprotect.out")
|
||||
SMALL_BINARY = tests.binaries.get("crash_simple.out.hardcoded")
|
||||
|
||||
|
||||
def test_mprotect(start_binary):
|
||||
def test_mprotect_executes_properly(start_binary):
|
||||
"""
|
||||
Tests mprotect command
|
||||
It will mark some memory as executable, then this binary will print "mprotect_ok"
|
||||
Tests the mprotect command
|
||||
"""
|
||||
start_binary(MPROTECT_BINARY)
|
||||
start_binary(SMALL_BINARY)
|
||||
|
||||
gdb.execute("starti")
|
||||
# get addr of func
|
||||
addr = int(gdb.parse_and_eval("&func"))
|
||||
addr_aligned = pwndbg.lib.memory.page_align(addr)
|
||||
pc = pwndbg.gdblib.regs.pc
|
||||
|
||||
# sizeof
|
||||
size = int(gdb.parse_and_eval("sizeof(func)"))
|
||||
size_aligned = pwndbg.lib.memory.page_align(size)
|
||||
# Check if we can use mprotect with address provided as value
|
||||
# and to set page permissions to RWX
|
||||
gdb.execute("mprotect %d 4096 PROT_EXEC|PROT_READ|PROT_WRITE" % pc)
|
||||
vm = pwndbg.gdblib.vmmap.find(pc)
|
||||
assert vm.read and vm.write and vm.execute
|
||||
|
||||
vmmaps_before = gdb.execute("vmmap -x", to_string=True).splitlines()
|
||||
|
||||
# mark memory as executable
|
||||
gdb.execute(
|
||||
"mprotect {} {} PROT_EXEC|PROT_READ|PROT_WRITE".format(
|
||||
hex(addr_aligned), pwndbg.lib.memory.PAGE_SIZE
|
||||
)
|
||||
)
|
||||
|
||||
vmmaps_after = gdb.execute("vmmap -x", to_string=True).splitlines()
|
||||
|
||||
# expect vmmaps_after to be one element longer than vmmaps_before
|
||||
assert len(vmmaps_after) == len(vmmaps_before) + 1
|
||||
|
||||
# get the changed vmmap entry
|
||||
vmmap_entry = [x for x in vmmaps_after if x not in vmmaps_before][0]
|
||||
|
||||
assert vmmap_entry.split()[2] == "rwxp"
|
||||
|
||||
# continue execution
|
||||
gdb.execute("continue")
|
||||
# Check if we can use mprotect with address provided as register
|
||||
# and to set page permissions to none
|
||||
gdb.execute("mprotect $pc 0x1000 PROT_NONE")
|
||||
vm = pwndbg.gdblib.vmmap.find(pc)
|
||||
assert not (vm.read and vm.write and vm.execute)
|
||||
|
||||
|
||||
def test_cannot_run_mprotect_when_not_running(start_binary):
|
||||
|
|
Loading…
Reference in New Issue