GEF for CheriBSD Morello
Table of Contents
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 addresssnheap localcache
: list entries in local cacheLocalCache
(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 cacheLocalCache
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 theAlloc
and its slab. If the metaentry has theREMOTE_BACKEND_MARKER
bit asserted, that is, the chunk is owned by the backend (notAlloc
-bounded), then indicate it as aChunk
. Because backend chunks' metaentry are parsed differently depending on the specificRange
, we can make a best guess of the owningRange
. 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)