-
Notifications
You must be signed in to change notification settings - Fork 857
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add mmap command that executes the mmap syscall in the inferior #1952
Changes from 5 commits
22ccfc8
d16ea4b
3094725
fe67e57
9135adc
fa384dd
fbf257c
5724678
4ff8a92
340a68a
6c28047
0dc5afb
2339fc5
5b1a937
52b4266
897e888
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,239 @@ | ||
from __future__ import annotations | ||
|
||
import argparse | ||
|
||
import pwndbg.chain | ||
import pwndbg.color.message as message | ||
import pwndbg.commands | ||
import pwndbg.enhance | ||
import pwndbg.gdblib.file | ||
import pwndbg.gdblib.shellcode | ||
import pwndbg.lib.memory | ||
import pwndbg.wrappers.checksec | ||
import pwndbg.wrappers.readelf | ||
from pwndbg.commands import CommandCategory | ||
|
||
parser = argparse.ArgumentParser( | ||
formatter_class=argparse.RawTextHelpFormatter, | ||
description=""" | ||
Calls the mmap syscall and prints its resulting address. | ||
|
||
Note that the mmap syscall may fail for various reasons | ||
(see `man mmap`) and, in case of failure, its return value | ||
will not be a valid pointer. | ||
|
||
PROT values: NONE (0), READ (1), WRITE (2), EXEC (4) | ||
MAP values: SHARED (1), PRIVATE (2), SHARED_VALIDATE (3), FIXED (0x10), | ||
ANONYMOUS (0x20) | ||
|
||
Flags and protection values can be either a string containing the names of the | ||
flags or permissions or a single number corresponding to the bitwise OR of the | ||
protection and flag numbers. | ||
|
||
Examples: | ||
mmap 0x0 4096 PROT_READ|PROT_WRITE|PROT_EXEC MAP_PRIVATE|MAP_ANONYMOUS -1 0 | ||
- Maps a new private+anonymous page with RWX permissions at a location | ||
decided by the kernel. | ||
|
||
mmap 0x0 4096 PROT_READ MAP_PRIVATE 10 0 | ||
- Maps 4096 bytes of the file pointed to by file descriptor number 10 with | ||
read permission at a location decided by the kernel. | ||
|
||
mmap 0xdeadbeef 0x1000 | ||
- Maps a new private+anonymous page with RWX permissions at a page boundary | ||
near 0xdeadbeef. | ||
""", | ||
) | ||
parser.add_argument( | ||
"addr", help="Address hint to be given to mmap.", type=pwndbg.commands.sloppy_gdb_parse | ||
) | ||
parser.add_argument( | ||
"length", | ||
help="Length of the mapping, in bytes. Needs to be greater than zero.", | ||
type=int, | ||
) | ||
parser.add_argument( | ||
"prot", | ||
help='Prot enum or int as in mmap(2). Eg. "PROT_READ|PROT_EXEC" or 7 (for RWX).', | ||
type=str, | ||
nargs="?", | ||
default="7", | ||
) | ||
parser.add_argument( | ||
"flags", | ||
help='Flags enum or int as in mmap(2). Eg. "MAP_PRIVATE|MAP_ANONYMOUS" or 0x22.', | ||
type=str, | ||
nargs="?", | ||
default="0x22", | ||
) | ||
parser.add_argument( | ||
"fd", | ||
help="File descriptor of the file to be mapped, or -1 if using MAP_ANONYMOUS.", | ||
type=int, | ||
nargs="?", | ||
default=-1, | ||
) | ||
parser.add_argument( | ||
"offset", | ||
help="Offset from the start of the file, in bytes, if using file based mapping.", | ||
type=int, | ||
nargs="?", | ||
default=0, | ||
) | ||
parser.add_argument( | ||
"--quiet", "-q", help="Disable address validity warnings and hints", action="store_true" | ||
) | ||
parser.add_argument( | ||
"--force", "-f", help="Force potentially unsafe actions to happen", action="store_true" | ||
) | ||
|
||
|
||
prot_dict = { | ||
"PROT_NONE": 0x0, | ||
"PROT_READ": 0x1, | ||
"PROT_WRITE": 0x2, | ||
"PROT_EXEC": 0x4, | ||
} | ||
|
||
flag_dict = { | ||
"MAP_SHARED": 0x1, | ||
"MAP_PRIVATE": 0x2, | ||
"MAP_SHARED_VALIDATE": 0x3, | ||
"MAP_FIXED": 0x10, | ||
"MAP_ANONYMOUS": 0x20, | ||
} | ||
|
||
|
||
def prot_str_to_val(protstr): | ||
"""Heuristic to convert PROT_EXEC|PROT_WRITE to integer value.""" | ||
prot_int = 0 | ||
for k, v in prot_dict.items(): | ||
if k in protstr: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fwiw There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, which isn't optimal, but I figured that if it's good enough for There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. its probably okay for now |
||
prot_int |= v | ||
return prot_int | ||
|
||
|
||
def flag_str_to_val(flagstr): | ||
"""Heuristic to convert MAP_SHARED|MAP_FIXED to integer value.""" | ||
flag_int = 0 | ||
for k, v in flag_dict.items(): | ||
if k in flagstr: | ||
flag_int |= v | ||
return flag_int | ||
|
||
|
||
def parse_str_or_int(val, parser): | ||
""" | ||
Try parsing a string with one of the parsers above or by converting it to | ||
an int, or passes the value through if it is already an integer. | ||
""" | ||
if type(val) == str: | ||
disconnect3d marked this conversation as resolved.
Show resolved
Hide resolved
|
||
candidate = parser(val) | ||
if candidate != 0: | ||
return candidate | ||
return int(val, 0) | ||
elif type(val) == int: | ||
disconnect3d marked this conversation as resolved.
Show resolved
Hide resolved
|
||
return val | ||
else: | ||
# Getting here is a bug, we shouldn't be seeing other types at all. | ||
raise TypeError("invalid type for value: {type(val)}") | ||
disconnect3d marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
|
||
@pwndbg.commands.ArgparsedCommand(parser, category=CommandCategory.MEMORY) | ||
@pwndbg.commands.OnlyWhenRunning | ||
def mmap(addr, length, prot=7, flags=0x22, fd=-1, offset=0, quiet=False, force=False) -> None: | ||
try: | ||
prot_int = parse_str_or_int(prot, prot_str_to_val) | ||
except ValueError as e: | ||
print(message.error(f'Invalid protection value "{prot}": {e}')) | ||
disconnect3d marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
try: | ||
flag_int = parse_str_or_int(flags, flag_str_to_val) | ||
except ValueError as e: | ||
print(message.error(f'Invalid flags value "{flags}": {e}')) | ||
disconnect3d marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
aligned_addr = int(pwndbg.lib.memory.page_align(addr)) | ||
if flag_int & flag_dict["MAP_FIXED"] != 0: | ||
# When using MAP_FIXED, it's only safe to call mmap(2) when the address | ||
# overlaps no other maps. We want to make sure that, unless the user | ||
# _really_ knows what they're doing, this call will be safe. | ||
# | ||
# Additionally, it's nice to highlight cases where the call is likely | ||
# to fail because the address is not properly aligned. | ||
addr = int(addr) | ||
if addr != aligned_addr and not quiet: | ||
print( | ||
message.warn( | ||
f"""\ | ||
Address {addr:#x} is not properly aligned. Calling mmap with MAP_FIXED and an | ||
unaligned address is likely to fail. Consider using the address {aligned_addr:#x} | ||
instead.\ | ||
""" | ||
) | ||
) | ||
|
||
page = pwndbg.lib.memory.Page(addr, int(length), 0, 0) | ||
collisions = [] | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In theory, there should never be more than a single collision (in practice if we have broken vmmap info there will be). We can safely assume a single collision here. Its fine if we print a single one if there are more There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't think I follow. If any range is permissible for our mmap, it could, in the worst case, technically collide with all of the mappings, no? Nothing's really stopping you from trying to call this with There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Oh... you are right. For some reason I was thinking that we map a single address instead of a range. Forgive my stupidness :) |
||
vm = pwndbg.gdblib.vmmap.get() | ||
for i in range(len(vm)): | ||
cand = vm[i] | ||
if cand.end > page.start and cand.start < page.end: | ||
collisions.append(cand) | ||
if cand.start >= page.end: | ||
# No more collisions are possible. | ||
break | ||
|
||
if len(collisions) > 0: | ||
m = message.error if not force else message.warn | ||
|
||
if not force or not quiet: | ||
print( | ||
m( | ||
f"""\ | ||
Trying to mmap with MAP_FIXED for an address range that collides with {len(collisions)} | ||
existing range{'s' if len(collisions) > 1 else ''}:\ | ||
""" | ||
) | ||
) | ||
for c in collisions: | ||
print(m(f" {c}")) | ||
print( | ||
m( | ||
""" | ||
This operation is destructive and will delete all of the listed mappings.\ | ||
""" | ||
) | ||
) | ||
if not force: | ||
print( | ||
m( | ||
"Run this command again with `--force` if you still \ | ||
wish to proceed." | ||
) | ||
) | ||
return | ||
|
||
elif int(addr) != aligned_addr and not quiet: | ||
# Highlight to the user that the address they've specified is likely to | ||
# be changed by the kernel. | ||
print( | ||
message.warn( | ||
f"""\ | ||
Address {addr:#x} is not properly aligned. It is likely to be changed to an | ||
aligned address by the kernel automatically. If this is not desired, consider | ||
using the address {aligned_addr:#x} instead.\ | ||
""" | ||
) | ||
) | ||
|
||
pointer = pwndbg.gdblib.shellcode.exec_syscall( | ||
"SYS_mmap", | ||
int(pwndbg.lib.memory.page_align(addr)), | ||
int(length), | ||
prot_int, | ||
flag_int, | ||
int(fd), | ||
int(offset), | ||
) | ||
|
||
print(f"mmap returned {pointer:#x}") | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we maybe also support
RWX
orrwx
?