Fix #932,#788: fix command parsing

When we moved to argparse command parsing we introduced `gdb_sloppy_parse` which wasn't perfect: e.g. for `gdb.parse_and_eval("__libc_start_main")` would return a `gdb.Value()` whose `.type.name` was `long long`.

As a result when code that used `gdb_sloppy_parse` then casted the result to `int(gdb_value)` it crashed because for some reason GDB errored.

This commit fixes the issues related to it by adding `AddressExpr` and `HexOrAddressExpr` functions.

It also adds tests for some of the windbg compatibility commands and fixes some nifty details here and there.
pull/979/head
Disconnect3d 4 years ago
parent d861d6e2fc
commit 1d70e14418

@ -321,6 +321,11 @@ class ArgparsedCommand:
_ArgparsedCommand(self.parser, function, alias)
return _ArgparsedCommand(self.parser, function)
# We use a 64-bit max value literal here instead of pwndbg.arch.current
# as realistically its ok to pull off the biggest possible type here
# We cache its GDB value type which is 'unsigned long long'
_mask = 0xffffffffFFFFFFFF
_mask_val_type = gdb.Value(_mask).type
def sloppy_gdb_parse(s):
"""
@ -333,6 +338,34 @@ def sloppy_gdb_parse(s):
:return: Whatever gdb.parse_and_eval returns or string.
"""
try:
return gdb.parse_and_eval(s)
val = gdb.parse_and_eval(s)
# We can't just return int(val) because GDB may return:
# "Python Exception <class 'gdb.error'> Cannot convert value to long."
# e.g. for:
# pwndbg> pi int(gdb.parse_and_eval('__libc_start_main'))
#
# Here, the _mask_val.type should be `unsigned long long`
return int(val.cast(_mask_val_type))
except (TypeError, gdb.error):
return s
def AddressExpr(s):
"""
Parses an address expression. Returns an int.
"""
val = sloppy_gdb_parse(s)
if not isinstance(val, int):
raise argparse.ArgumentError('Incorrect address (or GDB expression): %s' % s)
return val
def HexOrAddressExpr(s):
"""
Parses string as hexidecimal int or an address expression. Returns an int.
(e.g. '1234' will return 0x1234)
"""
try:
return int(s, 16)
except ValueError:
return AddressExpr(s)

