Repository: pwndbg/pwndbg

Issue:
Add support for telescope to show lower addresses instead of higher addresses
I often need to see a few 8 byte values above (and including) a particular address. For example, if I have a heap chunk at `0x557bb502e2e0`, this is how telescope currently works:
```
pwndbg> telescope 0x557bb502e2e0
00:0000│  0x557bb502e2e0 ◂— 0x0
01:0008│  0x557bb502e2e8 ◂— 0x21 /* '!' */
02:0010│  0x557bb502e2f0 ◂— 0x20 /* ' ' */
03:0018│  0x557bb502e2f8 ◂— 0x1
04:0020│  0x557bb502e300 —▸ 0x557bb502e310 ◂— 'BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB'
05:0028│  0x557bb502e308 ◂— 0x31 /* '1' */
06:0030│  0x557bb502e310 ◂— 'BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB'
07:0038│  0x557bb502e318 ◂— 'BBBBBBBBBBBBBBBBBBBBBBBB'
```
Now I may have expected that an overflow should have overwritten the first few pointers of this heap chunk, but for some reason it didn't, and I suspect I'm a few bytes off, maybe some small multiple of 8. I'd like to check right above this chunk (lower addresses), so currently I can do something like this (or similarly with `x/xg`):
```
pwndbg> telescope 0x557bb502e2e0-0x8*4 5
00:0000│ rax rdi 0x557bb502e2c0 ◂— 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'
... ↓            3 skipped
04:0020│         0x557bb502e2e0 ◂— 0x0
```

This works, but I think it's a verbose and inconvenient way to do it. If I want to look farther back, I need to change both the `4` and the `5` to make it do the right thing, as opposed to changing just one number.

It'd be nice to either have a flag `telescope -r` or a new command `rtelescope` that take an address and an optional count and prints the values at lower memory addresses. The example above would just be `telescope -r 0x557bb502e2e0 5`, which is much easier.


Discussion:
Woah, that's pretty neat :D  

```
pwndbg> telescope $rsp 10
00:0000│ rbp rsp 0x7fffffffe100 —▸ 0x555555554760 (__libc_csu_init) ◂— push   r15
01:0008│         0x7fffffffe108 —▸ 0x7ffff7a03c87 (__libc_start_main+231) ◂— mov    edi, eax
02:0010│         0x7fffffffe110 ◂— 0x1
03:0018│         0x7fffffffe118 —▸ 0x7fffffffe1e8 —▸ 0x7fffffffe47d ◂— '/home/dc/a.out'
04:0020│         0x7fffffffe120 ◂— 0x100008000
05:0028│         0x7fffffffe128 —▸ 0x55555555464a (main) ◂— push   rbp
06:0030│         0x7fffffffe130 ◂— 0x0
07:0038│         0x7fffffffe138 ◂— 0xaa19dbde9607590
08:0040│         0x7fffffffe140 —▸ 0x555555554540 (_start) ◂— xor    ebp, ebp
09:0048│         0x7fffffffe148 —▸ 0x7fffffffe1e0 ◂— 0x1
pwndbg> telescope $rsp 10 -r
00:0000│         0x7fffffffe0b8 ◂— 0x0
01:0008│         0x7fffffffe0c0 ◂— 0x1
02:0010│         0x7fffffffe0c8 —▸ 0x5555555547ad (__libc_csu_init+77) ◂— add    rbx, 1
03:0018│         0x7fffffffe0d0 —▸ 0x7ffff7de3b40 (_dl_fini) ◂— push   rbp
04:0020│         0x7fffffffe0d8 ◂— 0x0
05:0028│         0x7fffffffe0e0 —▸ 0x555555554760 (__libc_csu_init) ◂— push   r15
06:0030│         0x7fffffffe0e8 —▸ 0x555555554540 (_start) ◂— xor    ebp, ebp
07:0038│         0x7fffffffe0f0 —▸ 0x7fffffffe1e0 ◂— 0x1
08:0040│         0x7fffffffe0f8 ◂— 0x0
09:0048│ rbp rsp 0x7fffffffe100 —▸ 0x555555554760 (__libc_csu_init) ◂— push   r15
pwndbg>
```


Here's a patch that adds above result.

```diff
diff --git a/pwndbg/commands/telescope.py b/pwndbg/commands/telescope.py
index c524705..263eecd 100644
--- a/pwndbg/commands/telescope.py
+++ b/pwndbg/commands/telescope.py
@@ -38,9 +38,10 @@ parser = argparse.ArgumentParser(description="""
     """)
 parser.add_argument("address", nargs="?", default=None, type=int, help="The address to telescope at.")
 parser.add_argument("count", nargs="?", default=telescope_lines, type=int, help="The number of lines to show.")
+parser.add_argument("-r", "--reverse", dest="reverse", action='store_true', default=False, help='Show <count> previous addresses instead of next ones')
 @pwndbg.commands.ArgparsedCommand(parser)
 @pwndbg.commands.OnlyWhenRunning
-def telescope(address=None, count=telescope_lines, to_string=False):
+def telescope(address=None, count=telescope_lines, to_string=False, reverse=False):
     """
     Recursively dereferences pointers starting at the specified address
     ($sp by default)
@@ -53,6 +54,10 @@ def telescope(address=None, count=telescope_lines, to_string=False):
         telescope.offset = 0

     address = int(address if address else pwndbg.regs.sp) & pwndbg.arch.ptrmask
+
+    if reverse:
+        address -= (count - 1) * ptrsize
+
     count   = max(int(count), 1) & pwndbg.arch.ptrmask
     delimiter = T.delimiter(offset_delimiter)
     separator = T.separator(offset_separator)
```

The only question left is: what would be the best name for this flag? "Reverse"? "FromBack"? Uh...
Thanks for the quick implementation! Reverse was the first thought I had, as it's telescope but going in the reverse direction.

Resolving patch:
diff --git a/pwndbg/commands/telescope.py b/pwndbg/commands/telescope.py
--- a/pwndbg/commands/telescope.py
+++ b/pwndbg/commands/telescope.py
@@ -36,14 +36,18 @@
     """)
 parser.add_argument("address", nargs="?", default=None, type=int, help="The address to telescope at.")
 parser.add_argument("count", nargs="?", default=telescope_lines, type=int, help="The number of lines to show.")
+parser.add_argument("-r", "--reverse", dest="reverse", action='store_true', default=False,
+                    help='Show <count> previous addresses instead of next ones')
+
+
 @pwndbg.commands.ArgparsedCommand(parser)
 @pwndbg.commands.OnlyWhenRunning
-def telescope(address=None, count=telescope_lines, to_string=False):
+def telescope(address=None, count=telescope_lines, to_string=False, reverse=False):
     """
     Recursively dereferences pointers starting at the specified address
     ($sp by default)
     """
-    ptrsize   = pwndbg.typeinfo.ptrsize
+    ptrsize = pwndbg.typeinfo.ptrsize
     if telescope.repeat:
         address = telescope.last_address + ptrsize
         telescope.offset += 1
@@ -51,10 +55,14 @@ def telescope(address=None, count=telescope_lines, to_string=False):
         telescope.offset = 0
 
     address = int(address if address else pwndbg.regs.sp) & pwndbg.arch.ptrmask
-    count   = max(int(count), 1) & pwndbg.arch.ptrmask
+    count = max(int(count), 1) & pwndbg.arch.ptrmask
     delimiter = T.delimiter(offset_delimiter)
     separator = T.separator(offset_separator)
 
+    # Allow invocation of telescope -r to dump previous addresses
+    if reverse:
+        address -= (count - 1) * ptrsize
+
     # Allow invocation of "telescope 20" to dump 20 bytes at the stack pointer
     if address < pwndbg.memory.MMAP_MIN_ADDR and not pwndbg.memory.peek(address):
         count   = address


Test patch:
diff --git a/tests/binaries/telescope_binary.c b/tests/binaries/telescope_binary.c
new file mode 100644
--- /dev/null
+++ b/tests/binaries/telescope_binary.c
@@ -0,0 +1,22 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+struct A {
+    void* func_ptr;
+    void* stack_ptr;
+    void* heap_ptr;
+    char buf[16];
+    char gap[128];
+};
+
+void break_here() {}
+
+int main() {
+    struct A a = {};
+    a.func_ptr = (void*)main;
+    a.stack_ptr = (void*) &a;
+    a.heap_ptr = malloc(10);
+    strcpy(a.buf, "aaa");
+    break_here();
+}
diff --git a/tests/test_command_telescope.py b/tests/test_command_telescope.py
new file mode 100644
--- /dev/null
+++ b/tests/test_command_telescope.py
@@ -0,0 +1,59 @@
+import gdb
+
+import tests
+
+TELESCOPE_BINARY = tests.binaries.get('telescope_binary.out')
+
+
+def test_command_telescope(start_binary):
+    """
+    Tests simple telescope
+    """
+    start_binary(TELESCOPE_BINARY)
+
+    gdb.execute('break break_here')
+    gdb.execute('run')
+    gdb.execute('up')
+
+    expected_str = gdb.execute('print a', to_string=True)
+    expected_lines = expected_str.split('\n')
+
+    result_str = gdb.execute('telescope &a', to_string=True)
+    result_lines = result_str.split('\n')
+
+    for i in range(4):
+        expected_addr = expected_lines[i+1].split(' ')[4].strip(',"')
+        assert expected_addr in result_lines[i]
+
+
+def test_command_telescope_reverse(start_binary):
+    """
+    Tests reversed telescope
+    """
+    start_binary(TELESCOPE_BINARY)
+
+    gdb.execute('break break_here')
+    gdb.execute('run')
+    gdb.execute('up')
+
+    expected_str = gdb.execute('print a', to_string=True)
+    expected_lines = expected_str.split('\n')
+
+    result_str = gdb.execute('telescope ((uint8_t*)&a)+0x38 -r', to_string=True)
+    result_lines = result_str.split('\n')
+
+    for i in range(4):
+        expected_addr = expected_lines[i+1].split(' ')[4].strip(',"')
+        assert expected_addr in result_lines[i]
+
+
+def test_command_telescope_n_records(start_binary):
+    """
+    Tests telescope defined number of records
+    """
+    start_binary(TELESCOPE_BINARY)
+
+    n = 3
+    gdb.execute('entry')
+    result_str = gdb.execute('telescope $rsp {}'.format(n), to_string=True)
+    assert len(result_str.strip('\n').split('\n')) == n