Why the need for this? Because having some handy commands to immediately generate a CLI visualization is very helpful when debugging a binary for exploit developers (or software development). For example, we can quickly get an idea of the state of the heap, which speeds up the process of debugging heap-based exploits. gef-cheri enables this for the CheriBSD platform in the Morello architecture (CHERI-enabled ARM64). You can still apply the same gef-cheri script to analyse non-CHERI binaries, in which case the behavior should be the same as the original gef.

Setup

To load the gef-cheri python script when you start a debugging session, add this line to ~/.gdbinit:

source /path/to/gef-cheri/gef.py

Some commands

Here are some commands that I often use.

vmmap

Getting process memory mappings information is different in FreeBSD (and CheriBSD) and in Linux. gef-cheri is taught to identify a platform other than Linux. In addition, the original gef identifies the heap memory mappings by assuming that heap memory is managed by the glibc memory allocator. This is not true in FreeBSD (and CheriBSD), so we have to implement a heap memory blocks identification logic for each heap manager other than glibc malloc. Below is the output of the vmmap command against a binary in CheriBSD running the jemalloc memory allocator.

gef> vm
[ Legend:  Code | Heap | Stack ]
Start                              End                                Offset                             Perm Path
0x0000000000100000 0x0000000000101000 0x0000000000000000 r-- /home/roundofthree/heap_game
0x0000000000101000 0x0000000000110000 0x0000000000001000 --- 
0x0000000000110000 0x0000000000111000 0x0000000000000000 r-x /home/roundofthree/heap_game
0x0000000000111000 0x0000000000120000 0x0000000000011000 --- 
0x0000000000120000 0x0000000000122000 0x0000000000000000 r-- /home/roundofthree/heap_game
0x0000000000122000 0x0000000000131000 0x0000000000022000 --- 
0x0000000000131000 0x0000000000132000 0x0000000000000000 rw- 
0x0000000040131000 0x0000000040139000 0x0000000000000000 r-- /libexec/ld-elf.so.1
0x0000000040139000 0x0000000040148000 0x0000000000008000 --- 
0x0000000040148000 0x0000000040163000 0x0000000000007000 r-x /libexec/ld-elf.so.1
0x0000000040163000 0x0000000040172000 0x0000000000032000 --- 
0x0000000040172000 0x0000000040175000 0x0000000000021000 rw- /libexec/ld-elf.so.1
0x0000000040175000 0x0000000040184000 0x0000000000044000 --- 
0x0000000040184000 0x0000000040185000 0x0000000000023000 rw- /libexec/ld-elf.so.1
0x0000000040185000 0x0000000040187000 0x0000000000000000 rw- 
0x0000000040187000 0x0000000040190000 0x0000000000002000 rw- 
0x0000000040191000 0x0000000040223000 0x0000000000000000 r-- /lib/libc.so.7
0x0000000040223000 0x0000000040232000 0x0000000000092000 --- 
0x0000000040232000 0x0000000040370000 0x0000000000091000 r-x /lib/libc.so.7
0x0000000040370000 0x000000004037f000 0x00000000001df000 --- 
0x000000004037f000 0x000000004039c000 0x00000000001ce000 r-- /lib/libc.so.7
0x000000004039c000 0x00000000403ab000 0x000000000020b000 --- 
0x00000000403ab000 0x00000000403b6000 0x00000000001ea000 rw- /lib/libc.so.7
0x00000000403b6000 0x00000000407e7000 0x0000000000000000 rw- 
0x00000000407e7000 0x00000000407e9000 0x0000000000000000 rw- 
0x00000000407e9000 0x00000000407f0000 0x0000000000000000 --- 
0x00000000407f0000 0x0000000040811000 0x0000000000000000 rw- 
0x0000000040811000 0x0000000040818000 0x0000000000000000 --- 
0x0000000040818000 0x0000000040828000 0x0000000000000000 rw- 
0x0000000040828000 0x0000000040877000 0x0000000000010000 rw- 
0x0000000040a00000 0x0000000040c00000 0x0000000000000000 rw- [heap block]
0x0000000040c00000 0x0000000040e00000 0x0000000000200000 rw- [heap]
0x0000000040e00000 0x0000000041400000 0x0000000000400000 rw- [heap block]
0x0000fbfdbffff000 0x0000fbfdc0000000 0x0000000000000000 rw- 
0x0000fbfdc0000000 0x0000fe0000000000 0x0000000000000000 rw- 
0x0000ffffbfeff000 0x0000ffffbff80000 0x0000000000000000 rw- 
0x0000ffffbff80000 0x0000fffffff60000 0x00000000001b001b --- 
0x0000fffffff60000 0x0000fffffff80000 0x0000000000000000 rw- [stack]
0x0000fffffffff000 0x0001000000000000 0x0000000000000000 r-x

telescope

One notable difference is that the stack alignment is 0x10 in CHERI-enabled platforms. By default, telescope without arguments will display a stack view starting from the address pointed by $csp ($rcsp if in Restricted mode, see CHERI ISAv9). We also can use the capability tag as a filter to indicate whether to dereference or not, it’s more accurate than guessing whether a value is a code or data pointer.

gef> telescope
0x0000fffffff7f800│+0x0000: 0x0000fffffff7f820 [rwRW,0xffffbff80000-0xfffffff80000,len=0x40000000]  →  0x0000fffffff7f850 [rwRW,0xffffbff80000-0xfffffff80000,len=0x40000000]  →  0x0000fffffff7fd00 [rwRW,0xffffbff80000-0xfffffff80000,len=0x40000000]  →  0x0000fffffff7fe60 [rwRW,0xffffbff80000-0xfffffff80000,len=0x40000000]  →  0x0000fffffff7ff60 [rwRW,0xffffbff80000-0xfffffff80000,len=0x40000000]  →  0x0000fffffff7ff80 [rwRW,0xffffbff80000-0xfffffff80000,len=0x40000000]  →  0x0000fffffff7ffe0 [rwRW,0xffffbff80000-0xfffffff80000,len=0x40000000]	 ← $c29, $csp
0x0000fffffff7f810│+0x0010: 0x00000000402bc9d9 [rxRX,0x40191000-0x407e7000,len=0x656000] (sen)  →  <_sread+0020> cmp w0,  #0x1
0x0000fffffff7f820│+0x0020: 0x0000fffffff7f850 [rwRW,0xffffbff80000-0xfffffff80000,len=0x40000000]  →  0x0000fffffff7fd00 [rwRW,0xffffbff80000-0xfffffff80000,len=0x40000000]  →  0x0000fffffff7fe60 [rwRW,0xffffbff80000-0xfffffff80000,len=0x40000000]  →  0x0000fffffff7ff60 [rwRW,0xffffbff80000-0xfffffff80000,len=0x40000000]  →  0x0000fffffff7ff80 [rwRW,0xffffbff80000-0xfffffff80000,len=0x40000000]  →  0x0000fffffff7ffe0 [rwRW,0xffffbff80000-0xfffffff80000,len=0x40000000]  →  0x0000000000000000
0x0000fffffff7f830│+0x0030: 0x00000000402bbfa1 [rxRX,0x40191000-0x407e7000,len=0x656000] (sen)  →  <__srefill+0154> ldrh w8,  [c19,  #24]
0x0000fffffff7f840│+0x0040: 0x00000000403adfa0 [rwRWX,0x403adfa0-0x403ae510,len=0x570]  →  0x0000000040c17000 [rwRW,0x40c17000-0x40c18000,len=0x1000]  →  0x00000000000a0a31 ("1\n\n"?)
0x0000fffffff7f850│+0x0050: 0x0000fffffff7fd00 [rwRW,0xffffbff80000-0xfffffff80000,len=0x40000000]  →  0x0000fffffff7fe60 [rwRW,0xffffbff80000-0xfffffff80000,len=0x40000000]  →  0x0000fffffff7ff60 [rwRW,0xffffbff80000-0xfffffff80000,len=0x40000000]  →  0x0000fffffff7ff80 [rwRW,0xffffbff80000-0xfffffff80000,len=0x40000000]  →  0x0000fffffff7ffe0 [rwRW,0xffffbff80000-0xfffffff80000,len=0x40000000]  →  0x0000000000000000
0x0000fffffff7f860│+0x0060: 0x00000000402c11d9 [rxRX,0x40191000-0x407e7000,len=0x656000] (sen)  →  <__svfscanf+0538> cbnz w0,  0x402c23ec <__svfscanf+5964>
0x0000fffffff7f870│+0x0070: 0x0000000000000000                
0x0000fffffff7f880│+0x0080: 0x0000fffffff7f967 [rwRW,0xfffffff7f968-0xfffffff7fb69,len=0x201]  →  0x5d40007c50fb2000
0x0000fffffff7f890│+0x0090: 0x0000fffffff7f9d0 [rwRW,0xffffbff80000-0xfffffff80000,len=0x40000000]  →  0x0000fffffff7fc10 [rwRW,0xffffbff80000-0xfffffff80000,len=0x40000000]  →  0x0000fffffff7fdc0 [rwRW,0xffffbff80000-0xfffffff80000,len=0x40000000]  →  0x0000fffffff7fea0 [rwRW,0xfffffff7fea0-0xfffffff7feb0,len=0x10]  →  0x0000fffffff7ff24 [rwRW,0xfffffff7ff24-0xfffffff7ff28,len=0x4]  →  0x0000000100000001
gef>  
0x0000fffffff7f8a0│+0x00a0: 0x0000fffffff7fc70 [rwRW,0xfffffff7fc70-0xfffffff7fcf0,len=0x80]  →  0x0000000000001800
0x0000fffffff7f8b0│+0x00b0: 0x0000fffffff7fa00 [rwRW,0xfffffff7fa00-0xfffffff7fa80,len=0x80]  →  0xfffffffffffffffffb5d25837d7ff700
0x0000fffffff7f8c0│+0x00c0: 0x0000000a0611487b                
0x0000fffffff7f8d0│+0x00d0: 0x00000000403ae170 [rwRWX,0x403adfa0-0x403ae510,len=0x570]  →  0x0000000040c16000 [rwRW,0x40c16000-0x40c17000,len=0x1000]"Has chunk 2 been freed? (1 for Yes, 0 for No): "
0x0000fffffff7f8e0│+0x00e0: 0x0000000000000001                
0x0000fffffff7f8f0│+0x00f0: 0x00000000403ad5a0 [rwRWX,0x403ad5a0-0x403ad700,len=0x160]  →  0x0000000000000000
0x0000fffffff7f900│+0x0100: 0x000000004081a240 [rwRW,0x4081a240-0x4081a250,len=0x10]  →  0x0000000000000000
0x0000fffffff7f910│+0x0110: 0x0000000000000000                
0x0000fffffff7f920│+0x0120: 0x0000fffffff7fb6c [rwRW,0xfffffff7fb6c-0xfffffff7fc6c,len=0x100]  →  0xfffffffffffffffffff7fb1fdc5d4000
0x0000fffffff7f930│+0x0130: 0x0000ffffffffffff [-,0xffffbff80000-0xfffffff80000,len=0x40000000] (bad)

xinfo

This is exactly the same command in the original gef script.

gef>  xinfo 0x40c28000
──────────────────────────────────────────────────────────────────────────────────────────────── xinfo: 0x40c28000 ────────────────────────────────────────────────────────────────────────────────────────────────
Page: 0x0000000040c00000  →  0x0000000040e00000 (size=0x200000)
Permissions: rw-
Pathname: [heap]
Offset (from page): 0x28000
Inode: 0

scancap

gef-cheri implements the needle-in-haystack search with an additional filter of valid capabilities (valid tag). For example, the example below scans for valid capabilities in the stack memory region that points to the heap memory region.

gef>  scan stack heap 
[+] In '[stack]' (0xfffffff60000-0xfffffff80000), permission=rw-
  0xfffffff7cb50  →  0x00000040c0e000 [rwRW,0x40c00000-0x40e00000,len=0x200000] 
  0xfffffff7df80  →  0x00000040c1f000 [rwRW,0x40c00000-0x40e00000,len=0x200000] 
  0xfffffff7e880  →  0x00000040c28000 [rwRW,0x40c00000-0x40e00000,len=0x200000] 
  0xfffffff7f6a0  →  0x00000040c16000 [rwRW,0x40c16000-0x40c17000,len=0x1000] 
  0xfffffff7fb50  →  0x000000001009b1 [rR,0x100993-0x1009b4,len=0x21] 
  0xfffffff7fdb0  →  0x00000000100945 [rR,0x100945-0x100949,len=0x4] 
  0xfffffff7fe70  →  0x00000000110d35 [rxRX,0x100000-0x1313c0,len=0x313c0] (sen) 
  0xfffffff7fe90  →  0x00000000110b8d [rxRX,0x100000-0x1313c0,len=0x313c0] (sen) 
  0xfffffff7feb0  →  0x00000040c28000 [rwRW,0x40c28000-0x40c28010,len=0x10] 
  0xfffffff7ff40  →  0x00000040c20000 [rwRW,0x40c20000-0x40c200a0,len=0xa0] 
  0xfffffff7ff90  →  0x00000000110b59 [rxRX,0x100000-0x1313c0,len=0x313c0] (sen)

More heap managers

To analyze heaps managed by allocators other than glibc allocator, I developed some plugins in gef-plugins repo. To load it at the start of a debugging session, add this line to ~/.gdbinit:

gef config gef.extra_plugins_dir /path/to/gef-plugins

jemalloc

  • jheap chunks: list in use heap allocations
gef>  jheap chunks
Chunk(addr=0x40c00000, size=0xe000 (Large), status=Used)
    [0x0000000040c00000     00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00    ................]
Chunk(addr=0x40c0e000, size=0x8 (Small), status=Used)
    [0x0000000040c0e000     e2 03 00 00 e8 03 00 00 00 00 00 00 00 00 00 00    ................]
Chunk(addr=0x40c0f000, size=0xe0 (Small), status=Used)
    [0x0000000040c0f000     70 f0 c0 40 00 00 00 00 00 f0 d0 70 00 40 5d dc    p..@.......p.@].]
