Repository: pwndbg/pwndbg

Issue:
find_fake_fast is broken
### Description

I've been going through the HeapLab course on Udemy which uses pwndbag. In going through the fast dup example find_fake_fast is used for finding a fake fastbin with a usable size field. While attempting to do this pwndbg fails with the following error:
```
pwndbg> find_fake_fast &__malloc_hook
FAKE CHUNKS
Traceback (most recent call last):
  File "/home/ubuntu/pwndbg/pwndbg/commands/__init__.py", line 136, in __call__
    return self.function(*args, **kwargs)
  File "/home/ubuntu/pwndbg/pwndbg/commands/__init__.py", line 227, in _OnlyWhenRunning
    return function(*a, **kw)
  File "/home/ubuntu/pwndbg/pwndbg/commands/__init__.py", line 265, in _OnlyWithLibcDebugSyms
    return function(*a, **kw)
  File "/home/ubuntu/pwndbg/pwndbg/commands/__init__.py", line 245, in _OnlyWhenHeapIsInitialized
    return function(*a, **kw)
  File "/home/ubuntu/pwndbg/pwndbg/commands/heap.py", line 279, in malloc_chunk
    if cursor == top_chunk:
gdb.MemoryError: Cannot access memory at address 0x30d7080900003131
```

Using git bisect I found that the bug was introduced in f90db722a52e7b6da5712251e5cf30ff6e67427e. Going to the previous commit, 5062e4afce533692eaea79488ff316b3a688b17f, returns to a working state.

### Steps to reproduce

1. Load executable into gdb
1. run executable with pwntools script that cases a fake fast error
1. find_fake_fast &__malloc_hook

### My setup

Gdb:      9.1
Python:   3.8.2 (default, Apr 27 2020, 15:53:34)  [GCC 9.3.0]
Pwndbg:   1.1.0 build: 606eae0
Capstone: 4.0.1024
Unicorn:  1.0.2


Discussion:
@GrosQuildu can you look at this?
@jrozner Hey, can you post exact reproduce steps?
```
pwndbg> find_fake_fast &__malloc_hook
FAKE CHUNKS
Fake chunk | Allocated chunk | PREV_INUSE | IS_MMAPED | NON_MAIN_ARENA
Addr: 0x7ffff7dcfc0d
prev_size: 0xfff7dcbd60000000
size: 0x7f
fd: 0xfff7a7b410000000
bk: 0xfff7a7c79000007f
fd_nextsize: 0x7f
bk_nextsize: 0x00
```
this works properly on a binary that doesn't do anything suspicious
@disconnect3d I can send the the binary, glibc, and exploit I'm using. Can't upload the binary or so here though

Can't reproduce, please send binary and exploit to e28a95 at gmail.com
No idea what happens, because `cursor` should be simple int and `top_chunk` should not be dereferenced.
You can upload ZIP files, just zip together the bincy / glibc / exploit
together.

*Zach Riggle*


On Mon, Jun 8, 2020 at 3:03 AM Paweł Płatek <notifications@github.com>
wrote:

> Can't reproduce, please send binary and exploit to e28a95 at gmail.com
> No idea what happens, because cursor should be simple int and top_chunk
> should not be dereferenced.
>
> —
> You are receiving this because you are subscribed to this thread.
> Reply to this email directly, view it on GitHub
> <https://github.com/pwndbg/pwndbg/issues/781#issuecomment-640438395>, or
> unsubscribe
> <https://github.com/notifications/unsubscribe-auth/AAA3IGFA4NZRFOPHEYB3TALRVSLNRANCNFSM4NW5C4FA>
> .
>


[find_fake_fast.zip](https://github.com/pwndbg/pwndbg/files/4750328/find_fake_fast.zip)

Ok, `arena` is wrongly created: https://github.com/pwndbg/pwndbg/blob/af0b065b42a3f8e31ea62523ac7088f006f1c183/pwndbg/heap/ptmalloc.py#L336-L340
This returns `<gdb.Value object at 0x7fb87f22aef0>` such that:
```py
>>> print(arena.address)
0xadbb0a0000197b0c
>>> print(arena)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
gdb.MemoryError: Cannot access memory at address 0xadbb0a0000197b0c
```
And the same error when `arena['top']` is compared. How should we fix the code above? So how to check if gdb.Value points to something in vmmap?
> how to check if gdb.Value points to something in vmmap?

e.g.
```
>>> next(i for i in pwndbg.vmmap.get() if 0x7ffff7dd2000 in i)
Page('    0x7ffff7dd2000     0x7ffff7dd3000 rw-p     1000 25000  /lib/x86_64-linux-gnu/libselinux.so.1')
```

It will raise a `StopIteration` if the value is not there.

Resolving patch:
diff --git a/pwndbg/commands/heap.py b/pwndbg/commands/heap.py
--- a/pwndbg/commands/heap.py
+++ b/pwndbg/commands/heap.py
@@ -245,9 +245,6 @@ def malloc_chunk(addr, fake=False, verbose=False, simple=False):
     fields_to_print = set()  # in addition to addr and size
     out_fields = "Addr: {}\n".format(M.get(cursor))
 
-    arena = allocator.get_arena_for_chunk(cursor)
-    arena_address = None
-
     if fake:
         headers_to_print.append(message.on("Fake chunk"))
         verbose = True  # print all fields for fake chunks
@@ -272,8 +269,10 @@ def malloc_chunk(addr, fake=False, verbose=False, simple=False):
         print('')
         return
 
+    arena = allocator.get_arena_for_chunk(cursor)
+    arena_address = None
     is_top = False
-    if arena:
+    if not fake and arena:
         arena_address = arena.address
         top_chunk = arena['top']
         if cursor == top_chunk:
@@ -498,6 +497,9 @@ def find_fake_fast(addr, size=None):
     max_fast = allocator.global_max_fast
     max_fastbin = allocator.fastbin_index(max_fast)
     start = int(addr) - max_fast + psize
+    if start < 0:
+        print(message.warn('addr - global_max_fast is negative, if the max_fast is not corrupted, you gave wrong address'))
+        start = 0  # TODO, maybe some better way to handle case when global_max_fast is overwritten with something large
     mem = pwndbg.memory.read(start, max_fast - psize, partial=True)
 
     fmt = {
diff --git a/pwndbg/heap/ptmalloc.py b/pwndbg/heap/ptmalloc.py
--- a/pwndbg/heap/ptmalloc.py
+++ b/pwndbg/heap/ptmalloc.py
@@ -334,8 +334,9 @@ def get_arena(self, arena_addr=None):
             return self.main_arena
 
         try:
+            next(i for i in pwndbg.vmmap.get() if arena_addr in i)
             return pwndbg.memory.poi(self.malloc_state, arena_addr)
-        except gdb.MemoryError:
+        except (gdb.MemoryError, StopIteration):
             # print(message.warn('Bad arena address {}'.format(arena_addr.address)))
             return None
 


Test patch: