Tags: pwn 

Rating: 3.0

### Notepad--

Connecting to the challenge gives the following menu:
Welcome to Notepad--
Pick an existing notebook or create a new one
[p]ick notebook
[a]dd notebook
[d]elete notebook
[l]ist notebook


In this challenge you can manage notebooks.
This includes adding, deleting, and listing notebooks.
Picking an added notebook results in the following menu:

Operations with notebook "MyNotebook"
[a]dd tab
[v]iew tab
[u]pdate tab
[d]elete tab
[l]ist tabs


So, every Notebook can hold 'tabs'.
We can add, view, change, and delete tabs in the selected notebook.
Note that listing all notebooks prints their names while viewing a single notebook shows its contents.

Let's load the binary in a decompiler and look at the datastructures.
After adding some names you can come to the following structs:

struct notebook {
char name[16];
int64_t number_of_tabs;
struct tab tabs[64];

struct tab {
char name[16];
int64_t c_size;
char *content;

Thus, the sizes are `sizeof(struct tab) == 0x20` and `sizeof(struct notebook) == 0x818`.
All notebooks are stored in an array `struct notebook notebooks[16]` in the bss segment.
The content of a tab is stored in malloc'd memory and its size is kept in the `c_size` member.

So far, so good. But where is the vulnerability?
Looking at the function for adding a notebook you can see that the name of the notebook is read by `scanf("%s", buffer)`.
Thus, you can easily overflow into the `number_of_tabs` member and change its value.
By setting it to a large number, one is able to read and write out of the bounds of the tab array.

Let's have a closer look at the memory layout.
Since everything (except the tab content) is stored in a linear layout, overflowing the tab array results in access to the subsequent notebooks data (e.g. `notebooks[1]` is `notebooks[0].tab[64]`).
Fortunatelly, the size of the notebook is not a multiple of the tab size, so this access is misaligned by 8 bytes.
In the following graphic you can see that `notebooks[0].tab[65]` is no longer at the beginning of a tab but points in the middle of a tab name.

+-----------> 0x0 +----------------+
| name |
0x8 +----------------+
| name |
notebooks[0].tab[0] +----------------+
| | number_of_tabs |
+----------> 0x18 +----------------+ -+
| tab[0].name | |
+----------------+ |
| tab[0].name | |
+----------------+ |
| tab[0].c_size | |
notebooks[0].tab[1] +----------------+ |
| | tab[0].content |-----> (malloc'd memory) |
+----------> 0x38 +----------------+ |
| tab[1].name | |
+----------------+ | This part contains
| tab[1].name | +- the 64 (0x40) tab structs
+----------------+ | of notebooks[0]
| tab[1].c_size | |
+----------------+ |
| tab[1].content |-----> (malloc'd memory) |
0x58 +----------------+ |
| ... | |
+----------------+ |
| | |
| | |
notebooks[1] | | |
| | | |
+---------> 0x818 +----------------+ -+
| name |
| name |
| number_of_tabs |
notebooks[0].tab[65] +----------------+
| | tab[0].name |
+---------> 0x838 +----------------+
| tab[0].name |
| tab[0].c_size |
| tab[0].content |---->... (malloc'd memory)
notebooks[0].tab[66] +----------------+
| | ... |
+---------> 0x858 +----------------+
| |

Going further, in `notebook[3]` this index points to the `content` member of the tabs.
Therefore, we can first read that pointer in the `content` member to get a heap leak and then overwrite it (by updating the tab out of bounds) to get arbitrary read and write.

For the heap leak let's create four notebooks:
notebook_add("A"*0x10 + "\xc4")

Note: Indices in user interaction are one-based, which is mapped to zero-based array access.
In this writeup all indices are zero-based.

The first notebook is now capable of accessing the contents of the subsequent notebooks.
By adding a tab to `notebooks[3]` and listing all tabs of `notebooks[0]` we receive a heap address since
the access to the name of `notebooks[0].tab[195]` results in the access of the `content` member of `notebooks[3].tab[0]`.

The tab which was added to `notebooks[3]` in the previous step should have a content of length 8.
This makes the implementation of arbitrary read and write easier since this is the size which will be passed to `read` and `write` syscalls later.

Arbitrary read and write can now be implemented by updating the name of `notebooks[0].tab[195]` with the target address.
This overwrites the `content` pointer of `notebooks[3].tab[0]` with the desired address.
By viewing (arbitrary read) or updating (arbitrary write) `notebooks[3].tab[0]` we can now read or write the target address.

Given these primitives it goes straight forward to starting a shell.
By first adding and then removing a couple of tabs one can get a libc address to the heap.
These tabs must be large enough to be added to `unsorted_bin`, e.g. size `0x100`.
Also, there must be at least 8 tabs removed such that the `tcache` is filled and at least one tab is stored in an `unsorted_bin`.
Then the chunk holds a pointer to libc and we can leak said libc pointer with the arbitrary read.

Since the version of libc is not given, one must fingerprint the entries of `notepad`s `.got`.
From the libc address we can retrieve a pointer to the binary itself.
For this purpose we use a pointer to `notepad`s `stdin` or `stdout` which is located in `libc`s `.got`.
Since said pointer is located close to our libc leak, even with a different libc version it is likely that we are able to extract the address of `notepad`.
Given the address of `notepad`, one can get the `.got` entries of `notepad` and identify `libc`.

Then one might overwrite the `__free_hook` with `system`, add a tab with `/bin/sh` as content and pop a shell by deleting the tab.

Flag: `VolgaCTF{i5_g1ibc_mall0c_irr3p@rable?}`.