diff --git a/pwndbg/commands/mprotect.py b/pwndbg/commands/mprotect.py index 2d72d65f7..29624489d 100644 --- a/pwndbg/commands/mprotect.py +++ b/pwndbg/commands/mprotect.py @@ -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 ` 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) + + # save the registers + saved_registers = {reg: pwndbg.gdblib.regs[reg] for reg in regs_to_save} - pwndbg.gdblib.memory.write(pwndbg.gdblib.regs.rip, shellcode) + # 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") + # get the return value + ret = pwndbg.gdblib.regs[current_regs.retval] + + print("mprotect returned %d (%s)" % (ret, current_regs.retval)) + # restore registers and memory - pwndbg.gdblib.memory.write(saved_rip, saved_instruction_bytes) + pwndbg.gdblib.memory.write(saved_registers[current_regs.pc], saved_instruction_bytes) - 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 + # restore the registers + for register, value in saved_registers.items(): + setattr(pwndbg.gdblib.regs, register, value) diff --git a/tests/binaries/mprotect.c b/tests/binaries/mprotect.c deleted file mode 100644 index ba204f1f0..000000000 --- a/tests/binaries/mprotect.c +++ /dev/null @@ -1,17 +0,0 @@ -#include -#include - -// -// 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; -} diff --git a/tests/test_mprotect.py b/tests/test_mprotect.py index 7378b8106..f091aec1b 100644 --- a/tests/test_mprotect.py +++ b/tests/test_mprotect.py @@ -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):