@ -41,7 +41,7 @@ def address_or_module_name(s):
raise argparse.ArgumentTypeError('Unknown hexdump argument type.')
parser = argparse.ArgumentParser(description='Hexdumps data at the specified address or module name (or at $sp)')
parser.add_argument('address_or_module', type=address_or_module_name, nargs='?', default='$sp',
parser.add_argument('address', type=address_or_module_name, nargs='?', default='$sp',
help='Address or module name to dump')
parser.add_argument('count', nargs='?', default=pwndbg.config.hexdump_bytes,
help='Number of bytes to dump')
@ -49,8 +49,7 @@ parser.add_argument('count', nargs='?', default=pwndbg.config.hexdump_bytes,
@pwndbg.commands.ArgparsedCommand(parser)
@pwndbg.commands.OnlyWhenRunning
def hexdump(address_or_module=None, count=pwndbg.config.hexdump_bytes):
address = address_or_module
def hexdump(address=None, count=pwndbg.config.hexdump_bytes):
if hexdump.repeat:
address = hexdump.last_address
hexdump.offset += 1

@ -29,8 +29,8 @@ def get_type(size):
}[size]
parser = argparse.ArgumentParser(description="Starting at the specified address, dump N bytes.")
parser.add_argument("address", type=int, help="The address to dump from.")
parser.add_argument("count", type=int, default=64, nargs="?", help="The number of bytes to dump.")
parser.add_argument("address", type=pwndbg.commands.HexOrAddressExpr, help="The address to dump from.")
parser.add_argument("count", type=pwndbg.commands.AddressExpr, default=64, nargs="?", help="The number of bytes to dump.")
@pwndbg.commands.ArgparsedCommand(parser)
@pwndbg.commands.OnlyWhenRunning
def db(address, count=64):
@ -42,8 +42,8 @@ def db(address, count=64):
parser = argparse.ArgumentParser(description="Starting at the specified address, dump N words.")
parser.add_argument("address", type=int, help="The address to dump from.")
parser.add_argument("count", type=int, default=32, nargs="?", help="The number of words to dump.")
parser.add_argument("address", type=pwndbg.commands.HexOrAddressExpr, help="The address to dump from.")
parser.add_argument("count", type=pwndbg.commands.AddressExpr, default=32, nargs="?", help="The number of words to dump.")
@pwndbg.commands.ArgparsedCommand(parser)
@pwndbg.commands.OnlyWhenRunning
def dw(address, count=32):
@ -55,8 +55,8 @@ def dw(address, count=32):
parser = argparse.ArgumentParser(description="Starting at the specified address, dump N dwords.")
parser.add_argument("address", type=int, help="The address to dump from.")
parser.add_argument("count", type=int, default=16, nargs="?", help="The number of dwords to dump.")
parser.add_argument("address", type=pwndbg.commands.HexOrAddressExpr, help="The address to dump from.")
parser.add_argument("count", type=pwndbg.commands.AddressExpr, default=16, nargs="?", help="The number of dwords to dump.")
@pwndbg.commands.ArgparsedCommand(parser)
@pwndbg.commands.OnlyWhenRunning
def dd(address, count=16):
@ -67,8 +67,8 @@ def dd(address, count=16):
return dX(4, address, count, repeat=dd.repeat)
parser = argparse.ArgumentParser(description="Starting at the specified address, dump N qwords.")
parser.add_argument("address", type=int, help="The address to dump from.")
parser.add_argument("count", type=int, default=8, nargs="?", help="The number of qwords to dump.")
parser.add_argument("address", type=pwndbg.commands.HexOrAddressExpr, help="The address to dump from.")
parser.add_argument("count", type=pwndbg.commands.AddressExpr, default=8, nargs="?", help="The number of qwords to dump.")
@pwndbg.commands.ArgparsedCommand(parser)
@pwndbg.commands.OnlyWhenRunning
def dq(address, count=8):
@ -79,12 +79,12 @@ def dq(address, count=8):
return dX(8, address, count, repeat=dq.repeat)
parser = argparse.ArgumentParser(description="Starting at the specified address, hexdump.")
parser.add_argument("address", type=int, help="The address to dump from.")
parser.add_argument("count", type=int, default=8, nargs="?", help="The number of bytes to hexdump.")
parser.add_argument("address", type=pwndbg.commands.HexOrAddressExpr, help="The address to dump from.")
parser.add_argument("count", type=pwndbg.commands.AddressExpr, default=8, nargs="?", help="The number of bytes to hexdump.")
@pwndbg.commands.ArgparsedCommand(parser)
@pwndbg.commands.OnlyWhenRunning
def dc(address, count=8):
return pwndbg.commands.hexdump.hexdump(address=address, count=count, repeat=dc.repeat)
return pwndbg.commands.hexdump.hexdump(address=address, count=count)
def dX(size, address, count, to_string=False, repeat=False):
"""
@ -109,6 +109,10 @@ def dX(size, address, count, to_string=False, repeat=False):
except gdb.MemoryError:
break
if not values:
print('Could not access the provided address')
return
n_rows = int(math.ceil(count * size / float(16)))
row_sz = int(16 / size)
rows = [values[i*row_sz:(i+1)*row_sz] for i in range(n_rows)]
@ -140,7 +144,7 @@ def enhex(size, value):
parser = argparse.ArgumentParser(description="Write hex bytes at the specified address.")
parser.add_argument("address", type=int, help="The address to write to.")
parser.add_argument("address", type=pwndbg.commands.HexOrAddressExpr, help="The address to write to.")
parser.add_argument("data", type=str, nargs="*", help="The bytes to write.")
@pwndbg.commands.ArgparsedCommand(parser)
@pwndbg.commands.OnlyWhenRunning
@ -152,7 +156,7 @@ def eb(address, data):
parser = argparse.ArgumentParser(description="Write hex words at the specified address.")
parser.add_argument("address", type=int, help="The address to write to.")
parser.add_argument("address", type=pwndbg.commands.HexOrAddressExpr, help="The address to write to.")
parser.add_argument("data", type=str, nargs="*", help="The words to write.")
@pwndbg.commands.ArgparsedCommand(parser)
@pwndbg.commands.OnlyWhenRunning
@ -164,7 +168,7 @@ def ew(address, data):
parser = argparse.ArgumentParser(description="Write hex dwords at the specified address.")
parser.add_argument("address", type=int, help="The address to write to.")
parser.add_argument("address", type=pwndbg.commands.HexOrAddressExpr, help="The address to write to.")
parser.add_argument("data", type=str, nargs="*", help="The dwords to write.")
@pwndbg.commands.ArgparsedCommand(parser)
@pwndbg.commands.OnlyWhenRunning
@ -176,7 +180,7 @@ def ed(address, data):
parser = argparse.ArgumentParser(description="Write hex qwords at the specified address.")
parser.add_argument("address", type=int, help="The address to write to.")
parser.add_argument("address", type=pwndbg.commands.HexOrAddressExpr, help="The address to write to.")
parser.add_argument("data", type=str, nargs="*", help="The qwords to write.")
@pwndbg.commands.ArgparsedCommand(parser)
@pwndbg.commands.OnlyWhenRunning
@ -188,7 +192,7 @@ def eq(address, data):
parser = argparse.ArgumentParser(description="Write a string at the specified address.")
parser.add_argument("address", type=int, help="The address to write to.")
parser.add_argument("address", type=pwndbg.commands.HexOrAddressExpr, help="The address to write to.")
parser.add_argument("data", type=str, help="The string to write.")
@pwndbg.commands.ArgparsedCommand(parser)
@pwndbg.commands.OnlyWhenRunning
@ -199,7 +203,7 @@ def ez(address, data):
return eX(1, address, data, hex=False)
parser = argparse.ArgumentParser(description="Write a string at the specified address.") #TODO Is eza just ez? If so just alias. I had trouble finding windbg documentation defining ez
parser.add_argument("address", type=int, help="The address to write to.")
parser.add_argument("address", type=pwndbg.commands.HexOrAddressExpr, help="The address to write to.")
parser.add_argument("data", type=str, help="The string to write.")
@pwndbg.commands.ArgparsedCommand(parser)
@pwndbg.commands.OnlyWhenRunning
@ -213,31 +217,47 @@ def eX(size, address, data, hex=True):
"""
This relies on windbg's default hex encoding being enforced
"""
address = pwndbg.commands.fix(address)
if address is None:
if not data:
print('Cannot write empty data into memory.')
return
for i, bytestr in enumerate(data):
if hex:
bytestr = str(bytestr)
if hex:
# Early validation if all data is hex
for string in data:
if string.startswith('0x'):
string = string[2:]
if any(ch not in '0123456789abcdefABCDEF' for ch in string):
print('Incorrect data format: it must all be a hex value (0x1234 or 1234, both interpreted as 0x1234)')
return
if bytestr.startswith('0x'):
bytestr = bytestr[2:]
writes = 0
for i, string in enumerate(data):
if hex:
if string.startswith('0x'):
string = string[2:]
bytestr = bytestr.rjust(size*2, '0')
string = string.rjust(size*2, '0')
data = codecs.decode(bytestr, 'hex')
data = codecs.decode(string, 'hex')
else:
data = bytestr
data = string
if pwndbg.arch.endian == 'little':
data = data[::-1]
pwndbg.memory.write(address + (i * size), data)
try:
pwndbg.memory.write(address + (i * size), data)
writes += 1
except gdb.error:
print('Cannot access memory at address %#x' % address)
if writes > 0:
print('(Made %d writes to memory; skipping further writes)' % writes)
return
parser = argparse.ArgumentParser(description="Dump pointers and symbols at the specified address.")
parser.add_argument("addr", type=int, help="The address to dump from.")
parser.add_argument("addr", type=pwndbg.commands.HexOrAddressExpr, help="The address to dump from.")
@pwndbg.commands.ArgparsedCommand(parser,aliases=['kd','dps','dqs']) #TODO are these really all the same? They had identical implementation...
@pwndbg.commands.OnlyWhenRunning
def dds(addr):
@ -249,28 +269,35 @@ def dds(addr):
da_parser = argparse.ArgumentParser()
da_parser.description = 'Dump a string at the specified address.'
da_parser.add_argument('address', help='Address to dump')
da_parser.add_argument('address', type=pwndbg.commands.HexOrAddressExpr, help='Address to dump')
da_parser.add_argument('max', type=int, nargs='?', default=256,
help='Maximum string length')
@pwndbg.commands.ArgparsedCommand(da_parser)
@pwndbg.commands.OnlyWhenRunning
def da(address, max):
address = int(address)
address &= pwndbg.arch.ptrmask
print("%x" % address, repr(pwndbg.strings.get(address, max)))
ds_parser = argparse.ArgumentParser()
ds_parser.description = 'Dump a string at the specified address.'
ds_parser.add_argument('address', help='Address to dump')
ds_parser.add_argument('address', type=pwndbg.commands.HexOrAddressExpr, help='Address to dump')
ds_parser.add_argument('max', type=int, nargs='?', default=256,
help='Maximum string length')
@pwndbg.commands.ArgparsedCommand(ds_parser)
@pwndbg.commands.OnlyWhenRunning
def ds(address, max):
address = int(address)
address &= pwndbg.arch.ptrmask
print("%x" % address, repr(pwndbg.strings.get(address, max)))
# We do change the max length to the default if its too low
# because the truncated display is not that ideal/not the same as GDB's yet
# (ours: "truncated ...", GDBs: "truncated "...)
if max < 256:
print('Max str len of %d too low, changing to 256' % max)
max = 256
string = pwndbg.strings.get(address, max, maxread=4096)
if string:
print("%x %r" % (address, string))
else:
print("Data at address can't be dereferenced or is not a printable null-terminated string or is too short.")
print("Perhaps try: db <address> <count> or hexdump <address>")
@pwndbg.commands.ArgparsedCommand("List breakpoints.")
def bl():

@ -95,6 +95,7 @@ def write(addr, data):
if isinstance(data, str):
data = bytes(data, 'utf8')
# Throws an exception if can't access memory
gdb.selected_inferior().write_memory(addr, data)
@ -146,8 +147,10 @@ def string(addr, max=4096):
if peek(addr):
data = bytearray(read(addr, max, partial=True))
if b'\x00' in data:
return data.split(b'\x00')[0]
try:
return data[:data.index(b'\x00')]
except ValueError:
pass
return bytearray()

@ -32,17 +32,27 @@ def update_length():
else:
length = int(message)
def get(address, maxlen = None):
def get(address, maxlen=None, maxread=None):
"""
Returns a printable C-string from address.
Returns `None` if string contains non-printable chars
or if the `maxlen` length data does not end up with a null byte.
"""
if maxlen is None:
maxlen = length
if maxread is None:
maxread = length
try:
sz = pwndbg.memory.string(address, maxlen)
sz = sz.decode('latin-1', 'replace')
sz = pwndbg.memory.string(address, maxread)
except gdb.error: # should not happen, but sanity check?
return None
sz = sz.decode('latin-1', 'replace')
if not sz or not all(s in string.printable for s in sz):
return None
except Exception as e:
if not sz or not all(s in string.printable for s in sz):
return None
if len(sz) < maxlen or not maxlen:

@ -0,0 +1,32 @@
global _start
; This binary is there to test commands that access memory
; like dq, dd, dw, db, dc etc.
; So while the program does nothing, we create some data for testing those
_start:
nop
data:
dq 0x0
dq 0x1
dq 0x0000000100000002
dq 0x0001000200030004
dq 0x0102030405060708
data2:
dq 0x1122334455667788
dq 0x0123456789abcdef
dq 0x0
dq 0xffffffffffffffff
dq 0x0011223344556677
dq 0x8899aabbccddeeff
short_str:
db "some cstring here"
db 0
long_str:
db "long string: "
times 300 db 'A'
db 0

@ -11,12 +11,11 @@ _start_binary_called = False
@pytest.fixture
def start_binary():
"""
Returns function that launches given binary with 'start' command
Returns function that launches given binary with 'starti' command
"""
def _start_binary(path, *args):
gdb.execute('file ' + path)
gdb.execute('break _start')
gdb.execute('run ' + ' '.join(args))
gdb.execute('starti ' + ' '.join(args))
global _start_binary_called
# if _start_binary_called:

@ -0,0 +1,259 @@
import gdb
import pwndbg
import tests
MEMORY_BINARY = tests.binaries.get('memory.out')
data_addr = '0x400081'
def test_windbg_dX_commands(start_binary):
"""
Tests windbg compatibility commands that dump memory
like dq, dw, db, ds etc.
"""
start_binary(MEMORY_BINARY)
# Try to fail commands in different way
for cmd_prefix in ('dq', 'dd', 'dw', 'db'):
# With a non-existent symbol
cmd = cmd_prefix + ' nonexistentsymbol'
assert gdb.execute(cmd, to_string=True) == (
'usage: XX [-h] address [count]\n'
"XX: error: argument address: invalid HexOrAddressExpr value: 'nonexistentsymbol'\n"
).replace('XX', cmd_prefix)
# With an invalid/unmapped address
cmd = cmd_prefix + ' 0'
assert gdb.execute(cmd, to_string=True) == 'Could not access the provided address\n'
#################################################
#### dq command tests
#################################################
# Try `dq` with symbol, &symbol, 0x<address> and <address> without 0x prefix (treated as hex!)
dq1 = gdb.execute('dq data', to_string=True)
dq2 = gdb.execute('dq &data', to_string=True)
dq3 = gdb.execute('dq %s' % data_addr, to_string=True)
dq4 = gdb.execute('dq %s' % data_addr.replace('0x', ''), to_string=True)
assert dq1 == dq2 == dq3 == dq4 == (
'0000000000400081 0000000000000000 0000000000000001\n'
'0000000000400091 0000000100000002 0001000200030004\n'
'00000000004000a1 0102030405060708 1122334455667788\n'
'00000000004000b1 0123456789abcdef 0000000000000000\n'
)
# Try `dq` with different counts
dq_count1 = gdb.execute('dq data 2', to_string=True)
dq_count2 = gdb.execute('dq &data 2', to_string=True)
dq_count3 = gdb.execute('dq %s 2' % data_addr, to_string=True)
assert dq_count1 == dq_count2 == dq_count3 == '0000000000400081 0000000000000000 0000000000000001\n'
assert gdb.execute('dq data 1', to_string=True) == '0000000000400081 0000000000000000\n'
assert gdb.execute('dq data 3', to_string=True) == (
'0000000000400081 0000000000000000 0000000000000001\n'
'0000000000400091 0000000100000002\n'
)
# Try 'dq' with count equal to a register, but lets set it before ;)
# also note that we use `data2` here
assert gdb.execute('set $eax=4', to_string=True) == '' # assert as a sanity check
assert gdb.execute('dq data2 $eax', to_string=True) == (
'00000000004000a9 1122334455667788 0123456789abcdef\n'
'00000000004000b9 0000000000000000 ffffffffffffffff\n'
)
# See if we can repeat dq command (use count for shorter data)
assert gdb.execute('dq data2 2', to_string=True) == (
'00000000004000a9 1122334455667788 0123456789abcdef\n'
)
# TODO/FIXME: Can we test command repeating here? Neither passing `from_tty=True`
# or setting `pwndbg.commands.windbg.dq.repeat = True` works here
#assert gdb.execute('dq data2 2', to_string=True, from_tty=True) == (
# '00000000004000b9 0000000000000000 ffffffffffffffff\n'
#)
#################################################
#### dd command tests
#################################################
dd1 = gdb.execute('dd data', to_string=True)
dd2 = gdb.execute('dd &data', to_string=True)
dd3 = gdb.execute('dd %s' % data_addr, to_string=True)
dd4 = gdb.execute('dd %s' % data_addr.replace('0x', ''), to_string=True)
assert dd1 == dd2 == dd3 == dd4 == (
'0000000000400081 00000000 00000000 00000001 00000000\n'
'0000000000400091 00000002 00000001 00030004 00010002\n'
'00000000004000a1 05060708 01020304 55667788 11223344\n'
'00000000004000b1 89abcdef 01234567 00000000 00000000\n'
)
# count tests
assert gdb.execute('dd data 4', to_string=True) == (
'0000000000400081 00000000 00000000 00000001 00000000\n'
)
assert gdb.execute('dd data 3', to_string=True) == (
'0000000000400081 00000000 00000000 00000001\n'
)
#################################################
#### dw command tests
#################################################
dw1 = gdb.execute('dw data', to_string=True)
dw2 = gdb.execute('dw &data', to_string=True)
dw3 = gdb.execute('dw %s' % data_addr, to_string=True)
dw4 = gdb.execute('dw %s' % data_addr.replace('0x', ''), to_string=True)
assert dw1 == dw2 == dw3 == dw4 == (
'0000000000400081 0000 0000 0000 0000 0001 0000 0000 0000\n'
'0000000000400091 0002 0000 0001 0000 0004 0003 0002 0001\n'
'00000000004000a1 0708 0506 0304 0102 7788 5566 3344 1122\n'
'00000000004000b1 cdef 89ab 4567 0123 0000 0000 0000 0000\n'
)
# count tests
assert gdb.execute('dw data 8', to_string=True) == (
'0000000000400081 0000 0000 0000 0000 0001 0000 0000 0000\n'
)
assert gdb.execute('dw data 8/2', to_string=True) == (
'0000000000400081 0000 0000 0000 0000\n'
)
assert gdb.execute('dw data $eax', to_string=True) == (
'0000000000400081 0000 0000 0000 0000\n'
)
#################################################
#### db command tests
#################################################
db1 = gdb.execute('db data', to_string=True)
db2 = gdb.execute('db &data', to_string=True)
db3 = gdb.execute('db %s' % data_addr, to_string=True)
db4 = gdb.execute('db %s' % data_addr.replace('0x', ''), to_string=True)
assert db1 == db2 == db3 == db4 == (
'0000000000400081 00 00 00 00 00 00 00 00 01 00 00 00 00 00 00 00\n'
'0000000000400091 02 00 00 00 01 00 00 00 04 00 03 00 02 00 01 00\n'
'00000000004000a1 08 07 06 05 04 03 02 01 88 77 66 55 44 33 22 11\n'
'00000000004000b1 ef cd ab 89 67 45 23 01 00 00 00 00 00 00 00 00\n'
)
# count tests
assert gdb.execute('db data 31', to_string=True) == (
'0000000000400081 00 00 00 00 00 00 00 00 01 00 00 00 00 00 00 00\n'
'0000000000400091 02 00 00 00 01 00 00 00 04 00 03 00 02 00 01\n'
)
assert gdb.execute('db data $ax', to_string=True) == (
'0000000000400081 00 00 00 00\n'
)
#################################################
#### dc command tests
#################################################
dc1 = gdb.execute('dc data', to_string=True)
dc2 = gdb.execute('dc &data', to_string=True)
dc3 = gdb.execute('dc %s' % data_addr, to_string=True)
dc4 = gdb.execute('dc %s' % data_addr.replace('0x', ''), to_string=True)
assert dc1 == dc2 == dc3 == dc4 == (
'+0000 0x400081 00 00 00 00 00 00 00 00 '
'│....│....│ │ │\n'
)
assert gdb.execute('dc data 3', to_string=True) == (
'+0000 0x400081 00 00 00 │... '
'│ │ │ │\n'
)
#################################################
#### ds command tests
#################################################
ds1 = gdb.execute('ds short_str', to_string=True)
ds2 = gdb.execute('ds &short_str', to_string=True)
ds3 = gdb.execute('ds 0x4000d9', to_string=True)
ds4 = gdb.execute('ds 4000d9', to_string=True)
assert ds1 == ds2 == ds3 == ds4 == "4000d9 'some cstring here'\n"
# Check too low maxlen
assert gdb.execute('ds short_str 5', to_string=True) == (
'Max str len of 5 too low, changing to 256\n'
"4000d9 'some cstring here'\n"
)
# Check output for a string longer than (the default) maxlen of 256
assert gdb.execute('ds long_str', to_string=True) == (
"4000eb 'long string: "
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA...'\n"
)
# Check impossible address
assert gdb.execute('ds 0', to_string=True) == (
"Data at address can't be dereferenced or is not a printable null-terminated "
'string or is too short.\n'
'Perhaps try: db <address> <count> or hexdump <address>\n'
)
def test_windbg_eX_commands(start_binary):
"""
Tests windbg compatibility commands that write to memory
like eq, ed, ew, eb etc.
"""
start_binary(MEMORY_BINARY)
# Try to fail commands in different way
for cmd_prefix in ('eq', 'ed', 'ew', 'eb'):
# With a non-existent symbol
cmd = cmd_prefix + ' nonexistentsymbol'
assert gdb.execute(cmd, to_string=True) == (
'usage: XX [-h] address [data ...]\n'
"XX: error: argument address: invalid HexOrAddressExpr value: 'nonexistentsymbol'\n"
).replace('XX', cmd_prefix)
# With no data arguments provided
cmd = cmd_prefix + ' 0'
assert gdb.execute(cmd, to_string=True) == 'Cannot write empty data into memory.\n'
# With invalid/unmapped address 0
cmd = cmd_prefix + ' 0 1122'
assert gdb.execute(cmd, to_string=True) == (
'Cannot access memory at address 0x0\n'
)
# With invalid data which can't be parsed as hex
cmd = cmd_prefix + ' 0 x'
assert gdb.execute(cmd, to_string=True) == (
'Incorrect data format: it must all be a hex value (0x1234 or 1234, both '
'interpreted as 0x1234)\n'
)
#########################################
### Test eq write
#########################################
assert gdb.execute('eq $sp 0xcafebabe', to_string=True) == ''
assert '0x00000000cafebabe' in gdb.execute('x/xg $sp', to_string=True)
assert gdb.execute('eq $sp 0xbabe 0xcafe', to_string=True) == ''
assert '0x000000000000babe\t0x000000000000cafe' in gdb.execute('x/2xg $sp', to_string=True)
assert gdb.execute('eq $sp cafe000000000000 babe000000000000', to_string=True) == ''
assert '0xcafe000000000000\t0xbabe000000000000' in gdb.execute('x/2xg $sp', to_string=True)
# TODO/FIXME: implement tests for others (ed, ew, eb etc)
#########################################
### Test write & output on partial write
#########################################
# e.g. when we make a write to the last stack address
stack_ea = pwndbg.regs[pwndbg.regs.stack]
stack_page = pwndbg.vmmap.find(stack_ea)
# Last possible address on stack where we can perform an 8-byte write
stack_last_qword_ea = stack_page.end - 8
assert gdb.execute('eq %#x 0xCAFEBABEdeadbeef 0xABCD' % stack_last_qword_ea, to_string=True) == (
'Cannot access memory at address 0x7fffffffeff8\n'
'(Made 1 writes to memory; skipping further writes)\n'
)
# Check if the write actually occured
assert pwndbg.memory.read(stack_last_qword_ea, 8) == b'\xef\xbe\xad\xde\xbe\xba\xfe\xca'
Loading…
Cancel
Save