Fixes piebase and breakrva on remote debugging (#500)

Three things here:

1. This fixes `piebase` and `breakrva` commands - a bug with remote targets mentioned in https://github.com/pwndbg/pwndbg/issues/488#issuecomment-403213457.

2. It also adds a check if result address is still in the memory pages belonging to the given module. This works now as:

```
pwndbg> breakrva main
Offset 0x555555554601 rebased to module /home/dc/pwndbg_bug/a.out as 0xaaaaaaaa8601 is beyond module's memory pages:
    0x555555554000     0x555555555000 r-xp     1000 0      /home/dc/pwndbg_bug/a.out
    0x555555754000     0x555555755000 r--p     1000 0      /home/dc/pwndbg_bug/a.out
    0x555555755000     0x555555756000 rw-p     1000 1000   /home/dc/pwndbg_bug/a.out
```

3. It gives a better output for `piebase`:
```
pwndbg> piebase 1
Calculated VA from /home/dc/pwndbg_bug/a.out = 0x555555554001
```

---

To reproduce the fixed bug, launch any binary on a gdbserver:

```
gdbserver 127.0.0.1:4444 ./a.out
```

Then start a debugging session:

```
gdb -q -ex 'target remote 127.0.0.1:4444' ./a.out
```

and fire e.g. `breakrva 123`.

---

Below you can see the bug case and explanation why it occured:

```
pwndbg> breakrva 1
There are no mappings for specified address or module.
'breakrva': Break at RVA from PIE base.
Traceback (most recent call last):
  File "/home/dc/pwndbg/pwndbg/commands/__init__.py", line 109, in __call__
    return self.function(*args, **kwargs)
  File "/home/dc/pwndbg/pwndbg/commands/__init__.py", line 200, in _OnlyWhenRunning
    return function(*a, **kw)
  File "/home/dc/pwndbg/pwndbg/commands/pie.py", line 61, in breakrva
    spec = "*%#x" % (addr)
TypeError: %x format: an integer is required, not NoneType
```

So what is the issue here?

1. We have the same logic in both `piebase` and `breakrva` - if the user doesn't specify second argument - a module name - we retrieve it with `get_exe_name`:

```python
def breakrva(offset=None, module=None):
    offset = int(offset)
    if not module:
        module = get_exe_name()

    addr = translate_addr(offset, module)
    spec = "*%#x" % (addr)

    # [ ... - some more code, not important here ]
```

2. The `get_exe_name` returns just `pwndbg.auxv.get().get('AT_EXECFN', pwndbg.proc.exe)`. The difference is important here. On the case shown above the `pwndbg.auxv.get()['AT_EXECFN']` returns `./a.out` while `pwndbg.proc.exe` returns the full path: `/home/dc/pwndbg_bug/a.out`.
3. This `module` is then passed to `translate_addr` as can be seen on the code above.
4. The `translate_addr` tries to retrieve memory page (`Page` instance) which belongs to the module:
```python
def translate_addr(offset, module):
    mod_filter = lambda page: module in page.objfile
    pages = list(filter(mod_filter, pwndbg.vmmap.get()))

    if not pages:
        print('There are no mappings for specified address or module.')
        return

    # [ ... - some more code, not important here ]
```
5. The `translate_addr` returns `None` because the `page.objfile` for e.g. binary objfile returns its full path as can be seen below:
```
(Pdb) pwndbg.vmmap.get()[0].objfile
'/home/dc/pwndbg_bug/a.out'
```

6. Because we returned `None`, the `spec = "*%#x" % (addr)` string formatting for breakrva or `print(hex(addr))` for piebase fails.
pull/502/head
Disconnect3d 8 years ago committed by GitHub
parent b392843d9f
commit 33a5c2720f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -19,13 +19,23 @@ def translate_addr(offset, module):
pages = list(filter(mod_filter, pwndbg.vmmap.get()))
if not pages:
print('There are no mappings for specified address or module.')
print('There are no memory pages in `vmmap` '
'for specified address=0x%x and module=%s' % (offset, module))
return
return min(map(lambda page: page.vaddr, pages)) + offset
first_page = min(pages, key=lambda page: page.vaddr)
addr = first_page.vaddr + offset
if not any(addr in p for p in pages):
print('Offset 0x%x rebased to module %s as 0x%x is beyond module\'s '
'memory pages:' % (offset, module, addr))
for p in pages:
print(p)
return
return addr
def get_exe_name():
return pwndbg.auxv.get().get('AT_EXECFN', pwndbg.proc.exe)
parser = argparse.ArgumentParser()
parser.description = 'Calculate VA of RVA from PIE base.'
@ -39,9 +49,12 @@ parser.add_argument('module', type=str, nargs='?', default='',
def piebase(offset=None, module=None):
offset = int(offset)
if not module:
module = get_exe_name()
module = pwndbg.proc.exe
addr = translate_addr(offset, module)
print(hex(addr))
if addr is not None:
print('Calculated VA from %s = 0x%x' % (module, addr))
parser = argparse.ArgumentParser()
@ -56,10 +69,12 @@ parser.add_argument('module', type=str, nargs='?', default='',
def breakrva(offset=None, module=None):
offset = int(offset)
if not module:
module = get_exe_name()
module = pwndbg.proc.exe
addr = translate_addr(offset, module)
spec = "*%#x" % (addr)
gdb.Breakpoint(spec)
if addr is not None:
spec = "*%#x" % (addr)
gdb.Breakpoint(spec)
@pwndbg.commands.QuietSloppyParsedCommand

Loading…
Cancel
Save