Chunk(addr=0x40c16000, size=0x1000 (Small), status=Used)
    [0x0000000040c16000     48 61 73 20 63 68 75 6e 6b 20 32 20 62 65 65 6e    Has chunk 2 been]
Chunk(addr=0x40c17000, size=0x1000 (Small), status=Used)
    [0x0000000040c17000     31 0a 0a 00 00 00 00 00 00 00 00 00 00 00 00 00    1...............]
Chunk(addr=0x40c20000, size=0xa0 (Small), status=Used)
    [0x0000000040c20000     00 50 c2 40 00 00 00 00 07 50 07 30 00 40 5d dc    .P.@.....P.0.@].]
Chunk(addr=0x40c25000, size=0x6000 (Large), status=Used)
    [0x0000000040c25000     00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00    ................]
Chunk(addr=0x40c2b000, size=0x10000 (Large), status=Used)
    [0x0000000040c2b000     00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00    ................]
  • jheap chunk <address>: inspect a heap allocation
gef>  jheap chunk 0x40c40000
Chunk(addr=0x40c40000, size=0x4000 (Large), status=Free)
In quarantine: Yes
In tcache: No
Extent base: 0x40c40000
Extent size: 0x4000
  • jheap uaf [noheap]: scan for freed heap allocations that are pointed by valid capabilities in memory. Optionally, exclude capabilities stored in the heap.
