pwndbg / pwndbg

Exploit Development and Reverse Engineering with GDB Made Easy
https://pwndbg.re/
MIT License
7.22k stars 867 forks source link

visualise heap chunks crashes when given unmapped address #2304

Closed k4lizen closed 3 weeks ago

k4lizen commented 1 month ago

Heres the trace:

> vis 1 0x555555b370
vis_heap_chunks: An unknown error occurred when running this command.
Maybe you can try to determine the libc symbols addresses manually, set them appropriately and re-run this command. For this, see the `heap_config` command output and set the `main_arena`, `mp_`, `global_max_fast`, `tcache` and `thread_arena` addresses.
╭─────────────────────────────── Traceback (most recent call last) ────────────────────────────────╮
│ ----/pwndbg/pwndbg/commands/__init__.py:196 in __call__                             │
│                                                                                                  │
│   193 │                                                                                          │
│   194 │   def __call__(self, *args: Any, **kwargs: Any) -> str | None:                           │
│   195 │   │   try:                                                                               │
│ ❱ 196 │   │   │   return self.function(*args, **kwargs)                                          │
│   197 │   │   except TypeError:                                                                  │
│   198 │   │   │   print(f"{self.function.__name__.strip()!r}: {self.function.__doc__.strip()}"   │
│   199 │   │   │   pwndbg.exception.handle(self.function.__name__)                                │
│                                                                                                  │
│ -----/pwndbg/pwndbg/commands/__init__.py:449 in _OnlyWithResolvedHeapSyms            │
│                                                                                                  │
│   446 │   │   │   and isinstance(pwndbg.gdblib.heap.current, GlibcMemoryAllocator)               │
│   447 │   │   │   and pwndbg.gdblib.heap.current.can_be_resolved()                               │
│   448 │   │   ):                                                                                 │
│ ❱ 449 │   │   │   return _try2run_heap_command(function, *a, **kw)                               │
│   450 │   │   else:                                                                              │
│   451 │   │   │   if (                                                                           │
│   452 │   │   │   │   isinstance(pwndbg.gdblib.heap.current, DebugSymsHeap)                      │
│                                                                                                  │
│----/pwndbg/pwndbg/commands/__init__.py:426 in _try2run_heap_command                │
│                                                                                                  │
│   423 │   │   else:                                                                              │
│   424 │   │   │   w("You can try `set resolve-heap-via-heuristic force` and re-run this comman   │
│   425 │   │   if pwndbg.config.exception_verbose or pwndbg.config.exception_debugger:            │
│ ❱ 426 │   │   │   raise err                                                                      │
│   427 │   │                                                                                      │
│   428 │   │   pwndbg.exception.inform_verbose_and_debug()                                        │
│   429 │   return None                                                                            │
│                                                                                                  │
│ -----/pwndbg/pwndbg/commands/__init__.py:401 in _try2run_heap_command                │
│                                                                                                  │
│   398 │   w = log.warning                                                                        │
│   399 │   # Note: We will still raise the error for developers when exception-* is set to "on"   │
│   400 │   try:                                                                                   │
│ ❱ 401 │   │   return function(*a, **kw)                                                          │
│   402 │   except SymbolUnresolvableError as err:                                                 │
│   403 │   │   e(f"{function.__name__}: Fail to resolve the symbol: `{err.symbol}`")              │
│   404 │   │   if "thread_arena" == err.symbol:                                                   │
│                                                                                                  │
│ -----/pwndbg/pwndbg/commands/__init__.py:382 in _OnlyWhenHeapIsInitialized           │
│                                                                                                  │
│   379 │   @functools.wraps(function)                                                             │
│   380 │   def _OnlyWhenHeapIsInitialized(*a: P.args, **kw: P.kwargs) -> Optional[T]:             │
│   381 │   │   if pwndbg.gdblib.heap.current is not None and pwndbg.gdblib.heap.current.is_init   │
│ ❱ 382 │   │   │   return function(*a, **kw)                                                      │
│   383 │   │   else:                                                                              │
│   384 │   │   │   log.error(f"{function.__name__}: Heap is not initialized yet.")                │
│   385 │   │   │   return None                                                                    │
│                                                                                                  │
│ -----/pwndbg/pwndbg/commands/__init__.py:290 in _OnlyWhenUserspace                   │
│                                                                                                  │
│   287 │   @functools.wraps(function)                                                             │
│   288 │   def _OnlyWhenUserspace(*a: P.args, **kw: P.kwargs) -> Optional[T]:                     │
│   289 │   │   if not pwndbg.gdblib.qemu.is_qemu_kernel():                                        │
│ ❱ 290 │   │   │   return function(*a, **kw)                                                      │
│   291 │   │   else:                                                                              │
│   292 │   │   │   log.error(                                                                     │
│   293 │   │   │   │   f"{function.__name__}: This command may only be run when not debugging a   │
│                                                                                                  │
│ -----/pwndbg/pwndbg/commands/heap.py:990 in vis_heap_chunks                          │
│                                                                                                  │
│    987 │                                                                                         │
│    988 │   if addr is not None:                                                                  │
│    989 │   │   cursor = int(addr)                                                                │
│ ❱  990 │   │   heap_region = Heap(cursor)                                                        │
│    991 │   │   arena = heap_region.arena                                                         │
│    992 │   else:                                                                                 │
│    993 │   │   arena = allocator.thread_arena                                                    │
│                                                                                                  │
│ -----/pwndbg/pwndbg/gdblib/heap/ptmalloc.py:522 in __init__                          │
│                                                                                                  │
│    519 │   │   │   │   self._memory_region = heap_region                                         │
│    520 │   │   │   │   self._gdbValue = None                                                     │
│    521 │   │                                                                                     │
│ ❱  522 │   │   self.start = self._memory_region.start                                            │
│    523 │   │   # i686 alignment heuristic                                                        │
│    524 │   │   if Chunk(self.start).size == 0:                                                   │
│    525 │   │   │   self.start += pwndbg.gdblib.arch.ptrsize * 2                                  │
╰──────────────────────────────────────────────────────────────────────────────────────────────────╯
AttributeError: 'NoneType' object has no attribute 'start'

Gdb session history:

disable 1 2
c
heap
bins
c
vis 1 70 b3 55 55  55 55
vis 1 0x555555b370
Platform: Linux-6.9.7-artix
OS: Artix Linux
Architecture: x86_64
Endian: little
Charset: utf-8
Gdb:      15.1
Python:   3.12.4 
Pwndbg:   2024.02.14 build: 460e07d
Capstone: 5.0.1280
Unicorn:  2.0.1
CptGibbon commented 1 month ago

That's the error I see when I try to use vis on an unmapped address. The address in your example is 5 bytes long, which is uncommon in the x86_64 arch it says you're running on. Could you check that this error occurs when using the command on mapped memory?

k4lizen commented 1 month ago

I don't remember the exact environment in which the crash happend because I figured that the stack trace is probably enough to figure out what caused the crash so

Could you check that this error occurs when using the command on mapped memory?

Is a bit tough, but the command generally does work for me with no problems when visualising actual heap chunks so

That's the error I see when I try to use vis on an unmapped address.

This is probably the issue. Is it really supposed to error out with an unreadable error if I give it an unmapped address though?

CptGibbon commented 1 month ago

Error messages that make it clear if you made a mistake are definitely preferable to whatever gibberish Python emits in this scenario, so if you're keen on putting together a PR I reckon addressing it at the memory-read level could be a good start.

e.g. any command that expects a mapped virtual memory address could print something useful when passed an unmapped address. (though this can get difficult when only part of a struct is unmapped for example)

Keen to see what you come up with 👌