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:
alufers 2022-10-18 02:12:10 +02:00 committed by GitHub
parent c50ba4612e
commit 1c06c52b47
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 58 additions and 69 deletions

View File

@ -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)

View File

@ -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;
}

View File

@ -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):