pwnfreemedium

spelling-bee

b01lersc

Task: a tiny Forth interpreter with user-defined words, deletion, and a startup leak of dosys(). Solution: exploit a use-after-free in forget, groom tcache to reclaim a stale word_t as name chunks, forge a fake word_t, and redirect execution to dosys(\"sh\").

$ ls tags/ techniques/
tcache_groominguse_after_free_exploitationfake_structure_forgery

$ cat /etc/rate-limit

Rate limit reached (20 reads/hour per IP). Showing preview only — full content returns at the next hour roll-over.

spelling-bee — b01lersc

Description

The original organizer description was not preserved in the task directory.

We are given a small Forth-like interpreter implemented in C. The goal is to study how user-defined words are compiled and deleted, then turn a memory-management bug into code execution and read the flag.

Analysis

The core runtime object is:

typedef struct word { long flags; long length; long referenced_by; void (*code)(void *); void *param; } word_t;

Dictionary entries are stored separately as:

typedef struct dictionary { char *name; word_t *word; struct dictionary *next; bool alloc_name; } dict_t;

When the interpreter compiles a new word, it appends raw word_t * pointers into compile_def. There is no indirection layer: compiled words directly reference the original word_t objects.

That would still be safe if deletion respected ownership, but delete_word() does not check referenced_by before freeing the target word. So if word a was compiled to call word b, then forget b frees b even though a still contains a pointer to b.word_t. This is a classic dangling-pointer bug and gives us a use-after-free primitive.

The challenge also prints the runtime address of dosys() at startup:

printf("%p\n", dosys);

That immediately leaks the PIE base, so we can compute both dosys and the address of g_stack.

The heap layout is especially convenient:

  • sizeof(word_t) lands in the 0x30 tcache bin.
  • sizeof(dict_t) also lands in the 0x30 tcache bin.
  • The initial compile_def allocation for a freshly compiled word lands in the 0x90 tcache bin.

...