gef>  jheap uaf noheap
[+] In '[stack]' (0xfffffff60000-0xfffffff80000), permission=rw-
  0xfffffff7df80  →  0x00000040c1f000 [rwRW,0x40c00000-0x40e00000,len=0x200000] 

snmalloc

  • snheap info: print pagemap address
  • snheap localcache: list entries in local cache LocalCache (also called small fast free lists)
  • snheap slabs: lists slabs in the core allocator (there can be multiple slabs per small size class, and large slabs)
  • snheap remote: lists the remote deallocation queue of the current of given thread(s)
  • snheap freelists: list entries in local cache LocalCache in the local allocator, the deallocation queue in remote allocators and active slab free lists in the core allocator
gef>  snheap freelists 
[+] Guessed the LocalAlloc address to be 0x40a42070
────────────────────────────────────────────────────────────────────────────── Thread 1: localcache for LocalAlloc(addr=0x40a42070) ──────────────────────────────────────────────────────────────────────────────
small_fast_free_lists[idx=2, size=0x60, count=169]  →  Alloc(addr=0x42024060)  →  Alloc(addr=0x420240c0)  →  Alloc(addr=0x42024120)  →  Alloc(addr=0x42024180)  →  ...  →  Alloc(addr=0x42027f60) 

small_fast_free_lists[idx=3, size=0x80, count=127]  →  Alloc(addr=0x42008080)  →  Alloc(addr=0x42008100)  →  Alloc(addr=0x42008180)  →  Alloc(addr=0x42008200)  →  ...  →  Alloc(addr=0x4200bf80) 

small_fast_free_lists[idx=4, size=0xa0, count=101]  →  Alloc(addr=0x420200a0)  →  Alloc(addr=0x42020140)  →  Alloc(addr=0x420201e0)  →  Alloc(addr=0x42020280)  →  ...  →  Alloc(addr=0x42023f20) 

small_fast_free_lists[idx=6, size=0xe0, count=72]  →  Alloc(addr=0x420100e0)  →  Alloc(addr=0x420101c0)  →  Alloc(addr=0x420102a0)  →  Alloc(addr=0x42010380)  →  ...  →  Alloc(addr=0x42013f00) 

small_fast_free_lists[idx=20, size=0xa00, count=5]  →  Alloc(addr=0x42004a00)  →  Alloc(addr=0x42005400)  →  Alloc(addr=0x42005e00)  →  Alloc(addr=0x42006800)  →  ...  →  Alloc(addr=0x42007200) 

small_fast_free_lists[idx=23, size=0x1000, count=3]  →  Alloc(addr=0x42015000)  →  Alloc(addr=0x42016000)  →  Alloc(addr=0x42017000) 

small_fast_free_lists[idx=30, size=0x3800, count=3]  →  Alloc(addr=0x42083800)  →  Alloc(addr=0x42087000)  →  Alloc(addr=0x4208a800) 

small_fast_free_lists[idx=34, size=0x7000, count=3]  →  Alloc(addr=0x42047000)  →  Alloc(addr=0x4204e000)  →  Alloc(addr=0x42055000) 

────────────────────────────────────────────────────────────── Thread 1: active slabs for CoreAlloc(addr=0x42000000)  ←  LocalAlloc(addr=0x40a42070) ──────────────────────────────────────────────────────────────
SlabMetadataCache[idx=34, alloc_size=0x7000, unused=0, length=0]: 
 | →  SlabMetadata(addr=0x42000d80, needed=3, sleeping=False, large=False) (count=1)  →  Alloc(addr=0x42040000) 

───────────────────────────────────────────────────────────────── Thread 1: laden for CoreAlloc(addr=0x42000000)  ←  LocalAlloc(addr=0x40a42070) ─────────────────────────────────────────────────────────────────
SlabMetadata(addr=0x42000e80, needed=1, sleeping=True, large=False) (count=0) 

SlabMetadata(addr=0x42000e00, needed=32, sleeping=True, large=False) (count=0) 

SlabMetadata(addr=0x42000d00, needed=25, sleeping=True, large=False) (count=0) 

SlabMetadata(addr=0x42000c80, needed=1, sleeping=True, large=False) (count=0) 

SlabMetadata(addr=0x42000c00, needed=18, sleeping=True, large=False) (count=0) 

SlabMetadata(addr=0x42000b80, needed=32, sleeping=True, large=False) (count=0) 

SlabMetadata(addr=0x42000b00, needed=1, sleeping=True, large=False) (count=0) 

───────────────────────────────────────────────────── Thread 1: remote deallocation queue for RemoteAllocator(addr=0x42000800)  ←  CoreAlloc(addr=0x42000000) ─────────────────────────────────────────────────────
[+] Remote deallocation list is empty
  • snheap chunk <address>: lists details about the Alloc and its slab. If the metaentry has the REMOTE_BACKEND_MARKER bit asserted, that is, the chunk is owned by the backend (not Alloc-bounded), then indicate it as a Chunk. Because backend chunks' metaentry are parsed differently depending on the specific Range, we can make a best guess of the owning Range. In the case that CHERI revocation is enabled, also print whether it is quarantined and its revocation bit value.
gef>  snheap chunk 0x42024060
Alloc(addr=0x42024060)
Object in quarantine: False
Start of object: 0x42024060
Object size: 0x60
Offset into object: 0x0
Metaentry @ 0x50210120
Associated slab details:
SlabMetadata(addr=0x42000e00, needed=32, sleeping=True, large=False) (count=0) 
[+] Free list is empty

mrs wrapper

CheriBSD 23.11 malloc is shipped with the mrs wrapper around memory allocation APIs: mrs_malloc, mrs_free…, to enforce heap temporal safety. This plugin provides commands to query for mrs-specific information:

  • mrs info: display general information about the mrs quarantine, global state and the revocation bitmap.
gef>  mrs info 
──────────────────────────────────────────────────────────────────────────────────────────────────── Thread 1 ────────────────────────────────────────────────────────────────────────────────────────────────────
Allocated size: 0x8a90
Max allocated size: 0x8a90
Quarantine size: 0x5100
Quarantine max size: 0x5100
Entire revocation map capability: 0x0000fc0000000000 [rw,0xfc0000000000-0xfe0000000000,len=0x20000000000]
  • mrs chunk <address>: query whether this chunk is owned by the allocator or quarantined. Also show shadow bitmap offset and value. The information we can query is limited because the capability load generation counter registers are not available to gdb in ring 3, so we can’t inspect the kernel internal state of caprevoke unless debugging the kernel or using qemu.
gef>  mrs chunk 0x40c40000
Chunk(addr=0x40c40000)
In quarantine: Yes
Revocation bit address: 0xfc0000818800
Revocation bit set (first word): Yes
  • mrs quarantine: print the quarantined chunks (and their shadow bit values of the allocation first word).
gef>  mrs quarantine 
application_quarantine[size=0x5100, count=4] 
  →  Chunk(addr=0x40c20700, size=0x700)
  →  Chunk(addr=0x40c36000, size=0x400)
  →  Chunk(addr=0x40c40000, size=0x4000)
  →  Chunk(addr=0x40c44000, size=0x600)